From 6957a4ac076e87914fc2d1163477c5aa31b9283f Mon Sep 17 00:00:00 2001 From: Kongqun Yang Date: Wed, 15 Jan 2014 14:44:11 -0800 Subject: [PATCH] Implement packager driving program packager_main I am not sure whether it is a good idea to define command line flags in the actual source. I created several flags definition .h to host these flags for now. Change-Id: Ib6ca60d8656e8015a64dafff8e0a98a47676bbd2 --- app/fixed_key_encryption_flags.h | 34 +++++ app/muxer_flags.h | 66 ++++++++++ app/packager_main.cc | 215 +++++++++++++++++++++++++++++++ app/widevine_encryption_flags.h | 44 +++++++ packager.gyp | 12 ++ 5 files changed, 371 insertions(+) create mode 100644 app/fixed_key_encryption_flags.h create mode 100644 app/muxer_flags.h create mode 100644 app/packager_main.cc create mode 100644 app/widevine_encryption_flags.h diff --git a/app/fixed_key_encryption_flags.h b/app/fixed_key_encryption_flags.h new file mode 100644 index 0000000000..ab6ab3cc4a --- /dev/null +++ b/app/fixed_key_encryption_flags.h @@ -0,0 +1,34 @@ +// Copyright (c) 2013 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. +// +// Defines command line flags for fixed key encryption. + +#ifndef APP_FIXED_KEY_ENCRYPTION_FLAGS_H_ +#define APP_FIXED_KEY_ENCRYPTION_FLAGS_H_ + +#include + +DEFINE_bool(enable_fixed_key_encryption, + false, + "Enable encryption with fixed key."); +DEFINE_string(key_id, "", "Key id in hex string format."); +DEFINE_string(key, "", "Key in hex string format."); +DEFINE_string(pssh, "", "PSSH in hex string format."); + +static bool IsNotEmptyWithFixedKeyEncryption(const char* flag_name, + const std::string& flag_value) { + return FLAGS_enable_fixed_key_encryption ? !flag_value.empty() : true; +} + +static bool dummy_key_id_validator = + google::RegisterFlagValidator(&FLAGS_key_id, + &IsNotEmptyWithFixedKeyEncryption); +static bool dummy_key_validator = + google::RegisterFlagValidator(&FLAGS_key, + &IsNotEmptyWithFixedKeyEncryption); +static bool dummy_pssh_validator = + google::RegisterFlagValidator(&FLAGS_pssh, + &IsNotEmptyWithFixedKeyEncryption); + +#endif // APP_FIXED_KEY_ENCRYPTION_FLAGS_H_ diff --git a/app/muxer_flags.h b/app/muxer_flags.h new file mode 100644 index 0000000000..3cc726380e --- /dev/null +++ b/app/muxer_flags.h @@ -0,0 +1,66 @@ +// Copyright (c) 2013 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. +// +// Defines Muxer flags. + +#ifndef APP_MUXER_FLAGS_H_ +#define APP_MUXER_FLAGS_H_ + +#include + +DEFINE_bool(audio, false, "Add the first audio stream to muxer."); +DEFINE_bool(video, false, "Add the first video stream to muxer."); +DEFINE_double(clear_lead, + 10.0, + "Clear lead in seconds if encryption is enabled."); + +DEFINE_bool(single_segment, + true, + "Generate a single segment for the media presentation. This option " + "should be set for on demand profile."); +DEFINE_double(segment_duration, + 10.0f, + "Segment duration in seconds. If single_segment is specified, " + "this parameter sets the duration of a subsegment; otherwise, " + "this parameter sets the duration of a segment. Actual segment " + "durations may not be exactly as requested."); +DEFINE_bool(segment_sap_aligned, + true, + "Force segments to begin with stream access points."); +DEFINE_double(fragment_duration, + 2.0f, + "Fragment duration in seconds. Should not be larger than " + "the segment duration. Actual fragment durations may not be " + "exactly as requested."); +DEFINE_bool(fragment_sap_aligned, + true, + "Force fragments to begin with stream access points. This flag " + "implies segment_sap_aligned."); +DEFINE_int32(num_subsegments_per_sidx, + 1, + "For ISO BMFF only. Set the number of subsegments in each " + "SIDX box. If 0, a single SIDX box is used per segment; if " + "-1, no SIDX box is used; Otherwise, the muxer packs N " + "subsegments in the root SIDX of the segment, with " + "segment_duration/N/fragment_duration fragments per " + "subsegment."); +DEFINE_string(output, + "", + "Output file path. If segment_template is not specified, " + "the muxer generates this single output file with all " + "segments concatenated; Otherwise, it specifies the " + "initialization segment name."); +DEFINE_string(segment_template, + "", + "Output segment name pattern for generated segments. It " + "can furthermore be configured using a subset of " + "SegmentTemplate identifiers: $Number$, $Bandwidth$ and " + "$Time$."); +DEFINE_string(temp_file, + "", + "Specify a temporary file for on demand media file creation. If " + "not specified, a new file will be created in an OS-dependent " + "temporary directory."); + +#endif // APP_MUXER_FLAGS_H_ diff --git a/app/packager_main.cc b/app/packager_main.cc new file mode 100644 index 0000000000..d8bd1e5dba --- /dev/null +++ b/app/packager_main.cc @@ -0,0 +1,215 @@ +// Copyright (c) 2013 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. + +#include +#include + +#include "app/fixed_key_encryption_flags.h" +#include "app/muxer_flags.h" +#include "app/widevine_encryption_flags.h" +#include "base/file_util.h" +#include "base/logging.h" +#include "base/strings/stringprintf.h" +#include "media/base/demuxer.h" +#include "media/base/fixed_encryptor_source.h" +#include "media/base/widevine_encryptor_source.h" +#include "media/base/media_stream.h" +#include "media/base/muxer_options.h" +#include "media/base/request_signer.h" +#include "media/base/stream_info.h" +#include "media/file/file.h" +#include "media/mp4/mp4_muxer.h" + +DEFINE_bool(dump_stream_info, false, "Dump demuxed stream info."); + +namespace { +const char kUsage[] = + "Packager driver program. Sample Usage:\n%s [flags]"; +} // namespace + +namespace media { + +void DumpStreamInfo(const std::vector& streams) { + std::cout << "Found " << streams.size() << " stream(s)." << std::endl; + for (size_t i = 0; i < streams.size(); ++i) + std::cout << "Stream [" << i << "] " << streams[i]->ToString() << std::endl; +} + +// Create and initialize encryptor source. +scoped_ptr CreateEncryptorSource() { + scoped_ptr encryptor_source; + if (FLAGS_enable_widevine_encryption) { + std::string rsa_private_key; + if (!File::ReadFileToString(FLAGS_signing_key_path.c_str(), + &rsa_private_key)) { + LOG(ERROR) << "Failed to read from rsa_key_file."; + return scoped_ptr(); + } + + scoped_ptr signer( + RsaRequestSigner::CreateSigner(FLAGS_signer, rsa_private_key)); + if (!signer) { + LOG(ERROR) << "Cannot create signer object from " + << FLAGS_signing_key_path; + return scoped_ptr(); + } + + WidevineEncryptorSource::TrackType track_type = + WidevineEncryptorSource::GetTrackTypeFromString(FLAGS_track_type); + if (track_type == WidevineEncryptorSource::TRACK_TYPE_UNKNOWN) { + LOG(ERROR) << "Unknown track_type specified."; + return scoped_ptr(); + } + + encryptor_source.reset(new WidevineEncryptorSource( + FLAGS_server_url, FLAGS_content_id, track_type, signer.Pass())); + } else if (FLAGS_enable_fixed_key_encryption) { + encryptor_source.reset( + new FixedEncryptorSource(FLAGS_key_id, FLAGS_key, FLAGS_pssh)); + } + + if (encryptor_source) { + Status status = encryptor_source->Initialize(); + if (!status.ok()) { + LOG(ERROR) << "Encryptor source failed to initialize: " + << status.ToString(); + return scoped_ptr(); + } + } + return encryptor_source.Pass(); +} + +bool GetMuxerOptions(MuxerOptions* muxer_options) { + DCHECK(muxer_options); + + muxer_options->single_segment = FLAGS_single_segment; + muxer_options->segment_duration = FLAGS_segment_duration; + muxer_options->fragment_duration = FLAGS_fragment_duration; + muxer_options->segment_sap_aligned = FLAGS_segment_sap_aligned; + muxer_options->fragment_sap_aligned = FLAGS_fragment_sap_aligned; + muxer_options->num_subsegments_per_sidx = FLAGS_num_subsegments_per_sidx; + muxer_options->output_file_name = FLAGS_output; + muxer_options->segment_template = FLAGS_segment_template; + muxer_options->temp_file_name = FLAGS_temp_file; + + // Create a temp file if needed. + if (muxer_options->single_segment && muxer_options->temp_file_name.empty()) { + base::FilePath path; + if (!file_util::CreateTemporaryFile(&path)) { + LOG(ERROR) << "Failed to create a temporary file."; + return false; + } + muxer_options->temp_file_name = path.value(); + } + return true; +} + +MediaStream* FindFirstStreamOfType(const std::vector& streams, + StreamType stream_type) { + typedef std::vector::const_iterator StreamIterator; + for (StreamIterator it = streams.begin(); it != streams.end(); ++it) { + if ((*it)->info()->stream_type() == stream_type) + return *it; + } + return NULL; +} +MediaStream* FindFirstVideoStream(const std::vector& streams) { + return FindFirstStreamOfType(streams, kStreamVideo); +} +MediaStream* FindFirstAudioStream(const std::vector& streams) { + return FindFirstStreamOfType(streams, kStreamAudio); +} + +bool AddStreamToMuxer(const std::vector& streams, Muxer* muxer) { + DCHECK(muxer); + + if (!FLAGS_video && !FLAGS_audio) { + LOG(ERROR) << "Required: add_video_stream or add_audio_stream."; + return false; + } + + MediaStream* stream = FLAGS_video ? FindFirstVideoStream(streams) + : FindFirstAudioStream(streams); + if (!stream) { + LOG(ERROR) << "Cannot find a " << (FLAGS_video ? "video" : "audio") + << " stream to mux."; + return false; + } + Status status = muxer->AddStream(stream); + if (!status.ok()) { + LOG(ERROR) << "Muxer failed to add stream: " << status.ToString(); + return false; + } + return true; +} + +bool RunPackager(const std::string& input) { + Status status; + + // Setup and initialize Demuxer. + Demuxer demuxer(input, NULL); + status = demuxer.Initialize(); + if (!status.ok()) { + LOG(ERROR) << "Demuxer failed to initialize: " << status.ToString(); + return false; + } + + if (FLAGS_dump_stream_info) + DumpStreamInfo(demuxer.streams()); + + if (FLAGS_output.empty()) { + LOG(INFO) << "No output specified. Exiting."; + return true; + } + + // Setup muxer. + MuxerOptions muxer_options; + if (!GetMuxerOptions(&muxer_options)) + return false; + + scoped_ptr muxer(new mp4::MP4Muxer(muxer_options)); + if (!AddStreamToMuxer(demuxer.streams(), muxer.get())) + return false; + + scoped_ptr encryptor_source; + if (FLAGS_enable_widevine_encryption || FLAGS_enable_fixed_key_encryption) { + encryptor_source = CreateEncryptorSource(); + if (!encryptor_source) + return false; + } + muxer->SetEncryptorSource(encryptor_source.get(), FLAGS_clear_lead); + + status = muxer->Initialize(); + if (!status.ok()) { + LOG(ERROR) << "Muxer failed to initialize: " << status.ToString(); + return false; + } + + // Start remuxing process. + status = demuxer.Run(); + if (!status.ok()) { + LOG(ERROR) << "Remuxing failed: " << status.ToString(); + return false; + } + status = muxer->Finalize(); + if (!status.ok()) { + LOG(ERROR) << "Muxer failed to finalize: " << status.ToString(); + return false; + } + + std::cout << "Packaging completed successfully." << std::endl; + return true; +} + +} // namespace media + +int main(int argc, char** argv) { + google::SetUsageMessage(base::StringPrintf(kUsage, argv[0])); + google::ParseCommandLineFlags(&argc, &argv, true); + if (argc < 2) { + google::ShowUsageWithFlags(argv[0]); + return 1; + } + return media::RunPackager(argv[1]) ? 0 : 1; +} diff --git a/app/widevine_encryption_flags.h b/app/widevine_encryption_flags.h new file mode 100644 index 0000000000..82bb49a4d2 --- /dev/null +++ b/app/widevine_encryption_flags.h @@ -0,0 +1,44 @@ +// Copyright (c) 2013 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. +// +// Defines command line flags for widevine_encryption. + +#ifndef APP_WIDEVINE_ENCRYPTION_FLAGS_H_ +#define APP_WIDEVINE_ENCRYPTION_FLAGS_H_ + +#include + +DEFINE_bool(enable_widevine_encryption, + false, + "Enable encryption with Widevine license server/proxy."); +DEFINE_string(server_url, "", "License server url."); +DEFINE_string(content_id, "", "Content Id."); +DEFINE_string(track_type, "SD", "Track type: HD, SD or AUDIO."); +DEFINE_string(signer, "", "The name of the signer."); +DEFINE_string(signing_key_path, + "", + "Stores PKCS#1 RSA private key for request signing."); + +static bool IsNotEmptyWithWidevineEncryption(const char* flag_name, + const std::string& flag_value) { + return FLAGS_enable_widevine_encryption ? !flag_value.empty() : true; +} + +static bool dummy_server_url_validator = + google::RegisterFlagValidator(&FLAGS_server_url, + &IsNotEmptyWithWidevineEncryption); +static bool dummy_content_id_validator = + google::RegisterFlagValidator(&FLAGS_content_id, + &IsNotEmptyWithWidevineEncryption); +static bool dummy_track_type_validator = + google::RegisterFlagValidator(&FLAGS_track_type, + &IsNotEmptyWithWidevineEncryption); +static bool dummy_signer_validator = + google::RegisterFlagValidator(&FLAGS_signer, + &IsNotEmptyWithWidevineEncryption); +static bool dummy_rsa_key_file_validator = + google::RegisterFlagValidator(&FLAGS_signing_key_path, + &IsNotEmptyWithWidevineEncryption); + +#endif // APP_WIDEVINE_ENCRYPTION_FLAGS_H_ diff --git a/packager.gyp b/packager.gyp index e5c9212c7b..6e7460e3e4 100644 --- a/packager.gyp +++ b/packager.gyp @@ -272,5 +272,17 @@ 'testing/gtest.gyp:gtest', ], }, + { + 'target_name': 'packager_main', + 'type': 'executable', + 'sources': [ + 'app/packager_main.cc', + ], + 'dependencies': [ + 'file', + 'mp4', + 'third_party/gflags/gflags.gyp:gflags', + ], + }, ], }