diff --git a/CMakeLists.txt b/CMakeLists.txt index df14ac85a6..176a4500e9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1668,6 +1668,8 @@ add_library(${CoreLibName} ${CoreLinkType} Core/HW/SimpleAudioDec.h Core/HW/AsyncIOManager.cpp Core/HW/AsyncIOManager.h + Core/HW/Camera.cpp + Core/HW/Camera.h Core/HW/MediaEngine.cpp Core/HW/MediaEngine.h Core/HW/MpegDemux.cpp diff --git a/Core/Config.cpp b/Core/Config.cpp index 3dd37d49c7..0e7a27f655 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -707,8 +707,8 @@ static ConfigSetting graphicsSettings[] = { #ifdef _WIN32 ConfigSetting("D3D11Device", &g_Config.sD3D11Device, "", true, false), #endif -#if defined(_WIN32) && !PPSSPP_PLATFORM(UWP) - ConfigSetting("WinCameraDevice", &g_Config.sWinCameraDevice, "", true, false), +#if (defined(_WIN32) && !PPSSPP_PLATFORM(UWP)) || PPSSPP_PLATFORM(LINUX) + ConfigSetting("CameraDevice", &g_Config.sCameraDevice, "", true, false), #endif ConfigSetting("VendorBugChecksEnabled", &g_Config.bVendorBugChecksEnabled, true, false, false), ReportedConfigSetting("RenderingMode", &g_Config.iRenderingMode, 1, true, true), diff --git a/Core/Config.h b/Core/Config.h index 3252164101..88be654ea3 100644 --- a/Core/Config.h +++ b/Core/Config.h @@ -128,7 +128,7 @@ public: // If not set, will use the "best" device. std::string sVulkanDevice; std::string sD3D11Device; // Windows only - std::string sWinCameraDevice; // Windows only + std::string sCameraDevice; bool bSoftwareRendering; bool bHardwareTransform; // only used in the GLES backend diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index 22d258ecad..021cbfb153 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -375,6 +375,7 @@ + @@ -903,6 +904,7 @@ + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index 7f096c1b50..d1257aa716 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -740,6 +740,9 @@ HLE\Libraries + + HW + @@ -1373,6 +1376,9 @@ HLE\Libraries + + HW + diff --git a/Core/HLE/sceUsb.cpp b/Core/HLE/sceUsb.cpp index 361f55f26b..cd2e5e5e16 100644 --- a/Core/HLE/sceUsb.cpp +++ b/Core/HLE/sceUsb.cpp @@ -99,6 +99,16 @@ static int sceUsbDeactivate(u32 pid) { return 0; } +static int sceUsbWaitState(int state, int waitMode, u32 timeoutAddr) { + ERROR_LOG(HLE, "UNIMPL sceUsbWaitStat(%i, %i, %08x)", state, waitMode, timeoutAddr); + return sceUsbGetState(); +} + +static int sceUsbWaitStateCB(int state, int waitMode, u32 timeoutAddr) { + ERROR_LOG(HLE, "UNIMPL sceUsbWaitStateCB(%i, %i, %08x)", state, waitMode, timeoutAddr); + return 0; +} + const HLEFunction sceUsb[] = { {0XAE5DE6AF, &WrapI_CUU, "sceUsbStart", 'i', "sxx"}, @@ -108,8 +118,8 @@ const HLEFunction sceUsb[] = {0X112CC951, nullptr, "sceUsbGetDrvState", '?', "" }, {0X586DB82C, &WrapI_U, "sceUsbActivate", 'i', "x" }, {0XC572A9C8, &WrapI_U, "sceUsbDeactivate", 'i', "x" }, - {0X5BE0E002, nullptr, "sceUsbWaitState", '?', "" }, - {0X616F2B61, nullptr, "sceUsbWaitStateCB", '?', "" }, + {0X5BE0E002, &WrapI_IIU, "sceUsbWaitState", '?', "xxx"}, + {0X616F2B61, &WrapI_IIU, "sceUsbWaitStateCB", '?', "xxx"}, {0X1C360735, nullptr, "sceUsbWaitCancel", '?', "" }, }; diff --git a/Core/HLE/sceUsbCam.cpp b/Core/HLE/sceUsbCam.cpp index 50f1147b05..da850ce8f9 100644 --- a/Core/HLE/sceUsbCam.cpp +++ b/Core/HLE/sceUsbCam.cpp @@ -19,55 +19,95 @@ #include #include "base/NativeApp.h" +#include "ppsspp_config.h" #include "Common/ChunkFile.h" #include "Core/HLE/HLE.h" -#include "Core/HLE/FunctionWrappers.h" #include "Core/HLE/sceUsbCam.h" +#include "Core/HW/Camera.h" #include "Core/MemMapHelpers.h" #if defined(_WIN32) && !PPSSPP_PLATFORM(UWP) +#ifndef NOMINMAX +#define NOMINMAX +#endif #include "Windows/CaptureDevice.h" -#undef min #endif - -PspUsbCamSetupMicParam micParam; -PspUsbCamSetupVideoParam videoParam; +Camera::Config *config; unsigned int videoBufferLength = 0; unsigned int nextVideoFrame = 0; uint8_t *videoBuffer; std::mutex videoBufferMutex; -bool isShutDown = false; enum { VIDEO_BUFFER_SIZE = 40 * 1000, }; void __UsbCamInit() { - videoBuffer = new uint8_t[VIDEO_BUFFER_SIZE]; - isShutDown = false; + config = new Camera::Config; + config->mode = Camera::Mode::Unused; + config->type = Camera::ConfigType::CfNone; + videoBuffer = new uint8_t[VIDEO_BUFFER_SIZE]; +} + +void __UsbCamDoState(PointerWrap &p) { + auto s = p.Section("sceUsbCam", 0, 1); + if (!s) { + return; + } + + p.Do(*config); + if (config->mode == Camera::Mode::Video) { // stillImage? TBD + Camera::startCapture(); + } } void __UsbCamShutdown() { -#if defined(_WIN32) && !PPSSPP_PLATFORM(UWP) - if (winCamera) { - winCamera->sendMessage({ CAPTUREDEVIDE_COMMAND::SHUTDOWN, nullptr }); - } -#endif - isShutDown = true; - + Camera::stopCapture(); delete[] videoBuffer; videoBuffer = nullptr; + delete[] config; + config = nullptr; } // TODO: Technically, we should store the videoBuffer into the savestate, if this // module has been initialized. +static int getCameraResolution(Camera::ConfigType type, int *width, int *height) { + if (type == Camera::ConfigType::CfStill || type == Camera::ConfigType::CfVideo) { + switch(config->stillParam.resolution) { + case 0: *width = 160; *height = 120; return 0; + case 1: *width = 176; *height = 144; return 0; + case 2: *width = 320; *height = 240; return 0; + case 3: *width = 352; *height = 288; return 0; + case 4: *width = 640; *height = 480; return 0; + case 5: *width =1024; *height = 768; return 0; + case 6: *width =1280; *height = 960; return 0; + case 7: *width = 480; *height = 272; return 0; + case 8: *width = 360; *height = 272; return 0; + } + } else if (type == Camera::ConfigType::CfStillEx || type == Camera::ConfigType::CfVideoEx) { + switch(config->stillExParam.resolution) { + case 0: *width = 160; *height = 120; return 0; + case 1: *width = 176; *height = 144; return 0; + case 2: *width = 320; *height = 240; return 0; + case 3: *width = 352; *height = 288; return 0; + case 4: *width = 360; *height = 272; return 0; + case 5: *width = 480; *height = 272; return 0; + case 6: *width = 640; *height = 480; return 0; + case 7: *width =1024; *height = 768; return 0; + case 8: *width =1280; *height = 960; return 0; + } + } + *width = 0; *height = 0; return 1; +} + + static int sceUsbCamSetupMic(u32 paramAddr, u32 workareaAddr, int wasize) { INFO_LOG(HLE, "UNIMPL sceUsbCamSetupMic"); - if (Memory::IsValidRange(paramAddr, sizeof(micParam))) { - Memory::ReadStruct(paramAddr, &micParam); + if (Memory::IsValidRange(paramAddr, sizeof(PspUsbCamSetupMicParam))) { + Memory::ReadStruct(paramAddr, &config->micParam); } return 0; } @@ -89,59 +129,37 @@ static int sceUsbCamReadMicBlocking(u32 bufAddr, u32 size) { Memory::Write_U8(i & 0xFF, bufAddr + i); } } - hleEatMicro(1000000 / micParam.frequency * (size / 2)); + hleEatMicro(1000000 / config->micParam.frequency * (size / 2)); return size; } static int sceUsbCamSetupVideo(u32 paramAddr, u32 workareaAddr, int wasize) { - INFO_LOG(HLE, "UNIMPL sceUsbCamSetupVideo"); - if (Memory::IsValidRange(paramAddr, sizeof(videoParam))) { - Memory::ReadStruct(paramAddr, &videoParam); + if (Memory::IsValidRange(paramAddr, sizeof(PspUsbCamSetupVideoParam))) { + Memory::ReadStruct(paramAddr, &config->videoParam); } + config->type = Camera::ConfigType::CfVideo; + return 0; +} - INFO_LOG(HLE, "UNIMPL sceUsbCamSetupVideo - size: %d", videoParam.size); - INFO_LOG(HLE, "UNIMPL sceUsbCamSetupVideo - resolution: %d", videoParam.resolution); - INFO_LOG(HLE, "UNIMPL sceUsbCamSetupVideo - framesize: %d", videoParam.framesize); - - std::lock_guard lock(videoBufferMutex); - videoBufferLength = sizeof(sceUsbCamDummyImage); - memset(videoBuffer, 0, VIDEO_BUFFER_SIZE); - memcpy(videoBuffer, sceUsbCamDummyImage, sizeof(sceUsbCamDummyImage)); +static int sceUsbCamSetupVideoEx(u32 paramAddr, u32 workareaAddr, int wasize) { + if (Memory::IsValidRange(paramAddr, sizeof(PspUsbCamSetupVideoExParam))) { + Memory::ReadStruct(paramAddr, &config->videoExParam); + } + config->type = Camera::ConfigType::CfVideoEx; return 0; } static int sceUsbCamStartVideo() { - INFO_LOG(HLE, "UNIMPL sceUsbCamStartVideo"); -#if defined(_WIN32) && !PPSSPP_PLATFORM(UWP) - if (winCamera) { - if (winCamera->isShutDown()) { - delete winCamera; - winCamera = new WindowsCaptureDevice(CAPTUREDEVIDE_TYPE::VIDEO); - winCamera->sendMessage({ CAPTUREDEVIDE_COMMAND::INITIALIZE, nullptr }); - } - - winCamera->sendMessage({ CAPTUREDEVIDE_COMMAND::START, nullptr }); - } - -#else - System_SendMessage("camera_command", "startVideo"); -#endif + std::lock_guard lock(videoBufferMutex); + videoBufferLength = sizeof(sceUsbCamDummyImage); + memset(videoBuffer, 0, VIDEO_BUFFER_SIZE); + memcpy(videoBuffer, sceUsbCamDummyImage, sizeof(sceUsbCamDummyImage)); + Camera::startCapture(); return 0; } static int sceUsbCamStopVideo() { - INFO_LOG(HLE, "UNIMPL sceUsbCamStopVideo"); -#if defined(_WIN32) && !PPSSPP_PLATFORM(UWP) - if (winCamera) - winCamera->sendMessage({ CAPTUREDEVIDE_COMMAND::STOP, nullptr }); -#else - System_SendMessage("camera_command", "stopVideo"); -#endif - return 0; -} - -static int sceUsbCamAutoImageReverseSW(int rev) { - INFO_LOG(HLE, "UNIMPL sceUsbCamAutoImageReverseSW"); + Camera::stopCapture(); return 0; } @@ -169,6 +187,29 @@ static int sceUsbCamPollReadVideoFrameEnd() { return nextVideoFrame; } +static int sceUsbCamSetupStill(u32 paramAddr) { + INFO_LOG(HLE, "UNIMPL sceUsbCamSetupStill"); + if (Memory::IsValidRange(paramAddr, sizeof(PspUsbCamSetupStillParam))) { + Memory::ReadStruct(paramAddr, &config->stillParam); + } + config->type = Camera::ConfigType::CfStill; + return 0; +} + +static int sceUsbCamSetupStillEx(u32 paramAddr) { + INFO_LOG(HLE, "UNIMPL sceUsbCamSetupStillEx"); + if (Memory::IsValidRange(paramAddr, sizeof(PspUsbCamSetupStillExParam))) { + Memory::ReadStruct(paramAddr, &config->stillExParam); + } + config->type = Camera::ConfigType::CfStillEx; + return 0; +} + +static int sceUsbCamAutoImageReverseSW(int rev) { + INFO_LOG(HLE, "UNIMPL sceUsbCamAutoImageReverseSW"); + return 0; +} + const HLEFunction sceUsbCam[] = { { 0X03ED7A82, &WrapI_UUI, "sceUsbCamSetupMic", 'i', "xxi" }, @@ -183,7 +224,7 @@ const HLEFunction sceUsbCam[] = { 0X08AEE98A, nullptr, "sceUsbCamSetMicGain", '?', "" }, { 0X17F7B2FB, &WrapI_UUI, "sceUsbCamSetupVideo", 'i', "xxi" }, - { 0XCFE9E999, nullptr, "sceUsbCamSetupVideoEx", '?', "" }, + { 0XCFE9E999, &WrapI_UUI, "sceUsbCamSetupVideoEx", 'i', "xxi" }, { 0X574A8C3F, &WrapI_V, "sceUsbCamStartVideo", 'i', "" }, { 0X6CF32CB9, &WrapI_V, "sceUsbCamStopVideo", 'i', "" }, { 0X7DAC0C71, &WrapI_UU, "sceUsbCamReadVideoFrameBlocking", 'i', "xx" }, @@ -192,8 +233,8 @@ const HLEFunction sceUsbCam[] = { 0X41E73E95, &WrapI_V, "sceUsbCamPollReadVideoFrameEnd", 'i', "" }, { 0XDF9D0C92, nullptr, "sceUsbCamGetReadVideoFrameSize", '?', "" }, - { 0X3F0CF289, nullptr, "sceUsbCamSetupStill", '?', "" }, - { 0X0A41A298, nullptr, "sceUsbCamSetupStillEx", '?', "" }, + { 0X3F0CF289, &WrapI_U, "sceUsbCamSetupStill", 'i', "x" }, + { 0X0A41A298, &WrapI_U, "sceUsbCamSetupStillEx", 'i', "x" }, { 0X61BE5CAC, nullptr, "sceUsbCamStillInputBlocking", '?', "" }, { 0XFB0A6C5D, nullptr, "sceUsbCamStillInput", '?', "" }, { 0X7563AFA1, nullptr, "sceUsbCamStillWaitInputEnd", '?', "" }, @@ -233,10 +274,65 @@ void Register_sceUsbCam() RegisterModule("sceUsbCam", ARRAY_SIZE(sceUsbCam), sceUsbCam); } +std::vector Camera::getDeviceList() { + #if PPSSPP_PLATFORM(LINUX) && !PPSSPP_PLATFORM(ANDROID) + return __v4l_getDeviceList(); + #elif defined(_WIN32) && !PPSSPP_PLATFORM(UWP) + if (winCamera) { + return winCamera->getDeviceList(); + } + #endif + return std::vector(); +} + +int Camera::startCapture() { + int width, height; + getCameraResolution(config->type, &width, &height); + INFO_LOG(HLE, "%s resolution: %dx%d", __FUNCTION__, width, height); + + config->mode = Camera::Mode::Video; + #if PPSSPP_PLATFORM(ANDROID) + System_SendMessage("camera_command", "startVideo"); + #elif PPSSPP_PLATFORM(LINUX) + __v4l_startCapture(width, height); + #elif defined(_WIN32) && !PPSSPP_PLATFORM(UWP) + if (winCamera) { + if (winCamera->isShutDown()) { + delete winCamera; + winCamera = new WindowsCaptureDevice(CAPTUREDEVIDE_TYPE::VIDEO); + winCamera->sendMessage({ CAPTUREDEVIDE_COMMAND::INITIALIZE, nullptr }); + } + winCamera->sendMessage({ CAPTUREDEVIDE_COMMAND::START, nullptr }); + } + #else + ERROR_LOG(HLE, "%s not implemented", __FUNCTION__); + #endif + return 0; +} + +int Camera::stopCapture() { + INFO_LOG(HLE, "%s", __FUNCTION__); + #if PPSSPP_PLATFORM(ANDROID) + System_SendMessage("camera_command", "stopVideo"); + #elif PPSSPP_PLATFORM(LINUX) + __v4l_stopCapture(); + #elif defined(_WIN32) && !PPSSPP_PLATFORM(UWP) + if (winCamera) { + winCamera->sendMessage({ CAPTUREDEVIDE_COMMAND::STOP, nullptr }); + winCamera->sendMessage({ CAPTUREDEVIDE_COMMAND::SHUTDOWN, nullptr }); + } + #else + ERROR_LOG(HLE, "%s not implemented", __FUNCTION__); + #endif + config->mode = Camera::Mode::Unused; + return 0; +} + void Camera::pushCameraImage(long long length, unsigned char* image) { std::lock_guard lock(videoBufferMutex); - if (isShutDown) + if (!videoBuffer) { return; + } memset(videoBuffer, 0, VIDEO_BUFFER_SIZE); if (length > VIDEO_BUFFER_SIZE) { videoBufferLength = 0; diff --git a/Core/HLE/sceUsbCam.h b/Core/HLE/sceUsbCam.h index ebb5388db6..3d6bfbcf3f 100644 --- a/Core/HLE/sceUsbCam.h +++ b/Core/HLE/sceUsbCam.h @@ -17,16 +17,18 @@ #pragma once +#include +#include +#include +#include "Core/HLE/FunctionWrappers.h" + void Register_sceUsbCam(); void __UsbCamInit(); +void __UsbCamDoState(PointerWrap &p); void __UsbCamShutdown(); -namespace Camera { - void pushCameraImage(long long length, unsigned char *image); -} - -typedef struct PspUsbCamSetupMicParam { +typedef struct { int size; u32 unk; int gain; @@ -34,7 +36,30 @@ typedef struct PspUsbCamSetupMicParam { int frequency; } PspUsbCamSetupMicParam; -typedef struct PspUsbCamSetupVideoParam { +typedef struct { + int size; + int resolution; + int jpegsize; + int reverseflags; + int delay; + int complevel; +} PspUsbCamSetupStillParam; + +typedef struct { + int size; + u32 unk; + int resolution; + int jpegsize; + int complevel; + u32 unk2; + u32 unk3; + int flip; + int mirror; + int delay; + u32 unk4[5]; +} PspUsbCamSetupStillExParam; + +typedef struct { int size; int resolution; int framerate; @@ -49,6 +74,54 @@ typedef struct PspUsbCamSetupVideoParam { int evlevel; } PspUsbCamSetupVideoParam; +typedef struct { + int size; + u32 unk; + int resolution; + int framerate; + u32 unk2; + u32 unk3; + int wb; + int saturation; + int brightness; + int contrast; + int sharpness; + u32 unk4; + u32 unk5; + u32 unk6[3]; + int effectmode; + u32 unk7; + u32 unk8; + u32 unk9; + u32 unk10; + u32 unk11; + int framesize; + u32 unk12; + int evlevel; +} PspUsbCamSetupVideoExParam; + +namespace Camera { + enum Mode { Unused, Still, Video }; + enum ConfigType { CfNone, CfStill, CfStillEx, CfVideo, CfVideoEx }; + + typedef struct { + Mode mode; + ConfigType type; + PspUsbCamSetupMicParam micParam; + union { + PspUsbCamSetupStillParam stillParam; + PspUsbCamSetupStillExParam stillExParam; + PspUsbCamSetupVideoParam videoParam; + PspUsbCamSetupVideoExParam videoExParam; + }; + } Config; + + std::vector getDeviceList(); + int startCapture(); + int stopCapture(); + void pushCameraImage(long long length, unsigned char *image); +} + static const unsigned char sceUsbCamDummyImage[] = { 0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46, 0x00, 0x01, 0x01, 0x01, 0x00, 0x60, 0x00, 0x60, 0x00, 0x00, 0xFF, 0xE1, 0x00, 0x68, 0x45, 0x78, 0x69, 0x66, 0x00, 0x00, 0x4D, 0x4D, diff --git a/Core/HW/Camera.cpp b/Core/HW/Camera.cpp new file mode 100644 index 0000000000..2260f452e8 --- /dev/null +++ b/Core/HW/Camera.cpp @@ -0,0 +1,314 @@ +// Copyright (c) 2020- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#include "Camera.h" + +#if PPSSPP_PLATFORM(LINUX) && !PPSSPP_PLATFORM(ANDROID) + +std::vector __v4l_getDeviceList() { + std::vector deviceList; + for (int i = 0; i < 64; i++) { + char path[256]; + snprintf(path, sizeof(path), "/dev/video%d", i); + if (access(path, F_OK) < 0) { + break; + } + int fd = -1; + if((fd = open(path, O_RDONLY)) < 0) { + ERROR_LOG(HLE, "Cannot open '%s'; errno=%d(%s)", path, errno, strerror(errno)); + continue; + } + struct v4l2_capability video_cap; + if(ioctl(fd, VIDIOC_QUERYCAP, &video_cap) < 0) { + ERROR_LOG(HLE, "VIDIOC_QUERYCAP"); + goto cont; + } else { + char device[256]; + snprintf(device, sizeof(device), "%d:%s", i, video_cap.card); + deviceList.push_back(device); + } +cont: + close(fd); + fd = -1; + } + return deviceList; +} + +void convert_frame(unsigned char *inData, AVPixelFormat inFormat, unsigned char **outData, int *outLen) { + // resize + uint8_t *src[4] = {0}; + uint8_t *dst[4] = {0}; + int srcStride[4], dstStride[4]; + + unsigned char *rgbData = (unsigned char*)malloc(v4l_ideal_width * v4l_ideal_height * 3); + + av_image_fill_linesizes(srcStride, inFormat, v4l_hw_width); + av_image_fill_linesizes(dstStride, AV_PIX_FMT_RGB24, v4l_ideal_width); + + av_image_fill_pointers(src, inFormat, v4l_hw_height, inData, srcStride); + av_image_fill_pointers(dst, AV_PIX_FMT_RGB24, v4l_ideal_height, rgbData, dstStride); + + sws_scale(sws_context, + src, srcStride, 0, v4l_height_fixed_aspect, + dst, dstStride); + + // compress jpeg + *outLen = v4l_ideal_width * v4l_ideal_height * 3; + *outData = (unsigned char*)malloc(*outLen); + + jpge::params params; + params.m_quality = 60; + params.m_subsampling = jpge::H2V2; + params.m_two_pass_flag = false; + jpge::compress_image_to_jpeg_file_in_memory( + *outData, *outLen, v4l_ideal_width, v4l_ideal_height, 3, rgbData, params); + free(rgbData); +} + +void *v4l_loop(void *data) { + setCurrentThreadName("v4l_loop"); + while (v4l_fd >= 0) { + struct v4l2_buffer buf; + memset(&buf, 0, sizeof(buf)); + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + + if (ioctl(v4l_fd, VIDIOC_DQBUF, &buf) == -1) { + ERROR_LOG(HLE, "VIDIOC_DQBUF; errno=%d(%s)", errno, strerror(errno)); + switch (errno) { + case EAGAIN: + continue; + default: + return NULL; + } + } + + unsigned char *jpegData; + int jpegLen; + + if (v4l_format == V4L2_PIX_FMT_YUYV) { + convert_frame((unsigned char*)v4l_buffer, AV_PIX_FMT_YUYV422, &jpegData, &jpegLen); + } else if (v4l_format == V4L2_PIX_FMT_MJPEG) { + // decompress jpeg + int width, height, req_comps; + unsigned char *rgbData = jpgd::decompress_jpeg_image_from_memory( + (unsigned char*)v4l_buffer, buf.bytesused, &width, &height, &req_comps, 3); + + convert_frame(rgbData, AV_PIX_FMT_RGB24, &jpegData, &jpegLen); + free(rgbData); + } + + Camera::pushCameraImage(jpegLen, jpegData); + free(jpegData); + + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + if (ioctl(v4l_fd, VIDIOC_QBUF, &buf) == -1) { + ERROR_LOG(HLE, "VIDIOC_QBUF"); + return NULL; + } + } +} + +int __v4l_startCapture(int ideal_width, int ideal_height) { + if (v4l_fd >= 0) { + __v4l_stopCapture(); + } + v4l_ideal_width = ideal_width; + v4l_ideal_height = ideal_height; + + int dev_index = 0; + char dev_name[64]; + sscanf(g_Config.sCameraDevice.c_str(), "%d:", &dev_index); + snprintf(dev_name, sizeof(dev_name), "/dev/video%d", dev_index); + + if ((v4l_fd = open(dev_name, O_RDWR)) == -1) { + ERROR_LOG(HLE, "Cannot open '%s'; errno=%d(%s)", dev_name, errno, strerror(errno)); + return -1; + } + + struct v4l2_capability cap; + memset(&cap, 0, sizeof(cap)); + if (ioctl(v4l_fd, VIDIOC_QUERYCAP, &cap) == -1) { + ERROR_LOG(HLE, "VIDIOC_QUERYCAP"); + return -1; + } + if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) { + ERROR_LOG(HLE, "V4L2_CAP_VIDEO_CAPTURE"); + return -1; + } + if (!(cap.capabilities & V4L2_CAP_STREAMING)) { + ERROR_LOG(HLE, "V4L2_CAP_STREAMING"); + return -1; + } + + struct v4l2_format fmt; + memset(&fmt, 0, sizeof(fmt)); + fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + fmt.fmt.pix.pixelformat = 0; + + // select a pixel format + struct v4l2_fmtdesc desc; + memset(&desc, 0, sizeof(desc)); + desc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + while (ioctl(v4l_fd, VIDIOC_ENUM_FMT, &desc) == 0) { + desc.index++; + INFO_LOG(HLE, "V4L2: pixel format supported: %s", desc.description); + if (fmt.fmt.pix.pixelformat != 0) { + continue; + } else if (desc.pixelformat == V4L2_PIX_FMT_YUYV + || desc.pixelformat == V4L2_PIX_FMT_MJPEG) { + INFO_LOG(HLE, "V4L2: %s selected", desc.description); + fmt.fmt.pix.pixelformat = desc.pixelformat; + v4l_format = desc.pixelformat; + } + } + if (fmt.fmt.pix.pixelformat == 0) { + ERROR_LOG(HLE, "V4L2: No supported format found"); + return -1; + } + + // select a frame size + fmt.fmt.pix.width = 0; + fmt.fmt.pix.height = 0; + struct v4l2_frmsizeenum frmsize; + memset(&frmsize, 0, sizeof(frmsize)); + frmsize.pixel_format = fmt.fmt.pix.pixelformat; + while (ioctl(v4l_fd, VIDIOC_ENUM_FRAMESIZES, &frmsize) == 0) { + frmsize.index++; + if (frmsize.type == V4L2_FRMSIZE_TYPE_DISCRETE) { + INFO_LOG(HLE, "V4L2: frame size supported: %dx%d", frmsize.discrete.width, frmsize.discrete.height); + if (fmt.fmt.pix.width != 0 && fmt.fmt.pix.height != 0) { + continue; + } else if (frmsize.discrete.width >= ideal_width && frmsize.discrete.height >= ideal_height) { + fmt.fmt.pix.width = frmsize.discrete.width; + fmt.fmt.pix.height = frmsize.discrete.height; + } + } + } + + if (fmt.fmt.pix.width == 0 && fmt.fmt.pix.height == 0) { + fmt.fmt.pix.width = ideal_width; + fmt.fmt.pix.height = ideal_height; + } + INFO_LOG(HLE, "V4L2: asking for %dx%d", fmt.fmt.pix.width, fmt.fmt.pix.height); + if (ioctl(v4l_fd, VIDIOC_S_FMT, &fmt) == -1) { + ERROR_LOG(HLE, "VIDIOC_S_FMT"); + return -1; + } + v4l_hw_width = fmt.fmt.pix.width; + v4l_hw_height = fmt.fmt.pix.height; + INFO_LOG(HLE, "V4L2: will receive %dx%d", v4l_hw_width, v4l_hw_height); + v4l_height_fixed_aspect = v4l_hw_width * ideal_height / ideal_width; + INFO_LOG(HLE, "V4L2: will use %dx%d", v4l_hw_width, v4l_height_fixed_aspect); + + // create a converter context + if (v4l_format == V4L2_PIX_FMT_YUYV) { + sws_context = sws_getContext( + v4l_hw_width, v4l_height_fixed_aspect, AV_PIX_FMT_YUYV422, + ideal_width, ideal_height, AV_PIX_FMT_RGB24, + SWS_BICUBIC, NULL, NULL, NULL); + } else if (v4l_format == V4L2_PIX_FMT_MJPEG) { + sws_context = sws_getContext( + v4l_hw_width, v4l_height_fixed_aspect, AV_PIX_FMT_RGB24, + ideal_width, ideal_height, AV_PIX_FMT_RGB24, + SWS_BICUBIC, NULL, NULL, NULL); + } + + + struct v4l2_requestbuffers req; + memset(&req, 0, sizeof(req)); + req.count = 1; + req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + req.memory = V4L2_MEMORY_MMAP; + if (ioctl(v4l_fd, VIDIOC_REQBUFS, &req) == -1) { + ERROR_LOG(HLE, "VIDIOC_REQBUFS"); + return -1; + } + + struct v4l2_buffer buf; + memset(&buf, 0, sizeof(buf)); + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = 0; + if (ioctl(v4l_fd, VIDIOC_QUERYBUF, &buf) == -1) { + ERROR_LOG(HLE, "VIDIOC_QUERYBUF"); + return -1; + } + + v4l_length = buf.length; + v4l_buffer = mmap(NULL, + v4l_length, + PROT_READ | PROT_WRITE, + MAP_SHARED, + v4l_fd, buf.m.offset); + if (v4l_buffer == MAP_FAILED) { + ERROR_LOG(HLE, "MAP_FAILED"); + return -1; + } + + memset(&buf, 0, sizeof(buf)); + enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.type = type; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = 0; + if (ioctl(v4l_fd, VIDIOC_QBUF, &buf) == -1) { + ERROR_LOG(HLE, "VIDIOC_QBUF"); + return -1; + } + + if (ioctl(v4l_fd, VIDIOC_STREAMON, &type) == -1) { + ERROR_LOG(HLE, "VIDIOC_STREAMON"); + return -1; + } + + pthread_create(&v4l_thread, NULL, v4l_loop, NULL); + + return 0; +} + +int __v4l_stopCapture() { + enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + if (v4l_fd < 0) { + goto exit; + } + + if (ioctl(v4l_fd, VIDIOC_STREAMOFF, &type) == -1) { + ERROR_LOG(HLE, "VIDIOC_STREAMOFF"); + goto exit; + } + + if (munmap(v4l_buffer, v4l_length) == -1) { + ERROR_LOG(HLE, "munmap"); + goto exit; + } + + if (close(v4l_fd) == -1) { + ERROR_LOG(HLE, "close"); + goto exit; + } + + v4l_fd = -1; + //pthread_join(v4l_thread, NULL); + +exit: + v4l_fd = -1; + return 0; +} + +#endif // PPSSPP_PLATFORM(LINUX) && !PPSSPP_PLATFORM(ANDROID) diff --git a/Core/HW/Camera.h b/Core/HW/Camera.h new file mode 100644 index 0000000000..fb082c10cd --- /dev/null +++ b/Core/HW/Camera.h @@ -0,0 +1,66 @@ +// Copyright (c) 2020- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#pragma once + +#include "ppsspp_config.h" +#include "Core/Config.h" +#include "Log.h" + +#if PPSSPP_PLATFORM(LINUX) && !PPSSPP_PLATFORM(ANDROID) +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "thread/threadutil.h" +#include "Core/HLE/sceUsbCam.h" + +#include +#include "ext/jpge/jpgd.h" +#include "ext/jpge/jpge.h" + +extern "C" { +#include "libswscale/swscale.h" +#include "libavutil/imgutils.h" +} + + static int v4l_fd = -1; + static uint32_t v4l_format; + static int v4l_hw_width; + static int v4l_hw_height; + static int v4l_height_fixed_aspect; + static int v4l_ideal_width; + static int v4l_ideal_height; + + static pthread_t v4l_thread; + static void *v4l_buffer; + static int v4l_length; + static struct SwsContext *sws_context; + + std::vector __v4l_getDeviceList(); + int __v4l_startCapture(int width, int height); + int __v4l_stopCapture(); +#endif diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index 71c468467e..90130177a2 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -58,6 +58,7 @@ #include "Core/Reporting.h" #include "Core/TextureReplacer.h" #include "Core/WebServer.h" +#include "Core/HLE/sceUsbCam.h" #include "GPU/Common/PostShader.h" #include "android/jni/TestRunner.h" #include "GPU/GPUInterface.h" @@ -68,7 +69,6 @@ #include "Windows/MainWindow.h" #include #include "Windows/W32Util/ShellUtil.h" -#include "Windows/CaptureDevice.h" #endif GameSettingsScreen::GameSettingsScreen(std::string gamePath, std::string gameID, bool editThenRestore) @@ -263,14 +263,14 @@ void GameSettingsScreen::CreateViews() { softwareGPU->SetEnabled(false); } -#if defined(_WIN32) && !PPSSPP_PLATFORM(UWP) - if (winCamera && winCamera->getDeviceList().size() >= 1) { +#if (defined(_WIN32) && !PPSSPP_PLATFORM(UWP)) || PPSSPP_PLATFORM(LINUX) + std::vector cameraList = Camera::getDeviceList(); + if (cameraList.size() >= 1) { graphicsSettings->Add(new ItemHeader(gr->T("Camera"))); - PopupMultiChoiceDynamic *cameraChoice = graphicsSettings->Add(new PopupMultiChoiceDynamic(&g_Config.sWinCameraDevice, gr->T("Camera Device"), winCamera->getDeviceList(), nullptr, screenManager())); + PopupMultiChoiceDynamic *cameraChoice = graphicsSettings->Add(new PopupMultiChoiceDynamic(&g_Config.sCameraDevice, gr->T("Camera Device"), cameraList, nullptr, screenManager())); } #endif - graphicsSettings->Add(new ItemHeader(gr->T("Frame Rate Control"))); static const char *frameSkip[] = {"Off", "1", "2", "3", "4", "5", "6", "7", "8"}; graphicsSettings->Add(new PopupMultiChoice(&g_Config.iFrameSkip, gr->T("Frame Skipping"), frameSkip, 0, ARRAY_SIZE(frameSkip), gr->GetName(), screenManager())); diff --git a/Windows/CaptureDevice.cpp b/Windows/CaptureDevice.cpp index ef0907f19e..429f4270ee 100644 --- a/Windows/CaptureDevice.cpp +++ b/Windows/CaptureDevice.cpp @@ -283,7 +283,7 @@ void ReaderCallback::imgConvert( void ReaderCallback::imgInvert(unsigned char *dst, unsigned char *src, const int &srcW, const int &srcH, const GUID &srcFormat, const int &srcStride) { AVPixelFormat srcAvFormat = getAVVideoFormatbyMFVideoFormat(srcFormat); int dstLineSizes[4] = { 0, 0, 0, 0 }; - + av_image_fill_linesizes(dstLineSizes, srcAvFormat, srcW); if(srcFormat == MFVideoFormat_RGB32) @@ -453,7 +453,7 @@ bool WindowsCaptureDevice::start() { switch (state) { case CAPTUREDEVIDE_STATE::STOPPED: for (auto &name : deviceList) { - if (name == g_Config.sWinCameraDevice) { + if (name == g_Config.sCameraDevice) { selection = count; break; } @@ -510,7 +510,7 @@ bool WindowsCaptureDevice::start() { ); if (SUCCEEDED(hr)) hr = setDeviceParam(pType);*/ // Don't support on Win7 - + // Request the first frame, in asnyc mode, OnReadSample will be called when ReadSample completed. if (SUCCEEDED(hr)) { hr = m_pReader->ReadSample( diff --git a/android/src/org/ppsspp/ppsspp/NativeActivity.java b/android/src/org/ppsspp/ppsspp/NativeActivity.java index 5676afee23..251a0f5ddc 100644 --- a/android/src/org/ppsspp/ppsspp/NativeActivity.java +++ b/android/src/org/ppsspp/ppsspp/NativeActivity.java @@ -1409,12 +1409,6 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C // Only keep the screen bright ingame. window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } - } else if (command.equals("event")) { - if (params.equals("exitgame")) { - if (mCameraHelper != null) { - mCameraHelper.stopCamera(); - } - } } return false; }