CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
Path: blob/master/Core/HW/Camera.cpp
Views: 1401
// Copyright (c) 2020- PPSSPP Project.12// This program is free software: you can redistribute it and/or modify3// it under the terms of the GNU General Public License as published by4// the Free Software Foundation, version 2.0 or later versions.56// This program is distributed in the hope that it will be useful,7// but WITHOUT ANY WARRANTY; without even the implied warranty of8// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the9// GNU General Public License 2.0 for more details.1011// A copy of the GPL 2.0 should have been included with the program.12// If not, see http://www.gnu.org/licenses/1314// Official git repository and contact information can be found at15// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.16#include "ppsspp_config.h"17#include "Camera.h"18#include "Core/Config.h"1920#ifdef USE_FFMPEG21void convert_frame(int inw, int inh, unsigned char *inData, AVPixelFormat inFormat,22int outw, int outh, unsigned char **outData, int *outLen) {23struct SwsContext *sws_context = sws_getContext(24inw, inh, inFormat,25outw, outh, AV_PIX_FMT_RGB24,26SWS_BICUBIC, NULL, NULL, NULL);2728// resize29uint8_t *src[4] = {0};30uint8_t *dst[4] = {0};31int srcStride[4], dstStride[4];3233unsigned char *rgbData = (unsigned char*)malloc(outw * outh * 4);3435av_image_fill_linesizes(srcStride, inFormat, inw);36av_image_fill_linesizes(dstStride, AV_PIX_FMT_RGB24, outw);3738av_image_fill_pointers(src, inFormat, inh, inData, srcStride);39av_image_fill_pointers(dst, AV_PIX_FMT_RGB24, outh, rgbData, dstStride);4041sws_scale(sws_context,42src, srcStride, 0, inh,43dst, dstStride);4445// compress jpeg46*outLen = outw * outh * 2;47*outData = (unsigned char*)malloc(*outLen);4849jpge::params params;50params.m_quality = 60;51params.m_subsampling = jpge::H2V2;52params.m_two_pass_flag = false;53jpge::compress_image_to_jpeg_file_in_memory(54*outData, *outLen, outw, outh, 3, rgbData, params);55free(rgbData);56}57#endif //USE_FFMPEG585960void __cameraDummyImage(int width, int height, unsigned char** outData, int* outLen) {61#ifdef USE_FFMPEG62unsigned char *rgbData = (unsigned char *)malloc(3 * width * height);63if (!rgbData) {64*outData = nullptr;65return;66}67for (int y = 0; y < height; y++) {68for (int x = 0; x < width; x++) {69rgbData[3 * (y * width + x) + 0] = x*255/width;70rgbData[3 * (y * width + x) + 1] = x*255/width;71rgbData[3 * (y * width + x) + 2] = y*255/height;72}73}7475*outLen = width * height * 2;76*outData = (unsigned char*)malloc(*outLen);7778jpge::params params;79params.m_quality = 60;80params.m_subsampling = jpge::H2V2;81params.m_two_pass_flag = false;82jpge::compress_image_to_jpeg_file_in_memory(83*outData, *outLen, width, height, 3, rgbData, params);84free(rgbData);85#endif //USE_FFMPEG86}878889#if defined(USING_QT_UI)9091std::vector<std::string> __qt_getDeviceList() {92std::vector<std::string> deviceList;93const QList<QCameraInfo> cameras = QCameraInfo::availableCameras();94for (const QCameraInfo &cameraInfo : cameras) {95deviceList.push_back(cameraInfo.deviceName().toStdString()96+ " (" + cameraInfo.description().toStdString() + ")");97}98return deviceList;99}100101QList<QVideoFrame::PixelFormat> MyViewfinder::supportedPixelFormats(QAbstractVideoBuffer::HandleType handleType) const {102Q_UNUSED(handleType);103// Return the formats you will support104return QList<QVideoFrame::PixelFormat>()105<< QVideoFrame::Format_RGB24106<< QVideoFrame::Format_YUYV107;108}109110bool MyViewfinder::present(const QVideoFrame &frame) {111#ifdef USE_FFMPEG112if (frame.isValid()) {113QVideoFrame cloneFrame(frame);114cloneFrame.map(QAbstractVideoBuffer::ReadOnly);115116unsigned char *jpegData = nullptr;117int jpegLen = 0;118119QVideoFrame::PixelFormat frameFormat = cloneFrame.pixelFormat();120if (frameFormat == QVideoFrame::Format_RGB24) {121convert_frame(cloneFrame.size().width(), cloneFrame.size().height(),122(unsigned char*)cloneFrame.bits(), AV_PIX_FMT_RGB24,123qtc_ideal_width, qtc_ideal_height, &jpegData, &jpegLen);124125} else if (frameFormat == QVideoFrame::Format_YUYV) {126convert_frame(cloneFrame.size().width(), cloneFrame.size().height(),127(unsigned char*)cloneFrame.bits(), AV_PIX_FMT_YUYV422,128qtc_ideal_width, qtc_ideal_height, &jpegData, &jpegLen);129}130131if (jpegData) {132Camera::pushCameraImage(jpegLen, jpegData);133free(jpegData);134jpegData = nullptr;135}136137cloneFrame.unmap();138return true;139}140#endif //USE_FFMPEG141return false;142}143144int __qt_startCapture(int width, int height) {145if (qt_camera != nullptr) {146ERROR_LOG(Log::HLE, "camera already started");147return -1;148}149150char selectedCamera[80];151sscanf(g_Config.sCameraDevice.c_str(), "%80s ", &selectedCamera[0]);152153const QList<QCameraInfo> availableCameras = QCameraInfo::availableCameras();154if (availableCameras.size() < 1) {155delete qt_camera;156qt_camera = nullptr;157ERROR_LOG(Log::HLE, "no camera found");158return -1;159}160for (const QCameraInfo &cameraInfo : availableCameras) {161if (cameraInfo.deviceName() == selectedCamera) {162qt_camera = new QCamera(cameraInfo);163}164}165if (qt_camera == nullptr) {166qt_camera = new QCamera();167if (qt_camera == nullptr) {168ERROR_LOG(Log::HLE, "cannot open camera");169return -1;170}171}172173qtc_ideal_width = width;174qtc_ideal_height = height;175176qt_viewfinder = new MyViewfinder;177178QCameraViewfinderSettings viewfinderSettings = qt_camera->viewfinderSettings();179viewfinderSettings.setResolution(640, 480);180viewfinderSettings.setMinimumFrameRate(15.0);181viewfinderSettings.setMaximumFrameRate(15.0);182183qt_camera->setViewfinderSettings(viewfinderSettings);184qt_camera->setViewfinder(qt_viewfinder);185qt_camera->start();186187return 0;188}189190int __qt_stopCapture() {191if (qt_camera != nullptr) {192qt_camera->stop();193qt_camera->unload();194delete qt_camera;195delete qt_viewfinder;196qt_camera = nullptr;197}198return 0;199}200201//endif defined(USING_QT_UI)202#elif PPSSPP_PLATFORM(LINUX) && !PPSSPP_PLATFORM(ANDROID)203204std::vector<std::string> __v4l_getDeviceList() {205std::vector<std::string> deviceList;206#ifdef USE_FFMPEG207for (int i = 0; i < 64; i++) {208char path[256];209snprintf(path, sizeof(path), "/dev/video%d", i);210if (access(path, F_OK) < 0) {211break;212}213int fd = -1;214if((fd = open(path, O_RDONLY)) < 0) {215ERROR_LOG(Log::HLE, "Cannot open '%s'; errno=%d(%s)", path, errno, strerror(errno));216continue;217}218struct v4l2_capability video_cap;219if(ioctl(fd, VIDIOC_QUERYCAP, &video_cap) < 0) {220ERROR_LOG(Log::HLE, "VIDIOC_QUERYCAP");221goto cont;222} else {223char device[256];224snprintf(device, sizeof(device), "%d:%s", i, video_cap.card);225deviceList.push_back(device);226}227cont:228close(fd);229fd = -1;230}231#endif //USE_FFMPEG232return deviceList;233}234235void *v4l_loop(void *data) {236#ifdef USE_FFMPEG237SetCurrentThreadName("v4l_loop");238while (v4l_fd >= 0) {239struct v4l2_buffer buf;240memset(&buf, 0, sizeof(buf));241buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;242buf.memory = V4L2_MEMORY_MMAP;243244if (ioctl(v4l_fd, VIDIOC_DQBUF, &buf) == -1) {245ERROR_LOG(Log::HLE, "VIDIOC_DQBUF; errno=%d(%s)", errno, strerror(errno));246switch (errno) {247case EAGAIN:248continue;249default:250return nullptr;251}252}253254unsigned char *jpegData = nullptr;255int jpegLen = 0;256257if (v4l_format == V4L2_PIX_FMT_YUYV) {258convert_frame(v4l_hw_width, v4l_hw_height, (unsigned char*)v4l_buffers[buf.index].start, AV_PIX_FMT_YUYV422,259v4l_ideal_width, v4l_ideal_height, &jpegData, &jpegLen);260} else if (v4l_format == V4L2_PIX_FMT_JPEG261|| v4l_format == V4L2_PIX_FMT_MJPEG) {262// decompress jpeg263int width, height, req_comps;264unsigned char *rgbData = jpgd::decompress_jpeg_image_from_memory(265(unsigned char*)v4l_buffers[buf.index].start, buf.bytesused, &width, &height, &req_comps, 3);266267convert_frame(v4l_hw_width, v4l_hw_height, (unsigned char*)rgbData, AV_PIX_FMT_RGB24,268v4l_ideal_width, v4l_ideal_height, &jpegData, &jpegLen);269free(rgbData);270}271272if (jpegData) {273Camera::pushCameraImage(jpegLen, jpegData);274free(jpegData);275jpegData = nullptr;276}277278buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;279buf.memory = V4L2_MEMORY_MMAP;280if (ioctl(v4l_fd, VIDIOC_QBUF, &buf) == -1) {281ERROR_LOG(Log::HLE, "VIDIOC_QBUF");282return nullptr;283}284}285return nullptr;286#endif //USE_FFMPEG287}288289int __v4l_startCapture(int ideal_width, int ideal_height) {290#ifdef USE_FFMPEG291if (v4l_fd >= 0) {292__v4l_stopCapture();293}294v4l_ideal_width = ideal_width;295v4l_ideal_height = ideal_height;296297int dev_index = 0;298char dev_name[64];299sscanf(g_Config.sCameraDevice.c_str(), "%d:", &dev_index);300snprintf(dev_name, sizeof(dev_name), "/dev/video%d", dev_index);301302if ((v4l_fd = open(dev_name, O_RDWR)) == -1) {303ERROR_LOG(Log::HLE, "Cannot open '%s'; errno=%d(%s)", dev_name, errno, strerror(errno));304return -1;305}306307struct v4l2_capability cap;308memset(&cap, 0, sizeof(cap));309if (ioctl(v4l_fd, VIDIOC_QUERYCAP, &cap) == -1) {310ERROR_LOG(Log::HLE, "VIDIOC_QUERYCAP");311return -1;312}313if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {314ERROR_LOG(Log::HLE, "V4L2_CAP_VIDEO_CAPTURE");315return -1;316}317if (!(cap.capabilities & V4L2_CAP_STREAMING)) {318ERROR_LOG(Log::HLE, "V4L2_CAP_STREAMING");319return -1;320}321322struct v4l2_format fmt;323memset(&fmt, 0, sizeof(fmt));324fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;325fmt.fmt.pix.pixelformat = 0;326327// select a pixel format328struct v4l2_fmtdesc desc;329memset(&desc, 0, sizeof(desc));330desc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;331while (ioctl(v4l_fd, VIDIOC_ENUM_FMT, &desc) == 0) {332desc.index++;333INFO_LOG(Log::HLE, "V4L2: pixel format supported: %s", desc.description);334if (fmt.fmt.pix.pixelformat != 0) {335continue;336} else if (desc.pixelformat == V4L2_PIX_FMT_YUYV337|| desc.pixelformat == V4L2_PIX_FMT_JPEG338|| desc.pixelformat == V4L2_PIX_FMT_MJPEG) {339INFO_LOG(Log::HLE, "V4L2: %s selected", desc.description);340fmt.fmt.pix.pixelformat = desc.pixelformat;341v4l_format = desc.pixelformat;342}343}344if (fmt.fmt.pix.pixelformat == 0) {345ERROR_LOG(Log::HLE, "V4L2: No supported format found");346return -1;347}348349// select a frame size350fmt.fmt.pix.width = 0;351fmt.fmt.pix.height = 0;352struct v4l2_frmsizeenum frmsize;353memset(&frmsize, 0, sizeof(frmsize));354frmsize.pixel_format = fmt.fmt.pix.pixelformat;355while (ioctl(v4l_fd, VIDIOC_ENUM_FRAMESIZES, &frmsize) == 0) {356frmsize.index++;357if (frmsize.type == V4L2_FRMSIZE_TYPE_DISCRETE) {358INFO_LOG(Log::HLE, "V4L2: frame size supported: %dx%d", frmsize.discrete.width, frmsize.discrete.height);359bool matchesIdeal = frmsize.discrete.width >= ideal_width && frmsize.discrete.height >= ideal_height;360bool zeroPix = fmt.fmt.pix.width == 0 && fmt.fmt.pix.height == 0;361bool pixLarger = frmsize.discrete.width < fmt.fmt.pix.width && frmsize.discrete.height < fmt.fmt.pix.height;362if (matchesIdeal && (zeroPix || pixLarger)) {363fmt.fmt.pix.width = frmsize.discrete.width;364fmt.fmt.pix.height = frmsize.discrete.height;365}366}367}368369if (fmt.fmt.pix.width == 0 && fmt.fmt.pix.height == 0) {370fmt.fmt.pix.width = ideal_width;371fmt.fmt.pix.height = ideal_height;372}373INFO_LOG(Log::HLE, "V4L2: asking for %dx%d", fmt.fmt.pix.width, fmt.fmt.pix.height);374if (ioctl(v4l_fd, VIDIOC_S_FMT, &fmt) == -1) {375ERROR_LOG(Log::HLE, "VIDIOC_S_FMT");376return -1;377}378v4l_hw_width = fmt.fmt.pix.width;379v4l_hw_height = fmt.fmt.pix.height;380INFO_LOG(Log::HLE, "V4L2: will receive %dx%d", v4l_hw_width, v4l_hw_height);381v4l_height_fixed_aspect = v4l_hw_width * ideal_height / ideal_width;382INFO_LOG(Log::HLE, "V4L2: will use %dx%d", v4l_hw_width, v4l_height_fixed_aspect);383384struct v4l2_requestbuffers req;385memset(&req, 0, sizeof(req));386req.count = 1;387req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;388req.memory = V4L2_MEMORY_MMAP;389if (ioctl(v4l_fd, VIDIOC_REQBUFS, &req) == -1) {390ERROR_LOG(Log::HLE, "VIDIOC_REQBUFS");391return -1;392}393v4l_buffer_count = req.count;394INFO_LOG(Log::HLE, "V4L2: buffer count: %d", v4l_buffer_count);395v4l_buffers = (v4l_buf_t*) calloc(v4l_buffer_count, sizeof(v4l_buf_t));396397for (int buf_id = 0; buf_id < v4l_buffer_count; buf_id++) {398struct v4l2_buffer buf;399memset(&buf, 0, sizeof(buf));400buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;401buf.memory = V4L2_MEMORY_MMAP;402buf.index = buf_id;403if (ioctl(v4l_fd, VIDIOC_QUERYBUF, &buf) == -1) {404ERROR_LOG(Log::HLE, "VIDIOC_QUERYBUF");405return -1;406}407408v4l_buffers[buf_id].length = buf.length;409v4l_buffers[buf_id].start = mmap(NULL,410buf.length,411PROT_READ | PROT_WRITE,412MAP_SHARED,413v4l_fd, buf.m.offset);414if (v4l_buffers[buf_id].start == MAP_FAILED) {415ERROR_LOG(Log::HLE, "MAP_FAILED");416return -1;417}418419memset(&buf, 0, sizeof(buf));420buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;421buf.memory = V4L2_MEMORY_MMAP;422buf.index = buf_id;423if (ioctl(v4l_fd, VIDIOC_QBUF, &buf) == -1) {424ERROR_LOG(Log::HLE, "VIDIOC_QBUF");425return -1;426}427}428429enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;430if (ioctl(v4l_fd, VIDIOC_STREAMON, &type) == -1) {431ERROR_LOG(Log::HLE, "VIDIOC_STREAMON");432return -1;433}434435pthread_create(&v4l_thread, NULL, v4l_loop, NULL);436#endif //USE_FFMPEG437return 0;438}439440int __v4l_stopCapture() {441enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;442443if (v4l_fd < 0) {444goto exit;445}446447if (ioctl(v4l_fd, VIDIOC_STREAMOFF, &type) == -1) {448ERROR_LOG(Log::HLE, "VIDIOC_STREAMOFF");449goto exit;450}451452for (int buf_id = 0; buf_id < v4l_buffer_count; buf_id++) {453if (munmap(v4l_buffers[buf_id].start, v4l_buffers[buf_id].length) == -1) {454ERROR_LOG(Log::HLE, "munmap");455goto exit;456}457}458459if (close(v4l_fd) == -1) {460ERROR_LOG(Log::HLE, "close");461goto exit;462}463464v4l_fd = -1;465//pthread_join(v4l_thread, NULL);466467exit:468v4l_fd = -1;469return 0;470}471472#endif // PPSSPP_PLATFORM(LINUX) && !PPSSPP_PLATFORM(ANDROID)473474475