mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-01 14:58:14 +00:00
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) <[email protected]>
This commit is contained in:
1 parent
07556d064e
commit
9e54cebcc2
3 files changed
+46
-28
No files matched your search
+32
-13
@@ -16,6 +16,7 @@
|
|||||||
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <atomic>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -436,28 +437,46 @@ int Camera::getMaxFrameSize() {
|
|||||||
return framesize;
|
return framesize;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Re-encodes a frame at lower quality until it fits maxSize, like the PSP camera compresses to the
|
// The JPEG quality that fit the last frame. Frames of one scene are similar in size, so it's usually
|
||||||
// game's framesize. Most platforms' capture code encodes at a fixed quality, so this is the common fallback.
|
// right first time. Only the capture thread uses it, but atomic in case a platform has several.
|
||||||
|
static std::atomic<int> g_jpegQuality{ 80 };
|
||||||
|
|
||||||
|
int Camera::encodeToFit(int maxSize, const std::function<int(int quality)> &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<uint8_t> *out) {
|
static bool RecompressToFit(const unsigned char *image, long long length, int maxSize, std::vector<uint8_t> *out) {
|
||||||
int width = 0, height = 0, comps = 0;
|
int width = 0, height = 0, comps = 0;
|
||||||
unsigned char *rgb = jpgd::decompress_jpeg_image_from_memory(image, (int)length, &width, &height, &comps, 3);
|
unsigned char *rgb = jpgd::decompress_jpeg_image_from_memory(image, (int)length, &width, &height, &comps, 3);
|
||||||
if (!rgb) {
|
if (!rgb) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
bool fits = false;
|
|
||||||
out->resize(width * height * 3 + 1024);
|
out->resize(width * height * 3 + 1024);
|
||||||
jpge::params params;
|
int size = Camera::encodeToFit(maxSize, [&](int quality) {
|
||||||
for (int quality = 70; quality >= 10; quality -= 15) {
|
jpge::params params;
|
||||||
int size = (int)out->size();
|
|
||||||
params.m_quality = quality;
|
params.m_quality = quality;
|
||||||
if (jpge::compress_image_to_jpeg_file_in_memory(out->data(), size, width, height, 3, rgb, params) && size <= maxSize) {
|
int outSize = (int)out->size();
|
||||||
out->resize(size);
|
return jpge::compress_image_to_jpeg_file_in_memory(out->data(), outSize, width, height, 3, rgb, params) ? outSize : -1;
|
||||||
fits = true;
|
});
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
free(rgb);
|
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) {
|
void Camera::pushCameraImage(long long length, unsigned char* image) {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <functional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "Core/HLE/FunctionWrappers.h"
|
#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
|
// The largest JPEG frame the game accepts (framesize in the video setup). The PSP camera compresses
|
||||||
// to fit it, so capture code should too.
|
// to fit it, so capture code should too.
|
||||||
int getMaxFrameSize();
|
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<int(int quality)> &encode);
|
||||||
}
|
}
|
||||||
+10
-15
@@ -247,22 +247,17 @@ HRESULT ReaderCallback::OnReadSample(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compress image to jpeg from RGB24. Like the PSP camera, lower the quality until the frame
|
// Compress image to jpeg from RGB24. Like the PSP camera, fit the size the game asked for:
|
||||||
// fits the size the game asked for: Go!Edit stores frames in fixed 15KB slots.
|
// Go!Edit stores frames in fixed 15KB slots.
|
||||||
const int maxFrameSize = Camera::getMaxFrameSize();
|
imgJpegSize = Camera::encodeToFit(Camera::getMaxFrameSize(), [&](int quality) {
|
||||||
jpge::params params;
|
jpge::params params;
|
||||||
for (int quality = 85; ; quality -= 10) {
|
|
||||||
imgJpegSize = device->imgJpegSize;
|
|
||||||
params.m_quality = quality;
|
params.m_quality = quality;
|
||||||
jpge::compress_image_to_jpeg_file_in_memory(
|
int size = device->imgJpegSize;
|
||||||
device->imageJpeg, imgJpegSize,
|
return jpge::compress_image_to_jpeg_file_in_memory(
|
||||||
dstW,
|
device->imageJpeg, size, dstW, dstH, 3, device->imageRGB, params) ? size : -1;
|
||||||
dstH,
|
});
|
||||||
3,
|
if (imgJpegSize < 0) {
|
||||||
device->imageRGB, params);
|
imgJpegSize = 0;
|
||||||
if (imgJpegSize <= maxFrameSize || quality <= 15) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
Reference in new issue
Block a user