7 #include "packager/packager.h"
9 #include "packager/app/libcrypto_threading.h"
10 #include "packager/app/packager_util.h"
11 #include "packager/app/stream_descriptor.h"
12 #include "packager/base/at_exit.h"
13 #include "packager/base/files/file_path.h"
14 #include "packager/base/logging.h"
15 #include "packager/base/path_service.h"
16 #include "packager/base/strings/stringprintf.h"
17 #include "packager/base/threading/simple_thread.h"
18 #include "packager/base/time/clock.h"
19 #include "packager/file/file.h"
20 #include "packager/hls/base/hls_notifier.h"
21 #include "packager/hls/base/simple_hls_notifier.h"
22 #include "packager/media/base/container_names.h"
23 #include "packager/media/base/fourccs.h"
24 #include "packager/media/base/key_source.h"
25 #include "packager/media/base/muxer_options.h"
26 #include "packager/media/base/muxer_util.h"
27 #include "packager/media/chunking/chunking_handler.h"
28 #include "packager/media/crypto/encryption_handler.h"
29 #include "packager/media/demuxer/demuxer.h"
30 #include "packager/media/event/combined_muxer_listener.h"
31 #include "packager/media/event/hls_notify_muxer_listener.h"
32 #include "packager/media/event/mpd_notify_muxer_listener.h"
33 #include "packager/media/event/vod_media_info_dump_muxer_listener.h"
34 #include "packager/media/formats/mp2t/ts_muxer.h"
35 #include "packager/media/formats/mp4/mp4_muxer.h"
36 #include "packager/media/formats/webm/webm_muxer.h"
37 #include "packager/media/trick_play/trick_play_handler.h"
38 #include "packager/mpd/base/dash_iop_mpd_notifier.h"
39 #include "packager/mpd/base/media_info.pb.h"
40 #include "packager/mpd/base/mpd_builder.h"
41 #include "packager/mpd/base/simple_mpd_notifier.h"
42 #include "packager/version/version.h"
48 using media::KeySource;
49 using media::MuxerOptions;
54 const char kMediaInfoSuffix[] =
".media_info";
59 std::string DetermineTextFileFormat(
const std::string& file) {
62 LOG(ERROR) <<
"Failed to open file " << file
63 <<
" to determine file format.";
66 MediaContainerName container_name = DetermineContainer(
67 reinterpret_cast<const uint8_t*>(content.data()), content.size());
68 if (container_name == CONTAINER_WEBVTT) {
70 }
else if (container_name == CONTAINER_TTML) {
77 MediaContainerName GetOutputFormat(
const StreamDescriptor& descriptor) {
78 MediaContainerName output_format = CONTAINER_UNKNOWN;
79 if (!descriptor.output_format.empty()) {
80 output_format = DetermineContainerFromFormatName(descriptor.output_format);
81 if (output_format == CONTAINER_UNKNOWN) {
82 LOG(ERROR) <<
"Unable to determine output format from '"
83 << descriptor.output_format <<
"'.";
86 const std::string& output_name = descriptor.output.empty()
87 ? descriptor.segment_template
89 if (output_name.empty())
90 return CONTAINER_UNKNOWN;
91 output_format = DetermineContainerFromFileName(output_name);
92 if (output_format == CONTAINER_UNKNOWN) {
93 LOG(ERROR) <<
"Unable to determine output format from '" << output_name
100 Status ValidateStreamDescriptor(
bool dump_stream_info,
101 const StreamDescriptor& stream) {
102 if (stream.input.empty()) {
103 return Status(error::INVALID_ARGUMENT,
"Stream input not specified.");
108 if (dump_stream_info && stream.output.empty() &&
109 stream.segment_template.empty()) {
113 if (stream.output.empty() && stream.segment_template.empty()) {
114 return Status(error::INVALID_ARGUMENT,
115 "Streams must specify 'output' or 'segment template'.");
119 if (stream.stream_selector.empty()) {
120 return Status(error::INVALID_ARGUMENT,
121 "Stream stream_selector not specified.");
125 if (stream.segment_template.length()) {
126 Status template_check = ValidateSegmentTemplate(stream.segment_template);
127 if (!template_check.ok()) {
128 return template_check;
134 const MediaContainerName output_format = GetOutputFormat(stream);
136 if (output_format == CONTAINER_UNKNOWN) {
137 return Status(error::INVALID_ARGUMENT,
"Unsupported output format.");
138 }
else if (output_format == MediaContainerName::CONTAINER_MPEG2TS) {
139 if (stream.segment_template.empty()) {
140 return Status(error::INVALID_ARGUMENT,
141 "Please specify segment_template. Single file TS output is "
148 if (stream.output.length()) {
149 return Status(error::INVALID_ARGUMENT,
150 "All TS segments must be self-initializing. Stream "
151 "descriptors 'output' or 'init_segment' are not allowed.");
156 if (stream.segment_template.length() && stream.output.empty()) {
157 return Status(error::INVALID_ARGUMENT,
158 "Please specify 'init_segment'. All non-TS multi-segment "
159 "content must provide an init segment.");
166 Status ValidateParams(
const PackagingParams& packaging_params,
167 const std::vector<StreamDescriptor>& stream_descriptors) {
168 if (!packaging_params.chunking_params.segment_sap_aligned &&
169 packaging_params.chunking_params.subsegment_sap_aligned) {
170 return Status(error::INVALID_ARGUMENT,
171 "Setting segment_sap_aligned to false but "
172 "subsegment_sap_aligned to true is not allowed.");
175 if (stream_descriptors.empty()) {
176 return Status(error::INVALID_ARGUMENT,
177 "Stream descriptors cannot be empty.");
182 const bool on_demand_dash_profile =
183 stream_descriptors.begin()->segment_template.empty();
184 for (
const auto& descriptor : stream_descriptors) {
185 if (on_demand_dash_profile != descriptor.segment_template.empty()) {
186 return Status(error::INVALID_ARGUMENT,
187 "Inconsistent stream descriptor specification: "
188 "segment_template should be specified for none or all "
189 "stream descriptors.");
192 Status stream_check = ValidateStreamDescriptor(
193 packaging_params.test_params.dump_stream_info, descriptor);
195 if (!stream_check.ok()) {
200 if (packaging_params.output_media_info && !on_demand_dash_profile) {
202 return Status(error::UNIMPLEMENTED,
203 "--output_media_info is only supported for on-demand profile "
204 "(not using segment_template).");
210 class StreamDescriptorCompareFn {
212 bool operator()(
const StreamDescriptor& a,
const StreamDescriptor& b) {
213 if (a.input == b.input) {
214 if (a.stream_selector == b.stream_selector)
216 return a.trick_play_factor > b.trick_play_factor;
218 return a.stream_selector < b.stream_selector;
221 return a.input < b.input;
226 typedef std::multiset<StreamDescriptor, StreamDescriptorCompareFn>
227 StreamDescriptorList;
231 class FakeClock :
public base::Clock {
233 base::Time Now()
override {
return base::Time(); }
236 class Job :
public base::SimpleThread {
238 Job(
const std::string& name, std::shared_ptr<OriginHandler> work)
239 : SimpleThread(name),
241 wait_(base::WaitableEvent::ResetPolicy::MANUAL,
242 base::WaitableEvent::InitialState::NOT_SIGNALED) {}
246 status_ = work_->Initialize();
254 const Status& status()
const {
return status_; }
256 base::WaitableEvent* wait() {
return &wait_; }
259 Job(
const Job&) =
delete;
260 Job& operator=(
const Job&) =
delete;
262 void Run()
override {
264 status_ = work_->Run();
268 std::shared_ptr<OriginHandler> work_;
271 base::WaitableEvent wait_;
274 bool StreamInfoToTextMediaInfo(
const StreamDescriptor& stream_descriptor,
275 MediaInfo* text_media_info) {
276 const std::string& language = stream_descriptor.language;
277 const std::string format = DetermineTextFileFormat(stream_descriptor.input);
278 if (format.empty()) {
279 LOG(ERROR) <<
"Failed to determine the text file format for "
280 << stream_descriptor.input;
284 if (!
File::Copy(stream_descriptor.input.c_str(),
285 stream_descriptor.output.c_str())) {
286 LOG(ERROR) <<
"Failed to copy the input file (" << stream_descriptor.input
287 <<
") to output file (" << stream_descriptor.output <<
").";
291 text_media_info->set_media_file_name(stream_descriptor.output);
292 text_media_info->set_container_type(MediaInfo::CONTAINER_TEXT);
294 if (stream_descriptor.bandwidth != 0) {
295 text_media_info->set_bandwidth(stream_descriptor.bandwidth);
300 const int kDefaultTextBandwidth = 256;
301 text_media_info->set_bandwidth(kDefaultTextBandwidth);
304 MediaInfo::TextInfo* text_info = text_media_info->mutable_text_info();
305 text_info->set_format(format);
306 if (!language.empty())
307 text_info->set_language(language);
312 std::unique_ptr<MuxerListener> CreateMuxerListener(
313 const StreamDescriptor& stream,
315 bool output_media_info,
316 MpdNotifier* mpd_notifier,
317 hls::HlsNotifier* hls_notifier) {
318 std::unique_ptr<CombinedMuxerListener> combined_listener(
319 new CombinedMuxerListener);
321 if (output_media_info) {
322 std::unique_ptr<MuxerListener> listener(
323 new VodMediaInfoDumpMuxerListener(stream.output + kMediaInfoSuffix));
324 combined_listener->AddListener(std::move(listener));
328 std::unique_ptr<MuxerListener> listener(
329 new MpdNotifyMuxerListener(mpd_notifier));
330 combined_listener->AddListener(std::move(listener));
336 std::string group_id = stream.hls_group_id;
337 std::string name = stream.hls_name;
338 std::string hls_playlist_name = stream.hls_playlist_name;
339 if (group_id.empty())
342 name = base::StringPrintf(
"stream_%d", stream_number);
343 if (hls_playlist_name.empty())
344 hls_playlist_name = base::StringPrintf(
"stream_%d.m3u8", stream_number);
346 std::unique_ptr<MuxerListener> listener(
new HlsNotifyMuxerListener(
347 hls_playlist_name, name, group_id, hls_notifier));
348 combined_listener->AddListener(std::move(listener));
351 return std::move(combined_listener);
354 std::shared_ptr<Muxer> CreateMuxer(
const PackagingParams& packaging_params,
355 const StreamDescriptor& stream,
357 std::unique_ptr<MuxerListener> listener) {
358 const MediaContainerName format = GetOutputFormat(stream);
360 MuxerOptions options;
361 options.mp4_params = packaging_params.mp4_output_params;
362 options.temp_dir = packaging_params.temp_dir;
363 options.bandwidth = stream.bandwidth;
364 options.output_file_name = stream.output;
365 options.segment_template = stream.segment_template;
367 std::shared_ptr<Muxer> muxer;
371 muxer = std::make_shared<webm::WebMMuxer>(options);
373 case CONTAINER_MPEG2TS:
374 muxer = std::make_shared<mp2t::TsMuxer>(options);
377 muxer = std::make_shared<mp4::MP4Muxer>(options);
380 LOG(ERROR) <<
"Cannot support muxing to " << format;
391 muxer->set_clock(clock);
395 muxer->SetMuxerListener(std::move(listener));
401 std::shared_ptr<MediaHandler> CreateCryptoHandler(
402 const PackagingParams& packaging_params,
403 const StreamDescriptor& stream,
404 KeySource* key_source) {
405 if (stream.skip_encryption) {
414 EncryptionParams encryption_params = packaging_params.encryption_params;
419 if (GetOutputFormat(stream) == CONTAINER_MPEG2TS) {
420 VLOG(1) <<
"Use Apple Sample AES encryption for MPEG2TS.";
421 encryption_params.protection_scheme = kAppleSampleAesProtectionScheme;
424 if (!encryption_params.stream_label_func) {
425 const int kDefaultMaxSdPixels = 768 * 576;
426 const int kDefaultMaxHdPixels = 1920 * 1080;
427 const int kDefaultMaxUhd1Pixels = 4096 * 2160;
428 encryption_params.stream_label_func = std::bind(
430 kDefaultMaxHdPixels, kDefaultMaxUhd1Pixels, std::placeholders::_1);
433 return std::make_shared<EncryptionHandler>(encryption_params, key_source);
436 Status CreateRemuxJobs(
const StreamDescriptorList& stream_descriptors,
437 const PackagingParams& packaging_params,
438 FakeClock* fake_clock,
439 KeySource* encryption_key_source,
440 MpdNotifier* mpd_notifier,
441 hls::HlsNotifier* hls_notifier,
442 std::vector<std::unique_ptr<Job>>* jobs) {
444 DCHECK(!(mpd_notifier && hls_notifier));
447 std::shared_ptr<Demuxer> demuxer;
448 std::shared_ptr<TrickPlayHandler> trick_play_handler;
450 std::string previous_input;
451 std::string previous_stream_selector;
452 int stream_number = 0;
453 for (StreamDescriptorList::const_iterator
454 stream_iter = stream_descriptors.begin();
455 stream_iter != stream_descriptors.end();
456 ++stream_iter, ++stream_number) {
457 MediaContainerName output_format = GetOutputFormat(*stream_iter);
460 MuxerOptions stream_muxer_options;
461 stream_muxer_options.mp4_params = packaging_params.mp4_output_params;
462 stream_muxer_options.temp_dir = packaging_params.temp_dir;
463 stream_muxer_options.output_file_name = stream_iter->output;
464 if (!stream_iter->segment_template.empty()) {
465 Status template_check =
466 ValidateSegmentTemplate(stream_iter->segment_template);
467 if (!template_check.ok()) {
468 return template_check;
470 stream_muxer_options.segment_template = stream_iter->segment_template;
472 stream_muxer_options.bandwidth = stream_iter->bandwidth;
474 if (stream_iter->stream_selector ==
"text" &&
475 output_format != CONTAINER_MOV) {
476 MediaInfo text_media_info;
477 if (!StreamInfoToTextMediaInfo(*stream_iter, &text_media_info)) {
478 return Status(error::INVALID_ARGUMENT,
479 "Could not create media info for stream.");
484 if (!mpd_notifier->NotifyNewContainer(text_media_info, &unused)) {
485 LOG(ERROR) <<
"Failed to process text file " << stream_iter->input;
487 mpd_notifier->Flush();
489 }
else if (packaging_params.output_media_info) {
491 text_media_info, stream_iter->output + kMediaInfoSuffix);
496 if (stream_iter->input != previous_input) {
498 demuxer = std::make_shared<Demuxer>(stream_iter->input);
500 demuxer->set_dump_stream_info(
501 packaging_params.test_params.dump_stream_info);
502 if (packaging_params.decryption_params.key_provider !=
503 KeyProvider::kNone) {
504 std::unique_ptr<KeySource> decryption_key_source(
505 CreateDecryptionKeySource(packaging_params.decryption_params));
506 if (!decryption_key_source) {
508 error::INVALID_ARGUMENT,
509 "Must define decryption key source when defining key provider");
511 demuxer->SetKeySource(std::move(decryption_key_source));
513 jobs->emplace_back(
new media::Job(
"RemuxJob", demuxer));
514 trick_play_handler.reset();
515 previous_input = stream_iter->input;
517 if (stream_iter->output.empty() && stream_iter->segment_template.empty())
520 DCHECK(!jobs->empty());
526 if (stream_iter->stream_selector != previous_stream_selector) {
527 previous_stream_selector = stream_iter->stream_selector;
528 trick_play_handler.reset();
532 std::unique_ptr<MuxerListener> muxer_listener = CreateMuxerListener(
533 *stream_iter, stream_number, packaging_params.output_media_info,
534 mpd_notifier, hls_notifier);
535 std::shared_ptr<Muxer> muxer = CreateMuxer(
536 packaging_params, *stream_iter,
537 packaging_params.test_params.inject_fake_clock ? fake_clock :
nullptr,
538 std::move(muxer_listener));
541 return Status(error::INVALID_ARGUMENT,
"Failed to create muxer for " +
542 stream_iter->input +
":" +
543 stream_iter->stream_selector);
549 if (stream_iter->trick_play_factor > 0) {
550 if (!trick_play_handler) {
551 trick_play_handler.reset(
new TrickPlayHandler());
553 trick_play_handler->SetHandlerForTrickPlay(stream_iter->trick_play_factor,
555 if (trick_play_handler->IsConnected())
557 }
else if (trick_play_handler) {
558 trick_play_handler->SetHandlerForMainStream(std::move(muxer));
559 DCHECK(trick_play_handler->IsConnected());
563 std::vector<std::shared_ptr<MediaHandler>> handlers;
565 auto chunking_handler =
566 std::make_shared<ChunkingHandler>(packaging_params.chunking_params);
567 handlers.push_back(chunking_handler);
569 std::shared_ptr<MediaHandler> crypto_handler = CreateCryptoHandler(
570 packaging_params, *stream_iter, encryption_key_source);
571 if (crypto_handler) {
572 handlers.push_back(crypto_handler);
577 if (trick_play_handler) {
578 handlers.push_back(trick_play_handler);
580 handlers.push_back(std::move(muxer));
585 demuxer->SetHandler(stream_iter->stream_selector, chunking_handler));
586 status.Update(ConnectHandlers(handlers));
591 if (!stream_iter->language.empty())
592 demuxer->SetLanguageOverride(stream_iter->stream_selector,
593 stream_iter->language);
598 for (
const std::unique_ptr<Job>& job : *jobs) {
600 status.Update(job->status());
605 Status RunJobs(
const std::vector<std::unique_ptr<Job>>& jobs) {
611 std::vector<Job*> active_jobs;
612 std::vector<base::WaitableEvent*> active_waits;
616 for (
auto& job : jobs) {
619 active_jobs.push_back(job.get());
620 active_waits.push_back(job->wait());
625 while (status.ok() && active_jobs.size()) {
629 base::WaitableEvent::WaitMany(active_waits.data(), active_waits.size());
630 Job* job = active_jobs[done];
633 status.Update(job->status());
636 active_jobs.erase(active_jobs.begin() + done);
637 active_waits.erase(active_waits.begin() + done);
642 for (
auto& job : active_jobs) {
646 for (
auto& job : active_jobs) {
656 struct Packager::PackagerInternal {
657 media::FakeClock fake_clock;
658 std::unique_ptr<KeySource> encryption_key_source;
659 std::unique_ptr<MpdNotifier> mpd_notifier;
660 std::unique_ptr<hls::HlsNotifier> hls_notifier;
661 std::vector<std::unique_ptr<media::Job>> jobs;
662 BufferCallbackParams buffer_callback_params;
665 Packager::Packager() {}
667 Packager::~Packager() {}
671 const std::vector<StreamDescriptor>& stream_descriptors) {
673 static base::AtExitManager exit;
677 return Status(error::INVALID_ARGUMENT,
"Already initialized.");
680 media::ValidateParams(packaging_params, stream_descriptors);
681 if (!param_check.ok()) {
686 SetPackagerVersionForTesting(
690 std::unique_ptr<PackagerInternal>
internal(
new PackagerInternal);
694 internal->encryption_key_source = CreateEncryptionKeySource(
695 static_cast<media::FourCC>(
698 if (!internal->encryption_key_source)
699 return Status(error::INVALID_ARGUMENT,
"Failed to create key source.");
708 if (internal->buffer_callback_params.write_func) {
710 internal->buffer_callback_params, mpd_params.
mpd_output);
716 const bool on_demand_dash_profile =
717 stream_descriptors.begin()->segment_template.empty();
719 media::GetMpdOptions(on_demand_dash_profile, mpd_params);
725 if (!internal->mpd_notifier->Init()) {
726 LOG(ERROR) <<
"MpdNotifier failed to initialize.";
727 return Status(error::INVALID_ARGUMENT,
728 "Failed to initialize MpdNotifier.");
733 base::FilePath master_playlist_path(
735 base::FilePath master_playlist_name = master_playlist_path.BaseName();
740 master_playlist_path.DirName().AsEndingWithSeparator().AsUTF8Unsafe(),
741 master_playlist_name.AsUTF8Unsafe()));
744 media::StreamDescriptorList stream_descriptor_list;
746 if (internal->buffer_callback_params.read_func ||
747 internal->buffer_callback_params.write_func) {
749 if (internal->buffer_callback_params.read_func) {
751 internal->buffer_callback_params, descriptor.input);
753 if (internal->buffer_callback_params.write_func) {
755 internal->buffer_callback_params, descriptor.output);
757 internal->buffer_callback_params, descriptor.segment_template);
759 stream_descriptor_list.insert(descriptor_copy);
761 stream_descriptor_list.insert(descriptor);
765 Status status = media::CreateRemuxJobs(
766 stream_descriptor_list, packaging_params, &internal->fake_clock,
767 internal->encryption_key_source.get(),
internal->mpd_notifier.get(),
768 internal->hls_notifier.get(), &
internal->jobs);
771 internal_ = std::move(
internal);
779 return Status(error::INVALID_ARGUMENT,
"Not yet initialized.");
780 Status status = media::RunJobs(internal_->jobs);
784 if (internal_->hls_notifier) {
785 if (!internal_->hls_notifier->Flush())
786 return Status(error::INVALID_ARGUMENT,
"Failed to flush Hls.");
788 if (internal_->mpd_notifier) {
789 if (!internal_->mpd_notifier->Flush())
790 return Status(error::INVALID_ARGUMENT,
"Failed to flush Mpd.");
797 LOG(INFO) <<
"Not yet initialized. Return directly.";
800 for (
const std::unique_ptr<media::Job>& job : internal_->jobs)
805 return GetPackagerVersion();
813 if (stream_attributes.stream_type ==
814 EncryptionParams::EncryptedStreamAttributes::kAudio)
816 if (stream_attributes.stream_type ==
817 EncryptionParams::EncryptedStreamAttributes::kVideo) {
818 const int pixels = stream_attributes.oneof.video.width *
819 stream_attributes.oneof.video.height;
820 if (pixels <= max_sd_pixels)
822 if (pixels <= max_hd_pixels)
824 if (pixels <= max_uhd1_pixels)
BufferCallbackParams buffer_callback_params
Buffer callback params.
std::string master_playlist_output
HLS master playlist output path.
DASH MPD related parameters.
Defines a single input/output stream.
std::string input
Input/source media file path or network stream URL. Required.
HlsParams hls_params
HLS related parameters.
Status Initialize(const PackagingParams &packaging_params, const std::vector< StreamDescriptor > &stream_descriptors)
static std::string DefaultStreamLabelFunction(int max_sd_pixels, int max_hd_pixels, int max_uhd1_pixels, const EncryptionParams::EncryptedStreamAttributes &stream_attributes)
std::string segment_template
Specifies segment template. Can be empty.
static bool Copy(const char *from_file_name, const char *to_file_name)
static bool ReadFileToString(const char *file_name, std::string *contents)
bool generate_dash_if_iop_compliant_mpd
Try to generate DASH-IF IOP compliant MPD.
static std::string GetLibraryVersion()
std::string injected_library_version
MpdParams mpd_params
DASH MPD related parameters.
double time_shift_buffer_depth
EncryptionParams encryption_params
Encryption and Decryption Parameters.
std::string mpd_output
MPD output file path.
static std::string MakeCallbackFileName(const BufferCallbackParams &callback_params, const std::string &name)
Encrypted stream information that is used to determine stream label.
void Cancel()
Cancel packaging. Note that it has to be called from another thread.
HlsPlaylistType playlist_type
HLS playlist type. See HLS specification for details.