From 3d7635d5de9b1627f68adfe08a711bdda71474b4 Mon Sep 17 00:00:00 2001 From: KongQun Yang Date: Fri, 10 Mar 2017 18:49:55 -0800 Subject: [PATCH] Integrate EncryptionHandler Change-Id: I639e5707994a6601ee02078cd6f77cb38b481585 --- packager/app/packager_main.cc | 68 ++- packager/app/packager_util.cc | 32 ++ packager/app/packager_util.h | 4 + packager/media/base/muxer.cc | 60 +-- packager/media/base/muxer.h | 53 +- .../nal_unit_to_byte_stream_converter.cc | 1 - packager/media/formats/mp2t/mp2t.gyp | 1 + .../formats/mp2t/pes_packet_generator.cc | 102 +--- .../media/formats/mp2t/pes_packet_generator.h | 11 - .../mp2t/pes_packet_generator_unittest.cc | 484 +++--------------- packager/media/formats/mp2t/ts_muxer.cc | 9 +- packager/media/formats/mp2t/ts_segmenter.cc | 54 +- packager/media/formats/mp2t/ts_segmenter.h | 19 +- .../formats/mp2t/ts_segmenter_unittest.cc | 131 +---- .../formats/mp4/encrypting_fragmenter.cc | 380 -------------- .../media/formats/mp4/encrypting_fragmenter.h | 111 ---- packager/media/formats/mp4/fragmenter.cc | 95 +++- packager/media/formats/mp4/fragmenter.h | 10 +- .../formats/mp4/key_rotation_fragmenter.cc | 138 ----- .../formats/mp4/key_rotation_fragmenter.h | 81 --- packager/media/formats/mp4/mp4.gyp | 4 - .../media/formats/mp4/mp4_media_parser.cc | 10 +- packager/media/formats/mp4/mp4_muxer.cc | 59 ++- packager/media/formats/mp4/segmenter.cc | 254 ++------- packager/media/formats/mp4/segmenter.h | 38 +- .../webm/encrypted_segmenter_unittest.cc | 46 +- packager/media/formats/webm/encryptor.cc | 161 ++---- packager/media/formats/webm/encryptor.h | 55 +- .../media/formats/webm/encryptor_unittest.cc | 158 ++++++ .../webm/multi_segment_segmenter_unittest.cc | 2 +- packager/media/formats/webm/segmenter.cc | 122 ++--- packager/media/formats/webm/segmenter.h | 37 +- .../media/formats/webm/segmenter_test_base.h | 7 +- .../webm/single_segment_segmenter_unittest.cc | 2 +- packager/media/formats/webm/webm.gyp | 1 + .../media/formats/webm/webm_cluster_parser.cc | 7 +- packager/media/formats/webm/webm_muxer.cc | 25 +- .../media/formats/wvm/wvm_media_parser.cc | 6 +- packager/media/test/packager_test.cc | 45 +- 39 files changed, 753 insertions(+), 2130 deletions(-) delete mode 100644 packager/media/formats/mp4/encrypting_fragmenter.cc delete mode 100644 packager/media/formats/mp4/encrypting_fragmenter.h delete mode 100644 packager/media/formats/mp4/key_rotation_fragmenter.cc delete mode 100644 packager/media/formats/mp4/key_rotation_fragmenter.h create mode 100644 packager/media/formats/webm/encryptor_unittest.cc diff --git a/packager/app/packager_main.cc b/packager/app/packager_main.cc index 1104820d50..05834a5947 100644 --- a/packager/app/packager_main.cc +++ b/packager/app/packager_main.cc @@ -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 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>* 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 key_source(CreateDecryptionKeySource()); - if (!key_source) + std::unique_ptr 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 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(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( + 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>& 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 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> 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; } diff --git a/packager/app/packager_util.cc b/packager/app/packager_util.cc index d7b02c7dfa..f82d4dc547 100644 --- a/packager/app/packager_util.cc +++ b/packager/app/packager_util.cc @@ -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 CreateSigner() { std::unique_ptr 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; diff --git a/packager/app/packager_util.h b/packager/app/packager_util.h index efd3b53359..a6c17a4d6e 100644 --- a/packager/app/packager_util.h +++ b/packager/app/packager_util.h @@ -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 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(); diff --git a/packager/media/base/muxer.cc b/packager/media/base/muxer.cc index 87edbf79ee..1139bb0df7 100644 --- a/packager/media/base/muxer.cc +++ b/packager/media/base/muxer.cc @@ -8,43 +8,19 @@ #include -#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 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)); diff --git a/packager/media/base/muxer.h b/packager/media/base/muxer.h index 347a52fd3c..feb1f90f0b 100644 --- a/packager/media/base/muxer.h +++ b/packager/media/base/muxer.h @@ -13,7 +13,6 @@ #include #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> 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 muxer_listener_; diff --git a/packager/media/codecs/nal_unit_to_byte_stream_converter.cc b/packager/media/codecs/nal_unit_to_byte_stream_converter.cc index d33a7ce463..a2cdc37cd4 100644 --- a/packager/media/codecs/nal_unit_to_byte_stream_converter.cc +++ b/packager/media/codecs/nal_unit_to_byte_stream_converter.cc @@ -164,7 +164,6 @@ bool NalUnitToByteStreamConverter::ConvertUnitToByteStream( size_t sample_size, bool is_key_frame, std::vector* output) { - LOG(INFO) << "ConvertUnitToByte"; return ConvertUnitToByteStreamWithSubsamples( sample, sample_size, is_key_frame, false, output, nullptr); // Skip subsample update. diff --git a/packager/media/formats/mp2t/mp2t.gyp b/packager/media/formats/mp2t/mp2t.gyp index ffa8a4dd24..71bca4d8e2 100644 --- a/packager/media/formats/mp2t/mp2t.gyp +++ b/packager/media/formats/mp2t/mp2t.gyp @@ -55,6 +55,7 @@ ], 'dependencies': [ '../../base/media_base.gyp:media_base', + '../../crypto/crypto.gyp:crypto', '../../codecs/codecs.gyp:codecs', ], }, diff --git a/packager/media/formats/mp2t/pes_packet_generator.cc b/packager/media/formats/mp2t/pes_packet_generator.cc index e14ad42ea6..ca74e1417a 100644 --- a/packager/media/formats/mp2t/pes_packet_generator.cc +++ b/packager/media/formats/mp2t/pes_packet_generator.cc @@ -10,8 +10,6 @@ #include #include -#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* 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(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(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* 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 sample) { current_processing_pes_->set_dts(timescale_scale_ * sample->dts()); if (stream_type_ == kStreamVideo) { DCHECK(converter_); + std::vector subsamples; + if (sample->decrypt_config()) + subsamples = sample->decrypt_config()->subsamples(); + const bool kEscapeEncryptedNalu = true; std::vector 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 sample) { std::vector 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 sample) { return true; } -bool PesPacketGenerator::SetEncryptionKey( - std::unique_ptr encryption_key) { - if (stream_type_ == kStreamVideo) { - std::unique_ptr 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(); } diff --git a/packager/media/formats/mp2t/pes_packet_generator.h b/packager/media/formats/mp2t/pes_packet_generator.h index ff23cd85a2..e84b6356eb 100644 --- a/packager/media/formats/mp2t/pes_packet_generator.h +++ b/packager/media/formats/mp2t/pes_packet_generator.h @@ -10,8 +10,6 @@ #include #include -#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 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 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> pes_packets_; - // Current encryption key. - std::unique_ptr encryptor_; - DISALLOW_COPY_AND_ASSIGN(PesPacketGenerator); }; diff --git a/packager/media/formats/mp2t/pes_packet_generator_unittest.cc b/packager/media/formats/mp2t/pes_packet_generator_unittest.cc index 3d54c36d17..6c02b86532 100644 --- a/packager/media/formats/mp2t/pes_packet_generator_unittest.cc +++ b/packager/media/formats/mp2t/pes_packet_generator_unittest.cc @@ -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* output)); + bool escape_encrypted_nalu, + std::vector* output, + std::vector* 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 stream_info( - CreateVideoStreamInfo(kH264Codec)); - EXPECT_TRUE(generator_.Initialize(*stream_info)); - EXPECT_EQ(0u, generator_.NumberOfReadyPesPackets()); - - std::shared_ptr 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 mock( - new MockNalUnitToByteStreamConverter()); - - // Returning only the input data so that it doesn't have all the unnecessary - // NALUs to test encryption. - std::vector 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 all_zero(16, 0); - std::unique_ptr 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 pes_packet = generator_.GetNextPesPacket(); - ASSERT_TRUE(pes_packet); - - std::vector 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 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 sample = - MediaSample::CopyFrom(input, input_size, kIsKeyFrame); - - std::unique_ptr mock( - new MockAACAudioSpecificConfig()); - EXPECT_CALL(*mock, ConvertToADTS(_)).WillOnce(Return(true)); - - UseMockAACAudioSpecificConfig(std::move(mock)); - - const std::vector all_zero(16, 0); - std::unique_ptr 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 pes_packet = generator_.GetNextPesPacket(); - ASSERT_TRUE(pes_packet); - - std::vector 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 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 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 stream_info( + CreateVideoStreamInfo(kH264Codec)); + EXPECT_TRUE(generator_.Initialize(*stream_info)); + EXPECT_EQ(0u, generator_.NumberOfReadyPesPackets()); + + std::shared_ptr 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 key_id(16, 0); + const std::vector iv(8, 0); + const std::vector subsamples = { + SubsampleEntry(0x12, 0x110), SubsampleEntry(0x122, 0x11000)}; + std::unique_ptr decrypt_config( + new DecryptConfig(key_id, iv, subsamples)); + sample->set_is_encrypted(true); + sample->set_decrypt_config(std::move(decrypt_config)); + + std::vector expected_data(kAnyData, kAnyData + arraysize(kAnyData)); + + std::unique_ptr 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 expected_data(kAnyData, kAnyData + arraysize(kAnyData)); std::unique_ptr 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 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 diff --git a/packager/media/formats/mp2t/ts_muxer.cc b/packager/media/formats/mp2t/ts_muxer.cc index 616448150a..cc1874c082 100644 --- a/packager/media/formats/mp2t/ts_muxer.cc +++ b/packager/media/formats/mp2t/ts_muxer.cc @@ -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 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, diff --git a/packager/media/formats/mp2t/ts_segmenter.cc b/packager/media/formats/mp2t/ts_segmenter.cc index b6fe894080..0a36e84306 100644 --- a/packager/media/formats/mp2t/ts_segmenter.cc +++ b/packager/media/formats/mp2t/ts_segmenter.cc @@ -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 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 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; } diff --git a/packager/media/formats/mp2t/ts_segmenter.h b/packager/media/formats/mp2t/ts_segmenter.h index 347207585f..468d5cafa0 100644 --- a/packager/media/formats/mp2t/ts_segmenter.h +++ b/packager/media/formats/mp2t/ts_segmenter.h @@ -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 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); }; diff --git a/packager/media/formats/mp2t/ts_segmenter_unittest.cc b/packager/media/formats/mp2t/ts_segmenter_unittest.cc index 3b5d3bf45a..21c134f667 100644 --- a/packager/media/formats/mp2t/ts_segmenter_unittest.cc +++ b/packager/media/formats/mp2t/ts_segmenter_unittest.cc @@ -8,7 +8,6 @@ #include #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 sample)); - MOCK_METHOD1(SetEncryptionKeyMock, bool(EncryptionKey* encryption_key)); - bool SetEncryptionKey( - std::unique_ptr 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 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 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 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)); } diff --git a/packager/media/formats/mp4/encrypting_fragmenter.cc b/packager/media/formats/mp4/encrypting_fragmenter.cc deleted file mode 100644 index 351e8156f3..0000000000 --- a/packager/media/formats/mp4/encrypting_fragmenter.cc +++ /dev/null @@ -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 - -#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* subsamples) { - CHECK_LT(cipher_bytes, std::numeric_limits::max()); - const uint64_t kUInt16Max = std::numeric_limits::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(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(stream_info); - return video_stream_info.nalu_length_size(); -} -} // namespace - -EncryptingFragmenter::EncryptingFragmenter( - std::shared_ptr info, - TrackFragment* traf, - std::unique_ptr 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 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(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(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(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 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(new AesCtrEncryptor()))); - break; - case FOURCC_cbcs: - encryptor.reset(new AesPatternCryptor( - crypt_byte_block(), skip_byte_block(), - AesPatternCryptor::kEncryptIfCryptByteBlockRemaining, - AesCryptor::kUseConstantIv, - std::unique_ptr(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 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 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(frame.uncompressed_header_size); - subsample.cipher_bytes = static_cast( - 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(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(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 diff --git a/packager/media/formats/mp4/encrypting_fragmenter.h b/packager/media/formats/mp4/encrypting_fragmenter.h deleted file mode 100644 index f3a6a7cdc5..0000000000 --- a/packager/media/formats/mp4/encrypting_fragmenter.h +++ /dev/null @@ -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 -#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 info, - TrackFragment* traf, - std::unique_ptr 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 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 encryption_key) { - encryption_key_ = std::move(encryption_key); - } - - private: - void EncryptBytes(uint8_t* data, size_t size); - Status EncryptSample(std::shared_ptr sample); - - // Should we enable subsample encryption? - bool IsSubsampleEncryptionRequired(); - - std::shared_ptr info_; - std::unique_ptr encryption_key_; - std::unique_ptr 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 vpx_parser_; - std::unique_ptr header_parser_; - - DISALLOW_COPY_AND_ASSIGN(EncryptingFragmenter); -}; - -} // namespace mp4 -} // namespace media -} // namespace shaka - -#endif // MEDIA_FORMATS_MP4_ENCRYPTING_FRAGMENTER_H_ diff --git a/packager/media/formats/mp4/fragmenter.cc b/packager/media/formats/mp4/fragmenter.cc index 784b946d67..94675c2be2 100644 --- a/packager/media/formats/mp4/fragmenter.cc +++ b/packager/media/formats/mp4/fragmenter.cc @@ -27,17 +27,35 @@ uint64_t GetSeekPreroll(const StreamInfo& stream_info) { static_cast(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 info, TrackFragment* traf) - : use_decoding_timestamp_in_timeline_(false), +Fragmenter::Fragmenter(std::shared_ptr 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 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(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(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; diff --git a/packager/media/formats/mp4/fragmenter.h b/packager/media/formats/mp4/fragmenter.h index 7c38b67a63..d1eef2786a 100644 --- a/packager/media/formats/mp4/fragmenter.h +++ b/packager/media/formats/mp4/fragmenter.h @@ -33,21 +33,21 @@ class Fragmenter { /// @param traf points to a TrackFragment box. Fragmenter(std::shared_ptr 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 sample); + Status AddSample(std::shared_ptr 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* entries, T* default_value); private: + Status FinalizeFragmentForEncryption(); // Check if the current fragment starts with SAP. bool StartsWithSAP(); + std::shared_ptr stream_info_; bool use_decoding_timestamp_in_timeline_; TrackFragment* traf_; uint64_t seek_preroll_; diff --git a/packager/media/formats/mp4/key_rotation_fragmenter.cc b/packager/media/formats/mp4/key_rotation_fragmenter.cc deleted file mode 100644 index 43fe30640f..0000000000 --- a/packager/media/formats/mp4/key_rotation_fragmenter.cc +++ /dev/null @@ -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 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(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 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& 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(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 diff --git a/packager/media/formats/mp4/key_rotation_fragmenter.h b/packager/media/formats/mp4/key_rotation_fragmenter.h deleted file mode 100644 index 3dca1442ac..0000000000 --- a/packager/media/formats/mp4/key_rotation_fragmenter.h +++ /dev/null @@ -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 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_ diff --git a/packager/media/formats/mp4/mp4.gyp b/packager/media/formats/mp4/mp4.gyp index 2ebc5f2636..d8d2ab32b8 100644 --- a/packager/media/formats/mp4/mp4.gyp +++ b/packager/media/formats/mp4/mp4.gyp @@ -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', diff --git a/packager/media/formats/mp4/mp4_media_parser.cc b/packager/media/formats/mp4/mp4_media_parser.cc index 721cb51037..005a03a3fc 100644 --- a/packager/media/formats/mp4/mp4_media_parser.cc +++ b/packager/media/formats/mp4/mp4_media_parser.cc @@ -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 video_stream_info(new VideoStreamInfo( track->header.track_id, timescale, duration, video_codec, diff --git a/packager/media/formats/mp4/mp4_muxer.cc b/packager/media/formats/mp4/mp4_muxer.cc index 1b1b6c5961..00e011e791 100644 --- a/packager/media/formats/mp4/mp4_muxer.cc +++ b/packager/media/formats/mp4/mp4_muxer.cc @@ -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) { diff --git a/packager/media/formats/mp4/segmenter.cc b/packager/media/formats/mp4/segmenter.cc index d7b8894ba2..f76d166073 100644 --- a/packager/media/formats/mp4/segmenter.cc +++ b/packager/media/formats/mp4/segmenter.cc @@ -9,17 +9,15 @@ #include #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(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(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>& 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 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_)); + fragmenters_[i].reset(new Fragmenter(streams[i], &moof_->tracks[i])); } 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 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 : fragmenters_) { @@ -387,7 +206,7 @@ Status Segmenter::FinalizeSegment(size_t stream_id, bool is_subsegment) { for (std::unique_ptr& 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& 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 diff --git a/packager/media/formats/mp4/segmenter.h b/packager/media/formats/mp4/segmenter.h index cf416485af..27c7459811 100644 --- a/packager/media/formats/mp4/segmenter.h +++ b/packager/media/formats/mp4/segmenter.h @@ -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>& 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 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 ftyp_; std::unique_ptr moov_; diff --git a/packager/media/formats/webm/encrypted_segmenter_unittest.cc b/packager/media/formats/webm/encrypted_segmenter_unittest.cc index 01b2bfaff4..51eb9f1a43 100644 --- a/packager/media/formats/webm/encrypted_segmenter_unittest.cc +++ b/packager/media/formats/webm/encrypted_segmenter_unittest.cc @@ -6,19 +6,22 @@ #include #include -#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( - options, info_.get(), key_source_.get(), &segmenter_)); + options, info_.get(), &segmenter_)); } std::shared_ptr info_; std::unique_ptr segmenter_; - std::unique_ptr 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 sample = CreateSample(kKeyFrame, kDuration, kNoSideData); + if (i >= 3) { + sample->set_is_encrypted(true); + std::unique_ptr decrypt_config( + new DecryptConfig(info_->encryption_config().key_id, + std::vector(kIv, kIv + sizeof(kIv)), + std::vector())); + sample->set_decrypt_config(std::move(decrypt_config)); + } ASSERT_OK(segmenter_->AddSample(sample)); } ASSERT_OK( diff --git a/packager/media/formats/webm/encryptor.cc b/packager/media/formats/webm/encryptor.cc index fc146faec9..ae160d53b8 100644 --- a/packager/media/formats/webm/encryptor.cc +++ b/packager/media/formats/webm/encryptor.cc @@ -6,22 +6,18 @@ #include "packager/media/formats/webm/encryptor.h" -#include -#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& 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(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 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 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 + - kWebMNumPartitionsSize + - (kWebMPartitionOffsetSize * num_partitions); + 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(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( - vpx_frame.frame_size - vpx_frame.uncompressed_header_size); - encrypted_size -= encrypted_size % kAesBlockSize; - uint32_t clear_size = - static_cast(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); + 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); - 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) { - offsets_buffer.AppendInt(partition_offset); - } } DCHECK_EQ(num_partitions * kWebMPartitionOffsetSize, offsets_buffer.Size()); @@ -143,23 +92,16 @@ Status Encryptor::EncryptFrame(std::shared_ptr 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 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 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 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 diff --git a/packager/media/formats/webm/encryptor.h b/packager/media/formats/webm/encryptor.h index 5c873e2915..b0c5400ee0 100644 --- a/packager/media/formats/webm/encryptor.h +++ b/packager/media/formats/webm/encryptor.h @@ -7,63 +7,26 @@ #ifndef MEDIA_FORMATS_WEBM_ENCRYPTOR_H_ #define MEDIA_FORMATS_WEBM_ENCRYPTOR_H_ -#include -#include "packager/base/macros.h" -#include "packager/media/base/key_source.h" +#include + #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& 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 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 key_; - std::unique_ptr encryptor_; - std::unique_ptr 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 diff --git a/packager/media/formats/webm/encryptor_unittest.cc b/packager/media/formats/webm/encryptor_unittest.cc new file mode 100644 index 0000000000..24c23770b7 --- /dev/null +++ b/packager/media/formats/webm/encryptor_unittest.cc @@ -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 +#include +#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(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(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(kData, kData + sizeof(kData)), + std::vector(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 {}; + +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 decrypt_config( + new DecryptConfig(std::vector(kKeyId, kKeyId + sizeof(kKeyId)), + std::vector(kIv, kIv + sizeof(kIv)), + std::vector( + 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(kIv, kIv + sizeof(kIv)), + std::vector(sample->data() + 1, + sample->data() + 1 + sizeof(kIv))); + EXPECT_EQ(std::vector(test_case.subsample_partition_data, + test_case.subsample_partition_data + + test_case.subsample_partition_data_size), + std::vector(sample->data() + 1 + sizeof(kIv), + sample->data() + 1 + sizeof(kIv) + + test_case.subsample_partition_data_size)); + EXPECT_EQ(std::vector(kData, kData + sizeof(kData)), + std::vector(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 diff --git a/packager/media/formats/webm/multi_segment_segmenter_unittest.cc b/packager/media/formats/webm/multi_segment_segmenter_unittest.cc index eb93f2e983..c04094c680 100644 --- a/packager/media/formats/webm/multi_segment_segmenter_unittest.cc +++ b/packager/media/formats/webm/multi_segment_segmenter_unittest.cc @@ -102,7 +102,7 @@ class MultiSegmentSegmenterTest : public SegmentTestBase { void InitializeSegmenter(const MuxerOptions& options) { ASSERT_NO_FATAL_FAILURE( CreateAndInitializeSegmenter( - options, info_.get(), NULL, &segmenter_)); + options, info_.get(), &segmenter_)); } std::string TemplateFileName(int number) const { diff --git a/packager/media/formats/webm/segmenter.cc b/packager/media/formats/webm/segmenter.cc index c9057bb42b..5b7721aae3 100644 --- a/packager/media/formats/webm/segmenter.cc +++ b/packager/media/formats/webm/segmenter.cc @@ -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 track; + Status status; switch (info_->stream_type()) { - case kStreamVideo: - status = CreateVideoTrack(static_cast(info_)); + case kStreamVideo: { + std::unique_ptr video_track(new VideoTrack(&seed)); + status = InitializeVideoTrack(static_cast(info_), + video_track.get()); + track = std::move(video_track); break; - case kStreamAudio: - status = CreateAudioTrack(static_cast(info_)); + } + case kStreamAudio: { + std::unique_ptr audio_track(new AudioTrack(&seed)); + status = InitializeAudioTrack(static_cast(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 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 diff --git a/packager/media/formats/webm/segmenter.h b/packager/media/formats/webm/segmenter.h index 27137e78bc..be63eb0647 100644 --- a/packager/media/formats/webm/segmenter.h +++ b/packager/media/formats/webm/segmenter.h @@ -9,7 +9,6 @@ #include #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_; - double clear_lead_ = 0; - // Encryption is enabled only after clear_lead_. - bool enable_encryption_ = false; std::unique_ptr cluster_; mkvmuxer::Cues cues_; diff --git a/packager/media/formats/webm/segmenter_test_base.h b/packager/media/formats/webm/segmenter_test_base.h index 36aec58921..71fde6ad11 100644 --- a/packager/media/formats/webm/segmenter_test_base.h +++ b/packager/media/formats/webm/segmenter_test_base.h @@ -47,14 +47,11 @@ class SegmentTestBase : public ::testing::Test { void CreateAndInitializeSegmenter( const MuxerOptions& options, StreamInfo* info, - KeySource* key_source, std::unique_ptr* result) const { std::unique_ptr 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); } diff --git a/packager/media/formats/webm/single_segment_segmenter_unittest.cc b/packager/media/formats/webm/single_segment_segmenter_unittest.cc index d8e2456bd7..544004d1d8 100644 --- a/packager/media/formats/webm/single_segment_segmenter_unittest.cc +++ b/packager/media/formats/webm/single_segment_segmenter_unittest.cc @@ -141,7 +141,7 @@ class SingleSegmentSegmenterTest : public SegmentTestBase { void InitializeSegmenter(const MuxerOptions& options) { ASSERT_NO_FATAL_FAILURE( CreateAndInitializeSegmenter( - options, info_.get(), NULL, &segmenter_)); + options, info_.get(), &segmenter_)); } std::shared_ptr info_; diff --git a/packager/media/formats/webm/webm.gyp b/packager/media/formats/webm/webm.gyp index 1aeb63c9ec..db7989099d 100644 --- a/packager/media/formats/webm/webm.gyp +++ b/packager/media/formats/webm/webm.gyp @@ -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', diff --git a/packager/media/formats/webm/webm_cluster_parser.cc b/packager/media/formats/webm/webm_cluster_parser.cc index 49b00dd725..9126435488 100644 --- a/packager/media/formats/webm/webm_cluster_parser.cc +++ b/packager/media/formats/webm/webm_cluster_parser.cc @@ -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) { diff --git a/packager/media/formats/webm/webm_muxer.cc b/packager/media/formats/webm/webm_muxer.cc index 82ab685290..fa626cf0a6 100644 --- a/packager/media/formats/webm/webm_muxer.cc +++ b/packager/media/formats/webm/webm_muxer.cc @@ -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 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); diff --git a/packager/media/formats/wvm/wvm_media_parser.cc b/packager/media/formats/wvm/wvm_media_parser.cc index 5351338251..31d58b8f10 100644 --- a/packager/media/formats/wvm/wvm_media_parser.cc +++ b/packager/media/formats/wvm/wvm_media_parser.cc @@ -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 diff --git a/packager/media/test/packager_test.cc b/packager/media/test/packager_test.cc index 1a6faa7de8..ed428839eb 100644 --- a/packager/media/test/packager_test.cc +++ b/packager/media/test/packager_test.cc @@ -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 { 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,17 +170,17 @@ 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(SetupChunkingOptions()); ASSERT_OK(demuxer.SetHandler("video", chunking_handler)); - ASSERT_OK(chunking_handler->SetHandler(0, muxer_video)); + if (enable_encryption) { + auto encryption_handler = std::make_shared( + 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_audio; @@ -176,17 +189,17 @@ 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(SetupChunkingOptions()); ASSERT_OK(demuxer.SetHandler("audio", chunking_handler)); - ASSERT_OK(chunking_handler->SetHandler(0, muxer_audio)); + if (enable_encryption) { + auto encryption_handler = std::make_shared( + 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());