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