From 9e54cebcc21a5c0630bbcd9d507e5ba4fb6259ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 29 Sep 2026 12:29:04 -0600 Subject: [PATCH] Camera: Remember the JPEG quality that fit the last frame Start from the quality that fit the previous frame instead of the top, so most frames encode once. Step down while a frame is too big, and step back up when one comes out under half the limit. Windows and the recompression fallback share the logic. Co-Authored-By: Claude Opus 5.5 (1M context) --- Core/HLE/sceUsbCam.cpp | 45 ++++++++++++++++++++++++++++----------- Core/HLE/sceUsbCam.h | 4 ++++ Windows/CaptureDevice.cpp | 25 +++++++++------------- 3 files changed, 46 insertions(+), 28 deletions(-) diff --git a/Core/HLE/sceUsbCam.cpp b/Core/HLE/sceUsbCam.cpp index 87f91ddbc0..4ba4a09326 100644 --- a/Core/HLE/sceUsbCam.cpp +++ b/Core/HLE/sceUsbCam.cpp @@ -16,6 +16,7 @@ // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. #include +#include #include #include @@ -436,28 +437,46 @@ int Camera::getMaxFrameSize() { return framesize; } -// Re-encodes a frame at lower quality until it fits maxSize, like the PSP camera compresses to the -// game's framesize. Most platforms' capture code encodes at a fixed quality, so this is the common fallback. +// The JPEG quality that fit the last frame. Frames of one scene are similar in size, so it's usually +// right first time. Only the capture thread uses it, but atomic in case a platform has several. +static std::atomic g_jpegQuality{ 80 }; + +int Camera::encodeToFit(int maxSize, const std::function &encode) { + int quality = g_jpegQuality; + int size = encode(quality); + while ((size < 0 || size > maxSize) && quality > 10) { + quality = std::max(10, quality - 10); + size = encode(quality); + } + // Way under the limit: try a better quality next time. + if (size >= 0 && size < maxSize / 2 && quality < 90) { + quality += 10; + } + g_jpegQuality = quality; + return size; +} + +// Re-encodes a frame until it fits maxSize, like the PSP camera compresses to the game's framesize. +// Most platforms' capture code encodes at a fixed quality, so this is the common fallback. static bool RecompressToFit(const unsigned char *image, long long length, int maxSize, std::vector *out) { int width = 0, height = 0, comps = 0; unsigned char *rgb = jpgd::decompress_jpeg_image_from_memory(image, (int)length, &width, &height, &comps, 3); if (!rgb) { return false; } - bool fits = false; out->resize(width * height * 3 + 1024); - jpge::params params; - for (int quality = 70; quality >= 10; quality -= 15) { - int size = (int)out->size(); + int size = Camera::encodeToFit(maxSize, [&](int quality) { + jpge::params params; params.m_quality = quality; - if (jpge::compress_image_to_jpeg_file_in_memory(out->data(), size, width, height, 3, rgb, params) && size <= maxSize) { - out->resize(size); - fits = true; - break; - } - } + int outSize = (int)out->size(); + return jpge::compress_image_to_jpeg_file_in_memory(out->data(), outSize, width, height, 3, rgb, params) ? outSize : -1; + }); free(rgb); - return fits; + if (size < 0 || size > maxSize) { + return false; + } + out->resize(size); + return true; } void Camera::pushCameraImage(long long length, unsigned char* image) { diff --git a/Core/HLE/sceUsbCam.h b/Core/HLE/sceUsbCam.h index d356dbc8a4..a5db932bc6 100644 --- a/Core/HLE/sceUsbCam.h +++ b/Core/HLE/sceUsbCam.h @@ -18,6 +18,7 @@ #pragma once #include +#include #include #include #include "Core/HLE/FunctionWrappers.h" @@ -124,4 +125,7 @@ namespace Camera { // The largest JPEG frame the game accepts (framesize in the video setup). The PSP camera compresses // to fit it, so capture code should too. int getMaxFrameSize(); + // Calls encode(quality), which returns the JPEG size or -1, starting from the quality that fit the + // previous frame and going down until the result fits maxSize. Returns the final size (or -1). + int encodeToFit(int maxSize, const std::function &encode); } diff --git a/Windows/CaptureDevice.cpp b/Windows/CaptureDevice.cpp index c2b2d069a0..64f73bdbd1 100644 --- a/Windows/CaptureDevice.cpp +++ b/Windows/CaptureDevice.cpp @@ -247,22 +247,17 @@ HRESULT ReaderCallback::OnReadSample( } } - // Compress image to jpeg from RGB24. Like the PSP camera, lower the quality until the frame - // fits the size the game asked for: Go!Edit stores frames in fixed 15KB slots. - const int maxFrameSize = Camera::getMaxFrameSize(); - jpge::params params; - for (int quality = 85; ; quality -= 10) { - imgJpegSize = device->imgJpegSize; + // Compress image to jpeg from RGB24. Like the PSP camera, fit the size the game asked for: + // Go!Edit stores frames in fixed 15KB slots. + imgJpegSize = Camera::encodeToFit(Camera::getMaxFrameSize(), [&](int quality) { + jpge::params params; params.m_quality = quality; - jpge::compress_image_to_jpeg_file_in_memory( - device->imageJpeg, imgJpegSize, - dstW, - dstH, - 3, - device->imageRGB, params); - if (imgJpegSize <= maxFrameSize || quality <= 15) { - break; - } + int size = device->imgJpegSize; + return jpge::compress_image_to_jpeg_file_in_memory( + device->imageJpeg, size, dstW, dstH, 3, device->imageRGB, params) ? size : -1; + }); + if (imgJpegSize < 0) { + imgJpegSize = 0; } } #endif