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