mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-11-05 13:20:11 +00:00
feat(libSceVideodec2): expose HEVC picture metadata
This commit is contained in:
1 parent
74acfad65b
commit
2aa0ffbbcf
10 files changed
+1186
-13
No files matched your search
@@ -1,6 +1,7 @@
|
||||
add_library(libSceVideodec2 SHARED EXCLUDE_FROM_ALL
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Unimplemented.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/HevcMetadata.cpp
|
||||
)
|
||||
|
||||
target_include_directories(libSceVideodec2 PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
@@ -9,6 +10,17 @@ configure_windows_unwind(libSceVideodec2)
|
||||
target_link_libraries(libSceVideodec2 PRIVATE ffmpeg)
|
||||
|
||||
if(BUILD_TESTING)
|
||||
add_test_executable(guest_hevc_metadata_tests ${LIBS_INCLUDE_DIR}/tests/GuestHevcMetadata.cpp)
|
||||
target_include_directories(guest_hevc_metadata_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_hevc_metadata_tests PRIVATE libSceVideodec2 libc)
|
||||
configure_windows_unwind(guest_hevc_metadata_tests)
|
||||
add_test(NAME guest_hevc_metadata COMMAND guest_hevc_metadata_tests)
|
||||
set_tests_properties(guest_hevc_metadata PROPERTIES TIMEOUT 10)
|
||||
if(WIN32)
|
||||
set_tests_properties(guest_hevc_metadata PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceVideodec2>")
|
||||
endif()
|
||||
|
||||
add_test_executable(guest_videodec2_tests ${LIBS_INCLUDE_DIR}/tests/GuestVideodec2.cpp)
|
||||
target_include_directories(guest_videodec2_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_videodec2_tests PRIVATE libSceVideodec2 libc)
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include "prx/libc/include/General.hpp"
|
||||
#include "HevcMetadata.hpp"
|
||||
#include "prx/libc/include/GuestArena.hpp"
|
||||
|
||||
extern "C" {
|
||||
#include <libavcodec/avcodec.h>
|
||||
@@ -27,6 +29,8 @@ namespace {
|
||||
|
||||
using namespace SceVideodec2;
|
||||
|
||||
struct UnsupportedHevcMetadata {};
|
||||
|
||||
constexpr std::uint64_t WorkMemoryBytes = 1u << 20u;
|
||||
constexpr std::uint32_t PitchAlignment = 256;
|
||||
constexpr std::uint32_t HeightAlignment = 32;
|
||||
@@ -188,6 +192,7 @@ struct Picture {
|
||||
Crop crop{};
|
||||
bool hevcIdr = false;
|
||||
std::uint8_t bitDepthMinus8 = 0;
|
||||
Videodec2Hevc::Metadata hevc;
|
||||
};
|
||||
|
||||
struct PictureHeader {
|
||||
@@ -229,6 +234,7 @@ struct Decoder {
|
||||
bool flushing = false;
|
||||
std::array<Vp9PictureInfo, 8> vp9References{};
|
||||
Vp9PictureInfo vp9Colors{};
|
||||
Videodec2Hevc::State hevc;
|
||||
|
||||
~Decoder() {
|
||||
sws_freeContext(scaler);
|
||||
@@ -547,6 +553,7 @@ int APS5_VABI sceVideodec2Decode_nid_postfix(std::uint64_t handle, const InputDa
|
||||
const Picture picture{input->ptsData, input->dtsData, input->attachedData};
|
||||
if (decoder.flushing) {
|
||||
avcodec_flush_buffers(decoder.context);
|
||||
decoder.hevc.afterEnd = true;
|
||||
decoder.flushing = false;
|
||||
}
|
||||
if (input->auSize > static_cast<std::uint64_t>(std::numeric_limits<int>::max())) throw std::runtime_error("Videodec2: invalid access unit");
|
||||
@@ -595,6 +602,12 @@ int APS5_VABI sceVideodec2Decode_nid_postfix(std::uint64_t handle, const InputDa
|
||||
packet->pts = decoder.nextKey;
|
||||
auto tagged = picture;
|
||||
tagged.hevcIdr = decoder.codec == CodecHevc && StartsHevcIdr(*unit);
|
||||
if (decoder.codec == CodecHevc) {
|
||||
tagged.hevc = Videodec2Hevc::Parse_nid_no_patch(decoder.hevc, *unit);
|
||||
tagged.hevc.info.ptsData = picture.pts;
|
||||
tagged.hevc.info.dtsData = picture.dts;
|
||||
tagged.hevc.info.attachedData = picture.attached;
|
||||
}
|
||||
decoder.inputs.emplace(decoder.nextKey++, tagged);
|
||||
Send(decoder, packet);
|
||||
EmitDecoded(handle, decoder, frame, output);
|
||||
@@ -628,6 +641,7 @@ int APS5_VABI sceVideodec2Reset_nid_postfix(std::uint64_t handle) {
|
||||
std::erase_if(pictureOwners, [handle](const auto& owner) { return owner.second == handle; });
|
||||
}
|
||||
avcodec_flush_buffers(decoder.context);
|
||||
decoder.hevc.afterEnd = true;
|
||||
decoder.ready.clear();
|
||||
decoder.inputs.clear();
|
||||
decoder.parameterSets.clear();
|
||||
@@ -635,9 +649,11 @@ int APS5_VABI sceVideodec2Reset_nid_postfix(std::uint64_t handle) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceVideodec2GetPictureInfo_nid_postfix(const OutputInfo* output, AvcPictureInfo* first, void* second) {
|
||||
static int GetPictureInfo(const OutputInfo* output, AvcPictureInfo* first, void* second, bool allowHevc) {
|
||||
(void)second;
|
||||
if (!output || !first) throw std::runtime_error("Videodec2: invalid argument pointer");
|
||||
if (output->codecType == CodecHevc && (!Fits(output, offsetof(OutputInfo, frameBuffer) + sizeof(output->frameBuffer)) || !Fits(first, sizeof(Videodec2Hevc::PictureInfo))))
|
||||
throw std::runtime_error("Videodec2: HEVC picture structure is too small");
|
||||
const auto owner = [output]() -> std::uint64_t {
|
||||
std::lock_guard guard(lock);
|
||||
const auto found = pictureOwners.find(output->frameBuffer);
|
||||
@@ -658,7 +674,17 @@ int APS5_VABI sceVideodec2GetPictureInfo_nid_postfix(const OutputInfo* output, A
|
||||
info->thisSize = size;
|
||||
return 0;
|
||||
}
|
||||
if (decoder->codec != CodecAvc) throw std::runtime_error("Videodec2: picture info is only implemented for H.264 and VP9");
|
||||
if (decoder->codec == CodecHevc) {
|
||||
if (!allowHevc) throw UnsupportedHevcMetadata{};
|
||||
auto* info = reinterpret_cast<Videodec2Hevc::PictureInfo*>(first);
|
||||
if (!Fits(info, sizeof(*info))) throw std::runtime_error("Videodec2: HEVC picture structure is too small");
|
||||
if (!found->second.hevc.unsupported.empty()) throw UnsupportedHevcMetadata{};
|
||||
const auto size = info->thisSize;
|
||||
GuestArena::HostWrite access(info, sizeof(*info));
|
||||
*info = found->second.hevc.info;
|
||||
info->thisSize = size;
|
||||
return 0;
|
||||
}
|
||||
const auto& picture = found->second;
|
||||
first->isValid = true;
|
||||
first->ptsData = picture.pts;
|
||||
@@ -673,12 +699,51 @@ int APS5_VABI sceVideodec2GetPictureInfo_nid_postfix(const OutputInfo* output, A
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
first->isValid = false;
|
||||
if (output->codecType == CodecHevc) {
|
||||
GuestArena::HostWrite access(&first->isValid, sizeof(first->isValid));
|
||||
first->isValid = false;
|
||||
} else first->isValid = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceVideodec2GetPictureInfo_nid_postfix(const OutputInfo* output, AvcPictureInfo* first, void* second) {
|
||||
try { return GetPictureInfo(output, first, second, true); }
|
||||
catch (const UnsupportedHevcMetadata&) { NotImplemented_nid_no_patch(__func__); }
|
||||
}
|
||||
|
||||
int APS5_VABI sceVideodec2GetAvcPictureInfo_nid_postfix(const OutputInfo* output, AvcPictureInfo* first, AvcPictureInfo* second) {
|
||||
return sceVideodec2GetPictureInfo_nid_postfix(output, first, second);
|
||||
try { return GetPictureInfo(output, first, second, false); }
|
||||
catch (const UnsupportedHevcMetadata&) { NotImplemented_nid_no_patch(__func__); }
|
||||
}
|
||||
|
||||
int APS5_VABI sceVideodec2GetHevcPictureInfo_nid_postfix(const OutputInfo* output, Videodec2Hevc::PictureInfo* info) {
|
||||
if (!output || !info) throw std::runtime_error("Videodec2: invalid HEVC picture argument");
|
||||
if (!Fits(output, offsetof(OutputInfo, frameBuffer) + sizeof(output->frameBuffer)) || !Fits(info, sizeof(*info)))
|
||||
throw std::runtime_error("Videodec2: HEVC picture structure is too small");
|
||||
if (output->codecType != CodecHevc) throw std::runtime_error("Videodec2: HEVC info requested for another codec");
|
||||
const auto owner = [output]() -> std::uint64_t {
|
||||
std::lock_guard guard(lock);
|
||||
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;
|
||||
if (decoder->codec != CodecHevc) throw std::runtime_error("Videodec2: HEVC info requested for another codec");
|
||||
const auto found = decoder->pictures.find(output->frameBuffer);
|
||||
if (found == decoder->pictures.end()) throw std::runtime_error("Videodec2: owned HEVC picture is missing");
|
||||
if (!found->second.hevc.unsupported.empty()) NotImplemented_nid_no_patch(__func__);
|
||||
const auto size = info->thisSize;
|
||||
GuestArena::HostWrite access(info, sizeof(*info));
|
||||
*info = found->second.hevc.info;
|
||||
info->thisSize = size;
|
||||
return 0;
|
||||
}
|
||||
GuestArena::HostWrite access(&info->isValid, sizeof(info->isValid));
|
||||
info->isValid = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int APS5_VABI sceVideodec2GetVp9PictureInfo_nid_postfix(const OutputInfo* output, Vp9PictureInfo* info) {
|
||||
|
||||
@@ -0,0 +1,505 @@
|
||||
#include "HevcMetadata.hpp"
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
class Bits {
|
||||
public:
|
||||
explicit Bits(std::span<const std::uint8_t> bytes) : bytes(bytes) {}
|
||||
|
||||
std::uint32_t Read(unsigned count) {
|
||||
if (count > 32 || count > bytes.size() * 8 - position) throw std::runtime_error("truncated HEVC syntax");
|
||||
std::uint32_t value = 0;
|
||||
for (unsigned bit = 0; bit < count; ++bit, ++position)
|
||||
value = (value << 1) | ((bytes[position / 8] >> (7 - position % 8)) & 1);
|
||||
return value;
|
||||
}
|
||||
|
||||
void Skip(std::size_t count) {
|
||||
if (count > bytes.size() * 8 - position) throw std::runtime_error("truncated HEVC syntax");
|
||||
position += count;
|
||||
}
|
||||
|
||||
std::uint32_t Unsigned(std::uint32_t maximum = std::numeric_limits<std::uint32_t>::max()) {
|
||||
unsigned zeros = 0;
|
||||
while (Read(1) == 0) if (++zeros > 31) throw std::runtime_error("HEVC exponential code overflow");
|
||||
const auto value = ((std::uint32_t{1} << zeros) - 1) + Read(zeros);
|
||||
if (value > maximum) throw std::runtime_error("HEVC syntax value outside its domain");
|
||||
return value;
|
||||
}
|
||||
|
||||
void Signed() { Unsigned(); }
|
||||
|
||||
private:
|
||||
std::span<const std::uint8_t> bytes;
|
||||
std::size_t position = 0;
|
||||
};
|
||||
|
||||
std::vector<std::uint8_t> Rbsp(std::span<const std::uint8_t> nal) {
|
||||
std::vector<std::uint8_t> bytes;
|
||||
bytes.reserve(nal.size());
|
||||
unsigned zeros = 0;
|
||||
for (std::size_t index = 0; index < nal.size(); ++index) {
|
||||
const auto value = nal[index];
|
||||
if (zeros == 2 && value == 3) {
|
||||
if (index + 1 < nal.size() && nal[index + 1] > 3) throw std::runtime_error("invalid HEVC emulation prevention");
|
||||
zeros = 0;
|
||||
continue;
|
||||
}
|
||||
if (zeros == 2 && value < 3) throw std::runtime_error("missing HEVC emulation prevention");
|
||||
bytes.push_back(value);
|
||||
zeros = value == 0 ? std::min(zeros + 1, 2u) : 0;
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
std::uint32_t Profile(Bits& bits, unsigned sublayers, bool* mixed = nullptr, Videodec2Hevc::PictureInfo* info = nullptr) {
|
||||
const auto space = bits.Read(2);
|
||||
const auto tier = bits.Read(1);
|
||||
const auto profileId = bits.Read(5);
|
||||
bits.Skip(32);
|
||||
const bool progressive = bits.Read(1) != 0;
|
||||
const bool interlaced = bits.Read(1) != 0;
|
||||
if (mixed) *mixed = progressive && interlaced;
|
||||
bits.Skip(1);
|
||||
const auto frameOnly = bits.Read(1);
|
||||
bits.Skip(44);
|
||||
const auto levelId = bits.Read(8);
|
||||
if (info) {
|
||||
info->profileSpace = static_cast<std::uint8_t>(space);
|
||||
info->tier = static_cast<std::uint8_t>(tier);
|
||||
info->profile = static_cast<std::uint8_t>(profileId);
|
||||
info->progressiveSource = progressive;
|
||||
info->interlacedSource = interlaced;
|
||||
info->frameOnlyConstraint = static_cast<std::uint8_t>(frameOnly);
|
||||
info->level = static_cast<std::uint8_t>(levelId);
|
||||
}
|
||||
std::array<bool, 7> profile{};
|
||||
std::array<bool, 7> level{};
|
||||
for (unsigned layer = 0; layer < sublayers; ++layer) {
|
||||
profile[layer] = bits.Read(1) != 0;
|
||||
level[layer] = bits.Read(1) != 0;
|
||||
}
|
||||
if (sublayers != 0) bits.Skip(2 * (8 - sublayers));
|
||||
for (unsigned layer = 0; layer < sublayers; ++layer) {
|
||||
if (profile[layer]) bits.Skip(88);
|
||||
if (level[layer]) bits.Skip(8);
|
||||
}
|
||||
return progressive == interlaced ? 2 : progressive ? 1 : 0;
|
||||
}
|
||||
|
||||
void Ordering(Bits& bits, unsigned sublayers, Videodec2Hevc::PictureInfo* info = nullptr) {
|
||||
const auto present = bits.Read(1);
|
||||
const unsigned begin = present != 0 ? 0 : sublayers;
|
||||
unsigned buffering = 0;
|
||||
for (unsigned layer = begin; layer <= sublayers; ++layer) {
|
||||
buffering = std::max(buffering, bits.Unsigned(15));
|
||||
bits.Unsigned();
|
||||
bits.Unsigned();
|
||||
}
|
||||
if (info) {
|
||||
info->subLayerOrderingPresent = static_cast<std::uint8_t>(present);
|
||||
info->maxDecPicBufferingMinus1 = static_cast<std::uint8_t>(buffering);
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<unsigned, Videodec2Hevc::Vps> ParseVps(Bits& bits) {
|
||||
const auto id = bits.Read(4);
|
||||
bits.Skip(2);
|
||||
const auto layers = bits.Read(6);
|
||||
const auto sublayers = bits.Read(3);
|
||||
if (layers != 0 || sublayers > 6) throw std::runtime_error("multilayer HEVC VPS metadata is unsupported");
|
||||
bits.Skip(17);
|
||||
Profile(bits, sublayers);
|
||||
Ordering(bits, sublayers);
|
||||
const auto maxLayer = bits.Read(6);
|
||||
const auto sets = bits.Unsigned(1023);
|
||||
if (maxLayer != 0 || sets != 0) throw std::runtime_error("HEVC layer-set metadata is unsupported");
|
||||
Videodec2Hevc::Vps vps;
|
||||
vps.timing = bits.Read(1) != 0;
|
||||
if (vps.timing) {
|
||||
vps.numUnitsInTick = bits.Read(32);
|
||||
vps.timeScale = bits.Read(32);
|
||||
}
|
||||
return {id, vps};
|
||||
}
|
||||
|
||||
void ScalingLists(Bits& bits) {
|
||||
for (unsigned size = 0; size < 4; ++size) {
|
||||
for (unsigned matrix = 0; matrix < 6; matrix += size == 3 ? 3 : 1) {
|
||||
if (bits.Read(1) == 0) bits.Unsigned();
|
||||
else {
|
||||
if (size > 1) bits.Signed();
|
||||
const unsigned coefficients = size == 0 ? 16 : 64;
|
||||
for (unsigned coefficient = 0; coefficient < coefficients; ++coefficient) bits.Signed();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ShortTermReferences(Bits& bits) {
|
||||
const auto count = bits.Unsigned(64);
|
||||
std::array<unsigned, 64> deltas{};
|
||||
for (unsigned index = 0; index < count; ++index) {
|
||||
if (index != 0 && bits.Read(1) != 0) {
|
||||
bits.Skip(1);
|
||||
bits.Unsigned();
|
||||
for (unsigned reference = 0; reference <= deltas[index - 1]; ++reference) {
|
||||
const bool used = bits.Read(1) != 0;
|
||||
if (used || bits.Read(1) != 0) ++deltas[index];
|
||||
}
|
||||
} else {
|
||||
const auto negative = bits.Unsigned(16);
|
||||
const auto positive = bits.Unsigned(16);
|
||||
if (negative + positive > 16) throw std::runtime_error("too many HEVC short-term references");
|
||||
deltas[index] = negative + positive;
|
||||
for (unsigned reference = 0; reference < deltas[index]; ++reference) {
|
||||
bits.Unsigned();
|
||||
bits.Skip(1);
|
||||
}
|
||||
}
|
||||
if (deltas[index] > 16) throw std::runtime_error("too many predicted HEVC references");
|
||||
}
|
||||
}
|
||||
|
||||
void Hrd(Bits& bits, unsigned sublayers) {
|
||||
const bool nal = bits.Read(1) != 0;
|
||||
const bool vcl = bits.Read(1) != 0;
|
||||
bool subpicture = false;
|
||||
if (nal || vcl) {
|
||||
subpicture = bits.Read(1) != 0;
|
||||
if (subpicture) bits.Skip(19);
|
||||
bits.Skip(8);
|
||||
if (subpicture) bits.Skip(4);
|
||||
bits.Skip(15);
|
||||
}
|
||||
for (unsigned layer = 0; layer <= sublayers; ++layer) {
|
||||
const bool general = bits.Read(1) != 0;
|
||||
const bool fixed = general || bits.Read(1) != 0;
|
||||
bool lowDelay = false;
|
||||
if (fixed) bits.Unsigned();
|
||||
else lowDelay = bits.Read(1) != 0;
|
||||
const auto count = lowDelay ? 0 : bits.Unsigned(31);
|
||||
for (unsigned kind = 0; kind < 2; ++kind) {
|
||||
if (!(kind == 0 ? nal : vcl)) continue;
|
||||
for (unsigned entry = 0; entry <= count; ++entry) {
|
||||
bits.Unsigned();
|
||||
bits.Unsigned();
|
||||
if (subpicture) {
|
||||
bits.Unsigned();
|
||||
bits.Unsigned();
|
||||
}
|
||||
bits.Skip(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Vui(Bits& bits, unsigned sublayers, Videodec2Hevc::PictureInfo& info) {
|
||||
info.aspectRatioInfoPresent = bits.Read(1);
|
||||
if (info.aspectRatioInfoPresent) {
|
||||
info.aspectRatioIdc = static_cast<std::uint8_t>(bits.Read(8));
|
||||
if (info.aspectRatioIdc == 255) {
|
||||
info.sarWidth = static_cast<std::uint16_t>(bits.Read(16));
|
||||
info.sarHeight = static_cast<std::uint16_t>(bits.Read(16));
|
||||
} else {
|
||||
constexpr std::uint16_t ratios[17][2] = {{0, 0}, {1, 1}, {12, 11}, {10, 11}, {16, 11}, {40, 33}, {24, 11}, {20, 11}, {32, 11}, {80, 33}, {18, 11}, {15, 11}, {64, 33}, {160, 99}, {4, 3}, {3, 2}, {2, 1}};
|
||||
if (info.aspectRatioIdc > 16) throw std::runtime_error("reserved HEVC aspect ratio");
|
||||
info.sarWidth = ratios[info.aspectRatioIdc][0];
|
||||
info.sarHeight = ratios[info.aspectRatioIdc][1];
|
||||
}
|
||||
}
|
||||
if (bits.Read(1) != 0) bits.Skip(1);
|
||||
info.videoSignalTypePresent = static_cast<std::uint8_t>(bits.Read(1));
|
||||
if (info.videoSignalTypePresent) {
|
||||
info.videoFormat = static_cast<std::uint8_t>(bits.Read(3));
|
||||
info.fullRange = static_cast<std::uint8_t>(bits.Read(1));
|
||||
info.colourDescriptionPresent = static_cast<std::uint8_t>(bits.Read(1));
|
||||
if (info.colourDescriptionPresent) {
|
||||
info.colourPrimaries = static_cast<std::uint8_t>(bits.Read(8));
|
||||
info.transferCharacteristics = static_cast<std::uint8_t>(bits.Read(8));
|
||||
info.matrixCoefficients = static_cast<std::uint8_t>(bits.Read(8));
|
||||
}
|
||||
}
|
||||
info.chromaLocInfoPresent = static_cast<std::uint8_t>(bits.Read(1));
|
||||
if (info.chromaLocInfoPresent) {
|
||||
info.chromaLocTop = static_cast<std::uint8_t>(bits.Unsigned(5));
|
||||
info.chromaLocBottom = static_cast<std::uint8_t>(bits.Unsigned(5));
|
||||
}
|
||||
bits.Skip(1);
|
||||
info.fieldSequence = static_cast<std::uint8_t>(bits.Read(1));
|
||||
info.frameFieldInfoPresent = static_cast<std::uint8_t>(bits.Read(1));
|
||||
info.defaultDisplayWindow = bits.Read(1);
|
||||
if (info.defaultDisplayWindow) {
|
||||
info.displayLeft = bits.Unsigned();
|
||||
info.displayRight = bits.Unsigned();
|
||||
info.displayTop = bits.Unsigned();
|
||||
info.displayBottom = bits.Unsigned();
|
||||
}
|
||||
info.timingInfoPresent = static_cast<std::uint8_t>(bits.Read(1));
|
||||
if (info.timingInfoPresent) {
|
||||
info.numUnitsInTick = bits.Read(32);
|
||||
info.timeScale = bits.Read(32);
|
||||
if (bits.Read(1) != 0) bits.Unsigned();
|
||||
if (bits.Read(1) != 0) Hrd(bits, sublayers);
|
||||
}
|
||||
if (bits.Read(1) != 0) {
|
||||
bits.Skip(3);
|
||||
for (unsigned value = 0; value < 5; ++value) bits.Unsigned();
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<unsigned, Videodec2Hevc::Sps> ParseSps(Bits& bits) {
|
||||
Videodec2Hevc::Sps sps;
|
||||
auto& info = sps.info;
|
||||
info.thisSize = sizeof(info);
|
||||
info.isValid = true;
|
||||
info.videoFormat = 5;
|
||||
info.colourPrimaries = 2;
|
||||
info.transferCharacteristics = 2;
|
||||
info.matrixCoefficients = 2;
|
||||
sps.vps = bits.Read(4);
|
||||
const auto sublayers = bits.Read(3);
|
||||
if (sublayers > 6) throw std::runtime_error("invalid HEVC temporal layers");
|
||||
bits.Skip(1);
|
||||
bool mixed = false;
|
||||
info.sourceScanType = Profile(bits, sublayers, &mixed, &info);
|
||||
info.frameFieldInfoPresent = mixed ? 1 : 0;
|
||||
const auto id = bits.Unsigned(15);
|
||||
const auto chroma = bits.Unsigned(3);
|
||||
if (chroma == 3) bits.Skip(1);
|
||||
info.width = bits.Unsigned();
|
||||
info.height = bits.Unsigned();
|
||||
info.conformanceWindow = bits.Read(1);
|
||||
if (info.conformanceWindow) {
|
||||
info.confLeft = bits.Unsigned();
|
||||
info.confRight = bits.Unsigned();
|
||||
info.confTop = bits.Unsigned();
|
||||
info.confBottom = bits.Unsigned();
|
||||
}
|
||||
info.bitDepthLumaMinus8 = static_cast<std::uint8_t>(bits.Unsigned(8));
|
||||
info.bitDepthChromaMinus8 = static_cast<std::uint8_t>(bits.Unsigned(8));
|
||||
const auto orderBits = bits.Unsigned(12) + 4;
|
||||
Ordering(bits, sublayers, &info);
|
||||
for (unsigned value = 0; value < 6; ++value) bits.Unsigned();
|
||||
if (bits.Read(1) != 0 && bits.Read(1) != 0) ScalingLists(bits);
|
||||
bits.Skip(2);
|
||||
if (bits.Read(1) != 0) {
|
||||
bits.Skip(8);
|
||||
bits.Unsigned();
|
||||
bits.Unsigned();
|
||||
bits.Skip(1);
|
||||
}
|
||||
ShortTermReferences(bits);
|
||||
if (bits.Read(1) != 0) {
|
||||
const auto count = bits.Unsigned(32);
|
||||
for (unsigned reference = 0; reference < count; ++reference) bits.Skip(orderBits + 1);
|
||||
}
|
||||
bits.Skip(2);
|
||||
if (bits.Read(1) != 0) Vui(bits, sublayers, info);
|
||||
if (bits.Read(1) != 0 && bits.Read(8) != 0) sps.unsupported = "HEVC SPS extension metadata is unsupported";
|
||||
if (sublayers != 0) sps.unsupported = "HEVC sub-layer metadata projection is unsupported";
|
||||
if (info.conformanceWindow || info.defaultDisplayWindow) sps.unsupported = "HEVC crop/window metadata projection is unsupported";
|
||||
return {id, std::move(sps)};
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t> PictureTiming(std::span<const std::uint8_t> bytes, Videodec2Hevc::PictureInfo& flags, bool& transferAmbiguous) {
|
||||
std::vector<std::uint8_t> timing;
|
||||
std::size_t position = 0;
|
||||
while (position < bytes.size() && !(bytes[position] == 0x80 && position + 1 == bytes.size())) {
|
||||
const auto number = [&]() {
|
||||
std::size_t value = 0;
|
||||
for (;;) {
|
||||
if (position == bytes.size()) throw std::runtime_error("truncated HEVC SEI header");
|
||||
const auto next = bytes[position++];
|
||||
if (value > std::numeric_limits<std::size_t>::max() - next) throw std::runtime_error("HEVC SEI length overflow");
|
||||
value += next;
|
||||
if (next != 255) return value;
|
||||
}
|
||||
};
|
||||
const auto type = number();
|
||||
const auto size = number();
|
||||
if (size > bytes.size() - position) throw std::runtime_error("truncated HEVC SEI payload");
|
||||
if (type == 1) {
|
||||
if (flags.pictureTimingPresent) throw std::runtime_error("multiple HEVC picture-timing messages");
|
||||
flags.pictureTimingPresent = 1;
|
||||
timing.assign(bytes.begin() + position, bytes.begin() + position + size);
|
||||
} else if (type == 0) flags.bufferingPeriodPresent = 1;
|
||||
else if (type == 45) flags.framePackingPresent = 1;
|
||||
else if (type == 147) {
|
||||
if (size != 1 || (flags.alternativeTransferPresent && flags.preferredTransfer != bytes[position])) {
|
||||
transferAmbiguous = true;
|
||||
throw std::runtime_error("invalid or conflicting HEVC alternative-transfer metadata");
|
||||
}
|
||||
flags.alternativeTransferPresent = 1;
|
||||
flags.preferredTransfer = bytes[position];
|
||||
} else if (type != 4 && type != 5 && type != 6 && type != 129 && type != 132 && type != 136 && type != 137 && type != 144) {
|
||||
throw std::runtime_error("HEVC SEI metadata payload type " + std::to_string(type) + " is unsupported");
|
||||
}
|
||||
position += size;
|
||||
}
|
||||
return timing;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace Videodec2Hevc {
|
||||
|
||||
Metadata Parse_nid_no_patch(State& state, std::span<const std::uint8_t> annexB) {
|
||||
Metadata metadata;
|
||||
metadata.unsupported = state.unsupported;
|
||||
PictureInfo flags{};
|
||||
std::vector<std::size_t> starts;
|
||||
for (std::size_t position = 0; position + 2 < annexB.size(); ++position) {
|
||||
if (annexB[position] == 0 && annexB[position + 1] == 0 && annexB[position + 2] == 1) {
|
||||
starts.push_back(position + 3);
|
||||
position += 2;
|
||||
}
|
||||
}
|
||||
std::optional<unsigned> pictureSps;
|
||||
std::optional<unsigned> pictureType;
|
||||
std::optional<Sps> selectedSps;
|
||||
std::optional<Vps> selectedVps;
|
||||
std::vector<std::uint8_t> timing;
|
||||
bool transferAmbiguous = false;
|
||||
for (std::size_t index = 0; index < starts.size(); ++index) {
|
||||
const auto start = starts[index];
|
||||
auto end = index + 1 == starts.size() ? annexB.size() : starts[index + 1] - 3;
|
||||
while (end > start && annexB[end - 1] == 0) --end;
|
||||
unsigned type = 64;
|
||||
try {
|
||||
if (end - start < 2) throw std::runtime_error("truncated HEVC NAL header");
|
||||
type = (annexB[start] >> 1) & 63;
|
||||
if ((annexB[start] & 0x81) != 0 || (annexB[start + 1] & 0xf8) != 0)
|
||||
throw std::runtime_error("multilayer HEVC picture metadata is unsupported");
|
||||
if ((annexB[start + 1] & 7) == 0) throw std::runtime_error("invalid HEVC temporal identifier");
|
||||
const auto bytes = Rbsp(annexB.subspan(start + 2, end - start - 2));
|
||||
Bits bits(bytes);
|
||||
if (type == 32) {
|
||||
flags.vpsPresent = 1;
|
||||
auto [id, vps] = ParseVps(bits);
|
||||
vps.bytes = bytes;
|
||||
if (state.vps[id]) {
|
||||
vps.unsupported = state.vps[id]->unsupported;
|
||||
if (state.vps[id]->bytes != bytes) vps.unsupported = "HEVC VPS replacement metadata is unsupported until decoder recreation";
|
||||
}
|
||||
state.vps[id] = std::move(vps);
|
||||
} else if (type == 33) {
|
||||
flags.spsPresent = 1;
|
||||
auto [id, sps] = ParseSps(bits);
|
||||
sps.bytes = bytes;
|
||||
if (state.sps[id]) {
|
||||
if (!state.sps[id]->unsupported.empty()) sps.unsupported = state.sps[id]->unsupported;
|
||||
if (state.sps[id]->bytes != bytes) sps.unsupported = "HEVC SPS replacement metadata is unsupported until decoder recreation";
|
||||
}
|
||||
state.sps[id] = std::move(sps);
|
||||
} else if (type == 34) {
|
||||
flags.ppsPresent = 1;
|
||||
const auto id = bits.Unsigned(63);
|
||||
Pps pps{bits.Unsigned(15), bytes, {}};
|
||||
if (state.pps[id]) {
|
||||
pps.unsupported = state.pps[id]->unsupported;
|
||||
if (state.pps[id]->bytes != bytes) pps.unsupported = "HEVC PPS replacement metadata is unsupported until decoder recreation";
|
||||
}
|
||||
state.pps[id] = std::move(pps);
|
||||
} else if (type == 35) flags.delimiterPresent = 1;
|
||||
else if (type == 36) flags.endSequencePresent = 1;
|
||||
else if (type == 37) flags.endStreamPresent = 1;
|
||||
else if (type == 39 || type == 40) {
|
||||
auto message = PictureTiming(bytes, flags, transferAmbiguous);
|
||||
if (!message.empty()) {
|
||||
if (type != 39 || !timing.empty()) throw std::runtime_error("unsupported HEVC picture-timing association");
|
||||
timing = std::move(message);
|
||||
}
|
||||
} else if (type < 32) {
|
||||
const bool first = bits.Read(1) != 0;
|
||||
if (type >= 16 && type <= 23) bits.Skip(1);
|
||||
const auto pps = bits.Unsigned(63);
|
||||
if (!state.pps[pps]) throw std::runtime_error("HEVC picture parameter set is unavailable");
|
||||
if (!state.pps[pps]->unsupported.empty() && metadata.unsupported.empty()) metadata.unsupported = state.pps[pps]->unsupported;
|
||||
const auto sps = state.pps[pps]->sps;
|
||||
if (pictureSps && (first || *pictureSps != sps)) throw std::runtime_error("multiple HEVC pictures in one access unit");
|
||||
if (pictureType && *pictureType != type) throw std::runtime_error("mixed HEVC slice NAL types in one access unit");
|
||||
if (!pictureSps) {
|
||||
if (!state.sps[sps]) throw std::runtime_error("HEVC sequence parameter set is unavailable");
|
||||
selectedSps = state.sps[sps];
|
||||
selectedVps = state.vps[selectedSps->vps];
|
||||
pictureType = type;
|
||||
flags.idr = type == 19 || type == 20;
|
||||
flags.irap = type >= 16 && type <= 23;
|
||||
}
|
||||
pictureSps = sps;
|
||||
} else if (type == 38) flags.fillerPresent = 1;
|
||||
else throw std::runtime_error("reserved HEVC NAL metadata is unsupported");
|
||||
} catch (const std::runtime_error& error) {
|
||||
if (metadata.unsupported.empty()) metadata.unsupported = error.what();
|
||||
if (type >= 32 && type <= 34 && state.unsupported.empty()) state.unsupported = error.what();
|
||||
}
|
||||
}
|
||||
if (!selectedSps) {
|
||||
if (flags.endSequencePresent || flags.endStreamPresent) state.afterEnd = true;
|
||||
if ((flags.alternativeTransferPresent || transferAmbiguous) && state.unsupported.empty())
|
||||
state.unsupported = "HEVC alternative transfer in a pictureless access unit is unsupported";
|
||||
if (metadata.unsupported.empty()) metadata.unsupported = "HEVC sequence parameter set is unavailable";
|
||||
return metadata;
|
||||
}
|
||||
const auto& sps = *selectedSps;
|
||||
metadata.info = sps.info;
|
||||
const bool newSequence = !state.hadPicture || (*pictureType >= 16 && *pictureType <= 20) || (*pictureType == 21 && state.afterEnd);
|
||||
if (newSequence) {
|
||||
state.preferredTransfer.reset();
|
||||
state.transferUnsupported.clear();
|
||||
}
|
||||
if ((state.afterEnd || !state.hadPicture) && !flags.irap)
|
||||
state.transferUnsupported = "HEVC non-IRAP continuation after reset/flush is unsupported";
|
||||
if (transferAmbiguous) state.transferUnsupported = "ambiguous HEVC alternative-transfer metadata";
|
||||
if (flags.alternativeTransferPresent) {
|
||||
if (state.preferredTransfer && *state.preferredTransfer != flags.preferredTransfer)
|
||||
state.transferUnsupported = "conflicting HEVC alternative transfers within one CLVS";
|
||||
state.preferredTransfer = flags.preferredTransfer;
|
||||
}
|
||||
if (state.preferredTransfer) metadata.info.preferredTransfer = *state.preferredTransfer;
|
||||
if (!state.transferUnsupported.empty() && metadata.unsupported.empty()) metadata.unsupported = state.transferUnsupported;
|
||||
state.hadPicture = true;
|
||||
state.afterEnd = flags.endSequencePresent || flags.endStreamPresent;
|
||||
if (!sps.unsupported.empty() && metadata.unsupported.empty()) metadata.unsupported = sps.unsupported;
|
||||
if (!selectedVps) {
|
||||
if (metadata.unsupported.empty()) metadata.unsupported = "HEVC video parameter set is unavailable";
|
||||
} else if (!metadata.info.timingInfoPresent && selectedVps->timing) {
|
||||
if (metadata.unsupported.empty()) metadata.unsupported = "HEVC VPS-only timing projection is unsupported";
|
||||
}
|
||||
if (selectedVps && !selectedVps->unsupported.empty() && metadata.unsupported.empty()) metadata.unsupported = selectedVps->unsupported;
|
||||
if (metadata.info.frameFieldInfoPresent) {
|
||||
try {
|
||||
if (timing.empty()) throw std::runtime_error("HEVC picture timing is unavailable");
|
||||
Bits bits(timing);
|
||||
metadata.info.picStruct = bits.Read(4);
|
||||
metadata.info.sourceScanType = bits.Read(2);
|
||||
metadata.info.duplicate = bits.Read(1);
|
||||
if (metadata.info.picStruct > 12 || metadata.info.sourceScanType > 2)
|
||||
throw std::runtime_error("reserved HEVC picture-timing metadata");
|
||||
} catch (const std::runtime_error& error) {
|
||||
if (metadata.unsupported.empty()) metadata.unsupported = error.what();
|
||||
}
|
||||
}
|
||||
metadata.info.vpsPresent = flags.vpsPresent;
|
||||
metadata.info.spsPresent = flags.spsPresent;
|
||||
metadata.info.ppsPresent = flags.ppsPresent;
|
||||
metadata.info.delimiterPresent = flags.delimiterPresent;
|
||||
metadata.info.endSequencePresent = flags.endSequencePresent;
|
||||
metadata.info.endStreamPresent = flags.endStreamPresent;
|
||||
metadata.info.fillerPresent = flags.fillerPresent;
|
||||
metadata.info.pictureTimingPresent = flags.pictureTimingPresent;
|
||||
metadata.info.bufferingPeriodPresent = flags.bufferingPeriodPresent;
|
||||
metadata.info.framePackingPresent = flags.framePackingPresent;
|
||||
metadata.info.alternativeTransferPresent = flags.alternativeTransferPresent;
|
||||
metadata.info.idr = flags.idr;
|
||||
metadata.info.irap = flags.irap;
|
||||
return metadata;
|
||||
}
|
||||
|
||||
} // namespace Videodec2Hevc
|
||||
@@ -0,0 +1,61 @@
|
||||
#ifndef CORE_LIBS_PRX_LIBSCEVIDEODEC2_HEVCMETADATA_HPP
|
||||
#define CORE_LIBS_PRX_LIBSCEVIDEODEC2_HEVCMETADATA_HPP
|
||||
|
||||
#include "prx/libSceVideodec2/include/Api.hpp"
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Videodec2Hevc {
|
||||
|
||||
using PictureInfo = SceVideodec2::HevcPictureInfo;
|
||||
|
||||
static_assert(sizeof(PictureInfo) == 184 && offsetof(PictureInfo, width) == 40 && offsetof(PictureInfo, picStruct) == 80 && offsetof(PictureInfo, vpsPresent) == 136);
|
||||
static_assert(offsetof(PictureInfo, fillerPresent) == 142 && offsetof(PictureInfo, idr) == 147 && offsetof(PictureInfo, profile) == 151 && offsetof(PictureInfo, cropping) == 165 && offsetof(PictureInfo, cropBottom) == 180);
|
||||
|
||||
struct Metadata {
|
||||
PictureInfo info{};
|
||||
std::string unsupported;
|
||||
};
|
||||
|
||||
struct Vps {
|
||||
bool timing = false;
|
||||
std::uint32_t numUnitsInTick = 0;
|
||||
std::uint32_t timeScale = 0;
|
||||
std::vector<std::uint8_t> bytes;
|
||||
std::string unsupported;
|
||||
};
|
||||
|
||||
struct Sps {
|
||||
PictureInfo info{};
|
||||
unsigned vps = 0;
|
||||
std::string unsupported;
|
||||
std::vector<std::uint8_t> bytes;
|
||||
};
|
||||
|
||||
struct Pps {
|
||||
unsigned sps = 0;
|
||||
std::vector<std::uint8_t> bytes;
|
||||
std::string unsupported;
|
||||
};
|
||||
|
||||
struct State {
|
||||
std::array<std::optional<Vps>, 16> vps;
|
||||
std::array<std::optional<Sps>, 16> sps;
|
||||
std::array<std::optional<Pps>, 64> pps;
|
||||
std::string unsupported;
|
||||
std::optional<std::uint8_t> preferredTransfer;
|
||||
std::string transferUnsupported;
|
||||
bool hadPicture = false;
|
||||
bool afterEnd = false;
|
||||
};
|
||||
|
||||
Metadata Parse_nid_no_patch(State& state, std::span<const std::uint8_t> annexB);
|
||||
|
||||
} // namespace Videodec2Hevc
|
||||
|
||||
#endif
|
||||
@@ -52,10 +52,6 @@ int APS5_VABI sceVideodec2QueryHevcDecoderMemoryInfo() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
|
||||
int APS5_VABI sceVideodec2GetHevcPictureInfo() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
|
||||
int APS5_VABI sceVideodec2InitLibrary() {
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
}
|
||||
|
||||
@@ -155,6 +155,88 @@ struct Vp9PictureInfo {
|
||||
|
||||
}
|
||||
|
||||
namespace SceVideodec2 {
|
||||
|
||||
struct HevcPictureInfo {
|
||||
std::uint64_t thisSize;
|
||||
bool isValid;
|
||||
std::uint64_t ptsData;
|
||||
std::uint64_t dtsData;
|
||||
std::uint64_t attachedData;
|
||||
std::uint32_t width;
|
||||
std::uint32_t height;
|
||||
std::uint8_t bitDepthLumaMinus8;
|
||||
std::uint8_t bitDepthChromaMinus8;
|
||||
std::uint8_t timingInfoPresent;
|
||||
std::uint32_t numUnitsInTick;
|
||||
std::uint32_t timeScale;
|
||||
std::uint32_t aspectRatioInfoPresent;
|
||||
std::uint8_t aspectRatioIdc;
|
||||
std::uint16_t sarWidth;
|
||||
std::uint16_t sarHeight;
|
||||
std::uint8_t videoSignalTypePresent;
|
||||
std::uint8_t videoFormat;
|
||||
std::uint8_t fullRange;
|
||||
std::uint8_t colourDescriptionPresent;
|
||||
std::uint8_t colourPrimaries;
|
||||
std::uint8_t transferCharacteristics;
|
||||
std::uint8_t matrixCoefficients;
|
||||
std::uint8_t frameFieldInfoPresent;
|
||||
std::uint32_t picStruct;
|
||||
std::uint32_t sourceScanType;
|
||||
std::uint32_t duplicate;
|
||||
std::uint32_t conformanceWindow;
|
||||
std::uint32_t confLeft;
|
||||
std::uint32_t confRight;
|
||||
std::uint32_t confTop;
|
||||
std::uint32_t confBottom;
|
||||
std::uint32_t defaultDisplayWindow;
|
||||
std::uint32_t displayLeft;
|
||||
std::uint32_t displayRight;
|
||||
std::uint32_t displayTop;
|
||||
std::uint32_t displayBottom;
|
||||
std::uint8_t chromaLocInfoPresent;
|
||||
std::uint8_t chromaLocTop;
|
||||
std::uint8_t chromaLocBottom;
|
||||
std::uint8_t fieldSequence;
|
||||
std::uint8_t vpsPresent;
|
||||
std::uint8_t spsPresent;
|
||||
std::uint8_t ppsPresent;
|
||||
std::uint8_t delimiterPresent;
|
||||
std::uint8_t endSequencePresent;
|
||||
std::uint8_t endStreamPresent;
|
||||
std::uint8_t fillerPresent;
|
||||
std::uint8_t pictureTimingPresent;
|
||||
std::uint8_t bufferingPeriodPresent;
|
||||
std::uint8_t framePackingPresent;
|
||||
std::uint8_t alternativeTransferPresent;
|
||||
std::uint8_t idr;
|
||||
std::uint8_t irap;
|
||||
std::uint8_t profileSpace;
|
||||
std::uint8_t tier;
|
||||
std::uint8_t profile;
|
||||
std::uint8_t progressiveSource;
|
||||
std::uint8_t interlacedSource;
|
||||
std::uint8_t frameOnlyConstraint;
|
||||
std::uint8_t level;
|
||||
std::uint8_t subLayerProfilePresent;
|
||||
std::uint8_t subLayerLevelPresent;
|
||||
std::uint8_t subLayerProfileSpace;
|
||||
std::uint8_t subLayerTier;
|
||||
std::uint8_t subLayerProfile;
|
||||
std::uint8_t subLayerLevel;
|
||||
std::uint8_t subLayerOrderingPresent;
|
||||
std::uint8_t maxDecPicBufferingMinus1;
|
||||
std::uint8_t preferredTransfer;
|
||||
std::uint8_t cropping;
|
||||
std::uint32_t cropLeft;
|
||||
std::uint32_t cropRight;
|
||||
std::uint32_t cropTop;
|
||||
std::uint32_t cropBottom;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
int APS5_VABI sceVideodec2QueryComputeMemoryInfo_nid_postfix(SceVideodec2::ComputeMemoryInfo* info);
|
||||
@@ -181,7 +263,7 @@ int APS5_VABI sceVideodec2PresetDecoderConfigInfoVp9();
|
||||
int APS5_VABI sceVideodec2CreateDecoderBid();
|
||||
int APS5_VABI sceVideodec2CreateHevcDecoder();
|
||||
int APS5_VABI sceVideodec2QueryHevcDecoderMemoryInfo();
|
||||
int APS5_VABI sceVideodec2GetHevcPictureInfo();
|
||||
int APS5_VABI sceVideodec2GetHevcPictureInfo_nid_postfix(const SceVideodec2::OutputInfo* output, SceVideodec2::HevcPictureInfo* info);
|
||||
int APS5_VABI sceVideodec2InitLibrary();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,399 @@
|
||||
#include "prx/libSceVideodec2/HevcMetadata.hpp"
|
||||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
#include "HevcFixture.hpp"
|
||||
#include "HevcMetadataFixture.hpp"
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
using namespace SceVideodec2;
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr std::uint32_t codecHevc = 974921;
|
||||
|
||||
void Require(bool value, const char* message) {
|
||||
if (value) return;
|
||||
std::fprintf(stderr, "HEVC metadata: %s\n", message);
|
||||
std::abort();
|
||||
}
|
||||
|
||||
template<typename TCall>
|
||||
void Rejects(TCall call, const char* message) {
|
||||
try { call(); }
|
||||
catch (const std::runtime_error&) { return; }
|
||||
Require(false, message);
|
||||
}
|
||||
|
||||
struct Decoder {
|
||||
std::uint64_t handle = 0;
|
||||
DecoderMemoryInfo memory{sizeof(memory)};
|
||||
std::vector<std::uint8_t> buffer;
|
||||
FrameBuffer frame{sizeof(frame)};
|
||||
OutputInfo output{sizeof(output)};
|
||||
|
||||
explicit Decoder(unsigned profile) {
|
||||
const DecoderConfigInfo config{sizeof(config), 1, codecHevc, profile, 0, 64, 48, 4, 1};
|
||||
Require(sceVideodec2QueryDecoderMemoryInfo_nid_postfix(&config, &memory) == 0, "query memory");
|
||||
Require(sceVideodec2CreateDecoder_nid_postfix(&config, &memory, &handle) == 0, "create decoder");
|
||||
buffer.resize(memory.maxFrameBufferSize);
|
||||
frame.frameBuffer = buffer.data();
|
||||
frame.frameBufferSize = buffer.size();
|
||||
}
|
||||
|
||||
~Decoder() { if (handle) sceVideodec2DeleteDecoder_nid_postfix(handle); }
|
||||
|
||||
void Decode(const std::vector<std::uint8_t>& bytes, std::uint64_t tag) {
|
||||
const InputData input{sizeof(input), bytes.data(), bytes.size(), 1000 + tag, 2000 + tag, 3000 + tag};
|
||||
Require(sceVideodec2Decode_nid_postfix(handle, &input, &frame, &output) == 0, "decode unit");
|
||||
}
|
||||
|
||||
Videodec2Hevc::PictureInfo Query() {
|
||||
struct Guarded {
|
||||
Videodec2Hevc::PictureInfo info{sizeof(info)};
|
||||
std::uint64_t guard = 0x123456789abcdef0;
|
||||
} result;
|
||||
Require(sceVideodec2GetHevcPictureInfo_nid_postfix(&output, &result.info) == 0, "query HEVC info");
|
||||
Require(result.info.thisSize == sizeof(result.info) && result.info.isValid, "valid HEVC picture");
|
||||
Require(result.guard == 0x123456789abcdef0, "HEVC info overwrote adjacent memory");
|
||||
Videodec2Hevc::PictureInfo generic{sizeof(generic)};
|
||||
Require(sceVideodec2GetPictureInfo_nid_postfix(&output, reinterpret_cast<AvcPictureInfo*>(&generic), nullptr) == 0, "generic HEVC query");
|
||||
Require(generic.ptsData == result.info.ptsData && generic.dtsData == result.info.dtsData && generic.attachedData == result.info.attachedData && generic.width == result.info.width && generic.timeScale == result.info.timeScale && generic.spsPresent == result.info.spsPresent, "generic and dedicated info disagree");
|
||||
return result.info;
|
||||
}
|
||||
};
|
||||
|
||||
void RejectUnchanged(Decoder& decoder);
|
||||
|
||||
void CheckCommon(const Videodec2Hevc::PictureInfo& info, unsigned bitDepth) {
|
||||
Require(info.width == 64 && info.height == 40, "coded luma dimensions");
|
||||
Require(info.bitDepthLumaMinus8 == bitDepth && info.bitDepthChromaMinus8 == bitDepth, "source bit depths");
|
||||
Require(info.timingInfoPresent == 1 && info.numUnitsInTick == 1 && info.timeScale == 30, "VUI timing syntax");
|
||||
Require(info.aspectRatioInfoPresent == 1 && info.aspectRatioIdc == 1 && info.sarWidth == 1 && info.sarHeight == 1, "VUI sample aspect ratio");
|
||||
Require(info.videoSignalTypePresent == 1 && info.videoFormat == 5 && info.fullRange == 0 && info.colourDescriptionPresent == 0 && info.colourPrimaries == 2 && info.transferCharacteristics == 2 && info.matrixCoefficients == 2, "signalled video format and inferred colours");
|
||||
Require(info.frameFieldInfoPresent == 0 && info.picStruct == 0 && info.sourceScanType == 1 && info.duplicate == 0, "PTL progressive scan inference");
|
||||
Require(info.defaultDisplayWindow == 0 && info.displayLeft == 0 && info.displayRight == 0 && info.displayTop == 0 && info.displayBottom == 0, "default display window inference");
|
||||
Require(info.chromaLocInfoPresent == 0 && info.chromaLocTop == 0 && info.chromaLocBottom == 0 && info.fieldSequence == 0, "inferred chroma location and field sequence");
|
||||
Require(info.profileSpace == 0 && info.tier == 0 && info.profile == (bitDepth == 0 ? 1 : 2) && info.level == 30 && info.progressiveSource == 1 && info.interlacedSource == 0 && info.frameOnlyConstraint == 1, "general profile-tier-level syntax");
|
||||
Require(info.subLayerProfilePresent == 0 && info.subLayerLevelPresent == 0 && info.subLayerProfileSpace == 0 && info.subLayerTier == 0 && info.subLayerProfile == 0 && info.subLayerLevel == 0 && info.subLayerOrderingPresent == 1 && info.maxDecPicBufferingMinus1 == 4, "single-layer PTL and DPB syntax");
|
||||
Require(info.cropping == 0 && info.cropLeft == 0 && info.cropRight == 0 && info.cropTop == 0 && info.cropBottom == 0, "absent tail cropping fields");
|
||||
}
|
||||
|
||||
void Clips(bool alternativeTransfer = false) {
|
||||
Decoder decoder(1);
|
||||
std::size_t pictures = 0;
|
||||
const auto take = [&] {
|
||||
if (!decoder.output.isValid) return;
|
||||
const auto info = decoder.Query();
|
||||
CheckCommon(info, 0);
|
||||
constexpr std::array<unsigned, 6> order{0, 3, 2, 1, 5, 4};
|
||||
Require(pictures < order.size(), "too many pictures");
|
||||
const auto tag = order[pictures];
|
||||
Require(info.ptsData == 1000 + tag && info.dtsData == 2000 + tag && info.attachedData == 3000 + tag, "per-picture access-unit association through B-frame reordering");
|
||||
Require(info.vpsPresent == (pictures == 0) && info.spsPresent == (pictures == 0) && info.ppsPresent == (pictures == 0) && info.delimiterPresent == 1, "presence is from the emitted picture's unit");
|
||||
Require(info.conformanceWindow == 0 && info.confLeft == 0 && info.confRight == 0 && info.confTop == 0 && info.confBottom == 0, "no conformance window");
|
||||
Require(info.idr == (pictures == 0) && info.irap == (pictures == 0) && info.fillerPresent == 0, "per-picture IDR/IRAP and filler flags");
|
||||
Require(info.alternativeTransferPresent == (alternativeTransfer && pictures == 0) && info.preferredTransfer == (alternativeTransfer ? 16 : 0), "alternative transfer persists through B-frame reordering");
|
||||
++pictures;
|
||||
};
|
||||
auto units = HevcFixture::hevcUnits(HevcFixture::HevcStream.data(), HevcFixture::HevcStream.size());
|
||||
if (alternativeTransfer) {
|
||||
const std::array<std::uint8_t, 9> sei{0, 0, 1, 0x4e, 1, 147, 1, 16, 0x80};
|
||||
units[0].insert(units[0].begin() + 7, sei.begin(), sei.end());
|
||||
}
|
||||
for (std::size_t unit = 0; unit < units.size(); ++unit) {
|
||||
decoder.Decode(units[unit], unit);
|
||||
take();
|
||||
}
|
||||
for (std::size_t drain = 0; drain <= units.size(); ++drain) {
|
||||
Require(sceVideodec2Flush_nid_postfix(decoder.handle, &decoder.frame, &decoder.output) == 0, "drain clip");
|
||||
if (!decoder.output.isValid) break;
|
||||
take();
|
||||
}
|
||||
Require(pictures == 6, "decoded all six picture metadata records");
|
||||
Videodec2Hevc::PictureInfo invalid{sizeof(invalid)};
|
||||
invalid.isValid = true;
|
||||
Require(sceVideodec2Reset_nid_postfix(decoder.handle) == 0, "reset");
|
||||
Require(sceVideodec2GetHevcPictureInfo_nid_postfix(&decoder.output, &invalid) == 0 && !invalid.isValid, "reset releases picture metadata");
|
||||
invalid.thisSize = 8;
|
||||
invalid.isValid = true;
|
||||
Rejects([&] { sceVideodec2GetPictureInfo_nid_postfix(&decoder.output, reinterpret_cast<AvcPictureInfo*>(&invalid), nullptr); }, "unowned generic HEVC query accepted an undersized destination");
|
||||
Require(invalid.isValid, "unowned undersized generic query changed destination");
|
||||
decoder.Decode(units.front(), 99);
|
||||
Require(sceVideodec2Flush_nid_postfix(decoder.handle, &decoder.frame, &decoder.output) == 0 && decoder.output.isValid, "decode after reset");
|
||||
Require(decoder.Query().attachedData == 3099, "new metadata after reset");
|
||||
}
|
||||
|
||||
void Single(std::span<const std::uint8_t> stream, unsigned bitDepth, unsigned bottomCrop, bool delimiter) {
|
||||
Decoder decoder(bitDepth == 0 ? 1 : 2);
|
||||
decoder.Decode({stream.begin(), stream.end()}, 7);
|
||||
if (!decoder.output.isValid) Require(sceVideodec2Flush_nid_postfix(decoder.handle, &decoder.frame, &decoder.output) == 0, "single-picture drain");
|
||||
if (bottomCrop != 0) {
|
||||
Require(decoder.output.isValid, "cropped picture remains decodable");
|
||||
RejectUnchanged(decoder);
|
||||
return;
|
||||
}
|
||||
const auto info = decoder.Query();
|
||||
CheckCommon(info, bitDepth);
|
||||
Require(info.attachedData == 3007 && info.vpsPresent == 1 && info.spsPresent == 1 && info.ppsPresent == 1 && info.delimiterPresent == delimiter, "single-picture tags and presence");
|
||||
Require(info.idr == 1 && info.irap == 1 && info.preferredTransfer == 0 && info.pictureTimingPresent == 0, "single-picture tail metadata");
|
||||
Require(info.conformanceWindow == (bottomCrop != 0) && info.confBottom == bottomCrop && info.confLeft == 0 && info.confRight == 0 && info.confTop == 0, "raw chroma-unit conformance offsets");
|
||||
auto badOutput = decoder.output;
|
||||
auto badInfo = info;
|
||||
Rejects([&] { sceVideodec2GetHevcPictureInfo_nid_postfix(nullptr, &badInfo); }, "null output accepted");
|
||||
Rejects([&] { sceVideodec2GetHevcPictureInfo_nid_postfix(&badOutput, nullptr); }, "null destination accepted");
|
||||
badInfo.thisSize = sizeof(badInfo) - 1;
|
||||
Rejects([&] { sceVideodec2GetHevcPictureInfo_nid_postfix(&badOutput, &badInfo); }, "undersized destination accepted");
|
||||
badInfo = info;
|
||||
badOutput.thisSize = 8;
|
||||
Rejects([&] { sceVideodec2GetHevcPictureInfo_nid_postfix(&badOutput, &badInfo); }, "undersized output accepted");
|
||||
badOutput = decoder.output;
|
||||
badOutput.codecType = 1;
|
||||
Rejects([&] { sceVideodec2GetHevcPictureInfo_nid_postfix(&badOutput, &badInfo); }, "another codec accepted");
|
||||
std::array<std::uint8_t, sizeof(badInfo)> before;
|
||||
std::memcpy(before.data(), &badInfo, before.size());
|
||||
Rejects([&] { sceVideodec2GetAvcPictureInfo_nid_postfix(&decoder.output, reinterpret_cast<AvcPictureInfo*>(&badInfo), nullptr); }, "AVC-specific getter accepted HEVC with a large buffer");
|
||||
Require(std::memcmp(before.data(), &badInfo, before.size()) == 0, "AVC-specific failure modified its output");
|
||||
Rejects([&] { sceVideodec2GetAvcPictureInfo_nid_postfix(&badOutput, reinterpret_cast<AvcPictureInfo*>(&badInfo), nullptr); }, "AVC-specific getter trusted forged codec tag");
|
||||
Require(std::memcmp(before.data(), &badInfo, before.size()) == 0, "forged codec tag changed AVC failure output");
|
||||
}
|
||||
|
||||
void UnsupportedPicture() {
|
||||
Decoder decoder(1);
|
||||
auto units = HevcFixture::hevcUnits(HevcFixture::HevcStream.data(), HevcFixture::HevcStream.size());
|
||||
const std::array<std::uint8_t, 9> unknownSei{0, 0, 1, 0x4e, 1, 200, 1, 0x42, 0x80};
|
||||
units[0].insert(units[0].begin() + 7, unknownSei.begin(), unknownSei.end());
|
||||
decoder.Decode(units[0], 0);
|
||||
Require(sceVideodec2Flush_nid_postfix(decoder.handle, &decoder.frame, &decoder.output) == 0 && decoder.output.isValid, "unsupported metadata retains decoded output");
|
||||
Videodec2Hevc::PictureInfo untouched;
|
||||
std::memset(&untouched, 0x5a, sizeof(untouched));
|
||||
untouched.thisSize = sizeof(untouched);
|
||||
std::array<std::uint8_t, sizeof(untouched)> before;
|
||||
std::memcpy(before.data(), &untouched, before.size());
|
||||
Rejects([&] { sceVideodec2GetHevcPictureInfo_nid_postfix(&decoder.output, &untouched); }, "unknown SEI metadata did not throw");
|
||||
Require(std::memcmp(before.data(), &untouched, before.size()) == 0, "unsupported query changed destination");
|
||||
Rejects([&] { sceVideodec2GetPictureInfo_nid_postfix(&decoder.output, reinterpret_cast<AvcPictureInfo*>(&untouched), nullptr); }, "generic unknown metadata did not throw");
|
||||
Require(std::memcmp(before.data(), &untouched, before.size()) == 0, "generic unsupported query changed destination");
|
||||
Require(sceVideodec2Reset_nid_postfix(decoder.handle) == 0, "clear unsupported metadata on reset");
|
||||
const auto clean = HevcFixture::hevcUnits(HevcFixture::HevcStream.data(), HevcFixture::HevcStream.size());
|
||||
decoder.Decode(clean[0], 80);
|
||||
Require(sceVideodec2Flush_nid_postfix(decoder.handle, &decoder.frame, &decoder.output) == 0 && decoder.output.isValid, "clean replacement picture");
|
||||
Require(decoder.Query().ptsData == 1080, "replacement metadata does not retain unsupported reason");
|
||||
const auto previous = decoder.output;
|
||||
Require(sceVideodec2DeleteDecoder_nid_postfix(decoder.handle) == 0, "delete owner");
|
||||
decoder.handle = 0;
|
||||
untouched = {sizeof(untouched)};
|
||||
untouched.isValid = true;
|
||||
Require(sceVideodec2GetHevcPictureInfo_nid_postfix(&previous, &untouched) == 0 && !untouched.isValid, "deleted owner releases metadata");
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t> Replacement(unsigned wanted) {
|
||||
const auto& original = HevcFixture::HevcMain10Stream;
|
||||
std::vector<std::size_t> starts;
|
||||
for (std::size_t index = 0; index + 2 < original.size(); ++index)
|
||||
if (original[index] == 0 && original[index + 1] == 0 && original[index + 2] == 1) starts.push_back(index + 3);
|
||||
for (std::size_t index = 0; index < starts.size(); ++index) {
|
||||
const auto header = starts[index];
|
||||
if (((original[header] >> 1) & 63) != wanted) continue;
|
||||
const auto end = index + 1 == starts.size() ? original.size() : starts[index + 1] - 3;
|
||||
std::vector<bool> bits;
|
||||
unsigned zeros = 0;
|
||||
for (std::size_t byte = header + 2; byte < end; ++byte) {
|
||||
const auto value = original[byte];
|
||||
if (zeros == 2 && value == 3) { zeros = 0; continue; }
|
||||
for (int bit = 7; bit >= 0; --bit) bits.push_back(((value >> bit) & 1) != 0);
|
||||
zeros = value == 0 ? zeros + 1 : 0;
|
||||
}
|
||||
while (!bits.empty() && !bits.back()) bits.pop_back();
|
||||
Require(bits.size() >= 3, "parameter-set fixture trailing bits");
|
||||
bits.pop_back();
|
||||
Require(!bits.back(), "fixture extension flag is zero");
|
||||
if (wanted == 32) {
|
||||
bits.pop_back();
|
||||
Require(!bits.back(), "fixture VPS timing flag is zero");
|
||||
bits.pop_back();
|
||||
bits.push_back(true);
|
||||
for (const unsigned value : std::array{1u, 30u})
|
||||
for (int bit = 31; bit >= 0; --bit) bits.push_back(((value >> bit) & 1) != 0);
|
||||
bits.insert(bits.end(), {false, true, false});
|
||||
} else {
|
||||
bits.back() = true;
|
||||
bits.insert(bits.end(), 8, false);
|
||||
}
|
||||
bits.push_back(true);
|
||||
while (bits.size() % 8 != 0) bits.push_back(false);
|
||||
std::vector<std::uint8_t> encoded;
|
||||
zeros = 0;
|
||||
for (std::size_t byte = 0; byte < bits.size(); byte += 8) {
|
||||
std::uint8_t value = 0;
|
||||
for (unsigned bit = 0; bit < 8; ++bit) value = static_cast<std::uint8_t>((value << 1) | bits[byte + bit]);
|
||||
if (zeros == 2 && value <= 3) { encoded.push_back(3); zeros = 0; }
|
||||
encoded.push_back(value);
|
||||
zeros = value == 0 ? zeros + 1 : 0;
|
||||
}
|
||||
std::vector<std::uint8_t> result(original.begin(), original.begin() + header + 2);
|
||||
result.insert(result.end(), encoded.begin(), encoded.end());
|
||||
result.insert(result.end(), original.begin() + end, original.end());
|
||||
return result;
|
||||
}
|
||||
Require(false, "fixture parameter set not found");
|
||||
return {};
|
||||
}
|
||||
|
||||
void RejectUnchanged(Decoder& decoder) {
|
||||
Videodec2Hevc::PictureInfo info;
|
||||
std::memset(&info, 0x6b, sizeof(info));
|
||||
info.thisSize = sizeof(info);
|
||||
std::array<std::uint8_t, sizeof(info)> before;
|
||||
std::memcpy(before.data(), &info, sizeof(info));
|
||||
Rejects([&] { sceVideodec2GetHevcPictureInfo_nid_postfix(&decoder.output, &info); }, "ambiguous replacement metadata succeeded");
|
||||
Require(std::memcmp(before.data(), &info, sizeof(info)) == 0, "ambiguous replacement query mutated output");
|
||||
}
|
||||
|
||||
void ParameterReplacements() {
|
||||
const std::vector<std::uint8_t> original(HevcFixture::HevcMain10Stream.begin(), HevcFixture::HevcMain10Stream.end());
|
||||
for (const unsigned type : std::array{0u, 32u, 33u, 34u}) {
|
||||
Decoder decoder(2);
|
||||
decoder.Decode(original, 10);
|
||||
Require(sceVideodec2Flush_nid_postfix(decoder.handle, &decoder.frame, &decoder.output) == 0 && decoder.output.isValid, "initial replacement-test picture");
|
||||
Require(decoder.Query().attachedData == 3010, "initial parameter metadata");
|
||||
const auto previous = decoder.output;
|
||||
std::vector<std::uint8_t> nextBuffer(decoder.buffer.size());
|
||||
decoder.frame.frameBuffer = nextBuffer.data();
|
||||
decoder.Decode(type == 0 ? original : Replacement(type), 20);
|
||||
Require(sceVideodec2Flush_nid_postfix(decoder.handle, &decoder.frame, &decoder.output) == 0 && decoder.output.isValid, "decode repeated or replaced parameter sets");
|
||||
if (type == 0) Require(decoder.Query().attachedData == 3020, "identical repeated headers remain supported");
|
||||
else RejectUnchanged(decoder);
|
||||
Videodec2Hevc::PictureInfo old{sizeof(old)};
|
||||
Require(sceVideodec2GetHevcPictureInfo_nid_postfix(&previous, &old) == 0 && old.isValid && old.attachedData == 3010, "previous picture metadata remains immutable after parameter updates");
|
||||
if (type != 0) {
|
||||
Require(sceVideodec2Reset_nid_postfix(decoder.handle) == 0, "reset after replacement");
|
||||
decoder.Decode(original, 30);
|
||||
Require(sceVideodec2Flush_nid_postfix(decoder.handle, &decoder.frame, &decoder.output) == 0 && decoder.output.isValid, "decode after replacement reset");
|
||||
RejectUnchanged(decoder);
|
||||
}
|
||||
}
|
||||
Decoder recreated(2);
|
||||
recreated.Decode(original, 40);
|
||||
Require(sceVideodec2Flush_nid_postfix(recreated.handle, &recreated.frame, &recreated.output) == 0 && recreated.output.isValid, "recreated backend picture");
|
||||
Require(recreated.Query().attachedData == 3040, "new backend context recovers supported metadata");
|
||||
}
|
||||
|
||||
void ExtendedMetadata() {
|
||||
for (const bool interlaced : {false, true}) {
|
||||
const auto bytes = interlaced ? std::span<const std::uint8_t>(HevcMetadataFixture::Interlaced) : std::span<const std::uint8_t>(HevcMetadataFixture::Rich);
|
||||
Decoder decoder(1);
|
||||
decoder.Decode({bytes.begin(), bytes.end()}, 50);
|
||||
if (!decoder.output.isValid) Require(sceVideodec2Flush_nid_postfix(decoder.handle, &decoder.frame, &decoder.output) == 0, "extended metadata flush");
|
||||
const auto info = decoder.Query();
|
||||
Require(info.width == 64 && info.height == 48 && info.numUnitsInTick == 1 && info.timeScale == 24, "extended dimensions and timing");
|
||||
if (interlaced) {
|
||||
Require(info.fieldSequence == 1 && info.frameFieldInfoPresent == 1 && info.picStruct == 1 && info.sourceScanType == 0 && info.duplicate == 0, "picture-timing SEI and field metadata");
|
||||
Require(info.pictureTimingPresent == 1 && info.progressiveSource == 0 && info.interlacedSource == 1 && info.frameOnlyConstraint == 0, "interlaced PTL and SEI presence tail");
|
||||
} else {
|
||||
Require(info.aspectRatioIdc == 2 && info.sarWidth == 12 && info.sarHeight == 11, "table-derived non-square sample aspect");
|
||||
Require(info.videoSignalTypePresent == 1 && info.videoFormat == 1 && info.fullRange == 1 && info.colourDescriptionPresent == 1 && info.colourPrimaries == 9 && info.transferCharacteristics == 16 && info.matrixCoefficients == 9, "explicit HDR color syntax");
|
||||
Require(info.chromaLocInfoPresent == 1 && info.chromaLocTop == 3 && info.chromaLocBottom == 3, "explicit chroma sample locations");
|
||||
Require(info.defaultDisplayWindow == 0 && info.cropping == 0, "unwindowed rich picture");
|
||||
}
|
||||
Require(info.profile == 4 && info.level == 30 && info.subLayerOrderingPresent == 1 && info.maxDecPicBufferingMinus1 == 2 && info.idr == 1 && info.irap == 1, "extended PTL and DPB tail");
|
||||
}
|
||||
Decoder windowed(1);
|
||||
windowed.Decode({HevcMetadataFixture::Windowed.begin(), HevcMetadataFixture::Windowed.end()}, 51);
|
||||
if (!windowed.output.isValid) Require(sceVideodec2Flush_nid_postfix(windowed.handle, &windowed.frame, &windowed.output) == 0, "windowed picture flush");
|
||||
Require(windowed.output.isValid, "windowed picture remains decodable");
|
||||
RejectUnchanged(windowed);
|
||||
}
|
||||
|
||||
void CabacPadding() {
|
||||
Decoder decoder(2);
|
||||
std::vector<std::uint8_t> padded(HevcFixture::HevcMain10Stream.begin(), HevcFixture::HevcMain10Stream.end());
|
||||
padded.insert(padded.end(), {0, 0, 3});
|
||||
decoder.Decode(padded, 60);
|
||||
Require(sceVideodec2Flush_nid_postfix(decoder.handle, &decoder.frame, &decoder.output) == 0 && decoder.output.isValid, "terminal CABAC prevention byte decode");
|
||||
Require(decoder.Query().attachedData == 3060, "terminal CABAC prevention byte metadata");
|
||||
Videodec2Hevc::State state;
|
||||
padded.back() = 2;
|
||||
const auto invalid = Videodec2Hevc::Parse_nid_no_patch(state, padded);
|
||||
Require(!invalid.unsupported.empty(), "missing emulation prevention was accepted");
|
||||
}
|
||||
|
||||
void MalformedParameterHistory() {
|
||||
const std::vector<std::uint8_t> original(HevcFixture::HevcMain10Stream.begin(), HevcFixture::HevcMain10Stream.end());
|
||||
Decoder decoder(2);
|
||||
decoder.Decode(original, 70);
|
||||
Require(sceVideodec2Flush_nid_postfix(decoder.handle, &decoder.frame, &decoder.output) == 0 && decoder.output.isValid, "initial malformed-history picture");
|
||||
Require(decoder.Query().attachedData == 3070, "initial malformed-history metadata");
|
||||
std::vector<std::uint8_t> malformed{0, 0, 1, 0x44, 1, 0x80};
|
||||
malformed.insert(malformed.end(), original.begin(), original.end());
|
||||
bool failed = false;
|
||||
try { decoder.Decode(malformed, 71); }
|
||||
catch (const std::runtime_error&) { failed = true; }
|
||||
if (!failed) {
|
||||
Require(sceVideodec2Flush_nid_postfix(decoder.handle, &decoder.frame, &decoder.output) == 0 && decoder.output.isValid, "malformed parameter output");
|
||||
RejectUnchanged(decoder);
|
||||
}
|
||||
Require(sceVideodec2Reset_nid_postfix(decoder.handle) == 0, "reset malformed parameter state");
|
||||
decoder.Decode(original, 72);
|
||||
Require(sceVideodec2Flush_nid_postfix(decoder.handle, &decoder.frame, &decoder.output) == 0 && decoder.output.isValid, "clean decode after malformed parameter reset");
|
||||
RejectUnchanged(decoder);
|
||||
}
|
||||
|
||||
void TransferAssociation() {
|
||||
const auto original = HevcFixture::hevcUnits(HevcFixture::HevcStream.data(), HevcFixture::HevcStream.size());
|
||||
const std::array<std::uint8_t, 9> transfer{0, 0, 1, 0x4e, 1, 147, 1, 16, 0x80};
|
||||
{
|
||||
Decoder decoder(1);
|
||||
auto units = original;
|
||||
units[0].insert(units[0].begin() + 7, transfer.begin(), transfer.end());
|
||||
auto conflict = transfer;
|
||||
conflict[7] = 18;
|
||||
units[0].insert(units[0].begin() + 7, conflict.begin(), conflict.end());
|
||||
unsigned pictures = 0;
|
||||
for (std::size_t index = 0; index < units.size(); ++index) {
|
||||
decoder.Decode(units[index], index);
|
||||
if (decoder.output.isValid) { RejectUnchanged(decoder); ++pictures; }
|
||||
}
|
||||
for (;;) {
|
||||
Require(sceVideodec2Flush_nid_postfix(decoder.handle, &decoder.frame, &decoder.output) == 0, "conflicting transfer drain");
|
||||
if (!decoder.output.isValid) break;
|
||||
RejectUnchanged(decoder);
|
||||
++pictures;
|
||||
}
|
||||
Require(pictures == 6, "conflicting transfers retain all decoded pictures");
|
||||
Require(sceVideodec2Reset_nid_postfix(decoder.handle) == 0, "reset conflicting transfer sequence");
|
||||
decoder.Decode(original[0], 90);
|
||||
Require(sceVideodec2Flush_nid_postfix(decoder.handle, &decoder.frame, &decoder.output) == 0 && decoder.output.isValid, "new IRAP transfer sequence");
|
||||
Require(decoder.Query().preferredTransfer == 0, "new CLVS clears prior transfer ambiguity");
|
||||
}
|
||||
{
|
||||
Decoder decoder(1);
|
||||
try { decoder.Decode({transfer.begin(), transfer.end()}, 91); }
|
||||
catch (const std::runtime_error&) {}
|
||||
Require(sceVideodec2Reset_nid_postfix(decoder.handle) == 0, "reset pictureless transfer packet");
|
||||
decoder.Decode(original[0], 92);
|
||||
Require(sceVideodec2Flush_nid_postfix(decoder.handle, &decoder.frame, &decoder.output) == 0 && decoder.output.isValid, "decode after pictureless transfer");
|
||||
RejectUnchanged(decoder);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
Clips();
|
||||
Clips(true);
|
||||
Single(HevcFixture::HevcMain10Stream, 2, 0, true);
|
||||
Single(HevcFixture::HevcCroppedStream, 0, 2, false);
|
||||
UnsupportedPicture();
|
||||
ParameterReplacements();
|
||||
ExtendedMetadata();
|
||||
CabacPadding();
|
||||
MalformedParameterHistory();
|
||||
TransferAssociation();
|
||||
}
|
||||
|
||||
@@ -31,6 +31,5 @@ int main() {
|
||||
passed = ReportsNotImplemented("sceVideodec2CreateDecoderBid", [] { return sceVideodec2CreateDecoderBid(); }) && passed;
|
||||
passed = ReportsNotImplemented("sceVideodec2CreateHevcDecoder", [] { return sceVideodec2CreateHevcDecoder(); }) && passed;
|
||||
passed = ReportsNotImplemented("sceVideodec2QueryHevcDecoderMemoryInfo", [] { return sceVideodec2QueryHevcDecoderMemoryInfo(); }) && passed;
|
||||
passed = ReportsNotImplemented("sceVideodec2GetHevcPictureInfo", [] { return sceVideodec2GetHevcPictureInfo(); }) && passed;
|
||||
return passed ? 0 : 1;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
#ifndef CORE_LIBS_TESTS_HEVCMETADATAFIXTURE_HPP
|
||||
#define CORE_LIBS_TESTS_HEVCMETADATAFIXTURE_HPP
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
namespace HevcMetadataFixture {
|
||||
|
||||
inline constexpr std::array<std::uint8_t, 115> Rich{
|
||||
0x00, 0x00, 0x00, 0x01, 0x46, 0x01, 0x10, 0x00, 0x00, 0x00, 0x01, 0x40, 0x01, 0x0c, 0x01, 0xff,
|
||||
0xff, 0x04, 0x08, 0x00, 0x00, 0x03, 0x00, 0x9f, 0xa8, 0x00, 0x00, 0x03, 0x00, 0x00, 0x1e, 0xba,
|
||||
0x02, 0x40, 0x00, 0x00, 0x00, 0x01, 0x42, 0x01, 0x01, 0x04, 0x08, 0x00, 0x00, 0x03, 0x00, 0x9f,
|
||||
0xa8, 0x00, 0x00, 0x03, 0x00, 0x00, 0x1e, 0xa0, 0x20, 0x83, 0x16, 0x5b, 0xab, 0x93, 0x2b, 0xc0,
|
||||
0x93, 0x84, 0x88, 0x04, 0xc8, 0x40, 0x80, 0x00, 0x00, 0x03, 0x00, 0x80, 0x00, 0x00, 0x0c, 0x04,
|
||||
0x00, 0x00, 0x00, 0x01, 0x44, 0x01, 0xc1, 0x73, 0xc0, 0x89, 0x00, 0x00, 0x01, 0x28, 0x01, 0xaf,
|
||||
0x78, 0xf7, 0x04, 0x03, 0xff, 0xdb, 0xa3, 0xff, 0xed, 0x27, 0xd2, 0xf6, 0xc3, 0x94, 0x40, 0x83,
|
||||
0xc0, 0x00, 0x78,
|
||||
};
|
||||
|
||||
inline constexpr std::array<std::uint8_t, 118> Windowed{
|
||||
0x00, 0x00, 0x00, 0x01, 0x46, 0x01, 0x10, 0x00, 0x00, 0x00, 0x01, 0x40, 0x01, 0x0c, 0x01, 0xff,
|
||||
0xff, 0x04, 0x08, 0x00, 0x00, 0x03, 0x00, 0x9f, 0xa8, 0x00, 0x00, 0x03, 0x00, 0x00, 0x1e, 0xba,
|
||||
0x02, 0x40, 0x00, 0x00, 0x00, 0x01, 0x42, 0x01, 0x01, 0x04, 0x08, 0x00, 0x00, 0x03, 0x00, 0x9f,
|
||||
0xa8, 0x00, 0x00, 0x03, 0x00, 0x00, 0x1e, 0xa0, 0x20, 0x83, 0x16, 0x5b, 0xab, 0x93, 0x2b, 0xc0,
|
||||
0x93, 0x84, 0x88, 0x04, 0xc8, 0x41, 0x6c, 0xae, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x03,
|
||||
0x00, 0x30, 0x10, 0x00, 0x00, 0x00, 0x01, 0x44, 0x01, 0xc1, 0x73, 0xc0, 0x89, 0x00, 0x00, 0x01,
|
||||
0x28, 0x01, 0xaf, 0x78, 0xf7, 0x04, 0x03, 0xff, 0xdb, 0xa3, 0xff, 0xed, 0x27, 0xd2, 0xf6, 0xc3,
|
||||
0x94, 0x40, 0x83, 0xc0, 0x00, 0x78,
|
||||
};
|
||||
|
||||
inline constexpr std::array<std::uint8_t, 130> Interlaced{
|
||||
0x00, 0x00, 0x00, 0x01, 0x46, 0x01, 0x10, 0x00, 0x00, 0x00, 0x01, 0x40, 0x01, 0x0c, 0x01, 0xff,
|
||||
0xff, 0x04, 0x08, 0x00, 0x00, 0x03, 0x00, 0x4f, 0xa8, 0x00, 0x00, 0x03, 0x00, 0x00, 0x1e, 0xba,
|
||||
0x02, 0x40, 0x00, 0x00, 0x00, 0x01, 0x42, 0x01, 0x01, 0x04, 0x08, 0x00, 0x00, 0x03, 0x00, 0x4f,
|
||||
0xa8, 0x00, 0x00, 0x03, 0x00, 0x00, 0x1e, 0xa0, 0x20, 0x83, 0x16, 0x5b, 0xab, 0x93, 0x2b, 0xc0,
|
||||
0x5a, 0x1a, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x03, 0x00, 0x30, 0x10, 0x00, 0x00, 0x00,
|
||||
0x01, 0x44, 0x01, 0xc1, 0x73, 0xc0, 0x89, 0x00, 0x00, 0x01, 0x4e, 0x01, 0x81, 0x01, 0x0f, 0x80,
|
||||
0x00, 0x00, 0x01, 0x4e, 0x01, 0x01, 0x01, 0x11, 0x80, 0x00, 0x00, 0x01, 0x28, 0x01, 0xaf, 0x78,
|
||||
0xf7, 0x04, 0x03, 0xff, 0xec, 0x02, 0x34, 0x1e, 0x93, 0x2c, 0x7e, 0xf5, 0xd4, 0x40, 0x83, 0xc0,
|
||||
0x00, 0x78,
|
||||
};
|
||||
|
||||
} // namespace HevcMetadataFixture
|
||||
|
||||
#endif
|
||||
@@ -5,16 +5,25 @@
|
||||
- [sceVideodec2InitLibrary](../../../core/libs/prx/libSceVideodec2/Unimplemented.cpp) (libSceVideodec2) throws: the FFmpeg decoders this module creates need no library-level initialisation, and neither SharpProspero nor EVO-PLAYER-PS5 declares this call, so its arguments and result are unknown. A title imports it from this module
|
||||
- [sceVideodec2GetAvcPictureInfo](../../../core/libs/prx/libSceVideodec2/Export.cpp) (libSceVideodec2) - runs the `sceVideodec2GetPictureInfo` body (KytyPS5 binds both NIDs to one function; SharpProspero gives both the same `outputInfo, first, second` signature); the second (bottom field) picture info is never written, since only progressive H.264 output is modelled. prosper calls the AVC layout unestablished (no title observed calling it)
|
||||
|
||||
- [sceVideodec2TermLibrary and twelve other exports](../../../core/libs/prx/libSceVideodec2/Unimplemented.cpp) (libSceVideodec2) - report that they are not implemented; what they do on the console is not known. The others are `QueryLibraryInfo`, `QueryDecoderInfo`, `MapMemory`, `MapDirectMemory`, `PresetDecoderConfigInfo` (plain, `Avc`, `Hevc`, `Vp9`), `CreateDecoderBid`, `CreateHevcDecoder`, `QueryHevcDecoderMemoryInfo` and `GetHevcPictureInfo`. They are exported so that a title which imports them with `sceVideodec2InitLibrary` and calls none of them directly loads. Only `sceVideodec2MapDirectMemory` has a known parameter list, `(decoder, memory)`, from [EVO-PLAYER-PS5's `sce_videodec2.h`](https://github.com/sainsaji/EVO-PLAYER-PS5/blob/9715f6ee9bf9b53461d6fdf9a2e11b4c48357783/projects/evoplayer/media/include/sce/sce_videodec2.h#L173); the others take none here
|
||||
- [sceVideodec2TermLibrary and eleven other exports](../../../core/libs/prx/libSceVideodec2/Unimplemented.cpp) (libSceVideodec2) - report that they are not implemented; what they do on the console is not known. The others are `QueryLibraryInfo`, `QueryDecoderInfo`, `MapMemory`, `MapDirectMemory`, `PresetDecoderConfigInfo` (plain, `Avc`, `Hevc`, `Vp9`), `CreateDecoderBid`, `CreateHevcDecoder` and `QueryHevcDecoderMemoryInfo`. They are exported so that a title which imports them with `sceVideodec2InitLibrary` and calls none of them directly loads. Only `sceVideodec2MapDirectMemory` has a known parameter list, `(decoder, memory)`, from [EVO-PLAYER-PS5's `sce_videodec2.h`](https://github.com/sainsaji/EVO-PLAYER-PS5/blob/9715f6ee9bf9b53461d6fdf9a2e11b4c48357783/projects/evoplayer/media/include/sce/sce_videodec2.h#L173); the others take none here
|
||||
|
||||
### Functional
|
||||
|
||||
- [sceVideodec2Decode and sceVideodec2Flush](../../../core/libs/prx/libSceVideodec2/Export.cpp) (libSceVideodec2): the decoder core's discarded flag is not modelled: a returned picture always has `OutputInfo.isDiscardedFrame` (byte 0xb) at 0, and an unshown VP9 frame returns no picture. Per a #10913 review reading firmware 13.42's module (not reproduced here), the byte is written only with a returned picture and is left as the caller set it otherwise, as here. Name and offset: [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Video/Videodec2.cs#L150-L192) and [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/83f8bfd3c9c35fcb135ef6c33dfd88bbfe066ee2/src/libs/libVideoDec2.cpp#L91-L105); shadPS4 leaves it as padding. PPSA05110 sets only `thisSize` before its VP9 Decode (0x13728dd) and Flush (0x1371f7e) and takes a picture only when the byte is 0
|
||||
|
||||
- [sceVideodec2CreateDecoder](../../../core/libs/prx/libSceVideodec2/Export.cpp) (libSceVideodec2) - One title (PPSA02225) disassembled for this entry calls it once (arguments set up at 0x8b5095-0x8b50a6) with three pointers: a structure built on its stack, a structure inside its decoder object and the address of a field of the same object. A nonzero result makes it leave the decoder setup; on one path (a flag byte set and two 32-bit fields equal to values it computed before) it passes that field to the next Videodec2 call. The codec type it requests and the meaning of the structure fields are not shown.
|
||||
|
||||
- [AVC parameter-set units](../../../core/libs/prx/libSceVideodec2/Export.cpp) (libSceVideodec2) - an H.264 access unit with no slice NAL (types 1 to 5), such as the sequence and picture parameter sets Twispero (PPSA99474) submits on their own before a stream's first slices, is kept and put in front of the next unit that has a slice, and `sceVideodec2Decode` returns 0 with no picture for it, since libavcodec refuses a packet without a slice. What the console returns for such a unit is not known. HEVC units of parameter sets alone are still passed to libavcodec as they are
|
||||
|
||||
- [sceVideodec2CreateDecoder](../../../core/libs/prx/libSceVideodec2/Export.cpp) (libSceVideodec2) decodes HEVC (codec type 974921) and VP9 (2382845) through FFmpeg, besides H.264. The tags come from SharpProspero's `Videodec2CodecType` and EVO-PLAYER-PS5's `sce_videodec2.h`, which records them from hardware runs. HEVC input is converted from length-prefixed NAL units to Annex B as for H.264, and VP9 frames are passed through. Output is NV12 for 8-bit pictures. A 10-bit picture is written as 16-bit little-endian words with the sample in the low 10 bits, Y rows then interleaved UV rows at `framePitchInBytes` times the picture height, with `framePitch` in samples and `framePitchInBytes` in bytes (twice the width, aligned to 256), and `QueryDecoderMemoryInfo` sizes a frame of an HEVC or VP9 profile 2 configuration with two bytes per sample. Evidence is from [EVO-PLAYER-PS5](https://github.com/sainsaji/EVO-PLAYER-PS5/tree/9715f6ee9bf9b53461d6fdf9a2e11b4c48357783), which decodes and presents 1080p HEVC Main10 on hardware through this path (`docs/evo-pro/on-demand-decoder-handoff.md`): it takes a picture as 10-bit when `pitch_bytes >= pitch * 2`, puts UV at `pitch_bytes` times the height and samples with a low-bit factor (`projects/evoplayer/media/src/evo_vdec_native.c`, `ro_harvest`), and its hardware logs give 6128 KB and 24496 KB frame buffers for 1920x1088 and 3840x2176 Main10 decoders after rounding to 16 KiB. The 1080p size is what this formula gives; at 3840x2176 it gives 16 KiB less, so the console's size there has another term that is not known. Unverified: `frameFormat` (still 0; no consumer found reads it), VP9 profile 2 output (no hardware evidence), and the layout of 12-bit pictures and of decoded formats other than yuv420p10le, which throw. `sceVideodec2GetPictureInfo` and `sceVideodec2GetAvcPictureInfo` throw for HEVC pictures (`SceVideodec2HevcPictureInfo` is not filled), and `sceVideodec2GetHevcPictureInfo` reports that it is not implemented. One title (PPSA02225) disassembled for this entry calls it once (arguments set up at 0x8b5095-0x8b50a6) with three pointers: a structure built on its stack, a structure inside its decoder object and the address of a field of the same object. A nonzero result makes it leave the decoder setup; on one path (a flag byte set and two 32-bit fields equal to values it computed before) it passes that field to the next Videodec2 call. The codec type it requests and the meaning of the structure fields are not shown.
|
||||
- [HEVC and VP9 output gaps](../../../core/libs/prx/libSceVideodec2/Export.cpp) (libSceVideodec2): console `frameFormat` remains unknown and is 0; VP9 profile 2 output has no hardware evidence. Twelve-bit pictures and formats other than yuv420p10le throw. HEVC Main10 uses low-bit 16-bit little-endian samples, with interleaved UV after `framePitchInBytes * height`, following [EVO-PLAYER-PS5](https://github.com/sainsaji/EVO-PLAYER-PS5/tree/9715f6ee9bf9b53461d6fdf9a2e11b4c48357783) `projects/evoplayer/media/src/evo_vdec_native.c`, `ro_harvest`: the hardware consumer detects ten bits when byte pitch is at least twice sample pitch and uses a low-bit factor.
|
||||
|
||||
- [HEVC frame-buffer memory sizing](../../../core/libs/prx/libSceVideodec2/Export.cpp) (libSceVideodec2): the console has an unknown extra term at 3840x2176. The two-byte-per-sample formula gives 16 KiB less than the hardware size of 24496 KB after 16 KiB alignment, while matching 6128 KB at 1920x1088. Evidence is in [EVO-PLAYER-PS5 on-demand-decoder-handoff.md](https://github.com/sainsaji/EVO-PLAYER-PS5/blob/9715f6ee9bf9b53461d6fdf9a2e11b4c48357783/docs/evo-pro/on-demand-decoder-handoff.md).
|
||||
|
||||
- [sceVideodec2GetVp9PictureInfo](../../../core/libs/prx/libSceVideodec2/Export.cpp) uses the 88-byte layout and two-pointer signature from [SharpProspero Videodec2.cs](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Video/Videodec2.cs). Frame metadata follows the [VP9 specification](https://storage.googleapis.com/downloads.webmproject.org/docs/vp9/vp9-bitstream-specification-v0.7-20170222-draft.pdf), sections 5.26 and 6.2: profile, key/intra-only flags, bit depth, color space/range, coded/render dimensions and superframe count are retained per emitted buffer. Level is 0 because the elementary stream does not signal a level; the console value is unverified. Null pointers, undersized structures and querying another codec throw; console error codes and support for other structure sizes are unknown. Pictures deeper than 10 bits are rejected. Superframes are submitted as individual coded frames to keep each output associated with its input metadata. `sceVideodec2GetPictureInfo` writes this layout for a VP9 picture, as shadPS4's `videodec2.cpp` writes the decoder's own layout; Mortal Shell 2 (PPSA34008) calls it with `thisSize` 88.
|
||||
- [sceVideodec2Decode after sceVideodec2Flush](../../../core/libs/prx/libSceVideodec2/Export.cpp) (libSceVideodec2) without `sceVideodec2Reset` restarts FFmpeg's decoder (`avcodec_flush_buffers`) and decodes the unit, where it threw before. TEKKEN DARK RESURRECTION (PPSA31371), which runs in Sony's PSP emulator, decodes after a flush without a reset (#7118), so the console accepts the call. Pictures the flush decoded but has not returned yet are returned before the new ones. A flush with no picture pending (each decoded unit has returned its picture) leaves the decoder and its reference pictures as they are, so a stream that continues without a new IDR picture keeps decoding: Daxter (PPSA09695), also in the PSP emulator, flushes after every access unit of its H.264 movies, and restarting the decoder there lost every P picture until the next keyframe. What the console returns for pending pictures, and for a stream that continues across a flush that had pictures pending, is not measured.
|
||||
- [sceVideodec2Decode](../../../core/libs/prx/libSceVideodec2/Export.cpp) (libSceVideodec2) and `sceVideodec2Flush` output the coded picture, with the chroma plane after its luma rows, as the console does for 1080p H.264 and HEVC streams coded at 1920x1088. An HEVC or VP9 picture whose size is not a multiple of 16, such as HEVC coded at 1920x1080, is output at that size; whether the console pads it is unverified. `sceVideodec2GetPictureInfo` reports no frame cropping
|
||||
- [sceVideodec2Decode of an empty access unit](../../../core/libs/prx/libSceVideodec2/Export.cpp) (libSceVideodec2) returns `0x811D010E` for a null `auData` and `0x811D010D` for an `auSize` of 0, with no picture, where it threw before. The values are `ACCESS_UNIT_POINTER` and `ACCESS_UNIT_SIZE` in the error table of [shadPS4's videodec_error.h](https://github.com/shadps4-emu/shadPS4/blob/f3f0c14c1350a768558dfd1fd058d259ccecaca2/src/core/libraries/videodec/videodec_error.h), whose libSceVdecsw codes this repository already uses; they are not measured on a PS5, and neither is whether the console leaves the decoder's state alone on that call, as this does. Media Player Classic PS5 (PPSA99015) submits empty units: its own converter reads a 4-byte length prefix of `00 00 01 xx` as a start code and drops the rest as a non-base HEVC layer. It goes on after the error.
|
||||
- [sceVideodec2Decode and sceVideodec2Flush](../../../core/libs/prx/libSceVideodec2/Export.cpp) (libSceVideodec2): the decoder core's discarded flag is not modelled: a returned picture always has `OutputInfo.isDiscardedFrame` (byte 0xb) at 0, and an unshown VP9 frame returns no picture. Per a #10913 review reading firmware 13.42's module (not reproduced here), the byte is written only with a returned picture and is left as the caller set it otherwise, as here. Name and offset: [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Video/Videodec2.cs#L150-L192) and [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/83f8bfd3c9c35fcb135ef6c33dfd88bbfe066ee2/src/libs/libVideoDec2.cpp#L91-L105); shadPS4 leaves it as padding. PPSA05110 sets only `thisSize` before its VP9 Decode (0x13728dd) and Flush (0x1371f7e) and takes a picture only when the byte is 0
|
||||
|
||||
- [HEVC picture metadata](../../../core/libs/prx/libSceVideodec2/HevcMetadata.hpp): exact PS5 field values and error codes remain unverified. It uses the complete 184-byte layout from [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Video/Videodec2.cs) and syntax from ITU-T H.265 (09/2023). The typed HEVC and generic getters associate each emitted picture with its submitted access-unit metadata, including delayed and flushed pictures. The typed AVC getter still rejects HEVC. Invalid arguments, undersized structures and cross-codec typed queries throw; an unowned picture only clears isValid after size validation.
|
||||
|
||||
- [Unsupported HEVC metadata projections](../../../core/libs/prx/libSceVideodec2/HevcMetadata.cpp): crop/default-display windows, temporal sublayers, multilayer streams, SPS extensions, VPS-only timing, unavailable parameter sets, unsupported SEI/NAL syntax and ambiguous picture-timing association throw at query time before output writes. Differing parameter sets under an existing ID and malformed parameter histories keep queries unsupported through Reset until decoder recreation, since FFmpeg retains parameter state. Alternative-transfer persistence follows CLVS boundaries; conflicts, pictureless transfer messages and ambiguous continuation after reset/flush reject metadata. Unknown crop units, singular sublayer selection and console handling of these cases are not guessed. Decode behavior is retained when metadata cannot be projected.
|
||||
Reference in new issue
Block a user