Integrate EncryptionHandler

Change-Id: I639e5707994a6601ee02078cd6f77cb38b481585
This commit is contained in:
KongQun Yang 2017-03-10 18:49:55 -08:00
parent 16615095f0
commit 3d7635d5de
39 changed files with 753 additions and 2130 deletions

View File

@ -35,6 +35,7 @@
#include "packager/media/base/muxer_options.h"
#include "packager/media/base/muxer_util.h"
#include "packager/media/chunking/chunking_handler.h"
#include "packager/media/crypto/encryption_handler.h"
#include "packager/media/event/hls_notify_muxer_listener.h"
#include "packager/media/event/mpd_notify_muxer_listener.h"
#include "packager/media/event/vod_media_info_dump_muxer_listener.h"
@ -137,21 +138,6 @@ std::string DetermineTextFileFormat(const std::string& file) {
return "";
}
FourCC GetProtectionScheme(const std::string& protection_scheme) {
if (protection_scheme == "cenc") {
return FOURCC_cenc;
} else if (protection_scheme == "cens") {
return FOURCC_cens;
} else if (protection_scheme == "cbc1") {
return FOURCC_cbc1;
} else if (protection_scheme == "cbcs") {
return FOURCC_cbcs;
} else {
LOG(ERROR) << "Unknown protection scheme: " << protection_scheme;
return FOURCC_NULL;
}
}
} // namespace
// A fake clock that always return time 0 (epoch). Should only be used for
@ -237,9 +223,10 @@ std::shared_ptr<Muxer> CreateOutputMuxer(const MuxerOptions& options,
bool CreateRemuxJobs(const StreamDescriptorList& stream_descriptors,
const ChunkingOptions& chunking_options,
const EncryptionOptions& encryption_options,
const MuxerOptions& muxer_options,
FakeClock* fake_clock,
KeySource* key_source,
KeySource* encryption_key_source,
MpdNotifier* mpd_notifier,
hls::HlsNotifier* hls_notifier,
std::vector<std::unique_ptr<RemuxJob>>* remux_jobs) {
@ -300,10 +287,11 @@ bool CreateRemuxJobs(const StreamDescriptorList& stream_descriptors,
demuxer->set_dump_stream_info(FLAGS_dump_stream_info);
if (FLAGS_enable_widevine_decryption ||
FLAGS_enable_fixed_key_decryption) {
std::unique_ptr<KeySource> key_source(CreateDecryptionKeySource());
if (!key_source)
std::unique_ptr<KeySource> decryption_key_source(
CreateDecryptionKeySource());
if (!decryption_key_source)
return false;
demuxer->SetKeySource(std::move(key_source));
demuxer->SetKeySource(std::move(decryption_key_source));
}
remux_jobs->emplace_back(new RemuxJob(std::move(demuxer)));
previous_input = stream_iter->input;
@ -317,16 +305,6 @@ bool CreateRemuxJobs(const StreamDescriptorList& stream_descriptors,
CreateOutputMuxer(stream_muxer_options, stream_iter->output_format));
if (FLAGS_use_fake_clock_for_muxer) muxer->set_clock(fake_clock);
if (key_source) {
muxer->SetKeySource(key_source,
FLAGS_max_sd_pixels,
FLAGS_max_hd_pixels,
FLAGS_max_uhd1_pixels,
FLAGS_clear_lead,
FLAGS_crypto_period_duration,
GetProtectionScheme(FLAGS_protection_scheme));
}
std::unique_ptr<MuxerListener> muxer_listener;
DCHECK(!(FLAGS_output_media_info && mpd_notifier));
if (FLAGS_output_media_info) {
@ -363,8 +341,26 @@ bool CreateRemuxJobs(const StreamDescriptorList& stream_descriptors,
if (muxer_listener)
muxer->SetMuxerListener(std::move(muxer_listener));
Status status;
auto chunking_handler = std::make_shared<ChunkingHandler>(chunking_options);
Status status = chunking_handler->SetHandler(0, std::move(muxer));
if (encryption_key_source) {
auto new_encryption_options = encryption_options;
// Use Sample AES in MPEG2TS.
// TODO(kqyang): Consider adding a new flag to enable Sample AES as we
// will support CENC in TS in the future.
if (stream_iter->output_format == CONTAINER_MPEG2TS) {
LOG(INFO) << "Use Apple Sample AES encryption for MPEG2TS.";
new_encryption_options.protection_scheme =
kAppleSampleAesProtectionScheme;
}
auto encryption_handler = std::make_shared<EncryptionHandler>(
new_encryption_options, encryption_key_source);
status.Update(encryption_handler->SetHandler(0, std::move(muxer)));
status.Update(
chunking_handler->SetHandler(0, std::move(encryption_handler)));
} else {
status.Update(chunking_handler->SetHandler(0, std::move(muxer)));
}
auto* demuxer = remux_jobs->back()->demuxer();
const std::string& stream_selector = stream_iter->stream_selector;
@ -415,10 +411,6 @@ Status RunRemuxJobs(const std::vector<std::unique_ptr<RemuxJob>>& remux_jobs) {
}
bool RunPackager(const StreamDescriptorList& stream_descriptors) {
const FourCC protection_scheme = GetProtectionScheme(FLAGS_protection_scheme);
if (protection_scheme == FOURCC_NULL)
return false;
if (FLAGS_output_media_info && !FLAGS_mpd_output.empty()) {
NOTIMPLEMENTED() << "ERROR: --output_media_info and --mpd_output do not "
"work together.";
@ -433,6 +425,8 @@ bool RunPackager(const StreamDescriptorList& stream_descriptors) {
}
ChunkingOptions chunking_options = GetChunkingOptions();
EncryptionOptions encryption_options = GetEncryptionOptions();
MuxerOptions muxer_options = GetMuxerOptions();
DCHECK(!stream_descriptors.empty());
@ -461,6 +455,8 @@ bool RunPackager(const StreamDescriptorList& stream_descriptors) {
std::unique_ptr<KeySource> encryption_key_source;
if (FLAGS_enable_widevine_encryption || FLAGS_enable_fixed_key_encryption ||
FLAGS_enable_playready_encryption) {
if (encryption_options.protection_scheme == FOURCC_NULL)
return false;
encryption_key_source = CreateEncryptionKeySource();
if (!encryption_key_source)
return false;
@ -497,8 +493,8 @@ bool RunPackager(const StreamDescriptorList& stream_descriptors) {
std::vector<std::unique_ptr<RemuxJob>> remux_jobs;
FakeClock fake_clock;
if (!CreateRemuxJobs(stream_descriptors, chunking_options, muxer_options,
&fake_clock, encryption_key_source.get(),
if (!CreateRemuxJobs(stream_descriptors, chunking_options, encryption_options,
muxer_options, &fake_clock, encryption_key_source.get(),
mpd_notifier.get(), hls_notifier.get(), &remux_jobs)) {
return false;
}

View File

@ -22,6 +22,7 @@
#include "packager/media/base/request_signer.h"
#include "packager/media/base/widevine_key_source.h"
#include "packager/media/chunking/chunking_handler.h"
#include "packager/media/crypto/encryption_handler.h"
#include "packager/media/file/file.h"
#include "packager/mpd/base/mpd_options.h"
@ -35,6 +36,24 @@ DEFINE_bool(dump_stream_info, false, "Dump demuxed stream info.");
namespace shaka {
namespace media {
namespace {
FourCC GetProtectionScheme(const std::string& protection_scheme) {
if (protection_scheme == "cenc") {
return FOURCC_cenc;
} else if (protection_scheme == "cens") {
return FOURCC_cens;
} else if (protection_scheme == "cbc1") {
return FOURCC_cbc1;
} else if (protection_scheme == "cbcs") {
return FOURCC_cbcs;
} else {
LOG(ERROR) << "Unknown protection scheme: " << protection_scheme;
return FOURCC_NULL;
}
}
} // namespace
std::unique_ptr<RequestSigner> CreateSigner() {
std::unique_ptr<RequestSigner> signer;
@ -156,6 +175,19 @@ ChunkingOptions GetChunkingOptions() {
return chunking_options;
}
EncryptionOptions GetEncryptionOptions() {
EncryptionOptions encryption_options;
encryption_options.clear_lead_in_seconds = FLAGS_clear_lead;
encryption_options.protection_scheme =
GetProtectionScheme(FLAGS_protection_scheme);
encryption_options.max_sd_pixels = FLAGS_max_sd_pixels;
encryption_options.max_hd_pixels = FLAGS_max_hd_pixels;
encryption_options.max_uhd1_pixels = FLAGS_max_uhd1_pixels;
encryption_options.crypto_period_duration_in_seconds =
FLAGS_crypto_period_duration;
return encryption_options;
}
MuxerOptions GetMuxerOptions() {
MuxerOptions muxer_options;
muxer_options.num_subsegments_per_sidx = FLAGS_num_subsegments_per_sidx;

View File

@ -23,6 +23,7 @@ namespace media {
class KeySource;
struct ChunkingOptions;
struct EncryptionOptions;
struct MuxerOptions;
/// Create KeySource based on provided command line options for content
@ -40,6 +41,9 @@ std::unique_ptr<KeySource> CreateDecryptionKeySource();
/// @return ChunkingOptions from provided command line options.
ChunkingOptions GetChunkingOptions();
/// @return EncryptionOptions from provided command line options.
EncryptionOptions GetEncryptionOptions();
/// @return MuxerOptions from provided command line options.
MuxerOptions GetMuxerOptions();

View File

@ -8,43 +8,19 @@
#include <algorithm>
#include "packager/media/base/fourccs.h"
#include "packager/media/base/media_sample.h"
namespace shaka {
namespace media {
namespace {
const bool kInitialEncryptionInfo = true;
} // namespace
Muxer::Muxer(const MuxerOptions& options)
: options_(options),
encryption_key_source_(NULL),
max_sd_pixels_(0),
max_hd_pixels_(0),
max_uhd1_pixels_(0),
clear_lead_in_seconds_(0),
crypto_period_duration_in_seconds_(0),
protection_scheme_(FOURCC_NULL),
cancelled_(false),
clock_(NULL) {}
: options_(options), cancelled_(false), clock_(NULL) {}
Muxer::~Muxer() {}
void Muxer::SetKeySource(KeySource* encryption_key_source,
uint32_t max_sd_pixels,
uint32_t max_hd_pixels,
uint32_t max_uhd1_pixels,
double clear_lead_in_seconds,
double crypto_period_duration_in_seconds,
FourCC protection_scheme) {
DCHECK(encryption_key_source);
encryption_key_source_ = encryption_key_source;
max_sd_pixels_ = max_sd_pixels;
max_hd_pixels_ = max_hd_pixels;
max_uhd1_pixels_ = max_uhd1_pixels;
clear_lead_in_seconds_ = clear_lead_in_seconds;
crypto_period_duration_in_seconds_ = crypto_period_duration_in_seconds;
protection_scheme_ = protection_scheme;
}
void Muxer::Cancel() {
cancelled_ = true;
}
@ -63,10 +39,34 @@ Status Muxer::Process(std::unique_ptr<StreamData> stream_data) {
switch (stream_data->stream_data_type) {
case StreamDataType::kStreamInfo:
streams_.push_back(std::move(stream_data->stream_info));
if (muxer_listener_ && streams_.back()->is_encrypted()) {
const EncryptionConfig& encryption_config =
streams_.back()->encryption_config();
muxer_listener_->OnEncryptionInfoReady(
kInitialEncryptionInfo, encryption_config.protection_scheme,
encryption_config.key_id, encryption_config.constant_iv,
encryption_config.key_system_info);
}
return InitializeMuxer();
case StreamDataType::kSegmentInfo:
case StreamDataType::kSegmentInfo: {
auto& segment_info = stream_data->segment_info;
if (muxer_listener_) {
const EncryptionConfig* encryption_config =
segment_info->key_rotation_encryption_config.get();
if (encryption_config) {
muxer_listener_->OnEncryptionInfoReady(
!kInitialEncryptionInfo, encryption_config->protection_scheme,
encryption_config->key_id, encryption_config->constant_iv,
encryption_config->key_system_info);
}
if (segment_info->is_encrypted && !encryption_started_) {
encryption_started_ = true;
muxer_listener_->OnEncryptionStart();
}
}
return FinalizeSegment(stream_data->stream_index,
std::move(stream_data->segment_info));
std::move(segment_info));
}
case StreamDataType::kMediaSample:
return AddSample(stream_data->stream_index,
std::move(stream_data->media_sample));

View File

@ -13,7 +13,6 @@
#include <vector>
#include "packager/base/time/clock.h"
#include "packager/media/base/fourccs.h"
#include "packager/media/base/media_handler.h"
#include "packager/media/base/muxer_options.h"
#include "packager/media/base/status.h"
@ -23,9 +22,7 @@
namespace shaka {
namespace media {
class KeySource;
class MediaSample;
class MediaStream;
/// Muxer is responsible for taking elementary stream samples and producing
/// media containers. An optional KeySource can be provided to Muxer
@ -35,37 +32,6 @@ class Muxer : public MediaHandler {
explicit Muxer(const MuxerOptions& options);
virtual ~Muxer();
// TODO(kqyang): refactor max_sd_pixels through crypto_period_duration into
// an encapsulated EncryptionParams structure.
/// Set encryption key source.
/// @param encryption_key_source points to the encryption key source. The
/// caller retains ownership, and should not be NULL.
/// @param max_sd_pixels specifies the threshold to determine whether a video
/// track should be considered as SD. If the max pixels per frame is
/// no higher than max_sd_pixels, it is SD.
/// @param max_hd_pixels specifies the threshold to determine whether a video
/// track should be considered as HD. If the max pixels per frame is
/// higher than max_sd_pixels, but no higher than max_hd_pixels,
/// it is HD.
/// @param max_uhd1_pixels specifies the threshold to determine whether a video
/// track should be considered as UHD1. If the max pixels per frame is
/// higher than max_hd_pixels, but no higher than max_uhd1_pixels,
/// it is UHD1. Otherwise it is UHD2.
/// @param clear_lead_in_seconds specifies clear lead duration in seconds.
/// @param crypto_period_duration_in_seconds specifies crypto period duration
/// in seconds. A positive value means key rotation is enabled, the
/// key source must support key rotation in this case.
/// @param protection_scheme specifies the protection scheme: 'cenc', 'cens',
/// 'cbc1', 'cbcs'.
void SetKeySource(KeySource* encryption_key_source,
uint32_t max_sd_pixels,
uint32_t max_hd_pixels,
uint32_t max_uhd1_pixels,
double clear_lead_in_seconds,
double crypto_period_duration_in_seconds,
FourCC protection_scheme);
/// Cancel a muxing job in progress. Will cause @a Run to exit with an error
/// status of type CANCELLED.
void Cancel();
@ -101,20 +67,9 @@ class Muxer : public MediaHandler {
/// @}
const MuxerOptions& options() const { return options_; }
KeySource* encryption_key_source() {
return encryption_key_source_;
}
uint32_t max_sd_pixels() const { return max_sd_pixels_; }
uint32_t max_hd_pixels() const { return max_hd_pixels_; }
uint32_t max_uhd1_pixels() const { return max_uhd1_pixels_; }
double clear_lead_in_seconds() const { return clear_lead_in_seconds_; }
double crypto_period_duration_in_seconds() const {
return crypto_period_duration_in_seconds_;
}
MuxerListener* muxer_listener() { return muxer_listener_.get(); }
ProgressListener* progress_listener() { return progress_listener_.get(); }
base::Clock* clock() { return clock_; }
FourCC protection_scheme() const { return protection_scheme_; }
private:
// Initialize the muxer.
@ -133,13 +88,7 @@ class Muxer : public MediaHandler {
MuxerOptions options_;
std::vector<std::shared_ptr<StreamInfo>> streams_;
KeySource* encryption_key_source_;
uint32_t max_sd_pixels_;
uint32_t max_hd_pixels_;
uint32_t max_uhd1_pixels_;
double clear_lead_in_seconds_;
double crypto_period_duration_in_seconds_;
FourCC protection_scheme_;
bool encryption_started_ = false;
bool cancelled_;
std::unique_ptr<MuxerListener> muxer_listener_;

View File

@ -164,7 +164,6 @@ bool NalUnitToByteStreamConverter::ConvertUnitToByteStream(
size_t sample_size,
bool is_key_frame,
std::vector<uint8_t>* output) {
LOG(INFO) << "ConvertUnitToByte";
return ConvertUnitToByteStreamWithSubsamples(
sample, sample_size, is_key_frame, false, output,
nullptr); // Skip subsample update.

View File

@ -55,6 +55,7 @@
],
'dependencies': [
'../../base/media_base.gyp:media_base',
'../../crypto/crypto.gyp:crypto',
'../../codecs/codecs.gyp:codecs',
],
},

View File

@ -10,8 +10,6 @@
#include <cstring>
#include <memory>
#include "packager/media/base/aes_encryptor.h"
#include "packager/media/base/aes_pattern_cryptor.h"
#include "packager/media/base/audio_stream_info.h"
#include "packager/media/base/buffer_writer.h"
#include "packager/media/base/media_sample.h"
@ -29,61 +27,6 @@ namespace {
const uint8_t kVideoStreamId = 0xE0;
const uint8_t kAudioStreamId = 0xC0;
const double kTsTimescale = 90000.0;
// |target_data| is input as well as output. On success |target_data| contains
// the encrypted sample. The input data should be Nal unit byte stream.
// This function constructs encrypted sample in |encrypted_sample_data| then
// swap with target_data on success.
bool EncryptH264Sample(AesCryptor* encryptor,
std::vector<uint8_t>* target_data) {
BufferWriter encrypted_sample_data(target_data->size() * 1.5);
const int kLeadingClearBytesSize = 32;
// Any Nalu smaller than 48 bytes shall not be encrypted.
const uint64_t kSmallNalUnitSize = 48;
NaluReader nalu_reader(Nalu::kH264, 0, target_data->data(),
target_data->size());
NaluReader::Result result;
Nalu nalu;
while ((result = nalu_reader.Advance(&nalu)) == NaluReader::Result::kOk) {
encrypted_sample_data.AppendInt(static_cast<uint32_t>(0x00000001));
const uint64_t nalu_total_size = nalu.header_size() + nalu.payload_size();
if (nalu.type() != Nalu::H264NaluType::H264_NonIDRSlice &&
nalu.type() != Nalu::H264NaluType::H264_IDRSlice) {
VLOG(3) << "Found Nalu type: " << nalu.type() << " skipping encryption.";
encrypted_sample_data.AppendArray(nalu.data(), nalu_total_size);
continue;
}
if (nalu_total_size <= kSmallNalUnitSize) {
encrypted_sample_data.AppendArray(nalu.data(), nalu_total_size);
continue;
}
const uint8_t* current = nalu.data() + kLeadingClearBytesSize;
const uint64_t bytes_remaining = nalu_total_size - kLeadingClearBytesSize;
if (!encryptor->Crypt(current, bytes_remaining,
const_cast<uint8_t*>(current))) {
return false;
}
EscapeNalByteSequence(nalu.data(), nalu_total_size, &encrypted_sample_data);
}
encrypted_sample_data.SwapBuffer(target_data);
return true;
}
bool EncryptAacSample(AesCryptor* encryptor,
std::vector<uint8_t>* target_data) {
const int kUnencryptedLeaderSize = 16;
if (target_data->size() <= kUnencryptedLeaderSize)
return true;
uint8_t* data_ptr = target_data->data() + kUnencryptedLeaderSize;
return encryptor->Crypt(
data_ptr, target_data->size() - kUnencryptedLeaderSize, data_ptr);
}
} // namespace
PesPacketGenerator::PesPacketGenerator() {}
@ -130,20 +73,18 @@ bool PesPacketGenerator::PushSample(std::shared_ptr<MediaSample> sample) {
current_processing_pes_->set_dts(timescale_scale_ * sample->dts());
if (stream_type_ == kStreamVideo) {
DCHECK(converter_);
std::vector<SubsampleEntry> subsamples;
if (sample->decrypt_config())
subsamples = sample->decrypt_config()->subsamples();
const bool kEscapeEncryptedNalu = true;
std::vector<uint8_t> byte_stream;
if (!converter_->ConvertUnitToByteStream(
if (!converter_->ConvertUnitToByteStreamWithSubsamples(
sample->data(), sample->data_size(), sample->is_key_frame(),
&byte_stream)) {
kEscapeEncryptedNalu, &byte_stream, &subsamples)) {
LOG(ERROR) << "Failed to convert sample to byte stream.";
return false;
}
if (encryptor_) {
if (!EncryptH264Sample(encryptor_.get(), &byte_stream)) {
LOG(ERROR) << "Failed to encrypt byte stream.";
return false;
}
}
current_processing_pes_->mutable_data()->swap(byte_stream);
current_processing_pes_->set_stream_id(kVideoStreamId);
pes_packets_.push_back(std::move(current_processing_pes_));
@ -155,13 +96,6 @@ bool PesPacketGenerator::PushSample(std::shared_ptr<MediaSample> sample) {
std::vector<uint8_t> aac_frame(sample->data(),
sample->data() + sample->data_size());
if (encryptor_) {
if (!EncryptAacSample(encryptor_.get(), &aac_frame)) {
LOG(ERROR) << "Failed to encrypt ADTS AAC.";
return false;
}
}
// TODO(rkuroiwa): ConvertToADTS() makes another copy of aac_frame internally.
// Optimize copying in this function, possibly by adding a method on
// AACAudioSpecificConfig that takes {pointer, length} pair and returns a
@ -177,30 +111,6 @@ bool PesPacketGenerator::PushSample(std::shared_ptr<MediaSample> sample) {
return true;
}
bool PesPacketGenerator::SetEncryptionKey(
std::unique_ptr<EncryptionKey> encryption_key) {
if (stream_type_ == kStreamVideo) {
std::unique_ptr<AesCbcEncryptor> cbc(
new AesCbcEncryptor(CbcPaddingScheme::kNoPadding));
const uint8_t kEncryptedBlocks = 1;
const uint8_t kClearBlocks = 9;
encryptor_.reset(new AesPatternCryptor(
kEncryptedBlocks, kClearBlocks,
AesPatternCryptor::kSkipIfCryptByteBlockRemaining,
AesCryptor::ConstantIvFlag::kUseConstantIv, std::move(cbc)));
} else if (stream_type_ == kStreamAudio) {
encryptor_.reset(
new AesCbcEncryptor(CbcPaddingScheme::kNoPadding,
AesCryptor::ConstantIvFlag::kUseConstantIv));
} else {
LOG(ERROR) << "Cannot encrypt stream type: " << stream_type_;
return false;
}
return encryptor_->InitializeWithIv(encryption_key->key, encryption_key->iv);
}
size_t PesPacketGenerator::NumberOfReadyPesPackets() {
return pes_packets_.size();
}

View File

@ -10,8 +10,6 @@
#include <list>
#include <memory>
#include "packager/media/base/aes_cryptor.h"
#include "packager/media/base/key_source.h"
#include "packager/media/base/media_sample.h"
#include "packager/media/base/stream_info.h"
@ -46,12 +44,6 @@ class PesPacketGenerator {
/// @return true on success, false otherwise.
virtual bool PushSample(std::shared_ptr<MediaSample> sample);
/// Sets the encryption key for encrypting samples.
/// @param encryption_key is the key that will be used to encrypt further
/// samples.
/// @return true on success, false otherwise.
virtual bool SetEncryptionKey(std::unique_ptr<EncryptionKey> encryption_key);
/// @return The number of PES packets that are ready to be consumed.
virtual size_t NumberOfReadyPesPackets();
@ -83,9 +75,6 @@ class PesPacketGenerator {
std::list<std::unique_ptr<PesPacket>> pes_packets_;
// Current encryption key.
std::unique_ptr<AesCryptor> encryptor_;
DISALLOW_COPY_AND_ASSIGN(PesPacketGenerator);
};

View File

@ -18,12 +18,21 @@
namespace shaka {
namespace media {
inline bool operator==(const SubsampleEntry& lhs, const SubsampleEntry& rhs) {
return lhs.clear_bytes == rhs.clear_bytes &&
lhs.cipher_bytes == rhs.cipher_bytes;
}
namespace mp2t {
using ::testing::_;
using ::testing::DoAll;
using ::testing::SetArgPointee;
using ::testing::Eq;
using ::testing::IsEmpty;
using ::testing::Pointee;
using ::testing::Return;
using ::testing::SetArgPointee;
namespace {
@ -33,6 +42,7 @@ const uint8_t kAnyData[] = {
};
const bool kIsKeyFrame = true;
const bool kEscapeEncryptedNalu = true;
// Only Codec and extra data matter for this test. Other values are
// bogus.
@ -88,11 +98,13 @@ class MockNalUnitToByteStreamConverter : public NalUnitToByteStreamConverter {
MOCK_METHOD2(Initialize,
bool(const uint8_t* decoder_configuration_data,
size_t decoder_configuration_data_size));
MOCK_METHOD4(ConvertUnitToByteStream,
MOCK_METHOD6(ConvertUnitToByteStreamWithSubsamples,
bool(const uint8_t* sample,
size_t sample_size,
bool is_key_frame,
std::vector<uint8_t>* output));
bool escape_encrypted_nalu,
std::vector<uint8_t>* output,
std::vector<SubsampleEntry>* subsamples));
};
class MockAACAudioSpecificConfig : public AACAudioSpecificConfig {
@ -133,91 +145,6 @@ class PesPacketGeneratorTest : public ::testing::Test {
generator_.adts_converter_ = std::move(mock);
}
void H264EncryptionTest(const uint8_t* input,
size_t input_size,
const uint8_t* expected_output,
size_t expected_output_size) {
std::shared_ptr<VideoStreamInfo> stream_info(
CreateVideoStreamInfo(kH264Codec));
EXPECT_TRUE(generator_.Initialize(*stream_info));
EXPECT_EQ(0u, generator_.NumberOfReadyPesPackets());
std::shared_ptr<MediaSample> sample =
MediaSample::CopyFrom(input, input_size, kIsKeyFrame);
const uint32_t kPts = 12345;
const uint32_t kDts = 12300;
sample->set_pts(kPts);
sample->set_dts(kDts);
std::unique_ptr<MockNalUnitToByteStreamConverter> mock(
new MockNalUnitToByteStreamConverter());
// Returning only the input data so that it doesn't have all the unnecessary
// NALUs to test encryption.
std::vector<uint8_t> clear_data(input, input + input_size);
EXPECT_CALL(*mock, ConvertUnitToByteStream(_, input_size, kIsKeyFrame, _))
.WillOnce(DoAll(SetArgPointee<3>(clear_data), Return(true)));
UseMockNalUnitToByteStreamConverter(std::move(mock));
const std::vector<uint8_t> all_zero(16, 0);
std::unique_ptr<EncryptionKey> encryption_key(new EncryptionKey());
encryption_key->key = all_zero;
encryption_key->iv = all_zero;
EXPECT_TRUE(generator_.SetEncryptionKey(std::move(encryption_key)));
EXPECT_TRUE(generator_.PushSample(sample));
EXPECT_EQ(1u, generator_.NumberOfReadyPesPackets());
std::unique_ptr<PesPacket> pes_packet = generator_.GetNextPesPacket();
ASSERT_TRUE(pes_packet);
std::vector<uint8_t> expected(expected_output,
expected_output + expected_output_size);
ASSERT_EQ(expected.size(), pes_packet->data().size());
for (size_t i = 0; i < expected.size(); ++i) {
EXPECT_EQ(expected[i], pes_packet->data()[i]) << " mismatch at " << i;
}
//EXPECT_EQ(expected, pes_packet->data());
}
// The input data should be the size of an aac frame, i.e. should not be the
// size of an ADTS frame.
void AacEncryptionTest(const uint8_t* input,
size_t input_size,
const uint8_t* expected_output,
size_t expected_output_size) {
std::shared_ptr<AudioStreamInfo> stream_info(
CreateAudioStreamInfo(kAacCodec));
EXPECT_TRUE(generator_.Initialize(*stream_info));
EXPECT_EQ(0u, generator_.NumberOfReadyPesPackets());
// For aac, the input from MediaSample is used. Then ADTS header is added,
// so EXPECT_CALL does not return the |input| data.
std::shared_ptr<MediaSample> sample =
MediaSample::CopyFrom(input, input_size, kIsKeyFrame);
std::unique_ptr<MockAACAudioSpecificConfig> mock(
new MockAACAudioSpecificConfig());
EXPECT_CALL(*mock, ConvertToADTS(_)).WillOnce(Return(true));
UseMockAACAudioSpecificConfig(std::move(mock));
const std::vector<uint8_t> all_zero(16, 0);
std::unique_ptr<EncryptionKey> encryption_key(new EncryptionKey());
encryption_key->key = all_zero;
encryption_key->iv = all_zero;
EXPECT_TRUE(generator_.SetEncryptionKey(std::move(encryption_key)));
EXPECT_TRUE(generator_.PushSample(sample));
EXPECT_EQ(1u, generator_.NumberOfReadyPesPackets());
std::unique_ptr<PesPacket> pes_packet = generator_.GetNextPesPacket();
ASSERT_TRUE(pes_packet);
std::vector<uint8_t> expected(expected_output,
expected_output + expected_output_size);
EXPECT_EQ(expected, pes_packet->data());
}
PesPacketGenerator generator_;
};
@ -270,9 +197,57 @@ TEST_F(PesPacketGeneratorTest, AddVideoSample) {
std::unique_ptr<MockNalUnitToByteStreamConverter> mock(
new MockNalUnitToByteStreamConverter());
EXPECT_CALL(*mock,
ConvertUnitToByteStream(_, arraysize(kAnyData), kIsKeyFrame, _))
.WillOnce(DoAll(SetArgPointee<3>(expected_data), Return(true)));
EXPECT_CALL(*mock, ConvertUnitToByteStreamWithSubsamples(
_, arraysize(kAnyData), kIsKeyFrame,
kEscapeEncryptedNalu, _, Pointee(IsEmpty())))
.WillOnce(DoAll(SetArgPointee<4>(expected_data), Return(true)));
UseMockNalUnitToByteStreamConverter(std::move(mock));
EXPECT_TRUE(generator_.PushSample(sample));
EXPECT_EQ(1u, generator_.NumberOfReadyPesPackets());
std::unique_ptr<PesPacket> pes_packet = generator_.GetNextPesPacket();
ASSERT_TRUE(pes_packet);
EXPECT_EQ(0u, generator_.NumberOfReadyPesPackets());
EXPECT_EQ(0xe0, pes_packet->stream_id());
EXPECT_EQ(kPts, pes_packet->pts());
EXPECT_EQ(kDts, pes_packet->dts());
EXPECT_EQ(expected_data, pes_packet->data());
EXPECT_TRUE(generator_.Flush());
}
TEST_F(PesPacketGeneratorTest, AddEncryptedVideoSample) {
std::shared_ptr<VideoStreamInfo> stream_info(
CreateVideoStreamInfo(kH264Codec));
EXPECT_TRUE(generator_.Initialize(*stream_info));
EXPECT_EQ(0u, generator_.NumberOfReadyPesPackets());
std::shared_ptr<MediaSample> sample =
MediaSample::CopyFrom(kAnyData, arraysize(kAnyData), kIsKeyFrame);
const uint32_t kPts = 12345;
const uint32_t kDts = 12300;
sample->set_pts(kPts);
sample->set_dts(kDts);
const std::vector<uint8_t> key_id(16, 0);
const std::vector<uint8_t> iv(8, 0);
const std::vector<SubsampleEntry> subsamples = {
SubsampleEntry(0x12, 0x110), SubsampleEntry(0x122, 0x11000)};
std::unique_ptr<DecryptConfig> decrypt_config(
new DecryptConfig(key_id, iv, subsamples));
sample->set_is_encrypted(true);
sample->set_decrypt_config(std::move(decrypt_config));
std::vector<uint8_t> expected_data(kAnyData, kAnyData + arraysize(kAnyData));
std::unique_ptr<MockNalUnitToByteStreamConverter> mock(
new MockNalUnitToByteStreamConverter());
EXPECT_CALL(*mock, ConvertUnitToByteStreamWithSubsamples(
_, arraysize(kAnyData), kIsKeyFrame,
kEscapeEncryptedNalu, _, Pointee(Eq(subsamples))))
.WillOnce(DoAll(SetArgPointee<4>(expected_data), Return(true)));
UseMockNalUnitToByteStreamConverter(std::move(mock));
@ -302,8 +277,9 @@ TEST_F(PesPacketGeneratorTest, AddVideoSampleFailedToConvert) {
std::vector<uint8_t> expected_data(kAnyData, kAnyData + arraysize(kAnyData));
std::unique_ptr<MockNalUnitToByteStreamConverter> mock(
new MockNalUnitToByteStreamConverter());
EXPECT_CALL(*mock,
ConvertUnitToByteStream(_, arraysize(kAnyData), kIsKeyFrame, _))
EXPECT_CALL(*mock, ConvertUnitToByteStreamWithSubsamples(
_, arraysize(kAnyData), kIsKeyFrame,
kEscapeEncryptedNalu, _, Pointee(IsEmpty())))
.WillOnce(Return(false));
UseMockNalUnitToByteStreamConverter(std::move(mock));
@ -384,8 +360,9 @@ TEST_F(PesPacketGeneratorTest, TimeStampScaling) {
std::unique_ptr<MockNalUnitToByteStreamConverter> mock(
new MockNalUnitToByteStreamConverter());
EXPECT_CALL(*mock,
ConvertUnitToByteStream(_, arraysize(kAnyData), kIsKeyFrame, _))
EXPECT_CALL(*mock, ConvertUnitToByteStreamWithSubsamples(
_, arraysize(kAnyData), kIsKeyFrame,
kEscapeEncryptedNalu, _, Pointee(IsEmpty())))
.WillOnce(Return(true));
UseMockNalUnitToByteStreamConverter(std::move(mock));
@ -404,323 +381,6 @@ TEST_F(PesPacketGeneratorTest, TimeStampScaling) {
EXPECT_TRUE(generator_.Flush());
}
// The nalu is too small for it to be encrypted. Verify it is not modified.
TEST_F(PesPacketGeneratorTest, H264SampleEncryptionSmallNalu) {
const uint8_t kNaluData[] = {
0x00, 0x00, 0x00, 0x01, 0x61, 0xbb, 0xcc, 0xdd,
};
ASSERT_NO_FATAL_FAILURE(H264EncryptionTest(kNaluData, arraysize(kNaluData),
kNaluData, arraysize(kNaluData)));
}
// Verify that sample encryption works.
TEST_F(PesPacketGeneratorTest, H264SampleEncryption) {
// Use the following command to encrypt data.
// openssl aes-128-cbc -nopad -e -in input -K
// "00000000000000000000000000000000" -iv "00000000000000000000000000000000" >
// enc
const uint8_t kNaluData[] = {
0x00, 0x00, 0x00, 0x01, // Start code.
0x61, // nalu type 1; this type should get encrypted.
// Bogus data but should not be encrypted.
0x00, 0x01, 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,
// Next 16 bytes should be encrypted.
0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A,
0x2B, 0x2C, 0x2D, 0x2E,
// Next 144 bytes should be in the clear.
0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A,
0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52,
0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E,
0x5F, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A,
0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76,
0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 0x80, 0x81, 0x82,
0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E,
0x8F, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A,
0x9B, 0x9C, 0x9D, 0x9E, 0x9F, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6,
0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2,
0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE,
// Next 16 bytes should be encrypted.
0xBF, 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA,
0xCB, 0xCC, 0xCD, 0xCE,
// This last bytes should not be encrypted.
0xCF,
};
const uint8_t kEncryptedNaluData[] = {
0x00, 0x00, 0x00, 0x01, // Start code.
0x61, // nalu type 1; should get encrypted.
// Bogus data but should sample encrypted.
0x00, 0x01, 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,
// Encrypted 16 bytes.
0x93, 0x3A, 0x2C, 0x38, 0x86, 0x4B, 0x64, 0xE2, 0x62, 0x7E, 0xCC, 0x75,
0x71, 0xFB, 0x60, 0x7C,
// Next 144 bytes should be in the clear.
0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A,
0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52,
0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E,
0x5F, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A,
0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76,
0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 0x80, 0x81, 0x82,
0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E,
0x8F, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A,
0x9B, 0x9C, 0x9D, 0x9E, 0x9F, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6,
0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2,
0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE,
// Encrypted 16 bytes.
0xB7, 0x1C, 0x64, 0xAE, 0x90, 0xA4, 0x35, 0x88, 0x4F, 0xD1, 0x30, 0xC2,
0x06, 0x2E, 0xF8, 0xA5,
// This last bytes should not be encrypted.
0xCF,
};
ASSERT_NO_FATAL_FAILURE(H264EncryptionTest(kNaluData, arraysize(kNaluData),
kEncryptedNaluData,
arraysize(kEncryptedNaluData)));
}
// If any block is encrypted, then the whole nal unit must be re-escaped.
TEST_F(PesPacketGeneratorTest, H264SampleEncryptionVerifyReescape) {
const uint8_t kNaluData[] = {
0x00, 0x00, 0x00, 0x01, // Start code.
0x61, // nalu type 1; this type should get encrypted.
// Bogus data but should not be encrypted.
// But 0x00 0x00 0x03 should be re-escaped.
0x00, 0x00, 0x03, 0x02, 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,
// Next 16 bytes should be encrypted.
// Note that there is 0x00 0x00 0x03 sequence that will be reescaped.
0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A,
0x2B, 0x2C, 0x2D, 0x2E,
// Next 144 bytes should be in the clear.
0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A,
0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52,
0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E,
0x5F, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A,
0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76,
0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 0x80, 0x81, 0x82,
0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E,
0x8F, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A,
// Still part of clear data, but this line includes 0x00 0x00 0x03
// which should be re-escaped.
0x9B, 0x9C, 0x9D, 0x00, 0x00, 0x03, 0x01, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6,
0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2,
0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE,
// Next 16 bytes should be encrypted.
0xBF, 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA,
0xCB, 0xCC, 0xCD, 0xCE,
// This last bytes should not be encrypted.
0xCF,
};
const uint8_t kEncryptedNaluData[] = {
0x00, 0x00, 0x00, 0x01, // Start code.
0x61, // nalu type 1; should get encrypted.
// Bogus data but should not be encrypted.
0x00, 0x00, 0x03, 0x03, 0x02, 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,
// Encrypted 16 bytes.
0x93, 0x3A, 0x2C, 0x38, 0x86, 0x4B, 0x64, 0xE2, 0x62, 0x7E, 0xCC, 0x75,
0x71, 0xFB, 0x60, 0x7C,
// Next 144 bytes should be in the clear.
0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A,
0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52,
0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E,
0x5F, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A,
0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76,
0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 0x80, 0x81, 0x82,
0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E,
0x8F, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A,
// Extra 0x03 is added.
0x9B, 0x9C, 0x9D, 0x00, 0x00, 0x03, 0x03, 0x01, 0xA2, 0xA3, 0xA4, 0xA5,
0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1,
0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD,
0xBE,
// Encrypted 16 bytes.
0xB7, 0x1C, 0x64, 0xAE, 0x90, 0xA4, 0x35, 0x88, 0x4F, 0xD1, 0x30, 0xC2,
0x06, 0x2E, 0xF8, 0xA5,
// This last bytes should not be encrypted.
0xCF,
};
ASSERT_NO_FATAL_FAILURE(H264EncryptionTest(kNaluData, arraysize(kNaluData),
kEncryptedNaluData,
arraysize(kEncryptedNaluData)));
}
// Verify that if the last there are only 16 bytes left, then it doesn't get
// encrypted.
TEST_F(PesPacketGeneratorTest, H264SampleEncryptionLast16ByteNotEncrypted) {
const uint8_t kNaluData[] = {
0x00, 0x00, 0x00, 0x01, // Start code.
0x61, // nalu type 1; should get encrypted.
// Bogus data but should not be encrypted.
0x00, 0x01, 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,
// Next 16 bytes should be encrypted.
0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A,
0x2B, 0x2C, 0x2D, 0x2E,
// Next 144 bytes should be in the clear.
0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A,
0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52,
0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E,
0x5F, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A,
0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76,
0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 0x80, 0x81, 0x82,
0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E,
0x8F, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A,
0x9B, 0x9C, 0x9D, 0x9E, 0x9F, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6,
0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2,
0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE,
// These 16 bytes should not be encrypted.
0xBF, 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA,
0xCB, 0xCC, 0xCD, 0xCE,
};
const uint8_t kEncryptedNaluData[] = {
0x00, 0x00, 0x00, 0x01, // Start code.
0x61, // nalu type 1; should get encrypted.
// Bogus data but should not be encrypted.
0x00, 0x01, 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,
// Encrypted 16 bytes.
0x93, 0x3A, 0x2C, 0x38, 0x86, 0x4B, 0x64, 0xE2, 0x62, 0x7E, 0xCC, 0x75,
0x71, 0xFB, 0x60, 0x7C,
// Next 144 bytes should be in the clear.
0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A,
0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52,
0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E,
0x5F, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A,
0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76,
0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 0x80, 0x81, 0x82,
0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E,
0x8F, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A,
0x9B, 0x9C, 0x9D, 0x9E, 0x9F, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6,
0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2,
0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE,
// These 16 bytes should not be encrypted.
0xBF, 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA,
0xCB, 0xCC, 0xCD, 0xCE,
};
ASSERT_NO_FATAL_FAILURE(H264EncryptionTest(kNaluData, arraysize(kNaluData),
kEncryptedNaluData,
arraysize(kEncryptedNaluData)));
}
// The sample is too small and it doesn't need to be encrypted.
TEST_F(PesPacketGeneratorTest, AacSampleEncryptionSmallSample) {
const uint8_t kClearData[] = {
0x00, 0x01, 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,
};
ASSERT_NO_FATAL_FAILURE(AacEncryptionTest(kClearData, arraysize(kClearData),
kClearData, arraysize(kClearData)));
}
// Verify that AAC can be encrypted.
TEST_F(PesPacketGeneratorTest, AacSampleEncryption) {
// The data is long enough so that 2 blocks (32 bytes) are encrypted.
const uint8_t kClearData[] = {
// First 16 bytes are always clear.
0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12,
0x13, 0x14, 0x15, 0x16,
// Next 32 bytes (2 blocks) are encrypted.
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,
0x33, 0x34, 0x35, 0x36,
// The last 2 bytes are in the clear.
0x37, 0x38,
};
const uint8_t kExpectedOutputData[] = {
// First 16 bytes are always clear.
0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12,
0x13, 0x14, 0x15, 0x16,
// Encrypted bytes.
0xE3, 0x42, 0x9B, 0x27, 0x33, 0x67, 0x68, 0x08, 0xA5, 0xB3, 0x3E, 0xB1,
0xEE, 0xFC, 0x9E, 0x0A, 0x8E, 0x0C, 0x73, 0xC5, 0x57, 0xEE, 0x58, 0xC7,
0x48, 0x74, 0x2A, 0x12, 0x38, 0x4F, 0x4E, 0xAC,
// The last 2 bytes are in the clear.
0x37, 0x38,
};
ASSERT_NO_FATAL_FAILURE(AacEncryptionTest(kClearData, arraysize(kClearData),
kExpectedOutputData,
arraysize(kExpectedOutputData)));
}
// Verify that all the bytes after the leading few bytes are encrypted.
// Note that this is different from h264 encryption where it doesn't encrypt the
// last 16.
TEST_F(PesPacketGeneratorTest, AacSampleEncryptionLastBytesAreEncrypted) {
const uint8_t kClearData[] = {
// First 16 bytes are always clear.
0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12,
0x13, 0x14, 0x15, 0x16,
// Next 32 bytes (2 blocks) are encrypted.
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,
0x33, 0x34, 0x35, 0x36,
};
const uint8_t kExpectedOutputData[] = {
// First 16 bytes are always clear.
0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12,
0x13, 0x14, 0x15, 0x16,
// Encrypted bytes.
0xE3, 0x42, 0x9B, 0x27, 0x33, 0x67, 0x68, 0x08, 0xA5, 0xB3, 0x3E, 0xB1,
0xEE, 0xFC, 0x9E, 0x0A, 0x8E, 0x0C, 0x73, 0xC5, 0x57, 0xEE, 0x58, 0xC7,
0x48, 0x74, 0x2A, 0x12, 0x38, 0x4F, 0x4E, 0xAC,
};
ASSERT_NO_FATAL_FAILURE(AacEncryptionTest(kClearData, arraysize(kClearData),
kExpectedOutputData,
arraysize(kExpectedOutputData)));
}
} // namespace mp2t
} // namespace media
} // namespace shaka

View File

@ -22,9 +22,7 @@ Status TsMuxer::InitializeMuxer() {
return Status(error::MUXER_FAILURE, "Cannot handle more than one streams.");
segmenter_.reset(new TsSegmenter(options(), muxer_listener()));
Status status = segmenter_->Initialize(
*streams()[0], encryption_key_source(), max_sd_pixels(), max_hd_pixels(),
max_uhd1_pixels(), clear_lead_in_seconds());
Status status = segmenter_->Initialize(*streams()[0]);
FireOnMediaStartEvent();
return status;
}
@ -43,6 +41,11 @@ Status TsMuxer::AddSample(size_t stream_id,
Status TsMuxer::FinalizeSegment(size_t stream_id,
std::shared_ptr<SegmentInfo> segment_info) {
DCHECK_EQ(stream_id, 0u);
if (segment_info->key_rotation_encryption_config) {
NOTIMPLEMENTED() << "Key rotation is not implemented for TS.";
return Status(error::UNIMPLEMENTED,
"Key rotation is not implemented for TS");
}
return segment_info->is_subsegment
? Status::OK
: segmenter_->FinalizeSegment(segment_info->start_timestamp,

View File

@ -31,12 +31,7 @@ TsSegmenter::TsSegmenter(const MuxerOptions& options, MuxerListener* listener)
pes_packet_generator_(new PesPacketGenerator()) {}
TsSegmenter::~TsSegmenter() {}
Status TsSegmenter::Initialize(const StreamInfo& stream_info,
KeySource* encryption_key_source,
uint32_t max_sd_pixels,
uint32_t max_hd_pixels,
uint32_t max_uhd1_pixels,
double clear_lead_in_seconds) {
Status TsSegmenter::Initialize(const StreamInfo& stream_info) {
if (muxer_options_.segment_template.empty())
return Status(error::MUXER_FAILURE, "Segment template not specified.");
if (!ts_writer_->Initialize(stream_info))
@ -46,37 +41,6 @@ Status TsSegmenter::Initialize(const StreamInfo& stream_info,
"Failed to initialize PesPacketGenerator.");
}
if (encryption_key_source) {
std::unique_ptr<EncryptionKey> encryption_key(new EncryptionKey());
const KeySource::TrackType type =
GetTrackTypeForEncryption(stream_info, max_sd_pixels,
max_hd_pixels, max_uhd1_pixels);
Status status = encryption_key_source->GetKey(type, encryption_key.get());
if (encryption_key->iv.empty()) {
if (!AesCryptor::GenerateRandomIv(FOURCC_cbcs, &encryption_key->iv)) {
return Status(error::INTERNAL_ERROR, "Failed to generate random iv.");
}
}
if (!status.ok())
return status;
encryption_key_ = std::move(encryption_key);
clear_lead_in_seconds_ = clear_lead_in_seconds;
if (listener_) {
// For now this only happens once, so send true.
const bool kIsInitialEncryptionInfo = true;
listener_->OnEncryptionInfoReady(
kIsInitialEncryptionInfo, FOURCC_cbcs, encryption_key_->key_id,
encryption_key_->iv, encryption_key_->key_system_info);
}
status = NotifyEncrypted();
if (!status.ok())
return status;
}
timescale_scale_ = kTsTimescale / stream_info.time_scale();
return Status::OK;
}
@ -86,6 +50,9 @@ Status TsSegmenter::Finalize() {
}
Status TsSegmenter::AddSample(std::shared_ptr<MediaSample> sample) {
if (sample->is_encrypted())
ts_writer_->SignalEncrypted();
if (!ts_writer_file_opened_ && !sample->is_key_frame())
LOG(WARNING) << "A segment will start with a non key frame.";
@ -161,21 +128,8 @@ Status TsSegmenter::FinalizeSegment(uint64_t start_timestamp,
duration * timescale_scale_, file_size);
}
ts_writer_file_opened_ = false;
total_duration_in_seconds_ += duration * timescale_scale_ / kTsTimescale;
}
current_segment_path_.clear();
return NotifyEncrypted();
}
Status TsSegmenter::NotifyEncrypted() {
if (encryption_key_ && total_duration_in_seconds_ >= clear_lead_in_seconds_) {
if (listener_)
listener_->OnEncryptionStart();
if (!pes_packet_generator_->SetEncryptionKey(std::move(encryption_key_)))
return Status(error::INTERNAL_ERROR, "Failed to set encryption key.");
ts_writer_->SignalEncrypted();
}
return Status::OK;
}

View File

@ -36,15 +36,9 @@ class TsSegmenter {
~TsSegmenter();
/// Initialize the object.
/// Key rotation is not supported.
/// @param stream_info is the stream info for the segmenter.
/// @return OK on success.
Status Initialize(const StreamInfo& stream_info,
KeySource* encryption_key_source,
uint32_t max_sd_pixels,
uint32_t max_hd_pixels,
uint32_t max_uhd1_pixels,
double clear_lead_in_seconds);
Status Initialize(const StreamInfo& stream_info);
/// Finalize the segmenter.
/// @return OK on success.
@ -83,9 +77,6 @@ class TsSegmenter {
// it will open one. This will not close the file.
Status WritePesPacketsToFile();
// If conditions are met, notify objects that the data is encrypted.
Status NotifyEncrypted();
const MuxerOptions& muxer_options_;
MuxerListener* const listener_;
@ -107,14 +98,6 @@ class TsSegmenter {
// the segment has been finalized.
std::string current_segment_path_;
std::unique_ptr<EncryptionKey> encryption_key_;
double clear_lead_in_seconds_ = 0;
// The total duration of the segments that it has segmented. This only
// includes segments that have been finailzed. IOW, this does not count the
// current segments duration.
double total_duration_in_seconds_ = 0.0;
DISALLOW_COPY_AND_ASSIGN(TsSegmenter);
};

View File

@ -8,7 +8,6 @@
#include <gtest/gtest.h>
#include "packager/media/base/audio_stream_info.h"
#include "packager/media/base/fixed_key_source.h"
#include "packager/media/base/test/status_test_util.h"
#include "packager/media/base/video_stream_info.h"
#include "packager/media/event/mock_muxer_listener.h"
@ -54,11 +53,6 @@ class MockPesPacketGenerator : public PesPacketGenerator {
public:
MOCK_METHOD1(Initialize, bool(const StreamInfo& info));
MOCK_METHOD1(PushSample, bool(std::shared_ptr<MediaSample> sample));
MOCK_METHOD1(SetEncryptionKeyMock, bool(EncryptionKey* encryption_key));
bool SetEncryptionKey(
std::unique_ptr<EncryptionKey> encryption_key) override {
return SetEncryptionKeyMock(encryption_key.get());
}
MOCK_METHOD0(NumberOfReadyPesPackets, size_t());
@ -88,12 +82,6 @@ class MockTsWriter : public TsWriter {
}
};
// TODO(rkuroiwa): Add mock_key_source.{h,cc} in media/base.
class MockKeySource : public FixedKeySource {
public:
MOCK_METHOD2(GetKey, Status(TrackType track_type, EncryptionKey* key));
};
} // namespace
class TsSegmenterTest : public ::testing::Test {
@ -124,7 +112,7 @@ TEST_F(TsSegmenterTest, Initialize) {
segmenter.InjectPesPacketGeneratorForTesting(
std::move(mock_pes_packet_generator_));
EXPECT_OK(segmenter.Initialize(*stream_info, nullptr, 0, 0, 0, 0));
EXPECT_OK(segmenter.Initialize(*stream_info));
}
TEST_F(TsSegmenterTest, AddSample) {
@ -170,7 +158,7 @@ TEST_F(TsSegmenterTest, AddSample) {
segmenter.InjectPesPacketGeneratorForTesting(
std::move(mock_pes_packet_generator_));
EXPECT_OK(segmenter.Initialize(*stream_info, nullptr, 0, 0, 0, 0));
EXPECT_OK(segmenter.Initialize(*stream_info));
EXPECT_OK(segmenter.AddSample(sample));
}
@ -265,7 +253,7 @@ TEST_F(TsSegmenterTest, PassedSegmentDuration) {
segmenter.InjectTsWriterForTesting(std::move(mock_ts_writer_));
segmenter.InjectPesPacketGeneratorForTesting(
std::move(mock_pes_packet_generator_));
EXPECT_OK(segmenter.Initialize(*stream_info, nullptr, 0, 0, 0, 0));
EXPECT_OK(segmenter.Initialize(*stream_info));
EXPECT_OK(segmenter.AddSample(sample1));
EXPECT_OK(segmenter.FinalizeSegment(kFirstPts, sample1->duration()));
EXPECT_OK(segmenter.AddSample(sample2));
@ -292,7 +280,7 @@ TEST_F(TsSegmenterTest, InitializeThenFinalize) {
segmenter.InjectTsWriterForTesting(std::move(mock_ts_writer_));
segmenter.InjectPesPacketGeneratorForTesting(
std::move(mock_pes_packet_generator_));
EXPECT_OK(segmenter.Initialize(*stream_info, nullptr, 0, 0, 0, 0));
EXPECT_OK(segmenter.Initialize(*stream_info));
EXPECT_OK(segmenter.Finalize());
}
@ -322,103 +310,18 @@ TEST_F(TsSegmenterTest, FinalizeSegment) {
segmenter.InjectTsWriterForTesting(std::move(mock_ts_writer_));
segmenter.InjectPesPacketGeneratorForTesting(
std::move(mock_pes_packet_generator_));
EXPECT_OK(segmenter.Initialize(*stream_info, nullptr, 0, 0, 0, 0));
EXPECT_OK(segmenter.Initialize(*stream_info));
segmenter.SetTsWriterFileOpenedForTesting(true);
EXPECT_OK(segmenter.FinalizeSegment(0, 100 /* arbitrary duration */));
}
TEST_F(TsSegmenterTest, WithEncryptionNoClearLead) {
std::shared_ptr<VideoStreamInfo> stream_info(new VideoStreamInfo(
kTrackId, kTimeScale, kDuration, kH264Codec, kCodecString, kExtraData,
arraysize(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight,
kTrickPlayRate, kNaluLengthSize, kLanguage, kIsEncrypted));
MuxerOptions options;
options.segment_template = "file$Number$.ts";
MockMuxerListener mock_listener;
EXPECT_CALL(mock_listener, OnEncryptionInfoReady(_, _, _, _, _));
TsSegmenter segmenter(options, &mock_listener);
EXPECT_CALL(*mock_ts_writer_, Initialize(_)).WillOnce(Return(true));
EXPECT_CALL(*mock_ts_writer_, SignalEncrypted());
EXPECT_CALL(*mock_pes_packet_generator_, Initialize(_))
.WillOnce(Return(true));
EXPECT_CALL(*mock_pes_packet_generator_, SetEncryptionKeyMock(_))
.WillOnce(Return(true));
segmenter.InjectTsWriterForTesting(std::move(mock_ts_writer_));
segmenter.InjectPesPacketGeneratorForTesting(
std::move(mock_pes_packet_generator_));
MockKeySource mock_key_source;
EXPECT_CALL(mock_key_source, GetKey(KeySource::TRACK_TYPE_HD, _))
.WillOnce(Return(Status::OK));
const uint32_t k480pPixels = 640 * 480;
const uint32_t k1080pPixels = 1920 * 1080;
const uint32_t k2160pPixels = 4096 * 2160;
// Set this to 0 so that Finalize will call
// PesPacketGenerator::SetEncryptionKey().
// Even tho no samples have been added.
const double kClearLeadSeconds = 0;
EXPECT_OK(segmenter.Initialize(*stream_info, &mock_key_source, k480pPixels,
k1080pPixels, k2160pPixels,
kClearLeadSeconds));
}
// Verify that the muxer listener pointer is not used without checking that it's
// not null.
TEST_F(TsSegmenterTest, WithEncryptionNoClearLeadNoMuxerListener) {
std::shared_ptr<VideoStreamInfo> stream_info(new VideoStreamInfo(
kTrackId, kTimeScale, kDuration, kH264Codec, kCodecString, kExtraData,
arraysize(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight,
kTrickPlayRate, kNaluLengthSize, kLanguage, kIsEncrypted));
MuxerOptions options;
// options.segment_duration = 10.0;
options.segment_template = "file$Number$.ts";
TsSegmenter segmenter(options, nullptr);
EXPECT_CALL(*mock_ts_writer_, Initialize(_)).WillOnce(Return(true));
EXPECT_CALL(*mock_ts_writer_, SignalEncrypted());
EXPECT_CALL(*mock_pes_packet_generator_, Initialize(_))
.WillOnce(Return(true));
EXPECT_CALL(*mock_pes_packet_generator_, SetEncryptionKeyMock(_))
.WillOnce(Return(true));
segmenter.InjectTsWriterForTesting(std::move(mock_ts_writer_));
segmenter.InjectPesPacketGeneratorForTesting(
std::move(mock_pes_packet_generator_));
MockKeySource mock_key_source;
EXPECT_CALL(mock_key_source, GetKey(KeySource::TRACK_TYPE_HD, _))
.WillOnce(Return(Status::OK));
const uint32_t k480pPixels = 640 * 480;
const uint32_t k1080pPixels = 1920 * 1080;
const uint32_t k2160pPixels = 4096 * 2160;
// Set this to 0 so that Finalize will call
// PesPacketGenerator::SetEncryptionKey().
// Even tho no samples have been added.
const double kClearLeadSeconds = 0;
EXPECT_OK(segmenter.Initialize(*stream_info, &mock_key_source, k480pPixels,
k1080pPixels, k2160pPixels,
kClearLeadSeconds));
}
// Verify that encryption notification is sent to objects after clear lead.
TEST_F(TsSegmenterTest, WithEncryptionWithClearLead) {
TEST_F(TsSegmenterTest, EncryptedSample) {
std::shared_ptr<VideoStreamInfo> stream_info(new VideoStreamInfo(
kTrackId, kTimeScale, kDuration, kH264Codec, kCodecString, kExtraData,
arraysize(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight,
kTrickPlayRate, kNaluLengthSize, kLanguage, kIsEncrypted));
MuxerOptions options;
const uint32_t k1080pPixels = 1920 * 1080;
const uint32_t k2160pPixels = 4096 * 2160;
const double kClearLeadSeconds = 1.0;
options.segment_template = "file$Number$.ts";
MockMuxerListener mock_listener;
@ -479,33 +382,19 @@ TEST_F(TsSegmenterTest, WithEncryptionWithClearLead) {
.InSequence(pes_packet_sequence)
.WillOnce(Return(new PesPacket()));
MockPesPacketGenerator* mock_pes_packet_generator_raw =
mock_pes_packet_generator_.get();
MockTsWriter* mock_ts_writer_raw = mock_ts_writer_.get();
segmenter.InjectTsWriterForTesting(std::move(mock_ts_writer_));
segmenter.InjectPesPacketGeneratorForTesting(
std::move(mock_pes_packet_generator_));
MockKeySource mock_key_source;
// This should be called AFTER the first AddSample().
EXPECT_CALL(mock_key_source, GetKey(KeySource::TRACK_TYPE_HD, _))
.WillOnce(Return(Status::OK));
EXPECT_CALL(mock_listener, OnEncryptionInfoReady(_, _, _, _, _));
EXPECT_OK(segmenter.Initialize(*stream_info, &mock_key_source, 0,
k1080pPixels, k2160pPixels,
kClearLeadSeconds));
EXPECT_OK(segmenter.Initialize(*stream_info));
EXPECT_OK(segmenter.AddSample(sample1));
// Encryption should be setup after finalizing the first segment.
// These should be called when finalize segment is called.
EXPECT_CALL(mock_listener, OnEncryptionStart());
EXPECT_CALL(*mock_pes_packet_generator_raw, SetEncryptionKeyMock(_))
.WillOnce(Return(true));
EXPECT_OK(segmenter.FinalizeSegment(1, sample1->duration()));
// Signal encrypted if sample is encrypted.
EXPECT_CALL(*mock_ts_writer_raw, SignalEncrypted());
EXPECT_OK(segmenter.FinalizeSegment(0, sample1->duration()));
sample2->set_is_encrypted(true);
EXPECT_OK(segmenter.AddSample(sample2));
}

View File

@ -1,380 +0,0 @@
// Copyright 2014 Google Inc. 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/formats/mp4/encrypting_fragmenter.h"
#include <limits>
#include "packager/media/base/aes_encryptor.h"
#include "packager/media/base/aes_pattern_cryptor.h"
#include "packager/media/base/buffer_reader.h"
#include "packager/media/base/key_source.h"
#include "packager/media/base/media_sample.h"
#include "packager/media/codecs/nalu_reader.h"
#include "packager/media/codecs/vp8_parser.h"
#include "packager/media/codecs/vp9_parser.h"
#include "packager/media/formats/mp4/box_definitions.h"
namespace shaka {
namespace media {
namespace mp4 {
namespace {
const size_t kCencBlockSize = 16u;
// Adds one or more subsamples to |*subsamples|. This may add more than one
// if one of the values overflows the integer in the subsample.
void AddSubsamples(uint64_t clear_bytes,
uint64_t cipher_bytes,
std::vector<SubsampleEntry>* subsamples) {
CHECK_LT(cipher_bytes, std::numeric_limits<uint32_t>::max());
const uint64_t kUInt16Max = std::numeric_limits<uint16_t>::max();
while (clear_bytes > kUInt16Max) {
subsamples->push_back(SubsampleEntry(kUInt16Max, 0));
clear_bytes -= kUInt16Max;
}
if (clear_bytes > 0 || cipher_bytes > 0)
subsamples->push_back(SubsampleEntry(clear_bytes, cipher_bytes));
}
Codec GetCodec(const StreamInfo& stream_info) {
if (stream_info.stream_type() != kStreamVideo) return kUnknownCodec;
const VideoStreamInfo& video_stream_info =
static_cast<const VideoStreamInfo&>(stream_info);
return video_stream_info.codec();
}
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();
}
} // namespace
EncryptingFragmenter::EncryptingFragmenter(
std::shared_ptr<StreamInfo> info,
TrackFragment* traf,
std::unique_ptr<EncryptionKey> encryption_key,
int64_t clear_time,
FourCC protection_scheme,
uint8_t crypt_byte_block,
uint8_t skip_byte_block,
MuxerListener* listener)
: Fragmenter(info, traf),
info_(info),
encryption_key_(std::move(encryption_key)),
nalu_length_size_(GetNaluLengthSize(*info)),
video_codec_(GetCodec(*info)),
clear_time_(clear_time),
protection_scheme_(protection_scheme),
crypt_byte_block_(crypt_byte_block),
skip_byte_block_(skip_byte_block),
listener_(listener) {
DCHECK(encryption_key_);
switch (video_codec_) {
case kCodecVP9:
vpx_parser_.reset(new VP9Parser);
break;
case kCodecH264:
header_parser_.reset(new H264VideoSliceHeaderParser);
break;
case kCodecHVC1:
FALLTHROUGH_INTENDED;
case kCodecHEV1:
header_parser_.reset(new H265VideoSliceHeaderParser);
break;
default:
if (nalu_length_size_ > 0) {
LOG(WARNING) << "Unknown video codec '" << video_codec_
<< "', whole subsamples will be encrypted.";
}
}
}
EncryptingFragmenter::~EncryptingFragmenter() {}
Status EncryptingFragmenter::AddSample(std::shared_ptr<MediaSample> sample) {
DCHECK(sample);
if (!fragment_initialized()) {
Status status = InitializeFragment(sample->dts());
if (!status.ok())
return status;
}
if (encryptor_) {
Status status = EncryptSample(sample);
if (!status.ok())
return status;
}
return Fragmenter::AddSample(sample);
}
Status EncryptingFragmenter::InitializeFragment(int64_t first_sample_dts) {
Status status = Fragmenter::InitializeFragment(first_sample_dts);
if (!status.ok())
return status;
if (header_parser_ && !header_parser_->Initialize(info_->codec_config()))
return Status(error::MUXER_FAILURE, "Fail to read SPS and PPS data.");
traf()->auxiliary_size.sample_info_sizes.clear();
traf()->auxiliary_offset.offsets.clear();
if (IsSubsampleEncryptionRequired()) {
traf()->sample_encryption.flags |=
SampleEncryption::kUseSubsampleEncryption;
}
traf()->sample_encryption.sample_encryption_entries.clear();
const bool enable_encryption = clear_time_ <= 0;
if (!enable_encryption) {
// This fragment should be in clear text.
// At most two sample description entries, an encrypted entry and a clear
// entry, are generated. The 1-based clear entry index is always 2.
const uint32_t kClearSampleDescriptionIndex = 2;
traf()->header.flags |=
TrackFragmentHeader::kSampleDescriptionIndexPresentMask;
traf()->header.sample_description_index = kClearSampleDescriptionIndex;
} else {
if (listener_)
listener_->OnEncryptionStart();
}
return PrepareFragmentForEncryption(enable_encryption);
}
void EncryptingFragmenter::FinalizeFragment() {
if (encryptor_) {
DCHECK_LE(clear_time_, 0);
FinalizeFragmentForEncryption();
} else {
DCHECK_GT(clear_time_, 0);
clear_time_ -= fragment_duration();
}
Fragmenter::FinalizeFragment();
}
Status EncryptingFragmenter::PrepareFragmentForEncryption(
bool enable_encryption) {
return (!enable_encryption || encryptor_) ? Status::OK : CreateEncryptor();
}
void EncryptingFragmenter::FinalizeFragmentForEncryption() {
// The offset will be adjusted in Segmenter after knowing moof size.
traf()->auxiliary_offset.offsets.push_back(0);
// For 'cbcs' scheme, Constant IVs SHALL be used.
const uint8_t per_sample_iv_size =
(protection_scheme_ == FOURCC_cbcs) ? 0 :
static_cast<uint8_t>(encryptor_->iv().size());
traf()->sample_encryption.iv_size = per_sample_iv_size;
// Optimize saiz box.
SampleAuxiliaryInformationSize& saiz = traf()->auxiliary_size;
saiz.sample_count =
static_cast<uint32_t>(traf()->runs[0].sample_sizes.size());
if (!saiz.sample_info_sizes.empty()) {
if (!OptimizeSampleEntries(&saiz.sample_info_sizes,
&saiz.default_sample_info_size)) {
saiz.default_sample_info_size = 0;
}
} else {
// |sample_info_sizes| table is filled in only for subsample encryption,
// otherwise |sample_info_size| is just the IV size.
DCHECK(!IsSubsampleEncryptionRequired());
saiz.default_sample_info_size = static_cast<uint8_t>(per_sample_iv_size);
}
// It should only happen with full sample encryption + constant iv, i.e.
// 'cbcs' applying to audio.
if (saiz.default_sample_info_size == 0 && saiz.sample_info_sizes.empty()) {
DCHECK_EQ(protection_scheme_, FOURCC_cbcs);
DCHECK(!IsSubsampleEncryptionRequired());
// ISO/IEC 23001-7:2016(E) The sample auxiliary information would then be
// empty and should be emitted. Clear saiz and saio boxes so they are not
// written.
saiz.sample_count = 0;
traf()->auxiliary_offset.offsets.clear();
}
}
Status EncryptingFragmenter::CreateEncryptor() {
DCHECK(encryption_key_);
std::unique_ptr<AesCryptor> encryptor;
switch (protection_scheme_) {
case FOURCC_cenc:
encryptor.reset(new AesCtrEncryptor);
break;
case FOURCC_cbc1:
encryptor.reset(new AesCbcEncryptor(kNoPadding));
break;
case FOURCC_cens:
encryptor.reset(new AesPatternCryptor(
crypt_byte_block(), skip_byte_block(),
AesPatternCryptor::kEncryptIfCryptByteBlockRemaining,
AesCryptor::kDontUseConstantIv,
std::unique_ptr<AesCryptor>(new AesCtrEncryptor())));
break;
case FOURCC_cbcs:
encryptor.reset(new AesPatternCryptor(
crypt_byte_block(), skip_byte_block(),
AesPatternCryptor::kEncryptIfCryptByteBlockRemaining,
AesCryptor::kUseConstantIv,
std::unique_ptr<AesCryptor>(new AesCbcEncryptor(kNoPadding))));
break;
default:
return Status(error::MUXER_FAILURE, "Unsupported protection scheme.");
}
DCHECK(!encryption_key_->iv.empty());
const bool initialized =
encryptor->InitializeWithIv(encryption_key_->key, encryption_key_->iv);
if (!initialized)
return Status(error::MUXER_FAILURE, "Failed to create the encryptor.");
encryptor_ = std::move(encryptor);
return Status::OK;
}
void EncryptingFragmenter::EncryptBytes(uint8_t* data, size_t size) {
DCHECK(encryptor_);
CHECK(encryptor_->Crypt(data, size, data));
}
Status EncryptingFragmenter::EncryptSample(
std::shared_ptr<MediaSample> sample) {
DCHECK(encryptor_);
SampleEncryptionEntry sample_encryption_entry;
// For 'cbcs' scheme, Constant IVs SHALL be used.
if (protection_scheme_ != FOURCC_cbcs)
sample_encryption_entry.initialization_vector = encryptor_->iv();
uint8_t* data = sample->writable_data();
if (IsSubsampleEncryptionRequired()) {
if (vpx_parser_) {
std::vector<VPxFrameInfo> vpx_frames;
if (!vpx_parser_->Parse(sample->data(), sample->data_size(),
&vpx_frames)) {
return Status(error::MUXER_FAILURE, "Failed to parse vpx frame.");
}
const bool is_superframe = vpx_frames.size() > 1;
for (const VPxFrameInfo& frame : vpx_frames) {
SubsampleEntry subsample;
subsample.clear_bytes =
static_cast<uint16_t>(frame.uncompressed_header_size);
subsample.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 = subsample.cipher_bytes % kCencBlockSize;
subsample.clear_bytes += misalign_bytes;
subsample.cipher_bytes -= misalign_bytes;
sample_encryption_entry.subsamples.push_back(subsample);
if (subsample.cipher_bytes > 0)
EncryptBytes(data + subsample.clear_bytes, subsample.cipher_bytes);
data += frame.frame_size;
}
// Add subsample for the superframe index if exists.
if (is_superframe) {
size_t index_size = sample->data() + sample->data_size() - data;
DCHECK_LE(index_size, 2 + vpx_frames.size() * 4);
DCHECK_GE(index_size, 2 + vpx_frames.size() * 1);
SubsampleEntry subsample;
subsample.clear_bytes = static_cast<uint16_t>(index_size);
subsample.cipher_bytes = 0;
sample_encryption_entry.subsamples.push_back(subsample);
}
} else {
const Nalu::CodecType nalu_type =
(video_codec_ == kCodecHVC1 || video_codec_ == kCodecHEV1)
? Nalu::kH265
: Nalu::kH264;
NaluReader reader(nalu_type, nalu_length_size_, data,
sample->data_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) {
if (nalu.is_video_slice()) {
// For video-slice NAL units, encrypt the video slice. This skips
// the frame header. If this is an unrecognized codec (e.g. H.265),
// the whole NAL unit will be encrypted.
const int64_t video_slice_header_size =
header_parser_ ? header_parser_->GetHeaderSize(nalu) : 0;
if (video_slice_header_size < 0)
return Status(error::MUXER_FAILURE, "Failed to read slice header.");
uint64_t current_clear_bytes =
nalu.header_size() + video_slice_header_size;
uint64_t cipher_bytes = nalu.payload_size() - video_slice_header_size;
// 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.
if (protection_scheme_ == FOURCC_cbc1 ||
protection_scheme_ == FOURCC_cens) {
const uint16_t misalign_bytes = cipher_bytes % kCencBlockSize;
current_clear_bytes += misalign_bytes;
cipher_bytes -= misalign_bytes;
}
const uint8_t* nalu_data = nalu.data() + current_clear_bytes;
EncryptBytes(const_cast<uint8_t*>(nalu_data), cipher_bytes);
AddSubsamples(
accumulated_clear_bytes + nalu_length_size_ + current_clear_bytes,
cipher_bytes, &sample_encryption_entry.subsamples);
accumulated_clear_bytes = 0;
} else {
// For non-video-slice NAL units, don't encrypt.
accumulated_clear_bytes +=
nalu_length_size_ + nalu.header_size() + nalu.payload_size();
}
}
if (result != NaluReader::kEOStream)
return Status(error::MUXER_FAILURE, "Failed to parse NAL units.");
AddSubsamples(accumulated_clear_bytes, 0,
&sample_encryption_entry.subsamples);
}
DCHECK_EQ(sample_encryption_entry.GetTotalSizeOfSubsamples(),
sample->data_size());
// The length of per-sample auxiliary datum, defined in CENC ch. 7.
traf()->auxiliary_size.sample_info_sizes.push_back(
sample_encryption_entry.ComputeSize());
} else {
DCHECK_LE(crypt_byte_block(), 1u);
DCHECK_EQ(skip_byte_block(), 0u);
EncryptBytes(data, sample->data_size());
}
traf()->sample_encryption.sample_encryption_entries.push_back(
sample_encryption_entry);
encryptor_->UpdateIv();
return Status::OK;
}
bool EncryptingFragmenter::IsSubsampleEncryptionRequired() {
return vpx_parser_ || nalu_length_size_ != 0;
}
} // namespace mp4
} // namespace media
} // namespace shaka

View File

@ -1,111 +0,0 @@
// Copyright 2014 Google Inc. 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 MEDIA_FORMATS_MP4_ENCRYPTING_FRAGMENTER_H_
#define MEDIA_FORMATS_MP4_ENCRYPTING_FRAGMENTER_H_
#include <memory>
#include "packager/media/base/fourccs.h"
#include "packager/media/codecs/video_slice_header_parser.h"
#include "packager/media/codecs/vpx_parser.h"
#include "packager/media/event/muxer_listener.h"
#include "packager/media/formats/mp4/fragmenter.h"
namespace shaka {
namespace media {
class AesCryptor;
class StreamInfo;
struct EncryptionKey;
namespace mp4 {
/// EncryptingFragmenter generates MP4 fragments with sample encrypted.
class EncryptingFragmenter : public Fragmenter {
public:
/// @param info contains stream information.
/// @param traf points to a TrackFragment box.
/// @param encryption_key contains the encryption parameters.
/// @param clear_time specifies clear lead duration in units of the current
/// track's timescale.
/// @param protection_scheme specifies the protection scheme: 'cenc', 'cens',
/// 'cbc1', 'cbcs'.
/// @param crypt_byte_block indicates number of encrypted blocks (16-byte) in
/// pattern based encryption.
/// @param skip_byte_block indicates number of unencrypted blocks (16-byte)
/// in pattern based encryption.
EncryptingFragmenter(std::shared_ptr<StreamInfo> info,
TrackFragment* traf,
std::unique_ptr<EncryptionKey> encryption_key,
int64_t clear_time,
FourCC protection_scheme,
uint8_t crypt_byte_block,
uint8_t skip_byte_block,
MuxerListener* listener);
~EncryptingFragmenter() override;
/// @name Fragmenter implementation overrides.
/// @{
Status AddSample(std::shared_ptr<MediaSample> sample) override;
Status InitializeFragment(int64_t first_sample_dts) override;
void FinalizeFragment() override;
/// @}
protected:
/// Prepare current fragment for encryption.
/// @return OK on success, an error status otherwise.
virtual Status PrepareFragmentForEncryption(bool enable_encryption);
/// Finalize current fragment for encryption.
virtual void FinalizeFragmentForEncryption();
/// Create the encryptor for the internal encryption key. The existing
/// encryptor will be reset if it is not NULL.
/// @return OK on success, an error status otherwise.
Status CreateEncryptor();
const EncryptionKey* encryption_key() const { return encryption_key_.get(); }
AesCryptor* encryptor() { return encryptor_.get(); }
FourCC protection_scheme() const { return protection_scheme_; }
uint8_t crypt_byte_block() const { return crypt_byte_block_; }
uint8_t skip_byte_block() const { return skip_byte_block_; }
void set_encryption_key(std::unique_ptr<EncryptionKey> encryption_key) {
encryption_key_ = std::move(encryption_key);
}
private:
void EncryptBytes(uint8_t* data, size_t size);
Status EncryptSample(std::shared_ptr<MediaSample> sample);
// Should we enable subsample encryption?
bool IsSubsampleEncryptionRequired();
std::shared_ptr<StreamInfo> info_;
std::unique_ptr<EncryptionKey> encryption_key_;
std::unique_ptr<AesCryptor> encryptor_;
// If this stream contains AVC, subsample encryption specifies that the size
// and type of NAL units remain unencrypted. This function returns the size of
// the size field in bytes. Can be 1, 2 or 4 bytes.
const uint8_t nalu_length_size_;
const Codec video_codec_;
int64_t clear_time_;
const FourCC protection_scheme_;
const uint8_t crypt_byte_block_;
const uint8_t skip_byte_block_;
MuxerListener* listener_;
std::unique_ptr<VPxParser> vpx_parser_;
std::unique_ptr<VideoSliceHeaderParser> header_parser_;
DISALLOW_COPY_AND_ASSIGN(EncryptingFragmenter);
};
} // namespace mp4
} // namespace media
} // namespace shaka
#endif // MEDIA_FORMATS_MP4_ENCRYPTING_FRAGMENTER_H_

View File

@ -27,17 +27,35 @@ uint64_t GetSeekPreroll(const StreamInfo& stream_info) {
static_cast<const AudioStreamInfo&>(stream_info);
return audio_stream_info.seek_preroll_ns();
}
void NewSampleEncryptionEntry(const DecryptConfig& decrypt_config,
bool use_constant_iv,
TrackFragment* traf) {
SampleEncryption& sample_encryption = traf->sample_encryption;
SampleEncryptionEntry sample_encryption_entry;
if (!use_constant_iv)
sample_encryption_entry.initialization_vector = decrypt_config.iv();
sample_encryption_entry.subsamples = decrypt_config.subsamples();
sample_encryption.sample_encryption_entries.push_back(
sample_encryption_entry);
traf->auxiliary_size.sample_info_sizes.push_back(
sample_encryption_entry.ComputeSize());
}
} // namespace
Fragmenter::Fragmenter(std::shared_ptr<StreamInfo> info, TrackFragment* traf)
: use_decoding_timestamp_in_timeline_(false),
Fragmenter::Fragmenter(std::shared_ptr<StreamInfo> stream_info,
TrackFragment* traf)
: stream_info_(std::move(stream_info)),
use_decoding_timestamp_in_timeline_(false),
traf_(traf),
seek_preroll_(GetSeekPreroll(*info)),
seek_preroll_(GetSeekPreroll(*stream_info_)),
fragment_initialized_(false),
fragment_finalized_(false),
fragment_duration_(0),
earliest_presentation_time_(kInvalidTime),
first_sap_time_(kInvalidTime) {
DCHECK(stream_info_);
DCHECK(traf);
}
@ -66,6 +84,12 @@ Status Fragmenter::AddSample(std::shared_ptr<MediaSample> sample) {
traf_->runs[0].sample_flags.push_back(
sample->is_key_frame() ? 0 : TrackFragmentHeader::kNonKeySampleMask);
if (sample->decrypt_config()) {
NewSampleEncryptionEntry(
*sample->decrypt_config(),
!stream_info_->encryption_config().constant_iv.empty(), traf_);
}
data_->AppendArray(sample->data(), sample->data_size());
fragment_duration_ += sample->duration();
@ -96,11 +120,15 @@ Status Fragmenter::InitializeFragment(int64_t first_sample_dts) {
traf_->runs.clear();
traf_->runs.resize(1);
traf_->runs[0].flags = TrackFragmentRun::kDataOffsetPresentMask;
traf_->auxiliary_size.sample_info_sizes.clear();
traf_->auxiliary_offset.offsets.clear();
traf_->sample_encryption.sample_encryption_entries.clear();
traf_->sample_group_descriptions.clear();
traf_->sample_to_groups.clear();
traf_->header.sample_description_index = 1; // 1-based.
traf_->header.flags = TrackFragmentHeader::kDefaultBaseIsMoofMask |
TrackFragmentHeader::kSampleDescriptionIndexPresentMask;
fragment_duration_ = 0;
earliest_presentation_time_ = kInvalidTime;
first_sap_time_ = kInvalidTime;
@ -108,7 +136,13 @@ Status Fragmenter::InitializeFragment(int64_t first_sample_dts) {
return Status::OK;
}
void Fragmenter::FinalizeFragment() {
Status Fragmenter::FinalizeFragment() {
if (stream_info_->is_encrypted()) {
Status status = FinalizeFragmentForEncryption();
if (!status.ok())
return status;
}
// Optimize trun box.
traf_->runs[0].sample_count =
static_cast<uint32_t>(traf_->runs[0].sample_sizes.size());
@ -164,6 +198,7 @@ void Fragmenter::FinalizeFragment() {
fragment_finalized_ = true;
fragment_initialized_ = false;
return Status::OK;
}
void Fragmenter::GenerateSegmentReference(SegmentReference* reference) {
@ -181,6 +216,58 @@ void Fragmenter::GenerateSegmentReference(SegmentReference* reference) {
reference->earliest_presentation_time = earliest_presentation_time_;
}
Status Fragmenter::FinalizeFragmentForEncryption() {
SampleEncryption& sample_encryption = traf_->sample_encryption;
if (sample_encryption.sample_encryption_entries.empty()) {
// This fragment is not encrypted.
// There are two sample description entries, an encrypted entry and a clear
// entry, are generated. The 1-based clear entry index is always 2.
const uint32_t kClearSampleDescriptionIndex = 2;
traf_->header.sample_description_index = kClearSampleDescriptionIndex;
return Status::OK;
}
if (sample_encryption.sample_encryption_entries.size() !=
traf_->runs[0].sample_sizes.size()) {
LOG(ERROR) << "Partially encrypted segment is not supported";
return Status(error::MUXER_FAILURE,
"Partially encrypted segment is not supported.");
}
const SampleEncryptionEntry& sample_encryption_entry =
sample_encryption.sample_encryption_entries.front();
const bool use_subsample_encryption =
!sample_encryption_entry.subsamples.empty();
if (use_subsample_encryption)
traf_->sample_encryption.flags |= SampleEncryption::kUseSubsampleEncryption;
traf_->sample_encryption.iv_size =
sample_encryption_entry.initialization_vector.size();
// The offset will be adjusted in Segmenter after knowing moof size.
traf_->auxiliary_offset.offsets.push_back(0);
// Optimize saiz box.
SampleAuxiliaryInformationSize& saiz = traf_->auxiliary_size;
saiz.sample_count = static_cast<uint32_t>(saiz.sample_info_sizes.size());
DCHECK_EQ(saiz.sample_info_sizes.size(),
traf_->sample_encryption.sample_encryption_entries.size());
if (!OptimizeSampleEntries(&saiz.sample_info_sizes,
&saiz.default_sample_info_size)) {
saiz.default_sample_info_size = 0;
}
// It should only happen with full sample encryption + constant iv, i.e.
// 'cbcs' applying to audio.
if (saiz.default_sample_info_size == 0 && saiz.sample_info_sizes.empty()) {
DCHECK(!use_subsample_encryption);
// ISO/IEC 23001-7:2016(E) The sample auxiliary information would then be
// empty and should be omitted. Clear saiz and saio boxes so they are not
// written.
saiz.sample_count = 0;
traf_->auxiliary_offset.offsets.clear();
}
return Status::OK;
}
bool Fragmenter::StartsWithSAP() {
DCHECK(!traf_->runs.empty());
uint32_t start_sample_flag;

View File

@ -33,21 +33,21 @@ class Fragmenter {
/// @param traf points to a TrackFragment box.
Fragmenter(std::shared_ptr<StreamInfo> info, TrackFragment* traf);
virtual ~Fragmenter();
~Fragmenter();
/// Add a sample to the fragmenter.
/// @param sample points to the sample to be added.
/// @return OK on success, an error status otherwise.
virtual Status AddSample(std::shared_ptr<MediaSample> sample);
Status AddSample(std::shared_ptr<MediaSample> sample);
/// Initialize the fragment with default data.
/// @param first_sample_dts specifies the decoding timestamp for the first
/// sample for this fragment.
/// @return OK on success, an error status otherwise.
virtual Status InitializeFragment(int64_t first_sample_dts);
Status InitializeFragment(int64_t first_sample_dts);
/// Finalize and optimize the fragment.
virtual void FinalizeFragment();
Status FinalizeFragment();
/// Fill @a reference with current fragment information.
void GenerateSegmentReference(SegmentReference* reference);
@ -82,9 +82,11 @@ class Fragmenter {
bool OptimizeSampleEntries(std::vector<T>* entries, T* default_value);
private:
Status FinalizeFragmentForEncryption();
// Check if the current fragment starts with SAP.
bool StartsWithSAP();
std::shared_ptr<StreamInfo> stream_info_;
bool use_decoding_timestamp_in_timeline_;
TrackFragment* traf_;
uint64_t seek_preroll_;

View File

@ -1,138 +0,0 @@
// Copyright 2014 Google Inc. 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/formats/mp4/key_rotation_fragmenter.h"
#include "packager/media/base/aes_encryptor.h"
#include "packager/media/formats/mp4/box_definitions.h"
namespace shaka {
namespace media {
namespace mp4 {
namespace {
const bool kInitialEncryptionInfo = true;
} // namespace
KeyRotationFragmenter::KeyRotationFragmenter(MovieFragment* moof,
std::shared_ptr<StreamInfo> info,
TrackFragment* traf,
KeySource* encryption_key_source,
KeySource::TrackType track_type,
int64_t crypto_period_duration,
int64_t clear_time,
FourCC protection_scheme,
uint8_t crypt_byte_block,
uint8_t skip_byte_block,
MuxerListener* muxer_listener)
: EncryptingFragmenter(info,
traf,
std::unique_ptr<EncryptionKey>(new EncryptionKey()),
clear_time,
protection_scheme,
crypt_byte_block,
skip_byte_block,
muxer_listener),
moof_(moof),
encryption_key_source_(encryption_key_source),
track_type_(track_type),
crypto_period_duration_(crypto_period_duration),
prev_crypto_period_index_(-1),
muxer_listener_(muxer_listener) {
DCHECK(moof);
DCHECK(encryption_key_source);
}
KeyRotationFragmenter::~KeyRotationFragmenter() {}
Status KeyRotationFragmenter::PrepareFragmentForEncryption(
bool enable_encryption) {
bool need_to_refresh_encryptor = !encryptor();
int64_t current_crypto_period_index =
traf()->decode_time.decode_time / crypto_period_duration_;
if (current_crypto_period_index != prev_crypto_period_index_) {
std::unique_ptr<EncryptionKey> encryption_key(new EncryptionKey());
Status status = encryption_key_source_->GetCryptoPeriodKey(
current_crypto_period_index, track_type_, encryption_key.get());
if (!status.ok())
return status;
if (encryption_key->iv.empty()) {
if (!AesCryptor::GenerateRandomIv(protection_scheme(),
&encryption_key->iv)) {
return Status(error::INTERNAL_ERROR, "Failed to generate random iv.");
}
}
set_encryption_key(std::move(encryption_key));
prev_crypto_period_index_ = current_crypto_period_index;
need_to_refresh_encryptor = true;
}
DCHECK(encryption_key());
const std::vector<ProtectionSystemSpecificInfo>& system_info =
encryption_key()->key_system_info;
moof_->pssh.resize(system_info.size());
for (size_t i = 0; i < system_info.size(); i++) {
moof_->pssh[i].raw_box = system_info[i].CreateBox();
}
if (muxer_listener_) {
muxer_listener_->OnEncryptionInfoReady(
!kInitialEncryptionInfo, protection_scheme(), encryption_key()->key_id,
encryption_key()->iv, encryption_key()->key_system_info);
}
// Skip the following steps if the current fragment is not going to be
// encrypted. 'pssh' box needs to be included in the fragment, which is
// performed above, regardless of whether the fragment is encrypted. This is
// necessary for two reasons: 1) Requesting keys before reaching encrypted
// content avoids playback delay due to license requests; 2) In Chrome, CDM
// must be initialized before starting the playback and CDM can only be
// initialized with a valid 'pssh'.
if (!enable_encryption) {
DCHECK(!encryptor());
return Status::OK;
}
if (need_to_refresh_encryptor) {
Status status = CreateEncryptor();
if (!status.ok())
return status;
}
DCHECK(encryptor());
// Key rotation happens in fragment boundary only in this implementation,
// i.e. there is at most one key for the fragment. So there should be only
// one entry in SampleGroupDescription box and one entry in SampleToGroup box.
// Fill in SampleGroupDescription box information.
traf()->sample_group_descriptions.resize(
traf()->sample_group_descriptions.size() + 1);
SampleGroupDescription& sample_group_description =
traf()->sample_group_descriptions.back();
sample_group_description.grouping_type = FOURCC_seig;
sample_group_description.cenc_sample_encryption_info_entries.resize(1);
CencSampleEncryptionInfoEntry& sample_group_entry =
sample_group_description.cenc_sample_encryption_info_entries.back();
sample_group_entry.is_protected = 1;
if (protection_scheme() == FOURCC_cbcs) {
// For 'cbcs' scheme, Constant IVs SHALL be used.
sample_group_entry.per_sample_iv_size = 0;
sample_group_entry.constant_iv = encryptor()->iv();
} else {
sample_group_entry.per_sample_iv_size =
static_cast<uint8_t>(encryptor()->iv().size());
}
sample_group_entry.crypt_byte_block = crypt_byte_block();
sample_group_entry.skip_byte_block = skip_byte_block();
sample_group_entry.key_id = encryption_key()->key_id;
return Status::OK;
}
} // namespace mp4
} // namespace media
} // namespace shaka

View File

@ -1,81 +0,0 @@
// Copyright 2014 Google Inc. 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 MEDIA_FORMATS_MP4_KEY_ROTATION_FRAGMENTER_H_
#define MEDIA_FORMATS_MP4_KEY_ROTATION_FRAGMENTER_H_
#include "packager/media/base/fourccs.h"
#include "packager/media/base/key_source.h"
#include "packager/media/event/muxer_listener.h"
#include "packager/media/formats/mp4/encrypting_fragmenter.h"
namespace shaka {
namespace media {
namespace mp4 {
struct MovieFragment;
/// KeyRotationFragmenter generates MP4 fragments with sample encrypted by
/// rotation keys.
class KeyRotationFragmenter : public EncryptingFragmenter {
public:
/// @param moof points to a MovieFragment box.
/// @param info contains stream information.
/// @param traf points to a TrackFragment box.
/// @param encryption_key_source points to the source which generates
/// encryption keys.
/// @param track_type indicates whether SD key or HD key should be used to
/// encrypt the video content.
/// @param crypto_period_duration specifies crypto period duration in units
/// of the current track's timescale.
/// @param clear_time specifies clear lead duration in units of the current
/// track's timescale.
/// @param protection_scheme specifies the protection scheme: 'cenc', 'cens',
/// 'cbc1', 'cbcs'.
/// @param crypt_byte_block indicates number of encrypted blocks (16-byte) in
/// pattern based encryption.
/// @param skip_byte_block indicates number of unencrypted blocks (16-byte)
/// in pattern based encryption.
/// @param muxer_listener is a pointer to MuxerListener for notifying
/// muxer related events. This may be null.
KeyRotationFragmenter(MovieFragment* moof,
std::shared_ptr<StreamInfo> info,
TrackFragment* traf,
KeySource* encryption_key_source,
KeySource::TrackType track_type,
int64_t crypto_period_duration,
int64_t clear_time,
FourCC protection_scheme,
uint8_t crypt_byte_block,
uint8_t skip_byte_block,
MuxerListener* muxer_listener);
~KeyRotationFragmenter() override;
protected:
/// @name Fragmenter implementation overrides.
/// @{
Status PrepareFragmentForEncryption(bool enable_encryption) override;
/// @}
private:
MovieFragment* moof_;
KeySource* encryption_key_source_;
KeySource::TrackType track_type_;
const int64_t crypto_period_duration_;
int64_t prev_crypto_period_index_;
// For notifying new pssh boxes to the event handler.
MuxerListener* const muxer_listener_;
DISALLOW_COPY_AND_ASSIGN(KeyRotationFragmenter);
};
} // namespace mp4
} // namespace media
} // namespace shaka
#endif // MEDIA_FORMATS_MP4_KEY_ROTATION_FRAGMENTER_H_

View File

@ -26,12 +26,8 @@
'composition_offset_iterator.h',
'decoding_time_iterator.cc',
'decoding_time_iterator.h',
'encrypting_fragmenter.cc',
'encrypting_fragmenter.h',
'fragmenter.cc',
'fragmenter.h',
'key_rotation_fragmenter.cc',
'key_rotation_fragmenter.h',
'mp4_media_parser.cc',
'mp4_media_parser.h',
'mp4_muxer.cc',

View File

@ -462,8 +462,11 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) {
break;
}
// The stream will be decrypted if a |decryptor_source_| is available.
const bool is_encrypted =
entry.sinf.info.track_encryption.default_is_protected == 1;
decryptor_source_
? false
: entry.sinf.info.track_encryption.default_is_protected == 1;
DVLOG(1) << "is_audio_track_encrypted_: " << is_encrypted;
streams.emplace_back(new AudioStreamInfo(
track->header.track_id, timescale, duration, codec,
@ -558,8 +561,11 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) {
return false;
}
// The stream will be decrypted if a |decryptor_source_| is available.
const bool is_encrypted =
entry.sinf.info.track_encryption.default_is_protected == 1;
decryptor_source_
? false
: entry.sinf.info.track_encryption.default_is_protected == 1;
DVLOG(1) << "is_video_track_encrypted_: " << is_encrypted;
std::shared_ptr<VideoStreamInfo> video_stream_info(new VideoStreamInfo(
track->header.track_id, timescale, duration, video_codec,

View File

@ -76,6 +76,29 @@ FourCC CodecToFourCC(Codec codec) {
}
}
void GenerateSinf(FourCC old_type,
const EncryptionConfig& encryption_config,
ProtectionSchemeInfo* sinf) {
sinf->format.format = old_type;
DCHECK_NE(encryption_config.protection_scheme, FOURCC_NULL);
sinf->type.type = encryption_config.protection_scheme;
// The version of cenc implemented here. CENC 4.
const int kCencSchemeVersion = 0x00010000;
sinf->type.version = kCencSchemeVersion;
auto& track_encryption = sinf->info.track_encryption;
track_encryption.default_is_protected = 1;
track_encryption.default_crypt_byte_block =
encryption_config.crypt_byte_block;
track_encryption.default_skip_byte_block = encryption_config.skip_byte_block;
track_encryption.default_per_sample_iv_size =
encryption_config.per_sample_iv_size;
track_encryption.default_constant_iv = encryption_config.constant_iv;
track_encryption.default_kid = encryption_config.key_id;
}
} // namespace
MP4Muxer::MP4Muxer(const MuxerOptions& options) : Muxer(options) {}
@ -126,6 +149,14 @@ Status MP4Muxer::InitializeMuxer() {
NOTIMPLEMENTED() << "Not implemented for stream type: "
<< streams()[i]->stream_type();
}
if (streams()[i]->is_encrypted()) {
const auto& key_system_info =
streams()[i]->encryption_config().key_system_info;
moov->pssh.resize(key_system_info.size());
for (size_t j = 0; j < key_system_info.size(); j++)
moov->pssh[j].raw_box = key_system_info[j].CreateBox();
}
}
if (options().segment_template.empty()) {
@ -136,12 +167,8 @@ Status MP4Muxer::InitializeMuxer() {
new MultiSegmentSegmenter(options(), std::move(ftyp), std::move(moov)));
}
const Status segmenter_initialized = segmenter_->Initialize(
streams(), muxer_listener(), progress_listener(), encryption_key_source(),
max_sd_pixels(), max_hd_pixels(), max_uhd1_pixels(),
clear_lead_in_seconds(), crypto_period_duration_in_seconds(),
protection_scheme());
const Status segmenter_initialized =
segmenter_->Initialize(streams(), muxer_listener(), progress_listener());
if (!segmenter_initialized.ok())
return segmenter_initialized;
@ -173,7 +200,7 @@ Status MP4Muxer::FinalizeSegment(size_t stream_id,
VLOG(3) << "Finalize " << (segment_info->is_subsegment ? "sub" : "")
<< "segment " << segment_info->start_timestamp << " duration "
<< segment_info->duration;
return segmenter_->FinalizeSegment(stream_id, segment_info->is_subsegment);
return segmenter_->FinalizeSegment(stream_id, std::move(segment_info));
}
void MP4Muxer::InitializeTrak(const StreamInfo* info, Track* trak) {
@ -236,6 +263,15 @@ void MP4Muxer::GenerateVideoTrak(const VideoStreamInfo* video_info,
trak->media.information.sample_table.description;
sample_description.type = kVideo;
sample_description.video_entries.push_back(video);
if (video_info->is_encrypted()) {
// Add a second entry for clear content.
sample_description.video_entries.push_back(video);
// Convert the first entry to an encrypted entry.
VideoSampleEntry& entry = sample_description.video_entries[0];
GenerateSinf(entry.format, video_info->encryption_config(), &entry.sinf);
entry.format = FOURCC_encv;
}
}
void MP4Muxer::GenerateAudioTrak(const AudioStreamInfo* audio_info,
@ -289,6 +325,15 @@ void MP4Muxer::GenerateAudioTrak(const AudioStreamInfo* audio_info,
sample_description.type = kAudio;
sample_description.audio_entries.push_back(audio);
if (audio_info->is_encrypted()) {
// Add a second entry for clear content.
sample_description.audio_entries.push_back(audio);
// Convert the first entry to an encrypted entry.
AudioSampleEntry& entry = sample_description.audio_entries[0];
GenerateSinf(entry.format, audio_info->encryption_config(), &entry.sinf);
entry.format = FOURCC_enca;
}
// Opus requires at least one sample group description box and at least one
// sample to group box with grouping type 'roll' within sample table box.
if (audio_info->codec() == kCodecOpus) {

View File

@ -9,17 +9,15 @@
#include <algorithm>
#include "packager/base/logging.h"
#include "packager/media/base/aes_cryptor.h"
#include "packager/media/base/buffer_writer.h"
#include "packager/media/base/key_source.h"
#include "packager/media/base/media_sample.h"
#include "packager/media/base/muxer_options.h"
#include "packager/media/base/muxer_util.h"
#include "packager/media/base/video_stream_info.h"
#include "packager/media/event/muxer_listener.h"
#include "packager/media/base/stream_info.h"
#include "packager/media/chunking/chunking_handler.h"
#include "packager/media/event/progress_listener.h"
#include "packager/media/formats/mp4/box_definitions.h"
#include "packager/media/formats/mp4/key_rotation_fragmenter.h"
#include "packager/media/formats/mp4/fragmenter.h"
#include "packager/version/version.h"
namespace shaka {
@ -27,25 +25,6 @@ namespace media {
namespace mp4 {
namespace {
const size_t kCencKeyIdSize = 16u;
// The version of cenc implemented here. CENC 4.
const int kCencSchemeVersion = 0x00010000;
// The default KID for key rotation is all 0s.
const uint8_t kKeyRotationDefaultKeyId[] = {
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
};
// Defines protection pattern for pattern-based encryption.
struct ProtectionPattern {
uint8_t crypt_byte_block;
uint8_t skip_byte_block;
};
static_assert(arraysize(kKeyRotationDefaultKeyId) == kCencKeyIdSize,
"cenc_key_id_must_be_size_16");
uint64_t Rescale(uint64_t time_in_old_scale,
uint32_t old_scale,
@ -53,95 +32,6 @@ uint64_t Rescale(uint64_t time_in_old_scale,
return static_cast<double>(time_in_old_scale) / old_scale * new_scale;
}
ProtectionPattern GetProtectionPattern(FourCC protection_scheme,
TrackType track_type) {
ProtectionPattern pattern;
if (protection_scheme != FOURCC_cbcs && protection_scheme != FOURCC_cens) {
// Not using pattern encryption.
pattern.crypt_byte_block = 0u;
pattern.skip_byte_block = 0u;
} else if (track_type != kVideo) {
// Tracks other than video are protected using whole-block full-sample
// encryption, which is essentially a pattern of 1:0. Note that this may not
// be the same as the non-pattern based encryption counterparts, e.g. in
// 'cens' for full sample encryption, the whole sample is encrypted up to
// the last 16-byte boundary, see 23001-7:2016(E) 9.7; while in 'cenc' for
// full sample encryption, the last partial 16-byte block is also encrypted,
// see 23001-7:2016(E) 9.4.2. Another difference is the use of constant iv.
pattern.crypt_byte_block = 1u;
pattern.skip_byte_block = 0u;
} else {
// Use 1:9 pattern for video.
const uint8_t kCryptByteBlock = 1u;
const uint8_t kSkipByteBlock = 9u;
pattern.crypt_byte_block = kCryptByteBlock;
pattern.skip_byte_block = kSkipByteBlock;
}
return pattern;
}
void GenerateSinf(const EncryptionKey& encryption_key,
FourCC old_type,
FourCC protection_scheme,
ProtectionPattern pattern,
ProtectionSchemeInfo* sinf) {
sinf->format.format = old_type;
DCHECK_NE(protection_scheme, FOURCC_NULL);
sinf->type.type = protection_scheme;
sinf->type.version = kCencSchemeVersion;
auto& track_encryption = sinf->info.track_encryption;
track_encryption.default_is_protected = 1;
DCHECK(!encryption_key.iv.empty());
if (protection_scheme == FOURCC_cbcs) {
// ISO/IEC 23001-7:2016 10.4.1
// For 'cbcs' scheme, Constant IVs SHALL be used.
track_encryption.default_per_sample_iv_size = 0;
track_encryption.default_constant_iv = encryption_key.iv;
} else {
track_encryption.default_per_sample_iv_size =
static_cast<uint8_t>(encryption_key.iv.size());
}
track_encryption.default_crypt_byte_block = pattern.crypt_byte_block;
track_encryption.default_skip_byte_block = pattern.skip_byte_block;
track_encryption.default_kid = encryption_key.key_id;
}
void GenerateEncryptedSampleEntry(const EncryptionKey& encryption_key,
double clear_lead_in_seconds,
FourCC protection_scheme,
ProtectionPattern pattern,
SampleDescription* description) {
DCHECK(description);
if (description->type == kVideo) {
DCHECK_EQ(1u, description->video_entries.size());
// Add a second entry for clear content if needed.
if (clear_lead_in_seconds > 0)
description->video_entries.push_back(description->video_entries[0]);
// Convert the first entry to an encrypted entry.
VideoSampleEntry& entry = description->video_entries[0];
GenerateSinf(encryption_key, entry.format, protection_scheme, pattern,
&entry.sinf);
entry.format = FOURCC_encv;
} else {
DCHECK_EQ(kAudio, description->type);
DCHECK_EQ(1u, description->audio_entries.size());
// Add a second entry for clear content if needed.
if (clear_lead_in_seconds > 0)
description->audio_entries.push_back(description->audio_entries[0]);
// Convert the first entry to an encrypted entry.
AudioSampleEntry& entry = description->audio_entries[0];
GenerateSinf(encryption_key, entry.format, protection_scheme, pattern,
&entry.sinf);
entry.format = FOURCC_enca;
}
}
} // namespace
Segmenter::Segmenter(const MuxerOptions& options,
@ -153,7 +43,6 @@ Segmenter::Segmenter(const MuxerOptions& options,
moof_(new MovieFragment()),
fragment_buffer_(new BufferWriter()),
sidx_(new SegmentIndex()),
muxer_listener_(NULL),
progress_listener_(NULL),
progress_target_(0),
accumulated_progress_(0),
@ -164,14 +53,7 @@ Segmenter::~Segmenter() {}
Status Segmenter::Initialize(
const std::vector<std::shared_ptr<StreamInfo>>& streams,
MuxerListener* muxer_listener,
ProgressListener* progress_listener,
KeySource* encryption_key_source,
uint32_t max_sd_pixels,
uint32_t max_hd_pixels,
uint32_t max_uhd1_pixels,
double clear_lead_in_seconds,
double crypto_period_duration_in_seconds,
FourCC protection_scheme) {
ProgressListener* progress_listener) {
DCHECK_LT(0u, streams.size());
muxer_listener_ = muxer_listener;
progress_listener_ = progress_listener;
@ -179,8 +61,6 @@ Status Segmenter::Initialize(
moof_->tracks.resize(streams.size());
fragmenters_.resize(streams.size());
const bool key_rotation_enabled = crypto_period_duration_in_seconds != 0;
const bool kInitialEncryptionInfo = true;
for (uint32_t i = 0; i < streams.size(); ++i) {
moof_->tracks[i].header.track_id = i + 1;
@ -189,77 +69,7 @@ Status Segmenter::Initialize(
if (sidx_->reference_id == 0)
sidx_->reference_id = i + 1;
}
if (!encryption_key_source) {
fragmenters_[i].reset(new Fragmenter(streams[i], &moof_->tracks[i]));
continue;
}
KeySource::TrackType track_type = GetTrackTypeForEncryption(
*streams[i], max_sd_pixels, max_hd_pixels, max_uhd1_pixels);
SampleDescription& description =
moov_->tracks[i].media.information.sample_table.description;
ProtectionPattern pattern =
GetProtectionPattern(protection_scheme, description.type);
if (key_rotation_enabled) {
// Fill encrypted sample entry with default key.
EncryptionKey encryption_key;
encryption_key.key_id.assign(
kKeyRotationDefaultKeyId,
kKeyRotationDefaultKeyId + arraysize(kKeyRotationDefaultKeyId));
if (!AesCryptor::GenerateRandomIv(protection_scheme,
&encryption_key.iv)) {
return Status(error::INTERNAL_ERROR, "Failed to generate random iv.");
}
GenerateEncryptedSampleEntry(encryption_key, clear_lead_in_seconds,
protection_scheme, pattern, &description);
if (muxer_listener_) {
muxer_listener_->OnEncryptionInfoReady(
kInitialEncryptionInfo, protection_scheme, encryption_key.key_id,
encryption_key.iv, encryption_key.key_system_info);
}
fragmenters_[i].reset(new KeyRotationFragmenter(
moof_.get(), streams[i], &moof_->tracks[i], encryption_key_source,
track_type,
crypto_period_duration_in_seconds * streams[i]->time_scale(),
clear_lead_in_seconds * streams[i]->time_scale(), protection_scheme,
pattern.crypt_byte_block, pattern.skip_byte_block, muxer_listener_));
continue;
}
std::unique_ptr<EncryptionKey> encryption_key(new EncryptionKey());
Status status =
encryption_key_source->GetKey(track_type, encryption_key.get());
if (!status.ok())
return status;
if (encryption_key->iv.empty()) {
if (!AesCryptor::GenerateRandomIv(protection_scheme,
&encryption_key->iv)) {
return Status(error::INTERNAL_ERROR, "Failed to generate random iv.");
}
}
GenerateEncryptedSampleEntry(*encryption_key, clear_lead_in_seconds,
protection_scheme, pattern, &description);
if (moov_->pssh.empty()) {
moov_->pssh.resize(encryption_key->key_system_info.size());
for (size_t i = 0; i < encryption_key->key_system_info.size(); i++) {
moov_->pssh[i].raw_box = encryption_key->key_system_info[i].CreateBox();
}
if (muxer_listener_) {
muxer_listener_->OnEncryptionInfoReady(
kInitialEncryptionInfo, protection_scheme, encryption_key->key_id,
encryption_key->iv, encryption_key->key_system_info);
}
}
fragmenters_[i].reset(new EncryptingFragmenter(
streams[i], &moof_->tracks[i], std::move(encryption_key),
clear_lead_in_seconds * streams[i]->time_scale(), protection_scheme,
pattern.crypt_byte_block, pattern.skip_byte_block, muxer_listener_));
}
if (options_.mp4_use_decoding_timestamp_in_timeline) {
@ -333,11 +143,20 @@ Status Segmenter::AddSample(size_t stream_id,
return Status::OK;
}
Status Segmenter::FinalizeSegment(size_t stream_id, bool is_subsegment) {
Status Segmenter::FinalizeSegment(size_t stream_id,
std::shared_ptr<SegmentInfo> segment_info) {
if (segment_info->key_rotation_encryption_config) {
FinalizeFragmentForKeyRotation(
stream_id, segment_info->is_encrypted,
*segment_info->key_rotation_encryption_config);
}
DCHECK_LT(stream_id, fragmenters_.size());
Fragmenter* fragmenter = fragmenters_[stream_id].get();
DCHECK(fragmenter);
fragmenter->FinalizeFragment();
Status status = fragmenter->FinalizeFragment();
if (!status.ok())
return status;
// Check if all tracks are ready for fragmentation.
for (const std::unique_ptr<Fragmenter>& fragmenter : fragmenters_) {
@ -387,7 +206,7 @@ Status Segmenter::FinalizeSegment(size_t stream_id, bool is_subsegment) {
for (std::unique_ptr<Fragmenter>& fragmenter : fragmenters_)
fragmenter->ClearFragmentFinalized();
if (!is_subsegment) {
if (!segment_info->is_subsegment) {
Status status = DoFinalizeSegment();
// Reset segment information to initial state.
sidx_->references.clear();
@ -435,6 +254,45 @@ uint32_t Segmenter::GetReferenceStreamId() {
return sidx_->reference_id - 1;
}
void Segmenter::FinalizeFragmentForKeyRotation(
size_t stream_id,
bool fragment_encrypted,
const EncryptionConfig& encryption_config) {
const std::vector<ProtectionSystemSpecificInfo>& system_info =
encryption_config.key_system_info;
moof_->pssh.resize(system_info.size());
for (size_t i = 0; i < system_info.size(); i++)
moof_->pssh[i].raw_box = system_info[i].CreateBox();
// Skip the following steps if the current fragment is not going to be
// encrypted. 'pssh' box needs to be included in the fragment, which is
// performed above, regardless of whether the fragment is encrypted. This is
// necessary for two reasons: 1) Requesting keys before reaching encrypted
// content avoids playback delay due to license requests; 2) In Chrome, CDM
// must be initialized before starting the playback and CDM can only be
// initialized with a valid 'pssh'.
if (!fragment_encrypted)
return;
DCHECK_LT(stream_id, moof_->tracks.size());
TrackFragment& traf = moof_->tracks[stream_id];
traf.sample_group_descriptions.resize(traf.sample_group_descriptions.size() +
1);
SampleGroupDescription& sample_group_description =
traf.sample_group_descriptions.back();
sample_group_description.grouping_type = FOURCC_seig;
sample_group_description.cenc_sample_encryption_info_entries.resize(1);
CencSampleEncryptionInfoEntry& sample_group_entry =
sample_group_description.cenc_sample_encryption_info_entries.back();
sample_group_entry.is_protected = 1;
sample_group_entry.per_sample_iv_size = encryption_config.per_sample_iv_size;
sample_group_entry.constant_iv = encryption_config.constant_iv;
sample_group_entry.crypt_byte_block = encryption_config.crypt_byte_block;
sample_group_entry.skip_byte_block = encryption_config.skip_byte_block;
sample_group_entry.key_id = encryption_config.key_id;
}
} // namespace mp4
} // namespace media
} // namespace shaka

View File

@ -18,10 +18,11 @@
namespace shaka {
namespace media {
struct EncryptionConfig;
struct MuxerOptions;
struct SegmentInfo;
class BufferWriter;
class KeySource;
class MediaSample;
class MuxerListener;
class ProgressListener;
@ -50,35 +51,10 @@ class Segmenter {
/// @param streams contains the vector of MediaStreams to be segmented.
/// @param muxer_listener receives muxer events. Can be NULL.
/// @param progress_listener receives progress updates. Can be NULL.
/// @param encryption_key_source points to the key source which contains
/// the encryption keys. It can be NULL to indicate that no encryption
/// is required.
/// @param max_sd_pixels specifies the threshold to determine whether a video
/// track should be considered as SD. If the max pixels per frame is
/// no higher than max_sd_pixels, it is SD.
/// @param max_hd_pixels specifies the threshold to determine whether a video
/// track should be considered as HD. If the max pixels per frame is
/// higher than max_sd_pixels, but no higher than max_hd_pixels,
/// it is HD.
/// @param max_uhd1_pixels specifies the threshold to determine whether a video
/// track should be considered as UHD1. If the max pixels per frame is
/// higher than max_hd_pixels, but no higher than max_uhd1_pixels,
/// it is UHD1. Otherwise it is UHD2.
/// @param clear_time specifies clear lead duration in seconds.
/// @param crypto_period_duration specifies crypto period duration in seconds.
/// @param protection_scheme specifies the protection scheme: 'cenc', 'cens',
/// 'cbc1', 'cbcs'.
/// @return OK on success, an error status otherwise.
Status Initialize(const std::vector<std::shared_ptr<StreamInfo>>& streams,
MuxerListener* muxer_listener,
ProgressListener* progress_listener,
KeySource* encryption_key_source,
uint32_t max_sd_pixels,
uint32_t max_hd_pixels,
uint32_t max_uhd1_pixels,
double clear_lead_in_seconds,
double crypto_period_duration_in_seconds,
FourCC protection_scheme);
ProgressListener* progress_listener);
/// Finalize the segmenter.
/// @return OK on success, an error status otherwise.
@ -94,7 +70,8 @@ class Segmenter {
/// @param stream_id is the zero-based stream index.
/// @param is_subsegment indicates if it is a subsegment (fragment).
/// @return OK on success, an error status otherwise.
Status FinalizeSegment(size_t stream_id, bool is_subsegment);
Status FinalizeSegment(size_t stream_id,
std::shared_ptr<SegmentInfo> segment_info);
/// @return true if there is an initialization range, while setting @a offset
/// and @a size; or false if initialization range does not apply.
@ -138,6 +115,11 @@ class Segmenter {
uint32_t GetReferenceStreamId();
void FinalizeFragmentForKeyRotation(
size_t stream_id,
bool fragment_encrypted,
const EncryptionConfig& encryption_config);
const MuxerOptions& options_;
std::unique_ptr<FileType> ftyp_;
std::unique_ptr<Movie> moov_;

View File

@ -6,19 +6,22 @@
#include <gtest/gtest.h>
#include <memory>
#include "packager/media/base/fixed_key_source.h"
#include "packager/media/formats/webm/segmenter_test_base.h"
namespace shaka {
namespace media {
namespace {
const uint64_t kDuration = 1000;
const uint64_t kDuration = 1000u;
const bool kSubsegment = true;
const std::string kKeyId = "4c6f72656d20697073756d20646f6c6f";
const std::string kIv = "0123456789012345";
const std::string kKey = "01234567890123456789012345678901";
const std::string kPsshData = "";
const uint8_t kPerSampleIvSize = 8u;
const uint8_t kKeyId[] = {
0x4c, 0x6f, 0x72, 0x65, 0x6d, 0x20, 0x69, 0x70,
0x73, 0x75, 0x6d, 0x20, 0x64, 0x6f, 0x6c, 0x6f,
};
const uint8_t kIv[] = {
0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x23, 0x45,
};
const uint8_t kBasicSupportData[] = {
// ID: EBML Header omitted.
// ID: Segment, Payload Size: 432
@ -174,7 +177,7 @@ const uint8_t kBasicSupportData[] = {
// IV:
0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x23, 0x45,
// Frame Data:
0xcc, 0x03, 0xef, 0xc4, 0xf4,
0xde, 0xad, 0xbe, 0xef, 0x00,
// ID: BlockGroup, Payload Size: 24
0xa0, 0x98,
// ID: Block, Payload Size: 18
@ -182,34 +185,37 @@ const uint8_t kBasicSupportData[] = {
// Signal Byte: Encrypted
0x01,
// IV:
0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x23, 0x46,
0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x23, 0x45,
// Frame Data:
0xbf, 0x38, 0x72, 0x20, 0xac,
0xde, 0xad, 0xbe, 0xef, 0x00,
// BlockDuration: 1000
0x9b, 0x82, 0x03, 0xe8,
};
} // namespace
class EncrypedSegmenterTest : public SegmentTestBase {
class EncryptedSegmenterTest : public SegmentTestBase {
public:
EncrypedSegmenterTest() : info_(CreateVideoStreamInfo()) {}
EncryptedSegmenterTest() : info_(CreateVideoStreamInfo()) {
EncryptionConfig encryption_config;
encryption_config.per_sample_iv_size = kPerSampleIvSize;
encryption_config.key_id.assign(kKeyId, kKeyId + sizeof(kKeyId));
info_->set_is_encrypted(true);
info_->set_encryption_config(encryption_config);
}
protected:
void InitializeSegmenter(const MuxerOptions& options) {
key_source_ =
FixedKeySource::CreateFromHexStrings(kKeyId, kKey, kPsshData, kIv);
ASSERT_NO_FATAL_FAILURE(
CreateAndInitializeSegmenter<webm::TwoPassSingleSegmentSegmenter>(
options, info_.get(), key_source_.get(), &segmenter_));
options, info_.get(), &segmenter_));
}
std::shared_ptr<StreamInfo> info_;
std::unique_ptr<webm::Segmenter> segmenter_;
std::unique_ptr<KeySource> key_source_;
};
TEST_F(EncrypedSegmenterTest, BasicSupport) {
TEST_F(EncryptedSegmenterTest, BasicSupport) {
MuxerOptions options = CreateMuxerOptions();
ASSERT_NO_FATAL_FAILURE(InitializeSegmenter(options));
@ -221,6 +227,14 @@ TEST_F(EncrypedSegmenterTest, BasicSupport) {
ASSERT_OK(segmenter_->FinalizeSegment(0, 3 * kDuration, !kSubsegment));
std::shared_ptr<MediaSample> sample =
CreateSample(kKeyFrame, kDuration, kNoSideData);
if (i >= 3) {
sample->set_is_encrypted(true);
std::unique_ptr<DecryptConfig> decrypt_config(
new DecryptConfig(info_->encryption_config().key_id,
std::vector<uint8_t>(kIv, kIv + sizeof(kIv)),
std::vector<SubsampleEntry>()));
sample->set_decrypt_config(std::move(decrypt_config));
}
ASSERT_OK(segmenter_->AddSample(sample));
}
ASSERT_OK(

View File

@ -6,22 +6,18 @@
#include "packager/media/formats/webm/encryptor.h"
#include <gflags/gflags.h>
#include "packager/media/base/aes_encryptor.h"
#include "packager/media/base/buffer_writer.h"
#include "packager/media/base/fourccs.h"
#include "packager/media/base/media_sample.h"
#include "packager/media/codecs/vp9_parser.h"
#include "packager/media/formats/webm/webm_constants.h"
namespace shaka {
namespace media {
namespace webm {
namespace {
const size_t kAesBlockSize = 16;
Status UpdateTrackForEncryption(const std::vector<uint8_t>& key_id,
mkvmuxer::Track* track) {
DCHECK_EQ(track->content_encoding_entries_size(), 0u);
Status CreateContentEncryption(mkvmuxer::Track* track, EncryptionKey* key) {
if (!track->AddContentEncoding()) {
return Status(error::INTERNAL_ERROR,
"Could not add ContentEncoding to track.");
@ -43,98 +39,51 @@ Status CreateContentEncryption(mkvmuxer::Track* track, EncryptionKey* key) {
return Status(error::INTERNAL_ERROR, "Cipher Mode is not CTR.");
}
if (!key->key_id.empty() &&
!encoding->SetEncryptionID(
reinterpret_cast<const uint8_t*>(key->key_id.data()),
key->key_id.size())) {
if (!encoding->SetEncryptionID(key_id.data(), key_id.size())) {
return Status(error::INTERNAL_ERROR, "Error setting encryption ID.");
}
return Status::OK;
}
} // namespace
Encryptor::Encryptor() {}
Encryptor::~Encryptor() {}
Status Encryptor::Initialize(MuxerListener* muxer_listener,
KeySource::TrackType track_type,
Codec codec,
KeySource* key_source,
bool webm_subsample_encryption) {
DCHECK(key_source);
return CreateEncryptor(muxer_listener, track_type, codec, key_source,
webm_subsample_encryption);
}
Status Encryptor::AddTrackInfo(mkvmuxer::Track* track) {
DCHECK(key_);
return CreateContentEncryption(track, key_.get());
}
Status Encryptor::EncryptFrame(std::shared_ptr<MediaSample> sample,
bool encrypt_frame) {
DCHECK(encryptor_);
void UpdateFrameForEncryption(MediaSample* sample) {
const size_t sample_size = sample->data_size();
// 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_) {
if (!vpx_parser_->Parse(sample->data(), sample_size, &vpx_frames)) {
return Status(error::MUXER_FAILURE, "Failed to parse VPx frame.");
}
}
if (encrypt_frame) {
const size_t iv_size = encryptor_->iv().size();
if (iv_size != kWebMIvSize) {
return Status(error::MUXER_FAILURE,
"Incorrect size WebM encryption IV.");
}
if (vpx_frames.size()) {
if (sample->decrypt_config()) {
auto* decrypt_config = sample->decrypt_config();
const size_t iv_size = decrypt_config->iv().size();
DCHECK_EQ(iv_size, kWebMIvSize);
if (!decrypt_config->subsamples().empty()) {
auto& subsamples = decrypt_config->subsamples();
// Use partitioned subsample encryption: | signal_byte(3) | iv
// | num_partitions | partition_offset * n | enc_data |
if (vpx_frames.size() > kWebMMaxSubsamples) {
return Status(error::MUXER_FAILURE,
"Maximum number of VPx encryption partitions exceeded.");
}
size_t num_partitions =
vpx_frames.size() == 1 ? 1 : vpx_frames.size() * 2;
size_t header_size = kWebMSignalByteSize + iv_size +
DCHECK_LT(subsamples.size(), kWebMMaxSubsamples);
const size_t num_partitions =
2 * subsamples.size() - 1 -
(subsamples.back().cipher_bytes == 0 ? 1 : 0);
const size_t header_size = kWebMSignalByteSize + iv_size +
kWebMNumPartitionsSize +
(kWebMPartitionOffsetSize * num_partitions);
sample->resize_data(header_size + sample_size);
uint8_t* sample_data = sample->writable_data();
memmove(sample_data + header_size, sample_data, sample_size);
sample_data[0] = kWebMEncryptedSignal | kWebMPartitionedSignal;
memcpy(sample_data + kWebMSignalByteSize, encryptor_->iv().data(),
memcpy(sample_data + kWebMSignalByteSize, decrypt_config->iv().data(),
iv_size);
sample_data[kWebMSignalByteSize + kWebMIvSize] =
static_cast<uint8_t>(num_partitions);
BufferWriter offsets_buffer;
uint32_t partition_offset = 0;
BufferWriter offsets_buffer(kWebMPartitionOffsetSize * num_partitions);
for (const auto& vpx_frame : vpx_frames) {
uint32_t encrypted_size = static_cast<uint32_t>(
vpx_frame.frame_size - vpx_frame.uncompressed_header_size);
encrypted_size -= encrypted_size % kAesBlockSize;
uint32_t clear_size =
static_cast<uint32_t>(vpx_frame.frame_size - encrypted_size);
partition_offset += clear_size;
for (size_t i = 0; i < subsamples.size() - 1; ++i) {
partition_offset += subsamples[i].clear_bytes;
offsets_buffer.AppendInt(partition_offset);
if (encrypted_size > 0) {
uint8_t* encrypted_ptr = sample_data + header_size + partition_offset;
if (!encryptor_->Crypt(encrypted_ptr, encrypted_size, encrypted_ptr)) {
return Status(error::MUXER_FAILURE, "Failed to encrypt the frame.");
}
partition_offset += encrypted_size;
}
if (num_partitions > 1) {
partition_offset += subsamples[i].cipher_bytes;
offsets_buffer.AppendInt(partition_offset);
}
// Add another partition between the clear bytes and cipher bytes if
// cipher bytes is not zero.
if (subsamples.back().cipher_bytes != 0) {
partition_offset += subsamples.back().clear_bytes;
offsets_buffer.AppendInt(partition_offset);
}
DCHECK_EQ(num_partitions * kWebMPartitionOffsetSize,
offsets_buffer.Size());
@ -143,23 +92,16 @@ Status Encryptor::EncryptFrame(std::shared_ptr<MediaSample> sample,
offsets_buffer.Buffer(), offsets_buffer.Size());
} else {
// Use whole-frame encryption: | signal_byte(1) | iv | enc_data |
sample->resize_data(sample_size + iv_size + kWebMSignalByteSize);
uint8_t* sample_data = sample->writable_data();
// Encrypt the data in-place.
if (!encryptor_->Crypt(sample_data, sample_size, sample_data)) {
return Status(error::MUXER_FAILURE, "Failed to encrypt the frame.");
}
// First move the sample data to after the IV; then write the IV and
// signal byte.
memmove(sample_data + iv_size + kWebMSignalByteSize, sample_data,
sample_size);
sample_data[0] = kWebMEncryptedSignal;
memcpy(sample_data + 1, encryptor_->iv().data(), iv_size);
memcpy(sample_data + 1, decrypt_config->iv().data(), iv_size);
}
encryptor_->UpdateIv();
} else {
// Clear sample: | signal_byte(0) | data |
sample->resize_data(sample_size + 1);
@ -167,45 +109,6 @@ Status Encryptor::EncryptFrame(std::shared_ptr<MediaSample> sample,
memmove(sample_data + 1, sample_data, sample_size);
sample_data[0] = 0x00;
}
return Status::OK;
}
Status Encryptor::CreateEncryptor(MuxerListener* muxer_listener,
KeySource::TrackType track_type,
Codec codec,
KeySource* key_source,
bool webm_subsample_encryption) {
std::unique_ptr<EncryptionKey> encryption_key(new EncryptionKey());
Status status = key_source->GetKey(track_type, encryption_key.get());
if (!status.ok())
return status;
if (encryption_key->iv.empty()) {
if (!AesCryptor::GenerateRandomIv(FOURCC_cenc, &encryption_key->iv))
return Status(error::INTERNAL_ERROR, "Failed to generate random iv.");
}
DCHECK_EQ(kWebMIvSize, encryption_key->iv.size());
std::unique_ptr<AesCtrEncryptor> encryptor(new AesCtrEncryptor());
const bool initialized =
encryptor->InitializeWithIv(encryption_key->key, encryption_key->iv);
if (!initialized)
return Status(error::INTERNAL_ERROR, "Failed to create the encryptor.");
if (webm_subsample_encryption && codec == kCodecVP9) {
// Allocate VP9 parser to do subsample encryption of VP9.
vpx_parser_.reset(new VP9Parser);
}
if (muxer_listener) {
const bool kInitialEncryptionInfo = true;
muxer_listener->OnEncryptionInfoReady(
kInitialEncryptionInfo, FOURCC_cenc, encryption_key->key_id,
encryptor->iv(), encryption_key->key_system_info);
}
key_ = std::move(encryption_key);
encryptor_ = std::move(encryptor);
return Status::OK;
}
} // namespace webm

View File

@ -7,63 +7,26 @@
#ifndef MEDIA_FORMATS_WEBM_ENCRYPTOR_H_
#define MEDIA_FORMATS_WEBM_ENCRYPTOR_H_
#include <memory>
#include "packager/base/macros.h"
#include "packager/media/base/key_source.h"
#include <vector>
#include "packager/media/base/status.h"
#include "packager/media/base/stream_info.h"
#include "packager/media/codecs/vpx_parser.h"
#include "packager/media/event/muxer_listener.h"
#include "packager/third_party/libwebm/src/mkvmuxer.hpp"
namespace shaka {
namespace media {
class AesCtrEncryptor;
class MediaSample;
namespace webm {
/// A helper class used to encrypt WebM frames before being written to the
/// Cluster. This can also handle unencrypted frames.
class Encryptor {
public:
Encryptor();
~Encryptor();
/// Adds the encryption info with the specified key_id to the given track.
/// @return OK on success, an error status otherwise.
Status UpdateTrackForEncryption(const std::vector<uint8_t>& key_id,
mkvmuxer::Track* track);
/// Initializes the encryptor with the given key source.
/// @return OK on success, an error status otherwise.
Status Initialize(MuxerListener* muxer_listener,
KeySource::TrackType track_type,
Codec codec,
KeySource* key_source,
bool webm_subsample_encryption);
/// Adds the encryption info to the given track. Initialize must be called
/// first.
/// @return OK on success, an error status otherwise.
Status AddTrackInfo(mkvmuxer::Track* track);
/// Encrypt the data. This needs to be told whether the current frame will
/// be encrypted (e.g. for a clear lead).
/// @return OK on success, an error status otherwise.
Status EncryptFrame(std::shared_ptr<MediaSample> sample, bool encrypt_frame);
private:
// Create the encryptor for the internal encryption key.
Status CreateEncryptor(MuxerListener* muxer_listener,
KeySource::TrackType track_type,
Codec codec,
KeySource* key_source,
bool webm_subsample_encryption);
private:
std::unique_ptr<EncryptionKey> key_;
std::unique_ptr<AesCtrEncryptor> encryptor_;
std::unique_ptr<VPxParser> vpx_parser_;
DISALLOW_COPY_AND_ASSIGN(Encryptor);
};
/// Update the frame with signal bytes and encryption information if it is
/// encrypted.
void UpdateFrameForEncryption(MediaSample* sample);
} // namespace webm
} // namespace media

View File

@ -0,0 +1,158 @@
// Copyright 2017 Google Inc. 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/formats/webm/encryptor.h"
#include <gtest/gtest.h>
#include <memory>
#include "packager/media/base/media_sample.h"
#include "packager/media/base/test/status_test_util.h"
#include "packager/media/formats/webm/webm_constants.h"
namespace shaka {
namespace media {
namespace webm {
namespace {
const uint8_t kKeyId[] = {
0x4c, 0x6f, 0x72, 0x65, 0x6d, 0x20, 0x69, 0x70,
0x73, 0x75, 0x6d, 0x20, 0x64, 0x6f, 0x6c, 0x6f,
};
const uint8_t kIv[] = {
0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x23, 0x45,
};
// Some dummy data for testing.
const uint8_t kData[] = {0x11, 0x22, 0x33, 0x44, 0x55};
const bool kKeyFrame = true;
} // namespace
TEST(EncryptionUtilTest, UpdateTrack) {
unsigned int seed = 0;
mkvmuxer::VideoTrack video_track(&seed);
ASSERT_OK(UpdateTrackForEncryption(
std::vector<uint8_t>(kKeyId, kKeyId + sizeof(kKeyId)), &video_track));
}
TEST(EncryptionUtilTest, UpdateTrackWithEmptyKeyId) {
const uint8_t kKeyId[] = {};
unsigned int seed = 0;
mkvmuxer::VideoTrack video_track(&seed);
Status status = UpdateTrackForEncryption(
std::vector<uint8_t>(kKeyId, kKeyId + sizeof(kKeyId)), &video_track);
EXPECT_EQ(error::INTERNAL_ERROR, status.error_code());
}
TEST(EncryptionUtilTest, SampleNotEncrypted) {
auto sample = MediaSample::CopyFrom(kData, sizeof(kData), kKeyFrame);
UpdateFrameForEncryption(sample.get());
ASSERT_EQ(sizeof(kData) + 1, sample->data_size());
EXPECT_EQ(0u, sample->data()[0]);
EXPECT_EQ(std::vector<uint8_t>(kData, kData + sizeof(kData)),
std::vector<uint8_t>(sample->data() + 1,
sample->data() + sample->data_size()));
}
namespace {
struct EncryptionTestCase {
const SubsampleEntry* subsamples;
size_t num_subsamples;
const uint8_t* subsample_partition_data;
size_t subsample_partition_data_size;
};
const SubsampleEntry kSubsamples1[] = {
SubsampleEntry(0x12, 0x100),
};
const uint8_t kSubsamplePartitionData1[] = {
0x01, 0x00, 0x00, 0x00, 0x12,
};
const SubsampleEntry kSubsamples2[] = {
SubsampleEntry(0x12, 0x100), SubsampleEntry(0x25, 0),
};
const uint8_t kSubsamplePartitionData2[] = {
0x02, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x01, 0x12,
};
const SubsampleEntry kSubsamples3[] = {
SubsampleEntry(0x12, 0x100), SubsampleEntry(0x25, 0x8000),
SubsampleEntry(0x234, 0),
};
const uint8_t kSubsamplePartitionData3[] = {
0x04, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x01, 0x12,
0x00, 0x00, 0x01, 0x37, 0x00, 0x00, 0x81, 0x37,
};
const SubsampleEntry kSubsamples4[] = {
SubsampleEntry(0x12, 0x100), SubsampleEntry(0x25, 0x8000),
SubsampleEntry(0x234, 0x88000), SubsampleEntry(0x02, 0x20),
};
const uint8_t kSubsamplePartitionData4[] = {
0x07, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x01, 0x12, 0x00,
0x00, 0x01, 0x37, 0x00, 0x00, 0x81, 0x37, 0x00, 0x00, 0x83,
0x6B, 0x00, 0x09, 0x03, 0x6B, 0x00, 0x09, 0x03, 0x6D,
};
EncryptionTestCase kEncryptionTestCases[] = {
// Special case with no subsamples.
{nullptr, 0, nullptr, 0},
{kSubsamples1, arraysize(kSubsamples1), kSubsamplePartitionData1,
arraysize(kSubsamplePartitionData1)},
{kSubsamples2, arraysize(kSubsamples2), kSubsamplePartitionData2,
arraysize(kSubsamplePartitionData2)},
{kSubsamples3, arraysize(kSubsamples3), kSubsamplePartitionData3,
arraysize(kSubsamplePartitionData3)},
{kSubsamples4, arraysize(kSubsamples4), kSubsamplePartitionData4,
arraysize(kSubsamplePartitionData4)},
};
} // namespace
class EncryptionUtilEncryptedTest
: public ::testing::TestWithParam<EncryptionTestCase> {};
TEST_P(EncryptionUtilEncryptedTest, SampleEncrypted) {
const EncryptionTestCase& test_case = GetParam();
auto sample = MediaSample::CopyFrom(kData, sizeof(kData), kKeyFrame);
sample->set_is_encrypted(true);
std::unique_ptr<DecryptConfig> decrypt_config(
new DecryptConfig(std::vector<uint8_t>(kKeyId, kKeyId + sizeof(kKeyId)),
std::vector<uint8_t>(kIv, kIv + sizeof(kIv)),
std::vector<SubsampleEntry>(
test_case.subsamples,
test_case.subsamples + test_case.num_subsamples)));
sample->set_decrypt_config(std::move(decrypt_config));
UpdateFrameForEncryption(sample.get());
ASSERT_EQ(
sizeof(kData) + sizeof(kIv) + test_case.subsample_partition_data_size + 1,
sample->data_size());
if (test_case.num_subsamples > 0)
EXPECT_EQ(kWebMEncryptedSignal | kWebMPartitionedSignal, sample->data()[0]);
else
EXPECT_EQ(kWebMEncryptedSignal, sample->data()[0]);
EXPECT_EQ(std::vector<uint8_t>(kIv, kIv + sizeof(kIv)),
std::vector<uint8_t>(sample->data() + 1,
sample->data() + 1 + sizeof(kIv)));
EXPECT_EQ(std::vector<uint8_t>(test_case.subsample_partition_data,
test_case.subsample_partition_data +
test_case.subsample_partition_data_size),
std::vector<uint8_t>(sample->data() + 1 + sizeof(kIv),
sample->data() + 1 + sizeof(kIv) +
test_case.subsample_partition_data_size));
EXPECT_EQ(std::vector<uint8_t>(kData, kData + sizeof(kData)),
std::vector<uint8_t>(sample->data() + 1 + sizeof(kIv) +
test_case.subsample_partition_data_size,
sample->data() + sample->data_size()));
}
INSTANTIATE_TEST_CASE_P(Encryption,
EncryptionUtilEncryptedTest,
testing::ValuesIn(kEncryptionTestCases));
} // namespace webm
} // namespace media
} // namespace shaka

View File

@ -102,7 +102,7 @@ class MultiSegmentSegmenterTest : public SegmentTestBase {
void InitializeSegmenter(const MuxerOptions& options) {
ASSERT_NO_FATAL_FAILURE(
CreateAndInitializeSegmenter<webm::MultiSegmentSegmenter>(
options, info_.get(), NULL, &segmenter_));
options, info_.get(), &segmenter_));
}
std::string TemplateFileName(int number) const {

View File

@ -17,10 +17,15 @@
#include "packager/media/codecs/vp_codec_configuration_record.h"
#include "packager/media/event/muxer_listener.h"
#include "packager/media/event/progress_listener.h"
#include "packager/media/formats/webm/encryptor.h"
#include "packager/media/formats/webm/webm_constants.h"
#include "packager/third_party/libwebm/src/mkvmuxerutil.hpp"
#include "packager/third_party/libwebm/src/webmids.hpp"
#include "packager/version/version.h"
using mkvmuxer::AudioTrack;
using mkvmuxer::VideoTrack;
namespace shaka {
namespace media {
namespace webm {
@ -35,15 +40,9 @@ Segmenter::~Segmenter() {}
Status Segmenter::Initialize(StreamInfo* info,
ProgressListener* progress_listener,
MuxerListener* muxer_listener,
KeySource* encryption_key_source,
uint32_t max_sd_pixels,
uint32_t max_hd_pixels,
uint32_t max_uhd1_pixels,
double clear_lead_in_seconds) {
MuxerListener* muxer_listener) {
muxer_listener_ = muxer_listener;
info_ = info;
clear_lead_ = clear_lead_in_seconds;
// Use media duration as progress target.
progress_target_ = info_->duration();
@ -65,24 +64,26 @@ Status Segmenter::Initialize(StreamInfo* info,
segment_info_.set_duration(1);
}
Status status;
if (encryption_key_source) {
status = InitializeEncryptor(encryption_key_source,
max_sd_pixels,
max_hd_pixels,
max_uhd1_pixels);
if (!status.ok())
return status;
}
// Create the track info.
// The seed is only used to create a UID which we overwrite later.
unsigned int seed = 0;
std::unique_ptr<mkvmuxer::Track> track;
Status status;
switch (info_->stream_type()) {
case kStreamVideo:
status = CreateVideoTrack(static_cast<VideoStreamInfo*>(info_));
case kStreamVideo: {
std::unique_ptr<VideoTrack> video_track(new VideoTrack(&seed));
status = InitializeVideoTrack(static_cast<VideoStreamInfo*>(info_),
video_track.get());
track = std::move(video_track);
break;
case kStreamAudio:
status = CreateAudioTrack(static_cast<AudioStreamInfo*>(info_));
}
case kStreamAudio: {
std::unique_ptr<AudioTrack> audio_track(new AudioTrack(&seed));
status = InitializeAudioTrack(static_cast<AudioStreamInfo*>(info_),
audio_track.get());
track = std::move(audio_track);
break;
}
default:
NOTIMPLEMENTED() << "Not implemented for stream type: "
<< info_->stream_type();
@ -91,6 +92,20 @@ Status Segmenter::Initialize(StreamInfo* info,
if (!status.ok())
return status;
if (info_->is_encrypted()) {
if (info->encryption_config().per_sample_iv_size != kWebMIvSize)
return Status(error::MUXER_FAILURE, "Incorrect size WebM encryption IV.");
status = UpdateTrackForEncryption(info_->encryption_config().key_id,
track.get());
if (!status.ok())
return status;
}
tracks_.AddTrack(track.get(), info_->track_id());
// number() is only available after the above instruction.
track_id_ = track->number();
// |tracks_| owns |track|.
track.release();
return DoInitialize();
}
@ -126,25 +141,8 @@ Status Segmenter::AddSample(std::shared_ptr<MediaSample> sample) {
if (!status.ok())
return status;
// Encrypt the frame.
if (encryptor_) {
// Don't enable encryption in the middle of a segment, i.e. only at the
// first frame of a segment.
if (new_segment_ && !enable_encryption_) {
if (sample->pts() - first_timestamp_ >=
clear_lead_ * info_->time_scale()) {
enable_encryption_ = true;
if (muxer_listener_)
muxer_listener_->OnEncryptionStart();
}
}
status = encryptor_->EncryptFrame(sample, enable_encryption_);
if (!status.ok()) {
LOG(ERROR) << "Error encrypting frame.";
return status;
}
}
if (info_->is_encrypted())
UpdateFrameForEncryption(sample.get());
new_subsegment_ = false;
new_segment_ = false;
@ -242,13 +240,8 @@ void Segmenter::UpdateProgress(uint64_t progress) {
}
}
Status Segmenter::CreateVideoTrack(VideoStreamInfo* info) {
// The seed is only used to create a UID which we overwrite later.
unsigned int seed = 0;
mkvmuxer::VideoTrack* track = new mkvmuxer::VideoTrack(&seed);
if (!track)
return Status(error::INTERNAL_ERROR, "Failed to create video track.");
Status Segmenter::InitializeVideoTrack(const VideoStreamInfo* info,
VideoTrack* track) {
if (info->codec() == kCodecVP8) {
track->set_codec_id(mkvmuxer::Tracks::kVp8CodecId);
} else if (info->codec() == kCodecVP9) {
@ -283,22 +276,11 @@ Status Segmenter::CreateVideoTrack(VideoStreamInfo* info) {
track->set_display_height(info->height());
track->set_display_width(info->width() * info->pixel_width() /
info->pixel_height());
if (encryptor_)
encryptor_->AddTrackInfo(track);
tracks_.AddTrack(track, info->track_id());
track_id_ = track->number();
return Status::OK;
}
Status Segmenter::CreateAudioTrack(AudioStreamInfo* info) {
// The seed is only used to create a UID which we overwrite later.
unsigned int seed = 0;
mkvmuxer::AudioTrack* track = new mkvmuxer::AudioTrack(&seed);
if (!track)
return Status(error::INTERNAL_ERROR, "Failed to create audio track.");
Status Segmenter::InitializeAudioTrack(const AudioStreamInfo* info,
AudioTrack* track) {
if (info->codec() == kCodecOpus) {
track->set_codec_id(mkvmuxer::Tracks::kOpusCodecId);
} else if (info->codec() == kCodecVorbis) {
@ -322,29 +304,9 @@ Status Segmenter::CreateAudioTrack(AudioStreamInfo* info) {
track->set_channels(info->num_channels());
track->set_seek_pre_roll(info->seek_preroll_ns());
track->set_codec_delay(info->codec_delay_ns());
if (encryptor_)
encryptor_->AddTrackInfo(track);
tracks_.AddTrack(track, info->track_id());
track_id_ = track->number();
return Status::OK;
}
Status Segmenter::InitializeEncryptor(KeySource* key_source,
uint32_t max_sd_pixels,
uint32_t max_hd_pixels,
uint32_t max_uhd1_pixels) {
encryptor_.reset(new Encryptor());
const KeySource::TrackType track_type =
GetTrackTypeForEncryption(*info_, max_sd_pixels, max_hd_pixels,
max_uhd1_pixels);
if (track_type == KeySource::TrackType::TRACK_TYPE_UNKNOWN)
return Status::OK;
return encryptor_->Initialize(muxer_listener_, track_type, info_->codec(),
key_source, options_.webm_subsample_encryption);
}
Status Segmenter::WriteFrame(bool write_duration) {
// Create a frame manually so we can create non-SimpleBlock frames. This
// is required to allow the frame duration to be added. If the duration

View File

@ -9,7 +9,6 @@
#include <memory>
#include "packager/media/base/status.h"
#include "packager/media/formats/webm/encryptor.h"
#include "packager/media/formats/webm/mkv_writer.h"
#include "packager/media/formats/webm/seek_head.h"
#include "packager/third_party/libwebm/src/mkvmuxer.hpp"
@ -20,9 +19,7 @@ namespace media {
struct MuxerOptions;
class AudioStreamInfo;
class KeySource;
class MediaSample;
class StreamInfo;
class MuxerListener;
class ProgressListener;
class StreamInfo;
@ -40,30 +37,10 @@ class Segmenter {
/// Status::OK results in an undefined behavior.
/// @param info The stream info for the stream being segmented.
/// @param muxer_listener receives muxer events. Can be NULL.
/// @param encryption_key_source points to the key source which contains
/// the encryption keys. It can be NULL to indicate that no encryption
/// is required.
/// @param max_sd_pixels specifies the threshold to determine whether a video
/// track should be considered as SD. If the max pixels per frame is
/// no higher than max_sd_pixels, it is SD.
/// @param max_hd_pixels specifies the threshold to determine whether a video
/// track should be considered as HD. If the max pixels per frame is
/// higher than max_sd_pixels, but no higher than max_hd_pixels,
/// it is HD.
/// @param max_uhd1_pixels specifies the threshold to determine whether a video
/// track should be considered as UHD1. If the max pixels per frame is
/// higher than max_hd_pixels, but no higher than max_uhd1_pixels,
/// it is UHD1. Otherwise it is UHD2.
/// @param clear_time specifies clear lead duration in seconds.
/// @return OK on success, an error status otherwise.
Status Initialize(StreamInfo* info,
ProgressListener* progress_listener,
MuxerListener* muxer_listener,
KeySource* encryption_key_source,
uint32_t max_sd_pixels,
uint32_t max_hd_pixels,
uint32_t max_uhd1_pixels,
double clear_lead_in_seconds);
MuxerListener* muxer_listener);
/// Finalize the segmenter.
/// @return OK on success, an error status otherwise.
@ -121,10 +98,10 @@ class Segmenter {
virtual Status DoFinalize() = 0;
private:
Status CreateVideoTrack(VideoStreamInfo* info);
Status CreateAudioTrack(AudioStreamInfo* info);
Status InitializeEncryptor(KeySource* key_source, uint32_t max_sd_pixels,
uint32_t max_hd_pixels, uint32_t max_uhd1_pixels);
Status InitializeAudioTrack(const AudioStreamInfo* info,
mkvmuxer::AudioTrack* track);
Status InitializeVideoTrack(const VideoStreamInfo* info,
mkvmuxer::VideoTrack* track);
// Writes the previous frame to the file.
Status WriteFrame(bool write_duration);
@ -142,10 +119,6 @@ class Segmenter {
uint64_t reference_frame_timestamp_ = 0;
const MuxerOptions& options_;
std::unique_ptr<Encryptor> encryptor_;
double clear_lead_ = 0;
// Encryption is enabled only after clear_lead_.
bool enable_encryption_ = false;
std::unique_ptr<mkvmuxer::Cluster> cluster_;
mkvmuxer::Cues cues_;

View File

@ -47,14 +47,11 @@ class SegmentTestBase : public ::testing::Test {
void CreateAndInitializeSegmenter(
const MuxerOptions& options,
StreamInfo* info,
KeySource* key_source,
std::unique_ptr<webm::Segmenter>* result) const {
std::unique_ptr<S> segmenter(new S(options));
ASSERT_OK(segmenter->Initialize(
info, NULL /* progress_listener */, NULL /* muxer_listener */,
key_source, 0 /* max_sd_pixels */, 0 /* max_hd_pixels */,
0 /* max_uhd1_pixels */, 1 /* clear_lead_in_seconds */));
ASSERT_OK(segmenter->Initialize(info, nullptr /* progress_listener */,
nullptr /* muxer_listener */));
*result = std::move(segmenter);
}

View File

@ -141,7 +141,7 @@ class SingleSegmentSegmenterTest : public SegmentTestBase {
void InitializeSegmenter(const MuxerOptions& options) {
ASSERT_NO_FATAL_FAILURE(
CreateAndInitializeSegmenter<webm::TwoPassSingleSegmentSegmenter>(
options, info_.get(), NULL, &segmenter_));
options, info_.get(), &segmenter_));
}
std::shared_ptr<StreamInfo> info_;

View File

@ -69,6 +69,7 @@
'cluster_builder.cc',
'cluster_builder.h',
'encrypted_segmenter_unittest.cc',
'encryptor_unittest.cc',
'multi_segment_segmenter_unittest.cc',
'segmenter_test_base.cc',
'segmenter_test_base.h',

View File

@ -57,8 +57,13 @@ WebMClusterParser::WebMClusterParser(
true,
video_default_duration,
new_sample_cb) {
if (decryption_key_source)
if (decryption_key_source) {
decryptor_source_.reset(new DecryptorSource(decryption_key_source));
if (audio_stream_info_)
audio_stream_info_->set_is_encrypted(false);
if (video_stream_info_)
video_stream_info_->set_is_encrypted(false);
}
for (WebMTracksParser::TextTracks::const_iterator it = text_tracks.begin();
it != text_tracks.end();
++it) {

View File

@ -24,16 +24,10 @@ WebMMuxer::~WebMMuxer() {}
Status WebMMuxer::InitializeMuxer() {
CHECK_EQ(streams().size(), 1U);
if (crypto_period_duration_in_seconds() > 0) {
NOTIMPLEMENTED() << "Key rotation is not implemented for WebM";
return Status(error::UNIMPLEMENTED,
"Key rotation is not implemented for WebM");
}
if (encryption_key_source() && (protection_scheme() != FOURCC_cenc)) {
NOTIMPLEMENTED()
<< "WebM does not support protection scheme other than 'cenc'.";
return Status(error::UNIMPLEMENTED,
if (streams()[0]->is_encrypted() &&
streams()[0]->encryption_config().protection_scheme != FOURCC_cenc) {
LOG(ERROR) << "WebM does not support protection scheme other than 'cenc'.";
return Status(error::INVALID_ARGUMENT,
"WebM does not support protection scheme other than 'cenc'.");
}
@ -44,10 +38,7 @@ Status WebMMuxer::InitializeMuxer() {
}
Status initialized = segmenter_->Initialize(
streams()[0].get(), progress_listener(), muxer_listener(),
encryption_key_source(), max_sd_pixels(), max_hd_pixels(),
max_uhd1_pixels(), clear_lead_in_seconds());
streams()[0].get(), progress_listener(), muxer_listener());
if (!initialized.ok())
return initialized;
@ -78,6 +69,12 @@ Status WebMMuxer::FinalizeSegment(size_t stream_id,
std::shared_ptr<SegmentInfo> segment_info) {
DCHECK(segmenter_);
DCHECK_EQ(stream_id, 0u);
if (segment_info->key_rotation_encryption_config) {
NOTIMPLEMENTED() << "Key rotation is not implemented for WebM.";
return Status(error::UNIMPLEMENTED,
"Key rotation is not implemented for WebM");
}
return segmenter_->FinalizeSegment(segment_info->start_timestamp,
segment_info->duration,
segment_info->is_subsegment);

View File

@ -744,7 +744,8 @@ bool WvmMediaParser::ParseIndexEntry() {
stream_id_count_, time_scale, track_duration, video_codec,
std::string(), video_codec_config.data(), video_codec_config.size(),
video_width, video_height, pixel_width, pixel_height, trick_play_rate,
nalu_length_size, std::string(), true));
nalu_length_size, std::string(),
decryption_key_source_ ? false : true));
program_demux_stream_map_[base::UintToString(index_program_id_) + ":" +
base::UintToString(
video_pes_stream_id
@ -760,7 +761,8 @@ bool WvmMediaParser::ParseIndexEntry() {
std::string(), audio_codec_config.data(), audio_codec_config.size(),
kAacSampleSizeBits, num_channels, sampling_frequency,
0 /* seek preroll */, 0 /* codec delay */, 0 /* max bitrate */,
0 /* avg bitrate */, std::string(), true));
0 /* avg bitrate */, std::string(),
decryption_key_source_ ? false : true));
program_demux_stream_map_[base::UintToString(index_program_id_) + ":" +
base::UintToString(
audio_pes_stream_id

View File

@ -18,6 +18,7 @@
#include "packager/media/base/stream_info.h"
#include "packager/media/base/test/status_test_util.h"
#include "packager/media/chunking/chunking_handler.h"
#include "packager/media/crypto/encryption_handler.h"
#include "packager/media/formats/mp4/mp4_muxer.h"
#include "packager/media/test/test_data_util.h"
@ -91,6 +92,7 @@ class PackagerTestBasic : public ::testing::TestWithParam<const char*> {
bool ContentsEqual(const std::string& file1, const std::string file2);
ChunkingOptions SetupChunkingOptions();
EncryptionOptions SetupEncryptionOptions();
MuxerOptions SetupMuxerOptions(const std::string& output,
bool single_segment);
void Remux(const std::string& input,
@ -139,6 +141,17 @@ ChunkingOptions PackagerTestBasic::SetupChunkingOptions() {
return options;
}
EncryptionOptions PackagerTestBasic::SetupEncryptionOptions() {
EncryptionOptions options;
options.clear_lead_in_seconds = kClearLeadInSeconds;
options.protection_scheme = FOURCC_cenc;
options.max_sd_pixels = kMaxSDPixels;
options.max_hd_pixels = kMaxHDPixels;
options.max_uhd1_pixels = kMaxUHD1Pixels;
options.crypto_period_duration_in_seconds = kCryptoDurationInSeconds;
return options;
}
void PackagerTestBasic::Remux(const std::string& input,
const std::string& video_output,
const std::string& audio_output,
@ -157,18 +170,18 @@ void PackagerTestBasic::Remux(const std::string& input,
new mp4::MP4Muxer(SetupMuxerOptions(video_output, single_segment)));
muxer_video->set_clock(&fake_clock_);
if (enable_encryption) {
muxer_video->SetKeySource(encryption_key_source.get(),
kMaxSDPixels, kMaxHDPixels,
kMaxUHD1Pixels, kClearLeadInSeconds,
kCryptoDurationInSeconds, FOURCC_cenc);
}
auto chunking_handler =
std::make_shared<ChunkingHandler>(SetupChunkingOptions());
ASSERT_OK(demuxer.SetHandler("video", chunking_handler));
if (enable_encryption) {
auto encryption_handler = std::make_shared<EncryptionHandler>(
SetupEncryptionOptions(), encryption_key_source.get());
ASSERT_OK(chunking_handler->SetHandler(0, encryption_handler));
ASSERT_OK(encryption_handler->SetHandler(0, muxer_video));
} else {
ASSERT_OK(chunking_handler->SetHandler(0, muxer_video));
}
}
std::shared_ptr<Muxer> muxer_audio;
if (!audio_output.empty()) {
@ -176,18 +189,18 @@ void PackagerTestBasic::Remux(const std::string& input,
new mp4::MP4Muxer(SetupMuxerOptions(audio_output, single_segment)));
muxer_audio->set_clock(&fake_clock_);
if (enable_encryption) {
muxer_audio->SetKeySource(encryption_key_source.get(),
kMaxSDPixels, kMaxHDPixels,
kMaxUHD1Pixels, kClearLeadInSeconds,
kCryptoDurationInSeconds, FOURCC_cenc);
}
auto chunking_handler =
std::make_shared<ChunkingHandler>(SetupChunkingOptions());
ASSERT_OK(demuxer.SetHandler("audio", chunking_handler));
if (enable_encryption) {
auto encryption_handler = std::make_shared<EncryptionHandler>(
SetupEncryptionOptions(), encryption_key_source.get());
ASSERT_OK(chunking_handler->SetHandler(0, encryption_handler));
ASSERT_OK(encryption_handler->SetHandler(0, muxer_audio));
} else {
ASSERT_OK(chunking_handler->SetHandler(0, muxer_audio));
}
}
ASSERT_OK(demuxer.Initialize());
// Start remuxing process.