|
16 | 16 |
|
17 | 17 | #include <algorithm> |
18 | 18 | #include <functional> |
| 19 | +#include <iomanip> |
19 | 20 |
|
20 | 21 | #include "starboard/common/check_op.h" |
21 | 22 |
|
22 | 23 | namespace starboard { |
23 | 24 |
|
24 | 25 | namespace { |
25 | 26 |
|
| 27 | +constexpr uint8_t kIamfSequenceHeaderObu = 31; |
| 28 | + |
26 | 29 | template <typename AccessUnit> |
27 | 30 | int64_t CalculateAverageBitrate(const std::vector<AccessUnit>& access_units) { |
28 | 31 | if (access_units.empty()) { |
@@ -71,6 +74,29 @@ SbPlayerSampleInfo ConvertToPlayerSampleInfo( |
71 | 74 | return sample_info; |
72 | 75 | } |
73 | 76 |
|
| 77 | +// Helper function to read a LEB128 value. |
| 78 | +bool ReadLeb128(const uint8_t** data, const uint8_t* end, uint32_t* value) { |
| 79 | + SB_DCHECK(data && *data); |
| 80 | + SB_DCHECK(end); |
| 81 | + SB_DCHECK(value); |
| 82 | + *value = 0; |
| 83 | + for (size_t i = 0; i < 5; ++i) { |
| 84 | + if (*data >= end) { |
| 85 | + return false; |
| 86 | + } |
| 87 | + uint8_t byte = *(*data)++; |
| 88 | + if (i == 4 && (byte & 0x7f) > 0x0f) { |
| 89 | + // A 32-bit value can't use more than 4 bits from the 5th LEB128 byte. |
| 90 | + return false; |
| 91 | + } |
| 92 | + *value |= (uint32_t)(byte & 0x7f) << (i * 7); |
| 93 | + if (!(byte & 0x80)) { |
| 94 | + return true; |
| 95 | + } |
| 96 | + } |
| 97 | + return false; |
| 98 | +} |
| 99 | + |
74 | 100 | } // namespace |
75 | 101 |
|
76 | 102 | using std::placeholders::_1; |
@@ -157,7 +183,16 @@ std::string VideoDmpReader::audio_mime_type() const { |
157 | 183 | ss << "audio/wav; codecs=\"1\";"; |
158 | 184 | break; |
159 | 185 | case kSbMediaAudioCodecIamf: |
160 | | - ss << "audio/mp4; codecs=\"iamf\";"; |
| 186 | + if (!dmp_info_.iamf_primary_profile.has_value()) { |
| 187 | + return ""; |
| 188 | + } |
| 189 | + // Only Opus IAMF substreams are currently supported. |
| 190 | + ss << "audio/mp4; codecs=\"iamf."; |
| 191 | + ss << std::setw(3) << std::setfill('0') << std::hex |
| 192 | + << static_cast<int>(*dmp_info_.iamf_primary_profile) << "." |
| 193 | + << std::setw(3) << std::setfill('0') << std::hex |
| 194 | + << static_cast<int>(dmp_info_.iamf_additional_profile.value_or(0)) |
| 195 | + << ".Opus\";"; |
161 | 196 | break; |
162 | 197 | default: |
163 | 198 | SB_NOTREACHED() << "Unsupported audio codec: " << dmp_info_.audio_codec; |
@@ -230,6 +265,52 @@ const AudioSampleInfo& VideoDmpReader::GetAudioSampleInfo(size_t index) { |
230 | 265 | return au.audio_sample_info(); |
231 | 266 | } |
232 | 267 |
|
| 268 | +// Parse IAMF Config OBUs for the primary and additional profiles, |
| 269 | +// based on IAMF specification v1.0.0-errata. |
| 270 | +// https://aomediacodec.github.io/iamf/v1.1.0.html#codecsparameter. |
| 271 | +void VideoDmpReader::ParseIamfConfigOBU() { |
| 272 | + if (dmp_info_.audio_codec != kSbMediaAudioCodecIamf) { |
| 273 | + return; |
| 274 | + } |
| 275 | + |
| 276 | + if (audio_access_units_.empty()) { |
| 277 | + SB_LOG(WARNING) << "IAMF stream has no access units."; |
| 278 | + return; |
| 279 | + } |
| 280 | + |
| 281 | + const auto& data = audio_access_units_[0].data(); |
| 282 | + const uint8_t* ptr = data.data(); |
| 283 | + const uint8_t* end = ptr + data.size(); |
| 284 | + |
| 285 | + uint8_t header_byte = *ptr++; |
| 286 | + uint8_t obu_type = (header_byte >> 3) & 0x1f; |
| 287 | + |
| 288 | + uint32_t obu_size; |
| 289 | + if (!ReadLeb128(&ptr, end, &obu_size)) { |
| 290 | + SB_LOG(ERROR) << "Failed to parse OBU size."; |
| 291 | + return; |
| 292 | + } |
| 293 | + |
| 294 | + if (static_cast<size_t>(end - ptr) < obu_size) { |
| 295 | + SB_LOG(ERROR) << "OBU size exceeds access unit size."; |
| 296 | + return; |
| 297 | + } |
| 298 | + const uint8_t* obu_end = ptr + obu_size; |
| 299 | + |
| 300 | + if (obu_type == kIamfSequenceHeaderObu) { |
| 301 | + if (ptr + sizeof(uint32_t) + 2 > obu_end) { |
| 302 | + return; |
| 303 | + } |
| 304 | + // Skip ia_code (4 bytes). |
| 305 | + ptr += sizeof(uint32_t); |
| 306 | + |
| 307 | + dmp_info_.iamf_primary_profile = *ptr++; |
| 308 | + dmp_info_.iamf_additional_profile = *ptr; |
| 309 | + } |
| 310 | + // The Sequence Header must be the first OBU. If it isn't found, leave the |
| 311 | + // profile values unset. |
| 312 | +} |
| 313 | + |
233 | 314 | void VideoDmpReader::ParseHeader(uint32_t* dmp_writer_version) { |
234 | 315 | SB_DCHECK(dmp_writer_version); |
235 | 316 | SB_DCHECK(!reverse_byte_order_.has_value()); |
@@ -305,6 +386,11 @@ void VideoDmpReader::Parse() { |
305 | 386 | while (ParseOneRecord()) { |
306 | 387 | } |
307 | 388 |
|
| 389 | + if (dmp_info_.audio_codec == kSbMediaAudioCodecIamf && |
| 390 | + !dmp_info_.iamf_primary_profile.has_value()) { |
| 391 | + ParseIamfConfigOBU(); |
| 392 | + } |
| 393 | + |
308 | 394 | dmp_info_.audio_access_units_size = audio_access_units_.size(); |
309 | 395 | dmp_info_.audio_bitrate = CalculateAverageBitrate(audio_access_units_); |
310 | 396 | dmp_info_.video_access_units_size = video_access_units_.size(); |
|
0 commit comments