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_options.h"
|
||||||
#include "packager/media/base/muxer_util.h"
|
#include "packager/media/base/muxer_util.h"
|
||||||
#include "packager/media/chunking/chunking_handler.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/hls_notify_muxer_listener.h"
|
||||||
#include "packager/media/event/mpd_notify_muxer_listener.h"
|
#include "packager/media/event/mpd_notify_muxer_listener.h"
|
||||||
#include "packager/media/event/vod_media_info_dump_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 "";
|
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
|
} // namespace
|
||||||
|
|
||||||
// A fake clock that always return time 0 (epoch). Should only be used for
|
// 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,
|
bool CreateRemuxJobs(const StreamDescriptorList& stream_descriptors,
|
||||||
const ChunkingOptions& chunking_options,
|
const ChunkingOptions& chunking_options,
|
||||||
|
const EncryptionOptions& encryption_options,
|
||||||
const MuxerOptions& muxer_options,
|
const MuxerOptions& muxer_options,
|
||||||
FakeClock* fake_clock,
|
FakeClock* fake_clock,
|
||||||
KeySource* key_source,
|
KeySource* encryption_key_source,
|
||||||
MpdNotifier* mpd_notifier,
|
MpdNotifier* mpd_notifier,
|
||||||
hls::HlsNotifier* hls_notifier,
|
hls::HlsNotifier* hls_notifier,
|
||||||
std::vector<std::unique_ptr<RemuxJob>>* remux_jobs) {
|
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);
|
demuxer->set_dump_stream_info(FLAGS_dump_stream_info);
|
||||||
if (FLAGS_enable_widevine_decryption ||
|
if (FLAGS_enable_widevine_decryption ||
|
||||||
FLAGS_enable_fixed_key_decryption) {
|
FLAGS_enable_fixed_key_decryption) {
|
||||||
std::unique_ptr<KeySource> key_source(CreateDecryptionKeySource());
|
std::unique_ptr<KeySource> decryption_key_source(
|
||||||
if (!key_source)
|
CreateDecryptionKeySource());
|
||||||
|
if (!decryption_key_source)
|
||||||
return false;
|
return false;
|
||||||
demuxer->SetKeySource(std::move(key_source));
|
demuxer->SetKeySource(std::move(decryption_key_source));
|
||||||
}
|
}
|
||||||
remux_jobs->emplace_back(new RemuxJob(std::move(demuxer)));
|
remux_jobs->emplace_back(new RemuxJob(std::move(demuxer)));
|
||||||
previous_input = stream_iter->input;
|
previous_input = stream_iter->input;
|
||||||
|
@ -317,16 +305,6 @@ bool CreateRemuxJobs(const StreamDescriptorList& stream_descriptors,
|
||||||
CreateOutputMuxer(stream_muxer_options, stream_iter->output_format));
|
CreateOutputMuxer(stream_muxer_options, stream_iter->output_format));
|
||||||
if (FLAGS_use_fake_clock_for_muxer) muxer->set_clock(fake_clock);
|
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;
|
std::unique_ptr<MuxerListener> muxer_listener;
|
||||||
DCHECK(!(FLAGS_output_media_info && mpd_notifier));
|
DCHECK(!(FLAGS_output_media_info && mpd_notifier));
|
||||||
if (FLAGS_output_media_info) {
|
if (FLAGS_output_media_info) {
|
||||||
|
@ -363,8 +341,26 @@ bool CreateRemuxJobs(const StreamDescriptorList& stream_descriptors,
|
||||||
if (muxer_listener)
|
if (muxer_listener)
|
||||||
muxer->SetMuxerListener(std::move(muxer_listener));
|
muxer->SetMuxerListener(std::move(muxer_listener));
|
||||||
|
|
||||||
|
Status status;
|
||||||
auto chunking_handler = std::make_shared<ChunkingHandler>(chunking_options);
|
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();
|
auto* demuxer = remux_jobs->back()->demuxer();
|
||||||
const std::string& stream_selector = stream_iter->stream_selector;
|
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) {
|
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()) {
|
if (FLAGS_output_media_info && !FLAGS_mpd_output.empty()) {
|
||||||
NOTIMPLEMENTED() << "ERROR: --output_media_info and --mpd_output do not "
|
NOTIMPLEMENTED() << "ERROR: --output_media_info and --mpd_output do not "
|
||||||
"work together.";
|
"work together.";
|
||||||
|
@ -433,6 +425,8 @@ bool RunPackager(const StreamDescriptorList& stream_descriptors) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ChunkingOptions chunking_options = GetChunkingOptions();
|
ChunkingOptions chunking_options = GetChunkingOptions();
|
||||||
|
EncryptionOptions encryption_options = GetEncryptionOptions();
|
||||||
|
|
||||||
MuxerOptions muxer_options = GetMuxerOptions();
|
MuxerOptions muxer_options = GetMuxerOptions();
|
||||||
|
|
||||||
DCHECK(!stream_descriptors.empty());
|
DCHECK(!stream_descriptors.empty());
|
||||||
|
@ -461,6 +455,8 @@ bool RunPackager(const StreamDescriptorList& stream_descriptors) {
|
||||||
std::unique_ptr<KeySource> encryption_key_source;
|
std::unique_ptr<KeySource> encryption_key_source;
|
||||||
if (FLAGS_enable_widevine_encryption || FLAGS_enable_fixed_key_encryption ||
|
if (FLAGS_enable_widevine_encryption || FLAGS_enable_fixed_key_encryption ||
|
||||||
FLAGS_enable_playready_encryption) {
|
FLAGS_enable_playready_encryption) {
|
||||||
|
if (encryption_options.protection_scheme == FOURCC_NULL)
|
||||||
|
return false;
|
||||||
encryption_key_source = CreateEncryptionKeySource();
|
encryption_key_source = CreateEncryptionKeySource();
|
||||||
if (!encryption_key_source)
|
if (!encryption_key_source)
|
||||||
return false;
|
return false;
|
||||||
|
@ -497,8 +493,8 @@ bool RunPackager(const StreamDescriptorList& stream_descriptors) {
|
||||||
|
|
||||||
std::vector<std::unique_ptr<RemuxJob>> remux_jobs;
|
std::vector<std::unique_ptr<RemuxJob>> remux_jobs;
|
||||||
FakeClock fake_clock;
|
FakeClock fake_clock;
|
||||||
if (!CreateRemuxJobs(stream_descriptors, chunking_options, muxer_options,
|
if (!CreateRemuxJobs(stream_descriptors, chunking_options, encryption_options,
|
||||||
&fake_clock, encryption_key_source.get(),
|
muxer_options, &fake_clock, encryption_key_source.get(),
|
||||||
mpd_notifier.get(), hls_notifier.get(), &remux_jobs)) {
|
mpd_notifier.get(), hls_notifier.get(), &remux_jobs)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
#include "packager/media/base/request_signer.h"
|
#include "packager/media/base/request_signer.h"
|
||||||
#include "packager/media/base/widevine_key_source.h"
|
#include "packager/media/base/widevine_key_source.h"
|
||||||
#include "packager/media/chunking/chunking_handler.h"
|
#include "packager/media/chunking/chunking_handler.h"
|
||||||
|
#include "packager/media/crypto/encryption_handler.h"
|
||||||
#include "packager/media/file/file.h"
|
#include "packager/media/file/file.h"
|
||||||
#include "packager/mpd/base/mpd_options.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 shaka {
|
||||||
namespace media {
|
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> CreateSigner() {
|
||||||
std::unique_ptr<RequestSigner> signer;
|
std::unique_ptr<RequestSigner> signer;
|
||||||
|
@ -156,6 +175,19 @@ ChunkingOptions GetChunkingOptions() {
|
||||||
return chunking_options;
|
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 GetMuxerOptions() {
|
||||||
MuxerOptions muxer_options;
|
MuxerOptions muxer_options;
|
||||||
muxer_options.num_subsegments_per_sidx = FLAGS_num_subsegments_per_sidx;
|
muxer_options.num_subsegments_per_sidx = FLAGS_num_subsegments_per_sidx;
|
||||||
|
|
|
@ -23,6 +23,7 @@ namespace media {
|
||||||
|
|
||||||
class KeySource;
|
class KeySource;
|
||||||
struct ChunkingOptions;
|
struct ChunkingOptions;
|
||||||
|
struct EncryptionOptions;
|
||||||
struct MuxerOptions;
|
struct MuxerOptions;
|
||||||
|
|
||||||
/// Create KeySource based on provided command line options for content
|
/// 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.
|
/// @return ChunkingOptions from provided command line options.
|
||||||
ChunkingOptions GetChunkingOptions();
|
ChunkingOptions GetChunkingOptions();
|
||||||
|
|
||||||
|
/// @return EncryptionOptions from provided command line options.
|
||||||
|
EncryptionOptions GetEncryptionOptions();
|
||||||
|
|
||||||
/// @return MuxerOptions from provided command line options.
|
/// @return MuxerOptions from provided command line options.
|
||||||
MuxerOptions GetMuxerOptions();
|
MuxerOptions GetMuxerOptions();
|
||||||
|
|
||||||
|
|
|
@ -8,43 +8,19 @@
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
#include "packager/media/base/fourccs.h"
|
|
||||||
#include "packager/media/base/media_sample.h"
|
#include "packager/media/base/media_sample.h"
|
||||||
|
|
||||||
namespace shaka {
|
namespace shaka {
|
||||||
namespace media {
|
namespace media {
|
||||||
|
namespace {
|
||||||
|
const bool kInitialEncryptionInfo = true;
|
||||||
|
} // namespace
|
||||||
|
|
||||||
Muxer::Muxer(const MuxerOptions& options)
|
Muxer::Muxer(const MuxerOptions& options)
|
||||||
: options_(options),
|
: options_(options), cancelled_(false), clock_(NULL) {}
|
||||||
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) {}
|
|
||||||
|
|
||||||
Muxer::~Muxer() {}
|
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() {
|
void Muxer::Cancel() {
|
||||||
cancelled_ = true;
|
cancelled_ = true;
|
||||||
}
|
}
|
||||||
|
@ -63,10 +39,34 @@ Status Muxer::Process(std::unique_ptr<StreamData> stream_data) {
|
||||||
switch (stream_data->stream_data_type) {
|
switch (stream_data->stream_data_type) {
|
||||||
case StreamDataType::kStreamInfo:
|
case StreamDataType::kStreamInfo:
|
||||||
streams_.push_back(std::move(stream_data->stream_info));
|
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();
|
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,
|
return FinalizeSegment(stream_data->stream_index,
|
||||||
std::move(stream_data->segment_info));
|
std::move(segment_info));
|
||||||
|
}
|
||||||
case StreamDataType::kMediaSample:
|
case StreamDataType::kMediaSample:
|
||||||
return AddSample(stream_data->stream_index,
|
return AddSample(stream_data->stream_index,
|
||||||
std::move(stream_data->media_sample));
|
std::move(stream_data->media_sample));
|
||||||
|
|
|
@ -13,7 +13,6 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "packager/base/time/clock.h"
|
#include "packager/base/time/clock.h"
|
||||||
#include "packager/media/base/fourccs.h"
|
|
||||||
#include "packager/media/base/media_handler.h"
|
#include "packager/media/base/media_handler.h"
|
||||||
#include "packager/media/base/muxer_options.h"
|
#include "packager/media/base/muxer_options.h"
|
||||||
#include "packager/media/base/status.h"
|
#include "packager/media/base/status.h"
|
||||||
|
@ -23,9 +22,7 @@
|
||||||
namespace shaka {
|
namespace shaka {
|
||||||
namespace media {
|
namespace media {
|
||||||
|
|
||||||
class KeySource;
|
|
||||||
class MediaSample;
|
class MediaSample;
|
||||||
class MediaStream;
|
|
||||||
|
|
||||||
/// Muxer is responsible for taking elementary stream samples and producing
|
/// Muxer is responsible for taking elementary stream samples and producing
|
||||||
/// media containers. An optional KeySource can be provided to Muxer
|
/// media containers. An optional KeySource can be provided to Muxer
|
||||||
|
@ -35,37 +32,6 @@ class Muxer : public MediaHandler {
|
||||||
explicit Muxer(const MuxerOptions& options);
|
explicit Muxer(const MuxerOptions& options);
|
||||||
virtual ~Muxer();
|
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
|
/// Cancel a muxing job in progress. Will cause @a Run to exit with an error
|
||||||
/// status of type CANCELLED.
|
/// status of type CANCELLED.
|
||||||
void Cancel();
|
void Cancel();
|
||||||
|
@ -101,20 +67,9 @@ class Muxer : public MediaHandler {
|
||||||
/// @}
|
/// @}
|
||||||
|
|
||||||
const MuxerOptions& options() const { return options_; }
|
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(); }
|
MuxerListener* muxer_listener() { return muxer_listener_.get(); }
|
||||||
ProgressListener* progress_listener() { return progress_listener_.get(); }
|
ProgressListener* progress_listener() { return progress_listener_.get(); }
|
||||||
base::Clock* clock() { return clock_; }
|
base::Clock* clock() { return clock_; }
|
||||||
FourCC protection_scheme() const { return protection_scheme_; }
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Initialize the muxer.
|
// Initialize the muxer.
|
||||||
|
@ -133,13 +88,7 @@ class Muxer : public MediaHandler {
|
||||||
|
|
||||||
MuxerOptions options_;
|
MuxerOptions options_;
|
||||||
std::vector<std::shared_ptr<StreamInfo>> streams_;
|
std::vector<std::shared_ptr<StreamInfo>> streams_;
|
||||||
KeySource* encryption_key_source_;
|
bool encryption_started_ = false;
|
||||||
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 cancelled_;
|
bool cancelled_;
|
||||||
|
|
||||||
std::unique_ptr<MuxerListener> muxer_listener_;
|
std::unique_ptr<MuxerListener> muxer_listener_;
|
||||||
|
|
|
@ -164,7 +164,6 @@ bool NalUnitToByteStreamConverter::ConvertUnitToByteStream(
|
||||||
size_t sample_size,
|
size_t sample_size,
|
||||||
bool is_key_frame,
|
bool is_key_frame,
|
||||||
std::vector<uint8_t>* output) {
|
std::vector<uint8_t>* output) {
|
||||||
LOG(INFO) << "ConvertUnitToByte";
|
|
||||||
return ConvertUnitToByteStreamWithSubsamples(
|
return ConvertUnitToByteStreamWithSubsamples(
|
||||||
sample, sample_size, is_key_frame, false, output,
|
sample, sample_size, is_key_frame, false, output,
|
||||||
nullptr); // Skip subsample update.
|
nullptr); // Skip subsample update.
|
||||||
|
|
|
@ -55,6 +55,7 @@
|
||||||
],
|
],
|
||||||
'dependencies': [
|
'dependencies': [
|
||||||
'../../base/media_base.gyp:media_base',
|
'../../base/media_base.gyp:media_base',
|
||||||
|
'../../crypto/crypto.gyp:crypto',
|
||||||
'../../codecs/codecs.gyp:codecs',
|
'../../codecs/codecs.gyp:codecs',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
@ -10,8 +10,6 @@
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <memory>
|
#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/audio_stream_info.h"
|
||||||
#include "packager/media/base/buffer_writer.h"
|
#include "packager/media/base/buffer_writer.h"
|
||||||
#include "packager/media/base/media_sample.h"
|
#include "packager/media/base/media_sample.h"
|
||||||
|
@ -29,61 +27,6 @@ namespace {
|
||||||
const uint8_t kVideoStreamId = 0xE0;
|
const uint8_t kVideoStreamId = 0xE0;
|
||||||
const uint8_t kAudioStreamId = 0xC0;
|
const uint8_t kAudioStreamId = 0xC0;
|
||||||
const double kTsTimescale = 90000.0;
|
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
|
} // namespace
|
||||||
|
|
||||||
PesPacketGenerator::PesPacketGenerator() {}
|
PesPacketGenerator::PesPacketGenerator() {}
|
||||||
|
@ -130,20 +73,18 @@ bool PesPacketGenerator::PushSample(std::shared_ptr<MediaSample> sample) {
|
||||||
current_processing_pes_->set_dts(timescale_scale_ * sample->dts());
|
current_processing_pes_->set_dts(timescale_scale_ * sample->dts());
|
||||||
if (stream_type_ == kStreamVideo) {
|
if (stream_type_ == kStreamVideo) {
|
||||||
DCHECK(converter_);
|
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;
|
std::vector<uint8_t> byte_stream;
|
||||||
if (!converter_->ConvertUnitToByteStream(
|
if (!converter_->ConvertUnitToByteStreamWithSubsamples(
|
||||||
sample->data(), sample->data_size(), sample->is_key_frame(),
|
sample->data(), sample->data_size(), sample->is_key_frame(),
|
||||||
&byte_stream)) {
|
kEscapeEncryptedNalu, &byte_stream, &subsamples)) {
|
||||||
LOG(ERROR) << "Failed to convert sample to byte stream.";
|
LOG(ERROR) << "Failed to convert sample to byte stream.";
|
||||||
return false;
|
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_->mutable_data()->swap(byte_stream);
|
||||||
current_processing_pes_->set_stream_id(kVideoStreamId);
|
current_processing_pes_->set_stream_id(kVideoStreamId);
|
||||||
pes_packets_.push_back(std::move(current_processing_pes_));
|
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(),
|
std::vector<uint8_t> aac_frame(sample->data(),
|
||||||
sample->data() + sample->data_size());
|
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.
|
// TODO(rkuroiwa): ConvertToADTS() makes another copy of aac_frame internally.
|
||||||
// Optimize copying in this function, possibly by adding a method on
|
// Optimize copying in this function, possibly by adding a method on
|
||||||
// AACAudioSpecificConfig that takes {pointer, length} pair and returns a
|
// AACAudioSpecificConfig that takes {pointer, length} pair and returns a
|
||||||
|
@ -177,30 +111,6 @@ bool PesPacketGenerator::PushSample(std::shared_ptr<MediaSample> sample) {
|
||||||
return true;
|
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() {
|
size_t PesPacketGenerator::NumberOfReadyPesPackets() {
|
||||||
return pes_packets_.size();
|
return pes_packets_.size();
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,8 +10,6 @@
|
||||||
#include <list>
|
#include <list>
|
||||||
#include <memory>
|
#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/media_sample.h"
|
||||||
#include "packager/media/base/stream_info.h"
|
#include "packager/media/base/stream_info.h"
|
||||||
|
|
||||||
|
@ -46,12 +44,6 @@ class PesPacketGenerator {
|
||||||
/// @return true on success, false otherwise.
|
/// @return true on success, false otherwise.
|
||||||
virtual bool PushSample(std::shared_ptr<MediaSample> sample);
|
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.
|
/// @return The number of PES packets that are ready to be consumed.
|
||||||
virtual size_t NumberOfReadyPesPackets();
|
virtual size_t NumberOfReadyPesPackets();
|
||||||
|
|
||||||
|
@ -83,9 +75,6 @@ class PesPacketGenerator {
|
||||||
|
|
||||||
std::list<std::unique_ptr<PesPacket>> pes_packets_;
|
std::list<std::unique_ptr<PesPacket>> pes_packets_;
|
||||||
|
|
||||||
// Current encryption key.
|
|
||||||
std::unique_ptr<AesCryptor> encryptor_;
|
|
||||||
|
|
||||||
DISALLOW_COPY_AND_ASSIGN(PesPacketGenerator);
|
DISALLOW_COPY_AND_ASSIGN(PesPacketGenerator);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -18,12 +18,21 @@
|
||||||
|
|
||||||
namespace shaka {
|
namespace shaka {
|
||||||
namespace media {
|
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 {
|
namespace mp2t {
|
||||||
|
|
||||||
using ::testing::_;
|
using ::testing::_;
|
||||||
using ::testing::DoAll;
|
using ::testing::DoAll;
|
||||||
using ::testing::SetArgPointee;
|
using ::testing::Eq;
|
||||||
|
using ::testing::IsEmpty;
|
||||||
|
using ::testing::Pointee;
|
||||||
using ::testing::Return;
|
using ::testing::Return;
|
||||||
|
using ::testing::SetArgPointee;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
@ -33,6 +42,7 @@ const uint8_t kAnyData[] = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const bool kIsKeyFrame = true;
|
const bool kIsKeyFrame = true;
|
||||||
|
const bool kEscapeEncryptedNalu = true;
|
||||||
|
|
||||||
// Only Codec and extra data matter for this test. Other values are
|
// Only Codec and extra data matter for this test. Other values are
|
||||||
// bogus.
|
// bogus.
|
||||||
|
@ -88,11 +98,13 @@ class MockNalUnitToByteStreamConverter : public NalUnitToByteStreamConverter {
|
||||||
MOCK_METHOD2(Initialize,
|
MOCK_METHOD2(Initialize,
|
||||||
bool(const uint8_t* decoder_configuration_data,
|
bool(const uint8_t* decoder_configuration_data,
|
||||||
size_t decoder_configuration_data_size));
|
size_t decoder_configuration_data_size));
|
||||||
MOCK_METHOD4(ConvertUnitToByteStream,
|
MOCK_METHOD6(ConvertUnitToByteStreamWithSubsamples,
|
||||||
bool(const uint8_t* sample,
|
bool(const uint8_t* sample,
|
||||||
size_t sample_size,
|
size_t sample_size,
|
||||||
bool is_key_frame,
|
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 {
|
class MockAACAudioSpecificConfig : public AACAudioSpecificConfig {
|
||||||
|
@ -133,91 +145,6 @@ class PesPacketGeneratorTest : public ::testing::Test {
|
||||||
generator_.adts_converter_ = std::move(mock);
|
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_;
|
PesPacketGenerator generator_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -270,9 +197,57 @@ TEST_F(PesPacketGeneratorTest, AddVideoSample) {
|
||||||
|
|
||||||
std::unique_ptr<MockNalUnitToByteStreamConverter> mock(
|
std::unique_ptr<MockNalUnitToByteStreamConverter> mock(
|
||||||
new MockNalUnitToByteStreamConverter());
|
new MockNalUnitToByteStreamConverter());
|
||||||
EXPECT_CALL(*mock,
|
EXPECT_CALL(*mock, ConvertUnitToByteStreamWithSubsamples(
|
||||||
ConvertUnitToByteStream(_, arraysize(kAnyData), kIsKeyFrame, _))
|
_, arraysize(kAnyData), kIsKeyFrame,
|
||||||
.WillOnce(DoAll(SetArgPointee<3>(expected_data), Return(true)));
|
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));
|
UseMockNalUnitToByteStreamConverter(std::move(mock));
|
||||||
|
|
||||||
|
@ -302,8 +277,9 @@ TEST_F(PesPacketGeneratorTest, AddVideoSampleFailedToConvert) {
|
||||||
std::vector<uint8_t> expected_data(kAnyData, kAnyData + arraysize(kAnyData));
|
std::vector<uint8_t> expected_data(kAnyData, kAnyData + arraysize(kAnyData));
|
||||||
std::unique_ptr<MockNalUnitToByteStreamConverter> mock(
|
std::unique_ptr<MockNalUnitToByteStreamConverter> mock(
|
||||||
new MockNalUnitToByteStreamConverter());
|
new MockNalUnitToByteStreamConverter());
|
||||||
EXPECT_CALL(*mock,
|
EXPECT_CALL(*mock, ConvertUnitToByteStreamWithSubsamples(
|
||||||
ConvertUnitToByteStream(_, arraysize(kAnyData), kIsKeyFrame, _))
|
_, arraysize(kAnyData), kIsKeyFrame,
|
||||||
|
kEscapeEncryptedNalu, _, Pointee(IsEmpty())))
|
||||||
.WillOnce(Return(false));
|
.WillOnce(Return(false));
|
||||||
|
|
||||||
UseMockNalUnitToByteStreamConverter(std::move(mock));
|
UseMockNalUnitToByteStreamConverter(std::move(mock));
|
||||||
|
@ -384,8 +360,9 @@ TEST_F(PesPacketGeneratorTest, TimeStampScaling) {
|
||||||
|
|
||||||
std::unique_ptr<MockNalUnitToByteStreamConverter> mock(
|
std::unique_ptr<MockNalUnitToByteStreamConverter> mock(
|
||||||
new MockNalUnitToByteStreamConverter());
|
new MockNalUnitToByteStreamConverter());
|
||||||
EXPECT_CALL(*mock,
|
EXPECT_CALL(*mock, ConvertUnitToByteStreamWithSubsamples(
|
||||||
ConvertUnitToByteStream(_, arraysize(kAnyData), kIsKeyFrame, _))
|
_, arraysize(kAnyData), kIsKeyFrame,
|
||||||
|
kEscapeEncryptedNalu, _, Pointee(IsEmpty())))
|
||||||
.WillOnce(Return(true));
|
.WillOnce(Return(true));
|
||||||
|
|
||||||
UseMockNalUnitToByteStreamConverter(std::move(mock));
|
UseMockNalUnitToByteStreamConverter(std::move(mock));
|
||||||
|
@ -404,323 +381,6 @@ TEST_F(PesPacketGeneratorTest, TimeStampScaling) {
|
||||||
EXPECT_TRUE(generator_.Flush());
|
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 mp2t
|
||||||
} // namespace media
|
} // namespace media
|
||||||
} // namespace shaka
|
} // namespace shaka
|
||||||
|
|
|
@ -22,9 +22,7 @@ Status TsMuxer::InitializeMuxer() {
|
||||||
return Status(error::MUXER_FAILURE, "Cannot handle more than one streams.");
|
return Status(error::MUXER_FAILURE, "Cannot handle more than one streams.");
|
||||||
|
|
||||||
segmenter_.reset(new TsSegmenter(options(), muxer_listener()));
|
segmenter_.reset(new TsSegmenter(options(), muxer_listener()));
|
||||||
Status status = segmenter_->Initialize(
|
Status status = segmenter_->Initialize(*streams()[0]);
|
||||||
*streams()[0], encryption_key_source(), max_sd_pixels(), max_hd_pixels(),
|
|
||||||
max_uhd1_pixels(), clear_lead_in_seconds());
|
|
||||||
FireOnMediaStartEvent();
|
FireOnMediaStartEvent();
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
@ -43,6 +41,11 @@ Status TsMuxer::AddSample(size_t stream_id,
|
||||||
Status TsMuxer::FinalizeSegment(size_t stream_id,
|
Status TsMuxer::FinalizeSegment(size_t stream_id,
|
||||||
std::shared_ptr<SegmentInfo> segment_info) {
|
std::shared_ptr<SegmentInfo> segment_info) {
|
||||||
DCHECK_EQ(stream_id, 0u);
|
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
|
return segment_info->is_subsegment
|
||||||
? Status::OK
|
? Status::OK
|
||||||
: segmenter_->FinalizeSegment(segment_info->start_timestamp,
|
: segmenter_->FinalizeSegment(segment_info->start_timestamp,
|
||||||
|
|
|
@ -31,12 +31,7 @@ TsSegmenter::TsSegmenter(const MuxerOptions& options, MuxerListener* listener)
|
||||||
pes_packet_generator_(new PesPacketGenerator()) {}
|
pes_packet_generator_(new PesPacketGenerator()) {}
|
||||||
TsSegmenter::~TsSegmenter() {}
|
TsSegmenter::~TsSegmenter() {}
|
||||||
|
|
||||||
Status TsSegmenter::Initialize(const StreamInfo& stream_info,
|
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) {
|
|
||||||
if (muxer_options_.segment_template.empty())
|
if (muxer_options_.segment_template.empty())
|
||||||
return Status(error::MUXER_FAILURE, "Segment template not specified.");
|
return Status(error::MUXER_FAILURE, "Segment template not specified.");
|
||||||
if (!ts_writer_->Initialize(stream_info))
|
if (!ts_writer_->Initialize(stream_info))
|
||||||
|
@ -46,37 +41,6 @@ Status TsSegmenter::Initialize(const StreamInfo& stream_info,
|
||||||
"Failed to initialize PesPacketGenerator.");
|
"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();
|
timescale_scale_ = kTsTimescale / stream_info.time_scale();
|
||||||
return Status::OK;
|
return Status::OK;
|
||||||
}
|
}
|
||||||
|
@ -86,6 +50,9 @@ Status TsSegmenter::Finalize() {
|
||||||
}
|
}
|
||||||
|
|
||||||
Status TsSegmenter::AddSample(std::shared_ptr<MediaSample> sample) {
|
Status TsSegmenter::AddSample(std::shared_ptr<MediaSample> sample) {
|
||||||
|
if (sample->is_encrypted())
|
||||||
|
ts_writer_->SignalEncrypted();
|
||||||
|
|
||||||
if (!ts_writer_file_opened_ && !sample->is_key_frame())
|
if (!ts_writer_file_opened_ && !sample->is_key_frame())
|
||||||
LOG(WARNING) << "A segment will start with a non 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);
|
duration * timescale_scale_, file_size);
|
||||||
}
|
}
|
||||||
ts_writer_file_opened_ = false;
|
ts_writer_file_opened_ = false;
|
||||||
total_duration_in_seconds_ += duration * timescale_scale_ / kTsTimescale;
|
|
||||||
}
|
}
|
||||||
current_segment_path_.clear();
|
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;
|
return Status::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,15 +36,9 @@ class TsSegmenter {
|
||||||
~TsSegmenter();
|
~TsSegmenter();
|
||||||
|
|
||||||
/// Initialize the object.
|
/// Initialize the object.
|
||||||
/// Key rotation is not supported.
|
|
||||||
/// @param stream_info is the stream info for the segmenter.
|
/// @param stream_info is the stream info for the segmenter.
|
||||||
/// @return OK on success.
|
/// @return OK on success.
|
||||||
Status Initialize(const StreamInfo& stream_info,
|
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);
|
|
||||||
|
|
||||||
/// Finalize the segmenter.
|
/// Finalize the segmenter.
|
||||||
/// @return OK on success.
|
/// @return OK on success.
|
||||||
|
@ -83,9 +77,6 @@ class TsSegmenter {
|
||||||
// it will open one. This will not close the file.
|
// it will open one. This will not close the file.
|
||||||
Status WritePesPacketsToFile();
|
Status WritePesPacketsToFile();
|
||||||
|
|
||||||
// If conditions are met, notify objects that the data is encrypted.
|
|
||||||
Status NotifyEncrypted();
|
|
||||||
|
|
||||||
const MuxerOptions& muxer_options_;
|
const MuxerOptions& muxer_options_;
|
||||||
MuxerListener* const listener_;
|
MuxerListener* const listener_;
|
||||||
|
|
||||||
|
@ -107,14 +98,6 @@ class TsSegmenter {
|
||||||
// the segment has been finalized.
|
// the segment has been finalized.
|
||||||
std::string current_segment_path_;
|
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);
|
DISALLOW_COPY_AND_ASSIGN(TsSegmenter);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,6 @@
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
#include "packager/media/base/audio_stream_info.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/test/status_test_util.h"
|
||||||
#include "packager/media/base/video_stream_info.h"
|
#include "packager/media/base/video_stream_info.h"
|
||||||
#include "packager/media/event/mock_muxer_listener.h"
|
#include "packager/media/event/mock_muxer_listener.h"
|
||||||
|
@ -54,11 +53,6 @@ class MockPesPacketGenerator : public PesPacketGenerator {
|
||||||
public:
|
public:
|
||||||
MOCK_METHOD1(Initialize, bool(const StreamInfo& info));
|
MOCK_METHOD1(Initialize, bool(const StreamInfo& info));
|
||||||
MOCK_METHOD1(PushSample, bool(std::shared_ptr<MediaSample> sample));
|
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());
|
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
|
} // namespace
|
||||||
|
|
||||||
class TsSegmenterTest : public ::testing::Test {
|
class TsSegmenterTest : public ::testing::Test {
|
||||||
|
@ -124,7 +112,7 @@ TEST_F(TsSegmenterTest, Initialize) {
|
||||||
segmenter.InjectPesPacketGeneratorForTesting(
|
segmenter.InjectPesPacketGeneratorForTesting(
|
||||||
std::move(mock_pes_packet_generator_));
|
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) {
|
TEST_F(TsSegmenterTest, AddSample) {
|
||||||
|
@ -170,7 +158,7 @@ TEST_F(TsSegmenterTest, AddSample) {
|
||||||
segmenter.InjectPesPacketGeneratorForTesting(
|
segmenter.InjectPesPacketGeneratorForTesting(
|
||||||
std::move(mock_pes_packet_generator_));
|
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));
|
EXPECT_OK(segmenter.AddSample(sample));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -265,7 +253,7 @@ TEST_F(TsSegmenterTest, PassedSegmentDuration) {
|
||||||
segmenter.InjectTsWriterForTesting(std::move(mock_ts_writer_));
|
segmenter.InjectTsWriterForTesting(std::move(mock_ts_writer_));
|
||||||
segmenter.InjectPesPacketGeneratorForTesting(
|
segmenter.InjectPesPacketGeneratorForTesting(
|
||||||
std::move(mock_pes_packet_generator_));
|
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.AddSample(sample1));
|
||||||
EXPECT_OK(segmenter.FinalizeSegment(kFirstPts, sample1->duration()));
|
EXPECT_OK(segmenter.FinalizeSegment(kFirstPts, sample1->duration()));
|
||||||
EXPECT_OK(segmenter.AddSample(sample2));
|
EXPECT_OK(segmenter.AddSample(sample2));
|
||||||
|
@ -292,7 +280,7 @@ TEST_F(TsSegmenterTest, InitializeThenFinalize) {
|
||||||
segmenter.InjectTsWriterForTesting(std::move(mock_ts_writer_));
|
segmenter.InjectTsWriterForTesting(std::move(mock_ts_writer_));
|
||||||
segmenter.InjectPesPacketGeneratorForTesting(
|
segmenter.InjectPesPacketGeneratorForTesting(
|
||||||
std::move(mock_pes_packet_generator_));
|
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());
|
EXPECT_OK(segmenter.Finalize());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -322,103 +310,18 @@ TEST_F(TsSegmenterTest, FinalizeSegment) {
|
||||||
segmenter.InjectTsWriterForTesting(std::move(mock_ts_writer_));
|
segmenter.InjectTsWriterForTesting(std::move(mock_ts_writer_));
|
||||||
segmenter.InjectPesPacketGeneratorForTesting(
|
segmenter.InjectPesPacketGeneratorForTesting(
|
||||||
std::move(mock_pes_packet_generator_));
|
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);
|
segmenter.SetTsWriterFileOpenedForTesting(true);
|
||||||
EXPECT_OK(segmenter.FinalizeSegment(0, 100 /* arbitrary duration */));
|
EXPECT_OK(segmenter.FinalizeSegment(0, 100 /* arbitrary duration */));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(TsSegmenterTest, WithEncryptionNoClearLead) {
|
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;
|
|
||||||
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) {
|
|
||||||
std::shared_ptr<VideoStreamInfo> stream_info(new VideoStreamInfo(
|
std::shared_ptr<VideoStreamInfo> stream_info(new VideoStreamInfo(
|
||||||
kTrackId, kTimeScale, kDuration, kH264Codec, kCodecString, kExtraData,
|
kTrackId, kTimeScale, kDuration, kH264Codec, kCodecString, kExtraData,
|
||||||
arraysize(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight,
|
arraysize(kExtraData), kWidth, kHeight, kPixelWidth, kPixelHeight,
|
||||||
kTrickPlayRate, kNaluLengthSize, kLanguage, kIsEncrypted));
|
kTrickPlayRate, kNaluLengthSize, kLanguage, kIsEncrypted));
|
||||||
MuxerOptions options;
|
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";
|
options.segment_template = "file$Number$.ts";
|
||||||
|
|
||||||
MockMuxerListener mock_listener;
|
MockMuxerListener mock_listener;
|
||||||
|
@ -479,33 +382,19 @@ TEST_F(TsSegmenterTest, WithEncryptionWithClearLead) {
|
||||||
.InSequence(pes_packet_sequence)
|
.InSequence(pes_packet_sequence)
|
||||||
.WillOnce(Return(new PesPacket()));
|
.WillOnce(Return(new PesPacket()));
|
||||||
|
|
||||||
MockPesPacketGenerator* mock_pes_packet_generator_raw =
|
|
||||||
mock_pes_packet_generator_.get();
|
|
||||||
|
|
||||||
MockTsWriter* mock_ts_writer_raw = mock_ts_writer_.get();
|
MockTsWriter* mock_ts_writer_raw = mock_ts_writer_.get();
|
||||||
|
|
||||||
segmenter.InjectTsWriterForTesting(std::move(mock_ts_writer_));
|
segmenter.InjectTsWriterForTesting(std::move(mock_ts_writer_));
|
||||||
segmenter.InjectPesPacketGeneratorForTesting(
|
segmenter.InjectPesPacketGeneratorForTesting(
|
||||||
std::move(mock_pes_packet_generator_));
|
std::move(mock_pes_packet_generator_));
|
||||||
|
|
||||||
MockKeySource mock_key_source;
|
EXPECT_OK(segmenter.Initialize(*stream_info));
|
||||||
// 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.AddSample(sample1));
|
EXPECT_OK(segmenter.AddSample(sample1));
|
||||||
|
|
||||||
// Encryption should be setup after finalizing the first segment.
|
EXPECT_OK(segmenter.FinalizeSegment(1, sample1->duration()));
|
||||||
// These should be called when finalize segment is called.
|
// Signal encrypted if sample is encrypted.
|
||||||
EXPECT_CALL(mock_listener, OnEncryptionStart());
|
|
||||||
EXPECT_CALL(*mock_pes_packet_generator_raw, SetEncryptionKeyMock(_))
|
|
||||||
.WillOnce(Return(true));
|
|
||||||
EXPECT_CALL(*mock_ts_writer_raw, SignalEncrypted());
|
EXPECT_CALL(*mock_ts_writer_raw, SignalEncrypted());
|
||||||
EXPECT_OK(segmenter.FinalizeSegment(0, sample1->duration()));
|
sample2->set_is_encrypted(true);
|
||||||
EXPECT_OK(segmenter.AddSample(sample2));
|
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);
|
static_cast<const AudioStreamInfo&>(stream_info);
|
||||||
return audio_stream_info.seek_preroll_ns();
|
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
|
} // namespace
|
||||||
|
|
||||||
Fragmenter::Fragmenter(std::shared_ptr<StreamInfo> info, TrackFragment* traf)
|
Fragmenter::Fragmenter(std::shared_ptr<StreamInfo> stream_info,
|
||||||
: use_decoding_timestamp_in_timeline_(false),
|
TrackFragment* traf)
|
||||||
|
: stream_info_(std::move(stream_info)),
|
||||||
|
use_decoding_timestamp_in_timeline_(false),
|
||||||
traf_(traf),
|
traf_(traf),
|
||||||
seek_preroll_(GetSeekPreroll(*info)),
|
seek_preroll_(GetSeekPreroll(*stream_info_)),
|
||||||
fragment_initialized_(false),
|
fragment_initialized_(false),
|
||||||
fragment_finalized_(false),
|
fragment_finalized_(false),
|
||||||
fragment_duration_(0),
|
fragment_duration_(0),
|
||||||
earliest_presentation_time_(kInvalidTime),
|
earliest_presentation_time_(kInvalidTime),
|
||||||
first_sap_time_(kInvalidTime) {
|
first_sap_time_(kInvalidTime) {
|
||||||
|
DCHECK(stream_info_);
|
||||||
DCHECK(traf);
|
DCHECK(traf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,6 +84,12 @@ Status Fragmenter::AddSample(std::shared_ptr<MediaSample> sample) {
|
||||||
traf_->runs[0].sample_flags.push_back(
|
traf_->runs[0].sample_flags.push_back(
|
||||||
sample->is_key_frame() ? 0 : TrackFragmentHeader::kNonKeySampleMask);
|
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());
|
data_->AppendArray(sample->data(), sample->data_size());
|
||||||
fragment_duration_ += sample->duration();
|
fragment_duration_ += sample->duration();
|
||||||
|
|
||||||
|
@ -96,11 +120,15 @@ Status Fragmenter::InitializeFragment(int64_t first_sample_dts) {
|
||||||
traf_->runs.clear();
|
traf_->runs.clear();
|
||||||
traf_->runs.resize(1);
|
traf_->runs.resize(1);
|
||||||
traf_->runs[0].flags = TrackFragmentRun::kDataOffsetPresentMask;
|
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_group_descriptions.clear();
|
||||||
traf_->sample_to_groups.clear();
|
traf_->sample_to_groups.clear();
|
||||||
traf_->header.sample_description_index = 1; // 1-based.
|
traf_->header.sample_description_index = 1; // 1-based.
|
||||||
traf_->header.flags = TrackFragmentHeader::kDefaultBaseIsMoofMask |
|
traf_->header.flags = TrackFragmentHeader::kDefaultBaseIsMoofMask |
|
||||||
TrackFragmentHeader::kSampleDescriptionIndexPresentMask;
|
TrackFragmentHeader::kSampleDescriptionIndexPresentMask;
|
||||||
|
|
||||||
fragment_duration_ = 0;
|
fragment_duration_ = 0;
|
||||||
earliest_presentation_time_ = kInvalidTime;
|
earliest_presentation_time_ = kInvalidTime;
|
||||||
first_sap_time_ = kInvalidTime;
|
first_sap_time_ = kInvalidTime;
|
||||||
|
@ -108,7 +136,13 @@ Status Fragmenter::InitializeFragment(int64_t first_sample_dts) {
|
||||||
return Status::OK;
|
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.
|
// Optimize trun box.
|
||||||
traf_->runs[0].sample_count =
|
traf_->runs[0].sample_count =
|
||||||
static_cast<uint32_t>(traf_->runs[0].sample_sizes.size());
|
static_cast<uint32_t>(traf_->runs[0].sample_sizes.size());
|
||||||
|
@ -164,6 +198,7 @@ void Fragmenter::FinalizeFragment() {
|
||||||
|
|
||||||
fragment_finalized_ = true;
|
fragment_finalized_ = true;
|
||||||
fragment_initialized_ = false;
|
fragment_initialized_ = false;
|
||||||
|
return Status::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Fragmenter::GenerateSegmentReference(SegmentReference* reference) {
|
void Fragmenter::GenerateSegmentReference(SegmentReference* reference) {
|
||||||
|
@ -181,6 +216,58 @@ void Fragmenter::GenerateSegmentReference(SegmentReference* reference) {
|
||||||
reference->earliest_presentation_time = earliest_presentation_time_;
|
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() {
|
bool Fragmenter::StartsWithSAP() {
|
||||||
DCHECK(!traf_->runs.empty());
|
DCHECK(!traf_->runs.empty());
|
||||||
uint32_t start_sample_flag;
|
uint32_t start_sample_flag;
|
||||||
|
|
|
@ -33,21 +33,21 @@ class Fragmenter {
|
||||||
/// @param traf points to a TrackFragment box.
|
/// @param traf points to a TrackFragment box.
|
||||||
Fragmenter(std::shared_ptr<StreamInfo> info, TrackFragment* traf);
|
Fragmenter(std::shared_ptr<StreamInfo> info, TrackFragment* traf);
|
||||||
|
|
||||||
virtual ~Fragmenter();
|
~Fragmenter();
|
||||||
|
|
||||||
/// Add a sample to the fragmenter.
|
/// Add a sample to the fragmenter.
|
||||||
/// @param sample points to the sample to be added.
|
/// @param sample points to the sample to be added.
|
||||||
/// @return OK on success, an error status otherwise.
|
/// @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.
|
/// Initialize the fragment with default data.
|
||||||
/// @param first_sample_dts specifies the decoding timestamp for the first
|
/// @param first_sample_dts specifies the decoding timestamp for the first
|
||||||
/// sample for this fragment.
|
/// sample for this fragment.
|
||||||
/// @return OK on success, an error status otherwise.
|
/// @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.
|
/// Finalize and optimize the fragment.
|
||||||
virtual void FinalizeFragment();
|
Status FinalizeFragment();
|
||||||
|
|
||||||
/// Fill @a reference with current fragment information.
|
/// Fill @a reference with current fragment information.
|
||||||
void GenerateSegmentReference(SegmentReference* reference);
|
void GenerateSegmentReference(SegmentReference* reference);
|
||||||
|
@ -82,9 +82,11 @@ class Fragmenter {
|
||||||
bool OptimizeSampleEntries(std::vector<T>* entries, T* default_value);
|
bool OptimizeSampleEntries(std::vector<T>* entries, T* default_value);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
Status FinalizeFragmentForEncryption();
|
||||||
// Check if the current fragment starts with SAP.
|
// Check if the current fragment starts with SAP.
|
||||||
bool StartsWithSAP();
|
bool StartsWithSAP();
|
||||||
|
|
||||||
|
std::shared_ptr<StreamInfo> stream_info_;
|
||||||
bool use_decoding_timestamp_in_timeline_;
|
bool use_decoding_timestamp_in_timeline_;
|
||||||
TrackFragment* traf_;
|
TrackFragment* traf_;
|
||||||
uint64_t seek_preroll_;
|
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',
|
'composition_offset_iterator.h',
|
||||||
'decoding_time_iterator.cc',
|
'decoding_time_iterator.cc',
|
||||||
'decoding_time_iterator.h',
|
'decoding_time_iterator.h',
|
||||||
'encrypting_fragmenter.cc',
|
|
||||||
'encrypting_fragmenter.h',
|
|
||||||
'fragmenter.cc',
|
'fragmenter.cc',
|
||||||
'fragmenter.h',
|
'fragmenter.h',
|
||||||
'key_rotation_fragmenter.cc',
|
|
||||||
'key_rotation_fragmenter.h',
|
|
||||||
'mp4_media_parser.cc',
|
'mp4_media_parser.cc',
|
||||||
'mp4_media_parser.h',
|
'mp4_media_parser.h',
|
||||||
'mp4_muxer.cc',
|
'mp4_muxer.cc',
|
||||||
|
|
|
@ -462,8 +462,11 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The stream will be decrypted if a |decryptor_source_| is available.
|
||||||
const bool is_encrypted =
|
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;
|
DVLOG(1) << "is_audio_track_encrypted_: " << is_encrypted;
|
||||||
streams.emplace_back(new AudioStreamInfo(
|
streams.emplace_back(new AudioStreamInfo(
|
||||||
track->header.track_id, timescale, duration, codec,
|
track->header.track_id, timescale, duration, codec,
|
||||||
|
@ -558,8 +561,11 @@ bool MP4MediaParser::ParseMoov(BoxReader* reader) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The stream will be decrypted if a |decryptor_source_| is available.
|
||||||
const bool is_encrypted =
|
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;
|
DVLOG(1) << "is_video_track_encrypted_: " << is_encrypted;
|
||||||
std::shared_ptr<VideoStreamInfo> video_stream_info(new VideoStreamInfo(
|
std::shared_ptr<VideoStreamInfo> video_stream_info(new VideoStreamInfo(
|
||||||
track->header.track_id, timescale, duration, video_codec,
|
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
|
} // namespace
|
||||||
|
|
||||||
MP4Muxer::MP4Muxer(const MuxerOptions& options) : Muxer(options) {}
|
MP4Muxer::MP4Muxer(const MuxerOptions& options) : Muxer(options) {}
|
||||||
|
@ -126,6 +149,14 @@ Status MP4Muxer::InitializeMuxer() {
|
||||||
NOTIMPLEMENTED() << "Not implemented for stream type: "
|
NOTIMPLEMENTED() << "Not implemented for stream type: "
|
||||||
<< streams()[i]->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()) {
|
if (options().segment_template.empty()) {
|
||||||
|
@ -136,12 +167,8 @@ Status MP4Muxer::InitializeMuxer() {
|
||||||
new MultiSegmentSegmenter(options(), std::move(ftyp), std::move(moov)));
|
new MultiSegmentSegmenter(options(), std::move(ftyp), std::move(moov)));
|
||||||
}
|
}
|
||||||
|
|
||||||
const Status segmenter_initialized = segmenter_->Initialize(
|
const Status segmenter_initialized =
|
||||||
streams(), muxer_listener(), progress_listener(), encryption_key_source(),
|
segmenter_->Initialize(streams(), muxer_listener(), progress_listener());
|
||||||
max_sd_pixels(), max_hd_pixels(), max_uhd1_pixels(),
|
|
||||||
clear_lead_in_seconds(), crypto_period_duration_in_seconds(),
|
|
||||||
protection_scheme());
|
|
||||||
|
|
||||||
if (!segmenter_initialized.ok())
|
if (!segmenter_initialized.ok())
|
||||||
return segmenter_initialized;
|
return segmenter_initialized;
|
||||||
|
|
||||||
|
@ -173,7 +200,7 @@ Status MP4Muxer::FinalizeSegment(size_t stream_id,
|
||||||
VLOG(3) << "Finalize " << (segment_info->is_subsegment ? "sub" : "")
|
VLOG(3) << "Finalize " << (segment_info->is_subsegment ? "sub" : "")
|
||||||
<< "segment " << segment_info->start_timestamp << " duration "
|
<< "segment " << segment_info->start_timestamp << " duration "
|
||||||
<< segment_info->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) {
|
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;
|
trak->media.information.sample_table.description;
|
||||||
sample_description.type = kVideo;
|
sample_description.type = kVideo;
|
||||||
sample_description.video_entries.push_back(video);
|
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,
|
void MP4Muxer::GenerateAudioTrak(const AudioStreamInfo* audio_info,
|
||||||
|
@ -289,6 +325,15 @@ void MP4Muxer::GenerateAudioTrak(const AudioStreamInfo* audio_info,
|
||||||
sample_description.type = kAudio;
|
sample_description.type = kAudio;
|
||||||
sample_description.audio_entries.push_back(audio);
|
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
|
// 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.
|
// sample to group box with grouping type 'roll' within sample table box.
|
||||||
if (audio_info->codec() == kCodecOpus) {
|
if (audio_info->codec() == kCodecOpus) {
|
||||||
|
|
|
@ -9,17 +9,15 @@
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
#include "packager/base/logging.h"
|
#include "packager/base/logging.h"
|
||||||
#include "packager/media/base/aes_cryptor.h"
|
|
||||||
#include "packager/media/base/buffer_writer.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/media_sample.h"
|
||||||
#include "packager/media/base/muxer_options.h"
|
#include "packager/media/base/muxer_options.h"
|
||||||
#include "packager/media/base/muxer_util.h"
|
#include "packager/media/base/muxer_util.h"
|
||||||
#include "packager/media/base/video_stream_info.h"
|
#include "packager/media/base/stream_info.h"
|
||||||
#include "packager/media/event/muxer_listener.h"
|
#include "packager/media/chunking/chunking_handler.h"
|
||||||
#include "packager/media/event/progress_listener.h"
|
#include "packager/media/event/progress_listener.h"
|
||||||
#include "packager/media/formats/mp4/box_definitions.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"
|
#include "packager/version/version.h"
|
||||||
|
|
||||||
namespace shaka {
|
namespace shaka {
|
||||||
|
@ -27,25 +25,6 @@ namespace media {
|
||||||
namespace mp4 {
|
namespace mp4 {
|
||||||
|
|
||||||
namespace {
|
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,
|
uint64_t Rescale(uint64_t time_in_old_scale,
|
||||||
uint32_t 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;
|
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
|
} // namespace
|
||||||
|
|
||||||
Segmenter::Segmenter(const MuxerOptions& options,
|
Segmenter::Segmenter(const MuxerOptions& options,
|
||||||
|
@ -153,7 +43,6 @@ Segmenter::Segmenter(const MuxerOptions& options,
|
||||||
moof_(new MovieFragment()),
|
moof_(new MovieFragment()),
|
||||||
fragment_buffer_(new BufferWriter()),
|
fragment_buffer_(new BufferWriter()),
|
||||||
sidx_(new SegmentIndex()),
|
sidx_(new SegmentIndex()),
|
||||||
muxer_listener_(NULL),
|
|
||||||
progress_listener_(NULL),
|
progress_listener_(NULL),
|
||||||
progress_target_(0),
|
progress_target_(0),
|
||||||
accumulated_progress_(0),
|
accumulated_progress_(0),
|
||||||
|
@ -164,14 +53,7 @@ Segmenter::~Segmenter() {}
|
||||||
Status Segmenter::Initialize(
|
Status Segmenter::Initialize(
|
||||||
const std::vector<std::shared_ptr<StreamInfo>>& streams,
|
const std::vector<std::shared_ptr<StreamInfo>>& streams,
|
||||||
MuxerListener* muxer_listener,
|
MuxerListener* muxer_listener,
|
||||||
ProgressListener* progress_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) {
|
|
||||||
DCHECK_LT(0u, streams.size());
|
DCHECK_LT(0u, streams.size());
|
||||||
muxer_listener_ = muxer_listener;
|
muxer_listener_ = muxer_listener;
|
||||||
progress_listener_ = progress_listener;
|
progress_listener_ = progress_listener;
|
||||||
|
@ -179,8 +61,6 @@ Status Segmenter::Initialize(
|
||||||
|
|
||||||
moof_->tracks.resize(streams.size());
|
moof_->tracks.resize(streams.size());
|
||||||
fragmenters_.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) {
|
for (uint32_t i = 0; i < streams.size(); ++i) {
|
||||||
moof_->tracks[i].header.track_id = i + 1;
|
moof_->tracks[i].header.track_id = i + 1;
|
||||||
|
@ -189,77 +69,7 @@ Status Segmenter::Initialize(
|
||||||
if (sidx_->reference_id == 0)
|
if (sidx_->reference_id == 0)
|
||||||
sidx_->reference_id = i + 1;
|
sidx_->reference_id = i + 1;
|
||||||
}
|
}
|
||||||
if (!encryption_key_source) {
|
|
||||||
fragmenters_[i].reset(new Fragmenter(streams[i], &moof_->tracks[i]));
|
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_));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options_.mp4_use_decoding_timestamp_in_timeline) {
|
if (options_.mp4_use_decoding_timestamp_in_timeline) {
|
||||||
|
@ -333,11 +143,20 @@ Status Segmenter::AddSample(size_t stream_id,
|
||||||
return Status::OK;
|
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());
|
DCHECK_LT(stream_id, fragmenters_.size());
|
||||||
Fragmenter* fragmenter = fragmenters_[stream_id].get();
|
Fragmenter* fragmenter = fragmenters_[stream_id].get();
|
||||||
DCHECK(fragmenter);
|
DCHECK(fragmenter);
|
||||||
fragmenter->FinalizeFragment();
|
Status status = fragmenter->FinalizeFragment();
|
||||||
|
if (!status.ok())
|
||||||
|
return status;
|
||||||
|
|
||||||
// Check if all tracks are ready for fragmentation.
|
// Check if all tracks are ready for fragmentation.
|
||||||
for (const std::unique_ptr<Fragmenter>& fragmenter : fragmenters_) {
|
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_)
|
for (std::unique_ptr<Fragmenter>& fragmenter : fragmenters_)
|
||||||
fragmenter->ClearFragmentFinalized();
|
fragmenter->ClearFragmentFinalized();
|
||||||
if (!is_subsegment) {
|
if (!segment_info->is_subsegment) {
|
||||||
Status status = DoFinalizeSegment();
|
Status status = DoFinalizeSegment();
|
||||||
// Reset segment information to initial state.
|
// Reset segment information to initial state.
|
||||||
sidx_->references.clear();
|
sidx_->references.clear();
|
||||||
|
@ -435,6 +254,45 @@ uint32_t Segmenter::GetReferenceStreamId() {
|
||||||
return sidx_->reference_id - 1;
|
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 mp4
|
||||||
} // namespace media
|
} // namespace media
|
||||||
} // namespace shaka
|
} // namespace shaka
|
||||||
|
|
|
@ -18,10 +18,11 @@
|
||||||
namespace shaka {
|
namespace shaka {
|
||||||
namespace media {
|
namespace media {
|
||||||
|
|
||||||
|
struct EncryptionConfig;
|
||||||
struct MuxerOptions;
|
struct MuxerOptions;
|
||||||
|
struct SegmentInfo;
|
||||||
|
|
||||||
class BufferWriter;
|
class BufferWriter;
|
||||||
class KeySource;
|
|
||||||
class MediaSample;
|
class MediaSample;
|
||||||
class MuxerListener;
|
class MuxerListener;
|
||||||
class ProgressListener;
|
class ProgressListener;
|
||||||
|
@ -50,35 +51,10 @@ class Segmenter {
|
||||||
/// @param streams contains the vector of MediaStreams to be segmented.
|
/// @param streams contains the vector of MediaStreams to be segmented.
|
||||||
/// @param muxer_listener receives muxer events. Can be NULL.
|
/// @param muxer_listener receives muxer events. Can be NULL.
|
||||||
/// @param progress_listener receives progress updates. 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.
|
/// @return OK on success, an error status otherwise.
|
||||||
Status Initialize(const std::vector<std::shared_ptr<StreamInfo>>& streams,
|
Status Initialize(const std::vector<std::shared_ptr<StreamInfo>>& streams,
|
||||||
MuxerListener* muxer_listener,
|
MuxerListener* muxer_listener,
|
||||||
ProgressListener* progress_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);
|
|
||||||
|
|
||||||
/// Finalize the segmenter.
|
/// Finalize the segmenter.
|
||||||
/// @return OK on success, an error status otherwise.
|
/// @return OK on success, an error status otherwise.
|
||||||
|
@ -94,7 +70,8 @@ class Segmenter {
|
||||||
/// @param stream_id is the zero-based stream index.
|
/// @param stream_id is the zero-based stream index.
|
||||||
/// @param is_subsegment indicates if it is a subsegment (fragment).
|
/// @param is_subsegment indicates if it is a subsegment (fragment).
|
||||||
/// @return OK on success, an error status otherwise.
|
/// @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
|
/// @return true if there is an initialization range, while setting @a offset
|
||||||
/// and @a size; or false if initialization range does not apply.
|
/// and @a size; or false if initialization range does not apply.
|
||||||
|
@ -138,6 +115,11 @@ class Segmenter {
|
||||||
|
|
||||||
uint32_t GetReferenceStreamId();
|
uint32_t GetReferenceStreamId();
|
||||||
|
|
||||||
|
void FinalizeFragmentForKeyRotation(
|
||||||
|
size_t stream_id,
|
||||||
|
bool fragment_encrypted,
|
||||||
|
const EncryptionConfig& encryption_config);
|
||||||
|
|
||||||
const MuxerOptions& options_;
|
const MuxerOptions& options_;
|
||||||
std::unique_ptr<FileType> ftyp_;
|
std::unique_ptr<FileType> ftyp_;
|
||||||
std::unique_ptr<Movie> moov_;
|
std::unique_ptr<Movie> moov_;
|
||||||
|
|
|
@ -6,19 +6,22 @@
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include "packager/media/base/fixed_key_source.h"
|
|
||||||
#include "packager/media/formats/webm/segmenter_test_base.h"
|
#include "packager/media/formats/webm/segmenter_test_base.h"
|
||||||
|
|
||||||
namespace shaka {
|
namespace shaka {
|
||||||
namespace media {
|
namespace media {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
const uint64_t kDuration = 1000;
|
const uint64_t kDuration = 1000u;
|
||||||
const bool kSubsegment = true;
|
const bool kSubsegment = true;
|
||||||
const std::string kKeyId = "4c6f72656d20697073756d20646f6c6f";
|
const uint8_t kPerSampleIvSize = 8u;
|
||||||
const std::string kIv = "0123456789012345";
|
const uint8_t kKeyId[] = {
|
||||||
const std::string kKey = "01234567890123456789012345678901";
|
0x4c, 0x6f, 0x72, 0x65, 0x6d, 0x20, 0x69, 0x70,
|
||||||
const std::string kPsshData = "";
|
0x73, 0x75, 0x6d, 0x20, 0x64, 0x6f, 0x6c, 0x6f,
|
||||||
|
};
|
||||||
|
const uint8_t kIv[] = {
|
||||||
|
0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x23, 0x45,
|
||||||
|
};
|
||||||
const uint8_t kBasicSupportData[] = {
|
const uint8_t kBasicSupportData[] = {
|
||||||
// ID: EBML Header omitted.
|
// ID: EBML Header omitted.
|
||||||
// ID: Segment, Payload Size: 432
|
// ID: Segment, Payload Size: 432
|
||||||
|
@ -174,7 +177,7 @@ const uint8_t kBasicSupportData[] = {
|
||||||
// IV:
|
// IV:
|
||||||
0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x23, 0x45,
|
0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x23, 0x45,
|
||||||
// Frame Data:
|
// Frame Data:
|
||||||
0xcc, 0x03, 0xef, 0xc4, 0xf4,
|
0xde, 0xad, 0xbe, 0xef, 0x00,
|
||||||
// ID: BlockGroup, Payload Size: 24
|
// ID: BlockGroup, Payload Size: 24
|
||||||
0xa0, 0x98,
|
0xa0, 0x98,
|
||||||
// ID: Block, Payload Size: 18
|
// ID: Block, Payload Size: 18
|
||||||
|
@ -182,34 +185,37 @@ const uint8_t kBasicSupportData[] = {
|
||||||
// Signal Byte: Encrypted
|
// Signal Byte: Encrypted
|
||||||
0x01,
|
0x01,
|
||||||
// IV:
|
// IV:
|
||||||
0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x23, 0x46,
|
0x01, 0x23, 0x45, 0x67, 0x89, 0x01, 0x23, 0x45,
|
||||||
// Frame Data:
|
// Frame Data:
|
||||||
0xbf, 0x38, 0x72, 0x20, 0xac,
|
0xde, 0xad, 0xbe, 0xef, 0x00,
|
||||||
// BlockDuration: 1000
|
// BlockDuration: 1000
|
||||||
0x9b, 0x82, 0x03, 0xe8,
|
0x9b, 0x82, 0x03, 0xe8,
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
class EncrypedSegmenterTest : public SegmentTestBase {
|
class EncryptedSegmenterTest : public SegmentTestBase {
|
||||||
public:
|
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:
|
protected:
|
||||||
void InitializeSegmenter(const MuxerOptions& options) {
|
void InitializeSegmenter(const MuxerOptions& options) {
|
||||||
key_source_ =
|
|
||||||
FixedKeySource::CreateFromHexStrings(kKeyId, kKey, kPsshData, kIv);
|
|
||||||
ASSERT_NO_FATAL_FAILURE(
|
ASSERT_NO_FATAL_FAILURE(
|
||||||
CreateAndInitializeSegmenter<webm::TwoPassSingleSegmentSegmenter>(
|
CreateAndInitializeSegmenter<webm::TwoPassSingleSegmentSegmenter>(
|
||||||
options, info_.get(), key_source_.get(), &segmenter_));
|
options, info_.get(), &segmenter_));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<StreamInfo> info_;
|
std::shared_ptr<StreamInfo> info_;
|
||||||
std::unique_ptr<webm::Segmenter> segmenter_;
|
std::unique_ptr<webm::Segmenter> segmenter_;
|
||||||
std::unique_ptr<KeySource> key_source_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
TEST_F(EncrypedSegmenterTest, BasicSupport) {
|
TEST_F(EncryptedSegmenterTest, BasicSupport) {
|
||||||
MuxerOptions options = CreateMuxerOptions();
|
MuxerOptions options = CreateMuxerOptions();
|
||||||
ASSERT_NO_FATAL_FAILURE(InitializeSegmenter(options));
|
ASSERT_NO_FATAL_FAILURE(InitializeSegmenter(options));
|
||||||
|
|
||||||
|
@ -221,6 +227,14 @@ TEST_F(EncrypedSegmenterTest, BasicSupport) {
|
||||||
ASSERT_OK(segmenter_->FinalizeSegment(0, 3 * kDuration, !kSubsegment));
|
ASSERT_OK(segmenter_->FinalizeSegment(0, 3 * kDuration, !kSubsegment));
|
||||||
std::shared_ptr<MediaSample> sample =
|
std::shared_ptr<MediaSample> sample =
|
||||||
CreateSample(kKeyFrame, kDuration, kNoSideData);
|
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(segmenter_->AddSample(sample));
|
||||||
}
|
}
|
||||||
ASSERT_OK(
|
ASSERT_OK(
|
||||||
|
|
|
@ -6,22 +6,18 @@
|
||||||
|
|
||||||
#include "packager/media/formats/webm/encryptor.h"
|
#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/buffer_writer.h"
|
||||||
#include "packager/media/base/fourccs.h"
|
|
||||||
#include "packager/media/base/media_sample.h"
|
#include "packager/media/base/media_sample.h"
|
||||||
#include "packager/media/codecs/vp9_parser.h"
|
|
||||||
#include "packager/media/formats/webm/webm_constants.h"
|
#include "packager/media/formats/webm/webm_constants.h"
|
||||||
|
|
||||||
namespace shaka {
|
namespace shaka {
|
||||||
namespace media {
|
namespace media {
|
||||||
namespace webm {
|
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()) {
|
if (!track->AddContentEncoding()) {
|
||||||
return Status(error::INTERNAL_ERROR,
|
return Status(error::INTERNAL_ERROR,
|
||||||
"Could not add ContentEncoding to track.");
|
"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.");
|
return Status(error::INTERNAL_ERROR, "Cipher Mode is not CTR.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!key->key_id.empty() &&
|
if (!encoding->SetEncryptionID(key_id.data(), key_id.size())) {
|
||||||
!encoding->SetEncryptionID(
|
|
||||||
reinterpret_cast<const uint8_t*>(key->key_id.data()),
|
|
||||||
key->key_id.size())) {
|
|
||||||
return Status(error::INTERNAL_ERROR, "Error setting encryption ID.");
|
return Status(error::INTERNAL_ERROR, "Error setting encryption ID.");
|
||||||
}
|
}
|
||||||
return Status::OK;
|
return Status::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
void UpdateFrameForEncryption(MediaSample* sample) {
|
||||||
|
|
||||||
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_);
|
|
||||||
|
|
||||||
const size_t sample_size = sample->data_size();
|
const size_t sample_size = sample->data_size();
|
||||||
// We need to parse the frame (which also updates the vpx parser) even if the
|
if (sample->decrypt_config()) {
|
||||||
// frame is not encrypted as the next (encrypted) frame may be dependent on
|
auto* decrypt_config = sample->decrypt_config();
|
||||||
// this clear frame.
|
const size_t iv_size = decrypt_config->iv().size();
|
||||||
std::vector<VPxFrameInfo> vpx_frames;
|
DCHECK_EQ(iv_size, kWebMIvSize);
|
||||||
if (vpx_parser_) {
|
if (!decrypt_config->subsamples().empty()) {
|
||||||
if (!vpx_parser_->Parse(sample->data(), sample_size, &vpx_frames)) {
|
auto& subsamples = decrypt_config->subsamples();
|
||||||
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()) {
|
|
||||||
// Use partitioned subsample encryption: | signal_byte(3) | iv
|
// Use partitioned subsample encryption: | signal_byte(3) | iv
|
||||||
// | num_partitions | partition_offset * n | enc_data |
|
// | num_partitions | partition_offset * n | enc_data |
|
||||||
|
DCHECK_LT(subsamples.size(), kWebMMaxSubsamples);
|
||||||
if (vpx_frames.size() > kWebMMaxSubsamples) {
|
const size_t num_partitions =
|
||||||
return Status(error::MUXER_FAILURE,
|
2 * subsamples.size() - 1 -
|
||||||
"Maximum number of VPx encryption partitions exceeded.");
|
(subsamples.back().cipher_bytes == 0 ? 1 : 0);
|
||||||
}
|
const size_t header_size = kWebMSignalByteSize + iv_size +
|
||||||
size_t num_partitions =
|
|
||||||
vpx_frames.size() == 1 ? 1 : vpx_frames.size() * 2;
|
|
||||||
size_t header_size = kWebMSignalByteSize + iv_size +
|
|
||||||
kWebMNumPartitionsSize +
|
kWebMNumPartitionsSize +
|
||||||
(kWebMPartitionOffsetSize * num_partitions);
|
(kWebMPartitionOffsetSize * num_partitions);
|
||||||
sample->resize_data(header_size + sample_size);
|
sample->resize_data(header_size + sample_size);
|
||||||
uint8_t* sample_data = sample->writable_data();
|
uint8_t* sample_data = sample->writable_data();
|
||||||
memmove(sample_data + header_size, sample_data, sample_size);
|
memmove(sample_data + header_size, sample_data, sample_size);
|
||||||
sample_data[0] = kWebMEncryptedSignal | kWebMPartitionedSignal;
|
sample_data[0] = kWebMEncryptedSignal | kWebMPartitionedSignal;
|
||||||
memcpy(sample_data + kWebMSignalByteSize, encryptor_->iv().data(),
|
memcpy(sample_data + kWebMSignalByteSize, decrypt_config->iv().data(),
|
||||||
iv_size);
|
iv_size);
|
||||||
sample_data[kWebMSignalByteSize + kWebMIvSize] =
|
sample_data[kWebMSignalByteSize + kWebMIvSize] =
|
||||||
static_cast<uint8_t>(num_partitions);
|
static_cast<uint8_t>(num_partitions);
|
||||||
|
|
||||||
|
BufferWriter offsets_buffer;
|
||||||
uint32_t partition_offset = 0;
|
uint32_t partition_offset = 0;
|
||||||
BufferWriter offsets_buffer(kWebMPartitionOffsetSize * num_partitions);
|
for (size_t i = 0; i < subsamples.size() - 1; ++i) {
|
||||||
for (const auto& vpx_frame : vpx_frames) {
|
partition_offset += subsamples[i].clear_bytes;
|
||||||
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;
|
|
||||||
offsets_buffer.AppendInt(partition_offset);
|
offsets_buffer.AppendInt(partition_offset);
|
||||||
if (encrypted_size > 0) {
|
partition_offset += subsamples[i].cipher_bytes;
|
||||||
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);
|
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);
|
||||||
}
|
}
|
||||||
DCHECK_EQ(num_partitions * kWebMPartitionOffsetSize,
|
DCHECK_EQ(num_partitions * kWebMPartitionOffsetSize,
|
||||||
offsets_buffer.Size());
|
offsets_buffer.Size());
|
||||||
|
@ -143,23 +92,16 @@ Status Encryptor::EncryptFrame(std::shared_ptr<MediaSample> sample,
|
||||||
offsets_buffer.Buffer(), offsets_buffer.Size());
|
offsets_buffer.Buffer(), offsets_buffer.Size());
|
||||||
} else {
|
} else {
|
||||||
// Use whole-frame encryption: | signal_byte(1) | iv | enc_data |
|
// Use whole-frame encryption: | signal_byte(1) | iv | enc_data |
|
||||||
|
|
||||||
sample->resize_data(sample_size + iv_size + kWebMSignalByteSize);
|
sample->resize_data(sample_size + iv_size + kWebMSignalByteSize);
|
||||||
uint8_t* sample_data = sample->writable_data();
|
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
|
// First move the sample data to after the IV; then write the IV and
|
||||||
// signal byte.
|
// signal byte.
|
||||||
memmove(sample_data + iv_size + kWebMSignalByteSize, sample_data,
|
memmove(sample_data + iv_size + kWebMSignalByteSize, sample_data,
|
||||||
sample_size);
|
sample_size);
|
||||||
sample_data[0] = kWebMEncryptedSignal;
|
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 {
|
} else {
|
||||||
// Clear sample: | signal_byte(0) | data |
|
// Clear sample: | signal_byte(0) | data |
|
||||||
sample->resize_data(sample_size + 1);
|
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);
|
memmove(sample_data + 1, sample_data, sample_size);
|
||||||
sample_data[0] = 0x00;
|
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
|
} // namespace webm
|
||||||
|
|
|
@ -7,63 +7,26 @@
|
||||||
#ifndef MEDIA_FORMATS_WEBM_ENCRYPTOR_H_
|
#ifndef MEDIA_FORMATS_WEBM_ENCRYPTOR_H_
|
||||||
#define MEDIA_FORMATS_WEBM_ENCRYPTOR_H_
|
#define MEDIA_FORMATS_WEBM_ENCRYPTOR_H_
|
||||||
|
|
||||||
#include <memory>
|
#include <vector>
|
||||||
#include "packager/base/macros.h"
|
|
||||||
#include "packager/media/base/key_source.h"
|
|
||||||
#include "packager/media/base/status.h"
|
#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"
|
#include "packager/third_party/libwebm/src/mkvmuxer.hpp"
|
||||||
|
|
||||||
namespace shaka {
|
namespace shaka {
|
||||||
namespace media {
|
namespace media {
|
||||||
|
|
||||||
class AesCtrEncryptor;
|
|
||||||
class MediaSample;
|
class MediaSample;
|
||||||
|
|
||||||
namespace webm {
|
namespace webm {
|
||||||
|
|
||||||
/// A helper class used to encrypt WebM frames before being written to the
|
/// Adds the encryption info with the specified key_id to the given track.
|
||||||
/// Cluster. This can also handle unencrypted frames.
|
|
||||||
class Encryptor {
|
|
||||||
public:
|
|
||||||
Encryptor();
|
|
||||||
~Encryptor();
|
|
||||||
|
|
||||||
/// Initializes the encryptor with the given key source.
|
|
||||||
/// @return OK on success, an error status otherwise.
|
/// @return OK on success, an error status otherwise.
|
||||||
Status Initialize(MuxerListener* muxer_listener,
|
Status UpdateTrackForEncryption(const std::vector<uint8_t>& key_id,
|
||||||
KeySource::TrackType track_type,
|
mkvmuxer::Track* track);
|
||||||
Codec codec,
|
|
||||||
KeySource* key_source,
|
|
||||||
bool webm_subsample_encryption);
|
|
||||||
|
|
||||||
/// Adds the encryption info to the given track. Initialize must be called
|
/// Update the frame with signal bytes and encryption information if it is
|
||||||
/// first.
|
/// encrypted.
|
||||||
/// @return OK on success, an error status otherwise.
|
void UpdateFrameForEncryption(MediaSample* sample);
|
||||||
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);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace webm
|
} // namespace webm
|
||||||
} // namespace media
|
} // 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) {
|
void InitializeSegmenter(const MuxerOptions& options) {
|
||||||
ASSERT_NO_FATAL_FAILURE(
|
ASSERT_NO_FATAL_FAILURE(
|
||||||
CreateAndInitializeSegmenter<webm::MultiSegmentSegmenter>(
|
CreateAndInitializeSegmenter<webm::MultiSegmentSegmenter>(
|
||||||
options, info_.get(), NULL, &segmenter_));
|
options, info_.get(), &segmenter_));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string TemplateFileName(int number) const {
|
std::string TemplateFileName(int number) const {
|
||||||
|
|
|
@ -17,10 +17,15 @@
|
||||||
#include "packager/media/codecs/vp_codec_configuration_record.h"
|
#include "packager/media/codecs/vp_codec_configuration_record.h"
|
||||||
#include "packager/media/event/muxer_listener.h"
|
#include "packager/media/event/muxer_listener.h"
|
||||||
#include "packager/media/event/progress_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/mkvmuxerutil.hpp"
|
||||||
#include "packager/third_party/libwebm/src/webmids.hpp"
|
#include "packager/third_party/libwebm/src/webmids.hpp"
|
||||||
#include "packager/version/version.h"
|
#include "packager/version/version.h"
|
||||||
|
|
||||||
|
using mkvmuxer::AudioTrack;
|
||||||
|
using mkvmuxer::VideoTrack;
|
||||||
|
|
||||||
namespace shaka {
|
namespace shaka {
|
||||||
namespace media {
|
namespace media {
|
||||||
namespace webm {
|
namespace webm {
|
||||||
|
@ -35,15 +40,9 @@ Segmenter::~Segmenter() {}
|
||||||
|
|
||||||
Status Segmenter::Initialize(StreamInfo* info,
|
Status Segmenter::Initialize(StreamInfo* info,
|
||||||
ProgressListener* progress_listener,
|
ProgressListener* progress_listener,
|
||||||
MuxerListener* muxer_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) {
|
|
||||||
muxer_listener_ = muxer_listener;
|
muxer_listener_ = muxer_listener;
|
||||||
info_ = info;
|
info_ = info;
|
||||||
clear_lead_ = clear_lead_in_seconds;
|
|
||||||
|
|
||||||
// Use media duration as progress target.
|
// Use media duration as progress target.
|
||||||
progress_target_ = info_->duration();
|
progress_target_ = info_->duration();
|
||||||
|
@ -65,24 +64,26 @@ Status Segmenter::Initialize(StreamInfo* info,
|
||||||
segment_info_.set_duration(1);
|
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.
|
// 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()) {
|
switch (info_->stream_type()) {
|
||||||
case kStreamVideo:
|
case kStreamVideo: {
|
||||||
status = CreateVideoTrack(static_cast<VideoStreamInfo*>(info_));
|
std::unique_ptr<VideoTrack> video_track(new VideoTrack(&seed));
|
||||||
|
status = InitializeVideoTrack(static_cast<VideoStreamInfo*>(info_),
|
||||||
|
video_track.get());
|
||||||
|
track = std::move(video_track);
|
||||||
break;
|
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;
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
NOTIMPLEMENTED() << "Not implemented for stream type: "
|
NOTIMPLEMENTED() << "Not implemented for stream type: "
|
||||||
<< info_->stream_type();
|
<< info_->stream_type();
|
||||||
|
@ -91,6 +92,20 @@ Status Segmenter::Initialize(StreamInfo* info,
|
||||||
if (!status.ok())
|
if (!status.ok())
|
||||||
return status;
|
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();
|
return DoInitialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,25 +141,8 @@ Status Segmenter::AddSample(std::shared_ptr<MediaSample> sample) {
|
||||||
if (!status.ok())
|
if (!status.ok())
|
||||||
return status;
|
return status;
|
||||||
|
|
||||||
// Encrypt the frame.
|
if (info_->is_encrypted())
|
||||||
if (encryptor_) {
|
UpdateFrameForEncryption(sample.get());
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
new_subsegment_ = false;
|
new_subsegment_ = false;
|
||||||
new_segment_ = false;
|
new_segment_ = false;
|
||||||
|
@ -242,13 +240,8 @@ void Segmenter::UpdateProgress(uint64_t progress) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Status Segmenter::CreateVideoTrack(VideoStreamInfo* info) {
|
Status Segmenter::InitializeVideoTrack(const VideoStreamInfo* info,
|
||||||
// The seed is only used to create a UID which we overwrite later.
|
VideoTrack* track) {
|
||||||
unsigned int seed = 0;
|
|
||||||
mkvmuxer::VideoTrack* track = new mkvmuxer::VideoTrack(&seed);
|
|
||||||
if (!track)
|
|
||||||
return Status(error::INTERNAL_ERROR, "Failed to create video track.");
|
|
||||||
|
|
||||||
if (info->codec() == kCodecVP8) {
|
if (info->codec() == kCodecVP8) {
|
||||||
track->set_codec_id(mkvmuxer::Tracks::kVp8CodecId);
|
track->set_codec_id(mkvmuxer::Tracks::kVp8CodecId);
|
||||||
} else if (info->codec() == kCodecVP9) {
|
} else if (info->codec() == kCodecVP9) {
|
||||||
|
@ -283,22 +276,11 @@ Status Segmenter::CreateVideoTrack(VideoStreamInfo* info) {
|
||||||
track->set_display_height(info->height());
|
track->set_display_height(info->height());
|
||||||
track->set_display_width(info->width() * info->pixel_width() /
|
track->set_display_width(info->width() * info->pixel_width() /
|
||||||
info->pixel_height());
|
info->pixel_height());
|
||||||
|
|
||||||
if (encryptor_)
|
|
||||||
encryptor_->AddTrackInfo(track);
|
|
||||||
|
|
||||||
tracks_.AddTrack(track, info->track_id());
|
|
||||||
track_id_ = track->number();
|
|
||||||
return Status::OK;
|
return Status::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
Status Segmenter::CreateAudioTrack(AudioStreamInfo* info) {
|
Status Segmenter::InitializeAudioTrack(const AudioStreamInfo* info,
|
||||||
// The seed is only used to create a UID which we overwrite later.
|
AudioTrack* track) {
|
||||||
unsigned int seed = 0;
|
|
||||||
mkvmuxer::AudioTrack* track = new mkvmuxer::AudioTrack(&seed);
|
|
||||||
if (!track)
|
|
||||||
return Status(error::INTERNAL_ERROR, "Failed to create audio track.");
|
|
||||||
|
|
||||||
if (info->codec() == kCodecOpus) {
|
if (info->codec() == kCodecOpus) {
|
||||||
track->set_codec_id(mkvmuxer::Tracks::kOpusCodecId);
|
track->set_codec_id(mkvmuxer::Tracks::kOpusCodecId);
|
||||||
} else if (info->codec() == kCodecVorbis) {
|
} else if (info->codec() == kCodecVorbis) {
|
||||||
|
@ -322,29 +304,9 @@ Status Segmenter::CreateAudioTrack(AudioStreamInfo* info) {
|
||||||
track->set_channels(info->num_channels());
|
track->set_channels(info->num_channels());
|
||||||
track->set_seek_pre_roll(info->seek_preroll_ns());
|
track->set_seek_pre_roll(info->seek_preroll_ns());
|
||||||
track->set_codec_delay(info->codec_delay_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;
|
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) {
|
Status Segmenter::WriteFrame(bool write_duration) {
|
||||||
// Create a frame manually so we can create non-SimpleBlock frames. This
|
// 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
|
// is required to allow the frame duration to be added. If the duration
|
||||||
|
|
|
@ -9,7 +9,6 @@
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include "packager/media/base/status.h"
|
#include "packager/media/base/status.h"
|
||||||
#include "packager/media/formats/webm/encryptor.h"
|
|
||||||
#include "packager/media/formats/webm/mkv_writer.h"
|
#include "packager/media/formats/webm/mkv_writer.h"
|
||||||
#include "packager/media/formats/webm/seek_head.h"
|
#include "packager/media/formats/webm/seek_head.h"
|
||||||
#include "packager/third_party/libwebm/src/mkvmuxer.hpp"
|
#include "packager/third_party/libwebm/src/mkvmuxer.hpp"
|
||||||
|
@ -20,9 +19,7 @@ namespace media {
|
||||||
struct MuxerOptions;
|
struct MuxerOptions;
|
||||||
|
|
||||||
class AudioStreamInfo;
|
class AudioStreamInfo;
|
||||||
class KeySource;
|
|
||||||
class MediaSample;
|
class MediaSample;
|
||||||
class StreamInfo;
|
|
||||||
class MuxerListener;
|
class MuxerListener;
|
||||||
class ProgressListener;
|
class ProgressListener;
|
||||||
class StreamInfo;
|
class StreamInfo;
|
||||||
|
@ -40,30 +37,10 @@ class Segmenter {
|
||||||
/// Status::OK results in an undefined behavior.
|
/// Status::OK results in an undefined behavior.
|
||||||
/// @param info The stream info for the stream being segmented.
|
/// @param info The stream info for the stream being segmented.
|
||||||
/// @param muxer_listener receives muxer events. Can be NULL.
|
/// @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.
|
/// @return OK on success, an error status otherwise.
|
||||||
Status Initialize(StreamInfo* info,
|
Status Initialize(StreamInfo* info,
|
||||||
ProgressListener* progress_listener,
|
ProgressListener* progress_listener,
|
||||||
MuxerListener* muxer_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);
|
|
||||||
|
|
||||||
/// Finalize the segmenter.
|
/// Finalize the segmenter.
|
||||||
/// @return OK on success, an error status otherwise.
|
/// @return OK on success, an error status otherwise.
|
||||||
|
@ -121,10 +98,10 @@ class Segmenter {
|
||||||
virtual Status DoFinalize() = 0;
|
virtual Status DoFinalize() = 0;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Status CreateVideoTrack(VideoStreamInfo* info);
|
Status InitializeAudioTrack(const AudioStreamInfo* info,
|
||||||
Status CreateAudioTrack(AudioStreamInfo* info);
|
mkvmuxer::AudioTrack* track);
|
||||||
Status InitializeEncryptor(KeySource* key_source, uint32_t max_sd_pixels,
|
Status InitializeVideoTrack(const VideoStreamInfo* info,
|
||||||
uint32_t max_hd_pixels, uint32_t max_uhd1_pixels);
|
mkvmuxer::VideoTrack* track);
|
||||||
|
|
||||||
// Writes the previous frame to the file.
|
// Writes the previous frame to the file.
|
||||||
Status WriteFrame(bool write_duration);
|
Status WriteFrame(bool write_duration);
|
||||||
|
@ -142,10 +119,6 @@ class Segmenter {
|
||||||
uint64_t reference_frame_timestamp_ = 0;
|
uint64_t reference_frame_timestamp_ = 0;
|
||||||
|
|
||||||
const MuxerOptions& options_;
|
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_;
|
std::unique_ptr<mkvmuxer::Cluster> cluster_;
|
||||||
mkvmuxer::Cues cues_;
|
mkvmuxer::Cues cues_;
|
||||||
|
|
|
@ -47,14 +47,11 @@ class SegmentTestBase : public ::testing::Test {
|
||||||
void CreateAndInitializeSegmenter(
|
void CreateAndInitializeSegmenter(
|
||||||
const MuxerOptions& options,
|
const MuxerOptions& options,
|
||||||
StreamInfo* info,
|
StreamInfo* info,
|
||||||
KeySource* key_source,
|
|
||||||
std::unique_ptr<webm::Segmenter>* result) const {
|
std::unique_ptr<webm::Segmenter>* result) const {
|
||||||
std::unique_ptr<S> segmenter(new S(options));
|
std::unique_ptr<S> segmenter(new S(options));
|
||||||
|
|
||||||
ASSERT_OK(segmenter->Initialize(
|
ASSERT_OK(segmenter->Initialize(info, nullptr /* progress_listener */,
|
||||||
info, NULL /* progress_listener */, NULL /* muxer_listener */,
|
nullptr /* muxer_listener */));
|
||||||
key_source, 0 /* max_sd_pixels */, 0 /* max_hd_pixels */,
|
|
||||||
0 /* max_uhd1_pixels */, 1 /* clear_lead_in_seconds */));
|
|
||||||
*result = std::move(segmenter);
|
*result = std::move(segmenter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -141,7 +141,7 @@ class SingleSegmentSegmenterTest : public SegmentTestBase {
|
||||||
void InitializeSegmenter(const MuxerOptions& options) {
|
void InitializeSegmenter(const MuxerOptions& options) {
|
||||||
ASSERT_NO_FATAL_FAILURE(
|
ASSERT_NO_FATAL_FAILURE(
|
||||||
CreateAndInitializeSegmenter<webm::TwoPassSingleSegmentSegmenter>(
|
CreateAndInitializeSegmenter<webm::TwoPassSingleSegmentSegmenter>(
|
||||||
options, info_.get(), NULL, &segmenter_));
|
options, info_.get(), &segmenter_));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<StreamInfo> info_;
|
std::shared_ptr<StreamInfo> info_;
|
||||||
|
|
|
@ -69,6 +69,7 @@
|
||||||
'cluster_builder.cc',
|
'cluster_builder.cc',
|
||||||
'cluster_builder.h',
|
'cluster_builder.h',
|
||||||
'encrypted_segmenter_unittest.cc',
|
'encrypted_segmenter_unittest.cc',
|
||||||
|
'encryptor_unittest.cc',
|
||||||
'multi_segment_segmenter_unittest.cc',
|
'multi_segment_segmenter_unittest.cc',
|
||||||
'segmenter_test_base.cc',
|
'segmenter_test_base.cc',
|
||||||
'segmenter_test_base.h',
|
'segmenter_test_base.h',
|
||||||
|
|
|
@ -57,8 +57,13 @@ WebMClusterParser::WebMClusterParser(
|
||||||
true,
|
true,
|
||||||
video_default_duration,
|
video_default_duration,
|
||||||
new_sample_cb) {
|
new_sample_cb) {
|
||||||
if (decryption_key_source)
|
if (decryption_key_source) {
|
||||||
decryptor_source_.reset(new DecryptorSource(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();
|
for (WebMTracksParser::TextTracks::const_iterator it = text_tracks.begin();
|
||||||
it != text_tracks.end();
|
it != text_tracks.end();
|
||||||
++it) {
|
++it) {
|
||||||
|
|
|
@ -24,16 +24,10 @@ WebMMuxer::~WebMMuxer() {}
|
||||||
Status WebMMuxer::InitializeMuxer() {
|
Status WebMMuxer::InitializeMuxer() {
|
||||||
CHECK_EQ(streams().size(), 1U);
|
CHECK_EQ(streams().size(), 1U);
|
||||||
|
|
||||||
if (crypto_period_duration_in_seconds() > 0) {
|
if (streams()[0]->is_encrypted() &&
|
||||||
NOTIMPLEMENTED() << "Key rotation is not implemented for WebM";
|
streams()[0]->encryption_config().protection_scheme != FOURCC_cenc) {
|
||||||
return Status(error::UNIMPLEMENTED,
|
LOG(ERROR) << "WebM does not support protection scheme other than 'cenc'.";
|
||||||
"Key rotation is not implemented for WebM");
|
return Status(error::INVALID_ARGUMENT,
|
||||||
}
|
|
||||||
|
|
||||||
if (encryption_key_source() && (protection_scheme() != FOURCC_cenc)) {
|
|
||||||
NOTIMPLEMENTED()
|
|
||||||
<< "WebM does not support protection scheme other than 'cenc'.";
|
|
||||||
return Status(error::UNIMPLEMENTED,
|
|
||||||
"WebM does not support protection scheme other than 'cenc'.");
|
"WebM does not support protection scheme other than 'cenc'.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,10 +38,7 @@ Status WebMMuxer::InitializeMuxer() {
|
||||||
}
|
}
|
||||||
|
|
||||||
Status initialized = segmenter_->Initialize(
|
Status initialized = segmenter_->Initialize(
|
||||||
streams()[0].get(), progress_listener(), muxer_listener(),
|
streams()[0].get(), progress_listener(), muxer_listener());
|
||||||
encryption_key_source(), max_sd_pixels(), max_hd_pixels(),
|
|
||||||
max_uhd1_pixels(), clear_lead_in_seconds());
|
|
||||||
|
|
||||||
if (!initialized.ok())
|
if (!initialized.ok())
|
||||||
return initialized;
|
return initialized;
|
||||||
|
|
||||||
|
@ -78,6 +69,12 @@ Status WebMMuxer::FinalizeSegment(size_t stream_id,
|
||||||
std::shared_ptr<SegmentInfo> segment_info) {
|
std::shared_ptr<SegmentInfo> segment_info) {
|
||||||
DCHECK(segmenter_);
|
DCHECK(segmenter_);
|
||||||
DCHECK_EQ(stream_id, 0u);
|
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,
|
return segmenter_->FinalizeSegment(segment_info->start_timestamp,
|
||||||
segment_info->duration,
|
segment_info->duration,
|
||||||
segment_info->is_subsegment);
|
segment_info->is_subsegment);
|
||||||
|
|
|
@ -744,7 +744,8 @@ bool WvmMediaParser::ParseIndexEntry() {
|
||||||
stream_id_count_, time_scale, track_duration, video_codec,
|
stream_id_count_, time_scale, track_duration, video_codec,
|
||||||
std::string(), video_codec_config.data(), video_codec_config.size(),
|
std::string(), video_codec_config.data(), video_codec_config.size(),
|
||||||
video_width, video_height, pixel_width, pixel_height, trick_play_rate,
|
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_) + ":" +
|
program_demux_stream_map_[base::UintToString(index_program_id_) + ":" +
|
||||||
base::UintToString(
|
base::UintToString(
|
||||||
video_pes_stream_id
|
video_pes_stream_id
|
||||||
|
@ -760,7 +761,8 @@ bool WvmMediaParser::ParseIndexEntry() {
|
||||||
std::string(), audio_codec_config.data(), audio_codec_config.size(),
|
std::string(), audio_codec_config.data(), audio_codec_config.size(),
|
||||||
kAacSampleSizeBits, num_channels, sampling_frequency,
|
kAacSampleSizeBits, num_channels, sampling_frequency,
|
||||||
0 /* seek preroll */, 0 /* codec delay */, 0 /* max bitrate */,
|
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_) + ":" +
|
program_demux_stream_map_[base::UintToString(index_program_id_) + ":" +
|
||||||
base::UintToString(
|
base::UintToString(
|
||||||
audio_pes_stream_id
|
audio_pes_stream_id
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
#include "packager/media/base/stream_info.h"
|
#include "packager/media/base/stream_info.h"
|
||||||
#include "packager/media/base/test/status_test_util.h"
|
#include "packager/media/base/test/status_test_util.h"
|
||||||
#include "packager/media/chunking/chunking_handler.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/formats/mp4/mp4_muxer.h"
|
||||||
#include "packager/media/test/test_data_util.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);
|
bool ContentsEqual(const std::string& file1, const std::string file2);
|
||||||
|
|
||||||
ChunkingOptions SetupChunkingOptions();
|
ChunkingOptions SetupChunkingOptions();
|
||||||
|
EncryptionOptions SetupEncryptionOptions();
|
||||||
MuxerOptions SetupMuxerOptions(const std::string& output,
|
MuxerOptions SetupMuxerOptions(const std::string& output,
|
||||||
bool single_segment);
|
bool single_segment);
|
||||||
void Remux(const std::string& input,
|
void Remux(const std::string& input,
|
||||||
|
@ -139,6 +141,17 @@ ChunkingOptions PackagerTestBasic::SetupChunkingOptions() {
|
||||||
return options;
|
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,
|
void PackagerTestBasic::Remux(const std::string& input,
|
||||||
const std::string& video_output,
|
const std::string& video_output,
|
||||||
const std::string& audio_output,
|
const std::string& audio_output,
|
||||||
|
@ -157,18 +170,18 @@ void PackagerTestBasic::Remux(const std::string& input,
|
||||||
new mp4::MP4Muxer(SetupMuxerOptions(video_output, single_segment)));
|
new mp4::MP4Muxer(SetupMuxerOptions(video_output, single_segment)));
|
||||||
muxer_video->set_clock(&fake_clock_);
|
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 =
|
auto chunking_handler =
|
||||||
std::make_shared<ChunkingHandler>(SetupChunkingOptions());
|
std::make_shared<ChunkingHandler>(SetupChunkingOptions());
|
||||||
ASSERT_OK(demuxer.SetHandler("video", chunking_handler));
|
ASSERT_OK(demuxer.SetHandler("video", chunking_handler));
|
||||||
|
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));
|
ASSERT_OK(chunking_handler->SetHandler(0, muxer_video));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::shared_ptr<Muxer> muxer_audio;
|
std::shared_ptr<Muxer> muxer_audio;
|
||||||
if (!audio_output.empty()) {
|
if (!audio_output.empty()) {
|
||||||
|
@ -176,18 +189,18 @@ void PackagerTestBasic::Remux(const std::string& input,
|
||||||
new mp4::MP4Muxer(SetupMuxerOptions(audio_output, single_segment)));
|
new mp4::MP4Muxer(SetupMuxerOptions(audio_output, single_segment)));
|
||||||
muxer_audio->set_clock(&fake_clock_);
|
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 =
|
auto chunking_handler =
|
||||||
std::make_shared<ChunkingHandler>(SetupChunkingOptions());
|
std::make_shared<ChunkingHandler>(SetupChunkingOptions());
|
||||||
ASSERT_OK(demuxer.SetHandler("audio", chunking_handler));
|
ASSERT_OK(demuxer.SetHandler("audio", chunking_handler));
|
||||||
|
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(chunking_handler->SetHandler(0, muxer_audio));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ASSERT_OK(demuxer.Initialize());
|
ASSERT_OK(demuxer.Initialize());
|
||||||
// Start remuxing process.
|
// Start remuxing process.
|
||||||
|
|
Loading…
Reference in New Issue