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