tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit ddafaa057aca13f8875b3a4b8f33fd4aab7dfea8
parent 57c1ff3d5f4c7f5a5bd559c09352bef51aecdfe1
Author: Andrew Osmond <aosmond@gmail.com>
Date:   Fri,  2 Jan 2026 03:48:20 +0000

Bug 2008270 - Consolidate encoder functionality that extracts AVCC extradata from AnnexB samples. r=media-playback-reviewers,jolin,alwu

This will be useful for FFmpegVideoEncoder on Android which needs to do
a similar function. This patch also makes the extraction/transformation
a bit safer by validating the position of the SPS/PPS NAL units before
extracting the necessary information.

Differential Revision: https://phabricator.services.mozilla.com/D277717

Diffstat:
Mdom/media/gtest/TestMediaDataEncoder.cpp | 8++++----
Mdom/media/platforms/agnostic/bytestreams/AnnexB.cpp | 54+++++++++++++++++++++++++++++++++++++++++++++++++-----
Mdom/media/platforms/agnostic/bytestreams/AnnexB.h | 9++++++++-
Mdom/media/platforms/agnostic/bytestreams/gtest/TestByteStreams.cpp | 51++++++++++++++++++++++++++++++++++++++++++---------
Mdom/media/platforms/android/AndroidDataEncoder.cpp | 26++++++++++----------------
Mdom/media/platforms/wmf/WMFDataEncoderUtils.cpp | 22++++------------------
6 files changed, 117 insertions(+), 53 deletions(-)

diff --git a/dom/media/gtest/TestMediaDataEncoder.cpp b/dom/media/gtest/TestMediaDataEncoder.cpp @@ -426,7 +426,7 @@ static void H264EncodesTest(Usage aUsage, GET_OR_RETURN_ON_ERROR(Encode(e, 1UL, aFrameSource)); EXPECT_EQ(output.Length(), 1UL); EXPECT_TRUE(isAVCC ? AnnexB::IsAVCC(output[0]) - : AnnexB::IsAnnexB(output[0])); + : AnnexB::IsAnnexB(*output[0])); WaitForShutdown(e); output.Clear(); @@ -461,7 +461,7 @@ static void H264EncodesTest(Usage aUsage, } } else { for (auto frame : output) { - EXPECT_TRUE(AnnexB::IsAnnexB(frame)); + EXPECT_TRUE(AnnexB::IsAnnexB(*frame)); } } @@ -549,7 +549,7 @@ static void H264EncodeBatchTest( } } else { for (auto frame : output) { - EXPECT_TRUE(AnnexB::IsAnnexB(frame)); + EXPECT_TRUE(AnnexB::IsAnnexB(*frame)); } } @@ -740,7 +740,7 @@ TEST_F(MediaDataEncoderTest, H264AVCC) { GET_OR_RETURN_ON_ERROR(Encode(e, NUM_FRAMES, mData)); EXPECT_EQ(output.Length(), NUM_FRAMES); for (auto frame : output) { - EXPECT_FALSE(AnnexB::IsAnnexB(frame)); + EXPECT_FALSE(AnnexB::IsAnnexB(*frame)); if (frame->mKeyframe) { // The extradata may be included at the beginning, whenever it changes, // or with every keyframe to support robust seeking or decoder resets. diff --git a/dom/media/platforms/agnostic/bytestreams/AnnexB.cpp b/dom/media/platforms/agnostic/bytestreams/AnnexB.cpp @@ -334,6 +334,19 @@ void AnnexB::ParseNALEntries(const Span<const uint8_t>& aSpan, } /* static */ +size_t AnnexB::FindNalType(const Span<const uint8_t>& aSpan, + const nsTArray<AnnexB::NALEntry>& aNalEntries, + NAL_TYPES aType, size_t aStartIndex) { + for (size_t i = aStartIndex; i < aNalEntries.Length(); ++i) { + uint8_t nalUnitType = aSpan[aNalEntries[i].mOffset] & 0x1f; + if (nalUnitType == aType) { + return i; + } + } + return SIZE_MAX; +} + +/* static */ bool AnnexB::FindAllNalTypes(const Span<const uint8_t>& aSpan, const nsTArray<NAL_TYPES>& aTypes) { nsTArray<AnnexB::NALEntry> nalEntries; @@ -382,12 +395,43 @@ static Result<mozilla::Ok, nsresult> ParseNALUnits(ByteWriter<BigEndian>& aBw, return Ok(); } +/* static */ +RefPtr<MediaByteBuffer> AnnexB::ExtractExtraDataForAVCC( + const Span<const uint8_t>& aSpan) { + if (!IsAnnexB(aSpan)) { + return nullptr; + } + + nsTArray<NALEntry> paramSets; + ParseNALEntries(aSpan, paramSets); + + size_t spsIndex = + FindNalType(aSpan, paramSets, H264_NAL_SPS, /* aStartIndex */ 0); + if (spsIndex == SIZE_MAX) { + return nullptr; + } + + size_t ppsIndex = + FindNalType(aSpan, paramSets, H264_NAL_PPS, /* aStartIndex */ 0); + if (ppsIndex == SIZE_MAX) { + return nullptr; + } + + auto avcc = MakeRefPtr<MediaByteBuffer>(); + const auto& spsEntry = paramSets.ElementAt(spsIndex); + const auto& ppsEntry = paramSets.ElementAt(ppsIndex); + const auto sps = aSpan.Subspan(spsEntry.mOffset, spsEntry.mSize); + const auto pps = aSpan.Subspan(ppsEntry.mOffset, ppsEntry.mSize); + H264::WriteExtraData(avcc, sps[1], sps[2], sps[3], sps, pps); + return avcc; +} + bool AnnexB::ConvertSampleToAVCC(mozilla::MediaRawData* aSample, const RefPtr<MediaByteBuffer>& aAVCCHeader) { if (IsAVCC(aSample)) { return ConvertAVCCTo4BytesAVCC(aSample).isOk(); } - if (!IsAnnexB(aSample)) { + if (!IsAnnexB(*aSample)) { // Not AnnexB, nothing to convert. return true; } @@ -433,7 +477,7 @@ Result<mozilla::Ok, nsresult> AnnexB::ConvertSampleToHVCC( if (IsHVCC(aSample)) { return ConvertHVCCTo4BytesHVCC(aSample); } - if (!IsAnnexB(aSample)) { + if (!IsAnnexB(*aSample)) { // Not AnnexB, nothing to convert. return Ok(); } @@ -489,11 +533,11 @@ bool AnnexB::IsHVCC(const mozilla::MediaRawData* aSample) { } /* static */ -bool AnnexB::IsAnnexB(const mozilla::MediaRawData* aSample) { - if (aSample->Size() < 4) { +bool AnnexB::IsAnnexB(const Span<const uint8_t>& aSpan) { + if (aSpan.Length() < 4) { return false; } - uint32_t header = mozilla::BigEndian::readUint32(aSample->Data()); + uint32_t header = mozilla::BigEndian::readUint32(aSpan.Elements()); return header == 0x00000001 || (header >> 8) == 0x000001; } diff --git a/dom/media/platforms/agnostic/bytestreams/AnnexB.h b/dom/media/platforms/agnostic/bytestreams/AnnexB.h @@ -39,6 +39,9 @@ class AnnexB { static mozilla::Result<mozilla::Ok, nsresult> ConvertHVCCSampleToAnnexB( mozilla::MediaRawData* aSample, bool aAddSPS = true); + // Extract extradata for AVCC from an Annex B sample. + static RefPtr<MediaByteBuffer> ExtractExtraDataForAVCC( + const Span<const uint8_t>& aSpan); // Convert a sample from Annex B to AVCC. // an AVCC extradata must not be set. static bool ConvertSampleToAVCC( @@ -66,7 +69,7 @@ class AnnexB { // Returns true if format is HVCC and sample has valid extradata. static bool IsHVCC(const mozilla::MediaRawData* aSample); // Returns true if format is AnnexB. - static bool IsAnnexB(const mozilla::MediaRawData* aSample); + static bool IsAnnexB(const Span<const uint8_t>& aSpan); // Parse NAL entries from the bytes stream to know the offset and the size of // each NAL in the bytes stream. @@ -78,6 +81,10 @@ class AnnexB { const nsTArray<NAL_TYPES>& aTypes); private: + static size_t FindNalType(const Span<const uint8_t>& aSpan, + const nsTArray<AnnexB::NALEntry>& aNalEntries, + NAL_TYPES aType, size_t aStartIndex); + // AVCC box parser helper. static mozilla::Result<mozilla::Ok, nsresult> ConvertSPSOrPPS( mozilla::BufferReader& aReader, uint8_t aCount, diff --git a/dom/media/platforms/agnostic/bytestreams/gtest/TestByteStreams.cpp b/dom/media/platforms/agnostic/bytestreams/gtest/TestByteStreams.cpp @@ -204,7 +204,7 @@ TEST(AnnexB, AVCCToAnnexBConversion) << "AnnexB sample should be the same size as the AVCC sample -- the 4 " "byte NAL length data (AVCC) is replaced with 4 bytes of NAL " "separator (AnnexB)"; - EXPECT_TRUE(AnnexB::IsAnnexB(rawDataClone)) + EXPECT_TRUE(AnnexB::IsAnnexB(*rawDataClone)) << "The sample should be AnnexB following conversion"; } @@ -221,7 +221,7 @@ TEST(AnnexB, AVCCToAnnexBConversion) "byte NAL length data (AVCC) is replaced with 4 bytes of NAL " "separator (AnnexB) and SPS data is not added as the frame is not a " "keyframe"; - EXPECT_TRUE(AnnexB::IsAnnexB(rawDataClone)) + EXPECT_TRUE(AnnexB::IsAnnexB(*rawDataClone)) << "The sample should be AnnexB following conversion"; } @@ -235,7 +235,7 @@ TEST(AnnexB, AVCCToAnnexBConversion) EXPECT_GT(rawDataClone->Size(), rawData->Size()) << "AnnexB sample should be larger than the AVCC sample because we've " "added SPS data"; - EXPECT_TRUE(AnnexB::IsAnnexB(rawDataClone)) + EXPECT_TRUE(AnnexB::IsAnnexB(*rawDataClone)) << "The sample should be AnnexB following conversion"; // We could verify the SPS and PPS data we add, but we don't have great // tooling to do so. Consider doing so in future. @@ -273,7 +273,7 @@ TEST(AnnexB, AVCCToAnnexBConversion) EXPECT_EQ(rawCryptoDataClone->mCrypto.mEncryptedSizes[0], rawCryptoData->mCrypto.mEncryptedSizes[0]) << "Conversion should not affect encrypted sizes"; - EXPECT_TRUE(AnnexB::IsAnnexB(rawCryptoDataClone)) + EXPECT_TRUE(AnnexB::IsAnnexB(*rawCryptoDataClone)) << "The sample should be AnnexB following conversion"; } } @@ -291,7 +291,7 @@ TEST(AnnexB, HVCCToAnnexBConversion) << "AnnexB sample should be the same size as the HVCC sample -- the 4 " "byte NAL length data (HVCC) is replaced with 4 bytes of NAL " "separator (AnnexB)"; - EXPECT_TRUE(AnnexB::IsAnnexB(rawDataClone)) + EXPECT_TRUE(AnnexB::IsAnnexB(*rawDataClone)) << "The sample should be AnnexB following conversion"; } { @@ -307,7 +307,7 @@ TEST(AnnexB, HVCCToAnnexBConversion) "byte NAL length data (HVCC) is replaced with 4 bytes of NAL " "separator (AnnexB) and SPS data is not added as the frame is not a " "keyframe"; - EXPECT_TRUE(AnnexB::IsAnnexB(rawDataClone)) + EXPECT_TRUE(AnnexB::IsAnnexB(*rawDataClone)) << "The sample should be AnnexB following conversion"; } { @@ -320,7 +320,7 @@ TEST(AnnexB, HVCCToAnnexBConversion) EXPECT_GT(rawDataClone->Size(), rawData->Size()) << "AnnexB sample should be larger than the HVCC sample because we've " "added SPS data"; - EXPECT_TRUE(AnnexB::IsAnnexB(rawDataClone)) + EXPECT_TRUE(AnnexB::IsAnnexB(*rawDataClone)) << "The sample should be AnnexB following conversion"; // We could verify the SPS and PPS data we add, but we don't have great // tooling to do so. Consider doing so in future. @@ -357,7 +357,7 @@ TEST(AnnexB, HVCCToAnnexBConversion) EXPECT_EQ(rawCryptoDataClone->mCrypto.mEncryptedSizes[0], rawCryptoData->mCrypto.mEncryptedSizes[0]) << "Conversion should not affect encrypted sizes"; - EXPECT_TRUE(AnnexB::IsAnnexB(rawCryptoDataClone)) + EXPECT_TRUE(AnnexB::IsAnnexB(*rawCryptoDataClone)) << "The sample should be AnnexB following conversion"; } } @@ -873,6 +873,39 @@ TEST(H264, CreateNewExtraData) EXPECT_TRUE(res.isErr()); } +TEST(H264, AnnexBExtractExtraDataForAVCC) +{ + // First create an AnnexB config + const uint8_t annexBBytesBuffer[]{// AnnexB delimiter + 0x00, 0x00, 0x00, 0x01, + // SPS NAL unit + 0x67, 0x64, 0x00, 0x1F, + // AnnexB delimiter + 0x00, 0x00, 0x00, 0x01, + // PPS NAL unit + 0x68, 0xCE}; + auto annexBExtradata = MakeRefPtr<mozilla::MediaByteBuffer>(); + annexBExtradata->AppendElements(annexBBytesBuffer, + std::size(annexBBytesBuffer)); + + // Extract the AVCC extradata from the AnnexB bytestream + auto avccExtradata = AnnexB::ExtractExtraDataForAVCC(*annexBExtradata); + ASSERT_TRUE(!!avccExtradata); + + // Now parse that extradata to make sure it matches the original AnnexB + auto res = AVCCConfig::Parse(avccExtradata); + ASSERT_TRUE(res.isOk()); + auto avcc = res.unwrap(); + EXPECT_EQ(avcc.NumSPS(), 1u); + EXPECT_EQ(avcc.NumSPSExt(), 0u); + EXPECT_EQ(avcc.NumPPS(), 1u); + EXPECT_EQ(avcc.NALUSize(), 4); + EXPECT_EQ(avcc.mConfigurationVersion, 1u); + EXPECT_EQ(avcc.mAVCProfileIndication, 100u); + EXPECT_EQ(avcc.mProfileCompatibility, 0u); + EXPECT_EQ(avcc.mAVCLevelIndication, 31u); +} + TEST(H265, HVCCParsingSuccess) { { @@ -1171,7 +1204,7 @@ TEST(H265, AnnexBToHVCC) Result<Ok, nsresult> result = AnnexB::ConvertHVCCSampleToAnnexB(rawDataClone, /* aAddSps */ false); EXPECT_TRUE(result.isOk()) << "HVCC to AnnexB Conversion should succeed"; - EXPECT_TRUE(AnnexB::IsAnnexB(rawDataClone)) + EXPECT_TRUE(AnnexB::IsAnnexB(*rawDataClone)) << "The sample should be AnnexB following conversion"; auto rv = AnnexB::ConvertSampleToHVCC(rawDataClone); diff --git a/dom/media/platforms/android/AndroidDataEncoder.cpp b/dom/media/platforms/android/AndroidDataEncoder.cpp @@ -279,20 +279,7 @@ static RefPtr<MediaByteBuffer> ExtractCodecConfig( if (!aAsAVCC) { return config; } - // Convert to avcC. - nsTArray<AnnexB::NALEntry> paramSets; - AnnexB::ParseNALEntries( - Span<const uint8_t>(config->Elements(), config->Length()), paramSets); - - auto avcc = MakeRefPtr<MediaByteBuffer>(); - AnnexB::NALEntry& sps = paramSets.ElementAt(0); - AnnexB::NALEntry& pps = paramSets.ElementAt(1); - const uint8_t* spsPtr = config->Elements() + sps.mOffset; - H264::WriteExtraData( - avcc, spsPtr[1], spsPtr[2], spsPtr[3], - Span<const uint8_t>(spsPtr, sps.mSize), - Span<const uint8_t>(config->Elements() + pps.mOffset, pps.mSize)); - return avcc; + return AnnexB::ExtractExtraDataForAVCC(*config); } void AndroidDataEncoder::ProcessOutput( @@ -339,8 +326,15 @@ void AndroidDataEncoder::ProcessOutput( if (size > 0) { if ((flags & java::sdk::MediaCodec::BUFFER_FLAG_CODEC_CONFIG) != 0) { - mConfigData = ExtractCodecConfig(aBuffer, offset, size, - IsAVCC(mConfig.mCodecSpecific)); + auto configData = ExtractCodecConfig(aBuffer, offset, size, + IsAVCC(mConfig.mCodecSpecific)); + if (configData) { + mConfigData = std::move(configData); + } else { + MOZ_ASSERT_UNREACHABLE("Bad config data!"); + Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, + "fail to extract codec config"_ns)); + } return; } RefPtr<MediaRawData> output; diff --git a/dom/media/platforms/wmf/WMFDataEncoderUtils.cpp b/dom/media/platforms/wmf/WMFDataEncoderUtils.cpp @@ -96,28 +96,14 @@ EncodeSupportSet CanCreateWMFEncoder(const EncoderConfig& aConfig) { static already_AddRefed<MediaByteBuffer> ParseH264Parameters( const nsTArray<uint8_t>& aHeader, const bool aAsAnnexB) { + if (!aAsAnnexB) { + return AnnexB::ExtractExtraDataForAVCC(aHeader).forget(); + } size_t length = aHeader.Length(); auto annexB = MakeRefPtr<MediaByteBuffer>(length); PodCopy(annexB->Elements(), aHeader.Elements(), length); annexB->SetLength(length); - if (aAsAnnexB) { - return annexB.forget(); - } - - // Convert to avcC. - nsTArray<AnnexB::NALEntry> paramSets; - AnnexB::ParseNALEntries( - Span<const uint8_t>(annexB->Elements(), annexB->Length()), paramSets); - - auto avcc = MakeRefPtr<MediaByteBuffer>(); - AnnexB::NALEntry& sps = paramSets.ElementAt(0); - AnnexB::NALEntry& pps = paramSets.ElementAt(1); - const uint8_t* spsPtr = annexB->Elements() + sps.mOffset; - H264::WriteExtraData( - avcc, spsPtr[1], spsPtr[2], spsPtr[3], - Span<const uint8_t>(spsPtr, sps.mSize), - Span<const uint8_t>(annexB->Elements() + pps.mOffset, pps.mSize)); - return avcc.forget(); + return annexB.forget(); } static uint32_t GetProfile(H264_PROFILE aProfileLevel) {