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