From d1d75f477c2d22f237e634791cc371e9fb4d4600 Mon Sep 17 00:00:00 2001 From: Jacob Trimble Date: Wed, 18 Nov 2015 13:11:31 -0800 Subject: [PATCH] Add encryption support to WebM. This does not support key-rotation and will give an error. This is because WebM does not have a way to indicate a change in key ID using media segments. b/22463551 Change-Id: I9b3dac818dc370302a5afc0d25d8a060b64d30cd --- packager/media/base/aes_encryptor.h | 1 + packager/media/base/media_sample.h | 4 + .../webm/encrypted_segmenter_unittest.cc | 216 ++++++++++++++++++ packager/media/formats/webm/encryptor.cc | 134 +++++++++++ packager/media/formats/webm/encryptor.h | 64 ++++++ .../webm/multi_segment_segmenter_unittest.cc | 2 +- packager/media/formats/webm/segmenter.cc | 52 ++++- packager/media/formats/webm/segmenter.h | 14 +- .../media/formats/webm/segmenter_test_base.h | 6 +- .../webm/single_segment_segmenter_unittest.cc | 4 +- packager/media/formats/webm/webm.gyp | 3 + packager/media/formats/webm/webm_muxer.cc | 9 +- 12 files changed, 499 insertions(+), 10 deletions(-) create mode 100644 packager/media/formats/webm/encrypted_segmenter_unittest.cc create mode 100644 packager/media/formats/webm/encryptor.cc create mode 100644 packager/media/formats/webm/encryptor.h diff --git a/packager/media/base/aes_encryptor.h b/packager/media/base/aes_encryptor.h index d061e692ca..edb8a5b437 100644 --- a/packager/media/base/aes_encryptor.h +++ b/packager/media/base/aes_encryptor.h @@ -44,6 +44,7 @@ class AesCtrEncryptor { /// @name Various forms of encrypt calls. /// block_offset() will be updated according to input plaintext size. + /// The plaintext and ciphertext pointers can be the same address. /// @{ bool Encrypt(const uint8_t* plaintext, size_t plaintext_size, diff --git a/packager/media/base/media_sample.h b/packager/media/base/media_sample.h index f0c61cf659..66f33606c2 100644 --- a/packager/media/base/media_sample.h +++ b/packager/media/base/media_sample.h @@ -122,6 +122,10 @@ class MediaSample : public base::RefCountedThreadSafe { data_.assign(data, data + data_size); } + void resize_data(const size_t data_size) { + data_.resize(data_size); + } + void set_is_key_frame(bool value) { is_key_frame_ = value; } diff --git a/packager/media/formats/webm/encrypted_segmenter_unittest.cc b/packager/media/formats/webm/encrypted_segmenter_unittest.cc new file mode 100644 index 0000000000..2a4b0aacdc --- /dev/null +++ b/packager/media/formats/webm/encrypted_segmenter_unittest.cc @@ -0,0 +1,216 @@ +// Copyright (c) 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "packager/media/formats/webm/single_segment_segmenter.h" +#include "packager/media/formats/webm/two_pass_single_segment_segmenter.h" + +#include + +#include "packager/base/memory/scoped_ptr.h" +#include "packager/media/formats/webm/segmenter_test_base.h" + +namespace edash_packager { +namespace media { +namespace { + +const uint64_t kDuration = 1000; +const std::string kKeyId = "68656c6c6f20776f726c64"; +const std::string kIv = "0123456789012345"; +const std::string kKey = "01234567890123456789012345678901"; +const std::string kPsshData = ""; + +const uint8_t kBasicSupportData[] = { + // ID: EBML Header, Payload Size: 31 + 0x1a, 0x45, 0xdf, 0xa3, 0x9f, + // EBMLVersion: 1 + 0x42, 0x86, 0x81, 0x01, + // EBMLReadVersion: 1 + 0x42, 0xf7, 0x81, 0x01, + // EBMLMaxIDLength: 4 + 0x42, 0xf2, 0x81, 0x04, + // EBMLMaxSizeLength: 8 + 0x42, 0xf3, 0x81, 0x08, + // DocType: 'webm' + 0x42, 0x82, 0x84, 0x77, 0x65, 0x62, 0x6d, + // DocTypeVersion: 2 + 0x42, 0x87, 0x81, 0x02, + // DocTypeReadVersion: 2 + 0x42, 0x85, 0x81, 0x02, + // ID: Segment, Payload Size: 400 + 0x18, 0x53, 0x80, 0x67, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x90, + // ID: SeekHead, Payload Size: 30 + 0x11, 0x4d, 0x9b, 0x74, 0x9e, + // ID: Seek, Payload Size: 12 + 0x4d, 0xbb, 0x8c, + // SeekID: binary(4) (Cluster) + 0x53, 0xab, 0x84, 0x1f, 0x43, 0xb6, 0x75, + // SeekPosition: 322 + 0x53, 0xac, 0x82, 0x01, 0x42, + // ID: Seek, Payload Size: 12 + 0x4d, 0xbb, 0x8c, + // SeekID: binary(4) (Cues) + 0x53, 0xab, 0x84, 0x1c, 0x53, 0xbb, 0x6b, + // SeekPosition: 429 + 0x53, 0xac, 0x82, 0x01, 0xad, + // ID: Void, Payload Size: 52 + 0xec, 0xb4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // ID: Info, Payload Size: 88 + 0x15, 0x49, 0xa9, 0x66, 0xd8, + // TimecodeScale: 1000000 + 0x2a, 0xd7, 0xb1, 0x83, 0x0f, 0x42, 0x40, + // Duration: float(5000) + 0x44, 0x89, 0x84, 0x45, 0x9c, 0x40, 0x00, + // MuxingApp: 'libwebm-0.2.1.0' + 0x4d, 0x80, 0x8f, 0x6c, 0x69, 0x62, 0x77, 0x65, 0x62, 0x6d, 0x2d, 0x30, + 0x2e, 0x32, 0x2e, 0x31, 0x2e, 0x30, + // WritingApp: 'https://github.com/google/edash-packager version test' + 0x57, 0x41, 0xb5, + 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, + 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2f, 0x65, 0x64, 0x61, 0x73, 0x68, 0x2d, 0x70, 0x61, 0x63, 0x6b, + 0x61, 0x67, 0x65, 0x72, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x20, 0x74, 0x65, 0x73, 0x74, + // ID: Tracks, Payload Size: 87 + 0x16, 0x54, 0xae, 0x6b, 0xd7, + // ID: Track, Payload Size: 85 + 0xae, 0xd5, + // TrackNumber: 1 + 0xd7, 0x81, 0x01, + // TrackUID: 1 + 0x73, 0xc5, 0x81, 0x01, + // TrackType: 1 + 0x83, 0x81, 0x01, + // CodecID: 'V_VP8' + 0x86, 0x85, 0x56, 0x5f, 0x56, 0x50, 0x38, + // Language: 'en' + 0x22, 0xb5, 0x9c, 0x82, 0x65, 0x6e, + // ID: ContentEncodings, Payload Size: 43 + 0x6d, 0x80, 0xab, + // ID: ContentEncoding, Payload Size: 40 + 0x62, 0x40, 0xa8, + // ContentEncodingOrder: 0 + 0x50, 0x31, 0x81, 0x00, + // ContentEncodingScope: 1 + 0x50, 0x32, 0x81, 0x01, + // ContentEncodingType: 1 + 0x50, 0x33, 0x81, 0x01, + // ID: ContentEncryption, Payload Size: 25 + 0x50, 0x35, 0x99, + // ContentEncAlgo: 5 + 0x47, 0xe1, 0x81, 0x05, + // ContentEncKeyID: binary(11) + 0x47, 0xe2, 0x8b, + 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, + 0x64, + // ID: ContentEncAESSettings, Payload Size: 4 + 0x47, 0xe7, 0x84, + // AESSettingsCipherMode: 1 + 0x47, 0xe8, 0x81, 0x01, + // ID: Video, Payload Size: 14 + 0xe0, 0x8e, + // PixelWidth: 100 + 0xb0, 0x81, 0x64, + // PixelHeight: 100 + 0xba, 0x81, 0x64, + // DisplayWidth: 100 + 0x54, 0xb0, 0x81, 0x64, + // DisplayHeight: 100 + 0x54, 0xba, 0x81, 0x64, + // ID: Cluster, Payload Size: 95 + 0x1f, 0x43, 0xb6, 0x75, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5f, + // Timecode: 0 + 0xe7, 0x81, 0x00, + // ID: SimpleBlock, Payload Size: 10 + 0xa3, 0x8a, 0x81, 0x00, 0x00, 0x80, + // Signal Byte: Clear + 0x00, + // Frame Data: + 0xde, 0xad, 0xbe, 0xef, 0x00, + // ID: SimpleBlock, Payload Size: 18 + 0xa3, 0x92, 0x81, 0x03, 0xe8, 0x80, + // Signal Byte: Encrypted + 0x01, + // IV: + 0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x23, 0x45, + // Frame Data: + 0xcc, 0x03, 0xef, 0xc4, 0xf4, + // ID: SimpleBlock, Payload Size: 18 + 0xa3, 0x92, 0x81, 0x07, 0xd0, 0x80, + // Signal Byte: Encrypted + 0x01, + // IV: + 0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x23, 0x46, + // Frame Data: + 0xbf, 0x38, 0x72, 0x20, 0xac, + // ID: SimpleBlock, Payload Size: 18 + 0xa3, 0x92, 0x81, 0x0b, 0xb8, 0x80, + // Signal Byte: Encrypted + 0x01, + // IV: + 0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x23, 0x47, + // Frame Data: + 0x0d, 0x8e, 0xae, 0xbe, 0xd0, + // ID: SimpleBlock, Payload Size: 18 + 0xa3, 0x92, 0x81, 0x0f, 0xa0, 0x80, + // Signal Byte: Encrypted + 0x01, + // IV: + 0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x23, 0x48, + // Frame Data: + 0xa5, 0x97, 0xf8, 0x9e, 0x87, + // ID: Cues, Payload Size: 14 + 0x1c, 0x53, 0xbb, 0x6b, 0x8e, + // ID: CuePoint, Payload Size: 12 + 0xbb, 0x8c, + // CueTime: 0 + 0xb3, 0x81, 0x00, + // ID: CueTrackPositions, Payload Size: 7 + 0xb7, 0x87, + // CueTrack: 1 + 0xf7, 0x81, 0x01, + // CueClusterPosition: 274 + 0xf1, 0x82, 0x01, 0x12 +}; + +} // namespace + +class EncrypedSegmenterTest : public SegmentTestBase { + public: + EncrypedSegmenterTest() : info_(CreateVideoStreamInfo()) {} + + protected: + void InitializeSegmenter(const MuxerOptions& options) { + key_source_ = KeySource::CreateFromHexStrings(kKeyId, kKey, kPsshData, kIv); + ASSERT_NO_FATAL_FAILURE( + CreateAndInitializeSegmenter( + options, info_.get(), key_source_.get(), &segmenter_)); + } + + scoped_refptr info_; + scoped_ptr segmenter_; + scoped_ptr key_source_; +}; + +TEST_F(EncrypedSegmenterTest, BasicSupport) { + MuxerOptions options = CreateMuxerOptions(); + ASSERT_NO_FATAL_FAILURE(InitializeSegmenter(options)); + + // Write the samples to the Segmenter. + for (int i = 0; i < 5; i++) { + scoped_refptr sample = + CreateSample(kKeyFrame, kDuration, kNoSideData); + ASSERT_OK(segmenter_->AddSample(sample)); + } + ASSERT_OK(segmenter_->Finalize()); + + ASSERT_FILE_EQ(OutputFileName().c_str(), kBasicSupportData); +} + +} // namespace media +} // namespace edash_packager + diff --git a/packager/media/formats/webm/encryptor.cc b/packager/media/formats/webm/encryptor.cc new file mode 100644 index 0000000000..1f3cff6ec6 --- /dev/null +++ b/packager/media/formats/webm/encryptor.cc @@ -0,0 +1,134 @@ +// Copyright 2015 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 "packager/media/base/aes_encryptor.h" +#include "packager/media/base/media_sample.h" + +namespace edash_packager { +namespace media { +namespace webm { +namespace { + +// Generate 64bit IV by default. +const size_t kDefaultIvSize = 8u; + +Status CreateContentEncryption(mkvmuxer::Track* track, EncryptionKey* key) { + if (!track->AddContentEncoding()) { + return Status(error::INTERNAL_ERROR, + "Could not add ContentEncoding to track."); + } + + mkvmuxer::ContentEncoding* const encoding = + track->GetContentEncodingByIndex(0); + if (!encoding) { + return Status(error::INTERNAL_ERROR, + "Could not add ContentEncoding to track."); + } + + mkvmuxer::ContentEncAESSettings* const aes = encoding->enc_aes_settings(); + if (!aes) { + return Status(error::INTERNAL_ERROR, + "Error getting ContentEncAESSettings."); + } + if (aes->cipher_mode() != mkvmuxer::ContentEncAESSettings::kCTR) { + 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())) { + 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, + KeySource* key_source) { + DCHECK(key_source); + return CreateEncryptor(muxer_listener, track_type, key_source); +} + +Status Encryptor::AddTrackInfo(mkvmuxer::Track* track) { + DCHECK(key_); + return CreateContentEncryption(track, key_.get()); +} + +Status Encryptor::EncryptFrame(scoped_refptr sample, + bool encrypt_frame) { + DCHECK(encryptor_); + + const size_t sample_size = sample->data_size(); + if (encrypt_frame) { + // | 1 | iv | enc_data | + const size_t iv_size = encryptor_->iv().size(); + sample->resize_data(sample_size + iv_size + 1); + uint8_t* sample_data = sample->writable_data(); + + // Encrypt the data in-place. + if (!encryptor_->Encrypt(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 + 1, sample_data, sample_size); + sample_data[0] = 0x01; + memcpy(sample_data + 1, encryptor_->iv().data(), iv_size); + + encryptor_->UpdateIv(); + } else { + // | 0 | data | + sample->resize_data(sample_size + 1); + uint8_t* sample_data = sample->writable_data(); + 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, + KeySource* key_source) { + scoped_ptr encryption_key(new EncryptionKey()); + Status status = key_source->GetKey(track_type, encryption_key.get()); + if (!status.ok()) + return status; + + 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::INTERNAL_ERROR, "Failed to create the encryptor."); + + if (muxer_listener) { + const bool kInitialEncryptionInfo = true; + muxer_listener->OnEncryptionInfoReady( + kInitialEncryptionInfo, key_source->UUID(), key_source->SystemName(), + encryption_key->key_id, encryption_key->pssh); + } + + key_ = encryption_key.Pass(); + encryptor_ = encryptor.Pass(); + return Status::OK; +} + +} // namespace webm +} // namespace media +} // namespace edash_packager diff --git a/packager/media/formats/webm/encryptor.h b/packager/media/formats/webm/encryptor.h new file mode 100644 index 0000000000..b479c8ff65 --- /dev/null +++ b/packager/media/formats/webm/encryptor.h @@ -0,0 +1,64 @@ +// Copyright 2015 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_WEBM_ENCRYPTOR_H_ +#define MEDIA_FORMATS_WEBM_ENCRYPTOR_H_ + +#include "packager/base/memory/ref_counted.h" +#include "packager/base/memory/scoped_ptr.h" +#include "packager/media/base/key_source.h" +#include "packager/media/base/status.h" +#include "packager/media/event/muxer_listener.h" +#include "packager/third_party/libwebm/src/mkvmuxer.hpp" + +namespace edash_packager { +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(); + + /// 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, + KeySource* key_source); + + /// 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(scoped_refptr sample, + bool encrypt_frame); + + private: + // Create the encryptor for the internal encryption key. + Status CreateEncryptor(MuxerListener* muxer_listener, + KeySource::TrackType track_type, + KeySource* key_source); + + private: + scoped_ptr key_; + scoped_ptr encryptor_; +}; + +} // namespace webm +} // namespace media +} // namespace edash_packager + +#endif // MEDIA_FORMATS_WEBM_ENCRYPTOR_H_ diff --git a/packager/media/formats/webm/multi_segment_segmenter_unittest.cc b/packager/media/formats/webm/multi_segment_segmenter_unittest.cc index 29e3b2f1ac..708104f097 100644 --- a/packager/media/formats/webm/multi_segment_segmenter_unittest.cc +++ b/packager/media/formats/webm/multi_segment_segmenter_unittest.cc @@ -111,7 +111,7 @@ class MultiSegmentSegmenterTest : public SegmentTestBase { void InitializeSegmenter(const MuxerOptions& options) { ASSERT_NO_FATAL_FAILURE( CreateAndInitializeSegmenter( - options, info_.get(), &segmenter_)); + options, info_.get(), NULL, &segmenter_)); } scoped_refptr info_; diff --git a/packager/media/formats/webm/segmenter.cc b/packager/media/formats/webm/segmenter.cc index 764e1bc6df..d0233bd985 100644 --- a/packager/media/formats/webm/segmenter.cc +++ b/packager/media/formats/webm/segmenter.cc @@ -46,9 +46,12 @@ Status Segmenter::Initialize(scoped_ptr writer, StreamInfo* info, ProgressListener* progress_listener, MuxerListener* muxer_listener, - KeySource* encryption_key_source) { + KeySource* encryption_key_source, + uint32_t max_sd_pixels, + double clear_lead_in_seconds) { muxer_listener_ = muxer_listener; info_ = info; + clear_lead_ = clear_lead_in_seconds; // Use media duration as progress target. progress_target_ = info_->duration(); @@ -67,8 +70,14 @@ Status Segmenter::Initialize(scoped_ptr writer, segment_info_.set_duration(1); } - // Create the track info. Status status; + if (encryption_key_source) { + status = InitializeEncryptor(encryption_key_source, max_sd_pixels); + if (!status.ok()) + return status; + } + + // Create the track info. switch (info_->stream_type()) { case kStreamVideo: status = CreateVideoTrack(static_cast(info_)); @@ -120,6 +129,18 @@ Status Segmenter::AddSample(scoped_refptr sample) { if (!status.ok()) return status; + // Encrypt the frame. + if (encryptor_) { + const bool encrypt_frame = + static_cast(total_duration_) / info_->time_scale() >= + clear_lead_; + status = encryptor_->EncryptFrame(sample, encrypt_frame); + if (!status.ok()) { + LOG(ERROR) << "Error encrypting frame."; + return status; + } + } + const int64_t time_ns = sample->pts() * kSecondsToNs / info_->time_scale(); bool addframe_result; @@ -258,6 +279,9 @@ Status Segmenter::CreateVideoTrack(VideoStreamInfo* info) { 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; @@ -292,11 +316,35 @@ Status Segmenter::CreateAudioTrack(AudioStreamInfo* info) { track->set_sample_rate(info->sampling_frequency()); track->set_channels(info->num_channels()); + 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) { + encryptor_.reset(new Encryptor()); + switch (info_->stream_type()) { + case kStreamVideo: { + VideoStreamInfo* video_info = static_cast(info_); + uint32_t pixels = video_info->width() * video_info->height(); + KeySource::TrackType type = (pixels > max_sd_pixels) + ? KeySource::TRACK_TYPE_HD + : KeySource::TRACK_TYPE_SD; + return encryptor_->Initialize(muxer_listener_, type, key_source); + } + case kStreamAudio: + return encryptor_->Initialize( + muxer_listener_, KeySource::TrackType::TRACK_TYPE_AUDIO, key_source); + default: + // Other streams are not encrypted. + return Status::OK; + } +} + } // namespace webm } // namespace media } // namespace edash_packager diff --git a/packager/media/formats/webm/segmenter.h b/packager/media/formats/webm/segmenter.h index 3c9bc956b4..f6e0bfd56d 100644 --- a/packager/media/formats/webm/segmenter.h +++ b/packager/media/formats/webm/segmenter.h @@ -10,6 +10,7 @@ #include "packager/base/memory/ref_counted.h" #include "packager/base/memory/scoped_ptr.h" #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" @@ -44,12 +45,18 @@ class Segmenter { /// @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 or HD. If the track has more + /// pixels per frame than max_sd_pixels, it is HD, SD otherwise. + /// @param clear_time specifies clear lead duration in seconds. /// @return OK on success, an error status otherwise. Status Initialize(scoped_ptr writer, StreamInfo* info, ProgressListener* progress_listener, MuxerListener* muxer_listener, - KeySource* encryption_key_source); + KeySource* encryption_key_source, + uint32_t max_sd_pixels, + double clear_lead_in_seconds); /// Finalize the segmenter. /// @return OK on success, an error status otherwise. @@ -60,8 +67,6 @@ class Segmenter { /// @return OK on success, an error status otherwise. Status AddSample(scoped_refptr sample); - // TODO(modmaker): Add key source support. - /// @return true if there is an initialization range, while setting @a start /// and @a end; or false if initialization range does not apply. virtual bool GetInitRangeStartAndEnd(uint32_t* start, uint32_t* end) = 0; @@ -107,6 +112,7 @@ class Segmenter { private: Status CreateVideoTrack(VideoStreamInfo* info); Status CreateAudioTrack(AudioStreamInfo* info); + Status InitializeEncryptor(KeySource* key_source, uint32_t max_sd_pixels); // This is called when there needs to be a new subsegment. This does nothing // in single-segment mode. In multi-segment mode this creates a new Cluster @@ -121,6 +127,8 @@ class Segmenter { Status FinalizeSegment(uint64_t end_timescale); const MuxerOptions& options_; + scoped_ptr encryptor_; + double clear_lead_; scoped_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 08903fd689..6c955113ec 100644 --- a/packager/media/formats/webm/segmenter_test_base.h +++ b/packager/media/formats/webm/segmenter_test_base.h @@ -46,12 +46,16 @@ class SegmentTestBase : public ::testing::Test { template void CreateAndInitializeSegmenter(const MuxerOptions& options, StreamInfo* info, + KeySource* key_source, scoped_ptr* result) const { scoped_ptr segmenter(new S(options)); scoped_ptr writer(new MkvWriter()); ASSERT_OK(writer->Open(options.output_file_name)); - ASSERT_OK(segmenter->Initialize(writer.Pass(), info, NULL, NULL, NULL)); + ASSERT_OK(segmenter->Initialize( + writer.Pass(), info, NULL /* progress_listener */, + NULL /* muxer_listener */, key_source, 0 /* max_sd_pixels */, + 1 /* clear_lead_in_seconds */)); *result = segmenter.Pass(); } diff --git a/packager/media/formats/webm/single_segment_segmenter_unittest.cc b/packager/media/formats/webm/single_segment_segmenter_unittest.cc index 7681ab2c86..104f3695d3 100644 --- a/packager/media/formats/webm/single_segment_segmenter_unittest.cc +++ b/packager/media/formats/webm/single_segment_segmenter_unittest.cc @@ -148,11 +148,11 @@ class SingleSegmentSegmenterTest : public SegmentTestBase, if (!GetParam()) { ASSERT_NO_FATAL_FAILURE( CreateAndInitializeSegmenter( - options, info_.get(), &segmenter_)); + options, info_.get(), NULL, &segmenter_)); } else { ASSERT_NO_FATAL_FAILURE( CreateAndInitializeSegmenter( - options, info_.get(), &segmenter_)); + options, info_.get(), NULL, &segmenter_)); } } diff --git a/packager/media/formats/webm/webm.gyp b/packager/media/formats/webm/webm.gyp index 2fee605082..a01626bc43 100644 --- a/packager/media/formats/webm/webm.gyp +++ b/packager/media/formats/webm/webm.gyp @@ -13,6 +13,8 @@ 'target_name': 'webm', 'type': '<(component)', 'sources': [ + 'encryptor.cc', + 'encryptor.h', 'mkv_writer.cc', 'mkv_writer.h', 'multi_segment_segmenter.cc', @@ -65,6 +67,7 @@ 'sources': [ 'cluster_builder.cc', 'cluster_builder.h', + 'encrypted_segmenter_unittest.cc', 'multi_segment_segmenter_unittest.cc', 'opus_packet_builder.cc', 'opus_packet_builder.h', diff --git a/packager/media/formats/webm/webm_muxer.cc b/packager/media/formats/webm/webm_muxer.cc index 999c758980..73fb319df3 100644 --- a/packager/media/formats/webm/webm_muxer.cc +++ b/packager/media/formats/webm/webm_muxer.cc @@ -24,6 +24,12 @@ WebMMuxer::~WebMMuxer() {} Status WebMMuxer::Initialize() { 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"); + } + scoped_ptr writer(new MkvWriter); Status status = writer->Open(options().output_file_name); if (!status.ok()) @@ -39,7 +45,8 @@ Status WebMMuxer::Initialize() { Status initialized = segmenter_->Initialize( writer.Pass(), streams()[0]->info().get(), progress_listener(), - muxer_listener(), encryption_key_source()); + muxer_listener(), encryption_key_source(), max_sd_pixels(), + clear_lead_in_seconds()); if (!initialized.ok()) return initialized;