DASH Media Packaging SDK
 All Classes Namespaces Functions Variables Typedefs Enumerator
packager_main.cc
1 // Copyright 2014 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 <gflags/gflags.h>
8 #include <iostream>
9 
10 #include "packager/app/fixed_key_encryption_flags.h"
11 #include "packager/app/libcrypto_threading.h"
12 #include "packager/app/mpd_flags.h"
13 #include "packager/app/muxer_flags.h"
14 #include "packager/app/packager_util.h"
15 #include "packager/app/stream_descriptor.h"
16 #include "packager/app/vlog_flags.h"
17 #include "packager/app/widevine_encryption_flags.h"
18 #include "packager/base/command_line.h"
19 #include "packager/base/logging.h"
20 #include "packager/base/stl_util.h"
21 #include "packager/base/strings/string_split.h"
22 #include "packager/base/strings/stringprintf.h"
23 #include "packager/base/threading/simple_thread.h"
24 #include "packager/base/time/clock.h"
25 #include "packager/media/base/container_names.h"
26 #include "packager/media/base/demuxer.h"
27 #include "packager/media/base/key_source.h"
28 #include "packager/media/base/muxer_options.h"
29 #include "packager/media/base/muxer_util.h"
30 #include "packager/media/event/mpd_notify_muxer_listener.h"
31 #include "packager/media/event/vod_media_info_dump_muxer_listener.h"
32 #include "packager/media/file/file.h"
33 #include "packager/media/formats/mp4/mp4_muxer.h"
34 #include "packager/mpd/base/dash_iop_mpd_notifier.h"
35 #include "packager/mpd/base/media_info.pb.h"
36 #include "packager/mpd/base/mpd_builder.h"
37 #include "packager/mpd/base/simple_mpd_notifier.h"
38 
39 DEFINE_bool(use_fake_clock_for_muxer,
40  false,
41  "Set to true to use a fake clock for muxer. With this flag set, "
42  "creation time and modification time in outputs are set to 0. "
43  "Should only be used for testing.");
44 
45 namespace {
46 const char kUsage[] =
47  "Packager driver program. Sample Usage:\n"
48  "%s [flags] <stream_descriptor> ...\n"
49  "stream_descriptor consists of comma separated field_name/value pairs:\n"
50  "field_name=value,[field_name=value,]...\n"
51  "Supported field names are as follows:\n"
52  " - input (in): Required input/source media file path or network stream "
53  "URL.\n"
54  " - stream_selector (stream): Required field with value 'audio', 'video', "
55  "or stream number (zero based).\n"
56  " - output (out): Required output file (single file) or initialization "
57  "file path (multiple file).\n"
58  " - segment_template (segment): Optional value which specifies the "
59  "naming pattern for the segment files, and that the stream should be "
60  "split into multiple files. Its presence should be consistent across "
61  "streams.\n"
62  " - bandwidth (bw): Optional value which contains a user-specified "
63  "content bit rate for the stream, in bits/sec. If specified, this value is "
64  "propagated to the $Bandwidth$ template parameter for segment names. "
65  "If not specified, its value may be estimated.\n"
66  " - language (lang): Optional value which contains a user-specified "
67  "language tag. If specified, this value overrides any language metadata "
68  "in the input track.\n";
69 
70 const char kMediaInfoSuffix[] = ".media_info";
71 
72 enum ExitStatus {
73  kSuccess = 0,
74  kNoArgument,
75  kArgumentValidationFailed,
76  kPackagingFailed,
77  kInternalError,
78 };
79 
80 // TODO(rkuroiwa): Write TTML and WebVTT parser (demuxing) for a better check
81 // and for supporting live/segmenting (muxing). With a demuxer and a muxer,
82 // CreateRemuxJobs() shouldn't treat text as a special case.
83 std::string DetermineTextFileFormat(const std::string& file) {
84  std::string content;
85  if (!edash_packager::media::File::ReadFileToString(file.c_str(), &content)) {
86  LOG(ERROR) << "Failed to open file " << file
87  << " to determine file format.";
88  return "";
89  }
90  edash_packager::media::MediaContainerName container_name =
91  edash_packager::media::DetermineContainer(
92  reinterpret_cast<const uint8_t*>(content.data()), content.size());
93  if (container_name == edash_packager::media::CONTAINER_WEBVTT) {
94  return "vtt";
95  } else if (container_name == edash_packager::media::CONTAINER_TTML) {
96  return "ttml";
97  }
98 
99  return "";
100 }
101 
102 } // namespace
103 
104 namespace edash_packager {
105 namespace media {
106 
107 // A fake clock that always return time 0 (epoch). Should only be used for
108 // testing.
109 class FakeClock : public base::Clock {
110  public:
111  base::Time Now() override { return base::Time(); }
112 };
113 
114 // Demux, Mux(es) and worker thread used to remux a source file/stream.
115 class RemuxJob : public base::SimpleThread {
116  public:
117  RemuxJob(scoped_ptr<Demuxer> demuxer)
118  : SimpleThread("RemuxJob"),
119  demuxer_(demuxer.Pass()) {}
120 
121  ~RemuxJob() override {
122  STLDeleteElements(&muxers_);
123  }
124 
125  void AddMuxer(scoped_ptr<Muxer> mux) {
126  muxers_.push_back(mux.release());
127  }
128 
129  Demuxer* demuxer() { return demuxer_.get(); }
130  Status status() { return status_; }
131 
132  private:
133  void Run() override {
134  DCHECK(demuxer_);
135  status_ = demuxer_->Run();
136  }
137 
138  scoped_ptr<Demuxer> demuxer_;
139  std::vector<Muxer*> muxers_;
140  Status status_;
141 
142  DISALLOW_COPY_AND_ASSIGN(RemuxJob);
143 };
144 
145 bool StreamInfoToTextMediaInfo(const StreamDescriptor& stream_descriptor,
146  const MuxerOptions& stream_muxer_options,
147  MediaInfo* text_media_info) {
148  const std::string& language = stream_descriptor.language;
149  std::string format = DetermineTextFileFormat(stream_descriptor.input);
150  if (format.empty()) {
151  LOG(ERROR) << "Failed to determine the text file format for "
152  << stream_descriptor.input;
153  return false;
154  }
155 
156  if (!File::Copy(stream_descriptor.input.c_str(),
157  stream_muxer_options.output_file_name.c_str())) {
158  LOG(ERROR) << "Failed to copy the input file (" << stream_descriptor.input
159  << ") to output file (" << stream_muxer_options.output_file_name
160  << ").";
161  return false;
162  }
163 
164  text_media_info->set_media_file_name(stream_muxer_options.output_file_name);
165  text_media_info->set_container_type(MediaInfo::CONTAINER_TEXT);
166 
167  if (stream_muxer_options.bandwidth != 0) {
168  text_media_info->set_bandwidth(stream_muxer_options.bandwidth);
169  } else {
170  // Text files are usually small and since the input is one file; there's no
171  // way for the player to do ranged requests. So set this value to something
172  // reasonable.
173  text_media_info->set_bandwidth(256);
174  }
175 
176  MediaInfo::TextInfo* text_info = text_media_info->mutable_text_info();
177  text_info->set_format(format);
178  if (!language.empty())
179  text_info->set_language(language);
180 
181  return true;
182 }
183 
184 bool CreateRemuxJobs(const StreamDescriptorList& stream_descriptors,
185  const MuxerOptions& muxer_options,
186  FakeClock* fake_clock,
187  KeySource* key_source,
188  MpdNotifier* mpd_notifier,
189  std::vector<RemuxJob*>* remux_jobs) {
190  DCHECK(remux_jobs);
191 
192  std::string previous_input;
193  for (StreamDescriptorList::const_iterator stream_iter =
194  stream_descriptors.begin();
195  stream_iter != stream_descriptors.end();
196  ++stream_iter) {
197  // Process stream descriptor.
198  MuxerOptions stream_muxer_options(muxer_options);
199  stream_muxer_options.output_file_name = stream_iter->output;
200  if (!stream_iter->segment_template.empty()) {
201  if (!ValidateSegmentTemplate(stream_iter->segment_template)) {
202  LOG(ERROR) << "ERROR: segment template with '"
203  << stream_iter->segment_template << "' is invalid.";
204  return false;
205  }
206  stream_muxer_options.segment_template = stream_iter->segment_template;
207  }
208  stream_muxer_options.bandwidth = stream_iter->bandwidth;
209 
210  // Handle text input.
211  if (stream_iter->stream_selector == "text") {
212  MediaInfo text_media_info;
213  if (!StreamInfoToTextMediaInfo(*stream_iter, stream_muxer_options,
214  &text_media_info)) {
215  return false;
216  }
217 
218  if (mpd_notifier) {
219  uint32 unused;
220  if (!mpd_notifier->NotifyNewContainer(text_media_info, &unused)) {
221  LOG(ERROR) << "Failed to process text file " << stream_iter->input;
222  } else {
223  mpd_notifier->Flush();
224  }
225  } else if (FLAGS_output_media_info) {
227  text_media_info,
228  stream_muxer_options.output_file_name + kMediaInfoSuffix);
229  } else {
230  NOTIMPLEMENTED()
231  << "--mpd_output or --output_media_info flags are "
232  "required for text output. Skipping manifest related output for "
233  << stream_iter->input;
234  }
235  continue;
236  }
237 
238  if (stream_iter->input != previous_input) {
239  // New remux job needed. Create demux and job thread.
240  scoped_ptr<Demuxer> demuxer(new Demuxer(stream_iter->input));
241  if (FLAGS_enable_widevine_decryption ||
242  FLAGS_enable_fixed_key_decryption) {
243  scoped_ptr<KeySource> key_source(CreateDecryptionKeySource());
244  if (!key_source)
245  return false;
246  demuxer->SetKeySource(key_source.Pass());
247  }
248  Status status = demuxer->Initialize();
249  if (!status.ok()) {
250  LOG(ERROR) << "Demuxer failed to initialize: " << status.ToString();
251  return false;
252  }
253  if (FLAGS_dump_stream_info) {
254  printf("\nFile \"%s\":\n", stream_iter->input.c_str());
255  DumpStreamInfo(demuxer->streams());
256  if (stream_iter->output.empty())
257  continue; // just need stream info.
258  }
259  remux_jobs->push_back(new RemuxJob(demuxer.Pass()));
260  previous_input = stream_iter->input;
261  }
262  DCHECK(!remux_jobs->empty());
263 
264  scoped_ptr<Muxer> muxer(new mp4::MP4Muxer(stream_muxer_options));
265  if (FLAGS_use_fake_clock_for_muxer) muxer->set_clock(fake_clock);
266 
267  if (key_source) {
268  muxer->SetKeySource(key_source,
269  FLAGS_max_sd_pixels,
270  FLAGS_clear_lead,
271  FLAGS_crypto_period_duration);
272  }
273 
274  scoped_ptr<MuxerListener> muxer_listener;
275  DCHECK(!(FLAGS_output_media_info && mpd_notifier));
276  if (FLAGS_output_media_info) {
277  const std::string output_media_info_file_name =
278  stream_muxer_options.output_file_name + kMediaInfoSuffix;
279  scoped_ptr<VodMediaInfoDumpMuxerListener>
280  vod_media_info_dump_muxer_listener(
281  new VodMediaInfoDumpMuxerListener(output_media_info_file_name));
282  vod_media_info_dump_muxer_listener->SetContentProtectionSchemeIdUri(
283  FLAGS_scheme_id_uri);
284  muxer_listener = vod_media_info_dump_muxer_listener.Pass();
285  }
286  if (mpd_notifier) {
287  scoped_ptr<MpdNotifyMuxerListener> mpd_notify_muxer_listener(
288  new MpdNotifyMuxerListener(mpd_notifier));
289  mpd_notify_muxer_listener->SetContentProtectionSchemeIdUri(
290  FLAGS_scheme_id_uri);
291  muxer_listener = mpd_notify_muxer_listener.Pass();
292  }
293 
294  if (muxer_listener)
295  muxer->SetMuxerListener(muxer_listener.Pass());
296 
297  if (!AddStreamToMuxer(remux_jobs->back()->demuxer()->streams(),
298  stream_iter->stream_selector,
299  stream_iter->language,
300  muxer.get()))
301  return false;
302  remux_jobs->back()->AddMuxer(muxer.Pass());
303  }
304 
305  return true;
306 }
307 
308 Status RunRemuxJobs(const std::vector<RemuxJob*>& remux_jobs) {
309  // Start the job threads.
310  for (std::vector<RemuxJob*>::const_iterator job_iter = remux_jobs.begin();
311  job_iter != remux_jobs.end();
312  ++job_iter) {
313  (*job_iter)->Start();
314  }
315 
316  // Wait for all jobs to complete or an error occurs.
317  Status status;
318  bool all_joined;
319  do {
320  all_joined = true;
321  for (std::vector<RemuxJob*>::const_iterator job_iter = remux_jobs.begin();
322  job_iter != remux_jobs.end();
323  ++job_iter) {
324  if ((*job_iter)->HasBeenJoined()) {
325  status = (*job_iter)->status();
326  if (!status.ok())
327  break;
328  } else {
329  all_joined = false;
330  (*job_iter)->Join();
331  }
332  }
333  } while (!all_joined && status.ok());
334 
335  return status;
336 }
337 
338 bool RunPackager(const StreamDescriptorList& stream_descriptors) {
339  if (!AssignFlagsFromProfile())
340  return false;
341 
342  if (FLAGS_output_media_info && !FLAGS_mpd_output.empty()) {
343  NOTIMPLEMENTED() << "ERROR: --output_media_info and --mpd_output do not "
344  "work together.";
345  return false;
346  }
347  if (FLAGS_output_media_info && !FLAGS_single_segment) {
348  // TODO(rkuroiwa, kqyang): Support partial media info dump for live.
349  NOTIMPLEMENTED() << "ERROR: --output_media_info is only supported if "
350  "--single_segment is true.";
351  return false;
352  }
353 
354  // Get basic muxer options.
355  MuxerOptions muxer_options;
356  if (!GetMuxerOptions(&muxer_options))
357  return false;
358 
359  MpdOptions mpd_options;
360  if (!GetMpdOptions(&mpd_options))
361  return false;
362 
363  // Create encryption key source if needed.
364  scoped_ptr<KeySource> encryption_key_source;
365  if (FLAGS_enable_widevine_encryption || FLAGS_enable_fixed_key_encryption) {
366  encryption_key_source = CreateEncryptionKeySource();
367  if (!encryption_key_source)
368  return false;
369  }
370 
371  scoped_ptr<MpdNotifier> mpd_notifier;
372  if (!FLAGS_mpd_output.empty()) {
373  DashProfile profile =
374  FLAGS_single_segment ? kOnDemandProfile : kLiveProfile;
375  std::vector<std::string> base_urls;
376  base::SplitString(FLAGS_base_urls, ',', &base_urls);
377  if (FLAGS_generate_dash_if_iop_compliant_mpd) {
378  mpd_notifier.reset(new DashIopMpdNotifier(profile, mpd_options, base_urls,
379  FLAGS_mpd_output));
380  } else {
381  mpd_notifier.reset(new SimpleMpdNotifier(profile, mpd_options, base_urls,
382  FLAGS_mpd_output));
383  }
384  if (!mpd_notifier->Init()) {
385  LOG(ERROR) << "MpdNotifier failed to initialize.";
386  return false;
387  }
388  }
389 
390  std::vector<RemuxJob*> remux_jobs;
391  STLElementDeleter<std::vector<RemuxJob*> > scoped_jobs_deleter(&remux_jobs);
392  FakeClock fake_clock;
393  if (!CreateRemuxJobs(stream_descriptors, muxer_options, &fake_clock,
394  encryption_key_source.get(), mpd_notifier.get(),
395  &remux_jobs)) {
396  return false;
397  }
398 
399  Status status = RunRemuxJobs(remux_jobs);
400  if (!status.ok()) {
401  LOG(ERROR) << "Packaging Error: " << status.ToString();
402  return false;
403  }
404 
405  printf("Packaging completed successfully.\n");
406  return true;
407 }
408 
409 int PackagerMain(int argc, char** argv) {
410  // Needed to enable VLOG/DVLOG through --vmodule or --v.
411  base::CommandLine::Init(argc, argv);
412  CHECK(logging::InitLogging(logging::LoggingSettings()));
413 
414  google::SetUsageMessage(base::StringPrintf(kUsage, argv[0]));
415  google::ParseCommandLineFlags(&argc, &argv, true);
416  if (argc < 2) {
417  google::ShowUsageWithFlags(argv[0]);
418  return kNoArgument;
419  }
420 
422  return kArgumentValidationFailed;
423 
424  edash_packager::media::LibcryptoThreading libcrypto_threading;
425  // TODO(tinskip): Make InsertStreamDescriptor a member of
426  // StreamDescriptorList.
427  StreamDescriptorList stream_descriptors;
428  for (int i = 1; i < argc; ++i) {
429  if (!InsertStreamDescriptor(argv[i], &stream_descriptors))
430  return kArgumentValidationFailed;
431  }
432  return RunPackager(stream_descriptors) ? kSuccess : kPackagingFailed;
433 }
434 
435 } // namespace media
436 } // namespace edash_packager
437 
438 int main(int argc, char** argv) {
439  return edash_packager::media::PackagerMain(argc, argv);
440 }
static bool ReadFileToString(const char *file_name, std::string *contents)
Definition: file.cc:160
Convenience class which initializes and terminates libcrypto threading.
static bool WriteMediaInfoToFile(const MediaInfo &media_info, const std::string &output_file_path)
static bool Copy(const char *from_file_name, const char *to_file_name)
Definition: file.cc:178