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