Shaka Packager SDK
subsample_generator.cc
1 // Copyright 2018 Google LLC. 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/crypto/subsample_generator.h>
8 
9 #include <algorithm>
10 #include <limits>
11 
12 #include <absl/log/check.h>
13 
14 #include <packager/macros/compiler.h>
15 #include <packager/media/base/decrypt_config.h>
16 #include <packager/media/base/video_stream_info.h>
17 #include <packager/media/codecs/av1_parser.h>
18 #include <packager/media/codecs/video_slice_header_parser.h>
19 #include <packager/media/codecs/vp8_parser.h>
20 #include <packager/media/codecs/vp9_parser.h>
21 
22 namespace shaka {
23 namespace media {
24 namespace {
25 
26 const size_t kAesBlockSize = 16u;
27 
28 uint8_t GetNaluLengthSize(const StreamInfo& stream_info) {
29  if (stream_info.stream_type() != kStreamVideo)
30  return 0;
31 
32  const VideoStreamInfo& video_stream_info =
33  static_cast<const VideoStreamInfo&>(stream_info);
34  return video_stream_info.nalu_length_size();
35 }
36 
37 bool ShouldAlignProtectedData(Codec codec,
38  FourCC protection_scheme,
39  bool vp9_subsample_encryption) {
40  switch (codec) {
41  case kCodecVP9:
42  // "VP Codec ISO Media File Format Binding" document requires that the
43  // encrypted bytes of each frame within the superframe must be block
44  // aligned so that the counter state can be computed for each frame
45  // within the superframe.
46  // ISO/IEC 23001-7:2016 10.2 'cbc1' 10.3 'cens'
47  // The BytesOfProtectedData size SHALL be a multiple of 16 bytes to
48  // avoid partial blocks in Subsamples.
49  // For consistency, apply block alignment to all frames when VP9 subsample
50  // encryption is enabled.
51  return vp9_subsample_encryption;
52  default:
53  // ISO/IEC 23001-7:2016 10.2 'cbc1' 10.3 'cens'
54  // The BytesOfProtectedData size SHALL be a multiple of 16 bytes to avoid
55  // partial blocks in Subsamples.
56  // CMAF requires 'cenc' scheme BytesOfProtectedData SHALL be a multiple of
57  // 16 bytes; while 'cbcs' scheme BytesOfProtectedData SHALL start on the
58  // first byte of video data following the slice header.
59  // https://aomediacodec.github.io/av1-isobmff/#subsample-encryption AV1
60  // has a similar clause.
61  return protection_scheme == FOURCC_cbc1 ||
62  protection_scheme == FOURCC_cens ||
63  protection_scheme == FOURCC_cenc;
64  }
65 }
66 
67 // A convenient util class to organize subsamples, e.g. combine consecutive
68 // subsamples with only clear bytes, split subsamples if the clear bytes exceeds
69 // 2^16 etc.
70 class SubsampleOrganizer {
71  public:
72  SubsampleOrganizer(bool align_protected_data,
73  std::vector<SubsampleEntry>* subsamples)
74  : align_protected_data_(align_protected_data), subsamples_(subsamples) {}
75 
76  ~SubsampleOrganizer() {
77  if (accumulated_clear_bytes_ > 0) {
78  PushSubsample(accumulated_clear_bytes_, 0);
79  accumulated_clear_bytes_ = 0;
80  }
81  }
82 
83  void AddSubsample(size_t clear_bytes, size_t cipher_bytes) {
84  DCHECK_LT(clear_bytes, std::numeric_limits<uint32_t>::max());
85  DCHECK_LT(cipher_bytes, std::numeric_limits<uint32_t>::max());
86 
87  if (align_protected_data_ && cipher_bytes != 0) {
88  const size_t misalign_bytes = cipher_bytes % kAesBlockSize;
89  clear_bytes += misalign_bytes;
90  cipher_bytes -= misalign_bytes;
91  }
92 
93  accumulated_clear_bytes_ += clear_bytes;
94  // Accumulated clear bytes are handled later.
95  if (cipher_bytes == 0)
96  return;
97 
98  PushSubsample(accumulated_clear_bytes_, cipher_bytes);
99  accumulated_clear_bytes_ = 0;
100  }
101 
102  private:
103  SubsampleOrganizer(const SubsampleOrganizer&) = delete;
104  SubsampleOrganizer& operator=(const SubsampleOrganizer&) = delete;
105 
106  void PushSubsample(size_t clear_bytes, size_t cipher_bytes) {
107  const uint16_t kUInt16Max = std::numeric_limits<uint16_t>::max();
108  while (clear_bytes > kUInt16Max) {
109  subsamples_->emplace_back(kUInt16Max, 0);
110  clear_bytes -= kUInt16Max;
111  }
112  subsamples_->emplace_back(static_cast<uint16_t>(clear_bytes),
113  static_cast<uint32_t>(cipher_bytes));
114  }
115 
116  const bool align_protected_data_ = false;
117  std::vector<SubsampleEntry>* const subsamples_ = nullptr;
118  size_t accumulated_clear_bytes_ = 0;
119 };
120 
121 } // namespace
122 
123 SubsampleGenerator::SubsampleGenerator(bool vp9_subsample_encryption)
124  : vp9_subsample_encryption_(vp9_subsample_encryption) {}
125 
126 SubsampleGenerator::~SubsampleGenerator() {}
127 
128 Status SubsampleGenerator::Initialize(FourCC protection_scheme,
129  const StreamInfo& stream_info) {
130  codec_ = stream_info.codec();
131  nalu_length_size_ = GetNaluLengthSize(stream_info);
132 
133  switch (codec_) {
134  case kCodecAV1:
135  av1_parser_.reset(new AV1Parser);
136  break;
137  case kCodecVP9:
138  if (vp9_subsample_encryption_)
139  vpx_parser_.reset(new VP9Parser);
140  break;
141  case kCodecH264:
142  header_parser_.reset(new H264VideoSliceHeaderParser);
143  break;
144  case kCodecH265:
145  case kCodecH265DolbyVision:
146  header_parser_.reset(new H265VideoSliceHeaderParser);
147  break;
148  default:
149  // Other codecs should have nalu length size == 0.
150  if (nalu_length_size_ > 0) {
151  LOG(WARNING) << "Unknown video codec '" << codec_ << "'";
152  return Status(error::ENCRYPTION_FAILURE, "Unknown video codec.");
153  }
154  }
155  if (av1_parser_) {
156  // Parse configOBUs in AV1CodecConfigurationRecord if exists.
157  // https://aomediacodec.github.io/av1-isobmff/#av1codecconfigurationbox-syntax.
158  const size_t kConfigOBUsOffset = 4;
159  const bool has_config_obus =
160  stream_info.codec_config().size() > kConfigOBUsOffset;
161  std::vector<AV1Parser::Tile> tiles;
162  if (has_config_obus &&
163  !av1_parser_->Parse(
164  &stream_info.codec_config()[kConfigOBUsOffset],
165  stream_info.codec_config().size() - kConfigOBUsOffset, &tiles)) {
166  return Status(
167  error::ENCRYPTION_FAILURE,
168  "Failed to parse configOBUs in AV1CodecConfigurationRecord.");
169  }
170  DCHECK(tiles.empty());
171  }
172  if (header_parser_) {
173  CHECK_NE(nalu_length_size_, 0u) << "AnnexB stream is not supported yet";
174  if (!header_parser_->Initialize(stream_info.codec_config())) {
175  return Status(error::ENCRYPTION_FAILURE,
176  "Failed to read SPS and PPS data.");
177  }
178  }
179 
180  align_protected_data_ = ShouldAlignProtectedData(codec_, protection_scheme,
181  vp9_subsample_encryption_);
182 
183  if (protection_scheme == kAppleSampleAesProtectionScheme) {
184  const size_t kH264LeadingClearBytesSize = 32u;
185  const size_t kAudioLeadingClearBytesSize = 16u;
186  switch (codec_) {
187  case kCodecH264:
188  leading_clear_bytes_size_ = kH264LeadingClearBytesSize;
189  min_protected_data_size_ =
190  leading_clear_bytes_size_ + kAesBlockSize + 1u;
191  break;
192  case kCodecAAC:
193  FALLTHROUGH_INTENDED;
194  case kCodecAC3:
195  leading_clear_bytes_size_ = kAudioLeadingClearBytesSize;
196  min_protected_data_size_ = leading_clear_bytes_size_ + kAesBlockSize;
197  break;
198  case kCodecEAC3:
199  // E-AC3 encryption is handled by SampleAesEc3Cryptor, which also
200  // manages leading clear bytes.
201  leading_clear_bytes_size_ = 0;
202  min_protected_data_size_ = leading_clear_bytes_size_ + kAesBlockSize;
203  break;
204  default:
205  LOG(ERROR) << "Unexpected codec for SAMPLE-AES " << codec_;
206  return Status(error::ENCRYPTION_FAILURE,
207  "Unexpected codec for SAMPLE-AES.");
208  }
209  }
210  return Status::OK;
211 }
212 
214  const uint8_t* frame,
215  size_t frame_size,
216  std::vector<SubsampleEntry>* subsamples) {
217  subsamples->clear();
218  switch (codec_) {
219  case kCodecAV1:
220  return GenerateSubsamplesFromAV1Frame(frame, frame_size, subsamples);
221  case kCodecH264:
222  FALLTHROUGH_INTENDED;
223  case kCodecH265:
224  case kCodecH265DolbyVision:
225  return GenerateSubsamplesFromH26xFrame(frame, frame_size, subsamples);
226  case kCodecVP9:
227  if (vp9_subsample_encryption_)
228  return GenerateSubsamplesFromVPxFrame(frame, frame_size, subsamples);
229  // Full sample encrypted so no subsamples.
230  break;
231  default:
232  // Other codecs are full sample encrypted unless there are clear leading
233  // bytes.
234  if (leading_clear_bytes_size_ > 0) {
235  SubsampleOrganizer subsample_organizer(align_protected_data_,
236  subsamples);
237  const size_t clear_bytes =
238  std::min(frame_size, leading_clear_bytes_size_);
239  const size_t cipher_bytes = frame_size - clear_bytes;
240  subsample_organizer.AddSubsample(clear_bytes, cipher_bytes);
241  } else {
242  // Full sample encrypted so no subsamples.
243  }
244  break;
245  }
246  return Status::OK;
247 }
248 
249 void SubsampleGenerator::InjectVpxParserForTesting(
250  std::unique_ptr<VPxParser> vpx_parser) {
251  vpx_parser_ = std::move(vpx_parser);
252 }
253 
254 void SubsampleGenerator::InjectVideoSliceHeaderParserForTesting(
255  std::unique_ptr<VideoSliceHeaderParser> header_parser) {
256  header_parser_ = std::move(header_parser);
257 }
258 
259 void SubsampleGenerator::InjectAV1ParserForTesting(
260  std::unique_ptr<AV1Parser> av1_parser) {
261  av1_parser_ = std::move(av1_parser);
262 }
263 
264 Status SubsampleGenerator::GenerateSubsamplesFromVPxFrame(
265  const uint8_t* frame,
266  size_t frame_size,
267  std::vector<SubsampleEntry>* subsamples) {
268  DCHECK(vpx_parser_);
269  std::vector<VPxFrameInfo> vpx_frames;
270  if (!vpx_parser_->Parse(frame, frame_size, &vpx_frames))
271  return Status(error::ENCRYPTION_FAILURE, "Failed to parse vpx frame.");
272 
273  SubsampleOrganizer subsample_organizer(align_protected_data_, subsamples);
274 
275  size_t total_size = 0;
276  for (const VPxFrameInfo& vpx_frame : vpx_frames) {
277  subsample_organizer.AddSubsample(
278  vpx_frame.uncompressed_header_size,
279  vpx_frame.frame_size - vpx_frame.uncompressed_header_size);
280  total_size += vpx_frame.frame_size;
281  }
282  // Add subsample for the superframe index if exists.
283  const bool is_superframe = vpx_frames.size() > 1;
284  if (is_superframe) {
285  const size_t index_size = frame_size - total_size;
286  DCHECK_LE(index_size, 2 + vpx_frames.size() * 4);
287  DCHECK_GE(index_size, 2 + vpx_frames.size() * 1);
288  subsample_organizer.AddSubsample(index_size, 0);
289  } else {
290  DCHECK_EQ(total_size, frame_size);
291  }
292  return Status::OK;
293 }
294 
295 Status SubsampleGenerator::GenerateSubsamplesFromH26xFrame(
296  const uint8_t* frame,
297  size_t frame_size,
298  std::vector<SubsampleEntry>* subsamples) {
299  DCHECK_NE(nalu_length_size_, 0u);
300  DCHECK(header_parser_);
301 
302  SubsampleOrganizer subsample_organizer(align_protected_data_, subsamples);
303 
304  const Nalu::CodecType nalu_type =
305  (codec_ == kCodecH265 || codec_ == kCodecH265DolbyVision) ? Nalu::kH265
306  : Nalu::kH264;
307  NaluReader reader(nalu_type, nalu_length_size_, frame, frame_size);
308 
309  Nalu nalu;
310  NaluReader::Result result;
311  while ((result = reader.Advance(&nalu)) == NaluReader::kOk) {
312  // |header_parser_| is only used if |leading_clear_bytes_size_| is not
313  // availble. See lines below.
314  if (leading_clear_bytes_size_ == 0 && !header_parser_->ProcessNalu(nalu)) {
315  LOG(ERROR) << "Failed to process NAL unit: NAL type = " << nalu.type();
316  return Status(error::ENCRYPTION_FAILURE, "Failed to process NAL unit.");
317  }
318 
319  const size_t nalu_total_size = nalu.header_size() + nalu.payload_size();
320  size_t clear_bytes = 0;
321  if (nalu.is_video_slice() && nalu_total_size >= min_protected_data_size_) {
322  clear_bytes = leading_clear_bytes_size_;
323  if (clear_bytes == 0) {
324  // For video-slice NAL units, encrypt the video slice. This skips
325  // the frame header.
326  const int64_t video_slice_header_size =
327  header_parser_->GetHeaderSize(nalu);
328  if (video_slice_header_size < 0) {
329  LOG(ERROR) << "Failed to read slice header.";
330  return Status(error::ENCRYPTION_FAILURE,
331  "Failed to read slice header.");
332  }
333  clear_bytes = nalu.header_size() + video_slice_header_size;
334  }
335  } else {
336  // For non-video-slice or small NAL units, don't encrypt.
337  clear_bytes = nalu_total_size;
338  }
339  const size_t cipher_bytes = nalu_total_size - clear_bytes;
340  subsample_organizer.AddSubsample(nalu_length_size_ + clear_bytes,
341  cipher_bytes);
342  }
343  if (result != NaluReader::kEOStream) {
344  LOG(ERROR) << "Failed to parse NAL units.";
345  return Status(error::ENCRYPTION_FAILURE, "Failed to parse NAL units.");
346  }
347  return Status::OK;
348 }
349 
350 Status SubsampleGenerator::GenerateSubsamplesFromAV1Frame(
351  const uint8_t* frame,
352  size_t frame_size,
353  std::vector<SubsampleEntry>* subsamples) {
354  DCHECK(av1_parser_);
355  std::vector<AV1Parser::Tile> av1_tiles;
356  if (!av1_parser_->Parse(frame, frame_size, &av1_tiles))
357  return Status(error::ENCRYPTION_FAILURE, "Failed to parse AV1 frame.");
358 
359  SubsampleOrganizer subsample_organizer(align_protected_data_, subsamples);
360 
361  size_t last_tile_end_offset = 0;
362  for (const AV1Parser::Tile& tile : av1_tiles) {
363  DCHECK_LE(last_tile_end_offset, tile.start_offset_in_bytes);
364  // Per AV1 in ISO-BMFF spec [1], only decode_tile is encrypted.
365  // [1] https://aomediacodec.github.io/av1-isobmff/#subsample-encryption
366  subsample_organizer.AddSubsample(
367  tile.start_offset_in_bytes - last_tile_end_offset, tile.size_in_bytes);
368  last_tile_end_offset = tile.start_offset_in_bytes + tile.size_in_bytes;
369  }
370  DCHECK_LE(last_tile_end_offset, frame_size);
371  if (last_tile_end_offset < frame_size)
372  subsample_organizer.AddSubsample(frame_size - last_tile_end_offset, 0);
373  return Status::OK;
374 }
375 
376 } // namespace media
377 } // namespace shaka
Abstract class holds stream information.
Definition: stream_info.h:71
SubsampleGenerator(bool vp9_subsample_encryption)
virtual Status GenerateSubsamples(const uint8_t *frame, size_t frame_size, std::vector< SubsampleEntry > *subsamples)
virtual Status Initialize(FourCC protection_scheme, const StreamInfo &stream_info)
Class to parse a vp9 bit stream.
Definition: vp9_parser.h:20
All the methods that are virtual are virtual for mocking.
Definition: crypto_flags.cc:66