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/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
22namespace shaka {
23namespace media {
24namespace {
25
26const size_t kAesBlockSize = 16u;
27
28uint8_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
37bool 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.
70class 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 size_t clear_at_end = 0;
88 if (align_protected_data_ && cipher_bytes != 0) {
89 clear_at_end = cipher_bytes % kAesBlockSize;
90 cipher_bytes -= clear_at_end;
91 }
92
93 accumulated_clear_bytes_ += clear_bytes;
94 // Accumulated clear bytes are handled later.
95 if (cipher_bytes == 0) {
96 accumulated_clear_bytes_ += clear_at_end;
97 return;
98 }
99
100 PushSubsample(accumulated_clear_bytes_, cipher_bytes);
101 accumulated_clear_bytes_ = clear_at_end;
102 }
103
104 private:
105 SubsampleOrganizer(const SubsampleOrganizer&) = delete;
106 SubsampleOrganizer& operator=(const SubsampleOrganizer&) = delete;
107
108 void PushSubsample(size_t clear_bytes, size_t cipher_bytes) {
109 const uint16_t kUInt16Max = std::numeric_limits<uint16_t>::max();
110 while (clear_bytes > kUInt16Max) {
111 subsamples_->emplace_back(kUInt16Max, 0);
112 clear_bytes -= kUInt16Max;
113 }
114 subsamples_->emplace_back(static_cast<uint16_t>(clear_bytes),
115 static_cast<uint32_t>(cipher_bytes));
116 }
117
118 const bool align_protected_data_ = false;
119 std::vector<SubsampleEntry>* const subsamples_ = nullptr;
120 size_t accumulated_clear_bytes_ = 0;
121};
122
123} // namespace
124
125SubsampleGenerator::SubsampleGenerator(bool vp9_subsample_encryption,
126 bool cencv1)
127 : vp9_subsample_encryption_(vp9_subsample_encryption), cencv1_(cencv1) {}
128
129SubsampleGenerator::~SubsampleGenerator() {}
130
131Status SubsampleGenerator::Initialize(FourCC protection_scheme,
132 const StreamInfo& stream_info) {
133 codec_ = stream_info.codec();
134 nalu_length_size_ = GetNaluLengthSize(stream_info);
135
136 switch (codec_) {
137 case kCodecAV1:
138 av1_parser_.reset(new AV1Parser);
139 break;
140 case kCodecVP9:
141 if (vp9_subsample_encryption_)
142 vpx_parser_.reset(new VP9Parser);
143 break;
144 case kCodecH264:
145 header_parser_.reset(new H264VideoSliceHeaderParser);
146 break;
147 case kCodecH265:
148 case kCodecH265DolbyVision:
149 header_parser_.reset(new H265VideoSliceHeaderParser);
150 break;
151 default:
152 // Other codecs should have nalu length size == 0.
153 if (nalu_length_size_ > 0) {
154 LOG(WARNING) << "Unknown video codec '" << codec_ << "'";
155 return Status(error::ENCRYPTION_FAILURE, "Unknown video codec.");
156 }
157 }
158 if (av1_parser_) {
159 // Parse configOBUs in AV1CodecConfigurationRecord if exists.
160 // https://aomediacodec.github.io/av1-isobmff/#av1codecconfigurationbox-syntax.
161 const size_t kConfigOBUsOffset = 4;
162 const bool has_config_obus =
163 stream_info.codec_config().size() > kConfigOBUsOffset;
164 std::vector<AV1Parser::Tile> tiles;
165 if (has_config_obus &&
166 !av1_parser_->Parse(
167 &stream_info.codec_config()[kConfigOBUsOffset],
168 stream_info.codec_config().size() - kConfigOBUsOffset, &tiles)) {
169 return Status(
170 error::ENCRYPTION_FAILURE,
171 "Failed to parse configOBUs in AV1CodecConfigurationRecord.");
172 }
173 DCHECK(tiles.empty());
174 }
175 if (header_parser_) {
176 CHECK_NE(nalu_length_size_, 0u) << "AnnexB stream is not supported yet";
177 if (!header_parser_->Initialize(stream_info.codec_config())) {
178 return Status(error::ENCRYPTION_FAILURE,
179 "Failed to read SPS and PPS data.");
180 }
181 if (!header_parser_->InitializeLayered(
182 stream_info.layered_codec_config())) {
183 return Status(error::ENCRYPTION_FAILURE,
184 "Failed to read parameter sets for the layered case.");
185 }
186 }
187
188 align_protected_data_ = ShouldAlignProtectedData(codec_, protection_scheme,
189 vp9_subsample_encryption_);
190
191 if (protection_scheme == kAppleSampleAesProtectionScheme) {
192 const size_t kH264LeadingClearBytesSize = 32u;
193 const size_t kAudioLeadingClearBytesSize = 16u;
194 switch (codec_) {
195 case kCodecH264:
196 leading_clear_bytes_size_ = kH264LeadingClearBytesSize;
197 min_protected_data_size_ =
198 leading_clear_bytes_size_ + kAesBlockSize + 1u;
199 break;
200 case kCodecAAC:
201 FALLTHROUGH_INTENDED;
202 case kCodecAC3:
203 leading_clear_bytes_size_ = kAudioLeadingClearBytesSize;
204 min_protected_data_size_ = leading_clear_bytes_size_ + kAesBlockSize;
205 break;
206 case kCodecEAC3:
207 // E-AC3 encryption is handled by SampleAesEc3Cryptor, which also
208 // manages leading clear bytes.
209 leading_clear_bytes_size_ = 0;
210 min_protected_data_size_ = leading_clear_bytes_size_ + kAesBlockSize;
211 break;
212 default:
213 LOG(ERROR) << "Unexpected codec for SAMPLE-AES " << codec_;
214 return Status(error::ENCRYPTION_FAILURE,
215 "Unexpected codec for SAMPLE-AES.");
216 }
217 }
218 return Status::OK;
219}
220
222 const uint8_t* frame,
223 size_t frame_size,
224 std::vector<SubsampleEntry>* subsamples) {
225 subsamples->clear();
226 switch (codec_) {
227 case kCodecAV1:
228 return GenerateSubsamplesFromAV1Frame(frame, frame_size, subsamples);
229 case kCodecH264:
230 FALLTHROUGH_INTENDED;
231 case kCodecH265:
232 case kCodecH265DolbyVision:
233 return GenerateSubsamplesFromH26xFrame(frame, frame_size, subsamples);
234 case kCodecVP9:
235 if (vp9_subsample_encryption_)
236 return GenerateSubsamplesFromVPxFrame(frame, frame_size, subsamples);
237 // Full sample encrypted so no subsamples.
238 break;
239 default:
240 // Other codecs are full sample encrypted unless there are clear leading
241 // bytes.
242 if (leading_clear_bytes_size_ > 0) {
243 SubsampleOrganizer subsample_organizer(align_protected_data_,
244 subsamples);
245 const size_t clear_bytes =
246 std::min(frame_size, leading_clear_bytes_size_);
247 const size_t cipher_bytes = frame_size - clear_bytes;
248 subsample_organizer.AddSubsample(clear_bytes, cipher_bytes);
249 } else {
250 // Full sample encrypted so no subsamples.
251 }
252 break;
253 }
254 return Status::OK;
255}
256
257void SubsampleGenerator::InjectVpxParserForTesting(
258 std::unique_ptr<VPxParser> vpx_parser) {
259 vpx_parser_ = std::move(vpx_parser);
260}
261
262void SubsampleGenerator::InjectVideoSliceHeaderParserForTesting(
263 std::unique_ptr<VideoSliceHeaderParser> header_parser) {
264 header_parser_ = std::move(header_parser);
265}
266
267void SubsampleGenerator::InjectAV1ParserForTesting(
268 std::unique_ptr<AV1Parser> av1_parser) {
269 av1_parser_ = std::move(av1_parser);
270}
271
272Status SubsampleGenerator::GenerateSubsamplesFromVPxFrame(
273 const uint8_t* frame,
274 size_t frame_size,
275 std::vector<SubsampleEntry>* subsamples) {
276 DCHECK(vpx_parser_);
277 std::vector<VPxFrameInfo> vpx_frames;
278 if (!vpx_parser_->Parse(frame, frame_size, &vpx_frames))
279 return Status(error::ENCRYPTION_FAILURE, "Failed to parse vpx frame.");
280
281 SubsampleOrganizer subsample_organizer(align_protected_data_, subsamples);
282
283 size_t total_size = 0;
284 for (const VPxFrameInfo& vpx_frame : vpx_frames) {
285 subsample_organizer.AddSubsample(
286 vpx_frame.uncompressed_header_size,
287 vpx_frame.frame_size - vpx_frame.uncompressed_header_size);
288 total_size += vpx_frame.frame_size;
289 }
290 // Add subsample for the superframe index if exists.
291 const bool is_superframe = vpx_frames.size() > 1;
292 if (is_superframe) {
293 const size_t index_size = frame_size - total_size;
294 DCHECK_LE(index_size, 2 + vpx_frames.size() * 4);
295 DCHECK_GE(index_size, 2 + vpx_frames.size() * 1);
296 subsample_organizer.AddSubsample(index_size, 0);
297 } else {
298 DCHECK_EQ(total_size, frame_size);
299 }
300 return Status::OK;
301}
302
303Status SubsampleGenerator::GenerateSubsamplesFromH26xFrame(
304 const uint8_t* frame,
305 size_t frame_size,
306 std::vector<SubsampleEntry>* subsamples) {
307 DCHECK_NE(nalu_length_size_, 0u);
308 DCHECK(header_parser_);
309
310 SubsampleOrganizer subsample_organizer(align_protected_data_, subsamples);
311
312 const Nalu::CodecType nalu_type =
313 (codec_ == kCodecH265 || codec_ == kCodecH265DolbyVision) ? Nalu::kH265
314 : Nalu::kH264;
315 NaluReader reader(nalu_type, nalu_length_size_, frame, frame_size);
316
317 Nalu nalu;
318 NaluReader::Result result;
319 while ((result = reader.Advance(&nalu)) == NaluReader::kOk) {
320 // |header_parser_| is only used if |leading_clear_bytes_size_| is not
321 // availble. See lines below.
322 if (leading_clear_bytes_size_ == 0 && !header_parser_->ProcessNalu(nalu)) {
323 LOG(ERROR) << "Failed to process NAL unit: NAL type = " << nalu.type();
324 return Status(error::ENCRYPTION_FAILURE, "Failed to process NAL unit.");
325 }
326
327 const size_t nalu_total_size = nalu.header_size() + nalu.payload_size();
328 size_t clear_bytes = 0;
329 if (cencv1_) {
330 // For CENCv1, only the NALU header is clear; all other data for any NALU
331 // type is encrypted.
332 clear_bytes = nalu.header_size();
333 } else if (nalu.is_video_slice() &&
334 nalu_total_size >= min_protected_data_size_) {
335 clear_bytes = leading_clear_bytes_size_;
336 if (clear_bytes == 0) {
337 // For video-slice NAL units, encrypt the video slice. This skips
338 // the frame header.
339 const int64_t video_slice_header_size =
340 header_parser_->GetHeaderSize(nalu);
341 if (video_slice_header_size < 0) {
342 LOG(ERROR) << "Failed to read slice header.";
343 return Status(error::ENCRYPTION_FAILURE,
344 "Failed to read slice header.");
345 }
346 clear_bytes = nalu.header_size() + video_slice_header_size;
347 }
348 } else {
349 // For non-video-slice or small NAL units, don't encrypt.
350 clear_bytes = nalu_total_size;
351 }
352 const size_t cipher_bytes = nalu_total_size - clear_bytes;
353 subsample_organizer.AddSubsample(nalu_length_size_ + clear_bytes,
354 cipher_bytes);
355 }
356 if (result != NaluReader::kEOStream) {
357 LOG(ERROR) << "Failed to parse NAL units.";
358 return Status(error::ENCRYPTION_FAILURE, "Failed to parse NAL units.");
359 }
360 return Status::OK;
361}
362
363Status SubsampleGenerator::GenerateSubsamplesFromAV1Frame(
364 const uint8_t* frame,
365 size_t frame_size,
366 std::vector<SubsampleEntry>* subsamples) {
367 DCHECK(av1_parser_);
368 std::vector<AV1Parser::Tile> av1_tiles;
369 if (!av1_parser_->Parse(frame, frame_size, &av1_tiles))
370 return Status(error::ENCRYPTION_FAILURE, "Failed to parse AV1 frame.");
371
372 SubsampleOrganizer subsample_organizer(align_protected_data_, subsamples);
373
374 size_t last_tile_end_offset = 0;
375 for (const AV1Parser::Tile& tile : av1_tiles) {
376 DCHECK_LE(last_tile_end_offset, tile.start_offset_in_bytes);
377 // Per AV1 in ISO-BMFF spec [1], only decode_tile is encrypted.
378 // [1] https://aomediacodec.github.io/av1-isobmff/#subsample-encryption
379 subsample_organizer.AddSubsample(
380 tile.start_offset_in_bytes - last_tile_end_offset, tile.size_in_bytes);
381 last_tile_end_offset = tile.start_offset_in_bytes + tile.size_in_bytes;
382 }
383 DCHECK_LE(last_tile_end_offset, frame_size);
384 if (last_tile_end_offset < frame_size)
385 subsample_organizer.AddSubsample(frame_size - last_tile_end_offset, 0);
386 return Status::OK;
387}
388
389} // namespace media
390} // 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.