Files
AnyPS5/core/libs/tests/GuestVdecCore.cpp
T
Yunus Shaikh 935b2b1a26 fix(libSceVdecCore): refuse access units past the FFmpeg packet limit
sceVdecCoreSetDecodeInput passed the 64-bit auSize to av_new_packet as an
int and to memcpy as a size_t, so a size past 32 bits allocated its low
bits and copied the whole length. The size is now checked once against
the largest packet av_new_packet accepts, INT_MAX less
AV_INPUT_BUFFER_PADDING_SIZE and one, and that one length is used for the
allocation and the copy. A larger unit is refused with the
invalid-argument error before it is read.
2026-11-01 00:47:45 -07:00

526 lines
32 KiB
C++

#include "prx/libSceVdecCore/include/Api.hpp"
#include "prx/libSceVdecCore/include/Decoder.hpp"
#include "H264Fixture.hpp"
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <vector>
#ifdef _WIN32
#include <windows.h>
#else
#include <dlfcn.h>
#endif
namespace {
constexpr int InvalidArgument = static_cast<int>(0x80C00001);
constexpr int InvalidCodecType = static_cast<int>(0x80C00003);
constexpr int NoPicture = static_cast<int>(0x80C00010);
constexpr int DecodeRunning = static_cast<int>(0x80C00018);
constexpr std::int64_t PtsInvalid = -1;
constexpr std::uint64_t SlotSize = 0x10000;
constexpr std::uint32_t OutputPitch = 64;
constexpr std::array<std::uint64_t, 6> VisiblePictureHashes{
0x5871fdc85dfa274eull, 0x26ce4afb974cffc4ull, 0xd2e739633733d5baull,
0x54feb3d290e028fdull, 0xfa1342a4ba1819c8ull, 0x870125dd7f079a4cull,
};
constexpr std::uint32_t CodecAvc = 0;
constexpr std::uint32_t CodecHevc = 4;
constexpr std::uint32_t DecoderVdec2 = 2;
constexpr std::uint32_t DecoderVdec2Special2 = 8;
void Require(bool condition, const char* message) {
if (!condition) {
std::fprintf(stderr, "VdecCore: %s\n", message);
std::abort();
}
}
void* Resolve(const char* name) {
#ifdef _WIN32
const HMODULE module = GetModuleHandleA("libSceVdecCore.prx");
Require(module != nullptr, "library module");
return reinterpret_cast<void*>(GetProcAddress(module, name));
#else
return dlsym(RTLD_DEFAULT, name);
#endif
}
VdecCoreConfigInfo Config(std::int32_t width, std::int32_t height, std::int32_t horizontalAlign, std::int32_t dpb) {
VdecCoreConfigInfo config{};
config.decoderType = DecoderVdec2;
config.codecType = CodecAvc;
config.maxFrameWidth = width;
config.maxFrameHeight = height;
config.horizontalAlign = horizontalAlign;
config.maxDpbFrameCount = dpb;
return config;
}
void CheckExports() {
for (const char* name : {"sceVdecCoreQueryInstanceSize", "sceVdecCoreQueryFrameBufferInfo", "sceVdecCoreQueryComputeResourceInfo",
"sceVdecCoreCreateDecoder", "sceVdecCoreDeleteDecoder", "sceVdecCoreMapMemoryBlock"}) {
Require(Resolve(name) != nullptr, name);
}
}
void CheckInstanceSize() {
VdecCoreConfigInfo config = Config(0, 0, 0, 0);
std::uint64_t instance = 1, compute = 1, reserved = 1;
Require(sceVdecCoreQueryInstanceSize(nullptr, &instance, &compute, &reserved) == InvalidArgument, "instance size without a config");
config.codecType = 5;
Require(sceVdecCoreQueryInstanceSize(&config, &instance, &compute, &reserved) == InvalidCodecType, "instance size for codec type 5");
Require(instance == 1 && compute == 1 && reserved == 1, "a refused query leaves its outputs alone");
config.codecType = CodecHevc;
Require(sceVdecCoreQueryInstanceSize(&config, &instance, &compute, &reserved) == 0, "instance size");
Require(instance == 0x40000 && compute == 0x10000 && reserved == 0, "instance, compute and reserved sizes");
Require(sceVdecCoreQueryInstanceSize(&config, nullptr, nullptr, nullptr) == 0, "instance size with no outputs");
}
void RequireFrameBuffer(const VdecCoreConfigInfo& config, std::uint32_t size, std::uint32_t minCount, std::uint32_t align, std::uint32_t width, std::uint32_t height, const char* message) {
VdecCoreFrameBufferInfo info{};
Require(sceVdecCoreQueryFrameBufferInfo(&config, &info) == 0, message);
Require(info.frameBufferSize == size, message);
Require(info.frameBufferAlignment == 256, message);
Require(info.minFrameBufferCount == minCount, message);
Require(info.horizontalAlignment == align, message);
Require(info.frameWidth == width && info.frameHeight == height, message);
}
void CheckFrameBufferInfo() {
VdecCoreFrameBufferInfo info{};
VdecCoreConfigInfo config = Config(0, 0, 0, 0);
Require(sceVdecCoreQueryFrameBufferInfo(nullptr, &info) == InvalidArgument, "frame buffer info without a config");
Require(sceVdecCoreQueryFrameBufferInfo(&config, nullptr) == InvalidArgument, "frame buffer info without an output");
config.codecType = 5;
config.maxDpbFrameCount = 17;
Require(sceVdecCoreQueryFrameBufferInfo(&config, &info) == InvalidCodecType, "the codec type is checked before the DPB count");
config.codecType = CodecAvc;
Require(sceVdecCoreQueryFrameBufferInfo(&config, &info) == InvalidArgument, "DPB count 17");
config.maxDpbFrameCount = -2;
Require(sceVdecCoreQueryFrameBufferInfo(&config, &info) == InvalidArgument, "DPB count -2");
RequireFrameBuffer(Config(0, 0, 0, 0), 0x340000, 17, 256, 1920, 1080, "1920x1080 defaults");
RequireFrameBuffer(Config(64, 40, 256, 0), 0x10000, 17, 256, 64, 40, "64x40, DPB 0");
RequireFrameBuffer(Config(64, 40, 256, -1), 0x10000, 17, 256, 64, 40, "64x40, DPB -1");
RequireFrameBuffer(Config(64, 40, 256, 16), 0x10000, 17, 256, 64, 40, "64x40, DPB 16");
RequireFrameBuffer(Config(64, 40, 256, 4), 0x10000, 5, 256, 64, 40, "64x40, DPB 4");
RequireFrameBuffer(Config(3840, 2160, 0, 0), 0xbe0000, 17, 256, 3840, 2160, "3840x2160");
RequireFrameBuffer(Config(1280, 720, 100, 0), 0x160000, 17, 100, 1280, 720, "1280x720 with a horizontal alignment of 100");
}
void CheckComputeResourceInfo() {
Require(sceVdecCoreQueryComputeResourceInfo(nullptr) == InvalidArgument, "compute resource info without an output");
VdecCoreComputeResourceInfo info{7, 7, 7, 7};
Require(sceVdecCoreQueryComputeResourceInfo(&info) == 0, "compute resource info");
Require(info.memoryType == 0 && info.size == 0x10000 && info.alignment == 0x10000 && info.reserved == 0, "compute resource sizes");
}
void CheckCreateAndDelete() {
VdecCoreConfigInfo config = Config(0, 0, 0, 0);
VdecCoreMemoryInfo memory{};
void* decoder = &memory;
Require(sceVdecCoreCreateDecoder(nullptr, &memory, nullptr, &decoder) == InvalidArgument, "create without a config");
Require(sceVdecCoreCreateDecoder(&config, &memory, nullptr, nullptr) == InvalidArgument, "create without an output");
config.decoderType = DecoderVdec2Special2 + 1;
Require(sceVdecCoreCreateDecoder(&config, &memory, nullptr, &decoder) == InvalidArgument, "create with decoder type 9");
config.decoderType = DecoderVdec2;
config.codecType = 5;
Require(sceVdecCoreCreateDecoder(&config, &memory, nullptr, &decoder) == InvalidCodecType, "create with codec type 5");
Require(decoder == &memory, "a refused create leaves the output alone");
config.codecType = CodecAvc;
void* avc = nullptr;
Require(sceVdecCoreCreateDecoder(&config, &memory, nullptr, &avc) == 0 && avc != nullptr, "create an AVC decoder");
config.codecType = CodecHevc;
config.decoderType = DecoderVdec2Special2;
void* hevc = nullptr;
Require(sceVdecCoreCreateDecoder(&config, nullptr, nullptr, &hevc) == 0 && hevc != nullptr && hevc != avc, "create an HEVC decoder of type 8");
Require(sceVdecCoreDeleteDecoder(nullptr) == InvalidArgument, "delete without a decoder");
Require(sceVdecCoreDeleteDecoder(avc) == 0, "delete the AVC decoder");
Require(sceVdecCoreDeleteDecoder(hevc) == 0, "delete the HEVC decoder");
}
std::vector<void*> Slots(void* decoder) {
std::array<void*, 16> slots{};
const std::uint32_t count = VdecCoreFrameBufferSlots_nid_no_patch(decoder, slots.data(), static_cast<std::uint32_t>(slots.size()));
Require(count <= slots.size(), "slot count");
return std::vector<void*>(slots.begin(), slots.begin() + count);
}
void CheckMapMemoryBlock() {
Require(sceVdecCoreMapMemoryBlock(nullptr, nullptr, 0) == InvalidArgument, "map without a decoder");
const VdecCoreConfigInfo config = Config(64, 40, 256, 0);
VdecCoreFrameBufferInfo info{};
Require(sceVdecCoreQueryFrameBufferInfo(&config, &info) == 0 && info.frameBufferSize == 0x10000, "64x40 frame buffer size");
void* decoder = nullptr;
Require(sceVdecCoreCreateDecoder(&config, nullptr, nullptr, &decoder) == 0, "create the decoder to map into");
Require(Slots(decoder).empty(), "a new decoder has no frame buffer slots");
std::vector<std::uint8_t> memory(0x50000);
std::uint8_t* base = memory.data();
const std::array<VdecCoreMemoryBlock, 3> blocks{{
{base, 0x28000, 256, 0},
{base + 0x30000, 0x8000, 256, 0},
{nullptr, 0x20000, 256, 0},
}};
Require(sceVdecCoreMapMemoryBlock(decoder, blocks.data(), static_cast<std::uint32_t>(blocks.size())) == 0, "map three blocks");
const std::vector<void*> cut = Slots(decoder);
Require(cut.size() == 2, "a 2.5-slot block gives two slots; a short block and a null block give none");
Require(cut[0] == base && cut[1] == base + info.frameBufferSize, "slots start at the block and are one frame buffer apart");
Require(sceVdecCoreMapMemoryBlock(decoder, nullptr, 3) == 0, "map with no blocks");
Require(sceVdecCoreMapMemoryBlock(decoder, blocks.data(), 0) == 0, "map zero blocks");
Require(Slots(decoder).size() == 2, "mapping nothing adds no slots");
const VdecCoreMemoryBlock exact{base + 0x40000, 0x10000, 256, 0};
Require(sceVdecCoreMapMemoryBlock(decoder, &exact, 1) == 0, "map a block of exactly one frame buffer");
const std::vector<void*> grown = Slots(decoder);
Require(grown.size() == 3 && grown[2] == base + 0x40000, "an exact block adds one slot after the earlier ones");
Require(sceVdecCoreDeleteDecoder(decoder) == 0, "delete the mapped decoder");
}
void* CreateAvc(std::int32_t width) {
const VdecCoreConfigInfo config = Config(width, static_cast<std::int32_t>(H264Fixture::Height), 256, 0);
void* decoder = nullptr;
Require(sceVdecCoreCreateDecoder(&config, nullptr, nullptr, &decoder) == 0 && decoder != nullptr, "create an AVC decoder");
return decoder;
}
const std::vector<std::vector<std::uint8_t>>& Units() {
static const std::vector<std::vector<std::uint8_t>> units = H264Fixture::accessUnits(H264Fixture::Stream.data(), H264Fixture::Stream.size(), false);
Require(units.size() == H264Fixture::PictureHashes.size(), "the fixture's access units");
return units;
}
void Feed(void* decoder, std::size_t unit, std::int64_t pts, std::int64_t dts, std::uint64_t userData) {
const auto& data = Units()[unit];
const VdecCoreInputData input{data.data(), data.size(), pts, dts, userData, 0, 0};
std::array<std::uint32_t, 2> status{};
Require(sceVdecCoreSetDecodeInput(decoder, &input, nullptr, status.data()) == 0, "set an access unit as decode input");
Require(status[0] == 1 && status[1] == 1, "the input status of an accepted access unit");
}
void FeedAll(void* decoder, std::int64_t ptsBase, std::int64_t dtsBase, std::uint64_t userBase) {
for (std::size_t unit = 0; unit < Units().size(); ++unit) {
const auto offset = static_cast<std::int64_t>(unit);
Feed(decoder, unit, ptsBase == PtsInvalid ? PtsInvalid : ptsBase + offset, dtsBase == PtsInvalid ? PtsInvalid : dtsBase + offset, userBase + unit);
}
}
std::uint32_t Flush(void* decoder) {
std::uint32_t count = 0xffffffffu;
Require(sceVdecCoreFlushDecodeOutput(decoder, &count, nullptr) == 0, "flush the decode output");
return count;
}
VdecCoreOutputInfo Output(void* decoder, VdecCoreFrameBuffer* frameBuffer) {
VdecCoreOutputInfo output{};
Require(sceVdecCoreGetDecodeOutput(decoder, frameBuffer, 0, &output, nullptr) == 0, "get a decoded picture");
return output;
}
std::uint64_t VisibleHash(const std::uint8_t* buffer, std::uint32_t pitch, std::uint32_t frameHeight) {
std::uint64_t hash = 0xcbf29ce484222325ull;
const auto mix = [&](const std::uint8_t* row) {
for (std::uint32_t column = 0; column < H264Fixture::Width; ++column) hash = (hash ^ row[column]) * 0x100000001b3ull;
};
for (std::uint32_t row = 0; row < H264Fixture::Height; ++row) mix(buffer + static_cast<std::size_t>(row) * pitch);
const auto* chroma = buffer + static_cast<std::size_t>(pitch) * frameHeight;
for (std::uint32_t row = 0; row < H264Fixture::Height / 2; ++row) mix(chroma + static_cast<std::size_t>(row) * pitch);
return hash;
}
bool ExtendsLastRows(const std::uint8_t* buffer, std::uint32_t pitch, std::uint32_t frameHeight) {
const auto* chroma = buffer + static_cast<std::size_t>(pitch) * frameHeight;
const auto* lastLuma = buffer + static_cast<std::size_t>(pitch) * (H264Fixture::Height - 1);
const auto* lastChroma = chroma + static_cast<std::size_t>(pitch) * (H264Fixture::Height / 2 - 1);
for (std::uint32_t row = H264Fixture::Height; row < frameHeight; ++row) {
if (std::memcmp(buffer + static_cast<std::size_t>(row) * pitch, lastLuma, H264Fixture::Width) != 0) return false;
}
for (std::uint32_t row = H264Fixture::Height / 2; row < frameHeight / 2; ++row) {
if (std::memcmp(chroma + static_cast<std::size_t>(row) * pitch, lastChroma, H264Fixture::Width) != 0) return false;
}
return true;
}
const VdecCoreHwPictureInfo& HwInfo(const VdecCoreOutputInfo& output, std::uint64_t allocatedSize) {
return *reinterpret_cast<const VdecCoreHwPictureInfo*>(static_cast<const std::uint8_t*>(output.frameBuffer) + allocatedSize - sizeof(VdecCoreHwPictureInfo));
}
void RequirePicture(const VdecCoreOutputInfo& output, std::size_t picture, std::uint64_t allocatedSize, std::uint64_t pts, std::uint64_t userData, const char* message) {
const std::uint32_t height = H264Fixture::CodedHeight;
auto* buffer = static_cast<const std::uint8_t*>(output.frameBuffer);
Require(output.pictureCount == 1 && buffer != nullptr && output.frameFormat == 0 && output.reserved0 == 0, message);
Require(output.frameWidth == H264Fixture::Width && output.framePitch == OutputPitch && output.frameHeight == height && output.framePitchInBytes == OutputPitch, message);
Require(output.lumaBuffer == buffer && output.chromaBuffer == buffer + static_cast<std::size_t>(OutputPitch) * height, message);
const VdecCorePicture& first = output.pictures[0];
Require(first.flags == 0 && first.reserved0 == 0 && first.pictureInfo == nullptr && first.frameBuffer == buffer && first.frameBufferSize == OutputPitch * height * 3 / 2 && first.framePitch == OutputPitch && first.reserved1 == 0, message);
Require(output.pictures[1].frameBuffer == nullptr, message);
Require(first.pts == pts && first.userData == userData, message);
Require(output.aspectRatio == 1 && output.sarWidth == 1 && output.videoFormat == 5 && output.colourPrimaries == 1 && output.transferCharacteristics == 1 && output.videoFullRangeFlag == 0 && output.colourDescriptionPresentFlag == 1, message);
Require(VisibleHash(buffer, output.framePitch, output.frameHeight) == VisiblePictureHashes[picture], message);
Require(ExtendsLastRows(buffer, output.framePitch, output.frameHeight), message);
const VdecCoreHwPictureInfo& info = HwInfo(output, allocatedSize);
Require(info.codecType == 0 && info.userData == userData && info.pts == pts && info.dts == first.dts && info.valid == 1, message);
Require(info.width == H264Fixture::Width && info.height == height && info.cropLeft == 0 && info.cropRight == 0 && info.cropTop == 0 && info.cropBottom == (height - H264Fixture::Height) / 2, message);
Require(info.sarWidth == 1 && info.sarHeight == 1 && info.flags == 0x03, message);
}
void CheckAccessUnitLength() {
const std::uint64_t limit = VdecCoreMaxAccessUnitSize_nid_no_patch();
Require(limit > 0 && limit < 0x7fffffffull, "the largest access unit leaves room for the packet padding in an int");
Require(VdecCoreAccessUnitLength_nid_no_patch(0) < 0, "an empty access unit has no length");
Require(VdecCoreAccessUnitLength_nid_no_patch(1) == 1, "a one-byte access unit keeps its length");
Require(VdecCoreAccessUnitLength_nid_no_patch(limit) == static_cast<std::int32_t>(limit), "the largest access unit keeps its length");
Require(VdecCoreAccessUnitLength_nid_no_patch(limit + 1) < 0, "an access unit one past the packet limit has no length");
Require(VdecCoreAccessUnitLength_nid_no_patch(0x7fffffffull) < 0, "an access unit of the largest int has no length");
Require(VdecCoreAccessUnitLength_nid_no_patch((1ull << 32) + 16) < 0, "an access unit whose low 32 bits look small has no length");
Require(VdecCoreAccessUnitLength_nid_no_patch(~0ull) < 0, "the largest 64-bit size has no length");
}
void CheckDecodeArguments() {
void* decoder = CreateAvc(static_cast<std::int32_t>(H264Fixture::Width));
const auto& unit = Units()[0];
VdecCoreInputData input{unit.data(), unit.size(), 0, 0, 0, 0, 0};
std::array<std::uint32_t, 2> status{7, 7};
Require(sceVdecCoreSetDecodeInput(nullptr, &input, nullptr, status.data()) == InvalidArgument, "set input without a decoder");
Require(sceVdecCoreSetDecodeInput(decoder, nullptr, nullptr, status.data()) == InvalidArgument, "set input without input data");
Require(status[0] == 7 && status[1] == 7, "an input refused for a missing decoder or input leaves the status alone");
input.auData = nullptr;
Require(sceVdecCoreSetDecodeInput(decoder, &input, nullptr, status.data()) == InvalidArgument, "set input without access unit data");
Require(status[0] == 1 && status[1] == 1, "the status is written before the access unit is checked");
status = {7, 7};
input.auData = unit.data();
input.auSize = 0;
Require(sceVdecCoreSetDecodeInput(decoder, &input, nullptr, status.data()) == InvalidArgument, "set input with an empty access unit");
Require(status[0] == 1 && status[1] == 1, "the status is written before the access unit size is checked");
const std::uint64_t limit = VdecCoreMaxAccessUnitSize_nid_no_patch();
const std::array<std::uint64_t, 4> oversized{1ull << 31, limit + 1, (1ull << 32) + unit.size(), ~0ull};
for (const auto size : oversized) {
status = {7, 7};
input.auSize = size;
Require(sceVdecCoreSetDecodeInput(decoder, &input, nullptr, status.data()) == InvalidArgument, "set input with an access unit past the packet limit");
Require(status[0] == 1 && status[1] == 1, "the status is written before an oversized access unit is refused");
}
input.auSize = unit.size();
Require(sceVdecCoreSetDecodeInput(decoder, &input, nullptr, nullptr) == 0, "set input without a status");
std::uint32_t count = 7;
std::uint64_t pointers[2] = {7, 7};
VdecCoreOutputInfo output{};
VdecCoreFrameBuffer frameBuffer{};
Require(sceVdecCoreResetDecoder(nullptr, nullptr) == InvalidArgument, "reset without a decoder");
Require(sceVdecCoreSyncDecode(nullptr, &count) == InvalidArgument, "sync without a decoder");
Require(sceVdecCoreSyncDecodeWptr(nullptr, &count, &pointers[0], &pointers[1]) == InvalidArgument, "write-pointer sync without a decoder");
Require(sceVdecCoreTrySyncDecode(nullptr, &count) == InvalidArgument, "try-sync without a decoder");
Require(sceVdecCoreTrySyncDecodeWptr(nullptr, &count, &pointers[0], &pointers[1]) == InvalidArgument, "write-pointer try-sync without a decoder");
Require(sceVdecCoreGetDecodeOutput(nullptr, &frameBuffer, 0, &output, nullptr) == InvalidArgument, "get output without a decoder");
Require(sceVdecCoreGetDecodeOutput(decoder, &frameBuffer, 0, nullptr, nullptr) == InvalidArgument, "get output without output info");
Require(sceVdecCoreFlushDecodeOutput(nullptr, &count, nullptr) == InvalidArgument, "flush without a decoder");
Require(count == 7 && pointers[0] == 7 && pointers[1] == 7, "refused calls leave their outputs alone");
Require(sceVdecCoreSyncDecode(decoder, nullptr) == 0, "sync without a count");
Require(sceVdecCoreFlushDecodeOutput(decoder, nullptr, nullptr) == 0, "flush without a count");
std::uint32_t held = 7;
Require(sceVdecCoreTrySyncDecode(decoder, nullptr) == 0 && sceVdecCoreTrySyncDecode(decoder, &held) == 0 && held == 1, "try-sync after a flush counts the held picture");
Require(sceVdecCoreResetDecoder(decoder, nullptr) == 0, "reset");
Require(sceVdecCoreDeleteDecoder(decoder) == 0, "delete the argument decoder");
}
void CheckEmptyQueue() {
void* decoder = CreateAvc(100);
std::uint32_t count = 7;
Require(sceVdecCoreTrySyncDecode(decoder, &count) == 0 && count == 0, "try-sync on an idle decoder");
count = 7;
Require(sceVdecCoreSyncDecode(decoder, &count) == 0 && count == 0, "sync on an idle decoder");
VdecCoreOutputInfo output;
std::memset(&output, 0xab, sizeof(output));
Require(sceVdecCoreGetDecodeOutput(decoder, nullptr, 0, &output, nullptr) == NoPicture, "get output from an empty queue");
Require(output.frameWidth == 100 && output.framePitch == 128 && output.frameHeight == H264Fixture::Height, "an empty queue reports the maximum width, its 64-byte pitch and the maximum height");
Require(output.pictureCount == 0 && output.frameBuffer == nullptr && output.frameFormat == 0 && output.reserved0 == 0, "an empty queue reports no picture");
Require(output.pictures[0].frameBuffer == nullptr && output.pictures[1].frameBuffer == nullptr && output.lumaBuffer == nullptr && output.chromaBuffer == nullptr, "an empty queue reports no buffers");
Require(output.pictures[0].pts == 0xababababababababull && output.aspectRatio == 0xababababu && output.colourDescriptionPresentFlag == 0xababababu, "an empty queue leaves the other fields alone");
Require(sceVdecCoreDeleteDecoder(decoder) == 0, "delete the idle decoder");
}
void CheckDecodeIntoSlots() {
void* decoder = CreateAvc(static_cast<std::int32_t>(H264Fixture::Width));
std::vector<std::uint8_t> memory(SlotSize * 4);
const VdecCoreMemoryBlock block{memory.data(), memory.size(), 256, 0};
Require(sceVdecCoreMapMemoryBlock(decoder, &block, 1) == 0, "map four frame buffer slots");
const std::vector<void*> slots = Slots(decoder);
Require(slots.size() == 4, "four slots");
std::size_t pictures = 0;
std::vector<std::uint64_t> dts;
const auto take = [&](std::uint32_t available) {
for (std::uint32_t index = 0; index < available; ++index) {
const VdecCoreOutputInfo output = Output(decoder, nullptr);
Require(pictures < H264Fixture::PictureHashes.size(), "more pictures than access units");
Require(output.frameBuffer == slots[pictures % slots.size()], "pictures go to the mapped slots in turn");
const auto unit = H264Fixture::DisplayOrderUnits[pictures];
RequirePicture(output, pictures, SlotSize, 1000 + unit, 3000 + unit, "a picture decoded into a slot");
dts.push_back(output.pictures[0].dts);
++pictures;
}
};
for (std::size_t unit = 0; unit < Units().size(); ++unit) {
Feed(decoder, unit, 1000 + static_cast<std::int64_t>(unit), 2000 + static_cast<std::int64_t>(unit), 3000 + unit);
std::uint32_t tried = 0xffffffffu;
const int result = sceVdecCoreTrySyncDecode(decoder, &tried);
Require(result == 0 || (result == DecodeRunning && tried == 0), "try-sync during a decode");
std::uint32_t count = 0xffffffffu;
Require(sceVdecCoreSyncDecode(decoder, &count) == 0, "sync after an access unit");
take(count);
}
take(Flush(decoder));
Require(pictures == H264Fixture::PictureHashes.size(), "every access unit gives a picture");
for (const auto value : dts) Require(value >= 2000 && value < 2000 + Units().size(), "a picture's dts is one of the inputs'");
VdecCoreOutputInfo output{};
Require(sceVdecCoreGetDecodeOutput(decoder, nullptr, 0, &output, nullptr) == NoPicture, "no picture after the flushed ones");
Require(sceVdecCoreDeleteDecoder(decoder) == 0, "delete the slot decoder");
}
void CheckInvalidPts() {
void* decoder = CreateAvc(static_cast<std::int32_t>(H264Fixture::Width));
FeedAll(decoder, PtsInvalid, PtsInvalid, 5000);
Require(Flush(decoder) == H264Fixture::PictureHashes.size(), "a flush holds every picture");
for (std::size_t picture = 0; picture < H264Fixture::PictureHashes.size(); ++picture) {
const VdecCoreOutputInfo output = Output(decoder, nullptr);
RequirePicture(output, picture, SlotSize, ~std::uint64_t{0}, 5000 + H264Fixture::DisplayOrderUnits[picture], "a picture without a pts");
Require(output.pictures[0].dts == ~std::uint64_t{0}, "a picture without a dts takes the last input's invalid dts");
}
Require(sceVdecCoreDeleteDecoder(decoder) == 0, "delete the invalid pts decoder");
}
void CheckDestinations() {
void* decoder = CreateAvc(static_cast<std::int32_t>(H264Fixture::Width));
FeedAll(decoder, 1000, 2000, 3000);
Require(Flush(decoder) == H264Fixture::PictureHashes.size(), "a flush holds every picture for the fallback");
const VdecCoreOutputInfo fallback = Output(decoder, nullptr);
RequirePicture(fallback, 0, SlotSize, 1000, 3000, "a picture in the fallback buffer");
const VdecCoreOutputInfo again = Output(decoder, nullptr);
Require(again.frameBuffer == fallback.frameBuffer, "the fallback buffer is reused");
RequirePicture(again, 1, SlotSize, 1000 + H264Fixture::DisplayOrderUnits[1], 3000 + H264Fixture::DisplayOrderUnits[1], "a second picture in the fallback buffer");
std::vector<std::uint8_t> slotMemory(SlotSize);
const VdecCoreMemoryBlock block{slotMemory.data(), slotMemory.size(), 256, 0};
Require(sceVdecCoreMapMemoryBlock(decoder, &block, 1) == 0, "map one slot");
std::vector<std::uint8_t> caller(0x2000);
VdecCoreFrameBuffer frameBuffer{caller.data(), caller.size()};
const VdecCoreOutputInfo supplied = Output(decoder, &frameBuffer);
Require(supplied.frameBuffer == caller.data(), "a caller frame buffer comes before the slots");
RequirePicture(supplied, 2, caller.size(), 1000 + H264Fixture::DisplayOrderUnits[2], 3000 + H264Fixture::DisplayOrderUnits[2], "a picture in the caller's frame buffer");
VdecCoreFrameBuffer unaddressed{nullptr, caller.size()};
const VdecCoreOutputInfo slot = Output(decoder, &unaddressed);
Require(slot.frameBuffer == slotMemory.data(), "a frame buffer without an address leaves the picture to the slots");
RequirePicture(slot, 3, SlotSize, 1000 + H264Fixture::DisplayOrderUnits[3], 3000 + H264Fixture::DisplayOrderUnits[3], "a picture in a slot keeps the slot's size, not the caller's");
Require(sceVdecCoreDeleteDecoder(decoder) == 0, "delete the destination decoder");
}
void CheckOutputStaysInItsDestination() {
void* decoder = CreateAvc(static_cast<std::int32_t>(H264Fixture::Width));
FeedAll(decoder, 1000, 2000, 3000);
Require(Flush(decoder) == H264Fixture::PictureHashes.size(), "a flush holds every picture for the destination bounds");
constexpr std::uint8_t Guard = 0x5a;
std::vector<std::uint8_t> memory(SlotSize * 2, Guard);
const VdecCoreMemoryBlock block{memory.data(), SlotSize, 256, 0};
Require(sceVdecCoreMapMemoryBlock(decoder, &block, 1) == 0, "map one slot followed by guard bytes");
VdecCoreFrameBuffer larger{nullptr, SlotSize * 2};
const VdecCoreOutputInfo slot = Output(decoder, &larger);
Require(slot.frameBuffer == memory.data(), "a frame buffer with no address and a larger size goes to the slot");
RequirePicture(slot, 0, SlotSize, 1000, 3000, "a picture in a slot under a larger caller size");
Require(std::all_of(memory.begin() + static_cast<std::ptrdiff_t>(SlotSize), memory.end(), [](std::uint8_t byte) { return byte == Guard; }), "a picture in a slot under a larger caller size wrote past the slot");
const std::uint32_t pictureSize = OutputPitch * H264Fixture::CodedHeight * 3 / 2;
std::vector<std::uint8_t> tight(pictureSize + 0x200, Guard);
VdecCoreFrameBuffer small{tight.data(), tight.size()};
const VdecCoreOutputInfo caller = Output(decoder, &small);
Require(caller.frameBuffer == tight.data(), "a caller frame buffer with room for the picture only");
const auto unit = H264Fixture::DisplayOrderUnits[1];
Require(caller.pictures[0].pts == 1000 + unit && caller.pictures[0].userData == 3000 + unit, "the picture in a caller frame buffer with room for the picture only");
Require(VisibleHash(tight.data(), caller.framePitch, caller.frameHeight) == VisiblePictureHashes[1] && ExtendsLastRows(tight.data(), caller.framePitch, caller.frameHeight), "picture information too large for the room after a picture overwrote the picture");
Require(std::all_of(tight.begin() + static_cast<std::ptrdiff_t>(pictureSize), tight.end(), [](std::uint8_t byte) { return byte == Guard; }), "picture information was written into a caller frame buffer with no room for it after the picture");
Require(sceVdecCoreDeleteDecoder(decoder) == 0, "delete the bounds decoder");
}
void CheckFlushResetAndSync() {
void* decoder = CreateAvc(static_cast<std::int32_t>(H264Fixture::Width));
std::vector<std::uint8_t> memory(SlotSize * 3);
const VdecCoreMemoryBlock block{memory.data(), memory.size(), 256, 0};
Require(sceVdecCoreMapMemoryBlock(decoder, &block, 1) == 0, "map three slots");
const std::vector<void*> slots = Slots(decoder);
FeedAll(decoder, 1000, 2000, 3000);
const std::uint32_t held = Flush(decoder);
Require(held == H264Fixture::PictureHashes.size(), "a flush returns every held picture");
std::uint32_t count = 0;
Require(sceVdecCoreSyncDecode(decoder, &count) == 0 && count == held, "sync after a flush counts the held pictures");
std::uint32_t tried = 0;
Require(sceVdecCoreTrySyncDecode(decoder, &tried) == 0 && tried == held, "try-sync after a sync counts the held pictures");
std::uint64_t pointers[2] = {7, 7};
std::uint32_t forwarded = 0;
Require(sceVdecCoreSyncDecodeWptr(decoder, &forwarded, &pointers[0], &pointers[1]) == 0 && forwarded == held, "the write-pointer sync forwards to the sync");
forwarded = 0;
Require(sceVdecCoreTrySyncDecodeWptr(decoder, &forwarded, &pointers[0], &pointers[1]) == 0 && forwarded == held, "the write-pointer try-sync forwards to the try-sync");
Require(pointers[0] == 7 && pointers[1] == 7, "the write-pointer variants leave the pointers alone");
Require(Output(decoder, nullptr).frameBuffer == slots[0], "the first picture goes to the first slot");
Require(Output(decoder, nullptr).frameBuffer == slots[1], "the second picture goes to the second slot");
FeedAll(decoder, 1000, 2000, 3000);
Require(sceVdecCoreResetDecoder(decoder, nullptr) == 0, "reset with queued input");
count = 7;
Require(sceVdecCoreSyncDecode(decoder, &count) == 0 && count == 0, "a reset drops the queued input and its pictures");
tried = 7;
Require(sceVdecCoreTrySyncDecode(decoder, &tried) == 0 && tried == 0, "try-sync after a reset");
VdecCoreOutputInfo output{};
Require(sceVdecCoreGetDecodeOutput(decoder, nullptr, 0, &output, nullptr) == NoPicture, "no picture after a reset");
Require(Flush(decoder) == 0, "a flush after a reset holds nothing");
FeedAll(decoder, 1000, 2000, 3000);
Require(Flush(decoder) == H264Fixture::PictureHashes.size(), "the decoder decodes again after a reset");
const VdecCoreOutputInfo restarted = Output(decoder, nullptr);
Require(restarted.frameBuffer == slots[0], "a reset starts the slots again");
RequirePicture(restarted, 0, SlotSize, 1000, 3000, "the first picture after a reset");
Require(sceVdecCoreDeleteDecoder(decoder) == 0, "delete the flushed decoder");
}
void CheckDeleteWithQueuedInput() {
void* decoder = CreateAvc(static_cast<std::int32_t>(H264Fixture::Width));
for (int round = 0; round < 8; ++round) FeedAll(decoder, 1000, 2000, 3000);
Require(sceVdecCoreDeleteDecoder(decoder) == 0, "delete a decoder with queued input");
void* held = CreateAvc(static_cast<std::int32_t>(H264Fixture::Width));
FeedAll(held, 1000, 2000, 3000);
Require(Flush(held) == H264Fixture::PictureHashes.size(), "a flush before the delete holds every picture");
Require(sceVdecCoreDeleteDecoder(held) == 0, "delete a decoder with held pictures");
}
}
int main() {
CheckExports();
CheckInstanceSize();
CheckFrameBufferInfo();
CheckComputeResourceInfo();
CheckCreateAndDelete();
CheckMapMemoryBlock();
CheckAccessUnitLength();
CheckDecodeArguments();
CheckEmptyQueue();
CheckDecodeIntoSlots();
CheckInvalidPts();
CheckDestinations();
CheckOutputStaysInItsDestination();
CheckFlushResetAndSync();
CheckDeleteWithQueuedInput();
return 0;
}