Implement top level API: packager.cc/h
Change-Id: I2b4fbc0b9f6df5617e9f59173e144c7be1b01d2b
This commit is contained in:
parent
2d2825290c
commit
db45711750
|
@ -25,8 +25,8 @@ DEFINE_string(iv,
|
|||
DEFINE_string(pssh,
|
||||
"",
|
||||
"One or more PSSH boxes in hex string format. If not specified, "
|
||||
"will generate a v1 common PSSH box according to "
|
||||
"https://goo.gl/507mKp.");
|
||||
"will generate a v1 common PSSH box as specified in "
|
||||
"https://goo.gl/s8RIhr.");
|
||||
|
||||
namespace shaka {
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ DEFINE_bool(include_common_pssh,
|
|||
false,
|
||||
"When using Widevine encryption, include an additional v1 PSSH box "
|
||||
"for the common system ID that includes the key IDs. See: "
|
||||
"https://goo.gl/507mKp");
|
||||
"https://goo.gl/s8RIhr");
|
||||
DEFINE_string(key_server_url, "", "Key server url. Required for encryption and "
|
||||
"decryption");
|
||||
DEFINE_string(content_id, "", "Content Id (hex).");
|
||||
|
|
|
@ -0,0 +1,683 @@
|
|||
// 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/packager.h"
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
#include <iostream>
|
||||
|
||||
#include "packager/app/libcrypto_threading.h"
|
||||
#include "packager/app/packager_util.h"
|
||||
#include "packager/app/stream_descriptor.h"
|
||||
#include "packager/base/files/file_path.h"
|
||||
#include "packager/base/logging.h"
|
||||
#include "packager/base/path_service.h"
|
||||
#include "packager/base/strings/stringprintf.h"
|
||||
#include "packager/base/threading/simple_thread.h"
|
||||
#include "packager/base/time/clock.h"
|
||||
#include "packager/hls/base/hls_notifier.h"
|
||||
#include "packager/hls/base/simple_hls_notifier.h"
|
||||
#include "packager/media/base/container_names.h"
|
||||
#include "packager/media/base/fourccs.h"
|
||||
#include "packager/media/base/key_source.h"
|
||||
#include "packager/media/base/muxer_options.h"
|
||||
#include "packager/media/base/muxer_util.h"
|
||||
#include "packager/media/chunking/chunking_handler.h"
|
||||
#include "packager/media/crypto/encryption_handler.h"
|
||||
#include "packager/media/demuxer/demuxer.h"
|
||||
#include "packager/media/event/hls_notify_muxer_listener.h"
|
||||
#include "packager/media/event/mpd_notify_muxer_listener.h"
|
||||
#include "packager/media/event/vod_media_info_dump_muxer_listener.h"
|
||||
#include "packager/media/file/file.h"
|
||||
#include "packager/media/formats/mp2t/ts_muxer.h"
|
||||
#include "packager/media/formats/mp4/mp4_muxer.h"
|
||||
#include "packager/media/formats/webm/webm_muxer.h"
|
||||
#include "packager/media/trick_play/trick_play_handler.h"
|
||||
#include "packager/mpd/base/dash_iop_mpd_notifier.h"
|
||||
#include "packager/mpd/base/media_info.pb.h"
|
||||
#include "packager/mpd/base/mpd_builder.h"
|
||||
#include "packager/mpd/base/simple_mpd_notifier.h"
|
||||
|
||||
DEFINE_bool(dump_stream_info, false, "Dump demuxed stream info.");
|
||||
DEFINE_bool(use_fake_clock_for_muxer,
|
||||
false,
|
||||
"Set to true to use a fake clock for muxer. With this flag set, "
|
||||
"creation time and modification time in outputs are set to 0. "
|
||||
"Should only be used for testing.");
|
||||
|
||||
namespace shaka {
|
||||
|
||||
// TODO(kqyang): Clean up namespaces.
|
||||
using media::ChunkingOptions;
|
||||
using media::Demuxer;
|
||||
using media::EncryptionOptions;
|
||||
using media::KeySource;
|
||||
using media::MuxerOptions;
|
||||
using media::Status;
|
||||
namespace error = media::error;
|
||||
|
||||
namespace media {
|
||||
namespace {
|
||||
|
||||
const char kMediaInfoSuffix[] = ".media_info";
|
||||
|
||||
// TODO(rkuroiwa): Write TTML and WebVTT parser (demuxing) for a better check
|
||||
// and for supporting live/segmenting (muxing). With a demuxer and a muxer,
|
||||
// CreateRemuxJobs() shouldn't treat text as a special case.
|
||||
std::string DetermineTextFileFormat(const std::string& file) {
|
||||
std::string content;
|
||||
if (!File::ReadFileToString(file.c_str(), &content)) {
|
||||
LOG(ERROR) << "Failed to open file " << file
|
||||
<< " to determine file format.";
|
||||
return "";
|
||||
}
|
||||
MediaContainerName container_name = DetermineContainer(
|
||||
reinterpret_cast<const uint8_t*>(content.data()), content.size());
|
||||
if (container_name == CONTAINER_WEBVTT) {
|
||||
return "vtt";
|
||||
} else if (container_name == CONTAINER_TTML) {
|
||||
return "ttml";
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
MediaContainerName GetOutputFormat(const StreamDescriptor& descriptor) {
|
||||
MediaContainerName output_format = CONTAINER_UNKNOWN;
|
||||
if (!descriptor.output_format.empty()) {
|
||||
output_format = DetermineContainerFromFormatName(descriptor.output_format);
|
||||
if (output_format == CONTAINER_UNKNOWN) {
|
||||
LOG(ERROR) << "Unable to determine output format from '"
|
||||
<< descriptor.output_format << "'.";
|
||||
}
|
||||
} else {
|
||||
const std::string& output_name = descriptor.output.empty()
|
||||
? descriptor.segment_template
|
||||
: descriptor.output;
|
||||
if (output_name.empty())
|
||||
return CONTAINER_UNKNOWN;
|
||||
output_format = DetermineContainerFromFileName(output_name);
|
||||
if (output_format == CONTAINER_UNKNOWN) {
|
||||
LOG(ERROR) << "Unable to determine output format from '" << output_name
|
||||
<< "'.";
|
||||
}
|
||||
}
|
||||
return output_format;
|
||||
}
|
||||
|
||||
bool ValidateStreamDescriptor(bool dump_stream_info,
|
||||
const StreamDescriptor& descriptor) {
|
||||
// Validate and insert the descriptor
|
||||
if (descriptor.input.empty()) {
|
||||
LOG(ERROR) << "Stream input not specified.";
|
||||
return false;
|
||||
}
|
||||
if (!dump_stream_info && descriptor.stream_selector.empty()) {
|
||||
LOG(ERROR) << "Stream stream_selector not specified.";
|
||||
return false;
|
||||
}
|
||||
|
||||
// We should have either output or segment_template specified.
|
||||
const bool output_specified =
|
||||
!descriptor.output.empty() || !descriptor.segment_template.empty();
|
||||
if (!output_specified) {
|
||||
if (!FLAGS_dump_stream_info) {
|
||||
LOG(ERROR) << "Stream output not specified.";
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
const MediaContainerName output_format = GetOutputFormat(descriptor);
|
||||
if (output_format == CONTAINER_UNKNOWN)
|
||||
return false;
|
||||
|
||||
if (output_format == MediaContainerName::CONTAINER_MPEG2TS) {
|
||||
if (descriptor.segment_template.empty()) {
|
||||
LOG(ERROR) << "Please specify segment_template. Single file TS output "
|
||||
"is not supported.";
|
||||
return false;
|
||||
}
|
||||
// Note that MPEG2 TS doesn't need a separate initialization segment, so
|
||||
// output field is not needed.
|
||||
if (!descriptor.output.empty()) {
|
||||
LOG(WARNING) << "TS init_segment '" << descriptor.output
|
||||
<< "' ignored. TS muxer does not support initialization "
|
||||
"segment generation.";
|
||||
}
|
||||
} else {
|
||||
if (descriptor.output.empty()) {
|
||||
LOG(ERROR) << "init_segment is required for format " << output_format;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ValidateParams(const PackagingParams& packaging_params,
|
||||
const std::vector<StreamDescriptor>& stream_descriptors) {
|
||||
if (!packaging_params.chunking_params.segment_sap_aligned &&
|
||||
packaging_params.chunking_params.subsegment_sap_aligned) {
|
||||
LOG(ERROR) << "Setting segment_sap_aligned to false but "
|
||||
"subsegment_sap_aligned to true is not allowed.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (packaging_params.output_media_info &&
|
||||
!packaging_params.mpd_params.mpd_output.empty()) {
|
||||
LOG(ERROR) << "output_media_info and MPD output do not work together.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (packaging_params.output_media_info &&
|
||||
!packaging_params.hls_params.master_playlist_output.empty()) {
|
||||
LOG(ERROR) << "output_media_info and HLS output do not work together.";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Since there isn't a muxer listener that can output both MPD and HLS,
|
||||
// disallow specifying both MPD and HLS flags.
|
||||
if (!packaging_params.mpd_params.mpd_output.empty() &&
|
||||
!packaging_params.hls_params.master_playlist_output.empty()) {
|
||||
LOG(ERROR) << "output both MPD and HLS are not supported.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (stream_descriptors.empty()) {
|
||||
LOG(ERROR) << "Stream descriptors cannot be empty.";
|
||||
return false;
|
||||
}
|
||||
|
||||
// On demand profile generates single file segment while live profile
|
||||
// generates multiple segments specified using segment template.
|
||||
const bool on_demand_dash_profile =
|
||||
stream_descriptors.begin()->segment_template.empty();
|
||||
for (const auto& descriptor : stream_descriptors) {
|
||||
if (on_demand_dash_profile != descriptor.segment_template.empty()) {
|
||||
LOG(ERROR) << "Inconsistent stream descriptor specification: "
|
||||
"segment_template should be specified for none or all "
|
||||
"stream descriptors.";
|
||||
return false;
|
||||
}
|
||||
if (!ValidateStreamDescriptor(FLAGS_dump_stream_info, descriptor))
|
||||
return false;
|
||||
}
|
||||
if (packaging_params.output_media_info && !on_demand_dash_profile) {
|
||||
// TODO(rkuroiwa, kqyang): Support partial media info dump for live.
|
||||
NOTIMPLEMENTED() << "ERROR: --output_media_info is only supported for "
|
||||
"on-demand profile (not using segment_template).";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
class StreamDescriptorCompareFn {
|
||||
public:
|
||||
bool operator()(const StreamDescriptor& a, const StreamDescriptor& b) {
|
||||
if (a.input == b.input) {
|
||||
if (a.stream_selector == b.stream_selector)
|
||||
// Stream with high trick_play_factor is at the beginning.
|
||||
return a.trick_play_factor > b.trick_play_factor;
|
||||
else
|
||||
return a.stream_selector < b.stream_selector;
|
||||
}
|
||||
|
||||
return a.input < b.input;
|
||||
}
|
||||
};
|
||||
|
||||
/// Sorted list of StreamDescriptor.
|
||||
typedef std::multiset<StreamDescriptor, StreamDescriptorCompareFn>
|
||||
StreamDescriptorList;
|
||||
|
||||
// A fake clock that always return time 0 (epoch). Should only be used for
|
||||
// testing.
|
||||
class FakeClock : public base::Clock {
|
||||
public:
|
||||
base::Time Now() override { return base::Time(); }
|
||||
};
|
||||
|
||||
// Demux, Mux(es) and worker thread used to remux a source file/stream.
|
||||
class RemuxJob : public base::SimpleThread {
|
||||
public:
|
||||
RemuxJob(std::unique_ptr<Demuxer> demuxer)
|
||||
: SimpleThread("RemuxJob"), demuxer_(std::move(demuxer)) {}
|
||||
|
||||
~RemuxJob() override {}
|
||||
|
||||
Demuxer* demuxer() { return demuxer_.get(); }
|
||||
Status status() { return status_; }
|
||||
|
||||
private:
|
||||
RemuxJob(const RemuxJob&) = delete;
|
||||
RemuxJob& operator=(const RemuxJob&) = delete;
|
||||
|
||||
void Run() override {
|
||||
DCHECK(demuxer_);
|
||||
status_ = demuxer_->Run();
|
||||
}
|
||||
|
||||
std::unique_ptr<Demuxer> demuxer_;
|
||||
Status status_;
|
||||
};
|
||||
|
||||
bool StreamInfoToTextMediaInfo(const StreamDescriptor& stream_descriptor,
|
||||
const MuxerOptions& stream_muxer_options,
|
||||
MediaInfo* text_media_info) {
|
||||
const std::string& language = stream_descriptor.language;
|
||||
const std::string format = DetermineTextFileFormat(stream_descriptor.input);
|
||||
if (format.empty()) {
|
||||
LOG(ERROR) << "Failed to determine the text file format for "
|
||||
<< stream_descriptor.input;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!File::Copy(stream_descriptor.input.c_str(),
|
||||
stream_muxer_options.output_file_name.c_str())) {
|
||||
LOG(ERROR) << "Failed to copy the input file (" << stream_descriptor.input
|
||||
<< ") to output file (" << stream_muxer_options.output_file_name
|
||||
<< ").";
|
||||
return false;
|
||||
}
|
||||
|
||||
text_media_info->set_media_file_name(stream_muxer_options.output_file_name);
|
||||
text_media_info->set_container_type(MediaInfo::CONTAINER_TEXT);
|
||||
|
||||
if (stream_muxer_options.bandwidth != 0) {
|
||||
text_media_info->set_bandwidth(stream_muxer_options.bandwidth);
|
||||
} else {
|
||||
// Text files are usually small and since the input is one file; there's no
|
||||
// way for the player to do ranged requests. So set this value to something
|
||||
// reasonable.
|
||||
const int kDefaultTextBandwidth = 256;
|
||||
text_media_info->set_bandwidth(kDefaultTextBandwidth);
|
||||
}
|
||||
|
||||
MediaInfo::TextInfo* text_info = text_media_info->mutable_text_info();
|
||||
text_info->set_format(format);
|
||||
if (!language.empty())
|
||||
text_info->set_language(language);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::shared_ptr<Muxer> CreateOutputMuxer(const MuxerOptions& options,
|
||||
MediaContainerName container) {
|
||||
if (container == CONTAINER_WEBM) {
|
||||
return std::shared_ptr<Muxer>(new webm::WebMMuxer(options));
|
||||
} else if (container == CONTAINER_MPEG2TS) {
|
||||
return std::shared_ptr<Muxer>(new mp2t::TsMuxer(options));
|
||||
} else {
|
||||
DCHECK_EQ(container, CONTAINER_MOV);
|
||||
return std::shared_ptr<Muxer>(new mp4::MP4Muxer(options));
|
||||
}
|
||||
}
|
||||
|
||||
bool CreateRemuxJobs(const StreamDescriptorList& stream_descriptors,
|
||||
const PackagingParams& packaging_params,
|
||||
const ChunkingOptions& chunking_options,
|
||||
const EncryptionOptions& encryption_options,
|
||||
const MuxerOptions& muxer_options,
|
||||
FakeClock* fake_clock,
|
||||
KeySource* encryption_key_source,
|
||||
MpdNotifier* mpd_notifier,
|
||||
hls::HlsNotifier* hls_notifier,
|
||||
std::vector<std::unique_ptr<RemuxJob>>* remux_jobs) {
|
||||
// No notifiers OR (mpd_notifier XOR hls_notifier); which is NAND.
|
||||
DCHECK(!(mpd_notifier && hls_notifier));
|
||||
DCHECK(remux_jobs);
|
||||
|
||||
std::shared_ptr<TrickPlayHandler> trick_play_handler;
|
||||
|
||||
std::string previous_input;
|
||||
std::string previous_stream_selector;
|
||||
int stream_number = 0;
|
||||
for (StreamDescriptorList::const_iterator
|
||||
stream_iter = stream_descriptors.begin();
|
||||
stream_iter != stream_descriptors.end();
|
||||
++stream_iter, ++stream_number) {
|
||||
MediaContainerName output_format = GetOutputFormat(*stream_iter);
|
||||
|
||||
// Process stream descriptor.
|
||||
MuxerOptions stream_muxer_options(muxer_options);
|
||||
stream_muxer_options.output_file_name = stream_iter->output;
|
||||
if (!stream_iter->segment_template.empty()) {
|
||||
if (!ValidateSegmentTemplate(stream_iter->segment_template)) {
|
||||
LOG(ERROR) << "ERROR: segment template with '"
|
||||
<< stream_iter->segment_template << "' is invalid.";
|
||||
return false;
|
||||
}
|
||||
stream_muxer_options.segment_template = stream_iter->segment_template;
|
||||
}
|
||||
stream_muxer_options.bandwidth = stream_iter->bandwidth;
|
||||
|
||||
if (stream_iter->stream_selector == "text" &&
|
||||
output_format != CONTAINER_MOV) {
|
||||
MediaInfo text_media_info;
|
||||
if (!StreamInfoToTextMediaInfo(*stream_iter, stream_muxer_options,
|
||||
&text_media_info)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mpd_notifier) {
|
||||
uint32_t unused;
|
||||
if (!mpd_notifier->NotifyNewContainer(text_media_info, &unused)) {
|
||||
LOG(ERROR) << "Failed to process text file " << stream_iter->input;
|
||||
} else {
|
||||
mpd_notifier->Flush();
|
||||
}
|
||||
} else if (packaging_params.output_media_info) {
|
||||
VodMediaInfoDumpMuxerListener::WriteMediaInfoToFile(
|
||||
text_media_info,
|
||||
stream_muxer_options.output_file_name + kMediaInfoSuffix);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (stream_iter->input != previous_input) {
|
||||
// New remux job needed. Create demux and job thread.
|
||||
std::unique_ptr<Demuxer> demuxer(new Demuxer(stream_iter->input));
|
||||
demuxer->set_dump_stream_info(FLAGS_dump_stream_info);
|
||||
if (packaging_params.decryption_params.key_provider !=
|
||||
KeyProvider::kNone) {
|
||||
std::unique_ptr<KeySource> decryption_key_source(
|
||||
CreateDecryptionKeySource(packaging_params.decryption_params));
|
||||
if (!decryption_key_source)
|
||||
return false;
|
||||
demuxer->SetKeySource(std::move(decryption_key_source));
|
||||
}
|
||||
remux_jobs->emplace_back(new RemuxJob(std::move(demuxer)));
|
||||
trick_play_handler.reset();
|
||||
previous_input = stream_iter->input;
|
||||
// Skip setting up muxers if output is not needed.
|
||||
if (stream_iter->output.empty() && stream_iter->segment_template.empty())
|
||||
continue;
|
||||
}
|
||||
DCHECK(!remux_jobs->empty());
|
||||
|
||||
// Each stream selector requires an individual trick play handler.
|
||||
// E.g., an input with two video streams needs two trick play handlers.
|
||||
// TODO(hmchen): add a test case in packager_test.py for two video streams
|
||||
// input.
|
||||
if (stream_iter->stream_selector != previous_stream_selector) {
|
||||
previous_stream_selector = stream_iter->stream_selector;
|
||||
trick_play_handler.reset();
|
||||
}
|
||||
|
||||
std::shared_ptr<Muxer> muxer(
|
||||
CreateOutputMuxer(stream_muxer_options, output_format));
|
||||
if (FLAGS_use_fake_clock_for_muxer) muxer->set_clock(fake_clock);
|
||||
|
||||
std::unique_ptr<MuxerListener> muxer_listener;
|
||||
DCHECK(!(packaging_params.output_media_info && mpd_notifier));
|
||||
if (packaging_params.output_media_info) {
|
||||
const std::string output_media_info_file_name =
|
||||
stream_muxer_options.output_file_name + kMediaInfoSuffix;
|
||||
std::unique_ptr<VodMediaInfoDumpMuxerListener>
|
||||
vod_media_info_dump_muxer_listener(
|
||||
new VodMediaInfoDumpMuxerListener(output_media_info_file_name));
|
||||
muxer_listener = std::move(vod_media_info_dump_muxer_listener);
|
||||
}
|
||||
if (mpd_notifier) {
|
||||
std::unique_ptr<MpdNotifyMuxerListener> mpd_notify_muxer_listener(
|
||||
new MpdNotifyMuxerListener(mpd_notifier));
|
||||
muxer_listener = std::move(mpd_notify_muxer_listener);
|
||||
}
|
||||
|
||||
if (hls_notifier) {
|
||||
// TODO(rkuroiwa): Do some smart stuff to group the audios, e.g. detect
|
||||
// languages.
|
||||
std::string group_id = stream_iter->hls_group_id;
|
||||
std::string name = stream_iter->hls_name;
|
||||
std::string hls_playlist_name = stream_iter->hls_playlist_name;
|
||||
if (group_id.empty())
|
||||
group_id = "audio";
|
||||
if (name.empty())
|
||||
name = base::StringPrintf("stream_%d", stream_number);
|
||||
if (hls_playlist_name.empty())
|
||||
hls_playlist_name = base::StringPrintf("stream_%d.m3u8", stream_number);
|
||||
|
||||
muxer_listener.reset(new HlsNotifyMuxerListener(hls_playlist_name, name,
|
||||
group_id, hls_notifier));
|
||||
}
|
||||
|
||||
if (muxer_listener)
|
||||
muxer->SetMuxerListener(std::move(muxer_listener));
|
||||
|
||||
// Create a new trick_play_handler. Note that the stream_decriptors
|
||||
// are sorted so that for the same input and stream_selector, the main
|
||||
// stream is always the last one following the trick play streams.
|
||||
if (stream_iter->trick_play_factor > 0) {
|
||||
if (!trick_play_handler) {
|
||||
trick_play_handler.reset(new TrickPlayHandler());
|
||||
}
|
||||
trick_play_handler->SetHandlerForTrickPlay(stream_iter->trick_play_factor,
|
||||
std::move(muxer));
|
||||
if (trick_play_handler->IsConnected())
|
||||
continue;
|
||||
} else if (trick_play_handler) {
|
||||
trick_play_handler->SetHandlerForMainStream(std::move(muxer));
|
||||
DCHECK(trick_play_handler->IsConnected());
|
||||
continue;
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<MediaHandler>> handlers;
|
||||
|
||||
auto chunking_handler = std::make_shared<ChunkingHandler>(chunking_options);
|
||||
handlers.push_back(chunking_handler);
|
||||
|
||||
Status status;
|
||||
if (encryption_key_source && !stream_iter->skip_encryption) {
|
||||
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 (output_format == CONTAINER_MPEG2TS) {
|
||||
VLOG(1) << "Use Apple Sample AES encryption for MPEG2TS.";
|
||||
new_encryption_options.protection_scheme =
|
||||
kAppleSampleAesProtectionScheme;
|
||||
}
|
||||
handlers.emplace_back(
|
||||
new EncryptionHandler(new_encryption_options, encryption_key_source));
|
||||
}
|
||||
|
||||
// If trick_play_handler is available, muxer should already be connected to
|
||||
// trick_play_handler.
|
||||
if (trick_play_handler) {
|
||||
handlers.push_back(trick_play_handler);
|
||||
} else {
|
||||
handlers.push_back(std::move(muxer));
|
||||
}
|
||||
|
||||
auto* demuxer = remux_jobs->back()->demuxer();
|
||||
const std::string& stream_selector = stream_iter->stream_selector;
|
||||
status.Update(demuxer->SetHandler(stream_selector, chunking_handler));
|
||||
status.Update(ConnectHandlers(handlers));
|
||||
|
||||
if (!status.ok()) {
|
||||
LOG(ERROR) << "Failed to setup graph: " << status;
|
||||
return false;
|
||||
}
|
||||
if (!stream_iter->language.empty())
|
||||
demuxer->SetLanguageOverride(stream_selector, stream_iter->language);
|
||||
}
|
||||
|
||||
// Initialize processing graph.
|
||||
for (const std::unique_ptr<RemuxJob>& job : *remux_jobs) {
|
||||
Status status = job->demuxer()->Initialize();
|
||||
if (!status.ok()) {
|
||||
LOG(ERROR) << "Failed to initialize processing graph " << status;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Status RunRemuxJobs(const std::vector<std::unique_ptr<RemuxJob>>& remux_jobs) {
|
||||
// Start the job threads.
|
||||
for (const std::unique_ptr<RemuxJob>& job : remux_jobs)
|
||||
job->Start();
|
||||
|
||||
// Wait for all jobs to complete or an error occurs.
|
||||
Status status;
|
||||
bool all_joined;
|
||||
do {
|
||||
all_joined = true;
|
||||
for (const std::unique_ptr<RemuxJob>& job : remux_jobs) {
|
||||
if (job->HasBeenJoined()) {
|
||||
status = job->status();
|
||||
if (!status.ok())
|
||||
break;
|
||||
} else {
|
||||
all_joined = false;
|
||||
job->Join();
|
||||
}
|
||||
}
|
||||
} while (!all_joined && status.ok());
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace media
|
||||
|
||||
std::string EncryptionParams::DefaultStreamLabelFunction(
|
||||
int max_sd_pixels,
|
||||
int max_hd_pixels,
|
||||
int max_uhd1_pixels,
|
||||
const EncryptedStreamAttributes& stream_attributes) {
|
||||
if (stream_info.stream_type == EncryptedStreamAttributes::kAudio)
|
||||
return "AUDIO";
|
||||
if (stream_info.stream_type == EncryptedStreamAttributes::kVideo) {
|
||||
const int pixels = stream_attributes.oneof.video.width *
|
||||
stream_attributes.oneof.video.height;
|
||||
if (pixels <= max_sd_pixels) return "SD";
|
||||
if (pixels <= max_hd_pixels) return "HD";
|
||||
if (pixels <= max_uhd1_pixels) return "UHD1";
|
||||
return "UHD2";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
struct ShakaPackager::PackagerInternal {
|
||||
media::FakeClock fake_clock;
|
||||
std::unique_ptr<KeySource> encryption_key_source;
|
||||
std::unique_ptr<MpdNotifier> mpd_notifier;
|
||||
std::unique_ptr<hls::HlsNotifier> hls_notifier;
|
||||
std::vector<std::unique_ptr<media::RemuxJob>> remux_jobs;
|
||||
};
|
||||
|
||||
ShakaPackager::ShakaPackager() {}
|
||||
|
||||
ShakaPackager::~ShakaPackager() {}
|
||||
|
||||
Status ShakaPackager::Initialize(
|
||||
const PackagingParams& packaging_params,
|
||||
const std::vector<StreamDescriptor>& stream_descriptors) {
|
||||
|
||||
static media::LibcryptoThreading libcrypto_threading;
|
||||
|
||||
if (internal_)
|
||||
return Status(error::INVALID_ARGUMENT, "Already initialized.");
|
||||
|
||||
if (!media::ValidateParams(packaging_params, stream_descriptors))
|
||||
return Status(error::INVALID_ARGUMENT, "Invalid packaging params.");
|
||||
|
||||
std::unique_ptr<PackagerInternal> internal(new PackagerInternal);
|
||||
|
||||
ChunkingOptions chunking_options =
|
||||
media::GetChunkingOptions(packaging_params.chunking_params);
|
||||
EncryptionOptions encryption_options =
|
||||
media::GetEncryptionOptions(packaging_params.encryption_params);
|
||||
MuxerOptions muxer_options = media::GetMuxerOptions(
|
||||
packaging_params.temp_dir, packaging_params.mp4_output_params);
|
||||
|
||||
const bool on_demand_dash_profile =
|
||||
stream_descriptors.begin()->segment_template.empty();
|
||||
MpdOptions mpd_options =
|
||||
media::GetMpdOptions(on_demand_dash_profile, packaging_params.mpd_params);
|
||||
|
||||
// Create encryption key source if needed.
|
||||
if (packaging_params.encryption_params.key_provider != KeyProvider::kNone) {
|
||||
if (encryption_options.protection_scheme == media::FOURCC_NULL)
|
||||
return Status(error::INVALID_ARGUMENT, "Invalid protection scheme.");
|
||||
internal->encryption_key_source =
|
||||
CreateEncryptionKeySource(encryption_options.protection_scheme,
|
||||
packaging_params.encryption_params);
|
||||
if (!internal->encryption_key_source)
|
||||
return Status(error::INVALID_ARGUMENT, "Failed to create key source.");
|
||||
}
|
||||
|
||||
const MpdParams& mpd_params = packaging_params.mpd_params;
|
||||
if (!mpd_params.mpd_output.empty()) {
|
||||
if (mpd_params.generate_dash_if_iop_compliant_mpd) {
|
||||
internal->mpd_notifier.reset(new DashIopMpdNotifier(
|
||||
mpd_options, mpd_params.base_urls, mpd_params.mpd_output));
|
||||
} else {
|
||||
internal->mpd_notifier.reset(new SimpleMpdNotifier(
|
||||
mpd_options, mpd_params.base_urls, mpd_params.mpd_output));
|
||||
}
|
||||
if (!internal->mpd_notifier->Init()) {
|
||||
LOG(ERROR) << "MpdNotifier failed to initialize.";
|
||||
return Status(error::INVALID_ARGUMENT,
|
||||
"Failed to initialize MpdNotifier.");
|
||||
}
|
||||
}
|
||||
|
||||
const HlsParams& hls_params = packaging_params.hls_params;
|
||||
if (!hls_params.master_playlist_output.empty()) {
|
||||
base::FilePath master_playlist_path(
|
||||
base::FilePath::FromUTF8Unsafe(hls_params.master_playlist_output));
|
||||
base::FilePath master_playlist_name = master_playlist_path.BaseName();
|
||||
|
||||
internal->hls_notifier.reset(new hls::SimpleHlsNotifier(
|
||||
hls::HlsNotifier::HlsProfile::kOnDemandProfile, hls_params.base_url,
|
||||
master_playlist_path.DirName().AsEndingWithSeparator().AsUTF8Unsafe(),
|
||||
master_playlist_name.AsUTF8Unsafe()));
|
||||
}
|
||||
|
||||
media::StreamDescriptorList stream_descriptor_list;
|
||||
for (const StreamDescriptor& descriptor : stream_descriptors)
|
||||
stream_descriptor_list.insert(descriptor);
|
||||
if (!media::CreateRemuxJobs(
|
||||
stream_descriptor_list, packaging_params, chunking_options,
|
||||
encryption_options, muxer_options, &internal->fake_clock,
|
||||
internal->encryption_key_source.get(), internal->mpd_notifier.get(),
|
||||
internal->hls_notifier.get(), &internal->remux_jobs)) {
|
||||
return Status(error::INVALID_ARGUMENT, "Failed to create remux jobs.");
|
||||
}
|
||||
internal_ = std::move(internal);
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
Status ShakaPackager::Run() {
|
||||
if (!internal_)
|
||||
return Status(error::INVALID_ARGUMENT, "Not yet initialized.");
|
||||
Status status = media::RunRemuxJobs(internal_->remux_jobs);
|
||||
if (!status.ok())
|
||||
return status;
|
||||
|
||||
if (internal_->hls_notifier) {
|
||||
if (!internal_->hls_notifier->Flush())
|
||||
return Status(error::INVALID_ARGUMENT, "Failed to flush Hls.");
|
||||
}
|
||||
if (internal_->mpd_notifier) {
|
||||
if (!internal_->mpd_notifier->Flush())
|
||||
return Status(error::INVALID_ARGUMENT, "Failed to flush Mpd.");
|
||||
}
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
void ShakaPackager::Cancel() {
|
||||
if (!internal_) {
|
||||
LOG(INFO) << "Not yet initialized. Return directly.";
|
||||
return;
|
||||
}
|
||||
for (const std::unique_ptr<media::RemuxJob>& job : internal_->remux_jobs)
|
||||
job->demuxer()->Cancel();
|
||||
}
|
||||
|
||||
} // namespace shaka
|
|
@ -0,0 +1,427 @@
|
|||
// 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
|
||||
|
||||
#ifndef PACKAGER_PACKAGER_H_
|
||||
#define PACKAGER_PACKAGER_H_
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// TODO(kqyang): Refactor status.h and move it under packager/.
|
||||
#include "packager/media/base/status.h"
|
||||
|
||||
namespace shaka {
|
||||
|
||||
/// MP4 (ISO-BMFF) output related parameters.
|
||||
struct Mp4OutputParams {
|
||||
// Include pssh in the encrypted stream. CMAF recommends carrying
|
||||
// license acquisition information in the manifest and not duplicate the
|
||||
// information in the stream. (This is not a hard requirement so we are still
|
||||
// CMAF compatible even if pssh is included in the stream.)
|
||||
bool include_pssh_in_stream = true;
|
||||
/// 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
|
||||
/// will pack N subsegments in the root SIDX of the segment, with
|
||||
/// segment_duration/N/subsegment_duration fragments per subsegment.
|
||||
/// This flag is ingored for DASH MPD with on-demand profile.
|
||||
const int kNoSidxBoxInSegment = -1;
|
||||
const int kSingleSidxPerSegment = 0;
|
||||
int num_subsegments_per_sidx = kSingleSidxPerSegment;
|
||||
/// Set the flag use_decoding_timestamp_in_timeline, which if set to true, use
|
||||
/// decoding timestamp instead of presentation timestamp in media timeline,
|
||||
/// which is needed to workaround a Chromium bug that decoding timestamp is
|
||||
/// used in buffered range, https://crbug.com/398130.
|
||||
bool use_decoding_timestamp_in_timeline = false;
|
||||
};
|
||||
|
||||
/// Chunking (segmentation) related parameters.
|
||||
struct ChunkingParams {
|
||||
/// Segment duration in seconds.
|
||||
double segment_duration_in_seconds = 0;
|
||||
/// Subsegment duration in seconds. Should not be larger than the segment
|
||||
/// duration.
|
||||
double subsegment_duration_in_seconds = 0;
|
||||
|
||||
/// Force segments to begin with stream access points. Actual segment duration
|
||||
/// may not be exactly what is specified by segment_duration.
|
||||
bool segment_sap_aligned = true;
|
||||
/// Force subsegments to begin with stream access points. Actual subsegment
|
||||
/// duration may not be exactly what is specified by subsegment_duration.
|
||||
/// Setting to subsegment_sap_aligned to true but segment_sap_aligned to false
|
||||
/// is not allowed.
|
||||
bool subsegment_sap_aligned = true;
|
||||
};
|
||||
|
||||
/// DASH MPD related parameters.
|
||||
struct MpdParams {
|
||||
/// MPD output file path.
|
||||
std::string mpd_output;
|
||||
/// BaseURLs for the MPD. The values will be added as <BaseURL> element(s)
|
||||
/// under the <MPD> element.
|
||||
std::vector<std::string> base_urls;
|
||||
/// Set MPD@minBufferTime attribute, which specifies, in seconds, a common
|
||||
/// duration used in the definition of the MPD representation data rate. A
|
||||
/// client can be assured of having enough data for continous playout
|
||||
/// providing playout begins at min_buffer_time after the first bit is
|
||||
/// received.
|
||||
double min_buffer_time = 2.0;
|
||||
/// Generate static MPD for live profile. Note that this flag has no effect
|
||||
/// for on-demand profile, in which case static MPD is always used.
|
||||
bool generate_static_live_mpd = false;
|
||||
/// Set MPD@timeShiftBufferDepth attribute, which is the guaranteed duration
|
||||
/// of the time shifting buffer for 'dynamic' media presentations, in seconds.
|
||||
double time_shift_buffer_depth = 0;
|
||||
/// Set MPD@suggestedPresentationDelay attribute. For 'dynamic' media
|
||||
/// presentations, it specifies a delay, in seconds, to be added to the media
|
||||
/// presentation time. The attribute is not set if the value is 0; the client
|
||||
/// is expected to choose a suitable value in this case.
|
||||
const double kSuggestedPresentationDelayNotSet = 0;
|
||||
double suggested_presentation_delay = kSuggestedPresentationDelayNotSet;
|
||||
/// Set MPD@minimumUpdatePeriod attribute, which indicates to the player how
|
||||
/// often to refresh the MPD in seconds. For dynamic MPD only.
|
||||
double minimum_update_period = 0;
|
||||
/// The tracks tagged with this language will have <Role ... value=\"main\" />
|
||||
/// in the manifest. This allows the player to choose the correct default
|
||||
/// language for the content.
|
||||
std::string default_language;
|
||||
/// Try to generate DASH-IF IOP compliant MPD.
|
||||
bool generate_dash_if_iop_compliant_mpd = true;
|
||||
};
|
||||
|
||||
/// HLS related parameters.
|
||||
struct HlsParams {
|
||||
/// HLS master playlist output path.
|
||||
std::string master_playlist_output;
|
||||
/// The base URL for the Media Playlists and media files listed in the
|
||||
/// playlists. This is the prefix for the files.
|
||||
std::string base_url;
|
||||
};
|
||||
|
||||
/// Encryption / decryption key providers.
|
||||
enum class KeyProvider {
|
||||
kNone = 0,
|
||||
kWidevine = 1,
|
||||
kPlayready = 2,
|
||||
kRawKey = 3,
|
||||
};
|
||||
|
||||
/// Signer credential for Widevine license server.
|
||||
struct WidevineSigner {
|
||||
/// Name of the signer / content provider.
|
||||
std::string signer_name;
|
||||
|
||||
enum class SigningKeyType {
|
||||
kNone,
|
||||
kAes,
|
||||
kRsa,
|
||||
};
|
||||
/// Specifies the signing key type, which determines whether AES or RSA key
|
||||
/// are used to authenticate the signer. A type of 'kNone' is invalid.
|
||||
SigningKeyType signing_key_type = SigningKeyType::kNone;
|
||||
struct {
|
||||
/// AES signing key.
|
||||
std::string key;
|
||||
/// AES signing IV.
|
||||
std::string iv;
|
||||
} aes;
|
||||
struct {
|
||||
/// RSA signing private key.
|
||||
std::string key;
|
||||
} rsa;
|
||||
};
|
||||
|
||||
/// Widevine encryption parameters.
|
||||
struct WidevineEncryptionParams {
|
||||
/// Widevine license / key server URL.
|
||||
std::string key_server_url;
|
||||
/// Generates and includes an additional v1 PSSH box for the common system ID.
|
||||
/// See: https://goo.gl/s8RIhr.
|
||||
// TODO(kqyang): Move to EncryptionParams and support common PSSH generation
|
||||
// in all key providers.
|
||||
bool include_common_pssh = false;
|
||||
/// Content identifier.
|
||||
std::vector<uint8_t> content_id;
|
||||
/// The name of a stored policy, which specifies DRM content rights.
|
||||
std::string policy;
|
||||
/// Signer credential for Widevine license / key server.
|
||||
WidevineSigner signer;
|
||||
};
|
||||
|
||||
/// Playready encryption parameters.
|
||||
/// Two different modes of playready key acquisition is supported:
|
||||
/// (1) Fetch from a key server. `key_server_url` and `program_identifier` are
|
||||
/// required. The presence of other parameters may be necessary depends
|
||||
/// on server configuration.
|
||||
/// (2) Provide the raw key directly. Both `key_id` and `key` are required.
|
||||
/// We are planning to merge this mode with `RawKeyEncryptionParams`.
|
||||
struct PlayreadyEncryptionParams {
|
||||
/// Playready license / key server URL.
|
||||
std::string key_server_url;
|
||||
/// Playready program identifier.
|
||||
std::string program_identifier;
|
||||
/// Absolute path to the Certificate Authority file for the server cert in PEM
|
||||
/// format.
|
||||
std::string ca_file;
|
||||
/// Absolute path to client certificate file.
|
||||
std::string client_cert_file;
|
||||
/// Absolute path to the private key file.
|
||||
std::string client_cert_private_key_file;
|
||||
/// Password to the private key file.
|
||||
std::string client_cert_private_key_password;
|
||||
|
||||
// TODO(kqyang): move raw playready key generation to RawKey.
|
||||
/// Provides a raw Playready KeyId.
|
||||
std::string key_id;
|
||||
/// Provides a raw Playready Key.
|
||||
std::string key;
|
||||
};
|
||||
|
||||
/// Raw key encryption parameters, i.e. with key parameters provided.
|
||||
struct RawKeyEncryptionParams {
|
||||
/// An optional initialization vector. If not provided, a random `iv` will be
|
||||
/// generated. Note that this parameter should only be used during testing.
|
||||
std::string iv;
|
||||
/// Inject a custom `pssh` or multiple concatenated `psshs`. If not provided,
|
||||
/// a common system pssh will be generated.
|
||||
std::string pssh;
|
||||
|
||||
using StreamLabel = std::string;
|
||||
struct KeyPair {
|
||||
std::string key_id;
|
||||
std::string key;
|
||||
};
|
||||
/// Defines the KeyPair for the streams. An empty `StreamLabel` indicates the
|
||||
/// default `KeyPair`, which applies to all the `StreamLabels` not present in
|
||||
/// `key_map`.
|
||||
std::map<StreamLabel, KeyPair> key_map;
|
||||
};
|
||||
|
||||
/// Encryption parameters.
|
||||
struct EncryptionParams {
|
||||
/// Specifies the key provider, which determines which key provider is used
|
||||
/// and which encryption params is valid. 'kNone' means not to encrypt the
|
||||
/// streams.
|
||||
KeyProvider key_provider = KeyProvider::kNone;
|
||||
// Only one of the three fields is valid.
|
||||
WidevineEncryptionParams widevine;
|
||||
PlayreadyEncryptionParams playready;
|
||||
RawKeyEncryptionParams raw_key;
|
||||
|
||||
/// Clear lead duration in seconds.
|
||||
double clear_lead_in_seconds = 0;
|
||||
/// The protection scheme: "cenc", "cens", "cbc1", "cbcs".
|
||||
std::string protection_scheme = "cenc";
|
||||
/// Crypto period duration in seconds. A positive value means key rotation is
|
||||
/// enabled, the key provider must support key rotation in this case.
|
||||
const double kNoKeyRotation = 0;
|
||||
double crypto_period_duration_in_seconds = 0;
|
||||
/// Enable/disable subsample encryption for VP9.
|
||||
bool vp9_subsample_encryption = true;
|
||||
|
||||
/// Encrypted stream information that is used to determine stream label.
|
||||
struct EncryptedStreamAttributes {
|
||||
enum StreamType {
|
||||
kUnknown,
|
||||
kVideo,
|
||||
kAudio,
|
||||
};
|
||||
|
||||
StreamType stream_type = kUnknown;
|
||||
union OneOf {
|
||||
OneOf() {}
|
||||
|
||||
struct {
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
float frame_rate = 0;
|
||||
int bit_depth = 0;
|
||||
} video;
|
||||
|
||||
struct {
|
||||
int number_of_channels = 0;
|
||||
} audio;
|
||||
} oneof;
|
||||
};
|
||||
/// Default stream label function implementation.
|
||||
/// @param max_sd_pixels 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, i.e. [0,
|
||||
/// max_sd_pixels], it is SD.
|
||||
/// @param max_hd_pixels: 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, i.e. (max_sd_pixels,
|
||||
/// max_hd_pixels], it is HD.
|
||||
/// @param max_uhd1_pixels: 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, i.e. (max_hd_pixels,
|
||||
/// max_uhd1_pixels], it is UHD1. Otherwise it is
|
||||
/// UHD2.
|
||||
/// @param stream_info Encrypted stream info.
|
||||
/// @return the stream label associated with `stream_info`. Can be "AUDIO",
|
||||
/// "SD", "HD", "UHD1" or "UHD2".
|
||||
static std::string DefaultStreamLabelFunction(
|
||||
int max_sd_pixels,
|
||||
int max_hd_pixels,
|
||||
int max_uhd1_pixels,
|
||||
const EncryptedStreamAttributes& stream_attributes);
|
||||
const int kDefaultMaxSdPixels = 768 * 576;
|
||||
const int kDefaultMaxHdPixels = 1920 * 1080;
|
||||
const int kDefaultMaxUhd1Pixels = 4096 * 2160;
|
||||
/// Stream label function assigns a stream label to the stream to be
|
||||
/// encrypted. Stream label is used to associate KeyPair with streams. Streams
|
||||
/// with the same stream label always uses the same keyPair; Streams with
|
||||
/// different stream label could use the same or different KeyPairs.
|
||||
std::function<std::string(const EncryptedStreamAttributes& stream_attributes)>
|
||||
stream_label_func =
|
||||
std::bind(&EncryptionParams::DefaultStreamLabelFunction,
|
||||
kDefaultMaxSdPixels,
|
||||
kDefaultMaxHdPixels,
|
||||
kDefaultMaxUhd1Pixels,
|
||||
std::placeholders::_1);
|
||||
};
|
||||
|
||||
/// Widevine decryption parameters.
|
||||
struct WidevineDecryptionParams {
|
||||
/// Widevine license / key server URL.
|
||||
std::string key_server_url;
|
||||
/// Signer credential for Widevine license / key server.
|
||||
WidevineSigner signer;
|
||||
};
|
||||
|
||||
/// Raw key decryption parameters, i.e. with key parameters provided.
|
||||
struct RawKeyDecryptionParams {
|
||||
using StreamLabel = std::string;
|
||||
struct KeyPair {
|
||||
std::string key_id;
|
||||
std::string key;
|
||||
};
|
||||
/// Defines the KeyPair for the streams. An empty `StreamLabel` indicates the
|
||||
/// default `KeyPair`, which applies to all the `StreamLabels` not present in
|
||||
/// `key_map`.
|
||||
std::map<StreamLabel, KeyPair> key_map;
|
||||
};
|
||||
|
||||
/// Decryption parameters.
|
||||
struct DecryptionParams {
|
||||
/// Specifies the key provider, which determines which key provider is used
|
||||
/// and which encryption params is valid. 'kNone' means not to decrypt the
|
||||
/// streams.
|
||||
KeyProvider key_provider = KeyProvider::kNone;
|
||||
// Only one of the two fields is valid.
|
||||
WidevineDecryptionParams widevine;
|
||||
RawKeyDecryptionParams raw_key;
|
||||
};
|
||||
|
||||
/// Packaging parameters.
|
||||
struct PackagingParams {
|
||||
/// Specify temporary directory for intermediate temporary files.
|
||||
std::string temp_dir;
|
||||
/// MP4 (ISO-BMFF) output related parameters.
|
||||
Mp4OutputParams mp4_output_params;
|
||||
/// Chunking (segmentation) related parameters.
|
||||
ChunkingParams chunking_params;
|
||||
|
||||
/// Manifest generation related parameters. Right now only one of
|
||||
/// `output_media_info`, `mpd_params` and `hls_params` should be set. Create a
|
||||
/// human readable format of MediaInfo. The output file name will be the name
|
||||
/// specified by output flag, suffixed with `.media_info`.
|
||||
bool output_media_info = false;
|
||||
/// DASH MPD related parameters.
|
||||
MpdParams mpd_params;
|
||||
/// HLS related parameters.
|
||||
HlsParams hls_params;
|
||||
|
||||
/// Encryption and Decryption Parameters.
|
||||
EncryptionParams encryption_params;
|
||||
DecryptionParams decryption_params;
|
||||
};
|
||||
|
||||
/// Defines a single input/output stream.
|
||||
struct StreamDescriptor {
|
||||
/// Input/source media file path or network stream URL. Required.
|
||||
std::string input;
|
||||
// TODO(kqyang): Add support for feeding data through read func.
|
||||
// std::function<int64_t(void* buffer, uint64_t length)> read_func;
|
||||
|
||||
/// Stream selector, can be `audio`, `video`, `text` or a zero based stream
|
||||
/// index. Required.
|
||||
std::string stream_selector;
|
||||
|
||||
/// Specifies output file path or init segment path (if segment template is
|
||||
/// specified). Can be empty for self initialization media segments.
|
||||
std::string output;
|
||||
/// Specifies segment template. Can be empty.
|
||||
std::string segment_template;
|
||||
// TODO: Add support for writing data through write func.
|
||||
// std::function<int64_t(const std::string& id, void* buffer, uint64_t
|
||||
// length)> write_func;
|
||||
|
||||
/// Optional value which specifies output container format, e.g. "mp4". If not
|
||||
/// specified, will detect from output / segment template name.
|
||||
std::string output_format;
|
||||
/// If set to true, the stream will not be encrypted. This is useful, e.g. to
|
||||
/// encrypt only video streams.
|
||||
bool skip_encryption = false;
|
||||
/// If set to a non-zero value, will generate a trick play / trick mode
|
||||
/// stream with frames sampled from the key frames in the original stream.
|
||||
/// `trick_play_factor` defines the sampling rate.
|
||||
uint32_t trick_play_factor = 0;
|
||||
/// Optional user-specified content bit rate for the stream, in bits/sec.
|
||||
/// If specified, this value is propagated to the `$Bandwidth$` template
|
||||
/// parameter for segment names. If not specified, its value may be estimated.
|
||||
uint32_t bandwidth = 0;
|
||||
/// Optional value which contains a user-specified language tag. If specified,
|
||||
/// this value overrides any language metadata in the input stream.
|
||||
std::string language;
|
||||
/// Required for audio when outputting HLS. It defines the name of the output
|
||||
/// stream, which is not necessarily the same as output. This is used as the
|
||||
/// `NAME` attribute for EXT-X-MEDIA.
|
||||
std::string hls_name;
|
||||
/// Required for audio when outputting HLS. It defines the group ID for the
|
||||
/// output stream. This is used as the GROUP-ID attribute for EXT-X-MEDIA.
|
||||
std::string hls_group_id;
|
||||
/// Required for HLS output. It defines the name of the playlist for the
|
||||
/// stream. Usually ends with `.m3u8`.
|
||||
std::string hls_playlist_name;
|
||||
};
|
||||
|
||||
class ShakaPackager {
|
||||
public:
|
||||
ShakaPackager();
|
||||
~ShakaPackager();
|
||||
|
||||
/// Initialize packaging pipeline.
|
||||
/// @param packaging_params contains the packaging parameters.
|
||||
/// @param stream_descriptors a list of stream descriptors.
|
||||
/// @return OK on success, an appropriate error code on failure.
|
||||
media::Status Initialize(
|
||||
const PackagingParams& packaging_params,
|
||||
const std::vector<StreamDescriptor>& stream_descriptors);
|
||||
|
||||
/// Run the pipeline to completion (or failed / been cancelled). Note
|
||||
/// that it blocks until completion.
|
||||
/// @return OK on success, an appropriate error code on failure.
|
||||
media::Status Run();
|
||||
|
||||
/// Cancel packaging. Note that it has to be called from another thread.
|
||||
void Cancel();
|
||||
|
||||
private:
|
||||
ShakaPackager(const ShakaPackager&) = delete;
|
||||
ShakaPackager& operator=(const ShakaPackager&) = delete;
|
||||
|
||||
struct PackagerInternal;
|
||||
std::unique_ptr<PackagerInternal> internal_;
|
||||
};
|
||||
|
||||
} // namespace shaka
|
||||
|
||||
#endif // PACKAGER_PACKAGER_H_
|
Loading…
Reference in New Issue