diff --git a/packager/app/test/packager_test.py b/packager/app/test/packager_test.py index fecd395acf..78280bba1f 100755 --- a/packager/app/test/packager_test.py +++ b/packager/app/test/packager_test.py @@ -1198,6 +1198,18 @@ class PackagerFunctionalTest(PackagerAppTest): self.assertPackageSuccess(streams, flags) self._CheckTestResults('flac-with-encryption', verify_decryption=True) + def testAv1Mp4WithEncryption(self): + self.assertPackageSuccess( + self._GetStreams(['video'], test_files=['bear-av1.mp4']), + self._GetFlags(encryption=True, output_dash=True, output_hls=True)) + self._CheckTestResults('av1-mp4-with-encryption', verify_decryption=True) + + def testAv1WebMWithEncryption(self): + self.assertPackageSuccess( + self._GetStreams(['video'], test_files=['bear-av1.webm']), + self._GetFlags(encryption=True, output_dash=True, output_hls=True)) + self._CheckTestResults('av1-webm-with-encryption', verify_decryption=True) + def testWvmInput(self): self.encryption_key = '9248d245390e0a49d483ba9b43fc69c3' self.assertPackageSuccess( diff --git a/packager/app/test/testdata/av1-mp4-with-encryption/bear-av1-video.mp4 b/packager/app/test/testdata/av1-mp4-with-encryption/bear-av1-video.mp4 new file mode 100644 index 0000000000..ea9660b110 Binary files /dev/null and b/packager/app/test/testdata/av1-mp4-with-encryption/bear-av1-video.mp4 differ diff --git a/packager/app/test/testdata/av1-mp4-with-encryption/decrypted-bear-av1-video-0.mp4 b/packager/app/test/testdata/av1-mp4-with-encryption/decrypted-bear-av1-video-0.mp4 new file mode 100644 index 0000000000..fd64d498e1 Binary files /dev/null and b/packager/app/test/testdata/av1-mp4-with-encryption/decrypted-bear-av1-video-0.mp4 differ diff --git a/packager/app/test/testdata/av1-mp4-with-encryption/output.m3u8 b/packager/app/test/testdata/av1-mp4-with-encryption/output.m3u8 new file mode 100644 index 0000000000..2ced9b0908 --- /dev/null +++ b/packager/app/test/testdata/av1-mp4-with-encryption/output.m3u8 @@ -0,0 +1,5 @@ +#EXTM3U +## Generated with https://github.com/google/shaka-packager version -- + +#EXT-X-STREAM-INF:BANDWIDTH=69777,AVERAGE-BANDWIDTH=69777,CODECS="av01.0.00M.08.0.110",RESOLUTION=320x240 +stream_0.m3u8 diff --git a/packager/app/test/testdata/av1-mp4-with-encryption/output.mpd b/packager/app/test/testdata/av1-mp4-with-encryption/output.mpd new file mode 100644 index 0000000000..9b12e8ca71 --- /dev/null +++ b/packager/app/test/testdata/av1-mp4-with-encryption/output.mpd @@ -0,0 +1,18 @@ + + + + + + + + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== + + + bear-av1-video.mp4 + + + + + + + diff --git a/packager/app/test/testdata/av1-mp4-with-encryption/stream_0.m3u8 b/packager/app/test/testdata/av1-mp4-with-encryption/stream_0.m3u8 new file mode 100644 index 0000000000..a36c7d0b9e --- /dev/null +++ b/packager/app/test/testdata/av1-mp4-with-encryption/stream_0.m3u8 @@ -0,0 +1,10 @@ +#EXTM3U +#EXT-X-VERSION:6 +## Generated with https://github.com/google/shaka-packager version -- +#EXT-X-TARGETDURATION:3 +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-MAP:URI="bear-av1-video.mp4",BYTERANGE="1041@0" +#EXTINF:2.736, +#EXT-X-BYTERANGE:23864@1085 +bear-av1-video.mp4 +#EXT-X-ENDLIST diff --git a/packager/app/test/testdata/av1-webm-with-encryption/bear-av1-video.webm b/packager/app/test/testdata/av1-webm-with-encryption/bear-av1-video.webm new file mode 100644 index 0000000000..670eb6c143 Binary files /dev/null and b/packager/app/test/testdata/av1-webm-with-encryption/bear-av1-video.webm differ diff --git a/packager/app/test/testdata/av1-webm-with-encryption/decrypted-bear-av1-video-0.webm b/packager/app/test/testdata/av1-webm-with-encryption/decrypted-bear-av1-video-0.webm new file mode 100644 index 0000000000..c4bdeb58c1 Binary files /dev/null and b/packager/app/test/testdata/av1-webm-with-encryption/decrypted-bear-av1-video-0.webm differ diff --git a/packager/app/test/testdata/av1-webm-with-encryption/output.m3u8 b/packager/app/test/testdata/av1-webm-with-encryption/output.m3u8 new file mode 100644 index 0000000000..720cf71c45 --- /dev/null +++ b/packager/app/test/testdata/av1-webm-with-encryption/output.m3u8 @@ -0,0 +1,5 @@ +#EXTM3U +## Generated with https://github.com/google/shaka-packager version -- + +#EXT-X-STREAM-INF:BANDWIDTH=69426,AVERAGE-BANDWIDTH=69426,CODECS="av01.0.00M.08.0.110",RESOLUTION=320x240 +stream_0.m3u8 diff --git a/packager/app/test/testdata/av1-webm-with-encryption/output.mpd b/packager/app/test/testdata/av1-webm-with-encryption/output.mpd new file mode 100644 index 0000000000..c42890dfc2 --- /dev/null +++ b/packager/app/test/testdata/av1-webm-with-encryption/output.mpd @@ -0,0 +1,17 @@ + + + + + + + AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA== + + + bear-av1-video.webm + + + + + + + diff --git a/packager/app/test/testdata/av1-webm-with-encryption/stream_0.m3u8 b/packager/app/test/testdata/av1-webm-with-encryption/stream_0.m3u8 new file mode 100644 index 0000000000..4e96051ceb --- /dev/null +++ b/packager/app/test/testdata/av1-webm-with-encryption/stream_0.m3u8 @@ -0,0 +1,10 @@ +#EXTM3U +#EXT-X-VERSION:6 +## Generated with https://github.com/google/shaka-packager version -- +#EXT-X-TARGETDURATION:3 +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-MAP:URI="bear-av1-video.webm",BYTERANGE="358@0" +#EXTINF:2.735, +#EXT-X-BYTERANGE:23735@377 +bear-av1-video.webm +#EXT-X-ENDLIST diff --git a/packager/media/codecs/av1_parser.cc b/packager/media/codecs/av1_parser.cc index c7cb7a2f82..a3be5f2493 100644 --- a/packager/media/codecs/av1_parser.cc +++ b/packager/media/codecs/av1_parser.cc @@ -252,17 +252,22 @@ int FindLatestForward(int shifted_order_hints[], AV1Parser::AV1Parser() = default; AV1Parser::~AV1Parser() = default; -bool AV1Parser::Parse(const uint8_t* data, size_t data_size) { +bool AV1Parser::Parse(const uint8_t* data, + size_t data_size, + std::vector* tiles) { + tiles->clear(); + BitReader reader(data, data_size); while (reader.bits_available() > 0) { - if (!ParseOpenBitstreamUnit(&reader)) + if (!ParseOpenBitstreamUnit(&reader, tiles)) return false; } return true; } // 5.3.1. General OBU syntax. -bool AV1Parser::ParseOpenBitstreamUnit(BitReader* reader) { +bool AV1Parser::ParseOpenBitstreamUnit(BitReader* reader, + std::vector* tiles) { ObuHeader obu_header; RCHECK(ParseObuHeader(reader, &obu_header)); @@ -284,10 +289,10 @@ bool AV1Parser::ParseOpenBitstreamUnit(BitReader* reader) { RCHECK(ParseFrameHeaderObu(obu_header, reader)); break; case OBU_TILE_GROUP: - RCHECK(ParseTileGroupObu(obu_size, reader)); + RCHECK(ParseTileGroupObu(obu_size, reader, tiles)); break; case OBU_FRAME: - RCHECK(ParseFrameObu(obu_header, obu_size, reader)); + RCHECK(ParseFrameObu(obu_header, obu_size, reader, tiles)); break; default: // Skip all OBUs we are not interested. @@ -1638,18 +1643,21 @@ bool AV1Parser::SkipTemporalPointInfo(BitReader* reader) { // 5.10. Frame OBU syntax. bool AV1Parser::ParseFrameObu(const ObuHeader& obu_header, size_t size, - BitReader* reader) { + BitReader* reader, + std::vector* tiles) { const size_t start_bit_pos = reader->bit_position(); RCHECK(ParseFrameHeaderObu(obu_header, reader)); RCHECK(ByteAlignment(reader)); const size_t end_bit_pos = reader->bit_position(); const size_t header_bytes = (end_bit_pos - start_bit_pos) / 8; - RCHECK(ParseTileGroupObu(size - header_bytes, reader)); + RCHECK(ParseTileGroupObu(size - header_bytes, reader, tiles)); return true; } // 5.11.1. General tile group OBU syntax. -bool AV1Parser::ParseTileGroupObu(size_t size, BitReader* reader) { +bool AV1Parser::ParseTileGroupObu(size_t size, + BitReader* reader, + std::vector* tiles) { const TileInfo& tile_info = frame_header_.tile_info; const size_t start_bit_pos = reader->bit_position(); @@ -1680,6 +1688,7 @@ bool AV1Parser::ParseTileGroupObu(size_t size, BitReader* reader) { tile_size = tile_size_minus_1 + 1; size -= tile_size + tile_info.tile_size_bytes; } + tiles->push_back({reader->bit_position() / 8, tile_size}); RCHECK(reader->SkipBits(tile_size * 8)); // Skip the tile. } diff --git a/packager/media/codecs/av1_parser.h b/packager/media/codecs/av1_parser.h index cd6e4812a8..6d69cab5de 100644 --- a/packager/media/codecs/av1_parser.h +++ b/packager/media/codecs/av1_parser.h @@ -10,6 +10,8 @@ #include #include +#include + namespace shaka { namespace media { @@ -19,15 +21,25 @@ class BitReader; /// https://aomediacodec.github.io/av1-spec/. class AV1Parser { public: + struct Tile { + size_t start_offset_in_bytes; + size_t size_in_bytes; + }; + AV1Parser(); - ~AV1Parser(); + virtual ~AV1Parser(); /// Parse an AV1 sample. Note that the sample data SHALL be a sequence of OBUs /// forming a Temporal Unit, with each OBU SHALL follow the /// open_bitstream_unit Low Overhead Bitstream Format syntax. See /// https://aomediacodec.github.io/av1-isobmff/#sampleformat for details. + /// @param[out] on success, tiles will be filled with the tile information if + /// @a data contains Frame OBU or TileGroup OBU; It will be empty + /// otherwise. /// @return true on success, false otherwise. - bool Parse(const uint8_t* data, size_t data_size); + virtual bool Parse(const uint8_t* data, + size_t data_size, + std::vector* tiles); private: AV1Parser(const AV1Parser&) = delete; @@ -183,7 +195,7 @@ class AV1Parser { bool subsampling_y = false; }; - bool ParseOpenBitstreamUnit(BitReader* reader); + bool ParseOpenBitstreamUnit(BitReader* reader, std::vector* tiles); bool ParseObuHeader(BitReader* reader, ObuHeader* obu_header); bool ParseObuExtensionHeader(BitReader* reader, ObuExtensionHeader* obu_extension_header); @@ -248,10 +260,13 @@ class AV1Parser { // Frame OBU. bool ParseFrameObu(const ObuHeader& obu_header, size_t size, - BitReader* reader); + BitReader* reader, + std::vector* tiles); // TileGroup OBU. - bool ParseTileGroupObu(size_t size, BitReader* reader); + bool ParseTileGroupObu(size_t size, + BitReader* reader, + std::vector* tiles); bool SegFeatureActiveIdx(int idx, int feature); // Decoding process related helper functions. diff --git a/packager/media/codecs/av1_parser_unittest.cc b/packager/media/codecs/av1_parser_unittest.cc index cf510db0b4..4e8df86a45 100644 --- a/packager/media/codecs/av1_parser_unittest.cc +++ b/packager/media/codecs/av1_parser_unittest.cc @@ -6,18 +6,28 @@ #include "packager/media/codecs/av1_parser.h" +#include #include #include "packager/media/test/test_data_util.h" +using ::testing::ElementsAre; + namespace shaka { namespace media { +inline bool operator==(const AV1Parser::Tile& lhs, const AV1Parser::Tile& rhs) { + return lhs.start_offset_in_bytes == rhs.start_offset_in_bytes && + lhs.size_in_bytes == rhs.size_in_bytes; +} + TEST(AV1ParserTest, ParseIFrameSuccess) { const std::vector buffer = ReadTestDataFile("av1-I-frame-320x240"); AV1Parser parser; - ASSERT_TRUE(parser.Parse(buffer.data(), buffer.size())); + std::vector tiles; + ASSERT_TRUE(parser.Parse(buffer.data(), buffer.size(), &tiles)); + EXPECT_THAT(tiles, ElementsAre(AV1Parser::Tile{0x1d, 0x4e1})); } } // namespace media diff --git a/packager/media/crypto/subsample_generator.cc b/packager/media/crypto/subsample_generator.cc index 48d4e706da..784c3e40d1 100644 --- a/packager/media/crypto/subsample_generator.cc +++ b/packager/media/crypto/subsample_generator.cc @@ -11,6 +11,7 @@ #include "packager/media/base/decrypt_config.h" #include "packager/media/base/video_stream_info.h" +#include "packager/media/codecs/av1_parser.h" #include "packager/media/codecs/video_slice_header_parser.h" #include "packager/media/codecs/vp8_parser.h" #include "packager/media/codecs/vp9_parser.h" @@ -30,6 +31,39 @@ uint8_t GetNaluLengthSize(const StreamInfo& stream_info) { return video_stream_info.nalu_length_size(); } +bool ShouldAlignProtectedData(Codec codec, + FourCC protection_scheme, + bool vp9_subsample_encryption) { + switch (codec) { + case kCodecAV1: + // Per AV1 in ISO-BMFF spec [1], BytesOfProtectedData SHALL be a multiple + // of 16 bytes. + // [1] https://aomediacodec.github.io/av1-isobmff/#subsample-encryption + return true; + case kCodecVP9: + // "VP Codec ISO Media File Format Binding" document requires that the + // encrypted bytes of each frame within the superframe must be block + // aligned so that the counter state can be computed for each frame + // within the superframe. + // ISO/IEC 23001-7:2016 10.2 'cbc1' 10.3 'cens' + // The BytesOfProtectedData size SHALL be a multiple of 16 bytes to + // avoid partial blocks in Subsamples. + // For consistency, apply block alignment to all frames when VP9 subsample + // encryption is enabled. + return vp9_subsample_encryption; + default: + // ISO/IEC 23001-7:2016 10.2 'cbc1' 10.3 'cens' + // The BytesOfProtectedData size SHALL be a multiple of 16 bytes to avoid + // partial blocks in Subsamples. + // CMAF requires 'cenc' scheme BytesOfProtectedData SHALL be a multiple of + // 16 bytes; while 'cbcs' scheme BytesOfProtectedData SHALL start on the + // first byte of video data following the slice header. + return protection_scheme == FOURCC_cbc1 || + protection_scheme == FOURCC_cens || + protection_scheme == FOURCC_cenc; + } +} + // A convenient util class to organize subsamples, e.g. combine consecutive // subsamples with only clear bytes, split subsamples if the clear bytes exceeds // 2^16 etc. @@ -97,6 +131,9 @@ Status SubsampleGenerator::Initialize(FourCC protection_scheme, nalu_length_size_ = GetNaluLengthSize(stream_info); switch (codec_) { + case kCodecAV1: + av1_parser_.reset(new AV1Parser); + break; case kCodecVP9: if (vp9_subsample_encryption_) vpx_parser_.reset(new VP9Parser); @@ -114,39 +151,33 @@ Status SubsampleGenerator::Initialize(FourCC protection_scheme, return Status(error::ENCRYPTION_FAILURE, "Unknown video codec."); } } + if (av1_parser_) { + // Parse configOBUs in AV1CodecConfigurationRecord if exists. + // https://aomediacodec.github.io/av1-isobmff/#av1codecconfigurationbox-syntax. + const size_t kConfigOBUsOffset = 4; + const bool has_config_obus = + stream_info.codec_config().size() > kConfigOBUsOffset; + std::vector tiles; + if (has_config_obus && + !av1_parser_->Parse( + &stream_info.codec_config()[kConfigOBUsOffset], + stream_info.codec_config().size() - kConfigOBUsOffset, &tiles)) { + return Status( + error::ENCRYPTION_FAILURE, + "Failed to parse configOBUs in AV1CodecConfigurationRecord."); + } + DCHECK(tiles.empty()); + } if (header_parser_) { CHECK_NE(nalu_length_size_, 0u) << "AnnexB stream is not supported yet"; if (!header_parser_->Initialize(stream_info.codec_config())) { return Status(error::ENCRYPTION_FAILURE, - "Fail to read SPS and PPS data."); + "Failed to read SPS and PPS data."); } } - switch (codec_) { - case kCodecVP9: - // "VP Codec ISO Media File Format Binding" document requires that the - // encrypted bytes of each frame within the superframe must be block - // aligned so that the counter state can be computed for each frame - // within the superframe. - // ISO/IEC 23001-7:2016 10.2 'cbc1' 10.3 'cens' - // The BytesOfProtectedData size SHALL be a multiple of 16 bytes to - // avoid partial blocks in Subsamples. - // For consistency, apply block alignment to all frames when VP9 subsample - // encryption is enabled. - align_protected_data_ = vp9_subsample_encryption_; - break; - default: - // ISO/IEC 23001-7:2016 10.2 'cbc1' 10.3 'cens' - // The BytesOfProtectedData size SHALL be a multiple of 16 bytes to avoid - // partial blocks in Subsamples. - // CMAF requires 'cenc' scheme BytesOfProtectedData SHALL be a multiple of - // 16 bytes; while 'cbcs' scheme BytesOfProtectedData SHALL start on the - // first byte of video data following the slice header. - align_protected_data_ = protection_scheme == FOURCC_cbc1 || - protection_scheme == FOURCC_cens || - protection_scheme == FOURCC_cenc; - break; - } + align_protected_data_ = ShouldAlignProtectedData(codec_, protection_scheme, + vp9_subsample_encryption_); if (protection_scheme == kAppleSampleAesProtectionScheme) { const size_t kH264LeadingClearBytesSize = 32u; @@ -184,6 +215,8 @@ Status SubsampleGenerator::GenerateSubsamples( std::vector* subsamples) { subsamples->clear(); switch (codec_) { + case kCodecAV1: + return GenerateSubsamplesFromAV1Frame(frame, frame_size, subsamples); case kCodecH264: FALLTHROUGH_INTENDED; case kCodecH265: @@ -221,6 +254,11 @@ void SubsampleGenerator::InjectVideoSliceHeaderParserForTesting( header_parser_ = std::move(header_parser); } +void SubsampleGenerator::InjectAV1ParserForTesting( + std::unique_ptr av1_parser) { + av1_parser_ = std::move(av1_parser); +} + Status SubsampleGenerator::GenerateSubsamplesFromVPxFrame( const uint8_t* frame, size_t frame_size, @@ -299,5 +337,31 @@ Status SubsampleGenerator::GenerateSubsamplesFromH26xFrame( return Status::OK; } +Status SubsampleGenerator::GenerateSubsamplesFromAV1Frame( + const uint8_t* frame, + size_t frame_size, + std::vector* subsamples) { + DCHECK(av1_parser_); + std::vector av1_tiles; + if (!av1_parser_->Parse(frame, frame_size, &av1_tiles)) + return Status(error::ENCRYPTION_FAILURE, "Failed to parse AV1 frame."); + + SubsampleOrganizer subsample_organizer(align_protected_data_, subsamples); + + size_t last_tile_end_offset = 0; + for (const AV1Parser::Tile& tile : av1_tiles) { + DCHECK_LE(last_tile_end_offset, tile.start_offset_in_bytes); + // Per AV1 in ISO-BMFF spec [1], only decode_tile is encrypted. + // [1] https://aomediacodec.github.io/av1-isobmff/#subsample-encryption + subsample_organizer.AddSubsample( + tile.start_offset_in_bytes - last_tile_end_offset, tile.size_in_bytes); + last_tile_end_offset = tile.start_offset_in_bytes + tile.size_in_bytes; + } + DCHECK_LE(last_tile_end_offset, frame_size); + if (last_tile_end_offset < frame_size) + subsample_organizer.AddSubsample(frame_size - last_tile_end_offset, 0); + return Status::OK; +} + } // namespace media } // namespace shaka diff --git a/packager/media/crypto/subsample_generator.h b/packager/media/crypto/subsample_generator.h index 5a5ae5a589..75cb5b6533 100644 --- a/packager/media/crypto/subsample_generator.h +++ b/packager/media/crypto/subsample_generator.h @@ -17,6 +17,7 @@ namespace shaka { namespace media { +class AV1Parser; class VideoSliceHeaderParser; class VPxParser; struct SubsampleEntry; @@ -63,6 +64,7 @@ class SubsampleGenerator { void InjectVpxParserForTesting(std::unique_ptr vpx_parser); void InjectVideoSliceHeaderParserForTesting( std::unique_ptr header_parser); + void InjectAV1ParserForTesting(std::unique_ptr av1_parser); private: SubsampleGenerator(const SubsampleGenerator&) = delete; @@ -76,6 +78,10 @@ class SubsampleGenerator { const uint8_t* frame, size_t frame_size, std::vector* subsamples); + Status GenerateSubsamplesFromAV1Frame( + const uint8_t* frame, + size_t frame_size, + std::vector* subsamples); const bool vp9_subsample_encryption_ = false; // Whether the protected portion should be AES block (16 bytes) aligned. @@ -95,6 +101,8 @@ class SubsampleGenerator { std::unique_ptr vpx_parser_; // Video slice header parser for NAL strucutred streams. std::unique_ptr header_parser_; + // AV1 parser for AV1 streams. + std::unique_ptr av1_parser_; }; } // namespace media diff --git a/packager/media/crypto/subsample_generator_unittest.cc b/packager/media/crypto/subsample_generator_unittest.cc index a0c44cb4c7..13fb21d5fc 100644 --- a/packager/media/crypto/subsample_generator_unittest.cc +++ b/packager/media/crypto/subsample_generator_unittest.cc @@ -11,6 +11,7 @@ #include "packager/media/base/audio_stream_info.h" #include "packager/media/base/video_stream_info.h" +#include "packager/media/codecs/av1_parser.h" #include "packager/media/codecs/video_slice_header_parser.h" #include "packager/media/codecs/vpx_parser.h" #include "packager/status_test_util.h" @@ -30,8 +31,7 @@ using ::testing::Values; using ::testing::WithParamInterface; const bool kVP9SubsampleEncryption = true; -// Use H264 code config. -const uint8_t kH264CodecConfig[]{ +const uint8_t kH264CodecConfig[] = { // clang-format off // Header 0x01, 0x64, 0x00, 0x1e, 0xff, @@ -49,6 +49,7 @@ const uint8_t kH264CodecConfig[]{ 0x68, 0xeb, 0xe3, 0xcb, 0x22, 0xc0, // clang-format on }; +const uint8_t kAV1CodecConfig[] = {0x00, 0x01, 0x02, 0x03}; const int kTrackId = 1; const uint32_t kTimeScale = 1000; const uint64_t kDuration = 10000; @@ -71,6 +72,10 @@ VideoStreamInfo GetVideoStreamInfo(Codec codec) { codec_config = kH264CodecConfig; codec_config_size = sizeof(kH264CodecConfig); break; + case kCodecAV1: + codec_config = kAV1CodecConfig; + codec_config_size = sizeof(kAV1CodecConfig); + break; default: // We do not care about the codec configs for other codecs in this file. break; @@ -121,6 +126,14 @@ class MockVideoSliceHeaderParser : public VideoSliceHeaderParser { MOCK_METHOD1(GetHeaderSize, int64_t(const Nalu& nalu)); }; +class MockAV1Parser : public AV1Parser { + public: + MOCK_METHOD3(Parse, + bool(const uint8_t* data, + size_t data_size, + std::vector* tiles)); +}; + class SubsampleGeneratorTest : public Test, public WithParamInterface { public: SubsampleGeneratorTest() : protection_scheme_(GetParam()) {} @@ -345,6 +358,58 @@ TEST_P(SubsampleGeneratorTest, H264SubsampleEncryption) { EXPECT_THAT(subsamples, ElementsAreArray(kExpectedAlignedSubsamples)); } +TEST_P(SubsampleGeneratorTest, AV1ParserFailed) { + SubsampleGenerator generator(kVP9SubsampleEncryption); + ASSERT_OK( + generator.Initialize(protection_scheme_, GetVideoStreamInfo(kCodecAV1))); + + constexpr size_t kFrameSize = 50; + constexpr uint8_t kFrame[kFrameSize] = {}; + + std::unique_ptr mock_av1_parser(new MockAV1Parser); + EXPECT_CALL(*mock_av1_parser, Parse(kFrame, kFrameSize, _)) + .WillOnce(Return(false)); + + generator.InjectAV1ParserForTesting(std::move(mock_av1_parser)); + + std::vector subsamples; + ASSERT_NOT_OK(generator.GenerateSubsamples(kFrame, kFrameSize, &subsamples)); +} + +TEST_P(SubsampleGeneratorTest, AV1SubsampleEncryption) { + SubsampleGenerator generator(kVP9SubsampleEncryption); + ASSERT_OK( + generator.Initialize(protection_scheme_, GetVideoStreamInfo(kCodecAV1))); + + constexpr size_t kFrameSize = 50; + constexpr uint8_t kFrame[kFrameSize] = {}; + constexpr size_t kTileOffsets[] = {4, 11}; + constexpr size_t kTileSizes[] = {6, 33}; + // AV1 block align protected data for all protection schemes. + const SubsampleEntry kExpectedSubsamples[] = { + // {4,6},{11-4-6,33},{50-11-33,0} block aligned => {10,0},{2,32},{6,0}. + // Then merge consecutive clear-only subsamples. + {12, 32}, + {6, 0}, + }; + + std::vector tiles(2); + for (int i = 0; i < 2; i++) { + tiles[i].start_offset_in_bytes = kTileOffsets[i]; + tiles[i].size_in_bytes = kTileSizes[i]; + } + + std::unique_ptr mock_av1_parser(new MockAV1Parser); + EXPECT_CALL(*mock_av1_parser, Parse(kFrame, kFrameSize, _)) + .WillOnce(DoAll(SetArgPointee<2>(tiles), Return(true))); + + generator.InjectAV1ParserForTesting(std::move(mock_av1_parser)); + + std::vector subsamples; + ASSERT_OK(generator.GenerateSubsamples(kFrame, kFrameSize, &subsamples)); + EXPECT_THAT(subsamples, ElementsAreArray(kExpectedSubsamples)); +} + TEST_P(SubsampleGeneratorTest, AACIsFullSampleEncrypted) { SubsampleGenerator generator(kVP9SubsampleEncryption); ASSERT_OK(