Skip to content
77 changes: 77 additions & 0 deletions starboard/shared/starboard/media/iamf_util.cc
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,18 @@

#include <cctype>
#include <cstdlib>
#include <optional>
#include <string>
#include <string_view>
#include <vector>

#include "starboard/common/string.h"

namespace starboard {
namespace {

constexpr uint8_t kIamfSequenceHeaderObu = 31;

// Checks if |input| is a valid IAMF profile value, and stores the converted
// value in |*profile| if so.
bool StringToProfile(const std::string& input, uint32_t* profile) {
Expand All @@ -44,6 +49,31 @@ bool StringToProfile(const std::string& input, uint32_t* profile) {
*profile = converted_val;
return true;
}

// Helper function to read a LEB128 value.
std::optional<uint32_t> ReadLeb128(std::string_view& view) {
uint32_t decoded_value = 0;
for (size_t i = 0; i < 5; ++i) {
if (view.empty()) {
return std::nullopt;
}

uint8_t byte = view.front();
view.remove_prefix(1);

if (i == 4 && (byte & 0x7f) > 0x0f) {
// A 32-bit value can't use more than 4 bits from the 5th LEB128 byte.
return std::nullopt;
}

decoded_value |= (uint32_t)(byte & 0x7f) << (i * 7);

if (!(byte & 0x80)) {
return decoded_value;
}
}
return std::nullopt;
}
} // namespace

IamfMimeUtil::IamfMimeUtil(const std::string& mime_type) {
Expand Down Expand Up @@ -122,4 +152,51 @@ IamfMimeUtil::IamfMimeUtil(const std::string& mime_type) {
additional_profile_ = additional_profile;
}

// static.
Result<IamfMimeUtil::IamfProfileInfo> IamfMimeUtil::ParseIamfSequenceHeaderObu(
const std::vector<uint8_t>& data) {
const uint8_t* read_head = data.data();
const uint8_t* end = read_head + data.size();

const uint8_t header_byte = *read_head++;
uint8_t obu_type = (header_byte >> 3) & 0x1f;
if (obu_type != kIamfSequenceHeaderObu) {
return Failure(FormatString(
"Tried to read OBU: %d instead of IA Sequence Header OBU "
"type %d in ParseIamfSequenceHeaderObu().",
static_cast<int>(obu_type), static_cast<int>(kIamfSequenceHeaderObu)));
}

std::string_view view(reinterpret_cast<const char*>(read_head),
end - read_head);
const size_t size_before_leb128 = view.size();
std::optional<uint32_t> obu_size = ReadLeb128(view);

if (!obu_size.has_value()) {
return Failure(FormatString("Failed to parse OBU size."));
}

const size_t leb128_byte_count = size_before_leb128 - view.size();
read_head += leb128_byte_count;

if (static_cast<size_t>(end - read_head) < *obu_size) {
return Failure(FormatString("Parsed OBU size %u exceeds the data size %zu.",
*obu_size,
static_cast<size_t>(end - read_head)));
}

const uint8_t* obu_end = read_head + *obu_size;

if (read_head + sizeof(uint32_t) + 2 > obu_end) {
return Failure(FormatString(
"Expected IA Sequence Header OBU data size %zu exceeds the parsed OBU "
"size %u.",
sizeof(uint32_t) + 2, *obu_size));
}
// Skip ia_code (4 bytes).
read_head += sizeof(uint32_t);

return Success(IamfProfileInfo{*read_head++, *read_head});
}

} // namespace starboard
15 changes: 15 additions & 0 deletions starboard/shared/starboard/media/iamf_util.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,13 @@
#define STARBOARD_SHARED_STARBOARD_MEDIA_IAMF_UTIL_H_

#include <limits>
#include <optional>
#include <string>
#include <vector>

#include "starboard/common/log.h"
#include "starboard/common/result.h"
#include "starboard/shared/internal_only.h"

namespace starboard {

Expand All @@ -41,8 +45,19 @@ constexpr uint32_t kIamfProfileMax = 255;
// Always check is_valid() before calling the getter functions.
class IamfMimeUtil {
public:
struct IamfProfileInfo {
uint8_t primary_profile;
uint8_t additional_profile;
};

explicit IamfMimeUtil(const std::string& mime_type);

// Parses IAMF Config OBUs for the primary and additional profiles,
// based on IAMF specification v1.0.0-errata.
// https://aomediacodec.github.io/iamf/v1.1.0.html#codecsparameter.
static Result<IamfProfileInfo> ParseIamfSequenceHeaderObu(
const std::vector<uint8_t>& data);

bool is_valid() const {
return primary_profile_ <= kIamfProfileMax &&
additional_profile_ <= kIamfProfileMax &&
Expand Down
107 changes: 106 additions & 1 deletion starboard/shared/starboard/media/iamf_util_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "starboard/shared/starboard/media/iamf_util.h"

#include <string>
#include <vector>

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

Expand Down Expand Up @@ -246,7 +247,111 @@ TEST(IamfUtilTest, Profile) {
EXPECT_TRUE(util.is_valid());
ASSERT_NE(util.primary_profile(), kIamfProfileSimple);
ASSERT_NE(util.primary_profile(), kIamfProfileBase);
ASSERT_EQ(util.primary_profile(), 2);
ASSERT_EQ(util.primary_profile(), 2U);
}

TEST(IamfUtilTest, ParsesSequenceHeaderObu) {
// From iamf_simple_profile_5_1.dmp.
const std::vector<uint8_t> kSimpleProfileSequenceHeaderObu = {
0xF8, 0x06, 0x69, 0x61, 0x6D, 0x66, 0x00, 0x00};

// From iamf_base_profile_stereo_ambisonics.dmp.
const std::vector<uint8_t> kBaseProfileSequenceHeaderObu = {
0xF8, 0x06, 0x69, 0x61, 0x6D, 0x66, 0x01, 0x01};

auto result =
IamfMimeUtil::ParseIamfSequenceHeaderObu(kSimpleProfileSequenceHeaderObu);

ASSERT_TRUE(result.has_value()) << result.error();
EXPECT_EQ(result->primary_profile, kIamfProfileSimple);
EXPECT_EQ(result->additional_profile, kIamfProfileSimple);

result =
IamfMimeUtil::ParseIamfSequenceHeaderObu(kBaseProfileSequenceHeaderObu);

ASSERT_TRUE(result.has_value()) << result.error();
EXPECT_EQ(result->primary_profile, kIamfProfileBase);
EXPECT_EQ(result->additional_profile, kIamfProfileBase);
}

TEST(IamfUtilTest, ParseSequenceHeaderFailsOnInvalidObuType) {
// First byte 0x08 means OBU type 1, not 31.
const uint8_t kInvalidObuType[] = {0x08, 0x06, 0x69, 0x61,
0x6D, 0x66, 0x00, 0x00};
std::vector<uint8_t> data(kInvalidObuType,
kInvalidObuType + SB_ARRAY_SIZE(kInvalidObuType));
auto result = IamfMimeUtil::ParseIamfSequenceHeaderObu(data);
ASSERT_FALSE(result.has_value())
<< "Parsed IA Sequence Header OBU when an error was expected. Primary "
"profile: "
<< result->primary_profile
<< " additonal profile: " << result->additional_profile;
}

TEST(IamfUtilTest, ParseSequenceHeaderFailsOnTruncatedData) {
const uint8_t kTruncatedData[] = {0xF8, 0x06, 0x69, 0x61,
0x6D}; // Truncated ia_code.
std::vector<uint8_t> data(kTruncatedData,
kTruncatedData + SB_ARRAY_SIZE(kTruncatedData));
auto result = IamfMimeUtil::ParseIamfSequenceHeaderObu(data);
ASSERT_FALSE(result.has_value())
<< "Parsed IA Sequence Header OBU when an error was expected. Primary "
"profile: "
<< result->primary_profile
<< " additonal profile: " << result->additional_profile;
}

TEST(IamfUtilTest, ParseSequenceHeaderFailsOnTruncatedLeb128) {
const uint8_t kTruncatedLeb128[] = {0xF8, 0x81}; // Incomplete LEB128 size.
std::vector<uint8_t> data(kTruncatedLeb128,
kTruncatedLeb128 + SB_ARRAY_SIZE(kTruncatedLeb128));
auto result = IamfMimeUtil::ParseIamfSequenceHeaderObu(data);
ASSERT_FALSE(result.has_value())
<< "Parsed IA Sequence Header OBU when an error was expected. Primary "
"profile: "
<< result->primary_profile
<< " additonal profile: " << result->additional_profile;
}

TEST(IamfUtilTest, ParseSequenceHeaderFailsOnInvalid5ByteLeb128) {
// This is an invalid encoding for a 32-bit value.
const uint8_t kInvalidLeb128[] = {0xF8, 0x81, 0x81, 0x81, 0x81, 0x10};
std::vector<uint8_t> data(kInvalidLeb128,
kInvalidLeb128 + SB_ARRAY_SIZE(kInvalidLeb128));
auto result = IamfMimeUtil::ParseIamfSequenceHeaderObu(data);
ASSERT_FALSE(result.has_value())
<< "Parsed IA Sequence Header OBU when an error was expected. Primary "
"profile: "
<< result->primary_profile
<< " additonal profile: " << result->additional_profile;
}

TEST(IamfUtilTest, ParseSequenceHeaderFailsWhenObuSizeExceedsBufferSize) {
// LEB128 size is 7, but only 6 bytes remain.
const uint8_t kObuSizeTooLarge[] = {0xF8, 0x07, 0x69, 0x61,
0x6D, 0x66, 0x00, 0x00};
std::vector<uint8_t> data(kObuSizeTooLarge,
kObuSizeTooLarge + SB_ARRAY_SIZE(kObuSizeTooLarge));
auto result = IamfMimeUtil::ParseIamfSequenceHeaderObu(data);
ASSERT_FALSE(result.has_value())
<< "Parsed IA Sequence Header OBU when an error was expected. Primary "
"profile: "
<< result->primary_profile
<< " additonal profile: " << result->additional_profile;
}

TEST(IamfUtilTest, ParseSequenceHeaderFailsWhenObuSizeIsTooSmall) {
// LEB128 size is 5, but 6 bytes are needed for ia_code and profiles.
const uint8_t kObuSizeTooSmall[] = {0xF8, 0x05, 0x69, 0x61,
0x6D, 0x66, 0x00, 0x00};
std::vector<uint8_t> data(kObuSizeTooSmall,
kObuSizeTooSmall + SB_ARRAY_SIZE(kObuSizeTooSmall));
auto result = IamfMimeUtil::ParseIamfSequenceHeaderObu(data);
ASSERT_FALSE(result.has_value())
<< "Parsed IA Sequence Header OBU when an error was expected. Primary "
"profile: "
<< result->primary_profile
<< " additonal profile: " << result->additional_profile;
}

} // namespace
Expand Down
24 changes: 23 additions & 1 deletion starboard/shared/starboard/player/video_dmp_reader.cc
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@

#include <algorithm>
#include <functional>
#include <iomanip>

#include "starboard/common/check_op.h"
#include "starboard/shared/starboard/media/iamf_util.h"

namespace starboard {

Expand Down Expand Up @@ -157,7 +159,14 @@ std::string VideoDmpReader::audio_mime_type() const {
ss << "audio/wav; codecs=\"1\";";
break;
case kSbMediaAudioCodecIamf:
ss << "audio/mp4; codecs=\"iamf\";";
SB_CHECK(dmp_info_.iamf_primary_profile.has_value());
// Only Opus IAMF substreams are currently supported.
ss << "audio/mp4; codecs=\"iamf.";
ss << std::setw(3) << std::setfill('0') << std::hex
<< static_cast<int>(*dmp_info_.iamf_primary_profile) << "."
<< std::setw(3) << std::setfill('0') << std::hex
<< static_cast<int>(dmp_info_.iamf_additional_profile.value_or(0))
<< ".Opus\";";
break;
default:
SB_NOTREACHED() << "Unsupported audio codec: " << dmp_info_.audio_codec;
Expand Down Expand Up @@ -305,6 +314,19 @@ void VideoDmpReader::Parse() {
while (ParseOneRecord()) {
}

if (dmp_info_.audio_codec == kSbMediaAudioCodecIamf &&
!dmp_info_.iamf_primary_profile.has_value()) {
dmp_info_.iamf_primary_profile = 0;
dmp_info_.iamf_additional_profile = 0;
std::vector<uint8_t> data(audio_access_units_[0].data().data(),
audio_access_units_[0].data().data() +
audio_access_units_[0].data().size());
auto result = IamfMimeUtil::ParseIamfSequenceHeaderObu(data);
SB_CHECK(result) << result.error();
dmp_info_.iamf_primary_profile = result->primary_profile;
dmp_info_.iamf_additional_profile = result->additional_profile;
}

dmp_info_.audio_access_units_size = audio_access_units_.size();
dmp_info_.audio_bitrate = CalculateAverageBitrate(audio_access_units_);
dmp_info_.video_access_units_size = video_access_units_.size();
Expand Down
2 changes: 2 additions & 0 deletions starboard/shared/starboard/player/video_dmp_reader.h
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ class VideoDmpReader {
size_t audio_access_units_size = 0;
int64_t audio_bitrate = 0;
int audio_duration = 0;
std::optional<uint8_t> iamf_primary_profile;
std::optional<uint8_t> iamf_additional_profile;

SbMediaVideoCodec video_codec = kSbMediaVideoCodecNone;
size_t video_access_units_size = 0;
Expand Down
Loading