DASH Media Packaging SDK
 All Classes Namespaces Functions Variables Typedefs Enumerator
encrypting_fragmenter.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/encrypting_fragmenter.h"
8 
9 #include <limits>
10 
11 #include "packager/media/base/aes_encryptor.h"
12 #include "packager/media/base/aes_pattern_cryptor.h"
13 #include "packager/media/base/buffer_reader.h"
14 #include "packager/media/base/key_source.h"
15 #include "packager/media/base/media_sample.h"
16 #include "packager/media/filters/nalu_reader.h"
17 #include "packager/media/filters/vp8_parser.h"
18 #include "packager/media/filters/vp9_parser.h"
19 #include "packager/media/formats/mp4/box_definitions.h"
20 
21 namespace edash_packager {
22 namespace media {
23 namespace mp4 {
24 
25 namespace {
26 const size_t kCencBlockSize = 16u;
27 
28 // Adds one or more subsamples to |*subsamples|. This may add more than one
29 // if one of the values overflows the integer in the subsample.
30 void AddSubsamples(uint64_t clear_bytes,
31  uint64_t cipher_bytes,
32  std::vector<SubsampleEntry>* subsamples) {
33  CHECK_LT(cipher_bytes, std::numeric_limits<uint32_t>::max());
34  const uint64_t kUInt16Max = std::numeric_limits<uint16_t>::max();
35  while (clear_bytes > kUInt16Max) {
36  subsamples->push_back(SubsampleEntry(kUInt16Max, 0));
37  clear_bytes -= kUInt16Max;
38  }
39 
40  if (clear_bytes > 0 || cipher_bytes > 0)
41  subsamples->push_back(SubsampleEntry(clear_bytes, cipher_bytes));
42 }
43 
44 VideoCodec GetVideoCodec(const StreamInfo& stream_info) {
45  if (stream_info.stream_type() != kStreamVideo)
46  return kUnknownVideoCodec;
47  const VideoStreamInfo& video_stream_info =
48  static_cast<const VideoStreamInfo&>(stream_info);
49  return video_stream_info.codec();
50 }
51 
52 uint8_t GetNaluLengthSize(const StreamInfo& stream_info) {
53  if (stream_info.stream_type() != kStreamVideo)
54  return 0;
55 
56  const VideoStreamInfo& video_stream_info =
57  static_cast<const VideoStreamInfo&>(stream_info);
58  return video_stream_info.nalu_length_size();
59 }
60 } // namespace
61 
63  scoped_refptr<StreamInfo> info,
64  TrackFragment* traf,
65  scoped_ptr<EncryptionKey> encryption_key,
66  int64_t clear_time,
67  FourCC protection_scheme,
68  uint8_t crypt_byte_block,
69  uint8_t skip_byte_block)
70  : Fragmenter(traf),
71  info_(info),
72  encryption_key_(encryption_key.Pass()),
73  nalu_length_size_(GetNaluLengthSize(*info)),
74  video_codec_(GetVideoCodec(*info)),
75  clear_time_(clear_time),
76  protection_scheme_(protection_scheme),
77  crypt_byte_block_(crypt_byte_block),
78  skip_byte_block_(skip_byte_block) {
79  DCHECK(encryption_key_);
80  switch (video_codec_) {
81  case kCodecVP8:
82  vpx_parser_.reset(new VP8Parser);
83  break;
84  case kCodecVP9:
85  vpx_parser_.reset(new VP9Parser);
86  break;
87  case kCodecH264:
88  header_parser_.reset(new H264VideoSliceHeaderParser);
89  break;
90  case kCodecHVC1:
91  FALLTHROUGH_INTENDED;
92  case kCodecHEV1:
93  header_parser_.reset(new H265VideoSliceHeaderParser);
94  break;
95  default:
96  LOG(WARNING) << "Unknown video codec '" << video_codec_
97  << "', whole subsamples will be encrypted.";
98  }
99 }
100 
101 EncryptingFragmenter::~EncryptingFragmenter() {}
102 
103 Status EncryptingFragmenter::AddSample(scoped_refptr<MediaSample> sample) {
104  DCHECK(sample);
105  if (!fragment_initialized()) {
106  Status status = InitializeFragment(sample->dts());
107  if (!status.ok())
108  return status;
109  }
110  if (encryptor_) {
111  Status status = EncryptSample(sample);
112  if (!status.ok())
113  return status;
114  }
115  return Fragmenter::AddSample(sample);
116 }
117 
119  Status status = Fragmenter::InitializeFragment(first_sample_dts);
120  if (!status.ok())
121  return status;
122 
123  if (header_parser_ && !header_parser_->Initialize(info_->extra_data()))
124  return Status(error::MUXER_FAILURE, "Fail to read SPS and PPS data.");
125 
126  traf()->auxiliary_size.sample_info_sizes.clear();
127  traf()->auxiliary_offset.offsets.clear();
128  if (IsSubsampleEncryptionRequired()) {
129  traf()->sample_encryption.flags |=
130  SampleEncryption::kUseSubsampleEncryption;
131  }
132  traf()->sample_encryption.sample_encryption_entries.clear();
133 
134  const bool enable_encryption = clear_time_ <= 0;
135  if (!enable_encryption) {
136  // This fragment should be in clear text.
137  // At most two sample description entries, an encrypted entry and a clear
138  // entry, are generated. The 1-based clear entry index is always 2.
139  const uint32_t kClearSampleDescriptionIndex = 2;
140 
141  traf()->header.flags |=
142  TrackFragmentHeader::kSampleDescriptionIndexPresentMask;
143  traf()->header.sample_description_index = kClearSampleDescriptionIndex;
144  }
145  return PrepareFragmentForEncryption(enable_encryption);
146 }
147 
149  if (encryptor_) {
150  DCHECK_LE(clear_time_, 0);
152  } else {
153  DCHECK_GT(clear_time_, 0);
154  clear_time_ -= fragment_duration();
155  }
157 }
158 
160  bool enable_encryption) {
161  return (!enable_encryption || encryptor_) ? Status::OK : CreateEncryptor();
162 }
163 
165  // The offset will be adjusted in Segmenter after knowing moof size.
166  traf()->auxiliary_offset.offsets.push_back(0);
167 
168  // For 'cbcs' scheme, Constant IVs SHALL be used.
169  const size_t per_sample_iv_size =
170  (protection_scheme_ == FOURCC_cbcs) ? 0 : encryptor_->iv().size();
171  traf()->sample_encryption.iv_size = per_sample_iv_size;
172 
173  // Optimize saiz box.
174  SampleAuxiliaryInformationSize& saiz = traf()->auxiliary_size;
175  saiz.sample_count = traf()->runs[0].sample_sizes.size();
176  if (!saiz.sample_info_sizes.empty()) {
177  if (!OptimizeSampleEntries(&saiz.sample_info_sizes,
178  &saiz.default_sample_info_size)) {
179  saiz.default_sample_info_size = 0;
180  }
181  } else {
182  // |sample_info_sizes| table is filled in only for subsample encryption,
183  // otherwise |sample_info_size| is just the IV size.
184  DCHECK(!IsSubsampleEncryptionRequired());
185  saiz.default_sample_info_size = per_sample_iv_size;
186  }
187  // It should only happen with full sample encryption + constant iv, which is
188  // not a legal combination.
189  CHECK(!saiz.sample_info_sizes.empty() || saiz.default_sample_info_size != 0);
190 }
191 
193  DCHECK(encryption_key_);
194  scoped_ptr<AesCryptor> encryptor;
195  switch (protection_scheme_) {
196  case FOURCC_cenc:
197  encryptor.reset(new AesCtrEncryptor);
198  break;
199  case FOURCC_cbc1:
200  encryptor.reset(new AesCbcEncryptor(kNoPadding, kChainAcrossCalls));
201  break;
202  case FOURCC_cens:
203  encryptor.reset(
204  new AesPatternCryptor(crypt_byte_block(), skip_byte_block(),
205  AesPatternCryptor::kDontUseConstantIv,
206  scoped_ptr<AesCryptor>(new AesCtrEncryptor)));
207  break;
208  case FOURCC_cbcs:
209  encryptor.reset(
210  new AesPatternCryptor(crypt_byte_block(), skip_byte_block(),
211  AesPatternCryptor::kUseConstantIv,
212  scoped_ptr<AesCryptor>(new AesCbcEncryptor(
213  kNoPadding, kChainAcrossCalls))));
214  break;
215  default:
216  return Status(error::MUXER_FAILURE, "Unsupported protection scheme.");
217  }
218 
219  DCHECK(!encryption_key_->iv.empty());
220  const bool initialized =
221  encryptor->InitializeWithIv(encryption_key_->key, encryption_key_->iv);
222  if (!initialized)
223  return Status(error::MUXER_FAILURE, "Failed to create the encryptor.");
224  encryptor_ = encryptor.Pass();
225  return Status::OK;
226 }
227 
228 void EncryptingFragmenter::EncryptBytes(uint8_t* data, uint32_t size) {
229  DCHECK(encryptor_);
230  CHECK(encryptor_->Crypt(data, size, data));
231 }
232 
233 Status EncryptingFragmenter::EncryptSample(scoped_refptr<MediaSample> sample) {
234  DCHECK(encryptor_);
235 
236  SampleEncryptionEntry sample_encryption_entry;
237  // For 'cbcs' scheme, Constant IVs SHALL be used.
238  if (protection_scheme_ != FOURCC_cbcs)
239  sample_encryption_entry.initialization_vector = encryptor_->iv();
240  uint8_t* data = sample->writable_data();
241  if (IsSubsampleEncryptionRequired()) {
242  if (vpx_parser_) {
243  std::vector<VPxFrameInfo> vpx_frames;
244  if (!vpx_parser_->Parse(sample->data(), sample->data_size(),
245  &vpx_frames)) {
246  return Status(error::MUXER_FAILURE, "Failed to parse vpx frame.");
247  }
248 
249  const bool is_superframe = vpx_frames.size() > 1;
250  for (const VPxFrameInfo& frame : vpx_frames) {
251  SubsampleEntry subsample;
252  subsample.clear_bytes = frame.uncompressed_header_size;
253  subsample.cipher_bytes =
254  frame.frame_size - frame.uncompressed_header_size;
255 
256  // "VP Codec ISO Media File Format Binding" document requires that the
257  // encrypted bytes of each frame within the superframe must be block
258  // aligned so that the counter state can be computed for each frame
259  // within the superframe.
260  // For AES-CBC mode 'cbc1' scheme, clear data is sized appropriately so
261  // that the cipher data is block aligned.
262  if (is_superframe || protection_scheme_ == FOURCC_cbc1) {
263  const uint16_t misalign_bytes =
264  subsample.cipher_bytes % kCencBlockSize;
265  subsample.clear_bytes += misalign_bytes;
266  subsample.cipher_bytes -= misalign_bytes;
267  }
268 
269  sample_encryption_entry.subsamples.push_back(subsample);
270  if (subsample.cipher_bytes > 0)
271  EncryptBytes(data + subsample.clear_bytes, subsample.cipher_bytes);
272  data += frame.frame_size;
273  }
274  } else {
275  const NaluReader::CodecType nalu_type =
276  (video_codec_ == kCodecHVC1 || video_codec_ == kCodecHEV1)
277  ? NaluReader::kH265
278  : NaluReader::kH264;
279  NaluReader reader(nalu_type, nalu_length_size_, data,
280  sample->data_size());
281 
282  // Store the current length of clear data. This is used to squash
283  // multiple unencrypted NAL units into fewer subsample entries.
284  uint64_t accumulated_clear_bytes = 0;
285 
286  Nalu nalu;
287  NaluReader::Result result;
288  while ((result = reader.Advance(&nalu)) == NaluReader::kOk) {
289  if (nalu.is_video_slice()) {
290  // For video-slice NAL units, encrypt the video slice. This skips
291  // the frame header. If this is an unrecognized codec (e.g. H.265),
292  // the whole NAL unit will be encrypted.
293  const int64_t video_slice_header_size =
294  header_parser_ ? header_parser_->GetHeaderSize(nalu) : 0;
295  if (video_slice_header_size < 0)
296  return Status(error::MUXER_FAILURE, "Failed to read slice header.");
297 
298  uint64_t current_clear_bytes =
299  nalu.header_size() + video_slice_header_size;
300  uint64_t cipher_bytes = nalu.payload_size() - video_slice_header_size;
301 
302  // For AES-CBC mode 'cbc1' scheme, clear data is sized appropriately
303  // so that the cipher data is block aligned.
304  if (protection_scheme_ == FOURCC_cbc1) {
305  const uint16_t misalign_bytes = cipher_bytes % kCencBlockSize;
306  current_clear_bytes += misalign_bytes;
307  cipher_bytes -= misalign_bytes;
308  }
309 
310  const uint8_t* nalu_data = nalu.data() + current_clear_bytes;
311  EncryptBytes(const_cast<uint8_t*>(nalu_data), cipher_bytes);
312 
313  AddSubsamples(
314  accumulated_clear_bytes + nalu_length_size_ + current_clear_bytes,
315  cipher_bytes, &sample_encryption_entry.subsamples);
316  accumulated_clear_bytes = 0;
317  } else {
318  // For non-video-slice NAL units, don't encrypt.
319  accumulated_clear_bytes +=
320  nalu_length_size_ + nalu.header_size() + nalu.payload_size();
321  }
322  }
323  if (result != NaluReader::kEOStream)
324  return Status(error::MUXER_FAILURE, "Failed to parse NAL units.");
325  AddSubsamples(accumulated_clear_bytes, 0,
326  &sample_encryption_entry.subsamples);
327  }
328 
329  // The length of per-sample auxiliary datum, defined in CENC ch. 7.
330  traf()->auxiliary_size.sample_info_sizes.push_back(
331  sample_encryption_entry.ComputeSize());
332  } else {
333  uint64_t encryption_data_size = sample->data_size();
334  // AES-CBC mode requires all encrypted cipher blocks to be 16 bytes. The
335  // partial blocks are left unencrypted.
336  if (protection_scheme_ == FOURCC_cbc1)
337  encryption_data_size -= encryption_data_size % kCencBlockSize;
338  EncryptBytes(data, encryption_data_size);
339  }
340 
341  traf()->sample_encryption.sample_encryption_entries.push_back(
342  sample_encryption_entry);
343  encryptor_->UpdateIv();
344  return Status::OK;
345 }
346 
347 bool EncryptingFragmenter::IsSubsampleEncryptionRequired() {
348  return vpx_parser_ || nalu_length_size_ != 0;
349 }
350 
351 } // namespace mp4
352 } // namespace media
353 } // namespace edash_packager
Status InitializeFragment(int64_t first_sample_dts) override
virtual Status InitializeFragment(int64_t first_sample_dts)
Definition: fragmenter.cc:76
virtual Status AddSample(scoped_refptr< MediaSample > sample)
Definition: fragmenter.cc:36
Class to parse a vp9 bit stream.
Definition: vp9_parser.h:20
bool OptimizeSampleEntries(std::vector< T > *entries, T *default_value)
Definition: fragmenter.h:89
Status AddSample(scoped_refptr< MediaSample > sample) override
Implements pattern-based encryption/decryption.
void FinalizeFragment() override
Finalize and optimize the fragment.
virtual Status PrepareFragmentForEncryption(bool enable_encryption)
virtual void FinalizeFragmentForEncryption()
Finalize current fragment for encryption.
virtual void FinalizeFragment()
Finalize and optimize the fragment.
Definition: fragmenter.cc:93
EncryptingFragmenter(scoped_refptr< StreamInfo > info, TrackFragment *traf, scoped_ptr< EncryptionKey > encryption_key, int64_t clear_time, FourCC protection_scheme, uint8_t crypt_byte_block, uint8_t skip_byte_block)