Support subsample encryption in AV1
Closes #453. Change-Id: I68e46fb83cbf7e62a19fa83698cb66bfc0acd98d
This commit is contained in:
parent
5c4d930465
commit
4b19905bc2
|
@ -1198,6 +1198,18 @@ class PackagerFunctionalTest(PackagerAppTest):
|
||||||
self.assertPackageSuccess(streams, flags)
|
self.assertPackageSuccess(streams, flags)
|
||||||
self._CheckTestResults('flac-with-encryption', verify_decryption=True)
|
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):
|
def testWvmInput(self):
|
||||||
self.encryption_key = '9248d245390e0a49d483ba9b43fc69c3'
|
self.encryption_key = '9248d245390e0a49d483ba9b43fc69c3'
|
||||||
self.assertPackageSuccess(
|
self.assertPackageSuccess(
|
||||||
|
|
Binary file not shown.
BIN
packager/app/test/testdata/av1-mp4-with-encryption/decrypted-bear-av1-video-0.mp4
vendored
Normal file
BIN
packager/app/test/testdata/av1-mp4-with-encryption/decrypted-bear-av1-video-0.mp4
vendored
Normal file
Binary file not shown.
|
@ -0,0 +1,5 @@
|
||||||
|
#EXTM3U
|
||||||
|
## Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test>
|
||||||
|
|
||||||
|
#EXT-X-STREAM-INF:BANDWIDTH=69777,AVERAGE-BANDWIDTH=69777,CODECS="av01.0.00M.08.0.110",RESOLUTION=320x240
|
||||||
|
stream_0.m3u8
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test>-->
|
||||||
|
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd" xmlns:cenc="urn:mpeg:cenc:2013" profiles="urn:mpeg:dash:profile:isoff-on-demand:2011" minBufferTime="PT2S" type="static" mediaPresentationDuration="PT2.7360665798187256S">
|
||||||
|
<Period id="0">
|
||||||
|
<AdaptationSet id="0" contentType="video" width="320" height="240" frameRate="30000/1001" subsegmentAlignment="true" par="4:3">
|
||||||
|
<ContentProtection value="cenc" schemeIdUri="urn:mpeg:dash:mp4protection:2011" cenc:default_KID="31323334-3536-3738-3930-313233343536"/>
|
||||||
|
<ContentProtection schemeIdUri="urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b">
|
||||||
|
<cenc:pssh>AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA==</cenc:pssh>
|
||||||
|
</ContentProtection>
|
||||||
|
<Representation id="0" bandwidth="69777" codecs="av01.0.00M.08.0.110" mimeType="video/mp4" sar="1:1">
|
||||||
|
<BaseURL>bear-av1-video.mp4</BaseURL>
|
||||||
|
<SegmentBase indexRange="1041-1084" timescale="30000">
|
||||||
|
<Initialization range="0-1040"/>
|
||||||
|
</SegmentBase>
|
||||||
|
</Representation>
|
||||||
|
</AdaptationSet>
|
||||||
|
</Period>
|
||||||
|
</MPD>
|
|
@ -0,0 +1,10 @@
|
||||||
|
#EXTM3U
|
||||||
|
#EXT-X-VERSION:6
|
||||||
|
## Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test>
|
||||||
|
#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
|
Binary file not shown.
BIN
packager/app/test/testdata/av1-webm-with-encryption/decrypted-bear-av1-video-0.webm
vendored
Normal file
BIN
packager/app/test/testdata/av1-webm-with-encryption/decrypted-bear-av1-video-0.webm
vendored
Normal file
Binary file not shown.
|
@ -0,0 +1,5 @@
|
||||||
|
#EXTM3U
|
||||||
|
## Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test>
|
||||||
|
|
||||||
|
#EXT-X-STREAM-INF:BANDWIDTH=69426,AVERAGE-BANDWIDTH=69426,CODECS="av01.0.00M.08.0.110",RESOLUTION=320x240
|
||||||
|
stream_0.m3u8
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test>-->
|
||||||
|
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd" xmlns:cenc="urn:mpeg:cenc:2013" profiles="urn:mpeg:dash:profile:isoff-on-demand:2011" minBufferTime="PT2S" type="static" mediaPresentationDuration="PT2.734999895095825S">
|
||||||
|
<Period id="0">
|
||||||
|
<AdaptationSet id="0" contentType="video" width="320" height="240" frameRate="1000000/33000" subsegmentAlignment="true" par="4:3">
|
||||||
|
<ContentProtection schemeIdUri="urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b" cenc:default_KID="31323334-3536-3738-3930-313233343536">
|
||||||
|
<cenc:pssh>AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAExMjM0NTY3ODkwMTIzNDU2AAAAAA==</cenc:pssh>
|
||||||
|
</ContentProtection>
|
||||||
|
<Representation id="0" bandwidth="69426" codecs="av01.0.00M.08.0.110" mimeType="video/webm" sar="1:1">
|
||||||
|
<BaseURL>bear-av1-video.webm</BaseURL>
|
||||||
|
<SegmentBase indexRange="358-376" timescale="1000000">
|
||||||
|
<Initialization range="0-357"/>
|
||||||
|
</SegmentBase>
|
||||||
|
</Representation>
|
||||||
|
</AdaptationSet>
|
||||||
|
</Period>
|
||||||
|
</MPD>
|
|
@ -0,0 +1,10 @@
|
||||||
|
#EXTM3U
|
||||||
|
#EXT-X-VERSION:6
|
||||||
|
## Generated with https://github.com/google/shaka-packager version <tag>-<hash>-<test>
|
||||||
|
#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
|
|
@ -252,17 +252,22 @@ int FindLatestForward(int shifted_order_hints[],
|
||||||
AV1Parser::AV1Parser() = default;
|
AV1Parser::AV1Parser() = default;
|
||||||
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<Tile>* tiles) {
|
||||||
|
tiles->clear();
|
||||||
|
|
||||||
BitReader reader(data, data_size);
|
BitReader reader(data, data_size);
|
||||||
while (reader.bits_available() > 0) {
|
while (reader.bits_available() > 0) {
|
||||||
if (!ParseOpenBitstreamUnit(&reader))
|
if (!ParseOpenBitstreamUnit(&reader, tiles))
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5.3.1. General OBU syntax.
|
// 5.3.1. General OBU syntax.
|
||||||
bool AV1Parser::ParseOpenBitstreamUnit(BitReader* reader) {
|
bool AV1Parser::ParseOpenBitstreamUnit(BitReader* reader,
|
||||||
|
std::vector<Tile>* tiles) {
|
||||||
ObuHeader obu_header;
|
ObuHeader obu_header;
|
||||||
RCHECK(ParseObuHeader(reader, &obu_header));
|
RCHECK(ParseObuHeader(reader, &obu_header));
|
||||||
|
|
||||||
|
@ -284,10 +289,10 @@ bool AV1Parser::ParseOpenBitstreamUnit(BitReader* reader) {
|
||||||
RCHECK(ParseFrameHeaderObu(obu_header, reader));
|
RCHECK(ParseFrameHeaderObu(obu_header, reader));
|
||||||
break;
|
break;
|
||||||
case OBU_TILE_GROUP:
|
case OBU_TILE_GROUP:
|
||||||
RCHECK(ParseTileGroupObu(obu_size, reader));
|
RCHECK(ParseTileGroupObu(obu_size, reader, tiles));
|
||||||
break;
|
break;
|
||||||
case OBU_FRAME:
|
case OBU_FRAME:
|
||||||
RCHECK(ParseFrameObu(obu_header, obu_size, reader));
|
RCHECK(ParseFrameObu(obu_header, obu_size, reader, tiles));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// Skip all OBUs we are not interested.
|
// Skip all OBUs we are not interested.
|
||||||
|
@ -1638,18 +1643,21 @@ bool AV1Parser::SkipTemporalPointInfo(BitReader* reader) {
|
||||||
// 5.10. Frame OBU syntax.
|
// 5.10. Frame OBU syntax.
|
||||||
bool AV1Parser::ParseFrameObu(const ObuHeader& obu_header,
|
bool AV1Parser::ParseFrameObu(const ObuHeader& obu_header,
|
||||||
size_t size,
|
size_t size,
|
||||||
BitReader* reader) {
|
BitReader* reader,
|
||||||
|
std::vector<Tile>* tiles) {
|
||||||
const size_t start_bit_pos = reader->bit_position();
|
const size_t start_bit_pos = reader->bit_position();
|
||||||
RCHECK(ParseFrameHeaderObu(obu_header, reader));
|
RCHECK(ParseFrameHeaderObu(obu_header, reader));
|
||||||
RCHECK(ByteAlignment(reader));
|
RCHECK(ByteAlignment(reader));
|
||||||
const size_t end_bit_pos = reader->bit_position();
|
const size_t end_bit_pos = reader->bit_position();
|
||||||
const size_t header_bytes = (end_bit_pos - start_bit_pos) / 8;
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5.11.1. General tile group OBU syntax.
|
// 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<Tile>* tiles) {
|
||||||
const TileInfo& tile_info = frame_header_.tile_info;
|
const TileInfo& tile_info = frame_header_.tile_info;
|
||||||
const size_t start_bit_pos = reader->bit_position();
|
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;
|
tile_size = tile_size_minus_1 + 1;
|
||||||
size -= tile_size + tile_info.tile_size_bytes;
|
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.
|
RCHECK(reader->SkipBits(tile_size * 8)); // Skip the tile.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,8 @@
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace shaka {
|
namespace shaka {
|
||||||
namespace media {
|
namespace media {
|
||||||
|
|
||||||
|
@ -19,15 +21,25 @@ class BitReader;
|
||||||
/// https://aomediacodec.github.io/av1-spec/.
|
/// https://aomediacodec.github.io/av1-spec/.
|
||||||
class AV1Parser {
|
class AV1Parser {
|
||||||
public:
|
public:
|
||||||
|
struct Tile {
|
||||||
|
size_t start_offset_in_bytes;
|
||||||
|
size_t size_in_bytes;
|
||||||
|
};
|
||||||
|
|
||||||
AV1Parser();
|
AV1Parser();
|
||||||
~AV1Parser();
|
virtual ~AV1Parser();
|
||||||
|
|
||||||
/// Parse an AV1 sample. Note that the sample data SHALL be a sequence of OBUs
|
/// 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
|
/// forming a Temporal Unit, with each OBU SHALL follow the
|
||||||
/// open_bitstream_unit Low Overhead Bitstream Format syntax. See
|
/// open_bitstream_unit Low Overhead Bitstream Format syntax. See
|
||||||
/// https://aomediacodec.github.io/av1-isobmff/#sampleformat for details.
|
/// 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.
|
/// @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<Tile>* tiles);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
AV1Parser(const AV1Parser&) = delete;
|
AV1Parser(const AV1Parser&) = delete;
|
||||||
|
@ -183,7 +195,7 @@ class AV1Parser {
|
||||||
bool subsampling_y = false;
|
bool subsampling_y = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
bool ParseOpenBitstreamUnit(BitReader* reader);
|
bool ParseOpenBitstreamUnit(BitReader* reader, std::vector<Tile>* tiles);
|
||||||
bool ParseObuHeader(BitReader* reader, ObuHeader* obu_header);
|
bool ParseObuHeader(BitReader* reader, ObuHeader* obu_header);
|
||||||
bool ParseObuExtensionHeader(BitReader* reader,
|
bool ParseObuExtensionHeader(BitReader* reader,
|
||||||
ObuExtensionHeader* obu_extension_header);
|
ObuExtensionHeader* obu_extension_header);
|
||||||
|
@ -248,10 +260,13 @@ class AV1Parser {
|
||||||
// Frame OBU.
|
// Frame OBU.
|
||||||
bool ParseFrameObu(const ObuHeader& obu_header,
|
bool ParseFrameObu(const ObuHeader& obu_header,
|
||||||
size_t size,
|
size_t size,
|
||||||
BitReader* reader);
|
BitReader* reader,
|
||||||
|
std::vector<Tile>* tiles);
|
||||||
|
|
||||||
// TileGroup OBU.
|
// TileGroup OBU.
|
||||||
bool ParseTileGroupObu(size_t size, BitReader* reader);
|
bool ParseTileGroupObu(size_t size,
|
||||||
|
BitReader* reader,
|
||||||
|
std::vector<Tile>* tiles);
|
||||||
bool SegFeatureActiveIdx(int idx, int feature);
|
bool SegFeatureActiveIdx(int idx, int feature);
|
||||||
|
|
||||||
// Decoding process related helper functions.
|
// Decoding process related helper functions.
|
||||||
|
|
|
@ -6,18 +6,28 @@
|
||||||
|
|
||||||
#include "packager/media/codecs/av1_parser.h"
|
#include "packager/media/codecs/av1_parser.h"
|
||||||
|
|
||||||
|
#include <gmock/gmock.h>
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
#include "packager/media/test/test_data_util.h"
|
#include "packager/media/test/test_data_util.h"
|
||||||
|
|
||||||
|
using ::testing::ElementsAre;
|
||||||
|
|
||||||
namespace shaka {
|
namespace shaka {
|
||||||
namespace media {
|
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) {
|
TEST(AV1ParserTest, ParseIFrameSuccess) {
|
||||||
const std::vector<uint8_t> buffer = ReadTestDataFile("av1-I-frame-320x240");
|
const std::vector<uint8_t> buffer = ReadTestDataFile("av1-I-frame-320x240");
|
||||||
|
|
||||||
AV1Parser parser;
|
AV1Parser parser;
|
||||||
ASSERT_TRUE(parser.Parse(buffer.data(), buffer.size()));
|
std::vector<AV1Parser::Tile> tiles;
|
||||||
|
ASSERT_TRUE(parser.Parse(buffer.data(), buffer.size(), &tiles));
|
||||||
|
EXPECT_THAT(tiles, ElementsAre(AV1Parser::Tile{0x1d, 0x4e1}));
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace media
|
} // namespace media
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
|
|
||||||
#include "packager/media/base/decrypt_config.h"
|
#include "packager/media/base/decrypt_config.h"
|
||||||
#include "packager/media/base/video_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/video_slice_header_parser.h"
|
||||||
#include "packager/media/codecs/vp8_parser.h"
|
#include "packager/media/codecs/vp8_parser.h"
|
||||||
#include "packager/media/codecs/vp9_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();
|
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
|
// A convenient util class to organize subsamples, e.g. combine consecutive
|
||||||
// subsamples with only clear bytes, split subsamples if the clear bytes exceeds
|
// subsamples with only clear bytes, split subsamples if the clear bytes exceeds
|
||||||
// 2^16 etc.
|
// 2^16 etc.
|
||||||
|
@ -97,6 +131,9 @@ Status SubsampleGenerator::Initialize(FourCC protection_scheme,
|
||||||
nalu_length_size_ = GetNaluLengthSize(stream_info);
|
nalu_length_size_ = GetNaluLengthSize(stream_info);
|
||||||
|
|
||||||
switch (codec_) {
|
switch (codec_) {
|
||||||
|
case kCodecAV1:
|
||||||
|
av1_parser_.reset(new AV1Parser);
|
||||||
|
break;
|
||||||
case kCodecVP9:
|
case kCodecVP9:
|
||||||
if (vp9_subsample_encryption_)
|
if (vp9_subsample_encryption_)
|
||||||
vpx_parser_.reset(new VP9Parser);
|
vpx_parser_.reset(new VP9Parser);
|
||||||
|
@ -114,39 +151,33 @@ Status SubsampleGenerator::Initialize(FourCC protection_scheme,
|
||||||
return Status(error::ENCRYPTION_FAILURE, "Unknown video codec.");
|
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<AV1Parser::Tile> 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_) {
|
if (header_parser_) {
|
||||||
CHECK_NE(nalu_length_size_, 0u) << "AnnexB stream is not supported yet";
|
CHECK_NE(nalu_length_size_, 0u) << "AnnexB stream is not supported yet";
|
||||||
if (!header_parser_->Initialize(stream_info.codec_config())) {
|
if (!header_parser_->Initialize(stream_info.codec_config())) {
|
||||||
return Status(error::ENCRYPTION_FAILURE,
|
return Status(error::ENCRYPTION_FAILURE,
|
||||||
"Fail to read SPS and PPS data.");
|
"Failed to read SPS and PPS data.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (codec_) {
|
align_protected_data_ = ShouldAlignProtectedData(codec_, protection_scheme,
|
||||||
case kCodecVP9:
|
vp9_subsample_encryption_);
|
||||||
// "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;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (protection_scheme == kAppleSampleAesProtectionScheme) {
|
if (protection_scheme == kAppleSampleAesProtectionScheme) {
|
||||||
const size_t kH264LeadingClearBytesSize = 32u;
|
const size_t kH264LeadingClearBytesSize = 32u;
|
||||||
|
@ -184,6 +215,8 @@ Status SubsampleGenerator::GenerateSubsamples(
|
||||||
std::vector<SubsampleEntry>* subsamples) {
|
std::vector<SubsampleEntry>* subsamples) {
|
||||||
subsamples->clear();
|
subsamples->clear();
|
||||||
switch (codec_) {
|
switch (codec_) {
|
||||||
|
case kCodecAV1:
|
||||||
|
return GenerateSubsamplesFromAV1Frame(frame, frame_size, subsamples);
|
||||||
case kCodecH264:
|
case kCodecH264:
|
||||||
FALLTHROUGH_INTENDED;
|
FALLTHROUGH_INTENDED;
|
||||||
case kCodecH265:
|
case kCodecH265:
|
||||||
|
@ -221,6 +254,11 @@ void SubsampleGenerator::InjectVideoSliceHeaderParserForTesting(
|
||||||
header_parser_ = std::move(header_parser);
|
header_parser_ = std::move(header_parser);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SubsampleGenerator::InjectAV1ParserForTesting(
|
||||||
|
std::unique_ptr<AV1Parser> av1_parser) {
|
||||||
|
av1_parser_ = std::move(av1_parser);
|
||||||
|
}
|
||||||
|
|
||||||
Status SubsampleGenerator::GenerateSubsamplesFromVPxFrame(
|
Status SubsampleGenerator::GenerateSubsamplesFromVPxFrame(
|
||||||
const uint8_t* frame,
|
const uint8_t* frame,
|
||||||
size_t frame_size,
|
size_t frame_size,
|
||||||
|
@ -299,5 +337,31 @@ Status SubsampleGenerator::GenerateSubsamplesFromH26xFrame(
|
||||||
return Status::OK;
|
return Status::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Status SubsampleGenerator::GenerateSubsamplesFromAV1Frame(
|
||||||
|
const uint8_t* frame,
|
||||||
|
size_t frame_size,
|
||||||
|
std::vector<SubsampleEntry>* subsamples) {
|
||||||
|
DCHECK(av1_parser_);
|
||||||
|
std::vector<AV1Parser::Tile> 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 media
|
||||||
} // namespace shaka
|
} // namespace shaka
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
namespace shaka {
|
namespace shaka {
|
||||||
namespace media {
|
namespace media {
|
||||||
|
|
||||||
|
class AV1Parser;
|
||||||
class VideoSliceHeaderParser;
|
class VideoSliceHeaderParser;
|
||||||
class VPxParser;
|
class VPxParser;
|
||||||
struct SubsampleEntry;
|
struct SubsampleEntry;
|
||||||
|
@ -63,6 +64,7 @@ class SubsampleGenerator {
|
||||||
void InjectVpxParserForTesting(std::unique_ptr<VPxParser> vpx_parser);
|
void InjectVpxParserForTesting(std::unique_ptr<VPxParser> vpx_parser);
|
||||||
void InjectVideoSliceHeaderParserForTesting(
|
void InjectVideoSliceHeaderParserForTesting(
|
||||||
std::unique_ptr<VideoSliceHeaderParser> header_parser);
|
std::unique_ptr<VideoSliceHeaderParser> header_parser);
|
||||||
|
void InjectAV1ParserForTesting(std::unique_ptr<AV1Parser> av1_parser);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SubsampleGenerator(const SubsampleGenerator&) = delete;
|
SubsampleGenerator(const SubsampleGenerator&) = delete;
|
||||||
|
@ -76,6 +78,10 @@ class SubsampleGenerator {
|
||||||
const uint8_t* frame,
|
const uint8_t* frame,
|
||||||
size_t frame_size,
|
size_t frame_size,
|
||||||
std::vector<SubsampleEntry>* subsamples);
|
std::vector<SubsampleEntry>* subsamples);
|
||||||
|
Status GenerateSubsamplesFromAV1Frame(
|
||||||
|
const uint8_t* frame,
|
||||||
|
size_t frame_size,
|
||||||
|
std::vector<SubsampleEntry>* subsamples);
|
||||||
|
|
||||||
const bool vp9_subsample_encryption_ = false;
|
const bool vp9_subsample_encryption_ = false;
|
||||||
// Whether the protected portion should be AES block (16 bytes) aligned.
|
// Whether the protected portion should be AES block (16 bytes) aligned.
|
||||||
|
@ -95,6 +101,8 @@ class SubsampleGenerator {
|
||||||
std::unique_ptr<VPxParser> vpx_parser_;
|
std::unique_ptr<VPxParser> vpx_parser_;
|
||||||
// Video slice header parser for NAL strucutred streams.
|
// Video slice header parser for NAL strucutred streams.
|
||||||
std::unique_ptr<VideoSliceHeaderParser> header_parser_;
|
std::unique_ptr<VideoSliceHeaderParser> header_parser_;
|
||||||
|
// AV1 parser for AV1 streams.
|
||||||
|
std::unique_ptr<AV1Parser> av1_parser_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace media
|
} // namespace media
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
|
|
||||||
#include "packager/media/base/audio_stream_info.h"
|
#include "packager/media/base/audio_stream_info.h"
|
||||||
#include "packager/media/base/video_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/video_slice_header_parser.h"
|
||||||
#include "packager/media/codecs/vpx_parser.h"
|
#include "packager/media/codecs/vpx_parser.h"
|
||||||
#include "packager/status_test_util.h"
|
#include "packager/status_test_util.h"
|
||||||
|
@ -30,8 +31,7 @@ using ::testing::Values;
|
||||||
using ::testing::WithParamInterface;
|
using ::testing::WithParamInterface;
|
||||||
|
|
||||||
const bool kVP9SubsampleEncryption = true;
|
const bool kVP9SubsampleEncryption = true;
|
||||||
// Use H264 code config.
|
const uint8_t kH264CodecConfig[] = {
|
||||||
const uint8_t kH264CodecConfig[]{
|
|
||||||
// clang-format off
|
// clang-format off
|
||||||
// Header
|
// Header
|
||||||
0x01, 0x64, 0x00, 0x1e, 0xff,
|
0x01, 0x64, 0x00, 0x1e, 0xff,
|
||||||
|
@ -49,6 +49,7 @@ const uint8_t kH264CodecConfig[]{
|
||||||
0x68, 0xeb, 0xe3, 0xcb, 0x22, 0xc0,
|
0x68, 0xeb, 0xe3, 0xcb, 0x22, 0xc0,
|
||||||
// clang-format on
|
// clang-format on
|
||||||
};
|
};
|
||||||
|
const uint8_t kAV1CodecConfig[] = {0x00, 0x01, 0x02, 0x03};
|
||||||
const int kTrackId = 1;
|
const int kTrackId = 1;
|
||||||
const uint32_t kTimeScale = 1000;
|
const uint32_t kTimeScale = 1000;
|
||||||
const uint64_t kDuration = 10000;
|
const uint64_t kDuration = 10000;
|
||||||
|
@ -71,6 +72,10 @@ VideoStreamInfo GetVideoStreamInfo(Codec codec) {
|
||||||
codec_config = kH264CodecConfig;
|
codec_config = kH264CodecConfig;
|
||||||
codec_config_size = sizeof(kH264CodecConfig);
|
codec_config_size = sizeof(kH264CodecConfig);
|
||||||
break;
|
break;
|
||||||
|
case kCodecAV1:
|
||||||
|
codec_config = kAV1CodecConfig;
|
||||||
|
codec_config_size = sizeof(kAV1CodecConfig);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
// We do not care about the codec configs for other codecs in this file.
|
// We do not care about the codec configs for other codecs in this file.
|
||||||
break;
|
break;
|
||||||
|
@ -121,6 +126,14 @@ class MockVideoSliceHeaderParser : public VideoSliceHeaderParser {
|
||||||
MOCK_METHOD1(GetHeaderSize, int64_t(const Nalu& nalu));
|
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<Tile>* tiles));
|
||||||
|
};
|
||||||
|
|
||||||
class SubsampleGeneratorTest : public Test, public WithParamInterface<FourCC> {
|
class SubsampleGeneratorTest : public Test, public WithParamInterface<FourCC> {
|
||||||
public:
|
public:
|
||||||
SubsampleGeneratorTest() : protection_scheme_(GetParam()) {}
|
SubsampleGeneratorTest() : protection_scheme_(GetParam()) {}
|
||||||
|
@ -345,6 +358,58 @@ TEST_P(SubsampleGeneratorTest, H264SubsampleEncryption) {
|
||||||
EXPECT_THAT(subsamples, ElementsAreArray(kExpectedAlignedSubsamples));
|
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<MockAV1Parser> 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<SubsampleEntry> 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<AV1Parser::Tile> 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<MockAV1Parser> 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<SubsampleEntry> subsamples;
|
||||||
|
ASSERT_OK(generator.GenerateSubsamples(kFrame, kFrameSize, &subsamples));
|
||||||
|
EXPECT_THAT(subsamples, ElementsAreArray(kExpectedSubsamples));
|
||||||
|
}
|
||||||
|
|
||||||
TEST_P(SubsampleGeneratorTest, AACIsFullSampleEncrypted) {
|
TEST_P(SubsampleGeneratorTest, AACIsFullSampleEncrypted) {
|
||||||
SubsampleGenerator generator(kVP9SubsampleEncryption);
|
SubsampleGenerator generator(kVP9SubsampleEncryption);
|
||||||
ASSERT_OK(
|
ASSERT_OK(
|
||||||
|
|
Loading…
Reference in New Issue