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