Move subsample extraction out of EncryptionHandler

Created SubsampleGenerator to generate subsamples.

This is part of the EncryptionHandler clean up to make it more modular
and testable.

Change-Id: I6f4076b057027c72335beb3cbf1965341eb18031
This commit is contained in:
KongQun Yang 2018-10-02 16:08:32 -07:00
parent acaa6b9b3b
commit 5c4d930465
7 changed files with 1017 additions and 465 deletions

View File

@ -19,6 +19,8 @@
'encryption_handler.h',
'sample_aes_ec3_cryptor.cc',
'sample_aes_ec3_cryptor.h',
'subsample_generator.cc',
'subsample_generator.h',
],
'dependencies': [
'../base/media_base.gyp:media_base',
@ -31,6 +33,7 @@
'sources': [
'encryption_handler_unittest.cc',
'sample_aes_ec3_cryptor_unittest.cc',
'subsample_generator_unittest.cc',
],
'dependencies': [
'../../testing/gtest.gyp:gtest',

View File

@ -10,25 +10,21 @@
#include <stdint.h>
#include <algorithm>
#include <limits>
#include "packager/media/base/aes_encryptor.h"
#include "packager/media/base/audio_stream_info.h"
#include "packager/media/base/key_source.h"
#include "packager/media/base/macros.h"
#include "packager/media/base/media_sample.h"
#include "packager/media/base/video_stream_info.h"
#include "packager/media/codecs/video_slice_header_parser.h"
#include "packager/media/codecs/vp8_parser.h"
#include "packager/media/codecs/vp9_parser.h"
#include "packager/media/crypto/aes_encryptor_factory.h"
#include "packager/media/crypto/subsample_generator.h"
#include "packager/status_macros.h"
namespace shaka {
namespace media {
namespace {
const size_t kCencBlockSize = 16u;
// The encryption handler only supports a single output.
const size_t kStreamIndex = 0;
@ -44,31 +40,6 @@ const uint8_t kKeyRotationDefaultIv[] = {
0, 0, 0, 0, 0, 0, 0, 0,
};
// Adds one or more subsamples to |*decrypt_config|. This may add more than one
// if one of the values overflows the integer in the subsample.
void AddSubsample(uint64_t clear_bytes,
uint64_t cipher_bytes,
DecryptConfig* decrypt_config) {
CHECK_LT(cipher_bytes, std::numeric_limits<uint32_t>::max());
const uint64_t kUInt16Max = std::numeric_limits<uint16_t>::max();
while (clear_bytes > kUInt16Max) {
decrypt_config->AddSubsample(kUInt16Max, 0);
clear_bytes -= kUInt16Max;
}
if (clear_bytes > 0 || cipher_bytes > 0)
decrypt_config->AddSubsample(clear_bytes, cipher_bytes);
}
uint8_t GetNaluLengthSize(const StreamInfo& stream_info) {
if (stream_info.stream_type() != kStreamVideo)
return 0;
const VideoStreamInfo& video_stream_info =
static_cast<const VideoStreamInfo&>(stream_info);
return video_stream_info.nalu_length_size();
}
std::string GetStreamLabelForEncryption(
const StreamInfo& stream_info,
const std::function<std::string(
@ -102,6 +73,8 @@ EncryptionHandler::EncryptionHandler(const EncryptionParams& encryption_params,
protection_scheme_(
static_cast<FourCC>(encryption_params.protection_scheme)),
key_source_(key_source),
subsample_generator_(
new SubsampleGenerator(encryption_params.vp9_subsample_encryption)),
encryptor_factory_(new AesEncryptorFactory) {}
EncryptionHandler::~EncryptionHandler() = default;
@ -157,6 +130,8 @@ Status EncryptionHandler::ProcessStreamInfo(const StreamInfo& clear_info) {
DCHECK_NE(kStreamUnknown, clear_info.stream_type());
DCHECK_NE(kStreamText, clear_info.stream_type());
std::shared_ptr<StreamInfo> stream_info = clear_info.Clone();
RETURN_IF_ERROR(
subsample_generator_->Initialize(protection_scheme_, *stream_info));
remaining_clear_lead_ =
encryption_params_.clear_lead_in_seconds * stream_info->time_scale();
@ -164,36 +139,10 @@ Status EncryptionHandler::ProcessStreamInfo(const StreamInfo& clear_info) {
encryption_params_.crypto_period_duration_in_seconds *
stream_info->time_scale();
codec_ = stream_info->codec();
nalu_length_size_ = GetNaluLengthSize(*stream_info);
stream_label_ = GetStreamLabelForEncryption(
*stream_info, encryption_params_.stream_label_func);
switch (codec_) {
case kCodecVP9:
if (encryption_params_.vp9_subsample_encryption)
vpx_parser_.reset(new VP9Parser);
break;
case kCodecH264:
header_parser_.reset(new H264VideoSliceHeaderParser);
break;
case kCodecH265:
header_parser_.reset(new H265VideoSliceHeaderParser);
break;
default:
// Other codecs should have nalu length size == 0.
if (nalu_length_size_ > 0) {
LOG(WARNING) << "Unknown video codec '" << codec_ << "'";
return Status(error::ENCRYPTION_FAILURE, "Unknown video codec.");
}
}
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.");
}
}
RETURN_IF_ERROR(SetupProtectionPattern(stream_info->stream_type()));
SetupProtectionPattern(stream_info->stream_type());
EncryptionKey encryption_key;
const bool key_rotation_enabled = crypto_period_duration_ != 0;
@ -223,15 +172,11 @@ Status EncryptionHandler::ProcessMediaSample(
std::shared_ptr<const MediaSample> clear_sample) {
DCHECK(clear_sample);
// We need to parse the frame (which also updates the vpx parser) even if the
// frame is not encrypted as the next (encrypted) frame may be dependent on
// this clear frame.
std::vector<VPxFrameInfo> vpx_frames;
if (vpx_parser_ && !vpx_parser_->Parse(clear_sample->data(),
clear_sample->data_size(),
&vpx_frames)) {
return Status(error::ENCRYPTION_FAILURE, "Failed to parse vpx frame.");
}
// Process the frame even if the frame is not encrypted as the next
// (encrypted) frame may be dependent on this clear frame.
std::vector<SubsampleEntry> subsamples;
RETURN_IF_ERROR(subsample_generator_->GenerateSubsamples(
clear_sample->data(), clear_sample->data_size(), &subsamples));
// Need to setup the encryptor for new segments even if this segment does not
// need to be encrypted, so we can signal encryption metadata earlier to
@ -258,57 +203,43 @@ Status EncryptionHandler::ProcessMediaSample(
return DispatchMediaSample(kStreamIndex, std::move(clear_sample));
}
std::unique_ptr<DecryptConfig> decrypt_config(new DecryptConfig(
encryption_config_->key_id,
encryptor_->iv(),
std::vector<SubsampleEntry>(),
protection_scheme_,
crypt_byte_block_,
skip_byte_block_));
// Now that we know that this sample must be encrypted, make a copy of
// the sample first so that all the encryption operations can be done
// in-place.
std::shared_ptr<MediaSample> cipher_sample(clear_sample->Clone());
// |cipher_sample| above still contains the old clear sample data. We will
// use |cipher_sample_data| to hold cipher sample data then transfer it to
// |cipher_sample| after encryption.
std::shared_ptr<uint8_t> cipher_sample_data(
new uint8_t[clear_sample->data_size()], std::default_delete<uint8_t[]>());
if (vpx_parser_) {
if (!EncryptVpxFrame(vpx_frames, clear_sample->data(),
clear_sample->data_size(),
&cipher_sample_data.get()[0], decrypt_config.get())) {
return Status(error::ENCRYPTION_FAILURE, "Failed to encrypt VPX frame.");
const uint8_t* source = clear_sample->data();
uint8_t* dest = cipher_sample_data.get();
if (!subsamples.empty()) {
size_t total_size = 0;
for (const SubsampleEntry& subsample : subsamples) {
if (subsample.clear_bytes > 0) {
memcpy(dest, source, subsample.clear_bytes);
source += subsample.clear_bytes;
dest += subsample.clear_bytes;
total_size += subsample.clear_bytes;
}
if (subsample.cipher_bytes > 0) {
EncryptBytes(source, subsample.cipher_bytes, dest);
source += subsample.cipher_bytes;
dest += subsample.cipher_bytes;
total_size += subsample.cipher_bytes;
}
}
DCHECK_EQ(decrypt_config->GetTotalSizeOfSubsamples(),
clear_sample->data_size());
} else if (header_parser_) {
if (!EncryptNalFrame(clear_sample->data(), clear_sample->data_size(),
&cipher_sample_data.get()[0], decrypt_config.get())) {
return Status(error::ENCRYPTION_FAILURE, "Failed to encrypt NAL frame.");
}
DCHECK_EQ(decrypt_config->GetTotalSizeOfSubsamples(),
clear_sample->data_size());
DCHECK_EQ(total_size, clear_sample->data_size());
} else {
memcpy(&cipher_sample_data.get()[0], clear_sample->data(),
std::min(clear_sample->data_size(), leading_clear_bytes_size_));
if (clear_sample->data_size() > leading_clear_bytes_size_) {
// The residual block is left unecrypted (copied without encryption). No
// need to do special handling here.
EncryptBytes(clear_sample->data() + leading_clear_bytes_size_,
clear_sample->data_size() - leading_clear_bytes_size_,
&cipher_sample_data.get()[leading_clear_bytes_size_]);
}
EncryptBytes(source, clear_sample->data_size(), dest);
}
std::shared_ptr<MediaSample> cipher_sample(clear_sample->Clone());
cipher_sample->TransferData(std::move(cipher_sample_data),
clear_sample->data_size());
// Finish initializing the sample before sending it downstream. We must
// wait until now to finish the initialization as we will lose access to
// |decrypt_config| once we set it.
cipher_sample->set_is_encrypted(true);
std::unique_ptr<DecryptConfig> decrypt_config(new DecryptConfig(
encryption_config_->key_id, encryptor_->iv(), subsamples,
protection_scheme_, crypt_byte_block_, skip_byte_block_));
cipher_sample->set_decrypt_config(std::move(decrypt_config));
encryptor_->UpdateIv();
@ -316,33 +247,7 @@ Status EncryptionHandler::ProcessMediaSample(
return DispatchMediaSample(kStreamIndex, std::move(cipher_sample));
}
Status EncryptionHandler::SetupProtectionPattern(StreamType stream_type) {
if (protection_scheme_ == kAppleSampleAesProtectionScheme) {
const size_t kH264LeadingClearBytesSize = 32u;
const size_t kSmallNalUnitSize = 32u + 16u;
const size_t kAudioLeadingClearBytesSize = 16u;
switch (codec_) {
case kCodecH264:
leading_clear_bytes_size_ = kH264LeadingClearBytesSize;
min_protected_data_size_ = kSmallNalUnitSize + 1u;
break;
case kCodecAAC:
FALLTHROUGH_INTENDED;
case kCodecAC3:
FALLTHROUGH_INTENDED;
case kCodecEAC3:
// E-AC3 encryption is handled by SampleAesEc3Cryptor, which also
// manages leading clear bytes.
leading_clear_bytes_size_ =
codec_ == kCodecEAC3 ? 0 : kAudioLeadingClearBytesSize;
min_protected_data_size_ = leading_clear_bytes_size_ + 15u;
break;
default:
return Status(
error::ENCRYPTION_FAILURE,
"Only AAC/AC3/EAC3 and H264 are supported in Sample AES.");
}
}
void EncryptionHandler::SetupProtectionPattern(StreamType stream_type) {
if (stream_type == kStreamVideo &&
IsPatternEncryptionScheme(protection_scheme_)) {
// Use 1:9 pattern.
@ -355,7 +260,6 @@ Status EncryptionHandler::SetupProtectionPattern(StreamType stream_type) {
crypt_byte_block_ = 0u;
skip_byte_block_ = 0u;
}
return Status::OK;
}
bool EncryptionHandler::CreateEncryptor(const EncryptionKey& encryption_key) {
@ -384,124 +288,6 @@ bool EncryptionHandler::CreateEncryptor(const EncryptionKey& encryption_key) {
return true;
}
bool EncryptionHandler::EncryptVpxFrame(
const std::vector<VPxFrameInfo>& vpx_frames,
const uint8_t* source,
size_t source_size,
uint8_t* dest,
DecryptConfig* decrypt_config) {
const uint8_t* data = source;
for (const VPxFrameInfo& frame : vpx_frames) {
uint16_t clear_bytes =
static_cast<uint16_t>(frame.uncompressed_header_size);
uint32_t cipher_bytes = static_cast<uint32_t>(
frame.frame_size - frame.uncompressed_header_size);
// "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.
const uint16_t misalign_bytes = cipher_bytes % kCencBlockSize;
clear_bytes += misalign_bytes;
cipher_bytes -= misalign_bytes;
decrypt_config->AddSubsample(clear_bytes, cipher_bytes);
memcpy(dest, data, clear_bytes);
if (cipher_bytes > 0)
EncryptBytes(data + clear_bytes, cipher_bytes, dest + clear_bytes);
data += frame.frame_size;
dest += frame.frame_size;
}
// Add subsample for the superframe index if exists.
const bool is_superframe = vpx_frames.size() > 1;
if (is_superframe) {
size_t index_size = source + source_size - data;
DCHECK_LE(index_size, 2 + vpx_frames.size() * 4);
DCHECK_GE(index_size, 2 + vpx_frames.size() * 1);
uint16_t clear_bytes = static_cast<uint16_t>(index_size);
uint32_t cipher_bytes = 0;
decrypt_config->AddSubsample(clear_bytes, cipher_bytes);
memcpy(dest, data, clear_bytes);
}
return true;
}
bool EncryptionHandler::EncryptNalFrame(const uint8_t* source,
size_t source_size,
uint8_t* dest,
DecryptConfig* decrypt_config) {
DCHECK_NE(nalu_length_size_, 0u);
DCHECK(header_parser_);
const Nalu::CodecType nalu_type =
(codec_ == kCodecH265) ? Nalu::kH265 : Nalu::kH264;
NaluReader reader(nalu_type, nalu_length_size_, source, source_size);
// Store the current length of clear data. This is used to squash
// multiple unencrypted NAL units into fewer subsample entries.
uint64_t accumulated_clear_bytes = 0;
Nalu nalu;
NaluReader::Result result;
while ((result = reader.Advance(&nalu)) == NaluReader::kOk) {
const uint64_t nalu_total_size = nalu.header_size() + nalu.payload_size();
if (nalu.is_video_slice() && nalu_total_size >= min_protected_data_size_) {
uint64_t current_clear_bytes = leading_clear_bytes_size_;
if (current_clear_bytes == 0) {
// For video-slice NAL units, encrypt the video slice. This skips
// the frame header.
const int64_t video_slice_header_size =
header_parser_->GetHeaderSize(nalu);
if (video_slice_header_size < 0) {
LOG(ERROR) << "Failed to read slice header.";
return false;
}
current_clear_bytes = nalu.header_size() + video_slice_header_size;
}
uint64_t cipher_bytes = nalu_total_size - current_clear_bytes;
// 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.
if (protection_scheme_ == FOURCC_cbc1 ||
protection_scheme_ == FOURCC_cens ||
protection_scheme_ == FOURCC_cenc) {
const uint16_t misalign_bytes = cipher_bytes % kCencBlockSize;
current_clear_bytes += misalign_bytes;
cipher_bytes -= misalign_bytes;
}
accumulated_clear_bytes += nalu_length_size_ + current_clear_bytes;
AddSubsample(accumulated_clear_bytes, cipher_bytes, decrypt_config);
memcpy(dest, source, accumulated_clear_bytes);
source += accumulated_clear_bytes;
dest += accumulated_clear_bytes;
accumulated_clear_bytes = 0;
DCHECK_EQ(nalu.data() + current_clear_bytes, source);
EncryptBytes(source, cipher_bytes, dest);
source += cipher_bytes;
dest += cipher_bytes;
} else {
// For non-video-slice or small NAL units, don't encrypt.
accumulated_clear_bytes += nalu_length_size_ + nalu_total_size;
}
}
if (result != NaluReader::kEOStream) {
LOG(ERROR) << "Failed to parse NAL units.";
return false;
}
AddSubsample(accumulated_clear_bytes, 0, decrypt_config);
memcpy(dest, source, accumulated_clear_bytes);
return true;
}
void EncryptionHandler::EncryptBytes(const uint8_t* source,
size_t source_size,
uint8_t* dest) {
@ -511,14 +297,9 @@ void EncryptionHandler::EncryptBytes(const uint8_t* source,
CHECK(encryptor_->Crypt(source, source_size, dest));
}
void EncryptionHandler::InjectVpxParserForTesting(
std::unique_ptr<VPxParser> vpx_parser) {
vpx_parser_ = std::move(vpx_parser);
}
void EncryptionHandler::InjectVideoSliceHeaderParserForTesting(
std::unique_ptr<VideoSliceHeaderParser> header_parser) {
header_parser_ = std::move(header_parser);
void EncryptionHandler::InjectSubsampleGeneratorForTesting(
std::unique_ptr<SubsampleGenerator> generator) {
subsample_generator_ = std::move(generator);
}
void EncryptionHandler::InjectEncryptorFactoryForTesting(

View File

@ -16,10 +16,8 @@ namespace media {
class AesCryptor;
class AesEncryptorFactory;
class VideoSliceHeaderParser;
class VPxParser;
class SubsampleGenerator;
struct EncryptionKey;
struct VPxFrameInfo;
class EncryptionHandler : public MediaHandler {
public:
@ -46,21 +44,8 @@ class EncryptionHandler : public MediaHandler {
// Processes media sample and encrypts it if needed.
Status ProcessMediaSample(std::shared_ptr<const MediaSample> clear_sample);
Status SetupProtectionPattern(StreamType stream_type);
void SetupProtectionPattern(StreamType stream_type);
bool CreateEncryptor(const EncryptionKey& encryption_key);
// Encrypt a VPx frame with size |source_size|. |dest| should have at least
// |source_size| bytes.
bool EncryptVpxFrame(const std::vector<VPxFrameInfo>& vpx_frames,
const uint8_t* source,
size_t source_size,
uint8_t* dest,
DecryptConfig* decrypt_config);
// Encrypt a NAL unit frame with size |source_size|. |dest| should have at
// least |source_size| bytes.
bool EncryptNalFrame(const uint8_t* source,
size_t source_size,
uint8_t* dest,
DecryptConfig* decrypt_config);
// Encrypt an E-AC3 frame with size |source_size| according to SAMPLE-AES
// specification. |dest| should have at least |source_size| bytes.
bool SampleAesEncryptEac3Frame(const uint8_t* source,
@ -78,9 +63,8 @@ class EncryptionHandler : public MediaHandler {
std::vector<size_t>* syncframe_sizes);
// Testing injections.
void InjectVpxParserForTesting(std::unique_ptr<VPxParser> vpx_parser);
void InjectVideoSliceHeaderParserForTesting(
std::unique_ptr<VideoSliceHeaderParser> header_parser);
void InjectSubsampleGeneratorForTesting(
std::unique_ptr<SubsampleGenerator> generator);
void InjectEncryptorFactoryForTesting(
std::unique_ptr<AesEncryptorFactory> encryptor_factory);
@ -92,15 +76,6 @@ class EncryptionHandler : public MediaHandler {
std::shared_ptr<EncryptionConfig> encryption_config_;
std::unique_ptr<AesCryptor> encryptor_;
Codec codec_ = kUnknownCodec;
// Specifies the size of NAL unit length in bytes. Can be 1, 2 or 4 bytes. 0
// if it is not a NAL structured video.
uint8_t nalu_length_size_ = 0;
// For Sample AES, 32 bytes for Video and 16 bytes for audio.
size_t leading_clear_bytes_size_ = 0;
// For Sample AES, if the data size is less than this value, none of the bytes
// are encrypted. The size is 48+1 bytes for video NAL and 16+15 bytes for
// audio according to MPEG-2 Stream Encryption Format for HTTP Live Streaming.
size_t min_protected_data_size_ = 0;
// Remaining clear lead in the stream's time scale.
int64_t remaining_clear_lead_ = 0;
// Crypto period duration in the stream's time scale.
@ -109,16 +84,12 @@ class EncryptionHandler : public MediaHandler {
int64_t prev_crypto_period_index_ = -1;
bool check_new_crypto_period_ = false;
std::unique_ptr<SubsampleGenerator> subsample_generator_;
std::unique_ptr<AesEncryptorFactory> encryptor_factory_;
// Number of encrypted blocks (16-byte-block) in pattern based encryption.
uint8_t crypt_byte_block_ = 0;
/// Number of unencrypted blocks (16-byte-block) in pattern based encryption.
uint8_t skip_byte_block_ = 0;
std::unique_ptr<AesEncryptorFactory> encryptor_factory_;
// VPx parser for VPx streams.
std::unique_ptr<VPxParser> vpx_parser_;
// Video slice header parser for NAL strucutred streams.
std::unique_ptr<VideoSliceHeaderParser> header_parser_;
};
} // namespace media

View File

@ -13,9 +13,8 @@
#include "packager/media/base/media_handler_test_base.h"
#include "packager/media/base/mock_aes_cryptor.h"
#include "packager/media/base/raw_key_source.h"
#include "packager/media/codecs/video_slice_header_parser.h"
#include "packager/media/codecs/vpx_parser.h"
#include "packager/media/crypto/aes_encryptor_factory.h"
#include "packager/media/crypto/subsample_generator.h"
#include "packager/status_test_util.h"
namespace shaka {
@ -69,19 +68,16 @@ class MockKeySource : public RawKeySource {
EncryptionKey* key));
};
class MockVpxParser : public VPxParser {
class MockSubsampleGenerator : public SubsampleGenerator {
public:
MOCK_METHOD3(Parse,
bool(const uint8_t* data,
size_t data_size,
std::vector<VPxFrameInfo>* vpx_frames));
};
MockSubsampleGenerator() : SubsampleGenerator(true) {}
class MockVideoSliceHeaderParser : public VideoSliceHeaderParser {
public:
MOCK_METHOD1(Initialize,
bool(const std::vector<uint8_t>& decoder_configuration));
MOCK_METHOD1(GetHeaderSize, int64_t(const Nalu& nalu));
MOCK_METHOD2(Initialize,
Status(FourCC protection_scheme, const StreamInfo& stream_info));
MOCK_METHOD3(GenerateSubsamples,
Status(const uint8_t* frame,
size_t frame_size,
std::vector<SubsampleEntry>* subsamples));
};
class MockAesEncryptorFactory : public AesEncryptorFactory {
@ -118,6 +114,9 @@ class EncryptionHandlerTest : public MediaHandlerGraphTestBase {
encryption_handler_.reset(
new EncryptionHandler(new_encryption_params, &mock_key_source_));
SetUpGraph(1 /* one input */, 1 /* one output */, encryption_handler_);
// Inject default subsamples to avoid parsing problems.
const std::vector<SubsampleEntry> empty_subsamples;
InjectSubsamples(empty_subsamples);
}
Status Process(std::unique_ptr<StreamData> stream_data) {
@ -132,14 +131,15 @@ class EncryptionHandlerTest : public MediaHandlerGraphTestBase {
return encryption_key;
}
void InjectVpxParserForTesting(std::unique_ptr<VPxParser> vpx_parser) {
encryption_handler_->InjectVpxParserForTesting(std::move(vpx_parser));
}
void InjectSubsamples(const std::vector<SubsampleEntry>& subsamples) {
std::unique_ptr<MockSubsampleGenerator> mock_generator(
new MockSubsampleGenerator);
EXPECT_CALL(*mock_generator, GenerateSubsamples(_, _, _))
.WillRepeatedly(
DoAll(SetArgPointee<2>(subsamples), Return(Status::OK)));
void InjectVideoSliceHeaderParserForTesting(
std::unique_ptr<VideoSliceHeaderParser> header_parser) {
encryption_handler_->InjectVideoSliceHeaderParserForTesting(
std::move(header_parser));
encryption_handler_->InjectSubsampleGeneratorForTesting(
std::move(mock_generator));
}
void InjectEncryptorFactoryForTesting(
@ -199,154 +199,25 @@ TEST_F(EncryptionHandlerTest, CreateEncryptorFailed) {
namespace {
const bool kVp9SubsampleEncryption = true;
const bool kIsKeyFrame = true;
const bool kIsSubsegment = true;
const bool kEncrypted = true;
const int64_t kSegmentDuration = 1000;
// The data is based on H264. The same data is also used to test audio, which
// does not care the underlying data, and VP9, for which we will mock the
// parser.
const uint8_t kData[]{
// First NALU
0x30, 0x01, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20, 0x21,
0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x30, 0x31, 0x32, 0x33,
0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45,
0x46,
// Second NALU
0x31, 0x25, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20, 0x21,
0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x30, 0x31, 0x32, 0x33,
0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45,
0x46, 0x47,
// Third non-video-slice NALU for H264 or superframe index for VP9.
0x06, 0x67, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
};
// The contents of the data does not matter.
const uint8_t kData[] = {0x00, 0x01, 0x02, 0x03, 0x04,
0x05, 0x06, 0x07, 0x08, 0x09};
const size_t kDataSize = sizeof(kData);
// H264 subsample information for the the above data.
const size_t kNaluLengthSize = 1u;
const size_t kNaluHeaderSize = 1u;
const size_t kSubsampleSize1 = 49u;
const size_t kSliceHeaderSize1 = 1u;
const size_t kSubsampleSize2 = 50u;
const size_t kSliceHeaderSize2 = 16u;
const size_t kSubsampleSize3 = 7u;
// VP9 frame information for the above data. It should match H264 subsample
// information.
const size_t kVpxFrameSize1 = kSubsampleSize1;
const size_t kUncompressedHeaderSize1 =
kNaluLengthSize + kNaluHeaderSize + kSliceHeaderSize1;
const size_t kVpxFrameSize2 = kSubsampleSize2;
const size_t kUncompressedHeaderSize2 =
kNaluLengthSize + kNaluHeaderSize + kSliceHeaderSize2;
// Subsample pairs for the above data.
const size_t kClearSize1 = kUncompressedHeaderSize1;
const size_t kCipherSize1 = kVpxFrameSize1 - kUncompressedHeaderSize1;
const size_t kClearSize2 = kUncompressedHeaderSize2;
const size_t kCipherSize2 = kVpxFrameSize2 - kUncompressedHeaderSize2;
// Align cipher bytes for some protection schemes.
const size_t kAesBlockSize = 16u;
const size_t kAlignedClearSize1 = kClearSize1 + kCipherSize1 % kAesBlockSize;
static_assert(kAlignedClearSize1 != kClearSize1,
"Clearsize 1 should not be aligned");
const size_t kAlignedCipherSize1 = kCipherSize1 - kCipherSize1 % kAesBlockSize;
// Apple Sample AES.
const size_t kVideoLeadingClearBytesSize = 32u + kNaluLengthSize;
// Subsample 1 is <= 48 bytes, so not encrypted and merged with subsample2.
const size_t kSampleAesClearSize1 =
kSubsampleSize1 + kVideoLeadingClearBytesSize;
const size_t kSampleAesCipherSize1 =
kSubsampleSize2 - kVideoLeadingClearBytesSize;
} // namespace
inline bool operator==(const SubsampleEntry& lhs, const SubsampleEntry& rhs) {
return lhs.clear_bytes == rhs.clear_bytes &&
lhs.cipher_bytes == rhs.cipher_bytes;
}
class EncryptionHandlerEncryptionTest
: public EncryptionHandlerTest,
public WithParamInterface<std::tr1::tuple<FourCC, Codec, bool>> {
public WithParamInterface<std::tr1::tuple<FourCC, Codec>> {
public:
void SetUp() override {
protection_scheme_ = std::tr1::get<0>(GetParam());
codec_ = std::tr1::get<1>(GetParam());
vp9_subsample_encryption_ = std::tr1::get<2>(GetParam());
}
std::vector<VPxFrameInfo> GetMockVpxFrameInfo() {
std::vector<VPxFrameInfo> vpx_frames;
vpx_frames.resize(2);
vpx_frames[0].frame_size = kVpxFrameSize1;
vpx_frames[0].uncompressed_header_size = kUncompressedHeaderSize1;
vpx_frames[1].frame_size = kVpxFrameSize2;
vpx_frames[1].uncompressed_header_size = kUncompressedHeaderSize2;
return vpx_frames;
}
// The subsamples values should match |GetMockVpxFrameInfo| above.
std::vector<SubsampleEntry> GetExpectedSubsamples() {
std::vector<SubsampleEntry> subsamples;
if (codec_ == kCodecAAC ||
(codec_ == kCodecVP9 && !vp9_subsample_encryption_)) {
return subsamples;
}
if (protection_scheme_ == kAppleSampleAesProtectionScheme) {
subsamples.emplace_back(static_cast<uint16_t>(kSampleAesClearSize1),
static_cast<uint32_t>(kSampleAesCipherSize1));
subsamples.emplace_back(static_cast<uint16_t>(kSubsampleSize3), 0u);
} else {
if (codec_ == kCodecVP9 || protection_scheme_ == FOURCC_cbc1 ||
protection_scheme_ == FOURCC_cens ||
protection_scheme_ == FOURCC_cenc) {
// Align the encrypted bytes to multiple of 16 bytes.
subsamples.emplace_back(static_cast<uint16_t>(kAlignedClearSize1),
static_cast<uint32_t>(kAlignedCipherSize1));
// Subsample 2 is already aligned.
} else {
subsamples.emplace_back(static_cast<uint16_t>(kClearSize1),
static_cast<uint32_t>(kCipherSize1));
}
subsamples.emplace_back(static_cast<uint16_t>(kClearSize2),
static_cast<uint32_t>(kCipherSize2));
subsamples.emplace_back(static_cast<uint16_t>(kSubsampleSize3), 0u);
}
return subsamples;
}
// Inject vpx parser / video slice header parser if needed.
void InjectCodecParser() {
switch (codec_) {
case kCodecVP9:
if (vp9_subsample_encryption_) {
std::unique_ptr<MockVpxParser> mock_vpx_parser(new MockVpxParser);
EXPECT_CALL(*mock_vpx_parser, Parse(_, kDataSize, _))
.WillRepeatedly(
DoAll(SetArgPointee<2>(GetMockVpxFrameInfo()), Return(true)));
InjectVpxParserForTesting(std::move(mock_vpx_parser));
}
break;
case kCodecH264: {
std::unique_ptr<MockVideoSliceHeaderParser> mock_header_parser(
new MockVideoSliceHeaderParser);
if (protection_scheme_ == kAppleSampleAesProtectionScheme) {
EXPECT_CALL(*mock_header_parser, GetHeaderSize(_)).Times(0);
} else {
EXPECT_CALL(*mock_header_parser, GetHeaderSize(_))
.WillOnce(Return(kSliceHeaderSize1))
.WillOnce(Return(kSliceHeaderSize2))
.WillRepeatedly(Return(kSliceHeaderSize2));
}
InjectVideoSliceHeaderParserForTesting(std::move(mock_header_parser));
break;
}
default:
break;
}
}
uint8_t GetExpectedCryptByteBlock() {
@ -407,7 +278,6 @@ class EncryptionHandlerEncryptionTest
protected:
FourCC protection_scheme_;
Codec codec_;
bool vp9_subsample_encryption_;
};
TEST_P(EncryptionHandlerEncryptionTest, VerifyEncryptorFactoryParams) {
@ -444,7 +314,6 @@ TEST_P(EncryptionHandlerEncryptionTest, ClearLeadWithNoKeyRotation) {
EncryptionParams encryption_params;
encryption_params.protection_scheme = protection_scheme_;
encryption_params.clear_lead_in_seconds = kClearLeadInSeconds;
encryption_params.vp9_subsample_encryption = vp9_subsample_encryption_;
SetUpEncryptionHandler(encryption_params);
const EncryptionKey mock_encryption_key = GetMockEncryptionKey();
@ -475,8 +344,6 @@ TEST_P(EncryptionHandlerEncryptionTest, ClearLeadWithNoKeyRotation) {
ClearOutputStreamDataVector();
Mock::VerifyAndClearExpectations(&mock_key_source_);
InjectCodecParser();
// There are three segments. Only the third segment is encrypted.
for (int i = 0; i < 3; ++i) {
// Use single-frame segment for testing.
@ -501,7 +368,7 @@ TEST_P(EncryptionHandlerEncryptionTest, ClearLeadWithNoKeyRotation) {
decrypt_config->key_id());
EXPECT_EQ(std::vector<uint8_t>(kIv, kIv + sizeof(kIv)),
decrypt_config->iv());
EXPECT_EQ(GetExpectedSubsamples(), decrypt_config->subsamples());
EXPECT_TRUE(decrypt_config->subsamples().empty());
EXPECT_EQ(protection_scheme_, decrypt_config->protection_scheme());
EXPECT_EQ(GetExpectedCryptByteBlock(),
decrypt_config->crypt_byte_block());
@ -523,7 +390,6 @@ TEST_P(EncryptionHandlerEncryptionTest, ClearLeadWithKeyRotation) {
encryption_params.clear_lead_in_seconds = kClearLeadInSeconds;
encryption_params.crypto_period_duration_in_seconds =
kCryptoPeriodDurationInSeconds;
encryption_params.vp9_subsample_encryption = vp9_subsample_encryption_;
SetUpEncryptionHandler(encryption_params);
if (IsVideoCodec(codec_)) {
@ -550,8 +416,6 @@ TEST_P(EncryptionHandlerEncryptionTest, ClearLeadWithKeyRotation) {
encryption_config.key_id);
ClearOutputStreamDataVector();
InjectCodecParser();
// There are five segments with the first two not encrypted.
for (int i = 0; i < 5; ++i) {
if ((i % kSegmentsPerCryptoPeriod) == 0) {
@ -586,17 +450,109 @@ TEST_P(EncryptionHandlerEncryptionTest, ClearLeadWithKeyRotation) {
}
}
INSTANTIATE_TEST_CASE_P(
CencProtectionSchemes,
EncryptionHandlerEncryptionTest,
Combine(Values(FOURCC_cenc, FOURCC_cens, FOURCC_cbc1, FOURCC_cbcs),
Values(kCodecAAC, kCodecH264, kCodecVP9),
Values(kVp9SubsampleEncryption, !kVp9SubsampleEncryption)));
INSTANTIATE_TEST_CASE_P(AppleSampleAes,
INSTANTIATE_TEST_CASE_P(ProtectionSchemes,
EncryptionHandlerEncryptionTest,
Combine(Values(kAppleSampleAesProtectionScheme),
Values(kCodecAAC, kCodecH264),
Values(kVp9SubsampleEncryption)));
Combine(Values(kAppleSampleAesProtectionScheme,
FOURCC_cenc,
FOURCC_cens,
FOURCC_cbc1,
FOURCC_cbcs),
Values(kCodecAAC, kCodecH264)));
struct SubsampleTestCase {
std::vector<SubsampleEntry> subsamples;
std::vector<uint8_t> expected_output;
};
inline bool operator==(const SubsampleEntry& lhs, const SubsampleEntry& rhs) {
return lhs.clear_bytes == rhs.clear_bytes &&
lhs.cipher_bytes == rhs.cipher_bytes;
}
namespace {
const int64_t kSampleDuration = 1000;
// This mock encryption increases every byte by 0x10. See the function below.
const SubsampleTestCase kSubsampleTestCases[] = {
{
std::vector<SubsampleEntry>(), // No subsamples, i.e. full sample
// encrypted.
{0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19},
},
{
{{8, 2}}, // One subsample.
{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x18, 0x19},
},
{
{{6, 2}, {2, 0}}, // Two subsamples.
{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x16, 0x17, 0x08, 0x09},
},
{
{{6, 2}, {0, 2}}, // Two subsamples.
{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x16, 0x17, 0x18, 0x19},
},
};
bool MockEncrypt(const uint8_t* text,
size_t text_size,
uint8_t* crypt_text,
size_t* crypt_text_size) {
*crypt_text_size = text_size;
for (size_t i = 0; i < text_size; i++)
crypt_text[i] = text[i] + 0x10;
return true;
}
} // namespace
class EncryptionHandlerSubsampleTest
: public EncryptionHandlerTest,
public WithParamInterface<SubsampleTestCase> {};
INSTANTIATE_TEST_CASE_P(SubsampleTestCases,
EncryptionHandlerSubsampleTest,
ValuesIn(kSubsampleTestCases));
TEST_P(EncryptionHandlerSubsampleTest, SubsampleTest) {
std::unique_ptr<MockAesCryptor> mock_encryptor(new MockAesCryptor);
EXPECT_CALL(*mock_encryptor, CryptInternal(_, _, _, _))
.WillRepeatedly(Invoke(MockEncrypt));
ASSERT_TRUE(mock_encryptor->SetIv(
std::vector<uint8_t>(std::begin(kIv), std::end(kIv))));
std::unique_ptr<MockAesEncryptorFactory> mock_encryptor_factory(
new MockAesEncryptorFactory);
EXPECT_CALL(*mock_encryptor_factory, CreateEncryptor(_, _, _, _, _, _))
.WillOnce(Return(ByMove(std::move(mock_encryptor))));
InjectEncryptorFactoryForTesting(std::move(mock_encryptor_factory));
InjectSubsamples(GetParam().subsamples);
EXPECT_CALL(mock_key_source_, GetKey(_, _))
.WillOnce(
DoAll(SetArgPointee<1>(GetMockEncryptionKey()), Return(Status::OK)));
ASSERT_OK(Process(StreamData::FromStreamInfo(
kStreamIndex, GetVideoStreamInfo(kTimeScale, kCodecH264))));
ASSERT_OK(Process(StreamData::FromMediaSample(
kStreamIndex,
GetMediaSample(0, kSampleDuration, kIsKeyFrame, kData, kDataSize))));
const auto& output_stream_data = GetOutputStreamDataVector();
EXPECT_THAT(output_stream_data,
ElementsAre(IsStreamInfo(kStreamIndex, kTimeScale, kEncrypted, _),
IsMediaSample(kStreamIndex, 0, kSampleDuration,
kEncrypted, _)));
const MediaSample& sample = *output_stream_data.back()->media_sample;
EXPECT_EQ(
GetParam().expected_output,
std::vector<uint8_t>(sample.data(), sample.data() + sample.data_size()));
const DecryptConfig& decrypt_config = *sample.decrypt_config();
EXPECT_EQ(GetParam().subsamples, decrypt_config.subsamples());
}
class EncryptionHandlerTrackTypeTest : public EncryptionHandlerTest {};

View File

@ -0,0 +1,303 @@
// Copyright 2018 Google LLC. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
#include "packager/media/crypto/subsample_generator.h"
#include <algorithm>
#include <limits>
#include "packager/media/base/decrypt_config.h"
#include "packager/media/base/video_stream_info.h"
#include "packager/media/codecs/video_slice_header_parser.h"
#include "packager/media/codecs/vp8_parser.h"
#include "packager/media/codecs/vp9_parser.h"
namespace shaka {
namespace media {
namespace {
const size_t kAesBlockSize = 16u;
uint8_t GetNaluLengthSize(const StreamInfo& stream_info) {
if (stream_info.stream_type() != kStreamVideo)
return 0;
const VideoStreamInfo& video_stream_info =
static_cast<const VideoStreamInfo&>(stream_info);
return video_stream_info.nalu_length_size();
}
// 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.
class SubsampleOrganizer {
public:
SubsampleOrganizer(bool align_protected_data,
std::vector<SubsampleEntry>* subsamples)
: align_protected_data_(align_protected_data), subsamples_(subsamples) {}
~SubsampleOrganizer() {
if (accumulated_clear_bytes_ > 0) {
PushSubsample(accumulated_clear_bytes_, 0);
accumulated_clear_bytes_ = 0;
}
}
void AddSubsample(size_t clear_bytes, size_t cipher_bytes) {
DCHECK_LT(clear_bytes, std::numeric_limits<uint32_t>::max());
DCHECK_LT(cipher_bytes, std::numeric_limits<uint32_t>::max());
if (align_protected_data_ && cipher_bytes != 0) {
const size_t misalign_bytes = cipher_bytes % kAesBlockSize;
clear_bytes += misalign_bytes;
cipher_bytes -= misalign_bytes;
}
accumulated_clear_bytes_ += clear_bytes;
// Accumulated clear bytes are handled later.
if (cipher_bytes == 0)
return;
PushSubsample(accumulated_clear_bytes_, cipher_bytes);
accumulated_clear_bytes_ = 0;
}
private:
SubsampleOrganizer(const SubsampleOrganizer&) = delete;
SubsampleOrganizer& operator=(const SubsampleOrganizer&) = delete;
void PushSubsample(size_t clear_bytes, size_t cipher_bytes) {
const uint16_t kUInt16Max = std::numeric_limits<uint16_t>::max();
while (clear_bytes > kUInt16Max) {
subsamples_->emplace_back(kUInt16Max, 0);
clear_bytes -= kUInt16Max;
}
subsamples_->emplace_back(static_cast<uint16_t>(clear_bytes),
static_cast<uint32_t>(cipher_bytes));
}
const bool align_protected_data_ = false;
std::vector<SubsampleEntry>* const subsamples_ = nullptr;
size_t accumulated_clear_bytes_ = 0;
};
} // namespace
SubsampleGenerator::SubsampleGenerator(bool vp9_subsample_encryption)
: vp9_subsample_encryption_(vp9_subsample_encryption) {}
SubsampleGenerator::~SubsampleGenerator() {}
Status SubsampleGenerator::Initialize(FourCC protection_scheme,
const StreamInfo& stream_info) {
codec_ = stream_info.codec();
nalu_length_size_ = GetNaluLengthSize(stream_info);
switch (codec_) {
case kCodecVP9:
if (vp9_subsample_encryption_)
vpx_parser_.reset(new VP9Parser);
break;
case kCodecH264:
header_parser_.reset(new H264VideoSliceHeaderParser);
break;
case kCodecH265:
header_parser_.reset(new H265VideoSliceHeaderParser);
break;
default:
// Other codecs should have nalu length size == 0.
if (nalu_length_size_ > 0) {
LOG(WARNING) << "Unknown video codec '" << codec_ << "'";
return Status(error::ENCRYPTION_FAILURE, "Unknown video codec.");
}
}
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.");
}
}
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;
}
if (protection_scheme == kAppleSampleAesProtectionScheme) {
const size_t kH264LeadingClearBytesSize = 32u;
const size_t kAudioLeadingClearBytesSize = 16u;
switch (codec_) {
case kCodecH264:
leading_clear_bytes_size_ = kH264LeadingClearBytesSize;
min_protected_data_size_ =
leading_clear_bytes_size_ + kAesBlockSize + 1u;
break;
case kCodecAAC:
FALLTHROUGH_INTENDED;
case kCodecAC3:
leading_clear_bytes_size_ = kAudioLeadingClearBytesSize;
min_protected_data_size_ = leading_clear_bytes_size_ + kAesBlockSize;
break;
case kCodecEAC3:
// E-AC3 encryption is handled by SampleAesEc3Cryptor, which also
// manages leading clear bytes.
leading_clear_bytes_size_ = 0;
min_protected_data_size_ = leading_clear_bytes_size_ + kAesBlockSize;
break;
default:
LOG(ERROR) << "Unexpected codec for SAMPLE-AES " << codec_;
return Status(error::ENCRYPTION_FAILURE,
"Unexpected codec for SAMPLE-AES.");
}
}
return Status::OK;
}
Status SubsampleGenerator::GenerateSubsamples(
const uint8_t* frame,
size_t frame_size,
std::vector<SubsampleEntry>* subsamples) {
subsamples->clear();
switch (codec_) {
case kCodecH264:
FALLTHROUGH_INTENDED;
case kCodecH265:
return GenerateSubsamplesFromH26xFrame(frame, frame_size, subsamples);
case kCodecVP9:
if (vp9_subsample_encryption_)
return GenerateSubsamplesFromVPxFrame(frame, frame_size, subsamples);
// Full sample encrypted so no subsamples.
break;
default:
// Other codecs are full sample encrypted unless there are clear leading
// bytes.
if (leading_clear_bytes_size_ > 0) {
SubsampleOrganizer subsample_organizer(align_protected_data_,
subsamples);
const size_t clear_bytes =
std::min(frame_size, leading_clear_bytes_size_);
const size_t cipher_bytes = frame_size - clear_bytes;
subsample_organizer.AddSubsample(clear_bytes, cipher_bytes);
} else {
// Full sample encrypted so no subsamples.
}
break;
}
return Status::OK;
}
void SubsampleGenerator::InjectVpxParserForTesting(
std::unique_ptr<VPxParser> vpx_parser) {
vpx_parser_ = std::move(vpx_parser);
}
void SubsampleGenerator::InjectVideoSliceHeaderParserForTesting(
std::unique_ptr<VideoSliceHeaderParser> header_parser) {
header_parser_ = std::move(header_parser);
}
Status SubsampleGenerator::GenerateSubsamplesFromVPxFrame(
const uint8_t* frame,
size_t frame_size,
std::vector<SubsampleEntry>* subsamples) {
DCHECK(vpx_parser_);
std::vector<VPxFrameInfo> vpx_frames;
if (!vpx_parser_->Parse(frame, frame_size, &vpx_frames))
return Status(error::ENCRYPTION_FAILURE, "Failed to parse vpx frame.");
SubsampleOrganizer subsample_organizer(align_protected_data_, subsamples);
size_t total_size = 0;
for (const VPxFrameInfo& frame : vpx_frames) {
subsample_organizer.AddSubsample(
frame.uncompressed_header_size,
frame.frame_size - frame.uncompressed_header_size);
total_size += frame.frame_size;
}
// Add subsample for the superframe index if exists.
const bool is_superframe = vpx_frames.size() > 1;
if (is_superframe) {
const size_t index_size = frame_size - total_size;
DCHECK_LE(index_size, 2 + vpx_frames.size() * 4);
DCHECK_GE(index_size, 2 + vpx_frames.size() * 1);
subsample_organizer.AddSubsample(index_size, 0);
} else {
DCHECK_EQ(total_size, frame_size);
}
return Status::OK;
}
Status SubsampleGenerator::GenerateSubsamplesFromH26xFrame(
const uint8_t* frame,
size_t frame_size,
std::vector<SubsampleEntry>* subsamples) {
DCHECK_NE(nalu_length_size_, 0u);
DCHECK(header_parser_);
SubsampleOrganizer subsample_organizer(align_protected_data_, subsamples);
const Nalu::CodecType nalu_type =
(codec_ == kCodecH265) ? Nalu::kH265 : Nalu::kH264;
NaluReader reader(nalu_type, nalu_length_size_, frame, frame_size);
Nalu nalu;
NaluReader::Result result;
while ((result = reader.Advance(&nalu)) == NaluReader::kOk) {
const size_t nalu_total_size = nalu.header_size() + nalu.payload_size();
size_t clear_bytes = 0;
if (nalu.is_video_slice() && nalu_total_size >= min_protected_data_size_) {
clear_bytes = leading_clear_bytes_size_;
if (clear_bytes == 0) {
// For video-slice NAL units, encrypt the video slice. This skips
// the frame header.
const int64_t video_slice_header_size =
header_parser_->GetHeaderSize(nalu);
if (video_slice_header_size < 0) {
LOG(ERROR) << "Failed to read slice header.";
return Status(error::ENCRYPTION_FAILURE,
"Failed to read slice header.");
}
clear_bytes = nalu.header_size() + video_slice_header_size;
}
} else {
// For non-video-slice or small NAL units, don't encrypt.
clear_bytes = nalu_total_size;
}
const size_t cipher_bytes = nalu_total_size - clear_bytes;
subsample_organizer.AddSubsample(nalu_length_size_ + clear_bytes,
cipher_bytes);
}
if (result != NaluReader::kEOStream) {
LOG(ERROR) << "Failed to parse NAL units.";
return Status(error::ENCRYPTION_FAILURE, "Failed to parse NAL units.");
}
return Status::OK;
}
} // namespace media
} // namespace shaka

View File

@ -0,0 +1,103 @@
// Copyright 2018 Google LLC. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
#ifndef PACKAGER_MEDIA_CRYPTO_SUBSAMPLE_GENERATOR_H_
#define PACKAGER_MEDIA_CRYPTO_SUBSAMPLE_GENERATOR_H_
#include <memory>
#include <vector>
#include "packager/media/base/fourccs.h"
#include "packager/media/base/stream_info.h"
#include "packager/status.h"
namespace shaka {
namespace media {
class VideoSliceHeaderParser;
class VPxParser;
struct SubsampleEntry;
/// Parsing and generating encryption subsamples from bitstreams. Note that the
/// class can be used to generate subsamples from both audio and video
/// bitstreams according to relevant specifications. For example, for video
/// streams, the most notable specifications are Common Encryption v3 spec [1]
/// and Apple SAMPLE AES spec [2]; for audio streams, they are full sample
/// encrypted except for Apple SAMPLE AES spec, which usually contains leading
/// clear bytes.
/// [1] https://www.iso.org/standard/68042.html
/// [2] https://apple.co/2Noi43q
class SubsampleGenerator {
public:
/// @param vp9_subsample_encryption determines if subsample encryption or full
/// sample encryption is used for VP9. Only relevant for VP9 codec.
explicit SubsampleGenerator(bool vp9_subsample_encryption);
virtual ~SubsampleGenerator();
/// Initialize the generator.
/// @param protection_scheme is the protection scheme to be used for the
/// encryption. It is used to determine if the protected data should be
/// block aligned.
/// @param stream_info contains stream information.
/// @returns OK on success, an error status otherwise.
virtual Status Initialize(FourCC protection_scheme,
const StreamInfo& stream_info);
/// Generates subsamples from the bitstream. Note that all frames should be
/// processed by this function even if it is not encrypted as the next
/// (encrypted) frame may be dependent on the previous clear frames.
/// @param frame points to the start of the frame.
/// @param frame_size is the size of the frame.
/// @param[out] subsamples will contain the output subsamples on success. It
/// will be empty if the frame should be full sample encrypted.
/// @returns OK on success, an error status otherwise.
virtual Status GenerateSubsamples(const uint8_t* frame,
size_t frame_size,
std::vector<SubsampleEntry>* subsamples);
// Testing injections.
void InjectVpxParserForTesting(std::unique_ptr<VPxParser> vpx_parser);
void InjectVideoSliceHeaderParserForTesting(
std::unique_ptr<VideoSliceHeaderParser> header_parser);
private:
SubsampleGenerator(const SubsampleGenerator&) = delete;
SubsampleGenerator& operator=(const SubsampleGenerator&) = delete;
Status GenerateSubsamplesFromVPxFrame(
const uint8_t* frame,
size_t frame_size,
std::vector<SubsampleEntry>* subsamples);
Status GenerateSubsamplesFromH26xFrame(
const uint8_t* frame,
size_t frame_size,
std::vector<SubsampleEntry>* subsamples);
const bool vp9_subsample_encryption_ = false;
// Whether the protected portion should be AES block (16 bytes) aligned.
bool align_protected_data_ = false;
Codec codec_ = kUnknownCodec;
// For NAL structured video only, the size of NAL unit length in bytes. Can be
// 1, 2 or 4 bytes.
uint8_t nalu_length_size_ = 0;
// For SAMPLE AES only, 32 bytes for Video and 16 bytes for audio.
size_t leading_clear_bytes_size_ = 0;
// For SAMPLE AES only, if the data size is less than this value, none of the
// bytes are encrypted. The size is 48+1 bytes for video NAL and 32 bytes for
// audio according to MPEG-2 Stream Encryption Format for HTTP Live Streaming.
size_t min_protected_data_size_ = 0;
// VPx parser for VPx streams.
std::unique_ptr<VPxParser> vpx_parser_;
// Video slice header parser for NAL strucutred streams.
std::unique_ptr<VideoSliceHeaderParser> header_parser_;
};
} // namespace media
} // namespace shaka
#endif // PACKAGER_MEDIA_CRYPTO_SUBSAMPLE_GENERATOR_H_

View File

@ -0,0 +1,435 @@
// Copyright 2018 Google LLC. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
#include "packager/media/crypto/subsample_generator.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "packager/media/base/audio_stream_info.h"
#include "packager/media/base/video_stream_info.h"
#include "packager/media/codecs/video_slice_header_parser.h"
#include "packager/media/codecs/vpx_parser.h"
#include "packager/status_test_util.h"
namespace shaka {
namespace media {
namespace {
using ::testing::_;
using ::testing::DoAll;
using ::testing::ElementsAre;
using ::testing::ElementsAreArray;
using ::testing::Return;
using ::testing::SetArgPointee;
using ::testing::Test;
using ::testing::Values;
using ::testing::WithParamInterface;
const bool kVP9SubsampleEncryption = true;
// Use H264 code config.
const uint8_t kH264CodecConfig[]{
// clang-format off
// Header
0x01, 0x64, 0x00, 0x1e, 0xff,
// SPS count (ignore top three bits)
0xe1,
// SPS
0x00, 0x19, // Size
0x67, 0x64, 0x00, 0x1e, 0xac, 0xd9, 0x40, 0xa0, 0x2f, 0xf9, 0x70, 0x11,
0x00, 0x00, 0x03, 0x03, 0xe9, 0x00, 0x00, 0xea, 0x60, 0x0f, 0x16, 0x2d,
0x96,
// PPS count
0x01,
// PPS
0x00, 0x06, // Size
0x68, 0xeb, 0xe3, 0xcb, 0x22, 0xc0,
// clang-format on
};
const int kTrackId = 1;
const uint32_t kTimeScale = 1000;
const uint64_t kDuration = 10000;
const char kCodecString[] = "codec string";
const char kLanguage[] = "eng";
const bool kEncrypted = true;
VideoStreamInfo GetVideoStreamInfo(Codec codec) {
const uint16_t kWidth = 10u;
const uint16_t kHeight = 20u;
const uint32_t kPixelWidth = 2u;
const uint32_t kPixelHeight = 3u;
const int16_t kTrickPlayFactor = 0;
const uint8_t kNaluLengthSize = 1u;
const uint8_t* codec_config = nullptr;
size_t codec_config_size = 0;
switch (codec) {
case kCodecH264:
codec_config = kH264CodecConfig;
codec_config_size = sizeof(kH264CodecConfig);
break;
default:
// We do not care about the codec configs for other codecs in this file.
break;
}
return VideoStreamInfo(kTrackId, kTimeScale, kDuration, codec,
H26xStreamFormat::kUnSpecified, kCodecString,
codec_config, codec_config_size, kWidth, kHeight,
kPixelWidth, kPixelHeight, kTrickPlayFactor,
kNaluLengthSize, kLanguage, !kEncrypted);
}
AudioStreamInfo GetAudioStreamInfo(Codec codec) {
const uint8_t kSampleBits = 1;
const uint8_t kNumChannels = 2;
const uint32_t kSamplingFrequency = 48000;
const uint64_t kSeekPrerollNs = 12345;
const uint64_t kCodecDelayNs = 56789;
const uint32_t kMaxBitrate = 13579;
const uint32_t kAvgBitrate = 13000;
const uint8_t kCodecConfig[] = {0x00};
return AudioStreamInfo(kTrackId, kTimeScale, kDuration, codec, kCodecString,
kCodecConfig, sizeof(kCodecConfig), kSampleBits,
kNumChannels, kSamplingFrequency, kSeekPrerollNs,
kCodecDelayNs, kMaxBitrate, kAvgBitrate, kLanguage,
!kEncrypted);
}
} // namespace
inline bool operator==(const SubsampleEntry& lhs, const SubsampleEntry& rhs) {
return lhs.clear_bytes == rhs.clear_bytes &&
lhs.cipher_bytes == rhs.cipher_bytes;
}
class MockVPxParser : public VPxParser {
public:
MOCK_METHOD3(Parse,
bool(const uint8_t* data,
size_t data_size,
std::vector<VPxFrameInfo>* vpx_frames));
};
class MockVideoSliceHeaderParser : public VideoSliceHeaderParser {
public:
MOCK_METHOD1(Initialize,
bool(const std::vector<uint8_t>& decoder_configuration));
MOCK_METHOD1(GetHeaderSize, int64_t(const Nalu& nalu));
};
class SubsampleGeneratorTest : public Test, public WithParamInterface<FourCC> {
public:
SubsampleGeneratorTest() : protection_scheme_(GetParam()) {}
protected:
FourCC protection_scheme_;
};
TEST_P(SubsampleGeneratorTest, VP9FullSampleEncryption) {
SubsampleGenerator generator(!kVP9SubsampleEncryption);
ASSERT_OK(
generator.Initialize(protection_scheme_, GetVideoStreamInfo(kCodecVP9)));
constexpr size_t kFrameSize = 50;
constexpr uint8_t kFrame[kFrameSize] = {};
std::vector<SubsampleEntry> subsamples;
ASSERT_OK(generator.GenerateSubsamples(kFrame, kFrameSize, &subsamples));
EXPECT_THAT(subsamples, ElementsAre());
}
TEST_P(SubsampleGeneratorTest, VP9ParseFailed) {
SubsampleGenerator generator(kVP9SubsampleEncryption);
ASSERT_OK(
generator.Initialize(protection_scheme_, GetVideoStreamInfo(kCodecVP9)));
constexpr size_t kFrameSize = 50;
constexpr uint8_t kFrame[kFrameSize] = {};
std::unique_ptr<MockVPxParser> mock_vpx_parser(new MockVPxParser);
EXPECT_CALL(*mock_vpx_parser, Parse(kFrame, kFrameSize, _))
.WillOnce(Return(false));
generator.InjectVpxParserForTesting(std::move(mock_vpx_parser));
std::vector<SubsampleEntry> subsamples;
ASSERT_NOT_OK(generator.GenerateSubsamples(kFrame, kFrameSize, &subsamples));
}
TEST_P(SubsampleGeneratorTest, VP9SubsampleEncryption) {
SubsampleGenerator generator(kVP9SubsampleEncryption);
ASSERT_OK(
generator.Initialize(protection_scheme_, GetVideoStreamInfo(kCodecVP9)));
constexpr size_t kFrameSize = 50;
constexpr uint8_t kFrame[kFrameSize] = {};
constexpr size_t kUncompressedHeaderSize = 20;
// VP9 block align protected data for all protection schemes.
const SubsampleEntry kExpectedSubsamples[] = {
// {20,30} block aligned.
{34, 16},
};
std::vector<VPxFrameInfo> vpx_frame_info(1);
vpx_frame_info[0].frame_size = kFrameSize;
vpx_frame_info[0].uncompressed_header_size = kUncompressedHeaderSize;
std::unique_ptr<MockVPxParser> mock_vpx_parser(new MockVPxParser);
EXPECT_CALL(*mock_vpx_parser, Parse(kFrame, kFrameSize, _))
.WillOnce(DoAll(SetArgPointee<2>(vpx_frame_info), Return(true)));
generator.InjectVpxParserForTesting(std::move(mock_vpx_parser));
std::vector<SubsampleEntry> subsamples;
ASSERT_OK(generator.GenerateSubsamples(kFrame, kFrameSize, &subsamples));
EXPECT_THAT(subsamples, ElementsAreArray(kExpectedSubsamples));
}
TEST_P(SubsampleGeneratorTest, VP9SubsampleEncryptionWithSuperFrame) {
SubsampleGenerator generator(kVP9SubsampleEncryption);
ASSERT_OK(
generator.Initialize(protection_scheme_, GetVideoStreamInfo(kCodecVP9)));
constexpr size_t kFrameSize = 50;
constexpr uint8_t kFrame[kFrameSize] = {};
// Super frame with two subframes.
constexpr size_t kSubFrameSizes[] = {10, 34};
constexpr size_t kUncompressedHeaderSizes[] = {4, 1};
// VP9 block align protected data for all protection schemes.
const SubsampleEntry kExpectedSubsamples[] = {
// {4,6},{1,33} block aligned => {10,0},{2,32}
// Then merge consecutive clear-only subsamples.
{12, 32},
// Superframe index (50 - 10 - 34).
{6, 0},
};
std::vector<VPxFrameInfo> vpx_frame_info(2);
for (int i = 0; i < 2; i++) {
vpx_frame_info[i].frame_size = kSubFrameSizes[i];
vpx_frame_info[i].uncompressed_header_size = kUncompressedHeaderSizes[i];
}
std::unique_ptr<MockVPxParser> mock_vpx_parser(new MockVPxParser);
EXPECT_CALL(*mock_vpx_parser, Parse(kFrame, kFrameSize, _))
.WillOnce(DoAll(SetArgPointee<2>(vpx_frame_info), Return(true)));
generator.InjectVpxParserForTesting(std::move(mock_vpx_parser));
std::vector<SubsampleEntry> subsamples;
ASSERT_OK(generator.GenerateSubsamples(kFrame, kFrameSize, &subsamples));
EXPECT_THAT(subsamples, ElementsAreArray(kExpectedSubsamples));
}
TEST_P(SubsampleGeneratorTest, VP9SubsampleEncryptionWithLargeSuperFrame) {
SubsampleGenerator generator(kVP9SubsampleEncryption);
ASSERT_OK(
generator.Initialize(protection_scheme_, GetVideoStreamInfo(kCodecVP9)));
constexpr size_t kFrameSize = 0x23456;
constexpr uint8_t kFrame[kFrameSize] = {};
// Super frame with two subframes.
constexpr size_t kSubFrameSizes[] = {0x10, 0x23000, 0x440};
constexpr size_t kUncompressedHeaderSizes[] = {4, 0x21000, 2};
// VP9 block align protected data for all protection schemes.
const SubsampleEntry kExpectedSubsamples[] = {
// {4,12},{1,0x23000-1} block aligned => {16,0},{0x21000,0x2000}
// Then split big clear_bytes, merge consecutive clear-only subsamples.
{0xffff, 0},
{0xffff, 0},
{0x1012, 0x2000},
// {2,0x440-2} block aligned.
{0x10, 0x430},
// Superframe index.
{6, 0},
};
std::vector<VPxFrameInfo> vpx_frame_info(3);
for (int i = 0; i < 3; i++) {
vpx_frame_info[i].frame_size = kSubFrameSizes[i];
vpx_frame_info[i].uncompressed_header_size = kUncompressedHeaderSizes[i];
}
std::unique_ptr<MockVPxParser> mock_vpx_parser(new MockVPxParser);
EXPECT_CALL(*mock_vpx_parser, Parse(kFrame, kFrameSize, _))
.WillOnce(DoAll(SetArgPointee<2>(vpx_frame_info), Return(true)));
generator.InjectVpxParserForTesting(std::move(mock_vpx_parser));
std::vector<SubsampleEntry> subsamples;
ASSERT_OK(generator.GenerateSubsamples(kFrame, kFrameSize, &subsamples));
EXPECT_THAT(subsamples, ElementsAreArray(kExpectedSubsamples));
}
TEST_P(SubsampleGeneratorTest, H264ParseFailed) {
SubsampleGenerator generator(kVP9SubsampleEncryption);
ASSERT_OK(
generator.Initialize(protection_scheme_, GetVideoStreamInfo(kCodecH264)));
constexpr uint8_t kFrame[] = {
// First NALU (nalu_size = 9).
0x09, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09};
constexpr size_t kFrameSize = sizeof(kFrame);
std::unique_ptr<MockVideoSliceHeaderParser> mock_video_slice_header_parser(
new MockVideoSliceHeaderParser);
EXPECT_CALL(*mock_video_slice_header_parser, GetHeaderSize(_))
.WillOnce(Return(-1));
generator.InjectVideoSliceHeaderParserForTesting(
std::move(mock_video_slice_header_parser));
std::vector<SubsampleEntry> subsamples;
ASSERT_NOT_OK(generator.GenerateSubsamples(kFrame, kFrameSize, &subsamples));
}
TEST_P(SubsampleGeneratorTest, H264SubsampleEncryption) {
SubsampleGenerator generator(kVP9SubsampleEncryption);
ASSERT_OK(
generator.Initialize(protection_scheme_, GetVideoStreamInfo(kCodecH264)));
constexpr uint8_t kFrame[] = {
// First NALU (nalu_size = 9).
0x09, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
// Second NALU (nalu_size = 0x25).
0x27, 0x25, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23,
0x24, 0x25, 0x26, 0x27,
// Third non-video-slice NALU (nalu_size = 0x32).
0x32, 0x67, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23,
0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
0x30, 0x31, 0x32};
constexpr size_t kFrameSize = sizeof(kFrame);
// There are two video slices.
const size_t kSliceHeaderSize[] = {4, 5};
const SubsampleEntry kExpectedUnalignedSubsamples[] = {
// clear_bytes = nalu_length_size (1) + type_size (1) + header_size (4).
// encrypted_bytes = nalu_size (9) - type_size (1) - header_size (4).
{6, 4},
// clear_bytes = nalu_length_size (1) + type_size (1) + header_size (5).
// encrypted_bytes = nalu_size (0x27) - type_size (1) - header_size (5).
{7, 0x21},
// Non-video slice, clear_bytes = nalu_length_size (1) + nalu_size (0x32).
// encrypted_bytes = 0.
{0x33, 0},
};
const SubsampleEntry kExpectedAlignedSubsamples[] = {
// {6,4},{7,0x21} block aligned => {10,0},{8,0x20}
// Then merge consecutive clear-only subsamples.
{18, 0x20},
{0x33, 0},
};
std::unique_ptr<MockVideoSliceHeaderParser> mock_video_slice_header_parser(
new MockVideoSliceHeaderParser);
EXPECT_CALL(*mock_video_slice_header_parser, GetHeaderSize(_))
.WillOnce(Return(kSliceHeaderSize[0]))
.WillOnce(Return(kSliceHeaderSize[1]));
generator.InjectVideoSliceHeaderParserForTesting(
std::move(mock_video_slice_header_parser));
std::vector<SubsampleEntry> subsamples;
ASSERT_OK(generator.GenerateSubsamples(kFrame, kFrameSize, &subsamples));
// Align subsamples for all CENC protection schemes except for cbcs.
if (protection_scheme_ == FOURCC_cbcs)
EXPECT_THAT(subsamples, ElementsAreArray(kExpectedUnalignedSubsamples));
else
EXPECT_THAT(subsamples, ElementsAreArray(kExpectedAlignedSubsamples));
}
TEST_P(SubsampleGeneratorTest, AACIsFullSampleEncrypted) {
SubsampleGenerator generator(kVP9SubsampleEncryption);
ASSERT_OK(
generator.Initialize(protection_scheme_, GetAudioStreamInfo(kCodecAAC)));
constexpr size_t kFrameSize = 50;
constexpr uint8_t kFrame[kFrameSize] = {};
std::vector<SubsampleEntry> subsamples;
ASSERT_OK(generator.GenerateSubsamples(kFrame, kFrameSize, &subsamples));
EXPECT_THAT(subsamples, ElementsAre());
}
INSTANTIATE_TEST_CASE_P(
CencProtectionSchemes,
SubsampleGeneratorTest,
Values(FOURCC_cenc, FOURCC_cens, FOURCC_cbc1, FOURCC_cbcs));
TEST(SampleAesSubsampleGeneratorTest, AAC) {
SubsampleGenerator generator(kVP9SubsampleEncryption);
ASSERT_OK(generator.Initialize(kAppleSampleAesProtectionScheme,
GetAudioStreamInfo(kCodecAAC)));
constexpr size_t kNumFrames = 4;
constexpr size_t kMaxFrameSize = 100;
constexpr size_t kFrameSizes[] = {6, 16, 17, 50};
constexpr uint8_t kFrames[kNumFrames][kMaxFrameSize] = {};
// 16 bytes clear lead.
const SubsampleEntry kExpectedSubsamples[] = {
{6, 0},
{16, 0},
{16, 1},
{16, 34},
};
for (int i = 0; i < 4; i++) {
std::vector<SubsampleEntry> subsamples;
ASSERT_OK(
generator.GenerateSubsamples(kFrames[i], kFrameSizes[i], &subsamples));
EXPECT_THAT(subsamples, ElementsAre(kExpectedSubsamples[i]));
}
}
TEST(SampleAesSubsampleGeneratorTest, H264) {
SubsampleGenerator generator(kVP9SubsampleEncryption);
ASSERT_OK(generator.Initialize(kAppleSampleAesProtectionScheme,
GetVideoStreamInfo(kCodecH264)));
constexpr uint8_t kFrame[] = {
// First NALU (nalu_size = 9).
0x09, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
// Second NALU (nalu_size = 0x30).
0x30, 0x25, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23,
0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
0x30,
// Third NALU (nalu_size = 0x31).
0x31, 0x25, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23,
0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
0x30, 0x31,
// Fourth non-video-slice NALU (nalu_size = 6).
0x32, 0x67, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23,
0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
0x30, 0x31, 0x32};
constexpr size_t kFrameSize = sizeof(kFrame);
const SubsampleEntry kExpectedSubsamples[] = {
// NAL units with nalu_size <= 32+16 is not encrypted, so
// the first two NALUs are left in clear {1+9,0},{1+48,0}.
// The third NALUs has a fixed 32 bytes clear lead, +1 byte NALU length
// size, so it is {1+32, 17}.
// Then merge consecutive clear-only subsamples.
{1 + 9 + 1 + 48 + 1 + 32, 17},
// Non video slice is not encrypted.
{0x33, 0},
};
std::vector<SubsampleEntry> subsamples;
ASSERT_OK(generator.GenerateSubsamples(kFrame, kFrameSize, &subsamples));
EXPECT_THAT(subsamples, ElementsAreArray(kExpectedSubsamples));
}
} // namespace media
} // namespace shaka