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/stream_descriptor.h"
16 #include "packager/base/at_exit.h"
17 #include "packager/base/files/file_path.h"
18 #include "packager/base/logging.h"
19 #include "packager/base/path_service.h"
20 #include "packager/base/strings/string_util.h"
21 #include "packager/base/strings/stringprintf.h"
22 #include "packager/base/threading/simple_thread.h"
23 #include "packager/base/time/clock.h"
24 #include "packager/file/file.h"
25 #include "packager/hls/base/hls_notifier.h"
26 #include "packager/hls/base/simple_hls_notifier.h"
27 #include "packager/media/ad_cue_generator/ad_cue_generator.h"
28 #include "packager/media/base/container_names.h"
29 #include "packager/media/base/fourccs.h"
30 #include "packager/media/base/key_source.h"
31 #include "packager/media/base/language_utils.h"
32 #include "packager/media/base/muxer.h"
33 #include "packager/media/base/muxer_options.h"
34 #include "packager/media/base/muxer_util.h"
35 #include "packager/media/chunking/chunking_handler.h"
36 #include "packager/media/crypto/encryption_handler.h"
37 #include "packager/media/demuxer/demuxer.h"
38 #include "packager/media/event/muxer_listener_factory.h"
39 #include "packager/media/event/vod_media_info_dump_muxer_listener.h"
40 #include "packager/media/formats/webvtt/text_readers.h"
41 #include "packager/media/formats/webvtt/webvtt_output_handler.h"
42 #include "packager/media/formats/webvtt/webvtt_parser.h"
43 #include "packager/media/formats/webvtt/webvtt_segmenter.h"
44 #include "packager/media/formats/webvtt/webvtt_to_mp4_handler.h"
45 #include "packager/media/replicator/replicator.h"
46 #include "packager/media/trick_play/trick_play_handler.h"
47 #include "packager/mpd/base/media_info.pb.h"
48 #include "packager/mpd/base/mpd_builder.h"
49 #include "packager/mpd/base/simple_mpd_notifier.h"
50 #include "packager/version/version.h"
51 
52 namespace shaka {
53 
54 // TODO(kqyang): Clean up namespaces.
55 using media::Demuxer;
56 using media::KeySource;
57 using media::MuxerOptions;
58 
59 namespace media {
60 namespace {
61 
62 const char kMediaInfoSuffix[] = ".media_info";
63 
64 MuxerOptions CreateMuxerOptions(const StreamDescriptor& stream,
65  const PackagingParams& params) {
66  MuxerOptions options;
67 
68  options.mp4_params = params.mp4_output_params;
69  options.temp_dir = params.temp_dir;
70  options.bandwidth = stream.bandwidth;
71  options.output_file_name = stream.output;
72  options.segment_template = stream.segment_template;
73 
74  return options;
75 }
76 
77 MuxerListenerFactory::StreamData ToMuxerListenerData(
78  const StreamDescriptor& stream) {
79  MuxerListenerFactory::StreamData data;
80  data.media_info_output = stream.output;
81  data.hls_group_id = stream.hls_group_id;
82  data.hls_name = stream.hls_name;
83  data.hls_playlist_name = stream.hls_playlist_name;
84  data.hls_iframe_playlist_name = stream.hls_iframe_playlist_name;
85  return data;
86 };
87 
88 // TODO(rkuroiwa): Write TTML and WebVTT parser (demuxing) for a better check
89 // and for supporting live/segmenting (muxing). With a demuxer and a muxer,
90 // CreateAllJobs() shouldn't treat text as a special case.
91 std::string DetermineTextFileFormat(const std::string& file) {
92  std::string content;
93  if (!File::ReadFileToString(file.c_str(), &content)) {
94  LOG(ERROR) << "Failed to open file " << file
95  << " to determine file format.";
96  return "";
97  }
98  MediaContainerName container_name = DetermineContainer(
99  reinterpret_cast<const uint8_t*>(content.data()), content.size());
100  if (container_name == CONTAINER_WEBVTT) {
101  return "vtt";
102  } else if (container_name == CONTAINER_TTML) {
103  return "ttml";
104  }
105 
106  return "";
107 }
108 
109 MediaContainerName GetOutputFormat(const StreamDescriptor& descriptor) {
110  MediaContainerName output_format = CONTAINER_UNKNOWN;
111  if (!descriptor.output_format.empty()) {
112  output_format = DetermineContainerFromFormatName(descriptor.output_format);
113  if (output_format == CONTAINER_UNKNOWN) {
114  LOG(ERROR) << "Unable to determine output format from '"
115  << descriptor.output_format << "'.";
116  }
117  } else {
118  const std::string& output_name = descriptor.output.empty()
119  ? descriptor.segment_template
120  : descriptor.output;
121  if (output_name.empty())
122  return CONTAINER_UNKNOWN;
123  output_format = DetermineContainerFromFileName(output_name);
124  if (output_format == CONTAINER_UNKNOWN) {
125  LOG(ERROR) << "Unable to determine output format from '" << output_name
126  << "'.";
127  }
128  }
129  return output_format;
130 }
131 
132 Status ValidateStreamDescriptor(bool dump_stream_info,
133  const StreamDescriptor& stream) {
134  if (stream.input.empty()) {
135  return Status(error::INVALID_ARGUMENT, "Stream input not specified.");
136  }
137 
138  // The only time a stream can have no outputs, is when dump stream info is
139  // set.
140  if (dump_stream_info && stream.output.empty() &&
141  stream.segment_template.empty()) {
142  return Status::OK;
143  }
144 
145  if (stream.output.empty() && stream.segment_template.empty()) {
146  return Status(error::INVALID_ARGUMENT,
147  "Streams must specify 'output' or 'segment template'.");
148  }
149 
150  // Whenever there is output, a stream must be selected.
151  if (stream.stream_selector.empty()) {
152  return Status(error::INVALID_ARGUMENT,
153  "Stream stream_selector not specified.");
154  }
155 
156  // If a segment template is provided, it must be valid.
157  if (stream.segment_template.length()) {
158  Status template_check = ValidateSegmentTemplate(stream.segment_template);
159  if (!template_check.ok()) {
160  return template_check;
161  }
162  }
163 
164  // There are some specifics that must be checked based on which format
165  // we are writing to.
166  const MediaContainerName output_format = GetOutputFormat(stream);
167 
168  if (output_format == CONTAINER_UNKNOWN) {
169  return Status(error::INVALID_ARGUMENT, "Unsupported output format.");
170  } else if (output_format == MediaContainerName::CONTAINER_MPEG2TS) {
171  if (stream.segment_template.empty()) {
172  return Status(
173  error::INVALID_ARGUMENT,
174  "Please specify 'segment_template'. Single file TS output is "
175  "not supported.");
176  }
177 
178  // Right now the init segment is saved in |output| for multi-segment
179  // content. However, for TS all segments must be self-initializing so
180  // there cannot be an init segment.
181  if (stream.output.length()) {
182  return Status(error::INVALID_ARGUMENT,
183  "All TS segments must be self-initializing. Stream "
184  "descriptors 'output' or 'init_segment' are not allowed.");
185  }
186  } else if (output_format == CONTAINER_WEBVTT) {
187  // There is no need for an init segment when outputting to WebVTT because
188  // there is no initialization data.
189  if (stream.segment_template.length() && stream.output.length()) {
190  return Status(
191  error::INVALID_ARGUMENT,
192  "Segmented WebVTT output cannot have an init segment. Do not specify "
193  "stream descriptors 'output' or 'init_segment' when using "
194  "'segment_template' with WebVtt.");
195  }
196  } else {
197  // For any other format, if there is a segment template, there must be an
198  // init segment provided.
199  if (stream.segment_template.length() && stream.output.empty()) {
200  return Status(error::INVALID_ARGUMENT,
201  "Please specify 'init_segment'. All non-TS multi-segment "
202  "content must provide an init segment.");
203  }
204  }
205 
206  return Status::OK;
207 }
208 
209 Status ValidateParams(const PackagingParams& packaging_params,
210  const std::vector<StreamDescriptor>& stream_descriptors) {
211  if (!packaging_params.chunking_params.segment_sap_aligned &&
212  packaging_params.chunking_params.subsegment_sap_aligned) {
213  return Status(error::INVALID_ARGUMENT,
214  "Setting segment_sap_aligned to false but "
215  "subsegment_sap_aligned to true is not allowed.");
216  }
217 
218  if (stream_descriptors.empty()) {
219  return Status(error::INVALID_ARGUMENT,
220  "Stream descriptors cannot be empty.");
221  }
222 
223  // On demand profile generates single file segment while live profile
224  // generates multiple segments specified using segment template.
225  const bool on_demand_dash_profile =
226  stream_descriptors.begin()->segment_template.empty();
227  for (const auto& descriptor : stream_descriptors) {
228  if (on_demand_dash_profile != descriptor.segment_template.empty()) {
229  return Status(error::INVALID_ARGUMENT,
230  "Inconsistent stream descriptor specification: "
231  "segment_template should be specified for none or all "
232  "stream descriptors.");
233  }
234 
235  Status stream_check = ValidateStreamDescriptor(
236  packaging_params.test_params.dump_stream_info, descriptor);
237 
238  if (!stream_check.ok()) {
239  return stream_check;
240  }
241  }
242 
243  if (packaging_params.output_media_info && !on_demand_dash_profile) {
244  // TODO(rkuroiwa, kqyang): Support partial media info dump for live.
245  return Status(error::UNIMPLEMENTED,
246  "--output_media_info is only supported for on-demand profile "
247  "(not using segment_template).");
248  }
249 
250  return Status::OK;
251 }
252 
253 bool StreamDescriptorCompareFn(const StreamDescriptor& a,
254  const StreamDescriptor& b) {
255  if (a.input == b.input) {
256  if (a.stream_selector == b.stream_selector) {
257  // The MPD notifier requires that the main track comes first, so make
258  // sure that happens.
259  if (a.trick_play_factor == 0 || b.trick_play_factor == 0) {
260  return a.trick_play_factor == 0;
261  } else {
262  return a.trick_play_factor > b.trick_play_factor;
263  }
264  } else {
265  return a.stream_selector < b.stream_selector;
266  }
267  }
268 
269  return a.input < b.input;
270 }
271 
272 // A fake clock that always return time 0 (epoch). Should only be used for
273 // testing.
274 class FakeClock : public base::Clock {
275  public:
276  base::Time Now() override { return base::Time(); }
277 };
278 
279 bool StreamInfoToTextMediaInfo(const StreamDescriptor& stream_descriptor,
280  MediaInfo* text_media_info) {
281  const std::string& language = stream_descriptor.language;
282  const std::string format = DetermineTextFileFormat(stream_descriptor.input);
283  if (format.empty()) {
284  LOG(ERROR) << "Failed to determine the text file format for "
285  << stream_descriptor.input;
286  return false;
287  }
288 
289  text_media_info->set_media_file_name(stream_descriptor.output);
290  text_media_info->set_container_type(MediaInfo::CONTAINER_TEXT);
291 
292  if (stream_descriptor.bandwidth != 0) {
293  text_media_info->set_bandwidth(stream_descriptor.bandwidth);
294  } else {
295  // Text files are usually small and since the input is one file; there's no
296  // way for the player to do ranged requests. So set this value to something
297  // reasonable.
298  const int kDefaultTextBandwidth = 256;
299  text_media_info->set_bandwidth(kDefaultTextBandwidth);
300  }
301 
302  MediaInfo::TextInfo* text_info = text_media_info->mutable_text_info();
303  text_info->set_format(format);
304  if (!language.empty())
305  text_info->set_language(language);
306 
307  return true;
308 }
309 
313 Status CreateDemuxer(const StreamDescriptor& stream,
314  const PackagingParams& packaging_params,
315  std::shared_ptr<Demuxer>* new_demuxer) {
316  std::shared_ptr<Demuxer> demuxer = std::make_shared<Demuxer>(stream.input);
317  demuxer->set_dump_stream_info(packaging_params.test_params.dump_stream_info);
318 
319  if (packaging_params.decryption_params.key_provider != KeyProvider::kNone) {
320  std::unique_ptr<KeySource> decryption_key_source(
321  CreateDecryptionKeySource(packaging_params.decryption_params));
322  if (!decryption_key_source) {
323  return Status(
324  error::INVALID_ARGUMENT,
325  "Must define decryption key source when defining key provider");
326  }
327  demuxer->SetKeySource(std::move(decryption_key_source));
328  }
329 
330  *new_demuxer = std::move(demuxer);
331  return Status::OK;
332 }
333 
334 std::shared_ptr<MediaHandler> CreateEncryptionHandler(
335  const PackagingParams& packaging_params,
336  const StreamDescriptor& stream,
337  KeySource* key_source) {
338  if (stream.skip_encryption) {
339  return nullptr;
340  }
341 
342  if (!key_source) {
343  return nullptr;
344  }
345 
346  // Make a copy so that we can modify it for this specific stream.
347  EncryptionParams encryption_params = packaging_params.encryption_params;
348 
349  // Use Sample AES in MPEG2TS.
350  // TODO(kqyang): Consider adding a new flag to enable Sample AES as we
351  // will support CENC in TS in the future.
352  if (GetOutputFormat(stream) == CONTAINER_MPEG2TS) {
353  VLOG(1) << "Use Apple Sample AES encryption for MPEG2TS.";
354  encryption_params.protection_scheme = kAppleSampleAesProtectionScheme;
355  }
356 
357  if (!stream.drm_label.empty()) {
358  const std::string& drm_label = stream.drm_label;
359  encryption_params.stream_label_func =
360  [drm_label](const EncryptionParams::EncryptedStreamAttributes&) {
361  return drm_label;
362  };
363  } else if (!encryption_params.stream_label_func) {
364  const int kDefaultMaxSdPixels = 768 * 576;
365  const int kDefaultMaxHdPixels = 1920 * 1080;
366  const int kDefaultMaxUhd1Pixels = 4096 * 2160;
367  encryption_params.stream_label_func = std::bind(
368  &Packager::DefaultStreamLabelFunction, kDefaultMaxSdPixels,
369  kDefaultMaxHdPixels, kDefaultMaxUhd1Pixels, std::placeholders::_1);
370  }
371 
372  return std::make_shared<EncryptionHandler>(encryption_params, key_source);
373 }
374 
375 Status CreateMp4ToMp4TextJob(const StreamDescriptor& stream,
376  const PackagingParams& packaging_params,
377  std::unique_ptr<MuxerListener> muxer_listener,
378  MuxerFactory* muxer_factory,
379  std::shared_ptr<OriginHandler>* root) {
380  Status status;
381  std::shared_ptr<Demuxer> demuxer;
382 
383  status.Update(CreateDemuxer(stream, packaging_params, &demuxer));
384  if (!stream.language.empty()) {
385  demuxer->SetLanguageOverride(stream.stream_selector, stream.language);
386  }
387 
388  std::shared_ptr<MediaHandler> chunker(
389  new ChunkingHandler(packaging_params.chunking_params));
390  std::shared_ptr<Muxer> muxer =
391  muxer_factory->CreateMuxer(GetOutputFormat(stream), stream);
392  muxer->SetMuxerListener(std::move(muxer_listener));
393 
394  status.Update(chunker->AddHandler(std::move(muxer)));
395  status.Update(demuxer->SetHandler(stream.stream_selector, chunker));
396 
397  return status;
398 }
399 
400 Status CreateHlsTextJob(const StreamDescriptor& stream,
401  const PackagingParams& packaging_params,
402  std::unique_ptr<MuxerListener> muxer_listener,
403  JobManager* job_manager) {
404  DCHECK(muxer_listener);
405  DCHECK(job_manager);
406 
407  if (stream.segment_template.empty()) {
408  return Status(error::INVALID_ARGUMENT,
409  "Cannot output text (" + stream.input +
410  ") to HLS with no segment template");
411  }
412 
413  const float segment_length_in_seconds =
414  packaging_params.chunking_params.segment_duration_in_seconds;
415  const uint64_t segment_length_in_ms =
416  static_cast<uint64_t>(segment_length_in_seconds * 1000);
417 
418  // Text files are usually small and since the input is one file;
419  // there's no way for the player to do ranged requests. So set this
420  // value to something reasonable if it is missing.
421  MuxerOptions muxer_options = CreateMuxerOptions(stream, packaging_params);
422  muxer_options.bandwidth = stream.bandwidth ? stream.bandwidth : 256;
423 
424  std::shared_ptr<WebVttSegmentedOutputHandler> output(
425  new WebVttSegmentedOutputHandler(muxer_options,
426  std::move(muxer_listener)));
427 
428  std::unique_ptr<FileReader> reader;
429  Status open_status = FileReader::Open(stream.input, &reader);
430  if (!open_status.ok()) {
431  return open_status;
432  }
433 
434  std::shared_ptr<OriginHandler> parser(
435  new WebVttParser(std::move(reader), stream.language));
436  std::shared_ptr<MediaHandler> segmenter(
437  new WebVttSegmenter(segment_length_in_ms));
438 
439  // Build in reverse to allow us to move the pointers.
440  Status status;
441  status.Update(segmenter->AddHandler(std::move(output)));
442  status.Update(parser->AddHandler(std::move(segmenter)));
443  if (!status.ok()) {
444  return status;
445  }
446 
447  job_manager->Add("Segmented Text Job", std::move(parser));
448  return Status::OK;
449 }
450 
451 Status CreateWebVttToMp4TextJob(const StreamDescriptor& stream,
452  const PackagingParams& packaging_params,
453  std::unique_ptr<MuxerListener> muxer_listener,
454  MuxerFactory* muxer_factory,
455  std::shared_ptr<OriginHandler>* root) {
456  Status status;
457  std::unique_ptr<FileReader> reader;
458  status = FileReader::Open(stream.input, &reader);
459 
460  if (!status.ok()) {
461  return status;
462  }
463 
464  std::shared_ptr<OriginHandler> parser(
465  new WebVttParser(std::move(reader), stream.language));
466  std::shared_ptr<MediaHandler> text_to_mp4(new WebVttToMp4Handler);
467  std::shared_ptr<MediaHandler> chunker(
468  new ChunkingHandler(packaging_params.chunking_params));
469  std::shared_ptr<Muxer> muxer =
470  muxer_factory->CreateMuxer(GetOutputFormat(stream), stream);
471  muxer->SetMuxerListener(std::move(muxer_listener));
472 
473  status.Update(chunker->AddHandler(std::move(muxer)));
474  status.Update(text_to_mp4->AddHandler(std::move(chunker)));
475  status.Update(parser->AddHandler(std::move(text_to_mp4)));
476 
477  *root = std::move(parser);
478 
479  return status;
480 }
481 
482 Status CreateTextJobs(
483  const std::vector<std::reference_wrapper<const StreamDescriptor>>& streams,
484  const PackagingParams& packaging_params,
485  MuxerListenerFactory* muxer_listener_factory,
486  MuxerFactory* muxer_factory,
487  MpdNotifier* mpd_notifier,
488  JobManager* job_manager) {
489  DCHECK(muxer_listener_factory);
490  DCHECK(job_manager);
491  for (const StreamDescriptor& stream : streams) {
492  if (GetOutputFormat(stream) == CONTAINER_MOV) {
493  std::unique_ptr<MuxerListener> muxer_listener =
494  muxer_listener_factory->CreateListener(ToMuxerListenerData(stream));
495 
496  std::shared_ptr<OriginHandler> root;
497  Status status;
498 
499  switch (DetermineContainerFromFileName(stream.input)) {
500  case CONTAINER_WEBVTT:
501  status.Update(CreateWebVttToMp4TextJob(stream, packaging_params,
502  std::move(muxer_listener),
503  muxer_factory, &root));
504  break;
505 
506  case CONTAINER_MOV:
507  status.Update(CreateMp4ToMp4TextJob(stream, packaging_params,
508  std::move(muxer_listener),
509  muxer_factory, &root));
510  break;
511 
512  default:
513  status.Update(
514  Status(error::INVALID_ARGUMENT,
515  "Text output format is not support for " + stream.input));
516  break;
517  }
518 
519  if (!status.ok()) {
520  return status;
521  }
522 
523  job_manager->Add("MP4 text job", std::move(root));
524  } else {
525  std::unique_ptr<MuxerListener> hls_listener =
526  muxer_listener_factory->CreateHlsListener(
527  ToMuxerListenerData(stream));
528 
529  // Check input to ensure that output is possible.
530  if (hls_listener) {
531  if (stream.segment_template.empty() || !stream.output.empty()) {
532  return Status(error::INVALID_ARGUMENT,
533  "segment_template needs to be specified for HLS text "
534  "output. Single file output is not supported yet.");
535  }
536  }
537 
538  if (mpd_notifier && !stream.segment_template.empty()) {
539  return Status(error::INVALID_ARGUMENT,
540  "Cannot create text output for MPD with segment output.");
541  }
542 
543  // If we are outputting to HLS, then create the HLS test pipeline that
544  // will create segmented text output.
545  if (hls_listener) {
546  Status status = CreateHlsTextJob(stream, packaging_params,
547  std::move(hls_listener), job_manager);
548  if (!status.ok()) {
549  return status;
550  }
551  }
552 
553  if (!stream.output.empty()) {
554  if (!File::Copy(stream.input.c_str(), stream.output.c_str())) {
555  std::string error;
556  base::StringAppendF(
557  &error, "Failed to copy the input file (%s) to output file (%s).",
558  stream.input.c_str(), stream.output.c_str());
559  return Status(error::FILE_FAILURE, error);
560  }
561 
562  MediaInfo text_media_info;
563  if (!StreamInfoToTextMediaInfo(stream, &text_media_info)) {
564  return Status(error::INVALID_ARGUMENT,
565  "Could not create media info for stream.");
566  }
567 
568  // If we are outputting to MPD, just add the input to the outputted
569  // manifest.
570  if (mpd_notifier) {
571  uint32_t unused;
572  if (mpd_notifier->NotifyNewContainer(text_media_info, &unused)) {
573  mpd_notifier->Flush();
574  } else {
575  return Status(error::PARSER_FAILURE,
576  "Failed to process text file " + stream.input);
577  }
578  }
579 
580  if (packaging_params.output_media_info) {
582  text_media_info, stream.output + kMediaInfoSuffix);
583  }
584  }
585  }
586  }
587 
588  return Status::OK;
589 }
590 
591 Status CreateAudioVideoJobs(
592  const std::vector<std::reference_wrapper<const StreamDescriptor>>& streams,
593  const PackagingParams& packaging_params,
594  KeySource* encryption_key_source,
595  MuxerListenerFactory* muxer_listener_factory,
596  MuxerFactory* muxer_factory,
597  JobManager* job_manager) {
598  DCHECK(muxer_listener_factory);
599  DCHECK(muxer_factory);
600  DCHECK(job_manager);
601 
602  // Demuxers are shared among all streams with the same input.
603  std::shared_ptr<Demuxer> demuxer;
604  // Chunkers can be shared among all streams with the same input (except for
605  // WVM files), which allows samples from the same input to be synced when
606  // doing chunking.
607  std::shared_ptr<MediaHandler> chunker;
608  bool is_wvm_file = false;
609  // Replicators are shared among all streams with the same input and stream
610  // selector.
611  std::shared_ptr<MediaHandler> replicator;
612 
613  std::string previous_input;
614  std::string previous_selector;
615 
616  for (const StreamDescriptor& stream : streams) {
617  // If we changed our input files, we need a new demuxer.
618  const bool new_input_file = stream.input != previous_input;
619  if (new_input_file) {
620  Status status = CreateDemuxer(stream, packaging_params, &demuxer);
621  if (!status.ok()) {
622  return status;
623  }
624 
625  job_manager->Add("RemuxJob", demuxer);
626 
627  // Share chunkers among all streams with the same input except for WVM
628  // file, which may contain multiple video files and the samples may not be
629  // interleaved either.
630  is_wvm_file =
631  DetermineContainerFromFileName(stream.input) == CONTAINER_WVM;
632  if (!is_wvm_file) {
633  chunker =
634  std::make_shared<ChunkingHandler>(packaging_params.chunking_params);
635  }
636  }
637 
638  if (!stream.language.empty()) {
639  demuxer->SetLanguageOverride(stream.stream_selector, stream.language);
640  }
641 
642  const bool new_stream =
643  new_input_file || previous_selector != stream.stream_selector;
644  previous_input = stream.input;
645  previous_selector = stream.stream_selector;
646 
647  // If the stream has no output, then there is no reason setting-up the rest
648  // of the pipeline.
649  if (stream.output.empty() && stream.segment_template.empty()) {
650  continue;
651  }
652 
653  if (new_stream) {
654  std::shared_ptr<MediaHandler> ad_cue_generator;
655  if (!packaging_params.ad_cue_generator_params.cue_points.empty()) {
656  ad_cue_generator = std::make_shared<AdCueGenerator>(
657  packaging_params.ad_cue_generator_params);
658  }
659 
660  if (is_wvm_file) {
661  chunker =
662  std::make_shared<ChunkingHandler>(packaging_params.chunking_params);
663  }
664 
665  std::shared_ptr<MediaHandler> encryptor = CreateEncryptionHandler(
666  packaging_params, stream, encryption_key_source);
667 
668  replicator = std::make_shared<Replicator>();
669 
670  Status status;
671  if (ad_cue_generator) {
672  status.Update(
673  demuxer->SetHandler(stream.stream_selector, ad_cue_generator));
674  status.Update(ad_cue_generator->AddHandler(chunker));
675  } else {
676  status.Update(demuxer->SetHandler(stream.stream_selector, chunker));
677  }
678  if (encryptor) {
679  status.Update(chunker->AddHandler(encryptor));
680  status.Update(encryptor->AddHandler(replicator));
681  } else {
682  status.Update(chunker->AddHandler(replicator));
683  }
684 
685  if (!status.ok()) {
686  return status;
687  }
688 
689  if (!stream.language.empty()) {
690  demuxer->SetLanguageOverride(stream.stream_selector, stream.language);
691  }
692  }
693 
694  // Create the muxer (output) for this track.
695  std::unique_ptr<MuxerListener> muxer_listener =
696  muxer_listener_factory->CreateListener(ToMuxerListenerData(stream));
697  std::shared_ptr<Muxer> muxer =
698  muxer_factory->CreateMuxer(GetOutputFormat(stream), stream);
699  muxer->SetMuxerListener(std::move(muxer_listener));
700 
701  if (!muxer) {
702  return Status(error::INVALID_ARGUMENT, "Failed to create muxer for " +
703  stream.input + ":" +
704  stream.stream_selector);
705  }
706 
707  std::shared_ptr<MediaHandler> trick_play;
708  if (stream.trick_play_factor) {
709  trick_play = std::make_shared<TrickPlayHandler>(stream.trick_play_factor);
710  }
711 
712  Status status;
713  if (trick_play) {
714  status.Update(replicator->AddHandler(trick_play));
715  status.Update(trick_play->AddHandler(muxer));
716  } else {
717  status.Update(replicator->AddHandler(muxer));
718  }
719 
720  if (!status.ok()) {
721  return status;
722  }
723  }
724 
725  return Status::OK;
726 }
727 
728 Status CreateAllJobs(const std::vector<StreamDescriptor>& stream_descriptors,
729  const PackagingParams& packaging_params,
730  MpdNotifier* mpd_notifier,
731  KeySource* encryption_key_source,
732  MuxerListenerFactory* muxer_listener_factory,
733  MuxerFactory* muxer_factory,
734  JobManager* job_manager) {
735  DCHECK(muxer_factory);
736  DCHECK(muxer_listener_factory);
737  DCHECK(job_manager);
738 
739  // Group all streams based on which pipeline they will use.
740  std::vector<std::reference_wrapper<const StreamDescriptor>> text_streams;
741  std::vector<std::reference_wrapper<const StreamDescriptor>>
742  audio_video_streams;
743 
744  for (const StreamDescriptor& stream : stream_descriptors) {
745  // TODO: Find a better way to determine what stream type a stream
746  // descriptor is as |stream_selector| may use an index. This would
747  // also allow us to use a simpler audio pipeline.
748  if (stream.stream_selector == "text") {
749  text_streams.push_back(stream);
750  } else {
751  audio_video_streams.push_back(stream);
752  }
753  }
754 
755  // Audio/Video streams need to be in sorted order so that demuxers and trick
756  // play handlers get setup correctly.
757  std::sort(audio_video_streams.begin(), audio_video_streams.end(),
758  media::StreamDescriptorCompareFn);
759 
760  Status status;
761  status.Update(CreateTextJobs(text_streams, packaging_params,
762  muxer_listener_factory, muxer_factory,
763  mpd_notifier, job_manager));
764  status.Update(CreateAudioVideoJobs(
765  audio_video_streams, packaging_params, encryption_key_source,
766  muxer_listener_factory, muxer_factory, job_manager));
767 
768  if (!status.ok()) {
769  return status;
770  }
771 
772  // Initialize processing graph.
773  status.Update(job_manager->InitializeJobs());
774 
775  return status;
776 }
777 
778 } // namespace
779 } // namespace media
780 
781 struct Packager::PackagerInternal {
782  media::FakeClock fake_clock;
783  std::unique_ptr<KeySource> encryption_key_source;
784  std::unique_ptr<MpdNotifier> mpd_notifier;
785  std::unique_ptr<hls::HlsNotifier> hls_notifier;
786  BufferCallbackParams buffer_callback_params;
787  media::JobManager job_manager;
788 };
789 
790 Packager::Packager() {}
791 
792 Packager::~Packager() {}
793 
795  const PackagingParams& packaging_params,
796  const std::vector<StreamDescriptor>& stream_descriptors) {
797  // Needed by base::WorkedPool used in ThreadedIoFile.
798  static base::AtExitManager exit;
799  static media::LibcryptoThreading libcrypto_threading;
800 
801  if (internal_)
802  return Status(error::INVALID_ARGUMENT, "Already initialized.");
803 
804  Status param_check =
805  media::ValidateParams(packaging_params, stream_descriptors);
806  if (!param_check.ok()) {
807  return param_check;
808  }
809 
810  if (!packaging_params.test_params.injected_library_version.empty()) {
811  SetPackagerVersionForTesting(
812  packaging_params.test_params.injected_library_version);
813  }
814 
815  std::unique_ptr<PackagerInternal> internal(new PackagerInternal);
816 
817  // Create encryption key source if needed.
818  if (packaging_params.encryption_params.key_provider != KeyProvider::kNone) {
819  internal->encryption_key_source = CreateEncryptionKeySource(
820  static_cast<media::FourCC>(
821  packaging_params.encryption_params.protection_scheme),
822  packaging_params.encryption_params);
823  if (!internal->encryption_key_source)
824  return Status(error::INVALID_ARGUMENT, "Failed to create key source.");
825  }
826 
827  // Store callback params to make it available during packaging.
828  internal->buffer_callback_params = packaging_params.buffer_callback_params;
829 
830  // Update MPD output and HLS output if callback param is specified.
831  MpdParams mpd_params = packaging_params.mpd_params;
832  HlsParams hls_params = packaging_params.hls_params;
833  if (internal->buffer_callback_params.write_func) {
835  internal->buffer_callback_params, mpd_params.mpd_output);
837  internal->buffer_callback_params, hls_params.master_playlist_output);
838  }
839 
840  if (!mpd_params.mpd_output.empty()) {
841  const bool on_demand_dash_profile =
842  stream_descriptors.begin()->segment_template.empty();
843  const MpdOptions mpd_options =
844  media::GetMpdOptions(on_demand_dash_profile, mpd_params);
845  internal->mpd_notifier.reset(new SimpleMpdNotifier(mpd_options));
846  if (!internal->mpd_notifier->Init()) {
847  LOG(ERROR) << "MpdNotifier failed to initialize.";
848  return Status(error::INVALID_ARGUMENT,
849  "Failed to initialize MpdNotifier.");
850  }
851  }
852 
853  if (!hls_params.master_playlist_output.empty()) {
854  internal->hls_notifier.reset(new hls::SimpleHlsNotifier(hls_params));
855  }
856 
857  std::vector<StreamDescriptor> streams_for_jobs;
858 
859  for (const StreamDescriptor& descriptor : stream_descriptors) {
860  // We may need to overwrite some values, so make a copy first.
861  StreamDescriptor copy = descriptor;
862 
863  if (internal->buffer_callback_params.read_func) {
864  copy.input = File::MakeCallbackFileName(internal->buffer_callback_params,
865  descriptor.input);
866  }
867 
868  if (internal->buffer_callback_params.write_func) {
869  copy.output = File::MakeCallbackFileName(internal->buffer_callback_params,
870  descriptor.output);
872  internal->buffer_callback_params, descriptor.segment_template);
873  }
874 
875  // Update language to ISO_639_2 code if set.
876  if (!copy.language.empty()) {
877  copy.language = LanguageToISO_639_2(descriptor.language);
878  if (copy.language == "und") {
879  return Status(
880  error::INVALID_ARGUMENT,
881  "Unknown/invalid language specified: " + descriptor.language);
882  }
883  }
884 
885  streams_for_jobs.push_back(copy);
886  }
887 
888  media::MuxerFactory muxer_factory(packaging_params);
889  if (packaging_params.test_params.inject_fake_clock) {
890  muxer_factory.OverrideClock(&internal->fake_clock);
891  }
892 
893  media::MuxerListenerFactory muxer_listener_factory(
894  packaging_params.output_media_info, internal->mpd_notifier.get(),
895  internal->hls_notifier.get());
896 
897  Status status = media::CreateAllJobs(
898  streams_for_jobs, packaging_params, internal->mpd_notifier.get(),
899  internal->encryption_key_source.get(), &muxer_listener_factory,
900  &muxer_factory, &internal->job_manager);
901 
902  if (!status.ok()) {
903  return status;
904  }
905 
906  internal_ = std::move(internal);
907  return Status::OK;
908 }
909 
911  if (!internal_)
912  return Status(error::INVALID_ARGUMENT, "Not yet initialized.");
913 
914  Status status = internal_->job_manager.RunJobs();
915  if (!status.ok())
916  return status;
917 
918  if (internal_->hls_notifier) {
919  if (!internal_->hls_notifier->Flush())
920  return Status(error::INVALID_ARGUMENT, "Failed to flush Hls.");
921  }
922  if (internal_->mpd_notifier) {
923  if (!internal_->mpd_notifier->Flush())
924  return Status(error::INVALID_ARGUMENT, "Failed to flush Mpd.");
925  }
926  return Status::OK;
927 }
928 
930  if (!internal_) {
931  LOG(INFO) << "Not yet initialized. Return directly.";
932  return;
933  }
934  internal_->job_manager.CancelJobs();
935 }
936 
938  return GetPackagerVersion();
939 }
940 
942  int max_sd_pixels,
943  int max_hd_pixels,
944  int max_uhd1_pixels,
945  const EncryptionParams::EncryptedStreamAttributes& stream_attributes) {
946  if (stream_attributes.stream_type ==
947  EncryptionParams::EncryptedStreamAttributes::kAudio)
948  return "AUDIO";
949  if (stream_attributes.stream_type ==
950  EncryptionParams::EncryptedStreamAttributes::kVideo) {
951  const int pixels = stream_attributes.oneof.video.width *
952  stream_attributes.oneof.video.height;
953  if (pixels <= max_sd_pixels)
954  return "SD";
955  if (pixels <= max_hd_pixels)
956  return "HD";
957  if (pixels <= max_uhd1_pixels)
958  return "UHD1";
959  return "UHD2";
960  }
961  return "";
962 }
963 
964 } // namespace shaka
BufferCallbackParams buffer_callback_params
Buffer callback params.
Definition: packager.h:62
std::string master_playlist_output
HLS master playlist output path.
Definition: hls_params.h:27
DASH MPD related parameters.
Definition: mpd_params.h:16
Defines a single input/output stream.
Definition: packager.h:69
std::string input
Input/source media file path or network stream URL. Required.
Definition: packager.h:71
HlsParams hls_params
HLS related parameters.
Definition: packager.h:55
Status Initialize(const PackagingParams &packaging_params, const std::vector< StreamDescriptor > &stream_descriptors)
Definition: packager.cc:794
static std::string DefaultStreamLabelFunction(int max_sd_pixels, int max_hd_pixels, int max_uhd1_pixels, const EncryptionParams::EncryptedStreamAttributes &stream_attributes)
Definition: packager.cc:941
HLS related parameters.
Definition: hls_params.h:23
std::string segment_template
Specifies segment template. Can be empty.
Definition: packager.h:81
static bool Copy(const char *from_file_name, const char *to_file_name)
Definition: file.cc:281
static bool ReadFileToString(const char *file_name, std::string *contents)
Definition: file.cc:216
bool inject_fake_clock
Definition: packager.h:31
Convenience class which initializes and terminates libcrypto threading.
All the methods that are virtual are virtual for mocking.
static std::string GetLibraryVersion()
Definition: packager.cc:937
std::string LanguageToISO_639_2(const std::string &language)
std::string injected_library_version
Definition: packager.h:34
MpdParams mpd_params
DASH MPD related parameters.
Definition: packager.h:53
static bool WriteMediaInfoToFile(const MediaInfo &media_info, const std::string &output_file_path)
EncryptionParams encryption_params
Encryption and Decryption Parameters.
Definition: packager.h:58
std::string mpd_output
MPD output file path.
Definition: mpd_params.h:18
Status Run()
Definition: packager.cc:910
static std::string MakeCallbackFileName(const BufferCallbackParams &callback_params, const std::string &name)
Definition: file.cc:354
std::string output
Definition: packager.h:79
Encrypted stream information that is used to determine stream label.
void OverrideClock(base::Clock *clock)
Defines Mpd Options.
Definition: mpd_options.h:25
void Cancel()
Cancel packaging. Note that it has to be called from another thread.
Definition: packager.cc:929
Packaging parameters.
Definition: packager.h:38
static Status Open(const std::string &filename, std::unique_ptr< FileReader > *out)
Definition: text_readers.cc:15
std::string language
Definition: packager.h:104