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:
parent
acaa6b9b3b
commit
5c4d930465
|
@ -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',
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {};
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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_
|
|
@ -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
|
Loading…
Reference in New Issue