Skip to content

Commit

Permalink
Add low-level support for writing and validating MixPresentationTags.
Browse files Browse the repository at this point in the history
  - The IAMF spec has an asymmetric requirement where there SHALL be at most one `content_language`, but if they are present the remaining ones can be ignored.
    - Like other asymmetric requirements the encoder typically refuses to produce data which seemingly violates the requirement.
  - On read we will process and store duplicate `content_language` tags.
    - This means we will store them, even if they are not "really" valid.
    - Add a `CreateFromBuffer/SucceedsWithDuplicateContentLanguageTags` test to guard this behavior.
    - Although for this CL, the read falls into the `ObuBase::footer_`.
  - On the write side we will typically refuse to write data which seemingly violates the requirement.
    - Following the existing style - this gets enforced in `ValidateAndWrite`.
    - Add tests to guard this behavior, and a parallel test that *other* tags MAY be duplicated.

PiperOrigin-RevId: 663496061
  • Loading branch information
jwcullen committed Aug 15, 2024
1 parent 4c9cf37 commit 99a18d0
Show file tree
Hide file tree
Showing 4 changed files with 235 additions and 5 deletions.
1 change: 0 additions & 1 deletion iamf/obu/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,6 @@ cc_library(
"//iamf/common:write_bit_buffer",
"@com_google_absl//absl/base:no_destructor",
"@com_google_absl//absl/container:flat_hash_map",
"@com_google_absl//absl/container:flat_hash_set",
"@com_google_absl//absl/log",
"@com_google_absl//absl/status",
"@com_google_absl//absl/status:statusor",
Expand Down
24 changes: 23 additions & 1 deletion iamf/obu/mix_presentation.cc
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@

#include "absl/base/no_destructor.h"
#include "absl/container/flat_hash_map.h"
#include "absl/container/flat_hash_set.h"
#include "absl/log/log.h"
#include "absl/status/status.h"
#include "absl/strings/str_cat.h"
Expand Down Expand Up @@ -328,6 +327,29 @@ absl::Status MixPresentationSubMix::ReadAndValidate(const int32_t& count_label,
return absl::OkStatus();
}

absl::Status MixPresentationTags::ValidateAndWrite(WriteBitBuffer& wb) const {
RETURN_IF_NOT_OK(wb.WriteUnsignedLiteral(num_tags, 8));
RETURN_IF_NOT_OK(ValidateVectorSizeEqual("tags", tags.size(), num_tags));

int count_content_language_tag = 0;

for (const auto& tag : tags) {
if (tag.tag_name == "content_language") {
count_content_language_tag++;
}
RETURN_IF_NOT_OK(wb.WriteString(tag.tag_name));
RETURN_IF_NOT_OK(wb.WriteString(tag.tag_value));
}
// Tags are freeform and may be duplicated. Except for the "content_language"
// tag which SHALL appear at most once.
if (count_content_language_tag > 1) {
return absl::InvalidArgumentError(
"Expected zero or one content_language tag.");
}

return absl::OkStatus();
}

// Validates and writes a `LoudspeakersSsConventionLayout` and sets
// `found_stereo_layout` to true if it is a stereo layout.
absl::Status LoudspeakersSsConventionLayout::Write(bool& found_stereo_layout,
Expand Down
28 changes: 28 additions & 0 deletions iamf/obu/mix_presentation.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#define OBU_MIX_PRESENTATION_H_

#include <cstdint>
#include <optional>
#include <string>
#include <utility>
#include <variant>
Expand Down Expand Up @@ -305,6 +306,29 @@ struct MixPresentationSubMix {
std::vector<MixPresentationLayout> layouts;
};

struct MixPresentationTags {
struct Tag {
friend bool operator==(const Tag& lhs, const Tag& rhs) = default;

std::string tag_name;
std::string tag_value;
};

friend bool operator==(const MixPresentationTags& lhs,
const MixPresentationTags& rhs) = default;

/*!\brief Writes the MixPresentationTags to the buffer.
*
* \param wb Buffer to write to.
* \return `absl::OkStatus()` if the MixPresentationTags is valid. A specific
* status if the write fails.
*/
absl::Status ValidateAndWrite(WriteBitBuffer& wb) const;

uint8_t num_tags;
std::vector<Tag> tags;
};

/*!\brief Metadata required for post-processing the mixed audio signal.
*
* The metadata specifies how to render, process and mix one or more audio
Expand Down Expand Up @@ -412,6 +436,10 @@ class MixPresentationObu : public ObuBase {

std::vector<MixPresentationSubMix> sub_mixes_;

// Implicitly included based on `obu_size` after writing the v1.0.0-errata
// payload.
std::optional<MixPresentationTags> mix_presentation_tags_;

private:
DecodedUleb128 mix_presentation_id_;
DecodedUleb128 count_label_;
Expand Down
187 changes: 184 additions & 3 deletions iamf/obu/tests/mix_presentation_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <variant>
#include <vector>
Expand Down Expand Up @@ -933,7 +934,7 @@ TEST(CreateFromBufferTest, RejectEmptyBitstream) {
MixPresentationObu::CreateFromBuffer(header, source.size(), buffer).ok());
}

TEST(CreateFromBufferTest, RejectNoSubMix) {
TEST(CreateFromBuffer, InvalidWithNoSubMixes) {
std::vector<uint8_t> source = {
// Start Mix OBU.
// mix_presentation_id
Expand All @@ -955,7 +956,7 @@ TEST(CreateFromBufferTest, RejectNoSubMix) {
MixPresentationObu::CreateFromBuffer(header, source.size(), buffer).ok());
}

TEST(CreateFromBufferTest, OneSubMix) {
TEST(CreateFromBuffer, ReadsOneSubMix) {
const std::vector<std::string> kAnnotationsLanguage = {"en-us"};
const std::vector<std::string> kLocalizedPresentationAnnotations = {"Mix 1"};
const std::vector<std::string> kAudioElementLocalizedElementAnnotations = {
Expand Down Expand Up @@ -996,7 +997,7 @@ TEST(CreateFromBufferTest, OneSubMix) {
ObuHeader header;
auto obu =
MixPresentationObu::CreateFromBuffer(header, source.size(), buffer);
EXPECT_THAT(obu, IsOk());
ASSERT_THAT(obu, IsOk());
EXPECT_EQ(obu->header_.obu_type, kObuIaMixPresentation);
EXPECT_EQ(obu->GetMixPresentationId(), 10);
EXPECT_EQ(obu->GetAnnotationsLanguage(), kAnnotationsLanguage);
Expand All @@ -1008,6 +1009,96 @@ TEST(CreateFromBufferTest, OneSubMix) {
kAudioElementLocalizedElementAnnotations);
}

TEST(CreateFromBufferTest, ReadsMixPresentationTagsIntoFooter) {
const std::vector<uint8_t> kMixPresentationTags = {
// Start MixPresentationTags.
1,
// Start Tag1.
'A', 'B', 'C', '\0', '1', '2', '3', '\0',
// End Tag1.
};
std::vector<uint8_t> source = {
// Start Mix OBU.
// mix_presentation_id
10,
// count_label
0,
// num_submixes
1,
// Start Submix.
1, 21,
// Start RenderingConfig.
RenderingConfig::kHeadphonesRenderingModeStereo << 6, 0,
// End RenderingConfig.
22, 23, 0x80, 0, 24, 25, 26, 0x80, 0, 27,
// num_layouts
1,
// Start Layout0.
(Layout::kLayoutTypeLoudspeakersSsConvention << 6) |
(LoudspeakersSsConventionLayout::kSoundSystemA_0_2_0 << 2),
0, 0, 31, 0, 32,
// End SubMix.
};
source.insert(source.end(), kMixPresentationTags.begin(),
kMixPresentationTags.end());
ReadBitBuffer buffer(1024, &source);
ObuHeader header;
auto obu =
MixPresentationObu::CreateFromBuffer(header, source.size(), buffer);
ASSERT_THAT(obu, IsOk());

EXPECT_FALSE(obu->mix_presentation_tags_.has_value());
EXPECT_EQ(obu->footer_, kMixPresentationTags);
}

TEST(CreateFromBufferTest, SucceedsWithDuplicateContentLanguageTags) {
const std::vector<uint8_t> kDuplicateContentLanguageTags = {
// Start MixPresentationTags.
2,
// `tag_name[0]`.
'c', 'o', 'n', 't', 'e', 'n', 't', '_', 'l', 'a', 'n', 'g', 'u', 'a', 'g',
'e', '\0',
// `tag_value[0]`.
'e', 'n', '-', 'u', 's', '\0',
// `tag_name[1]`.
'c', 'o', 'n', 't', 'e', 'n', 't', '_', 'l', 'a', 'n', 'g', 'u', 'a', 'g',
'e', '\0',
// `tag_value[1]`.
'e', 'n', '-', 'g', 'b', '\0'};
std::vector<uint8_t> source = {
// Start Mix OBU.
// mix_presentation_id
10,
// count_label
0,
// num_submixes
1,
// Start Submix.
1, 21,
// Start RenderingConfig.
RenderingConfig::kHeadphonesRenderingModeStereo << 6, 0,
// End RenderingConfig.
22, 23, 0x80, 0, 24, 25, 26, 0x80, 0, 27,
// num_layouts
1,
// Start Layout0.
(Layout::kLayoutTypeLoudspeakersSsConvention << 6) |
(LoudspeakersSsConventionLayout::kSoundSystemA_0_2_0 << 2),
0, 0, 31, 0, 32,
// End SubMix.
};
source.insert(source.end(), kDuplicateContentLanguageTags.begin(),
kDuplicateContentLanguageTags.end());
ReadBitBuffer buffer(1024, &source);
ObuHeader header;
auto obu =
MixPresentationObu::CreateFromBuffer(header, source.size(), buffer);
ASSERT_THAT(obu, IsOk());

EXPECT_FALSE(obu->mix_presentation_tags_.has_value());
EXPECT_EQ(obu->footer_, kDuplicateContentLanguageTags);
}

TEST(ReadSubMixAudioElementTest, AllFieldsPresent) {
std::vector<uint8_t> source = {
// Start SubMixAudioElement.
Expand Down Expand Up @@ -1220,5 +1311,95 @@ TEST(ReadMixPresentationSubMixTest, AudioElementAndMultipleLayouts) {
LoudspeakersSsConventionLayout::kSoundSystemA_0_2_0}));
}

TEST(MixPresentationTagsWriteAndValidate, WritesWithZeroTags) {
constexpr uint8_t kZeroNumTags = 0;
const MixPresentationTags kMixPresentationTagsWithZeroTags = {
.num_tags = kZeroNumTags};
const std::vector<uint8_t> kExpectedBuffer = {
// `num_tags`.
kZeroNumTags,
};
WriteBitBuffer wb(1024);

EXPECT_THAT(kMixPresentationTagsWithZeroTags.ValidateAndWrite(wb), IsOk());

EXPECT_EQ(wb.bit_buffer(), kExpectedBuffer);
}

TEST(MixPresentationTagsWriteAndValidate, WritesContentLanguageTag) {
constexpr uint8_t kOneTag = 1;
const MixPresentationTags kMixPresentationTagsWithContentLanguageTag = {
.num_tags = kOneTag, .tags = {{"content_language", "en-us"}}};
const std::vector<uint8_t> kExpectedBuffer = {
// `num_tags`.
kOneTag,
// `tag_name[0]`.
'c', 'o', 'n', 't', 'e', 'n', 't', '_', 'l', 'a', 'n', 'g', 'u', 'a', 'g',
'e', '\0',
// `tag_value[0]`.
'e', 'n', '-', 'u', 's', '\0'};
WriteBitBuffer wb(1024);

EXPECT_THAT(kMixPresentationTagsWithContentLanguageTag.ValidateAndWrite(wb),
IsOk());

EXPECT_EQ(wb.bit_buffer(), kExpectedBuffer);
}

TEST(MixPresentationTagsWriteAndValidate, WritesArbitraryTags) {
constexpr uint8_t kNumTags = 1;
const MixPresentationTags kMixPresentationTagsWithArbitraryTag = {
.num_tags = kNumTags, .tags = {{"ABC", "123"}}};
const std::vector<uint8_t> kExpectedBuffer = {// `num_tags`.
kNumTags,
// `tag_name[0]`.
'A', 'B', 'C', '\0',
// `tag_value[1]`.
'1', '2', '3', '\0'};
WriteBitBuffer wb(1024);

EXPECT_THAT(kMixPresentationTagsWithArbitraryTag.ValidateAndWrite(wb),
IsOk());

EXPECT_EQ(wb.bit_buffer(), kExpectedBuffer);
}

TEST(MixPresentationTagsWriteAndValidate, WritesDuplicateArbitraryTags) {
constexpr uint8_t kTwoTags = 2;
const MixPresentationTags kMixPresentationTagsWithArbitraryTag = {
.num_tags = kTwoTags, .tags = {{"tag", "value"}, {"tag", "value"}}};
const std::vector<uint8_t> kExpectedBuffer = {// `num_tags`.
kTwoTags,
// `tag_name[0]`.
't', 'a', 'g', '\0',
// `tag_value[0]`.
'v', 'a', 'l', 'u', 'e', '\0',
// `tag_name[1]`.
't', 'a', 'g', '\0',
// `tag_value[1]`.
'v', 'a', 'l', 'u', 'e', '\0'};
WriteBitBuffer wb(1024);

EXPECT_THAT(kMixPresentationTagsWithArbitraryTag.ValidateAndWrite(wb),
IsOk());

EXPECT_EQ(wb.bit_buffer(), kExpectedBuffer);
}

TEST(MixPresentationTagsWriteAndValidate, InvalidForDuplicateContentIdTag) {
constexpr uint8_t kTwoTags = 2;
const MixPresentationTags
kMixPresentationTagsWithDuplicateContentLanguageTag = {
.num_tags = kTwoTags,
.tags = {{"content_language", "en-us"},
{"content_language", "en-gb"}}};

WriteBitBuffer wb(1024);

EXPECT_FALSE(
kMixPresentationTagsWithDuplicateContentLanguageTag.ValidateAndWrite(wb)
.ok());
}

} // namespace
} // namespace iamf_tools

0 comments on commit 99a18d0

Please sign in to comment.