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