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