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  !packaging_params.mpd_params.use_segment_list) {
372  return Status(error::UNIMPLEMENTED,
373  "--generate_sidx_in_media_segments is required for DASH "
374  "on-demand profile (not using segment_template or segment list).");
375  }
376 
377  return Status::OK;
378 }
379 
380 bool StreamDescriptorCompareFn(const StreamDescriptor& a,
381  const StreamDescriptor& b) {
382  // This function is used by std::sort() to sort the stream descriptors.
383  // Note that std::sort() need a comparator that return true iff the first
384  // argument is strictly lower than the second one. That is: must return false
385  // when they are equal. The requirement is enforced in gcc/g++ but not in
386  // clang.
387  if (a.input == b.input) {
388  if (a.stream_selector == b.stream_selector) {
389  // The MPD notifier requires that the main track comes first, so make
390  // sure that happens.
391  return a.trick_play_factor < b.trick_play_factor;
392  }
393  return a.stream_selector < b.stream_selector;
394  }
395 
396  return a.input < b.input;
397 }
398 
399 // A fake clock that always return time 0 (epoch). Should only be used for
400 // testing.
401 class FakeClock : public base::Clock {
402  public:
403  base::Time Now() override { return base::Time(); }
404 };
405 
406 bool StreamInfoToTextMediaInfo(const StreamDescriptor& stream_descriptor,
407  MediaInfo* text_media_info) {
408  std::string codec;
409  if (!DetermineTextFileCodec(stream_descriptor.input, &codec)) {
410  LOG(ERROR) << "Failed to determine the text file format for "
411  << stream_descriptor.input;
412  return false;
413  }
414 
415  MediaInfo::TextInfo* text_info = text_media_info->mutable_text_info();
416  text_info->set_codec(codec);
417 
418  const std::string& language = stream_descriptor.language;
419  if (!language.empty()) {
420  text_info->set_language(language);
421  }
422 
423  text_media_info->set_media_file_name(stream_descriptor.output);
424  text_media_info->set_container_type(MediaInfo::CONTAINER_TEXT);
425 
426  if (stream_descriptor.bandwidth != 0) {
427  text_media_info->set_bandwidth(stream_descriptor.bandwidth);
428  } else {
429  // Text files are usually small and since the input is one file; there's no
430  // way for the player to do ranged requests. So set this value to something
431  // reasonable.
432  const int kDefaultTextBandwidth = 256;
433  text_media_info->set_bandwidth(kDefaultTextBandwidth);
434  }
435 
436  if (!stream_descriptor.dash_roles.empty()) {
437  for (const auto& dash_role : stream_descriptor.dash_roles) {
438  text_media_info->add_dash_roles(dash_role);
439  }
440  }
441 
442  return true;
443 }
444 
448 Status CreateDemuxer(const StreamDescriptor& stream,
449  const PackagingParams& packaging_params,
450  std::shared_ptr<Demuxer>* new_demuxer) {
451  std::shared_ptr<Demuxer> demuxer = std::make_shared<Demuxer>(stream.input);
452  demuxer->set_dump_stream_info(packaging_params.test_params.dump_stream_info);
453 
454  if (packaging_params.decryption_params.key_provider != KeyProvider::kNone) {
455  std::unique_ptr<KeySource> decryption_key_source(
456  CreateDecryptionKeySource(packaging_params.decryption_params));
457  if (!decryption_key_source) {
458  return Status(
459  error::INVALID_ARGUMENT,
460  "Must define decryption key source when defining key provider");
461  }
462  demuxer->SetKeySource(std::move(decryption_key_source));
463  }
464 
465  *new_demuxer = std::move(demuxer);
466  return Status::OK;
467 }
468 
469 std::shared_ptr<MediaHandler> CreateEncryptionHandler(
470  const PackagingParams& packaging_params,
471  const StreamDescriptor& stream,
472  KeySource* key_source) {
473  if (stream.skip_encryption) {
474  return nullptr;
475  }
476 
477  if (!key_source) {
478  return nullptr;
479  }
480 
481  // Make a copy so that we can modify it for this specific stream.
482  EncryptionParams encryption_params = packaging_params.encryption_params;
483 
484  // Use Sample AES in MPEG2TS.
485  // TODO(kqyang): Consider adding a new flag to enable Sample AES as we
486  // will support CENC in TS in the future.
487  if (GetOutputFormat(stream) == CONTAINER_MPEG2TS ||
488  GetOutputFormat(stream) == CONTAINER_AAC ||
489  GetOutputFormat(stream) == CONTAINER_AC3 ||
490  GetOutputFormat(stream) == CONTAINER_EAC3) {
491  VLOG(1) << "Use Apple Sample AES encryption for MPEG2TS or Packed Audio.";
492  encryption_params.protection_scheme = kAppleSampleAesProtectionScheme;
493  }
494 
495  if (!stream.drm_label.empty()) {
496  const std::string& drm_label = stream.drm_label;
497  encryption_params.stream_label_func =
498  [drm_label](const EncryptionParams::EncryptedStreamAttributes&) {
499  return drm_label;
500  };
501  } else if (!encryption_params.stream_label_func) {
502  const int kDefaultMaxSdPixels = 768 * 576;
503  const int kDefaultMaxHdPixels = 1920 * 1080;
504  const int kDefaultMaxUhd1Pixels = 4096 * 2160;
505  encryption_params.stream_label_func = std::bind(
506  &Packager::DefaultStreamLabelFunction, kDefaultMaxSdPixels,
507  kDefaultMaxHdPixels, kDefaultMaxUhd1Pixels, std::placeholders::_1);
508  }
509 
510  return std::make_shared<EncryptionHandler>(encryption_params, key_source);
511 }
512 
513 std::unique_ptr<MediaHandler> CreateTextChunker(
514  const ChunkingParams& chunking_params) {
515  const float segment_length_in_seconds =
516  chunking_params.segment_duration_in_seconds;
517  return std::unique_ptr<MediaHandler>(
518  new TextChunker(segment_length_in_seconds));
519 }
520 
521 Status CreateTtmlJobs(
522  const std::vector<std::reference_wrapper<const StreamDescriptor>>& streams,
523  const PackagingParams& packaging_params,
524  SyncPointQueue* sync_points,
525  MuxerFactory* muxer_factory,
526  MpdNotifier* mpd_notifier,
527  JobManager* job_manager) {
528  DCHECK(job_manager);
529  for (const StreamDescriptor& stream : streams) {
530  // Check input to ensure that output is possible.
531  if (!packaging_params.hls_params.master_playlist_output.empty() &&
532  !stream.dash_only) {
533  return Status(error::INVALID_ARGUMENT,
534  "HLS does not support TTML in xml format.");
535  }
536 
537  if (!stream.segment_template.empty()) {
538  return Status(error::INVALID_ARGUMENT,
539  "Segmented TTML is not supported.");
540  }
541 
542  if (GetOutputFormat(stream) != CONTAINER_TTML) {
543  return Status(error::INVALID_ARGUMENT,
544  "Converting TTML to other formats is not supported");
545  }
546 
547  if (!stream.output.empty()) {
548  if (!File::Copy(stream.input.c_str(), stream.output.c_str())) {
549  std::string error;
550  base::StringAppendF(
551  &error, "Failed to copy the input file (%s) to output file (%s).",
552  stream.input.c_str(), stream.output.c_str());
553  return Status(error::FILE_FAILURE, error);
554  }
555 
556  MediaInfo text_media_info;
557  if (!StreamInfoToTextMediaInfo(stream, &text_media_info)) {
558  return Status(error::INVALID_ARGUMENT,
559  "Could not create media info for stream.");
560  }
561 
562  // If we are outputting to MPD, just add the input to the outputted
563  // manifest.
564  if (mpd_notifier) {
565  uint32_t unused;
566  if (mpd_notifier->NotifyNewContainer(text_media_info, &unused)) {
567  mpd_notifier->Flush();
568  } else {
569  return Status(error::PARSER_FAILURE,
570  "Failed to process text file " + stream.input);
571  }
572  }
573 
574  if (packaging_params.output_media_info) {
576  text_media_info, stream.output + kMediaInfoSuffix);
577  }
578  }
579  }
580 
581  return Status::OK;
582 }
583 
584 Status CreateAudioVideoJobs(
585  const std::vector<std::reference_wrapper<const StreamDescriptor>>& streams,
586  const PackagingParams& packaging_params,
587  KeySource* encryption_key_source,
588  SyncPointQueue* sync_points,
589  MuxerListenerFactory* muxer_listener_factory,
590  MuxerFactory* muxer_factory,
591  JobManager* job_manager) {
592  DCHECK(muxer_listener_factory);
593  DCHECK(muxer_factory);
594  DCHECK(job_manager);
595  // Store all the demuxers in a map so that we can look up a stream's demuxer.
596  // This is step one in making this part of the pipeline less dependant on
597  // order.
598  std::map<std::string, std::shared_ptr<Demuxer>> sources;
599  std::map<std::string, std::shared_ptr<MediaHandler>> cue_aligners;
600 
601  for (const StreamDescriptor& stream : streams) {
602  bool seen_input_before = sources.find(stream.input) != sources.end();
603  if (seen_input_before) {
604  continue;
605  }
606 
607  RETURN_IF_ERROR(
608  CreateDemuxer(stream, packaging_params, &sources[stream.input]));
609  cue_aligners[stream.input] =
610  sync_points ? std::make_shared<CueAlignmentHandler>(sync_points)
611  : nullptr;
612  }
613 
614  for (auto& source : sources) {
615  job_manager->Add("RemuxJob", source.second);
616  }
617 
618  // Replicators are shared among all streams with the same input and stream
619  // selector.
620  std::shared_ptr<MediaHandler> replicator;
621 
622  std::string previous_input;
623  std::string previous_selector;
624 
625  for (const StreamDescriptor& stream : streams) {
626  // Get the demuxer for this stream.
627  auto& demuxer = sources[stream.input];
628  auto& cue_aligner = cue_aligners[stream.input];
629 
630  const bool new_input_file = stream.input != previous_input;
631  const bool new_stream =
632  new_input_file || previous_selector != stream.stream_selector;
633  const bool is_text = IsTextStream(stream);
634  previous_input = stream.input;
635  previous_selector = stream.stream_selector;
636 
637  // If the stream has no output, then there is no reason setting-up the rest
638  // of the pipeline.
639  if (stream.output.empty() && stream.segment_template.empty()) {
640  continue;
641  }
642 
643  // Just because it is a different stream descriptor does not mean it is a
644  // new stream. Multiple stream descriptors may have the same stream but
645  // only differ by trick play factor.
646  if (new_stream) {
647  if (!stream.language.empty()) {
648  demuxer->SetLanguageOverride(stream.stream_selector, stream.language);
649  }
650 
651  std::vector<std::shared_ptr<MediaHandler>> handlers;
652  if (is_text) {
653  handlers.emplace_back(
654  std::make_shared<TextPadder>(kDefaultTextZeroBiasMs));
655  }
656  if (sync_points) {
657  handlers.emplace_back(cue_aligner);
658  }
659  if (!is_text) {
660  handlers.emplace_back(std::make_shared<ChunkingHandler>(
661  packaging_params.chunking_params));
662  handlers.emplace_back(CreateEncryptionHandler(packaging_params, stream,
663  encryption_key_source));
664  }
665 
666  replicator = std::make_shared<Replicator>();
667  handlers.emplace_back(replicator);
668 
669  RETURN_IF_ERROR(MediaHandler::Chain(handlers));
670  RETURN_IF_ERROR(demuxer->SetHandler(stream.stream_selector, handlers[0]));
671  }
672 
673  // Create the muxer (output) for this track.
674  const auto output_format = GetOutputFormat(stream);
675  std::shared_ptr<Muxer> muxer =
676  muxer_factory->CreateMuxer(output_format, stream);
677  if (!muxer) {
678  return Status(error::INVALID_ARGUMENT, "Failed to create muxer for " +
679  stream.input + ":" +
680  stream.stream_selector);
681  }
682 
683  std::unique_ptr<MuxerListener> muxer_listener =
684  muxer_listener_factory->CreateListener(ToMuxerListenerData(stream));
685  muxer->SetMuxerListener(std::move(muxer_listener));
686 
687  std::vector<std::shared_ptr<MediaHandler>> handlers;
688  handlers.emplace_back(replicator);
689 
690  // Trick play is optional.
691  if (stream.trick_play_factor) {
692  handlers.emplace_back(
693  std::make_shared<TrickPlayHandler>(stream.trick_play_factor));
694  }
695 
696  if (stream.cc_index >= 0) {
697  handlers.emplace_back(
698  std::make_shared<CcStreamFilter>(stream.language, stream.cc_index));
699  }
700 
701  if (is_text &&
702  (!stream.segment_template.empty() || output_format == CONTAINER_MOV)) {
703  handlers.emplace_back(
704  CreateTextChunker(packaging_params.chunking_params));
705  }
706 
707  if (is_text && output_format == CONTAINER_MOV) {
708  const auto output_codec = GetTextOutputCodec(stream);
709  if (output_codec == CONTAINER_WEBVTT) {
710  handlers.emplace_back(std::make_shared<WebVttToMp4Handler>());
711  } else if (output_codec == CONTAINER_TTML) {
712  handlers.emplace_back(std::make_shared<ttml::TtmlToMp4Handler>());
713  }
714  }
715 
716  handlers.emplace_back(muxer);
717  RETURN_IF_ERROR(MediaHandler::Chain(handlers));
718  }
719 
720  return Status::OK;
721 }
722 
723 Status CreateAllJobs(const std::vector<StreamDescriptor>& stream_descriptors,
724  const PackagingParams& packaging_params,
725  MpdNotifier* mpd_notifier,
726  KeySource* encryption_key_source,
727  SyncPointQueue* sync_points,
728  MuxerListenerFactory* muxer_listener_factory,
729  MuxerFactory* muxer_factory,
730  JobManager* job_manager) {
731  DCHECK(muxer_factory);
732  DCHECK(muxer_listener_factory);
733  DCHECK(job_manager);
734 
735  // Group all streams based on which pipeline they will use.
736  std::vector<std::reference_wrapper<const StreamDescriptor>> ttml_streams;
737  std::vector<std::reference_wrapper<const StreamDescriptor>>
738  audio_video_streams;
739 
740  bool has_transport_audio_video_streams = false;
741  bool has_non_transport_audio_video_streams = false;
742 
743  for (const StreamDescriptor& stream : stream_descriptors) {
744  const auto input_container = DetermineContainerFromFileName(stream.input);
745  const auto output_format = GetOutputFormat(stream);
746  if (input_container == CONTAINER_TTML) {
747  ttml_streams.push_back(stream);
748  } else {
749  audio_video_streams.push_back(stream);
750  switch (output_format) {
751  case CONTAINER_MPEG2TS:
752  case CONTAINER_AAC:
753  case CONTAINER_MP3:
754  case CONTAINER_AC3:
755  case CONTAINER_EAC3:
756  has_transport_audio_video_streams = true;
757  break;
758  case CONTAINER_TTML:
759  case CONTAINER_WEBVTT:
760  break;
761  default:
762  has_non_transport_audio_video_streams = true;
763  break;
764  }
765  }
766  }
767 
768  // Audio/Video streams need to be in sorted order so that demuxers and trick
769  // play handlers get setup correctly.
770  std::sort(audio_video_streams.begin(), audio_video_streams.end(),
771  media::StreamDescriptorCompareFn);
772 
773  if (packaging_params.transport_stream_timestamp_offset_ms > 0) {
774  if (has_transport_audio_video_streams &&
775  has_non_transport_audio_video_streams) {
776  LOG(WARNING) << "There may be problems mixing transport streams and "
777  "non-transport streams. For example, the subtitles may "
778  "be out of sync with non-transport streams.";
779  } else if (has_non_transport_audio_video_streams) {
780  // Don't insert the X-TIMESTAMP-MAP in WebVTT if there is no transport
781  // stream.
782  muxer_factory->SetTsStreamOffset(0);
783  }
784  }
785 
786  RETURN_IF_ERROR(CreateTtmlJobs(ttml_streams, packaging_params, sync_points,
787  muxer_factory, mpd_notifier, job_manager));
788  RETURN_IF_ERROR(CreateAudioVideoJobs(
789  audio_video_streams, packaging_params, encryption_key_source, sync_points,
790  muxer_listener_factory, muxer_factory, job_manager));
791 
792  // Initialize processing graph.
793  return job_manager->InitializeJobs();
794 }
795 
796 } // namespace
797 } // namespace media
798 
799 struct Packager::PackagerInternal {
800  media::FakeClock fake_clock;
801  std::unique_ptr<KeySource> encryption_key_source;
802  std::unique_ptr<MpdNotifier> mpd_notifier;
803  std::unique_ptr<hls::HlsNotifier> hls_notifier;
804  BufferCallbackParams buffer_callback_params;
805  std::unique_ptr<media::JobManager> job_manager;
806 };
807 
808 Packager::Packager() {}
809 
810 Packager::~Packager() {}
811 
813  const PackagingParams& packaging_params,
814  const std::vector<StreamDescriptor>& stream_descriptors) {
815  // Needed by base::WorkedPool used in ThreadedIoFile.
816  static base::AtExitManager exit;
817  static media::LibcryptoThreading libcrypto_threading;
818 
819  if (internal_)
820  return Status(error::INVALID_ARGUMENT, "Already initialized.");
821 
822  RETURN_IF_ERROR(media::ValidateParams(packaging_params, stream_descriptors));
823 
824  if (!packaging_params.test_params.injected_library_version.empty()) {
825  SetPackagerVersionForTesting(
826  packaging_params.test_params.injected_library_version);
827  }
828 
829  std::unique_ptr<PackagerInternal> internal(new PackagerInternal);
830 
831  // Create encryption key source if needed.
832  if (packaging_params.encryption_params.key_provider != KeyProvider::kNone) {
833  internal->encryption_key_source = CreateEncryptionKeySource(
834  static_cast<media::FourCC>(
835  packaging_params.encryption_params.protection_scheme),
836  packaging_params.encryption_params);
837  if (!internal->encryption_key_source)
838  return Status(error::INVALID_ARGUMENT, "Failed to create key source.");
839  }
840 
841  // Update MPD output and HLS output if needed.
842  MpdParams mpd_params = packaging_params.mpd_params;
843  HlsParams hls_params = packaging_params.hls_params;
844 
845  // |target_segment_duration| is needed for bandwidth estimation and also for
846  // DASH approximate segment timeline.
847  const double target_segment_duration =
849  mpd_params.target_segment_duration = target_segment_duration;
850  hls_params.target_segment_duration = target_segment_duration;
851 
852  // Store callback params to make it available during packaging.
853  internal->buffer_callback_params = packaging_params.buffer_callback_params;
854  if (internal->buffer_callback_params.write_func) {
856  internal->buffer_callback_params, mpd_params.mpd_output);
858  internal->buffer_callback_params, hls_params.master_playlist_output);
859  }
860 
861  // Both DASH and HLS require language to follow RFC5646
862  // (https://tools.ietf.org/html/rfc5646), which requires the language to be
863  // in the shortest form.
864  mpd_params.default_language =
866  mpd_params.default_text_language =
868  hls_params.default_language =
870  hls_params.default_text_language =
872  hls_params.is_independent_segments =
873  packaging_params.chunking_params.segment_sap_aligned;
874 
875  if (!mpd_params.mpd_output.empty()) {
876  const bool on_demand_dash_profile =
877  stream_descriptors.begin()->segment_template.empty();
878  const MpdOptions mpd_options =
879  media::GetMpdOptions(on_demand_dash_profile, mpd_params);
880  internal->mpd_notifier.reset(new SimpleMpdNotifier(mpd_options));
881  if (!internal->mpd_notifier->Init()) {
882  LOG(ERROR) << "MpdNotifier failed to initialize.";
883  return Status(error::INVALID_ARGUMENT,
884  "Failed to initialize MpdNotifier.");
885  }
886  }
887 
888  if (!hls_params.master_playlist_output.empty()) {
889  internal->hls_notifier.reset(new hls::SimpleHlsNotifier(hls_params));
890  }
891 
892  std::unique_ptr<SyncPointQueue> sync_points;
893  if (!packaging_params.ad_cue_generator_params.cue_points.empty()) {
894  sync_points.reset(
895  new SyncPointQueue(packaging_params.ad_cue_generator_params));
896  }
897  if (packaging_params.single_threaded) {
898  internal->job_manager.reset(
899  new SingleThreadJobManager(std::move(sync_points)));
900  } else {
901  internal->job_manager.reset(new JobManager(std::move(sync_points)));
902  }
903 
904  std::vector<StreamDescriptor> streams_for_jobs;
905 
906  for (const StreamDescriptor& descriptor : stream_descriptors) {
907  // We may need to overwrite some values, so make a copy first.
908  StreamDescriptor copy = descriptor;
909 
910  if (internal->buffer_callback_params.read_func) {
911  copy.input = File::MakeCallbackFileName(internal->buffer_callback_params,
912  descriptor.input);
913  }
914 
915  if (internal->buffer_callback_params.write_func) {
916  copy.output = File::MakeCallbackFileName(internal->buffer_callback_params,
917  descriptor.output);
919  internal->buffer_callback_params, descriptor.segment_template);
920  }
921 
922  // Update language to ISO_639_2 code if set.
923  if (!copy.language.empty()) {
924  copy.language = LanguageToISO_639_2(descriptor.language);
925  if (copy.language == "und") {
926  return Status(
927  error::INVALID_ARGUMENT,
928  "Unknown/invalid language specified: " + descriptor.language);
929  }
930  }
931 
932  streams_for_jobs.push_back(copy);
933  }
934 
935  media::MuxerFactory muxer_factory(packaging_params);
936  if (packaging_params.test_params.inject_fake_clock) {
937  muxer_factory.OverrideClock(&internal->fake_clock);
938  }
939 
940  media::MuxerListenerFactory muxer_listener_factory(
941  packaging_params.output_media_info,
942  packaging_params.mpd_params.use_segment_list,
943  internal->mpd_notifier.get(), internal->hls_notifier.get());
944 
945  RETURN_IF_ERROR(media::CreateAllJobs(
946  streams_for_jobs, packaging_params, internal->mpd_notifier.get(),
947  internal->encryption_key_source.get(),
948  internal->job_manager->sync_points(), &muxer_listener_factory,
949  &muxer_factory, internal->job_manager.get()));
950 
951  internal_ = std::move(internal);
952  return Status::OK;
953 }
954 
956  if (!internal_)
957  return Status(error::INVALID_ARGUMENT, "Not yet initialized.");
958 
959  RETURN_IF_ERROR(internal_->job_manager->RunJobs());
960 
961  if (internal_->hls_notifier) {
962  if (!internal_->hls_notifier->Flush())
963  return Status(error::INVALID_ARGUMENT, "Failed to flush Hls.");
964  }
965  if (internal_->mpd_notifier) {
966  if (!internal_->mpd_notifier->Flush())
967  return Status(error::INVALID_ARGUMENT, "Failed to flush Mpd.");
968  }
969  return Status::OK;
970 }
971 
973  if (!internal_) {
974  LOG(INFO) << "Not yet initialized. Return directly.";
975  return;
976  }
977  internal_->job_manager->CancelJobs();
978 }
979 
981  return GetPackagerVersion();
982 }
983 
985  int max_sd_pixels,
986  int max_hd_pixels,
987  int max_uhd1_pixels,
988  const EncryptionParams::EncryptedStreamAttributes& stream_attributes) {
989  if (stream_attributes.stream_type ==
990  EncryptionParams::EncryptedStreamAttributes::kAudio)
991  return "AUDIO";
992  if (stream_attributes.stream_type ==
993  EncryptionParams::EncryptedStreamAttributes::kVideo) {
994  const int pixels = stream_attributes.oneof.video.width *
995  stream_attributes.oneof.video.height;
996  if (pixels <= max_sd_pixels)
997  return "SD";
998  if (pixels <= max_hd_pixels)
999  return "HD";
1000  if (pixels <= max_uhd1_pixels)
1001  return "UHD1";
1002  return "UHD2";
1003  }
1004  return "";
1005 }
1006 
1007 } // namespace shaka
static std::string MakeCallbackFileName(const BufferCallbackParams &callback_params, const std::string &name)
Definition: file.cc:402
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:300
Status Run()
Definition: packager.cc:955
void Cancel()
Cancel packaging. Note that it has to be called from another thread.
Definition: packager.cc:972
static std::string DefaultStreamLabelFunction(int max_sd_pixels, int max_hd_pixels, int max_uhd1_pixels, const EncryptionParams::EncryptedStreamAttributes &stream_attributes)
Definition: packager.cc:984
static std::string GetLibraryVersion()
Definition: packager.cc:980
Status Initialize(const PackagingParams &packaging_params, const std::vector< StreamDescriptor > &stream_descriptors)
Definition: packager.cc:812
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
bool use_segment_list
Definition: mpd_params.h:93
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