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