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