DASH Media Packaging SDK
 All Classes Namespaces Functions Variables Typedefs Enumerator
segmenter.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/segmenter.h"
8 
9 #include <algorithm>
10 
11 #include "packager/base/stl_util.h"
12 #include "packager/media/base/buffer_writer.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/muxer_options.h"
17 #include "packager/media/base/video_stream_info.h"
18 #include "packager/media/event/muxer_listener.h"
19 #include "packager/media/event/progress_listener.h"
20 #include "packager/media/formats/mp4/box_definitions.h"
21 #include "packager/media/formats/mp4/key_rotation_fragmenter.h"
22 
23 namespace edash_packager {
24 namespace media {
25 namespace mp4 {
26 
27 namespace {
28 
29 // Generate 64bit IV by default.
30 const size_t kDefaultIvSize = 8u;
31 const size_t kCencKeyIdSize = 16u;
32 
33 // The version of cenc implemented here. CENC 4.
34 const int kCencSchemeVersion = 0x00010000;
35 
36 // The default KID for key rotation is all 0s.
37 const uint8_t kKeyRotationDefaultKeyId[] = {
38  0, 0, 0, 0, 0, 0, 0, 0,
39  0, 0, 0, 0, 0, 0, 0, 0
40 };
41 
42 COMPILE_ASSERT(arraysize(kKeyRotationDefaultKeyId) == kCencKeyIdSize,
43  cenc_key_id_must_be_size_16);
44 
45 uint64_t Rescale(uint64_t time_in_old_scale,
46  uint32_t old_scale,
47  uint32_t new_scale) {
48  return static_cast<double>(time_in_old_scale) / old_scale * new_scale;
49 }
50 
51 void GenerateSinf(const EncryptionKey& encryption_key,
52  FourCC old_type,
53  EncryptionMode encryption_mode,
54  ProtectionSchemeInfo* sinf) {
55  sinf->format.format = old_type;
56 
57  if (encryption_mode == kEncryptionModeAesCtr){
58  sinf->type.type = FOURCC_CENC;
59  } else if (encryption_mode == kEncryptionModeAesCbc) {
60  sinf->type.type = FOURCC_CBC1;
61  }
62 
63  sinf->type.version = kCencSchemeVersion;
64  sinf->info.track_encryption.is_encrypted = true;
65  sinf->info.track_encryption.default_iv_size =
66  encryption_key.iv.empty() ? kDefaultIvSize : encryption_key.iv.size();
67  sinf->info.track_encryption.default_kid = encryption_key.key_id;
68 }
69 
70 void GenerateEncryptedSampleEntry(const EncryptionKey& encryption_key,
71  double clear_lead_in_seconds,
72  EncryptionMode encryption_mode,
73  SampleDescription* description) {
74  DCHECK(description);
75  if (description->type == kVideo) {
76  DCHECK_EQ(1u, description->video_entries.size());
77 
78  // Add a second entry for clear content if needed.
79  if (clear_lead_in_seconds > 0)
80  description->video_entries.push_back(description->video_entries[0]);
81 
82  // Convert the first entry to an encrypted entry.
83  VideoSampleEntry& entry = description->video_entries[0];
84  GenerateSinf(encryption_key, entry.format, encryption_mode, &entry.sinf);
85  entry.format = FOURCC_ENCV;
86  } else {
87  DCHECK_EQ(kAudio, description->type);
88  DCHECK_EQ(1u, description->audio_entries.size());
89 
90  // Add a second entry for clear content if needed.
91  if (clear_lead_in_seconds > 0)
92  description->audio_entries.push_back(description->audio_entries[0]);
93 
94  // Convert the first entry to an encrypted entry.
95  AudioSampleEntry& entry = description->audio_entries[0];
96  GenerateSinf(encryption_key, entry.format, encryption_mode, &entry.sinf);
97  entry.format = FOURCC_ENCA;
98  }
99 }
100 
101 KeySource::TrackType GetTrackTypeForEncryption(const StreamInfo& stream_info,
102  uint32_t max_sd_pixels) {
103  if (stream_info.stream_type() == kStreamAudio)
104  return KeySource::TRACK_TYPE_AUDIO;
105 
106  DCHECK_EQ(kStreamVideo, stream_info.stream_type());
107  const VideoStreamInfo& video_stream_info =
108  static_cast<const VideoStreamInfo&>(stream_info);
109  uint32_t pixels = video_stream_info.width() * video_stream_info.height();
110  return (pixels > max_sd_pixels) ? KeySource::TRACK_TYPE_HD
111  : KeySource::TRACK_TYPE_SD;
112 }
113 
114 } // namespace
115 
116 Segmenter::Segmenter(const MuxerOptions& options,
117  scoped_ptr<FileType> ftyp,
118  scoped_ptr<Movie> moov)
119  : options_(options),
120  ftyp_(ftyp.Pass()),
121  moov_(moov.Pass()),
122  moof_(new MovieFragment()),
123  fragment_buffer_(new BufferWriter()),
124  sidx_(new SegmentIndex()),
125  muxer_listener_(NULL),
126  progress_listener_(NULL),
127  progress_target_(0),
128  accumulated_progress_(0),
129  sample_duration_(0u) {}
130 
131 Segmenter::~Segmenter() { STLDeleteElements(&fragmenters_); }
132 
133 Status Segmenter::Initialize(const std::vector<MediaStream*>& streams,
134  MuxerListener* muxer_listener,
135  ProgressListener* progress_listener,
136  KeySource* encryption_key_source,
137  uint32_t max_sd_pixels,
138  double clear_lead_in_seconds,
139  double crypto_period_duration_in_seconds,
140  EncryptionMode encryption_mode) {
141  DCHECK_LT(0u, streams.size());
142  muxer_listener_ = muxer_listener;
143  progress_listener_ = progress_listener;
144  moof_->header.sequence_number = 0;
145 
146  moof_->tracks.resize(streams.size());
147  segment_durations_.resize(streams.size());
148  fragmenters_.resize(streams.size());
149  const bool key_rotation_enabled = crypto_period_duration_in_seconds != 0;
150  const bool kInitialEncryptionInfo = true;
151 
152  for (uint32_t i = 0; i < streams.size(); ++i) {
153  stream_map_[streams[i]] = i;
154  moof_->tracks[i].header.track_id = i + 1;
155  if (streams[i]->info()->stream_type() == kStreamVideo) {
156  // Use the first video stream as the reference stream (which is 1-based).
157  if (sidx_->reference_id == 0)
158  sidx_->reference_id = i + 1;
159  }
160  if (!encryption_key_source) {
161  fragmenters_[i] = new Fragmenter(&moof_->tracks[i]);
162  continue;
163  }
164 
165  KeySource::TrackType track_type =
166  GetTrackTypeForEncryption(*streams[i]->info(), max_sd_pixels);
167  SampleDescription& description =
168  moov_->tracks[i].media.information.sample_table.description;
169 
170  if (key_rotation_enabled) {
171  // Fill encrypted sample entry with default key.
172  EncryptionKey encryption_key;
173  encryption_key.key_id.assign(
174  kKeyRotationDefaultKeyId,
175  kKeyRotationDefaultKeyId + arraysize(kKeyRotationDefaultKeyId));
176  GenerateEncryptedSampleEntry(encryption_key, clear_lead_in_seconds,
177  encryption_mode, &description);
178  if (muxer_listener_) {
179  muxer_listener_->OnEncryptionInfoReady(
180  kInitialEncryptionInfo, encryption_key.key_id,
181  encryption_key.key_system_info);
182  }
183 
184  fragmenters_[i] = new KeyRotationFragmenter(
185  moof_.get(), streams[i]->info(), &moof_->tracks[i],
186  encryption_key_source, track_type,
187  crypto_period_duration_in_seconds * streams[i]->info()->time_scale(),
188  clear_lead_in_seconds * streams[i]->info()->time_scale(),
189  muxer_listener_, encryption_mode);
190  continue;
191  }
192 
193  scoped_ptr<EncryptionKey> encryption_key(new EncryptionKey());
194  Status status =
195  encryption_key_source->GetKey(track_type, encryption_key.get());
196  if (!status.ok())
197  return status;
198 
199  GenerateEncryptedSampleEntry(*encryption_key, clear_lead_in_seconds,
200  encryption_mode, &description);
201 
202  if (moov_->pssh.empty()) {
203  moov_->pssh.resize(encryption_key->key_system_info.size());
204  for (size_t i = 0; i < encryption_key->key_system_info.size(); i++) {
205  moov_->pssh[i].raw_box = encryption_key->key_system_info[i].CreateBox();
206  }
207 
208  if (muxer_listener_) {
209  muxer_listener_->OnEncryptionInfoReady(kInitialEncryptionInfo,
210  encryption_key->key_id,
211  encryption_key->key_system_info);
212  }
213  }
214 
215  fragmenters_[i] = new EncryptingFragmenter(
216  streams[i]->info(), &moof_->tracks[i], encryption_key.Pass(),
217  clear_lead_in_seconds * streams[i]->info()->time_scale(),
218  encryption_mode);
219  }
220 
221  // Choose the first stream if there is no VIDEO.
222  if (sidx_->reference_id == 0)
223  sidx_->reference_id = 1;
224  sidx_->timescale = streams[GetReferenceStreamId()]->info()->time_scale();
225 
226  // Use media duration as progress target.
227  progress_target_ = streams[GetReferenceStreamId()]->info()->duration();
228 
229  // Use the reference stream's time scale as movie time scale.
230  moov_->header.timescale = sidx_->timescale;
231  moof_->header.sequence_number = 1;
232 
233  // Fill in version information.
234  moov_->metadata.handler.handler_type = FOURCC_ID32;
235  moov_->metadata.id3v2.language.code = "eng";
236  moov_->metadata.id3v2.private_frame.owner =
237  "https://github.com/google/edash-packager";
238  moov_->metadata.id3v2.private_frame.value = options_.packager_version_string;
239  return DoInitialize();
240 }
241 
243  for (std::vector<Fragmenter*>::iterator it = fragmenters_.begin();
244  it != fragmenters_.end();
245  ++it) {
246  Status status = FinalizeFragment(true, *it);
247  if (!status.ok())
248  return status;
249  }
250 
251  // Set tracks and moov durations.
252  // Note that the updated moov box will be written to output file for VOD case
253  // only.
254  for (std::vector<Track>::iterator track = moov_->tracks.begin();
255  track != moov_->tracks.end();
256  ++track) {
257  track->header.duration = Rescale(track->media.header.duration,
258  track->media.header.timescale,
259  moov_->header.timescale);
260  if (track->header.duration > moov_->header.duration)
261  moov_->header.duration = track->header.duration;
262  }
263  moov_->extends.header.fragment_duration = moov_->header.duration;
264 
265  return DoFinalize();
266 }
267 
269  scoped_refptr<MediaSample> sample) {
270  // Find the fragmenter for this stream.
271  DCHECK(stream);
272  DCHECK(stream_map_.find(stream) != stream_map_.end());
273  uint32_t stream_id = stream_map_[stream];
274  Fragmenter* fragmenter = fragmenters_[stream_id];
275 
276  // Set default sample duration if it has not been set yet.
277  if (moov_->extends.tracks[stream_id].default_sample_duration == 0) {
278  moov_->extends.tracks[stream_id].default_sample_duration =
279  sample->duration();
280  }
281 
282  if (fragmenter->fragment_finalized()) {
283  return Status(error::FRAGMENT_FINALIZED,
284  "Current fragment is finalized already.");
285  }
286 
287  bool finalize_fragment = false;
288  if (fragmenter->fragment_duration() >=
289  options_.fragment_duration * stream->info()->time_scale()) {
290  if (sample->is_key_frame() || !options_.fragment_sap_aligned) {
291  finalize_fragment = true;
292  }
293  }
294  bool finalize_segment = false;
295  if (segment_durations_[stream_id] >=
296  options_.segment_duration * stream->info()->time_scale()) {
297  if (sample->is_key_frame() || !options_.segment_sap_aligned) {
298  finalize_segment = true;
299  finalize_fragment = true;
300  }
301  }
302 
303  Status status;
304  if (finalize_fragment) {
305  status = FinalizeFragment(finalize_segment, fragmenter);
306  if (!status.ok())
307  return status;
308  }
309 
310  status = fragmenter->AddSample(sample);
311  if (!status.ok())
312  return status;
313 
314  if (sample_duration_ == 0)
315  sample_duration_ = sample->duration();
316  moov_->tracks[stream_id].media.header.duration += sample->duration();
317  segment_durations_[stream_id] += sample->duration();
318  DCHECK_GE(segment_durations_[stream_id], fragmenter->fragment_duration());
319  return Status::OK;
320 }
321 
322 uint32_t Segmenter::GetReferenceTimeScale() const {
323  return moov_->header.timescale;
324 }
325 
326 double Segmenter::GetDuration() const {
327  if (moov_->header.timescale == 0) {
328  // Handling the case where this is not properly initialized.
329  return 0.0;
330  }
331 
332  return static_cast<double>(moov_->header.duration) / moov_->header.timescale;
333 }
334 
335 void Segmenter::UpdateProgress(uint64_t progress) {
336  accumulated_progress_ += progress;
337 
338  if (!progress_listener_) return;
339  if (progress_target_ == 0) return;
340  // It might happen that accumulated progress exceeds progress_target due to
341  // computation errors, e.g. rounding error. Cap it so it never reports > 100%
342  // progress.
343  if (accumulated_progress_ >= progress_target_) {
344  progress_listener_->OnProgress(1.0);
345  } else {
346  progress_listener_->OnProgress(static_cast<double>(accumulated_progress_) /
347  progress_target_);
348  }
349 }
350 
351 void Segmenter::SetComplete() {
352  if (!progress_listener_) return;
353  progress_listener_->OnProgress(1.0);
354 }
355 
356 Status Segmenter::FinalizeSegment() {
357  Status status = DoFinalizeSegment();
358 
359  // Reset segment information to initial state.
360  sidx_->references.clear();
361  std::vector<uint64_t>::iterator it = segment_durations_.begin();
362  for (; it != segment_durations_.end(); ++it)
363  *it = 0;
364 
365  return status;
366 }
367 
368 uint32_t Segmenter::GetReferenceStreamId() {
369  DCHECK(sidx_);
370  return sidx_->reference_id - 1;
371 }
372 
373 Status Segmenter::FinalizeFragment(bool finalize_segment,
374  Fragmenter* fragmenter) {
375  fragmenter->FinalizeFragment();
376 
377  // Check if all tracks are ready for fragmentation.
378  for (std::vector<Fragmenter*>::iterator it = fragmenters_.begin();
379  it != fragmenters_.end();
380  ++it) {
381  if (!(*it)->fragment_finalized())
382  return Status::OK;
383  }
384 
385  MediaData mdat;
386  // Data offset relative to 'moof': moof size + mdat header size.
387  // The code will also update box sizes for moof_ and its child boxes.
388  uint64_t data_offset = moof_->ComputeSize() + mdat.HeaderSize();
389  // 'traf' should follow 'mfhd' moof header box.
390  uint64_t next_traf_position = moof_->HeaderSize() + moof_->header.box_size();
391  for (size_t i = 0; i < moof_->tracks.size(); ++i) {
392  TrackFragment& traf = moof_->tracks[i];
393  if (traf.auxiliary_offset.offsets.size() > 0) {
394  DCHECK_EQ(traf.auxiliary_offset.offsets.size(), 1u);
395  DCHECK(!traf.sample_encryption.sample_encryption_entries.empty());
396 
397  next_traf_position += traf.box_size();
398  // SampleEncryption 'senc' box should be the last box in 'traf'.
399  // |auxiliary_offset| should point to the data of SampleEncryption.
400  traf.auxiliary_offset.offsets[0] =
401  next_traf_position - traf.sample_encryption.box_size() +
402  traf.sample_encryption.HeaderSize() +
403  sizeof(uint32_t); // for sample count field in 'senc'
404  }
405  traf.runs[0].data_offset = data_offset + mdat.data_size;
406  mdat.data_size += fragmenters_[i]->data()->Size();
407  }
408 
409  // Generate segment reference.
410  sidx_->references.resize(sidx_->references.size() + 1);
411  fragmenters_[GetReferenceStreamId()]->GenerateSegmentReference(
412  &sidx_->references[sidx_->references.size() - 1]);
413  sidx_->references[sidx_->references.size() - 1].referenced_size =
414  data_offset + mdat.data_size;
415 
416  // Write the fragment to buffer.
417  moof_->Write(fragment_buffer_.get());
418  mdat.WriteHeader(fragment_buffer_.get());
419  for (Fragmenter* fragmenter : fragmenters_)
420  fragment_buffer_->AppendBuffer(*fragmenter->data());
421 
422  // Increase sequence_number for next fragment.
423  ++moof_->header.sequence_number;
424 
425  if (finalize_segment)
426  return FinalizeSegment();
427 
428  return Status::OK;
429 }
430 
431 } // namespace mp4
432 } // namespace media
433 } // namespace edash_packager
virtual Status GetKey(TrackType track_type, EncryptionKey *key)=0
Status Initialize(scoped_ptr< MkvWriter > writer, StreamInfo *info, ProgressListener *progress_listener, MuxerListener *muxer_listener, KeySource *encryption_key_source, uint32_t max_sd_pixels, double clear_lead_in_seconds)
Definition: segmenter.cc:46
std::string packager_version_string
Specify the version string to be embedded in the output files.
Definition: muxer_options.h:71
This class listens to progress updates events.
EncryptingFragmenter generates MP4 fragments with sample encrypted.
virtual Status AddSample(scoped_refptr< MediaSample > sample)
Definition: fragmenter.cc:36
KeySource is responsible for encryption key acquisition.
Definition: key_source.h:31
virtual void OnProgress(double progress)=0
void UpdateProgress(uint64_t progress)
Update segmentation progress using ProgressListener.
Definition: segmenter.cc:247
Status AddSample(scoped_refptr< MediaSample > sample)
Definition: segmenter.cc:112