Merge pull request #4416 from muhzain05/fix/videodec2-picture-owner

fix(videodec2): answer picture info from the decoder that last wrote the buffer
This commit is contained in:
Dean 2026-10-13 07:24:18 +13:00 committed by GitHub
commit 6f47d9117f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 189 additions and 9 deletions

View File

@ -147,6 +147,7 @@ struct Decoder {
std::mutex lock;
std::map<std::uint64_t, std::shared_ptr<Decoder>> decoders;
std::map<const void*, std::uint64_t> pictureOwners;
std::uint64_t nextDecoder = 1;
std::uint64_t nextQueue = 1;
@ -266,7 +267,7 @@ bool WriteNv12(Decoder& decoder, const AVFrame& frame, std::uint8_t* target, std
return sws_scale(decoder.scaler, frame.data, frame.linesize, 0, frame.height, planes, strides) == frame.height;
}
void EmitDecoded(Decoder& decoder, FrameBuffer* frame, OutputInfo* output) {
void EmitDecoded(std::uint64_t handle, Decoder& decoder, FrameBuffer* frame, OutputInfo* output) {
output->isValid = false;
output->isErrorFrame = false;
output->pictureCount = 0;
@ -302,6 +303,8 @@ void EmitDecoded(Decoder& decoder, FrameBuffer* frame, OutputInfo* output) {
output->framePitchInBytes = pitch;
}
decoder.pictures[frame->frameBuffer] = timestamps;
std::lock_guard guard(lock);
if (decoders.contains(handle)) pictureOwners[frame->frameBuffer] = handle;
}
}
@ -358,6 +361,7 @@ int APS5_VABI sceVideodec2CreateDecoder_nid_postfix(const DecoderConfigInfo* con
int APS5_VABI sceVideodec2DeleteDecoder_nid_postfix(std::uint64_t handle) {
std::lock_guard guard(lock);
if (decoders.erase(handle) == 0) throw std::runtime_error("Videodec2: invalid decoder handle");
std::erase_if(pictureOwners, [handle](const auto& owner) { return owner.second == handle; });
return 0;
}
@ -383,7 +387,7 @@ int APS5_VABI sceVideodec2Decode_nid_postfix(std::uint64_t handle, const InputDa
packet->pts = decoder.nextKey;
decoder.inputs.emplace(decoder.nextKey++, picture);
Send(decoder, packet);
EmitDecoded(decoder, frame, output);
EmitDecoded(handle, decoder, frame, output);
return 0;
}
@ -397,7 +401,7 @@ int APS5_VABI sceVideodec2Flush_nid_postfix(std::uint64_t handle, FrameBuffer* f
Send(decoder, nullptr);
decoder.flushing = true;
}
EmitDecoded(decoder, frame, output);
EmitDecoded(handle, decoder, frame, output);
return 0;
}
@ -407,6 +411,10 @@ int APS5_VABI sceVideodec2Reset_nid_postfix(std::uint64_t handle) {
auto& decoder = *found;
std::lock_guard guard(decoder.mutex);
decoder.pictures.clear();
{
std::lock_guard ownersGuard(lock);
std::erase_if(pictureOwners, [handle](const auto& owner) { return owner.second == handle; });
}
avcodec_flush_buffers(decoder.context);
decoder.ready.clear();
decoder.inputs.clear();
@ -417,15 +425,18 @@ int APS5_VABI sceVideodec2Reset_nid_postfix(std::uint64_t handle) {
int APS5_VABI sceVideodec2GetPictureInfo_nid_postfix(const OutputInfo* output, AvcPictureInfo* first, void* second) {
(void)second;
if (!output || !first) throw std::runtime_error("Videodec2: invalid argument pointer");
std::vector<std::shared_ptr<Decoder>> all;
{
const auto owner = [output]() -> std::uint64_t {
std::lock_guard guard(lock);
for (const auto& [handle, decoder] : decoders) all.push_back(decoder);
}
for (const auto& decoder : all) {
const auto found = pictureOwners.find(output->frameBuffer);
return found != pictureOwners.end() ? found->second : 0;
};
for (auto handle = owner(); handle != 0; handle = owner()) {
const auto decoder = FindDecoder(handle);
if (!decoder) continue;
std::lock_guard guard(decoder->mutex);
if (owner() != handle) continue;
const auto found = decoder->pictures.find(output->frameBuffer);
if (found == decoder->pictures.end()) continue;
if (found == decoder->pictures.end()) throw std::runtime_error("Videodec2: owned picture is missing");
if (decoder->codec != CodecAvc) throw std::runtime_error("Videodec2: picture info is only implemented for H.264");
const auto& picture = found->second;
first->isValid = true;

View File

@ -1,9 +1,11 @@
#include <algorithm>
#include <array>
#include <atomic>
#include <cstdint>
#include <cstdio>
#include <stdexcept>
#include <string>
#include <thread>
#include <vector>
#include "prx/libc/include/General.hpp"
#include "H264Fixture.hpp"
@ -444,6 +446,171 @@ void testDecode(bool lengthPrefixed) {
checkThrows([&] { sceVideodec2DeleteDecoder_nid_postfix(handle); });
}
std::uint64_t createDecoder(std::uint32_t codec, std::uint32_t profile) {
const DecoderConfigInfo config{sizeof(DecoderConfigInfo), 1, codec, profile, 0, static_cast<std::int32_t>(Width), static_cast<std::int32_t>(Height), 4, 1};
DecoderMemoryInfo memory{sizeof(DecoderMemoryInfo)};
check(sceVideodec2QueryDecoderMemoryInfo_nid_postfix(&config, &memory) == 0, "memory query failed");
std::uint64_t handle = 0;
check(sceVideodec2CreateDecoder_nid_postfix(&config, &memory, &handle) == 0, "decoder creation failed");
return handle;
}
AvcPictureInfo query(std::vector<std::uint8_t>& buffer) {
OutputInfo output{sizeof(OutputInfo)};
output.frameBuffer = buffer.data();
AvcPictureInfo info{sizeof(AvcPictureInfo)};
check(sceVideodec2GetAvcPictureInfo_nid_postfix(&output, &info, nullptr) == 0, "picture info query failed");
return info;
}
bool owned(std::vector<std::uint8_t>& buffer, std::uint64_t pts) {
const auto info = query(buffer);
return info.isValid && info.ptsData == pts;
}
std::uint64_t publishAvc(std::uint64_t handle, std::uint64_t base, std::vector<std::uint8_t>& buffer) {
const auto units = accessUnits(false);
std::size_t pictures = 0;
const auto take = [&](const OutputInfo& output) {
if (!output.isValid) return false;
check(owned(buffer, base + DisplayOrderUnits[pictures]), "picture info does not follow the latest publication");
++pictures;
return true;
};
for (std::size_t unit = 0; unit < units.size(); ++unit) {
const InputData input{sizeof(InputData), units[unit].data(), units[unit].size(), base + unit, unit, base + unit};
FrameBuffer frame{sizeof(FrameBuffer), buffer.data(), buffer.size(), false};
OutputInfo output{sizeof(OutputInfo)};
check(sceVideodec2Decode_nid_postfix(handle, &input, &frame, &output) == 0, "decode failed");
take(output);
}
for (;;) {
FrameBuffer frame{sizeof(FrameBuffer), buffer.data(), buffer.size(), false};
OutputInfo output{sizeof(OutputInfo)};
check(sceVideodec2Flush_nid_postfix(handle, &frame, &output) == 0, "flush failed");
if (!take(output)) break;
}
check(pictures == PictureHashes.size(), "missing pictures after flush");
return base + DisplayOrderUnits[pictures - 1];
}
void publishClip(std::uint64_t handle, const std::vector<std::vector<std::uint8_t>>& units, std::vector<std::uint8_t>& buffer) {
for (std::size_t unit = 0; unit < units.size(); ++unit) {
const InputData input{sizeof(InputData), units[unit].data(), units[unit].size(), unit, unit, 0};
FrameBuffer frame{sizeof(FrameBuffer), buffer.data(), buffer.size(), false};
OutputInfo output{sizeof(OutputInfo)};
check(sceVideodec2Decode_nid_postfix(handle, &input, &frame, &output) == 0, "decode failed");
}
for (;;) {
FrameBuffer frame{sizeof(FrameBuffer), buffer.data(), buffer.size(), false};
OutputInfo output{sizeof(OutputInfo)};
check(sceVideodec2Flush_nid_postfix(handle, &frame, &output) == 0, "flush failed");
if (!output.isValid) break;
}
}
std::uint64_t frameBufferBytes() {
const DecoderConfigInfo config{sizeof(DecoderConfigInfo), 1, 1, 100, 0, static_cast<std::int32_t>(Width), static_cast<std::int32_t>(Height), 4, 1};
DecoderMemoryInfo memory{sizeof(DecoderMemoryInfo)};
check(sceVideodec2QueryDecoderMemoryInfo_nid_postfix(&config, &memory) == 0, "memory query failed");
return memory.maxFrameBufferSize;
}
void testPictureOwnership() {
std::vector<std::uint8_t> shared(frameBufferBytes()), other(frameBufferBytes());
{
const auto older = createDecoder(1, 100);
const auto newer = createDecoder(1, 100);
publishAvc(older, 1000, shared);
const auto latest = publishAvc(newer, 5000, shared);
check(owned(shared, latest), "an older decoder answered for a buffer a newer decoder wrote last");
check(sceVideodec2Reset_nid_postfix(newer) == 0, "reset failed");
check(!query(shared).isValid, "a stale picture survived the reset of the decoder that last wrote the buffer");
check(sceVideodec2DeleteDecoder_nid_postfix(older) == 0 && sceVideodec2DeleteDecoder_nid_postfix(newer) == 0, "delete failed");
}
{
const auto older = createDecoder(1, 100);
const auto newer = createDecoder(1, 100);
publishAvc(older, 1000, shared);
publishAvc(newer, 5000, shared);
check(sceVideodec2DeleteDecoder_nid_postfix(newer) == 0, "delete failed");
check(!query(shared).isValid, "a stale picture survived the deletion of the decoder that last wrote the buffer");
check(sceVideodec2DeleteDecoder_nid_postfix(older) == 0, "delete failed");
}
{
const auto older = createDecoder(1, 100);
const auto newer = createDecoder(1, 100);
publishAvc(older, 1000, shared);
const auto latest = publishAvc(newer, 5000, shared);
check(sceVideodec2Reset_nid_postfix(older) == 0, "reset failed");
check(owned(shared, latest), "resetting a decoder that no longer owns the buffer dropped its picture");
check(sceVideodec2DeleteDecoder_nid_postfix(older) == 0, "delete failed");
check(owned(shared, latest), "deleting a decoder that no longer owns the buffer dropped its picture");
check(sceVideodec2DeleteDecoder_nid_postfix(newer) == 0, "delete failed");
check(!query(shared).isValid, "a deleted decoder still owns its picture");
}
{
const auto older = createDecoder(1, 100);
const auto newer = createDecoder(1, 100);
publishAvc(newer, 5000, shared);
const auto latest = publishAvc(older, 1000, shared);
const auto third = createDecoder(1, 100);
const auto separate = publishAvc(third, 9000, other);
check(owned(shared, latest) && owned(other, separate), "distinct buffers are not independent");
check(sceVideodec2Reset_nid_postfix(older) == 0, "reset failed");
check(!query(shared).isValid && owned(other, separate), "resetting one buffer's owner changed another buffer");
check(sceVideodec2DeleteDecoder_nid_postfix(older) == 0 && sceVideodec2DeleteDecoder_nid_postfix(newer) == 0 && sceVideodec2DeleteDecoder_nid_postfix(third) == 0, "delete failed");
}
for (const auto codec : {CodecHevc, CodecVp9}) {
const auto units = codec == CodecHevc ? hevcUnits(HevcStream.data(), HevcStream.size()) : ivfFrames();
const auto avc = createDecoder(1, 100);
const auto foreign = createDecoder(codec, codec == CodecHevc ? 1 : 0);
publishAvc(avc, 1000, shared);
publishClip(foreign, units, shared);
checkThrows([&] { query(shared); });
check(sceVideodec2DeleteDecoder_nid_postfix(avc) == 0, "delete failed");
const auto newer = createDecoder(1, 100);
const auto latest = publishAvc(newer, 5000, shared);
check(owned(shared, latest), "a decoder of another codec answered for an AVC picture");
check(sceVideodec2DeleteDecoder_nid_postfix(foreign) == 0 && sceVideodec2DeleteDecoder_nid_postfix(newer) == 0, "delete failed");
}
}
void testConcurrentOwnership() {
std::vector<std::uint8_t> shared(frameBufferBytes());
const auto keeper = createDecoder(1, 100);
const auto kept = publishAvc(keeper, 1000, shared);
std::atomic<bool> done = false;
std::string failure;
std::thread reader([&] {
try {
while (!done.load()) {
const auto info = query(shared);
check(!info.isValid || info.ptsData == kept || (info.ptsData >= 5000 && info.ptsData < 5000 + PictureHashes.size()), "a query returned metadata no decoder published");
}
} catch (const std::exception& error) {
failure = error.what();
}
});
try {
for (int round = 0; round < 8; ++round) {
const auto writer = createDecoder(1, 100);
publishAvc(writer, 5000, shared);
if (round % 2 == 0) check(sceVideodec2Reset_nid_postfix(writer) == 0, "reset failed");
check(sceVideodec2DeleteDecoder_nid_postfix(writer) == 0, "delete failed");
check(!query(shared).isValid, "a removed writer left its picture behind");
}
} catch (...) {
done = true;
reader.join();
throw;
}
done = true;
reader.join();
check(failure.empty(), failure);
check(sceVideodec2DeleteDecoder_nid_postfix(keeper) == 0, "delete failed");
}
}
int main() {
@ -475,6 +642,8 @@ int main() {
testClip(CodecHevc, 1, hevcLengthPrefixed, HevcHashes);
testClip(CodecVp9, 0, ivfFrames(), Vp9Hashes);
testTenBit();
testPictureOwnership();
testConcurrentOwnership();
std::puts("Videodec2 tests passed");
return 0;
} catch (const std::exception& error) {