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