Shaka Packager SDK
Loading...
Searching...
No Matches
vp_codec_configuration_record.cc
1// Copyright 2015 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/codecs/vp_codec_configuration_record.h>
8
9#include <cstddef>
10#include <cstdint>
11#include <optional>
12#include <string>
13#include <vector>
14
15#include <absl/log/log.h>
16#include <absl/strings/str_format.h>
17#include <absl/strings/str_replace.h>
18
19#include <packager/media/base/bit_reader.h>
20#include <packager/media/base/buffer_reader.h>
21#include <packager/media/base/buffer_writer.h>
22#include <packager/media/base/rcheck.h>
23#include <packager/media/base/stream_info.h>
24
25namespace shaka {
26namespace media {
27namespace {
28enum VP9CodecFeatures {
29 kFeatureProfile = 1,
30 kFeatureLevel = 2,
31 kFeatureBitDepth = 3,
32 kFeatureChromaSubsampling = 4,
33};
34
35std::string VPCodecAsString(Codec codec) {
36 switch (codec) {
37 case kCodecVP8:
38 return "vp08";
39 case kCodecVP9:
40 return "vp09";
41 default:
42 LOG(WARNING) << "Unknown VP codec: " << codec;
43 return std::string();
44 }
45}
46
47template <typename T>
48void MergeField(const std::string& name,
49 const std::optional<T>& source_value,
50 std::optional<T>* dest_value) {
51 if (*dest_value) {
52 if (source_value && *source_value != **dest_value) {
53 LOG(WARNING) << "VPx " << name << " is inconsistent, "
54 << static_cast<int>(**dest_value) << " vs "
55 << static_cast<int>(*source_value);
56 }
57 } else {
58 // Only set dest_value if it is not set.
59 *dest_value = source_value;
60 }
61}
62
63enum VP9Level {
64 LEVEL_UNKNOWN = 0,
65 LEVEL_1 = 10,
66 LEVEL_1_1 = 11,
67 LEVEL_2 = 20,
68 LEVEL_2_1 = 21,
69 LEVEL_3 = 30,
70 LEVEL_3_1 = 31,
71 LEVEL_4 = 40,
72 LEVEL_4_1 = 41,
73 LEVEL_5 = 50,
74 LEVEL_5_1 = 51,
75 LEVEL_5_2 = 52,
76 LEVEL_6 = 60,
77 LEVEL_6_1 = 61,
78 LEVEL_6_2 = 62,
79 LEVEL_MAX = 255
80};
81
82struct VP9LevelCharacteristics {
83 uint64_t max_luma_sample_rate;
84 uint32_t max_luma_picture_size;
85 double max_avg_bitrate;
86 double max_cpb_size;
87 double min_compression_ratio;
88 uint8_t max_num_column_tiles;
89 uint32_t min_altref_distance;
90 uint8_t max_ref_frame_buffers;
91};
92
93struct VP9LevelDefinition {
94 VP9Level level;
95 VP9LevelCharacteristics characteristics;
96};
97
98VP9Level LevelFromCharacteristics(uint64_t luma_sample_rate,
99 uint32_t luma_picture_size) {
100 // https://www.webmproject.org/vp9/levels/.
101 const VP9LevelDefinition vp9_level_definitions[] = {
102 {LEVEL_1, {829440, 36864, 200, 400, 2, 1, 4, 8}},
103 {LEVEL_1_1, {2764800, 73728, 800, 1000, 2, 1, 4, 8}},
104 {LEVEL_2, {4608000, 122880, 1800, 1500, 2, 1, 4, 8}},
105 {LEVEL_2_1, {9216000, 245760, 3600, 2800, 2, 2, 4, 8}},
106 {LEVEL_3, {20736000, 552960, 7200, 6000, 2, 4, 4, 8}},
107 {LEVEL_3_1, {36864000, 983040, 12000, 10000, 2, 4, 4, 8}},
108 {LEVEL_4, {83558400, 2228224, 18000, 16000, 4, 4, 4, 8}},
109 {LEVEL_4_1, {160432128, 2228224, 30000, 18000, 4, 4, 5, 6}},
110 {LEVEL_5, {311951360, 8912896, 60000, 36000, 6, 8, 6, 4}},
111 {LEVEL_5_1, {588251136, 8912896, 120000, 46000, 8, 8, 10, 4}},
112 {LEVEL_5_2, {1176502272, 8912896, 180000, 90000, 8, 8, 10, 4}},
113 {LEVEL_6, {1176502272, 35651584, 180000, 90000, 8, 16, 10, 4}},
114 {LEVEL_6_1, {2353004544u, 35651584, 240000, 180000, 8, 16, 10, 4}},
115 {LEVEL_6_2, {4706009088u, 35651584, 480000, 360000, 8, 16, 10, 4}},
116 };
117
118 for (const VP9LevelDefinition& def : vp9_level_definitions) {
119 // All the characteristic fields except max_luma_sample_rate and
120 // max_luma_picture_size are ignored to avoid the extra complexities of
121 // computing those values. It may result in incorrect level being returned.
122 // If this is a problem, please file a bug to
123 // https://github.com/shaka-project/shaka-packager/issues.
124 if (luma_sample_rate <= def.characteristics.max_luma_sample_rate &&
125 luma_picture_size <= def.characteristics.max_luma_picture_size) {
126 return def.level;
127 }
128 }
129
130 LOG(WARNING) << "Cannot determine VP9 level for luma_sample_rate ("
131 << luma_sample_rate << ") or luma_picture_size ("
132 << luma_picture_size << "). Returning LEVEL_1.";
133 return LEVEL_1;
134}
135
136} // namespace
137
138VPCodecConfigurationRecord::VPCodecConfigurationRecord() {}
139
140VPCodecConfigurationRecord::VPCodecConfigurationRecord(
141 uint8_t profile,
142 uint8_t level,
143 uint8_t bit_depth,
144 uint8_t chroma_subsampling,
145 bool video_full_range_flag,
146 uint8_t color_primaries,
147 uint8_t transfer_characteristics,
148 uint8_t matrix_coefficients,
149 const std::vector<uint8_t>& codec_initialization_data)
150 : profile_(profile),
151 level_(level),
152 bit_depth_(bit_depth),
153 chroma_subsampling_(chroma_subsampling),
154 video_full_range_flag_(video_full_range_flag),
155 color_primaries_(color_primaries),
156 transfer_characteristics_(transfer_characteristics),
157 matrix_coefficients_(matrix_coefficients),
158 codec_initialization_data_(codec_initialization_data) {}
159
160VPCodecConfigurationRecord::~VPCodecConfigurationRecord() {};
161
162// https://www.webmproject.org/vp9/mp4/
163bool VPCodecConfigurationRecord::ParseMP4(const std::vector<uint8_t>& data) {
164 BitReader reader(data.data(), data.size());
165 uint8_t value;
166 RCHECK(reader.ReadBits(8, &value));
167 profile_ = value;
168 RCHECK(reader.ReadBits(8, &value));
169 level_ = value;
170 RCHECK(reader.ReadBits(4, &value));
171 bit_depth_ = value;
172 RCHECK(reader.ReadBits(3, &value));
173 chroma_subsampling_ = value;
174 bool bool_value;
175 RCHECK(reader.ReadBits(1, &bool_value));
176 video_full_range_flag_ = bool_value;
177 RCHECK(reader.ReadBits(8, &value));
178 color_primaries_ = value;
179 RCHECK(reader.ReadBits(8, &value));
180 transfer_characteristics_ = value;
181 RCHECK(reader.ReadBits(8, &value));
182 matrix_coefficients_ = value;
183
184 uint16_t codec_initialization_data_size = 0;
185 RCHECK(reader.ReadBits(16, &codec_initialization_data_size));
186 RCHECK(reader.bits_available() >= codec_initialization_data_size * 8u);
187 const size_t header_size = data.size() - reader.bits_available() / 8;
188 codec_initialization_data_.assign(
189 data.begin() + header_size,
190 data.begin() + header_size + codec_initialization_data_size);
191 return true;
192}
193
194// http://wiki.webmproject.org/vp9-codecprivate
195bool VPCodecConfigurationRecord::ParseWebM(const std::vector<uint8_t>& data) {
196 BufferReader reader(data.data(), data.size());
197
198 while (reader.HasBytes(1)) {
199 uint8_t id;
200 uint8_t size;
201 RCHECK(reader.Read1(&id));
202 RCHECK(reader.Read1(&size));
203
204 uint8_t value = 0;
205 switch (id) {
206 case kFeatureProfile:
207 RCHECK(size == 1);
208 RCHECK(reader.Read1(&value));
209 profile_ = value;
210 break;
211 case kFeatureLevel:
212 RCHECK(size == 1);
213 RCHECK(reader.Read1(&value));
214 level_ = value;
215 break;
216 case kFeatureBitDepth:
217 RCHECK(size == 1);
218 RCHECK(reader.Read1(&value));
219 bit_depth_ = value;
220 break;
221 case kFeatureChromaSubsampling:
222 RCHECK(size == 1);
223 RCHECK(reader.Read1(&value));
224 chroma_subsampling_ = value;
225 break;
226 default: {
227 LOG(WARNING) << "Skipping unknown VP9 codec feature " << id;
228 RCHECK(reader.SkipBytes(size));
229 }
230 }
231 }
232
233 return true;
234}
235
236void VPCodecConfigurationRecord::SetVP9Level(uint16_t width,
237 uint16_t height,
238 double sample_duration_seconds) {
239 // https://www.webmproject.org/vp9/levels/.
240
241 const uint32_t luma_picture_size = width * height;
242 // Alt-Ref frames are not taken into consideration intentionally to avoid the
243 // extra complexities. It may result in smaller luma_sample_rate may than the
244 // actual luma_sample_rate, leading to incorrect level being returned.
245 // If this is a problem, please file a bug to
246 // https://github.com/shaka-project/shaka-packager/issues.
247 const double kUnknownSampleDuration = 0.0;
248 // The decision is based on luma_picture_size only if duration is unknown.
249 uint64_t luma_sample_rate = 0;
250 if (sample_duration_seconds != kUnknownSampleDuration)
251 luma_sample_rate = luma_picture_size / sample_duration_seconds;
252
253 level_ = LevelFromCharacteristics(luma_sample_rate, luma_picture_size);
254}
255
256void VPCodecConfigurationRecord::WriteMP4(std::vector<uint8_t>* data) const {
257 BufferWriter writer;
258 writer.AppendInt(profile());
259 writer.AppendInt(level());
260 uint8_t bit_depth_chroma = (bit_depth() << 4) | (chroma_subsampling() << 1) |
261 (video_full_range_flag() ? 1 : 0);
262 writer.AppendInt(bit_depth_chroma);
263 writer.AppendInt(color_primaries());
264 writer.AppendInt(transfer_characteristics());
265 writer.AppendInt(matrix_coefficients());
266 uint16_t codec_initialization_data_size =
267 static_cast<uint16_t>(codec_initialization_data_.size());
268 writer.AppendInt(codec_initialization_data_size);
269 writer.AppendVector(codec_initialization_data_);
270 writer.SwapBuffer(data);
271}
272
273void VPCodecConfigurationRecord::WriteWebM(std::vector<uint8_t>* data) const {
274 BufferWriter writer;
275
276 if (profile_) {
277 writer.AppendInt(static_cast<uint8_t>(kFeatureProfile)); // ID = 1
278 writer.AppendInt(static_cast<uint8_t>(1)); // Length = 1
279 writer.AppendInt(*profile_);
280 }
281
282 if (level_) {
283 writer.AppendInt(static_cast<uint8_t>(kFeatureLevel)); // ID = 2
284 writer.AppendInt(static_cast<uint8_t>(1)); // Length = 1
285 writer.AppendInt(*level_);
286 }
287
288 if (bit_depth_) {
289 writer.AppendInt(static_cast<uint8_t>(kFeatureBitDepth)); // ID = 3
290 writer.AppendInt(static_cast<uint8_t>(1)); // Length = 1
291 writer.AppendInt(*bit_depth_);
292 }
293
294 if (chroma_subsampling_) {
295 // ID = 4, Length = 1
296 writer.AppendInt(static_cast<uint8_t>(kFeatureChromaSubsampling));
297 writer.AppendInt(static_cast<uint8_t>(1));
298 writer.AppendInt(*chroma_subsampling_);
299 }
300
301 writer.SwapBuffer(data);
302}
303
304std::string VPCodecConfigurationRecord::GetCodecString(Codec codec) const {
305 const std::string fields[] = {
306 absl::StrFormat("%d", profile()),
307 absl::StrFormat("%d", level()),
308 absl::StrFormat("%d", bit_depth()),
309 absl::StrFormat("%d", chroma_subsampling()),
310 absl::StrFormat("%d", color_primaries()),
311 absl::StrFormat("%d", transfer_characteristics()),
312 absl::StrFormat("%d", matrix_coefficients()),
313 (video_full_range_flag_ && *video_full_range_flag_) ? "01" : "00",
314 };
315
316 std::string codec_string = VPCodecAsString(codec);
317 for (const std::string& field : fields) {
318 // Make sure every field is at least 2-chars wide. The space will be
319 // replaced with '0' afterwards.
320 absl::StrAppendFormat(&codec_string, ".%2s", field.c_str());
321 }
322 absl::StrReplaceAll({{" ", "0"}}, &codec_string);
323 return codec_string;
324}
325
326void VPCodecConfigurationRecord::MergeFrom(
327 const VPCodecConfigurationRecord& other) {
328 MergeField("profile", other.profile_, &profile_);
329 MergeField("level", other.level_, &level_);
330 MergeField("bit depth", other.bit_depth_, &bit_depth_);
331 MergeField("chroma subsampling", other.chroma_subsampling_,
332 &chroma_subsampling_);
333 MergeField("video full range flag", other.video_full_range_flag_,
334 &video_full_range_flag_);
335 MergeField("color primaries", other.color_primaries_, &color_primaries_);
336 MergeField("transfer characteristics", other.transfer_characteristics_,
337 &transfer_characteristics_);
338 MergeField("matrix coefficients", other.matrix_coefficients_,
339 &matrix_coefficients_);
340
341 if (codec_initialization_data_.empty() ||
342 !other.codec_initialization_data_.empty()) {
343 if (!codec_initialization_data_.empty() &&
344 codec_initialization_data_ != other.codec_initialization_data_) {
345 LOG(WARNING) << "VPx codec initialization data is inconsistent";
346 }
347 codec_initialization_data_ = other.codec_initialization_data_;
348 }
349
350 MergeField("chroma location", other.chroma_location_, &chroma_location_);
351 UpdateChromaSubsamplingIfNeeded();
352}
353
354void VPCodecConfigurationRecord::SetChromaSubsampling(uint8_t subsampling_x,
355 uint8_t subsampling_y) {
356 VLOG(3) << "Set Chroma subsampling " << static_cast<int>(subsampling_x) << " "
357 << static_cast<int>(subsampling_y);
358 if (subsampling_x == 0 && subsampling_y == 0) {
359 chroma_subsampling_ = CHROMA_444;
360 } else if (subsampling_x == 0 && subsampling_y == 1) {
361 chroma_subsampling_ = CHROMA_440;
362 } else if (subsampling_x == 1 && subsampling_y == 0) {
363 chroma_subsampling_ = CHROMA_422;
364 } else if (subsampling_x == 1 && subsampling_y == 1) {
365 // VP9 assumes that chrome samples are collocated with luma samples if
366 // there is no explicit signaling outside of VP9 bitstream.
367 chroma_subsampling_ = CHROMA_420_COLLOCATED_WITH_LUMA;
368 } else {
369 LOG(WARNING) << "Unexpected chroma subsampling values: "
370 << static_cast<int>(subsampling_x) << " "
371 << static_cast<int>(subsampling_y);
372 }
373 UpdateChromaSubsamplingIfNeeded();
374}
375
376void VPCodecConfigurationRecord::SetChromaSubsampling(
377 ChromaSubsampling chroma_subsampling) {
378 chroma_subsampling_ = chroma_subsampling;
379 UpdateChromaSubsamplingIfNeeded();
380}
381
382void VPCodecConfigurationRecord::SetChromaLocation(uint8_t chroma_siting_x,
383 uint8_t chroma_siting_y) {
384 VLOG(3) << "Set Chroma Location " << static_cast<int>(chroma_siting_x) << " "
385 << static_cast<int>(chroma_siting_y);
386 if (chroma_siting_x == kLeftCollocated && chroma_siting_y == kTopCollocated) {
387 chroma_location_ = AVCHROMA_LOC_TOPLEFT;
388 } else if (chroma_siting_x == kLeftCollocated && chroma_siting_y == kHalf) {
389 chroma_location_ = AVCHROMA_LOC_LEFT;
390 } else if (chroma_siting_x == kHalf && chroma_siting_y == kTopCollocated) {
391 chroma_location_ = AVCHROMA_LOC_TOP;
392 } else if (chroma_siting_x == kHalf && chroma_siting_y == kHalf) {
393 chroma_location_ = AVCHROMA_LOC_CENTER;
394 } else {
395 LOG(WARNING) << "Unexpected chroma siting values: "
396 << static_cast<int>(chroma_siting_x) << " "
397 << static_cast<int>(chroma_siting_y);
398 }
399 UpdateChromaSubsamplingIfNeeded();
400}
401
402void VPCodecConfigurationRecord::UpdateChromaSubsamplingIfNeeded() {
403 // Use chroma location to fix the chroma subsampling format.
404 if (chroma_location_ && chroma_subsampling_ &&
405 (*chroma_subsampling_ == CHROMA_420_VERTICAL ||
406 *chroma_subsampling_ == CHROMA_420_COLLOCATED_WITH_LUMA)) {
407 if (*chroma_location_ == AVCHROMA_LOC_TOPLEFT)
408 chroma_subsampling_ = CHROMA_420_COLLOCATED_WITH_LUMA;
409 else if (*chroma_location_ == AVCHROMA_LOC_LEFT)
410 chroma_subsampling_ = CHROMA_420_VERTICAL;
411 VLOG(3) << "Chroma subsampling " << static_cast<int>(*chroma_subsampling_);
412 }
413}
414
415} // namespace media
416} // namespace shaka
A class to read bit streams.
Definition bit_reader.h:19
size_t bits_available() const
Definition bit_reader.h:91
bool ReadBits(size_t num_bits, T *out)
Definition bit_reader.h:37
bool HasBytes(size_t count)
bool SkipBytes(size_t num_bytes)
Class for parsing or writing VP codec configuration record.
All the methods that are virtual are virtual for mocking.