Skip to content

Commit fdc1753

Browse files
authored
media: Parse IAMF config in VideoDmpReader (#9116)
media: Parse IAMF config in VideoDmpReader VideoDmpReader now extracts IAMF primary and additional profiles from the stream's first access unit. This is achieved by parsing the IAMF Sequence Header OBU. This change enables the generation of an accurate and specific MIME type string for IAMF audio, moving from a generic "iamf" to a detailed "iamf.XXX.YYY.Opus" format. A precise MIME type is essential for correct media pipeline configuration and playback compatibility. Bug: 485254424
1 parent d1376b1 commit fdc1753

File tree

5 files changed

+253
-2
lines changed

5 files changed

+253
-2
lines changed

starboard/shared/starboard/media/iamf_util.cc

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,76 @@
1616

1717
#include <cctype>
1818
#include <cstdlib>
19+
#include <optional>
1920
#include <string>
21+
#include <string_view>
2022
#include <vector>
2123

2224
#include "starboard/common/string.h"
2325

2426
namespace starboard {
2527
namespace {
28+
29+
constexpr uint8_t kIamfSequenceHeaderObu = 31;
30+
31+
// A lightweight, forward-only reader for a raw byte buffer.
32+
class BufferReader {
33+
public:
34+
explicit BufferReader(const uint8_t* data, size_t size)
35+
: view_(reinterpret_cast<const char*>(data), size) {}
36+
37+
std::optional<uint8_t> ReadByte() {
38+
if (view_.empty()) {
39+
return std::nullopt;
40+
}
41+
const uint8_t byte = static_cast<const uint8_t>(view_.front());
42+
view_.remove_prefix(1);
43+
return byte;
44+
}
45+
46+
// Reads a LEB128-encoded unsigned integer.
47+
std::optional<uint32_t> ReadLeb128() {
48+
uint32_t decoded_value = 0;
49+
for (size_t i = 0; i < 5; ++i) {
50+
auto byte_opt = ReadByte();
51+
if (!byte_opt.has_value()) {
52+
// Not enough data.
53+
return std::nullopt;
54+
}
55+
uint8_t byte = *byte_opt;
56+
57+
if (i == 4 && (byte & 0x7f) > 0x0f) {
58+
// Invalid 5-byte encoding.
59+
return std::nullopt;
60+
}
61+
62+
decoded_value |= (uint32_t)(byte & 0x7f) << (i * 7);
63+
64+
if (!(byte & 0x80)) {
65+
return decoded_value;
66+
}
67+
}
68+
return std::nullopt; // Value exceeds 5 bytes
69+
}
70+
71+
bool Skip(size_t bytes_to_skip) {
72+
if (BytesRemaining() < bytes_to_skip) {
73+
return false;
74+
}
75+
view_.remove_prefix(bytes_to_skip);
76+
return true;
77+
}
78+
79+
const uint8_t* CurrentData() const {
80+
return reinterpret_cast<const uint8_t*>(view_.data());
81+
}
82+
83+
size_t BytesRemaining() const { return view_.size(); }
84+
85+
private:
86+
std::string_view view_;
87+
};
88+
2689
// Checks if |input| is a valid IAMF profile value, and stores the converted
2790
// value in |*profile| if so.
2891
bool StringToProfile(const std::string& input, uint32_t* profile) {
@@ -122,4 +185,52 @@ IamfMimeUtil::IamfMimeUtil(const std::string& mime_type) {
122185
additional_profile_ = additional_profile;
123186
}
124187

188+
// static.
189+
Result<IamfMimeUtil::IamfProfileInfo> IamfMimeUtil::ParseIamfSequenceHeaderObu(
190+
const std::vector<uint8_t>& data) {
191+
BufferReader reader(data.data(), data.size());
192+
193+
auto header_byte_opt = reader.ReadByte();
194+
if (!header_byte_opt) {
195+
return Failure("Truncated OBU header.");
196+
}
197+
198+
uint8_t obu_type = (*header_byte_opt >> 3) & 0x1f;
199+
if (obu_type != kIamfSequenceHeaderObu) {
200+
return Failure(FormatString(
201+
"Tried to read OBU: %d instead of IA Sequence Header OBU "
202+
"type %d in ParseIamfSequenceHeaderObu().",
203+
static_cast<int>(obu_type), static_cast<int>(kIamfSequenceHeaderObu)));
204+
}
205+
206+
auto obu_size_opt = reader.ReadLeb128();
207+
if (!obu_size_opt) {
208+
return Failure("Failed to parse OBU size.");
209+
}
210+
const uint32_t obu_size = *obu_size_opt;
211+
212+
if (reader.BytesRemaining() < obu_size) {
213+
return Failure(FormatString("Parsed OBU size %u exceeds the data size %zu.",
214+
obu_size, reader.BytesRemaining()));
215+
}
216+
217+
// Create a sub-reader for the OBU payload to ensure we don't read past the
218+
// specified OBU size.
219+
BufferReader obu_reader(reader.CurrentData(), obu_size);
220+
221+
// Skip ia_code.
222+
if (!obu_reader.Skip(sizeof(uint32_t))) {
223+
return Failure("Truncated OBU payload: missing ia_code.");
224+
}
225+
226+
auto primary_profile = obu_reader.ReadByte();
227+
auto additional_profile = obu_reader.ReadByte();
228+
229+
if (!primary_profile || !additional_profile) {
230+
return Failure("Truncated OBU payload: missing profile info.");
231+
}
232+
233+
return Success(IamfProfileInfo{*primary_profile, *additional_profile});
234+
}
235+
125236
} // namespace starboard

starboard/shared/starboard/media/iamf_util.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,13 @@
1616
#define STARBOARD_SHARED_STARBOARD_MEDIA_IAMF_UTIL_H_
1717

1818
#include <limits>
19+
#include <optional>
1920
#include <string>
21+
#include <vector>
2022

2123
#include "starboard/common/log.h"
24+
#include "starboard/common/result.h"
25+
#include "starboard/shared/internal_only.h"
2226

2327
namespace starboard {
2428

@@ -41,8 +45,19 @@ constexpr uint32_t kIamfProfileMax = 255;
4145
// Always check is_valid() before calling the getter functions.
4246
class IamfMimeUtil {
4347
public:
48+
struct IamfProfileInfo {
49+
uint8_t primary_profile;
50+
uint8_t additional_profile;
51+
};
52+
4453
explicit IamfMimeUtil(const std::string& mime_type);
4554

55+
// Parses IAMF Config OBUs for the primary and additional profiles,
56+
// based on IAMF specification v1.0.0-errata.
57+
// https://aomediacodec.github.io/iamf/v1.1.0.html#codecsparameter.
58+
static Result<IamfProfileInfo> ParseIamfSequenceHeaderObu(
59+
const std::vector<uint8_t>& data);
60+
4661
bool is_valid() const {
4762
return primary_profile_ <= kIamfProfileMax &&
4863
additional_profile_ <= kIamfProfileMax &&

starboard/shared/starboard/media/iamf_util_test.cc

Lines changed: 106 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "starboard/shared/starboard/media/iamf_util.h"
1616

1717
#include <string>
18+
#include <vector>
1819

1920
#include "testing/gtest/include/gtest/gtest.h"
2021

@@ -246,7 +247,111 @@ TEST(IamfUtilTest, Profile) {
246247
EXPECT_TRUE(util.is_valid());
247248
ASSERT_NE(util.primary_profile(), kIamfProfileSimple);
248249
ASSERT_NE(util.primary_profile(), kIamfProfileBase);
249-
ASSERT_EQ(util.primary_profile(), 2);
250+
ASSERT_EQ(util.primary_profile(), 2U);
251+
}
252+
253+
TEST(IamfUtilTest, ParsesSequenceHeaderObu) {
254+
// From iamf_simple_profile_5_1.dmp.
255+
const std::vector<uint8_t> kSimpleProfileSequenceHeaderObu = {
256+
0xF8, 0x06, 0x69, 0x61, 0x6D, 0x66, 0x00, 0x00};
257+
258+
// From iamf_base_profile_stereo_ambisonics.dmp.
259+
const std::vector<uint8_t> kBaseProfileSequenceHeaderObu = {
260+
0xF8, 0x06, 0x69, 0x61, 0x6D, 0x66, 0x01, 0x01};
261+
262+
auto result =
263+
IamfMimeUtil::ParseIamfSequenceHeaderObu(kSimpleProfileSequenceHeaderObu);
264+
265+
ASSERT_TRUE(result.has_value()) << result.error();
266+
EXPECT_EQ(result->primary_profile, kIamfProfileSimple);
267+
EXPECT_EQ(result->additional_profile, kIamfProfileSimple);
268+
269+
result =
270+
IamfMimeUtil::ParseIamfSequenceHeaderObu(kBaseProfileSequenceHeaderObu);
271+
272+
ASSERT_TRUE(result.has_value()) << result.error();
273+
EXPECT_EQ(result->primary_profile, kIamfProfileBase);
274+
EXPECT_EQ(result->additional_profile, kIamfProfileBase);
275+
}
276+
277+
TEST(IamfUtilTest, ParseSequenceHeaderFailsOnInvalidObuType) {
278+
// First byte 0x08 means OBU type 1, not 31.
279+
const uint8_t kInvalidObuType[] = {0x08, 0x06, 0x69, 0x61,
280+
0x6D, 0x66, 0x00, 0x00};
281+
std::vector<uint8_t> data(kInvalidObuType,
282+
kInvalidObuType + SB_ARRAY_SIZE(kInvalidObuType));
283+
auto result = IamfMimeUtil::ParseIamfSequenceHeaderObu(data);
284+
ASSERT_FALSE(result.has_value())
285+
<< "Parsed IA Sequence Header OBU when an error was expected. Primary "
286+
"profile: "
287+
<< result->primary_profile
288+
<< " additional profile: " << result->additional_profile;
289+
}
290+
291+
TEST(IamfUtilTest, ParseSequenceHeaderFailsOnTruncatedData) {
292+
const uint8_t kTruncatedData[] = {0xF8, 0x06, 0x69, 0x61,
293+
0x6D}; // Truncated ia_code.
294+
std::vector<uint8_t> data(kTruncatedData,
295+
kTruncatedData + SB_ARRAY_SIZE(kTruncatedData));
296+
auto result = IamfMimeUtil::ParseIamfSequenceHeaderObu(data);
297+
ASSERT_FALSE(result.has_value())
298+
<< "Parsed IA Sequence Header OBU when an error was expected. Primary "
299+
"profile: "
300+
<< result->primary_profile
301+
<< " additional profile: " << result->additional_profile;
302+
}
303+
304+
TEST(IamfUtilTest, ParseSequenceHeaderFailsOnTruncatedLeb128) {
305+
const uint8_t kTruncatedLeb128[] = {0xF8, 0x81}; // Incomplete LEB128 size.
306+
std::vector<uint8_t> data(kTruncatedLeb128,
307+
kTruncatedLeb128 + SB_ARRAY_SIZE(kTruncatedLeb128));
308+
auto result = IamfMimeUtil::ParseIamfSequenceHeaderObu(data);
309+
ASSERT_FALSE(result.has_value())
310+
<< "Parsed IA Sequence Header OBU when an error was expected. Primary "
311+
"profile: "
312+
<< result->primary_profile
313+
<< " additional profile: " << result->additional_profile;
314+
}
315+
316+
TEST(IamfUtilTest, ParseSequenceHeaderFailsOnInvalid5ByteLeb128) {
317+
// This is an invalid encoding for a 32-bit value.
318+
const uint8_t kInvalidLeb128[] = {0xF8, 0x81, 0x81, 0x81, 0x81, 0x10};
319+
std::vector<uint8_t> data(kInvalidLeb128,
320+
kInvalidLeb128 + SB_ARRAY_SIZE(kInvalidLeb128));
321+
auto result = IamfMimeUtil::ParseIamfSequenceHeaderObu(data);
322+
ASSERT_FALSE(result.has_value())
323+
<< "Parsed IA Sequence Header OBU when an error was expected. Primary "
324+
"profile: "
325+
<< result->primary_profile
326+
<< " additional profile: " << result->additional_profile;
327+
}
328+
329+
TEST(IamfUtilTest, ParseSequenceHeaderFailsWhenObuSizeExceedsBufferSize) {
330+
// LEB128 size is 7, but only 6 bytes remain.
331+
const uint8_t kObuSizeTooLarge[] = {0xF8, 0x07, 0x69, 0x61,
332+
0x6D, 0x66, 0x00, 0x00};
333+
std::vector<uint8_t> data(kObuSizeTooLarge,
334+
kObuSizeTooLarge + SB_ARRAY_SIZE(kObuSizeTooLarge));
335+
auto result = IamfMimeUtil::ParseIamfSequenceHeaderObu(data);
336+
ASSERT_FALSE(result.has_value())
337+
<< "Parsed IA Sequence Header OBU when an error was expected. Primary "
338+
"profile: "
339+
<< result->primary_profile
340+
<< " additional profile: " << result->additional_profile;
341+
}
342+
343+
TEST(IamfUtilTest, ParseSequenceHeaderFailsWhenObuSizeIsTooSmall) {
344+
// LEB128 size is 5, but 6 bytes are needed for ia_code and profiles.
345+
const uint8_t kObuSizeTooSmall[] = {0xF8, 0x05, 0x69, 0x61,
346+
0x6D, 0x66, 0x00, 0x00};
347+
std::vector<uint8_t> data(kObuSizeTooSmall,
348+
kObuSizeTooSmall + SB_ARRAY_SIZE(kObuSizeTooSmall));
349+
auto result = IamfMimeUtil::ParseIamfSequenceHeaderObu(data);
350+
ASSERT_FALSE(result.has_value())
351+
<< "Parsed IA Sequence Header OBU when an error was expected. Primary "
352+
"profile: "
353+
<< result->primary_profile
354+
<< " additional profile: " << result->additional_profile;
250355
}
251356

252357
} // namespace

starboard/shared/starboard/player/video_dmp_reader.cc

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@
1616

1717
#include <algorithm>
1818
#include <functional>
19+
#include <iomanip>
1920

2021
#include "starboard/common/check_op.h"
22+
#include "starboard/shared/starboard/media/iamf_util.h"
2123

2224
namespace starboard {
2325

@@ -157,7 +159,14 @@ std::string VideoDmpReader::audio_mime_type() const {
157159
ss << "audio/wav; codecs=\"1\";";
158160
break;
159161
case kSbMediaAudioCodecIamf:
160-
ss << "audio/mp4; codecs=\"iamf\";";
162+
SB_CHECK(dmp_info_.iamf_primary_profile.has_value());
163+
// Only Opus IAMF substreams are currently supported.
164+
ss << "audio/mp4; codecs=\"iamf.";
165+
ss << std::setw(3) << std::setfill('0') << std::hex
166+
<< static_cast<int>(*dmp_info_.iamf_primary_profile) << "."
167+
<< std::setw(3) << std::setfill('0') << std::hex
168+
<< static_cast<int>(dmp_info_.iamf_additional_profile.value_or(0))
169+
<< ".Opus\";";
161170
break;
162171
default:
163172
SB_NOTREACHED() << "Unsupported audio codec: " << dmp_info_.audio_codec;
@@ -305,6 +314,15 @@ void VideoDmpReader::Parse() {
305314
while (ParseOneRecord()) {
306315
}
307316

317+
if (dmp_info_.audio_codec == kSbMediaAudioCodecIamf &&
318+
!dmp_info_.iamf_primary_profile.has_value()) {
319+
auto result =
320+
IamfMimeUtil::ParseIamfSequenceHeaderObu(audio_access_units_[0].data());
321+
SB_CHECK(result) << result.error();
322+
dmp_info_.iamf_primary_profile = result->primary_profile;
323+
dmp_info_.iamf_additional_profile = result->additional_profile;
324+
}
325+
308326
dmp_info_.audio_access_units_size = audio_access_units_.size();
309327
dmp_info_.audio_bitrate = CalculateAverageBitrate(audio_access_units_);
310328
dmp_info_.video_access_units_size = video_access_units_.size();

starboard/shared/starboard/player/video_dmp_reader.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,8 @@ class VideoDmpReader {
138138
size_t audio_access_units_size = 0;
139139
int64_t audio_bitrate = 0;
140140
int audio_duration = 0;
141+
std::optional<uint8_t> iamf_primary_profile;
142+
std::optional<uint8_t> iamf_additional_profile;
141143

142144
SbMediaVideoCodec video_codec = kSbMediaVideoCodecNone;
143145
size_t video_access_units_size = 0;

0 commit comments

Comments
 (0)