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