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