fix(avplayer): make decode-ahead buffers optional

This commit is contained in:
hamb3r 2026-10-05 04:58:00 +03:00
parent c2fa7fd97f
commit cf10b5e609
3 changed files with 64 additions and 1 deletions

View File

@ -327,6 +327,7 @@ public:
return result;
}
}
addVideoDecodeAheadBuffers();
const auto start = startOffset;
startOffset = 0;
seek(start);
@ -605,7 +606,7 @@ private:
pitch = AlignUp(static_cast<std::uint32_t>(decoder.context->width), VideoPitchAlignment);
bufferHeight = AlignUp(static_cast<std::uint32_t>(decoder.context->height), VideoHeightAlignment);
decoder.bufferSize = pitch * bufferHeight * 3 / 2;
count = settings.videoBuffers + VideoDecodeAheadFrames;
count = settings.videoBuffers;
decoder.retained = settings.videoBuffers > 3 ? settings.videoBuffers - 3 : 0;
texture = true;
alignment = VideoBufferAlignment;
@ -625,6 +626,27 @@ private:
return SCE_OK;
}
void addVideoDecodeAheadBuffers() {
if (video.stream < 0) return;
const auto& memory = settings.memory;
std::vector<std::uint8_t*> extra;
extra.reserve(VideoDecodeAheadFrames);
for (std::uint32_t index = 0; index < VideoDecodeAheadFrames; ++index) {
auto* buffer = static_cast<std::uint8_t*>(memory.allocate_texture(
memory.object_ptr, VideoBufferAlignment, video.bufferSize));
if (!buffer) {
for (auto* allocated : extra) memory.deallocate_texture(memory.object_ptr, allocated);
return;
}
extra.push_back(buffer);
}
for (auto* buffer : extra) {
video.allocated.push_back(buffer);
video.free.push_back(buffer);
}
}
void releaseDecoders() {
const auto& memory = settings.memory;
for (auto* decoder : {&video, &audio}) {

View File

@ -13,6 +13,7 @@
#include <map>
#include <mutex>
#include <new>
#include <optional>
#include <stdexcept>
#include <string>
#include <thread>
@ -473,12 +474,17 @@ struct Allocations {
std::mutex mutex;
std::map<void*, Block> blocks;
int textures = 0;
std::optional<int> textureLimit;
};
Allocations allocations;
void* Allocate(std::uint32_t alignment, std::uint32_t size, bool texture) {
const auto aligned = static_cast<std::align_val_t>(std::max<std::uint32_t>(alignment, 16));
{
std::lock_guard lock(allocations.mutex);
if (texture && allocations.textureLimit && allocations.textures >= *allocations.textureLimit) return nullptr;
}
void* memory = ::operator new(size, aligned);
std::lock_guard lock(allocations.mutex);
allocations.blocks[memory] = {texture, aligned};
@ -494,6 +500,7 @@ void Release(void* memory, bool texture) {
if (found == allocations.blocks.end() || found->second.texture != texture) std::abort();
aligned = found->second.alignment;
allocations.blocks.erase(found);
if (texture) --allocations.textures;
}
::operator delete(memory, aligned);
}
@ -509,6 +516,16 @@ bool IsAllocation(const void* memory, bool texture) {
return found != allocations.blocks.end() && found->second.texture == texture;
}
void SetTextureLimit(std::optional<int> limit) {
std::lock_guard lock(allocations.mutex);
allocations.textureLimit = limit;
}
int TextureCount() {
std::lock_guard lock(allocations.mutex);
return allocations.textures;
}
struct Events {
std::mutex mutex;
std::vector<std::pair<std::int32_t, std::int32_t>> received;
@ -898,6 +915,28 @@ void TestWithoutAllocators() {
Check(sceAvPlayerClose(handle) == 0, "close failed");
}
void TestOptionalVideoBuffersRespectMemoryLimit() {
const auto run = [](int textureLimit, int expectedTextures) {
SetTextureLimit(textureLimit);
AvPlayerInitData init = InitData(nullptr);
init.num_output_video_framebuffers = 2;
auto* player = sceAvPlayerInit(&init);
Check(player != nullptr, "init with limited texture memory failed");
Check(sceAvPlayerAddSource(player, "/app0/avplayer.mp4") == 0, "add source with limited texture memory failed");
Check(WaitFor([&] { return TextureCount() == expectedTextures && sceAvPlayerIsActive(player); }), "unexpected optional video buffer count");
AvPlayerFrameInfoEx frame{};
Check(WaitFor([&] { return sceAvPlayerGetVideoDataEx(player, &frame) != 0; }), "no frame with limited texture memory");
CheckVideoPicture(frame);
Check(sceAvPlayerClose(player) == 0, "close with limited texture memory failed");
Check(TextureCount() == 0, "limited texture allocations were not released");
};
run(2, 2);
run(4, 2);
run(6, 6);
SetTextureLimit(std::nullopt);
}
void TestFileReplacementAutoStart() {
HostFile file;
AvPlayerInitData init = InitData(nullptr);
@ -951,6 +990,7 @@ int main() {
TestChangeStream();
TestExtendedExports();
TestWithoutAllocators();
TestOptionalVideoBuffersRespectMemoryLimit();
TestFileReplacementAutoStart();
TestHandedOutFramesStayIntact();
std::puts("AvPlayer tests passed");

View File

@ -24,6 +24,7 @@ Throughout the project, every function at every stage either **does exactly what
### Unknown function info
- [sceAvPlayerAddSource](../../core/libs/prx/libSceAvPlayer/src/Source.cpp) (libSceAvPlayer) - requests up to 4 additional decode-ahead video framebuffers from the memory replacement beyond `num_output_video_framebuffers` when memory is available; the console's internal buffering behaviour is unknown
- [sceVideoOutOpen](../../core/libs/prx/libSceVideoOut/src/Output.cpp) (libSceVideoOut) - the open param's first word is unknown (PPSA21564 passes 16; it is not the byte size, since the affinity mask is at offset 16); only 16 is accepted. Whether the param continues past offset 24 is also unknown
- [sceRtcParseDateTime](../../core/libs/prx/libSceRtc/Export.cpp) (libSceRtc) - accepted format assumed to be the ISO 8601 subset `YYYY-MM-DDTHH:MM:SS[.ffffff]` (T/t/space separator, optional fractional seconds) with the sibling RFC 3339 zone suffix (`Z` or `±HH:MM`); a string without a zone is taken as UTC, and anything outside this shape throws instead of returning a parse error, unverified against hardware
- [AMPR WaitOnAddress / WaitOnCounter](../../core/libs/prx/libkernel/Apr/src/Apr.cpp) (libSceAmpr) - compare encoding assumed to be the WAIT_REG_MEM one (0 always, 1 <, 2 <=, 3 ==, 4 !=, 5 >=, 6 >)