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