Shaka Packager SDK
Loading...
Searching...
No Matches
packager.cc
1// Copyright 2017 Google LLC. All rights reserved.
2//
3// Use of this source code is governed by a BSD-style
4// license that can be found in the LICENSE file or at
5// https://developers.google.com/open-source/licenses/bsd
6
7#include <packager/packager.h>
8
9#include <algorithm>
10#include <chrono>
11#include <optional>
12
13#include <absl/log/check.h>
14#include <absl/log/log.h>
15#include <absl/strings/match.h>
16#include <absl/strings/str_format.h>
17
18#include <packager/app/job_manager.h>
19#include <packager/app/muxer_factory.h>
20#include <packager/app/packager_util.h>
21#include <packager/app/single_thread_job_manager.h>
22#include <packager/file.h>
23#include <packager/hls/base/hls_notifier.h>
24#include <packager/hls/base/simple_hls_notifier.h>
25#include <packager/macros/logging.h>
26#include <packager/macros/status.h>
27#include <packager/media/base/cc_stream_filter.h>
28#include <packager/media/base/language_utils.h>
29#include <packager/media/base/muxer.h>
30#include <packager/media/base/muxer_util.h>
31#include <packager/media/chunking/chunking_handler.h>
32#include <packager/media/chunking/cue_alignment_handler.h>
33#include <packager/media/chunking/text_chunker.h>
34#include <packager/media/crypto/encryption_handler.h>
35#include <packager/media/demuxer/demuxer.h>
36#include <packager/media/event/muxer_listener_factory.h>
37#include <packager/media/event/vod_media_info_dump_muxer_listener.h>
38#include <packager/media/formats/ttml/ttml_to_mp4_handler.h>
39#include <packager/media/formats/webvtt/text_padder.h>
40#include <packager/media/formats/webvtt/webvtt_to_mp4_handler.h>
41#include <packager/media/replicator/replicator.h>
42#include <packager/media/trick_play/trick_play_handler.h>
43#include <packager/mpd/base/media_info.pb.h>
44#include <packager/mpd/base/simple_mpd_notifier.h>
45#include <packager/version/version.h>
46
47namespace shaka {
48
49// TODO(kqyang): Clean up namespaces.
50using media::Demuxer;
51using media::JobManager;
52using media::KeySource;
53using media::MuxerOptions;
54using media::SingleThreadJobManager;
55using media::SyncPointQueue;
56
57namespace media {
58namespace {
59
60const char kMediaInfoSuffix[] = ".media_info";
61
62MuxerListenerFactory::StreamData ToMuxerListenerData(
63 const StreamDescriptor& stream) {
64 MuxerListenerFactory::StreamData data;
65 data.media_info_output = stream.output;
66
67 data.hls_group_id = stream.hls_group_id;
68 data.hls_name = stream.hls_name;
69 data.hls_playlist_name = stream.hls_playlist_name;
70 data.hls_iframe_playlist_name = stream.hls_iframe_playlist_name;
71 data.hls_characteristics = stream.hls_characteristics;
72 data.forced_subtitle = stream.forced_subtitle;
73 data.hls_only = stream.hls_only;
74
75 data.dash_accessiblities = stream.dash_accessiblities;
76 data.dash_roles = stream.dash_roles;
77 data.dash_only = stream.dash_only;
78 data.index = stream.index;
79 data.dash_label = stream.dash_label;
80 data.input_format = stream.input_format;
81 return data;
82};
83
84// TODO(rkuroiwa): Write TTML and WebVTT parser (demuxing) for a better check
85// and for supporting live/segmenting (muxing). With a demuxer and a muxer,
86// CreateAllJobs() shouldn't treat text as a special case.
87bool DetermineTextFileCodec(const std::string& file, std::string* out) {
88 CHECK(out);
89
90 std::string content;
91 if (!File::ReadFileToString(file.c_str(), &content)) {
92 LOG(ERROR) << "Failed to open file " << file
93 << " to determine file format.";
94 return false;
95 }
96
97 const uint8_t* content_data =
98 reinterpret_cast<const uint8_t*>(content.data());
99 MediaContainerName container_name =
100 DetermineContainer(content_data, content.size());
101
102 if (container_name == CONTAINER_WEBVTT) {
103 *out = "wvtt";
104 return true;
105 }
106
107 if (container_name == CONTAINER_TTML) {
108 *out = "ttml";
109 return true;
110 }
111
112 return false;
113}
114
115MediaContainerName GetOutputFormat(const StreamDescriptor& descriptor) {
116 if (!descriptor.output_format.empty()) {
117 MediaContainerName format =
118 DetermineContainerFromFormatName(descriptor.output_format);
119 if (format == CONTAINER_UNKNOWN) {
120 LOG(ERROR) << "Unable to determine output format from '"
121 << descriptor.output_format << "'.";
122 }
123 return format;
124 }
125
126 std::optional<MediaContainerName> format_from_output;
127 std::optional<MediaContainerName> format_from_segment;
128 if (!descriptor.output.empty()) {
129 format_from_output = DetermineContainerFromFileName(descriptor.output);
130 if (format_from_output.value() == CONTAINER_UNKNOWN) {
131 LOG(ERROR) << "Unable to determine output format from '"
132 << descriptor.output << "'.";
133 }
134 }
135 if (!descriptor.segment_template.empty()) {
136 format_from_segment =
137 DetermineContainerFromFileName(descriptor.segment_template);
138 if (format_from_segment.value() == CONTAINER_UNKNOWN) {
139 LOG(ERROR) << "Unable to determine output format from '"
140 << descriptor.segment_template << "'.";
141 }
142 }
143
144 if (format_from_output && format_from_segment) {
145 if (format_from_output.value() != format_from_segment.value()) {
146 LOG(ERROR) << "Output format determined from '" << descriptor.output
147 << "' differs from output format determined from '"
148 << descriptor.segment_template << "'.";
149 return CONTAINER_UNKNOWN;
150 }
151 }
152
153 if (format_from_output)
154 return format_from_output.value();
155 if (format_from_segment)
156 return format_from_segment.value();
157 return CONTAINER_UNKNOWN;
158}
159
160MediaContainerName GetTextOutputCodec(const StreamDescriptor& descriptor) {
161 const auto output_container = GetOutputFormat(descriptor);
162 if (output_container != CONTAINER_MOV)
163 return output_container;
164
165 const auto input_container = DetermineContainerFromFileName(descriptor.input);
166 if (absl::AsciiStrToLower(descriptor.output_format) == "vtt+mp4" ||
167 absl::AsciiStrToLower(descriptor.output_format) == "webvtt+mp4") {
168 return CONTAINER_WEBVTT;
169 } else if (absl::AsciiStrToLower(descriptor.output_format) != "ttml+mp4" &&
170 input_container == CONTAINER_WEBVTT) {
171 // With WebVTT input, default to WebVTT output.
172 return CONTAINER_WEBVTT;
173 } else {
174 // Otherwise default to TTML since it has more features.
175 return CONTAINER_TTML;
176 }
177}
178
179bool IsTextStream(const StreamDescriptor& stream) {
180 if (stream.stream_selector == "text")
181 return true;
182 if (absl::AsciiStrToLower(stream.output_format) == "vtt+mp4" ||
183 absl::AsciiStrToLower(stream.output_format) == "webvtt+mp4" ||
184 absl::AsciiStrToLower(stream.output_format) == "ttml+mp4") {
185 return true;
186 }
187
188 auto output_format = GetOutputFormat(stream);
189 return output_format == CONTAINER_WEBVTT || output_format == CONTAINER_TTML;
190}
191
192Status ValidateStreamDescriptor(bool dump_stream_info,
193 const StreamDescriptor& stream) {
194 if (stream.input.empty()) {
195 return Status(error::INVALID_ARGUMENT, "Stream input not specified.");
196 }
197
198 // The only time a stream can have no outputs, is when dump stream info is
199 // set.
200 if (dump_stream_info && stream.output.empty() &&
201 stream.segment_template.empty()) {
202 return Status::OK;
203 }
204
205 if (stream.output.empty() && stream.segment_template.empty()) {
206 return Status(error::INVALID_ARGUMENT,
207 "Streams must specify 'output' or 'segment template'.");
208 }
209
210 // Whenever there is output, a stream must be selected.
211 if (stream.stream_selector.empty()) {
212 return Status(error::INVALID_ARGUMENT,
213 "Stream stream_selector not specified.");
214 }
215
216 // If a segment template is provided, it must be valid.
217 if (stream.segment_template.length()) {
218 RETURN_IF_ERROR(ValidateSegmentTemplate(stream.segment_template));
219 }
220
221 // There are some specifics that must be checked based on which format
222 // we are writing to.
223 const MediaContainerName output_format = GetOutputFormat(stream);
224
225 if (output_format == CONTAINER_UNKNOWN) {
226 return Status(error::INVALID_ARGUMENT, "Unsupported output format.");
227 }
228
229 if (output_format == CONTAINER_WEBVTT || output_format == CONTAINER_TTML ||
230 output_format == CONTAINER_AAC || output_format == CONTAINER_MP3 ||
231 output_format == CONTAINER_AC3 || output_format == CONTAINER_EAC3 ||
232 output_format == CONTAINER_MPEG2TS) {
233 // There is no need for an init segment when outputting because there is no
234 // initialization data.
235 if (stream.segment_template.length() && stream.output.length()) {
236 return Status(
237 error::INVALID_ARGUMENT,
238 "Segmented subtitles, PackedAudio or TS output cannot have an init "
239 "segment. Do not specify stream descriptors 'output' or "
240 "'init_segment' when using 'segment_template'.");
241 }
242 } else {
243 // For any other format, if there is a segment template, there must be an
244 // init segment provided.
245 if (stream.segment_template.length() && stream.output.empty()) {
246 return Status(error::INVALID_ARGUMENT,
247 "Please specify 'init_segment'. All non-TS multi-segment "
248 "content must provide an init segment.");
249 }
250 }
251
252 if (stream.output.find('$') != std::string::npos) {
253 if (output_format == CONTAINER_WEBVTT) {
254 return Status(
255 error::UNIMPLEMENTED,
256 "WebVTT output with one file per Representation per Period "
257 "is not supported yet. Please use fMP4 instead. If that needs to be "
258 "supported, please file a feature request on GitHub.");
259 }
260 // "$" is only allowed if the output file name is a template, which is
261 // used to support one file per Representation per Period when there are
262 // Ad Cues.
263 RETURN_IF_ERROR(ValidateSegmentTemplate(stream.output));
264 }
265
266 return Status::OK;
267}
268
269Status ValidateParams(const PackagingParams& packaging_params,
270 const std::vector<StreamDescriptor>& stream_descriptors) {
271 if (!packaging_params.chunking_params.segment_sap_aligned &&
272 packaging_params.chunking_params.subsegment_sap_aligned) {
273 return Status(error::INVALID_ARGUMENT,
274 "Setting segment_sap_aligned to false but "
275 "subsegment_sap_aligned to true is not allowed.");
276 }
277
278 if (packaging_params.chunking_params.start_segment_number < 0) {
279 return Status(error::INVALID_ARGUMENT,
280 "Negative --start_segment_number is not allowed.");
281 }
282
283 if (stream_descriptors.empty()) {
284 return Status(error::INVALID_ARGUMENT,
285 "Stream descriptors cannot be empty.");
286 }
287
288 // On demand profile generates single file segment while live profile
289 // generates multiple segments specified using segment template.
290 const bool on_demand_dash_profile =
291 stream_descriptors.begin()->segment_template.empty();
292 std::set<std::string> outputs;
293 std::set<std::string> segment_templates;
294 for (const auto& descriptor : stream_descriptors) {
295 if (on_demand_dash_profile != descriptor.segment_template.empty()) {
296 return Status(error::INVALID_ARGUMENT,
297 "Inconsistent stream descriptor specification: "
298 "segment_template should be specified for none or all "
299 "stream descriptors.");
300 }
301
302 RETURN_IF_ERROR(ValidateStreamDescriptor(
303 packaging_params.test_params.dump_stream_info, descriptor));
304
305 if (absl::StartsWith(descriptor.input, "udp://")) {
306 const HlsParams& hls_params = packaging_params.hls_params;
307 if (!hls_params.master_playlist_output.empty() &&
308 hls_params.playlist_type == HlsPlaylistType::kVod) {
309 LOG(WARNING)
310 << "Seeing UDP input with HLS Playlist Type set to VOD. The "
311 "playlists will only be generated when UDP socket is closed. "
312 "If you want to do live packaging, --hls_playlist_type needs to "
313 "be set to LIVE.";
314 }
315 // Skip the check for DASH as DASH defaults to 'dynamic' MPD when segment
316 // template is provided.
317 }
318
319 if (!descriptor.output.empty()) {
320 if (outputs.find(descriptor.output) != outputs.end()) {
321 return Status(
322 error::INVALID_ARGUMENT,
323 "Seeing duplicated outputs '" + descriptor.output +
324 "' in stream descriptors. Every output must be unique.");
325 }
326 outputs.insert(descriptor.output);
327 }
328 if (!descriptor.segment_template.empty()) {
329 if (segment_templates.find(descriptor.segment_template) !=
330 segment_templates.end()) {
331 return Status(error::INVALID_ARGUMENT,
332 "Seeing duplicated segment templates '" +
333 descriptor.segment_template +
334 "' in stream descriptors. Every segment template "
335 "must be unique.");
336 }
337 segment_templates.insert(descriptor.segment_template);
338 }
339 }
340
341 if (packaging_params.output_media_info && !on_demand_dash_profile) {
342 // TODO(rkuroiwa, kqyang): Support partial media info dump for live.
343 return Status(error::UNIMPLEMENTED,
344 "--output_media_info is only supported for on-demand profile "
345 "(not using segment_template).");
346 }
347
348 if (on_demand_dash_profile &&
349 !packaging_params.mpd_params.mpd_output.empty() &&
350 !packaging_params.mp4_output_params.generate_sidx_in_media_segments &&
351 !packaging_params.mpd_params.use_segment_list) {
352 return Status(error::UNIMPLEMENTED,
353 "--generate_sidx_in_media_segments is required for DASH "
354 "on-demand profile (not using segment_template or segment list).");
355 }
356
357 if (packaging_params.chunking_params.low_latency_dash_mode &&
358 packaging_params.chunking_params.subsegment_duration_in_seconds) {
359 // Low latency streaming requires data to be shipped as chunks,
360 // the smallest unit of video. Right now, each chunk contains
361 // one frame. Therefore, in low latency mode,
362 // a user specified --fragment_duration is irrelevant.
363 // TODO(caitlinocallaghan): Add a feature for users to specify the number
364 // of desired frames per chunk.
365 return Status(error::INVALID_ARGUMENT,
366 "--fragment_duration cannot be set "
367 "if --low_latency_dash_mode is enabled.");
368 }
369
370 if (packaging_params.mpd_params.low_latency_dash_mode &&
371 packaging_params.mpd_params.utc_timings.empty()) {
372 // Low latency DASH MPD requires a UTC Timing value
373 return Status(error::INVALID_ARGUMENT,
374 "--utc_timings must be be set "
375 "if --low_latency_dash_mode is enabled.");
376 }
377
378 return Status::OK;
379}
380
381bool StreamDescriptorCompareFn(const StreamDescriptor& a,
382 const StreamDescriptor& b) {
383 // This function is used by std::sort() to sort the stream descriptors.
384 // Note that std::sort() need a comparator that return true iff the first
385 // argument is strictly lower than the second one. That is: must return false
386 // when they are equal. The requirement is enforced in gcc/g++ but not in
387 // clang.
388 if (a.input == b.input) {
389 if (a.stream_selector == b.stream_selector) {
390 // The MPD notifier requires that the main track comes first, so make
391 // sure that happens.
392 return a.trick_play_factor < b.trick_play_factor;
393 }
394 return a.stream_selector < b.stream_selector;
395 }
396
397 return a.input < b.input;
398}
399
400// A fake clock that always return time 0 (epoch). Should only be used for
401// testing.
402class FakeClock : public Clock {
403 public:
404 time_point now() noexcept override {
405 return std::chrono::system_clock::time_point(std::chrono::seconds(0));
406 }
407};
408
409bool StreamInfoToTextMediaInfo(const StreamDescriptor& stream_descriptor,
410 MediaInfo* text_media_info) {
411 std::string codec;
412 if (!DetermineTextFileCodec(stream_descriptor.input, &codec)) {
413 LOG(ERROR) << "Failed to determine the text file format for "
414 << stream_descriptor.input;
415 return false;
416 }
417
418 MediaInfo::TextInfo* text_info = text_media_info->mutable_text_info();
419 text_info->set_codec(codec);
420
421 const std::string& language = stream_descriptor.language;
422 if (!language.empty()) {
423 text_info->set_language(language);
424 }
425
426 if (stream_descriptor.index.has_value()) {
427 text_media_info->set_index(stream_descriptor.index.value());
428 }
429
430 text_media_info->set_media_file_name(stream_descriptor.output);
431 text_media_info->set_container_type(MediaInfo::CONTAINER_TEXT);
432
433 if (stream_descriptor.bandwidth != 0) {
434 text_media_info->set_bandwidth(stream_descriptor.bandwidth);
435 } else {
436 // Text files are usually small and since the input is one file; there's no
437 // way for the player to do ranged requests. So set this value to something
438 // reasonable.
439 const int kDefaultTextBandwidth = 256;
440 text_media_info->set_bandwidth(kDefaultTextBandwidth);
441 }
442
443 if (!stream_descriptor.dash_roles.empty()) {
444 for (const auto& dash_role : stream_descriptor.dash_roles) {
445 text_media_info->add_dash_roles(dash_role);
446 }
447 }
448
449 return true;
450}
451
455Status CreateDemuxer(const StreamDescriptor& stream,
456 const PackagingParams& packaging_params,
457 std::shared_ptr<Demuxer>* new_demuxer) {
458 std::shared_ptr<Demuxer> demuxer = std::make_shared<Demuxer>(stream.input);
459 demuxer->set_dump_stream_info(packaging_params.test_params.dump_stream_info);
460 demuxer->set_input_format(stream.input_format);
461
462 if (packaging_params.decryption_params.key_provider != KeyProvider::kNone) {
463 std::unique_ptr<KeySource> decryption_key_source(
464 CreateDecryptionKeySource(packaging_params.decryption_params));
465 if (!decryption_key_source) {
466 return Status(
467 error::INVALID_ARGUMENT,
468 "Must define decryption key source when defining key provider");
469 }
470 demuxer->SetKeySource(std::move(decryption_key_source));
471 }
472
473 *new_demuxer = std::move(demuxer);
474 return Status::OK;
475}
476
477std::shared_ptr<MediaHandler> CreateEncryptionHandler(
478 const PackagingParams& packaging_params,
479 const StreamDescriptor& stream,
480 KeySource* key_source) {
481 if (stream.skip_encryption) {
482 return nullptr;
483 }
484
485 if (!key_source) {
486 return nullptr;
487 }
488
489 // Make a copy so that we can modify it for this specific stream.
490 EncryptionParams encryption_params = packaging_params.encryption_params;
491
492 // Use Sample AES in MPEG2TS.
493 // TODO(kqyang): Consider adding a new flag to enable Sample AES as we
494 // will support CENC in TS in the future.
495 if (GetOutputFormat(stream) == CONTAINER_MPEG2TS ||
496 GetOutputFormat(stream) == CONTAINER_AAC ||
497 GetOutputFormat(stream) == CONTAINER_AC3 ||
498 GetOutputFormat(stream) == CONTAINER_EAC3) {
499 VLOG(1) << "Use Apple Sample AES encryption for MPEG2TS or Packed Audio.";
500 encryption_params.protection_scheme = kAppleSampleAesProtectionScheme;
501 }
502
503 if (!stream.drm_label.empty()) {
504 const std::string& drm_label = stream.drm_label;
505 encryption_params.stream_label_func =
506 [drm_label](const EncryptionParams::EncryptedStreamAttributes&) {
507 return drm_label;
508 };
509 } else if (!encryption_params.stream_label_func) {
510 const int kDefaultMaxSdPixels = 768 * 576;
511 const int kDefaultMaxHdPixels = 1920 * 1080;
512 const int kDefaultMaxUhd1Pixels = 4096 * 2160;
513 encryption_params.stream_label_func = std::bind(
514 &Packager::DefaultStreamLabelFunction, kDefaultMaxSdPixels,
515 kDefaultMaxHdPixels, kDefaultMaxUhd1Pixels, std::placeholders::_1);
516 }
517
518 return std::make_shared<EncryptionHandler>(encryption_params, key_source);
519}
520
521std::unique_ptr<MediaHandler> CreateTextChunker(
522 const ChunkingParams& chunking_params) {
523 const float segment_length_in_seconds =
524 chunking_params.segment_duration_in_seconds;
525 return std::unique_ptr<MediaHandler>(new TextChunker(
526 segment_length_in_seconds, chunking_params.start_segment_number));
527}
528
529Status CreateTtmlJobs(
530 const std::vector<std::reference_wrapper<const StreamDescriptor>>& streams,
531 const PackagingParams& packaging_params,
532 SyncPointQueue* sync_points,
533 MuxerFactory* muxer_factory,
534 MpdNotifier* mpd_notifier,
535 JobManager* job_manager) {
536 DCHECK(job_manager);
537 for (const StreamDescriptor& stream : streams) {
538 // Check input to ensure that output is possible.
539 if (!packaging_params.hls_params.master_playlist_output.empty() &&
540 !stream.dash_only) {
541 return Status(error::INVALID_ARGUMENT,
542 "HLS does not support TTML in xml format.");
543 }
544
545 if (!stream.segment_template.empty()) {
546 return Status(error::INVALID_ARGUMENT,
547 "Segmented TTML is not supported.");
548 }
549
550 if (GetOutputFormat(stream) != CONTAINER_TTML) {
551 return Status(error::INVALID_ARGUMENT,
552 "Converting TTML to other formats is not supported");
553 }
554
555 if (!stream.output.empty()) {
556 if (!File::Copy(stream.input.c_str(), stream.output.c_str())) {
557 std::string error;
558 absl::StrAppendFormat(
559 &error, "Failed to copy the input file (%s) to output file (%s).",
560 stream.input.c_str(), stream.output.c_str());
561 return Status(error::FILE_FAILURE, error);
562 }
563
564 MediaInfo text_media_info;
565 if (!StreamInfoToTextMediaInfo(stream, &text_media_info)) {
566 return Status(error::INVALID_ARGUMENT,
567 "Could not create media info for stream.");
568 }
569
570 // If we are outputting to MPD, just add the input to the outputted
571 // manifest.
572 if (mpd_notifier) {
573 uint32_t unused;
574 if (mpd_notifier->NotifyNewContainer(text_media_info, &unused)) {
575 mpd_notifier->Flush();
576 } else {
577 return Status(error::PARSER_FAILURE,
578 "Failed to process text file " + stream.input);
579 }
580 }
581
582 if (packaging_params.output_media_info) {
584 text_media_info, stream.output + kMediaInfoSuffix);
585 }
586 }
587 }
588
589 return Status::OK;
590}
591
592Status CreateAudioVideoJobs(
593 const std::vector<std::reference_wrapper<const StreamDescriptor>>& streams,
594 const PackagingParams& packaging_params,
595 KeySource* encryption_key_source,
596 SyncPointQueue* sync_points,
597 MuxerListenerFactory* muxer_listener_factory,
598 MuxerFactory* muxer_factory,
599 JobManager* job_manager) {
600 DCHECK(muxer_listener_factory);
601 DCHECK(muxer_factory);
602 DCHECK(job_manager);
603 // Store all the demuxers in a map so that we can look up a stream's demuxer.
604 // This is step one in making this part of the pipeline less dependant on
605 // order.
606 std::map<std::string, std::shared_ptr<Demuxer>> sources;
607 std::map<std::string, std::shared_ptr<MediaHandler>> cue_aligners;
608
609 for (const StreamDescriptor& stream : streams) {
610 bool seen_input_before = sources.find(stream.input) != sources.end();
611 if (seen_input_before) {
612 continue;
613 }
614
615 RETURN_IF_ERROR(
616 CreateDemuxer(stream, packaging_params, &sources[stream.input]));
617 cue_aligners[stream.input] =
618 sync_points ? std::make_shared<CueAlignmentHandler>(sync_points)
619 : nullptr;
620 }
621
622 for (auto& source : sources) {
623 job_manager->Add("RemuxJob", source.second);
624 }
625
626 // Replicators are shared among all streams with the same input and stream
627 // selector.
628 std::shared_ptr<MediaHandler> replicator;
629
630 std::string previous_input;
631 std::string previous_selector;
632
633 for (const StreamDescriptor& stream : streams) {
634 // Get the demuxer for this stream.
635 auto& demuxer = sources[stream.input];
636 auto& cue_aligner = cue_aligners[stream.input];
637
638 const bool new_input_file = stream.input != previous_input;
639 const bool new_stream =
640 new_input_file || previous_selector != stream.stream_selector;
641 const bool is_text = IsTextStream(stream);
642 previous_input = stream.input;
643 previous_selector = stream.stream_selector;
644
645 // If the stream has no output, then there is no reason setting-up the rest
646 // of the pipeline.
647 if (stream.output.empty() && stream.segment_template.empty()) {
648 continue;
649 }
650
651 // Just because it is a different stream descriptor does not mean it is a
652 // new stream. Multiple stream descriptors may have the same stream but
653 // only differ by trick play factor.
654 if (new_stream) {
655 if (!stream.language.empty()) {
656 demuxer->SetLanguageOverride(stream.stream_selector, stream.language);
657 }
658
659 std::vector<std::shared_ptr<MediaHandler>> handlers;
660 if (is_text) {
661 handlers.emplace_back(std::make_shared<TextPadder>(
662 packaging_params.default_text_zero_bias_ms));
663 }
664 if (sync_points) {
665 handlers.emplace_back(cue_aligner);
666 }
667 if (!is_text) {
668 handlers.emplace_back(std::make_shared<ChunkingHandler>(
669 packaging_params.chunking_params));
670 handlers.emplace_back(CreateEncryptionHandler(packaging_params, stream,
671 encryption_key_source));
672 }
673
674 replicator = std::make_shared<Replicator>();
675 handlers.emplace_back(replicator);
676
677 RETURN_IF_ERROR(MediaHandler::Chain(handlers));
678 RETURN_IF_ERROR(demuxer->SetHandler(stream.stream_selector, handlers[0]));
679 }
680
681 // Create the muxer (output) for this track.
682 const auto output_format = GetOutputFormat(stream);
683 std::shared_ptr<Muxer> muxer =
684 muxer_factory->CreateMuxer(output_format, stream);
685 if (!muxer) {
686 return Status(error::INVALID_ARGUMENT, "Failed to create muxer for " +
687 stream.input + ":" +
688 stream.stream_selector);
689 }
690
691 std::unique_ptr<MuxerListener> muxer_listener =
692 muxer_listener_factory->CreateListener(ToMuxerListenerData(stream));
693 muxer->SetMuxerListener(std::move(muxer_listener));
694
695 std::vector<std::shared_ptr<MediaHandler>> handlers;
696 handlers.emplace_back(replicator);
697
698 // Trick play is optional.
699 if (stream.trick_play_factor) {
700 handlers.emplace_back(
701 std::make_shared<TrickPlayHandler>(stream.trick_play_factor));
702 }
703
704 if (stream.cc_index >= 0) {
705 handlers.emplace_back(
706 std::make_shared<CcStreamFilter>(stream.language, stream.cc_index));
707 }
708
709 if (is_text &&
710 (!stream.segment_template.empty() || output_format == CONTAINER_MOV)) {
711 handlers.emplace_back(
712 CreateTextChunker(packaging_params.chunking_params));
713 }
714
715 if (is_text && output_format == CONTAINER_MOV) {
716 const auto output_codec = GetTextOutputCodec(stream);
717 if (output_codec == CONTAINER_WEBVTT) {
718 handlers.emplace_back(std::make_shared<WebVttToMp4Handler>());
719 } else if (output_codec == CONTAINER_TTML) {
720 handlers.emplace_back(std::make_shared<ttml::TtmlToMp4Handler>());
721 }
722 }
723
724 handlers.emplace_back(muxer);
725 RETURN_IF_ERROR(MediaHandler::Chain(handlers));
726 }
727
728 return Status::OK;
729}
730
731Status CreateAllJobs(const std::vector<StreamDescriptor>& stream_descriptors,
732 const PackagingParams& packaging_params,
733 MpdNotifier* mpd_notifier,
734 KeySource* encryption_key_source,
735 SyncPointQueue* sync_points,
736 MuxerListenerFactory* muxer_listener_factory,
737 MuxerFactory* muxer_factory,
738 JobManager* job_manager) {
739 DCHECK(muxer_factory);
740 DCHECK(muxer_listener_factory);
741 DCHECK(job_manager);
742
743 // Group all streams based on which pipeline they will use.
744 std::vector<std::reference_wrapper<const StreamDescriptor>> ttml_streams;
745 std::vector<std::reference_wrapper<const StreamDescriptor>>
746 audio_video_streams;
747
748 bool has_transport_audio_video_streams = false;
749 bool has_non_transport_audio_video_streams = false;
750
751 for (const StreamDescriptor& stream : stream_descriptors) {
752 const auto input_container = DetermineContainerFromFileName(stream.input);
753 const auto output_format = GetOutputFormat(stream);
754 if (input_container == CONTAINER_TTML) {
755 ttml_streams.push_back(stream);
756 } else {
757 audio_video_streams.push_back(stream);
758 switch (output_format) {
759 case CONTAINER_MPEG2TS:
760 case CONTAINER_AAC:
761 case CONTAINER_MP3:
762 case CONTAINER_AC3:
763 case CONTAINER_EAC3:
764 has_transport_audio_video_streams = true;
765 break;
766 case CONTAINER_TTML:
767 case CONTAINER_WEBVTT:
768 break;
769 default:
770 has_non_transport_audio_video_streams = true;
771 break;
772 }
773 }
774 }
775
776 // Audio/Video streams need to be in sorted order so that demuxers and trick
777 // play handlers get setup correctly.
778 std::sort(audio_video_streams.begin(), audio_video_streams.end(),
779 media::StreamDescriptorCompareFn);
780
781 if (packaging_params.transport_stream_timestamp_offset_ms > 0) {
782 if (has_transport_audio_video_streams &&
783 has_non_transport_audio_video_streams) {
784 LOG(WARNING) << "There may be problems mixing transport streams and "
785 "non-transport streams. For example, the subtitles may "
786 "be out of sync with non-transport streams.";
787 } else if (has_non_transport_audio_video_streams) {
788 // Don't insert the X-TIMESTAMP-MAP in WebVTT if there is no transport
789 // stream.
790 muxer_factory->SetTsStreamOffset(0);
791 }
792 }
793
794 RETURN_IF_ERROR(CreateTtmlJobs(ttml_streams, packaging_params, sync_points,
795 muxer_factory, mpd_notifier, job_manager));
796 RETURN_IF_ERROR(CreateAudioVideoJobs(
797 audio_video_streams, packaging_params, encryption_key_source, sync_points,
798 muxer_listener_factory, muxer_factory, job_manager));
799
800 // Initialize processing graph.
801 return job_manager->InitializeJobs();
802}
803
804} // namespace
805} // namespace media
806
807struct Packager::PackagerInternal {
808 std::shared_ptr<media::FakeClock> fake_clock;
809 std::unique_ptr<KeySource> encryption_key_source;
810 std::unique_ptr<MpdNotifier> mpd_notifier;
811 std::unique_ptr<hls::HlsNotifier> hls_notifier;
812 BufferCallbackParams buffer_callback_params;
813 std::unique_ptr<media::JobManager> job_manager;
814};
815
816Packager::Packager() {}
817
818Packager::~Packager() {}
819
820Status Packager::Initialize(
821 const PackagingParams& packaging_params,
822 const std::vector<StreamDescriptor>& stream_descriptors) {
823 if (internal_)
824 return Status(error::INVALID_ARGUMENT, "Already initialized.");
825
826 RETURN_IF_ERROR(media::ValidateParams(packaging_params, stream_descriptors));
827
828 if (!packaging_params.test_params.injected_library_version.empty()) {
829 SetPackagerVersionForTesting(
830 packaging_params.test_params.injected_library_version);
831 }
832
833 std::unique_ptr<PackagerInternal> internal(new PackagerInternal);
834
835 // Create encryption key source if needed.
836 if (packaging_params.encryption_params.key_provider != KeyProvider::kNone) {
837 internal->encryption_key_source = CreateEncryptionKeySource(
838 static_cast<media::FourCC>(
839 packaging_params.encryption_params.protection_scheme),
840 packaging_params.encryption_params);
841 if (!internal->encryption_key_source)
842 return Status(error::INVALID_ARGUMENT, "Failed to create key source.");
843 }
844
845 // Update MPD output and HLS output if needed.
846 MpdParams mpd_params = packaging_params.mpd_params;
847 HlsParams hls_params = packaging_params.hls_params;
848
849 // |target_segment_duration| is needed for bandwidth estimation and also for
850 // DASH approximate segment timeline.
851 const double target_segment_duration =
852 packaging_params.chunking_params.segment_duration_in_seconds;
853 mpd_params.target_segment_duration = target_segment_duration;
854 hls_params.target_segment_duration = target_segment_duration;
855
856 // Store callback params to make it available during packaging.
857 internal->buffer_callback_params = packaging_params.buffer_callback_params;
858 if (internal->buffer_callback_params.write_func) {
859 mpd_params.mpd_output = File::MakeCallbackFileName(
860 internal->buffer_callback_params, mpd_params.mpd_output);
861 hls_params.master_playlist_output = File::MakeCallbackFileName(
862 internal->buffer_callback_params, hls_params.master_playlist_output);
863 }
864
865 // Both DASH and HLS require language to follow RFC5646
866 // (https://tools.ietf.org/html/rfc5646), which requires the language to be
867 // in the shortest form.
868 mpd_params.default_language =
869 LanguageToShortestForm(mpd_params.default_language);
870 mpd_params.default_text_language =
871 LanguageToShortestForm(mpd_params.default_text_language);
872 hls_params.default_language =
873 LanguageToShortestForm(hls_params.default_language);
874 hls_params.default_text_language =
875 LanguageToShortestForm(hls_params.default_text_language);
876 hls_params.is_independent_segments =
877 packaging_params.chunking_params.segment_sap_aligned;
878
879 if (!mpd_params.mpd_output.empty()) {
880 const bool on_demand_dash_profile =
881 stream_descriptors.begin()->segment_template.empty();
882 const MpdOptions mpd_options =
883 media::GetMpdOptions(on_demand_dash_profile, mpd_params);
884 internal->mpd_notifier.reset(new SimpleMpdNotifier(mpd_options));
885 if (!internal->mpd_notifier->Init()) {
886 LOG(ERROR) << "MpdNotifier failed to initialize.";
887 return Status(error::INVALID_ARGUMENT,
888 "Failed to initialize MpdNotifier.");
889 }
890 }
891
892 if (!hls_params.master_playlist_output.empty()) {
893 internal->hls_notifier.reset(new hls::SimpleHlsNotifier(hls_params));
894 }
895
896 std::unique_ptr<SyncPointQueue> sync_points;
897 if (!packaging_params.ad_cue_generator_params.cue_points.empty()) {
898 sync_points.reset(
899 new SyncPointQueue(packaging_params.ad_cue_generator_params));
900 }
901 if (packaging_params.single_threaded) {
902 internal->job_manager.reset(
903 new SingleThreadJobManager(std::move(sync_points)));
904 } else {
905 internal->job_manager.reset(new JobManager(std::move(sync_points)));
906 }
907
908 std::vector<StreamDescriptor> streams_for_jobs;
909
910 for (const StreamDescriptor& descriptor : stream_descriptors) {
911 // We may need to overwrite some values, so make a copy first.
912 StreamDescriptor copy = descriptor;
913
914 if (internal->buffer_callback_params.read_func) {
915 copy.input = File::MakeCallbackFileName(internal->buffer_callback_params,
916 descriptor.input);
917 }
918
919 if (internal->buffer_callback_params.write_func) {
920 copy.output = File::MakeCallbackFileName(internal->buffer_callback_params,
921 descriptor.output);
922 copy.segment_template = File::MakeCallbackFileName(
923 internal->buffer_callback_params, descriptor.segment_template);
924 }
925
926 // Update language to ISO_639_2 code if set.
927 if (!copy.language.empty()) {
928 copy.language = LanguageToISO_639_2(descriptor.language);
929 if (copy.language == "und") {
930 return Status(
931 error::INVALID_ARGUMENT,
932 "Unknown/invalid language specified: " + descriptor.language);
933 }
934 }
935
936 streams_for_jobs.push_back(copy);
937 }
938
939 media::MuxerFactory muxer_factory(packaging_params);
940 if (packaging_params.test_params.inject_fake_clock) {
941 internal->fake_clock.reset(new media::FakeClock());
942 muxer_factory.OverrideClock(internal->fake_clock);
943 }
944
945 media::MuxerListenerFactory muxer_listener_factory(
946 packaging_params.output_media_info,
947 packaging_params.mpd_params.use_segment_list,
948 internal->mpd_notifier.get(), internal->hls_notifier.get());
949
950 RETURN_IF_ERROR(media::CreateAllJobs(
951 streams_for_jobs, packaging_params, internal->mpd_notifier.get(),
952 internal->encryption_key_source.get(),
953 internal->job_manager->sync_points(), &muxer_listener_factory,
954 &muxer_factory, internal->job_manager.get()));
955
956 internal_ = std::move(internal);
957 return Status::OK;
958}
959
960Status Packager::Run() {
961 if (!internal_)
962 return Status(error::INVALID_ARGUMENT, "Not yet initialized.");
963
964 RETURN_IF_ERROR(internal_->job_manager->RunJobs());
965
966 if (internal_->hls_notifier) {
967 if (!internal_->hls_notifier->Flush())
968 return Status(error::INVALID_ARGUMENT, "Failed to flush Hls.");
969 }
970 if (internal_->mpd_notifier) {
971 if (!internal_->mpd_notifier->Flush())
972 return Status(error::INVALID_ARGUMENT, "Failed to flush Mpd.");
973 }
974 return Status::OK;
975}
976
977void Packager::Cancel() {
978 if (!internal_) {
979 LOG(INFO) << "Not yet initialized. Return directly.";
980 return;
981 }
982 internal_->job_manager->CancelJobs();
983}
984
985std::string Packager::GetLibraryVersion() {
986 return GetPackagerVersion();
987}
988
989std::string Packager::DefaultStreamLabelFunction(
990 int max_sd_pixels,
991 int max_hd_pixels,
992 int max_uhd1_pixels,
993 const EncryptionParams::EncryptedStreamAttributes& stream_attributes) {
994 if (stream_attributes.stream_type ==
995 EncryptionParams::EncryptedStreamAttributes::kAudio)
996 return "AUDIO";
997 if (stream_attributes.stream_type ==
998 EncryptionParams::EncryptedStreamAttributes::kVideo) {
999 const int pixels = stream_attributes.oneof.video.width *
1000 stream_attributes.oneof.video.height;
1001 if (pixels <= max_sd_pixels)
1002 return "SD";
1003 if (pixels <= max_hd_pixels)
1004 return "HD";
1005 if (pixels <= max_uhd1_pixels)
1006 return "UHD1";
1007 return "UHD2";
1008 }
1009 return "";
1010}
1011
1012} // namespace shaka
static bool WriteMediaInfoToFile(const MediaInfo &media_info, const std::string &output_file_path)
All the methods that are virtual are virtual for mocking.
std::string LanguageToISO_639_2(const std::string &language)
std::string LanguageToShortestForm(const std::string &language)