From 50ed02675120aa07acab19d3adeba5cf140a614d Mon Sep 17 00:00:00 2001 From: Kongqun Yang Date: Fri, 18 Apr 2014 11:49:49 -0700 Subject: [PATCH] Support key rotation when generating MP4 fragments Change-Id: I472e03a2d41ee450c12c0fe3012904628d6893e7 --- app/packager_main.cc | 6 +- app/widevine_encryption_flags.h | 4 + media/base/encryption_key_source.cc | 7 + media/base/encryption_key_source.h | 6 + media/base/muxer.cc | 8 +- media/base/muxer.h | 12 +- media/formats/mp4/fragmenter.cc | 141 ++++++++++++------- media/formats/mp4/fragmenter.h | 62 +++++--- media/formats/mp4/key_rotation_fragmenter.cc | 100 +++++++++++++ media/formats/mp4/key_rotation_fragmenter.h | 63 +++++++++ media/formats/mp4/mp4.gyp | 2 + media/formats/mp4/mp4_muxer.cc | 3 +- media/formats/mp4/segmenter.cc | 124 +++++++++------- media/formats/mp4/segmenter.h | 7 +- media/test/packager_test.cc | 7 +- 15 files changed, 424 insertions(+), 128 deletions(-) create mode 100644 media/formats/mp4/key_rotation_fragmenter.cc create mode 100644 media/formats/mp4/key_rotation_fragmenter.h diff --git a/app/packager_main.cc b/app/packager_main.cc index 3b753b2bd4..bb7b22fca7 100644 --- a/app/packager_main.cc +++ b/app/packager_main.cc @@ -219,8 +219,10 @@ bool RunPackager(const std::string& input) { LOG(ERROR) << "FLAGS_track_type should be either 'SD' or 'HD'"; return false; } - muxer->SetEncryptionKeySource( - encryption_key_source.get(), track_type, FLAGS_clear_lead); + muxer->SetEncryptionKeySource(encryption_key_source.get(), + track_type, + FLAGS_clear_lead, + FLAGS_crypto_period_duration); // Start remuxing process. status = demuxer.Run(); diff --git a/app/widevine_encryption_flags.h b/app/widevine_encryption_flags.h index 8590b680f1..1c269bb983 100644 --- a/app/widevine_encryption_flags.h +++ b/app/widevine_encryption_flags.h @@ -33,6 +33,10 @@ DEFINE_string(rsa_signing_key_path, "", "Stores PKCS#1 RSA private key for request signing. Exclusive " "with --aes_signing_key."); +DEFINE_int32(crypto_period_duration, + 0, + "Crypto period duration in seconds. If it is non-zero, key " + "rotation is enabled."); static bool IsNotEmptyWithWidevineEncryption(const char* flag_name, const std::string& flag_value) { diff --git a/media/base/encryption_key_source.cc b/media/base/encryption_key_source.cc index 864086b4ff..4404fc8df0 100644 --- a/media/base/encryption_key_source.cc +++ b/media/base/encryption_key_source.cc @@ -30,6 +30,13 @@ Status EncryptionKeySource::GetKey(TrackType track_type, EncryptionKey* key) { return Status::OK; } +Status EncryptionKeySource::GetCryptoPeriodKey(size_t crypto_period_index, + TrackType track_type, + EncryptionKey* key) { + NOTIMPLEMENTED(); + return Status(error::UNIMPLEMENTED, ""); +} + scoped_ptr EncryptionKeySource::CreateFromHexStrings( const std::string& key_id_hex, const std::string& key_hex, diff --git a/media/base/encryption_key_source.h b/media/base/encryption_key_source.h index 2d38b618c3..79486f0bc8 100644 --- a/media/base/encryption_key_source.h +++ b/media/base/encryption_key_source.h @@ -41,6 +41,12 @@ class EncryptionKeySource { /// @return OK on success, an error status otherwise. virtual Status GetKey(TrackType track_type, EncryptionKey* key); + /// Get encryption key of the specified track type at the specified index. + /// @return OK on success, an error status otherwise. + virtual Status GetCryptoPeriodKey(size_t crypto_period_index, + TrackType track_type, + EncryptionKey* key); + /// Create EncryptionKeySource object from hex strings. /// @param key_id_hex is the key id in hex string. /// @param key_hex is the key in hex string. diff --git a/media/base/muxer.cc b/media/base/muxer.cc index e7ae0464b3..4f18f1add9 100644 --- a/media/base/muxer.cc +++ b/media/base/muxer.cc @@ -14,10 +14,11 @@ namespace media { Muxer::Muxer(const MuxerOptions& options) : options_(options), - encryption_key_source_(NULL), initialized_(false), + encryption_key_source_(NULL), track_type_(EncryptionKeySource::TRACK_TYPE_SD), clear_lead_in_seconds_(0), + crypto_period_duration_in_seconds_(0), muxer_listener_(NULL), clock_(NULL) {} @@ -25,10 +26,13 @@ Muxer::~Muxer() {} void Muxer::SetEncryptionKeySource(EncryptionKeySource* encryption_key_source, EncryptionKeySource::TrackType track_type, - double clear_lead_in_seconds) { + double clear_lead_in_seconds, + double crypto_period_duration_in_seconds) { + DCHECK(encryption_key_source); encryption_key_source_ = encryption_key_source; track_type_ = track_type; clear_lead_in_seconds_ = clear_lead_in_seconds; + crypto_period_duration_in_seconds_ = crypto_period_duration_in_seconds; } void Muxer::AddStream(MediaStream* stream) { diff --git a/media/base/muxer.h b/media/base/muxer.h index 1863ac21c2..1a2093ecc0 100644 --- a/media/base/muxer.h +++ b/media/base/muxer.h @@ -45,9 +45,13 @@ class Muxer { /// @param track_type should be either SD or HD. It affects whether SD key or /// HD key is used to encrypt the video content. /// @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. void SetEncryptionKeySource(EncryptionKeySource* encryption_key_source, EncryptionKeySource::TrackType track_type, - double clear_lead_in_seconds); + double clear_lead_in_seconds, + double crypto_period_duration_in_seconds); /// Add video/audio stream. void AddStream(MediaStream* stream); @@ -78,6 +82,9 @@ class Muxer { } EncryptionKeySource::TrackType track_type() const { return track_type_; } double clear_lead_in_seconds() const { return clear_lead_in_seconds_; } + double crypto_period_duration_in_seconds() const { + return crypto_period_duration_in_seconds_; + } event::MuxerListener* muxer_listener() { return muxer_listener_; } base::Clock* clock() { return clock_; } @@ -99,11 +106,12 @@ class Muxer { scoped_refptr sample) = 0; MuxerOptions options_; + bool initialized_; std::vector streams_; EncryptionKeySource* encryption_key_source_; - bool initialized_; EncryptionKeySource::TrackType track_type_; double clear_lead_in_seconds_; + double crypto_period_duration_in_seconds_; event::MuxerListener* muxer_listener_; // An external injected clock, can be NULL. diff --git a/media/formats/mp4/fragmenter.cc b/media/formats/mp4/fragmenter.cc index 6698764af0..72cd559d65 100644 --- a/media/formats/mp4/fragmenter.cc +++ b/media/formats/mp4/fragmenter.cc @@ -9,11 +9,17 @@ #include "media/base/aes_encryptor.h" #include "media/base/buffer_reader.h" #include "media/base/buffer_writer.h" +#include "media/base/encryption_key_source.h" #include "media/base/media_sample.h" #include "media/formats/mp4/box_definitions.h" #include "media/formats/mp4/cenc.h" +namespace media { +namespace mp4 { + namespace { +// Generate 64bit IV by default. +const size_t kDefaultIvSize = 8u; const int64 kInvalidTime = kint64max; // Optimize sample entries table. If all values in |entries| are identical, @@ -36,33 +42,48 @@ bool OptimizeSampleEntries(std::vector* entries, T* default_value) { *default_value = value; return true; } + } // namespace -namespace media { -namespace mp4 { - Fragmenter::Fragmenter(TrackFragment* traf, - scoped_ptr encryptor, - int64 clear_time, - uint8 nalu_length_size, bool normalize_presentation_timestamp) - : encryptor_(encryptor.Pass()), - nalu_length_size_(nalu_length_size), - traf_(traf), + : traf_(traf), + nalu_length_size_(0), + clear_time_(0), fragment_finalized_(false), fragment_duration_(0), normalize_presentation_timestamp_(normalize_presentation_timestamp), presentation_start_time_(kInvalidTime), earliest_presentation_time_(kInvalidTime), - first_sap_time_(kInvalidTime), - clear_time_(clear_time) {} + first_sap_time_(kInvalidTime) { + DCHECK(traf); +} + +Fragmenter::Fragmenter(TrackFragment* traf, + bool normalize_presentation_timestamp, + scoped_ptr encryption_key, + int64 clear_time, + uint8 nalu_length_size) + : traf_(traf), + encryption_key_(encryption_key.Pass()), + nalu_length_size_(nalu_length_size), + clear_time_(clear_time), + fragment_finalized_(false), + fragment_duration_(0), + normalize_presentation_timestamp_(normalize_presentation_timestamp), + presentation_start_time_(kInvalidTime), + earliest_presentation_time_(kInvalidTime), + first_sap_time_(kInvalidTime) { + DCHECK(traf); + DCHECK(encryption_key_); +} Fragmenter::~Fragmenter() {} Status Fragmenter::AddSample(scoped_refptr sample) { CHECK_GT(sample->duration(), 0); - if (ShouldEncryptFragment()) { + if (encryptor_) { Status status = EncryptSample(sample); if (!status.ok()) return status; @@ -112,11 +133,9 @@ Status Fragmenter::AddSample(scoped_refptr sample) { return Status::OK; } -void Fragmenter::InitializeFragment() { +Status Fragmenter::InitializeFragment() { fragment_finalized_ = false; traf_->decode_time.decode_time += fragment_duration_; - traf_->auxiliary_size.sample_info_sizes.clear(); - traf_->auxiliary_offset.offsets.clear(); traf_->runs.clear(); traf_->runs.resize(1); traf_->runs[0].flags = TrackFragmentRun::kDataOffsetPresentMask; @@ -127,41 +146,28 @@ void Fragmenter::InitializeFragment() { data_.reset(new BufferWriter()); aux_data_.reset(new BufferWriter()); - if (ShouldEncryptFragment()) { - if (!IsSubsampleEncryptionRequired()) { - DCHECK(encryptor_); - traf_->auxiliary_size.default_sample_info_size = encryptor_->iv().size(); - } - } + if (!encryption_key_) + return Status::OK; + + // Enable encryption for this fragment if decode time passes clear lead. + if (static_cast(traf_->decode_time.decode_time) >= clear_time_) + return PrepareFragmentForEncryption(); + + // Otherwise, this fragment should be in clear text. + // We generate at most two sample description entries, encrypted entry and + // clear entry. The 1-based clear entry index is always 2. + const uint32 kClearSampleDescriptionIndex = 2; + + traf_->header.flags |= + TrackFragmentHeader::kSampleDescriptionIndexPresentMask; + traf_->header.sample_description_index = kClearSampleDescriptionIndex; + + return Status::OK; } void Fragmenter::FinalizeFragment() { - if (ShouldEncryptFragment()) { - DCHECK(encryptor_); - - // The offset will be adjusted in Segmenter when we know moof size. - traf_->auxiliary_offset.offsets.push_back(0); - - // Optimize saiz box. - SampleAuxiliaryInformationSize& saiz = traf_->auxiliary_size; - saiz.sample_count = 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 if (encryptor_ && clear_time_ > 0) { - // This fragment should be in clear. - // We generate at most two sample description entries, encrypted entry and - // clear entry. The 1-based clear entry index is always 2. - const uint32 kClearSampleDescriptionIndex = 2; - - traf_->header.flags |= - TrackFragmentHeader::kSampleDescriptionIndexPresentMask; - traf_->header.sample_description_index = kClearSampleDescriptionIndex; - clear_time_ -= fragment_duration_; - } + if (encryptor_) + FinalizeFragmentForEncryption(); // Optimize trun box. traf_->runs[0].sample_count = traf_->runs[0].sample_sizes.size(); @@ -203,6 +209,47 @@ void Fragmenter::GenerateSegmentReference(SegmentReference* reference) { reference->earliest_presentation_time = earliest_presentation_time_; } +Status Fragmenter::PrepareFragmentForEncryption() { + traf_->auxiliary_size.sample_info_sizes.clear(); + traf_->auxiliary_offset.offsets.clear(); + return encryptor_ ? Status::OK : CreateEncryptor(); +} + +void Fragmenter::FinalizeFragmentForEncryption() { + // The offset will be adjusted in Segmenter when we know moof size. + traf_->auxiliary_offset.offsets.push_back(0); + + // Optimize saiz box. + SampleAuxiliaryInformationSize& saiz = traf_->auxiliary_size; + saiz.sample_count = 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 = encryptor_->iv().size(); + } +} + +Status Fragmenter::CreateEncryptor() { + DCHECK(encryption_key_); + + scoped_ptr encryptor(new AesCtrEncryptor()); + const bool initialized = encryption_key_->iv.empty() + ? encryptor->InitializeWithRandomIv( + encryption_key_->key, kDefaultIvSize) + : encryptor->InitializeWithIv( + encryption_key_->key, encryption_key_->iv); + if (!initialized) + return Status(error::MUXER_FAILURE, "Failed to create the encryptor."); + encryptor_ = encryptor.Pass(); + return Status::OK; +} + void Fragmenter::EncryptBytes(uint8* data, uint32 size) { DCHECK(encryptor_); CHECK(encryptor_->Encrypt(data, size, data)); diff --git a/media/formats/mp4/fragmenter.h b/media/formats/mp4/fragmenter.h index 6183da6c07..417a99e88d 100644 --- a/media/formats/mp4/fragmenter.h +++ b/media/formats/mp4/fragmenter.h @@ -19,8 +19,11 @@ class AesCtrEncryptor; class BufferWriter; class MediaSample; +struct EncryptionKey; + namespace mp4 { +struct MovieFragment; struct SegmentReference; struct TrackFragment; @@ -30,26 +33,33 @@ struct TrackFragment; class Fragmenter { public: /// @param traf points to a TrackFragment box. - /// @param encryptor handles encryption of the samples. It can be NULL, which - /// indicates no encryption is required. + /// @param normalize_presentation_timestamp defines whether PTS should be + /// normalized to start from zero. + Fragmenter(TrackFragment* traf, + bool normalize_presentation_timestamp); + + /// @param traf points to a TrackFragment box. + /// @param normalize_presentation_timestamp defines whether PTS should be + /// normalized to start from zero. + /// @param encryption_key contains the encryption parameters. /// @param clear_time specifies clear lead duration in units of the current /// track's timescale. /// @param nalu_length_size NAL unit length size, in bytes, for subsample /// encryption. - /// @param normalize_presentation_timestamp defines whether PTS should be - /// normalized to start from zero. Fragmenter(TrackFragment* traf, - scoped_ptr encryptor, + bool normalize_presentation_timestamp, + scoped_ptr encryption_key, int64 clear_time, - uint8 nalu_length_size, - bool normalize_presentation_timestamp); - ~Fragmenter(); + uint8 nalu_length_size); + + virtual ~Fragmenter(); /// Add a sample to the fragmenter. Status AddSample(scoped_refptr sample); /// Initialize the fragment with default data. - void InitializeFragment(); + /// @return OK on success, an error status otherwise. + Status InitializeFragment(); /// Finalize and optimize the fragment. void FinalizeFragment(); @@ -66,34 +76,52 @@ class Fragmenter { BufferWriter* data() { return data_.get(); } BufferWriter* aux_data() { return aux_data_.get(); } + protected: + /// Prepare current fragment for encryption. + /// @return OK on success, an error status otherwise. + virtual Status PrepareFragmentForEncryption(); + /// 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(); + + TrackFragment* traf() { return traf_; } + EncryptionKey* encryption_key() { return encryption_key_.get(); } + AesCtrEncryptor* encryptor() { return encryptor_.get(); } + + void set_encryption_key(scoped_ptr encryption_key) { + encryption_key_ = encryption_key.Pass(); + } + private: void EncryptBytes(uint8* data, uint32 size); Status EncryptSample(scoped_refptr sample); - // Should we enable encrytion for the current fragment? - bool ShouldEncryptFragment() { - return (encryptor_ != NULL && clear_time_ <= 0); - } - // Should we enable subsample encryption? bool IsSubsampleEncryptionRequired() { return nalu_length_size_ != 0; } // Check if the current fragment starts with SAP. bool StartsWithSAP(); + TrackFragment* traf_; + + scoped_ptr encryption_key_; scoped_ptr encryptor_; // If this stream contains AVC, subsample encryption specifies that the size // and type of NAL units remain unencrypted. This field specifies the size of // the size field. Can be 1, 2 or 4 bytes. - uint8 nalu_length_size_; - TrackFragment* traf_; + const uint8 nalu_length_size_; + const int64 clear_time_; + bool fragment_finalized_; uint64 fragment_duration_; bool normalize_presentation_timestamp_; int64 presentation_start_time_; int64 earliest_presentation_time_; int64 first_sap_time_; - int64 clear_time_; scoped_ptr data_; scoped_ptr aux_data_; diff --git a/media/formats/mp4/key_rotation_fragmenter.cc b/media/formats/mp4/key_rotation_fragmenter.cc new file mode 100644 index 0000000000..9a4a0fd784 --- /dev/null +++ b/media/formats/mp4/key_rotation_fragmenter.cc @@ -0,0 +1,100 @@ +// 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 "media/formats/mp4/key_rotation_fragmenter.h" + +#include "media/base/aes_encryptor.h" +#include "media/formats/mp4/box_definitions.h" + +namespace media { +namespace mp4 { + +KeyRotationFragmenter::KeyRotationFragmenter( + MovieFragment* moof, + TrackFragment* traf, + bool normalize_presentation_timestamp, + EncryptionKeySource* encryption_key_source, + EncryptionKeySource::TrackType track_type, + int64 crypto_period_duration, + int64 clear_time, + uint8 nalu_length_size) + : Fragmenter(traf, + normalize_presentation_timestamp, + scoped_ptr(new EncryptionKey()), + clear_time, + nalu_length_size), + moof_(moof), + encryption_key_source_(encryption_key_source), + track_type_(track_type), + crypto_period_duration_(crypto_period_duration), + prev_crypto_period_index_(-1) { + DCHECK(moof); + DCHECK(encryption_key_source); +} + +KeyRotationFragmenter::~KeyRotationFragmenter() {} + +Status KeyRotationFragmenter::PrepareFragmentForEncryption() { + traf()->auxiliary_size.sample_info_sizes.clear(); + traf()->auxiliary_offset.offsets.clear(); + + size_t current_crypto_period_index = + traf()->decode_time.decode_time / crypto_period_duration_; + if (current_crypto_period_index != prev_crypto_period_index_) { + scoped_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; + set_encryption_key(encryption_key.Pass()); + + status = CreateEncryptor(); + if (!status.ok()) + return status; + prev_crypto_period_index_ = current_crypto_period_index; + } + + EncryptionKey* encryption_key = Fragmenter::encryption_key(); + DCHECK(encryption_key); + AesCtrEncryptor* encryptor = Fragmenter::encryptor(); + DCHECK(encryptor); + + // We support key rotation in fragment boundary only, i.e. there is at most + // one key for a single fragment. So we should have only one entry in + // Sample Group Description box and one entry in Sample to Group box. + // Fill in Sample Group Description box information. + traf()->sample_group_description.grouping_type = FOURCC_SEIG; + traf()->sample_group_description.entries.resize(1); + traf()->sample_group_description.entries[0].is_encrypted = true; + traf()->sample_group_description.entries[0].iv_size = encryptor->iv().size(); + traf()->sample_group_description.entries[0].key_id = encryption_key->key_id; + + // Fill in Sample to Group box information. + traf()->sample_to_group.grouping_type = FOURCC_SEIG; + traf()->sample_to_group.entries.resize(1); + // sample_count is adjusted in |FinalizeFragment| later. + traf()->sample_to_group.entries[0].group_description_index = + SampleToGroupEntry::kTrackFragmentGroupDescriptionIndexBase + 1; + + // We need one and only one pssh box. + if (moof_->pssh.empty()) + moof_->pssh.resize(1); + moof_->pssh[0].raw_box = encryption_key->pssh; + + return Status::OK; +} + +void KeyRotationFragmenter::FinalizeFragmentForEncryption() { + Fragmenter::FinalizeFragmentForEncryption(); + DCHECK_EQ(1u, traf()->sample_to_group.entries.size()); + traf()->sample_to_group.entries[0].sample_count = + traf()->auxiliary_size.sample_count; +} + +} // namespace media +} // namespace mp4 + + diff --git a/media/formats/mp4/key_rotation_fragmenter.h b/media/formats/mp4/key_rotation_fragmenter.h new file mode 100644 index 0000000000..379751f18b --- /dev/null +++ b/media/formats/mp4/key_rotation_fragmenter.h @@ -0,0 +1,63 @@ +// 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 "media/base/encryption_key_source.h" +#include "media/formats/mp4/fragmenter.h" + +namespace media { +namespace mp4 { + +class KeyRotationFragmenter : public Fragmenter { + public: + /// @param moof points to a MovieFragment box. + /// @param traf points to a TrackFragment box. + /// @param normalize_presentation_timestamp defines whether PTS should be + /// normalized to start from zero. + /// @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 nalu_length_size NAL unit length size, in bytes, for subsample + /// encryption. + KeyRotationFragmenter(MovieFragment* moof, + TrackFragment* traf, + bool normalize_presentation_timestamp, + EncryptionKeySource* encryption_key_source, + EncryptionKeySource::TrackType track_type, + int64 crypto_period_duration, + int64 clear_time, + uint8 nalu_length_size); + virtual ~KeyRotationFragmenter(); + + protected: + /// @name Fragmenter implementation overrides. + /// @{ + virtual Status PrepareFragmentForEncryption() OVERRIDE; + virtual void FinalizeFragmentForEncryption() OVERRIDE; + /// @} + + private: + MovieFragment* moof_; + + EncryptionKeySource* encryption_key_source_; + EncryptionKeySource::TrackType track_type_; + const int64 crypto_period_duration_; + size_t prev_crypto_period_index_; + + DISALLOW_COPY_AND_ASSIGN(KeyRotationFragmenter); +}; + +} // namespace mp4 +} // namespace media + +#endif // MEDIA_FORMATS_MP4_KEY_ROTATION_FRAGMENTER_H_ diff --git a/media/formats/mp4/mp4.gyp b/media/formats/mp4/mp4.gyp index f673b760b0..13e3ab5c47 100644 --- a/media/formats/mp4/mp4.gyp +++ b/media/formats/mp4/mp4.gyp @@ -42,6 +42,8 @@ 'fourccs.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/media/formats/mp4/mp4_muxer.cc b/media/formats/mp4/mp4_muxer.cc index 0913a63af0..d0c2957dc9 100644 --- a/media/formats/mp4/mp4_muxer.cc +++ b/media/formats/mp4/mp4_muxer.cc @@ -102,7 +102,8 @@ Status MP4Muxer::Initialize() { segmenter_->Initialize(streams(), encryption_key_source(), track_type(), - clear_lead_in_seconds()); + clear_lead_in_seconds(), + crypto_period_duration_in_seconds()); if (!segmenter_initialized.ok()) return segmenter_initialized; diff --git a/media/formats/mp4/segmenter.cc b/media/formats/mp4/segmenter.cc index 44fd0a6d50..14f03be8c1 100644 --- a/media/formats/mp4/segmenter.cc +++ b/media/formats/mp4/segmenter.cc @@ -9,7 +9,6 @@ #include #include "base/stl_util.h" -#include "media/base/aes_encryptor.h" #include "media/base/buffer_writer.h" #include "media/base/encryption_key_source.h" #include "media/base/media_sample.h" @@ -17,7 +16,7 @@ #include "media/base/muxer_options.h" #include "media/base/video_stream_info.h" #include "media/formats/mp4/box_definitions.h" -#include "media/formats/mp4/fragmenter.h" +#include "media/formats/mp4/key_rotation_fragmenter.h" namespace media { namespace mp4 { @@ -26,6 +25,7 @@ namespace { // Generate 64bit IV by default. const size_t kDefaultIvSize = 8u; +const size_t kCencKeyIdSize = 16u; // The version of cenc implemented here. CENC 4. const int kCencSchemeVersion = 0x00010000; @@ -34,21 +34,6 @@ uint64 Rescale(uint64 time_in_old_scale, uint32 old_scale, uint32 new_scale) { return static_cast(time_in_old_scale) / old_scale * new_scale; } -scoped_ptr CreateEncryptor( - const EncryptionKey& encryption_key) { - scoped_ptr encryptor(new AesCtrEncryptor()); - const bool initialized = - encryption_key.iv.empty() - ? encryptor->InitializeWithRandomIv(encryption_key.key, - kDefaultIvSize) - : encryptor->InitializeWithIv(encryption_key.key, encryption_key.iv); - if (!initialized) { - LOG(ERROR) << "Failed to the initialize encryptor."; - return scoped_ptr(); - } - return encryptor.Pass(); -} - void GenerateSinf(const EncryptionKey& encryption_key, FourCC old_type, ProtectionSchemeInfo* sinf) { @@ -91,6 +76,16 @@ void GenerateEncryptedSampleEntry(const EncryptionKey& encryption_key, } } +void GenerateEncryptedSampleEntryForKeyRotation( + double clear_lead_in_seconds, + SampleDescription* description) { + // Fill encrypted sample entry with default key. + EncryptionKey encryption_key; + encryption_key.key_id.assign(kCencKeyIdSize, 0); + GenerateEncryptedSampleEntry( + encryption_key, clear_lead_in_seconds, description); +} + } // namespace Segmenter::Segmenter(const MuxerOptions& options, @@ -110,7 +105,8 @@ Segmenter::~Segmenter() { STLDeleteElements(&fragmenters_); } Status Segmenter::Initialize(const std::vector& streams, EncryptionKeySource* encryption_key_source, EncryptionKeySource::TrackType track_type, - double clear_lead_in_seconds) { + double clear_lead_in_seconds, + double crypto_period_duration_in_seconds) { DCHECK_LT(0u, streams.size()); moof_->header.sequence_number = 0; @@ -129,41 +125,59 @@ Status Segmenter::Initialize(const std::vector& streams, if (sidx_->reference_id == 0) sidx_->reference_id = i + 1; } - scoped_ptr encryptor; - if (encryption_key_source) { - SampleDescription& description = - moov_->tracks[i].media.information.sample_table.description; - DCHECK(track_type == EncryptionKeySource::TRACK_TYPE_SD || - track_type == EncryptionKeySource::TRACK_TYPE_HD); - - EncryptionKey encryption_key; - Status status = encryption_key_source->GetKey( - description.type == kAudio ? EncryptionKeySource::TRACK_TYPE_AUDIO - : track_type, - &encryption_key); - if (!status.ok()) - return status; - - GenerateEncryptedSampleEntry( - encryption_key, clear_lead_in_seconds, &description); - - // We need one and only one pssh box. - if (moov_->pssh.empty()) { - moov_->pssh.resize(1); - moov_->pssh[0].raw_box = encryption_key.pssh; - } - - encryptor = CreateEncryptor(encryption_key); - if (!encryptor) - return Status(error::MUXER_FAILURE, "Failed to create the encryptor."); + if (!encryption_key_source) { + fragmenters_[i] = new Fragmenter( + &moof_->tracks[i], options_.normalize_presentation_timestamp); + continue; } - fragmenters_[i] = new Fragmenter( - &moof_->tracks[i], - encryptor.Pass(), - clear_lead_in_seconds * streams[i]->info()->time_scale(), - nalu_length_size, - options_.normalize_presentation_timestamp); + + DCHECK(track_type == EncryptionKeySource::TRACK_TYPE_SD || + track_type == EncryptionKeySource::TRACK_TYPE_HD); + SampleDescription& description = + moov_->tracks[i].media.information.sample_table.description; + EncryptionKeySource::TrackType cur_track_type = + description.type == kAudio ? EncryptionKeySource::TRACK_TYPE_AUDIO + : track_type; + + const bool key_rotation_enabled = crypto_period_duration_in_seconds != 0; + if (key_rotation_enabled) { + GenerateEncryptedSampleEntryForKeyRotation(clear_lead_in_seconds, + &description); + + fragmenters_[i] = new KeyRotationFragmenter( + moof_.get(), + &moof_->tracks[i], + options_.normalize_presentation_timestamp, + encryption_key_source, + cur_track_type, + crypto_period_duration_in_seconds * streams[i]->info()->time_scale(), + clear_lead_in_seconds * streams[i]->info()->time_scale(), + nalu_length_size); + continue; + } + + scoped_ptr encryption_key(new EncryptionKey()); + Status status = + encryption_key_source->GetKey(cur_track_type, encryption_key.get()); + if (!status.ok()) + return status; + + GenerateEncryptedSampleEntry( + *encryption_key, clear_lead_in_seconds, &description); + + // We need one and only one pssh box. + if (moov_->pssh.empty()) { + moov_->pssh.resize(1); + moov_->pssh[0].raw_box = encryption_key->pssh; + } + + fragmenters_[i] = + new Fragmenter(&moof_->tracks[i], + options_.normalize_presentation_timestamp, + encryption_key.Pass(), + clear_lead_in_seconds * streams[i]->info()->time_scale(), + nalu_length_size); } // Choose the first stream if there is no VIDEO. @@ -289,13 +303,17 @@ uint32 Segmenter::GetReferenceStreamId() { return sidx_->reference_id - 1; } -void Segmenter::InitializeFragments() { +Status Segmenter::InitializeFragments() { ++moof_->header.sequence_number; + Status status; for (std::vector::iterator it = fragmenters_.begin(); it != fragmenters_.end(); ++it) { - (*it)->InitializeFragment(); + status = (*it)->InitializeFragment(); + if (!status.ok()) + return status; } + return Status::OK; } Status Segmenter::FinalizeFragment(Fragmenter* fragmenter) { diff --git a/media/formats/mp4/segmenter.h b/media/formats/mp4/segmenter.h index 7de9526096..57f69077fe 100644 --- a/media/formats/mp4/segmenter.h +++ b/media/formats/mp4/segmenter.h @@ -49,17 +49,20 @@ class Segmenter { /// Initialize the segmenter. /// Calling other public methods of this class without this method returning /// Status::OK results in an undefined behavior. + /// @param streams contains the vector of MediaStreams to be segmented. /// @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 track_type indicates whether SD key or HD key should be used to /// encrypt the video content. /// @param clear_time specifies clear lead duration in seconds. + /// @param crypto_period_duration specifies crypto period duration in seconds. /// @return OK on success, an error status otherwise. Status Initialize(const std::vector& streams, EncryptionKeySource* encryption_key_source, EncryptionKeySource::TrackType track_type, - double clear_lead_in_seconds); + double clear_lead_in_seconds, + double crypto_period_duration_in_seconds); /// Finalize the segmenter. /// @return OK on success, an error status otherwise. @@ -102,7 +105,7 @@ class Segmenter { Status FinalizeSegment(); uint32 GetReferenceStreamId(); - void InitializeFragments(); + Status InitializeFragments(); Status FinalizeFragment(Fragmenter* fragment); const MuxerOptions& options_; diff --git a/media/test/packager_test.cc b/media/test/packager_test.cc index e62c44ea3b..5b4fa0c055 100644 --- a/media/test/packager_test.cc +++ b/media/test/packager_test.cc @@ -54,6 +54,7 @@ const char kPsshHex[] = "758382cd1a0d7769646576696e655f746573742211544553545f" "434f4e54454e545f49445f312a025344"; const double kClearLeadInSeconds = 1.5; +const double kCryptoDurationInSeconds = 0; // Key rotation is disabled. MediaStream* FindFirstStreamOfType(const std::vector& streams, StreamType stream_type) { @@ -164,7 +165,8 @@ void PackagerTestBasic::Remux(const std::string& input, if (enable_encryption) { muxer_video->SetEncryptionKeySource(encryption_key_source.get(), EncryptionKeySource::TRACK_TYPE_SD, - kClearLeadInSeconds); + kClearLeadInSeconds, + kCryptoDurationInSeconds); } } @@ -179,7 +181,8 @@ void PackagerTestBasic::Remux(const std::string& input, if (enable_encryption) { muxer_audio->SetEncryptionKeySource(encryption_key_source.get(), EncryptionKeySource::TRACK_TYPE_SD, - kClearLeadInSeconds); + kClearLeadInSeconds, + kCryptoDurationInSeconds); } }