DASH Media Packaging SDK
 All Classes Namespaces Functions Variables Typedefs
mp4_muxer.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 "packager/media/formats/mp4/mp4_muxer.h"
8 
9 #include "packager/base/time/clock.h"
10 #include "packager/base/time/time.h"
11 #include "packager/media/base/aes_encryptor.h"
12 #include "packager/media/base/audio_stream_info.h"
13 #include "packager/media/base/key_source.h"
14 #include "packager/media/base/media_sample.h"
15 #include "packager/media/base/media_stream.h"
16 #include "packager/media/base/video_stream_info.h"
17 #include "packager/media/event/muxer_listener.h"
18 #include "packager/media/file/file.h"
19 #include "packager/media/formats/mp4/box_definitions.h"
20 #include "packager/media/formats/mp4/es_descriptor.h"
21 #include "packager/media/formats/mp4/fourccs.h"
22 #include "packager/media/formats/mp4/multi_segment_segmenter.h"
23 #include "packager/media/formats/mp4/single_segment_segmenter.h"
24 
25 namespace {
26 // Sets the range start and end value from offset and size.
27 // |start| and |end| are for byte-range-spec specified in RFC2616.
28 void SetStartAndEndFromOffsetAndSize(size_t offset,
29  size_t size,
30  uint32_t* start,
31  uint32_t* end) {
32  DCHECK(start && end);
33  *start = static_cast<uint32_t>(offset);
34  // Note that ranges are inclusive. So we need - 1.
35  *end = *start + static_cast<uint32_t>(size) - 1;
36 }
37 } // namespace
38 
39 namespace edash_packager {
40 namespace media {
41 namespace mp4 {
42 
43 MP4Muxer::MP4Muxer(const MuxerOptions& options) : Muxer(options) {}
44 MP4Muxer::~MP4Muxer() {}
45 
46 Status MP4Muxer::Initialize() {
47  DCHECK(!streams().empty());
48 
49  scoped_ptr<FileType> ftyp(new FileType);
50  scoped_ptr<Movie> moov(new Movie);
51 
52  ftyp->major_brand = FOURCC_DASH;
53  ftyp->compatible_brands.push_back(FOURCC_ISO6);
54  ftyp->compatible_brands.push_back(FOURCC_MP41);
55  if (streams().size() == 1 &&
56  streams()[0]->info()->stream_type() == kStreamVideo)
57  ftyp->compatible_brands.push_back(FOURCC_AVC1);
58 
59  moov->header.creation_time = IsoTimeNow();
60  moov->header.modification_time = IsoTimeNow();
61  moov->header.next_track_id = streams().size() + 1;
62 
63  moov->tracks.resize(streams().size());
64  moov->extends.tracks.resize(streams().size());
65 
66  // Initialize tracks.
67  for (uint32_t i = 0; i < streams().size(); ++i) {
68  Track& trak = moov->tracks[i];
69  trak.header.track_id = i + 1;
70 
71  TrackExtends& trex = moov->extends.tracks[i];
72  trex.track_id = trak.header.track_id;
73  trex.default_sample_description_index = 1;
74 
75  switch (streams()[i]->info()->stream_type()) {
76  case kStreamVideo:
77  GenerateVideoTrak(
78  static_cast<VideoStreamInfo*>(streams()[i]->info().get()),
79  &trak,
80  i + 1);
81  break;
82  case kStreamAudio:
83  GenerateAudioTrak(
84  static_cast<AudioStreamInfo*>(streams()[i]->info().get()),
85  &trak,
86  i + 1);
87  break;
88  default:
89  NOTIMPLEMENTED() << "Not implemented for stream type: "
90  << streams()[i]->info()->stream_type();
91  }
92  }
93 
94  if (options().single_segment) {
95  segmenter_.reset(
96  new SingleSegmentSegmenter(options(), ftyp.Pass(), moov.Pass()));
97  } else {
98  segmenter_.reset(
99  new MultiSegmentSegmenter(options(), ftyp.Pass(), moov.Pass()));
100  }
101 
102  Status segmenter_initialized =
103  segmenter_->Initialize(streams(),
104  muxer_listener(),
105  progress_listener(),
106  encryption_key_source(),
107  max_sd_pixels(),
108  clear_lead_in_seconds(),
109  crypto_period_duration_in_seconds());
110 
111  if (!segmenter_initialized.ok())
112  return segmenter_initialized;
113 
114  FireOnMediaStartEvent();
115  return Status::OK;
116 }
117 
118 Status MP4Muxer::Finalize() {
119  DCHECK(segmenter_);
120  Status segmenter_finalized = segmenter_->Finalize();
121 
122  if (!segmenter_finalized.ok())
123  return segmenter_finalized;
124 
125  FireOnMediaEndEvent();
126  LOG(INFO) << "MP4 file '" << options().output_file_name << "' finalized.";
127  return Status::OK;
128 }
129 
130 Status MP4Muxer::DoAddSample(const MediaStream* stream,
131  scoped_refptr<MediaSample> sample) {
132  DCHECK(segmenter_);
133  return segmenter_->AddSample(stream, sample);
134 }
135 
136 void MP4Muxer::InitializeTrak(const StreamInfo* info, Track* trak) {
137  int64_t now = IsoTimeNow();
138  trak->header.creation_time = now;
139  trak->header.modification_time = now;
140  trak->header.duration = 0;
141  trak->media.header.creation_time = now;
142  trak->media.header.modification_time = now;
143  trak->media.header.timescale = info->time_scale();
144  trak->media.header.duration = 0;
145  if (!info->language().empty()) {
146  const size_t language_size = arraysize(trak->media.header.language) - 1;
147  if (info->language().size() != language_size) {
148  LOG(WARNING) << "'" << info->language() << "' is not a valid ISO-639-2 "
149  << "language code, ignoring.";
150  } else {
151  memcpy(trak->media.header.language, info->language().c_str(),
152  language_size + 1);
153  }
154  }
155 }
156 
157 void MP4Muxer::GenerateVideoTrak(const VideoStreamInfo* video_info,
158  Track* trak,
159  uint32_t track_id) {
160  InitializeTrak(video_info, trak);
161 
162  // width and height specify the track's visual presentation size as
163  // fixed-point 16.16 values.
164  uint32_t pixel_width = video_info->pixel_width();
165  uint32_t pixel_height = video_info->pixel_height();
166  if (pixel_width == 0 || pixel_height == 0) {
167  LOG(WARNING) << "pixel width/height are not set. Assuming 1:1.";
168  pixel_width = 1;
169  pixel_height = 1;
170  }
171  const double sample_aspect_ratio =
172  static_cast<double>(pixel_width) / pixel_height;
173  trak->header.width = video_info->width() * sample_aspect_ratio * 0x10000;
174  trak->header.height = video_info->height() * 0x10000;
175 
176  trak->media.handler.type = kVideo;
177 
178  VideoSampleEntry video;
179  video.format = FOURCC_AVC1;
180  video.width = video_info->width();
181  video.height = video_info->height();
182  video.avcc.data = video_info->extra_data();
183  if (pixel_width != 1 || pixel_height != 1) {
184  video.pixel_aspect.h_spacing = pixel_width;
185  video.pixel_aspect.v_spacing = pixel_height;
186  }
187 
188  SampleDescription& sample_description =
189  trak->media.information.sample_table.description;
190  sample_description.type = kVideo;
191  sample_description.video_entries.push_back(video);
192 }
193 
194 void MP4Muxer::GenerateAudioTrak(const AudioStreamInfo* audio_info,
195  Track* trak,
196  uint32_t track_id) {
197  InitializeTrak(audio_info, trak);
198 
199  trak->header.volume = 0x100;
200  trak->media.handler.type = kAudio;
201 
202  AudioSampleEntry audio;
203  audio.format = FOURCC_MP4A;
204  audio.channelcount = audio_info->num_channels();
205  audio.samplesize = audio_info->sample_bits();
206  audio.samplerate = audio_info->sampling_frequency();
207 
208  if (kCodecAAC) {
209  audio.esds.es_descriptor.set_object_type(kISO_14496_3); // MPEG4 AAC.
210  } else {
211  // Do we need to support other types?
212  NOTIMPLEMENTED();
213  }
214  audio.esds.es_descriptor.set_esid(track_id);
215  audio.esds.es_descriptor.set_decoder_specific_info(audio_info->extra_data());
216 
217  SampleDescription& sample_description =
218  trak->media.information.sample_table.description;
219  sample_description.type = kAudio;
220  sample_description.audio_entries.push_back(audio);
221 }
222 
223 bool MP4Muxer::GetInitRangeStartAndEnd(uint32_t* start, uint32_t* end) {
224  DCHECK(start && end);
225  size_t range_offset = 0;
226  size_t range_size = 0;
227  const bool has_range = segmenter_->GetInitRange(&range_offset, &range_size);
228 
229  if (!has_range)
230  return false;
231 
232  SetStartAndEndFromOffsetAndSize(range_offset, range_size, start, end);
233  return true;
234 }
235 
236 bool MP4Muxer::GetIndexRangeStartAndEnd(uint32_t* start, uint32_t* end) {
237  DCHECK(start && end);
238  size_t range_offset = 0;
239  size_t range_size = 0;
240  const bool has_range = segmenter_->GetIndexRange(&range_offset, &range_size);
241 
242  if (!has_range)
243  return false;
244 
245  SetStartAndEndFromOffsetAndSize(range_offset, range_size, start, end);
246  return true;
247 }
248 
249 void MP4Muxer::FireOnMediaStartEvent() {
250  if (!muxer_listener())
251  return;
252 
253  if (streams().size() > 1) {
254  LOG(ERROR) << "MuxerListener cannot take more than 1 stream.";
255  return;
256  }
257  DCHECK(!streams().empty()) << "Media started without a stream.";
258 
259  const uint32_t timescale = segmenter_->GetReferenceTimeScale();
260  muxer_listener()->OnMediaStart(options(),
261  *streams().front()->info(),
262  timescale,
263  MuxerListener::kContainerMp4);
264 }
265 
266 void MP4Muxer::FireOnMediaEndEvent() {
267  if (!muxer_listener())
268  return;
269 
270  uint32_t init_range_start = 0;
271  uint32_t init_range_end = 0;
272  const bool has_init_range =
273  GetInitRangeStartAndEnd(&init_range_start, &init_range_end);
274 
275  uint32_t index_range_start = 0;
276  uint32_t index_range_end = 0;
277  const bool has_index_range =
278  GetIndexRangeStartAndEnd(&index_range_start, &index_range_end);
279 
280  const float duration_seconds = static_cast<float>(segmenter_->GetDuration());
281 
282  const int64_t file_size =
283  File::GetFileSize(options().output_file_name.c_str());
284  if (file_size <= 0) {
285  LOG(ERROR) << "Invalid file size: " << file_size;
286  return;
287  }
288 
289  muxer_listener()->OnMediaEnd(has_init_range,
290  init_range_start,
291  init_range_end,
292  has_index_range,
293  index_range_start,
294  index_range_end,
295  duration_seconds,
296  file_size);
297 }
298 
299 uint64_t MP4Muxer::IsoTimeNow() {
300  // Time in seconds from Jan. 1, 1904 to epoch time, i.e. Jan. 1, 1970.
301  const uint64_t kIsomTimeOffset = 2082844800l;
302  return kIsomTimeOffset +
303  (clock() ? clock()->Now() : base::Time::Now()).ToDoubleT();
304 }
305 
306 } // namespace mp4
307 } // namespace media
308 } // namespace edash_packager
MP4Muxer(const MuxerOptions &options)
Create a MP4Muxer object from MuxerOptions.
Definition: mp4_muxer.cc:43
This structure contains the list of configuration options for Muxer.
Definition: muxer_options.h:18
static int64_t GetFileSize(const char *file_name)
Definition: file.cc:151