mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-30 12:25:49 +00:00
Fill the 120-byte SceVideodec2AvcPictureInfo for a single owned H.264 picture queried with thisSize 0x78: SPS fields, VUI, pic_struct from the picture-timing SEI and per-unit presence flags, parsed per ITU-T H.264 (08/2021) from the access unit that produced the picture. Layout from SharpProspero Videodec2.cs and shadPS4 videodec2.h. Other accepted sizes, two records and unsupported syntax still throw after the validity clears. Co-Authored-By: Claude Opus 5.5 <[email protected]>
370 lines
17 KiB
C++
370 lines
17 KiB
C++
#include "AvcMetadata.hpp"
|
|
#include <algorithm>
|
|
#include <initializer_list>
|
|
#include <limits>
|
|
#include <stdexcept>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
namespace {
|
|
|
|
constexpr std::size_t SliceHeaderBytes = 32;
|
|
|
|
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 H.264 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 H.264 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("H.264 exponential code overflow");
|
|
const auto value = ((std::uint32_t{1} << zeros) - 1) + Read(zeros);
|
|
if (value > maximum) throw std::runtime_error("H.264 syntax value outside its domain");
|
|
return value;
|
|
}
|
|
|
|
std::int64_t Signed() {
|
|
const std::int64_t code = Unsigned();
|
|
return (code & 1) != 0 ? (code + 1) / 2 : -(code / 2);
|
|
}
|
|
|
|
private:
|
|
std::span<const std::uint8_t> bytes;
|
|
std::size_t position = 0;
|
|
};
|
|
|
|
struct Slice {
|
|
unsigned pps = 0;
|
|
std::uint32_t frameNum = 0;
|
|
std::uint32_t field = 0;
|
|
std::uint32_t bottom = 0;
|
|
bool idr = false;
|
|
bool reference = false;
|
|
|
|
bool operator==(const Slice&) const = default;
|
|
};
|
|
|
|
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 H.264 emulation prevention");
|
|
zeros = 0;
|
|
continue;
|
|
}
|
|
if (zeros == 2 && value < 3) throw std::runtime_error("missing H.264 emulation prevention");
|
|
bytes.push_back(value);
|
|
zeros = value == 0 ? std::min(zeros + 1, 2u) : 0;
|
|
}
|
|
return bytes;
|
|
}
|
|
|
|
void ScalingList(Bits& bits, unsigned size) {
|
|
std::int64_t last = 8;
|
|
for (unsigned index = 0; index < size; ++index) {
|
|
const auto delta = bits.Signed();
|
|
if (delta < -128 || delta > 127) throw std::runtime_error("H.264 scaling list delta outside its domain");
|
|
const auto next = (last + delta + 256) % 256;
|
|
if (next == 0) return;
|
|
last = next;
|
|
}
|
|
}
|
|
|
|
std::pair<unsigned, unsigned> Hrd(Bits& bits) {
|
|
const auto count = bits.Unsigned(31) + 1;
|
|
bits.Skip(8);
|
|
for (unsigned entry = 0; entry < count; ++entry) {
|
|
bits.Unsigned();
|
|
bits.Unsigned();
|
|
bits.Skip(1);
|
|
}
|
|
bits.Skip(5);
|
|
const auto removal = bits.Read(5) + 1;
|
|
const auto output = bits.Read(5) + 1;
|
|
bits.Skip(5);
|
|
return {removal, output};
|
|
}
|
|
|
|
void Vui(Bits& bits, Videodec2Avc::Sps& sps) {
|
|
auto& info = sps.info;
|
|
info.aspectRatioInfoPresentFlag = static_cast<std::uint8_t>(bits.Read(1));
|
|
if (info.aspectRatioInfoPresentFlag) {
|
|
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 if (info.aspectRatioIdc <= 16) {
|
|
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}};
|
|
info.sarWidth = ratios[info.aspectRatioIdc][0];
|
|
info.sarHeight = ratios[info.aspectRatioIdc][1];
|
|
}
|
|
}
|
|
if (bits.Read(1) != 0) bits.Skip(1);
|
|
info.videoSignalTypePresentFlag = static_cast<std::uint8_t>(bits.Read(1));
|
|
if (info.videoSignalTypePresentFlag) {
|
|
info.videoFormat = static_cast<std::uint8_t>(bits.Read(3));
|
|
info.videoFullRangeFlag = static_cast<std::uint8_t>(bits.Read(1));
|
|
info.colourDescriptionPresentFlag = static_cast<std::uint8_t>(bits.Read(1));
|
|
if (info.colourDescriptionPresentFlag) {
|
|
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));
|
|
}
|
|
}
|
|
if (bits.Read(1) != 0) {
|
|
bits.Unsigned(5);
|
|
bits.Unsigned(5);
|
|
}
|
|
info.timingInfoPresentFlag = static_cast<std::uint8_t>(bits.Read(1));
|
|
if (info.timingInfoPresentFlag) {
|
|
info.numUnitsInTick = bits.Read(32);
|
|
info.timeScale = bits.Read(32);
|
|
info.fixedFrameRateFlag = static_cast<std::uint8_t>(bits.Read(1));
|
|
}
|
|
for (unsigned kind = 0; kind < 2; ++kind) {
|
|
if (bits.Read(1) == 0) continue;
|
|
const auto delays = Hrd(bits);
|
|
if (sps.delays && *sps.delays != delays) sps.unsupported = "differing H.264 NAL and VCL HRD delay lengths";
|
|
sps.delays = delays;
|
|
}
|
|
if (sps.delays) bits.Skip(1);
|
|
info.picStructPresentFlag = static_cast<std::uint8_t>(bits.Read(1));
|
|
info.bitstreamRestrictionFlag = static_cast<std::uint8_t>(bits.Read(1));
|
|
if (info.bitstreamRestrictionFlag) {
|
|
bits.Skip(1);
|
|
for (unsigned value = 0; value < 5; ++value) bits.Unsigned();
|
|
const auto buffering = bits.Unsigned();
|
|
if (buffering > 16) sps.unsupported = "H.264 max_dec_frame_buffering above 16";
|
|
info.maxDecFrameBuffering = static_cast<std::uint8_t>(std::min(buffering, 16u));
|
|
}
|
|
}
|
|
|
|
std::uint32_t MaxDpbMbs(const Videodec2Avc::PictureInfo& info) {
|
|
const unsigned level = info.levelIdc;
|
|
if (level == 9 || (level == 11 && info.constraintSet3Flag && (info.profileIdc == 66 || info.profileIdc == 77 || info.profileIdc == 88))) return 396;
|
|
constexpr std::pair<unsigned, std::uint32_t> limits[] = {{10, 396}, {11, 900}, {12, 2376}, {13, 2376}, {20, 2376}, {21, 4752}, {22, 8100}, {30, 8100}, {31, 18000}, {32, 20480},
|
|
{40, 32768}, {41, 32768}, {42, 34816}, {50, 110400}, {51, 184320}, {52, 184320}, {60, 696320}, {61, 696320}, {62, 696320}};
|
|
for (const auto& [idc, mbs] : limits) if (idc == level) return mbs;
|
|
return 0;
|
|
}
|
|
|
|
std::pair<unsigned, Videodec2Avc::Sps> ParseSps(Bits& bits) {
|
|
Videodec2Avc::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;
|
|
info.profileIdc = static_cast<std::uint8_t>(bits.Read(8));
|
|
for (auto* flag : {&info.constraintSet0Flag, &info.constraintSet1Flag, &info.constraintSet2Flag, &info.constraintSet3Flag, &info.constraintSet4Flag, &info.constraintSet5Flag})
|
|
*flag = static_cast<std::uint8_t>(bits.Read(1));
|
|
bits.Skip(2);
|
|
info.levelIdc = static_cast<std::uint8_t>(bits.Read(8));
|
|
const auto id = bits.Unsigned(31);
|
|
const auto profile = info.profileIdc;
|
|
if (profile == 100 || profile == 110 || profile == 122 || profile == 244 || profile == 44 || profile == 83 || profile == 86 || profile == 118 || profile == 128 || profile == 138 || profile == 139 || profile == 134 || profile == 135) {
|
|
const auto chroma = bits.Unsigned(3);
|
|
if (chroma == 3) sps.separateColourPlanes = bits.Read(1) != 0;
|
|
bits.Unsigned(6);
|
|
bits.Unsigned(6);
|
|
bits.Skip(1);
|
|
if (bits.Read(1) != 0)
|
|
for (unsigned list = 0; list < (chroma != 3 ? 8u : 12u); ++list)
|
|
if (bits.Read(1) != 0) ScalingList(bits, list < 6 ? 16 : 64);
|
|
}
|
|
sps.frameNumBits = bits.Unsigned(12) + 4;
|
|
const auto order = bits.Unsigned(2);
|
|
if (order == 0) bits.Unsigned(12);
|
|
else if (order == 1) {
|
|
bits.Skip(1);
|
|
bits.Signed();
|
|
bits.Signed();
|
|
const auto cycle = bits.Unsigned(255);
|
|
for (unsigned frame = 0; frame < cycle; ++frame) bits.Signed();
|
|
}
|
|
bits.Unsigned();
|
|
bits.Skip(1);
|
|
info.picWidthInMbsMinus1 = bits.Unsigned();
|
|
info.picHeightInMapUnitsMinus1 = bits.Unsigned();
|
|
info.frameMbsOnlyFlag = static_cast<std::uint8_t>(bits.Read(1));
|
|
if (!info.frameMbsOnlyFlag) bits.Skip(1);
|
|
bits.Skip(1);
|
|
info.frameCroppingFlag = static_cast<std::uint8_t>(bits.Read(1));
|
|
if (info.frameCroppingFlag) {
|
|
info.frameCropLeftOffset = bits.Unsigned();
|
|
info.frameCropRightOffset = bits.Unsigned();
|
|
info.frameCropTopOffset = bits.Unsigned();
|
|
info.frameCropBottomOffset = bits.Unsigned();
|
|
}
|
|
if (bits.Read(1) != 0) Vui(bits, sps);
|
|
if (!info.bitstreamRestrictionFlag) {
|
|
const bool intra = info.constraintSet3Flag && (profile == 44 || profile == 86 || profile == 100 || profile == 110 || profile == 122 || profile == 244);
|
|
const auto mbs = MaxDpbMbs(info);
|
|
if (!intra && mbs == 0) sps.unsupported = "H.264 level outside Table A-1";
|
|
const auto frames = std::uint64_t{mbs} / (std::uint64_t{info.picWidthInMbsMinus1} + 1) / ((2u - info.frameMbsOnlyFlag) * (std::uint64_t{info.picHeightInMapUnitsMinus1} + 1));
|
|
info.maxDecFrameBuffering = intra ? 0 : static_cast<std::uint8_t>(std::min<std::uint64_t>(frames, 16));
|
|
}
|
|
return {id, std::move(sps)};
|
|
}
|
|
|
|
void Sei(std::span<const std::uint8_t> bytes, Videodec2Avc::PictureInfo& flags, 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 H.264 SEI header");
|
|
const auto next = bytes[position++];
|
|
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 H.264 SEI payload");
|
|
if (type == 0) flags.bufferingPeriodSeiPresentFlag = 1;
|
|
else if (type == 1) {
|
|
if (flags.pictureTimingSeiPresentFlag) throw std::runtime_error("multiple H.264 picture-timing messages");
|
|
flags.pictureTimingSeiPresentFlag = 1;
|
|
timing.assign(bytes.begin() + position, bytes.begin() + position + size);
|
|
}
|
|
position += size;
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
namespace Videodec2Avc {
|
|
|
|
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<Slice> first;
|
|
std::optional<Sps> selected;
|
|
std::vector<std::uint8_t> timing;
|
|
bool sliced = 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;
|
|
const unsigned type = end > start ? annexB[start] & 0x1f : 0;
|
|
try {
|
|
if (end == start || (annexB[start] & 0x80) != 0) throw std::runtime_error("invalid H.264 NAL header");
|
|
if (sliced && type >= 6 && type <= 9 && metadata.unsupported.empty()) metadata.unsupported = "H.264 SEI, parameter set or delimiter after the slices of an access unit is unsupported";
|
|
const bool slice = type == 1 || type == 5;
|
|
sliced = sliced || slice;
|
|
const auto payload = annexB.subspan(start + 1, end - start - 1);
|
|
const auto bytes = Rbsp(slice ? payload.first(std::min(payload.size(), SliceHeaderBytes)) : payload);
|
|
Bits bits(bytes);
|
|
if (type == 7) {
|
|
flags.sequenceParameterSetPresentFlag = 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 = "H.264 SPS replacement metadata is unsupported until decoder recreation";
|
|
}
|
|
state.sps[id] = std::move(sps);
|
|
} else if (type == 8) {
|
|
flags.pictureParameterSetPresentFlag = 1;
|
|
const auto id = bits.Unsigned(255);
|
|
Pps pps{bits.Unsigned(31), bytes, {}};
|
|
if (state.pps[id]) {
|
|
pps.unsupported = state.pps[id]->unsupported;
|
|
if (state.pps[id]->bytes != bytes) pps.unsupported = "H.264 PPS replacement metadata is unsupported until decoder recreation";
|
|
}
|
|
state.pps[id] = std::move(pps);
|
|
} else if (type == 6) Sei(bytes, flags, timing);
|
|
else if (type == 9) {
|
|
if (flags.auDelimiterPresentFlag) throw std::runtime_error("multiple H.264 access unit delimiters");
|
|
flags.auDelimiterPresentFlag = 1;
|
|
} else if (type == 10) flags.endOfSequencePresentFlag = 1;
|
|
else if (type == 11) flags.endOfStreamPresentFlag = 1;
|
|
else if (type == 12) flags.fillerDataPresentFlag = 1;
|
|
else if (slice) {
|
|
const auto firstMb = bits.Unsigned();
|
|
bits.Unsigned(9);
|
|
const auto ppsId = bits.Unsigned(255);
|
|
if (!state.pps[ppsId]) throw std::runtime_error("H.264 picture parameter set is unavailable");
|
|
const auto& pps = *state.pps[ppsId];
|
|
if (!state.sps[pps.sps]) throw std::runtime_error("H.264 sequence parameter set is unavailable");
|
|
const auto& sps = *state.sps[pps.sps];
|
|
if (sps.separateColourPlanes) bits.Skip(2);
|
|
Slice current{ppsId, bits.Read(sps.frameNumBits), 0, 0, type == 5, (annexB[start] & 0x60) != 0};
|
|
if (!sps.info.frameMbsOnlyFlag) {
|
|
current.field = bits.Read(1);
|
|
if (current.field) current.bottom = bits.Read(1);
|
|
}
|
|
if (first) {
|
|
if (firstMb == 0 || current != *first) throw std::runtime_error("several H.264 primary pictures or mixed slice types in one access unit");
|
|
} else {
|
|
first = current;
|
|
selected = sps;
|
|
if (!pps.unsupported.empty() && metadata.unsupported.empty()) metadata.unsupported = pps.unsupported;
|
|
}
|
|
} else throw std::runtime_error("H.264 NAL unit type " + std::to_string(type) + " metadata is unsupported");
|
|
} catch (const std::runtime_error& error) {
|
|
if (metadata.unsupported.empty()) metadata.unsupported = error.what();
|
|
if ((type == 7 || type == 8) && state.unsupported.empty()) state.unsupported = error.what();
|
|
}
|
|
}
|
|
if (!selected) {
|
|
if (metadata.unsupported.empty()) metadata.unsupported = "H.264 access unit carries no picture";
|
|
return metadata;
|
|
}
|
|
auto& info = metadata.info;
|
|
info = selected->info;
|
|
if (!selected->unsupported.empty() && metadata.unsupported.empty()) metadata.unsupported = selected->unsupported;
|
|
if (first->field && metadata.unsupported.empty()) metadata.unsupported = "H.264 field picture metadata is unsupported";
|
|
info.idrPictureFlag = first->idr;
|
|
info.fieldPicFlag = static_cast<std::uint8_t>(first->field);
|
|
info.bottomFieldFlag = static_cast<std::uint8_t>(first->bottom);
|
|
if (info.picStructPresentFlag) {
|
|
try {
|
|
if (!flags.pictureTimingSeiPresentFlag) throw std::runtime_error("H.264 picture timing is unavailable");
|
|
Bits bits(timing);
|
|
if (selected->delays) bits.Skip(std::size_t{selected->delays->first} + selected->delays->second);
|
|
info.picStruct = static_cast<std::uint8_t>(bits.Read(4));
|
|
if (info.picStruct > 8) throw std::runtime_error("reserved H.264 picture structure");
|
|
} catch (const std::runtime_error& error) {
|
|
if (metadata.unsupported.empty()) metadata.unsupported = error.what();
|
|
}
|
|
}
|
|
info.sequenceParameterSetPresentFlag = flags.sequenceParameterSetPresentFlag;
|
|
info.pictureParameterSetPresentFlag = flags.pictureParameterSetPresentFlag;
|
|
info.auDelimiterPresentFlag = flags.auDelimiterPresentFlag;
|
|
info.endOfSequencePresentFlag = flags.endOfSequencePresentFlag;
|
|
info.endOfStreamPresentFlag = flags.endOfStreamPresentFlag;
|
|
info.fillerDataPresentFlag = flags.fillerDataPresentFlag;
|
|
info.pictureTimingSeiPresentFlag = flags.pictureTimingSeiPresentFlag;
|
|
info.bufferingPeriodSeiPresentFlag = flags.bufferingPeriodSeiPresentFlag;
|
|
return metadata;
|
|
}
|
|
|
|
} // namespace Videodec2Avc
|