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