Shaka Packager SDK
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 
47 namespace shaka {
48 
49 // TODO(kqyang): Clean up namespaces.
50 using media::Demuxer;
51 using media::JobManager;
52 using media::KeySource;
53 using media::MuxerOptions;
54 using media::SingleThreadJobManager;
55 using media::SyncPointQueue;
56 
57 namespace media {
58 namespace {
59 
60 const char kMediaInfoSuffix[] = ".media_info";
61 
62 MuxerListenerFactory::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.
87 bool 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 
115 MediaContainerName 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 
160 MediaContainerName 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 
179 bool 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 
192 Status 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 
269 Status 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 
381 bool 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.
402 class 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 
409 bool 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 
455 Status 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 
477 std::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 
521 std::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 
529 Status 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 
592 Status 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 
731 Status 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 
807 struct 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 
816 Packager::Packager() {}
817 
818 Packager::~Packager() {}
819 
820 Status 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 
960 Status 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 
977 void Packager::Cancel() {
978  if (!internal_) {
979  LOG(INFO) << "Not yet initialized. Return directly.";
980  return;
981  }
982  internal_->job_manager->CancelJobs();
983 }
984 
985 std::string Packager::GetLibraryVersion() {
986  return GetPackagerVersion();
987 }
988 
989 std::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.
Definition: crypto_flags.cc:66
std::string LanguageToISO_639_2(const std::string &language)
std::string LanguageToShortestForm(const std::string &language)