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