DASH Media Packaging SDK
 All Classes Namespaces Functions Variables Typedefs
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/widevine_encryption_flags.h"
17 #include "packager/base/logging.h"
18 #include "packager/base/stl_util.h"
19 #include "packager/base/strings/string_split.h"
20 #include "packager/base/strings/stringprintf.h"
21 #include "packager/base/threading/simple_thread.h"
22 #include "packager/base/time/clock.h"
23 #include "packager/media/base/demuxer.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/event/mpd_notify_muxer_listener.h"
28 #include "packager/media/event/vod_media_info_dump_muxer_listener.h"
29 #include "packager/media/formats/mp4/mp4_muxer.h"
30 #include "packager/mpd/base/dash_iop_mpd_notifier.h"
31 #include "packager/mpd/base/mpd_builder.h"
32 #include "packager/mpd/base/simple_mpd_notifier.h"
33 
34 DEFINE_bool(use_fake_clock_for_muxer,
35  false,
36  "Set to true to use a fake clock for muxer. With this flag set, "
37  "creation time and modification time in outputs are set to 0. "
38  "Should only be used for testing.");
39 
40 namespace {
41 const char kUsage[] =
42  "Packager driver program. Sample Usage:\n"
43  "%s [flags] <stream_descriptor> ...\n"
44  "stream_descriptor consists of comma separated field_name/value pairs:\n"
45  "field_name=value,[field_name=value,]...\n"
46  "Supported field names are as follows:\n"
47  " - input (in): Required input/source media file path or network stream "
48  "URL.\n"
49  " - stream_selector (stream): Required field with value 'audio', 'video', "
50  "or stream number (zero based).\n"
51  " - output (out): Required output file (single file) or initialization "
52  "file path (multiple file).\n"
53  " - segment_template (segment): Optional value which specifies the "
54  "naming pattern for the segment files, and that the stream should be "
55  "split into multiple files. Its presence should be consistent across "
56  "streams.\n"
57  " - bandwidth (bw): Optional value which contains a user-specified "
58  "content bit rate for the stream, in bits/sec. If specified, this value is "
59  "propagated to the $Bandwidth$ template parameter for segment names. "
60  "If not specified, its value may be estimated.\n"
61  " - language (lang): Optional value which contains a user-specified "
62  "language tag. If specified, this value overrides any language metadata "
63  "in the input track.\n";
64 
65 enum ExitStatus {
66  kSuccess = 0,
67  kNoArgument,
68  kArgumentValidationFailed,
69  kPackagingFailed,
70  kInternalError,
71 };
72 } // namespace
73 
74 namespace edash_packager {
75 namespace media {
76 
77 // A fake clock that always return time 0 (epoch). Should only be used for
78 // testing.
79 class FakeClock : public base::Clock {
80  public:
81  virtual base::Time Now() OVERRIDE { return base::Time(); }
82 };
83 
84 // Demux, Mux(es) and worker thread used to remux a source file/stream.
85 class RemuxJob : public base::SimpleThread {
86  public:
87  RemuxJob(scoped_ptr<Demuxer> demuxer)
88  : SimpleThread("RemuxJob"),
89  demuxer_(demuxer.Pass()) {}
90 
91  virtual ~RemuxJob() {
92  STLDeleteElements(&muxers_);
93  }
94 
95  void AddMuxer(scoped_ptr<Muxer> mux) {
96  muxers_.push_back(mux.release());
97  }
98 
99  Demuxer* demuxer() { return demuxer_.get(); }
100  Status status() { return status_; }
101 
102  private:
103  virtual void Run() OVERRIDE {
104  DCHECK(demuxer_);
105  status_ = demuxer_->Run();
106  }
107 
108  scoped_ptr<Demuxer> demuxer_;
109  std::vector<Muxer*> muxers_;
110  Status status_;
111 
112  DISALLOW_COPY_AND_ASSIGN(RemuxJob);
113 };
114 
115 bool CreateRemuxJobs(const StreamDescriptorList& stream_descriptors,
116  const MuxerOptions& muxer_options,
117  FakeClock* fake_clock,
118  KeySource* key_source,
119  MpdNotifier* mpd_notifier,
120  std::vector<RemuxJob*>* remux_jobs) {
121  DCHECK(remux_jobs);
122 
123  std::string previous_input;
124  for (StreamDescriptorList::const_iterator stream_iter =
125  stream_descriptors.begin();
126  stream_iter != stream_descriptors.end();
127  ++stream_iter) {
128  // Process stream descriptor.
129  MuxerOptions stream_muxer_options(muxer_options);
130  stream_muxer_options.output_file_name = stream_iter->output;
131  if (!stream_iter->segment_template.empty()) {
132  if (!ValidateSegmentTemplate(stream_iter->segment_template)) {
133  LOG(ERROR) << "ERROR: segment template with '"
134  << stream_iter->segment_template << "' is invalid.";
135  return false;
136  }
137  stream_muxer_options.segment_template = stream_iter->segment_template;
138  }
139  stream_muxer_options.bandwidth = stream_iter->bandwidth;
140 
141  if (stream_iter->input != previous_input) {
142  // New remux job needed. Create demux and job thread.
143  scoped_ptr<Demuxer> demuxer(new Demuxer(stream_iter->input));
144  if (FLAGS_enable_widevine_decryption ||
145  FLAGS_enable_fixed_key_decryption) {
146  scoped_ptr<KeySource> key_source(CreateDecryptionKeySource());
147  if (!key_source)
148  return false;
149  demuxer->SetKeySource(key_source.Pass());
150  }
151  Status status = demuxer->Initialize();
152  if (!status.ok()) {
153  LOG(ERROR) << "Demuxer failed to initialize: " << status.ToString();
154  return false;
155  }
156  if (FLAGS_dump_stream_info) {
157  printf("\nFile \"%s\":\n", stream_iter->input.c_str());
158  DumpStreamInfo(demuxer->streams());
159  if (stream_iter->output.empty())
160  continue; // just need stream info.
161  }
162  remux_jobs->push_back(new RemuxJob(demuxer.Pass()));
163  previous_input = stream_iter->input;
164  }
165  DCHECK(!remux_jobs->empty());
166 
167  scoped_ptr<Muxer> muxer(new mp4::MP4Muxer(stream_muxer_options));
168  if (FLAGS_use_fake_clock_for_muxer) muxer->set_clock(fake_clock);
169 
170  if (key_source) {
171  muxer->SetKeySource(key_source,
172  FLAGS_max_sd_pixels,
173  FLAGS_clear_lead,
174  FLAGS_crypto_period_duration);
175  }
176 
177  scoped_ptr<MuxerListener> muxer_listener;
178  DCHECK(!(FLAGS_output_media_info && mpd_notifier));
179  if (FLAGS_output_media_info) {
180  const std::string output_media_info_file_name =
181  stream_muxer_options.output_file_name + ".media_info";
182  scoped_ptr<VodMediaInfoDumpMuxerListener>
183  vod_media_info_dump_muxer_listener(
184  new VodMediaInfoDumpMuxerListener(output_media_info_file_name));
185  vod_media_info_dump_muxer_listener->SetContentProtectionSchemeIdUri(
186  FLAGS_scheme_id_uri);
187  muxer_listener = vod_media_info_dump_muxer_listener.Pass();
188  }
189  if (mpd_notifier) {
190  scoped_ptr<MpdNotifyMuxerListener> mpd_notify_muxer_listener(
191  new MpdNotifyMuxerListener(mpd_notifier));
192  mpd_notify_muxer_listener->SetContentProtectionSchemeIdUri(
193  FLAGS_scheme_id_uri);
194  muxer_listener = mpd_notify_muxer_listener.Pass();
195  }
196 
197  if (muxer_listener)
198  muxer->SetMuxerListener(muxer_listener.Pass());
199 
200  if (!AddStreamToMuxer(remux_jobs->back()->demuxer()->streams(),
201  stream_iter->stream_selector,
202  stream_iter->language,
203  muxer.get()))
204  return false;
205  remux_jobs->back()->AddMuxer(muxer.Pass());
206  }
207 
208  return true;
209 }
210 
211 Status RunRemuxJobs(const std::vector<RemuxJob*>& remux_jobs) {
212  // Start the job threads.
213  for (std::vector<RemuxJob*>::const_iterator job_iter = remux_jobs.begin();
214  job_iter != remux_jobs.end();
215  ++job_iter) {
216  (*job_iter)->Start();
217  }
218 
219  // Wait for all jobs to complete or an error occurs.
220  Status status;
221  bool all_joined;
222  do {
223  all_joined = true;
224  for (std::vector<RemuxJob*>::const_iterator job_iter = remux_jobs.begin();
225  job_iter != remux_jobs.end();
226  ++job_iter) {
227  if ((*job_iter)->HasBeenJoined()) {
228  status = (*job_iter)->status();
229  if (!status.ok())
230  break;
231  } else {
232  all_joined = false;
233  (*job_iter)->Join();
234  }
235  }
236  } while (!all_joined && status.ok());
237 
238  return status;
239 }
240 
241 bool RunPackager(const StreamDescriptorList& stream_descriptors) {
242  if (!AssignFlagsFromProfile())
243  return false;
244 
245  if (FLAGS_output_media_info && !FLAGS_mpd_output.empty()) {
246  NOTIMPLEMENTED() << "ERROR: --output_media_info and --mpd_output do not "
247  "work together.";
248  return false;
249  }
250  if (FLAGS_output_media_info && !FLAGS_single_segment) {
251  // TODO(rkuroiwa, kqyang): Support partial media info dump for live.
252  NOTIMPLEMENTED() << "ERROR: --output_media_info is only supported if "
253  "--single_segment is true.";
254  return false;
255  }
256 
257  // Get basic muxer options.
258  MuxerOptions muxer_options;
259  if (!GetMuxerOptions(&muxer_options))
260  return false;
261 
262  MpdOptions mpd_options;
263  if (!GetMpdOptions(&mpd_options))
264  return false;
265 
266  // Create encryption key source if needed.
267  scoped_ptr<KeySource> encryption_key_source;
268  if (FLAGS_enable_widevine_encryption || FLAGS_enable_fixed_key_encryption) {
269  encryption_key_source = CreateEncryptionKeySource();
270  if (!encryption_key_source)
271  return false;
272  }
273 
274  scoped_ptr<MpdNotifier> mpd_notifier;
275  if (!FLAGS_mpd_output.empty()) {
276  DashProfile profile =
277  FLAGS_single_segment ? kOnDemandProfile : kLiveProfile;
278  std::vector<std::string> base_urls;
279  base::SplitString(FLAGS_base_urls, ',', &base_urls);
280  if (FLAGS_generate_dash_if_iop_compliant_mpd) {
281  mpd_notifier.reset(new DashIopMpdNotifier(profile, mpd_options, base_urls,
282  FLAGS_mpd_output));
283  } else {
284  mpd_notifier.reset(new SimpleMpdNotifier(profile, mpd_options, base_urls,
285  FLAGS_mpd_output));
286  }
287  if (!mpd_notifier->Init()) {
288  LOG(ERROR) << "MpdNotifier failed to initialize.";
289  return false;
290  }
291  }
292 
293  std::vector<RemuxJob*> remux_jobs;
294  STLElementDeleter<std::vector<RemuxJob*> > scoped_jobs_deleter(&remux_jobs);
295  FakeClock fake_clock;
296  if (!CreateRemuxJobs(stream_descriptors, muxer_options, &fake_clock,
297  encryption_key_source.get(), mpd_notifier.get(),
298  &remux_jobs)) {
299  return false;
300  }
301 
302  Status status = RunRemuxJobs(remux_jobs);
303  if (!status.ok()) {
304  LOG(ERROR) << "Packaging Error: " << status.ToString();
305  return false;
306  }
307 
308  printf("Packaging completed successfully.\n");
309  return true;
310 }
311 
312 int PackagerMain(int argc, char** argv) {
313  google::SetUsageMessage(base::StringPrintf(kUsage, argv[0]));
314  google::ParseCommandLineFlags(&argc, &argv, true);
315  if (argc < 2) {
316  google::ShowUsageWithFlags(argv[0]);
317  return kNoArgument;
318  }
319 
321  return kArgumentValidationFailed;
322 
323  edash_packager::media::LibcryptoThreading libcrypto_threading;
324  // TODO(tinskip): Make InsertStreamDescriptor a member of
325  // StreamDescriptorList.
326  StreamDescriptorList stream_descriptors;
327  for (int i = 1; i < argc; ++i) {
328  if (!InsertStreamDescriptor(argv[i], &stream_descriptors))
329  return kArgumentValidationFailed;
330  }
331  return RunPackager(stream_descriptors) ? kSuccess : kPackagingFailed;
332 }
333 
334 } // namespace media
335 } // namespace edash_packager
336 
337 int main(int argc, char** argv) {
338  return edash_packager::media::PackagerMain(argc, argv);
339 }
Convenience class which initializes and terminates libcrypto threading.