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