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