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
This commit is contained in:
Jacob Trimble 2015-11-18 13:11:31 -08:00
parent 175606c88d
commit d1d75f477c
12 changed files with 499 additions and 10 deletions

View File

@ -44,6 +44,7 @@ class AesCtrEncryptor {
/// @name Various forms of encrypt calls. /// @name Various forms of encrypt calls.
/// block_offset() will be updated according to input plaintext size. /// 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, bool Encrypt(const uint8_t* plaintext,
size_t plaintext_size, size_t plaintext_size,

View File

@ -122,6 +122,10 @@ class MediaSample : public base::RefCountedThreadSafe<MediaSample> {
data_.assign(data, data + data_size); 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) { void set_is_key_frame(bool value) {
is_key_frame_ = value; is_key_frame_ = value;
} }

View File

@ -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 <gtest/gtest.h>
#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<webm::SingleSegmentSegmenter>(
options, info_.get(), key_source_.get(), &segmenter_));
}
scoped_refptr<StreamInfo> info_;
scoped_ptr<webm::Segmenter> segmenter_;
scoped_ptr<KeySource> 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<MediaSample> 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

View File

@ -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<const uint8*>(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<MediaSample> 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<EncryptionKey> encryption_key(new EncryptionKey());
Status status = key_source->GetKey(track_type, encryption_key.get());
if (!status.ok())
return status;
scoped_ptr<AesCtrEncryptor> 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

View File

@ -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<MediaSample> 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<EncryptionKey> key_;
scoped_ptr<AesCtrEncryptor> encryptor_;
};
} // namespace webm
} // namespace media
} // namespace edash_packager
#endif // MEDIA_FORMATS_WEBM_ENCRYPTOR_H_

View File

@ -111,7 +111,7 @@ class MultiSegmentSegmenterTest : public SegmentTestBase {
void InitializeSegmenter(const MuxerOptions& options) { void InitializeSegmenter(const MuxerOptions& options) {
ASSERT_NO_FATAL_FAILURE( ASSERT_NO_FATAL_FAILURE(
CreateAndInitializeSegmenter<webm::MultiSegmentSegmenter>( CreateAndInitializeSegmenter<webm::MultiSegmentSegmenter>(
options, info_.get(), &segmenter_)); options, info_.get(), NULL, &segmenter_));
} }
scoped_refptr<StreamInfo> info_; scoped_refptr<StreamInfo> info_;

View File

@ -46,9 +46,12 @@ Status Segmenter::Initialize(scoped_ptr<MkvWriter> writer,
StreamInfo* info, StreamInfo* info,
ProgressListener* progress_listener, ProgressListener* progress_listener,
MuxerListener* muxer_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; muxer_listener_ = muxer_listener;
info_ = info; info_ = info;
clear_lead_ = clear_lead_in_seconds;
// Use media duration as progress target. // Use media duration as progress target.
progress_target_ = info_->duration(); progress_target_ = info_->duration();
@ -67,8 +70,14 @@ Status Segmenter::Initialize(scoped_ptr<MkvWriter> writer,
segment_info_.set_duration(1); segment_info_.set_duration(1);
} }
// Create the track info.
Status status; 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()) { switch (info_->stream_type()) {
case kStreamVideo: case kStreamVideo:
status = CreateVideoTrack(static_cast<VideoStreamInfo*>(info_)); status = CreateVideoTrack(static_cast<VideoStreamInfo*>(info_));
@ -120,6 +129,18 @@ Status Segmenter::AddSample(scoped_refptr<MediaSample> sample) {
if (!status.ok()) if (!status.ok())
return status; return status;
// Encrypt the frame.
if (encryptor_) {
const bool encrypt_frame =
static_cast<double>(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 = const int64_t time_ns =
sample->pts() * kSecondsToNs / info_->time_scale(); sample->pts() * kSecondsToNs / info_->time_scale();
bool addframe_result; bool addframe_result;
@ -258,6 +279,9 @@ Status Segmenter::CreateVideoTrack(VideoStreamInfo* info) {
track->set_display_width(info->width() * info->pixel_width() / track->set_display_width(info->width() * info->pixel_width() /
info->pixel_height()); info->pixel_height());
if (encryptor_)
encryptor_->AddTrackInfo(track);
tracks_.AddTrack(track, info->track_id()); tracks_.AddTrack(track, info->track_id());
track_id_ = track->number(); track_id_ = track->number();
return Status::OK; return Status::OK;
@ -292,11 +316,35 @@ Status Segmenter::CreateAudioTrack(AudioStreamInfo* info) {
track->set_sample_rate(info->sampling_frequency()); track->set_sample_rate(info->sampling_frequency());
track->set_channels(info->num_channels()); track->set_channels(info->num_channels());
if (encryptor_)
encryptor_->AddTrackInfo(track);
tracks_.AddTrack(track, info->track_id()); tracks_.AddTrack(track, info->track_id());
track_id_ = track->number(); track_id_ = track->number();
return Status::OK; 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<VideoStreamInfo*>(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 webm
} // namespace media } // namespace media
} // namespace edash_packager } // namespace edash_packager

View File

@ -10,6 +10,7 @@
#include "packager/base/memory/ref_counted.h" #include "packager/base/memory/ref_counted.h"
#include "packager/base/memory/scoped_ptr.h" #include "packager/base/memory/scoped_ptr.h"
#include "packager/media/base/status.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/mkv_writer.h"
#include "packager/media/formats/webm/seek_head.h" #include "packager/media/formats/webm/seek_head.h"
#include "packager/third_party/libwebm/src/mkvmuxer.hpp" #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 /// @param encryption_key_source points to the key source which contains
/// the encryption keys. It can be NULL to indicate that no encryption /// the encryption keys. It can be NULL to indicate that no encryption
/// is required. /// 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. /// @return OK on success, an error status otherwise.
Status Initialize(scoped_ptr<MkvWriter> writer, Status Initialize(scoped_ptr<MkvWriter> writer,
StreamInfo* info, StreamInfo* info,
ProgressListener* progress_listener, ProgressListener* progress_listener,
MuxerListener* muxer_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. /// Finalize the segmenter.
/// @return OK on success, an error status otherwise. /// @return OK on success, an error status otherwise.
@ -60,8 +67,6 @@ class Segmenter {
/// @return OK on success, an error status otherwise. /// @return OK on success, an error status otherwise.
Status AddSample(scoped_refptr<MediaSample> sample); Status AddSample(scoped_refptr<MediaSample> sample);
// TODO(modmaker): Add key source support.
/// @return true if there is an initialization range, while setting @a start /// @return true if there is an initialization range, while setting @a start
/// and @a end; or false if initialization range does not apply. /// and @a end; or false if initialization range does not apply.
virtual bool GetInitRangeStartAndEnd(uint32_t* start, uint32_t* end) = 0; virtual bool GetInitRangeStartAndEnd(uint32_t* start, uint32_t* end) = 0;
@ -107,6 +112,7 @@ class Segmenter {
private: private:
Status CreateVideoTrack(VideoStreamInfo* info); Status CreateVideoTrack(VideoStreamInfo* info);
Status CreateAudioTrack(AudioStreamInfo* 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 // 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 // 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); Status FinalizeSegment(uint64_t end_timescale);
const MuxerOptions& options_; const MuxerOptions& options_;
scoped_ptr<Encryptor> encryptor_;
double clear_lead_;
scoped_ptr<mkvmuxer::Cluster> cluster_; scoped_ptr<mkvmuxer::Cluster> cluster_;
mkvmuxer::Cues cues_; mkvmuxer::Cues cues_;

View File

@ -46,12 +46,16 @@ class SegmentTestBase : public ::testing::Test {
template <typename S> template <typename S>
void CreateAndInitializeSegmenter(const MuxerOptions& options, void CreateAndInitializeSegmenter(const MuxerOptions& options,
StreamInfo* info, StreamInfo* info,
KeySource* key_source,
scoped_ptr<webm::Segmenter>* result) const { scoped_ptr<webm::Segmenter>* result) const {
scoped_ptr<S> segmenter(new S(options)); scoped_ptr<S> segmenter(new S(options));
scoped_ptr<MkvWriter> writer(new MkvWriter()); scoped_ptr<MkvWriter> writer(new MkvWriter());
ASSERT_OK(writer->Open(options.output_file_name)); 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(); *result = segmenter.Pass();
} }

View File

@ -148,11 +148,11 @@ class SingleSegmentSegmenterTest : public SegmentTestBase,
if (!GetParam()) { if (!GetParam()) {
ASSERT_NO_FATAL_FAILURE( ASSERT_NO_FATAL_FAILURE(
CreateAndInitializeSegmenter<webm::SingleSegmentSegmenter>( CreateAndInitializeSegmenter<webm::SingleSegmentSegmenter>(
options, info_.get(), &segmenter_)); options, info_.get(), NULL, &segmenter_));
} else { } else {
ASSERT_NO_FATAL_FAILURE( ASSERT_NO_FATAL_FAILURE(
CreateAndInitializeSegmenter<webm::TwoPassSingleSegmentSegmenter>( CreateAndInitializeSegmenter<webm::TwoPassSingleSegmentSegmenter>(
options, info_.get(), &segmenter_)); options, info_.get(), NULL, &segmenter_));
} }
} }

View File

@ -13,6 +13,8 @@
'target_name': 'webm', 'target_name': 'webm',
'type': '<(component)', 'type': '<(component)',
'sources': [ 'sources': [
'encryptor.cc',
'encryptor.h',
'mkv_writer.cc', 'mkv_writer.cc',
'mkv_writer.h', 'mkv_writer.h',
'multi_segment_segmenter.cc', 'multi_segment_segmenter.cc',
@ -65,6 +67,7 @@
'sources': [ 'sources': [
'cluster_builder.cc', 'cluster_builder.cc',
'cluster_builder.h', 'cluster_builder.h',
'encrypted_segmenter_unittest.cc',
'multi_segment_segmenter_unittest.cc', 'multi_segment_segmenter_unittest.cc',
'opus_packet_builder.cc', 'opus_packet_builder.cc',
'opus_packet_builder.h', 'opus_packet_builder.h',

View File

@ -24,6 +24,12 @@ WebMMuxer::~WebMMuxer() {}
Status WebMMuxer::Initialize() { Status WebMMuxer::Initialize() {
CHECK_EQ(streams().size(), 1U); 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<MkvWriter> writer(new MkvWriter); scoped_ptr<MkvWriter> writer(new MkvWriter);
Status status = writer->Open(options().output_file_name); Status status = writer->Open(options().output_file_name);
if (!status.ok()) if (!status.ok())
@ -39,7 +45,8 @@ Status WebMMuxer::Initialize() {
Status initialized = segmenter_->Initialize( Status initialized = segmenter_->Initialize(
writer.Pass(), streams()[0]->info().get(), progress_listener(), 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()) if (!initialized.ok())
return initialized; return initialized;