Integrate EncryptionHandler
Change-Id: I639e5707994a6601ee02078cd6f77cb38b481585
This commit is contained in:
parent
16615095f0
commit
3d7635d5de
|
@ -35,6 +35,7 @@
|
|||
#include "packager/media/base/muxer_options.h"
|
||||
#include "packager/media/base/muxer_util.h"
|
||||
#include "packager/media/chunking/chunking_handler.h"
|
||||
#include "packager/media/crypto/encryption_handler.h"
|
||||
#include "packager/media/event/hls_notify_muxer_listener.h"
|
||||
#include "packager/media/event/mpd_notify_muxer_listener.h"
|
||||
#include "packager/media/event/vod_media_info_dump_muxer_listener.h"
|
||||
|
@ -137,21 +138,6 @@ std::string DetermineTextFileFormat(const std::string& file) {
|
|||
return "";
|
||||
}
|
||||
|
||||
FourCC GetProtectionScheme(const std::string& protection_scheme) {
|
||||
if (protection_scheme == "cenc") {
|
||||
return FOURCC_cenc;
|
||||
} else if (protection_scheme == "cens") {
|
||||
return FOURCC_cens;
|
||||
} else if (protection_scheme == "cbc1") {
|
||||
return FOURCC_cbc1;
|
||||
} else if (protection_scheme == "cbcs") {
|
||||
return FOURCC_cbcs;
|
||||
} else {
|
||||
LOG(ERROR) << "Unknown protection scheme: " << protection_scheme;
|
||||
return FOURCC_NULL;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// A fake clock that always return time 0 (epoch). Should only be used for
|
||||
|
@ -237,9 +223,10 @@ std::shared_ptr<Muxer> CreateOutputMuxer(const MuxerOptions& options,
|
|||
|
||||
bool CreateRemuxJobs(const StreamDescriptorList& stream_descriptors,
|
||||
const ChunkingOptions& chunking_options,
|
||||
const EncryptionOptions& encryption_options,
|
||||
const MuxerOptions& muxer_options,
|
||||
FakeClock* fake_clock,
|
||||
KeySource* key_source,
|
||||
KeySource* encryption_key_source,
|
||||
MpdNotifier* mpd_notifier,
|
||||
hls::HlsNotifier* hls_notifier,
|
||||
std::vector<std::unique_ptr<RemuxJob>>* remux_jobs) {
|
||||
|
@ -300,10 +287,11 @@ bool CreateRemuxJobs(const StreamDescriptorList& stream_descriptors,
|
|||
demuxer->set_dump_stream_info(FLAGS_dump_stream_info);
|
||||
if (FLAGS_enable_widevine_decryption ||
|
||||
FLAGS_enable_fixed_key_decryption) {
|
||||
std::unique_ptr<KeySource> key_source(CreateDecryptionKeySource());
|
||||
if (!key_source)
|
||||
std::unique_ptr<KeySource> decryption_key_source(
|
||||
CreateDecryptionKeySource());
|
||||
if (!decryption_key_source)
|
||||
return false;
|
||||
demuxer->SetKeySource(std::move(key_source));
|
||||
demuxer->SetKeySource(std::move(decryption_key_source));
|
||||
}
|
||||
remux_jobs->emplace_back(new RemuxJob(std::move(demuxer)));
|
||||
previous_input = stream_iter->input;
|
||||
|
@ -317,16 +305,6 @@ bool CreateRemuxJobs(const StreamDescriptorList& stream_descriptors,
|
|||
CreateOutputMuxer(stream_muxer_options, stream_iter->output_format));
|
||||
if (FLAGS_use_fake_clock_for_muxer) muxer->set_clock(fake_clock);
|
||||
|
||||
if (key_source) {
|
||||
muxer->SetKeySource(key_source,
|
||||
FLAGS_max_sd_pixels,
|
||||
FLAGS_max_hd_pixels,
|
||||
FLAGS_max_uhd1_pixels,
|
||||
FLAGS_clear_lead,
|
||||
FLAGS_crypto_period_duration,
|
||||
GetProtectionScheme(FLAGS_protection_scheme));
|
||||
}
|
||||
|
||||
std::unique_ptr<MuxerListener> muxer_listener;
|
||||
DCHECK(!(FLAGS_output_media_info && mpd_notifier));
|
||||
if (FLAGS_output_media_info) {
|
||||
|
@ -363,8 +341,26 @@ bool CreateRemuxJobs(const StreamDescriptorList& stream_descriptors,
|
|||
if (muxer_listener)
|
||||
muxer->SetMuxerListener(std::move(muxer_listener));
|
||||
|
||||
Status status;
|
||||
auto chunking_handler = std::make_shared<ChunkingHandler>(chunking_options);
|
||||
Status status = chunking_handler->SetHandler(0, std::move(muxer));
|
||||
if (encryption_key_source) {
|
||||
auto new_encryption_options = encryption_options;
|
||||
// Use Sample AES in MPEG2TS.
|
||||
// TODO(kqyang): Consider adding a new flag to enable Sample AES as we
|
||||
// will support CENC in TS in the future.
|
||||
if (stream_iter->output_format == CONTAINER_MPEG2TS) {
|
||||
LOG(INFO) << "Use Apple Sample AES encryption for MPEG2TS.";
|
||||
new_encryption_options.protection_scheme =
|
||||
kAppleSampleAesProtectionScheme;
|
||||
}
|
||||
auto encryption_handler = std::make_shared<EncryptionHandler>(
|
||||
new_encryption_options, encryption_key_source);
|
||||
status.Update(encryption_handler->SetHandler(0, std::move(muxer)));
|
||||
status.Update(
|
||||
chunking_handler->SetHandler(0, std::move(encryption_handler)));
|
||||
} else {
|
||||
status.Update(chunking_handler->SetHandler(0, std::move(muxer)));
|
||||
}
|
||||
|
||||
auto* demuxer = remux_jobs->back()->demuxer();
|
||||
const std::string& stream_selector = stream_iter->stream_selector;
|
||||
|
@ -415,10 +411,6 @@ Status RunRemuxJobs(const std::vector<std::unique_ptr<RemuxJob>>& remux_jobs) {
|
|||
}
|
||||
|
||||
bool RunPackager(const StreamDescriptorList& stream_descriptors) {
|
||||
const FourCC protection_scheme = GetProtectionScheme(FLAGS_protection_scheme);
|
||||
if (protection_scheme == FOURCC_NULL)
|
||||
return false;
|
||||
|
||||
if (FLAGS_output_media_info && !FLAGS_mpd_output.empty()) {
|
||||
NOTIMPLEMENTED() << "ERROR: --output_media_info and --mpd_output do not "
|
||||
"work together.";
|
||||
|
@ -433,6 +425,8 @@ bool RunPackager(const StreamDescriptorList& stream_descriptors) {
|
|||
}
|
||||
|
||||
ChunkingOptions chunking_options = GetChunkingOptions();
|
||||
EncryptionOptions encryption_options = GetEncryptionOptions();
|
||||
|
||||
MuxerOptions muxer_options = GetMuxerOptions();
|
||||
|
||||
DCHECK(!stream_descriptors.empty());
|
||||
|
@ -461,6 +455,8 @@ bool RunPackager(const StreamDescriptorList& stream_descriptors) {
|
|||
std::unique_ptr<KeySource> encryption_key_source;
|
||||
if (FLAGS_enable_widevine_encryption || FLAGS_enable_fixed_key_encryption ||
|
||||
FLAGS_enable_playready_encryption) {
|
||||
if (encryption_options.protection_scheme == FOURCC_NULL)
|
||||
return false;
|
||||
encryption_key_source = CreateEncryptionKeySource();
|
||||
if (!encryption_key_source)
|
||||
return false;
|
||||
|
@ -497,8 +493,8 @@ bool RunPackager(const StreamDescriptorList& stream_descriptors) {
|
|||
|
||||
std::vector<std::unique_ptr<RemuxJob>> remux_jobs;
|
||||
FakeClock fake_clock;
|
||||
if (!CreateRemuxJobs(stream_descriptors, chunking_options, muxer_options,
|
||||
&fake_clock, encryption_key_source.get(),
|
||||
if (!CreateRemuxJobs(stream_descriptors, chunking_options, encryption_options,
|
||||
muxer_options, &fake_clock, encryption_key_source.get(),
|
||||
mpd_notifier.get(), hls_notifier.get(), &remux_jobs)) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#include "packager/media/base/request_signer.h"
|
||||
#include "packager/media/base/widevine_key_source.h"
|
||||
#include "packager/media/chunking/chunking_handler.h"
|
||||
#include "packager/media/crypto/encryption_handler.h"
|
||||
#include "packager/media/file/file.h"
|
||||
#include "packager/mpd/base/mpd_options.h"
|
||||
|
||||
|
@ -35,6 +36,24 @@ DEFINE_bool(dump_stream_info, false, "Dump demuxed stream info.");
|
|||
|
||||
namespace shaka {
|
||||
namespace media {
|
||||
namespace {
|
||||
|
||||
FourCC GetProtectionScheme(const std::string& protection_scheme) {
|
||||
if (protection_scheme == "cenc") {
|
||||
return FOURCC_cenc;
|
||||
} else if (protection_scheme == "cens") {
|
||||
return FOURCC_cens;
|
||||
} else if (protection_scheme == "cbc1") {
|
||||
return FOURCC_cbc1;
|
||||
} else if (protection_scheme == "cbcs") {
|
||||
return FOURCC_cbcs;
|
||||
} else {
|
||||
LOG(ERROR) << "Unknown protection scheme: " << protection_scheme;
|
||||
return FOURCC_NULL;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<RequestSigner> CreateSigner() {
|
||||
std::unique_ptr<RequestSigner> signer;
|
||||
|
@ -156,6 +175,19 @@ ChunkingOptions GetChunkingOptions() {
|
|||
return chunking_options;
|
||||
}
|
||||
|
||||
EncryptionOptions GetEncryptionOptions() {
|
||||
EncryptionOptions encryption_options;
|
||||
encryption_options.clear_lead_in_seconds = FLAGS_clear_lead;
|
||||
encryption_options.protection_scheme =
|
||||
GetProtectionScheme(FLAGS_protection_scheme);
|
||||
encryption_options.max_sd_pixels = FLAGS_max_sd_pixels;
|
||||
encryption_options.max_hd_pixels = FLAGS_max_hd_pixels;
|
||||
encryption_options.max_uhd1_pixels = FLAGS_max_uhd1_pixels;
|
||||
encryption_options.crypto_period_duration_in_seconds =
|
||||
FLAGS_crypto_period_duration;
|
||||
return encryption_options;
|
||||
}
|
||||
|
||||
MuxerOptions GetMuxerOptions() {
|
||||
MuxerOptions muxer_options;
|
||||
muxer_options.num_subsegments_per_sidx = FLAGS_num_subsegments_per_sidx;
|
||||
|
|
|
@ -23,6 +23,7 @@ namespace media {
|
|||
|
||||
class KeySource;
|
||||
struct ChunkingOptions;
|
||||
struct EncryptionOptions;
|
||||
struct MuxerOptions;
|
||||
|
||||
/// Create KeySource based on provided command line options for content
|
||||
|
@ -40,6 +41,9 @@ std::unique_ptr<KeySource> CreateDecryptionKeySource();
|
|||
/// @return ChunkingOptions from provided command line options.
|
||||
ChunkingOptions GetChunkingOptions();
|
||||
|
||||
/// @return EncryptionOptions from provided command line options.
|
||||
EncryptionOptions GetEncryptionOptions();
|
||||
|
||||
/// @return MuxerOptions from provided command line options.
|
||||
MuxerOptions GetMuxerOptions();
|
||||
|
||||
|
|
|
@ -8,43 +8,19 @@
|
|||
|
||||
#include <algorithm>
|
||||
|
||||
#include "packager/media/base/fourccs.h"
|
||||
#include "packager/media/base/media_sample.h"
|
||||
|
||||
namespace shaka {
|
||||
namespace media {
|
||||
namespace {
|
||||
const bool kInitialEncryptionInfo = true;
|
||||
} // namespace
|
||||
|
||||
Muxer::Muxer(const MuxerOptions& options)
|
||||
: options_(options),
|
||||
encryption_key_source_(NULL),
|
||||
max_sd_pixels_(0),
|
||||
max_hd_pixels_(0),
|
||||
max_uhd1_pixels_(0),
|
||||
clear_lead_in_seconds_(0),
|
||||
crypto_period_duration_in_seconds_(0),
|
||||
protection_scheme_(FOURCC_NULL),
|
||||
cancelled_(false),
|
||||
clock_(NULL) {}
|
||||
: options_(options), cancelled_(false), clock_(NULL) {}
|
||||
|
||||
Muxer::~Muxer() {}
|
||||
|
||||
void Muxer::SetKeySource(KeySource* encryption_key_source,
|
||||
uint32_t max_sd_pixels,
|
||||
uint32_t max_hd_pixels,
|
||||
uint32_t max_uhd1_pixels,
|
||||
double clear_lead_in_seconds,
|
||||
double crypto_period_duration_in_seconds,
|
||||
FourCC protection_scheme) {
|
||||
DCHECK(encryption_key_source);
|
||||
encryption_key_source_ = encryption_key_source;
|
||||
max_sd_pixels_ = max_sd_pixels;
|
||||
max_hd_pixels_ = max_hd_pixels;
|
||||
max_uhd1_pixels_ = max_uhd1_pixels;
|
||||
clear_lead_in_seconds_ = clear_lead_in_seconds;
|
||||
crypto_period_duration_in_seconds_ = crypto_period_duration_in_seconds;
|
||||
protection_scheme_ = protection_scheme;
|
||||
}
|
||||
|
||||
void Muxer::Cancel() {
|
||||
cancelled_ = true;
|
||||
}
|
||||
|
@ -63,10 +39,34 @@ Status Muxer::Process(std::unique_ptr<StreamData> stream_data) {
|
|||
switch (stream_data->stream_data_type) {
|
||||
case StreamDataType::kStreamInfo:
|
||||
streams_.push_back(std::move(stream_data->stream_info));
|
||||
if (muxer_listener_ && streams_.back()->is_encrypted()) {
|
||||
const EncryptionConfig& encryption_config =
|
||||
streams_.back()->encryption_config();
|
||||
muxer_listener_->OnEncryptionInfoReady(
|
||||
kInitialEncryptionInfo, encryption_config.protection_scheme,
|
||||
encryption_config.key_id, encryption_config.constant_iv,
|
||||
encryption_config.key_system_info);
|
||||
}
|
||||
return InitializeMuxer();
|
||||
case StreamDataType::kSegmentInfo:
|
||||
case StreamDataType::kSegmentInfo: {
|
||||
auto& segment_info = stream_data->segment_info;
|
||||
if (muxer_listener_) {
|
||||
const EncryptionConfig* encryption_config =
|
||||
segment_info->key_rotation_encryption_config.get();
|
||||
if (encryption_config) {
|
||||
muxer_listener_->OnEncryptionInfoReady(
|
||||
!kInitialEncryptionInfo, encryption_config->protection_scheme,
|
||||
encryption_config->key_id, encryption_config->constant_iv,
|
||||
encryption_config->key_system_info);
|
||||
}
|
||||
if (segment_info->is_encrypted && !encryption_started_) {
|
||||
encryption_started_ = true;
|
||||
muxer_listener_->OnEncryptionStart();
|
||||
}
|
||||
}
|
||||
return FinalizeSegment(stream_data->stream_index,
|
||||
std::move(stream_data->segment_info));
|
||||
std::move(segment_info));
|
||||
}
|
||||
case StreamDataType::kMediaSample:
|
||||
return AddSample(stream_data->stream_index,
|
||||
std::move(stream_data->media_sample));
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
#include <vector>
|
||||
|
||||
#include "packager/base/time/clock.h"
|
||||
#include "packager/media/base/fourccs.h"
|
||||
#include "packager/media/base/media_handler.h"
|
||||
#include "packager/media/base/muxer_options.h"
|
||||
#include "packager/media/base/status.h"
|
||||
|
@ -23,9 +22,7 @@
|
|||
namespace shaka {
|
||||
namespace media {
|
||||
|
||||
class KeySource;
|
||||
class MediaSample;
|
||||
class MediaStream;
|
||||
|
||||
/// Muxer is responsible for taking elementary stream samples and producing
|
||||
/// media containers. An optional KeySource can be provided to Muxer
|
||||
|
@ -35,37 +32,6 @@ class Muxer : public MediaHandler {
|
|||
explicit Muxer(const MuxerOptions& options);
|
||||
virtual ~Muxer();
|
||||
|
||||
// TODO(kqyang): refactor max_sd_pixels through crypto_period_duration into
|
||||
// an encapsulated EncryptionParams structure.
|
||||
|
||||
/// Set encryption key source.
|
||||
/// @param encryption_key_source points to the encryption key source. The
|
||||
/// caller retains ownership, and should not be NULL.
|
||||
/// @param max_sd_pixels specifies the threshold to determine whether a video
|
||||
/// track should be considered as SD. If the max pixels per frame is
|
||||
/// no higher than max_sd_pixels, it is SD.
|
||||
/// @param max_hd_pixels specifies the threshold to determine whether a video
|
||||
/// track should be considered as HD. If the max pixels per frame is
|
||||
/// higher than max_sd_pixels, but no higher than max_hd_pixels,
|
||||
/// it is HD.
|
||||
/// @param max_uhd1_pixels specifies the threshold to determine whether a video
|
||||
/// track should be considered as UHD1. If the max pixels per frame is
|
||||
/// higher than max_hd_pixels, but no higher than max_uhd1_pixels,
|
||||
/// it is UHD1. Otherwise it is UHD2.
|
||||
/// @param clear_lead_in_seconds specifies clear lead duration in seconds.
|
||||
/// @param crypto_period_duration_in_seconds specifies crypto period duration
|
||||
/// in seconds. A positive value means key rotation is enabled, the
|
||||
/// key source must support key rotation in this case.
|
||||
/// @param protection_scheme specifies the protection scheme: 'cenc', 'cens',
|
||||
/// 'cbc1', 'cbcs'.
|
||||
void SetKeySource(KeySource* encryption_key_source,
|
||||
uint32_t max_sd_pixels,
|
||||
uint32_t max_hd_pixels,
|
||||
uint32_t max_uhd1_pixels,
|
||||
double clear_lead_in_seconds,
|
||||
double crypto_period_duration_in_seconds,
|
||||
FourCC protection_scheme);
|
||||
|
||||
/// Cancel a muxing job in progress. Will cause @a Run to exit with an error
|
||||
/// status of type CANCELLED.
|
||||
void Cancel();
|
||||
|
@ -101,20 +67,9 @@ class Muxer : public MediaHandler {
|
|||
/// @}
|
||||
|
||||
const MuxerOptions& options() const { return options_; }
|
||||
KeySource* encryption_key_source() {
|
||||
return encryption_key_source_;
|
||||
}
|
||||
uint32_t max_sd_pixels() const { return max_sd_pixels_; }
|
||||
uint32_t max_hd_pixels() const { return max_hd_pixels_; }
|
||||
uint32_t max_uhd1_pixels() const { return max_uhd1_pixels_; }
|
||||
double clear_lead_in_seconds() const { return clear_lead_in_seconds_; }
|
||||
double crypto_period_duration_in_seconds() const {
|
||||
return crypto_period_duration_in_seconds_;
|
||||
}
|
||||
MuxerListener* muxer_listener() { return muxer_listener_.get(); }
|
||||
ProgressListener* progress_listener() { return progress_listener_.get(); }
|
||||
base::Clock* clock() { return clock_; }
|
||||
FourCC protection_scheme() const { return protection_scheme_; }
|
||||
|
||||
private:
|
||||
// Initialize the muxer.
|
||||
|
@ -133,13 +88,7 @@ class Muxer : public MediaHandler {
|
|||
|
||||
MuxerOptions options_;
|
||||
std::vector<std::shared_ptr<StreamInfo>> streams_;
|
||||
KeySource* encryption_key_source_;
|
||||
uint32_t max_sd_pixels_;
|
||||
uint32_t max_hd_pixels_;
|
||||
uint32_t max_uhd1_pixels_;
|
||||
double clear_lead_in_seconds_;
|
||||
double crypto_period_duration_in_seconds_;
|
||||
FourCC protection_scheme_;
|
||||
bool encryption_started_ = false;
|
||||
bool cancelled_;
|
||||
|
||||
std::unique_ptr<MuxerListener> muxer_listener_;
|
||||
|
|
|
@ -164,7 +164,6 @@ bool NalUnitToByteStreamConverter::ConvertUnitToByteStream(
|
|||
size_t sample_size,
|
||||
bool is_key_frame,
|
||||
std::vector<uint8_t>* output) {
|
||||
LOG(INFO) << "ConvertUnitToByte";
|
||||
return ConvertUnitToByteStreamWithSubsamples(
|
||||
sample, sample_size, is_key_frame, false, output,
|
||||
nullptr); // Skip subsample update.
|
||||
|
|
|
@ -55,6 +55,7 @@
|
|||
],
|
||||
'dependencies': [
|
||||
'../../base/media_base.gyp:media_base',
|
||||
'../../crypto/crypto.gyp:crypto',
|
||||
'../../codecs/codecs.gyp:codecs',
|
||||
],
|
||||
},
|
||||
|
|
|
@ -10,8 +10,6 @@
|
|||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
#include "packager/media/base/aes_encryptor.h"
|
||||
#include "packager/media/base/aes_pattern_cryptor.h"
|
||||
#include "packager/media/base/audio_stream_info.h"
|
||||
#include "packager/media/base/buffer_writer.h"
|
||||
#include "packager/media/base/media_sample.h"
|
||||
|
@ -29,61 +27,6 @@ namespace {
|
|||
const uint8_t kVideoStreamId = 0xE0;
|
||||
const uint8_t kAudioStreamId = 0xC0;
|
||||
const double kTsTimescale = 90000.0;
|
||||
|
||||
// |target_data| is input as well as output. On success |target_data| contains
|
||||
// the encrypted sample. The input data should be Nal unit byte stream.
|
||||
// This function constructs encrypted sample in |encrypted_sample_data| then
|
||||
// swap with target_data on success.
|
||||
bool EncryptH264Sample(AesCryptor* encryptor,
|
||||
std::vector<uint8_t>* target_data) {
|
||||
BufferWriter encrypted_sample_data(target_data->size() * 1.5);
|
||||
|
||||
const int kLeadingClearBytesSize = 32;
|
||||
// Any Nalu smaller than 48 bytes shall not be encrypted.
|
||||
const uint64_t kSmallNalUnitSize = 48;
|
||||
|
||||
NaluReader nalu_reader(Nalu::kH264, 0, target_data->data(),
|
||||
target_data->size());
|
||||
NaluReader::Result result;
|
||||
Nalu nalu;
|
||||
while ((result = nalu_reader.Advance(&nalu)) == NaluReader::Result::kOk) {
|
||||
encrypted_sample_data.AppendInt(static_cast<uint32_t>(0x00000001));
|
||||
const uint64_t nalu_total_size = nalu.header_size() + nalu.payload_size();
|
||||
if (nalu.type() != Nalu::H264NaluType::H264_NonIDRSlice &&
|
||||
nalu.type() != Nalu::H264NaluType::H264_IDRSlice) {
|
||||
VLOG(3) << "Found Nalu type: " << nalu.type() << " skipping encryption.";
|
||||
encrypted_sample_data.AppendArray(nalu.data(), nalu_total_size);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (nalu_total_size <= kSmallNalUnitSize) {
|
||||
encrypted_sample_data.AppendArray(nalu.data(), nalu_total_size);
|
||||
continue;
|
||||
}
|
||||
|
||||
const uint8_t* current = nalu.data() + kLeadingClearBytesSize;
|
||||
const uint64_t bytes_remaining = nalu_total_size - kLeadingClearBytesSize;
|
||||
|
||||
if (!encryptor->Crypt(current, bytes_remaining,
|
||||
const_cast<uint8_t*>(current))) {
|
||||
return false;
|
||||
}
|
||||
EscapeNalByteSequence(nalu.data(), nalu_total_size, &encrypted_sample_data);
|
||||
}
|
||||
|
||||
encrypted_sample_data.SwapBuffer(target_data);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EncryptAacSample(AesCryptor* encryptor,
|
||||
std::vector<uint8_t>* target_data) {
|
||||
const int kUnencryptedLeaderSize = 16;
|
||||
if (target_data->size() <= kUnencryptedLeaderSize)
|
||||
return true;
|
||||
uint8_t* data_ptr = target_data->data() + kUnencryptedLeaderSize;
|
||||
return encryptor->Crypt(
|
||||
data_ptr, target_data->size() - kUnencryptedLeaderSize, data_ptr);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
PesPacketGenerator::PesPacketGenerator() {}
|
||||
|
@ -130,20 +73,18 @@ bool PesPacketGenerator::PushSample(std::shared_ptr<MediaSample> sample) {
|
|||
current_processing_pes_->set_dts(timescale_scale_ * sample->dts());
|
||||
if (stream_type_ == kStreamVideo) {
|
||||
DCHECK(converter_);
|
||||
std::vector<SubsampleEntry> subsamples;
|
||||
if (sample->decrypt_config())
|
||||
subsamples = sample->decrypt_config()->subsamples();
|
||||
const bool kEscapeEncryptedNalu = true;
|
||||
std::vector<uint8_t> byte_stream;
|
||||
if (!converter_->ConvertUnitToByteStream(
|
||||
if (!converter_->ConvertUnitToByteStreamWithSubsamples(
|
||||
sample->data(), sample->data_size(), sample->is_key_frame(),
|
||||
&byte_stream)) {
|
||||
kEscapeEncryptedNalu, &byte_stream, &subsamples)) {
|
||||
LOG(ERROR) << "Failed to convert sample to byte stream.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (encryptor_) {
|
||||
if (!EncryptH264Sample(encryptor_.get(), &byte_stream)) {
|
||||
LOG(ERROR) << "Failed to encrypt byte stream.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
current_processing_pes_->mutable_data()->swap(byte_stream);
|
||||
current_processing_pes_->set_stream_id(kVideoStreamId);
|
||||
pes_packets_.push_back(std::move(current_processing_pes_));
|
||||
|
@ -155,13 +96,6 @@ bool PesPacketGenerator::PushSample(std::shared_ptr<MediaSample> sample) {
|
|||
std::vector<uint8_t> aac_frame(sample->data(),
|
||||
sample->data() + sample->data_size());
|
||||
|
||||
if (encryptor_) {
|
||||
if (!EncryptAacSample(encryptor_.get(), &aac_frame)) {
|
||||
LOG(ERROR) << "Failed to encrypt ADTS AAC.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(rkuroiwa): ConvertToADTS() makes another copy of aac_frame internally.
|
||||
// Optimize copying in this function, possibly by adding a method on
|
||||
// AACAudioSpecificConfig that takes {pointer, length} pair and returns a
|
||||
|
@ -177,30 +111,6 @@ bool PesPacketGenerator::PushSample(std::shared_ptr<MediaSample> sample) {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool PesPacketGenerator::SetEncryptionKey(
|
||||
std::unique_ptr<EncryptionKey> encryption_key) {
|
||||
if (stream_type_ == kStreamVideo) {
|
||||
std::unique_ptr<AesCbcEncryptor> cbc(
|
||||
new AesCbcEncryptor(CbcPaddingScheme::kNoPadding));
|
||||
|
||||
const uint8_t kEncryptedBlocks = 1;
|
||||
const uint8_t kClearBlocks = 9;
|
||||
encryptor_.reset(new AesPatternCryptor(
|
||||
kEncryptedBlocks, kClearBlocks,
|
||||
AesPatternCryptor::kSkipIfCryptByteBlockRemaining,
|
||||
AesCryptor::ConstantIvFlag::kUseConstantIv, std::move(cbc)));
|
||||
} else if (stream_type_ == kStreamAudio) {
|
||||
encryptor_.reset(
|
||||
new AesCbcEncryptor(CbcPaddingScheme::kNoPadding,
|
||||
AesCryptor::ConstantIvFlag::kUseConstantIv));
|
||||
} else {
|
||||
LOG(ERROR) << "Cannot encrypt stream type: " << stream_type_;
|
||||
return false;
|
||||
}
|
||||
|
||||
return encryptor_->InitializeWithIv(encryption_key->key, encryption_key->iv);
|
||||
}
|
||||
|
||||
size_t PesPacketGenerator::NumberOfReadyPesPackets() {
|
||||
return pes_packets_.size();
|
||||
}
|
||||
|
|
|
@ -10,8 +10,6 @@
|
|||
#include <list>
|
||||
#include <memory>
|
||||
|
||||
#include "packager/media/base/aes_cryptor.h"
|
||||
#include "packager/media/base/key_source.h"
|
||||
#include "packager/media/base/media_sample.h"
|
||||
#include "packager/media/base/stream_info.h"
|
||||
|
||||
|
@ -46,12 +44,6 @@ class PesPacketGenerator {
|
|||
/// @return true on success, false otherwise.
|
||||
virtual bool PushSample(std::shared_ptr<MediaSample> sample);
|
||||
|
||||
/// Sets the encryption key for encrypting samples.
|
||||
/// @param encryption_key is the key that will be used to encrypt further
|
||||
/// samples.
|
||||
/// @return true on success, false otherwise.
|
||||
virtual bool SetEncryptionKey(std::unique_ptr<EncryptionKey> encryption_key);
|
||||
|
||||
/// @return The number of PES packets that are ready to be consumed.
|
||||
virtual size_t NumberOfReadyPesPackets();
|
||||
|
||||
|
@ -83,9 +75,6 @@ class PesPacketGenerator {
|
|||
|
||||
std::list<std::unique_ptr<PesPacket>> pes_packets_;
|
||||
|
||||
// Current encryption key.
|
||||
std::unique_ptr<AesCryptor> encryptor_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(PesPacketGenerator);
|
||||
};
|
||||
|
||||
|
|
|
@ -18,12 +18,21 @@
|
|||
|
||||
namespace shaka {
|
||||
namespace media {
|
||||
|
||||
inline bool operator==(const SubsampleEntry& lhs, const SubsampleEntry& rhs) {
|
||||
return lhs.clear_bytes == rhs.clear_bytes &&
|
||||
lhs.cipher_bytes == rhs.cipher_bytes;
|
||||
}
|
||||
|
||||
namespace mp2t {
|
||||
|
||||
using ::testing::_;
|
||||
using ::testing::DoAll;
|
||||
using ::testing::SetArgPointee;
|
||||
using ::testing::Eq;
|
||||
using ::testing::IsEmpty;
|
||||
using ::testing::Pointee;
|
||||
using ::testing::Return;
|
||||
using ::testing::SetArgPointee;
|
||||
|
||||
namespace {
|
||||
|
||||
|
@ -33,6 +42,7 @@ const uint8_t kAnyData[] = {
|
|||
};
|
||||
|
||||
const bool kIsKeyFrame = true;
|
||||
const bool kEscapeEncryptedNalu = true;
|
||||
|
||||
// Only Codec and extra data matter for this test. Other values are
|
||||
// bogus.
|
||||
|
@ -88,11 +98,13 @@ class MockNalUnitToByteStreamConverter : public NalUnitToByteStreamConverter {
|
|||
MOCK_METHOD2(Initialize,
|
||||
bool(const uint8_t* decoder_configuration_data,
|
||||
size_t decoder_configuration_data_size));
|
||||
MOCK_METHOD4(ConvertUnitToByteStream,
|
||||
MOCK_METHOD6(ConvertUnitToByteStreamWithSubsamples,
|
||||
bool(const uint8_t* sample,
|
||||
size_t sample_size,
|
||||
bool is_key_frame,
|
||||
std::vector<uint8_t>* output));
|
||||
bool escape_encrypted_nalu,
|
||||
std::vector<uint8_t>* output,
|
||||
std::vector<SubsampleEntry>* subsamples));
|
||||
};
|
||||
|
||||
class MockAACAudioSpecificConfig : public AACAudioSpecificConfig {
|
||||
|
@ -133,91 +145,6 @@ class PesPacketGeneratorTest : public ::testing::Test {
|
|||
generator_.adts_converter_ = std::move(mock);
|
||||
}
|
||||
|
||||
void H264EncryptionTest(const uint8_t* input,
|
||||
size_t input_size,
|
||||
const uint8_t* expected_output,
|
||||
size_t expected_output_size) {
|
||||
std::shared_ptr<VideoStreamInfo> stream_info(
|
||||
CreateVideoStreamInfo(kH264Codec));
|
||||
EXPECT_TRUE(generator_.Initialize(*stream_info));
|
||||
EXPECT_EQ(0u, generator_.NumberOfReadyPesPackets());
|
||||
|
||||
std::shared_ptr<MediaSample> sample =
|
||||
MediaSample::CopyFrom(input, input_size, kIsKeyFrame);
|
||||
const uint32_t kPts = 12345;
|
||||
const uint32_t kDts = 12300;
|
||||
sample->set_pts(kPts);
|
||||
sample->set_dts(kDts);
|
||||
|
||||
std::unique_ptr<MockNalUnitToByteStreamConverter> mock(
|
||||
new MockNalUnitToByteStreamConverter());
|
||||
|
||||
// Returning only the input data so that it doesn't have all the unnecessary
|
||||
// NALUs to test encryption.
|
||||
std::vector<uint8_t> clear_data(input, input + input_size);
|
||||
EXPECT_CALL(*mock, ConvertUnitToByteStream(_, input_size, kIsKeyFrame, _))
|
||||
.WillOnce(DoAll(SetArgPointee<3>(clear_data), Return(true)));
|
||||
|
||||
UseMockNalUnitToByteStreamConverter(std::move(mock));
|
||||
|
||||
const std::vector<uint8_t> all_zero(16, 0);
|
||||
std::unique_ptr<EncryptionKey> encryption_key(new EncryptionKey());
|
||||
encryption_key->key = all_zero;
|
||||
encryption_key->iv = all_zero;
|
||||
EXPECT_TRUE(generator_.SetEncryptionKey(std::move(encryption_key)));
|
||||
|
||||
EXPECT_TRUE(generator_.PushSample(sample));
|
||||
EXPECT_EQ(1u, generator_.NumberOfReadyPesPackets());
|
||||
std::unique_ptr<PesPacket> pes_packet = generator_.GetNextPesPacket();
|
||||
ASSERT_TRUE(pes_packet);
|
||||
|
||||
std::vector<uint8_t> expected(expected_output,
|
||||
expected_output + expected_output_size);
|
||||
ASSERT_EQ(expected.size(), pes_packet->data().size());
|
||||
for (size_t i = 0; i < expected.size(); ++i) {
|
||||
EXPECT_EQ(expected[i], pes_packet->data()[i]) << " mismatch at " << i;
|
||||
}
|
||||
//EXPECT_EQ(expected, pes_packet->data());
|
||||
}
|
||||
|
||||
// The input data should be the size of an aac frame, i.e. should not be the
|
||||
// size of an ADTS frame.
|
||||
void AacEncryptionTest(const uint8_t* input,
|
||||
size_t input_size,
|
||||
const uint8_t* expected_output,
|
||||
size_t expected_output_size) {
|
||||
std::shared_ptr<AudioStreamInfo> stream_info(
|
||||
CreateAudioStreamInfo(kAacCodec));
|
||||
EXPECT_TRUE(generator_.Initialize(*stream_info));
|
||||
EXPECT_EQ(0u, generator_.NumberOfReadyPesPackets());
|
||||
|
||||
// For aac, the input from MediaSample is used. Then ADTS header is added,
|
||||
// so EXPECT_CALL does not return the |input| data.
|
||||
std::shared_ptr<MediaSample> sample =
|
||||
MediaSample::CopyFrom(input, input_size, kIsKeyFrame);
|
||||
|
||||
std::unique_ptr<MockAACAudioSpecificConfig> mock(
|
||||
new MockAACAudioSpecificConfig());
|
||||
EXPECT_CALL(*mock, ConvertToADTS(_)).WillOnce(Return(true));
|
||||
|
||||
UseMockAACAudioSpecificConfig(std::move(mock));
|
||||
|
||||
const std::vector<uint8_t> all_zero(16, 0);
|
||||
std::unique_ptr<EncryptionKey> encryption_key(new EncryptionKey());
|
||||
encryption_key->key = all_zero;
|
||||
encryption_key->iv = all_zero;
|
||||
EXPECT_TRUE(generator_.SetEncryptionKey(std::move(encryption_key)));
|
||||
|
||||
EXPECT_TRUE(generator_.PushSample(sample));
|
||||
EXPECT_EQ(1u, generator_.NumberOfReadyPesPackets());
|
||||
std::unique_ptr<PesPacket> pes_packet = generator_.GetNextPesPacket();
|
||||
ASSERT_TRUE(pes_packet);
|
||||
|
||||
std::vector<uint8_t> expected(expected_output,
|
||||
expected_output + expected_output_size);
|
||||
EXPECT_EQ(expected, pes_packet->data());
|
||||
}
|
||||
|
||||
PesPacketGenerator generator_;
|
||||
};
|
||||
|
||||
|
@ -270,9 +197,57 @@ TEST_F(PesPacketGeneratorTest, AddVideoSample) {
|
|||
|
||||
std::unique_ptr<MockNalUnitToByteStreamConverter> mock(
|
||||
new MockNalUnitToByteStreamConverter());
|
||||
EXPECT_CALL(*mock,
|
||||
ConvertUnitToByteStream(_, arraysize(kAnyData), kIsKeyFrame, _))
|
||||
.WillOnce(DoAll(SetArgPointee<3>(expected_data), Return(true)));
|
||||
EXPECT_CALL(*mock, ConvertUnitToByteStreamWithSubsamples(
|
||||
_, arraysize(kAnyData), kIsKeyFrame,
|
||||
kEscapeEncryptedNalu, _, Pointee(IsEmpty())))
|
||||
.WillOnce(DoAll(SetArgPointee<4>(expected_data), Return(true)));
|
||||
|
||||
UseMockNalUnitToByteStreamConverter(std::move(mock));
|
||||
|
||||
EXPECT_TRUE(generator_.PushSample(sample));
|
||||
EXPECT_EQ(1u, generator_.NumberOfReadyPesPackets());
|
||||
std::unique_ptr<PesPacket> pes_packet = generator_.GetNextPesPacket();
|
||||
ASSERT_TRUE(pes_packet);
|
||||
EXPECT_EQ(0u, generator_.NumberOfReadyPesPackets());
|
||||
|
||||
EXPECT_EQ(0xe0, pes_packet->stream_id());
|
||||
EXPECT_EQ(kPts, pes_packet->pts());
|
||||
EXPECT_EQ(kDts, pes_packet->dts());
|
||||
EXPECT_EQ(expected_data, pes_packet->data());
|
||||
|
||||
EXPECT_TRUE(generator_.Flush());
|
||||
}
|
||||
|
||||
TEST_F(PesPacketGeneratorTest, AddEncryptedVideoSample) {
|
||||
std::shared_ptr<VideoStreamInfo> stream_info(
|
||||
CreateVideoStreamInfo(kH264Codec));
|
||||
EXPECT_TRUE(generator_.Initialize(*stream_info));
|
||||
EXPECT_EQ(0u, generator_.NumberOfReadyPesPackets());
|
||||
|
||||
std::shared_ptr<MediaSample> sample =
|
||||
MediaSample::CopyFrom(kAnyData, arraysize(kAnyData), kIsKeyFrame);
|
||||
const uint32_t kPts = 12345;
|
||||
const uint32_t kDts = 12300;
|
||||
sample->set_pts(kPts);
|
||||
sample->set_dts(kDts);
|
||||
|
||||
const std::vector<uint8_t> key_id(16, 0);
|
||||
const std::vector<uint8_t> iv(8, 0);
|
||||
const std::vector<SubsampleEntry> subsamples = {
|
||||
SubsampleEntry(0x12, 0x110), SubsampleEntry(0x122, 0x11000)};
|
||||
std::unique_ptr<DecryptConfig> decrypt_config(
|
||||
new DecryptConfig(key_id, iv, subsamples));
|
||||
sample->set_is_encrypted(true);
|
||||
sample->set_decrypt_config(std::move(decrypt_config));
|
||||
|
||||
std::vector<uint8_t> expected_data(kAnyData, kAnyData + arraysize(kAnyData));
|
||||
|
||||
std::unique_ptr<MockNalUnitToByteStreamConverter> mock(
|
||||
new MockNalUnitToByteStreamConverter());
|
||||
EXPECT_CALL(*mock, ConvertUnitToByteStreamWithSubsamples(
|
||||
_, arraysize(kAnyData), kIsKeyFrame,
|
||||
kEscapeEncryptedNalu, _, Pointee(Eq(subsamples))))
|
||||
.WillOnce(DoAll(SetArgPointee<4>(expected_data), Return(true)));
|
||||
|
||||
UseMockNalUnitToByteStreamConverter(std::move(mock));
|
||||
|
||||
|
@ -302,8 +277,9 @@ TEST_F(PesPacketGeneratorTest, AddVideoSampleFailedToConvert) {
|
|||
std::vector<uint8_t> expected_data(kAnyData, kAnyData + arraysize(kAnyData));
|
||||
std::unique_ptr<MockNalUnitToByteStreamConverter> mock(
|
||||
new MockNalUnitToByteStreamConverter());
|
||||
EXPECT_CALL(*mock,
|
||||
ConvertUnitToByteStream(_, arraysize(kAnyData), kIsKeyFrame, _))
|
||||
EXPECT_CALL(*mock, ConvertUnitToByteStreamWithSubsamples(
|
||||
_, arraysize(kAnyData), kIsKeyFrame,
|
||||
kEscapeEncryptedNalu, _, Pointee(IsEmpty())))
|
||||
.WillOnce(Return(false));
|
||||
|
||||
UseMockNalUnitToByteStreamConverter(std::move(mock));
|
||||
|
@ -384,8 +360,9 @@ TEST_F(PesPacketGeneratorTest, TimeStampScaling) {
|
|||
|
||||
std::unique_ptr<MockNalUnitToByteStreamConverter> mock(
|
||||
new MockNalUnitToByteStreamConverter());
|
||||
EXPECT_CALL(*mock,
|
||||
ConvertUnitToByteStream(_, arraysize(kAnyData), kIsKeyFrame, _))
|
||||
EXPECT_CALL(*mock, ConvertUnitToByteStreamWithSubsamples(
|
||||
_, arraysize(kAnyData), kIsKeyFrame,
|
||||
kEscapeEncryptedNalu, _, Pointee(IsEmpty())))
|
||||
.WillOnce(Return(true));
|
||||
|
||||
UseMockNalUnitToByteStreamConverter(std::move(mock));
|
||||
|
@ -404,323 +381,6 @@ TEST_F(PesPacketGeneratorTest, TimeStampScaling) {
|
|||
EXPECT_TRUE(generator_.Flush());
|
||||
}
|
||||
|
||||
// The nalu is too small for it to be encrypted. Verify it is not modified.
|
||||
TEST_F(PesPacketGeneratorTest, H264SampleEncryptionSmallNalu) {
|
||||
const uint8_t kNaluData[] = {
|
||||
0x00, 0x00, 0x00, 0x01, 0x61, 0xbb, 0xcc, 0xdd,
|
||||
};
|
||||
|
||||
ASSERT_NO_FATAL_FAILURE(H264EncryptionTest(kNaluData, arraysize(kNaluData),
|
||||
kNaluData, arraysize(kNaluData)));
|
||||
}
|
||||
|
||||
// Verify that sample encryption works.
|
||||
TEST_F(PesPacketGeneratorTest, H264SampleEncryption) {
|
||||
// Use the following command to encrypt data.
|
||||
// openssl aes-128-cbc -nopad -e -in input -K
|
||||
// "00000000000000000000000000000000" -iv "00000000000000000000000000000000" >
|
||||
// enc
|
||||
const uint8_t kNaluData[] = {
|
||||
0x00, 0x00, 0x00, 0x01, // Start code.
|
||||
0x61, // nalu type 1; this type should get encrypted.
|
||||
// Bogus data but should not be encrypted.
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B,
|
||||
0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
|
||||
0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E,
|
||||
|
||||
// Next 16 bytes should be encrypted.
|
||||
0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A,
|
||||
0x2B, 0x2C, 0x2D, 0x2E,
|
||||
|
||||
// Next 144 bytes should be in the clear.
|
||||
0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A,
|
||||
0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
|
||||
0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52,
|
||||
0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E,
|
||||
0x5F, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A,
|
||||
0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76,
|
||||
0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 0x80, 0x81, 0x82,
|
||||
0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E,
|
||||
0x8F, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A,
|
||||
0x9B, 0x9C, 0x9D, 0x9E, 0x9F, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6,
|
||||
0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2,
|
||||
0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE,
|
||||
|
||||
// Next 16 bytes should be encrypted.
|
||||
0xBF, 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA,
|
||||
0xCB, 0xCC, 0xCD, 0xCE,
|
||||
|
||||
// This last bytes should not be encrypted.
|
||||
0xCF,
|
||||
};
|
||||
|
||||
const uint8_t kEncryptedNaluData[] = {
|
||||
0x00, 0x00, 0x00, 0x01, // Start code.
|
||||
0x61, // nalu type 1; should get encrypted.
|
||||
// Bogus data but should sample encrypted.
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B,
|
||||
0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
|
||||
0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E,
|
||||
|
||||
// Encrypted 16 bytes.
|
||||
0x93, 0x3A, 0x2C, 0x38, 0x86, 0x4B, 0x64, 0xE2, 0x62, 0x7E, 0xCC, 0x75,
|
||||
0x71, 0xFB, 0x60, 0x7C,
|
||||
|
||||
// Next 144 bytes should be in the clear.
|
||||
0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A,
|
||||
0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
|
||||
0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52,
|
||||
0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E,
|
||||
0x5F, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A,
|
||||
0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76,
|
||||
0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 0x80, 0x81, 0x82,
|
||||
0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E,
|
||||
0x8F, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A,
|
||||
0x9B, 0x9C, 0x9D, 0x9E, 0x9F, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6,
|
||||
0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2,
|
||||
0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE,
|
||||
|
||||
// Encrypted 16 bytes.
|
||||
0xB7, 0x1C, 0x64, 0xAE, 0x90, 0xA4, 0x35, 0x88, 0x4F, 0xD1, 0x30, 0xC2,
|
||||
0x06, 0x2E, 0xF8, 0xA5,
|
||||
|
||||
// This last bytes should not be encrypted.
|
||||
0xCF,
|
||||
};
|
||||
ASSERT_NO_FATAL_FAILURE(H264EncryptionTest(kNaluData, arraysize(kNaluData),
|
||||
kEncryptedNaluData,
|
||||
arraysize(kEncryptedNaluData)));
|
||||
}
|
||||
|
||||
// If any block is encrypted, then the whole nal unit must be re-escaped.
|
||||
TEST_F(PesPacketGeneratorTest, H264SampleEncryptionVerifyReescape) {
|
||||
const uint8_t kNaluData[] = {
|
||||
0x00, 0x00, 0x00, 0x01, // Start code.
|
||||
0x61, // nalu type 1; this type should get encrypted.
|
||||
// Bogus data but should not be encrypted.
|
||||
// But 0x00 0x00 0x03 should be re-escaped.
|
||||
0x00, 0x00, 0x03, 0x02, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B,
|
||||
0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
|
||||
0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E,
|
||||
|
||||
// Next 16 bytes should be encrypted.
|
||||
// Note that there is 0x00 0x00 0x03 sequence that will be reescaped.
|
||||
0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A,
|
||||
0x2B, 0x2C, 0x2D, 0x2E,
|
||||
|
||||
// Next 144 bytes should be in the clear.
|
||||
0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A,
|
||||
0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
|
||||
0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52,
|
||||
0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E,
|
||||
0x5F, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A,
|
||||
0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76,
|
||||
0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 0x80, 0x81, 0x82,
|
||||
0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E,
|
||||
0x8F, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A,
|
||||
// Still part of clear data, but this line includes 0x00 0x00 0x03
|
||||
// which should be re-escaped.
|
||||
0x9B, 0x9C, 0x9D, 0x00, 0x00, 0x03, 0x01, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6,
|
||||
0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2,
|
||||
0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE,
|
||||
|
||||
// Next 16 bytes should be encrypted.
|
||||
0xBF, 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA,
|
||||
0xCB, 0xCC, 0xCD, 0xCE,
|
||||
|
||||
// This last bytes should not be encrypted.
|
||||
0xCF,
|
||||
};
|
||||
|
||||
const uint8_t kEncryptedNaluData[] = {
|
||||
0x00, 0x00, 0x00, 0x01, // Start code.
|
||||
0x61, // nalu type 1; should get encrypted.
|
||||
// Bogus data but should not be encrypted.
|
||||
0x00, 0x00, 0x03, 0x03, 0x02, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A,
|
||||
0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
|
||||
0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E,
|
||||
|
||||
// Encrypted 16 bytes.
|
||||
0x93, 0x3A, 0x2C, 0x38, 0x86, 0x4B, 0x64, 0xE2, 0x62, 0x7E, 0xCC, 0x75,
|
||||
0x71, 0xFB, 0x60, 0x7C,
|
||||
|
||||
// Next 144 bytes should be in the clear.
|
||||
0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A,
|
||||
0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
|
||||
0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52,
|
||||
0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E,
|
||||
0x5F, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A,
|
||||
0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76,
|
||||
0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 0x80, 0x81, 0x82,
|
||||
0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E,
|
||||
0x8F, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A,
|
||||
// Extra 0x03 is added.
|
||||
0x9B, 0x9C, 0x9D, 0x00, 0x00, 0x03, 0x03, 0x01, 0xA2, 0xA3, 0xA4, 0xA5,
|
||||
0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1,
|
||||
0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD,
|
||||
0xBE,
|
||||
|
||||
// Encrypted 16 bytes.
|
||||
0xB7, 0x1C, 0x64, 0xAE, 0x90, 0xA4, 0x35, 0x88, 0x4F, 0xD1, 0x30, 0xC2,
|
||||
0x06, 0x2E, 0xF8, 0xA5,
|
||||
|
||||
// This last bytes should not be encrypted.
|
||||
0xCF,
|
||||
};
|
||||
ASSERT_NO_FATAL_FAILURE(H264EncryptionTest(kNaluData, arraysize(kNaluData),
|
||||
kEncryptedNaluData,
|
||||
arraysize(kEncryptedNaluData)));
|
||||
}
|
||||
|
||||
// Verify that if the last there are only 16 bytes left, then it doesn't get
|
||||
// encrypted.
|
||||
TEST_F(PesPacketGeneratorTest, H264SampleEncryptionLast16ByteNotEncrypted) {
|
||||
const uint8_t kNaluData[] = {
|
||||
0x00, 0x00, 0x00, 0x01, // Start code.
|
||||
0x61, // nalu type 1; should get encrypted.
|
||||
// Bogus data but should not be encrypted.
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B,
|
||||
0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
|
||||
0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E,
|
||||
|
||||
// Next 16 bytes should be encrypted.
|
||||
0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A,
|
||||
0x2B, 0x2C, 0x2D, 0x2E,
|
||||
|
||||
// Next 144 bytes should be in the clear.
|
||||
0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A,
|
||||
0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
|
||||
0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52,
|
||||
0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E,
|
||||
0x5F, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A,
|
||||
0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76,
|
||||
0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 0x80, 0x81, 0x82,
|
||||
0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E,
|
||||
0x8F, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A,
|
||||
0x9B, 0x9C, 0x9D, 0x9E, 0x9F, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6,
|
||||
0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2,
|
||||
0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE,
|
||||
|
||||
// These 16 bytes should not be encrypted.
|
||||
0xBF, 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA,
|
||||
0xCB, 0xCC, 0xCD, 0xCE,
|
||||
};
|
||||
|
||||
const uint8_t kEncryptedNaluData[] = {
|
||||
0x00, 0x00, 0x00, 0x01, // Start code.
|
||||
0x61, // nalu type 1; should get encrypted.
|
||||
// Bogus data but should not be encrypted.
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B,
|
||||
0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
|
||||
0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E,
|
||||
|
||||
// Encrypted 16 bytes.
|
||||
0x93, 0x3A, 0x2C, 0x38, 0x86, 0x4B, 0x64, 0xE2, 0x62, 0x7E, 0xCC, 0x75,
|
||||
0x71, 0xFB, 0x60, 0x7C,
|
||||
|
||||
// Next 144 bytes should be in the clear.
|
||||
0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A,
|
||||
0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
|
||||
0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52,
|
||||
0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E,
|
||||
0x5F, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A,
|
||||
0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76,
|
||||
0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 0x80, 0x81, 0x82,
|
||||
0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E,
|
||||
0x8F, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A,
|
||||
0x9B, 0x9C, 0x9D, 0x9E, 0x9F, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6,
|
||||
0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2,
|
||||
0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE,
|
||||
|
||||
// These 16 bytes should not be encrypted.
|
||||
0xBF, 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA,
|
||||
0xCB, 0xCC, 0xCD, 0xCE,
|
||||
};
|
||||
ASSERT_NO_FATAL_FAILURE(H264EncryptionTest(kNaluData, arraysize(kNaluData),
|
||||
kEncryptedNaluData,
|
||||
arraysize(kEncryptedNaluData)));
|
||||
}
|
||||
|
||||
// The sample is too small and it doesn't need to be encrypted.
|
||||
TEST_F(PesPacketGeneratorTest, AacSampleEncryptionSmallSample) {
|
||||
const uint8_t kClearData[] = {
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A,
|
||||
0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15,
|
||||
0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E,
|
||||
};
|
||||
|
||||
ASSERT_NO_FATAL_FAILURE(AacEncryptionTest(kClearData, arraysize(kClearData),
|
||||
kClearData, arraysize(kClearData)));
|
||||
}
|
||||
|
||||
// Verify that AAC can be encrypted.
|
||||
TEST_F(PesPacketGeneratorTest, AacSampleEncryption) {
|
||||
// The data is long enough so that 2 blocks (32 bytes) are encrypted.
|
||||
const uint8_t kClearData[] = {
|
||||
// First 16 bytes are always clear.
|
||||
0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12,
|
||||
0x13, 0x14, 0x15, 0x16,
|
||||
|
||||
// Next 32 bytes (2 blocks) are encrypted.
|
||||
0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22,
|
||||
0x23, 0x24, 0x25, 0x26,
|
||||
0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32,
|
||||
0x33, 0x34, 0x35, 0x36,
|
||||
|
||||
// The last 2 bytes are in the clear.
|
||||
0x37, 0x38,
|
||||
};
|
||||
|
||||
const uint8_t kExpectedOutputData[] = {
|
||||
// First 16 bytes are always clear.
|
||||
0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12,
|
||||
0x13, 0x14, 0x15, 0x16,
|
||||
|
||||
// Encrypted bytes.
|
||||
0xE3, 0x42, 0x9B, 0x27, 0x33, 0x67, 0x68, 0x08, 0xA5, 0xB3, 0x3E, 0xB1,
|
||||
0xEE, 0xFC, 0x9E, 0x0A, 0x8E, 0x0C, 0x73, 0xC5, 0x57, 0xEE, 0x58, 0xC7,
|
||||
0x48, 0x74, 0x2A, 0x12, 0x38, 0x4F, 0x4E, 0xAC,
|
||||
|
||||
// The last 2 bytes are in the clear.
|
||||
0x37, 0x38,
|
||||
};
|
||||
|
||||
ASSERT_NO_FATAL_FAILURE(AacEncryptionTest(kClearData, arraysize(kClearData),
|
||||
kExpectedOutputData,
|
||||
arraysize(kExpectedOutputData)));
|
||||
}
|
||||
|
||||
// Verify that all the bytes after the leading few bytes are encrypted.
|
||||
// Note that this is different from h264 encryption where it doesn't encrypt the
|
||||
// last 16.
|
||||
TEST_F(PesPacketGeneratorTest, AacSampleEncryptionLastBytesAreEncrypted) {
|
||||
const uint8_t kClearData[] = {
|
||||
// First 16 bytes are always clear.
|
||||
0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12,
|
||||
0x13, 0x14, 0x15, 0x16,
|
||||
|
||||
// Next 32 bytes (2 blocks) are encrypted.
|
||||
0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22,
|
||||
0x23, 0x24, 0x25, 0x26,
|
||||
0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32,
|
||||
0x33, 0x34, 0x35, 0x36,
|
||||
};
|
||||
|
||||
const uint8_t kExpectedOutputData[] = {
|
||||
// First 16 bytes are always clear.
|
||||
0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12,
|
||||
0x13, 0x14, 0x15, 0x16,
|
||||
|
||||
// Encrypted bytes.
|
||||
0xE3, 0x42, 0x9B, 0x27, 0x33, 0x67, 0x68, 0x08, 0xA5, 0xB3, 0x3E, 0xB1,
|
||||
0xEE, 0xFC, 0x9E, 0x0A, 0x8E, 0x0C, 0x73, 0xC5, 0x57, 0xEE, 0x58, 0xC7,
|
||||
0x48, 0x74, 0x2A, 0x12, 0x38, 0x4F, 0x4E, 0xAC,
|
||||
};
|
||||
ASSERT_NO_FATAL_FAILURE(AacEncryptionTest(kClearData, arraysize(kClearData),
|
||||
kExpectedOutputData,
|
||||
arraysize(kExpectedOutputData)));
|
||||
}
|
||||
|
||||
} // namespace mp2t
|
||||
} // namespace media
|
||||
} // namespace shaka
|
||||
|
|
|
@ -22,9 +22,7 @@ Status TsMuxer::InitializeMuxer() {
|
|||
return Status(error::MUXER_FAILURE, "Cannot handle more than one streams.");
|
||||
|
||||
segmenter_.reset(new TsSegmenter(options(), muxer_listener()));
|
||||
Status status = segmenter_->Initialize(
|
||||
*streams()[0], encryption_key_source(), max_sd_pixels(), max_hd_pixels(),
|
||||
max_uhd1_pixels(), clear_lead_in_seconds());
|
||||
Status status = segmenter_->Initialize(*streams()[0]);
|
||||
FireOnMediaStartEvent();
|
||||
return status;
|
||||
}
|
||||
|
@ -43,6 +41,11 @@ Status TsMuxer::AddSample(size_t stream_id,
|
|||
Status TsMuxer::FinalizeSegment(size_t stream_id,
|
||||
std::shared_ptr<SegmentInfo> segment_info) {
|
||||
DCHECK_EQ(stream_id, 0u);
|
||||
if (segment_info->key_rotation_encryption_config) {
|
||||
NOTIMPLEMENTED() << "Key rotation is not implemented for TS.";
|
||||
return Status(error::UNIMPLEMENTED,
|
||||
"Key rotation is not implemented for TS");
|
||||
}
|
||||
return segment_info->is_subsegment
|
||||
? Status::OK
|
||||
: segmenter_->FinalizeSegment(segment_info->start_timestamp,
|
||||
|
|
|
@ -31,12 +31,7 @@ TsSegmenter::TsSegmenter(const MuxerOptions& options, MuxerListener* listener)
|
|||
pes_packet_generator_(new PesPacketGenerator()) {}
|
||||
TsSegmenter::~TsSegmenter() {}
|
||||
|
||||
Status TsSegmenter::Initialize(const StreamInfo& stream_info,
|
||||
KeySource* encryption_key_source,
|
||||
uint32_t max_sd_pixels,
|
||||
uint32_t max_hd_pixels,
|
||||
uint32_t max_uhd1_pixels,
|
||||
double clear_lead_in_seconds) {
|
||||
Status TsSegmenter::Initialize(const StreamInfo& stream_info) {
|
||||
if (muxer_options_.segment_template.empty())
|
||||
return Status(error::MUXER_FAILURE, "Segment template not specified.");
|
||||
if (!ts_writer_->Initialize(stream_info))
|
||||
|
@ -46,37 +41,6 @@ Status TsSegmenter::Initialize(const StreamInfo& stream_info,
|
|||
"Failed to initialize PesPacketGenerator.");
|
||||
}
|
||||
|
||||
if (encryption_key_source) {
|
||||
std::unique_ptr<EncryptionKey> encryption_key(new EncryptionKey());
|
||||
const KeySource::TrackType type =
|
||||
GetTrackTypeForEncryption(stream_info, max_sd_pixels,
|
||||
max_hd_pixels, max_uhd1_pixels);
|
||||
Status status = encryption_key_source->GetKey(type, encryption_key.get());
|
||||
|
||||
if (encryption_key->iv.empty()) {
|
||||
if (!AesCryptor::GenerateRandomIv(FOURCC_cbcs, &encryption_key->iv)) {
|
||||
return Status(error::INTERNAL_ERROR, "Failed to generate random iv.");
|
||||
}
|
||||
}
|
||||
if (!status.ok())
|
||||
return status;
|
||||
|
||||
encryption_key_ = std::move(encryption_key);
|
||||
clear_lead_in_seconds_ = clear_lead_in_seconds;
|
||||
|
||||
if (listener_) {
|
||||
// For now this only happens once, so send true.
|
||||
const bool kIsInitialEncryptionInfo = true;
|
||||
listener_->OnEncryptionInfoReady(
|
||||
kIsInitialEncryptionInfo, FOURCC_cbcs, encryption_key_->key_id,
|
||||
encryption_key_->iv, encryption_key_->key_system_info);
|
||||
}
|
||||
|
||||
status = NotifyEncrypted();
|
||||
if (!status.ok())
|
||||
return status;
|
||||
}
|
||||
|
||||
timescale_scale_ = kTsTimescale / stream_info.time_scale();
|
||||
return Status::OK;
|
||||
}
|
||||
|
@ -86,6 +50,9 @@ Status TsSegmenter::Finalize() {
|
|||
}
|
||||
|
||||
Status TsSegmenter::AddSample(std::shared_ptr<MediaSample> sample) {
|
||||
if (sample->is_encrypted())
|
||||
ts_writer_->SignalEncrypted();
|
||||
|
||||
if (!ts_writer_file_opened_ && !sample->is_key_frame())
|
||||
LOG(WARNING) << "A segment will start with a non key frame.";
|
||||
|
||||
|
@ -161,21 +128,8 @@ Status TsSegmenter::FinalizeSegment(uint64_t start_timestamp,
|
|||
duration * timescale_scale_, file_size);
|
||||
}
|
||||
ts_writer_file_opened_ = false;
|
||||
total_duration_in_seconds_ += duration * timescale_scale_ / kTsTimescale;
|
||||
}
|
||||
current_segment_path_.clear();
|
||||
return NotifyEncrypted();
|
||||
}
|
||||
|
||||
Status TsSegmenter::NotifyEncrypted() {
|
||||
if (encryption_key_ && total_duration_in_seconds_ >= clear_lead_in_seconds_) {
|
||||
if (listener_)
|
||||
listener_->OnEncryptionStart();
|
||||
|
||||
if (!pes_packet_generator_->SetEncryptionKey(std::move(encryption_key_)))
|
||||
return Status(error::INTERNAL_ERROR, "Failed to set encryption key.");
|
||||
ts_writer_->SignalEncrypted();
|
||||
}
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
|
|
|
@ -36,15 +36,9 @@ class TsSegmenter {
|
|||
~TsSegmenter();
|
||||
|
||||
/// Initialize the object.
|
||||
/// Key rotation is not supported.
|
||||
/// @param stream_info is the stream info for the segmenter.
|
||||
/// @return OK on success.
|
||||
Status Initialize(const StreamInfo& stream_info,
|
||||
KeySource* encryption_key_source,
|
||||
uint32_t max_sd_pixels,
|
||||
uint32_t max_hd_pixels,
|
||||
uint32_t max_uhd1_pixels,
|
||||
double clear_lead_in_seconds);
|
||||
Status Initialize(const StreamInfo& stream_info);
|
||||
|
||||
/// Finalize the segmenter.
|
||||
/// @return OK on success.
|
||||
|
@ -83,9 +77,6 @@ class TsSegmenter {
|
|||
// it will open one. This will not close the file.
|
||||
Status WritePesPacketsToFile();
|
||||
|
||||
// If conditions are met, notify objects that the data is encrypted.
|
||||
Status NotifyEncrypted();
|
||||
|
||||
const MuxerOptions& muxer_options_;
|
||||
MuxerListener* const listener_;
|
||||
|
||||
|
@ -107,14 +98,6 @@ class TsSegmenter {
|
|||
// the segment has been finalized.
|
||||
std::string current_segment_path_;
|
||||
|
||||
std::unique_ptr<EncryptionKey> encryption_key_;
|
||||
double clear_lead_in_seconds_ = 0;
|
||||
|
||||
// The total duration of the segments that it has segmented. This only
|
||||
// includes segments that have been finailzed. IOW, this does not count the
|
||||
// current segments duration.
|
||||
double total_duration_in_seconds_ = 0.0;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(TsSegmenter);
|
||||
};
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#include "packager/media/base/audio_stream_info.h"
|
||||
#include "packager/media/base/fixed_key_source.h"
|
||||
#include "packager/media/base/test/status_test_util.h"
|
||||
#include "packager/media/base/video_stream_info.h"
|
||||
#include "packager/media/event/mock_muxer_listener.h"
|
||||
|
@ -54,11 +53,6 @@ class MockPesPacketGenerator : public PesPacketGenerator {
|
|||
public:
|
||||
MOCK_METHOD1(Initialize, bool(const StreamInfo& info));
|
||||
MOCK_METHOD1(PushSample, bool(std::shared_ptr<MediaSample> sample));
|
||||
MOCK_METHOD1(SetEncryptionKeyMock, bool(EncryptionKey* encryption_key));
|
||||
bool SetEncryptionKey(
|
||||
std::unique_ptr<EncryptionKey> encryption_key) override {
|
||||
return SetEncryptionKeyMock(encryption_key.get());
|
||||
}
|
||||
|
||||
MOCK_METHOD0(NumberOfReadyPesPackets, size_t());
|
||||
|
||||
|
@ -88,12 +82,6 @@ class MockTsWriter : public TsWriter {
|
|||
}
|
||||
};
|
||||
|
||||
// TODO(rkuroiwa): Add mock_key_source.{h,cc} in media/base.
|
||||
class MockKeySource : public FixedKeySource {
|
||||
public:
|
||||
MOCK_METHOD2(GetKey, Status(TrackType track_type, EncryptionKey* key));
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
class TsSegmenterTest : public ::testing::Test {
|
||||
|
@ -124,7 +112,7 @@ TEST_F(TsSegmenterTest, Initialize) {
|
|||
segmenter.InjectPesPacketGeneratorForTesting(
|
||||
std::move(mock_pes_packet_generator_));
|
||||
|
||||
EXPECT_OK(segmenter.Initialize(*stream_info, nullptr, 0, 0, 0, 0));
|
||||
EXPECT_OK(segmenter.Initialize(*stream_info));
|
||||
}
|
||||
|
||||
TEST_F(TsSegmenterTest, AddSample) {
|
||||
|
@ -170,7 +158,7 @@ TEST_F(TsSegmenterTest, AddSample) {
|
|||
segmenter.InjectPesPacketGeneratorForTesting(
|
||||
std::move(mock_pes_packet_generator_));
|
||||
|
||||
EXPECT_OK(segmenter.Initialize(*stream_info, nullptr, 0, 0, 0, 0));
|
||||
EXPECT_OK(segmenter.Initialize(*stream_info));
|
||||
EXPECT_OK(segmenter.AddSample(sample));
|
||||
}
|
||||
|
||||
|
@ -265,7 +253,7 @@ TEST_F(TsSegmenterTest, PassedSegmentDuration) {
|
|||
segmenter.InjectTsWriterForTesting(std::move(mock_ts_writer_));
|
||||
segmenter.InjectPesPacketGeneratorForTesting(
|
||||
std::move(mock_pes_packet_generator_));
|
||||
EXPECT_OK(segmenter.Initialize(*stream_info, nullptr, 0, 0, 0, 0));
|
||||
EXPECT_OK(segmenter.Initialize(*stream_info));
|
||||
EXPECT_OK(segmenter.AddSample(sample1));
|
||||
EXPECT_OK(segmenter.FinalizeSegment(kFirstPts, sample1->duration()));
|
||||
EXPECT_OK(segmenter.AddSample(sample2));
|
||||
|
@ -292,7 +280,7 @@ TEST_F(TsSegmenterTest, InitializeThenFinalize) {
|
|||
segmenter.InjectTsWriterForTesting(std::move(mock_ts_writer_));
|
||||
segmenter.InjectPesPacketGeneratorForTesting(
|
||||
std::move(mock_pes_packet_generator_));
|
||||
EXPECT_OK(segmenter.Initialize(*stream_info, nullptr, 0, 0, 0, 0));
|
||||
EXPECT_OK(segmenter.Initialize(*stream_info));
|
||||
EXPECT_OK(segmenter.Finalize());
|
||||
}
|
||||
|
||||
|
@ -322,103 +310,18 @@ TEST_F(TsSegmenterTest, FinalizeSegment) {
|
|||
segmenter.InjectTsWriterForTesting(std::move(mock_ts_writer_));
|
||||
segmenter.InjectPesPacketGeneratorForTesting(
|
||||
std::move(mock_pes_packet_generator_));
|
||||
EXPECT_OK(segmenter.Initialize(*stream_info, nullptr, 0, 0, 0, 0));
|
||||
EXPECT_OK(segmenter.Initialize(*stream_info));
|
||||
segmenter.SetTsWriterFileOpenedForTesting(true);
|
||||
EXPECT_OK(segmenter.FinalizeSegment(0, 100 /* arbitrary duration */));
|
||||
}
|
||||
|
||||
TEST_F(TsSegmenterTest, WithEncryptionNoClearLead) {
|
||||
std::shared_ptr<VideoStreamInfo> stream_info(new VideoStreamInfo(
|
||||
kTrackId, kTimeScale, kDuration, kH264Codec, kCodecString, kExtraData,
|
||||
arraysize(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight,
|
||||
kTrickPlayRate, kNaluLengthSize, kLanguage, kIsEncrypted));
|
||||
MuxerOptions options;
|
||||
options.segment_template = "file$Number$.ts";
|
||||
|
||||
MockMuxerListener mock_listener;
|
||||
EXPECT_CALL(mock_listener, OnEncryptionInfoReady(_, _, _, _, _));
|
||||
TsSegmenter segmenter(options, &mock_listener);
|
||||
|
||||
EXPECT_CALL(*mock_ts_writer_, Initialize(_)).WillOnce(Return(true));
|
||||
EXPECT_CALL(*mock_ts_writer_, SignalEncrypted());
|
||||
EXPECT_CALL(*mock_pes_packet_generator_, Initialize(_))
|
||||
.WillOnce(Return(true));
|
||||
|
||||
EXPECT_CALL(*mock_pes_packet_generator_, SetEncryptionKeyMock(_))
|
||||
.WillOnce(Return(true));
|
||||
|
||||
segmenter.InjectTsWriterForTesting(std::move(mock_ts_writer_));
|
||||
segmenter.InjectPesPacketGeneratorForTesting(
|
||||
std::move(mock_pes_packet_generator_));
|
||||
|
||||
MockKeySource mock_key_source;
|
||||
EXPECT_CALL(mock_key_source, GetKey(KeySource::TRACK_TYPE_HD, _))
|
||||
.WillOnce(Return(Status::OK));
|
||||
|
||||
const uint32_t k480pPixels = 640 * 480;
|
||||
const uint32_t k1080pPixels = 1920 * 1080;
|
||||
const uint32_t k2160pPixels = 4096 * 2160;
|
||||
// Set this to 0 so that Finalize will call
|
||||
// PesPacketGenerator::SetEncryptionKey().
|
||||
// Even tho no samples have been added.
|
||||
const double kClearLeadSeconds = 0;
|
||||
EXPECT_OK(segmenter.Initialize(*stream_info, &mock_key_source, k480pPixels,
|
||||
k1080pPixels, k2160pPixels,
|
||||
kClearLeadSeconds));
|
||||
}
|
||||
|
||||
// Verify that the muxer listener pointer is not used without checking that it's
|
||||
// not null.
|
||||
TEST_F(TsSegmenterTest, WithEncryptionNoClearLeadNoMuxerListener) {
|
||||
std::shared_ptr<VideoStreamInfo> stream_info(new VideoStreamInfo(
|
||||
kTrackId, kTimeScale, kDuration, kH264Codec, kCodecString, kExtraData,
|
||||
arraysize(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight,
|
||||
kTrickPlayRate, kNaluLengthSize, kLanguage, kIsEncrypted));
|
||||
MuxerOptions options;
|
||||
// options.segment_duration = 10.0;
|
||||
options.segment_template = "file$Number$.ts";
|
||||
|
||||
TsSegmenter segmenter(options, nullptr);
|
||||
|
||||
EXPECT_CALL(*mock_ts_writer_, Initialize(_)).WillOnce(Return(true));
|
||||
EXPECT_CALL(*mock_ts_writer_, SignalEncrypted());
|
||||
EXPECT_CALL(*mock_pes_packet_generator_, Initialize(_))
|
||||
.WillOnce(Return(true));
|
||||
|
||||
EXPECT_CALL(*mock_pes_packet_generator_, SetEncryptionKeyMock(_))
|
||||
.WillOnce(Return(true));
|
||||
|
||||
segmenter.InjectTsWriterForTesting(std::move(mock_ts_writer_));
|
||||
segmenter.InjectPesPacketGeneratorForTesting(
|
||||
std::move(mock_pes_packet_generator_));
|
||||
|
||||
MockKeySource mock_key_source;
|
||||
EXPECT_CALL(mock_key_source, GetKey(KeySource::TRACK_TYPE_HD, _))
|
||||
.WillOnce(Return(Status::OK));
|
||||
|
||||
const uint32_t k480pPixels = 640 * 480;
|
||||
const uint32_t k1080pPixels = 1920 * 1080;
|
||||
const uint32_t k2160pPixels = 4096 * 2160;
|
||||
// Set this to 0 so that Finalize will call
|
||||
// PesPacketGenerator::SetEncryptionKey().
|
||||
// Even tho no samples have been added.
|
||||
const double kClearLeadSeconds = 0;
|
||||
EXPECT_OK(segmenter.Initialize(*stream_info, &mock_key_source, k480pPixels,
|
||||
k1080pPixels, k2160pPixels,
|
||||
kClearLeadSeconds));
|
||||
}
|
||||
|
||||
// Verify that encryption notification is sent to objects after clear lead.
|
||||
TEST_F(TsSegmenterTest, WithEncryptionWithClearLead) {
|
||||
TEST_F(TsSegmenterTest, EncryptedSample) {
|
||||
std::shared_ptr<VideoStreamInfo> stream_info(new VideoStreamInfo(
|
||||
kTrackId, kTimeScale, kDuration, kH264Codec, kCodecString, kExtraData,
|
||||
arraysize(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight,
|
||||
kTrickPlayRate, kNaluLengthSize, kLanguage, kIsEncrypted));
|
||||
MuxerOptions options;
|
||||
|
||||
const uint32_t k1080pPixels = 1920 * 1080;
|
||||
const uint32_t k2160pPixels = 4096 * 2160;
|
||||
const double kClearLeadSeconds = 1.0;
|
||||
options.segment_template = "file$Number$.ts";
|
||||
|
||||
MockMuxerListener mock_listener;
|
||||
|
@ -479,33 +382,19 @@ TEST_F(TsSegmenterTest, WithEncryptionWithClearLead) {
|
|||
.InSequence(pes_packet_sequence)
|
||||
.WillOnce(Return(new PesPacket()));
|
||||
|
||||
MockPesPacketGenerator* mock_pes_packet_generator_raw =
|
||||
mock_pes_packet_generator_.get();
|
||||
|
||||
MockTsWriter* mock_ts_writer_raw = mock_ts_writer_.get();
|
||||
|
||||
segmenter.InjectTsWriterForTesting(std::move(mock_ts_writer_));
|
||||
segmenter.InjectPesPacketGeneratorForTesting(
|
||||
std::move(mock_pes_packet_generator_));
|
||||
|
||||
MockKeySource mock_key_source;
|
||||
// This should be called AFTER the first AddSample().
|
||||
EXPECT_CALL(mock_key_source, GetKey(KeySource::TRACK_TYPE_HD, _))
|
||||
.WillOnce(Return(Status::OK));
|
||||
|
||||
EXPECT_CALL(mock_listener, OnEncryptionInfoReady(_, _, _, _, _));
|
||||
EXPECT_OK(segmenter.Initialize(*stream_info, &mock_key_source, 0,
|
||||
k1080pPixels, k2160pPixels,
|
||||
kClearLeadSeconds));
|
||||
EXPECT_OK(segmenter.Initialize(*stream_info));
|
||||
EXPECT_OK(segmenter.AddSample(sample1));
|
||||
|
||||
// Encryption should be setup after finalizing the first segment.
|
||||
// These should be called when finalize segment is called.
|
||||
EXPECT_CALL(mock_listener, OnEncryptionStart());
|
||||
EXPECT_CALL(*mock_pes_packet_generator_raw, SetEncryptionKeyMock(_))
|
||||
.WillOnce(Return(true));
|
||||
EXPECT_OK(segmenter.FinalizeSegment(1, sample1->duration()));
|
||||
// Signal encrypted if sample is encrypted.
|
||||
EXPECT_CALL(*mock_ts_writer_raw, SignalEncrypted());
|
||||
EXPECT_OK(segmenter.FinalizeSegment(0, sample1->duration()));
|
||||
sample2->set_is_encrypted(true);
|
||||
EXPECT_OK(segmenter.AddSample(sample2));
|
||||
}
|
||||
|
||||
|
|
|
@ -1,380 +0,0 @@
|
|||
// Copyright 2014 Google Inc. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd
|
||||
|
||||
#include "packager/media/formats/mp4/encrypting_fragmenter.h"
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include "packager/media/base/aes_encryptor.h"
|
||||
#include "packager/media/base/aes_pattern_cryptor.h"
|
||||
#include "packager/media/base/buffer_reader.h"
|
||||
#include "packager/media/base/key_source.h"
|
||||
#include "packager/media/base/media_sample.h"
|
||||
#include "packager/media/codecs/nalu_reader.h"
|
||||
#include "packager/media/codecs/vp8_parser.h"
|
||||
#include "packager/media/codecs/vp9_parser.h"
|
||||
#include "packager/media/formats/mp4/box_definitions.h"
|
||||
|
||||
namespace shaka {
|
||||
namespace media {
|
||||
namespace mp4 {
|
||||
|
||||
namespace {
|
||||
const size_t kCencBlockSize = 16u;
|
||||
|
||||
// Adds one or more subsamples to |*subsamples|. This may add more than one
|
||||
// if one of the values overflows the integer in the subsample.
|
||||
void AddSubsamples(uint64_t clear_bytes,
|
||||
uint64_t cipher_bytes,
|
||||
std::vector<SubsampleEntry>* subsamples) {
|
||||
CHECK_LT(cipher_bytes, std::numeric_limits<uint32_t>::max());
|
||||
const uint64_t kUInt16Max = std::numeric_limits<uint16_t>::max();
|
||||
while (clear_bytes > kUInt16Max) {
|
||||
subsamples->push_back(SubsampleEntry(kUInt16Max, 0));
|
||||
clear_bytes -= kUInt16Max;
|
||||
}
|
||||
|
||||
if (clear_bytes > 0 || cipher_bytes > 0)
|
||||
subsamples->push_back(SubsampleEntry(clear_bytes, cipher_bytes));
|
||||
}
|
||||
|
||||
Codec GetCodec(const StreamInfo& stream_info) {
|
||||
if (stream_info.stream_type() != kStreamVideo) return kUnknownCodec;
|
||||
const VideoStreamInfo& video_stream_info =
|
||||
static_cast<const VideoStreamInfo&>(stream_info);
|
||||
return video_stream_info.codec();
|
||||
}
|
||||
|
||||
uint8_t GetNaluLengthSize(const StreamInfo& stream_info) {
|
||||
if (stream_info.stream_type() != kStreamVideo)
|
||||
return 0;
|
||||
|
||||
const VideoStreamInfo& video_stream_info =
|
||||
static_cast<const VideoStreamInfo&>(stream_info);
|
||||
return video_stream_info.nalu_length_size();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
EncryptingFragmenter::EncryptingFragmenter(
|
||||
std::shared_ptr<StreamInfo> info,
|
||||
TrackFragment* traf,
|
||||
std::unique_ptr<EncryptionKey> encryption_key,
|
||||
int64_t clear_time,
|
||||
FourCC protection_scheme,
|
||||
uint8_t crypt_byte_block,
|
||||
uint8_t skip_byte_block,
|
||||
MuxerListener* listener)
|
||||
: Fragmenter(info, traf),
|
||||
info_(info),
|
||||
encryption_key_(std::move(encryption_key)),
|
||||
nalu_length_size_(GetNaluLengthSize(*info)),
|
||||
video_codec_(GetCodec(*info)),
|
||||
clear_time_(clear_time),
|
||||
protection_scheme_(protection_scheme),
|
||||
crypt_byte_block_(crypt_byte_block),
|
||||
skip_byte_block_(skip_byte_block),
|
||||
listener_(listener) {
|
||||
DCHECK(encryption_key_);
|
||||
switch (video_codec_) {
|
||||
case kCodecVP9:
|
||||
vpx_parser_.reset(new VP9Parser);
|
||||
break;
|
||||
case kCodecH264:
|
||||
header_parser_.reset(new H264VideoSliceHeaderParser);
|
||||
break;
|
||||
case kCodecHVC1:
|
||||
FALLTHROUGH_INTENDED;
|
||||
case kCodecHEV1:
|
||||
header_parser_.reset(new H265VideoSliceHeaderParser);
|
||||
break;
|
||||
default:
|
||||
if (nalu_length_size_ > 0) {
|
||||
LOG(WARNING) << "Unknown video codec '" << video_codec_
|
||||
<< "', whole subsamples will be encrypted.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EncryptingFragmenter::~EncryptingFragmenter() {}
|
||||
|
||||
Status EncryptingFragmenter::AddSample(std::shared_ptr<MediaSample> sample) {
|
||||
DCHECK(sample);
|
||||
if (!fragment_initialized()) {
|
||||
Status status = InitializeFragment(sample->dts());
|
||||
if (!status.ok())
|
||||
return status;
|
||||
}
|
||||
if (encryptor_) {
|
||||
Status status = EncryptSample(sample);
|
||||
if (!status.ok())
|
||||
return status;
|
||||
}
|
||||
return Fragmenter::AddSample(sample);
|
||||
}
|
||||
|
||||
Status EncryptingFragmenter::InitializeFragment(int64_t first_sample_dts) {
|
||||
Status status = Fragmenter::InitializeFragment(first_sample_dts);
|
||||
if (!status.ok())
|
||||
return status;
|
||||
|
||||
if (header_parser_ && !header_parser_->Initialize(info_->codec_config()))
|
||||
return Status(error::MUXER_FAILURE, "Fail to read SPS and PPS data.");
|
||||
|
||||
traf()->auxiliary_size.sample_info_sizes.clear();
|
||||
traf()->auxiliary_offset.offsets.clear();
|
||||
if (IsSubsampleEncryptionRequired()) {
|
||||
traf()->sample_encryption.flags |=
|
||||
SampleEncryption::kUseSubsampleEncryption;
|
||||
}
|
||||
traf()->sample_encryption.sample_encryption_entries.clear();
|
||||
|
||||
const bool enable_encryption = clear_time_ <= 0;
|
||||
if (!enable_encryption) {
|
||||
// This fragment should be in clear text.
|
||||
// At most two sample description entries, an encrypted entry and a clear
|
||||
// entry, are generated. The 1-based clear entry index is always 2.
|
||||
const uint32_t kClearSampleDescriptionIndex = 2;
|
||||
|
||||
traf()->header.flags |=
|
||||
TrackFragmentHeader::kSampleDescriptionIndexPresentMask;
|
||||
traf()->header.sample_description_index = kClearSampleDescriptionIndex;
|
||||
} else {
|
||||
if (listener_)
|
||||
listener_->OnEncryptionStart();
|
||||
}
|
||||
return PrepareFragmentForEncryption(enable_encryption);
|
||||
}
|
||||
|
||||
void EncryptingFragmenter::FinalizeFragment() {
|
||||
if (encryptor_) {
|
||||
DCHECK_LE(clear_time_, 0);
|
||||
FinalizeFragmentForEncryption();
|
||||
} else {
|
||||
DCHECK_GT(clear_time_, 0);
|
||||
clear_time_ -= fragment_duration();
|
||||
}
|
||||
Fragmenter::FinalizeFragment();
|
||||
}
|
||||
|
||||
Status EncryptingFragmenter::PrepareFragmentForEncryption(
|
||||
bool enable_encryption) {
|
||||
return (!enable_encryption || encryptor_) ? Status::OK : CreateEncryptor();
|
||||
}
|
||||
|
||||
void EncryptingFragmenter::FinalizeFragmentForEncryption() {
|
||||
// The offset will be adjusted in Segmenter after knowing moof size.
|
||||
traf()->auxiliary_offset.offsets.push_back(0);
|
||||
|
||||
// For 'cbcs' scheme, Constant IVs SHALL be used.
|
||||
const uint8_t per_sample_iv_size =
|
||||
(protection_scheme_ == FOURCC_cbcs) ? 0 :
|
||||
static_cast<uint8_t>(encryptor_->iv().size());
|
||||
traf()->sample_encryption.iv_size = per_sample_iv_size;
|
||||
|
||||
// Optimize saiz box.
|
||||
SampleAuxiliaryInformationSize& saiz = traf()->auxiliary_size;
|
||||
saiz.sample_count =
|
||||
static_cast<uint32_t>(traf()->runs[0].sample_sizes.size());
|
||||
if (!saiz.sample_info_sizes.empty()) {
|
||||
if (!OptimizeSampleEntries(&saiz.sample_info_sizes,
|
||||
&saiz.default_sample_info_size)) {
|
||||
saiz.default_sample_info_size = 0;
|
||||
}
|
||||
} else {
|
||||
// |sample_info_sizes| table is filled in only for subsample encryption,
|
||||
// otherwise |sample_info_size| is just the IV size.
|
||||
DCHECK(!IsSubsampleEncryptionRequired());
|
||||
saiz.default_sample_info_size = static_cast<uint8_t>(per_sample_iv_size);
|
||||
}
|
||||
|
||||
// It should only happen with full sample encryption + constant iv, i.e.
|
||||
// 'cbcs' applying to audio.
|
||||
if (saiz.default_sample_info_size == 0 && saiz.sample_info_sizes.empty()) {
|
||||
DCHECK_EQ(protection_scheme_, FOURCC_cbcs);
|
||||
DCHECK(!IsSubsampleEncryptionRequired());
|
||||
// ISO/IEC 23001-7:2016(E) The sample auxiliary information would then be
|
||||
// empty and should be emitted. Clear saiz and saio boxes so they are not
|
||||
// written.
|
||||
saiz.sample_count = 0;
|
||||
traf()->auxiliary_offset.offsets.clear();
|
||||
}
|
||||
}
|
||||
|
||||
Status EncryptingFragmenter::CreateEncryptor() {
|
||||
DCHECK(encryption_key_);
|
||||
std::unique_ptr<AesCryptor> encryptor;
|
||||
switch (protection_scheme_) {
|
||||
case FOURCC_cenc:
|
||||
encryptor.reset(new AesCtrEncryptor);
|
||||
break;
|
||||
case FOURCC_cbc1:
|
||||
encryptor.reset(new AesCbcEncryptor(kNoPadding));
|
||||
break;
|
||||
case FOURCC_cens:
|
||||
encryptor.reset(new AesPatternCryptor(
|
||||
crypt_byte_block(), skip_byte_block(),
|
||||
AesPatternCryptor::kEncryptIfCryptByteBlockRemaining,
|
||||
AesCryptor::kDontUseConstantIv,
|
||||
std::unique_ptr<AesCryptor>(new AesCtrEncryptor())));
|
||||
break;
|
||||
case FOURCC_cbcs:
|
||||
encryptor.reset(new AesPatternCryptor(
|
||||
crypt_byte_block(), skip_byte_block(),
|
||||
AesPatternCryptor::kEncryptIfCryptByteBlockRemaining,
|
||||
AesCryptor::kUseConstantIv,
|
||||
std::unique_ptr<AesCryptor>(new AesCbcEncryptor(kNoPadding))));
|
||||
break;
|
||||
default:
|
||||
return Status(error::MUXER_FAILURE, "Unsupported protection scheme.");
|
||||
}
|
||||
|
||||
DCHECK(!encryption_key_->iv.empty());
|
||||
const bool initialized =
|
||||
encryptor->InitializeWithIv(encryption_key_->key, encryption_key_->iv);
|
||||
if (!initialized)
|
||||
return Status(error::MUXER_FAILURE, "Failed to create the encryptor.");
|
||||
encryptor_ = std::move(encryptor);
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
void EncryptingFragmenter::EncryptBytes(uint8_t* data, size_t size) {
|
||||
DCHECK(encryptor_);
|
||||
CHECK(encryptor_->Crypt(data, size, data));
|
||||
}
|
||||
|
||||
Status EncryptingFragmenter::EncryptSample(
|
||||
std::shared_ptr<MediaSample> sample) {
|
||||
DCHECK(encryptor_);
|
||||
|
||||
SampleEncryptionEntry sample_encryption_entry;
|
||||
// For 'cbcs' scheme, Constant IVs SHALL be used.
|
||||
if (protection_scheme_ != FOURCC_cbcs)
|
||||
sample_encryption_entry.initialization_vector = encryptor_->iv();
|
||||
uint8_t* data = sample->writable_data();
|
||||
if (IsSubsampleEncryptionRequired()) {
|
||||
if (vpx_parser_) {
|
||||
std::vector<VPxFrameInfo> vpx_frames;
|
||||
if (!vpx_parser_->Parse(sample->data(), sample->data_size(),
|
||||
&vpx_frames)) {
|
||||
return Status(error::MUXER_FAILURE, "Failed to parse vpx frame.");
|
||||
}
|
||||
|
||||
const bool is_superframe = vpx_frames.size() > 1;
|
||||
for (const VPxFrameInfo& frame : vpx_frames) {
|
||||
SubsampleEntry subsample;
|
||||
subsample.clear_bytes =
|
||||
static_cast<uint16_t>(frame.uncompressed_header_size);
|
||||
subsample.cipher_bytes = static_cast<uint32_t>(
|
||||
frame.frame_size - frame.uncompressed_header_size);
|
||||
|
||||
// "VP Codec ISO Media File Format Binding" document requires that the
|
||||
// encrypted bytes of each frame within the superframe must be block
|
||||
// aligned so that the counter state can be computed for each frame
|
||||
// within the superframe.
|
||||
// ISO/IEC 23001-7:2016 10.2 'cbc1' 10.3 'cens'
|
||||
// The BytesOfProtectedData size SHALL be a multiple of 16 bytes to
|
||||
// avoid partial blocks in Subsamples.
|
||||
// For consistency, apply block alignment to all frames.
|
||||
const uint16_t misalign_bytes = subsample.cipher_bytes % kCencBlockSize;
|
||||
subsample.clear_bytes += misalign_bytes;
|
||||
subsample.cipher_bytes -= misalign_bytes;
|
||||
|
||||
sample_encryption_entry.subsamples.push_back(subsample);
|
||||
if (subsample.cipher_bytes > 0)
|
||||
EncryptBytes(data + subsample.clear_bytes, subsample.cipher_bytes);
|
||||
data += frame.frame_size;
|
||||
}
|
||||
// Add subsample for the superframe index if exists.
|
||||
if (is_superframe) {
|
||||
size_t index_size = sample->data() + sample->data_size() - data;
|
||||
DCHECK_LE(index_size, 2 + vpx_frames.size() * 4);
|
||||
DCHECK_GE(index_size, 2 + vpx_frames.size() * 1);
|
||||
SubsampleEntry subsample;
|
||||
subsample.clear_bytes = static_cast<uint16_t>(index_size);
|
||||
subsample.cipher_bytes = 0;
|
||||
sample_encryption_entry.subsamples.push_back(subsample);
|
||||
}
|
||||
} else {
|
||||
const Nalu::CodecType nalu_type =
|
||||
(video_codec_ == kCodecHVC1 || video_codec_ == kCodecHEV1)
|
||||
? Nalu::kH265
|
||||
: Nalu::kH264;
|
||||
NaluReader reader(nalu_type, nalu_length_size_, data,
|
||||
sample->data_size());
|
||||
|
||||
// Store the current length of clear data. This is used to squash
|
||||
// multiple unencrypted NAL units into fewer subsample entries.
|
||||
uint64_t accumulated_clear_bytes = 0;
|
||||
|
||||
Nalu nalu;
|
||||
NaluReader::Result result;
|
||||
while ((result = reader.Advance(&nalu)) == NaluReader::kOk) {
|
||||
if (nalu.is_video_slice()) {
|
||||
// For video-slice NAL units, encrypt the video slice. This skips
|
||||
// the frame header. If this is an unrecognized codec (e.g. H.265),
|
||||
// the whole NAL unit will be encrypted.
|
||||
const int64_t video_slice_header_size =
|
||||
header_parser_ ? header_parser_->GetHeaderSize(nalu) : 0;
|
||||
if (video_slice_header_size < 0)
|
||||
return Status(error::MUXER_FAILURE, "Failed to read slice header.");
|
||||
|
||||
uint64_t current_clear_bytes =
|
||||
nalu.header_size() + video_slice_header_size;
|
||||
uint64_t cipher_bytes = nalu.payload_size() - video_slice_header_size;
|
||||
|
||||
// ISO/IEC 23001-7:2016 10.2 'cbc1' 10.3 'cens'
|
||||
// The BytesOfProtectedData size SHALL be a multiple of 16 bytes to
|
||||
// avoid partial blocks in Subsamples.
|
||||
if (protection_scheme_ == FOURCC_cbc1 ||
|
||||
protection_scheme_ == FOURCC_cens) {
|
||||
const uint16_t misalign_bytes = cipher_bytes % kCencBlockSize;
|
||||
current_clear_bytes += misalign_bytes;
|
||||
cipher_bytes -= misalign_bytes;
|
||||
}
|
||||
|
||||
const uint8_t* nalu_data = nalu.data() + current_clear_bytes;
|
||||
EncryptBytes(const_cast<uint8_t*>(nalu_data), cipher_bytes);
|
||||
|
||||
AddSubsamples(
|
||||
accumulated_clear_bytes + nalu_length_size_ + current_clear_bytes,
|
||||
cipher_bytes, &sample_encryption_entry.subsamples);
|
||||
accumulated_clear_bytes = 0;
|
||||
} else {
|
||||
// For non-video-slice NAL units, don't encrypt.
|
||||
accumulated_clear_bytes +=
|
||||
nalu_length_size_ + nalu.header_size() + nalu.payload_size();
|
||||
}
|
||||
}
|
||||
if (result != NaluReader::kEOStream)
|
||||
return Status(error::MUXER_FAILURE, "Failed to parse NAL units.");
|
||||
AddSubsamples(accumulated_clear_bytes, 0,
|
||||
&sample_encryption_entry.subsamples);
|
||||
}
|
||||
DCHECK_EQ(sample_encryption_entry.GetTotalSizeOfSubsamples(),
|
||||
sample->data_size());
|
||||
|
||||
// The length of per-sample auxiliary datum, defined in CENC ch. 7.
|
||||
traf()->auxiliary_size.sample_info_sizes.push_back(
|
||||
sample_encryption_entry.ComputeSize());
|
||||
} else {
|
||||
DCHECK_LE(crypt_byte_block(), 1u);
|
||||
DCHECK_EQ(skip_byte_block(), 0u);
|
||||
EncryptBytes(data, sample->data_size());
|
||||
}
|
||||
|
||||
traf()->sample_encryption.sample_encryption_entries.push_back(
|
||||
sample_encryption_entry);
|
||||
encryptor_->UpdateIv();
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
bool EncryptingFragmenter::IsSubsampleEncryptionRequired() {
|
||||
return vpx_parser_ || nalu_length_size_ != 0;
|
||||
}
|
||||
|
||||
} // namespace mp4
|
||||
} // namespace media
|
||||
} // namespace shaka
|
|
@ -1,111 +0,0 @@
|
|||
// Copyright 2014 Google Inc. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd
|
||||
|
||||
#ifndef MEDIA_FORMATS_MP4_ENCRYPTING_FRAGMENTER_H_
|
||||
#define MEDIA_FORMATS_MP4_ENCRYPTING_FRAGMENTER_H_
|
||||
|
||||
#include <memory>
|
||||
#include "packager/media/base/fourccs.h"
|
||||
#include "packager/media/codecs/video_slice_header_parser.h"
|
||||
#include "packager/media/codecs/vpx_parser.h"
|
||||
#include "packager/media/event/muxer_listener.h"
|
||||
#include "packager/media/formats/mp4/fragmenter.h"
|
||||
|
||||
namespace shaka {
|
||||
namespace media {
|
||||
|
||||
class AesCryptor;
|
||||
class StreamInfo;
|
||||
struct EncryptionKey;
|
||||
|
||||
namespace mp4 {
|
||||
|
||||
/// EncryptingFragmenter generates MP4 fragments with sample encrypted.
|
||||
class EncryptingFragmenter : public Fragmenter {
|
||||
public:
|
||||
/// @param info contains stream information.
|
||||
/// @param traf points to a TrackFragment box.
|
||||
/// @param encryption_key contains the encryption parameters.
|
||||
/// @param clear_time specifies clear lead duration in units of the current
|
||||
/// track's timescale.
|
||||
/// @param protection_scheme specifies the protection scheme: 'cenc', 'cens',
|
||||
/// 'cbc1', 'cbcs'.
|
||||
/// @param crypt_byte_block indicates number of encrypted blocks (16-byte) in
|
||||
/// pattern based encryption.
|
||||
/// @param skip_byte_block indicates number of unencrypted blocks (16-byte)
|
||||
/// in pattern based encryption.
|
||||
EncryptingFragmenter(std::shared_ptr<StreamInfo> info,
|
||||
TrackFragment* traf,
|
||||
std::unique_ptr<EncryptionKey> encryption_key,
|
||||
int64_t clear_time,
|
||||
FourCC protection_scheme,
|
||||
uint8_t crypt_byte_block,
|
||||
uint8_t skip_byte_block,
|
||||
MuxerListener* listener);
|
||||
|
||||
~EncryptingFragmenter() override;
|
||||
|
||||
/// @name Fragmenter implementation overrides.
|
||||
/// @{
|
||||
Status AddSample(std::shared_ptr<MediaSample> sample) override;
|
||||
Status InitializeFragment(int64_t first_sample_dts) override;
|
||||
void FinalizeFragment() override;
|
||||
/// @}
|
||||
|
||||
protected:
|
||||
/// Prepare current fragment for encryption.
|
||||
/// @return OK on success, an error status otherwise.
|
||||
virtual Status PrepareFragmentForEncryption(bool enable_encryption);
|
||||
/// Finalize current fragment for encryption.
|
||||
virtual void FinalizeFragmentForEncryption();
|
||||
|
||||
/// Create the encryptor for the internal encryption key. The existing
|
||||
/// encryptor will be reset if it is not NULL.
|
||||
/// @return OK on success, an error status otherwise.
|
||||
Status CreateEncryptor();
|
||||
|
||||
const EncryptionKey* encryption_key() const { return encryption_key_.get(); }
|
||||
AesCryptor* encryptor() { return encryptor_.get(); }
|
||||
FourCC protection_scheme() const { return protection_scheme_; }
|
||||
uint8_t crypt_byte_block() const { return crypt_byte_block_; }
|
||||
uint8_t skip_byte_block() const { return skip_byte_block_; }
|
||||
|
||||
void set_encryption_key(std::unique_ptr<EncryptionKey> encryption_key) {
|
||||
encryption_key_ = std::move(encryption_key);
|
||||
}
|
||||
|
||||
private:
|
||||
void EncryptBytes(uint8_t* data, size_t size);
|
||||
Status EncryptSample(std::shared_ptr<MediaSample> sample);
|
||||
|
||||
// Should we enable subsample encryption?
|
||||
bool IsSubsampleEncryptionRequired();
|
||||
|
||||
std::shared_ptr<StreamInfo> info_;
|
||||
std::unique_ptr<EncryptionKey> encryption_key_;
|
||||
std::unique_ptr<AesCryptor> encryptor_;
|
||||
// If this stream contains AVC, subsample encryption specifies that the size
|
||||
// and type of NAL units remain unencrypted. This function returns the size of
|
||||
// the size field in bytes. Can be 1, 2 or 4 bytes.
|
||||
const uint8_t nalu_length_size_;
|
||||
const Codec video_codec_;
|
||||
int64_t clear_time_;
|
||||
const FourCC protection_scheme_;
|
||||
const uint8_t crypt_byte_block_;
|
||||
const uint8_t skip_byte_block_;
|
||||
MuxerListener* listener_;
|
||||
|
||||
std::unique_ptr<VPxParser> vpx_parser_;
|
||||
std::unique_ptr<VideoSliceHeaderParser> header_parser_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(EncryptingFragmenter);
|
||||
};
|
||||
|
||||
} // namespace mp4
|
||||
} // namespace media
|
||||
} // namespace shaka
|
||||
|
||||
#endif // MEDIA_FORMATS_MP4_ENCRYPTING_FRAGMENTER_H_
|
|
@ -27,17 +27,35 @@ uint64_t GetSeekPreroll(const StreamInfo& stream_info) {
|
|||
static_cast<const AudioStreamInfo&>(stream_info);
|
||||
return audio_stream_info.seek_preroll_ns();
|
||||
}
|
||||
|
||||
void NewSampleEncryptionEntry(const DecryptConfig& decrypt_config,
|
||||
bool use_constant_iv,
|
||||
TrackFragment* traf) {
|
||||
SampleEncryption& sample_encryption = traf->sample_encryption;
|
||||
SampleEncryptionEntry sample_encryption_entry;
|
||||
if (!use_constant_iv)
|
||||
sample_encryption_entry.initialization_vector = decrypt_config.iv();
|
||||
sample_encryption_entry.subsamples = decrypt_config.subsamples();
|
||||
sample_encryption.sample_encryption_entries.push_back(
|
||||
sample_encryption_entry);
|
||||
traf->auxiliary_size.sample_info_sizes.push_back(
|
||||
sample_encryption_entry.ComputeSize());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Fragmenter::Fragmenter(std::shared_ptr<StreamInfo> info, TrackFragment* traf)
|
||||
: use_decoding_timestamp_in_timeline_(false),
|
||||
Fragmenter::Fragmenter(std::shared_ptr<StreamInfo> stream_info,
|
||||
TrackFragment* traf)
|
||||
: stream_info_(std::move(stream_info)),
|
||||
use_decoding_timestamp_in_timeline_(false),
|
||||
traf_(traf),
|
||||
seek_preroll_(GetSeekPreroll(*info)),
|
||||
seek_preroll_(GetSeekPreroll(*stream_info_)),
|
||||
fragment_initialized_(false),
|
||||
fragment_finalized_(false),
|
||||
fragment_duration_(0),
|
||||
earliest_presentation_time_(kInvalidTime),
|
||||
first_sap_time_(kInvalidTime) {
|
||||
DCHECK(stream_info_);
|
||||
DCHECK(traf);
|
||||
}
|
||||
|
||||
|
@ -66,6 +84,12 @@ Status Fragmenter::AddSample(std::shared_ptr<MediaSample> sample) {
|
|||
traf_->runs[0].sample_flags.push_back(
|
||||
sample->is_key_frame() ? 0 : TrackFragmentHeader::kNonKeySampleMask);
|
||||
|
||||
if (sample->decrypt_config()) {
|
||||
NewSampleEncryptionEntry(
|
||||
*sample->decrypt_config(),
|
||||
!stream_info_->encryption_config().constant_iv.empty(), traf_);
|
||||
}
|
||||
|
||||
data_->AppendArray(sample->data(), sample->data_size());
|
||||
fragment_duration_ += sample->duration();
|
||||
|
||||
|
@ -96,11 +120,15 @@ Status Fragmenter::InitializeFragment(int64_t first_sample_dts) {
|
|||
traf_->runs.clear();
|
||||
traf_->runs.resize(1);
|
||||
traf_->runs[0].flags = TrackFragmentRun::kDataOffsetPresentMask;
|
||||
traf_->auxiliary_size.sample_info_sizes.clear();
|
||||
traf_->auxiliary_offset.offsets.clear();
|
||||
traf_->sample_encryption.sample_encryption_entries.clear();
|
||||
traf_->sample_group_descriptions.clear();
|
||||
traf_->sample_to_groups.clear();
|
||||
traf_->header.sample_description_index = 1; // 1-based.
|
||||
traf_->header.flags = TrackFragmentHeader::kDefaultBaseIsMoofMask |
|
||||
TrackFragmentHeader::kSampleDescriptionIndexPresentMask;
|
||||
|
||||
fragment_duration_ = 0;
|
||||
earliest_presentation_time_ = kInvalidTime;
|
||||
first_sap_time_ = kInvalidTime;
|
||||
|
@ -108,7 +136,13 @@ Status Fragmenter::InitializeFragment(int64_t first_sample_dts) {
|
|||
return Status::OK;
|
||||
}
|
||||
|
||||
void Fragmenter::FinalizeFragment() {
|
||||
Status Fragmenter::FinalizeFragment() {
|
||||
if (stream_info_->is_encrypted()) {
|
||||
Status status = FinalizeFragmentForEncryption();
|
||||
if (!status.ok())
|
||||
return status;
|
||||
}
|
||||
|
||||
// Optimize trun box.
|
||||
traf_->runs[0].sample_count =
|
||||
static_cast<uint32_t>(traf_->runs[0].sample_sizes.size());
|
||||
|
@ -164,6 +198,7 @@ void Fragmenter::FinalizeFragment() {
|
|||
|
||||
fragment_finalized_ = true;
|
||||
fragment_initialized_ = false;
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
void Fragmenter::GenerateSegmentReference(SegmentReference* reference) {
|
||||
|
@ -181,6 +216,58 @@ void Fragmenter::GenerateSegmentReference(SegmentReference* reference) {
|
|||
reference->earliest_presentation_time = earliest_presentation_time_;
|
||||
}
|
||||
|
||||
Status Fragmenter::FinalizeFragmentForEncryption() {
|
||||
SampleEncryption& sample_encryption = traf_->sample_encryption;
|
||||
if (sample_encryption.sample_encryption_entries.empty()) {
|
||||
// This fragment is not encrypted.
|
||||
// There are two sample description entries, an encrypted entry and a clear
|
||||
// entry, are generated. The 1-based clear entry index is always 2.
|
||||
const uint32_t kClearSampleDescriptionIndex = 2;
|
||||
traf_->header.sample_description_index = kClearSampleDescriptionIndex;
|
||||
return Status::OK;
|
||||
}
|
||||
if (sample_encryption.sample_encryption_entries.size() !=
|
||||
traf_->runs[0].sample_sizes.size()) {
|
||||
LOG(ERROR) << "Partially encrypted segment is not supported";
|
||||
return Status(error::MUXER_FAILURE,
|
||||
"Partially encrypted segment is not supported.");
|
||||
}
|
||||
|
||||
const SampleEncryptionEntry& sample_encryption_entry =
|
||||
sample_encryption.sample_encryption_entries.front();
|
||||
const bool use_subsample_encryption =
|
||||
!sample_encryption_entry.subsamples.empty();
|
||||
if (use_subsample_encryption)
|
||||
traf_->sample_encryption.flags |= SampleEncryption::kUseSubsampleEncryption;
|
||||
traf_->sample_encryption.iv_size =
|
||||
sample_encryption_entry.initialization_vector.size();
|
||||
|
||||
// The offset will be adjusted in Segmenter after knowing moof size.
|
||||
traf_->auxiliary_offset.offsets.push_back(0);
|
||||
|
||||
// Optimize saiz box.
|
||||
SampleAuxiliaryInformationSize& saiz = traf_->auxiliary_size;
|
||||
saiz.sample_count = static_cast<uint32_t>(saiz.sample_info_sizes.size());
|
||||
DCHECK_EQ(saiz.sample_info_sizes.size(),
|
||||
traf_->sample_encryption.sample_encryption_entries.size());
|
||||
if (!OptimizeSampleEntries(&saiz.sample_info_sizes,
|
||||
&saiz.default_sample_info_size)) {
|
||||
saiz.default_sample_info_size = 0;
|
||||
}
|
||||
|
||||
// It should only happen with full sample encryption + constant iv, i.e.
|
||||
// 'cbcs' applying to audio.
|
||||
if (saiz.default_sample_info_size == 0 && saiz.sample_info_sizes.empty()) {
|
||||
DCHECK(!use_subsample_encryption);
|
||||
// ISO/IEC 23001-7:2016(E) The sample auxiliary information would then be
|
||||
// empty and should be omitted. Clear saiz and saio boxes so they are not
|
||||
// written.
|
||||
saiz.sample_count = 0;
|
||||
traf_->auxiliary_offset.offsets.clear();
|
||||
}
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
bool Fragmenter::StartsWithSAP() {
|
||||
DCHECK(!traf_->runs.empty());
|
||||
uint32_t start_sample_flag;
|
||||
|
|
|
@ -33,21 +33,21 @@ class Fragmenter {
|
|||
/// @param traf points to a TrackFragment box.
|
||||
Fragmenter(std::shared_ptr<StreamInfo> info, TrackFragment* traf);
|
||||
|
||||
virtual ~Fragmenter();
|
||||
~Fragmenter();
|
||||
|
||||
/// Add a sample to the fragmenter.
|
||||
/// @param sample points to the sample to be added.
|
||||
/// @return OK on success, an error status otherwise.
|
||||
virtual Status AddSample(std::shared_ptr<MediaSample> sample);
|
||||
Status AddSample(std::shared_ptr<MediaSample> sample);
|
||||
|
||||
/// Initialize the fragment with default data.
|
||||
/// @param first_sample_dts specifies the decoding timestamp for the first
|
||||
/// sample for this fragment.
|
||||
/// @return OK on success, an error status otherwise.
|
||||
virtual Status InitializeFragment(int64_t first_sample_dts);
|
||||
Status InitializeFragment(int64_t first_sample_dts);
|
||||
|
||||
/// Finalize and optimize the fragment.
|
||||
virtual void FinalizeFragment();
|
||||
Status FinalizeFragment();
|
||||
|
||||
/// Fill @a reference with current fragment information.
|
||||
void GenerateSegmentReference(SegmentReference* reference);
|
||||
|
@ -82,9 +82,11 @@ class Fragmenter {
|
|||
bool OptimizeSampleEntries(std::vector<T>* entries, T* default_value);
|
||||
|
||||
private:
|
||||
Status FinalizeFragmentForEncryption();
|
||||
// Check if the current fragment starts with SAP.
|
||||
bool StartsWithSAP();
|
||||
|
||||
std::shared_ptr<StreamInfo> stream_info_;
|
||||
bool use_decoding_timestamp_in_timeline_;
|
||||
TrackFragment* traf_;
|
||||
uint64_t seek_preroll_;
|
||||
|
|
|
@ -1,138 +0,0 @@
|
|||
// Copyright 2014 Google Inc. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd
|
||||
|
||||
#include "packager/media/formats/mp4/key_rotation_fragmenter.h"
|
||||
|
||||
#include "packager/media/base/aes_encryptor.h"
|
||||
#include "packager/media/formats/mp4/box_definitions.h"
|
||||
|
||||
namespace shaka {
|
||||
namespace media {
|
||||
namespace mp4 {
|
||||
|
||||
namespace {
|
||||
const bool kInitialEncryptionInfo = true;
|
||||
} // namespace
|
||||
|
||||
KeyRotationFragmenter::KeyRotationFragmenter(MovieFragment* moof,
|
||||
std::shared_ptr<StreamInfo> info,
|
||||
TrackFragment* traf,
|
||||
KeySource* encryption_key_source,
|
||||
KeySource::TrackType track_type,
|
||||
int64_t crypto_period_duration,
|
||||
int64_t clear_time,
|
||||
FourCC protection_scheme,
|
||||
uint8_t crypt_byte_block,
|
||||
uint8_t skip_byte_block,
|
||||
MuxerListener* muxer_listener)
|
||||
: EncryptingFragmenter(info,
|
||||
traf,
|
||||
std::unique_ptr<EncryptionKey>(new EncryptionKey()),
|
||||
clear_time,
|
||||
protection_scheme,
|
||||
crypt_byte_block,
|
||||
skip_byte_block,
|
||||
muxer_listener),
|
||||
moof_(moof),
|
||||
encryption_key_source_(encryption_key_source),
|
||||
track_type_(track_type),
|
||||
crypto_period_duration_(crypto_period_duration),
|
||||
prev_crypto_period_index_(-1),
|
||||
muxer_listener_(muxer_listener) {
|
||||
DCHECK(moof);
|
||||
DCHECK(encryption_key_source);
|
||||
}
|
||||
|
||||
KeyRotationFragmenter::~KeyRotationFragmenter() {}
|
||||
|
||||
Status KeyRotationFragmenter::PrepareFragmentForEncryption(
|
||||
bool enable_encryption) {
|
||||
bool need_to_refresh_encryptor = !encryptor();
|
||||
|
||||
int64_t current_crypto_period_index =
|
||||
traf()->decode_time.decode_time / crypto_period_duration_;
|
||||
if (current_crypto_period_index != prev_crypto_period_index_) {
|
||||
std::unique_ptr<EncryptionKey> encryption_key(new EncryptionKey());
|
||||
Status status = encryption_key_source_->GetCryptoPeriodKey(
|
||||
current_crypto_period_index, track_type_, encryption_key.get());
|
||||
if (!status.ok())
|
||||
return status;
|
||||
if (encryption_key->iv.empty()) {
|
||||
if (!AesCryptor::GenerateRandomIv(protection_scheme(),
|
||||
&encryption_key->iv)) {
|
||||
return Status(error::INTERNAL_ERROR, "Failed to generate random iv.");
|
||||
}
|
||||
}
|
||||
set_encryption_key(std::move(encryption_key));
|
||||
prev_crypto_period_index_ = current_crypto_period_index;
|
||||
need_to_refresh_encryptor = true;
|
||||
}
|
||||
|
||||
DCHECK(encryption_key());
|
||||
const std::vector<ProtectionSystemSpecificInfo>& system_info =
|
||||
encryption_key()->key_system_info;
|
||||
moof_->pssh.resize(system_info.size());
|
||||
for (size_t i = 0; i < system_info.size(); i++) {
|
||||
moof_->pssh[i].raw_box = system_info[i].CreateBox();
|
||||
}
|
||||
|
||||
if (muxer_listener_) {
|
||||
muxer_listener_->OnEncryptionInfoReady(
|
||||
!kInitialEncryptionInfo, protection_scheme(), encryption_key()->key_id,
|
||||
encryption_key()->iv, encryption_key()->key_system_info);
|
||||
}
|
||||
|
||||
// Skip the following steps if the current fragment is not going to be
|
||||
// encrypted. 'pssh' box needs to be included in the fragment, which is
|
||||
// performed above, regardless of whether the fragment is encrypted. This is
|
||||
// necessary for two reasons: 1) Requesting keys before reaching encrypted
|
||||
// content avoids playback delay due to license requests; 2) In Chrome, CDM
|
||||
// must be initialized before starting the playback and CDM can only be
|
||||
// initialized with a valid 'pssh'.
|
||||
if (!enable_encryption) {
|
||||
DCHECK(!encryptor());
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
if (need_to_refresh_encryptor) {
|
||||
Status status = CreateEncryptor();
|
||||
if (!status.ok())
|
||||
return status;
|
||||
}
|
||||
DCHECK(encryptor());
|
||||
|
||||
// Key rotation happens in fragment boundary only in this implementation,
|
||||
// i.e. there is at most one key for the fragment. So there should be only
|
||||
// one entry in SampleGroupDescription box and one entry in SampleToGroup box.
|
||||
// Fill in SampleGroupDescription box information.
|
||||
traf()->sample_group_descriptions.resize(
|
||||
traf()->sample_group_descriptions.size() + 1);
|
||||
SampleGroupDescription& sample_group_description =
|
||||
traf()->sample_group_descriptions.back();
|
||||
sample_group_description.grouping_type = FOURCC_seig;
|
||||
|
||||
sample_group_description.cenc_sample_encryption_info_entries.resize(1);
|
||||
CencSampleEncryptionInfoEntry& sample_group_entry =
|
||||
sample_group_description.cenc_sample_encryption_info_entries.back();
|
||||
sample_group_entry.is_protected = 1;
|
||||
if (protection_scheme() == FOURCC_cbcs) {
|
||||
// For 'cbcs' scheme, Constant IVs SHALL be used.
|
||||
sample_group_entry.per_sample_iv_size = 0;
|
||||
sample_group_entry.constant_iv = encryptor()->iv();
|
||||
} else {
|
||||
sample_group_entry.per_sample_iv_size =
|
||||
static_cast<uint8_t>(encryptor()->iv().size());
|
||||
}
|
||||
sample_group_entry.crypt_byte_block = crypt_byte_block();
|
||||
sample_group_entry.skip_byte_block = skip_byte_block();
|
||||
sample_group_entry.key_id = encryption_key()->key_id;
|
||||
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
} // namespace mp4
|
||||
} // namespace media
|
||||
} // namespace shaka
|
|
@ -1,81 +0,0 @@
|
|||
// Copyright 2014 Google Inc. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd
|
||||
|
||||
#ifndef MEDIA_FORMATS_MP4_KEY_ROTATION_FRAGMENTER_H_
|
||||
#define MEDIA_FORMATS_MP4_KEY_ROTATION_FRAGMENTER_H_
|
||||
|
||||
#include "packager/media/base/fourccs.h"
|
||||
#include "packager/media/base/key_source.h"
|
||||
#include "packager/media/event/muxer_listener.h"
|
||||
#include "packager/media/formats/mp4/encrypting_fragmenter.h"
|
||||
|
||||
namespace shaka {
|
||||
namespace media {
|
||||
namespace mp4 {
|
||||
|
||||
struct MovieFragment;
|
||||
|
||||
/// KeyRotationFragmenter generates MP4 fragments with sample encrypted by
|
||||
/// rotation keys.
|
||||
class KeyRotationFragmenter : public EncryptingFragmenter {
|
||||
public:
|
||||
/// @param moof points to a MovieFragment box.
|
||||
/// @param info contains stream information.
|
||||
/// @param traf points to a TrackFragment box.
|
||||
/// @param encryption_key_source points to the source which generates
|
||||
/// encryption keys.
|
||||
/// @param track_type indicates whether SD key or HD key should be used to
|
||||
/// encrypt the video content.
|
||||
/// @param crypto_period_duration specifies crypto period duration in units
|
||||
/// of the current track's timescale.
|
||||
/// @param clear_time specifies clear lead duration in units of the current
|
||||
/// track's timescale.
|
||||
/// @param protection_scheme specifies the protection scheme: 'cenc', 'cens',
|
||||
/// 'cbc1', 'cbcs'.
|
||||
/// @param crypt_byte_block indicates number of encrypted blocks (16-byte) in
|
||||
/// pattern based encryption.
|
||||
/// @param skip_byte_block indicates number of unencrypted blocks (16-byte)
|
||||
/// in pattern based encryption.
|
||||
/// @param muxer_listener is a pointer to MuxerListener for notifying
|
||||
/// muxer related events. This may be null.
|
||||
KeyRotationFragmenter(MovieFragment* moof,
|
||||
std::shared_ptr<StreamInfo> info,
|
||||
TrackFragment* traf,
|
||||
KeySource* encryption_key_source,
|
||||
KeySource::TrackType track_type,
|
||||
int64_t crypto_period_duration,
|
||||
int64_t clear_time,
|
||||
FourCC protection_scheme,
|
||||
uint8_t crypt_byte_block,
|
||||
uint8_t skip_byte_block,
|
||||
MuxerListener* muxer_listener);
|
||||
~KeyRotationFragmenter() override;
|
||||
|
||||
protected:
|
||||
/// @name Fragmenter implementation overrides.
|
||||
/// @{
|
||||
Status PrepareFragmentForEncryption(bool enable_encryption) override;
|
||||
/// @}
|
||||
|
||||
private:
|
||||
MovieFragment* moof_;
|
||||
|
||||
KeySource* encryption_key_source_;
|
||||
KeySource::TrackType track_type_;
|
||||
const int64_t crypto_period_duration_;
|
||||
int64_t prev_crypto_period_index_;
|
||||
|
||||
// For notifying new pssh boxes to the event handler.
|
||||
MuxerListener* const muxer_listener_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(KeyRotationFragmenter);
|
||||
};
|
||||
|
||||
} // namespace mp4
|
||||
} // namespace media
|
||||
} // namespace shaka
|
||||
|
||||
#endif // MEDIA_FORMATS_MP4_KEY_ROTATION_FRAGMENTER_H_
|
|
@ -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',
|
||||
|
|
|
@ -462,8 +462,11 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) {
|
|||
break;
|
||||
}
|
||||
|
||||
// The stream will be decrypted if a |decryptor_source_| is available.
|
||||
const bool is_encrypted =
|
||||
entry.sinf.info.track_encryption.default_is_protected == 1;
|
||||
decryptor_source_
|
||||
? false
|
||||
: entry.sinf.info.track_encryption.default_is_protected == 1;
|
||||
DVLOG(1) << "is_audio_track_encrypted_: " << is_encrypted;
|
||||
streams.emplace_back(new AudioStreamInfo(
|
||||
track->header.track_id, timescale, duration, codec,
|
||||
|
@ -558,8 +561,11 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) {
|
|||
return false;
|
||||
}
|
||||
|
||||
// The stream will be decrypted if a |decryptor_source_| is available.
|
||||
const bool is_encrypted =
|
||||
entry.sinf.info.track_encryption.default_is_protected == 1;
|
||||
decryptor_source_
|
||||
? false
|
||||
: entry.sinf.info.track_encryption.default_is_protected == 1;
|
||||
DVLOG(1) << "is_video_track_encrypted_: " << is_encrypted;
|
||||
std::shared_ptr<VideoStreamInfo> video_stream_info(new VideoStreamInfo(
|
||||
track->header.track_id, timescale, duration, video_codec,
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -9,17 +9,15 @@
|
|||
#include <algorithm>
|
||||
|
||||
#include "packager/base/logging.h"
|
||||
#include "packager/media/base/aes_cryptor.h"
|
||||
#include "packager/media/base/buffer_writer.h"
|
||||
#include "packager/media/base/key_source.h"
|
||||
#include "packager/media/base/media_sample.h"
|
||||
#include "packager/media/base/muxer_options.h"
|
||||
#include "packager/media/base/muxer_util.h"
|
||||
#include "packager/media/base/video_stream_info.h"
|
||||
#include "packager/media/event/muxer_listener.h"
|
||||
#include "packager/media/base/stream_info.h"
|
||||
#include "packager/media/chunking/chunking_handler.h"
|
||||
#include "packager/media/event/progress_listener.h"
|
||||
#include "packager/media/formats/mp4/box_definitions.h"
|
||||
#include "packager/media/formats/mp4/key_rotation_fragmenter.h"
|
||||
#include "packager/media/formats/mp4/fragmenter.h"
|
||||
#include "packager/version/version.h"
|
||||
|
||||
namespace shaka {
|
||||
|
@ -27,25 +25,6 @@ namespace media {
|
|||
namespace mp4 {
|
||||
|
||||
namespace {
|
||||
const size_t kCencKeyIdSize = 16u;
|
||||
|
||||
// The version of cenc implemented here. CENC 4.
|
||||
const int kCencSchemeVersion = 0x00010000;
|
||||
|
||||
// The default KID for key rotation is all 0s.
|
||||
const uint8_t kKeyRotationDefaultKeyId[] = {
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0
|
||||
};
|
||||
|
||||
// Defines protection pattern for pattern-based encryption.
|
||||
struct ProtectionPattern {
|
||||
uint8_t crypt_byte_block;
|
||||
uint8_t skip_byte_block;
|
||||
};
|
||||
|
||||
static_assert(arraysize(kKeyRotationDefaultKeyId) == kCencKeyIdSize,
|
||||
"cenc_key_id_must_be_size_16");
|
||||
|
||||
uint64_t Rescale(uint64_t time_in_old_scale,
|
||||
uint32_t old_scale,
|
||||
|
@ -53,95 +32,6 @@ uint64_t Rescale(uint64_t time_in_old_scale,
|
|||
return static_cast<double>(time_in_old_scale) / old_scale * new_scale;
|
||||
}
|
||||
|
||||
ProtectionPattern GetProtectionPattern(FourCC protection_scheme,
|
||||
TrackType track_type) {
|
||||
ProtectionPattern pattern;
|
||||
if (protection_scheme != FOURCC_cbcs && protection_scheme != FOURCC_cens) {
|
||||
// Not using pattern encryption.
|
||||
pattern.crypt_byte_block = 0u;
|
||||
pattern.skip_byte_block = 0u;
|
||||
} else if (track_type != kVideo) {
|
||||
// Tracks other than video are protected using whole-block full-sample
|
||||
// encryption, which is essentially a pattern of 1:0. Note that this may not
|
||||
// be the same as the non-pattern based encryption counterparts, e.g. in
|
||||
// 'cens' for full sample encryption, the whole sample is encrypted up to
|
||||
// the last 16-byte boundary, see 23001-7:2016(E) 9.7; while in 'cenc' for
|
||||
// full sample encryption, the last partial 16-byte block is also encrypted,
|
||||
// see 23001-7:2016(E) 9.4.2. Another difference is the use of constant iv.
|
||||
pattern.crypt_byte_block = 1u;
|
||||
pattern.skip_byte_block = 0u;
|
||||
} else {
|
||||
// Use 1:9 pattern for video.
|
||||
const uint8_t kCryptByteBlock = 1u;
|
||||
const uint8_t kSkipByteBlock = 9u;
|
||||
pattern.crypt_byte_block = kCryptByteBlock;
|
||||
pattern.skip_byte_block = kSkipByteBlock;
|
||||
}
|
||||
return pattern;
|
||||
}
|
||||
|
||||
void GenerateSinf(const EncryptionKey& encryption_key,
|
||||
FourCC old_type,
|
||||
FourCC protection_scheme,
|
||||
ProtectionPattern pattern,
|
||||
ProtectionSchemeInfo* sinf) {
|
||||
sinf->format.format = old_type;
|
||||
|
||||
DCHECK_NE(protection_scheme, FOURCC_NULL);
|
||||
sinf->type.type = protection_scheme;
|
||||
sinf->type.version = kCencSchemeVersion;
|
||||
|
||||
auto& track_encryption = sinf->info.track_encryption;
|
||||
track_encryption.default_is_protected = 1;
|
||||
DCHECK(!encryption_key.iv.empty());
|
||||
if (protection_scheme == FOURCC_cbcs) {
|
||||
// ISO/IEC 23001-7:2016 10.4.1
|
||||
// For 'cbcs' scheme, Constant IVs SHALL be used.
|
||||
track_encryption.default_per_sample_iv_size = 0;
|
||||
track_encryption.default_constant_iv = encryption_key.iv;
|
||||
} else {
|
||||
track_encryption.default_per_sample_iv_size =
|
||||
static_cast<uint8_t>(encryption_key.iv.size());
|
||||
}
|
||||
track_encryption.default_crypt_byte_block = pattern.crypt_byte_block;
|
||||
track_encryption.default_skip_byte_block = pattern.skip_byte_block;
|
||||
track_encryption.default_kid = encryption_key.key_id;
|
||||
}
|
||||
|
||||
void GenerateEncryptedSampleEntry(const EncryptionKey& encryption_key,
|
||||
double clear_lead_in_seconds,
|
||||
FourCC protection_scheme,
|
||||
ProtectionPattern pattern,
|
||||
SampleDescription* description) {
|
||||
DCHECK(description);
|
||||
if (description->type == kVideo) {
|
||||
DCHECK_EQ(1u, description->video_entries.size());
|
||||
|
||||
// Add a second entry for clear content if needed.
|
||||
if (clear_lead_in_seconds > 0)
|
||||
description->video_entries.push_back(description->video_entries[0]);
|
||||
|
||||
// Convert the first entry to an encrypted entry.
|
||||
VideoSampleEntry& entry = description->video_entries[0];
|
||||
GenerateSinf(encryption_key, entry.format, protection_scheme, pattern,
|
||||
&entry.sinf);
|
||||
entry.format = FOURCC_encv;
|
||||
} else {
|
||||
DCHECK_EQ(kAudio, description->type);
|
||||
DCHECK_EQ(1u, description->audio_entries.size());
|
||||
|
||||
// Add a second entry for clear content if needed.
|
||||
if (clear_lead_in_seconds > 0)
|
||||
description->audio_entries.push_back(description->audio_entries[0]);
|
||||
|
||||
// Convert the first entry to an encrypted entry.
|
||||
AudioSampleEntry& entry = description->audio_entries[0];
|
||||
GenerateSinf(encryption_key, entry.format, protection_scheme, pattern,
|
||||
&entry.sinf);
|
||||
entry.format = FOURCC_enca;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Segmenter::Segmenter(const MuxerOptions& options,
|
||||
|
@ -153,7 +43,6 @@ Segmenter::Segmenter(const MuxerOptions& options,
|
|||
moof_(new MovieFragment()),
|
||||
fragment_buffer_(new BufferWriter()),
|
||||
sidx_(new SegmentIndex()),
|
||||
muxer_listener_(NULL),
|
||||
progress_listener_(NULL),
|
||||
progress_target_(0),
|
||||
accumulated_progress_(0),
|
||||
|
@ -164,14 +53,7 @@ Segmenter::~Segmenter() {}
|
|||
Status Segmenter::Initialize(
|
||||
const std::vector<std::shared_ptr<StreamInfo>>& streams,
|
||||
MuxerListener* muxer_listener,
|
||||
ProgressListener* progress_listener,
|
||||
KeySource* encryption_key_source,
|
||||
uint32_t max_sd_pixels,
|
||||
uint32_t max_hd_pixels,
|
||||
uint32_t max_uhd1_pixels,
|
||||
double clear_lead_in_seconds,
|
||||
double crypto_period_duration_in_seconds,
|
||||
FourCC protection_scheme) {
|
||||
ProgressListener* progress_listener) {
|
||||
DCHECK_LT(0u, streams.size());
|
||||
muxer_listener_ = muxer_listener;
|
||||
progress_listener_ = progress_listener;
|
||||
|
@ -179,8 +61,6 @@ Status Segmenter::Initialize(
|
|||
|
||||
moof_->tracks.resize(streams.size());
|
||||
fragmenters_.resize(streams.size());
|
||||
const bool key_rotation_enabled = crypto_period_duration_in_seconds != 0;
|
||||
const bool kInitialEncryptionInfo = true;
|
||||
|
||||
for (uint32_t i = 0; i < streams.size(); ++i) {
|
||||
moof_->tracks[i].header.track_id = i + 1;
|
||||
|
@ -189,77 +69,7 @@ Status Segmenter::Initialize(
|
|||
if (sidx_->reference_id == 0)
|
||||
sidx_->reference_id = i + 1;
|
||||
}
|
||||
if (!encryption_key_source) {
|
||||
fragmenters_[i].reset(new Fragmenter(streams[i], &moof_->tracks[i]));
|
||||
continue;
|
||||
}
|
||||
|
||||
KeySource::TrackType track_type = GetTrackTypeForEncryption(
|
||||
*streams[i], max_sd_pixels, max_hd_pixels, max_uhd1_pixels);
|
||||
SampleDescription& description =
|
||||
moov_->tracks[i].media.information.sample_table.description;
|
||||
ProtectionPattern pattern =
|
||||
GetProtectionPattern(protection_scheme, description.type);
|
||||
|
||||
if (key_rotation_enabled) {
|
||||
// Fill encrypted sample entry with default key.
|
||||
EncryptionKey encryption_key;
|
||||
encryption_key.key_id.assign(
|
||||
kKeyRotationDefaultKeyId,
|
||||
kKeyRotationDefaultKeyId + arraysize(kKeyRotationDefaultKeyId));
|
||||
if (!AesCryptor::GenerateRandomIv(protection_scheme,
|
||||
&encryption_key.iv)) {
|
||||
return Status(error::INTERNAL_ERROR, "Failed to generate random iv.");
|
||||
}
|
||||
GenerateEncryptedSampleEntry(encryption_key, clear_lead_in_seconds,
|
||||
protection_scheme, pattern, &description);
|
||||
if (muxer_listener_) {
|
||||
muxer_listener_->OnEncryptionInfoReady(
|
||||
kInitialEncryptionInfo, protection_scheme, encryption_key.key_id,
|
||||
encryption_key.iv, encryption_key.key_system_info);
|
||||
}
|
||||
|
||||
fragmenters_[i].reset(new KeyRotationFragmenter(
|
||||
moof_.get(), streams[i], &moof_->tracks[i], encryption_key_source,
|
||||
track_type,
|
||||
crypto_period_duration_in_seconds * streams[i]->time_scale(),
|
||||
clear_lead_in_seconds * streams[i]->time_scale(), protection_scheme,
|
||||
pattern.crypt_byte_block, pattern.skip_byte_block, muxer_listener_));
|
||||
continue;
|
||||
}
|
||||
|
||||
std::unique_ptr<EncryptionKey> encryption_key(new EncryptionKey());
|
||||
Status status =
|
||||
encryption_key_source->GetKey(track_type, encryption_key.get());
|
||||
if (!status.ok())
|
||||
return status;
|
||||
if (encryption_key->iv.empty()) {
|
||||
if (!AesCryptor::GenerateRandomIv(protection_scheme,
|
||||
&encryption_key->iv)) {
|
||||
return Status(error::INTERNAL_ERROR, "Failed to generate random iv.");
|
||||
}
|
||||
}
|
||||
|
||||
GenerateEncryptedSampleEntry(*encryption_key, clear_lead_in_seconds,
|
||||
protection_scheme, pattern, &description);
|
||||
|
||||
if (moov_->pssh.empty()) {
|
||||
moov_->pssh.resize(encryption_key->key_system_info.size());
|
||||
for (size_t i = 0; i < encryption_key->key_system_info.size(); i++) {
|
||||
moov_->pssh[i].raw_box = encryption_key->key_system_info[i].CreateBox();
|
||||
}
|
||||
|
||||
if (muxer_listener_) {
|
||||
muxer_listener_->OnEncryptionInfoReady(
|
||||
kInitialEncryptionInfo, protection_scheme, encryption_key->key_id,
|
||||
encryption_key->iv, encryption_key->key_system_info);
|
||||
}
|
||||
}
|
||||
|
||||
fragmenters_[i].reset(new EncryptingFragmenter(
|
||||
streams[i], &moof_->tracks[i], std::move(encryption_key),
|
||||
clear_lead_in_seconds * streams[i]->time_scale(), protection_scheme,
|
||||
pattern.crypt_byte_block, pattern.skip_byte_block, muxer_listener_));
|
||||
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<SegmentInfo> segment_info) {
|
||||
if (segment_info->key_rotation_encryption_config) {
|
||||
FinalizeFragmentForKeyRotation(
|
||||
stream_id, segment_info->is_encrypted,
|
||||
*segment_info->key_rotation_encryption_config);
|
||||
}
|
||||
|
||||
DCHECK_LT(stream_id, fragmenters_.size());
|
||||
Fragmenter* fragmenter = fragmenters_[stream_id].get();
|
||||
DCHECK(fragmenter);
|
||||
fragmenter->FinalizeFragment();
|
||||
Status status = fragmenter->FinalizeFragment();
|
||||
if (!status.ok())
|
||||
return status;
|
||||
|
||||
// Check if all tracks are ready for fragmentation.
|
||||
for (const std::unique_ptr<Fragmenter>& fragmenter : fragmenters_) {
|
||||
|
@ -387,7 +206,7 @@ Status Segmenter::FinalizeSegment(size_t stream_id, bool is_subsegment) {
|
|||
|
||||
for (std::unique_ptr<Fragmenter>& fragmenter : fragmenters_)
|
||||
fragmenter->ClearFragmentFinalized();
|
||||
if (!is_subsegment) {
|
||||
if (!segment_info->is_subsegment) {
|
||||
Status status = DoFinalizeSegment();
|
||||
// Reset segment information to initial state.
|
||||
sidx_->references.clear();
|
||||
|
@ -435,6 +254,45 @@ uint32_t Segmenter::GetReferenceStreamId() {
|
|||
return sidx_->reference_id - 1;
|
||||
}
|
||||
|
||||
void Segmenter::FinalizeFragmentForKeyRotation(
|
||||
size_t stream_id,
|
||||
bool fragment_encrypted,
|
||||
const EncryptionConfig& encryption_config) {
|
||||
const std::vector<ProtectionSystemSpecificInfo>& system_info =
|
||||
encryption_config.key_system_info;
|
||||
moof_->pssh.resize(system_info.size());
|
||||
for (size_t i = 0; i < system_info.size(); i++)
|
||||
moof_->pssh[i].raw_box = system_info[i].CreateBox();
|
||||
|
||||
// Skip the following steps if the current fragment is not going to be
|
||||
// encrypted. 'pssh' box needs to be included in the fragment, which is
|
||||
// performed above, regardless of whether the fragment is encrypted. This is
|
||||
// necessary for two reasons: 1) Requesting keys before reaching encrypted
|
||||
// content avoids playback delay due to license requests; 2) In Chrome, CDM
|
||||
// must be initialized before starting the playback and CDM can only be
|
||||
// initialized with a valid 'pssh'.
|
||||
if (!fragment_encrypted)
|
||||
return;
|
||||
|
||||
DCHECK_LT(stream_id, moof_->tracks.size());
|
||||
TrackFragment& traf = moof_->tracks[stream_id];
|
||||
traf.sample_group_descriptions.resize(traf.sample_group_descriptions.size() +
|
||||
1);
|
||||
SampleGroupDescription& sample_group_description =
|
||||
traf.sample_group_descriptions.back();
|
||||
sample_group_description.grouping_type = FOURCC_seig;
|
||||
|
||||
sample_group_description.cenc_sample_encryption_info_entries.resize(1);
|
||||
CencSampleEncryptionInfoEntry& sample_group_entry =
|
||||
sample_group_description.cenc_sample_encryption_info_entries.back();
|
||||
sample_group_entry.is_protected = 1;
|
||||
sample_group_entry.per_sample_iv_size = encryption_config.per_sample_iv_size;
|
||||
sample_group_entry.constant_iv = encryption_config.constant_iv;
|
||||
sample_group_entry.crypt_byte_block = encryption_config.crypt_byte_block;
|
||||
sample_group_entry.skip_byte_block = encryption_config.skip_byte_block;
|
||||
sample_group_entry.key_id = encryption_config.key_id;
|
||||
}
|
||||
|
||||
} // namespace mp4
|
||||
} // namespace media
|
||||
} // namespace shaka
|
||||
|
|
|
@ -18,10 +18,11 @@
|
|||
namespace shaka {
|
||||
namespace media {
|
||||
|
||||
struct EncryptionConfig;
|
||||
struct MuxerOptions;
|
||||
struct SegmentInfo;
|
||||
|
||||
class BufferWriter;
|
||||
class KeySource;
|
||||
class MediaSample;
|
||||
class MuxerListener;
|
||||
class ProgressListener;
|
||||
|
@ -50,35 +51,10 @@ class Segmenter {
|
|||
/// @param streams contains the vector of MediaStreams to be segmented.
|
||||
/// @param muxer_listener receives muxer events. Can be NULL.
|
||||
/// @param progress_listener receives progress updates. Can be NULL.
|
||||
/// @param encryption_key_source points to the key source which contains
|
||||
/// the encryption keys. It can be NULL to indicate that no encryption
|
||||
/// is required.
|
||||
/// @param max_sd_pixels specifies the threshold to determine whether a video
|
||||
/// track should be considered as SD. If the max pixels per frame is
|
||||
/// no higher than max_sd_pixels, it is SD.
|
||||
/// @param max_hd_pixels specifies the threshold to determine whether a video
|
||||
/// track should be considered as HD. If the max pixels per frame is
|
||||
/// higher than max_sd_pixels, but no higher than max_hd_pixels,
|
||||
/// it is HD.
|
||||
/// @param max_uhd1_pixels specifies the threshold to determine whether a video
|
||||
/// track should be considered as UHD1. If the max pixels per frame is
|
||||
/// higher than max_hd_pixels, but no higher than max_uhd1_pixels,
|
||||
/// it is UHD1. Otherwise it is UHD2.
|
||||
/// @param clear_time specifies clear lead duration in seconds.
|
||||
/// @param crypto_period_duration specifies crypto period duration in seconds.
|
||||
/// @param protection_scheme specifies the protection scheme: 'cenc', 'cens',
|
||||
/// 'cbc1', 'cbcs'.
|
||||
/// @return OK on success, an error status otherwise.
|
||||
Status Initialize(const std::vector<std::shared_ptr<StreamInfo>>& streams,
|
||||
MuxerListener* muxer_listener,
|
||||
ProgressListener* progress_listener,
|
||||
KeySource* encryption_key_source,
|
||||
uint32_t max_sd_pixels,
|
||||
uint32_t max_hd_pixels,
|
||||
uint32_t max_uhd1_pixels,
|
||||
double clear_lead_in_seconds,
|
||||
double crypto_period_duration_in_seconds,
|
||||
FourCC protection_scheme);
|
||||
ProgressListener* progress_listener);
|
||||
|
||||
/// Finalize the segmenter.
|
||||
/// @return OK on success, an error status otherwise.
|
||||
|
@ -94,7 +70,8 @@ class Segmenter {
|
|||
/// @param stream_id is the zero-based stream index.
|
||||
/// @param is_subsegment indicates if it is a subsegment (fragment).
|
||||
/// @return OK on success, an error status otherwise.
|
||||
Status FinalizeSegment(size_t stream_id, bool is_subsegment);
|
||||
Status FinalizeSegment(size_t stream_id,
|
||||
std::shared_ptr<SegmentInfo> segment_info);
|
||||
|
||||
/// @return true if there is an initialization range, while setting @a offset
|
||||
/// and @a size; or false if initialization range does not apply.
|
||||
|
@ -138,6 +115,11 @@ class Segmenter {
|
|||
|
||||
uint32_t GetReferenceStreamId();
|
||||
|
||||
void FinalizeFragmentForKeyRotation(
|
||||
size_t stream_id,
|
||||
bool fragment_encrypted,
|
||||
const EncryptionConfig& encryption_config);
|
||||
|
||||
const MuxerOptions& options_;
|
||||
std::unique_ptr<FileType> ftyp_;
|
||||
std::unique_ptr<Movie> moov_;
|
||||
|
|
|
@ -6,19 +6,22 @@
|
|||
|
||||
#include <gtest/gtest.h>
|
||||
#include <memory>
|
||||
#include "packager/media/base/fixed_key_source.h"
|
||||
#include "packager/media/formats/webm/segmenter_test_base.h"
|
||||
|
||||
namespace shaka {
|
||||
namespace media {
|
||||
namespace {
|
||||
|
||||
const uint64_t kDuration = 1000;
|
||||
const uint64_t kDuration = 1000u;
|
||||
const bool kSubsegment = true;
|
||||
const std::string kKeyId = "4c6f72656d20697073756d20646f6c6f";
|
||||
const std::string kIv = "0123456789012345";
|
||||
const std::string kKey = "01234567890123456789012345678901";
|
||||
const std::string kPsshData = "";
|
||||
const uint8_t kPerSampleIvSize = 8u;
|
||||
const uint8_t kKeyId[] = {
|
||||
0x4c, 0x6f, 0x72, 0x65, 0x6d, 0x20, 0x69, 0x70,
|
||||
0x73, 0x75, 0x6d, 0x20, 0x64, 0x6f, 0x6c, 0x6f,
|
||||
};
|
||||
const uint8_t kIv[] = {
|
||||
0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x23, 0x45,
|
||||
};
|
||||
const uint8_t kBasicSupportData[] = {
|
||||
// ID: EBML Header omitted.
|
||||
// ID: Segment, Payload Size: 432
|
||||
|
@ -174,7 +177,7 @@ const uint8_t kBasicSupportData[] = {
|
|||
// IV:
|
||||
0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x23, 0x45,
|
||||
// Frame Data:
|
||||
0xcc, 0x03, 0xef, 0xc4, 0xf4,
|
||||
0xde, 0xad, 0xbe, 0xef, 0x00,
|
||||
// ID: BlockGroup, Payload Size: 24
|
||||
0xa0, 0x98,
|
||||
// ID: Block, Payload Size: 18
|
||||
|
@ -182,34 +185,37 @@ const uint8_t kBasicSupportData[] = {
|
|||
// Signal Byte: Encrypted
|
||||
0x01,
|
||||
// IV:
|
||||
0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x23, 0x46,
|
||||
0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x23, 0x45,
|
||||
// Frame Data:
|
||||
0xbf, 0x38, 0x72, 0x20, 0xac,
|
||||
0xde, 0xad, 0xbe, 0xef, 0x00,
|
||||
// BlockDuration: 1000
|
||||
0x9b, 0x82, 0x03, 0xe8,
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
class EncrypedSegmenterTest : public SegmentTestBase {
|
||||
class EncryptedSegmenterTest : public SegmentTestBase {
|
||||
public:
|
||||
EncrypedSegmenterTest() : info_(CreateVideoStreamInfo()) {}
|
||||
EncryptedSegmenterTest() : info_(CreateVideoStreamInfo()) {
|
||||
EncryptionConfig encryption_config;
|
||||
encryption_config.per_sample_iv_size = kPerSampleIvSize;
|
||||
encryption_config.key_id.assign(kKeyId, kKeyId + sizeof(kKeyId));
|
||||
info_->set_is_encrypted(true);
|
||||
info_->set_encryption_config(encryption_config);
|
||||
}
|
||||
|
||||
protected:
|
||||
void InitializeSegmenter(const MuxerOptions& options) {
|
||||
key_source_ =
|
||||
FixedKeySource::CreateFromHexStrings(kKeyId, kKey, kPsshData, kIv);
|
||||
ASSERT_NO_FATAL_FAILURE(
|
||||
CreateAndInitializeSegmenter<webm::TwoPassSingleSegmentSegmenter>(
|
||||
options, info_.get(), key_source_.get(), &segmenter_));
|
||||
options, info_.get(), &segmenter_));
|
||||
}
|
||||
|
||||
std::shared_ptr<StreamInfo> info_;
|
||||
std::unique_ptr<webm::Segmenter> segmenter_;
|
||||
std::unique_ptr<KeySource> key_source_;
|
||||
};
|
||||
|
||||
TEST_F(EncrypedSegmenterTest, BasicSupport) {
|
||||
TEST_F(EncryptedSegmenterTest, BasicSupport) {
|
||||
MuxerOptions options = CreateMuxerOptions();
|
||||
ASSERT_NO_FATAL_FAILURE(InitializeSegmenter(options));
|
||||
|
||||
|
@ -221,6 +227,14 @@ TEST_F(EncrypedSegmenterTest, BasicSupport) {
|
|||
ASSERT_OK(segmenter_->FinalizeSegment(0, 3 * kDuration, !kSubsegment));
|
||||
std::shared_ptr<MediaSample> sample =
|
||||
CreateSample(kKeyFrame, kDuration, kNoSideData);
|
||||
if (i >= 3) {
|
||||
sample->set_is_encrypted(true);
|
||||
std::unique_ptr<DecryptConfig> decrypt_config(
|
||||
new DecryptConfig(info_->encryption_config().key_id,
|
||||
std::vector<uint8_t>(kIv, kIv + sizeof(kIv)),
|
||||
std::vector<SubsampleEntry>()));
|
||||
sample->set_decrypt_config(std::move(decrypt_config));
|
||||
}
|
||||
ASSERT_OK(segmenter_->AddSample(sample));
|
||||
}
|
||||
ASSERT_OK(
|
||||
|
|
|
@ -6,22 +6,18 @@
|
|||
|
||||
#include "packager/media/formats/webm/encryptor.h"
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
#include "packager/media/base/aes_encryptor.h"
|
||||
#include "packager/media/base/buffer_writer.h"
|
||||
#include "packager/media/base/fourccs.h"
|
||||
#include "packager/media/base/media_sample.h"
|
||||
#include "packager/media/codecs/vp9_parser.h"
|
||||
#include "packager/media/formats/webm/webm_constants.h"
|
||||
|
||||
namespace shaka {
|
||||
namespace media {
|
||||
namespace webm {
|
||||
namespace {
|
||||
|
||||
const size_t kAesBlockSize = 16;
|
||||
Status UpdateTrackForEncryption(const std::vector<uint8_t>& key_id,
|
||||
mkvmuxer::Track* track) {
|
||||
DCHECK_EQ(track->content_encoding_entries_size(), 0u);
|
||||
|
||||
Status CreateContentEncryption(mkvmuxer::Track* track, EncryptionKey* key) {
|
||||
if (!track->AddContentEncoding()) {
|
||||
return Status(error::INTERNAL_ERROR,
|
||||
"Could not add ContentEncoding to track.");
|
||||
|
@ -43,98 +39,51 @@ Status CreateContentEncryption(mkvmuxer::Track* track, EncryptionKey* key) {
|
|||
return Status(error::INTERNAL_ERROR, "Cipher Mode is not CTR.");
|
||||
}
|
||||
|
||||
if (!key->key_id.empty() &&
|
||||
!encoding->SetEncryptionID(
|
||||
reinterpret_cast<const uint8_t*>(key->key_id.data()),
|
||||
key->key_id.size())) {
|
||||
if (!encoding->SetEncryptionID(key_id.data(), key_id.size())) {
|
||||
return Status(error::INTERNAL_ERROR, "Error setting encryption ID.");
|
||||
}
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Encryptor::Encryptor() {}
|
||||
|
||||
Encryptor::~Encryptor() {}
|
||||
|
||||
Status Encryptor::Initialize(MuxerListener* muxer_listener,
|
||||
KeySource::TrackType track_type,
|
||||
Codec codec,
|
||||
KeySource* key_source,
|
||||
bool webm_subsample_encryption) {
|
||||
DCHECK(key_source);
|
||||
return CreateEncryptor(muxer_listener, track_type, codec, key_source,
|
||||
webm_subsample_encryption);
|
||||
}
|
||||
|
||||
Status Encryptor::AddTrackInfo(mkvmuxer::Track* track) {
|
||||
DCHECK(key_);
|
||||
return CreateContentEncryption(track, key_.get());
|
||||
}
|
||||
|
||||
Status Encryptor::EncryptFrame(std::shared_ptr<MediaSample> sample,
|
||||
bool encrypt_frame) {
|
||||
DCHECK(encryptor_);
|
||||
|
||||
void UpdateFrameForEncryption(MediaSample* sample) {
|
||||
const size_t sample_size = sample->data_size();
|
||||
// We need to parse the frame (which also updates the vpx parser) even if the
|
||||
// frame is not encrypted as the next (encrypted) frame may be dependent on
|
||||
// this clear frame.
|
||||
std::vector<VPxFrameInfo> vpx_frames;
|
||||
if (vpx_parser_) {
|
||||
if (!vpx_parser_->Parse(sample->data(), sample_size, &vpx_frames)) {
|
||||
return Status(error::MUXER_FAILURE, "Failed to parse VPx frame.");
|
||||
}
|
||||
}
|
||||
|
||||
if (encrypt_frame) {
|
||||
const size_t iv_size = encryptor_->iv().size();
|
||||
if (iv_size != kWebMIvSize) {
|
||||
return Status(error::MUXER_FAILURE,
|
||||
"Incorrect size WebM encryption IV.");
|
||||
}
|
||||
if (vpx_frames.size()) {
|
||||
if (sample->decrypt_config()) {
|
||||
auto* decrypt_config = sample->decrypt_config();
|
||||
const size_t iv_size = decrypt_config->iv().size();
|
||||
DCHECK_EQ(iv_size, kWebMIvSize);
|
||||
if (!decrypt_config->subsamples().empty()) {
|
||||
auto& subsamples = decrypt_config->subsamples();
|
||||
// Use partitioned subsample encryption: | signal_byte(3) | iv
|
||||
// | num_partitions | partition_offset * n | enc_data |
|
||||
|
||||
if (vpx_frames.size() > kWebMMaxSubsamples) {
|
||||
return Status(error::MUXER_FAILURE,
|
||||
"Maximum number of VPx encryption partitions exceeded.");
|
||||
}
|
||||
size_t num_partitions =
|
||||
vpx_frames.size() == 1 ? 1 : vpx_frames.size() * 2;
|
||||
size_t header_size = kWebMSignalByteSize + iv_size +
|
||||
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<uint8_t>(num_partitions);
|
||||
|
||||
BufferWriter offsets_buffer;
|
||||
uint32_t partition_offset = 0;
|
||||
BufferWriter offsets_buffer(kWebMPartitionOffsetSize * num_partitions);
|
||||
for (const auto& vpx_frame : vpx_frames) {
|
||||
uint32_t encrypted_size = static_cast<uint32_t>(
|
||||
vpx_frame.frame_size - vpx_frame.uncompressed_header_size);
|
||||
encrypted_size -= encrypted_size % kAesBlockSize;
|
||||
uint32_t clear_size =
|
||||
static_cast<uint32_t>(vpx_frame.frame_size - encrypted_size);
|
||||
partition_offset += clear_size;
|
||||
for (size_t i = 0; i < subsamples.size() - 1; ++i) {
|
||||
partition_offset += subsamples[i].clear_bytes;
|
||||
offsets_buffer.AppendInt(partition_offset);
|
||||
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<MediaSample> sample,
|
|||
offsets_buffer.Buffer(), offsets_buffer.Size());
|
||||
} else {
|
||||
// Use whole-frame encryption: | signal_byte(1) | iv | enc_data |
|
||||
|
||||
sample->resize_data(sample_size + iv_size + kWebMSignalByteSize);
|
||||
uint8_t* sample_data = sample->writable_data();
|
||||
|
||||
// Encrypt the data in-place.
|
||||
if (!encryptor_->Crypt(sample_data, sample_size, sample_data)) {
|
||||
return Status(error::MUXER_FAILURE, "Failed to encrypt the frame.");
|
||||
}
|
||||
|
||||
// First move the sample data to after the IV; then write the IV and
|
||||
// signal byte.
|
||||
memmove(sample_data + iv_size + kWebMSignalByteSize, sample_data,
|
||||
sample_size);
|
||||
sample_data[0] = kWebMEncryptedSignal;
|
||||
memcpy(sample_data + 1, encryptor_->iv().data(), iv_size);
|
||||
memcpy(sample_data + 1, decrypt_config->iv().data(), iv_size);
|
||||
}
|
||||
encryptor_->UpdateIv();
|
||||
} else {
|
||||
// Clear sample: | signal_byte(0) | data |
|
||||
sample->resize_data(sample_size + 1);
|
||||
|
@ -167,45 +109,6 @@ Status Encryptor::EncryptFrame(std::shared_ptr<MediaSample> sample,
|
|||
memmove(sample_data + 1, sample_data, sample_size);
|
||||
sample_data[0] = 0x00;
|
||||
}
|
||||
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
Status Encryptor::CreateEncryptor(MuxerListener* muxer_listener,
|
||||
KeySource::TrackType track_type,
|
||||
Codec codec,
|
||||
KeySource* key_source,
|
||||
bool webm_subsample_encryption) {
|
||||
std::unique_ptr<EncryptionKey> encryption_key(new EncryptionKey());
|
||||
Status status = key_source->GetKey(track_type, encryption_key.get());
|
||||
if (!status.ok())
|
||||
return status;
|
||||
if (encryption_key->iv.empty()) {
|
||||
if (!AesCryptor::GenerateRandomIv(FOURCC_cenc, &encryption_key->iv))
|
||||
return Status(error::INTERNAL_ERROR, "Failed to generate random iv.");
|
||||
}
|
||||
DCHECK_EQ(kWebMIvSize, encryption_key->iv.size());
|
||||
std::unique_ptr<AesCtrEncryptor> encryptor(new AesCtrEncryptor());
|
||||
const bool initialized =
|
||||
encryptor->InitializeWithIv(encryption_key->key, encryption_key->iv);
|
||||
if (!initialized)
|
||||
return Status(error::INTERNAL_ERROR, "Failed to create the encryptor.");
|
||||
|
||||
if (webm_subsample_encryption && codec == kCodecVP9) {
|
||||
// Allocate VP9 parser to do subsample encryption of VP9.
|
||||
vpx_parser_.reset(new VP9Parser);
|
||||
}
|
||||
|
||||
if (muxer_listener) {
|
||||
const bool kInitialEncryptionInfo = true;
|
||||
muxer_listener->OnEncryptionInfoReady(
|
||||
kInitialEncryptionInfo, FOURCC_cenc, encryption_key->key_id,
|
||||
encryptor->iv(), encryption_key->key_system_info);
|
||||
}
|
||||
|
||||
key_ = std::move(encryption_key);
|
||||
encryptor_ = std::move(encryptor);
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
} // namespace webm
|
||||
|
|
|
@ -7,63 +7,26 @@
|
|||
#ifndef MEDIA_FORMATS_WEBM_ENCRYPTOR_H_
|
||||
#define MEDIA_FORMATS_WEBM_ENCRYPTOR_H_
|
||||
|
||||
#include <memory>
|
||||
#include "packager/base/macros.h"
|
||||
#include "packager/media/base/key_source.h"
|
||||
#include <vector>
|
||||
|
||||
#include "packager/media/base/status.h"
|
||||
#include "packager/media/base/stream_info.h"
|
||||
#include "packager/media/codecs/vpx_parser.h"
|
||||
#include "packager/media/event/muxer_listener.h"
|
||||
#include "packager/third_party/libwebm/src/mkvmuxer.hpp"
|
||||
|
||||
namespace shaka {
|
||||
namespace media {
|
||||
|
||||
class AesCtrEncryptor;
|
||||
class MediaSample;
|
||||
|
||||
namespace webm {
|
||||
|
||||
/// A helper class used to encrypt WebM frames before being written to the
|
||||
/// Cluster. This can also handle unencrypted frames.
|
||||
class Encryptor {
|
||||
public:
|
||||
Encryptor();
|
||||
~Encryptor();
|
||||
/// Adds the encryption info with the specified key_id to the given track.
|
||||
/// @return OK on success, an error status otherwise.
|
||||
Status UpdateTrackForEncryption(const std::vector<uint8_t>& key_id,
|
||||
mkvmuxer::Track* track);
|
||||
|
||||
/// Initializes the encryptor with the given key source.
|
||||
/// @return OK on success, an error status otherwise.
|
||||
Status Initialize(MuxerListener* muxer_listener,
|
||||
KeySource::TrackType track_type,
|
||||
Codec codec,
|
||||
KeySource* key_source,
|
||||
bool webm_subsample_encryption);
|
||||
|
||||
/// Adds the encryption info to the given track. Initialize must be called
|
||||
/// first.
|
||||
/// @return OK on success, an error status otherwise.
|
||||
Status AddTrackInfo(mkvmuxer::Track* track);
|
||||
|
||||
/// Encrypt the data. This needs to be told whether the current frame will
|
||||
/// be encrypted (e.g. for a clear lead).
|
||||
/// @return OK on success, an error status otherwise.
|
||||
Status EncryptFrame(std::shared_ptr<MediaSample> sample, bool encrypt_frame);
|
||||
|
||||
private:
|
||||
// Create the encryptor for the internal encryption key.
|
||||
Status CreateEncryptor(MuxerListener* muxer_listener,
|
||||
KeySource::TrackType track_type,
|
||||
Codec codec,
|
||||
KeySource* key_source,
|
||||
bool webm_subsample_encryption);
|
||||
|
||||
private:
|
||||
std::unique_ptr<EncryptionKey> key_;
|
||||
std::unique_ptr<AesCtrEncryptor> encryptor_;
|
||||
std::unique_ptr<VPxParser> vpx_parser_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(Encryptor);
|
||||
};
|
||||
/// Update the frame with signal bytes and encryption information if it is
|
||||
/// encrypted.
|
||||
void UpdateFrameForEncryption(MediaSample* sample);
|
||||
|
||||
} // namespace webm
|
||||
} // namespace media
|
||||
|
|
|
@ -0,0 +1,158 @@
|
|||
// Copyright 2017 Google Inc. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd
|
||||
|
||||
#include "packager/media/formats/webm/encryptor.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <memory>
|
||||
#include "packager/media/base/media_sample.h"
|
||||
#include "packager/media/base/test/status_test_util.h"
|
||||
#include "packager/media/formats/webm/webm_constants.h"
|
||||
|
||||
namespace shaka {
|
||||
namespace media {
|
||||
namespace webm {
|
||||
namespace {
|
||||
|
||||
const uint8_t kKeyId[] = {
|
||||
0x4c, 0x6f, 0x72, 0x65, 0x6d, 0x20, 0x69, 0x70,
|
||||
0x73, 0x75, 0x6d, 0x20, 0x64, 0x6f, 0x6c, 0x6f,
|
||||
};
|
||||
const uint8_t kIv[] = {
|
||||
0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x23, 0x45,
|
||||
};
|
||||
// Some dummy data for testing.
|
||||
const uint8_t kData[] = {0x11, 0x22, 0x33, 0x44, 0x55};
|
||||
const bool kKeyFrame = true;
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(EncryptionUtilTest, UpdateTrack) {
|
||||
unsigned int seed = 0;
|
||||
mkvmuxer::VideoTrack video_track(&seed);
|
||||
ASSERT_OK(UpdateTrackForEncryption(
|
||||
std::vector<uint8_t>(kKeyId, kKeyId + sizeof(kKeyId)), &video_track));
|
||||
}
|
||||
|
||||
TEST(EncryptionUtilTest, UpdateTrackWithEmptyKeyId) {
|
||||
const uint8_t kKeyId[] = {};
|
||||
unsigned int seed = 0;
|
||||
mkvmuxer::VideoTrack video_track(&seed);
|
||||
Status status = UpdateTrackForEncryption(
|
||||
std::vector<uint8_t>(kKeyId, kKeyId + sizeof(kKeyId)), &video_track);
|
||||
EXPECT_EQ(error::INTERNAL_ERROR, status.error_code());
|
||||
}
|
||||
|
||||
TEST(EncryptionUtilTest, SampleNotEncrypted) {
|
||||
auto sample = MediaSample::CopyFrom(kData, sizeof(kData), kKeyFrame);
|
||||
UpdateFrameForEncryption(sample.get());
|
||||
ASSERT_EQ(sizeof(kData) + 1, sample->data_size());
|
||||
EXPECT_EQ(0u, sample->data()[0]);
|
||||
EXPECT_EQ(std::vector<uint8_t>(kData, kData + sizeof(kData)),
|
||||
std::vector<uint8_t>(sample->data() + 1,
|
||||
sample->data() + sample->data_size()));
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
struct EncryptionTestCase {
|
||||
const SubsampleEntry* subsamples;
|
||||
size_t num_subsamples;
|
||||
const uint8_t* subsample_partition_data;
|
||||
size_t subsample_partition_data_size;
|
||||
};
|
||||
|
||||
const SubsampleEntry kSubsamples1[] = {
|
||||
SubsampleEntry(0x12, 0x100),
|
||||
};
|
||||
const uint8_t kSubsamplePartitionData1[] = {
|
||||
0x01, 0x00, 0x00, 0x00, 0x12,
|
||||
};
|
||||
const SubsampleEntry kSubsamples2[] = {
|
||||
SubsampleEntry(0x12, 0x100), SubsampleEntry(0x25, 0),
|
||||
};
|
||||
const uint8_t kSubsamplePartitionData2[] = {
|
||||
0x02, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x01, 0x12,
|
||||
};
|
||||
const SubsampleEntry kSubsamples3[] = {
|
||||
SubsampleEntry(0x12, 0x100), SubsampleEntry(0x25, 0x8000),
|
||||
SubsampleEntry(0x234, 0),
|
||||
};
|
||||
const uint8_t kSubsamplePartitionData3[] = {
|
||||
0x04, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x01, 0x12,
|
||||
0x00, 0x00, 0x01, 0x37, 0x00, 0x00, 0x81, 0x37,
|
||||
};
|
||||
const SubsampleEntry kSubsamples4[] = {
|
||||
SubsampleEntry(0x12, 0x100), SubsampleEntry(0x25, 0x8000),
|
||||
SubsampleEntry(0x234, 0x88000), SubsampleEntry(0x02, 0x20),
|
||||
};
|
||||
const uint8_t kSubsamplePartitionData4[] = {
|
||||
0x07, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x01, 0x12, 0x00,
|
||||
0x00, 0x01, 0x37, 0x00, 0x00, 0x81, 0x37, 0x00, 0x00, 0x83,
|
||||
0x6B, 0x00, 0x09, 0x03, 0x6B, 0x00, 0x09, 0x03, 0x6D,
|
||||
};
|
||||
|
||||
EncryptionTestCase kEncryptionTestCases[] = {
|
||||
// Special case with no subsamples.
|
||||
{nullptr, 0, nullptr, 0},
|
||||
{kSubsamples1, arraysize(kSubsamples1), kSubsamplePartitionData1,
|
||||
arraysize(kSubsamplePartitionData1)},
|
||||
{kSubsamples2, arraysize(kSubsamples2), kSubsamplePartitionData2,
|
||||
arraysize(kSubsamplePartitionData2)},
|
||||
{kSubsamples3, arraysize(kSubsamples3), kSubsamplePartitionData3,
|
||||
arraysize(kSubsamplePartitionData3)},
|
||||
{kSubsamples4, arraysize(kSubsamples4), kSubsamplePartitionData4,
|
||||
arraysize(kSubsamplePartitionData4)},
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
class EncryptionUtilEncryptedTest
|
||||
: public ::testing::TestWithParam<EncryptionTestCase> {};
|
||||
|
||||
TEST_P(EncryptionUtilEncryptedTest, SampleEncrypted) {
|
||||
const EncryptionTestCase& test_case = GetParam();
|
||||
|
||||
auto sample = MediaSample::CopyFrom(kData, sizeof(kData), kKeyFrame);
|
||||
sample->set_is_encrypted(true);
|
||||
std::unique_ptr<DecryptConfig> decrypt_config(
|
||||
new DecryptConfig(std::vector<uint8_t>(kKeyId, kKeyId + sizeof(kKeyId)),
|
||||
std::vector<uint8_t>(kIv, kIv + sizeof(kIv)),
|
||||
std::vector<SubsampleEntry>(
|
||||
test_case.subsamples,
|
||||
test_case.subsamples + test_case.num_subsamples)));
|
||||
sample->set_decrypt_config(std::move(decrypt_config));
|
||||
|
||||
UpdateFrameForEncryption(sample.get());
|
||||
ASSERT_EQ(
|
||||
sizeof(kData) + sizeof(kIv) + test_case.subsample_partition_data_size + 1,
|
||||
sample->data_size());
|
||||
if (test_case.num_subsamples > 0)
|
||||
EXPECT_EQ(kWebMEncryptedSignal | kWebMPartitionedSignal, sample->data()[0]);
|
||||
else
|
||||
EXPECT_EQ(kWebMEncryptedSignal, sample->data()[0]);
|
||||
EXPECT_EQ(std::vector<uint8_t>(kIv, kIv + sizeof(kIv)),
|
||||
std::vector<uint8_t>(sample->data() + 1,
|
||||
sample->data() + 1 + sizeof(kIv)));
|
||||
EXPECT_EQ(std::vector<uint8_t>(test_case.subsample_partition_data,
|
||||
test_case.subsample_partition_data +
|
||||
test_case.subsample_partition_data_size),
|
||||
std::vector<uint8_t>(sample->data() + 1 + sizeof(kIv),
|
||||
sample->data() + 1 + sizeof(kIv) +
|
||||
test_case.subsample_partition_data_size));
|
||||
EXPECT_EQ(std::vector<uint8_t>(kData, kData + sizeof(kData)),
|
||||
std::vector<uint8_t>(sample->data() + 1 + sizeof(kIv) +
|
||||
test_case.subsample_partition_data_size,
|
||||
sample->data() + sample->data_size()));
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(Encryption,
|
||||
EncryptionUtilEncryptedTest,
|
||||
testing::ValuesIn(kEncryptionTestCases));
|
||||
|
||||
} // namespace webm
|
||||
} // namespace media
|
||||
} // namespace shaka
|
|
@ -102,7 +102,7 @@ class MultiSegmentSegmenterTest : public SegmentTestBase {
|
|||
void InitializeSegmenter(const MuxerOptions& options) {
|
||||
ASSERT_NO_FATAL_FAILURE(
|
||||
CreateAndInitializeSegmenter<webm::MultiSegmentSegmenter>(
|
||||
options, info_.get(), NULL, &segmenter_));
|
||||
options, info_.get(), &segmenter_));
|
||||
}
|
||||
|
||||
std::string TemplateFileName(int number) const {
|
||||
|
|
|
@ -17,10 +17,15 @@
|
|||
#include "packager/media/codecs/vp_codec_configuration_record.h"
|
||||
#include "packager/media/event/muxer_listener.h"
|
||||
#include "packager/media/event/progress_listener.h"
|
||||
#include "packager/media/formats/webm/encryptor.h"
|
||||
#include "packager/media/formats/webm/webm_constants.h"
|
||||
#include "packager/third_party/libwebm/src/mkvmuxerutil.hpp"
|
||||
#include "packager/third_party/libwebm/src/webmids.hpp"
|
||||
#include "packager/version/version.h"
|
||||
|
||||
using mkvmuxer::AudioTrack;
|
||||
using mkvmuxer::VideoTrack;
|
||||
|
||||
namespace shaka {
|
||||
namespace media {
|
||||
namespace webm {
|
||||
|
@ -35,15 +40,9 @@ Segmenter::~Segmenter() {}
|
|||
|
||||
Status Segmenter::Initialize(StreamInfo* info,
|
||||
ProgressListener* progress_listener,
|
||||
MuxerListener* muxer_listener,
|
||||
KeySource* encryption_key_source,
|
||||
uint32_t max_sd_pixels,
|
||||
uint32_t max_hd_pixels,
|
||||
uint32_t max_uhd1_pixels,
|
||||
double clear_lead_in_seconds) {
|
||||
MuxerListener* muxer_listener) {
|
||||
muxer_listener_ = muxer_listener;
|
||||
info_ = info;
|
||||
clear_lead_ = clear_lead_in_seconds;
|
||||
|
||||
// Use media duration as progress target.
|
||||
progress_target_ = info_->duration();
|
||||
|
@ -65,24 +64,26 @@ Status Segmenter::Initialize(StreamInfo* info,
|
|||
segment_info_.set_duration(1);
|
||||
}
|
||||
|
||||
Status status;
|
||||
if (encryption_key_source) {
|
||||
status = InitializeEncryptor(encryption_key_source,
|
||||
max_sd_pixels,
|
||||
max_hd_pixels,
|
||||
max_uhd1_pixels);
|
||||
if (!status.ok())
|
||||
return status;
|
||||
}
|
||||
|
||||
// Create the track info.
|
||||
// The seed is only used to create a UID which we overwrite later.
|
||||
unsigned int seed = 0;
|
||||
std::unique_ptr<mkvmuxer::Track> track;
|
||||
Status status;
|
||||
switch (info_->stream_type()) {
|
||||
case kStreamVideo:
|
||||
status = CreateVideoTrack(static_cast<VideoStreamInfo*>(info_));
|
||||
case kStreamVideo: {
|
||||
std::unique_ptr<VideoTrack> video_track(new VideoTrack(&seed));
|
||||
status = InitializeVideoTrack(static_cast<VideoStreamInfo*>(info_),
|
||||
video_track.get());
|
||||
track = std::move(video_track);
|
||||
break;
|
||||
case kStreamAudio:
|
||||
status = CreateAudioTrack(static_cast<AudioStreamInfo*>(info_));
|
||||
}
|
||||
case kStreamAudio: {
|
||||
std::unique_ptr<AudioTrack> audio_track(new AudioTrack(&seed));
|
||||
status = InitializeAudioTrack(static_cast<AudioStreamInfo*>(info_),
|
||||
audio_track.get());
|
||||
track = std::move(audio_track);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
NOTIMPLEMENTED() << "Not implemented for stream type: "
|
||||
<< info_->stream_type();
|
||||
|
@ -91,6 +92,20 @@ Status Segmenter::Initialize(StreamInfo* info,
|
|||
if (!status.ok())
|
||||
return status;
|
||||
|
||||
if (info_->is_encrypted()) {
|
||||
if (info->encryption_config().per_sample_iv_size != kWebMIvSize)
|
||||
return Status(error::MUXER_FAILURE, "Incorrect size WebM encryption IV.");
|
||||
status = UpdateTrackForEncryption(info_->encryption_config().key_id,
|
||||
track.get());
|
||||
if (!status.ok())
|
||||
return status;
|
||||
}
|
||||
|
||||
tracks_.AddTrack(track.get(), info_->track_id());
|
||||
// number() is only available after the above instruction.
|
||||
track_id_ = track->number();
|
||||
// |tracks_| owns |track|.
|
||||
track.release();
|
||||
return DoInitialize();
|
||||
}
|
||||
|
||||
|
@ -126,25 +141,8 @@ Status Segmenter::AddSample(std::shared_ptr<MediaSample> sample) {
|
|||
if (!status.ok())
|
||||
return status;
|
||||
|
||||
// Encrypt the frame.
|
||||
if (encryptor_) {
|
||||
// Don't enable encryption in the middle of a segment, i.e. only at the
|
||||
// first frame of a segment.
|
||||
if (new_segment_ && !enable_encryption_) {
|
||||
if (sample->pts() - first_timestamp_ >=
|
||||
clear_lead_ * info_->time_scale()) {
|
||||
enable_encryption_ = true;
|
||||
if (muxer_listener_)
|
||||
muxer_listener_->OnEncryptionStart();
|
||||
}
|
||||
}
|
||||
|
||||
status = encryptor_->EncryptFrame(sample, enable_encryption_);
|
||||
if (!status.ok()) {
|
||||
LOG(ERROR) << "Error encrypting frame.";
|
||||
return status;
|
||||
}
|
||||
}
|
||||
if (info_->is_encrypted())
|
||||
UpdateFrameForEncryption(sample.get());
|
||||
|
||||
new_subsegment_ = false;
|
||||
new_segment_ = false;
|
||||
|
@ -242,13 +240,8 @@ void Segmenter::UpdateProgress(uint64_t progress) {
|
|||
}
|
||||
}
|
||||
|
||||
Status Segmenter::CreateVideoTrack(VideoStreamInfo* info) {
|
||||
// The seed is only used to create a UID which we overwrite later.
|
||||
unsigned int seed = 0;
|
||||
mkvmuxer::VideoTrack* track = new mkvmuxer::VideoTrack(&seed);
|
||||
if (!track)
|
||||
return Status(error::INTERNAL_ERROR, "Failed to create video track.");
|
||||
|
||||
Status Segmenter::InitializeVideoTrack(const VideoStreamInfo* info,
|
||||
VideoTrack* track) {
|
||||
if (info->codec() == kCodecVP8) {
|
||||
track->set_codec_id(mkvmuxer::Tracks::kVp8CodecId);
|
||||
} else if (info->codec() == kCodecVP9) {
|
||||
|
@ -283,22 +276,11 @@ Status Segmenter::CreateVideoTrack(VideoStreamInfo* info) {
|
|||
track->set_display_height(info->height());
|
||||
track->set_display_width(info->width() * info->pixel_width() /
|
||||
info->pixel_height());
|
||||
|
||||
if (encryptor_)
|
||||
encryptor_->AddTrackInfo(track);
|
||||
|
||||
tracks_.AddTrack(track, info->track_id());
|
||||
track_id_ = track->number();
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
Status Segmenter::CreateAudioTrack(AudioStreamInfo* info) {
|
||||
// The seed is only used to create a UID which we overwrite later.
|
||||
unsigned int seed = 0;
|
||||
mkvmuxer::AudioTrack* track = new mkvmuxer::AudioTrack(&seed);
|
||||
if (!track)
|
||||
return Status(error::INTERNAL_ERROR, "Failed to create audio track.");
|
||||
|
||||
Status Segmenter::InitializeAudioTrack(const AudioStreamInfo* info,
|
||||
AudioTrack* track) {
|
||||
if (info->codec() == kCodecOpus) {
|
||||
track->set_codec_id(mkvmuxer::Tracks::kOpusCodecId);
|
||||
} else if (info->codec() == kCodecVorbis) {
|
||||
|
@ -322,29 +304,9 @@ Status Segmenter::CreateAudioTrack(AudioStreamInfo* info) {
|
|||
track->set_channels(info->num_channels());
|
||||
track->set_seek_pre_roll(info->seek_preroll_ns());
|
||||
track->set_codec_delay(info->codec_delay_ns());
|
||||
|
||||
if (encryptor_)
|
||||
encryptor_->AddTrackInfo(track);
|
||||
|
||||
tracks_.AddTrack(track, info->track_id());
|
||||
track_id_ = track->number();
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
Status Segmenter::InitializeEncryptor(KeySource* key_source,
|
||||
uint32_t max_sd_pixels,
|
||||
uint32_t max_hd_pixels,
|
||||
uint32_t max_uhd1_pixels) {
|
||||
encryptor_.reset(new Encryptor());
|
||||
const KeySource::TrackType track_type =
|
||||
GetTrackTypeForEncryption(*info_, max_sd_pixels, max_hd_pixels,
|
||||
max_uhd1_pixels);
|
||||
if (track_type == KeySource::TrackType::TRACK_TYPE_UNKNOWN)
|
||||
return Status::OK;
|
||||
return encryptor_->Initialize(muxer_listener_, track_type, info_->codec(),
|
||||
key_source, options_.webm_subsample_encryption);
|
||||
}
|
||||
|
||||
Status Segmenter::WriteFrame(bool write_duration) {
|
||||
// Create a frame manually so we can create non-SimpleBlock frames. This
|
||||
// is required to allow the frame duration to be added. If the duration
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
|
||||
#include <memory>
|
||||
#include "packager/media/base/status.h"
|
||||
#include "packager/media/formats/webm/encryptor.h"
|
||||
#include "packager/media/formats/webm/mkv_writer.h"
|
||||
#include "packager/media/formats/webm/seek_head.h"
|
||||
#include "packager/third_party/libwebm/src/mkvmuxer.hpp"
|
||||
|
@ -20,9 +19,7 @@ namespace media {
|
|||
struct MuxerOptions;
|
||||
|
||||
class AudioStreamInfo;
|
||||
class KeySource;
|
||||
class MediaSample;
|
||||
class StreamInfo;
|
||||
class MuxerListener;
|
||||
class ProgressListener;
|
||||
class StreamInfo;
|
||||
|
@ -40,30 +37,10 @@ class Segmenter {
|
|||
/// Status::OK results in an undefined behavior.
|
||||
/// @param info The stream info for the stream being segmented.
|
||||
/// @param muxer_listener receives muxer events. Can be NULL.
|
||||
/// @param encryption_key_source points to the key source which contains
|
||||
/// the encryption keys. It can be NULL to indicate that no encryption
|
||||
/// is required.
|
||||
/// @param max_sd_pixels specifies the threshold to determine whether a video
|
||||
/// track should be considered as SD. If the max pixels per frame is
|
||||
/// no higher than max_sd_pixels, it is SD.
|
||||
/// @param max_hd_pixels specifies the threshold to determine whether a video
|
||||
/// track should be considered as HD. If the max pixels per frame is
|
||||
/// higher than max_sd_pixels, but no higher than max_hd_pixels,
|
||||
/// it is HD.
|
||||
/// @param max_uhd1_pixels specifies the threshold to determine whether a video
|
||||
/// track should be considered as UHD1. If the max pixels per frame is
|
||||
/// higher than max_hd_pixels, but no higher than max_uhd1_pixels,
|
||||
/// it is UHD1. Otherwise it is UHD2.
|
||||
/// @param clear_time specifies clear lead duration in seconds.
|
||||
/// @return OK on success, an error status otherwise.
|
||||
Status Initialize(StreamInfo* info,
|
||||
ProgressListener* progress_listener,
|
||||
MuxerListener* muxer_listener,
|
||||
KeySource* encryption_key_source,
|
||||
uint32_t max_sd_pixels,
|
||||
uint32_t max_hd_pixels,
|
||||
uint32_t max_uhd1_pixels,
|
||||
double clear_lead_in_seconds);
|
||||
MuxerListener* muxer_listener);
|
||||
|
||||
/// Finalize the segmenter.
|
||||
/// @return OK on success, an error status otherwise.
|
||||
|
@ -121,10 +98,10 @@ class Segmenter {
|
|||
virtual Status DoFinalize() = 0;
|
||||
|
||||
private:
|
||||
Status CreateVideoTrack(VideoStreamInfo* info);
|
||||
Status CreateAudioTrack(AudioStreamInfo* info);
|
||||
Status InitializeEncryptor(KeySource* key_source, uint32_t max_sd_pixels,
|
||||
uint32_t max_hd_pixels, uint32_t max_uhd1_pixels);
|
||||
Status InitializeAudioTrack(const AudioStreamInfo* info,
|
||||
mkvmuxer::AudioTrack* track);
|
||||
Status InitializeVideoTrack(const VideoStreamInfo* info,
|
||||
mkvmuxer::VideoTrack* track);
|
||||
|
||||
// Writes the previous frame to the file.
|
||||
Status WriteFrame(bool write_duration);
|
||||
|
@ -142,10 +119,6 @@ class Segmenter {
|
|||
uint64_t reference_frame_timestamp_ = 0;
|
||||
|
||||
const MuxerOptions& options_;
|
||||
std::unique_ptr<Encryptor> encryptor_;
|
||||
double clear_lead_ = 0;
|
||||
// Encryption is enabled only after clear_lead_.
|
||||
bool enable_encryption_ = false;
|
||||
|
||||
std::unique_ptr<mkvmuxer::Cluster> cluster_;
|
||||
mkvmuxer::Cues cues_;
|
||||
|
|
|
@ -47,14 +47,11 @@ class SegmentTestBase : public ::testing::Test {
|
|||
void CreateAndInitializeSegmenter(
|
||||
const MuxerOptions& options,
|
||||
StreamInfo* info,
|
||||
KeySource* key_source,
|
||||
std::unique_ptr<webm::Segmenter>* result) const {
|
||||
std::unique_ptr<S> segmenter(new S(options));
|
||||
|
||||
ASSERT_OK(segmenter->Initialize(
|
||||
info, NULL /* progress_listener */, NULL /* muxer_listener */,
|
||||
key_source, 0 /* max_sd_pixels */, 0 /* max_hd_pixels */,
|
||||
0 /* max_uhd1_pixels */, 1 /* clear_lead_in_seconds */));
|
||||
ASSERT_OK(segmenter->Initialize(info, nullptr /* progress_listener */,
|
||||
nullptr /* muxer_listener */));
|
||||
*result = std::move(segmenter);
|
||||
}
|
||||
|
||||
|
|
|
@ -141,7 +141,7 @@ class SingleSegmentSegmenterTest : public SegmentTestBase {
|
|||
void InitializeSegmenter(const MuxerOptions& options) {
|
||||
ASSERT_NO_FATAL_FAILURE(
|
||||
CreateAndInitializeSegmenter<webm::TwoPassSingleSegmentSegmenter>(
|
||||
options, info_.get(), NULL, &segmenter_));
|
||||
options, info_.get(), &segmenter_));
|
||||
}
|
||||
|
||||
std::shared_ptr<StreamInfo> info_;
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -24,16 +24,10 @@ WebMMuxer::~WebMMuxer() {}
|
|||
Status WebMMuxer::InitializeMuxer() {
|
||||
CHECK_EQ(streams().size(), 1U);
|
||||
|
||||
if (crypto_period_duration_in_seconds() > 0) {
|
||||
NOTIMPLEMENTED() << "Key rotation is not implemented for WebM";
|
||||
return Status(error::UNIMPLEMENTED,
|
||||
"Key rotation is not implemented for WebM");
|
||||
}
|
||||
|
||||
if (encryption_key_source() && (protection_scheme() != FOURCC_cenc)) {
|
||||
NOTIMPLEMENTED()
|
||||
<< "WebM does not support protection scheme other than 'cenc'.";
|
||||
return Status(error::UNIMPLEMENTED,
|
||||
if (streams()[0]->is_encrypted() &&
|
||||
streams()[0]->encryption_config().protection_scheme != FOURCC_cenc) {
|
||||
LOG(ERROR) << "WebM does not support protection scheme other than 'cenc'.";
|
||||
return Status(error::INVALID_ARGUMENT,
|
||||
"WebM does not support protection scheme other than 'cenc'.");
|
||||
}
|
||||
|
||||
|
@ -44,10 +38,7 @@ Status WebMMuxer::InitializeMuxer() {
|
|||
}
|
||||
|
||||
Status initialized = segmenter_->Initialize(
|
||||
streams()[0].get(), progress_listener(), muxer_listener(),
|
||||
encryption_key_source(), max_sd_pixels(), max_hd_pixels(),
|
||||
max_uhd1_pixels(), clear_lead_in_seconds());
|
||||
|
||||
streams()[0].get(), progress_listener(), muxer_listener());
|
||||
if (!initialized.ok())
|
||||
return initialized;
|
||||
|
||||
|
@ -78,6 +69,12 @@ Status WebMMuxer::FinalizeSegment(size_t stream_id,
|
|||
std::shared_ptr<SegmentInfo> segment_info) {
|
||||
DCHECK(segmenter_);
|
||||
DCHECK_EQ(stream_id, 0u);
|
||||
|
||||
if (segment_info->key_rotation_encryption_config) {
|
||||
NOTIMPLEMENTED() << "Key rotation is not implemented for WebM.";
|
||||
return Status(error::UNIMPLEMENTED,
|
||||
"Key rotation is not implemented for WebM");
|
||||
}
|
||||
return segmenter_->FinalizeSegment(segment_info->start_timestamp,
|
||||
segment_info->duration,
|
||||
segment_info->is_subsegment);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include "packager/media/base/stream_info.h"
|
||||
#include "packager/media/base/test/status_test_util.h"
|
||||
#include "packager/media/chunking/chunking_handler.h"
|
||||
#include "packager/media/crypto/encryption_handler.h"
|
||||
#include "packager/media/formats/mp4/mp4_muxer.h"
|
||||
#include "packager/media/test/test_data_util.h"
|
||||
|
||||
|
@ -91,6 +92,7 @@ class PackagerTestBasic : public ::testing::TestWithParam<const char*> {
|
|||
bool ContentsEqual(const std::string& file1, const std::string file2);
|
||||
|
||||
ChunkingOptions SetupChunkingOptions();
|
||||
EncryptionOptions SetupEncryptionOptions();
|
||||
MuxerOptions SetupMuxerOptions(const std::string& output,
|
||||
bool single_segment);
|
||||
void Remux(const std::string& input,
|
||||
|
@ -139,6 +141,17 @@ ChunkingOptions PackagerTestBasic::SetupChunkingOptions() {
|
|||
return options;
|
||||
}
|
||||
|
||||
EncryptionOptions PackagerTestBasic::SetupEncryptionOptions() {
|
||||
EncryptionOptions options;
|
||||
options.clear_lead_in_seconds = kClearLeadInSeconds;
|
||||
options.protection_scheme = FOURCC_cenc;
|
||||
options.max_sd_pixels = kMaxSDPixels;
|
||||
options.max_hd_pixels = kMaxHDPixels;
|
||||
options.max_uhd1_pixels = kMaxUHD1Pixels;
|
||||
options.crypto_period_duration_in_seconds = kCryptoDurationInSeconds;
|
||||
return options;
|
||||
}
|
||||
|
||||
void PackagerTestBasic::Remux(const std::string& input,
|
||||
const std::string& video_output,
|
||||
const std::string& audio_output,
|
||||
|
@ -157,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<ChunkingHandler>(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<EncryptionHandler>(
|
||||
SetupEncryptionOptions(), encryption_key_source.get());
|
||||
ASSERT_OK(chunking_handler->SetHandler(0, encryption_handler));
|
||||
ASSERT_OK(encryption_handler->SetHandler(0, muxer_video));
|
||||
} else {
|
||||
ASSERT_OK(chunking_handler->SetHandler(0, muxer_video));
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<Muxer> muxer_audio;
|
||||
|
@ -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<ChunkingHandler>(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<EncryptionHandler>(
|
||||
SetupEncryptionOptions(), encryption_key_source.get());
|
||||
ASSERT_OK(chunking_handler->SetHandler(0, encryption_handler));
|
||||
ASSERT_OK(encryption_handler->SetHandler(0, muxer_audio));
|
||||
} else {
|
||||
ASSERT_OK(chunking_handler->SetHandler(0, muxer_audio));
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT_OK(demuxer.Initialize());
|
||||
|
|
Loading…
Reference in New Issue