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