Merge pull request #7778 from YossiYad/fix/videodec2-avcc-length-startcode

fix(libSceVideodec2): frame an access unit whose length prefix looks like a start code
This commit is contained in:
Dean authored and GitHub committed 2026-10-20 15:52:57 +13:00
commit 4a7fd7fa4a
2 files changed
+136 -11

No files matched your search

+37 -11
View File
@@ -305,6 +305,8 @@ struct Decoder {
std::deque<FramePointer> ready;
std::vector<std::uint8_t> annexB;
std::vector<std::uint8_t> parameterSets;
bool lengthPrefixed = false;
bool shortStartCodes = false;
bool flushing = false;
std::array<Vp9PictureInfo, 8> vp9References{};
Vp9PictureInfo vp9Colors{};
@@ -381,21 +383,45 @@ void OpenCodec(Decoder& decoder) {
}
}
const std::vector<std::uint8_t>* ToAnnexB(Decoder& decoder, const std::uint8_t* data, std::size_t size) {
if (size >= 4 && data[0] == 0 && data[1] == 0 && (data[2] == 1 || (data[2] == 0 && data[3] == 1))) {
decoder.annexB.assign(data, data + size);
return &decoder.annexB;
}
decoder.annexB.clear();
bool TilesAsAvcc(const std::uint8_t* data, std::size_t size) {
if (size < 4) return false;
for (std::size_t index = 0; index < size;) {
if (size - index < 4) throw std::runtime_error("Videodec2: truncated NAL length");
if (size - index < 4) return false;
const std::size_t length = (static_cast<std::size_t>(data[index]) << 24u) | (static_cast<std::size_t>(data[index + 1]) << 16u) | (static_cast<std::size_t>(data[index + 2]) << 8u) | data[index + 3];
if (length == 0 || length > size - index - 4) throw std::runtime_error("Videodec2: invalid NAL length");
static constexpr std::uint8_t startCode[4] = {0, 0, 0, 1};
decoder.annexB.insert(decoder.annexB.end(), startCode, startCode + 4);
decoder.annexB.insert(decoder.annexB.end(), data + index + 4, data + index + 4 + length);
if (length == 0 || length > size - index - 4) return false;
index += 4 + length;
}
return true;
}
bool StartsWithStartCode(const std::uint8_t* data, std::size_t size) {
return size >= 3 && data[0] == 0 && data[1] == 0 && (data[2] == 1 || (size >= 4 && data[2] == 0 && data[3] == 1));
}
const std::vector<std::uint8_t>* ToAnnexB(Decoder& decoder, const std::uint8_t* data, std::size_t size) {
const bool tiles = TilesAsAvcc(data, size);
const bool opensWithStartCode = StartsWithStartCode(data, size);
if (!tiles && !opensWithStartCode) throw std::runtime_error("Videodec2: access unit is neither Annex-B nor length-prefixed");
const bool shortStartCode = opensWithStartCode && data[2] == 1;
bool lengthPrefixed = tiles;
if (tiles && opensWithStartCode) {
lengthPrefixed = shortStartCode ? !decoder.shortStartCodes : decoder.lengthPrefixed;
} else {
decoder.lengthPrefixed = tiles;
if (shortStartCode) decoder.shortStartCodes = true;
}
if (lengthPrefixed) {
decoder.annexB.clear();
for (std::size_t index = 0; index < size;) {
const std::size_t length = (static_cast<std::size_t>(data[index]) << 24u) | (static_cast<std::size_t>(data[index + 1]) << 16u) | (static_cast<std::size_t>(data[index + 2]) << 8u) | data[index + 3];
static constexpr std::uint8_t startCode[4] = {0, 0, 0, 1};
decoder.annexB.insert(decoder.annexB.end(), startCode, startCode + 4);
decoder.annexB.insert(decoder.annexB.end(), data + index + 4, data + index + 4 + length);
index += 4 + length;
}
return &decoder.annexB;
}
decoder.annexB.assign(data, data + size);
return &decoder.annexB;
}
+99
View File
@@ -596,6 +596,103 @@ void testDecode(bool lengthPrefixed, bool separateParameters = false) {
checkThrows([&] { sceVideodec2DeleteDecoder_nid_postfix(handle); });
}
void decodeInOrder(const std::vector<std::vector<std::uint8_t>>& units) {
const DecoderConfigInfo config{sizeof(DecoderConfigInfo), 0, 1, 100, 0, static_cast<std::int32_t>(Width), static_cast<std::int32_t>(Height), 4, 1};
DecoderMemoryInfo memory{sizeof(DecoderMemoryInfo)};
check(sceVideodec2QueryDecoderMemoryInfo_nid_postfix(&config, &memory) == 0, "memory query failed");
std::uint64_t handle = 0;
check(sceVideodec2CreateDecoder_nid_postfix(&config, &memory, &handle) == 0, "decoder creation failed");
std::vector<std::uint8_t> buffer(memory.maxFrameBufferSize);
std::size_t pictures = 0;
const auto take = [&](const OutputInfo& output) {
if (!output.isValid) return false;
check(pictures < PictureHashes.size(), "more pictures than access units");
check(!output.isErrorFrame && output.frameWidth == Width && output.frameHeight == CodedHeight, "unexpected picture geometry");
check(hashNv12(buffer.data(), output.framePitch, output.frameHeight) == PictureHashes[pictures], "picture " + std::to_string(pictures) + " differs from the reference");
++pictures;
return true;
};
for (std::size_t unit = 0; unit < units.size(); ++unit) {
const InputData input{sizeof(InputData), units[unit].data(), units[unit].size(), 1000 + unit, unit, 0xa0 + unit};
FrameBuffer frame{sizeof(FrameBuffer), buffer.data(), buffer.size(), false};
OutputInfo output{sizeof(OutputInfo)};
check(sceVideodec2Decode_nid_postfix(handle, &input, &frame, &output) == 0, "decode failed");
take(output);
}
for (;;) {
FrameBuffer frame{sizeof(FrameBuffer), buffer.data(), buffer.size(), false};
OutputInfo output{sizeof(OutputInfo)};
check(sceVideodec2Flush_nid_postfix(handle, &frame, &output) == 0, "flush failed");
if (!take(output)) break;
}
check(pictures == PictureHashes.size(), "missing pictures after flush");
check(sceVideodec2DeleteDecoder_nid_postfix(handle) == 0, "delete failed");
}
bool readsAsOneLengthPrefix(const std::vector<std::uint8_t>& unit) {
const std::size_t length = (static_cast<std::size_t>(unit[0]) << 24u) | (static_cast<std::size_t>(unit[1]) << 16u) | (static_cast<std::size_t>(unit[2]) << 8u) | unit[3];
return length + 4 == unit.size();
}
std::vector<std::uint8_t> lengthPrefixedFiller(std::size_t length) {
std::vector<std::uint8_t> filler;
for (int shift = 24; shift >= 0; shift -= 8) filler.push_back(static_cast<std::uint8_t>(length >> shift));
filler.push_back(0x0c);
filler.insert(filler.end(), length - 1, 0xff);
return filler;
}
void testLengthPrefixLooksLikeStartCode() {
auto units = accessUnits(true);
const auto filler = lengthPrefixedFiller(0x101);
check(!(units[0][0] == 0 && units[0][1] == 0 && (units[0][2] == 1 || units[0][3] == 1)), "the first length-prefixed unit already begins with start-code bytes");
units[1].insert(units[1].begin(), filler.begin(), filler.end());
check(units[1][0] == 0 && units[1][1] == 0 && units[1][2] == 1, "the crafted unit does not begin with start-code bytes");
decodeInOrder(units);
auto mixed = accessUnits(false);
const auto mixedFiller = lengthPrefixedFiller(0x123);
mixed[1] = accessUnits(true)[1];
mixed[1].insert(mixed[1].begin(), mixedFiller.begin(), mixedFiller.end());
check(mixed[0][2] == 0 && mixed[0][3] == 1 && mixed[1][0] == 0 && mixed[1][1] == 0 && mixed[1][2] == 1, "the length-prefixed unit does not follow an Annex-B unit with a four-byte start code");
decodeInOrder(mixed);
}
void testStartCodeLooksLikeLengthPrefix() {
auto units = accessUnits(false);
for (auto& unit : units) {
check(unit[0] == 0 && unit[1] == 0 && unit[2] == 0 && unit[3] == 1, "an access unit does not open with a four-byte start code");
unit.erase(unit.begin());
}
auto& first = units.front();
std::size_t sequence = 0;
std::size_t afterPicture = 0;
bool picture = false;
for (std::size_t index = 0; index + 3 < first.size(); ++index) {
if (first[index] != 0 || first[index + 1] != 0 || first[index + 2] != 1) continue;
const auto type = first[index + 3] & 0x1fu;
if (picture) {
afterPicture = first[index - 1] == 0 ? index - 1 : index;
break;
}
if (type == 7 && sequence == 0) sequence = index;
if (type == 8 && sequence != 0) picture = true;
index += 2;
}
check(sequence != 0 && afterPicture > sequence, "the first access unit has no SPS and PPS before another NAL");
const std::vector<std::uint8_t> delimiter(first.begin(), first.begin() + static_cast<std::ptrdiff_t>(first[sequence - 1] == 0 ? sequence - 1 : sequence));
check(delimiter.size() >= 4 && delimiter[2] == 1 && !readsAsOneLengthPrefix(delimiter), "the access unit delimiter is not an unambiguous unit with a three-byte start code");
std::vector<std::uint8_t> parameters(first.begin() + static_cast<std::ptrdiff_t>(sequence), first.begin() + static_cast<std::ptrdiff_t>(afterPicture));
first.erase(first.begin(), first.begin() + static_cast<std::ptrdiff_t>(afterPicture));
if (first[2] == 0) first.erase(first.begin());
const std::size_t padded = 4 + ((std::size_t{1} << 8u) | parameters[3]);
check(parameters.size() < padded, "the parameter sets are too long to pad");
parameters.resize(padded, 0);
check(readsAsOneLengthPrefix(parameters), "the padded parameter sets do not read as one length-prefixed NAL");
units.insert(units.begin(), {delimiter, parameters});
decodeInOrder(units);
}
std::uint64_t createDecoder(std::uint32_t codec, std::uint32_t profile) {
const DecoderConfigInfo config{sizeof(DecoderConfigInfo), 1, codec, profile, 0, static_cast<std::int32_t>(Width), static_cast<std::int32_t>(Height), 4, 1};
DecoderMemoryInfo memory{sizeof(DecoderMemoryInfo)};
@@ -804,6 +901,8 @@ int main() {
testFailures();
testDecode(false);
testDecode(true);
testLengthPrefixLooksLikeStartCode();
testStartCodeLooksLikeLengthPrefix();
testDecode(false, true);
const auto hevc = hevcUnits(HevcStream.data(), HevcStream.size());
testClip(CodecHevc, 1, hevc, HevcHashes);