Upgrade iamf_tools to e42e82092f6a46df4c1eebc0e67fa1fdd6788696
This project was upgraded with external_updater.
Usage: tools/external_updater/updater.sh update external/iamf_tools
For more info, check https://cs.android.com/android/platform/superproject/main/+/main:tools/external_updater/README.md
Bug: 388336285
Test: TreeHugger
Change-Id: I26bb4762aa7508ea05e87aafb92d7a4e761a6ae0
diff --git a/METADATA b/METADATA
index 0727d11..6d8d34d 100644
--- a/METADATA
+++ b/METADATA
@@ -14,13 +14,13 @@
last_upgrade_date {
year: 2025
month: 3
- day: 6
+ day: 24
}
homepage: "https://aomediacodec.github.io/iamf/"
identifier {
type: "Git"
value: "https://github.com/AOMediaCodec/iamf-tools"
- version: "612bf29777031b1d154a0126ba5ffe7b3b7a3c0f"
+ version: "e42e82092f6a46df4c1eebc0e67fa1fdd6788696"
primary_source: true
}
}
diff --git a/iamf/api/conversion/BUILD b/iamf/api/conversion/BUILD
index 9ccd766..4285786 100644
--- a/iamf/api/conversion/BUILD
+++ b/iamf/api/conversion/BUILD
@@ -12,6 +12,9 @@
"//iamf/api:types",
"//iamf/obu:mix_presentation",
"@com_google_absl//absl/log",
+ "@com_google_absl//absl/status",
+ "@com_google_absl//absl/status:statusor",
+ "@com_google_absl//absl/strings",
],
)
# keep-sorted end
diff --git a/iamf/api/conversion/mix_presentation_conversion.cc b/iamf/api/conversion/mix_presentation_conversion.cc
index 3cb7b65..cbf5c89 100644
--- a/iamf/api/conversion/mix_presentation_conversion.cc
+++ b/iamf/api/conversion/mix_presentation_conversion.cc
@@ -12,11 +12,61 @@
#include "iamf/api/conversion/mix_presentation_conversion.h"
+#include <variant>
+
#include "absl/log/log.h"
+#include "absl/status/status.h"
+#include "absl/strings/str_cat.h"
#include "iamf/api/types.h"
#include "iamf/obu/mix_presentation.h"
namespace iamf_tools {
+namespace {
+
+absl::StatusOr<api::OutputLayout> InternalLayoutToApiLayout(
+ const LoudspeakersReservedOrBinauralLayout& specific_layout) {
+ return absl::InvalidArgumentError("Invalid layout type.");
+}
+
+absl::StatusOr<api::OutputLayout> InternalLayoutToApiLayout(
+ const LoudspeakersSsConventionLayout& specific_layout) {
+ switch (specific_layout.sound_system) {
+ case LoudspeakersSsConventionLayout::kSoundSystemA_0_2_0:
+ return api::OutputLayout::kItu2051_SoundSystemA_0_2_0;
+ case LoudspeakersSsConventionLayout::kSoundSystemB_0_5_0:
+ return api::OutputLayout::kItu2051_SoundSystemB_0_5_0;
+ case LoudspeakersSsConventionLayout::kSoundSystemC_2_5_0:
+ return api::OutputLayout::kItu2051_SoundSystemC_2_5_0;
+ case LoudspeakersSsConventionLayout::kSoundSystemD_4_5_0:
+ return api::OutputLayout::kItu2051_SoundSystemD_4_5_0;
+ case LoudspeakersSsConventionLayout::kSoundSystemE_4_5_1:
+ return api::OutputLayout::kItu2051_SoundSystemE_4_5_1;
+ case LoudspeakersSsConventionLayout::kSoundSystemF_3_7_0:
+ return api::OutputLayout::kItu2051_SoundSystemF_3_7_0;
+ case LoudspeakersSsConventionLayout::kSoundSystemG_4_9_0:
+ return api::OutputLayout::kItu2051_SoundSystemG_4_9_0;
+ case LoudspeakersSsConventionLayout::kSoundSystemH_9_10_3:
+ return api::OutputLayout::kItu2051_SoundSystemH_9_10_3;
+ case LoudspeakersSsConventionLayout::kSoundSystemI_0_7_0:
+ return api::OutputLayout::kItu2051_SoundSystemI_0_7_0;
+ case LoudspeakersSsConventionLayout::kSoundSystemJ_4_7_0:
+ return api::OutputLayout::kItu2051_SoundSystemJ_4_7_0;
+ case LoudspeakersSsConventionLayout::kSoundSystem10_2_7_0:
+ return api::OutputLayout::kIAMF_SoundSystemExtension_2_7_0;
+ case LoudspeakersSsConventionLayout::kSoundSystem11_2_3_0:
+ return api::OutputLayout::kIAMF_SoundSystemExtension_2_3_0;
+ case LoudspeakersSsConventionLayout::kSoundSystem12_0_1_0:
+ return api::OutputLayout::kIAMF_SoundSystemExtension_0_1_0;
+ case LoudspeakersSsConventionLayout::kSoundSystem13_6_9_0:
+ return api::OutputLayout::kIAMF_SoundSystemExtension_6_9_0;
+ default:
+ return absl::InvalidArgumentError(
+ absl::StrCat("Invalid layout: ", specific_layout.sound_system));
+ };
+}
+
+} // namespace
+
Layout MakeLayout(LoudspeakersSsConventionLayout::SoundSystem sound_system) {
return {.layout_type = Layout::kLayoutTypeLoudspeakersSsConvention,
.specific_layout =
@@ -58,4 +108,12 @@
LOG(FATAL) << "Invalid output layout.";
}
+absl::StatusOr<api::OutputLayout> InternalToApiType(Layout internal_layout) {
+ return std::visit(
+ [](const auto& specific_layout) {
+ return InternalLayoutToApiLayout(specific_layout);
+ },
+ internal_layout.specific_layout);
+}
+
} // namespace iamf_tools
diff --git a/iamf/api/conversion/mix_presentation_conversion.h b/iamf/api/conversion/mix_presentation_conversion.h
index b5215fb..78d0cf1 100644
--- a/iamf/api/conversion/mix_presentation_conversion.h
+++ b/iamf/api/conversion/mix_presentation_conversion.h
@@ -13,6 +13,7 @@
#ifndef API_CONVERSION_MIX_PRESENTATION_METADATA_H_
#define API_CONVERSION_MIX_PRESENTATION_METADATA_H_
+#include "absl/status/statusor.h"
#include "iamf/api/types.h"
#include "iamf/obu/mix_presentation.h"
@@ -21,6 +22,9 @@
/*!\brief Converts the API-requested OutputLayout to an internal IAMF Layout. */
Layout ApiToInternalType(api::OutputLayout api_output_layout);
+/*!\brief Converts the internal IAMF Layout to the API OutputLayout. */
+absl::StatusOr<api::OutputLayout> InternalToApiType(Layout internal_layout);
+
} // namespace iamf_tools
#endif // API_CONVERSION_MIX_PRESENTATION_METADATA_H_
diff --git a/iamf/api/conversion/tests/BUILD b/iamf/api/conversion/tests/BUILD
index 7486ab5..e029d85 100644
--- a/iamf/api/conversion/tests/BUILD
+++ b/iamf/api/conversion/tests/BUILD
@@ -6,6 +6,7 @@
"//iamf/api:types",
"//iamf/api/conversion:mix_presentation_conversion",
"//iamf/obu:mix_presentation",
+ "@com_google_absl//absl/status:status_matchers",
"@com_google_googletest//:gtest_main",
],
)
diff --git a/iamf/api/conversion/tests/mix_presentation_conversion_test.cc b/iamf/api/conversion/tests/mix_presentation_conversion_test.cc
index 047f53e..f3604ff 100644
--- a/iamf/api/conversion/tests/mix_presentation_conversion_test.cc
+++ b/iamf/api/conversion/tests/mix_presentation_conversion_test.cc
@@ -15,6 +15,8 @@
#include <utility>
#include <variant>
+#include "absl/status/status_matchers.h"
+#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "iamf/api/types.h"
#include "iamf/obu/mix_presentation.h"
@@ -22,12 +24,43 @@
namespace iamf_tools {
namespace {
+using ::absl_testing::IsOk;
using ::testing::TestWithParam;
using LayoutPair =
std::pair<api::OutputLayout, LoudspeakersSsConventionLayout::SoundSystem>;
using ApiToInternalType_OutputLayout = TestWithParam<LayoutPair>;
+auto kApiOutputToInternalSoundSystemPairs = ::testing::Values(
+ LayoutPair(api::OutputLayout::kItu2051_SoundSystemA_0_2_0,
+ LoudspeakersSsConventionLayout::kSoundSystemA_0_2_0),
+ LayoutPair(api::OutputLayout::kItu2051_SoundSystemB_0_5_0,
+ LoudspeakersSsConventionLayout::kSoundSystemB_0_5_0),
+ LayoutPair(api::OutputLayout::kItu2051_SoundSystemC_2_5_0,
+ LoudspeakersSsConventionLayout::kSoundSystemC_2_5_0),
+ LayoutPair(api::OutputLayout::kItu2051_SoundSystemD_4_5_0,
+ LoudspeakersSsConventionLayout::kSoundSystemD_4_5_0),
+ LayoutPair(api::OutputLayout::kItu2051_SoundSystemE_4_5_1,
+ LoudspeakersSsConventionLayout::kSoundSystemE_4_5_1),
+ LayoutPair(api::OutputLayout::kItu2051_SoundSystemF_3_7_0,
+ LoudspeakersSsConventionLayout::kSoundSystemF_3_7_0),
+ LayoutPair(api::OutputLayout::kItu2051_SoundSystemG_4_9_0,
+ LoudspeakersSsConventionLayout::kSoundSystemG_4_9_0),
+ LayoutPair(api::OutputLayout::kItu2051_SoundSystemH_9_10_3,
+ LoudspeakersSsConventionLayout::kSoundSystemH_9_10_3),
+ LayoutPair(api::OutputLayout::kItu2051_SoundSystemI_0_7_0,
+ LoudspeakersSsConventionLayout::kSoundSystemI_0_7_0),
+ LayoutPair(api::OutputLayout::kItu2051_SoundSystemJ_4_7_0,
+ LoudspeakersSsConventionLayout::kSoundSystemJ_4_7_0),
+ LayoutPair(api::OutputLayout::kIAMF_SoundSystemExtension_2_7_0,
+ LoudspeakersSsConventionLayout::kSoundSystem10_2_7_0),
+ LayoutPair(api::OutputLayout::kIAMF_SoundSystemExtension_2_3_0,
+ LoudspeakersSsConventionLayout::kSoundSystem11_2_3_0),
+ LayoutPair(api::OutputLayout::kIAMF_SoundSystemExtension_0_1_0,
+ LoudspeakersSsConventionLayout::kSoundSystem12_0_1_0),
+ LayoutPair(api::OutputLayout::kIAMF_SoundSystemExtension_6_9_0,
+ LoudspeakersSsConventionLayout::kSoundSystem13_6_9_0));
+
TEST_P(ApiToInternalType_OutputLayout, ConvertsOutputStereoToInternalLayout) {
const auto& [api_output_layout, expected_specific_layout] = GetParam();
@@ -43,38 +76,30 @@
expected_specific_layout);
}
-INSTANTIATE_TEST_SUITE_P(
- ApiToInternalType_OutputLayout_Instantiation,
- ApiToInternalType_OutputLayout,
- ::testing::Values(
- LayoutPair(api::OutputLayout::kItu2051_SoundSystemA_0_2_0,
- LoudspeakersSsConventionLayout::kSoundSystemA_0_2_0),
- LayoutPair(api::OutputLayout::kItu2051_SoundSystemB_0_5_0,
- LoudspeakersSsConventionLayout::kSoundSystemB_0_5_0),
- LayoutPair(api::OutputLayout::kItu2051_SoundSystemC_2_5_0,
- LoudspeakersSsConventionLayout::kSoundSystemC_2_5_0),
- LayoutPair(api::OutputLayout::kItu2051_SoundSystemD_4_5_0,
- LoudspeakersSsConventionLayout::kSoundSystemD_4_5_0),
- LayoutPair(api::OutputLayout::kItu2051_SoundSystemE_4_5_1,
- LoudspeakersSsConventionLayout::kSoundSystemE_4_5_1),
- LayoutPair(api::OutputLayout::kItu2051_SoundSystemF_3_7_0,
- LoudspeakersSsConventionLayout::kSoundSystemF_3_7_0),
- LayoutPair(api::OutputLayout::kItu2051_SoundSystemG_4_9_0,
- LoudspeakersSsConventionLayout::kSoundSystemG_4_9_0),
- LayoutPair(api::OutputLayout::kItu2051_SoundSystemH_9_10_3,
- LoudspeakersSsConventionLayout::kSoundSystemH_9_10_3),
- LayoutPair(api::OutputLayout::kItu2051_SoundSystemI_0_7_0,
- LoudspeakersSsConventionLayout::kSoundSystemI_0_7_0),
- LayoutPair(api::OutputLayout::kItu2051_SoundSystemJ_4_7_0,
- LoudspeakersSsConventionLayout::kSoundSystemJ_4_7_0),
- LayoutPair(api::OutputLayout::kIAMF_SoundSystemExtension_2_7_0,
- LoudspeakersSsConventionLayout::kSoundSystem10_2_7_0),
- LayoutPair(api::OutputLayout::kIAMF_SoundSystemExtension_2_3_0,
- LoudspeakersSsConventionLayout::kSoundSystem11_2_3_0),
- LayoutPair(api::OutputLayout::kIAMF_SoundSystemExtension_0_1_0,
- LoudspeakersSsConventionLayout::kSoundSystem12_0_1_0),
- LayoutPair(api::OutputLayout::kIAMF_SoundSystemExtension_6_9_0,
- LoudspeakersSsConventionLayout::kSoundSystem13_6_9_0)));
+INSTANTIATE_TEST_SUITE_P(ApiToInternalType_OutputLayout_Instantiation,
+ ApiToInternalType_OutputLayout,
+ kApiOutputToInternalSoundSystemPairs);
+Layout MakeLayout(LoudspeakersSsConventionLayout::SoundSystem sound_system) {
+ return {.layout_type = Layout::kLayoutTypeLoudspeakersSsConvention,
+ .specific_layout =
+ LoudspeakersSsConventionLayout{.sound_system = sound_system}};
+};
+
+using InternalTypeToApi_OutputLayout = TestWithParam<LayoutPair>;
+
+TEST_P(InternalTypeToApi_OutputLayout, ConvertsInternalStereoToOutputLayout) {
+ const auto& [expected_api_output_layout, internal_sound_system] = GetParam();
+ Layout internal_layout = MakeLayout(internal_sound_system);
+
+ auto api_output_layout = InternalToApiType(internal_layout);
+
+ EXPECT_THAT(api_output_layout, IsOk());
+ EXPECT_EQ(*api_output_layout, expected_api_output_layout);
+}
+
+INSTANTIATE_TEST_SUITE_P(InternalTypeToApi_OutputLayout_Instantiation,
+ InternalTypeToApi_OutputLayout,
+ kApiOutputToInternalSoundSystemPairs);
} // namespace
} // namespace iamf_tools
diff --git a/iamf/api/decoder/BUILD b/iamf/api/decoder/BUILD
index 566e56b..dfe3aa8 100644
--- a/iamf/api/decoder/BUILD
+++ b/iamf/api/decoder/BUILD
@@ -10,12 +10,12 @@
deps = [
"//iamf/api:types",
"//iamf/api/conversion:mix_presentation_conversion",
- "//iamf/cli:audio_frame_with_data",
"//iamf/cli:obu_processor",
- "//iamf/cli:parameter_block_with_data",
"//iamf/cli:rendering_mix_presentation_finalizer",
"//iamf/common:read_bit_buffer",
"//iamf/common/utils:macros",
+ "//iamf/common/utils:sample_processing_utils",
+ "//iamf/obu:mix_presentation",
"@com_google_absl//absl/log",
"@com_google_absl//absl/status",
"@com_google_absl//absl/status:statusor",
diff --git a/iamf/api/decoder/iamf_decoder.cc b/iamf/api/decoder/iamf_decoder.cc
index b1c6bb4..b844eed 100644
--- a/iamf/api/decoder/iamf_decoder.cc
+++ b/iamf/api/decoder/iamf_decoder.cc
@@ -12,8 +12,8 @@
#include "iamf/api/decoder/iamf_decoder.h"
+#include <cstddef>
#include <cstdint>
-#include <list>
#include <memory>
#include <optional>
#include <queue>
@@ -25,12 +25,12 @@
#include "absl/types/span.h"
#include "iamf/api/conversion/mix_presentation_conversion.h"
#include "iamf/api/types.h"
-#include "iamf/cli/audio_frame_with_data.h"
#include "iamf/cli/obu_processor.h"
-#include "iamf/cli/parameter_block_with_data.h"
#include "iamf/cli/rendering_mix_presentation_finalizer.h"
#include "iamf/common/read_bit_buffer.h"
#include "iamf/common/utils/macros.h"
+#include "iamf/common/utils/sample_processing_utils.h"
+#include "iamf/obu/mix_presentation.h"
namespace iamf_tools {
namespace api {
@@ -40,8 +40,10 @@
// Holds the internal state of the decoder to hide it and necessary includes
// from API users.
struct IamfDecoder::DecoderState {
- DecoderState(std::unique_ptr<StreamBasedReadBitBuffer> read_bit_buffer)
- : read_bit_buffer(std::move(read_bit_buffer)) {}
+ DecoderState(std::unique_ptr<StreamBasedReadBitBuffer> read_bit_buffer,
+ const Layout& initial_requested_layout)
+ : read_bit_buffer(std::move(read_bit_buffer)),
+ layout(initial_requested_layout) {}
// Current status of the decoder.
Status status = Status::kAcceptingData;
@@ -59,8 +61,13 @@
// temporal units currently available.
std::queue<std::vector<std::vector<int32_t>>> rendered_pcm_samples;
- // The layout requested by the caller for the rendered output audio.
- OutputLayout requested_layout = OutputLayout::kItu2051_SoundSystemA_0_2_0;
+ // The layout used for the rendered output audio.
+ // Initially set to the requested Layout but updated by ObuProcessor.
+ Layout layout;
+
+ // TODO(b/379122580): Use the bit depth of the underlying content.
+ // Defaulting to int32 for now.
+ OutputSampleType output_sample_type = OutputSampleType::kInt32LittleEndian;
};
namespace {
@@ -70,19 +77,19 @@
// OBUs have been processed. Contracted to only return a resource exhausted
// error if there is not enough data to process the descriptor OBUs.
absl::StatusOr<std::unique_ptr<ObuProcessor>> CreateObuProcessor(
- const OutputLayout& requested_layout, bool contains_all_descriptor_obus,
- absl::Span<const uint8_t> bitstream,
- StreamBasedReadBitBuffer* read_bit_buffer) {
+ bool contains_all_descriptor_obus, absl::Span<const uint8_t> bitstream,
+ StreamBasedReadBitBuffer* read_bit_buffer, Layout& in_out_layout) {
// Happens only in the pure streaming case.
auto start_position = read_bit_buffer->Tell();
bool insufficient_data;
- // TODO(b/394376153): Update once we support other layouts.
auto obu_processor = ObuProcessor::CreateForRendering(
- ApiToInternalType(requested_layout),
+ in_out_layout,
RenderingMixPresentationFinalizer::ProduceNoSampleProcessors,
/*is_exhaustive_and_exact=*/contains_all_descriptor_obus, read_bit_buffer,
- insufficient_data);
+ in_out_layout, insufficient_data);
if (obu_processor == nullptr) {
+ // `insufficient_data` is true iff everything so far is valid but more data
+ // is needed.
if (insufficient_data && !contains_all_descriptor_obus) {
return absl::ResourceExhaustedError(
"Have not received enough data yet to process descriptor "
@@ -103,22 +110,24 @@
bool continue_processing = true;
while (continue_processing) {
auto start_position_for_temporal_unit = read_bit_buffer->Tell();
- std::list<AudioFrameWithData> audio_frames_for_temporal_unit;
- std::list<ParameterBlockWithData> parameter_blocks_for_temporal_unit;
- std::optional<int32_t> timestamp_for_temporal_unit;
+ std::optional<ObuProcessor::OutputTemporalUnit> output_temporal_unit;
// TODO(b/395889878): Add support for partial temporal units.
RETURN_IF_NOT_OK(obu_processor->ProcessTemporalUnit(
- audio_frames_for_temporal_unit, parameter_blocks_for_temporal_unit,
- timestamp_for_temporal_unit, continue_processing));
+ /*eos_is_end_of_sequence=*/false, output_temporal_unit,
+ continue_processing));
+ if (!output_temporal_unit.has_value()) {
+ break;
+ }
// Trivial IA Sequences may have empty temporal units. Do not try to
// render empty temporal unit.
- if (timestamp_for_temporal_unit.has_value()) {
+ if (output_temporal_unit.has_value()) {
absl::Span<const std::vector<int32_t>>
rendered_pcm_samples_for_temporal_unit;
RETURN_IF_NOT_OK(obu_processor->RenderTemporalUnitAndMeasureLoudness(
- *timestamp_for_temporal_unit, audio_frames_for_temporal_unit,
- parameter_blocks_for_temporal_unit,
+ output_temporal_unit->output_timestamp,
+ output_temporal_unit->output_audio_frames,
+ output_temporal_unit->output_parameter_blocks,
rendered_pcm_samples_for_temporal_unit));
rendered_pcm_samples.push(
std::vector(rendered_pcm_samples_for_temporal_unit.begin(),
@@ -135,6 +144,43 @@
return absl::OkStatus();
}
+size_t BytesPerSample(OutputSampleType sample_type) {
+ switch (sample_type) {
+ case OutputSampleType::kInt16LittleEndian:
+ return 2;
+ case OutputSampleType::kInt32LittleEndian:
+ return 4;
+ default:
+ return 0;
+ }
+}
+
+absl::Status WriteFrameToSpan(const std::vector<std::vector<int32_t>>& frame,
+ OutputSampleType sample_type,
+ absl::Span<uint8_t>& output_bytes,
+ size_t& bytes_written) {
+ const size_t bytes_per_sample = BytesPerSample(sample_type);
+ const size_t bits_per_sample = bytes_per_sample * 8;
+ const size_t required_size =
+ frame.size() * frame[0].size() * bytes_per_sample;
+ if (output_bytes.size() < required_size) {
+ return absl::InvalidArgumentError(
+ "Span does not have enough space to write output bytes.");
+ }
+ const bool big_endian = false;
+ size_t write_position = 0;
+ uint8_t* data = output_bytes.data();
+ for (int t = 0; t < frame.size(); t++) {
+ for (int c = 0; c < frame[0].size(); ++c) {
+ const uint32_t sample = static_cast<uint32_t>(frame[t][c]);
+ RETURN_IF_NOT_OK(WritePcmSample(sample, bits_per_sample, big_endian, data,
+ write_position));
+ }
+ }
+ bytes_written = write_position;
+ return absl::OkStatus();
+}
+
} // namespace
IamfDecoder::IamfDecoder(std::unique_ptr<DecoderState> state)
@@ -147,29 +193,31 @@
IamfDecoder::IamfDecoder(IamfDecoder&&) = default;
IamfDecoder& IamfDecoder::operator=(IamfDecoder&&) = default;
-absl::StatusOr<IamfDecoder> IamfDecoder::Create() {
+absl::StatusOr<IamfDecoder> IamfDecoder::Create(
+ const OutputLayout& requested_layout) {
std::unique_ptr<StreamBasedReadBitBuffer> read_bit_buffer =
StreamBasedReadBitBuffer::Create(kInitialBufferSize);
if (read_bit_buffer == nullptr) {
return absl::InternalError("Failed to create read bit buffer.");
}
- std::unique_ptr<DecoderState> state =
- std::make_unique<DecoderState>(std::move(read_bit_buffer));
+ std::unique_ptr<DecoderState> state = std::make_unique<DecoderState>(
+ std::move(read_bit_buffer), ApiToInternalType(requested_layout));
return IamfDecoder(std::move(state));
}
absl::StatusOr<IamfDecoder> IamfDecoder::CreateFromDescriptors(
+ const OutputLayout& requested_layout,
absl::Span<const uint8_t> descriptor_obus) {
- absl::StatusOr<IamfDecoder> decoder = Create();
+ absl::StatusOr<IamfDecoder> decoder = Create(requested_layout);
if (!decoder.ok()) {
return decoder.status();
}
RETURN_IF_NOT_OK(
decoder->state_->read_bit_buffer->PushBytes(descriptor_obus));
absl::StatusOr<std::unique_ptr<ObuProcessor>> obu_processor =
- CreateObuProcessor(decoder->state_->requested_layout,
- /*contains_all_descriptor_obus=*/true, descriptor_obus,
- decoder->state_->read_bit_buffer.get());
+ CreateObuProcessor(/*contains_all_descriptor_obus=*/true, descriptor_obus,
+ decoder->state_->read_bit_buffer.get(),
+ decoder->state_->layout);
if (!obu_processor.ok()) {
return obu_processor.status();
}
@@ -184,12 +232,12 @@
}
RETURN_IF_NOT_OK(state_->read_bit_buffer->PushBytes(bitstream));
if (!IsDescriptorProcessingComplete()) {
- auto obu_processor =
- CreateObuProcessor(state_->requested_layout,
- /*contains_all_descriptor_obus=*/false, bitstream,
- state_->read_bit_buffer.get());
+ auto obu_processor = CreateObuProcessor(
+ /*contains_all_descriptor_obus=*/false, bitstream,
+ state_->read_bit_buffer.get(), state_->layout);
if (obu_processor.ok()) {
state_->obu_processor = *std::move(obu_processor);
+ return absl::OkStatus();
} else if (absl::IsResourceExhausted(obu_processor.status())) {
// Don't have enough data to process the descriptor OBUs yet, but no
// errors have occurred.
@@ -213,53 +261,90 @@
"ConfigureMixPresentationId is not yet implemented.");
}
-absl::Status IamfDecoder::ConfigureOutputLayout(OutputLayout output_layout) {
- return absl::UnimplementedError(
- "ConfigureOutputLayout is not yet implemented.");
-}
-
-absl::Status IamfDecoder::ConfigureBitDepth(OutputFileBitDepth bit_depth) {
- return absl::UnimplementedError("ConfigureBitDepth is not yet implemented.");
+void IamfDecoder::ConfigureOutputSampleType(
+ OutputSampleType output_sample_type) {
+ state_->output_sample_type = output_sample_type;
}
absl::Status IamfDecoder::GetOutputTemporalUnit(
- std::vector<std::vector<int32_t>>& output_decoded_temporal_unit) {
+ absl::Span<uint8_t> output_bytes, size_t& bytes_written) {
+ bytes_written = 0;
if (state_->rendered_pcm_samples.empty()) {
- output_decoded_temporal_unit.clear();
return absl::OkStatus();
}
- output_decoded_temporal_unit = state_->rendered_pcm_samples.front();
- state_->rendered_pcm_samples.pop();
- return absl::OkStatus();
+ OutputSampleType output_sample_type = GetOutputSampleType();
+ absl::Status status =
+ WriteFrameToSpan(state_->rendered_pcm_samples.front(), output_sample_type,
+ output_bytes, bytes_written);
+ if (status.ok()) {
+ state_->rendered_pcm_samples.pop();
+ return absl::OkStatus();
+ }
+ return status;
}
-bool IamfDecoder::IsTemporalUnitAvailable() {
+bool IamfDecoder::IsTemporalUnitAvailable() const {
return !state_->rendered_pcm_samples.empty();
}
-bool IamfDecoder::IsDescriptorProcessingComplete() {
+bool IamfDecoder::IsDescriptorProcessingComplete() const {
return state_->obu_processor != nullptr;
}
+absl::StatusOr<OutputLayout> IamfDecoder::GetOutputLayout() const {
+ if (!IsDescriptorProcessingComplete()) {
+ return absl::FailedPreconditionError(
+ "GetOutputLayout() cannot be called before descriptor processing is "
+ "complete.");
+ }
+ return InternalToApiType(state_->layout);
+}
+
+absl::StatusOr<int> IamfDecoder::GetNumberOfOutputChannels() const {
+ if (!IsDescriptorProcessingComplete()) {
+ return absl::FailedPreconditionError(
+ "GetNumberOfOutputChannels() cannot be called before descriptor "
+ "processing is complete.");
+ }
+ int num_channels;
+ RETURN_IF_NOT_OK(MixPresentationObu::GetNumChannelsFromLayout(state_->layout,
+ num_channels));
+ return num_channels;
+}
+
absl::Status IamfDecoder::GetMixPresentations(
- std::vector<MixPresentationMetadata>& output_mix_presentation_metadata) {
+ std::vector<MixPresentationMetadata>& output_mix_presentation_metadata)
+ const {
return absl::UnimplementedError(
"GetMixPresentations is not yet implemented.");
}
-
-absl::Status IamfDecoder::GetSampleRate(uint32_t& output_sample_rate) {
- return absl::UnimplementedError("GetSampleRate is not yet implemented.");
+OutputSampleType IamfDecoder::GetOutputSampleType() const {
+ return state_->output_sample_type;
}
-absl::Status IamfDecoder::GetFrameSize(uint32_t& output_frame_size) {
- return absl::UnimplementedError("GetFrameSize is not yet implemented.");
+absl::StatusOr<uint32_t> IamfDecoder::GetSampleRate() const {
+ if (!IsDescriptorProcessingComplete()) {
+ return absl::FailedPreconditionError(
+ "GetSampleRate() cannot be called before descriptor processing is "
+ "complete.");
+ }
+ return state_->obu_processor->GetOutputSampleRate();
}
-absl::Status IamfDecoder::Flush(
- std::vector<std::vector<int32_t>>& output_decoded_temporal_unit,
- bool& output_is_done) {
+absl::StatusOr<uint32_t> IamfDecoder::GetFrameSize() const {
+ if (!IsDescriptorProcessingComplete()) {
+ return absl::FailedPreconditionError(
+ "GetFrameSize() cannot be called before descriptor processing is "
+ "complete.");
+ }
+
+ return state_->obu_processor->GetOutputFrameSize();
+}
+
+absl::Status IamfDecoder::Flush(absl::Span<uint8_t> output_bytes,
+ size_t& bytes_written, bool& output_is_done) {
state_->status = Status::kFlushCalled;
- RETURN_IF_NOT_OK(GetOutputTemporalUnit(output_decoded_temporal_unit));
+ RETURN_IF_NOT_OK(GetOutputTemporalUnit(output_bytes, bytes_written));
output_is_done = state_->rendered_pcm_samples.empty();
return absl::OkStatus();
}
diff --git a/iamf/api/decoder/iamf_decoder.h b/iamf/api/decoder/iamf_decoder.h
index b146b4f..643c04a 100644
--- a/iamf/api/decoder/iamf_decoder.h
+++ b/iamf/api/decoder/iamf_decoder.h
@@ -13,8 +13,7 @@
#ifndef API_DECODER_IAMF_DECODER_H_
#define API_DECODER_IAMF_DECODER_H_
-#include <sys/types.h>
-
+#include <cstddef>
#include <cstdint>
#include <memory>
#include <vector>
@@ -70,22 +69,32 @@
* This function should be used for pure streaming applications in which the
* descriptor OBUs are not known in advance.
*
+ * \param requested_layout Specifies the desired output layout. This layout
+ * will be used so long as it is present in the Descriptor OBUs that
+ * are later provided to Decode(). If not, a default layout will be
+ * selected.
+ *
* \return IamfDecoder upon success. Other specific statuses on
* failure.
*/
- static absl::StatusOr<IamfDecoder> Create();
+ static absl::StatusOr<IamfDecoder> Create(
+ const OutputLayout& requested_layout);
/*!\brief Creates an IamfDecoder from a known set of descriptor OBUs.
*
* This function should be used for applications in which the descriptor OBUs
* are known in advance.
*
+ * \param requested_layout Specifies the desired output layout. This layout
+ * will be used so long as it is present in the Descriptor OBUs that
+ * are provided. If not, a default layout will be selected.
* \param descriptor_obus Bitstream containing all the descriptor OBUs and
* only descriptor OBUs.
* \return IamfDecoder upon success. Other specific statuses on
* failure.
*/
static absl::StatusOr<IamfDecoder> CreateFromDescriptors(
+ const OutputLayout& requested_layout,
absl::Span<const uint8_t> descriptor_obus);
/*!\brief Configures the decoder with the desired mix presentation.
@@ -97,25 +106,13 @@
absl::Status ConfigureMixPresentationId(
MixPresentationId mix_presentation_id);
- /*!\brief Configures the decoder with the desired output layout.
- *
- * \param output_layout Specifies the desired output layout.
- * \return `absl::OkStatus()` upon success. Other specific statuses on
- * failure.
- */
- absl::Status ConfigureOutputLayout(OutputLayout output_layout);
-
/*!\brief Configures the decoder with the desired bit depth.
*
- * \param bit_depth Specifies the desired bit depth.
- * \return `absl::OkStatus()` upon success. Other specific statuses on
- * failure.
+ * Call this method to specify a specific output sample type. If it is not
+ * called, the output samples will be a default value, retrievable by
+ * `GetOutputSampleType`.
*/
- // TODO(b/379124235): Update OutputFileBitDepth to OutputBitDepth to
- // indicate that this is not specific to file-based decoding.
- // TODO(b/379122580): Decide how we would like to support float-based
- // decoding.
- absl::Status ConfigureBitDepth(OutputFileBitDepth bit_depth);
+ void ConfigureOutputSampleType(OutputSampleType output_sample_type);
/*!\brief Decodes the bitstream provided.
*
@@ -123,8 +120,10 @@
* both. User can provide as much data as they would like. To receive decoded
* temporal units, GetOutputTemporalUnit() should be called. If
* GetOutputTemporalUnit() has not been called, this function guarantees that
- * any temporal units received thus far have not been lost. See sample usages
- * for more details.
+ * any temporal units received thus far have not been lost. If descriptors are
+ * processed for the first time, function will exit before processing any
+ * temporal units. This provides the user a chance to configure the decoder as
+ * they see fit. See sample usages for more details.
*
* \param bitstream Bitstream to decode.
* \return `absl::OkStatus()` upon success. Other specific statuses on
@@ -139,14 +138,15 @@
* may be more than one temporal unit available. When this returns empty, the
* user should call Decode() again with more data.
*
- * \param output_decoded_temporal_unit Output parameter for the next temporal
- * unit of decoded audio. The outer vector corresponds to a tick, while
- * the inner vector corresponds to a channel.
+ * \param output_bytes Output buffer to receive bytes. Must be large enough
+ * to receive bytes. Maximum necessary size can be determined by
+ * GetFrameSize and GetOutputSampleType.
+ * \param bytes_written Number of bytes written to the output_bytes.
* \return `absl::OkStatus()` upon success. Other specific statuses on
* failure.
*/
- absl::Status GetOutputTemporalUnit(
- std::vector<std::vector<int32_t>>& output_decoded_temporal_unit);
+ absl::Status GetOutputTemporalUnit(absl::Span<uint8_t> output_bytes,
+ size_t& bytes_written);
/*!\brief Returns true iff a decoded temporal unit is available.
*
@@ -155,46 +155,89 @@
*
* \return true iff a decoded temporal unit is available.
*/
- bool IsTemporalUnitAvailable();
+ bool IsTemporalUnitAvailable() const;
/*!\brief Returns true iff the descriptor OBUs have been parsed.
*
* This function can be used for determining when configuration setters that
- * rely on descriptor OBU parsing can be called.
+ * rely on Descriptor OBU parsing can be called.
*
- * \return true iff the descriptor OBUs have been parsed.
+ * \return true iff the Descriptor OBUs have been parsed.
*/
- bool IsDescriptorProcessingComplete();
+ bool IsDescriptorProcessingComplete() const;
+
+ /*!\brief Gets the layout that will be used to render the audio.
+ *
+ * The actual Layout used for rendering may not the same as requested when
+ * creating the IamfDecoder, if the requested Layout could not be used.
+ * This function allows verifying the actual Layout used after Descriptor OBU
+ * parsing is complete.
+ *
+ * This function can only be used after all Descriptor OBUs have been parsed,
+ * i.e. IsDescriptorProcessingComplete() returns true.
+ *
+ * \return OutputLayout or error statuses on failure.
+ */
+ absl::StatusOr<OutputLayout> GetOutputLayout() const;
+
+ /*!\brief Gets the number of output channels.
+ *
+ * This function can only be used after all Descriptor OBUs have been parsed,
+ * i.e. IsDescriptorProcessingComplete() returns true.
+ *
+ * \return
+ */
+ absl::StatusOr<int> GetNumberOfOutputChannels() const;
/*!\brief Provides mix presentation information from the descriptor OBUs.
*
* This function can be used to determine which mix presentation the user
- * would like to configure the decoder with. It will fail if the descriptor
- * OBUs have not been parsed yet.
+ * would like to configure the decoder with.
+ *
+ * This function can only be used after all Descriptor OBUs have been parsed,
+ * i.e. IsDescriptorProcessingComplete() returns true.
*
* \param output_mix_presentation_metadatas Output parameter for the mix
* presentation metadata.
* \return `absl::OkStatus()` upon success. Other specific statuses on
* failure.
*/
- absl::Status GetMixPresentations(
- std::vector<MixPresentationMetadata>& output_mix_presentation_metadatas);
+ absl::Status GetMixPresentations(std::vector<MixPresentationMetadata>&
+ output_mix_presentation_metadatas) const;
+
+ /*!\brief Returns the current OutputSampleType.
+ *
+ * The value is either the value set by ConfigureOutputSampleType or a default
+ * which may vary based on content.
+ *
+ * This function can only be used after all Descriptor OBUs have been parsed,
+ * i.e. IsDescriptorProcessingComplete() returns true.
+ */
+ OutputSampleType GetOutputSampleType() const;
/*!\brief Gets the sample rate.
*
- * \param output_sample_rate Output parameter for the sample rate.
+ * This function can only be used after all Descriptor OBUs have been parsed,
+ * i.e. IsDescriptorProcessingComplete() returns true.
+ *
* \return `absl::OkStatus()` upon success. Other specific statuses on
* failure.
*/
- absl::Status GetSampleRate(uint32_t& output_sample_rate);
+ absl::StatusOr<uint32_t> GetSampleRate() const;
/*!\brief Gets the number of samples per frame.
*
- * \param output_frame_size Output parameter for the frame size.
- * \return `absl::OkStatus()` upon success. Other specific statuses on
- * failure.
+ * This function can only be used after all Descriptor OBUs have been parsed,
+ * i.e. IsDescriptorProcessingComplete() returns true.
+ *
+ * Returns the number of samples per frame of the output audio. The total
+ * number of samples in a time tick is the number of channels times the number
+ * of samples per frame.
+ *
+ * \return Number of samples per frame upon success. Other specific statuses
+ * on failure.
*/
- absl::Status GetFrameSize(uint32_t& output_frame_size);
+ absl::StatusOr<uint32_t> GetFrameSize() const;
/*!\brief Outputs the last temporal unit(s) of decoded audio.
*
@@ -210,9 +253,8 @@
* \return `absl::OkStatus()` upon success. Other specific statuses on
* failure.
*/
- absl::Status Flush(
- std::vector<std::vector<int32_t>>& output_decoded_temporal_unit,
- bool& output_is_done);
+ absl::Status Flush(absl::Span<uint8_t> output_bytes, size_t& bytes_written,
+ bool& output_is_done);
/*!\brief Closes the decoder.
*
diff --git a/iamf/api/decoder/tests/BUILD b/iamf/api/decoder/tests/BUILD
index 8c6255b..2d1493a 100644
--- a/iamf/api/decoder/tests/BUILD
+++ b/iamf/api/decoder/tests/BUILD
@@ -1,16 +1,37 @@
+load("@rules_cc//cc:cc_test.bzl", "cc_test")
+
# keep-sorted start block=yes prefix_order=cc_test newline_separated=yes
cc_test(
+ name = "iamf_decoder_fuzz_test",
+ srcs = ["iamf_decoder_fuzz_test.cc"],
+ target_compatible_with = [
+ "@platforms//os:linux",
+ ],
+ deps = [
+ "//iamf/api:types",
+ "//iamf/api/decoder:iamf_decoder",
+ "@com_google_absl//absl/status:status_matchers",
+ "@com_google_absl//absl/types:span",
+ "@com_google_fuzztest//fuzztest",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_test(
name = "iamf_decoder_test",
srcs = ["iamf_decoder_test.cc"],
deps = [
"//iamf/api:types",
"//iamf/api/decoder:iamf_decoder",
"//iamf/cli:audio_element_with_data",
+ "//iamf/cli:audio_frame_with_data",
+ "//iamf/cli:parameter_block_with_data",
"//iamf/cli/tests:cli_test_utils",
"//iamf/obu:audio_frame",
"//iamf/obu:codec_config",
"//iamf/obu:ia_sequence_header",
"//iamf/obu:mix_presentation",
+ "//iamf/obu:obu_base",
"//iamf/obu:obu_header",
"//iamf/obu:temporal_delimiter",
"//iamf/obu:types",
@@ -20,5 +41,4 @@
"@com_google_googletest//:gtest_main",
],
)
-
# keep-sorted end
diff --git a/iamf/api/decoder/tests/iamf_decoder_fuzz_test.cc b/iamf/api/decoder/tests/iamf_decoder_fuzz_test.cc
new file mode 100644
index 0000000..d07aeca
--- /dev/null
+++ b/iamf/api/decoder/tests/iamf_decoder_fuzz_test.cc
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2024, Alliance for Open Media. All rights reserved
+ *
+ * This source code is subject to the terms of the BSD 3-Clause Clear License
+ * and the Alliance for Open Media Patent License 1.0. If the BSD 3-Clause Clear
+ * License was not distributed with this source code in the LICENSE file, you
+ * can obtain it at www.aomedia.org/license/software-license/bsd-3-c-c. If the
+ * Alliance for Open Media Patent License 1.0 was not distributed with this
+ * source code in the PATENTS file, you can obtain it at
+ * www.aomedia.org/license/patent.
+ */
+
+#include <cstdint>
+#include <string>
+#include <vector>
+
+#include "absl/status/status_matchers.h"
+#include "absl/types/span.h"
+#include "fuzztest/fuzztest.h"
+#include "gmock/gmock.h"
+// [internal] Placeholder for FLAC fuzzing include.
+#include "iamf/api/decoder/iamf_decoder.h"
+#include "iamf/api/types.h"
+
+namespace iamf_tools {
+namespace {
+
+using api::OutputLayout;
+using ::fuzztest::Arbitrary;
+using ::fuzztest::ElementOf;
+
+constexpr OutputLayout kStereoLayout =
+ OutputLayout::kItu2051_SoundSystemA_0_2_0;
+
+void DoesNotDieWithBasicDecode(const std::string& data) {
+ absl::StatusOr<api::IamfDecoder> iamf_decoder =
+ api::IamfDecoder::Create(kStereoLayout);
+ std::vector<uint8_t> bitstream(data.begin(), data.end());
+ ASSERT_THAT(iamf_decoder, ::absl_testing::IsOk());
+
+ auto decode_status = iamf_decoder->Decode(bitstream);
+}
+
+FUZZ_TEST(IamfDecoderFuzzTest_ArbitraryBytes, DoesNotDieWithBasicDecode);
+
+void DoesNotDieCreateFromDescriptors(const std::string& data) {
+ std::vector<uint8_t> bitstream(data.begin(), data.end());
+
+ absl::StatusOr<api::IamfDecoder> iamf_decoder =
+ api::IamfDecoder::CreateFromDescriptors(kStereoLayout, bitstream);
+
+ if (iamf_decoder.ok()) {
+ auto decoder_status = iamf_decoder->Decode(bitstream);
+ }
+}
+
+FUZZ_TEST(IamfDecoderFuzzTest_ArbitraryBytesToDescriptors,
+ DoesNotDieCreateFromDescriptors);
+
+void DoesNotDieAllParams(api::OutputLayout output_layout,
+ api::OutputSampleType output_sample_type,
+ uint32_t mix_presentation_id, std::string data) {
+ std::vector<uint8_t> bitstream(data.begin(), data.end());
+ absl::StatusOr<api::IamfDecoder> iamf_decoder =
+ api::IamfDecoder::Create(kStereoLayout);
+ ASSERT_THAT(iamf_decoder, ::absl_testing::IsOk());
+
+ auto decode_status = iamf_decoder->Decode(bitstream);
+ iamf_decoder->ConfigureOutputSampleType(output_sample_type);
+}
+
+// // TODO(b/378912426): Update this to support all output layouts.
+auto AnyOutputLayout() {
+ return ElementOf<api::OutputLayout>({
+ api::OutputLayout::kItu2051_SoundSystemA_0_2_0,
+ api::OutputLayout::kItu2051_SoundSystemB_0_5_0,
+ api::OutputLayout::kItu2051_SoundSystemC_2_5_0,
+ api::OutputLayout::kItu2051_SoundSystemD_4_5_0,
+ api::OutputLayout::kItu2051_SoundSystemE_4_5_1,
+ api::OutputLayout::kItu2051_SoundSystemF_3_7_0,
+ api::OutputLayout::kItu2051_SoundSystemG_4_9_0,
+ api::OutputLayout::kItu2051_SoundSystemH_9_10_3,
+ api::OutputLayout::kItu2051_SoundSystemI_0_7_0,
+ api::OutputLayout::kItu2051_SoundSystemJ_4_7_0,
+ api::OutputLayout::kIAMF_SoundSystemExtension_2_7_0,
+ api::OutputLayout::kIAMF_SoundSystemExtension_2_3_0,
+ api::OutputLayout::kIAMF_SoundSystemExtension_0_1_0,
+ api::OutputLayout::kIAMF_SoundSystemExtension_6_9_0,
+ });
+}
+
+auto AnyOutputSampleType() {
+ return ElementOf<api::OutputSampleType>({
+ api::OutputSampleType::kInt16LittleEndian,
+ api::OutputSampleType::kInt32LittleEndian,
+ });
+}
+
+FUZZ_TEST(IamfDecoderFuzzTest_AllArbitraryParams, DoesNotDieAllParams)
+ .WithDomains(AnyOutputLayout(), // output_layout,
+ AnyOutputSampleType(), // output_sample_type,
+ Arbitrary<uint32_t>(), // mix_presentation_id,
+ Arbitrary<std::string>()); // data
+
+} // namespace
+} // namespace iamf_tools
diff --git a/iamf/api/decoder/tests/iamf_decoder_test.cc b/iamf/api/decoder/tests/iamf_decoder_test.cc
index 7d87332..e72c794 100644
--- a/iamf/api/decoder/tests/iamf_decoder_test.cc
+++ b/iamf/api/decoder/tests/iamf_decoder_test.cc
@@ -13,6 +13,7 @@
#include "iamf/api/decoder/iamf_decoder.h"
#include <array>
+#include <cstddef>
#include <cstdint>
#include <list>
#include <vector>
@@ -24,11 +25,14 @@
#include "gtest/gtest.h"
#include "iamf/api/types.h"
#include "iamf/cli/audio_element_with_data.h"
+#include "iamf/cli/audio_frame_with_data.h"
+#include "iamf/cli/parameter_block_with_data.h"
#include "iamf/cli/tests/cli_test_utils.h"
#include "iamf/obu/audio_frame.h"
#include "iamf/obu/codec_config.h"
#include "iamf/obu/ia_sequence_header.h"
#include "iamf/obu/mix_presentation.h"
+#include "iamf/obu/obu_base.h"
#include "iamf/obu/obu_header.h"
#include "iamf/obu/temporal_delimiter.h"
#include "iamf/obu/types.h"
@@ -37,8 +41,15 @@
namespace {
using ::absl_testing::IsOk;
+using ::absl_testing::IsOkAndHolds;
+using ::testing::Not;
+
+using api::OutputLayout;
+using ::testing::Not;
constexpr DecodedUleb128 kFirstCodecConfigId = 1;
+constexpr uint32_t kNumSamplesPerFrame = 8;
+constexpr uint32_t kBitDepth = 16;
constexpr DecodedUleb128 kSampleRate = 48000;
constexpr DecodedUleb128 kFirstAudioElementId = 2;
constexpr DecodedUleb128 kFirstSubstreamId = 18;
@@ -53,8 +64,8 @@
ObuHeader(), IASequenceHeaderObu::kIaCode,
ProfileVersion::kIamfSimpleProfile, ProfileVersion::kIamfBaseProfile);
absl::flat_hash_map<DecodedUleb128, CodecConfigObu> codec_configs;
- AddLpcmCodecConfigWithIdAndSampleRate(kFirstCodecConfigId, kSampleRate,
- codec_configs);
+ AddLpcmCodecConfig(kFirstCodecConfigId, kNumSamplesPerFrame, kBitDepth,
+ kSampleRate, codec_configs);
absl::flat_hash_map<DecodedUleb128, AudioElementWithData> audio_elements;
AddAmbisonicsMonoAudioElementWithSubstreamIds(
kFirstAudioElementId, kFirstCodecConfigId, {kFirstSubstreamId},
@@ -71,12 +82,116 @@
TEST(IsDescriptorProcessingComplete,
ReturnsFalseBeforeDescriptorObusAreProcessed) {
- auto decoder = api::IamfDecoder::Create();
+ auto decoder =
+ api::IamfDecoder::Create(OutputLayout::kItu2051_SoundSystemA_0_2_0);
+
EXPECT_FALSE(decoder->IsDescriptorProcessingComplete());
}
+TEST(IamfDecoder,
+ MethodsDependingOnDescriptorsFailBeforeDescriptorObusAreProcessed) {
+ auto decoder =
+ api::IamfDecoder::Create(OutputLayout::kItu2051_SoundSystemA_0_2_0);
+
+ EXPECT_THAT(decoder->GetOutputLayout(), Not(IsOk()));
+ EXPECT_THAT(decoder->GetNumberOfOutputChannels(), Not(IsOk()));
+ EXPECT_THAT(decoder->GetSampleRate(), Not(IsOk()));
+ EXPECT_THAT(decoder->GetFrameSize(), Not(IsOk()));
+ std::vector<api::MixPresentationMetadata> output_mix_presentation_metadatas;
+ EXPECT_THAT(decoder->GetMixPresentations(output_mix_presentation_metadatas),
+ Not(IsOk()));
+}
+
+TEST(GetOutputLayout, ReturnsOutputLayoutAfterDescriptorObusAreProcessed) {
+ auto decoder = api::IamfDecoder::CreateFromDescriptors(
+ OutputLayout::kItu2051_SoundSystemA_0_2_0, GenerateBasicDescriptorObus());
+
+ EXPECT_TRUE(decoder->IsDescriptorProcessingComplete());
+ auto output_layout = decoder->GetOutputLayout();
+ EXPECT_THAT(output_layout.status(), IsOk());
+ EXPECT_EQ(*output_layout, OutputLayout::kItu2051_SoundSystemA_0_2_0);
+ auto number_of_output_channels = decoder->GetNumberOfOutputChannels();
+ EXPECT_THAT(number_of_output_channels.status(), IsOk());
+ EXPECT_EQ(*number_of_output_channels, 2);
+}
+
+TEST(GetOutputLayout, ReturnsDefaultStereoLayoutIfNoMatchingLayoutExists) {
+ auto decoder = api::IamfDecoder::CreateFromDescriptors(
+ OutputLayout::kItu2051_SoundSystemE_4_5_1, GenerateBasicDescriptorObus());
+
+ EXPECT_TRUE(decoder->IsDescriptorProcessingComplete());
+ auto output_layout = decoder->GetOutputLayout();
+ EXPECT_THAT(output_layout.status(), IsOk());
+ EXPECT_EQ(*output_layout, OutputLayout::kItu2051_SoundSystemA_0_2_0);
+ auto number_of_output_channels = decoder->GetNumberOfOutputChannels();
+ EXPECT_THAT(number_of_output_channels.status(), IsOk());
+ EXPECT_EQ(*number_of_output_channels, 2);
+}
+
+TEST(GetOutputLayout,
+ ReturnsDefaultStereoLayoutIfNoMatchingLayoutExistsUsingDecode) {
+ auto decoder =
+ api::IamfDecoder::Create(OutputLayout::kItu2051_SoundSystemE_4_5_1);
+ std::vector<uint8_t> source_data = GenerateBasicDescriptorObus();
+ TemporalDelimiterObu temporal_delimiter_obu =
+ TemporalDelimiterObu(ObuHeader());
+ auto temporal_delimiter_bytes =
+ SerializeObusExpectOk({&temporal_delimiter_obu});
+ source_data.insert(source_data.end(), temporal_delimiter_bytes.begin(),
+ temporal_delimiter_bytes.end());
+
+ EXPECT_THAT(decoder->Decode(source_data), IsOk());
+
+ EXPECT_TRUE(decoder->IsDescriptorProcessingComplete());
+ auto output_layout = decoder->GetOutputLayout();
+ EXPECT_THAT(output_layout.status(), IsOk());
+ EXPECT_EQ(*output_layout, OutputLayout::kItu2051_SoundSystemA_0_2_0);
+ auto number_of_output_channels = decoder->GetNumberOfOutputChannels();
+ EXPECT_THAT(number_of_output_channels.status(), IsOk());
+ EXPECT_EQ(*number_of_output_channels, 2);
+}
+
+TEST(GetOutputLayout, ReturnsNonStereoLayoutWhenPresentInDescriptorObus) {
+ // Add a mix presentation with a non-stereo layout.
+ const IASequenceHeaderObu ia_sequence_header(
+ ObuHeader(), IASequenceHeaderObu::kIaCode,
+ ProfileVersion::kIamfSimpleProfile, ProfileVersion::kIamfBaseProfile);
+ absl::flat_hash_map<DecodedUleb128, CodecConfigObu> codec_configs;
+ AddLpcmCodecConfigWithIdAndSampleRate(kFirstCodecConfigId, kSampleRate,
+ codec_configs);
+ absl::flat_hash_map<DecodedUleb128, AudioElementWithData> audio_elements;
+ AddAmbisonicsMonoAudioElementWithSubstreamIds(
+ kFirstAudioElementId, kFirstCodecConfigId, {kFirstSubstreamId},
+ codec_configs, audio_elements);
+ std::vector<LoudspeakersSsConventionLayout::SoundSystem>
+ mix_presentation_layouts = {
+ LoudspeakersSsConventionLayout::kSoundSystemA_0_2_0,
+ LoudspeakersSsConventionLayout::kSoundSystemB_0_5_0};
+ std::list<MixPresentationObu> mix_presentation_obus;
+ AddMixPresentationObuWithConfigurableLayouts(
+ kFirstMixPresentationId, {kFirstAudioElementId},
+ kCommonMixGainParameterId, kCommonParameterRate, mix_presentation_layouts,
+ mix_presentation_obus);
+ std::vector<uint8_t> descriptor_obus = SerializeObusExpectOk(
+ {&ia_sequence_header, &codec_configs.at(kFirstCodecConfigId),
+ &audio_elements.at(kFirstAudioElementId).obu,
+ &mix_presentation_obus.front()});
+
+ auto decoder = api::IamfDecoder::CreateFromDescriptors(
+ OutputLayout::kItu2051_SoundSystemB_0_5_0, descriptor_obus);
+
+ EXPECT_TRUE(decoder->IsDescriptorProcessingComplete());
+ auto output_layout = decoder->GetOutputLayout();
+ EXPECT_THAT(output_layout.status(), IsOk());
+ EXPECT_EQ(*output_layout, OutputLayout::kItu2051_SoundSystemB_0_5_0);
+ auto number_of_output_channels = decoder->GetNumberOfOutputChannels();
+ EXPECT_THAT(number_of_output_channels.status(), IsOk());
+ EXPECT_EQ(*number_of_output_channels, 6);
+}
+
TEST(Create, SucceedsAndDecodeSucceedsWithPartialData) {
- auto decoder = api::IamfDecoder::Create();
+ auto decoder =
+ api::IamfDecoder::Create(OutputLayout::kItu2051_SoundSystemA_0_2_0);
EXPECT_THAT(decoder, IsOk());
std::vector<uint8_t> source_data = {0x01, 0x23, 0x45};
@@ -84,9 +199,24 @@
EXPECT_FALSE(decoder->IsDescriptorProcessingComplete());
}
-TEST(CreateFromDescriptors, Succeeds) {
+TEST(Create, SucceedsWithNonStereoLayout) {
auto decoder =
- api::IamfDecoder::CreateFromDescriptors(GenerateBasicDescriptorObus());
+ api::IamfDecoder::Create(OutputLayout::kItu2051_SoundSystemB_0_5_0);
+ EXPECT_THAT(decoder, IsOk());
+}
+
+TEST(CreateFromDescriptors, Succeeds) {
+ auto decoder = api::IamfDecoder::CreateFromDescriptors(
+ OutputLayout::kItu2051_SoundSystemA_0_2_0, GenerateBasicDescriptorObus());
+
+ EXPECT_THAT(decoder, IsOk());
+ EXPECT_TRUE(decoder->IsDescriptorProcessingComplete());
+}
+
+TEST(CreateFromDescriptors, SucceedsWithNonStereoLayout) {
+ auto decoder = api::IamfDecoder::CreateFromDescriptors(
+ OutputLayout::kItu2051_SoundSystemB_0_5_0, GenerateBasicDescriptorObus());
+
EXPECT_THAT(decoder, IsOk());
EXPECT_TRUE(decoder->IsDescriptorProcessingComplete());
}
@@ -95,13 +225,16 @@
auto descriptors = GenerateBasicDescriptorObus();
// remove the last byte to make the descriptor OBUs incomplete.
descriptors.pop_back();
- auto decoder = api::IamfDecoder::CreateFromDescriptors(descriptors);
+
+ auto decoder = api::IamfDecoder::CreateFromDescriptors(
+ OutputLayout::kItu2051_SoundSystemA_0_2_0, descriptors);
+
EXPECT_FALSE(decoder.ok());
}
TEST(CreateFromDescriptors, FailsWithDescriptorObuInSubsequentDecode) {
- auto decoder =
- api::IamfDecoder::CreateFromDescriptors(GenerateBasicDescriptorObus());
+ auto decoder = api::IamfDecoder::CreateFromDescriptors(
+ OutputLayout::kItu2051_SoundSystemA_0_2_0, GenerateBasicDescriptorObus());
EXPECT_THAT(decoder, IsOk());
EXPECT_TRUE(decoder->IsDescriptorProcessingComplete());
@@ -115,7 +248,8 @@
}
TEST(Decode, SucceedsAndProcessesDescriptorsWithTemporalDelimiterAtEnd) {
- auto decoder = api::IamfDecoder::Create();
+ auto decoder =
+ api::IamfDecoder::Create(OutputLayout::kItu2051_SoundSystemA_0_2_0);
ASSERT_THAT(decoder, IsOk());
std::vector<uint8_t> source_data = GenerateBasicDescriptorObus();
TemporalDelimiterObu temporal_delimiter_obu =
@@ -130,7 +264,8 @@
}
TEST(Decode, SucceedsWithMultiplePushesOfDescriptorObus) {
- auto decoder = api::IamfDecoder::Create();
+ auto decoder =
+ api::IamfDecoder::Create(OutputLayout::kItu2051_SoundSystemA_0_2_0);
ASSERT_THAT(decoder, IsOk());
std::vector<uint8_t> source_data = GenerateBasicDescriptorObus();
TemporalDelimiterObu temporal_delimiter_obu =
@@ -151,7 +286,8 @@
TEST(Decode, SucceedsWithSeparatePushesOfDescriptorAndTemporalUnits) {
std::vector<uint8_t> source_data = GenerateBasicDescriptorObus();
- auto decoder = api::IamfDecoder::CreateFromDescriptors(source_data);
+ auto decoder = api::IamfDecoder::CreateFromDescriptors(
+ OutputLayout::kItu2051_SoundSystemA_0_2_0, source_data);
ASSERT_THAT(decoder, IsOk());
EXPECT_FALSE(decoder->IsTemporalUnitAvailable());
AudioFrameObu audio_frame(ObuHeader(), kFirstSubstreamId,
@@ -162,7 +298,8 @@
}
TEST(Decode, SucceedsWithOneTemporalUnit) {
- auto decoder = api::IamfDecoder::Create();
+ auto decoder =
+ api::IamfDecoder::Create(OutputLayout::kItu2051_SoundSystemA_0_2_0);
ASSERT_THAT(decoder, IsOk());
std::vector<uint8_t> source_data = GenerateBasicDescriptorObus();
AudioFrameObu audio_frame(ObuHeader(), kFirstSubstreamId,
@@ -175,7 +312,8 @@
}
TEST(Decode, SucceedsWithMultipleTemporalUnits) {
- auto decoder = api::IamfDecoder::Create();
+ auto decoder =
+ api::IamfDecoder::Create(OutputLayout::kItu2051_SoundSystemA_0_2_0);
ASSERT_THAT(decoder, IsOk());
std::vector<uint8_t> source_data = GenerateBasicDescriptorObus();
AudioFrameObu audio_frame(ObuHeader(), kFirstSubstreamId,
@@ -187,8 +325,65 @@
EXPECT_THAT(decoder->Decode(source_data), IsOk());
}
+TEST(Decode, SucceedsWithMultipleTemporalUnitsForNonStereoLayout) {
+ auto decoder =
+ api::IamfDecoder::Create(OutputLayout::kIAMF_SoundSystemExtension_0_1_0);
+ ASSERT_THAT(decoder, IsOk());
+
+ absl::flat_hash_map<DecodedUleb128, CodecConfigObu> codec_config_obus;
+ AddLpcmCodecConfigWithIdAndSampleRate(kFirstCodecConfigId, kSampleRate,
+ codec_config_obus);
+ absl::flat_hash_map<DecodedUleb128, AudioElementWithData>
+ audio_elements_with_data;
+ AddAmbisonicsMonoAudioElementWithSubstreamIds(
+ kFirstAudioElementId, kFirstCodecConfigId, {kFirstSubstreamId},
+ codec_config_obus, audio_elements_with_data);
+ std::list<MixPresentationObu> mix_presentation_obus;
+ std::vector<LoudspeakersSsConventionLayout::SoundSystem>
+ sound_system_layouts = {
+ LoudspeakersSsConventionLayout::kSoundSystemA_0_2_0,
+ LoudspeakersSsConventionLayout::kSoundSystem12_0_1_0};
+ AddMixPresentationObuWithConfigurableLayouts(
+ kFirstMixPresentationId, {kFirstAudioElementId},
+ kCommonMixGainParameterId, kCommonParameterRate, sound_system_layouts,
+ mix_presentation_obus);
+
+ const std::list<AudioFrameWithData> empty_audio_frames_with_data = {};
+ const std::list<ParameterBlockWithData> empty_parameter_blocks_with_data = {};
+
+ const IASequenceHeaderObu ia_sequence_header(
+ ObuHeader(), IASequenceHeaderObu::kIaCode,
+ ProfileVersion::kIamfSimpleProfile, ProfileVersion::kIamfBaseProfile);
+ std::list<const ObuBase*> input_ia_sequence(
+ {&codec_config_obus.at(kFirstCodecConfigId),
+ &audio_elements_with_data.at(kFirstAudioElementId).obu,
+ &mix_presentation_obus.front()});
+ input_ia_sequence.push_front(&ia_sequence_header);
+
+ std::vector<uint8_t> source_data = SerializeObusExpectOk(input_ia_sequence);
+ AudioFrameObu audio_frame(ObuHeader(), kFirstSubstreamId,
+ kEightSampleAudioFrame);
+ auto temporal_units = SerializeObusExpectOk({&audio_frame, &audio_frame});
+ source_data.insert(source_data.end(), temporal_units.begin(),
+ temporal_units.end());
+
+ EXPECT_THAT(decoder->Decode(source_data), IsOk());
+ // Calling with empty due to forced exit after descriptor processing, so that
+ // we can get the output temporal unit.
+ EXPECT_THAT(decoder->Decode({}), IsOk());
+
+ const size_t expected_output_size = 8 * 4; // 8 samples, 32-bit ints, mono.
+ std::vector<uint8_t> output_data(expected_output_size);
+ size_t bytes_written;
+ EXPECT_THAT(decoder->GetOutputTemporalUnit(absl::MakeSpan(output_data),
+ bytes_written),
+ IsOk());
+ EXPECT_EQ(bytes_written, expected_output_size);
+}
+
TEST(Decode, FailsWhenCalledAfterFlush) {
- auto decoder = api::IamfDecoder::Create();
+ auto decoder =
+ api::IamfDecoder::Create(OutputLayout::kItu2051_SoundSystemA_0_2_0);
ASSERT_THAT(decoder, IsOk());
std::vector<uint8_t> source_data = GenerateBasicDescriptorObus();
AudioFrameObu audio_frame(ObuHeader(), kFirstSubstreamId,
@@ -196,23 +391,34 @@
auto temporal_units = SerializeObusExpectOk({&audio_frame, &audio_frame});
source_data.insert(source_data.end(), temporal_units.begin(),
temporal_units.end());
- EXPECT_THAT(decoder->Decode(source_data), IsOk());
- std::vector<std::vector<int32_t>> output_decoded_temporal_unit;
+ ASSERT_THAT(decoder->Decode(source_data), IsOk());
+ ASSERT_FALSE(decoder->IsTemporalUnitAvailable());
+ ASSERT_THAT(decoder->Decode({}), IsOk());
+ ASSERT_TRUE(decoder->IsTemporalUnitAvailable());
+
+ size_t expected_size = 2 * 8 * 4; // Stereo * 8 samples * int32.
+ std::vector<uint8_t> output_data(expected_size);
+ size_t bytes_written;
bool output_is_done;
- EXPECT_THAT(decoder->Flush(output_decoded_temporal_unit, output_is_done),
+ EXPECT_THAT(decoder->Flush(absl::MakeSpan(output_data), bytes_written,
+ output_is_done),
IsOk());
+ EXPECT_TRUE(output_is_done);
+ EXPECT_EQ(bytes_written, expected_size);
EXPECT_FALSE(decoder->Decode(source_data).ok());
}
TEST(IsTemporalUnitAvailable, ReturnsFalseAfterCreateFromDescriptorObus) {
- auto decoder =
- api::IamfDecoder::CreateFromDescriptors(GenerateBasicDescriptorObus());
+ auto decoder = api::IamfDecoder::CreateFromDescriptors(
+ OutputLayout::kItu2051_SoundSystemA_0_2_0, GenerateBasicDescriptorObus());
ASSERT_THAT(decoder, IsOk());
EXPECT_FALSE(decoder->IsTemporalUnitAvailable());
}
-TEST(IsTemporalUnitAvailable, ReturnsTrueAfterDecodingOneTemporalUnit) {
- auto decoder = api::IamfDecoder::Create();
+TEST(IsTemporalUnitAvailable,
+ TemporalUnitIsNotAvailableAfterDecodeWithNoTemporalDelimiterAtEnd) {
+ auto decoder =
+ api::IamfDecoder::Create(OutputLayout::kItu2051_SoundSystemA_0_2_0);
ASSERT_THAT(decoder, IsOk());
std::vector<uint8_t> source_data = GenerateBasicDescriptorObus();
AudioFrameObu audio_frame(ObuHeader(), kFirstSubstreamId,
@@ -222,11 +428,41 @@
temporal_unit.end());
ASSERT_THAT(decoder->Decode(source_data), IsOk());
+ EXPECT_FALSE(decoder->IsTemporalUnitAvailable());
+}
+
+TEST(IsTemporalUnitAvailable,
+ ReturnsTrueAfterDecodingOneTemporalUnitWithTemporalDelimiterAtEnd) {
+ auto decoder =
+ api::IamfDecoder::Create(OutputLayout::kItu2051_SoundSystemA_0_2_0);
+ ASSERT_THAT(decoder, IsOk());
+ std::vector<uint8_t> source_data = GenerateBasicDescriptorObus();
+ AudioFrameObu audio_frame(ObuHeader(), kFirstSubstreamId,
+ kEightSampleAudioFrame);
+ auto temporal_delimiter_obu = TemporalDelimiterObu(ObuHeader());
+ auto temporal_unit =
+ SerializeObusExpectOk({&audio_frame, &temporal_delimiter_obu});
+ source_data.insert(source_data.end(), temporal_unit.begin(),
+ temporal_unit.end());
+
+ ASSERT_THAT(decoder->Decode(source_data), IsOk());
+ EXPECT_TRUE(decoder->IsDescriptorProcessingComplete());
+ // Even though a temporal unit was provided, it has not been decoded yet. This
+ // is because Decode() returns after processing the descriptor OBUs, even if
+ // there is more data available. This is done to give the user a chance to do
+ // any configurations based on the descriptors before beginning to decode the
+ // temporal units.
+ EXPECT_FALSE(decoder->IsTemporalUnitAvailable());
+ // The user can call Decode() again to process the temporal unit still
+ // available in the buffer.
+ EXPECT_THAT(decoder->Decode({}), IsOk());
+ // Now, the temporal unit has been decoded and is available for output.
EXPECT_TRUE(decoder->IsTemporalUnitAvailable());
}
TEST(IsTemporalUnitAvailable, ReturnsTrueAfterDecodingMultipleTemporalUnits) {
- auto decoder = api::IamfDecoder::Create();
+ auto decoder =
+ api::IamfDecoder::Create(OutputLayout::kItu2051_SoundSystemA_0_2_0);
ASSERT_THAT(decoder, IsOk());
std::vector<uint8_t> source_data = GenerateBasicDescriptorObus();
AudioFrameObu audio_frame(ObuHeader(), kFirstSubstreamId,
@@ -236,28 +472,14 @@
temporal_units.end());
ASSERT_THAT(decoder->Decode(source_data), IsOk());
+ EXPECT_FALSE(decoder->IsTemporalUnitAvailable());
+ EXPECT_THAT(decoder->Decode({}), IsOk());
EXPECT_TRUE(decoder->IsTemporalUnitAvailable());
}
-TEST(IsTemporalUnitAvailable, ReturnsFalseAfterPoppingLastTemporalUnit) {
- auto decoder = api::IamfDecoder::Create();
- ASSERT_THAT(decoder, IsOk());
- std::vector<uint8_t> source_data = GenerateBasicDescriptorObus();
- AudioFrameObu audio_frame(ObuHeader(), kFirstSubstreamId,
- kEightSampleAudioFrame);
- auto temporal_unit = SerializeObusExpectOk({&audio_frame});
- source_data.insert(source_data.end(), temporal_unit.begin(),
- temporal_unit.end());
-
- ASSERT_THAT(decoder->Decode(source_data), IsOk());
- std::vector<std::vector<int32_t>> output_decoded_temporal_unit;
- ASSERT_THAT(decoder->GetOutputTemporalUnit(output_decoded_temporal_unit),
- IsOk());
- EXPECT_FALSE(decoder->IsTemporalUnitAvailable());
-}
-
-TEST(GetOutputTemporalUnit, FillsOutputVectorWithMultipleTemporalUnits) {
- auto decoder = api::IamfDecoder::Create();
+TEST(GetOutputTemporalUnit, FillsOutputVectorWithLastTemporalUnit) {
+ auto decoder =
+ api::IamfDecoder::Create(OutputLayout::kItu2051_SoundSystemA_0_2_0);
ASSERT_THAT(decoder, IsOk());
std::vector<uint8_t> source_data = GenerateBasicDescriptorObus();
AudioFrameObu audio_frame(ObuHeader(), kFirstSubstreamId,
@@ -266,65 +488,159 @@
source_data.insert(source_data.end(), temporal_units.begin(),
temporal_units.end());
ASSERT_THAT(decoder->Decode(source_data), IsOk());
+ EXPECT_FALSE(decoder->IsTemporalUnitAvailable());
+ EXPECT_THAT(decoder->Decode({}), IsOk());
+ EXPECT_TRUE(decoder->IsTemporalUnitAvailable());
- const std::vector<std::vector<int32_t>> expected_decoded_temporal_unit = {
- {23772706, 23773107}, {47591754, 47592556}, {71410802, 71412005},
- {95229849, 95231454}, {119048897, 119050903}, {142867944, 142870353},
- {166686992, 166689802}, {190506039, 190509251}};
+ size_t expected_size = 2 * 8 * 4;
+ std::vector<uint8_t> output_data(expected_size);
+ size_t bytes_written;
+ EXPECT_THAT(decoder->GetOutputTemporalUnit(absl::MakeSpan(output_data),
+ bytes_written),
+ IsOk());
- // Get the first temporal unit.
- std::vector<std::vector<int32_t>> output_decoded_temporal_unit;
- EXPECT_THAT(decoder->GetOutputTemporalUnit(output_decoded_temporal_unit),
+ EXPECT_EQ(bytes_written, expected_size);
+ EXPECT_FALSE(decoder->IsTemporalUnitAvailable());
+}
+
+TEST(GetOutputTemporalUnit, FillsOutputVectorWithInt16) {
+ auto decoder =
+ api::IamfDecoder::Create(OutputLayout::kItu2051_SoundSystemA_0_2_0);
+ decoder->ConfigureOutputSampleType(api::OutputSampleType::kInt16LittleEndian);
+ ASSERT_THAT(decoder, IsOk());
+ std::vector<uint8_t> source_data = GenerateBasicDescriptorObus();
+ AudioFrameObu audio_frame(ObuHeader(), kFirstSubstreamId,
+ kEightSampleAudioFrame);
+ auto temporal_units = SerializeObusExpectOk({&audio_frame, &audio_frame});
+ source_data.insert(source_data.end(), temporal_units.begin(),
+ temporal_units.end());
+
+ ASSERT_THAT(decoder->Decode(source_data), IsOk());
+ EXPECT_FALSE(decoder->IsTemporalUnitAvailable());
+ EXPECT_THAT(decoder->Decode({}), IsOk());
+ EXPECT_TRUE(decoder->IsTemporalUnitAvailable());
+
+ size_t expected_size = 2 * 8 * 2; // Stereo * 8 samples * 2 bytes (int16).
+ std::vector<uint8_t> output_data(expected_size);
+ size_t bytes_written;
+ EXPECT_THAT(decoder->GetOutputTemporalUnit(absl::MakeSpan(output_data),
+ bytes_written),
IsOk());
- EXPECT_EQ(output_decoded_temporal_unit, expected_decoded_temporal_unit);
- output_decoded_temporal_unit.clear();
- // Get the second temporal unit.
- EXPECT_THAT(decoder->GetOutputTemporalUnit(output_decoded_temporal_unit),
- IsOk());
- EXPECT_EQ(output_decoded_temporal_unit, expected_decoded_temporal_unit);
+
+ EXPECT_EQ(bytes_written, expected_size);
+}
+
+TEST(GetOutputTemporalUnit, FailsWhenBufferTooSmall) {
+ auto decoder =
+ api::IamfDecoder::Create(OutputLayout::kItu2051_SoundSystemA_0_2_0);
+ decoder->ConfigureOutputSampleType(api::OutputSampleType::kInt16LittleEndian);
+ ASSERT_THAT(decoder, IsOk());
+ std::vector<uint8_t> source_data = GenerateBasicDescriptorObus();
+ AudioFrameObu audio_frame(ObuHeader(), kFirstSubstreamId,
+ kEightSampleAudioFrame);
+ auto temporal_units = SerializeObusExpectOk({&audio_frame, &audio_frame});
+ source_data.insert(source_data.end(), temporal_units.begin(),
+ temporal_units.end());
+
+ ASSERT_THAT(decoder->Decode(source_data), IsOk());
+ EXPECT_FALSE(decoder->IsTemporalUnitAvailable());
+ EXPECT_THAT(decoder->Decode({}), IsOk());
+ EXPECT_TRUE(decoder->IsTemporalUnitAvailable());
+
+ size_t needed_size = 2 * 8 * 2; // Stereo * 8 samples * 2 bytes (int16).
+ std::vector<uint8_t> output_data(needed_size - 1); // Buffer too small.
+ size_t bytes_written;
+ EXPECT_THAT(decoder->GetOutputTemporalUnit(absl::MakeSpan(output_data),
+ bytes_written),
+ Not(IsOk()));
+ EXPECT_EQ(bytes_written, 0);
}
TEST(GetOutputTemporalUnit,
DoesNotFillOutputVectorWhenNoTemporalUnitIsAvailable) {
std::vector<uint8_t> source_data = GenerateBasicDescriptorObus();
- auto decoder = api::IamfDecoder::CreateFromDescriptors(source_data);
+ auto decoder = api::IamfDecoder::CreateFromDescriptors(
+ OutputLayout::kItu2051_SoundSystemA_0_2_0, source_data);
ASSERT_THAT(decoder, IsOk());
- std::vector<std::vector<int32_t>> output_decoded_temporal_unit;
- EXPECT_THAT(decoder->GetOutputTemporalUnit(output_decoded_temporal_unit),
+ std::vector<uint8_t> output_data;
+ size_t bytes_written;
+ EXPECT_THAT(decoder->GetOutputTemporalUnit(absl::MakeSpan(output_data),
+ bytes_written),
IsOk());
- EXPECT_TRUE(output_decoded_temporal_unit.empty());
+
+ EXPECT_EQ(bytes_written, 0);
}
TEST(Flush, SucceedsWithMultipleTemporalUnits) {
- auto decoder = api::IamfDecoder::Create();
+ auto decoder =
+ api::IamfDecoder::Create(OutputLayout::kItu2051_SoundSystemA_0_2_0);
ASSERT_THAT(decoder, IsOk());
std::vector<uint8_t> source_data = GenerateBasicDescriptorObus();
AudioFrameObu audio_frame(ObuHeader(), kFirstSubstreamId,
kEightSampleAudioFrame);
- auto temporal_units = SerializeObusExpectOk({&audio_frame, &audio_frame});
+ auto temporal_delimiter_obu = TemporalDelimiterObu(ObuHeader());
+ auto temporal_units =
+ SerializeObusExpectOk({&audio_frame, &temporal_delimiter_obu,
+ &audio_frame, &temporal_delimiter_obu});
source_data.insert(source_data.end(), temporal_units.begin(),
temporal_units.end());
ASSERT_THAT(decoder->Decode(source_data), IsOk());
- std::vector<std::vector<int32_t>> output_decoded_temporal_unit;
+ EXPECT_FALSE(decoder->IsTemporalUnitAvailable());
+ EXPECT_THAT(decoder->Decode({}), IsOk());
+ EXPECT_TRUE(decoder->IsTemporalUnitAvailable());
+
+ // Stereo * 8 samples * 4 bytes per sample
+ const size_t expected_size_per_temp_unit = 2 * 8 * 4;
+ std::vector<uint8_t> output_data(expected_size_per_temp_unit);
+ size_t bytes_written;
bool output_is_done;
- EXPECT_THAT(decoder->Flush(output_decoded_temporal_unit, output_is_done),
+ EXPECT_THAT(decoder->Flush(absl::MakeSpan(output_data), bytes_written,
+ output_is_done),
IsOk());
+ EXPECT_EQ(bytes_written, expected_size_per_temp_unit);
EXPECT_FALSE(output_is_done);
- EXPECT_THAT(decoder->Flush(output_decoded_temporal_unit, output_is_done),
+ EXPECT_THAT(decoder->Flush(absl::MakeSpan(output_data), bytes_written,
+ output_is_done),
IsOk());
+ EXPECT_EQ(bytes_written, expected_size_per_temp_unit);
EXPECT_TRUE(output_is_done);
}
TEST(Flush, SucceedsWithNoTemporalUnits) {
- auto decoder = api::IamfDecoder::Create();
+ auto decoder =
+ api::IamfDecoder::Create(OutputLayout::kItu2051_SoundSystemA_0_2_0);
ASSERT_THAT(decoder, IsOk());
+
std::vector<std::vector<int32_t>> output_decoded_temporal_unit;
+ std::vector<uint8_t> output_data;
+ size_t bytes_written;
bool output_is_done;
- EXPECT_THAT(decoder->Flush(output_decoded_temporal_unit, output_is_done),
+ EXPECT_THAT(decoder->Flush(absl::MakeSpan(output_data), bytes_written,
+ output_is_done),
IsOk());
+
+ EXPECT_EQ(bytes_written, 0);
EXPECT_TRUE(output_is_done);
}
+TEST(GetSampleRate, ReturnsSampleRateBasedOnCodecConfigObu) {
+ std::vector<uint8_t> source_data = GenerateBasicDescriptorObus();
+ auto decoder = api::IamfDecoder::CreateFromDescriptors(
+ api::OutputLayout::kItu2051_SoundSystemA_0_2_0, source_data);
+ ASSERT_THAT(decoder, IsOk());
+
+ EXPECT_THAT(decoder->GetSampleRate(), IsOkAndHolds(kSampleRate));
+}
+
+TEST(GetFrameSize, ReturnsFrameSizeBasedOnCodecConfigObu) {
+ std::vector<uint8_t> source_data = GenerateBasicDescriptorObus();
+ auto decoder = api::IamfDecoder::CreateFromDescriptors(
+ api::OutputLayout::kItu2051_SoundSystemA_0_2_0, source_data);
+ ASSERT_THAT(decoder, IsOk());
+
+ EXPECT_THAT(decoder->GetFrameSize(), IsOkAndHolds(kNumSamplesPerFrame));
+}
+
} // namespace
} // namespace iamf_tools
diff --git a/iamf/api/types.h b/iamf/api/types.h
index 173b712..76a7f85 100644
--- a/iamf/api/types.h
+++ b/iamf/api/types.h
@@ -57,13 +57,10 @@
kIAMF_SoundSystemExtension_6_9_0 = 13,
};
-/*!\brief Determines the format of the output file. */
-enum OutputFileBitDepth {
- kBitDepthAutomatic, // Automatically determine based on the bit-depth of
- // the input file.
- kBitDepth16,
- kBitDepth24,
- kBitDepth32,
+/*!\brief The requested format of the output samples. */
+enum class OutputSampleType {
+ kInt16LittleEndian = 1,
+ kInt32LittleEndian = 2,
};
/*!\brief A unique identifier for a `MixPresentation` in the IAMF stream. */
diff --git a/iamf/cli/BUILD b/iamf/cli/BUILD
index 231777c..5791758 100644
--- a/iamf/cli/BUILD
+++ b/iamf/cli/BUILD
@@ -292,6 +292,7 @@
":sample_processor_base",
"//iamf/common:read_bit_buffer",
"//iamf/common/utils:macros",
+ "//iamf/common/utils:validation_utils",
"//iamf/obu:audio_element",
"//iamf/obu:audio_frame",
"//iamf/obu:codec_config",
diff --git a/iamf/cli/adm_to_user_metadata/adm/panner.cc b/iamf/cli/adm_to_user_metadata/adm/panner.cc
index bd35d81..43ea63d 100644
--- a/iamf/cli/adm_to_user_metadata/adm/panner.cc
+++ b/iamf/cli/adm_to_user_metadata/adm/panner.cc
@@ -178,7 +178,7 @@
std::vector<uint8_t> output_buffer_char(
num_samples_to_read * op_wav_nch *
(ip_wav_bits_per_sample / kBitsPerByte));
- int write_position = 0;
+ size_t write_position = 0;
for (size_t i = 0; i < num_samples_to_read * op_wav_nch; ++i) {
RETURN_IF_NOT_OK(WritePcmSample(
op_buffer_int32[i], ip_wav_bits_per_sample,
diff --git a/iamf/cli/adm_to_user_metadata/iamf/mix_presentation_handler.cc b/iamf/cli/adm_to_user_metadata/iamf/mix_presentation_handler.cc
index 076fe49..85817ad 100644
--- a/iamf/cli/adm_to_user_metadata/iamf/mix_presentation_handler.cc
+++ b/iamf/cli/adm_to_user_metadata/iamf/mix_presentation_handler.cc
@@ -225,11 +225,9 @@
mix_presentation_obu_metadata.add_annotations_language("en-us");
mix_presentation_obu_metadata.add_localized_presentation_annotations(
"test_mix_pres");
- mix_presentation_obu_metadata.set_num_sub_mixes(1);
auto& mix_presentation_sub_mix =
*mix_presentation_obu_metadata.add_sub_mixes();
- mix_presentation_sub_mix.set_num_audio_elements(audio_objects.size());
for (const auto& audio_object : audio_objects) {
const auto status = SubMixAudioElementMetadataBuilder(
audio_object, audio_object_id_to_audio_element_id_[audio_object.id],
diff --git a/iamf/cli/adm_to_user_metadata/iamf/tests/mix_presentation_handler_test.cc b/iamf/cli/adm_to_user_metadata/iamf/tests/mix_presentation_handler_test.cc
index 9d92713..672a3e2 100644
--- a/iamf/cli/adm_to_user_metadata/iamf/tests/mix_presentation_handler_test.cc
+++ b/iamf/cli/adm_to_user_metadata/iamf/tests/mix_presentation_handler_test.cc
@@ -109,9 +109,9 @@
TEST(PopulateMixPresentation, PopulatesStereoSubmix) {
const auto& mix_presentation_metadata = GetMixObuMetataExpectOk();
- EXPECT_EQ(mix_presentation_metadata.num_sub_mixes(), 1);
+ EXPECT_EQ(mix_presentation_metadata.sub_mixes_size(), 1);
const auto& submix = mix_presentation_metadata.sub_mixes(0);
- EXPECT_EQ(submix.num_audio_elements(), 1);
+ EXPECT_EQ(submix.audio_elements_size(), 1);
const auto& audio_element = submix.audio_elements(0);
const uint32_t kExpectedAudioElementId = 0;
@@ -245,14 +245,14 @@
TEST(PopulateMixPresentation, AlwaysPopulatesExactlyOneSubmix) {
EXPECT_EQ(
GetMixObuMetataExpectOk({GetStereoAudioObject(), Get5_1AudioObject()})
- .num_sub_mixes(),
+ .sub_mixes_size(),
1);
}
TEST(PopulateMixPresentation, PopulatesOneAudioElementPerAudioObject) {
EXPECT_EQ(
GetMixObuMetataExpectOk({GetStereoAudioObject(), Get5_1AudioObject()})
.sub_mixes(0)
- .num_audio_elements(),
+ .audio_elements_size(),
2);
}
diff --git a/iamf/cli/adm_to_user_metadata/iamf/tests/user_metadata_generator_test.cc b/iamf/cli/adm_to_user_metadata/iamf/tests/user_metadata_generator_test.cc
index cddb207..8cae426 100644
--- a/iamf/cli/adm_to_user_metadata/iamf/tests/user_metadata_generator_test.cc
+++ b/iamf/cli/adm_to_user_metadata/iamf/tests/user_metadata_generator_test.cc
@@ -197,7 +197,7 @@
EXPECT_EQ(user_metadata.mix_presentation_metadata().size(), 1);
EXPECT_EQ(user_metadata.mix_presentation_metadata(0)
.sub_mixes(0)
- .num_audio_elements(),
+ .audio_elements_size(),
1);
}
@@ -216,16 +216,16 @@
GetAdmWithStereoAndToaObjectsAndTwoAudioProgrammes());
EXPECT_EQ(user_metadata.mix_presentation_metadata().size(), 2);
- EXPECT_EQ(user_metadata.mix_presentation_metadata(0).num_sub_mixes(), 1);
+ EXPECT_EQ(user_metadata.mix_presentation_metadata(0).sub_mixes_size(), 1);
EXPECT_EQ(user_metadata.mix_presentation_metadata(0)
.sub_mixes(0)
- .num_audio_elements(),
+ .audio_elements_size(),
kExpectedFirstMixPresentationNumAudioElements);
- EXPECT_EQ(user_metadata.mix_presentation_metadata(1).num_sub_mixes(), 1);
+ EXPECT_EQ(user_metadata.mix_presentation_metadata(1).sub_mixes_size(), 1);
EXPECT_EQ(user_metadata.mix_presentation_metadata(1)
.sub_mixes(0)
- .num_audio_elements(),
+ .audio_elements_size(),
kExpectedSecondMixPresentationNumAudioElements);
}
diff --git a/iamf/cli/ambisonic_encoder/ambisonic_encoder.h b/iamf/cli/ambisonic_encoder/ambisonic_encoder.h
index c9e367f..f7ac799 100644
--- a/iamf/cli/ambisonic_encoder/ambisonic_encoder.h
+++ b/iamf/cli/ambisonic_encoder/ambisonic_encoder.h
@@ -23,7 +23,7 @@
namespace iamf_tools {
-// TODO(b/400635711): Use the one in the iamfbr library once it is open-sourced.
+// TODO(b/400635711): Use the one in the obr library once it is open-sourced.
class AmbisonicEncoder {
public:
/*!\brief Ambisonic Encoder constructor.
diff --git a/iamf/cli/ambisonic_encoder/ambisonic_utils.h b/iamf/cli/ambisonic_encoder/ambisonic_utils.h
index 6e8eb83..236b136 100644
--- a/iamf/cli/ambisonic_encoder/ambisonic_utils.h
+++ b/iamf/cli/ambisonic_encoder/ambisonic_utils.h
@@ -18,7 +18,7 @@
#include "absl/log/check.h"
-// TODO(b/400635711): Use the one in the iamfbr library once it is open-sourced.
+// TODO(b/400635711): Use the one in the obr library once it is open-sourced.
// This code is forked from Resonance Audio's `misc_math.h`.
namespace iamf_tools {
// Defines conversion factor from degrees to radians.
diff --git a/iamf/cli/ambisonic_encoder/associated_legendre_polynomials_generator.cc b/iamf/cli/ambisonic_encoder/associated_legendre_polynomials_generator.cc
index 68deb2a..4ea7352 100644
--- a/iamf/cli/ambisonic_encoder/associated_legendre_polynomials_generator.cc
+++ b/iamf/cli/ambisonic_encoder/associated_legendre_polynomials_generator.cc
@@ -9,7 +9,7 @@
* source code in the PATENTS file, you can obtain it at
* www.aomedia.org/license/patent.
*/
-// TODO(b/400635711): Use the one in the iamfbr library once it is open-sourced.
+// TODO(b/400635711): Use the one in the obr library once it is open-sourced.
#include "iamf/cli/ambisonic_encoder/associated_legendre_polynomials_generator.h"
#include <cmath>
diff --git a/iamf/cli/ambisonic_encoder/associated_legendre_polynomials_generator.h b/iamf/cli/ambisonic_encoder/associated_legendre_polynomials_generator.h
index f88f987..5a9ebc7 100644
--- a/iamf/cli/ambisonic_encoder/associated_legendre_polynomials_generator.h
+++ b/iamf/cli/ambisonic_encoder/associated_legendre_polynomials_generator.h
@@ -9,7 +9,7 @@
* source code in the PATENTS file, you can obtain it at
* www.aomedia.org/license/patent.
*/
-// TODO(b/400635711): Use the one in the iamfbr library once it is open-sourced.
+// TODO(b/400635711): Use the one in the obr library once it is open-sourced.
#ifndef CLI_AMBISONIC_ENCODER_ASSOCIATED_LEGENDRE_POLYNOMIALS_GENERATOR_H_
#define CLI_AMBISONIC_ENCODER_ASSOCIATED_LEGENDRE_POLYNOMIALS_GENERATOR_H_
diff --git a/iamf/cli/cli_util.cc b/iamf/cli/cli_util.cc
index 07c7acd..5e6e554 100644
--- a/iamf/cli/cli_util.cc
+++ b/iamf/cli/cli_util.cc
@@ -102,7 +102,7 @@
const Layout& layout, int& output_submix_index, int& output_layout_index) {
for (int s = 0; s < mix_presentation_sub_mixes.size(); s++) {
const auto& sub_mix = mix_presentation_sub_mixes[s];
- for (int l = 0; l < sub_mix.num_layouts; l++) {
+ for (int l = 0; l < sub_mix.layouts.size(); l++) {
const auto& mix_presentation_layout = sub_mix.layouts[l];
if (layout == mix_presentation_layout.loudness_layout) {
output_submix_index = s;
@@ -184,24 +184,19 @@
}
absl::Status WritePcmFrameToBuffer(
- const std::vector<std::vector<int32_t>>& frame,
- uint32_t samples_to_trim_at_start, uint32_t samples_to_trim_at_end,
- uint8_t bit_depth, bool big_endian, std::vector<uint8_t>& buffer) {
- if (bit_depth % 8 != 0) {
+ const std::vector<std::vector<int32_t>>& frame, uint8_t bit_depth,
+ bool big_endian, std::vector<uint8_t>& buffer) {
+ if (bit_depth % 8 != 0) [[unlikely]] {
return absl::InvalidArgumentError(
"This function only supports an integer number of bytes.");
}
- const size_t num_samples =
- (frame.size() - samples_to_trim_at_start - samples_to_trim_at_end) *
- frame[0].size();
-
+ const size_t num_samples = frame.size() * frame[0].size();
buffer.resize(num_samples * (bit_depth / 8));
// The input frame is arranged in (time, channel) axes. Interlace these in
- // the output PCM and skip over any trimmed samples.
- int write_position = 0;
- for (int t = samples_to_trim_at_start;
- t < frame.size() - samples_to_trim_at_end; t++) {
+ // the output PCM.
+ size_t write_position = 0;
+ for (int t = 0; t < frame.size(); t++) {
for (int c = 0; c < frame[0].size(); ++c) {
const uint32_t sample = static_cast<uint32_t>(frame[t][c]);
RETURN_IF_NOT_OK(WritePcmSample(sample, bit_depth, big_endian,
diff --git a/iamf/cli/cli_util.h b/iamf/cli/cli_util.h
index 5a36510..f28d238 100644
--- a/iamf/cli/cli_util.h
+++ b/iamf/cli/cli_util.h
@@ -90,8 +90,6 @@
/*!\brief Writes interlaced PCM samples into the output buffer.
*
* \param frame Input frames arranged in (time, channel) axes.
- * \param samples_to_trim_at_start Samples to trim at the beginning.
- * \param samples_to_trim_at_end Samples to trim at the end.
* \param bit_depth Sample size in bits.
* \param big_endian Whether the sample should be written as big or little
* endian.
@@ -99,9 +97,8 @@
* \return `absl::OkStatus()` on success. A specific status on failure.
*/
absl::Status WritePcmFrameToBuffer(
- const std::vector<std::vector<int32_t>>& frame,
- uint32_t samples_to_trim_at_start, uint32_t samples_to_trim_at_end,
- uint8_t bit_depth, bool big_endian, std::vector<uint8_t>& buffer);
+ const std::vector<std::vector<int32_t>>& frame, uint8_t bit_depth,
+ bool big_endian, std::vector<uint8_t>& buffer);
/*!\brief Gets the common output sample rate and bit-deph of the input sets.
*
diff --git a/iamf/cli/codec/aac_encoder.cc b/iamf/cli/codec/aac_encoder.cc
index b8959c2..aed5eb7 100644
--- a/iamf/cli/codec/aac_encoder.cc
+++ b/iamf/cli/codec/aac_encoder.cc
@@ -11,6 +11,7 @@
*/
#include "iamf/cli/codec/aac_encoder.h"
+#include <cstddef>
#include <cstdint>
#include <memory>
#include <utility>
@@ -221,7 +222,7 @@
const bool big_endian = IsNativeBigEndian();
std::vector<INT_PCM> encoder_input_pcm(
num_samples_per_channel * num_channels_, 0);
- int write_position = 0;
+ size_t write_position = 0;
for (int t = 0; t < samples.size(); t++) {
for (int c = 0; c < samples[0].size(); ++c) {
// Convert all frames to INT_PCM samples for input for `fdk_aac` (usually
diff --git a/iamf/cli/codec/lpcm_encoder.cc b/iamf/cli/codec/lpcm_encoder.cc
index 217636b..e00bad5 100644
--- a/iamf/cli/codec/lpcm_encoder.cc
+++ b/iamf/cli/codec/lpcm_encoder.cc
@@ -72,10 +72,8 @@
auto& audio_frame = partial_audio_frame_with_data->obu.audio_frame_;
const bool big_endian = !(decoder_config_.sample_format_flags_bitmask_ &
LpcmDecoderConfig::kLpcmLittleEndian);
- RETURN_IF_NOT_OK(WritePcmFrameToBuffer(
- samples, /*samples_to_trim_at_start=*/0,
- /*samples_to_trim_at_end=*/0, decoder_config_.sample_size_, big_endian,
- audio_frame));
+ RETURN_IF_NOT_OK(WritePcmFrameToBuffer(samples, decoder_config_.sample_size_,
+ big_endian, audio_frame));
absl::MutexLock lock(&mutex_);
finalized_audio_frames_.emplace_back(
diff --git a/iamf/cli/codec/opus_encoder.cc b/iamf/cli/codec/opus_encoder.cc
index dd77564..e212cbd 100644
--- a/iamf/cli/codec/opus_encoder.cc
+++ b/iamf/cli/codec/opus_encoder.cc
@@ -11,6 +11,7 @@
*/
#include "iamf/cli/codec/opus_encoder.h"
+#include <cstddef>
#include <cstdint>
#include <memory>
#include <utility>
@@ -90,7 +91,7 @@
// Convert input to the array that will be passed to `opus_encode`.
std::vector<opus_int16> encoder_input_pcm(
num_samples_per_channel * num_channels, 0);
- int write_position = 0;
+ size_t write_position = 0;
for (int t = 0; t < samples.size(); t++) {
for (int c = 0; c < samples[0].size(); ++c) {
// Convert all frames to 16-bit samples for input to Opus.
diff --git a/iamf/cli/demixing_module.cc b/iamf/cli/demixing_module.cc
index 3be62a8..07f9d68 100644
--- a/iamf/cli/demixing_module.cc
+++ b/iamf/cli/demixing_module.cc
@@ -28,6 +28,7 @@
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
+#include "absl/strings/string_view.h"
#include "iamf/cli/audio_element_with_data.h"
#include "iamf/cli/audio_frame_decoder.h"
#include "iamf/cli/audio_frame_with_data.h"
@@ -614,8 +615,8 @@
}
// NOOP function if the frame is not a DecodedAudioFrame.
-absl::Status PassThroughReconGainData(const AudioFrameWithData& audio_frame,
- LabeledFrame& labeled_frame) {
+absl::Status PassThroughReconGainData(const AudioFrameWithData& /*audio_frame*/,
+ LabeledFrame& /*labeled_frame*/) {
return absl::OkStatus();
}
@@ -757,21 +758,16 @@
"Unsupported audio element type= ", obu.GetAudioElementType()));
}
}
-void LogForAudioElementId(
- DecodedUleb128 audio_element_id,
- const IdLabeledFrameMap& id_to_labeled_frame,
- const IdLabeledFrameMap& id_to_labeled_decoded_frame) {
- if (!id_to_labeled_frame.contains(audio_element_id) ||
- id_to_labeled_decoded_frame.contains(audio_element_id)) {
+void LogForAudioElementId(absl::string_view log_prefix,
+ DecodedUleb128 audio_element_id,
+ const IdLabeledFrameMap& id_to_labeled_frame) {
+ if (!id_to_labeled_frame.contains(audio_element_id)) {
return;
}
for (const auto& [label, samples] :
id_to_labeled_frame.at(audio_element_id).label_to_samples) {
- const auto& decoded_samples =
- id_to_labeled_decoded_frame.at(audio_element_id)
- .label_to_samples.at(label);
- LOG(INFO) << " Channel " << label << ":\tframe size= " << samples.size()
- << "; decoded frame size= " << decoded_samples.size();
+ LOG_FIRST_N(INFO, 1) << " Channel " << label << ":\t" << log_prefix
+ << " frame size= " << samples.size() << ".";
}
}
@@ -813,7 +809,8 @@
audio_element_id_to_demixing_metadata[audio_element_id]));
}
- return DemixingModule(std::move(audio_element_id_to_demixing_metadata));
+ return DemixingModule(DemixingMode::kDownMixingAndReconstruction,
+ std::move(audio_element_id_to_demixing_metadata));
}
absl::StatusOr<DemixingModule> DemixingModule::CreateForReconstruction(
@@ -839,7 +836,8 @@
iter->second.down_mixers.clear();
}
- return DemixingModule(std::move(audio_element_id_to_demixing_metadata));
+ return DemixingModule(DemixingMode::kReconstruction,
+ std::move(audio_element_id_to_demixing_metadata));
}
absl::Status DemixingModule::DownMixSamplesToSubstreams(
@@ -923,11 +921,14 @@
}
// TODO(b/288240600): Down-mix audio samples in a standalone function too.
-absl::Status DemixingModule::DemixAudioSamples(
- const std::list<AudioFrameWithData>& audio_frames,
- const std::list<DecodedAudioFrame>& decoded_audio_frames,
- IdLabeledFrameMap& id_to_labeled_frame,
- IdLabeledFrameMap& id_to_labeled_decoded_frame) const {
+absl::StatusOr<IdLabeledFrameMap> DemixingModule::DemixOriginalAudioSamples(
+ const std::list<AudioFrameWithData>& audio_frames) const {
+ if (demixing_mode_ == DemixingMode::kReconstruction) {
+ return absl::FailedPreconditionError(
+ "Demixing original audio samples is not available in reconstruction "
+ "mode.");
+ }
+ IdLabeledFrameMap id_to_labeled_frame;
for (const auto& [audio_element_id, demixing_metadata] :
audio_element_id_to_demixing_metadata_) {
// Process the original audio frames.
@@ -939,6 +940,18 @@
ApplyDemixers(demixing_metadata.demixers, labeled_frame));
id_to_labeled_frame[audio_element_id] = std::move(labeled_frame);
}
+
+ LogForAudioElementId("Original", audio_element_id, id_to_labeled_frame);
+ }
+
+ return id_to_labeled_frame;
+}
+
+absl::StatusOr<IdLabeledFrameMap> DemixingModule::DemixDecodedAudioSamples(
+ const std::list<DecodedAudioFrame>& decoded_audio_frames) const {
+ IdLabeledFrameMap id_to_labeled_decoded_frame;
+ for (const auto& [audio_element_id, demixing_metadata] :
+ audio_element_id_to_demixing_metadata_) {
// Process the decoded audio frames.
LabeledFrame labeled_decoded_frame;
RETURN_IF_NOT_OK(StoreSamplesForAudioElementId(
@@ -951,11 +964,11 @@
std::move(labeled_decoded_frame);
}
- LogForAudioElementId(audio_element_id, id_to_labeled_frame,
+ LogForAudioElementId("Decoded", audio_element_id,
id_to_labeled_decoded_frame);
}
- return absl::OkStatus();
+ return id_to_labeled_decoded_frame;
}
absl::Status DemixingModule::GetDownMixers(
diff --git a/iamf/cli/demixing_module.h b/iamf/cli/demixing_module.h
index a34049a..aa9b1f5 100644
--- a/iamf/cli/demixing_module.h
+++ b/iamf/cli/demixing_module.h
@@ -174,20 +174,28 @@
absl::flat_hash_map<uint32_t, SubstreamData>&
substream_id_to_substream_data) const;
- /*!\brief Demix audio samples.
+ /*!\brief Demix original audio samples.
+ *
+ * This is most useful when the original (before lossy codec) samples are
+ * known, such as when encoding original audio.
*
* \param audio_frames Audio Frames.
- * \param decoded_audio_frames Decoded Audio Frames.
- * \param id_to_labeled_frame Output data structure for samples.
- * \param id_to_labeled_decoded_frame Output data structure for decoded
- * samples.
- * \return `absl::OkStatus()` on success. A specific status on failure.
+ * \return Output data structure for samples, or a specific status on failure.
*/
- absl::Status DemixAudioSamples(
- const std::list<AudioFrameWithData>& audio_frames,
- const std::list<DecodedAudioFrame>& decoded_audio_frames,
- IdLabeledFrameMap& id_to_labeled_frame,
- IdLabeledFrameMap& id_to_labeled_decoded_frame) const;
+ absl::StatusOr<IdLabeledFrameMap> DemixOriginalAudioSamples(
+ const std::list<AudioFrameWithData>& audio_frames) const;
+
+ /*!\brief Demix decoded audio samples.
+ *
+ * This is most useful when the decoded (after lossy codec) samples are
+ * known, such as when decoding an IA Sequence, or when analyzing the effect
+ * of a lossy codec to determine appropriate recon gain values.
+ *
+ * \param decoded_audio_frames Decoded Audio Frames.
+ * \return Output data structure for samples, or a specific status on failure.
+ */
+ absl::StatusOr<IdLabeledFrameMap> DemixDecodedAudioSamples(
+ const std::list<DecodedAudioFrame>& decoded_audio_frame) const;
/*!\brief Gets the down-mixers associated with an Audio Element ID.
*
@@ -208,20 +216,27 @@
const std::list<Demixer>*& demixers) const;
private:
+ enum class DemixingMode { kDownMixingAndReconstruction, kReconstruction };
+
/*!\brief Private constructor.
*
* For use with `CreateForDownMixingAndReconstruction` and
* `CreateForReconstruction`.
*
+ * \param demixing_mode Mode of the class.
* \param audio_element_id_to_demixing_metadata Mapping from audio element ID
* to demixing metadata.
*/
DemixingModule(
+ DemixingMode demixing_mode,
absl::flat_hash_map<DecodedUleb128, DemixingMetadataForAudioElementId>&&
audio_element_id_to_demixing_metadata)
- : audio_element_id_to_demixing_metadata_(
+ : demixing_mode_(demixing_mode),
+ audio_element_id_to_demixing_metadata_(
std::move(audio_element_id_to_demixing_metadata)) {}
+ DemixingMode demixing_mode_;
+
const absl::flat_hash_map<DecodedUleb128, DemixingMetadataForAudioElementId>
audio_element_id_to_demixing_metadata_;
};
diff --git a/iamf/cli/iamf_encoder.cc b/iamf/cli/iamf_encoder.cc
index 339c322..01b0752 100644
--- a/iamf/cli/iamf_encoder.cc
+++ b/iamf/cli/iamf_encoder.cc
@@ -306,18 +306,24 @@
decoded_audio_frames.emplace_back(*decoded_audio_frame);
}
- // Demix the audio frames.
- IdLabeledFrameMap id_to_labeled_frame;
- IdLabeledFrameMap id_to_labeled_decoded_frame;
- RETURN_IF_NOT_OK(demixing_module_.DemixAudioSamples(
- audio_frames, decoded_audio_frames, id_to_labeled_frame,
- id_to_labeled_decoded_frame));
+ // Demix the original and decoded audio frames, differences between them are
+ // useful to compute the recon gain parameters.
+ const auto id_to_labeled_frame =
+ demixing_module_.DemixOriginalAudioSamples(audio_frames);
+ if (!id_to_labeled_frame.ok()) {
+ return id_to_labeled_frame.status();
+ }
+ const auto id_to_labeled_decoded_frame =
+ demixing_module_.DemixDecodedAudioSamples(decoded_audio_frames);
+ if (!id_to_labeled_decoded_frame.ok()) {
+ return id_to_labeled_decoded_frame.status();
+ }
// Recon gain parameter blocks are generated based on the original and
// demixed audio frames.
RETURN_IF_NOT_OK(parameter_block_generator_.GenerateReconGain(
- id_to_labeled_frame, id_to_labeled_decoded_frame, *global_timing_module_,
- temp_recon_gain_parameter_blocks_));
+ *id_to_labeled_frame, *id_to_labeled_decoded_frame,
+ *global_timing_module_, temp_recon_gain_parameter_blocks_));
// Move all generated parameter blocks belonging to this temporal unit to
// the output.
@@ -335,7 +341,7 @@
}
return mix_presentation_finalizer_.PushTemporalUnit(
- id_to_labeled_frame, output_start_timestamp, output_end_timestamp,
+ *id_to_labeled_frame, output_start_timestamp, output_end_timestamp,
parameter_blocks);
}
diff --git a/iamf/cli/obu_processor.cc b/iamf/cli/obu_processor.cc
index d600402..b639300 100644
--- a/iamf/cli/obu_processor.cc
+++ b/iamf/cli/obu_processor.cc
@@ -45,6 +45,7 @@
#include "iamf/cli/sample_processor_base.h"
#include "iamf/common/read_bit_buffer.h"
#include "iamf/common/utils/macros.h"
+#include "iamf/common/utils/validation_utils.h"
#include "iamf/obu/audio_element.h"
#include "iamf/obu/audio_frame.h"
#include "iamf/obu/codec_config.h"
@@ -204,9 +205,9 @@
return absl::OkStatus();
}
-// Returns an iterator to the first supported mix presentation in the list of
-// mix presentation OBUs or nullptr if none are supported.
-std::list<MixPresentationObu>::iterator GetFirstSupportedMixPresentation(
+// Returns a list of pointers to the supported mix presentations. Empty if none
+// are supported.
+std::list<MixPresentationObu*> GetSupportedMixPresentations(
const absl::flat_hash_map<uint32_t, AudioElementWithData>& audio_elements,
std::list<MixPresentationObu>& mix_presentation_obus) {
// TODO(b/377554944): Support `ProfileVersion::kIamfBaseEnhancedProfile`.
@@ -214,6 +215,7 @@
const absl::flat_hash_set<ProfileVersion> kSupportedProfiles = {
ProfileVersion::kIamfSimpleProfile, ProfileVersion::kIamfBaseProfile};
+ std::list<MixPresentationObu*> supported_mix_presentations;
std::string cumulative_error_message;
for (auto iter = mix_presentation_obus.begin();
iter != mix_presentation_obus.end(); ++iter) {
@@ -221,18 +223,51 @@
const auto status = ProfileFilter::FilterProfilesForMixPresentation(
audio_elements, *iter, profiles);
if (status.ok()) {
- return iter;
+ supported_mix_presentations.push_back(&*iter);
}
absl::StrAppend(&cumulative_error_message, status.message(), "\n");
}
- LOG(ERROR) << absl::StrCat(
- "No supported mix presentation presentation found in the bitstream.",
- cumulative_error_message);
- return mix_presentation_obus.end();
+ LOG(INFO) << "Filtered mix presentations: " << cumulative_error_message;
+ return supported_mix_presentations;
}
-// Resets the buffer to `start_position` and sets the `insufficient_data` flag
-// to `true`. Clears the output maps.
+// Searches for the desired layout in the supported mix presentations. If found,
+// the output_playback_layout is the same as the desired_layout. Otherwise, we
+// default to the first layout in the first unsupported mix presentation.
+absl::StatusOr<MixPresentationObu*> GetPlaybackLayoutAndMixPresentation(
+ const std::list<MixPresentationObu*>& supported_mix_presentations,
+ const Layout& desired_layout, Layout& output_playback_layout) {
+ for (const auto& mix_presentation : supported_mix_presentations) {
+ for (const auto& sub_mix : mix_presentation->sub_mixes_) {
+ for (const auto& layout : sub_mix.layouts) {
+ if (layout.loudness_layout == desired_layout) {
+ output_playback_layout = layout.loudness_layout;
+ return mix_presentation;
+ }
+ }
+ }
+ }
+ // If we get here, we didn't find the desired layout in any of the supported
+ // mix presentations. We default to the first layout in the first mix
+ // presentation.
+ MixPresentationObu* output_mix_presentation =
+ supported_mix_presentations.front();
+ if (output_mix_presentation->sub_mixes_.empty()) {
+ return absl::InvalidArgumentError(
+ "No submixes found in the first mix presentation.");
+ }
+ if (output_mix_presentation->sub_mixes_.front().layouts.empty()) {
+ return absl::InvalidArgumentError(
+ "No layouts found in the first submix of the first mix presentation.");
+ }
+ output_playback_layout = output_mix_presentation->sub_mixes_.front()
+ .layouts.front()
+ .loudness_layout;
+ return output_mix_presentation;
+}
+
+// Resets the buffer to `start_position` and sets the `insufficient_data`
+// flag to `true`. Clears the output maps.
absl::Status InsufficientDataReset(
ReadBitBuffer& read_bit_buffer, const int64_t start_position,
bool& insufficient_data,
@@ -253,20 +288,36 @@
"more data and try again.");
}
+void GetSampleRateAndFrameSize(
+ const absl::flat_hash_map<DecodedUleb128, CodecConfigObu>&
+ output_codec_config_obus,
+ std::optional<uint32_t>& output_sample_rate,
+ std::optional<uint32_t>& output_frame_size) {
+ if (output_codec_config_obus.size() != 1) {
+ LOG(WARNING) << "Expected exactly one codec config OBUs, but found "
+ << output_codec_config_obus.size();
+ return;
+ }
+ const auto& first_codec_config_obu = output_codec_config_obus.begin()->second;
+ output_sample_rate = first_codec_config_obu.GetOutputSampleRate();
+ output_frame_size = first_codec_config_obu.GetNumSamplesPerFrame();
+}
+
} // namespace
absl::Status ObuProcessor::InitializeInternal(bool is_exhaustive_and_exact,
- bool& insufficient_data) {
+ bool& output_insufficient_data) {
// Process the descriptor OBUs.
LOG(INFO) << "Starting Descriptor OBU processing";
RETURN_IF_NOT_OK(ObuProcessor::ProcessDescriptorObus(
is_exhaustive_and_exact, *read_bit_buffer_, ia_sequence_header_,
codec_config_obus_, audio_elements_, mix_presentations_,
- insufficient_data));
+ output_insufficient_data));
LOG(INFO) << "Processed Descriptor OBUs";
RETURN_IF_NOT_OK(CollectAndValidateParamDefinitions(
audio_elements_, mix_presentations_, param_definition_variants_));
-
+ GetSampleRateAndFrameSize(codec_config_obus_, output_sample_rate_,
+ output_frame_size_);
// Mapping from substream IDs to pointers to audio element with data.
for (const auto& [audio_element_id, audio_element_with_data] :
audio_elements_) {
@@ -300,7 +351,10 @@
absl::flat_hash_map<DecodedUleb128, AudioElementWithData>&
output_audio_elements_with_data,
std::list<MixPresentationObu>& output_mix_presentation_obus,
- bool& insufficient_data) {
+ bool& output_insufficient_data) {
+ // `output_insufficient_data` indicates a specific error condition and so is
+ // true iff we've received valid data but need more of it.
+ output_insufficient_data = false;
auto audio_element_obu_map =
absl::flat_hash_map<DecodedUleb128, AudioElementObu>();
const int64_t global_position_before_all_obus = read_bit_buffer.Tell();
@@ -314,9 +368,9 @@
absl::StatusCode::kResourceExhausted) {
// Can't read header because there is not enough data.
return InsufficientDataReset(
- read_bit_buffer, global_position_before_all_obus, insufficient_data,
- output_codec_config_obus, output_audio_elements_with_data,
- output_mix_presentation_obus);
+ read_bit_buffer, global_position_before_all_obus,
+ output_insufficient_data, output_codec_config_obus,
+ output_audio_elements_with_data, output_mix_presentation_obus);
} else {
// Some other error occurred, propagate it.
return header_metadata.status();
@@ -342,8 +396,7 @@
if (!processed_ia_header) {
return absl::InvalidArgumentError(
"An IA Sequence and/or descriptor OBUs must always start with an "
- "IA "
- "Header.");
+ "IA Header.");
}
// Break out of the while loop since we've reached the end of the
// descriptor OBUs; should not seek back to the beginning of the buffer
@@ -355,9 +408,9 @@
if (!read_bit_buffer.CanReadBytes(header_metadata->total_obu_size)) {
// This is a descriptor OBU for which we don't have enough data.
return InsufficientDataReset(
- read_bit_buffer, global_position_before_all_obus, insufficient_data,
- output_codec_config_obus, output_audio_elements_with_data,
- output_mix_presentation_obus);
+ read_bit_buffer, global_position_before_all_obus,
+ output_insufficient_data, output_codec_config_obus,
+ output_audio_elements_with_data, output_mix_presentation_obus);
}
// Now we know we can read the entire obu.
const int64_t position_before_header = read_bit_buffer.Tell();
@@ -455,7 +508,7 @@
audio_elements_with_data,
const absl::flat_hash_map<DecodedUleb128, CodecConfigObu>&
codec_config_obus,
- const absl::flat_hash_map<DecodedUleb128, const AudioElementWithData*>
+ const absl::flat_hash_map<DecodedUleb128, const AudioElementWithData*>&
substream_id_to_audio_element,
const absl::flat_hash_map<DecodedUleb128, ParamDefinitionVariant>&
param_definition_variants,
@@ -465,16 +518,32 @@
std::optional<ParameterBlockWithData>& output_parameter_block_with_data,
std::optional<TemporalDelimiterObu>& output_temporal_delimiter,
bool& continue_processing) {
- if (!read_bit_buffer.IsDataAvailable()) {
- continue_processing = false;
- return absl::OkStatus();
- }
-
continue_processing = true;
output_audio_frame_with_data.reset();
output_parameter_block_with_data.reset();
output_temporal_delimiter.reset();
+ auto header_metadata = ObuHeader::PeekObuTypeAndTotalObuSize(read_bit_buffer);
+ if (!header_metadata.ok()) {
+ if (header_metadata.status().code() ==
+ absl::StatusCode::kResourceExhausted) {
+ // Can't read header because there is not enough data. This is not an
+ // error, but we're done processing for now.
+ continue_processing = false;
+ return absl::OkStatus();
+ } else {
+ // Some other error occurred, propagate it.
+ return header_metadata.status();
+ }
+ }
+
+ if (!read_bit_buffer.CanReadBytes(header_metadata->total_obu_size)) {
+ // This is a temporal unit OBU for which we don't have enough data. This is
+ // not an error, but we're done processing for now.
+ continue_processing = false;
+ return absl::OkStatus();
+ }
+
const int64_t position_before_header = read_bit_buffer.Tell();
// Read in the header and determines the size of the payload in bytes.
@@ -571,14 +640,17 @@
std::unique_ptr<ObuProcessor> ObuProcessor::Create(
bool is_exhaustive_and_exact, ReadBitBuffer* read_bit_buffer,
- bool& insufficient_data) {
+ bool& output_insufficient_data) {
+ // `output_insufficient_data` indicates a specific error condition and so is
+ // true iff we've received valid data but need more of it.
+ output_insufficient_data = false;
if (read_bit_buffer == nullptr) {
return nullptr;
}
std::unique_ptr<ObuProcessor> obu_processor =
absl::WrapUnique(new ObuProcessor(read_bit_buffer));
if (const auto status = obu_processor->InitializeInternal(
- is_exhaustive_and_exact, insufficient_data);
+ is_exhaustive_and_exact, output_insufficient_data);
!status.ok()) {
LOG(ERROR) << status;
return nullptr;
@@ -587,25 +659,28 @@
}
std::unique_ptr<ObuProcessor> ObuProcessor::CreateForRendering(
- const Layout& playback_layout,
+ const Layout& desired_layout,
const RenderingMixPresentationFinalizer::SampleProcessorFactory&
sample_processor_factory,
bool is_exhaustive_and_exact, ReadBitBuffer* read_bit_buffer,
- bool& insufficient_data) {
+ Layout& output_layout, bool& output_insufficient_data) {
+ // `output_insufficient_data` indicates a specific error condition and so is
+ // true iff we've received valid data but need more of it.
+ output_insufficient_data = false;
if (read_bit_buffer == nullptr) {
return nullptr;
}
std::unique_ptr<ObuProcessor> obu_processor =
absl::WrapUnique(new ObuProcessor(read_bit_buffer));
if (const auto status = obu_processor->InitializeInternal(
- is_exhaustive_and_exact, insufficient_data);
+ is_exhaustive_and_exact, output_insufficient_data);
!status.ok()) {
LOG(ERROR) << status;
return nullptr;
}
if (const auto status = obu_processor->InitializeForRendering(
- playback_layout, sample_processor_factory);
+ desired_layout, sample_processor_factory, output_layout);
!status.ok()) {
LOG(ERROR) << status;
return nullptr;
@@ -613,19 +688,25 @@
return obu_processor;
}
+absl::StatusOr<uint32_t> ObuProcessor::GetOutputSampleRate() const {
+ RETURN_IF_NOT_OK(
+ ValidateHasValue(output_sample_rate_,
+ "Output sample rate, was this a trivial IA Sequence?"));
+ return *output_sample_rate_;
+}
+
+absl::StatusOr<uint32_t> ObuProcessor::GetOutputFrameSize() const {
+ RETURN_IF_NOT_OK(
+ ValidateHasValue(output_frame_size_,
+ "Output frame size, was this a trivial IA Sequence?"));
+ return *output_frame_size_;
+}
+
absl::Status ObuProcessor::InitializeForRendering(
- const Layout& playback_layout,
+ const Layout& desired_layout,
const RenderingMixPresentationFinalizer::SampleProcessorFactory&
- sample_processor_factory) {
- // TODO(b/339500539): Add support for other layouts. Downstream code is simple
- // and assumes there will be a matching layout in the first
- // Mix Presentation OBU. The IAMF spec REQUIRES this for
- // stereo. In general, layouts may require more careful
- // selection according to 7.3.1.
- // TODO(b/395625514): Add test coverage for this.
- if (!IsStereoLayout(playback_layout)) {
- return absl::InvalidArgumentError("Layout type is not supported.");
- }
+ sample_processor_factory,
+ Layout& output_layout) {
if (mix_presentations_.empty()) {
return absl::InvalidArgumentError("No mix presentation OBUs found.");
}
@@ -652,32 +733,39 @@
// TODO(b/340289717): Add a way to select the mix presentation if multiple
// are supported.
- const auto mix_presentation_to_render =
- GetFirstSupportedMixPresentation(audio_elements_, mix_presentations_);
- if (mix_presentation_to_render == mix_presentations_.end()) {
- return absl::NotFoundError("No supportedmix presentation OBUs found.");
+ const std::list<MixPresentationObu*> supported_mix_presentations =
+ GetSupportedMixPresentations(audio_elements_, mix_presentations_);
+ if (supported_mix_presentations.empty()) {
+ return absl::NotFoundError("No supported mix presentation OBUs found.");
}
- int desired_sub_mix_index;
- int desired_layout_index;
- RETURN_IF_NOT_OK(GetIndicesForLayout(mix_presentation_to_render->sub_mixes_,
- playback_layout, desired_sub_mix_index,
- desired_layout_index));
+ Layout playback_layout;
+ auto mix_presentation_to_render = GetPlaybackLayoutAndMixPresentation(
+ supported_mix_presentations, desired_layout, output_layout);
+ if (!mix_presentation_to_render.ok()) {
+ return mix_presentation_to_render.status();
+ }
+ int playback_sub_mix_index;
+ int playback_layout_index;
+ RETURN_IF_NOT_OK(GetIndicesForLayout(
+ (*mix_presentation_to_render)->sub_mixes_, output_layout,
+ playback_sub_mix_index, playback_layout_index));
decoding_layout_info_ = {
- .mix_presentation_id = mix_presentation_to_render->GetMixPresentationId(),
- .sub_mix_index = desired_sub_mix_index,
- .layout_index = desired_layout_index,
+ .mix_presentation_id =
+ (*mix_presentation_to_render)->GetMixPresentationId(),
+ .sub_mix_index = playback_sub_mix_index,
+ .layout_index = playback_layout_index,
};
auto forward_on_desired_layout =
[&sample_processor_factory, mix_presentation_to_render,
- desired_sub_mix_index, desired_layout_index](
+ playback_sub_mix_index, playback_layout_index](
DecodedUleb128 mix_presentation_id, int sub_mix_index,
int layout_index, const Layout& layout, int num_channels,
int sample_rate, int bit_depth, size_t max_input_samples_per_frame)
-> std::unique_ptr<SampleProcessorBase> {
if (mix_presentation_id ==
- mix_presentation_to_render->GetMixPresentationId() &&
- desired_sub_mix_index == sub_mix_index &&
- desired_layout_index == layout_index) {
+ (*mix_presentation_to_render)->GetMixPresentationId() &&
+ playback_sub_mix_index == sub_mix_index &&
+ playback_layout_index == layout_index) {
return sample_processor_factory(
mix_presentation_id, sub_mix_index, layout_index, layout,
num_channels, sample_rate, bit_depth, max_input_samples_per_frame);
@@ -732,11 +820,11 @@
}
absl::Status ObuProcessor::ProcessTemporalUnit(
- std::list<AudioFrameWithData>& output_audio_frames,
- std::list<ParameterBlockWithData>& output_parameter_blocks,
- std::optional<InternalTimestamp>& output_timestamp,
+ bool eos_is_end_of_sequence,
+ std::optional<OutputTemporalUnit>& output_temporal_unit,
bool& continue_processing) {
- while (true) {
+ continue_processing = true;
+ while (continue_processing) {
std::optional<AudioFrameWithData> audio_frame_with_data;
std::optional<ParameterBlockWithData> parameter_block_with_data;
std::optional<TemporalDelimiterObu> temporal_delimiter;
@@ -759,17 +847,22 @@
// The current temporal unit is considered finished if any of the
// following conditions is met:
- // - No more data to consume (i.e. `continue_processing == true`).
+ // - The end of sequence is reached.
// - The timestamp has advanced (i.e. when the next temporal unit gets its
// timestamp).
// - A temporal delimiter is encountered.
- if (!continue_processing || next_temporal_unit_.timestamp.has_value() ||
+ if ((!continue_processing && eos_is_end_of_sequence) ||
+ next_temporal_unit_.timestamp.has_value() ||
current_temporal_unit_.temporal_delimiter.has_value()) {
- output_audio_frames.splice(output_audio_frames.end(),
- current_temporal_unit_.audio_frames);
- output_parameter_blocks.splice(output_parameter_blocks.end(),
- current_temporal_unit_.parameter_blocks);
- output_timestamp = current_temporal_unit_.timestamp;
+ output_temporal_unit = OutputTemporalUnit();
+ output_temporal_unit->output_audio_frames =
+ std::move(current_temporal_unit_.audio_frames);
+ output_temporal_unit->output_parameter_blocks =
+ std::move(current_temporal_unit_.parameter_blocks);
+ if (current_temporal_unit_.timestamp.has_value()) {
+ output_temporal_unit->output_timestamp =
+ current_temporal_unit_.timestamp.value();
+ }
current_temporal_unit_ = std::move(next_temporal_unit_);
next_temporal_unit_ = TemporalUnitData();
break;
@@ -806,8 +899,6 @@
}
// Decode the temporal unit.
- const std::list<AudioFrameWithData> kIgnoreOriginalAudioFrames;
- IdLabeledFrameMap unused_original_labeled_frames;
std::optional<InternalTimestamp> end_timestamp;
// This resizing should happen only once per IA sequence, since all the
@@ -835,18 +926,16 @@
}
// Reconstruct the temporal unit and store the result in the output map.
- IdLabeledFrameMap decoded_labeled_frames_for_temporal_unit;
- RETURN_IF_NOT_OK(demixing_module_->DemixAudioSamples(
- kIgnoreOriginalAudioFrames, decoded_frames_for_temporal_unit_,
- unused_original_labeled_frames,
- decoded_labeled_frames_for_temporal_unit));
-
- // To be safe clear the unused map. But we expect it to be empty.
- unused_original_labeled_frames.clear();
+ const auto decoded_labeled_frames_for_temporal_unit =
+ demixing_module_->DemixDecodedAudioSamples(
+ decoded_frames_for_temporal_unit_);
+ if (!decoded_labeled_frames_for_temporal_unit.ok()) {
+ return decoded_labeled_frames_for_temporal_unit.status();
+ }
RETURN_IF_NOT_OK(mix_presentation_finalizer_->PushTemporalUnit(
- decoded_labeled_frames_for_temporal_unit, start_timestamp, *end_timestamp,
- parameter_blocks));
+ *decoded_labeled_frames_for_temporal_unit, start_timestamp,
+ *end_timestamp, parameter_blocks));
auto rendered_samples =
mix_presentation_finalizer_->GetPostProcessedSamplesAsSpan(
diff --git a/iamf/cli/obu_processor.h b/iamf/cli/obu_processor.h
index 8c9e7d1..cc607e3 100644
--- a/iamf/cli/obu_processor.h
+++ b/iamf/cli/obu_processor.h
@@ -21,6 +21,7 @@
#include "absl/container/flat_hash_map.h"
#include "absl/status/status.h"
+#include "absl/status/statusor.h"
#include "absl/types/span.h"
#include "iamf/cli/audio_element_with_data.h"
#include "iamf/cli/audio_frame_decoder.h"
@@ -70,6 +71,8 @@
* \return `absl::OkStatus()` if the process is successful. A specific status
* on failure.
*/
+ [[deprecated(
+ "Remove when all tests are ported. Use the non-static version instead.")]]
static absl::Status ProcessDescriptorObus(
bool is_exhaustive_and_exact, ReadBitBuffer& read_bit_buffer,
IASequenceHeaderObu& output_sequence_header,
@@ -113,7 +116,7 @@
audio_elements_with_data,
const absl::flat_hash_map<DecodedUleb128, CodecConfigObu>&
codec_config_obus,
- const absl::flat_hash_map<DecodedUleb128, const AudioElementWithData*>
+ const absl::flat_hash_map<DecodedUleb128, const AudioElementWithData*>&
substream_id_to_audio_element,
const absl::flat_hash_map<DecodedUleb128, ParamDefinitionVariant>&
param_definition_variants,
@@ -134,13 +137,14 @@
* descriptor OBUs.
* \param read_bit_buffer Pointer to the read bit buffer that reads the IAMF
* bitstream.
- * \param insufficient_data Whether the bitstream provided is insufficient to
- * process all descriptor OBUs.
+ * \param output_insufficient_data True iff the bitstream provided is
+ * insufficient to process all descriptor OBUs and there is no other
+ * error.
* \return std::unique_ptr<ObuProcessor> on success. `nullptr` on failure.
*/
static std::unique_ptr<ObuProcessor> Create(bool is_exhaustive_and_exact,
ReadBitBuffer* read_bit_buffer,
- bool& insufficient_data);
+ bool& output_insufficient_data);
/*!\brief Move constructor. */
ObuProcessor(ObuProcessor&& obu_processor) = delete;
@@ -150,8 +154,10 @@
* Creation succeeds only if the descriptor OBUs are successfully processed
* and all rendering modules are successfully initialized.
*
- * \param playback_layout Specifies the layout that will be used to render the
- * audio.
+ * \param desired_layout Specifies the desired layout that will be used to
+ * render the audio, if available in the mix presentations. If not
+ * available, the first layout in the first mix presentation will be
+ * used.
* \param sample_processor_factory Factory to create post processors.
* \param is_exhaustive_and_exact Whether the bitstream provided is meant to
* include all descriptor OBUs and no other data. This should only be
@@ -159,16 +165,37 @@
* descriptor OBUs.
* \param read_bit_buffer Pointer to the read bit buffer that reads the IAMF
* bitstream.
- * \param insufficient_data Whether the bitstream provided is insufficient to
- * process all descriptor OBUs.
+ * \param output_layout The layout that will be used to render the audio. This
+ * is the same as `desired_layout` if it is available in the mix
+ * presentations, otherwise a default layout is used.
+ * \param output_insufficient_data True iff the bitstream provided is
+ * insufficient to process all descriptor OBUs and there is no other
+ * error.
* \return Pointer to an ObuProcessor on success. `nullptr` on failure.
*/
static std::unique_ptr<ObuProcessor> CreateForRendering(
- const Layout& playback_layout,
+ const Layout& desired_layout,
const RenderingMixPresentationFinalizer::SampleProcessorFactory&
sample_processor_factory,
bool is_exhaustive_and_exact, ReadBitBuffer* read_bit_buffer,
- bool& insufficient_data);
+ Layout& output_layout, bool& output_insufficient_data);
+
+ /*!\brief Gets the sample rate of the output audio.
+ *
+ * \return Sample rate of the output audio, or a specific error code on
+ * failure.
+ */
+ absl::StatusOr<uint32_t> GetOutputSampleRate() const;
+
+ /*!\brief Gets the frame size of the output audio.
+ *
+ * Useful to determine the maximum number of samples per
+ * `RenderTemporalUnitAndMeasureLoudness` call.
+ *
+ * \return Number of samples in per frame of the output audio, or a specific
+ * specific error code on failure.
+ */
+ absl::StatusOr<uint32_t> GetOutputFrameSize() const;
// TODO(b/381072155): Consider removing this one in favor of
// `ProcessTemporalUnit()`, which outputs all OBUs
@@ -192,26 +219,28 @@
std::optional<TemporalDelimiterObu>& output_temporal_delimiter,
bool& continue_processing);
+ struct OutputTemporalUnit {
+ std::list<AudioFrameWithData> output_audio_frames;
+ std::list<ParameterBlockWithData> output_parameter_blocks;
+ InternalTimestamp output_timestamp;
+ };
+
// TODO(b/379819959): Also handle Temporal Delimiter OBUs.
/*!\brief Processes all OBUs from a Temporal Unit from the stored IA Sequence.
*
- * `Initialize()` must be called first to ready the input bitstream.
- *
- * \param output_audio_frames Output Audio Frames with the requisite
- * data.
- * \param output_parameter_blocks Output Parameter Blocks with the
- * requisite data.
- * \param output_timestamp Timestamp for the output temporal unit.
- * \param insufficient_data Whether the bitstream provided is insufficient to
- * process all descriptor OBUs.
+ * \param eos_is_end_of_sequence Whether reaching the end of the stream
+ * should be considered as the end of the sequence, and therefore the
+ * end of the temporal unit.
+ * \param output_temporal_unit Contains the data from the temporal unit that
+ * is processed.
* \param continue_processing Whether the processing should be continued.
* \return `absl::OkStatus()` if the process is successful. A specific status
* on failure.
*/
absl::Status ProcessTemporalUnit(
- std::list<AudioFrameWithData>& output_audio_frames,
- std::list<ParameterBlockWithData>& output_parameter_blocks,
- std::optional<int32_t>& output_timestamp, bool& continue_processing);
+ bool eos_is_end_of_sequence,
+ std::optional<OutputTemporalUnit>& output_temporal_unit,
+ bool& continue_processing);
/*!\brief Renders a temporal unit and measures loudness.
*
@@ -258,26 +287,33 @@
* include all descriptor OBUs and no other data. This should only be
* set to true if the user knows the exact boundaries of their set of
* descriptor OBUs.
+ \param output_insufficient_data True iff the bitstream provided is
+ * insufficient to process all descriptor OBUs and there is no other
+ * error.
* \return `absl::OkStatus()` if initialization is successful. A specific
* status on failure.
*/
absl::Status InitializeInternal(bool is_exhaustive_and_exact,
- bool& insufficient_data);
+ bool& output_insufficient_data);
/*!\brief Initializes the OBU processor for rendering.
*
* Must be called after `Initialize()` is called.
*
- * \param playback_layout Specifies the layout that will be used to render the
- * audio.
+ * \param desired_layout Specifies the layout that will be used to render the
+ * audio, if available.
* \param sample_processor_factory Factory to create post processors.
+ * \param output_layout The layout that will be used to render the audio. This
+ * is the same as `desired_layout` if it is available, otherwise a
+ * default layout is used.
* \return `absl::OkStatus()` if the process is successful. A specific status
* on failure.
*/
absl::Status InitializeForRendering(
- const Layout& playback_layout,
+ const Layout& desired_layout,
const RenderingMixPresentationFinalizer::SampleProcessorFactory&
- sample_processor_factory);
+ sample_processor_factory,
+ Layout& output_layout);
struct DecodingLayoutInfo {
DecodedUleb128 mix_presentation_id;
@@ -312,9 +348,11 @@
current_temporal_unit.timestamp = new_timestamp;
}
if (*current_temporal_unit.timestamp == new_timestamp) {
- current_temporal_unit.GetList<T>().push_back(std::move(obu_with_data));
+ current_temporal_unit.GetList<T>().push_back(
+ std::forward<T>(obu_with_data));
} else {
- next_temporal_unit.GetList<T>().push_back(std::move(obu_with_data));
+ next_temporal_unit.GetList<T>().push_back(
+ std::forward<T>(obu_with_data));
next_temporal_unit.timestamp = new_timestamp;
}
}
@@ -330,6 +368,9 @@
};
};
+ std::optional<uint32_t> output_sample_rate_;
+ std::optional<uint32_t> output_frame_size_;
+
absl::flat_hash_map<DecodedUleb128, ParamDefinitionVariant>
param_definition_variants_;
absl::flat_hash_map<DecodedUleb128, const AudioElementWithData*>
diff --git a/iamf/cli/obu_sequencer_base.cc b/iamf/cli/obu_sequencer_base.cc
index f8ec893..038019c 100644
--- a/iamf/cli/obu_sequencer_base.cc
+++ b/iamf/cli/obu_sequencer_base.cc
@@ -62,6 +62,42 @@
*/
typedef absl::btree_map<int32_t, TemporalUnitView> TemporalUnitMap;
+/*!\brief Helper class to abort an `ObuSequencerBase` on destruction.
+ *
+ * This class calls on an `ObuSequencerBase::Abort` on destruction. Or does
+ * nothing if `CancelAbort` is called.
+ *
+ * Typically, this is useful to create an instance of this class on the stack,
+ * in the scope of a function which has many locations where it may return an
+ * un-recoverable error. When those exit points are reached, the sequencer will
+ * automatically be aborted.
+ *
+ * Before any successful exit point, `CancelAbort` should be called, which will
+ * prevent the sequencer from being aborting.
+ */
+class AbortOnDestruct {
+ public:
+ /*!\brief Constructor
+ *
+ * \param obu_sequencer The `ObuSequencerBase` to abort on destruction.
+ */
+ explicit AbortOnDestruct(ObuSequencerBase* obu_sequencer)
+ : obu_sequencer(obu_sequencer) {}
+
+ /*!\brief Destructor */
+ ~AbortOnDestruct() {
+ if (obu_sequencer != nullptr) {
+ obu_sequencer->Abort();
+ }
+ }
+
+ /*!\brief Cancels the abort on destruction. */
+ void CancelAbort() { obu_sequencer = nullptr; }
+
+ private:
+ ObuSequencerBase* obu_sequencer;
+};
+
template <typename KeyValueMap, typename KeyComparator>
std::vector<uint32_t> SortedKeys(const KeyValueMap& map,
const KeyComparator& comparator) {
@@ -77,6 +113,8 @@
// frames. These would decode to an empty stream. Fallback to some reasonable,
// but arbitrary default values, when the true value is undefined.
+// Fallback number of samples per frame when there are no audio frames.
+constexpr uint32_t kFallbackSamplesPerFrame = 1024;
// Fallback sample rate when there are no Codec Config OBUs.
constexpr uint32_t kFallbackSampleRate = 48000;
// Fallback bit-depth when there are no Codec Config OBUs.
@@ -106,38 +144,6 @@
return num_channels;
}
-// Gets the first Presentation Timestamp (PTS); the timestamp of the first
-// sample that is not trimmed. Or zero of there are no untrimmed samples.
-absl::StatusOr<int64_t> GetFirstUntrimmedTimestamp(
- const TemporalUnitMap& temporal_unit_map) {
- if (temporal_unit_map.empty()) {
- return kFallbackFirstPts;
- }
-
- std::optional<int64_t> first_untrimmed_timestamp;
- for (const auto& [start_timestamp, temporal_unit] : temporal_unit_map) {
- if (temporal_unit.num_untrimmed_samples_ == 0) {
- // Fully trimmed frame. Wait for more.
- continue;
- }
- if (temporal_unit.num_samples_to_trim_at_start_ > 0 &&
- first_untrimmed_timestamp.has_value()) {
- return absl::InvalidArgumentError(
- "Temporal units must not have samples trimmed from the start, after "
- "the first untrimmed sample.");
- }
-
- // Found the first untrimmed sample. Get the timestamp. We only continue
- // looping to check that no more temporal units have samples trimmed from
- // the start, after the first untrimmed sample.
- first_untrimmed_timestamp =
- start_timestamp + temporal_unit.num_samples_to_trim_at_start_;
- }
-
- return first_untrimmed_timestamp.has_value() ? *first_untrimmed_timestamp
- : kFallbackFirstPts;
-}
-
// Gets the common sample rate and bit depth for the given codec config OBUs. Or
// falls back to default values if there are no codec configs.
absl::Status GetCommonSampleRateAndBitDepth(
@@ -177,6 +183,33 @@
return absl::OkStatus();
}
+absl::Status FillDescriptorStatistics(
+ const absl::flat_hash_map<uint32_t, CodecConfigObu>& codec_config_obus,
+ auto& descriptor_statistics) {
+ descriptor_statistics.common_samples_per_frame = kFallbackSamplesPerFrame;
+ descriptor_statistics.common_sample_rate = kFallbackSampleRate;
+ descriptor_statistics.common_bit_depth = kFallbackBitDepth;
+ descriptor_statistics.num_channels = kFallbackNumChannels;
+
+ bool requires_resampling = false;
+ RETURN_IF_NOT_OK(GetCommonSampleRateAndBitDepth(
+ codec_config_obus, descriptor_statistics.common_sample_rate,
+ descriptor_statistics.common_bit_depth, requires_resampling));
+ if (requires_resampling) {
+ return absl::UnimplementedError(
+ "Codec Config OBUs with different bit-depths and/or sample "
+ "rates are not in base-enhanced/base/simple profile; they are not "
+ "allowed in ISOBMFF.");
+ }
+
+ // This assumes all Codec Configs have the same sample rate and frame size.
+ // We may need to be more careful if IA Samples do not all (except the
+ // final) have the same duration in the future.
+ return GetCommonSamplesPerFrame(
+ codec_config_obus, descriptor_statistics.common_samples_per_frame);
+}
+
+[[deprecated("Remove this, and related types when `PickAndPlace` is removed.")]]
absl::Status GenerateTemporalUnitMap(
const std::list<AudioFrameWithData>& audio_frames,
const std::list<ParameterBlockWithData>& parameter_blocks,
@@ -223,6 +256,7 @@
return absl::OkStatus();
}
+
} // namespace
absl::Status ObuSequencerBase::WriteTemporalUnit(
@@ -350,9 +384,131 @@
: leb_generator_(leb_generator),
delay_descriptors_until_first_untrimmed_sample_(
delay_descriptors_until_first_untrimmed_sample),
- include_temporal_delimiters_(include_temporal_delimiters) {}
+ include_temporal_delimiters_(include_temporal_delimiters),
+ wb_(kBufferStartSize, leb_generator) {}
-ObuSequencerBase::~ObuSequencerBase() {};
+ObuSequencerBase::~ObuSequencerBase() {
+ switch (state_) {
+ case kInitialized:
+ return;
+ case kPushDescriptorObusCalled:
+ case kPushSerializedDescriptorsCalled:
+ LOG(ERROR) << "OBUs have been pushed, but `ObuSequencerBase` is being "
+ "destroyed without calling `Close` or `Abort`.";
+ return;
+ case kClosed:
+ return;
+ }
+ // The above switch is exhaustive.
+ LOG(FATAL) << "Unexpected state: " << static_cast<int>(state_);
+};
+
+absl::Status ObuSequencerBase::PushDescriptorObus(
+ const IASequenceHeaderObu& ia_sequence_header_obu,
+ const absl::flat_hash_map<uint32_t, CodecConfigObu>& codec_config_obus,
+ const absl::flat_hash_map<uint32_t, AudioElementWithData>& audio_elements,
+ const std::list<MixPresentationObu>& mix_presentation_obus,
+ const std::list<ArbitraryObu>& arbitrary_obus) {
+ // Many failure points should call `Abort`. We want to avoid leaving
+ // sequencers open if they may have invalid or corrupted IAMF data.
+ AbortOnDestruct abort_on_destruct(this);
+ switch (state_) {
+ case kInitialized:
+ break;
+ case kPushDescriptorObusCalled:
+ case kPushSerializedDescriptorsCalled:
+ return absl::FailedPreconditionError(
+ "`PushDescriptorObus` can only be called once.");
+ case kClosed:
+ return absl::FailedPreconditionError(
+ "`PushDescriptorObus` cannot be called after `Close` or `Abort`.");
+ }
+ state_ = kPushDescriptorObusCalled;
+ wb_.Reset();
+
+ // Serialize descriptor OBUS and adjacent arbitrary OBUs.
+ RETURN_IF_NOT_OK(ArbitraryObu::WriteObusWithHook(
+ ArbitraryObu::kInsertionHookBeforeDescriptors, arbitrary_obus, wb_));
+ // Write out the descriptor OBUs.
+ RETURN_IF_NOT_OK(ObuSequencerBase::WriteDescriptorObus(
+ ia_sequence_header_obu, codec_config_obus, audio_elements,
+ mix_presentation_obus, arbitrary_obus, wb_));
+ RETURN_IF_NOT_OK(ArbitraryObu::WriteObusWithHook(
+ ArbitraryObu::kInsertionHookAfterDescriptors, arbitrary_obus, wb_));
+ // Cache the descriptor OBUs, so we can validate "functional" equivalence if
+ // the user calls `UpdateDescriptorObusAndClose`.
+ DescriptorStatistics descriptor_statistics{.descriptor_obus =
+ wb_.bit_buffer()};
+ RETURN_IF_NOT_OK(
+ FillDescriptorStatistics(codec_config_obus, descriptor_statistics));
+ descriptor_statistics_.emplace(std::move(descriptor_statistics));
+
+ if (!delay_descriptors_until_first_untrimmed_sample_) {
+ // Avoid unnecessary delay, for concrete classes that don't need
+ // `first_pts`.
+ RETURN_IF_NOT_OK(PushSerializedDescriptorObus(
+ descriptor_statistics_->common_samples_per_frame,
+ descriptor_statistics_->common_sample_rate,
+ descriptor_statistics_->common_bit_depth,
+ descriptor_statistics_->first_untrimmed_timestamp,
+ descriptor_statistics_->num_channels,
+ descriptor_statistics_->descriptor_obus));
+
+ state_ = kPushSerializedDescriptorsCalled;
+ }
+
+ abort_on_destruct.CancelAbort();
+ return absl::OkStatus();
+}
+
+absl::Status ObuSequencerBase::PushTemporalUnit(
+ const TemporalUnitView& temporal_unit) {
+ // Many failure points should call `Abort`. We want to avoid leaving
+ // sequencers open if they may have invalid or corrupted IAMF data.
+ AbortOnDestruct abort_on_destruct(this);
+ switch (state_) {
+ case kInitialized:
+ return absl::FailedPreconditionError(
+ "PushDescriptorObus must be called before PushTemporalUnit.");
+ break;
+ case kPushDescriptorObusCalled:
+ case kPushSerializedDescriptorsCalled:
+ break;
+ case kClosed:
+ return absl::FailedPreconditionError(
+ "PushTemporalUnit can only be called before `Close` or `Abort`.");
+ }
+ wb_.Reset();
+
+ // Cache the frame for later
+ const int64_t start_timestamp =
+ static_cast<int64_t>(temporal_unit.start_timestamp_);
+ int num_samples = 0;
+ RETURN_IF_NOT_OK(WriteTemporalUnit(include_temporal_delimiters_,
+ temporal_unit, wb_, num_samples));
+ cumulative_num_samples_for_logging_ += num_samples;
+ num_temporal_units_for_logging_++;
+
+ if (!descriptor_statistics_->first_untrimmed_timestamp.has_value()) {
+ // Treat the initial temporal units as a special case, this helps gather
+ // statistics about the first untrimmed sample.
+ RETURN_IF_NOT_OK(HandleInitialTemporalUnits(
+ temporal_unit, absl::MakeConstSpan(wb_.bit_buffer())));
+
+ } else if (temporal_unit.num_samples_to_trim_at_start_ > 0) {
+ return absl::InvalidArgumentError(
+ "A unit has samples to trim at start, but the first untrimmed sample "
+ "was already found.");
+ } else [[likely]] {
+ // This is by far the most common case, after we have seen the first real
+ // frame of audio, we can handle this simply.
+ RETURN_IF_NOT_OK(PushSerializedTemporalUnit(
+ start_timestamp, num_samples, absl::MakeConstSpan(wb_.bit_buffer())));
+ }
+
+ abort_on_destruct.CancelAbort();
+ return absl::OkStatus();
+}
absl::Status ObuSequencerBase::PickAndPlace(
const IASequenceHeaderObu& ia_sequence_header_obu,
@@ -362,106 +518,172 @@
const std::list<AudioFrameWithData>& audio_frames,
const std::list<ParameterBlockWithData>& parameter_blocks,
const std::list<ArbitraryObu>& arbitrary_obus) {
- switch (state_) {
- case kInitialized:
- break;
- case kFlushed:
- return absl::FailedPreconditionError(
- "`PickAndPlace` should only be called once per instance.");
- }
-
- uint32_t common_sample_rate;
- uint8_t common_bit_depth;
- bool requires_resampling;
- RETURN_IF_NOT_OK(
- GetCommonSampleRateAndBitDepth(codec_config_obus, common_sample_rate,
- common_bit_depth, requires_resampling));
- if (requires_resampling) {
- return absl::UnimplementedError(
- "Codec Config OBUs with different bit-depths and/or sample "
- "rates are not in base-enhanced/base/simple profile; they are not "
- "allowed in ISOBMFF.");
- }
-
- // This assumes all Codec Configs have the same sample rate and frame size.
- // We may need to be more careful if IA Samples do not all (except the
- // final) have the same duration in the future.
- uint32_t common_samples_per_frame = 0;
- RETURN_IF_NOT_OK(
- GetCommonSamplesPerFrame(codec_config_obus, common_samples_per_frame));
-
- // Write the descriptor OBUs.
- WriteBitBuffer wb(kBufferStartSize, leb_generator_);
-
- RETURN_IF_NOT_OK(ArbitraryObu::WriteObusWithHook(
- ArbitraryObu::kInsertionHookBeforeDescriptors, arbitrary_obus, wb));
- // Write out the descriptor OBUs.
- RETURN_IF_NOT_OK(ObuSequencerBase::WriteDescriptorObus(
- ia_sequence_header_obu, codec_config_obus, audio_elements,
- mix_presentation_obus, arbitrary_obus, wb));
- RETURN_IF_NOT_OK(ArbitraryObu::WriteObusWithHook(
- ArbitraryObu::kInsertionHookAfterDescriptors, arbitrary_obus, wb));
+ RETURN_IF_NOT_OK(PushDescriptorObus(ia_sequence_header_obu, codec_config_obus,
+ audio_elements, mix_presentation_obus,
+ arbitrary_obus));
TemporalUnitMap temporal_unit_map;
RETURN_IF_NOT_OK(GenerateTemporalUnitMap(audio_frames, parameter_blocks,
arbitrary_obus, temporal_unit_map));
+ for (const auto& [timestamp, temporal_unit] : temporal_unit_map) {
+ RETURN_IF_NOT_OK(PushTemporalUnit(temporal_unit));
+ }
+ return Close();
+}
- // If `delay_descriptors_until_first_untrimmed_sample` is true, then concrete
- // class needs `first_untrimmed_timestamp`. Otherwise, it would cause an
- // unnecessary delay, because the PTS cannot be determined until the first
- // untrimmed sample is received.
- std::optional<int64_t> first_untrimmed_timestamp;
- if (delay_descriptors_until_first_untrimmed_sample_) {
- // TODO(b/397637224): When this class can be used iteratively, we need to
- // determine the first PTS from the initial audio frames
- // only.
- const auto temp_first_untrimmed_timestamp =
- GetFirstUntrimmedTimestamp(temporal_unit_map);
- if (!temp_first_untrimmed_timestamp.ok()) {
- return temp_first_untrimmed_timestamp.status();
- }
- first_untrimmed_timestamp = *temp_first_untrimmed_timestamp;
+absl::Status ObuSequencerBase::UpdateDescriptorObusAndClose(
+ const IASequenceHeaderObu& ia_sequence_header_obu,
+ const absl::flat_hash_map<uint32_t, CodecConfigObu>& codec_config_obus,
+ const absl::flat_hash_map<uint32_t, AudioElementWithData>& audio_elements,
+ const std::list<MixPresentationObu>& mix_presentation_obus,
+ const std::list<ArbitraryObu>& arbitrary_obus) {
+ // Many failure points should call `Abort`. We want to avoid leaving
+ // sequencers open if they may have invalid or corrupted IAMF data.
+ AbortOnDestruct abort_on_destruct(this);
+ switch (state_) {
+ case kInitialized:
+ return absl::FailedPreconditionError(
+ "`UpdateDescriptorObusAndClose` must be called after "
+ "`PushDescriptorObus`.");
+ case kPushDescriptorObusCalled:
+ case kPushSerializedDescriptorsCalled:
+ break;
+ case kClosed:
+ return absl::FailedPreconditionError(
+ "`Abort` or `Close` previously called.");
+ }
+ wb_.Reset();
+
+ // Serialize descriptor OBUS and adjacent arbitrary OBUs.
+ RETURN_IF_NOT_OK(ArbitraryObu::WriteObusWithHook(
+ ArbitraryObu::kInsertionHookBeforeDescriptors, arbitrary_obus, wb_));
+ // Write out the descriptor OBUs.
+ RETURN_IF_NOT_OK(ObuSequencerBase::WriteDescriptorObus(
+ ia_sequence_header_obu, codec_config_obus, audio_elements,
+ mix_presentation_obus, arbitrary_obus, wb_));
+ RETURN_IF_NOT_OK(ArbitraryObu::WriteObusWithHook(
+ ArbitraryObu::kInsertionHookAfterDescriptors, arbitrary_obus, wb_));
+
+ // We're a bit loose with what types of metadata we allow to change. Check
+ // at least the "functional" statistics are equivalent.
+ DescriptorStatistics descriptor_statistics{.descriptor_obus =
+ wb_.bit_buffer()};
+ RETURN_IF_NOT_OK(
+ FillDescriptorStatistics(codec_config_obus, descriptor_statistics));
+ if (descriptor_statistics_->common_samples_per_frame !=
+ descriptor_statistics.common_samples_per_frame ||
+ descriptor_statistics_->common_sample_rate !=
+ descriptor_statistics.common_sample_rate ||
+ descriptor_statistics_->common_bit_depth !=
+ descriptor_statistics.common_bit_depth ||
+ descriptor_statistics_->num_channels !=
+ descriptor_statistics.num_channels) {
+ return absl::FailedPreconditionError(
+ "Descriptor OBUs have changed size between finalizing and "
+ "closing.");
+ }
+ if (descriptor_statistics_->descriptor_obus.size() !=
+ descriptor_statistics.descriptor_obus.size()) {
+ return absl::UnimplementedError(
+ "Descriptor OBUs have changed size between finalizing and closing.");
}
- int64_t cumulative_num_samples_for_logging = 0;
- int64_t num_temporal_units_for_logging = 0;
- const auto wrote_ia_sequence = [&]() -> absl::Status {
- RETURN_IF_NOT_OK(PushSerializedDescriptorObus(
- common_samples_per_frame, common_sample_rate, common_bit_depth,
- first_untrimmed_timestamp, GetNumberOfChannels(audio_elements),
- absl::MakeConstSpan(wb.bit_buffer())));
- wb.Reset();
+ RETURN_IF_NOT_OK(
+ PushFinalizedDescriptorObus(absl::MakeConstSpan(wb_.bit_buffer())));
+ state_ = kPushSerializedDescriptorsCalled;
+ RETURN_IF_NOT_OK(Close());
- for (const auto& [timestamp, temporal_unit] : temporal_unit_map) {
- // Write the IA Sample to a `MediaSample`.
- int num_samples = 0;
-
- RETURN_IF_NOT_OK(WriteTemporalUnit(include_temporal_delimiters_,
- temporal_unit, wb, num_samples));
- RETURN_IF_NOT_OK(PushSerializedTemporalUnit(
- static_cast<int64_t>(timestamp), num_samples, wb.bit_buffer()));
-
- cumulative_num_samples_for_logging += num_samples;
- num_temporal_units_for_logging++;
- wb.Reset();
- }
- return absl::OkStatus();
- }();
- if (!wrote_ia_sequence.ok()) {
- // Something failed when writing the IA Sequence. Signal to clean up the
- // output, such as removing a bad file.
- Abort();
- return wrote_ia_sequence;
- }
-
- LOG(INFO) << "Wrote " << num_temporal_units_for_logging
- << " temporal units with a total of "
- << cumulative_num_samples_for_logging
- << " samples excluding padding.";
-
- Flush();
- state_ = kFlushed;
+ abort_on_destruct.CancelAbort();
return absl::OkStatus();
}
+absl::Status ObuSequencerBase::Close() {
+ switch (state_) {
+ case kInitialized:
+ break;
+ case kPushDescriptorObusCalled: {
+ // Ok, trivial IA sequences don't have a first untrimmed timestamp. So
+ // we will simply push the descriptors with a fallback PTS of 0.
+ descriptor_statistics_->first_untrimmed_timestamp = kFallbackFirstPts;
+
+ RETURN_IF_NOT_OK(PushSerializedDescriptorObus(
+ descriptor_statistics_->common_samples_per_frame,
+ descriptor_statistics_->common_sample_rate,
+ descriptor_statistics_->common_bit_depth,
+ descriptor_statistics_->first_untrimmed_timestamp,
+ descriptor_statistics_->num_channels,
+ descriptor_statistics_->descriptor_obus));
+ state_ = kPushSerializedDescriptorsCalled;
+ break;
+ }
+ case kPushSerializedDescriptorsCalled:
+ break;
+ case kClosed:
+ return absl::FailedPreconditionError(
+ "`Abort` or `Close` previously called.");
+ }
+ CloseDerived();
+ state_ = kClosed;
+ return absl::OkStatus();
+}
+
+void ObuSequencerBase::Abort() {
+ AbortDerived();
+ state_ = kClosed;
+}
+
+absl::Status ObuSequencerBase::HandleInitialTemporalUnits(
+ const TemporalUnitView& temporal_unit,
+ absl::Span<const uint8_t> serialized_temporal_unit) {
+ const bool found_first_untrimmed_sample =
+ temporal_unit.num_untrimmed_samples_ != 0;
+ if (found_first_untrimmed_sample) {
+ // Gather the PTS. For internal accuracy, we store this even if we don't
+ // need to delay the descriptors.
+ descriptor_statistics_->first_untrimmed_timestamp =
+ temporal_unit.start_timestamp_ +
+ temporal_unit.num_samples_to_trim_at_start_;
+ }
+
+ // Push immediately if we don't need to delay the descriptors.
+ if (!delay_descriptors_until_first_untrimmed_sample_) {
+ return PushSerializedTemporalUnit(temporal_unit.start_timestamp_,
+ temporal_unit.num_untrimmed_samples_,
+ serialized_temporal_unit);
+ }
+
+ if (!found_first_untrimmed_sample) {
+ // This frame is fully trimmed. Cache it for later.
+ delayed_temporal_units_.push_back(SerializedTemporalUnit{
+ .start_timestamp = temporal_unit.start_timestamp_,
+ .num_untrimmed_samples = temporal_unit.num_untrimmed_samples_,
+ .data = std::vector<uint8_t>(serialized_temporal_unit.begin(),
+ serialized_temporal_unit.end())});
+ return absl::OkStatus();
+ }
+
+ // Found the first untrimmed sample. Push out all delayed OBUs.
+ RETURN_IF_NOT_OK(PushSerializedDescriptorObus(
+ descriptor_statistics_->common_samples_per_frame,
+ descriptor_statistics_->common_sample_rate,
+ descriptor_statistics_->common_bit_depth,
+ descriptor_statistics_->first_untrimmed_timestamp,
+ descriptor_statistics_->num_channels,
+ descriptor_statistics_->descriptor_obus));
+ state_ = kPushSerializedDescriptorsCalled;
+
+ // Flush any delayed temporal units.
+ for (const auto& delayed_temporal_unit : delayed_temporal_units_) {
+ RETURN_IF_NOT_OK(PushSerializedTemporalUnit(
+ delayed_temporal_unit.start_timestamp,
+ delayed_temporal_unit.num_untrimmed_samples,
+ absl::MakeConstSpan(delayed_temporal_unit.data)));
+ }
+ delayed_temporal_units_.clear();
+ // Then finally, flush the current temporal unit.
+ return PushSerializedTemporalUnit(temporal_unit.start_timestamp_,
+ temporal_unit.num_untrimmed_samples_,
+ serialized_temporal_unit);
+}
+
} // namespace iamf_tools
diff --git a/iamf/cli/obu_sequencer_base.h b/iamf/cli/obu_sequencer_base.h
index cd7b748..955f14c 100644
--- a/iamf/cli/obu_sequencer_base.h
+++ b/iamf/cli/obu_sequencer_base.h
@@ -15,6 +15,7 @@
#include <cstdint>
#include <list>
#include <optional>
+#include <vector>
#include "absl/container/flat_hash_map.h"
#include "absl/status/status.h"
@@ -42,10 +43,26 @@
* // Create a concrete sequencer. Interface is dependent on the conreate
* // sequencer.
* std::unique_ptr<ObuSequencerBase> sequencer = ...;
- * // Gather all the OBUs.
- * // Sequence all of the OBUs.
- * sequencer->.PickAndPlace(...);
*
+ * // Call the `PushDescriptorObus` method.
+ * RETURN_IF_NOT_OK(sequencer->PushDescriptorObus(...));
+ *
+ * while (more data is available) {
+ * // Call the `PushTemporalUnit` method.
+ * RETURN_IF_NOT_OK(sequencer->PushTemporalUnit(...));
+ * }
+ * // Signal that no more data is coming.
+ * // Depending on the context, choose one of the closing functions. Either
+ * // `UpdateDescriptorObusAndClose` (preferred) or `Close`.
+ * RETURN_IF_NOT_OK(sequencer->UpdateDescriptorObusAndClose(...));
+ * // Or:
+ * RETURN_IF_NOT_OK(sequencer->Close());
+ *
+ * // Optionally. `Abort` may be called to clean up output. E.g. file-based
+ * // sequencers could delete their output file. `Abort` is most useful when
+ * // some component outside the class failes; failures in `PushDescriptorObus`,
+ * `PushTemporalUnit`, or `UpdateDescriptorObusAndClose` automatically call
+ * `Abort`.
*/
class ObuSequencerBase {
public:
@@ -61,6 +78,7 @@
* \param num_samples Number of samples written out.
* \return `absl::OkStatus()` on success. A specific status on failure.
*/
+ [[deprecated("Use this class as per the class documentation instead.")]]
static absl::Status WriteTemporalUnit(bool include_temporal_delimiters,
const TemporalUnitView& temporal_unit,
WriteBitBuffer& wb, int& num_samples);
@@ -78,6 +96,7 @@
* \param wb Write buffer to write to.
* \return `absl::OkStatus()` on success. A specific status on failure.
*/
+ [[deprecated("Use this class as per the class documentation instead.")]]
static absl::Status WriteDescriptorObus(
const IASequenceHeaderObu& ia_sequence_header_obu,
const absl::flat_hash_map<uint32_t, CodecConfigObu>& codec_config_obus,
@@ -101,6 +120,58 @@
/*!\brief Destructor.*/
virtual ~ObuSequencerBase() = 0;
+ /*!\brief Gathers statistics on and pushes the OBUs to some output.
+ *
+ * \param ia_sequence_header_obu IA Sequence Header OBU to write.
+ * \param codec_config_obus Codec Config OBUs to write.
+ * \param audio_elements Audio Element OBUs with data to write.
+ * \param mix_presentation_obus Mix Presentation OBUs to write.
+ * \param arbitrary_obus Arbitrary OBUs to write.
+ * \return `absl::OkStatus()` on success. A specific status on failure.
+ */
+ absl::Status PushDescriptorObus(
+ const IASequenceHeaderObu& ia_sequence_header_obu,
+ const absl::flat_hash_map<uint32_t, CodecConfigObu>& codec_config_obus,
+ const absl::flat_hash_map<uint32_t, AudioElementWithData>& audio_elements,
+ const std::list<MixPresentationObu>& mix_presentation_obus,
+ const std::list<ArbitraryObu>& arbitrary_obus);
+
+ /*!\brief Gathers statistics on and pushes the temporal unit to some output.
+ *
+ * \param temporal_unit Temporal unit to push.
+ * \return `absl::OkStatus()` on success. A specific status on failure.
+ */
+ absl::Status PushTemporalUnit(const TemporalUnitView& temporal_unit);
+
+ /*!\brief Finalizes the descriptor OBUs and closes the output.
+ *
+ * \param ia_sequence_header_obu IA Sequence Header OBU to write.
+ * \param codec_config_obus Codec Config OBUs to write.
+ * \param audio_elements Audio Element OBUs with data to write.
+ * \param mix_presentation_obus Mix Presentation OBUs to write.
+ * \param arbitrary_obus Arbitrary OBUs to write.
+ * \return `absl::OkStatus()` on success. A specific status on failure.
+ */
+ absl::Status UpdateDescriptorObusAndClose(
+ const IASequenceHeaderObu& ia_sequence_header_obu,
+ const absl::flat_hash_map<uint32_t, CodecConfigObu>& codec_config_obus,
+ const absl::flat_hash_map<uint32_t, AudioElementWithData>& audio_elements,
+ const std::list<MixPresentationObu>& mix_presentation_obus,
+ const std::list<ArbitraryObu>& arbitrary_obus);
+
+ /*!\brief Signals that no more data is coming, and closes the output.
+ *
+ * \return `absl::OkStatus()` on success. A specific status on failure.
+ */
+ absl::Status Close();
+
+ /*!\brief Aborts writing the output.
+ *
+ * Useful for sequencers which want to clean up their output. Such as to avoid
+ * leaving a stray file when encoding fails.
+ */
+ void Abort();
+
/*!\brief Pick and places OBUs and write to some output.
*
* \param ia_sequence_header_obu IA Sequence Header OBU to write.
@@ -112,6 +183,7 @@
* \param arbitrary_obus Arbitrary OBUs to write.
* \return `absl::OkStatus()` on success. A specific status on failure.
*/
+ [[deprecated("Use this class as per the class documentation instead.")]]
absl::Status PickAndPlace(
const IASequenceHeaderObu& ia_sequence_header_obu,
const absl::flat_hash_map<uint32_t, CodecConfigObu>& codec_config_obus,
@@ -157,25 +229,87 @@
int64_t timestamp, int num_samples,
absl::Span<const uint8_t> temporal_unit) = 0;
- /*!\brief Signals that no more data is coming. */
- virtual void Flush() = 0;
+ /*!\brief Pushes the finalized descriptor OBUs to some output.
+ *
+ * \param descriptor_obus Serialized finalized descriptor OBUs to push.
+ * \return `absl::OkStatus()` on success. A specific status on failure.
+ */
+ virtual absl::Status PushFinalizedDescriptorObus(
+ absl::Span<const uint8_t> descriptor_obus) = 0;
+
+ /*!\brief Signals that no more data is coming, and closes the output. */
+ virtual void CloseDerived() = 0;
/*!\brief Aborts writing the output.
*
* Useful for sequencers which want to clean up their output. Such as to avoid
* leaving a stray file when encoding fails.
*/
- virtual void Abort() = 0;
+ virtual void AbortDerived() = 0;
// The `LebGenerator` to use when writing OBUs.
const LebGenerator leb_generator_;
private:
- enum State { kInitialized, kFlushed };
+ /*!\brief Handles the initial temporal units.
+ *
+ * This function manages state to help process the initial temporal units up
+ * to and including the first one that has a real sample. In a typical IA
+ * Sequence, this would rarely be more few frames.
+ *
+ * \param temporal_unit Temporal unit to push.
+ * \param serialized_temporal_unit Serialized temporla unit.
+ * \return `absl::OkStatus()` on success. A specific status on failure.
+ */
+ absl::Status HandleInitialTemporalUnits(
+ const TemporalUnitView& temporal_unit,
+ absl::Span<const uint8_t> serialized_temporal_unit);
+
+ enum State {
+ // Initial state.
+ kInitialized,
+ // `PushDescriptorObus` has been called, but it may have been delayed when
+ // `delay_descriptors_until_first_untrimmed_sample_` is `true`.
+ kPushDescriptorObusCalled,
+ // Descriptors have been pushed, in this state temporal units are no longer
+ // delayed.
+ kPushSerializedDescriptorsCalled,
+ // `Close` or `Abort` has been called.
+ kClosed
+ };
State state_ = kInitialized;
const bool delay_descriptors_until_first_untrimmed_sample_;
const bool include_temporal_delimiters_;
+
+ // Statistics for the current IA Sequence. Convenient to hold, in order to
+ // validate that the finalized OBUs are consistent with the initial ones.
+ struct DescriptorStatistics {
+ uint32_t common_samples_per_frame = 0;
+ uint32_t common_sample_rate = 0;
+ uint8_t common_bit_depth = 0;
+ int num_channels = 0;
+ std::optional<int64_t> first_untrimmed_timestamp;
+ std::vector<uint8_t> descriptor_obus;
+ };
+ std::optional<DescriptorStatistics> descriptor_statistics_;
+
+ // Reusable scratch buffer.
+ WriteBitBuffer wb_;
+
+ int64_t num_temporal_units_for_logging_ = 0;
+ int64_t cumulative_num_samples_for_logging_ = 0;
+
+ // State for delayed OBUs. `delay_descriptors_until_first_untrimmed_sample_ ==
+ // true` implies we must cache and delayed OBUs until the first untrimmed
+ // sample is seen. In practical IA Sequences, this is rarely more than a few
+ // temporal units.
+ struct SerializedTemporalUnit {
+ int64_t start_timestamp;
+ uint32_t num_untrimmed_samples;
+ std::vector<uint8_t> data;
+ };
+ std::list<SerializedTemporalUnit> delayed_temporal_units_;
};
} // namespace iamf_tools
diff --git a/iamf/cli/obu_sequencer_iamf.cc b/iamf/cli/obu_sequencer_iamf.cc
index cb2e313..a55bd2d 100644
--- a/iamf/cli/obu_sequencer_iamf.cc
+++ b/iamf/cli/obu_sequencer_iamf.cc
@@ -87,14 +87,31 @@
return wb_.FlushAndWriteToFile(output_iamf_);
}
-void ObuSequencerIamf::Flush() {
+absl::Status ObuSequencerIamf::PushFinalizedDescriptorObus(
+ absl::Span<const uint8_t> descriptor_obus) {
+ if (output_iamf_.has_value()) {
+ // For good practice, restore the previous position in the file after we
+ // rewrite. But in reality this function usually will be called right before
+ // closing the file.
+ const auto previous_position = output_iamf_->tellg();
+ output_iamf_->seekg(0, std::ios::beg);
+ RETURN_IF_NOT_OK(wb_.WriteUint8Span(descriptor_obus));
+ RETURN_IF_NOT_OK(wb_.FlushAndWriteToFile(output_iamf_));
+
+ output_iamf_->seekg(previous_position);
+ }
+
+ return absl::OkStatus();
+}
+
+void ObuSequencerIamf::CloseDerived() {
if (output_iamf_.has_value() && output_iamf_->is_open()) {
output_iamf_->close();
output_iamf_ = std::nullopt;
}
}
-void ObuSequencerIamf::Abort() {
+void ObuSequencerIamf::AbortDerived() {
LOG(INFO) << "Aborting ObuSequencerIamf.";
MaybeRemoveFile(iamf_filename_, output_iamf_);
}
diff --git a/iamf/cli/obu_sequencer_iamf.h b/iamf/cli/obu_sequencer_iamf.h
index 4d83018..21a6b9a 100644
--- a/iamf/cli/obu_sequencer_iamf.h
+++ b/iamf/cli/obu_sequencer_iamf.h
@@ -72,14 +72,22 @@
int64_t /*timestamp*/, int /*num_samples*/,
absl::Span<const uint8_t> temporal_unit) override;
+ /*!\brief Pushes the finalized descriptor OBUs to the IAMF file.
+ *
+ * \param descriptor_obus Serialized finalized descriptor OBUs to push.
+ * \return `absl::OkStatus()` on success. A specific status on failure.
+ */
+ absl::Status PushFinalizedDescriptorObus(
+ absl::Span<const uint8_t> descriptor_obus) override;
+
/*!\brief Signals that no more data is coming. */
- void Flush() override;
+ void CloseDerived() override;
/*!\brief Aborts writing the output.
*
* Cleans up the output file if it exists.
*/
- void Abort() override;
+ void AbortDerived() override;
const std::string iamf_filename_;
std::optional<std::fstream> output_iamf_;
diff --git a/iamf/cli/profile_filter.cc b/iamf/cli/profile_filter.cc
index 1088277..e8c3414 100644
--- a/iamf/cli/profile_filter.cc
+++ b/iamf/cli/profile_filter.cc
@@ -219,9 +219,9 @@
absl::Status FilterProfileForNumSubmixes(
absl::string_view mix_presentation_id_for_debugging,
- int num_submixes_in_mix_presentation,
+ int num_sub_mixes_in_mix_presentation,
absl::flat_hash_set<ProfileVersion>& profile_versions) {
- if (num_submixes_in_mix_presentation > 1) {
+ if (num_sub_mixes_in_mix_presentation > 1) {
profile_versions.erase(ProfileVersion::kIamfSimpleProfile);
profile_versions.erase(ProfileVersion::kIamfBaseProfile);
profile_versions.erase(ProfileVersion::kIamfBaseEnhancedProfile);
@@ -230,7 +230,7 @@
if (profile_versions.empty()) {
return absl::InvalidArgumentError(
absl::StrCat(mix_presentation_id_for_debugging, " has ",
- num_submixes_in_mix_presentation,
+ num_sub_mixes_in_mix_presentation,
" sub mixes, but the requested profiles "
"do not support this number of sub-mixes."));
}
@@ -289,7 +289,7 @@
num_audio_elements_in_mix_presentation = 0;
num_channels_in_mix_presentation = 0;
for (const auto& sub_mix : mix_presentation_obu.sub_mixes_) {
- num_audio_elements_in_mix_presentation += sub_mix.num_audio_elements;
+ num_audio_elements_in_mix_presentation += sub_mix.audio_elements.size();
for (const auto& sub_mix_audio_element : sub_mix.audio_elements) {
auto iter = audio_elements.find(sub_mix_audio_element.audio_element_id);
if (iter == audio_elements.end()) {
diff --git a/iamf/cli/proto/mix_presentation.proto b/iamf/cli/proto/mix_presentation.proto
index 72df1a4..a10815f 100644
--- a/iamf/cli/proto/mix_presentation.proto
+++ b/iamf/cli/proto/mix_presentation.proto
@@ -135,7 +135,9 @@
}
message AnchoredLoudness {
- optional uint32 num_anchored_loudness = 1;
+ // `num_anchored_loudness` is ignored. The value in the bitstream is inferred
+ // based on the number of `anchor_elements`.
+ optional uint32 num_anchored_loudness = 1 [deprecated = true];
repeated AnchorElement anchor_elements = 2;
}
@@ -179,14 +181,18 @@
}
message MixPresentationSubMix {
- optional uint32 num_audio_elements = 1;
+ // `num_audio_elements` is ignored. The value in the bitstream is inferred
+ // based on the number of `audio_elements`.
+ optional uint32 num_audio_elements = 1 [deprecated = true];
repeated SubMixAudioElement audio_elements = 2;
// Superseded by `output_mix_gain`.
optional OutputMixConfig output_mix_config = 3 [deprecated = true];
optional MixGainParamDefinition output_mix_gain = 6;
- optional uint32 num_layouts = 4;
+ // `num_layouts` is ignored. The value in the bitstream is inferred
+ // based on the number of `layouts`.
+ optional uint32 num_layouts = 4 [deprecated = true];
repeated MixPresentationLayout layouts = 5;
// Next ID: 7
@@ -221,7 +227,9 @@
// Length should be equal to `count_label`.
repeated string localized_presentation_annotations = 10;
- optional uint32 num_sub_mixes = 3;
+ // `num_sub_mixes` is ignored. The value in the bitstream is inferred
+ // based on the number of `sub_mixes`.
+ optional uint32 num_sub_mixes = 3 [deprecated = true];
repeated MixPresentationSubMix sub_mixes = 4;
// When false [default]: The encoder will ignore the below
diff --git a/iamf/cli/proto_conversion/proto_to_obu/mix_presentation_generator.cc b/iamf/cli/proto_conversion/proto_to_obu/mix_presentation_generator.cc
index 2a18707..2e839d6 100644
--- a/iamf/cli/proto_conversion/proto_to_obu/mix_presentation_generator.cc
+++ b/iamf/cli/proto_conversion/proto_to_obu/mix_presentation_generator.cc
@@ -85,20 +85,25 @@
}
}
-void FillNumSubMixes(const iamf_tools_cli_proto::MixPresentationObuMetadata&
- mix_presentation_metadata,
- DecodedUleb128& num_sub_mixes,
- std::vector<MixPresentationSubMix>& sub_mixes) {
- num_sub_mixes = mix_presentation_metadata.num_sub_mixes();
+void ReserveNumSubMixes(const iamf_tools_cli_proto::MixPresentationObuMetadata&
+ mix_presentation_metadata,
+ std::vector<MixPresentationSubMix>& sub_mixes) {
+ if (mix_presentation_metadata.has_num_sub_mixes()) {
+ LOG(WARNING) << "Ignoring deprecated `num_sub_mixes` field."
+ << "Please remove it.";
+ }
- sub_mixes.reserve(mix_presentation_metadata.num_sub_mixes());
+ sub_mixes.reserve(mix_presentation_metadata.sub_mixes_size());
}
-void FillSubMixNumAudioElements(
+void ReserveSubMixNumAudioElements(
const iamf_tools_cli_proto::MixPresentationSubMix& input_sub_mix,
MixPresentationSubMix& sub_mix) {
- sub_mix.num_audio_elements = input_sub_mix.num_audio_elements();
- sub_mix.audio_elements.reserve(input_sub_mix.num_audio_elements());
+ if (input_sub_mix.has_num_audio_elements()) {
+ LOG(WARNING) << "Ignoring deprecated `num_audio_elements` field."
+ << "Please remove it.";
+ }
+ sub_mix.audio_elements.reserve(input_sub_mix.audio_elements_size());
}
absl::Status FillLocalizedElementAnnotations(
@@ -228,18 +233,13 @@
absl::Status FillLayouts(
const iamf_tools_cli_proto::MixPresentationSubMix& input_sub_mix,
MixPresentationSubMix& sub_mix) {
- sub_mix.num_layouts = input_sub_mix.num_layouts();
-
- if (input_sub_mix.layouts().size() != input_sub_mix.num_layouts()) {
- return absl::InvalidArgumentError(absl::StrCat(
- "Inconsistent number of layouts in user input. "
- "input_sub_mix.num_layouts()= ",
- input_sub_mix.num_layouts(), " vs input_sub_mix.layouts().size()= ",
- input_sub_mix.layouts().size()));
+ if (input_sub_mix.has_num_layouts()) {
+ LOG(WARNING) << "Ignoring deprecated `num_layouts` field."
+ << "Please remove it.";
}
// Reserve the layouts vector and copy in the layouts.
- sub_mix.layouts.reserve(input_sub_mix.num_layouts());
+ sub_mix.layouts.reserve(input_sub_mix.layouts_size());
for (const auto& input_layout : input_sub_mix.layouts()) {
const auto& input_loudness_layout = input_layout.loudness_layout();
@@ -315,10 +315,10 @@
const size_t num_tags = mix_presentation_tags.tags().size() +
(append_build_information_tag ? 1 : 0);
// At the OBU it must fit into a `uint8_t`.
+ uint8_t obu_num_tags;
RETURN_IF_NOT_OK(StaticCastIfInRange<size_t, uint8_t>(
- "MixPresentationTags.num_tags", num_tags,
- obu_mix_presentation_tags->num_tags));
- obu_mix_presentation_tags->tags.reserve(num_tags);
+ "Total number of MixPresentationTags.tags", num_tags, obu_num_tags));
+ obu_mix_presentation_tags->tags.reserve(obu_num_tags);
for (const auto& input_tag : mix_presentation_tags.tags()) {
obu_mix_presentation_tags->tags.emplace_back(MixPresentationTags::Tag{
.tag_name = input_tag.tag_name(),
@@ -416,11 +416,16 @@
// Not using anchored loudness.
return absl::OkStatus();
}
+ if (user_loudness.anchored_loudness().has_num_anchored_loudness()) {
+ LOG(WARNING) << "Ignoring deprecated `num_anchored_loudness` field. Please "
+ "remove it.";
+ }
- RETURN_IF_NOT_OK(StaticCastIfInRange<uint32_t, uint8_t>(
- "LoudnessInfo.anchored_loudness.num_anchored_loudness",
- user_loudness.anchored_loudness().num_anchored_loudness(),
- output_loudness.anchored_loudness.num_anchored_loudness));
+ uint8_t num_anchored_loudness;
+ RETURN_IF_NOT_OK(StaticCastIfInRange<size_t, uint8_t>(
+ "Number of LoudnessInfo.anchored_loudness",
+ user_loudness.anchored_loudness().anchor_elements_size(),
+ num_anchored_loudness));
for (const auto& metadata_anchor_element :
user_loudness.anchored_loudness().anchor_elements()) {
@@ -481,7 +486,6 @@
// Length `count_label`.
std::vector<std::string> localized_presentation_annotations;
- DecodedUleb128 num_sub_mixes;
// Length `num_sub_mixes`.
std::vector<MixPresentationSubMix> sub_mixes;
@@ -496,12 +500,11 @@
obu_args.annotations_language,
obu_args.localized_presentation_annotations);
- FillNumSubMixes(mix_presentation_metadata, obu_args.num_sub_mixes,
- obu_args.sub_mixes);
+ ReserveNumSubMixes(mix_presentation_metadata, obu_args.sub_mixes);
for (const auto& input_sub_mix : mix_presentation_metadata.sub_mixes()) {
MixPresentationSubMix sub_mix;
- FillSubMixNumAudioElements(input_sub_mix, sub_mix);
+ ReserveSubMixNumAudioElements(input_sub_mix, sub_mix);
for (const auto& input_sub_mix_audio_element :
input_sub_mix.audio_elements()) {
SubMixAudioElement sub_mix_audio_element;
@@ -541,8 +544,7 @@
GetHeaderFromMetadata(mix_presentation_metadata.obu_header()),
obu_args.mix_presentation_id, obu_args.count_label,
obu_args.annotations_language,
- obu_args.localized_presentation_annotations, obu_args.num_sub_mixes,
- obu_args.sub_mixes);
+ obu_args.localized_presentation_annotations, obu_args.sub_mixes);
obu.mix_presentation_tags_ = obu_args.mix_presentation_tags;
mix_presentation_obus.emplace_back(std::move(obu));
}
diff --git a/iamf/cli/proto_conversion/proto_to_obu/tests/mix_presentation_generator_test.cc b/iamf/cli/proto_conversion/proto_to_obu/tests/mix_presentation_generator_test.cc
index a322f7c..d80d4db 100644
--- a/iamf/cli/proto_conversion/proto_to_obu/tests/mix_presentation_generator_test.cc
+++ b/iamf/cli/proto_conversion/proto_to_obu/tests/mix_presentation_generator_test.cc
@@ -80,9 +80,7 @@
R"pb(
mix_presentation_id: 42
count_label: 0
- num_sub_mixes: 1
sub_mixes {
- num_audio_elements: 1
audio_elements {
audio_element_id: 300
rendering_config {
@@ -442,7 +440,6 @@
const auto& first_obu = generated_obus.front();
ASSERT_TRUE(first_obu.mix_presentation_tags_.has_value());
- EXPECT_EQ(first_obu.mix_presentation_tags_->num_tags, 0);
EXPECT_TRUE(first_obu.mix_presentation_tags_->tags.empty());
}
@@ -464,7 +461,6 @@
// Ok safely ignore the deprecated `num_tags` field.
const auto& first_obu = generated_obus.front();
ASSERT_TRUE(first_obu.mix_presentation_tags_.has_value());
- EXPECT_EQ(first_obu.mix_presentation_tags_->num_tags, 0);
EXPECT_TRUE(first_obu.mix_presentation_tags_->tags.empty());
}
@@ -533,7 +529,6 @@
const auto& first_obu = generated_obus.front();
ASSERT_TRUE(first_obu.mix_presentation_tags_.has_value());
- EXPECT_EQ(first_obu.mix_presentation_tags_->num_tags, 2);
ASSERT_EQ(first_obu.mix_presentation_tags_->tags.size(), 2);
EXPECT_EQ(first_obu.mix_presentation_tags_->tags[0].tag_name,
"content_language");
@@ -596,7 +591,7 @@
if (test_case.expected_num_tags.has_value()) {
EXPECT_TRUE(first_obu.mix_presentation_tags_.has_value());
- EXPECT_EQ(first_obu.mix_presentation_tags_->num_tags,
+ EXPECT_EQ(first_obu.mix_presentation_tags_->tags.size(),
*test_case.expected_num_tags);
// If the tags are present, the last tag may be the build information tag.
if (test_case.expect_build_information_tag_to_be_present) {
@@ -879,17 +874,64 @@
generator.Generate(kAppendBuildInformationTag, generated_obus_).ok());
}
-TEST_F(MixPresentationGeneratorTest, InvalidInconsistentNumberOfLayouts) {
- // There is one element in the `layouts` array.
- ASSERT_EQ(mix_presentation_metadata_.at(0).sub_mixes(0).layouts().size(), 1);
- // `num_layouts` is inconsistent with the number of layouts in the array.
- const uint32_t kInconsistentNumLayouts = 2;
- mix_presentation_metadata_.at(0).mutable_sub_mixes(0)->set_num_layouts(
- kInconsistentNumLayouts);
+TEST_F(MixPresentationGeneratorTest, IgnoresDeprecatedNumSubMixes) {
+ // This test assumes the default metadata has one sub mix.
+ constexpr uint32_t kExpectedNumSubMixes = 1;
+ ASSERT_EQ(mix_presentation_metadata_.at(0).sub_mixes_size(),
+ kExpectedNumSubMixes);
+ // Include a strange value for the deprecated `num_sub_mixes` field.
+ constexpr uint32_t kIncorrectIgnoredNumSubMixes = 2;
+ mix_presentation_metadata_.at(0).set_num_sub_mixes(
+ kIncorrectIgnoredNumSubMixes);
MixPresentationGenerator generator(mix_presentation_metadata_);
- EXPECT_FALSE(
- generator.Generate(kAppendBuildInformationTag, generated_obus_).ok());
+ EXPECT_THAT(generator.Generate(kAppendBuildInformationTag, generated_obus_),
+ IsOk());
+
+ // Regardless of the deprecated `num_layouts` field, the number of layouts is
+ // inferred the `layouts` array.
+ EXPECT_EQ(generated_obus_.back().GetNumSubMixes(), kExpectedNumSubMixes);
+ EXPECT_EQ(generated_obus_.back().sub_mixes_.size(), kExpectedNumSubMixes);
+}
+
+TEST_F(MixPresentationGeneratorTest, IgnoresDeprecatedNumAudioElements) {
+ // This test assumes the default metadata has one audio element.
+ constexpr uint32_t kExpectedNumAudioElements = 1;
+ ASSERT_EQ(mix_presentation_metadata_.at(0).sub_mixes(0).audio_elements_size(),
+ kExpectedNumAudioElements);
+ // Include a strange value for the deprecated `num_audio_elements`.
+ constexpr uint32_t kIncorrectIgnoredNumAudioElements = 2;
+ mix_presentation_metadata_.at(0).mutable_sub_mixes(0)->set_num_audio_elements(
+ kIncorrectIgnoredNumAudioElements);
+ MixPresentationGenerator generator(mix_presentation_metadata_);
+
+ EXPECT_THAT(generator.Generate(kAppendBuildInformationTag, generated_obus_),
+ IsOk());
+
+ // Regardless of the deprecated `num_audio_elements` field, the number of
+ // audio elements the `audio_elements` array.
+ EXPECT_EQ(generated_obus_.back().sub_mixes_[0].audio_elements.size(),
+ kExpectedNumAudioElements);
+}
+
+TEST_F(MixPresentationGeneratorTest, IgnoresDeprecatedNumLayouts) {
+ // This test assumes the default metadata has one layout.
+ constexpr uint32_t kExpectedNumLayouts = 1;
+ ASSERT_EQ(mix_presentation_metadata_.at(0).sub_mixes(0).layouts().size(),
+ kExpectedNumLayouts);
+ // Include a strange value for the deprecated `num_layouts`.
+ constexpr uint32_t kIncorrectIgnoredNumLayouts = 2;
+ mix_presentation_metadata_.at(0).mutable_sub_mixes(0)->set_num_layouts(
+ kIncorrectIgnoredNumLayouts);
+ MixPresentationGenerator generator(mix_presentation_metadata_);
+
+ EXPECT_THAT(generator.Generate(kAppendBuildInformationTag, generated_obus_),
+ IsOk());
+
+ // Regardless of the deprecated `num_layouts` field, the number of layouts is
+ // inferred from the `layouts` array.
+ EXPECT_EQ(generated_obus_.back().sub_mixes_[0].layouts.size(),
+ kExpectedNumLayouts);
}
TEST_F(MixPresentationGeneratorTest, CopiesUserLoudness) {
@@ -1138,7 +1180,6 @@
google::protobuf::TextFormat::ParseFromString(
R"pb(
anchored_loudness {
- num_anchored_loudness: 2
anchor_elements:
[ { anchor_element: ANCHOR_TYPE_DIALOGUE anchored_loudness: 1000 }
, { anchor_element: ANCHOR_TYPE_ALBUM anchored_loudness: 1001 }]
@@ -1148,7 +1189,6 @@
// Configured expected data. The function only writes to the
// `AnchoredLoudness`.
const AnchoredLoudness expected_output_loudness = {
- .num_anchored_loudness = 2,
.anchor_elements = {
{.anchor_element = AnchoredLoudnessElement::kAnchorElementDialogue,
.anchored_loudness = 1000},
@@ -1161,6 +1201,22 @@
EXPECT_EQ(output_loudness.anchored_loudness, expected_output_loudness);
}
+TEST(CopyUserAnchoredLoudness, IgnoresDeprecatedNumAnchoredLoudnessField) {
+ // Set up an anchored loudness which no anchor elements, but incorrectly
+ // claims there is one.
+ LoudnessInfo output_loudness = {.info_type = LoudnessInfo::kAnchoredLoudness};
+ iamf_tools_cli_proto::LoudnessInfo user_loudness;
+ user_loudness.mutable_anchored_loudness()->set_num_anchored_loudness(1);
+
+ EXPECT_THAT(MixPresentationGenerator::CopyUserAnchoredLoudness(
+ user_loudness, output_loudness),
+ IsOk());
+
+ // Regardless of the deprecated `num_anchored_loudness` field, the number of
+ // anchor elements is inferred from the `anchor_elements` array.
+ EXPECT_TRUE(output_loudness.anchored_loudness.anchor_elements.empty());
+}
+
TEST(CopyUserAnchoredLoudness, IllegalUnknownAnchorElementEnum) {
// `info_type` must be configured as a prerequisite.
LoudnessInfo output_loudness = {.info_type = LoudnessInfo::kAnchoredLoudness};
@@ -1170,7 +1226,6 @@
google::protobuf::TextFormat::ParseFromString(
R"pb(
anchored_loudness {
- num_anchored_loudness: 1
anchor_elements:
[ { anchor_element: ANCHOR_TYPE_NOT_DEFINED anchored_loudness: 1000 }
)pb",
diff --git a/iamf/cli/rendering_mix_presentation_finalizer.cc b/iamf/cli/rendering_mix_presentation_finalizer.cc
index 95e3400..9fce5fa 100644
--- a/iamf/cli/rendering_mix_presentation_finalizer.cc
+++ b/iamf/cli/rendering_mix_presentation_finalizer.cc
@@ -157,7 +157,10 @@
const uint32_t output_sample_rate =
sub_mix_audio_element.codec_config->GetOutputSampleRate();
if (common_sample_rate != output_sample_rate) {
- // TODO(b/274689885): Convert to a common sample rate and/or bit-depth.
+ // Theoretically, we would have to resample this audio element to the
+ // common sample rate. However, as of IAMF v1.1.0, the spec forbids
+ // multiple Codec Config OBUs. This case is not possible to occur with a
+ // single Codec Config OBU.
return absl::UnimplementedError(
absl::StrCat("OBUs with different sample rates not supported yet: (",
common_sample_rate, " != ", output_sample_rate, ")."));
@@ -622,10 +625,13 @@
rendering_bit_depth, common_num_samples_per_frame,
requires_resampling));
if (requires_resampling) {
- // TODO(b/274689885): Convert to a common sample rate and/or bit-depth.
+ // Detected multiple Codec Config OBUs with different sample rates or
+ // bit-depths. As of IAMF v1.1.0, multiple Codec Config OBUs in the same
+ // IA sequence are never permitted. The spec implies we would have to
+ // resample to a common sample rate and/or bit-depth.
return absl::UnimplementedError(
- "This implementation does not support mixing different sample rates "
- "or bit-depths.");
+ "This implementation does not support mixing Codec Config OBUs with "
+ "different sample rates or bit-depths.");
}
RETURN_IF_NOT_OK(GenerateRenderingMetadataForLayouts(
renderer_factory, loudness_calculator_factory, sample_processor_factory,
@@ -910,6 +916,7 @@
RETURN_IF_NOT_OK(FillLoudnessForMixPresentation(
validate_loudness, sub_mix_rendering_metadata_it->second,
mix_presentation_obu));
+ mix_presentation_obu.PrintObu();
}
// Flush the finalized OBUs and mark that this class should not use them
diff --git a/iamf/cli/temporal_unit_view.cc b/iamf/cli/temporal_unit_view.cc
index 8b4d5c5..f234c56 100644
--- a/iamf/cli/temporal_unit_view.cc
+++ b/iamf/cli/temporal_unit_view.cc
@@ -216,21 +216,24 @@
arbitrary_obus.end());
absl::c_sort(sorted_parameter_blocks, CompareParameterId);
absl::c_sort(sorted_audio_frames, CompareAudioElementIdAudioSubstreamId);
- return TemporalUnitView(std::move(sorted_parameter_blocks),
- std::move(sorted_audio_frames),
- std::move(copied_arbitrary_obus),
- statistics->num_samples_to_trim_at_start,
- statistics->num_untrimmed_samples);
+ return TemporalUnitView(
+ std::move(sorted_parameter_blocks), std::move(sorted_audio_frames),
+ std::move(copied_arbitrary_obus), statistics->start_timestamp,
+ statistics->end_timestamp, statistics->num_samples_to_trim_at_start,
+ statistics->num_untrimmed_samples);
}
TemporalUnitView::TemporalUnitView(
std::vector<const ParameterBlockWithData*>&& parameter_blocks,
std::vector<const AudioFrameWithData*>&& audio_frames,
std::vector<const ArbitraryObu*>&& arbitrary_obus,
+ InternalTimestamp start_timestamp, InternalTimestamp end_timestamp,
uint32_t num_samples_to_trim_at_start, uint32_t num_untrimmed_samples)
: parameter_blocks_(std::move(parameter_blocks)),
audio_frames_(std::move(audio_frames)),
arbitrary_obus_(std::move(arbitrary_obus)),
+ start_timestamp_(start_timestamp),
+ end_timestamp_(end_timestamp),
num_samples_to_trim_at_start_(num_samples_to_trim_at_start),
num_untrimmed_samples_(num_untrimmed_samples) {}
diff --git a/iamf/cli/temporal_unit_view.h b/iamf/cli/temporal_unit_view.h
index 59f9d20..8d6a245 100644
--- a/iamf/cli/temporal_unit_view.h
+++ b/iamf/cli/temporal_unit_view.h
@@ -22,6 +22,7 @@
#include "iamf/cli/audio_frame_with_data.h"
#include "iamf/cli/parameter_block_with_data.h"
#include "iamf/obu/arbitrary_obu.h"
+#include "iamf/obu/types.h"
namespace iamf_tools {
@@ -93,6 +94,8 @@
const std::vector<const ArbitraryObu*> arbitrary_obus_;
// Common statistics for this temporal unit.
+ const InternalTimestamp start_timestamp_;
+ const InternalTimestamp end_timestamp_;
const uint32_t num_samples_to_trim_at_start_;
const uint32_t num_untrimmed_samples_;
@@ -104,6 +107,8 @@
* \param parameter_blocks Parameter blocks to include in the view.
* \param audio_frames Audio frames to include in the view.
* \param arbitrary_obus Arbitrary OBUs to include in the view.
+ * \param start_timestamp Start timestamp of the temporal unit.
+ * \param end_timestamp End timestamp of the temporal unit.
* \param num_samples_to_trim_at_start Number of samples to trim at the start
* of the audio frames.
* \param num_untrimmed_samples Number of samples in the audio frames before
@@ -113,6 +118,7 @@
std::vector<const ParameterBlockWithData*>&& parameter_blocks,
std::vector<const AudioFrameWithData*>&& audio_frames,
std::vector<const ArbitraryObu*>&& arbitrary_obus,
+ InternalTimestamp start_timestamp, InternalTimestamp end_timestamp,
uint32_t num_samples_to_trim_at_start, uint32_t num_untrimmed_samples);
};
diff --git a/iamf/cli/tests/BUILD b/iamf/cli/tests/BUILD
index 94d3211..16b4b1f 100644
--- a/iamf/cli/tests/BUILD
+++ b/iamf/cli/tests/BUILD
@@ -342,6 +342,7 @@
"//iamf/cli:audio_frame_with_data",
"//iamf/cli:obu_sequencer_iamf",
"//iamf/cli:parameter_block_with_data",
+ "//iamf/cli:temporal_unit_view",
"//iamf/common:leb_generator",
"//iamf/common:read_bit_buffer",
"//iamf/obu:arbitrary_obu",
diff --git a/iamf/cli/tests/cli_test_utils.cc b/iamf/cli/tests/cli_test_utils.cc
index e3821fa..3d72e5a 100644
--- a/iamf/cli/tests/cli_test_utils.cc
+++ b/iamf/cli/tests/cli_test_utils.cc
@@ -36,6 +36,7 @@
#include "absl/status/status_matchers.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
+#include "absl/strings/str_replace.h"
#include "absl/strings/string_view.h"
#include "absl/types/span.h"
#include "gmock/gmock.h"
@@ -128,15 +129,14 @@
int temporal_unit_count = 0;
LOG(INFO) << "Starting Temporal Unit OBU processing";
while (continue_processing) {
- std::list<AudioFrameWithData> audio_frames_for_temporal_unit;
- std::list<ParameterBlockWithData> parameter_blocks_for_temporal_unit;
- std::optional<int32_t> timestamp_for_temporal_unit;
+ std::optional<ObuProcessor::OutputTemporalUnit> output_temporal_unit;
RETURN_IF_NOT_OK(obu_processor->ProcessTemporalUnit(
- audio_frames_for_temporal_unit, parameter_blocks_for_temporal_unit,
- timestamp_for_temporal_unit, continue_processing));
- audio_frames.splice(audio_frames.end(), audio_frames_for_temporal_unit);
+ /*eos_is_end_of_sequence=*/true, output_temporal_unit,
+ continue_processing));
+ audio_frames.splice(audio_frames.end(),
+ output_temporal_unit->output_audio_frames);
parameter_blocks.splice(parameter_blocks.end(),
- parameter_blocks_for_temporal_unit);
+ output_temporal_unit->output_parameter_blocks);
temporal_unit_count++;
}
LOG(INFO) << "Processed " << temporal_unit_count << " Temporal Unit OBUs";
@@ -149,8 +149,9 @@
return absl::OkStatus();
}
-void AddLpcmCodecConfigWithIdAndSampleRate(
- uint32_t codec_config_id, uint32_t sample_rate,
+void AddLpcmCodecConfig(
+ DecodedUleb128 codec_config_id, uint32_t num_samples_per_frame,
+ uint8_t sample_size, uint32_t sample_rate,
absl::flat_hash_map<uint32_t, CodecConfigObu>& codec_config_obus) {
// Initialize the Codec Config OBU.
ASSERT_EQ(codec_config_obus.find(codec_config_id), codec_config_obus.end());
@@ -158,15 +159,26 @@
CodecConfigObu obu(
ObuHeader(), codec_config_id,
{.codec_id = CodecConfig::kCodecIdLpcm,
- .num_samples_per_frame = 8,
+ .num_samples_per_frame = num_samples_per_frame,
.decoder_config = LpcmDecoderConfig{
.sample_format_flags_bitmask_ = LpcmDecoderConfig::kLpcmLittleEndian,
- .sample_size_ = 16,
+ .sample_size_ = sample_size,
.sample_rate_ = sample_rate}});
EXPECT_THAT(obu.Initialize(kOverrideAudioRollDistance), IsOk());
codec_config_obus.emplace(codec_config_id, std::move(obu));
}
+void AddLpcmCodecConfigWithIdAndSampleRate(
+ uint32_t codec_config_id, uint32_t sample_rate,
+ absl::flat_hash_map<uint32_t, CodecConfigObu>& codec_config_obus) {
+ // Many tests either don't care about the details. Or assumed these "default"
+ // values.
+ constexpr uint32_t kNumSamplesPerFrame = 8;
+ constexpr uint8_t kSampleSize = 16;
+ return AddLpcmCodecConfig(codec_config_id, kNumSamplesPerFrame, kSampleSize,
+ sample_rate, codec_config_obus);
+}
+
void AddOpusCodecConfigWithId(
uint32_t codec_config_id,
absl::flat_hash_map<uint32_t, CodecConfigObu>& codec_config_obus) {
@@ -305,30 +317,42 @@
const std::vector<DecodedUleb128>& audio_element_ids,
DecodedUleb128 common_parameter_id, DecodedUleb128 common_parameter_rate,
std::list<MixPresentationObu>& mix_presentations) {
+ // Configure one of the simplest mix presentation. Mix presentations REQUIRE
+ // at least one sub-mix and a stereo layout.
+ AddMixPresentationObuWithConfigurableLayouts(
+ mix_presentation_id, audio_element_ids, common_parameter_id,
+ common_parameter_rate,
+ {LoudspeakersSsConventionLayout::kSoundSystemA_0_2_0}, mix_presentations);
+}
+
+void AddMixPresentationObuWithConfigurableLayouts(
+ DecodedUleb128 mix_presentation_id,
+ const std::vector<DecodedUleb128>& audio_element_ids,
+ DecodedUleb128 common_parameter_id, DecodedUleb128 common_parameter_rate,
+ const std::vector<LoudspeakersSsConventionLayout::SoundSystem>&
+ sound_system_layouts,
+ std::list<MixPresentationObu>& mix_presentations) {
MixGainParamDefinition common_mix_gain_param_definition;
common_mix_gain_param_definition.parameter_id_ = common_parameter_id;
common_mix_gain_param_definition.parameter_rate_ = common_parameter_rate;
common_mix_gain_param_definition.param_definition_mode_ = true;
common_mix_gain_param_definition.default_mix_gain_ = 0;
+ std::vector<MixPresentationLayout> layouts;
+ for (const auto& sound_system : sound_system_layouts) {
+ layouts.push_back(
+ {.loudness_layout = {.layout_type =
+ Layout::kLayoutTypeLoudspeakersSsConvention,
+ .specific_layout =
+ LoudspeakersSsConventionLayout{
+ .sound_system = sound_system,
+ .reserved = 0}},
+ .loudness = {
+ .info_type = 0, .integrated_loudness = 0, .digital_peak = 0}});
+ }
- // Configure one of the simplest mix presentation. Mix presentations REQUIRE
- // at least one sub-mix and a stereo layout.
std::vector<MixPresentationSubMix> sub_mixes = {
- {.num_audio_elements =
- static_cast<DecodedUleb128>(audio_element_ids.size()),
- .output_mix_gain = common_mix_gain_param_definition,
- .num_layouts = 1,
- .layouts = {
- {.loudness_layout =
- {.layout_type = Layout::kLayoutTypeLoudspeakersSsConvention,
- .specific_layout =
- LoudspeakersSsConventionLayout{
- .sound_system = LoudspeakersSsConventionLayout::
- kSoundSystemA_0_2_0,
- .reserved = 0}},
- .loudness = {.info_type = 0,
- .integrated_loudness = 0,
- .digital_peak = 0}}}}};
+ {.output_mix_gain = common_mix_gain_param_definition,
+ .layouts = layouts}};
for (const auto& audio_element_id : audio_element_ids) {
sub_mixes[0].audio_elements.push_back({
.audio_element_id = audio_element_id,
@@ -343,9 +367,9 @@
});
}
- mix_presentations.push_back(MixPresentationObu(
- ObuHeader(), mix_presentation_id,
- /*count_label=*/0, {}, {}, sub_mixes.size(), sub_mixes));
+ mix_presentations.push_back(
+ MixPresentationObu(ObuHeader(), mix_presentation_id,
+ /*count_label=*/0, {}, {}, sub_mixes));
}
void AddParamDefinitionWithMode0AndOneSubblock(
@@ -408,19 +432,17 @@
std::string GetAndCleanupOutputFileName(absl::string_view suffix) {
const testing::TestInfo* const test_info =
testing::UnitTest::GetInstance()->current_test_info();
- std::string file_name =
- absl::StrCat(test_info->name(), "-", test_info->test_suite_name(), "-",
- test_info->test_case_name(), suffix);
+ std::string filename = absl::StrCat(test_info->name(), "-",
+ test_info->test_suite_name(), suffix);
- // It is possible that the test suite name and test case name contain the '/'
- // character. Replace it with '-' to form a legal file name.
- std::transform(file_name.begin(), file_name.end(), file_name.begin(),
- [](char c) { return (c == '/') ? '-' : c; });
- const std::filesystem::path test_specific_file_name =
- std::filesystem::path(::testing::TempDir()) / file_name;
+ // It is possible that the test suite name contains the '/' character.
+ // Replace it with '-' to form a legal file name.
+ absl::StrReplaceAll({{"/", "-"}}, &filename);
+ const std::filesystem::path test_specific_filename =
+ std::filesystem::path(::testing::TempDir()) / filename;
- std::filesystem::remove(test_specific_file_name);
- return test_specific_file_name.string();
+ std::filesystem::remove(test_specific_filename);
+ return test_specific_filename.string();
}
std::string GetAndCreateOutputDirectory(absl::string_view suffix) {
@@ -474,8 +496,8 @@
std::vector<DecodeSpecification> decode_specifications;
for (const auto& mix_presentation :
user_metadata.mix_presentation_metadata()) {
- for (int i = 0; i < mix_presentation.num_sub_mixes(); ++i) {
- for (int j = 0; j < mix_presentation.sub_mixes(i).num_layouts(); ++j) {
+ for (int i = 0; i < mix_presentation.sub_mixes_size(); ++i) {
+ for (int j = 0; j < mix_presentation.sub_mixes(i).layouts_size(); ++j) {
DecodeSpecification decode_specification;
decode_specification.mix_presentation_id =
mix_presentation.mix_presentation_id();
diff --git a/iamf/cli/tests/cli_test_utils.h b/iamf/cli/tests/cli_test_utils.h
index 5d74c94..eb86a9a 100644
--- a/iamf/cli/tests/cli_test_utils.h
+++ b/iamf/cli/tests/cli_test_utils.h
@@ -91,6 +91,19 @@
/*!\brief Adds a configurable LPCM `CodecConfigObu` to the output argument.
*
* \param codec_config_id `codec_config_id` of the OBU to create.
+ * \param num_samples_per_frame Number of samples per frame.
+ * \param sample_size Sample size.
+ * \param sample_rate `sample_rate` of the OBU to create.
+ * \param codec_config_obus Map to add the OBU to keyed by `codec_config_id`.
+ */
+void AddLpcmCodecConfig(
+ DecodedUleb128 codec_config_id, uint32_t num_samples_per_frame,
+ uint8_t sample_size, uint32_t sample_rate,
+ absl::flat_hash_map<uint32_t, CodecConfigObu>& codec_config_obus);
+
+/*!\brief Adds a configurable LPCM `CodecConfigObu` to the output argument.
+ *
+ * \param codec_config_id `codec_config_id` of the OBU to create.
* \param sample_rate `sample_rate` of the OBU to create.
* \param codec_config_obus Map to add the OBU to keyed by `codec_config_id`.
*/
@@ -162,13 +175,32 @@
* created OBU.
* \param common_parameter_rate `parameter_rate` of all parameters within the
* created OBU.
- * \param mix_presentations List to add OBU to.
+ * \param output_mix_presentations List to add OBU to.
*/
void AddMixPresentationObuWithAudioElementIds(
DecodedUleb128 mix_presentation_id,
const std::vector<DecodedUleb128>& audio_element_id,
DecodedUleb128 common_parameter_id, DecodedUleb128 common_parameter_rate,
- std::list<MixPresentationObu>& mix_presentations);
+ std::list<MixPresentationObu>& output_mix_presentations);
+
+/*!\brief Adds a configurable `MixPresentationObu` to the output argument.
+ *
+ * \param mix_presentation_id `mix_presentation_id` of the OBU to create.
+ * \param audio_element_ids `audio_element_id`s of the OBU to create.
+ * \param common_parameter_id `parameter_id` of all parameters within the
+ * created OBU.
+ * \param common_parameter_rate `parameter_rate` of all parameters within the
+ * created OBU.
+ * \param sound_system_layouts `sound_system`s of the OBU to create.
+ * \param output_mix_presentations List to add OBU to.
+ */
+void AddMixPresentationObuWithConfigurableLayouts(
+ DecodedUleb128 mix_presentation_id,
+ const std::vector<DecodedUleb128>& audio_element_id,
+ DecodedUleb128 common_parameter_id, DecodedUleb128 common_parameter_rate,
+ const std::vector<LoudspeakersSsConventionLayout::SoundSystem>&
+ sound_system_layouts,
+ std::list<MixPresentationObu>& output_mix_presentations);
/*!\brief Adds a configurable mix gain param definition to the output argument.
*
@@ -550,7 +582,7 @@
: ObuSequencerBase(leb_generator, include_temporal_delimiters,
delay_descriptors_until_first_untrimmed_sample) {}
- MOCK_METHOD(void, Abort, (), (override));
+ MOCK_METHOD(void, AbortDerived, (), (override));
MOCK_METHOD(absl::Status, PushSerializedDescriptorObus,
(uint32_t common_samples_per_frame, uint32_t common_sample_rate,
@@ -564,7 +596,10 @@
absl::Span<const uint8_t> temporal_unit),
(override));
- MOCK_METHOD(void, Flush, (), (override));
+ MOCK_METHOD(absl::Status, PushFinalizedDescriptorObus,
+ (absl::Span<const uint8_t> descriptor_obus), (override));
+
+ MOCK_METHOD(void, CloseDerived, (), (override));
};
} // namespace iamf_tools
diff --git a/iamf/cli/tests/cli_util_test.cc b/iamf/cli/tests/cli_util_test.cc
index 0b3fee6..b9a56f8 100644
--- a/iamf/cli/tests/cli_util_test.cc
+++ b/iamf/cli/tests/cli_util_test.cc
@@ -48,7 +48,6 @@
constexpr DecodedUleb128 kCodecConfigId = 21;
constexpr DecodedUleb128 kAudioElementId = 100;
-constexpr DecodedUleb128 kSecondAudioElementId = 101;
constexpr DecodedUleb128 kMixPresentationId = 100;
constexpr DecodedUleb128 kParameterId = 99999;
constexpr DecodedUleb128 kParameterRate = 48000;
@@ -62,12 +61,9 @@
std::vector<std::vector<int32_t>> frame_to_write = {{0x7f000000, 0x7e000000},
{0x7f000000, 0x7e000000}};
const uint8_t kBitDepth = 24;
- const uint32_t kSamplesToTrimAtStart = 0;
- const uint32_t kSamplesToTrimAtEnd = 0;
const bool kBigEndian = false;
std::vector<uint8_t> output_buffer;
- EXPECT_THAT(WritePcmFrameToBuffer(frame_to_write, kSamplesToTrimAtStart,
- kSamplesToTrimAtEnd, kBitDepth, kBigEndian,
+ EXPECT_THAT(WritePcmFrameToBuffer(frame_to_write, kBitDepth, kBigEndian,
output_buffer),
IsOk());
@@ -78,12 +74,9 @@
std::vector<std::vector<int32_t>> frame_to_write = {{0x7f001200, 0x7e003400},
{0x7f005600, 0x7e007800}};
const uint8_t kBitDepth = 24;
- const uint32_t kSamplesToTrimAtStart = 0;
- const uint32_t kSamplesToTrimAtEnd = 0;
const bool kBigEndian = true;
std::vector<uint8_t> output_buffer;
- EXPECT_THAT(WritePcmFrameToBuffer(frame_to_write, kSamplesToTrimAtStart,
- kSamplesToTrimAtEnd, kBitDepth, kBigEndian,
+ EXPECT_THAT(WritePcmFrameToBuffer(frame_to_write, kBitDepth, kBigEndian,
output_buffer),
IsOk());
@@ -96,12 +89,9 @@
std::vector<std::vector<int32_t>> frame_to_write = {{0x7f001200, 0x7e003400},
{0x7f005600, 0x7e007800}};
const uint8_t kBitDepth = 24;
- const uint32_t kSamplesToTrimAtStart = 0;
- const uint32_t kSamplesToTrimAtEnd = 0;
const bool kBigEndian = false;
std::vector<uint8_t> output_buffer;
- EXPECT_THAT(WritePcmFrameToBuffer(frame_to_write, kSamplesToTrimAtStart,
- kSamplesToTrimAtEnd, kBitDepth, kBigEndian,
+ EXPECT_THAT(WritePcmFrameToBuffer(frame_to_write, kBitDepth, kBigEndian,
output_buffer),
IsOk());
@@ -110,35 +100,14 @@
EXPECT_EQ(output_buffer, kExpectedBytes);
}
-TEST(WritePcmFrameToBuffer, TrimsSamples) {
- std::vector<std::vector<int32_t>> frame_to_write = {{0x7f001200, 0x7e003400},
- {0x7f005600, 0x7e007800}};
- const uint8_t kBitDepth = 24;
- const uint32_t kSamplesToTrimAtStart = 1;
- const uint32_t kSamplesToTrimAtEnd = 0;
- const bool kBigEndian = false;
- std::vector<uint8_t> output_buffer;
- EXPECT_THAT(WritePcmFrameToBuffer(frame_to_write, kSamplesToTrimAtStart,
- kSamplesToTrimAtEnd, kBitDepth, kBigEndian,
- output_buffer),
- IsOk());
-
- const std::vector<uint8_t> kExpectedBytes = {0x56, 0x00, 0x7f,
- 0x78, 0x00, 0x7e};
- EXPECT_EQ(output_buffer, kExpectedBytes);
-}
-
TEST(WritePcmFrameToBuffer, RequiresBitDepthIsMultipleOfEight) {
std::vector<std::vector<int32_t>> frame_to_write = {{0x7f001200, 0x7e003400},
{0x7f005600, 0x7e007800}};
const uint8_t kBitDepth = 23;
- const uint32_t kSamplesToTrimAtStart = 0;
- const uint32_t kSamplesToTrimAtEnd = 0;
const bool kBigEndian = false;
std::vector<uint8_t> output_buffer;
- EXPECT_FALSE(WritePcmFrameToBuffer(frame_to_write, kSamplesToTrimAtStart,
- kSamplesToTrimAtEnd, kBitDepth, kBigEndian,
+ EXPECT_FALSE(WritePcmFrameToBuffer(frame_to_write, kBitDepth, kBigEndian,
output_buffer)
.ok());
}
diff --git a/iamf/cli/tests/demixing_module_test.cc b/iamf/cli/tests/demixing_module_test.cc
index 9d1768b..503d258 100644
--- a/iamf/cli/tests/demixing_module_test.cc
+++ b/iamf/cli/tests/demixing_module_test.cc
@@ -48,10 +48,13 @@
namespace {
using ::absl_testing::IsOk;
+using ::absl_testing::IsOkAndHolds;
using enum ChannelLabel::Label;
-using testing::DoubleEq;
-using testing::DoubleNear;
-using testing::Pointwise;
+using ::testing::DoubleEq;
+using ::testing::DoubleNear;
+using ::testing::IsEmpty;
+using ::testing::Not;
+using ::testing::Pointwise;
constexpr DecodedUleb128 kAudioElementId = 137;
constexpr std::array<uint8_t, 12> kReconGainValues = {
@@ -263,7 +266,21 @@
EXPECT_TRUE(demixer->empty());
}
-TEST(DemixAudioSamples, OutputContainsOriginalAndDemixedSamples) {
+TEST(DemixOriginalAudioSamples, ReturnsErrorAfterCreateForReconstruction) {
+ absl::flat_hash_map<DecodedUleb128, AudioElementWithData> audio_elements;
+ InitAudioElementWithLabelsAndLayers(
+ {{kMonoSubstreamId, {kMono}}, {kL2SubstreamId, {kL2}}},
+ {ChannelAudioLayerConfig::kLayoutMono,
+ ChannelAudioLayerConfig::kLayoutStereo},
+ audio_elements);
+ auto demixing_module =
+ DemixingModule::CreateForReconstruction(audio_elements);
+ ASSERT_THAT(demixing_module, IsOk());
+
+ EXPECT_THAT(demixing_module->DemixOriginalAudioSamples({}), Not(IsOk()));
+}
+
+TEST(DemixDecodedAudioSamples, OutputContainsOriginalAndDemixedSamples) {
absl::flat_hash_map<DecodedUleb128, AudioElementWithData> audio_elements;
InitAudioElementWithLabelsAndLayers(
{{kMonoSubstreamId, {kMono}}, {kL2SubstreamId, {kL2}}},
@@ -290,23 +307,18 @@
auto demixing_module =
DemixingModule::CreateForReconstruction(audio_elements);
ASSERT_THAT(demixing_module, IsOk());
- IdLabeledFrameMap id_labeled_frame;
- IdLabeledFrameMap id_to_labeled_decoded_frame;
- EXPECT_THAT(demixing_module->DemixAudioSamples({}, decoded_audio_frames,
- id_labeled_frame,
- id_to_labeled_decoded_frame),
- IsOk());
+ const auto id_to_labeled_decoded_frame =
+ demixing_module->DemixDecodedAudioSamples(decoded_audio_frames);
+ ASSERT_THAT(id_to_labeled_decoded_frame, IsOk());
+ ASSERT_TRUE(id_to_labeled_decoded_frame->contains(kAudioElementId));
- const auto& labeled_frame = id_to_labeled_decoded_frame.at(kAudioElementId);
+ const auto& labeled_frame = id_to_labeled_decoded_frame->at(kAudioElementId);
EXPECT_TRUE(labeled_frame.label_to_samples.contains(kL2));
EXPECT_TRUE(labeled_frame.label_to_samples.contains(kMono));
EXPECT_TRUE(labeled_frame.label_to_samples.contains(kDemixedR2));
- // When being used for reconstruction the original audio frames are not
- // output.
- EXPECT_FALSE(id_labeled_frame.contains(kAudioElementId));
}
-TEST(DemixAudioSamples, OutputEchoesTimingInformation) {
+TEST(DemixDecodedAudioSamples, OutputEchoesTimingInformation) {
// These values are not very sensible, but as long as they are consistent
// between related frames it is OK.
const DecodedUleb128 kExpectedStartTimestamp = 99;
@@ -340,14 +352,13 @@
const auto demixing_module =
DemixingModule::CreateForReconstruction(audio_elements);
ASSERT_THAT(demixing_module, IsOk());
- IdLabeledFrameMap unused_id_labeled_frame;
- IdLabeledFrameMap id_to_labeled_decoded_frame;
- EXPECT_THAT(demixing_module->DemixAudioSamples({}, decoded_audio_frames,
- unused_id_labeled_frame,
- id_to_labeled_decoded_frame),
- IsOk());
- const auto& labeled_frame = id_to_labeled_decoded_frame.at(kAudioElementId);
+ const auto id_to_labeled_decoded_frame =
+ demixing_module->DemixDecodedAudioSamples(decoded_audio_frames);
+ ASSERT_THAT(id_to_labeled_decoded_frame, IsOk());
+ ASSERT_TRUE(id_to_labeled_decoded_frame->contains(kAudioElementId));
+
+ const auto& labeled_frame = id_to_labeled_decoded_frame->at(kAudioElementId);
EXPECT_EQ(labeled_frame.end_timestamp, kExpectedEndTimestamp);
EXPECT_EQ(labeled_frame.samples_to_trim_at_end,
kExpectedNumSamplesToTrimAtEnd);
@@ -355,7 +366,7 @@
kExpectedNumSamplesToTrimAtStart);
}
-TEST(DemixAudioSamples, OutputEchoesOriginalLabels) {
+TEST(DemixDecodedAudioSamples, OutputEchoesOriginalLabels) {
absl::flat_hash_map<DecodedUleb128, AudioElementWithData> audio_elements;
InitAudioElementWithLabelsAndLayers(
{{kMonoSubstreamId, {kMono}}, {kL2SubstreamId, {kL2}}},
@@ -384,14 +395,13 @@
ASSERT_THAT(demixing_module, IsOk());
IdLabeledFrameMap unused_id_labeled_frame;
- IdLabeledFrameMap id_to_labeled_decoded_frame;
- EXPECT_THAT(demixing_module->DemixAudioSamples({}, decoded_audio_frames,
- unused_id_labeled_frame,
- id_to_labeled_decoded_frame),
- IsOk());
+ const auto id_to_labeled_decoded_frame =
+ demixing_module->DemixDecodedAudioSamples(decoded_audio_frames);
+ ASSERT_THAT(id_to_labeled_decoded_frame, IsOk());
+ ASSERT_TRUE(id_to_labeled_decoded_frame->contains(kAudioElementId));
// Examine the demixed frame.
- const auto& labeled_frame = id_to_labeled_decoded_frame.at(kAudioElementId);
+ const auto& labeled_frame = id_to_labeled_decoded_frame->at(kAudioElementId);
constexpr std::array<int32_t, 3> kExpectedMonoSamples = {1, 2, 3};
constexpr std::array<int32_t, 3> kExpectedL2Samples = {9, 10, 11};
EXPECT_THAT(
@@ -402,7 +412,7 @@
Pointwise(InternalSampleMatchesIntegralSample(), kExpectedL2Samples));
}
-TEST(DemixAudioSamples, OutputHasReconstructedLayers) {
+TEST(DemixDecodedAudioSamples, OutputHasReconstructedLayers) {
absl::flat_hash_map<DecodedUleb128, AudioElementWithData> audio_elements;
InitAudioElementWithLabelsAndLayers(
@@ -431,21 +441,19 @@
DemixingModule::CreateForReconstruction(audio_elements);
ASSERT_THAT(demixing_module, IsOk());
- IdLabeledFrameMap unused_id_time_labeled_frame;
- IdLabeledFrameMap id_to_labeled_decoded_frame;
- EXPECT_THAT(demixing_module->DemixAudioSamples({}, decoded_audio_frames,
- unused_id_time_labeled_frame,
- id_to_labeled_decoded_frame),
- IsOk());
+ const auto id_to_labeled_decoded_frame =
+ demixing_module->DemixDecodedAudioSamples(decoded_audio_frames);
+ ASSERT_THAT(id_to_labeled_decoded_frame, IsOk());
+ ASSERT_TRUE(id_to_labeled_decoded_frame->contains(kAudioElementId));
// Examine the demixed frame.
- const auto& labeled_frame = id_to_labeled_decoded_frame.at(kAudioElementId);
+ const auto& labeled_frame = id_to_labeled_decoded_frame->at(kAudioElementId);
// D_R2 = M - (L2 - 6 dB) + 6 dB.
EXPECT_THAT(labeled_frame.label_to_samples.at(kDemixedR2),
Pointwise(InternalSampleMatchesIntegralSample(), {500}));
}
-TEST(DemixAudioSamples, OutputContainsReconGainAndLayerInfo) {
+TEST(DemixDecodedAudioSamples, OutputContainsReconGainAndLayerInfo) {
absl::flat_hash_map<DecodedUleb128, AudioElementWithData> audio_elements;
InitAudioElementWithLabelsAndLayers(
{{kMonoSubstreamId, {kMono}}, {kL2SubstreamId, {kL2}}},
@@ -479,14 +487,12 @@
const auto demixing_module =
DemixingModule::CreateForReconstruction(audio_elements);
ASSERT_THAT(demixing_module, IsOk());
- IdLabeledFrameMap id_labeled_frame;
- IdLabeledFrameMap id_to_labeled_decoded_frame;
- EXPECT_THAT(demixing_module->DemixAudioSamples({}, decoded_audio_frames,
- id_labeled_frame,
- id_to_labeled_decoded_frame),
- IsOk());
+ const auto id_to_labeled_decoded_frame =
+ demixing_module->DemixDecodedAudioSamples(decoded_audio_frames);
+ ASSERT_THAT(id_to_labeled_decoded_frame, IsOk());
+ ASSERT_TRUE(id_to_labeled_decoded_frame->contains(kAudioElementId));
- const auto& labeled_frame = id_to_labeled_decoded_frame.at(kAudioElementId);
+ const auto& labeled_frame = id_to_labeled_decoded_frame->at(kAudioElementId);
EXPECT_TRUE(labeled_frame.label_to_samples.contains(kL2));
EXPECT_TRUE(labeled_frame.label_to_samples.contains(kMono));
EXPECT_TRUE(labeled_frame.label_to_samples.contains(kDemixedR2));
@@ -995,19 +1001,18 @@
.label_to_samples[label] = expected_demixed_samples_as_internal_type;
}
- void TestDemixing(int expected_number_of_down_mixers) {
- IdLabeledFrameMap unused_id_to_labeled_frame, id_to_labeled_decoded_frame;
-
+ void TestLosslessDemixing(int expected_number_of_down_mixers) {
TestCreateDemixingModule(expected_number_of_down_mixers);
- EXPECT_THAT(demixing_module_->DemixAudioSamples(
- audio_frames_, decoded_audio_frames_,
- unused_id_to_labeled_frame, id_to_labeled_decoded_frame),
- IsOk());
+ const auto id_to_labeled_decoded_frame =
+ demixing_module_->DemixDecodedAudioSamples(decoded_audio_frames_);
+ ASSERT_THAT(id_to_labeled_decoded_frame, IsOk());
+ ASSERT_TRUE(id_to_labeled_decoded_frame->contains(kAudioElementId));
// Check that the demixed samples have the correct values.
const auto& actual_label_to_samples =
- id_to_labeled_decoded_frame[kAudioElementId].label_to_samples;
+ id_to_labeled_decoded_frame->at(kAudioElementId).label_to_samples;
+
const auto& expected_label_to_samples =
expected_id_to_labeled_decoded_frame_[kAudioElementId].label_to_samples;
EXPECT_EQ(actual_label_to_samples.size(), expected_label_to_samples.size());
@@ -1018,6 +1023,15 @@
EXPECT_THAT(samples, Pointwise(DoubleNear(kErrorTolerance),
expected_label_to_samples.at(label)));
}
+
+ // Also, since this is lossless, we expect demixing the original samples
+ // should give the same result.
+ const auto id_to_labeled_frame =
+ demixing_module_->DemixOriginalAudioSamples(audio_frames_);
+ ASSERT_THAT(id_to_labeled_frame, IsOk());
+ ASSERT_TRUE(id_to_labeled_frame->contains(kAudioElementId));
+ EXPECT_EQ(id_to_labeled_frame->at(kAudioElementId).label_to_samples,
+ actual_label_to_samples);
}
protected:
@@ -1027,22 +1041,22 @@
IdLabeledFrameMap expected_id_to_labeled_decoded_frame_;
}; // namespace
-TEST(DemixingModule, DemixingAudioSamplesSucceedsWithEmptyInputs) {
+TEST(DemixingModule, DemixingOriginalAudioSamplesSucceedsWithEmptyInputs) {
const auto demixing_module =
DemixingModule::CreateForDownMixingAndReconstruction({});
ASSERT_THAT(demixing_module, IsOk());
- // Call `DemixAudioSamples()`.
- IdLabeledFrameMap id_to_labeled_frame, id_to_labeled_decoded_frame;
- EXPECT_THAT(demixing_module->DemixAudioSamples(
- /*audio_frames=*/{},
- /*decoded_audio_frames=*/{}, id_to_labeled_frame,
- id_to_labeled_decoded_frame),
- IsOk());
+ EXPECT_THAT(demixing_module->DemixOriginalAudioSamples({}),
+ IsOkAndHolds(IsEmpty()));
+}
- // Expect empty outputs.
- EXPECT_TRUE(id_to_labeled_frame.empty());
- EXPECT_TRUE(id_to_labeled_decoded_frame.empty());
+TEST(DemixingModule, DemixingDecodedAudioSamplesSucceedsWithEmptyInputs) {
+ const auto demixing_module =
+ DemixingModule::CreateForDownMixingAndReconstruction({});
+ ASSERT_THAT(demixing_module, IsOk());
+
+ EXPECT_THAT(demixing_module->DemixDecodedAudioSamples({}),
+ IsOkAndHolds(IsEmpty()));
}
TEST_F(DemixingModuleTest, AmbisonicsHasNoDemixers) {
@@ -1053,7 +1067,7 @@
ConfigureLosslessAudioFrameAndDecodedAudioFrame({kA2}, {{1}});
ConfigureLosslessAudioFrameAndDecodedAudioFrame({kA3}, {{1}});
- TestDemixing(0);
+ TestLosslessDemixing(0);
}
TEST_F(DemixingModuleTest, S1ToS2Demixer) {
@@ -1069,11 +1083,11 @@
// D_R2 = M - (L2 - 6 dB) + 6 dB.
ConfiguredExpectedDemixingChannelFrame(kDemixedR2, {500, 1000});
- TestDemixing(1);
+ TestLosslessDemixing(1);
}
TEST_F(DemixingModuleTest,
- DemixAudioSamplesReturnsErrorIfAudioFrameIsMissingPcmSamples) {
+ DemixOriginalAudioSamplesReturnsErrorIfAudioFrameIsMissingPcmSamples) {
ConfigureAudioFrameMetadata({kL2, kR2});
ConfigureLosslessAudioFrameAndDecodedAudioFrame({kMono}, {{750}, {1500}});
ConfigureLosslessAudioFrameAndDecodedAudioFrame({kL2}, {{1000}, {2000}});
@@ -1082,11 +1096,8 @@
// Destroy the raw samples.
audio_frames_.back().pcm_samples = std::nullopt;
- EXPECT_FALSE(demixing_module_
- ->DemixAudioSamples(audio_frames_, decoded_audio_frames_,
- unused_id_to_labeled_frame,
- id_to_labeled_decoded_frame)
- .ok());
+ EXPECT_THAT(demixing_module_->DemixOriginalAudioSamples(audio_frames_),
+ Not(IsOk()));
}
TEST_F(DemixingModuleTest, S2ToS3Demixer) {
@@ -1108,7 +1119,7 @@
ConfiguredExpectedDemixingChannelFrame(kDemixedL3, {-1344, 993});
ConfiguredExpectedDemixingChannelFrame(kDemixedR3, {-1344, 993});
- TestDemixing(1);
+ TestLosslessDemixing(1);
}
TEST_F(DemixingModuleTest, S3ToS5AndTf2ToT2Demixers) {
@@ -1143,7 +1154,7 @@
ConfiguredExpectedDemixingChannelFrame(kDemixedLtf2, {-1165});
ConfiguredExpectedDemixingChannelFrame(kDemixedRtf2, {-165});
- TestDemixing(2);
+ TestLosslessDemixing(2);
}
TEST_F(DemixingModuleTest, S5ToS7Demixer) {
@@ -1176,7 +1187,7 @@
ConfiguredExpectedDemixingChannelFrame(kDemixedLrs7, {8000});
ConfiguredExpectedDemixingChannelFrame(kDemixedRrs7, {7000});
- TestDemixing(1);
+ TestLosslessDemixing(1);
}
TEST_F(DemixingModuleTest, T2ToT4Demixer) {
@@ -1205,7 +1216,7 @@
ConfiguredExpectedDemixingChannelFrame(kDemixedLtb4, {9000});
ConfiguredExpectedDemixingChannelFrame(kDemixedRtb4, {18000});
- TestDemixing(1);
+ TestLosslessDemixing(1);
}
} // namespace
diff --git a/iamf/cli/tests/iamf_encoder_test.cc b/iamf/cli/tests/iamf_encoder_test.cc
index 638c74d..45cf7ba 100644
--- a/iamf/cli/tests/iamf_encoder_test.cc
+++ b/iamf/cli/tests/iamf_encoder_test.cc
@@ -107,9 +107,7 @@
R"pb(
mix_presentation_id: 42
count_label: 0
- num_sub_mixes: 1
sub_mixes {
- num_audio_elements: 1
audio_elements {
audio_element_id: 300
rendering_config {
@@ -134,7 +132,6 @@
}
default_mix_gain: 0
}
- num_layouts: 1
layouts {
loudness_layout {
layout_type: LAYOUT_TYPE_LOUDSPEAKERS_SS_CONVENTION
diff --git a/iamf/cli/tests/obu_processor_test.cc b/iamf/cli/tests/obu_processor_test.cc
index 0982765..4ce2376 100644
--- a/iamf/cli/tests/obu_processor_test.cc
+++ b/iamf/cli/tests/obu_processor_test.cc
@@ -12,9 +12,11 @@
#include "iamf/cli/obu_processor.h"
+#include <array>
#include <cstddef>
#include <cstdint>
#include <filesystem>
+#include <iterator>
#include <list>
#include <memory>
#include <optional>
@@ -59,6 +61,7 @@
namespace {
using ::absl_testing::IsOk;
+using ::absl_testing::IsOkAndHolds;
using ::testing::IsNull;
using ::testing::Not;
using ::testing::NotNull;
@@ -76,6 +79,8 @@
constexpr DecodedUleb128 kSecondMixPresentationId = 4;
constexpr DecodedUleb128 kThirdMixPresentationId = 5;
constexpr DecodedUleb128 kCommonMixGainParameterId = 999;
+constexpr uint32_t kFrameSize = 1024;
+constexpr uint32_t kBitDepth = 16;
constexpr DecodedUleb128 kSampleRate = 48000;
constexpr DecodedUleb128 kCommonParameterRate = kSampleRate;
@@ -85,8 +90,8 @@
constexpr int64_t kBufferCapacity = 1024;
constexpr std::optional<uint8_t> kNoOutputFileBitDepthOverride = std::nullopt;
-constexpr uint8_t kOutputBitDepth24 = 24;
-constexpr uint8_t kOutputBitDepth32 = 32;
+constexpr std::array<uint8_t, 16> kArbitraryAudioFrame = {
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
std::vector<uint8_t> AddSequenceHeaderAndSerializeObusExpectOk(
const std::list<const ObuBase*>& input_ia_sequence_without_header) {
@@ -111,7 +116,7 @@
};
}
-TEST(ProcessDescriptorObus, InvalidWithoutIaSequenceHeader) {
+TEST(ProcessDescriptorObus, FailsWithEmptyBitstream) {
const std::vector<uint8_t> bitstream_without_ia_sequence_header =
SerializeObusExpectOk({});
IASequenceHeaderObu ia_sequence_header;
@@ -123,13 +128,16 @@
auto read_bit_buffer = MemoryBasedReadBitBuffer::CreateFromSpan(
kBufferCapacity,
absl::MakeConstSpan(bitstream_without_ia_sequence_header));
- bool insufficient_data = false;
+ bool insufficient_data;
EXPECT_FALSE(ObuProcessor::ProcessDescriptorObus(
/*is_exhaustive_and_exact=*/false, *read_bit_buffer,
ia_sequence_header, codec_config_obu,
audio_elements_with_data, mix_presentation_obus,
insufficient_data)
.ok());
+ // There's no data (and `is_exhaustive_and_exact` is false), so we need more
+ // data to proceed.
+ EXPECT_TRUE(insufficient_data);
}
TEST(ProcessDescriptorObus, CollectsCodecConfigsBeforeATemporalUnit) {
@@ -151,7 +159,7 @@
auto read_bit_buffer = MemoryBasedReadBitBuffer::CreateFromSpan(
kBufferCapacity, absl::MakeConstSpan(two_codec_configs_and_audio_frame));
- bool insufficient_data = false;
+ bool insufficient_data;
EXPECT_THAT(
ObuProcessor::ProcessDescriptorObus(
/*is_exhaustive_and_exact=*/false, *read_bit_buffer,
@@ -162,6 +170,10 @@
EXPECT_EQ(output_codec_config_obus.size(), 2);
EXPECT_TRUE(output_codec_config_obus.contains(kFirstCodecConfigId));
EXPECT_TRUE(output_codec_config_obus.contains(kSecondCodecConfigId));
+ // `insufficient_data` is false because we have successfully read all provided
+ // descriptor obus AND `is_exhaustive_and_exact` is true, meaning that the
+ // caller has indicated that there are no future Descriptor OBUs coming.
+ EXPECT_FALSE(insufficient_data);
}
TEST(ProcessDescriptorObus, CollectsCodecConfigsAtEndOfBitstream) {
@@ -181,13 +193,14 @@
auto read_bit_buffer = MemoryBasedReadBitBuffer::CreateFromSpan(
kBufferCapacity,
absl::MakeConstSpan(two_codec_configs_at_end_of_bitstream));
- bool insufficient_data = false;
+ bool insufficient_data;
EXPECT_THAT(
ObuProcessor::ProcessDescriptorObus(
/*is_exhaustive_and_exact=*/true, *read_bit_buffer,
ia_sequence_header, codec_config_obus, audio_elements_with_data,
mix_presentation_obus, insufficient_data),
IsOk());
+ // `is_exhaustive_and_exact` is true so it could not be a more-data situation.
EXPECT_FALSE(insufficient_data);
EXPECT_EQ(codec_config_obus.size(), 2);
@@ -214,13 +227,15 @@
kBufferCapacity,
absl::MakeConstSpan(two_codec_configs_at_end_of_bitstream));
auto start_position = read_bit_buffer->Tell();
- bool insufficient_data = false;
+ bool insufficient_data;
EXPECT_THAT(
ObuProcessor::ProcessDescriptorObus(
/*is_exhaustive_and_exact=*/false, *read_bit_buffer,
ia_sequence_header, codec_config_obus, audio_elements_with_data,
mix_presentation_obus, insufficient_data),
Not(IsOk()));
+ // `is_exhaustive_and_exact` is false so we won't know it's the end of the
+ // bitstream until we see a temporal unit. Need more data to know we're done.
EXPECT_TRUE(insufficient_data);
EXPECT_EQ(codec_config_obus.size(), 0);
EXPECT_EQ(read_bit_buffer->Tell(), start_position);
@@ -237,7 +252,7 @@
auto read_bit_buffer = MemoryBasedReadBitBuffer::CreateFromSpan(
kBufferCapacity, absl::MakeConstSpan(only_ia_sequence_header));
- bool insufficient_data = false;
+ bool insufficient_data;
EXPECT_THAT(
ObuProcessor::ProcessDescriptorObus(
/*is_exhaustive_and_exact=*/true, *read_bit_buffer,
@@ -249,6 +264,7 @@
ProfileVersion::kIamfSimpleProfile);
EXPECT_EQ(ia_sequence_header.GetAdditionalProfile(),
ProfileVersion::kIamfBaseProfile);
+ EXPECT_FALSE(insufficient_data);
}
TEST(ProcessDescriptorObus, DescriptorObusMustStartWithIaSequenceHeader) {
@@ -272,7 +288,7 @@
auto read_bit_buffer = MemoryBasedReadBitBuffer::CreateFromSpan(
kBufferCapacity,
absl::MakeConstSpan(ia_sequence_header_then_codec_config));
- bool insufficient_data = false;
+ bool insufficient_data;
EXPECT_THAT(ObuProcessor::ProcessDescriptorObus(
/*is_exhaustive_and_exact=*/true, *read_bit_buffer,
unused_ia_sequence_header, unused_codec_config_obus,
@@ -295,6 +311,9 @@
unused_audio_elements_with_data,
unused_mix_presentation_obus, insufficient_data)
.ok());
+ // `insufficient_data` is false as the error was due to an invalid ordering of
+ // OBUs, rather than not having enough data.
+ EXPECT_FALSE(insufficient_data);
}
TEST(ProcessDescriptorObus, SucceedsWithSuccessiveRedundantSequenceHeaders) {
@@ -311,7 +330,7 @@
auto read_bit_buffer = MemoryBasedReadBitBuffer::CreateFromSpan(
kBufferCapacity, absl::MakeConstSpan(bitstream));
- bool insufficient_data = false;
+ bool insufficient_data;
EXPECT_THAT(
ObuProcessor::ProcessDescriptorObus(
/*is_exhaustive_and_exact=*/true, *read_bit_buffer,
@@ -343,7 +362,7 @@
auto read_bit_buffer = MemoryBasedReadBitBuffer::CreateFromSpan(
kBufferCapacity, absl::MakeConstSpan(buffer));
- bool insufficient_data = false;
+ bool insufficient_data;
EXPECT_THAT(
ObuProcessor::ProcessDescriptorObus(
/*is_exhaustive_and_exact=*/true, *read_bit_buffer,
@@ -376,7 +395,7 @@
auto read_bit_buffer = MemoryBasedReadBitBuffer::CreateFromSpan(
kBufferCapacity,
absl::MakeConstSpan(ia_sequence_header_with_codec_configs));
- bool insufficient_data = false;
+ bool insufficient_data;
EXPECT_THAT(
ObuProcessor::ProcessDescriptorObus(
/*is_exhaustive_and_exact=*/true, *read_bit_buffer,
@@ -427,7 +446,7 @@
auto read_bit_buffer = MemoryBasedReadBitBuffer::CreateFromSpan(
kBufferCapacity,
absl::MakeConstSpan(zeroth_order_ambisonics_descriptor_obus));
- bool insufficient_data = false;
+ bool insufficient_data;
EXPECT_THAT(
ObuProcessor::ProcessDescriptorObus(
@@ -464,7 +483,7 @@
kBufferCapacity,
absl::MakeConstSpan(zeroth_order_ambisonics_descriptor_obus));
auto start_position = read_bit_buffer->Tell();
- bool insufficient_data = false;
+ bool insufficient_data;
EXPECT_THAT(
ObuProcessor::ProcessDescriptorObus(
@@ -473,6 +492,7 @@
mix_presentation_obus, insufficient_data),
Not(IsOk()));
+ // We've received a valid bitstream so far but not complete.
EXPECT_TRUE(insufficient_data);
EXPECT_EQ(codec_config_obus.size(), 0);
EXPECT_EQ(audio_elements_with_data.size(), 0);
@@ -502,7 +522,7 @@
auto read_bit_buffer = MemoryBasedReadBitBuffer::CreateFromSpan(
kBufferCapacity, absl::MakeConstSpan(bitstream));
auto start_position = read_bit_buffer->Tell();
- bool insufficient_data = false;
+ bool insufficient_data;
EXPECT_THAT(
ObuProcessor::ProcessDescriptorObus(
@@ -538,7 +558,7 @@
auto read_bit_buffer = MemoryBasedReadBitBuffer::CreateFromSpan(
kBufferCapacity, absl::MakeConstSpan(bitstream));
- bool insufficient_data = false;
+ bool insufficient_data;
EXPECT_THAT(
ObuProcessor::ProcessDescriptorObus(
@@ -586,7 +606,7 @@
auto read_bit_buffer = MemoryBasedReadBitBuffer::CreateFromSpan(
kBufferCapacity, absl::MakeConstSpan(bitstream));
auto start_position = read_bit_buffer->Tell();
- bool insufficient_data = false;
+ bool insufficient_data;
EXPECT_THAT(
ObuProcessor::ProcessDescriptorObus(
@@ -595,6 +615,7 @@
mix_presentation_obus, insufficient_data),
Not(IsOk()));
+ // We've received a valid bitstream so far but not complete.
EXPECT_TRUE(insufficient_data);
// Expect the reader position to be unchanged since we returned an error.
EXPECT_EQ(read_bit_buffer->Tell(), start_position);
@@ -617,7 +638,7 @@
auto read_bit_buffer = MemoryBasedReadBitBuffer::CreateFromSpan(
kBufferCapacity, absl::MakeConstSpan(bitstream));
auto start_position = read_bit_buffer->Tell();
- bool insufficient_data = false;
+ bool insufficient_data;
EXPECT_THAT(
ObuProcessor::ProcessDescriptorObus(
@@ -626,6 +647,7 @@
mix_presentation_obus, insufficient_data),
Not(IsOk()));
+ // We've received a valid bitstream so far but not complete.
EXPECT_TRUE(insufficient_data);
EXPECT_EQ(codec_config_obus.size(), 0);
EXPECT_EQ(audio_elements_with_data.size(), 0);
@@ -1261,6 +1283,496 @@
EXPECT_EQ(parameter_block_with_data->end_timestamp, kParameterBlockDuration);
}
+TEST(ProcessTemporalUnitObus,
+ ConsumesAllTemporalUnitsWithAnIncompleteHeaderAtEnd) {
+ AudioFrameObu audio_frame_obu(ObuHeader(), kFirstSubstreamId,
+ kArbitraryAudioFrame);
+
+ auto one_temporal_unit = SerializeObusExpectOk({&audio_frame_obu});
+
+ // Set up inputs with descriptors, one audio frame, and one incomplete header.
+ absl::flat_hash_map<DecodedUleb128, CodecConfigObu> codec_config_obus;
+ AddOpusCodecConfigWithId(kFirstCodecConfigId, codec_config_obus);
+ absl::flat_hash_map<DecodedUleb128, AudioElementWithData>
+ audio_elements_with_data;
+ AddAmbisonicsMonoAudioElementWithSubstreamIds(
+ kFirstAudioElementId, kFirstCodecConfigId, {kFirstSubstreamId},
+ codec_config_obus, audio_elements_with_data);
+ auto global_timing_module =
+ GlobalTimingModule::Create(audio_elements_with_data,
+ /*param_definitions=*/{});
+ ASSERT_THAT(global_timing_module, NotNull());
+
+ const absl::flat_hash_map<DecodedUleb128, const AudioElementWithData*>
+ substream_id_to_audio_element = {
+ {kFirstSubstreamId,
+ &audio_elements_with_data.at(kFirstAudioElementId)}};
+ ParametersManager parameters_manager(audio_elements_with_data);
+ ASSERT_THAT(parameters_manager.Initialize(), IsOk());
+ absl::flat_hash_map<DecodedUleb128, ParamDefinitionVariant> param_definitions;
+ // Add a single byte to the end of the temporal unit to represent an
+ // incomplete header (A header requires at least 2 bytes).
+ one_temporal_unit.push_back(0);
+ auto read_bit_buffer = MemoryBasedReadBitBuffer::CreateFromSpan(
+ kBufferCapacity, absl::MakeConstSpan(one_temporal_unit));
+
+ // Confirm that the first temporal unit is processed successfully.
+ bool continue_processing = true;
+ std::optional<AudioFrameWithData> audio_frame_with_data;
+ std::optional<ParameterBlockWithData> parameter_block_with_data;
+ std::optional<TemporalDelimiterObu> temporal_delimiter;
+ EXPECT_THAT(
+ ObuProcessor::ProcessTemporalUnitObu(
+ audio_elements_with_data, codec_config_obus,
+ substream_id_to_audio_element, param_definitions, parameters_manager,
+ *read_bit_buffer, *global_timing_module, audio_frame_with_data,
+ parameter_block_with_data, temporal_delimiter, continue_processing),
+ IsOk());
+ EXPECT_TRUE(audio_frame_with_data.has_value());
+
+ // Confirm that the second temporal unit it is incomplete.
+ auto start_position = read_bit_buffer->Tell();
+ EXPECT_THAT(
+ ObuProcessor::ProcessTemporalUnitObu(
+ audio_elements_with_data, codec_config_obus,
+ substream_id_to_audio_element, param_definitions, parameters_manager,
+ *read_bit_buffer, *global_timing_module, audio_frame_with_data,
+ parameter_block_with_data, temporal_delimiter, continue_processing),
+ IsOk());
+ EXPECT_FALSE(audio_frame_with_data.has_value());
+ EXPECT_FALSE(parameter_block_with_data.has_value());
+ EXPECT_FALSE(temporal_delimiter.has_value());
+ EXPECT_FALSE(continue_processing);
+ EXPECT_EQ(read_bit_buffer->Tell(), start_position);
+}
+
+TEST(ProcessTemporalUnitObus,
+ ConsumesAllTemporalUnitsWithAnIncompleteObuAtEnd) {
+ AudioFrameObu audio_frame_obu(ObuHeader(), kFirstSubstreamId,
+ kArbitraryAudioFrame);
+
+ auto ia_sequence = SerializeObusExpectOk({&audio_frame_obu});
+
+ // Set up inputs with descriptors, one audio frame, and one incomplete obu
+ absl::flat_hash_map<DecodedUleb128, CodecConfigObu> codec_config_obus;
+ AddOpusCodecConfigWithId(kFirstCodecConfigId, codec_config_obus);
+ absl::flat_hash_map<DecodedUleb128, AudioElementWithData>
+ audio_elements_with_data;
+ AddAmbisonicsMonoAudioElementWithSubstreamIds(
+ kFirstAudioElementId, kFirstCodecConfigId, {kFirstSubstreamId},
+ codec_config_obus, audio_elements_with_data);
+ auto global_timing_module =
+ GlobalTimingModule::Create(audio_elements_with_data,
+ /*param_definitions=*/{});
+ ASSERT_THAT(global_timing_module, NotNull());
+
+ const absl::flat_hash_map<DecodedUleb128, const AudioElementWithData*>
+ substream_id_to_audio_element = {
+ {kFirstSubstreamId,
+ &audio_elements_with_data.at(kFirstAudioElementId)}};
+ ParametersManager parameters_manager(audio_elements_with_data);
+ ASSERT_THAT(parameters_manager.Initialize(), IsOk());
+ absl::flat_hash_map<DecodedUleb128, ParamDefinitionVariant> param_definitions;
+ std::vector<uint8_t> extra_audio_frame_obu_header_bytes = {
+ kObuIaAudioFrameId0 << kObuTypeBitShift,
+ // `obu_size`. -> Non-zero size, but we have no bytes following.
+ 0x7f};
+ ia_sequence.insert(ia_sequence.end(),
+ extra_audio_frame_obu_header_bytes.begin(),
+ extra_audio_frame_obu_header_bytes.end());
+ auto read_bit_buffer = MemoryBasedReadBitBuffer::CreateFromSpan(
+ kBufferCapacity, absl::MakeConstSpan(ia_sequence));
+
+ // Confirm that the first temporal unit is processed successfully.
+ bool continue_processing = true;
+ std::optional<AudioFrameWithData> audio_frame_with_data;
+ std::optional<ParameterBlockWithData> parameter_block_with_data;
+ std::optional<TemporalDelimiterObu> temporal_delimiter;
+ EXPECT_THAT(
+ ObuProcessor::ProcessTemporalUnitObu(
+ audio_elements_with_data, codec_config_obus,
+ substream_id_to_audio_element, param_definitions, parameters_manager,
+ *read_bit_buffer, *global_timing_module, audio_frame_with_data,
+ parameter_block_with_data, temporal_delimiter, continue_processing),
+ IsOk());
+ EXPECT_TRUE(audio_frame_with_data.has_value());
+
+ // Confirm that the second temporal unit it is incomplete.
+ auto start_position = read_bit_buffer->Tell();
+ EXPECT_THAT(
+ ObuProcessor::ProcessTemporalUnitObu(
+ audio_elements_with_data, codec_config_obus,
+ substream_id_to_audio_element, param_definitions, parameters_manager,
+ *read_bit_buffer, *global_timing_module, audio_frame_with_data,
+ parameter_block_with_data, temporal_delimiter, continue_processing),
+ IsOk());
+ EXPECT_FALSE(audio_frame_with_data.has_value());
+ EXPECT_FALSE(parameter_block_with_data.has_value());
+ EXPECT_FALSE(temporal_delimiter.has_value());
+ EXPECT_FALSE(continue_processing);
+ EXPECT_EQ(read_bit_buffer->Tell(), start_position);
+}
+
+using OutputTemporalUnit = ObuProcessor::OutputTemporalUnit;
+
+TEST(ProcessTemporalUnit, ConsumesOneAudioFrameAsTemporalUnit) {
+ // Set up inputs with a single audio frame.
+ auto bitstream = InitAllDescriptorsForZerothOrderAmbisonics();
+ AudioFrameObu audio_frame_obu(ObuHeader(), kFirstSubstreamId,
+ kArbitraryAudioFrame);
+ auto temporal_unit_obus = SerializeObusExpectOk({&audio_frame_obu});
+ bitstream.insert(bitstream.end(), temporal_unit_obus.begin(),
+ temporal_unit_obus.end());
+ auto read_bit_buffer = MemoryBasedReadBitBuffer::CreateFromSpan(
+ kBufferCapacity, absl::MakeConstSpan(bitstream));
+ bool insufficient_data;
+ auto obu_processor =
+ ObuProcessor::Create(/*is_exhaustive_and_exact=*/false,
+ read_bit_buffer.get(), insufficient_data);
+ ASSERT_THAT(obu_processor, NotNull());
+ ASSERT_FALSE(insufficient_data);
+
+ // Call `ProcessTemporalUnit()` with `eos_is_end_of_sequence` set to true.
+ // This means that we can assume that the end of the stream implies the end of
+ // the temporal unit.
+ std::optional<OutputTemporalUnit> output_temporal_unit;
+ bool continue_processing = true;
+ EXPECT_THAT(obu_processor->ProcessTemporalUnit(
+ /*eos_is_end_of_sequence=*/true, output_temporal_unit,
+ continue_processing),
+ IsOk());
+
+ EXPECT_FALSE(continue_processing);
+ EXPECT_EQ(output_temporal_unit->output_audio_frames.size(), 1);
+}
+
+TEST(ProcessTemporalUnit, DoesNotConsumeOneAudioFrameAsTemporalUnit) {
+ // Set up inputs with a single audio frame.
+ auto bitstream = InitAllDescriptorsForZerothOrderAmbisonics();
+ AudioFrameObu audio_frame_obu(ObuHeader(), kFirstSubstreamId,
+ /*audio_frame=*/{2, 3, 4, 5, 6, 7, 8});
+ auto temporal_unit_obus = SerializeObusExpectOk({&audio_frame_obu});
+ bitstream.insert(bitstream.end(), temporal_unit_obus.begin(),
+ temporal_unit_obus.end());
+ auto read_bit_buffer = MemoryBasedReadBitBuffer::CreateFromSpan(
+ kBufferCapacity, absl::MakeConstSpan(bitstream));
+ bool insufficient_data;
+ auto obu_processor =
+ ObuProcessor::Create(/*is_exhaustive_and_exact=*/false,
+ read_bit_buffer.get(), insufficient_data);
+ ASSERT_THAT(obu_processor, NotNull());
+ ASSERT_FALSE(insufficient_data);
+
+ std::optional<OutputTemporalUnit> output_temporal_unit;
+ bool continue_processing = true;
+ EXPECT_THAT(obu_processor->ProcessTemporalUnit(
+ /*eos_is_end_of_sequence=*/false, output_temporal_unit,
+ continue_processing),
+ IsOk());
+
+ EXPECT_FALSE(continue_processing);
+ EXPECT_FALSE(output_temporal_unit.has_value());
+}
+
+TEST(ProcessTemporalUnit, ConsumesMultipleTemporalUnitsWithTemporalDelimiters) {
+ // Set up inputs with two audio frames and temporal delimiters.
+ auto bitstream = InitAllDescriptorsForZerothOrderAmbisonics();
+ auto temporal_delimiter_obu = TemporalDelimiterObu(ObuHeader());
+ std::vector<AudioFrameObu> audio_frame_obus;
+ audio_frame_obus.push_back(
+ AudioFrameObu(ObuHeader(), kFirstSubstreamId, kArbitraryAudioFrame));
+ audio_frame_obus.push_back(
+ AudioFrameObu(ObuHeader(), kFirstSubstreamId, kArbitraryAudioFrame));
+ const auto two_temporal_units_with_delimiter_obu =
+ SerializeObusExpectOk({&audio_frame_obus[0], &temporal_delimiter_obu,
+ &audio_frame_obus[1], &temporal_delimiter_obu});
+ bitstream.insert(bitstream.end(),
+ two_temporal_units_with_delimiter_obu.begin(),
+ two_temporal_units_with_delimiter_obu.end());
+ auto read_bit_buffer = MemoryBasedReadBitBuffer::CreateFromSpan(
+ kBufferCapacity, absl::MakeConstSpan(bitstream));
+ bool insufficient_data;
+ auto obu_processor =
+ ObuProcessor::Create(/*is_exhaustive_and_exact=*/false,
+ read_bit_buffer.get(), insufficient_data);
+ ASSERT_THAT(obu_processor, NotNull());
+ ASSERT_FALSE(insufficient_data);
+
+ std::optional<OutputTemporalUnit> output_temporal_unit;
+ bool continue_processing = true;
+ EXPECT_THAT(obu_processor->ProcessTemporalUnit(
+ /*eos_is_end_of_sequence=*/true, output_temporal_unit,
+ continue_processing),
+ IsOk());
+
+ // The first temporal unit is consumed; it should only contain the first
+ // audio frame.
+ EXPECT_TRUE(continue_processing);
+ EXPECT_EQ(output_temporal_unit->output_audio_frames.size(), 1);
+
+ output_temporal_unit.reset();
+ continue_processing = true;
+ EXPECT_THAT(obu_processor->ProcessTemporalUnit(
+ /*eos_is_end_of_sequence=*/true, output_temporal_unit,
+ continue_processing),
+ IsOk());
+ // Seeing a temporal delimiter at the end of the stream implies that the
+ // stream is incomplete.
+ EXPECT_TRUE(continue_processing);
+ EXPECT_EQ(output_temporal_unit->output_audio_frames.size(), 1);
+}
+
+TEST(ProcessTemporalUnit,
+ ConsumesMultipleTemporalUnitsWithoutTemporalDelimiters) {
+ // Set up inputs with two audio frames. Two audio frames are known to be in a
+ // separate temporal unit if they have the same substream ID. Their underlying
+ // timestamps are different.
+ auto bitstream = InitAllDescriptorsForZerothOrderAmbisonics();
+ std::vector<AudioFrameObu> audio_frame_obus;
+ audio_frame_obus.push_back(
+ AudioFrameObu(ObuHeader(), kFirstSubstreamId, kArbitraryAudioFrame));
+ audio_frame_obus.push_back(
+ AudioFrameObu(ObuHeader(), kFirstSubstreamId, kArbitraryAudioFrame));
+ const auto two_temporal_units =
+ SerializeObusExpectOk({&audio_frame_obus[0], &audio_frame_obus[1]});
+ bitstream.insert(bitstream.end(), two_temporal_units.begin(),
+ two_temporal_units.end());
+ auto read_bit_buffer = MemoryBasedReadBitBuffer::CreateFromSpan(
+ kBufferCapacity, absl::MakeConstSpan(bitstream));
+ bool insufficient_data;
+ auto obu_processor =
+ ObuProcessor::Create(/*is_exhaustive_and_exact=*/false,
+ read_bit_buffer.get(), insufficient_data);
+ ASSERT_THAT(obu_processor, NotNull());
+ ASSERT_FALSE(insufficient_data);
+
+ std::optional<OutputTemporalUnit> output_temporal_unit;
+ bool continue_processing = true;
+ EXPECT_THAT(obu_processor->ProcessTemporalUnit(
+ /*eos_is_end_of_sequence=*/true, output_temporal_unit,
+ continue_processing),
+ IsOk());
+
+ // The first temporal unit is consumed; it should only contain the first
+ // audio frame.
+ EXPECT_TRUE(continue_processing);
+ EXPECT_EQ(output_temporal_unit->output_audio_frames.size(), 1);
+
+ output_temporal_unit.reset();
+ continue_processing = true;
+ EXPECT_THAT(obu_processor->ProcessTemporalUnit(
+ /*eos_is_end_of_sequence=*/true, output_temporal_unit,
+ continue_processing),
+ IsOk());
+
+ EXPECT_FALSE(continue_processing);
+ EXPECT_EQ(output_temporal_unit->output_audio_frames.size(), 1);
+}
+
+TEST(ProcessTemporalUnit, ConsumesOnlyOneTemporalUnitFromTwoAudioFrames) {
+ // eos_is_end_of_sequence is false. Only one temporal unit is consumed because
+ // we don't know that the second temporal unit is finished.
+
+ // Set up inputs with two audio frames. Two audio
+ // frames are known to be in a separate temporal unit if they have the same
+ // substream ID. Their underlying timestamps are different.
+ auto bitstream = InitAllDescriptorsForZerothOrderAmbisonics();
+ std::vector<AudioFrameObu> audio_frame_obus;
+ audio_frame_obus.push_back(
+ AudioFrameObu(ObuHeader(), kFirstSubstreamId,
+ /*audio_frame=*/{2, 3, 4, 5, 6, 7, 8}));
+ audio_frame_obus.push_back(
+ AudioFrameObu(ObuHeader(), kFirstSubstreamId,
+ /*audio_frame=*/{2, 3, 4, 5, 6, 7, 8}));
+ const auto two_temporal_units =
+ SerializeObusExpectOk({&audio_frame_obus[0], &audio_frame_obus[1]});
+ bitstream.insert(bitstream.end(), two_temporal_units.begin(),
+ two_temporal_units.end());
+ auto read_bit_buffer = MemoryBasedReadBitBuffer::CreateFromSpan(
+ kBufferCapacity, absl::MakeConstSpan(bitstream));
+ bool insufficient_data;
+ auto obu_processor =
+ ObuProcessor::Create(/*is_exhaustive_and_exact=*/false,
+ read_bit_buffer.get(), insufficient_data);
+ ASSERT_THAT(obu_processor, NotNull());
+ ASSERT_FALSE(insufficient_data);
+
+ std::optional<OutputTemporalUnit> output_temporal_unit;
+ bool continue_processing = true;
+ EXPECT_THAT(obu_processor->ProcessTemporalUnit(
+ /*eos_is_end_of_sequence=*/false, output_temporal_unit,
+ continue_processing),
+ IsOk());
+
+ // The first temporal unit is consumed; it should only contain the first
+ // audio frame.
+ EXPECT_TRUE(continue_processing);
+ EXPECT_EQ(output_temporal_unit->output_audio_frames.size(), 1);
+
+ output_temporal_unit.reset();
+ continue_processing = true;
+ EXPECT_THAT(obu_processor->ProcessTemporalUnit(
+ /*eos_is_end_of_sequence=*/false, output_temporal_unit,
+ continue_processing),
+ IsOk());
+
+ EXPECT_FALSE(continue_processing);
+ EXPECT_FALSE(output_temporal_unit.has_value());
+}
+
+TEST(ProcessTemporalUnit,
+ ConsumesOnlyOneTemporalUnitFromTwoAudioFramesAndIncompleteObuAtEnd) {
+ // eos_is_end_of_sequence is false. Only one temporal unit is consumed because
+ // we don't know that the second temporal unit is finished.
+
+ // Set up inputs with two audio frames. Two audio
+ // frames are known to be in a separate temporal unit if they have the same
+ // substream ID. Their underlying timestamps are different.
+ auto bitstream = InitAllDescriptorsForZerothOrderAmbisonics();
+ std::vector<AudioFrameObu> audio_frame_obus;
+ audio_frame_obus.push_back(
+ AudioFrameObu(ObuHeader(), kFirstSubstreamId,
+ /*audio_frame=*/{2, 3, 4, 5, 6, 7, 8}));
+ audio_frame_obus.push_back(
+ AudioFrameObu(ObuHeader(), kFirstSubstreamId,
+ /*audio_frame=*/{2, 3, 4, 5, 6, 7, 8}));
+ auto two_temporal_units =
+ SerializeObusExpectOk({&audio_frame_obus[0], &audio_frame_obus[1]});
+ std::vector<uint8_t> extra_audio_frame_obu_header_bytes = {
+ kObuIaAudioFrameId0 << kObuTypeBitShift,
+ // `obu_size`. -> Non-zero size, but we have no bytes following.
+ 0x7f};
+ two_temporal_units.insert(two_temporal_units.end(),
+ extra_audio_frame_obu_header_bytes.begin(),
+ extra_audio_frame_obu_header_bytes.end());
+ bitstream.insert(bitstream.end(), two_temporal_units.begin(),
+ two_temporal_units.end());
+ auto read_bit_buffer = MemoryBasedReadBitBuffer::CreateFromSpan(
+ kBufferCapacity, absl::MakeConstSpan(bitstream));
+ bool insufficient_data;
+ auto obu_processor =
+ ObuProcessor::Create(/*is_exhaustive_and_exact=*/false,
+ read_bit_buffer.get(), insufficient_data);
+ ASSERT_THAT(obu_processor, NotNull());
+ ASSERT_FALSE(insufficient_data);
+
+ std::optional<OutputTemporalUnit> output_temporal_unit;
+ bool continue_processing = false;
+ EXPECT_THAT(obu_processor->ProcessTemporalUnit(
+ /*eos_is_end_of_sequence=*/false, output_temporal_unit,
+ continue_processing),
+ IsOk());
+
+ // The first temporal unit is consumed; it should only contain the first
+ // audio frame.
+ EXPECT_TRUE(continue_processing);
+ EXPECT_EQ(output_temporal_unit->output_audio_frames.size(), 1);
+
+ output_temporal_unit.reset();
+ continue_processing = true;
+ EXPECT_THAT(obu_processor->ProcessTemporalUnit(
+ /*eos_is_end_of_sequence=*/false, output_temporal_unit,
+ continue_processing),
+ IsOk());
+
+ // The second temporal unit is not consumed since we don't know that it is
+ // complete.
+ EXPECT_FALSE(continue_processing);
+ EXPECT_FALSE(output_temporal_unit.has_value());
+}
+
+TEST(ProcessTemporalUnit, ConsumesMultipleTemporalUnitsChunkedArbitrarily) {
+ // Set up inputs with two audio frames. Two audio frames are known to be in a
+ // separate temporal unit if they have the same substream ID. Their underlying
+ // timestamps are different.
+ auto bitstream = InitAllDescriptorsForZerothOrderAmbisonics();
+ auto read_bit_buffer = StreamBasedReadBitBuffer::Create(kBufferCapacity);
+ // Push descriptors.
+ ASSERT_THAT(read_bit_buffer->PushBytes(bitstream), IsOk());
+ bool insufficient_data;
+ auto obu_processor =
+ ObuProcessor::Create(/*is_exhaustive_and_exact=*/true,
+ read_bit_buffer.get(), insufficient_data);
+ ASSERT_THAT(obu_processor, NotNull());
+ ASSERT_FALSE(insufficient_data);
+
+ std::vector<AudioFrameObu> audio_frame_obus;
+ audio_frame_obus.push_back(
+ AudioFrameObu(ObuHeader(), kFirstSubstreamId,
+ /*audio_frame=*/{2, 3, 4, 5, 6, 7, 8}));
+ audio_frame_obus.push_back(
+ AudioFrameObu(ObuHeader(), kFirstSubstreamId,
+ /*audio_frame=*/{2, 3, 4, 5, 6, 7, 8}));
+ const auto two_temporal_units =
+ SerializeObusExpectOk({&audio_frame_obus[0], &audio_frame_obus[1]});
+
+ // Split the temporal units into three chunks.
+ const int64_t chunk_size = two_temporal_units.size() / 3;
+ std::vector<uint8_t> chunk_1(two_temporal_units.begin(),
+ two_temporal_units.begin() + chunk_size);
+ std::vector<uint8_t> chunk_2(two_temporal_units.begin() + chunk_size,
+ two_temporal_units.begin() + 2 * chunk_size);
+ std::vector<uint8_t> chunk_3(two_temporal_units.begin() + 2 * chunk_size,
+ two_temporal_units.end());
+
+ // Chunk 1.
+ ASSERT_THAT(read_bit_buffer->PushBytes(chunk_1), IsOk());
+ std::optional<OutputTemporalUnit> output_temporal_unit;
+ bool continue_processing = true;
+ EXPECT_THAT(obu_processor->ProcessTemporalUnit(
+ /*eos_is_end_of_sequence=*/false, output_temporal_unit,
+ continue_processing),
+ IsOk());
+
+ // Chunk 1 is not enough to finish reading the first audio frame, so the
+ // first temporal unit is not finished.
+ EXPECT_FALSE(continue_processing);
+ EXPECT_FALSE(output_temporal_unit.has_value());
+
+ // Chunk 2.
+ ASSERT_THAT(read_bit_buffer->PushBytes(chunk_2), IsOk());
+ continue_processing = true;
+ EXPECT_THAT(obu_processor->ProcessTemporalUnit(
+ /*eos_is_end_of_sequence=*/false, output_temporal_unit,
+ continue_processing),
+ IsOk());
+
+ // Chunk 2 is enough to finish reading the first audio frame, but not the
+ // second. Since we haven't finished reading the second audio frame, we cannot
+ // know that the first temporal unit is complete. Therefore we still do not
+ // have a temporal unit.
+ EXPECT_FALSE(continue_processing);
+ EXPECT_FALSE(output_temporal_unit.has_value());
+
+ // Chunk 3.
+ ASSERT_THAT(read_bit_buffer->PushBytes(chunk_3), IsOk());
+ continue_processing = true;
+ EXPECT_THAT(obu_processor->ProcessTemporalUnit(
+ /*eos_is_end_of_sequence=*/false, output_temporal_unit,
+ continue_processing),
+ IsOk());
+
+ // Chunk 3 is enough to finish reading the second audio frame, so the first
+ // temporal unit is now complete. But we don't know that the second temporal
+ // unit is complete since more data could be coming behind it.
+ EXPECT_TRUE(continue_processing);
+ EXPECT_EQ(output_temporal_unit->output_audio_frames.size(), 1);
+
+ // To get the second temporal unit, we make one final call with
+ // `eos_is_end_of_sequence` set to true. At this point, the bitstream is
+ // exhausted, but we can get the second temporal unit that we previously
+ // processed since we now know that the sequence is complete.
+ continue_processing = true;
+ output_temporal_unit.reset();
+ EXPECT_THAT(obu_processor->ProcessTemporalUnit(
+ /*eos_is_end_of_sequence=*/true, output_temporal_unit,
+ continue_processing),
+ IsOk());
+
+ EXPECT_FALSE(continue_processing);
+ EXPECT_EQ(output_temporal_unit->output_audio_frames.size(), 1);
+}
+
// TODO(b/377772983): Test rejecting processing temporal units with mismatching
// durations from parameter blocks and audio frames.
// TODO(b/377772983): Test rejecting processing temporal units where the
@@ -1277,7 +1789,7 @@
TEST(CollectObusFromIaSequence, ConsumesIaSequenceAndCollectsAllObus) {
auto bitstream = InitAllDescriptorsForZerothOrderAmbisonics();
AudioFrameObu audio_frame_obu(ObuHeader(), kFirstSubstreamId,
- /*audio_frame=*/{2, 3, 4, 5, 6, 7, 8});
+ kArbitraryAudioFrame);
const auto temporal_unit_obus = SerializeObusExpectOk({&audio_frame_obu});
bitstream.insert(bitstream.end(), temporal_unit_obus.begin(),
temporal_unit_obus.end());
@@ -1318,7 +1830,7 @@
SerializeObusExpectOk({&input_non_redundant_ia_sequence_header});
auto non_trivial_ia_sequence = InitAllDescriptorsForZerothOrderAmbisonics();
AudioFrameObu audio_frame_obu(ObuHeader(), kFirstSubstreamId,
- /*audio_frame=*/{2, 3, 4, 5, 6, 7, 8});
+ kArbitraryAudioFrame);
const auto temporal_unit_obus = SerializeObusExpectOk({&audio_frame_obu});
non_trivial_ia_sequence.insert(non_trivial_ia_sequence.end(),
temporal_unit_obus.begin(),
@@ -1366,7 +1878,7 @@
TEST(CollectObusFromIaSequence, ConsumesUpToNextIaSequence) {
auto bitstream = InitAllDescriptorsForZerothOrderAmbisonics();
AudioFrameObu audio_frame_obu(ObuHeader(), kFirstSubstreamId,
- /*audio_frame=*/{2, 3, 4, 5, 6, 7, 8});
+ kArbitraryAudioFrame);
const auto temporal_unit_obus = SerializeObusExpectOk({&audio_frame_obu});
bitstream.insert(bitstream.end(), temporal_unit_obus.begin(),
temporal_unit_obus.end());
@@ -1398,11 +1910,11 @@
EXPECT_EQ(read_bit_buffer->Tell(), first_ia_sequence_size * 8);
}
-TEST(NonStatic, CreateSucceeds) {
+TEST(Create, Succeeds) {
auto bitstream = InitAllDescriptorsForZerothOrderAmbisonics();
auto read_bit_buffer = MemoryBasedReadBitBuffer::CreateFromSpan(
kBufferCapacity, absl::MakeConstSpan(bitstream));
- bool insufficient_data = false;
+ bool insufficient_data;
auto obu_processor =
ObuProcessor::Create(/*is_exhaustive_and_exact=*/true,
@@ -1415,8 +1927,24 @@
EXPECT_EQ(obu_processor->mix_presentations_.size(), 1);
}
-TEST(NonStatic, CreateFailsOnNullReadBitBuffer) {
- bool insufficient_data = false;
+TEST(Create, SucceedsForTrivialIaSequence) {
+ const IASequenceHeaderObu kIaSequenceHeader(
+ ObuHeader(), IASequenceHeaderObu::kIaCode,
+ ProfileVersion::kIamfSimpleProfile, ProfileVersion::kIamfBaseProfile);
+ auto buffer = SerializeObusExpectOk({&kIaSequenceHeader});
+ auto read_bit_buffer = MemoryBasedReadBitBuffer::CreateFromSpan(
+ kBufferCapacity, absl::MakeConstSpan(buffer));
+ bool insufficient_data;
+ auto obu_processor =
+ ObuProcessor::Create(/*is_exhaustive_and_exact=*/true,
+ read_bit_buffer.get(), insufficient_data);
+
+ EXPECT_THAT(obu_processor, NotNull());
+ EXPECT_FALSE(insufficient_data);
+}
+
+TEST(Create, FailsOnNullReadBitBuffer) {
+ bool insufficient_data;
auto obu_processor = ObuProcessor::Create(/*is_exhaustive_and_exact=*/false,
nullptr, insufficient_data);
@@ -1425,31 +1953,150 @@
EXPECT_FALSE(insufficient_data);
}
-TEST(NonStatic, CreateFailsOnInsufficientData) {
+TEST(Create, FailsOnInsufficientData) {
auto bitstream = InitAllDescriptorsForZerothOrderAmbisonics();
auto read_bit_buffer = MemoryBasedReadBitBuffer::CreateFromSpan(
kBufferCapacity, absl::MakeConstSpan(bitstream));
- bool insufficient_data = true;
+ bool insufficient_data;
auto obu_processor =
ObuProcessor::Create(/*is_exhaustive_and_exact=*/false,
read_bit_buffer.get(), insufficient_data);
EXPECT_THAT(obu_processor, IsNull());
+ // We've received a valid bitstream so far but not complete.
EXPECT_TRUE(insufficient_data);
}
+TEST(GetOutputSampleRate, ReturnsSampleRateBasedOnCodecConfigObu) {
+ const IASequenceHeaderObu kIaSequenceHeader(
+ ObuHeader(), IASequenceHeaderObu::kIaCode,
+ ProfileVersion::kIamfSimpleProfile, ProfileVersion::kIamfBaseProfile);
+ absl::flat_hash_map<DecodedUleb128, CodecConfigObu> codec_config_obus;
+ AddLpcmCodecConfigWithIdAndSampleRate(kFirstCodecConfigId, kSampleRate,
+ codec_config_obus);
+ const auto buffer = SerializeObusExpectOk(
+ {&kIaSequenceHeader, &codec_config_obus.at(kFirstCodecConfigId)});
+ auto read_bit_buffer = MemoryBasedReadBitBuffer::CreateFromSpan(
+ kBufferCapacity, absl::MakeConstSpan(buffer));
+ bool insufficient_data;
+ auto obu_processor =
+ ObuProcessor::Create(/*is_exhaustive_and_exact=*/true,
+ read_bit_buffer.get(), insufficient_data);
+ ASSERT_THAT(obu_processor, NotNull());
+
+ EXPECT_THAT(obu_processor->GetOutputSampleRate(), IsOkAndHolds(kSampleRate));
+}
+
+TEST(GetOutputSampleRate, FailsForTrivialIaSequence) {
+ const IASequenceHeaderObu kIaSequenceHeader(
+ ObuHeader(), IASequenceHeaderObu::kIaCode,
+ ProfileVersion::kIamfSimpleProfile, ProfileVersion::kIamfBaseProfile);
+ const auto buffer = SerializeObusExpectOk({&kIaSequenceHeader});
+ auto read_bit_buffer = MemoryBasedReadBitBuffer::CreateFromSpan(
+ kBufferCapacity, absl::MakeConstSpan(buffer));
+ bool insufficient_data;
+ auto obu_processor =
+ ObuProcessor::Create(/*is_exhaustive_and_exact=*/true,
+ read_bit_buffer.get(), insufficient_data);
+ ASSERT_THAT(obu_processor, NotNull());
+
+ EXPECT_THAT(obu_processor->GetOutputSampleRate(), Not(IsOk()));
+}
+
+TEST(GetOutputSampleRate, FailsForMultipleCodecConfigObus) {
+ const IASequenceHeaderObu kIaSequenceHeader(
+ ObuHeader(), IASequenceHeaderObu::kIaCode,
+ ProfileVersion::kIamfSimpleProfile, ProfileVersion::kIamfBaseProfile);
+ absl::flat_hash_map<DecodedUleb128, CodecConfigObu> codec_config_obus;
+ AddLpcmCodecConfigWithIdAndSampleRate(kFirstCodecConfigId, kSampleRate,
+ codec_config_obus);
+ AddLpcmCodecConfigWithIdAndSampleRate(kSecondCodecConfigId, kSampleRate,
+ codec_config_obus);
+ const auto buffer = SerializeObusExpectOk(
+ {&kIaSequenceHeader, &codec_config_obus.at(kFirstCodecConfigId),
+ &codec_config_obus.at(kSecondCodecConfigId)});
+ auto read_bit_buffer = MemoryBasedReadBitBuffer::CreateFromSpan(
+ kBufferCapacity, absl::MakeConstSpan(buffer));
+ bool insufficient_data;
+ auto obu_processor =
+ ObuProcessor::Create(/*is_exhaustive_and_exact=*/true,
+ read_bit_buffer.get(), insufficient_data);
+ ASSERT_THAT(obu_processor, NotNull());
+
+ EXPECT_THAT(obu_processor->GetOutputSampleRate(), Not(IsOk()));
+}
+
+TEST(GetOutputFrameSize, ReturnsSampleRateBasedOnCodecConfigObu) {
+ const IASequenceHeaderObu kIaSequenceHeader(
+ ObuHeader(), IASequenceHeaderObu::kIaCode,
+ ProfileVersion::kIamfSimpleProfile, ProfileVersion::kIamfBaseProfile);
+ absl::flat_hash_map<DecodedUleb128, CodecConfigObu> codec_config_obus;
+ AddLpcmCodecConfig(kFirstCodecConfigId, kFrameSize, kBitDepth, kSampleRate,
+ codec_config_obus);
+ const auto buffer = SerializeObusExpectOk(
+ {&kIaSequenceHeader, &codec_config_obus.at(kFirstCodecConfigId)});
+ auto read_bit_buffer = MemoryBasedReadBitBuffer::CreateFromSpan(
+ kBufferCapacity, absl::MakeConstSpan(buffer));
+ bool insufficient_data;
+ auto obu_processor =
+ ObuProcessor::Create(/*is_exhaustive_and_exact=*/true,
+ read_bit_buffer.get(), insufficient_data);
+ ASSERT_THAT(obu_processor, NotNull());
+
+ EXPECT_THAT(obu_processor->GetOutputSampleRate(), IsOkAndHolds(kSampleRate));
+}
+
+TEST(GetOutputFrameSize, FailsForTrivialIaSequence) {
+ const IASequenceHeaderObu kIaSequenceHeader(
+ ObuHeader(), IASequenceHeaderObu::kIaCode,
+ ProfileVersion::kIamfSimpleProfile, ProfileVersion::kIamfBaseProfile);
+ const auto buffer = SerializeObusExpectOk({&kIaSequenceHeader});
+ auto read_bit_buffer = MemoryBasedReadBitBuffer::CreateFromSpan(
+ kBufferCapacity, absl::MakeConstSpan(buffer));
+ bool insufficient_data;
+ auto obu_processor =
+ ObuProcessor::Create(/*is_exhaustive_and_exact=*/true,
+ read_bit_buffer.get(), insufficient_data);
+ ASSERT_THAT(obu_processor, NotNull());
+
+ EXPECT_THAT(obu_processor->GetOutputFrameSize(), Not(IsOk()));
+}
+
+TEST(GetOutputFrameSize, FailsForMultipleCodecConfigObus) {
+ const IASequenceHeaderObu kIaSequenceHeader(
+ ObuHeader(), IASequenceHeaderObu::kIaCode,
+ ProfileVersion::kIamfSimpleProfile, ProfileVersion::kIamfBaseProfile);
+ absl::flat_hash_map<DecodedUleb128, CodecConfigObu> codec_config_obus;
+ AddLpcmCodecConfigWithIdAndSampleRate(kFirstCodecConfigId, kSampleRate,
+ codec_config_obus);
+ AddLpcmCodecConfigWithIdAndSampleRate(kSecondCodecConfigId, kSampleRate,
+ codec_config_obus);
+ const auto buffer = SerializeObusExpectOk(
+ {&kIaSequenceHeader, &codec_config_obus.at(kFirstCodecConfigId),
+ &codec_config_obus.at(kSecondCodecConfigId)});
+ auto read_bit_buffer = MemoryBasedReadBitBuffer::CreateFromSpan(
+ kBufferCapacity, absl::MakeConstSpan(buffer));
+ bool insufficient_data;
+ auto obu_processor =
+ ObuProcessor::Create(/*is_exhaustive_and_exact=*/true,
+ read_bit_buffer.get(), insufficient_data);
+ ASSERT_THAT(obu_processor, NotNull());
+
+ EXPECT_THAT(obu_processor->GetOutputFrameSize(), Not(IsOk()));
+}
+
TEST(NonStatic, ProcessTemporalUnitObu) {
auto bitstream = InitAllDescriptorsForZerothOrderAmbisonics();
AudioFrameObu audio_frame_obu(ObuHeader(), kFirstSubstreamId,
- /*audio_frame=*/{2, 3, 4, 5, 6, 7, 8});
+ kArbitraryAudioFrame);
const auto temporal_unit_obus = SerializeObusExpectOk({&audio_frame_obu});
bitstream.insert(bitstream.end(), temporal_unit_obus.begin(),
temporal_unit_obus.end());
auto read_bit_buffer = MemoryBasedReadBitBuffer::CreateFromSpan(
kBufferCapacity, absl::MakeConstSpan(bitstream));
- bool insufficient_data = false;
+ bool insufficient_data;
auto obu_processor =
ObuProcessor::Create(/*is_exhaustive_and_exact=*/false,
@@ -1481,14 +2128,15 @@
const std::vector<uint8_t>& bitstream_of_descriptors) {
auto read_bit_buffer = MemoryBasedReadBitBuffer::CreateFromSpan(
kBufferCapacity, absl::MakeConstSpan(bitstream_of_descriptors));
- bool insufficient_data = false;
+ bool insufficient_data;
const std::string output_filename_string(output_filename);
+ Layout unused_output_layout;
auto obu_processor = ObuProcessor::CreateForRendering(
kStereoLayout,
CreateAllWavWriters(output_filename_string, write_wav_header),
/*is_exhaustive_and_exact=*/true, read_bit_buffer.get(),
- insufficient_data);
+ unused_output_layout, insufficient_data);
ASSERT_THAT(obu_processor, NotNull());
ASSERT_FALSE(insufficient_data);
absl::Span<const std::vector<int32_t>> output_rendered_pcm_samples;
@@ -1722,12 +2370,13 @@
auto read_bit_buffer = MemoryBasedReadBitBuffer::CreateFromSpan(
kBufferCapacity, absl::MakeConstSpan(bitstream));
- bool insufficient_data = false;
+ Layout unused_output_layout;
+ bool insufficient_data;
auto obu_processor = ObuProcessor::CreateForRendering(
kStereoLayout,
RenderingMixPresentationFinalizer::ProduceNoSampleProcessors,
/*is_exhaustive_and_exact=*/true, read_bit_buffer.get(),
- insufficient_data);
+ unused_output_layout, insufficient_data);
ASSERT_THAT(obu_processor, NotNull());
ASSERT_FALSE(insufficient_data);
absl::Span<const std::vector<int32_t>> output_rendered_pcm_samples;
@@ -1832,14 +2481,15 @@
&mix_presentation_obus.front()});
auto read_bit_buffer = MemoryBasedReadBitBuffer::CreateFromSpan(
kBufferCapacity, absl::MakeConstSpan(bitstream));
- bool insufficient_data = false;
+ Layout unused_output_layout;
+ bool insufficient_data;
const std::string output_filename_string(output_filename);
auto obu_processor = ObuProcessor::CreateForRendering(
kStereoLayout,
CreateAllWavWriters(output_filename_string, kWriteWavHeader),
/*is_exhaustive_and_exact=*/true, read_bit_buffer.get(),
- insufficient_data);
+ unused_output_layout, insufficient_data);
ASSERT_THAT(obu_processor, NotNull());
ASSERT_FALSE(insufficient_data);
@@ -1993,12 +2643,13 @@
// Expect that the `ObuProcessor` rejects the rendering request.
auto read_bit_buffer = MemoryBasedReadBitBuffer::CreateFromSpan(
kBufferCapacity, absl::MakeConstSpan(bitstream));
- bool insufficient_data = false;
+ Layout unused_output_layout;
+ bool insufficient_data;
auto obu_processor = ObuProcessor::CreateForRendering(
kStereoLayout,
RenderingMixPresentationFinalizer::ProduceNoSampleProcessors,
/*is_exhaustive_and_exact=*/true, read_bit_buffer.get(),
- insufficient_data);
+ unused_output_layout, insufficient_data);
EXPECT_FALSE(insufficient_data);
EXPECT_THAT(obu_processor, IsNull());
}
@@ -2106,7 +2757,7 @@
&mix_presentation_obus.front()});
auto read_bit_buffer = MemoryBasedReadBitBuffer::CreateFromSpan(
kBufferCapacity, absl::MakeConstSpan(bitstream));
- bool insufficient_data = false;
+ bool insufficient_data;
// We expect arguments to be forwarded from the OBUs to the sample processor
// factory.
@@ -2130,12 +2781,211 @@
RenderingMixPresentationFinalizer::SampleProcessorFactory
sample_processor_factory = mock_sample_processor_factory.AsStdFunction();
+ Layout unused_output_layout;
EXPECT_THAT(ObuProcessor::CreateForRendering(
kStereoLayout, sample_processor_factory,
/*is_exhaustive_and_exact=*/true, read_bit_buffer.get(),
- insufficient_data),
+ unused_output_layout, insufficient_data),
NotNull());
}
+using testing::_;
+
+constexpr Layout k5_1_Layout = {
+ .layout_type = Layout::kLayoutTypeLoudspeakersSsConvention,
+ .specific_layout = LoudspeakersSsConventionLayout{
+ .sound_system = LoudspeakersSsConventionLayout::kSoundSystemB_0_5_0}};
+
+TEST(CreateForRendering, ForwardsChosenLayoutToSampleProcessorFactory) {
+ absl::flat_hash_map<DecodedUleb128, CodecConfigObu> codec_config_obus;
+ AddLpcmCodecConfigWithIdAndSampleRate(kFirstCodecConfigId, kSampleRate,
+ codec_config_obus);
+ absl::flat_hash_map<DecodedUleb128, AudioElementWithData>
+ audio_elements_with_data;
+ AddAmbisonicsMonoAudioElementWithSubstreamIds(
+ kFirstAudioElementId, kFirstCodecConfigId,
+ {kFirstSubstreamId, kSecondSubstreamId, kThirdSubstreamId,
+ kFourthSubstreamId},
+ codec_config_obus, audio_elements_with_data);
+ std::list<MixPresentationObu> mix_presentation_obus;
+ std::vector<LoudspeakersSsConventionLayout::SoundSystem>
+ sound_system_layouts = {
+ LoudspeakersSsConventionLayout::kSoundSystemA_0_2_0,
+ LoudspeakersSsConventionLayout::kSoundSystemB_0_5_0};
+ AddMixPresentationObuWithConfigurableLayouts(
+ kFirstMixPresentationId, {kFirstAudioElementId},
+ kCommonMixGainParameterId, kCommonParameterRate, sound_system_layouts,
+ mix_presentation_obus);
+
+ const std::list<AudioFrameWithData> empty_audio_frames_with_data = {};
+ const std::list<ParameterBlockWithData> empty_parameter_blocks_with_data = {};
+
+ const auto bitstream = AddSequenceHeaderAndSerializeObusExpectOk(
+ {&codec_config_obus.at(kFirstCodecConfigId),
+ &audio_elements_with_data.at(kFirstAudioElementId).obu,
+ &mix_presentation_obus.front()});
+ auto read_bit_buffer = MemoryBasedReadBitBuffer::CreateFromSpan(
+ kBufferCapacity, absl::MakeConstSpan(bitstream));
+ bool insufficient_data;
+
+ // We expect to use the second layout, since this is the only one that matches
+ // the desired layout.
+ constexpr int kSubmixIndex = 0;
+ constexpr int kLayoutIndex = 1;
+ const auto& forwarded_layout =
+ mix_presentation_obus.front().sub_mixes_[0].layouts[1].loudness_layout;
+
+ MockSampleProcessorFactory mock_sample_processor_factory;
+ EXPECT_CALL(mock_sample_processor_factory,
+ Call(kFirstMixPresentationId, kSubmixIndex, kLayoutIndex,
+ forwarded_layout, /*num_channels=*/6, _, _, _));
+ RenderingMixPresentationFinalizer::SampleProcessorFactory
+ sample_processor_factory = mock_sample_processor_factory.AsStdFunction();
+
+ Layout output_layout;
+ EXPECT_THAT(ObuProcessor::CreateForRendering(
+ k5_1_Layout, sample_processor_factory,
+ /*is_exhaustive_and_exact=*/true, read_bit_buffer.get(),
+ output_layout, insufficient_data),
+ NotNull());
+ EXPECT_EQ(output_layout, k5_1_Layout);
+}
+
+TEST(CreateForRendering, ForwardsDefaultLayoutToSampleProcessorFactory) {
+ absl::flat_hash_map<DecodedUleb128, CodecConfigObu> codec_config_obus;
+ AddLpcmCodecConfigWithIdAndSampleRate(kFirstCodecConfigId, kSampleRate,
+ codec_config_obus);
+ absl::flat_hash_map<DecodedUleb128, AudioElementWithData>
+ audio_elements_with_data;
+ AddAmbisonicsMonoAudioElementWithSubstreamIds(
+ kFirstAudioElementId, kFirstCodecConfigId,
+ {kFirstSubstreamId, kSecondSubstreamId, kThirdSubstreamId,
+ kFourthSubstreamId},
+ codec_config_obus, audio_elements_with_data);
+ std::list<MixPresentationObu> mix_presentation_obus;
+ std::vector<LoudspeakersSsConventionLayout::SoundSystem>
+ sound_system_layouts = {
+ LoudspeakersSsConventionLayout::kSoundSystemA_0_2_0,
+ LoudspeakersSsConventionLayout::kSoundSystemJ_4_7_0};
+ AddMixPresentationObuWithConfigurableLayouts(
+ kFirstMixPresentationId, {kFirstAudioElementId},
+ kCommonMixGainParameterId, kCommonParameterRate, sound_system_layouts,
+ mix_presentation_obus);
+
+ const std::list<AudioFrameWithData> empty_audio_frames_with_data = {};
+ const std::list<ParameterBlockWithData> empty_parameter_blocks_with_data = {};
+
+ const auto bitstream = AddSequenceHeaderAndSerializeObusExpectOk(
+ {&codec_config_obus.at(kFirstCodecConfigId),
+ &audio_elements_with_data.at(kFirstAudioElementId).obu,
+ &mix_presentation_obus.front()});
+ auto read_bit_buffer = MemoryBasedReadBitBuffer::CreateFromSpan(
+ kBufferCapacity, absl::MakeConstSpan(bitstream));
+ bool insufficient_data;
+
+ // We expect to use the first layout as default, since the desired layout is
+ // not available in the mix presentation.
+ constexpr int kSubmixIndex = 0;
+ constexpr int kLayoutIndex = 0;
+ const auto& forwarded_layout =
+ mix_presentation_obus.front().sub_mixes_[0].layouts[0].loudness_layout;
+
+ MockSampleProcessorFactory mock_sample_processor_factory;
+ EXPECT_CALL(mock_sample_processor_factory,
+ Call(kFirstMixPresentationId, kSubmixIndex, kLayoutIndex,
+ forwarded_layout, /*num_channels=*/2, _, _, _));
+ RenderingMixPresentationFinalizer::SampleProcessorFactory
+ sample_processor_factory = mock_sample_processor_factory.AsStdFunction();
+
+ Layout unused_output_layout;
+ EXPECT_THAT(ObuProcessor::CreateForRendering(
+ k5_1_Layout, sample_processor_factory,
+ /*is_exhaustive_and_exact=*/true, read_bit_buffer.get(),
+ unused_output_layout, insufficient_data),
+ NotNull());
+}
+
+TEST(CreateForRendering,
+ ForwardsChosenLayoutToSampleProcessorFactoryWithMultipleMixPresentations) {
+ absl::flat_hash_map<DecodedUleb128, CodecConfigObu> codec_config_obus;
+ AddLpcmCodecConfigWithIdAndSampleRate(kFirstCodecConfigId, kSampleRate,
+ codec_config_obus);
+ absl::flat_hash_map<DecodedUleb128, AudioElementWithData>
+ audio_elements_with_data;
+ AddAmbisonicsMonoAudioElementWithSubstreamIds(
+ kFirstAudioElementId, kFirstCodecConfigId,
+ {kFirstSubstreamId, kSecondSubstreamId, kThirdSubstreamId,
+ kFourthSubstreamId},
+ codec_config_obus, audio_elements_with_data);
+ std::list<MixPresentationObu> mix_presentation_obus;
+ std::vector<LoudspeakersSsConventionLayout::SoundSystem>
+ sound_system_layouts_first_mix_presentation = {
+ LoudspeakersSsConventionLayout::kSoundSystemA_0_2_0,
+ LoudspeakersSsConventionLayout::kSoundSystem10_2_7_0};
+ AddMixPresentationObuWithConfigurableLayouts(
+ kFirstMixPresentationId, {kFirstAudioElementId},
+ kCommonMixGainParameterId, kCommonParameterRate,
+ sound_system_layouts_first_mix_presentation, mix_presentation_obus);
+ std::vector<LoudspeakersSsConventionLayout::SoundSystem>
+ sound_system_layouts_second_mix_presentation = {
+ LoudspeakersSsConventionLayout::kSoundSystemA_0_2_0,
+ LoudspeakersSsConventionLayout::kSoundSystemB_0_5_0};
+ AddMixPresentationObuWithConfigurableLayouts(
+ kSecondMixPresentationId, {kFirstAudioElementId},
+ kCommonMixGainParameterId, kCommonParameterRate,
+ sound_system_layouts_second_mix_presentation, mix_presentation_obus);
+
+ const std::list<AudioFrameWithData> empty_audio_frames_with_data = {};
+ const std::list<ParameterBlockWithData> empty_parameter_blocks_with_data = {};
+
+ const auto bitstream = AddSequenceHeaderAndSerializeObusExpectOk(
+ {&codec_config_obus.at(kFirstCodecConfigId),
+ &audio_elements_with_data.at(kFirstAudioElementId).obu,
+ &mix_presentation_obus.front(),
+ &*(std::next(mix_presentation_obus.begin()))});
+ auto read_bit_buffer = MemoryBasedReadBitBuffer::CreateFromSpan(
+ kBufferCapacity, absl::MakeConstSpan(bitstream));
+ bool insufficient_data;
+
+ // We expect to use the second layout in the second mix presentation, since
+ // this is the only one that matches the desired layout.
+ constexpr int kSubmixIndex = 0;
+ constexpr int kLayoutIndex = 1;
+ const auto& forwarded_layout = (std::next(mix_presentation_obus.begin()))
+ ->sub_mixes_[0]
+ .layouts[1]
+ .loudness_layout;
+
+ MockSampleProcessorFactory mock_sample_processor_factory;
+ EXPECT_CALL(mock_sample_processor_factory,
+ Call(kSecondMixPresentationId, kSubmixIndex, kLayoutIndex,
+ forwarded_layout, /*num_channels=*/6, _, _, _));
+ RenderingMixPresentationFinalizer::SampleProcessorFactory
+ sample_processor_factory = mock_sample_processor_factory.AsStdFunction();
+
+ Layout output_layout;
+ EXPECT_THAT(ObuProcessor::CreateForRendering(
+ k5_1_Layout, sample_processor_factory,
+ /*is_exhaustive_and_exact=*/true, read_bit_buffer.get(),
+ output_layout, insufficient_data),
+ NotNull());
+ EXPECT_EQ(output_layout, k5_1_Layout);
+}
+
+TEST(CreateForRendering, NullReadBitBufferRejected) {
+ MockSampleProcessorFactory mock_sample_processor_factory;
+ auto sample_processor_factory = mock_sample_processor_factory.AsStdFunction();
+ ReadBitBuffer* read_bit_buffer_nullptr = nullptr;
+ bool insufficient_data;
+
+ Layout unused_output_layout;
+ EXPECT_THAT(ObuProcessor::CreateForRendering(
+ kStereoLayout, sample_processor_factory,
+ /*is_exhaustive_and_exact=*/true, read_bit_buffer_nullptr,
+ unused_output_layout, insufficient_data),
+ IsNull());
+ EXPECT_FALSE(insufficient_data);
+}
+
} // namespace
} // namespace iamf_tools
diff --git a/iamf/cli/tests/obu_sequencer_base_test.cc b/iamf/cli/tests/obu_sequencer_base_test.cc
index 81fc97d..fd33b8f 100644
--- a/iamf/cli/tests/obu_sequencer_base_test.cc
+++ b/iamf/cli/tests/obu_sequencer_base_test.cc
@@ -673,6 +673,24 @@
IsOk());
}
+TEST(PushDescriptorObus, SucceedsWithIaSequenceHeaderOnly) {
+ const IASequenceHeaderObu kIaSequenceHeader(
+ ObuHeader(), IASequenceHeaderObu::kIaCode,
+ ProfileVersion::kIamfSimpleProfile, ProfileVersion::kIamfBaseProfile);
+ const absl::flat_hash_map<DecodedUleb128, CodecConfigObu> kNoCodecConfigObus;
+ const absl::flat_hash_map<uint32_t, AudioElementWithData> kNoAudioElements;
+ const std::list<MixPresentationObu> kNoMixPresentationObus;
+ const std::list<ArbitraryObu> kNoArbitraryObus;
+ MockObuSequencer mock_obu_sequencer(
+ *LebGenerator::Create(), kDoNotIncludeTemporalDelimiters,
+ kDoNotDelayDescriptorsUntilTrimAtStartIsKnown);
+
+ EXPECT_THAT(mock_obu_sequencer.PushDescriptorObus(
+ kIaSequenceHeader, kNoCodecConfigObus, kNoAudioElements,
+ kNoMixPresentationObus, kNoArbitraryObus),
+ IsOk());
+}
+
TEST(PickAndPlace, SucceedsWithIaSequenceHeaderOnly) {
const IASequenceHeaderObu kIaSequenceHeader(
ObuHeader(), IASequenceHeaderObu::kIaCode,
@@ -694,6 +712,28 @@
IsOk());
}
+TEST(PushDescriptorObus, FailsWhenCalledTwice) {
+ const IASequenceHeaderObu kIaSequenceHeader(
+ ObuHeader(), IASequenceHeaderObu::kIaCode,
+ ProfileVersion::kIamfSimpleProfile, ProfileVersion::kIamfBaseProfile);
+ const absl::flat_hash_map<DecodedUleb128, CodecConfigObu> kNoCodecConfigObus;
+ const absl::flat_hash_map<uint32_t, AudioElementWithData> kNoAudioElements;
+ const std::list<MixPresentationObu> kNoMixPresentationObus;
+ const std::list<ArbitraryObu> kNoArbitraryObus;
+ MockObuSequencer mock_obu_sequencer(
+ *LebGenerator::Create(), kDoNotIncludeTemporalDelimiters,
+ kDoNotDelayDescriptorsUntilTrimAtStartIsKnown);
+ EXPECT_THAT(mock_obu_sequencer.PushDescriptorObus(
+ kIaSequenceHeader, kNoCodecConfigObus, kNoAudioElements,
+ kNoMixPresentationObus, kNoArbitraryObus),
+ IsOk());
+
+ EXPECT_THAT(mock_obu_sequencer.PushDescriptorObus(
+ kIaSequenceHeader, kNoCodecConfigObus, kNoAudioElements,
+ kNoMixPresentationObus, kNoArbitraryObus),
+ Not(IsOk()));
+}
+
TEST(PickAndPlace, FailsWhenCalledTwice) {
const IASequenceHeaderObu kIaSequenceHeader(
ObuHeader(), IASequenceHeaderObu::kIaCode,
@@ -720,7 +760,44 @@
Not(IsOk()));
}
-TEST(PickAndPlace, ForwardsPropertiesToPushDescriptorObus) {
+TEST(PushDescriptorObus, ForwardsPropertiesToPushSerializedDescriptorObus) {
+ const IASequenceHeaderObu kIaSequenceHeader(
+ ObuHeader(), IASequenceHeaderObu::kIaCode,
+ ProfileVersion::kIamfSimpleProfile, ProfileVersion::kIamfBaseProfile);
+ absl::flat_hash_map<DecodedUleb128, CodecConfigObu> codec_config_obus;
+ absl::flat_hash_map<uint32_t, AudioElementWithData> audio_elements;
+ std::list<MixPresentationObu> mix_presentation_obus;
+ const std::list<ArbitraryObu> kNoArbitraryObus;
+ InitializeDescriptorObusForTwoMonoAmbisonicsAudioElement(
+ codec_config_obus, audio_elements, mix_presentation_obus);
+ MockObuSequencer mock_obu_sequencer(
+ *LebGenerator::Create(), kDoNotIncludeTemporalDelimiters,
+ kDoNotDelayDescriptorsUntilTrimAtStartIsKnown);
+
+ // Several properties should match values derived from the descriptor OBUs.
+ const auto& codec_config_obu = codec_config_obus.begin()->second;
+ const uint32_t kExpectedCommonSamplesPerFrame =
+ codec_config_obu.GetNumSamplesPerFrame();
+ const uint32_t kExpectedCommonSampleRate =
+ codec_config_obu.GetOutputSampleRate();
+ const uint8_t kExpectedCommonBitDepth =
+ codec_config_obu.GetBitDepthToMeasureLoudness();
+ const std::optional<int64_t> kOmitFirstPts = std::nullopt;
+ const int kExpectedNumChannels = 2;
+ const std::vector<uint8_t> descriptor_obus = {1, 2, 3};
+ EXPECT_CALL(
+ mock_obu_sequencer,
+ PushSerializedDescriptorObus(
+ kExpectedCommonSamplesPerFrame, kExpectedCommonSampleRate,
+ kExpectedCommonBitDepth, kOmitFirstPts, kExpectedNumChannels, _));
+
+ EXPECT_THAT(mock_obu_sequencer.PushDescriptorObus(
+ kIaSequenceHeader, codec_config_obus, audio_elements,
+ mix_presentation_obus, kNoArbitraryObus),
+ IsOk());
+}
+
+TEST(PickAndPlace, ForwardsPropertiesToPushSerializedDescriptorObus) {
const IASequenceHeaderObu kIaSequenceHeader(
ObuHeader(), IASequenceHeaderObu::kIaCode,
ProfileVersion::kIamfSimpleProfile, ProfileVersion::kIamfBaseProfile);
@@ -760,6 +837,80 @@
IsOk());
}
+TEST(PushDescriptorObus,
+ WhenDescriptorsAreNotDelayedDescriptorsAreForwardedImmediately) {
+ // Configure the OBU sequencer to not delay descriptors. This means the
+ // properties can be forwarded right away.
+ const IASequenceHeaderObu kIaSequenceHeader(
+ ObuHeader(), IASequenceHeaderObu::kIaCode,
+ ProfileVersion::kIamfSimpleProfile, ProfileVersion::kIamfBaseProfile);
+ const absl::flat_hash_map<DecodedUleb128, CodecConfigObu> kNoCodecConfigObus;
+ const absl::flat_hash_map<uint32_t, AudioElementWithData> kNoAudioElements;
+ const std::list<MixPresentationObu> kNoMixPresentationObus;
+ const std::list<ArbitraryObu> kNoArbitraryObus;
+ MockObuSequencer mock_obu_sequencer(
+ *LebGenerator::Create(), kDoNotIncludeTemporalDelimiters,
+ kDoNotDelayDescriptorsUntilTrimAtStartIsKnown);
+
+ // The properties themselves are arbitrary, but "reasonable" defaults. This is
+ // to ensure certain OBU sequencers can have a file with reasonable
+ // properties, even if the IA Sequence is trivial.
+ const uint32_t kExpectedCommonSamplesPerFrame = 1024;
+ const uint32_t kExpectedCommonSampleRate = 48000;
+ const uint8_t kExpectedCommonBitDepth = 16;
+ const std::optional<int64_t> kFirstUntrimmedTimestamp = std::nullopt;
+ const int kExpectedNumChannels = 2;
+ EXPECT_CALL(mock_obu_sequencer,
+ PushSerializedDescriptorObus(
+ kExpectedCommonSamplesPerFrame, kExpectedCommonSampleRate,
+ kExpectedCommonBitDepth, kFirstUntrimmedTimestamp,
+ kExpectedNumChannels, _));
+
+ EXPECT_THAT(mock_obu_sequencer.PushDescriptorObus(
+ kIaSequenceHeader, kNoCodecConfigObus, kNoAudioElements,
+ kNoMixPresentationObus, kNoArbitraryObus),
+ IsOk());
+}
+
+TEST(
+ PushDescriptorObus,
+ WhenDescriptorsAreDelayedPropertiesAreForwardedAfterCloseForTrivialIaSequences) {
+ // Configure the OBU sequencer to delay descriptors until the first untrimmed
+ // sample is known. We can't detect it is a trivial IA Sequence, until the
+ // sequencer is closed.
+ const IASequenceHeaderObu kIaSequenceHeader(
+ ObuHeader(), IASequenceHeaderObu::kIaCode,
+ ProfileVersion::kIamfSimpleProfile, ProfileVersion::kIamfBaseProfile);
+ const absl::flat_hash_map<DecodedUleb128, CodecConfigObu> kNoCodecConfigObus;
+ const absl::flat_hash_map<uint32_t, AudioElementWithData> kNoAudioElements;
+ const std::list<MixPresentationObu> kNoMixPresentationObus;
+ const std::list<ArbitraryObu> kNoArbitraryObus;
+ MockObuSequencer mock_obu_sequencer(*LebGenerator::Create(),
+ kDoNotIncludeTemporalDelimiters,
+ kDelayDescriptorsUntilTrimAtStartIsKnown);
+ EXPECT_THAT(mock_obu_sequencer.PushDescriptorObus(
+ kIaSequenceHeader, kNoCodecConfigObus, kNoAudioElements,
+ kNoMixPresentationObus, kNoArbitraryObus),
+ IsOk());
+
+ // The properties themselves are arbitrary, but "reasonable" defaults. This is
+ // to ensure certain OBU sequencers can have a file with reasonable
+ // properties, even if the IA Sequence is trivial.
+ const uint32_t kExpectedCommonSamplesPerFrame = 1024;
+ const uint32_t kExpectedCommonSampleRate = 48000;
+ const uint8_t kExpectedCommonBitDepth = 16;
+ const std::optional<int64_t> kFirstUntrimmedTimestamp = 0;
+ const int kExpectedNumChannels = 2;
+ EXPECT_CALL(mock_obu_sequencer,
+ PushSerializedDescriptorObus(
+ kExpectedCommonSamplesPerFrame, kExpectedCommonSampleRate,
+ kExpectedCommonBitDepth, kFirstUntrimmedTimestamp,
+ kExpectedNumChannels, _));
+ // Finally at close time, we detect that there are no audio frames. Therefore
+ // we can make up a fake first timestamp.
+ EXPECT_THAT(mock_obu_sequencer.Close(), IsOk());
+}
+
TEST(PickAndPlace, ForwardsDefaultPropertiesForTrivialIaSequences) {
const IASequenceHeaderObu kIaSequenceHeader(
ObuHeader(), IASequenceHeaderObu::kIaCode,
@@ -777,7 +928,7 @@
// The properties themselves are arbitrary, but "reasonable" defaults. This is
// to ensure certain OBU sequencers can have a file with reasonable
// properties, even if the IA Sequence is trivial.
- const uint32_t kExpectedCommonSamplesPerFrame = 0;
+ const uint32_t kExpectedCommonSamplesPerFrame = 1024;
const uint32_t kExpectedCommonSampleRate = 48000;
const uint8_t kExpectedCommonBitDepth = 16;
const std::optional<int64_t> kFirstUntrimmedTimestamp = 0;
@@ -795,6 +946,34 @@
IsOk());
}
+TEST(PushDescriptorObus, ForwardsSerializedDescriptorObusToPushDescriptorObus) {
+ const IASequenceHeaderObu kIaSequenceHeader(
+ ObuHeader(), IASequenceHeaderObu::kIaCode,
+ ProfileVersion::kIamfSimpleProfile, ProfileVersion::kIamfBaseProfile);
+ absl::flat_hash_map<DecodedUleb128, CodecConfigObu> codec_config_obus;
+ absl::flat_hash_map<uint32_t, AudioElementWithData> audio_elements;
+ std::list<MixPresentationObu> mix_presentation_obus;
+ const std::list<ArbitraryObu> kNoArbitraryObus;
+ InitializeDescriptorObusForOneMonoAmbisonicsAudioElement(
+ codec_config_obus, audio_elements, mix_presentation_obus);
+ MockObuSequencer mock_obu_sequencer(
+ *LebGenerator::Create(), kDoNotIncludeTemporalDelimiters,
+ kDoNotDelayDescriptorsUntilTrimAtStartIsKnown);
+
+ // The spec prescribes an order among different types of descriptor OBUs.
+ const auto descriptor_obus = SerializeObusExpectOk(std::list<const ObuBase*>{
+ &kIaSequenceHeader, &codec_config_obus.begin()->second,
+ &audio_elements.begin()->second.obu, &mix_presentation_obus.front()});
+ EXPECT_CALL(mock_obu_sequencer,
+ PushSerializedDescriptorObus(_, _, _, _, _,
+ MakeConstSpan(descriptor_obus)));
+
+ EXPECT_THAT(mock_obu_sequencer.PushDescriptorObus(
+ kIaSequenceHeader, codec_config_obus, audio_elements,
+ mix_presentation_obus, kNoArbitraryObus),
+ IsOk());
+}
+
TEST(PickAndPlace, ForwardsSerializedDescriptorObusToPushDescriptorObus) {
const IASequenceHeaderObu kIaSequenceHeader(
ObuHeader(), IASequenceHeaderObu::kIaCode,
@@ -826,6 +1005,33 @@
IsOk());
}
+TEST(PushDescriptorObus, ForwardsArbitraryObusToPushSerializedDescriptorObus) {
+ const IASequenceHeaderObu kIaSequenceHeader(
+ ObuHeader(), IASequenceHeaderObu::kIaCode,
+ ProfileVersion::kIamfSimpleProfile, ProfileVersion::kIamfBaseProfile);
+ const absl::flat_hash_map<DecodedUleb128, CodecConfigObu> kNoCodecConfigObus;
+ const absl::flat_hash_map<uint32_t, AudioElementWithData> kNoAudioElements;
+ const std::list<MixPresentationObu> kNoMixPresentationObus;
+ const std::list<ArbitraryObu> kArbitraryObuAfterIaSequenceHeader(
+ {ArbitraryObu(kObuIaReserved25, ObuHeader(), {},
+ ArbitraryObu::kInsertionHookAfterIaSequenceHeader)});
+ MockObuSequencer mock_obu_sequencer(
+ *LebGenerator::Create(), kDoNotIncludeTemporalDelimiters,
+ kDoNotDelayDescriptorsUntilTrimAtStartIsKnown);
+
+ // Custom arbitrary OBUs can be placed according to their hook.
+ const auto descriptor_obus = SerializeObusExpectOk(std::list<const ObuBase*>{
+ &kIaSequenceHeader, &kArbitraryObuAfterIaSequenceHeader.front()});
+ EXPECT_CALL(mock_obu_sequencer,
+ PushSerializedDescriptorObus(_, _, _, _, _,
+ MakeConstSpan(descriptor_obus)));
+
+ EXPECT_THAT(mock_obu_sequencer.PushDescriptorObus(
+ kIaSequenceHeader, kNoCodecConfigObus, kNoAudioElements,
+ kNoMixPresentationObus, kArbitraryObuAfterIaSequenceHeader),
+ IsOk());
+}
+
TEST(PickAndPlace, ForwardsArbitraryObusToPushSerializedDescriptorObus) {
const IASequenceHeaderObu kIaSequenceHeader(
ObuHeader(), IASequenceHeaderObu::kIaCode,
@@ -856,6 +1062,41 @@
IsOk());
}
+TEST(PushTemporalUnit, ForwardsPropertiesToPushAllTemporalUnits) {
+ const IASequenceHeaderObu kIaSequenceHeader(
+ ObuHeader(), IASequenceHeaderObu::kIaCode,
+ ProfileVersion::kIamfSimpleProfile, ProfileVersion::kIamfBaseProfile);
+ absl::flat_hash_map<DecodedUleb128, CodecConfigObu> codec_config_obus;
+ absl::flat_hash_map<uint32_t, AudioElementWithData> audio_elements;
+ std::list<MixPresentationObu> mix_presentation_obus;
+ std::list<AudioFrameWithData> audio_frames;
+ const std::list<ParameterBlockWithData> kNoParameterBlocks;
+ const std::list<ArbitraryObu> kNoArbitraryObus;
+ InitializeOneFrameIaSequenceWithMixPresentation(
+ codec_config_obus, audio_elements, mix_presentation_obus, audio_frames);
+ audio_frames.front().obu.header_.num_samples_to_trim_at_start = 2;
+ audio_frames.front().obu.header_.num_samples_to_trim_at_end = 1;
+ const auto temporal_unit = TemporalUnitView::Create(
+ kNoParameterBlocks, audio_frames, kNoArbitraryObus);
+ ASSERT_THAT(temporal_unit, IsOk());
+ // We expect eight samples per frame, less the trimmed samples.
+ constexpr int kExpectedTimestamp = 0;
+ constexpr int kExpectedNumSamples = 5;
+ MockObuSequencer mock_obu_sequencer(
+ *LebGenerator::Create(), kDoNotIncludeTemporalDelimiters,
+ kDoNotDelayDescriptorsUntilTrimAtStartIsKnown);
+ EXPECT_THAT(mock_obu_sequencer.PushDescriptorObus(
+ kIaSequenceHeader, codec_config_obus, audio_elements,
+ mix_presentation_obus, kNoArbitraryObus),
+ IsOk());
+
+ EXPECT_CALL(
+ mock_obu_sequencer,
+ PushSerializedTemporalUnit(kExpectedTimestamp, kExpectedNumSamples, _));
+
+ EXPECT_THAT(mock_obu_sequencer.PushTemporalUnit(*temporal_unit), IsOk());
+}
+
TEST(PickAndPlace, ForwardsPropertiesToPushAllTemporalUnits) {
const IASequenceHeaderObu kIaSequenceHeader(
ObuHeader(), IASequenceHeaderObu::kIaCode,
@@ -925,6 +1166,55 @@
IsOk());
}
+TEST(PushTemporalUnit,
+ ForwardsNumUntrimmedSamplesToPushSerializedTemporalUnitWhenConfigured) {
+ const IASequenceHeaderObu kIaSequenceHeader(
+ ObuHeader(), IASequenceHeaderObu::kIaCode,
+ ProfileVersion::kIamfSimpleProfile, ProfileVersion::kIamfBaseProfile);
+ absl::flat_hash_map<DecodedUleb128, CodecConfigObu> codec_config_obus;
+ absl::flat_hash_map<uint32_t, AudioElementWithData> audio_elements;
+ std::list<MixPresentationObu> mix_presentation_obus;
+ const std::list<ParameterBlockWithData> kNoParameterBlocks;
+ const std::list<ArbitraryObu> kNoArbitraryObus;
+ std::list<AudioFrameWithData> first_audio_frame;
+ InitializeOneFrameIaSequenceWithMixPresentation(
+ codec_config_obus, audio_elements, mix_presentation_obus,
+ first_audio_frame);
+ first_audio_frame.back().obu.header_.num_samples_to_trim_at_start = 8;
+ const auto first_temporal_unit = TemporalUnitView::Create(
+ kNoParameterBlocks, first_audio_frame, kNoArbitraryObus);
+ ASSERT_THAT(first_temporal_unit, IsOk());
+ std::list<AudioFrameWithData> second_audio_frame;
+ AddEmptyAudioFrameWithAudioElementIdSubstreamIdAndTimestamps(
+ kFirstAudioElementId, kFirstSubstreamId, kSecondTimestamp,
+ kThirdTimestamp, audio_elements, second_audio_frame);
+ second_audio_frame.back().obu.header_.num_samples_to_trim_at_start = 3;
+ const auto second_temporal_unit = TemporalUnitView::Create(
+ kNoParameterBlocks, second_audio_frame, kNoArbitraryObus);
+ ASSERT_THAT(second_temporal_unit, IsOk());
+ // The first frame is fully trimmed. The second frame is partially trimmed.
+ constexpr std::optional<int64_t> kExpectedFirstUntrimmedTimestamp = 11;
+ MockObuSequencer mock_obu_sequencer(*LebGenerator::Create(),
+ kDoNotIncludeTemporalDelimiters,
+ kDelayDescriptorsUntilTrimAtStartIsKnown);
+ // Neither the initial descriptors, nor the first temporal unit have enough
+ // information to determine the first untrimmed timestamp.
+ EXPECT_THAT(mock_obu_sequencer.PushDescriptorObus(
+ kIaSequenceHeader, codec_config_obus, audio_elements,
+ mix_presentation_obus, kNoArbitraryObus),
+ IsOk());
+ EXPECT_THAT(mock_obu_sequencer.PushTemporalUnit(*first_temporal_unit),
+ IsOk());
+
+ // But by the second temporal unit, we can see the cumulative number of
+ // samples to trim at the start for this IA Sequence.
+ EXPECT_CALL(mock_obu_sequencer,
+ PushSerializedDescriptorObus(
+ _, _, _, kExpectedFirstUntrimmedTimestamp, _, _));
+ EXPECT_THAT(mock_obu_sequencer.PushTemporalUnit(*second_temporal_unit),
+ IsOk());
+}
+
TEST(PickAndPlace,
ForwardsNumUntrimmedSamplesToPushAllTemporalUnitsWhenConfigured) {
const IASequenceHeaderObu kIaSequenceHeader(
@@ -962,6 +1252,35 @@
IsOk());
}
+TEST(PushDescriptorObus, ReturnsErrorWhenResamplingWouldBeRequired) {
+ const IASequenceHeaderObu kIaSequenceHeader(
+ ObuHeader(), IASequenceHeaderObu::kIaCode,
+ ProfileVersion::kIamfSimpleProfile, ProfileVersion::kIamfBaseProfile);
+ absl::flat_hash_map<DecodedUleb128, CodecConfigObu> codec_config_obus;
+ // Theoretically, a future profile may support multiple codec config OBUs with
+ // different sample rates. The underlying code is written to only support IAMF
+ // v1.1.0 profiles, which all only support a single codec config OBU.
+ constexpr uint32_t kCodecConfigId = 1;
+ constexpr uint32_t kSecondCodecConfigId = 2;
+ constexpr uint32_t kSampleRate = 48000;
+ constexpr uint32_t kSecondSampleRate = 44100;
+ AddLpcmCodecConfigWithIdAndSampleRate(kCodecConfigId, kSampleRate,
+ codec_config_obus);
+ AddLpcmCodecConfigWithIdAndSampleRate(kSecondCodecConfigId, kSecondSampleRate,
+ codec_config_obus);
+ const absl::flat_hash_map<uint32_t, AudioElementWithData> kNoAudioElements;
+ const std::list<MixPresentationObu> kNoMixPresentationObus;
+ const std::list<ArbitraryObu> kNoArbitraryObus;
+ MockObuSequencer mock_obu_sequencer(*LebGenerator::Create(),
+ kDoNotIncludeTemporalDelimiters,
+ kDelayDescriptorsUntilTrimAtStartIsKnown);
+
+ EXPECT_THAT(mock_obu_sequencer.PushDescriptorObus(
+ kIaSequenceHeader, codec_config_obus, kNoAudioElements,
+ kNoMixPresentationObus, kNoArbitraryObus),
+ Not(IsOk()));
+}
+
TEST(PickAndPlace, ReturnsErrorWhenResamplingWouldBeRequired) {
const IASequenceHeaderObu kIaSequenceHeader(
ObuHeader(), IASequenceHeaderObu::kIaCode,
@@ -994,6 +1313,50 @@
Not(IsOk()));
}
+TEST(PushTemporalUnit,
+ ReturnsErrorWhenSamplesAreTrimmedFromStartAfterFirstUntrimmedSample) {
+ const IASequenceHeaderObu kIaSequenceHeader(
+ ObuHeader(), IASequenceHeaderObu::kIaCode,
+ ProfileVersion::kIamfSimpleProfile, ProfileVersion::kIamfBaseProfile);
+ absl::flat_hash_map<DecodedUleb128, CodecConfigObu> codec_config_obus;
+ absl::flat_hash_map<uint32_t, AudioElementWithData> audio_elements;
+ std::list<MixPresentationObu> mix_presentation_obus;
+ const std::list<ParameterBlockWithData> kNoParameterBlocks;
+ const std::list<ArbitraryObu> kNoArbitraryObus;
+ std::list<AudioFrameWithData> first_audio_frame;
+ InitializeOneFrameIaSequenceWithMixPresentation(
+ codec_config_obus, audio_elements, mix_presentation_obus,
+ first_audio_frame);
+ first_audio_frame.back().obu.header_.num_samples_to_trim_at_start = 0;
+ const auto first_temporal_unit = TemporalUnitView::Create(
+ kNoParameterBlocks, first_audio_frame, kNoArbitraryObus);
+ ASSERT_THAT(first_temporal_unit, IsOk());
+ // Corrupt the data by adding a second frame with samples trimmed from the
+ // start, after the first frame had no trimmed samples.
+ std::list<AudioFrameWithData> second_audio_frame;
+ AddEmptyAudioFrameWithAudioElementIdSubstreamIdAndTimestamps(
+ kFirstAudioElementId, kFirstSubstreamId, kSecondTimestamp,
+ kThirdTimestamp, audio_elements, second_audio_frame);
+ second_audio_frame.back().obu.header_.num_samples_to_trim_at_start = 1;
+ const auto second_temporal_unit = TemporalUnitView::Create(
+ kNoParameterBlocks, second_audio_frame, kNoArbitraryObus);
+ ASSERT_THAT(second_temporal_unit, IsOk());
+ MockObuSequencer mock_obu_sequencer(*LebGenerator::Create(),
+ kDoNotIncludeTemporalDelimiters,
+ kDelayDescriptorsUntilTrimAtStartIsKnown);
+ EXPECT_THAT(mock_obu_sequencer.PushDescriptorObus(
+ kIaSequenceHeader, codec_config_obus, audio_elements,
+ mix_presentation_obus, kNoArbitraryObus),
+ IsOk());
+ EXPECT_THAT(mock_obu_sequencer.PushTemporalUnit(*first_temporal_unit),
+ IsOk());
+
+ // The second temporal unit is corrupt, because it has samples trimmed from
+ // the start after the first temporal unit had no trimmed samples.
+ EXPECT_THAT(mock_obu_sequencer.PushTemporalUnit(*second_temporal_unit),
+ Not(IsOk()));
+}
+
TEST(PickAndPlace,
ReturnsErrorWhenSamplesAreTrimmedFromStartAfterFirstUntrimmedSample) {
const IASequenceHeaderObu kIaSequenceHeader(
@@ -1025,6 +1388,43 @@
Not(IsOk()));
}
+TEST(PushTemporalUnit, ForwardsObusToPushSerializedTemporalUnit) {
+ const IASequenceHeaderObu kIaSequenceHeader(
+ ObuHeader(), IASequenceHeaderObu::kIaCode,
+ ProfileVersion::kIamfSimpleProfile, ProfileVersion::kIamfBaseProfile);
+ absl::flat_hash_map<DecodedUleb128, CodecConfigObu> codec_config_obus;
+ absl::flat_hash_map<uint32_t, AudioElementWithData> audio_elements;
+ std::list<MixPresentationObu> mix_presentation_obus;
+ std::list<AudioFrameWithData> audio_frames;
+ DemixingParamDefinition param_definition =
+ CreateDemixingParamDefinition(kFirstDemixingParameterId);
+ std::list<ParameterBlockWithData> parameter_blocks;
+ InitializeOneParameterBlockAndOneAudioFrame(
+ param_definition, parameter_blocks, audio_frames, codec_config_obus,
+ audio_elements);
+ const std::list<ArbitraryObu> kNoArbitraryObus;
+ const auto temporal_unit = TemporalUnitView::Create(
+ parameter_blocks, audio_frames, kNoArbitraryObus);
+ ASSERT_THAT(temporal_unit, IsOk());
+ MockObuSequencer mock_obu_sequencer(
+ *LebGenerator::Create(), kDoNotIncludeTemporalDelimiters,
+ kDoNotDelayDescriptorsUntilTrimAtStartIsKnown);
+ EXPECT_THAT(mock_obu_sequencer.PushDescriptorObus(
+ kIaSequenceHeader, codec_config_obus, audio_elements,
+ mix_presentation_obus, kNoArbitraryObus),
+ IsOk());
+
+ // The spec prescribes an order among different types of OBUs.
+ const std::vector<uint8_t> serialized_temporal_unit =
+ SerializeObusExpectOk(std::list<const ObuBase*>{
+ parameter_blocks.front().obu.get(), &audio_frames.front().obu});
+ EXPECT_CALL(mock_obu_sequencer,
+ PushSerializedTemporalUnit(
+ _, _, MakeConstSpan(serialized_temporal_unit)));
+
+ EXPECT_THAT(mock_obu_sequencer.PushTemporalUnit(*temporal_unit), IsOk());
+}
+
TEST(PickAndPlace, ForwardsObusToPushSerializedTemporalUnit) {
const IASequenceHeaderObu kIaSequenceHeader(
ObuHeader(), IASequenceHeaderObu::kIaCode,
@@ -1059,6 +1459,44 @@
IsOk());
}
+TEST(PushTemporalUnit, ForwardsArbitraryObusToPushSerializedTemporalUnit) {
+ const IASequenceHeaderObu kIaSequenceHeader(
+ ObuHeader(), IASequenceHeaderObu::kIaCode,
+ ProfileVersion::kIamfSimpleProfile, ProfileVersion::kIamfBaseProfile);
+ absl::flat_hash_map<DecodedUleb128, CodecConfigObu> codec_config_obus;
+ absl::flat_hash_map<uint32_t, AudioElementWithData> audio_elements;
+ std::list<MixPresentationObu> mix_presentation_obus;
+ std::list<AudioFrameWithData> audio_frames;
+ InitializeOneFrameIaSequenceWithMixPresentation(
+ codec_config_obus, audio_elements, mix_presentation_obus, audio_frames);
+ const std::list<ParameterBlockWithData> kNoParameterBlocks;
+ const std::list<ArbitraryObu> kArbitraryObuBeforeFirstAudioFrame(
+ {ArbitraryObu(kObuIaReserved25, ObuHeader(), {},
+ ArbitraryObu::kInsertionHookAfterAudioFramesAtTick,
+ kFirstTimestamp)});
+ const auto first_temporal_unit = TemporalUnitView::Create(
+ kNoParameterBlocks, audio_frames, kArbitraryObuBeforeFirstAudioFrame);
+ ASSERT_THAT(first_temporal_unit, IsOk());
+ MockObuSequencer mock_obu_sequencer(
+ *LebGenerator::Create(), kDoNotIncludeTemporalDelimiters,
+ kDoNotDelayDescriptorsUntilTrimAtStartIsKnown);
+ EXPECT_THAT(mock_obu_sequencer.PushDescriptorObus(
+ kIaSequenceHeader, codec_config_obus, audio_elements,
+ mix_presentation_obus, kArbitraryObuBeforeFirstAudioFrame),
+ IsOk());
+
+ // Custom arbitrary OBUs can be placed according to their hook.
+ const std::vector<uint8_t> serialized_audio_frame = SerializeObusExpectOk(
+ std::list<const ObuBase*>{&audio_frames.front().obu,
+ &kArbitraryObuBeforeFirstAudioFrame.front()});
+ EXPECT_CALL(
+ mock_obu_sequencer,
+ PushSerializedTemporalUnit(_, _, MakeConstSpan(serialized_audio_frame)));
+
+ EXPECT_THAT(mock_obu_sequencer.PushTemporalUnit(*first_temporal_unit),
+ IsOk());
+}
+
TEST(PickAndPlace, ForwardsArbitraryObusToPushSerializedTemporalUnit) {
const IASequenceHeaderObu kIaSequenceHeader(
ObuHeader(), IASequenceHeaderObu::kIaCode,
@@ -1093,7 +1531,28 @@
IsOk());
}
-TEST(PickAndPlace, CallsFlushWhenDone) {
+TEST(Close, CallsCloseDerived) {
+ MockObuSequencer mock_obu_sequencer(
+ *LebGenerator::Create(), kDoNotIncludeTemporalDelimiters,
+ kDoNotDelayDescriptorsUntilTrimAtStartIsKnown);
+
+ // `CloseDerived` is called when done, which allows concrete implementation to
+ // finalize and optionally close their output streams.
+ EXPECT_CALL(mock_obu_sequencer, CloseDerived());
+
+ EXPECT_THAT(mock_obu_sequencer.Close(), IsOk());
+}
+
+TEST(Close, FailsWhenCalledTwice) {
+ MockObuSequencer mock_obu_sequencer(
+ *LebGenerator::Create(), kDoNotIncludeTemporalDelimiters,
+ kDoNotDelayDescriptorsUntilTrimAtStartIsKnown);
+ EXPECT_THAT(mock_obu_sequencer.Close(), IsOk());
+
+ EXPECT_THAT(mock_obu_sequencer.Close(), Not(IsOk()));
+}
+
+TEST(PickAndPlace, CallsCloseDerivedWhenDone) {
const IASequenceHeaderObu kIaSequenceHeader(
ObuHeader(), IASequenceHeaderObu::kIaCode,
ProfileVersion::kIamfSimpleProfile, ProfileVersion::kIamfBaseProfile);
@@ -1109,9 +1568,9 @@
*LebGenerator::Create(), kDoNotIncludeTemporalDelimiters,
kDoNotDelayDescriptorsUntilTrimAtStartIsKnown);
- // `Flush` is called when done, which allows concrete implementation to
+ // `CloseDerived` is called when done, which allows concrete implementation to
// finalize and optionally close their output streams.
- EXPECT_CALL(mock_obu_sequencer, Flush());
+ EXPECT_CALL(mock_obu_sequencer, CloseDerived());
EXPECT_THAT(mock_obu_sequencer.PickAndPlace(
kIaSequenceHeader, kNoCodecConfigObus, kNoAudioElements,
@@ -1120,7 +1579,46 @@
IsOk());
}
-TEST(PickAndPlace, CallsAbortWhenPushDescriptorObusFails) {
+TEST(Abort, CallsAbortDerived) {
+ MockObuSequencer mock_obu_sequencer(
+ *LebGenerator::Create(), kDoNotIncludeTemporalDelimiters,
+ kDoNotDelayDescriptorsUntilTrimAtStartIsKnown);
+
+ // `CloseDerived` is called when done, which allows concrete implementation to
+ // finalize and optionally close their output streams.
+ EXPECT_CALL(mock_obu_sequencer, AbortDerived());
+
+ mock_obu_sequencer.Abort();
+}
+
+TEST(PushDescriptorObus, CallsAbortDerivedWhenPushDescriptorObusFails) {
+ const IASequenceHeaderObu kIaSequenceHeader(
+ ObuHeader(), IASequenceHeaderObu::kIaCode,
+ ProfileVersion::kIamfSimpleProfile, ProfileVersion::kIamfBaseProfile);
+ absl::flat_hash_map<DecodedUleb128, CodecConfigObu> codec_config_obus;
+ absl::flat_hash_map<uint32_t, AudioElementWithData> audio_elements;
+ std::list<MixPresentationObu> mix_presentation_obus;
+ InitializeDescriptorObusForTwoMonoAmbisonicsAudioElement(
+ codec_config_obus, audio_elements, mix_presentation_obus);
+ const std::list<ArbitraryObu> kNoArbitraryObus;
+ MockObuSequencer mock_obu_sequencer(
+ *LebGenerator::Create(), kDoNotIncludeTemporalDelimiters,
+ kDoNotDelayDescriptorsUntilTrimAtStartIsKnown);
+
+ // If `PushSerializedDescriptorObus` fails, `Abort` is called. This allows
+ // concrete implementation to clean up and remove the file in one place.
+ EXPECT_CALL(mock_obu_sequencer,
+ PushSerializedDescriptorObus(_, _, _, _, _, _))
+ .WillOnce(Return(absl::InternalError("")));
+ EXPECT_CALL(mock_obu_sequencer, AbortDerived()).Times(1);
+
+ EXPECT_THAT(mock_obu_sequencer.PushDescriptorObus(
+ kIaSequenceHeader, codec_config_obus, audio_elements,
+ mix_presentation_obus, kNoArbitraryObus),
+ Not(IsOk()));
+}
+
+TEST(PickAndPlace, CallsAbortDerivedWhenPushDescriptorObusFails) {
const IASequenceHeaderObu kIaSequenceHeader(
ObuHeader(), IASequenceHeaderObu::kIaCode,
ProfileVersion::kIamfSimpleProfile, ProfileVersion::kIamfBaseProfile);
@@ -1141,7 +1639,7 @@
EXPECT_CALL(mock_obu_sequencer,
PushSerializedDescriptorObus(_, _, _, _, _, _))
.WillOnce(Return(absl::InternalError("")));
- EXPECT_CALL(mock_obu_sequencer, Abort()).Times(1);
+ EXPECT_CALL(mock_obu_sequencer, AbortDerived()).Times(1);
EXPECT_THAT(mock_obu_sequencer.PickAndPlace(
kIaSequenceHeader, codec_config_obus, audio_elements,
@@ -1150,7 +1648,40 @@
Not(IsOk()));
}
-TEST(PickAndPlace, CallsAbortWhenPushAllTemporalUnitsFails) {
+TEST(PushTemporalUnit, CallsAbortDerivedWhenPushAllTemporalUnitsFails) {
+ const IASequenceHeaderObu kIaSequenceHeader(
+ ObuHeader(), IASequenceHeaderObu::kIaCode,
+ ProfileVersion::kIamfSimpleProfile, ProfileVersion::kIamfBaseProfile);
+ absl::flat_hash_map<DecodedUleb128, CodecConfigObu> codec_config_obus;
+ absl::flat_hash_map<uint32_t, AudioElementWithData> audio_elements;
+ std::list<MixPresentationObu> mix_presentation_obus;
+ std::list<AudioFrameWithData> audio_frames;
+ InitializeOneFrameIaSequenceWithMixPresentation(
+ codec_config_obus, audio_elements, mix_presentation_obus, audio_frames);
+ const std::list<ParameterBlockWithData> kNoParameterBlocks;
+ const std::list<ArbitraryObu> kNoArbitraryObus;
+ const auto temporal_unit = TemporalUnitView::Create(
+ kNoParameterBlocks, audio_frames, kNoArbitraryObus);
+ ASSERT_THAT(temporal_unit, IsOk());
+ MockObuSequencer mock_obu_sequencer(
+ *LebGenerator::Create(), kDoNotIncludeTemporalDelimiters,
+ kDoNotDelayDescriptorsUntilTrimAtStartIsKnown);
+ EXPECT_THAT(mock_obu_sequencer.PushDescriptorObus(
+ kIaSequenceHeader, codec_config_obus, audio_elements,
+ mix_presentation_obus, kNoArbitraryObus),
+ IsOk());
+
+ // If `PushSerializedTemporalUnit` fails, `AbortDerived` is called. This
+ // allows concrete implementation to clean up and remove the file in one
+ // place.
+ EXPECT_CALL(mock_obu_sequencer, PushSerializedTemporalUnit(_, _, _))
+ .WillOnce(Return(absl::InternalError("")));
+ EXPECT_CALL(mock_obu_sequencer, AbortDerived()).Times(1);
+
+ EXPECT_THAT(mock_obu_sequencer.PushTemporalUnit(*temporal_unit), Not(IsOk()));
+}
+
+TEST(PickAndPlace, CallsAbortDerivedWhenPushAllTemporalUnitsFails) {
const IASequenceHeaderObu kIaSequenceHeader(
ObuHeader(), IASequenceHeaderObu::kIaCode,
ProfileVersion::kIamfSimpleProfile, ProfileVersion::kIamfBaseProfile);
@@ -1166,11 +1697,12 @@
*LebGenerator::Create(), kDoNotIncludeTemporalDelimiters,
kDoNotDelayDescriptorsUntilTrimAtStartIsKnown);
- // If `PushSerializedTemporalUnit` fails, `Abort` is called. This allows
- // concrete implementation to clean up and remove the file in one place.
+ // If `PushSerializedTemporalUnit` fails, `AbortDerived` is called. This
+ // allows concrete implementation to clean up and remove the file in one
+ // place.
EXPECT_CALL(mock_obu_sequencer, PushSerializedTemporalUnit(_, _, _))
.WillOnce(Return(absl::InternalError("")));
- EXPECT_CALL(mock_obu_sequencer, Abort()).Times(1);
+ EXPECT_CALL(mock_obu_sequencer, AbortDerived()).Times(1);
EXPECT_THAT(mock_obu_sequencer.PickAndPlace(
kIaSequenceHeader, codec_config_obus, audio_elements,
@@ -1179,5 +1711,261 @@
Not(IsOk()));
}
+TEST(PushTemporalUnit, FailsWhenBeforePushDescriptorObus) {
+ const IASequenceHeaderObu kIaSequenceHeader(
+ ObuHeader(), IASequenceHeaderObu::kIaCode,
+ ProfileVersion::kIamfSimpleProfile, ProfileVersion::kIamfBaseProfile);
+ absl::flat_hash_map<DecodedUleb128, CodecConfigObu> codec_config_obus;
+ absl::flat_hash_map<uint32_t, AudioElementWithData> audio_elements;
+ std::list<MixPresentationObu> mix_presentation_obus;
+ std::list<AudioFrameWithData> audio_frames;
+ InitializeOneFrameIaSequenceWithMixPresentation(
+ codec_config_obus, audio_elements, mix_presentation_obus, audio_frames);
+ const std::list<ParameterBlockWithData> kNoParameterBlocks;
+ const std::list<ArbitraryObu> kNoArbitraryObus;
+ const auto temporal_unit = TemporalUnitView::Create(
+ kNoParameterBlocks, audio_frames, kNoArbitraryObus);
+ ASSERT_THAT(temporal_unit, IsOk());
+ MockObuSequencer mock_obu_sequencer(
+ *LebGenerator::Create(), kDoNotIncludeTemporalDelimiters,
+ kDoNotDelayDescriptorsUntilTrimAtStartIsKnown);
+ // Omitted call to `PushDescriptorObus`. We can't accept temporal units yet.
+
+ EXPECT_THAT(mock_obu_sequencer.PushTemporalUnit(*temporal_unit), Not(IsOk()));
+}
+
+TEST(PushTemporalUnit, FailsWhenCalledAfterClose) {
+ const IASequenceHeaderObu kIaSequenceHeader(
+ ObuHeader(), IASequenceHeaderObu::kIaCode,
+ ProfileVersion::kIamfSimpleProfile, ProfileVersion::kIamfBaseProfile);
+ absl::flat_hash_map<DecodedUleb128, CodecConfigObu> codec_config_obus;
+ absl::flat_hash_map<uint32_t, AudioElementWithData> audio_elements;
+ std::list<MixPresentationObu> mix_presentation_obus;
+ std::list<AudioFrameWithData> audio_frames;
+ InitializeOneFrameIaSequenceWithMixPresentation(
+ codec_config_obus, audio_elements, mix_presentation_obus, audio_frames);
+ const std::list<ParameterBlockWithData> kNoParameterBlocks;
+ const std::list<ArbitraryObu> kNoArbitraryObus;
+ const auto temporal_unit = TemporalUnitView::Create(
+ kNoParameterBlocks, audio_frames, kNoArbitraryObus);
+ ASSERT_THAT(temporal_unit, IsOk());
+ MockObuSequencer mock_obu_sequencer(
+ *LebGenerator::Create(), kDoNotIncludeTemporalDelimiters,
+ kDoNotDelayDescriptorsUntilTrimAtStartIsKnown);
+ EXPECT_THAT(mock_obu_sequencer.Close(), IsOk());
+
+ EXPECT_THAT(mock_obu_sequencer.PushTemporalUnit(*temporal_unit), Not(IsOk()));
+}
+
+TEST(PushTemporalUnit, FailsWhenCalledAfterAbort) {
+ const IASequenceHeaderObu kIaSequenceHeader(
+ ObuHeader(), IASequenceHeaderObu::kIaCode,
+ ProfileVersion::kIamfSimpleProfile, ProfileVersion::kIamfBaseProfile);
+ absl::flat_hash_map<DecodedUleb128, CodecConfigObu> codec_config_obus;
+ absl::flat_hash_map<uint32_t, AudioElementWithData> audio_elements;
+ std::list<MixPresentationObu> mix_presentation_obus;
+ std::list<AudioFrameWithData> audio_frames;
+ InitializeOneFrameIaSequenceWithMixPresentation(
+ codec_config_obus, audio_elements, mix_presentation_obus, audio_frames);
+ const std::list<ParameterBlockWithData> kNoParameterBlocks;
+ const std::list<ArbitraryObu> kNoArbitraryObus;
+ const auto temporal_unit = TemporalUnitView::Create(
+ kNoParameterBlocks, audio_frames, kNoArbitraryObus);
+ ASSERT_THAT(temporal_unit, IsOk());
+ MockObuSequencer mock_obu_sequencer(
+ *LebGenerator::Create(), kDoNotIncludeTemporalDelimiters,
+ kDoNotDelayDescriptorsUntilTrimAtStartIsKnown);
+ mock_obu_sequencer.Abort();
+
+ EXPECT_THAT(mock_obu_sequencer.PushTemporalUnit(*temporal_unit), Not(IsOk()));
+}
+
+TEST(UpdateDescriptorObusAndClose,
+ ForwardsDescriptorObusToPushFinalizedDescriptorObus) {
+ const IASequenceHeaderObu kOriginalIaSequenceHeader(
+ ObuHeader(), IASequenceHeaderObu::kIaCode,
+ ProfileVersion::kIamfSimpleProfile, ProfileVersion::kIamfBaseProfile);
+ const IASequenceHeaderObu kUpdatedIaSequenceHeader(
+ ObuHeader(), IASequenceHeaderObu::kIaCode,
+ ProfileVersion::kIamfBaseProfile, ProfileVersion::kIamfBaseProfile);
+ const absl::flat_hash_map<DecodedUleb128, CodecConfigObu> kNoCodecConfigObus;
+ const absl::flat_hash_map<uint32_t, AudioElementWithData> kNoAudioElements;
+ const std::list<MixPresentationObu> kNoMixPresentationObus;
+ const std::list<ArbitraryObu> kNoArbitraryObus;
+ MockObuSequencer mock_obu_sequencer(
+ *LebGenerator::Create(), kDoNotIncludeTemporalDelimiters,
+ kDoNotDelayDescriptorsUntilTrimAtStartIsKnown);
+ EXPECT_THAT(mock_obu_sequencer.PushDescriptorObus(
+ kOriginalIaSequenceHeader, kNoCodecConfigObus,
+ kNoAudioElements, kNoMixPresentationObus, kNoArbitraryObus),
+ IsOk());
+ const auto expected_finalized_descriptor_obus = SerializeObusExpectOk(
+ std::list<const ObuBase*>{&kUpdatedIaSequenceHeader});
+
+ // Several properties should match values derived from the descriptor
+ // OBUs.
+ EXPECT_CALL(mock_obu_sequencer, PushFinalizedDescriptorObus(MakeConstSpan(
+ expected_finalized_descriptor_obus)));
+
+ EXPECT_THAT(mock_obu_sequencer.UpdateDescriptorObusAndClose(
+ kUpdatedIaSequenceHeader, kNoCodecConfigObus,
+ kNoAudioElements, kNoMixPresentationObus, kNoArbitraryObus),
+ IsOk());
+}
+
+TEST(UpdateDescriptorObusAndClose, FailsBeforePushDescriptorObus) {
+ const IASequenceHeaderObu kIaSequenceHeader(
+ ObuHeader(), IASequenceHeaderObu::kIaCode,
+ ProfileVersion::kIamfSimpleProfile, ProfileVersion::kIamfBaseProfile);
+ const absl::flat_hash_map<DecodedUleb128, CodecConfigObu> kNoCodecConfigObus;
+ const absl::flat_hash_map<uint32_t, AudioElementWithData> kNoAudioElements;
+ const std::list<MixPresentationObu> kNoMixPresentationObus;
+ const std::list<ArbitraryObu> kNoArbitraryObus;
+ MockObuSequencer mock_obu_sequencer(
+ *LebGenerator::Create(), kDoNotIncludeTemporalDelimiters,
+ kDoNotDelayDescriptorsUntilTrimAtStartIsKnown);
+
+ EXPECT_THAT(mock_obu_sequencer.UpdateDescriptorObusAndClose(
+ kIaSequenceHeader, kNoCodecConfigObus, kNoAudioElements,
+ kNoMixPresentationObus, kNoArbitraryObus),
+ Not(IsOk()));
+}
+
+TEST(UpdateDescriptorObusAndClose, SubsequentCloseCallsFails) {
+ const IASequenceHeaderObu kIaSequenceHeader(
+ ObuHeader(), IASequenceHeaderObu::kIaCode,
+ ProfileVersion::kIamfSimpleProfile, ProfileVersion::kIamfBaseProfile);
+ const absl::flat_hash_map<DecodedUleb128, CodecConfigObu> kNoCodecConfigObus;
+ const absl::flat_hash_map<uint32_t, AudioElementWithData> kNoAudioElements;
+ const std::list<MixPresentationObu> kNoMixPresentationObus;
+ const std::list<ArbitraryObu> kNoArbitraryObus;
+ MockObuSequencer mock_obu_sequencer(
+ *LebGenerator::Create(), kDoNotIncludeTemporalDelimiters,
+ kDoNotDelayDescriptorsUntilTrimAtStartIsKnown);
+ EXPECT_THAT(mock_obu_sequencer.PushDescriptorObus(
+ kIaSequenceHeader, kNoCodecConfigObus, kNoAudioElements,
+ kNoMixPresentationObus, kNoArbitraryObus),
+ IsOk());
+ EXPECT_THAT(mock_obu_sequencer.UpdateDescriptorObusAndClose(
+ kIaSequenceHeader, kNoCodecConfigObus, kNoAudioElements,
+ kNoMixPresentationObus, kNoArbitraryObus),
+ IsOk());
+
+ EXPECT_THAT(mock_obu_sequencer.Close(), Not(IsOk()));
+}
+
+TEST(UpdateDescriptorObusAndClose, CallsCloseDerived) {
+ const IASequenceHeaderObu kOriginalIaSequenceHeader(
+ ObuHeader(), IASequenceHeaderObu::kIaCode,
+ ProfileVersion::kIamfSimpleProfile, ProfileVersion::kIamfBaseProfile);
+ const IASequenceHeaderObu kUpdatedIaSequenceHeader(
+ ObuHeader(), IASequenceHeaderObu::kIaCode,
+ ProfileVersion::kIamfBaseProfile, ProfileVersion::kIamfBaseProfile);
+ const absl::flat_hash_map<DecodedUleb128, CodecConfigObu> kNoCodecConfigObus;
+ const absl::flat_hash_map<uint32_t, AudioElementWithData> kNoAudioElements;
+ const std::list<MixPresentationObu> kNoMixPresentationObus;
+ const std::list<ArbitraryObu> kNoArbitraryObus;
+ MockObuSequencer mock_obu_sequencer(
+ *LebGenerator::Create(), kDoNotIncludeTemporalDelimiters,
+ kDoNotDelayDescriptorsUntilTrimAtStartIsKnown);
+ EXPECT_THAT(mock_obu_sequencer.PushDescriptorObus(
+ kOriginalIaSequenceHeader, kNoCodecConfigObus,
+ kNoAudioElements, kNoMixPresentationObus, kNoArbitraryObus),
+ IsOk());
+ EXPECT_CALL(mock_obu_sequencer, CloseDerived()).Times(1);
+
+ EXPECT_THAT(mock_obu_sequencer.UpdateDescriptorObusAndClose(
+ kUpdatedIaSequenceHeader, kNoCodecConfigObus,
+ kNoAudioElements, kNoMixPresentationObus, kNoArbitraryObus),
+ IsOk());
+}
+
+TEST(UpdateDescriptorObusAndClose,
+ CallsAbortDerivedWhenPushFinalizedDescriptorObusFails) {
+ const IASequenceHeaderObu kOriginalIaSequenceHeader(
+ ObuHeader(), IASequenceHeaderObu::kIaCode,
+ ProfileVersion::kIamfSimpleProfile, ProfileVersion::kIamfBaseProfile);
+ const IASequenceHeaderObu kUpdatedIaSequenceHeader(
+ ObuHeader(), IASequenceHeaderObu::kIaCode,
+ ProfileVersion::kIamfBaseProfile, ProfileVersion::kIamfBaseProfile);
+ const absl::flat_hash_map<DecodedUleb128, CodecConfigObu> kNoCodecConfigObus;
+ const absl::flat_hash_map<uint32_t, AudioElementWithData> kNoAudioElements;
+ const std::list<MixPresentationObu> kNoMixPresentationObus;
+ const std::list<ArbitraryObu> kNoArbitraryObus;
+ MockObuSequencer mock_obu_sequencer(
+ *LebGenerator::Create(), kDoNotIncludeTemporalDelimiters,
+ kDoNotDelayDescriptorsUntilTrimAtStartIsKnown);
+ EXPECT_THAT(mock_obu_sequencer.PushDescriptorObus(
+ kOriginalIaSequenceHeader, kNoCodecConfigObus,
+ kNoAudioElements, kNoMixPresentationObus, kNoArbitraryObus),
+ IsOk());
+ ON_CALL(mock_obu_sequencer, PushFinalizedDescriptorObus(_))
+ .WillByDefault(Return(absl::InternalError("")));
+ EXPECT_CALL(mock_obu_sequencer, AbortDerived()).Times(1);
+
+ EXPECT_THAT(mock_obu_sequencer.UpdateDescriptorObusAndClose(
+ kUpdatedIaSequenceHeader, kNoCodecConfigObus,
+ kNoAudioElements, kNoMixPresentationObus, kNoArbitraryObus),
+ Not(IsOk()));
+}
+
+TEST(UpdateDescriptorObusAndClose, FailsWhenSerializedSizeChanges) {
+ const IASequenceHeaderObu kOriginalIaSequenceHeader(
+ ObuHeader(), IASequenceHeaderObu::kIaCode,
+ ProfileVersion::kIamfSimpleProfile, ProfileVersion::kIamfBaseProfile);
+ const IASequenceHeaderObu kResizedIaSequenceHeader(
+ ObuHeader{.obu_extension_flag = true, .extension_header_size = 0},
+ IASequenceHeaderObu::kIaCode, ProfileVersion::kIamfBaseProfile,
+ ProfileVersion::kIamfBaseProfile);
+ const absl::flat_hash_map<DecodedUleb128, CodecConfigObu> kNoCodecConfigObus;
+ const absl::flat_hash_map<uint32_t, AudioElementWithData> kNoAudioElements;
+ const std::list<MixPresentationObu> kNoMixPresentationObus;
+ const std::list<ArbitraryObu> kNoArbitraryObus;
+ MockObuSequencer mock_obu_sequencer(
+ *LebGenerator::Create(), kDoNotIncludeTemporalDelimiters,
+ kDoNotDelayDescriptorsUntilTrimAtStartIsKnown);
+ EXPECT_THAT(mock_obu_sequencer.PushDescriptorObus(
+ kOriginalIaSequenceHeader, kNoCodecConfigObus,
+ kNoAudioElements, kNoMixPresentationObus, kNoArbitraryObus),
+ IsOk());
+
+ // Derived classes may assume the descriptor OBUs are the same size, to
+ // permit writes in place. We could lift this restriction, but it's not
+ // clear it's worth the effort.
+ EXPECT_THAT(mock_obu_sequencer.UpdateDescriptorObusAndClose(
+ kResizedIaSequenceHeader, kNoCodecConfigObus,
+ kNoAudioElements, kNoMixPresentationObus, kNoArbitraryObus),
+ Not(IsOk()));
+}
+
+TEST(UpdateDescriptorObusAndClose, FailsWhenCodecConfigPropertiesChange) {
+ const IASequenceHeaderObu kIaSequenceHeader(
+ ObuHeader(), IASequenceHeaderObu::kIaCode,
+ ProfileVersion::kIamfSimpleProfile, ProfileVersion::kIamfBaseProfile);
+ absl::flat_hash_map<DecodedUleb128, CodecConfigObu>
+ original_codec_config_obus;
+ AddLpcmCodecConfigWithIdAndSampleRate(kCodecConfigId, 48000,
+ original_codec_config_obus);
+ absl::flat_hash_map<DecodedUleb128, CodecConfigObu>
+ modified_codec_config_obus;
+ AddLpcmCodecConfigWithIdAndSampleRate(kCodecConfigId, 44100,
+ modified_codec_config_obus);
+ const absl::flat_hash_map<uint32_t, AudioElementWithData> kNoAudioElements;
+ const std::list<MixPresentationObu> kNoMixPresentationObus;
+ const std::list<ArbitraryObu> kNoArbitraryObus;
+ MockObuSequencer mock_obu_sequencer(
+ *LebGenerator::Create(), kDoNotIncludeTemporalDelimiters,
+ kDoNotDelayDescriptorsUntilTrimAtStartIsKnown);
+ EXPECT_THAT(mock_obu_sequencer.PushDescriptorObus(
+ kIaSequenceHeader, original_codec_config_obus,
+ kNoAudioElements, kNoMixPresentationObus, kNoArbitraryObus),
+ IsOk());
+
+ EXPECT_THAT(mock_obu_sequencer.UpdateDescriptorObusAndClose(
+ kIaSequenceHeader, modified_codec_config_obus,
+ kNoAudioElements, kNoMixPresentationObus, kNoArbitraryObus),
+ Not(IsOk()));
+}
+
} // namespace
} // namespace iamf_tools
diff --git a/iamf/cli/tests/obu_sequencer_iamf_test.cc b/iamf/cli/tests/obu_sequencer_iamf_test.cc
index 3e539f8..12c9078 100644
--- a/iamf/cli/tests/obu_sequencer_iamf_test.cc
+++ b/iamf/cli/tests/obu_sequencer_iamf_test.cc
@@ -28,6 +28,7 @@
#include "iamf/cli/audio_element_with_data.h"
#include "iamf/cli/audio_frame_with_data.h"
#include "iamf/cli/parameter_block_with_data.h"
+#include "iamf/cli/temporal_unit_view.h"
#include "iamf/cli/tests/cli_test_utils.h"
#include "iamf/common/leb_generator.h"
#include "iamf/common/read_bit_buffer.h"
@@ -126,7 +127,7 @@
});
}
-class ObuSequencerTest : public ::testing::Test {
+class ObuSequencerIamfTest : public ::testing::Test {
public:
void InitializeDescriptorObus() {
ia_sequence_header_obu_.emplace(ObuHeader(), IASequenceHeaderObu::kIaCode,
@@ -270,7 +271,7 @@
IsOk());
}
-TEST_F(ObuSequencerTest, PickAndPlaceCreatesFileWithOneFrameIaSequence) {
+TEST_F(ObuSequencerIamfTest, PickAndPlaceCreatesFileWithOneFrameIaSequence) {
const std::string kOutputIamfFilename = GetAndCleanupOutputFileName(".iamf");
InitObusForOneFrameIaSequence();
ObuSequencerIamf sequencer(kOutputIamfFilename,
@@ -286,7 +287,7 @@
EXPECT_TRUE(std::filesystem::exists(kOutputIamfFilename));
}
-TEST_F(ObuSequencerTest, PickAndPlaceFileCanBeReadBacks) {
+TEST_F(ObuSequencerIamfTest, PickAndPlaceFileCanBeReadBacks) {
const std::string kOutputIamfFilename = GetAndCleanupOutputFileName(".iamf");
InitializeDescriptorObus();
AddEmptyAudioFrameWithAudioElementIdSubstreamIdAndTimestamps(
@@ -327,7 +328,8 @@
EXPECT_TRUE(parameter_blocks.empty());
}
-TEST_F(ObuSequencerTest, PickAndPlaceLeavesNoFileWhenDescriptorsAreInvalid) {
+TEST_F(ObuSequencerIamfTest,
+ PickAndPlaceLeavesNoFileWhenDescriptorsAreInvalid) {
constexpr uint32_t kInvalidIaCode = IASequenceHeaderObu::kIaCode + 1;
const std::string kOutputIamfFilename = GetAndCleanupOutputFileName(".iamf");
InitObusForOneFrameIaSequence();
@@ -349,7 +351,8 @@
EXPECT_FALSE(std::filesystem::exists(kOutputIamfFilename));
}
-TEST_F(ObuSequencerTest, PickAndPlaceLeavesNoFileWhenTemporalUnitsAreInvalid) {
+TEST_F(ObuSequencerIamfTest,
+ PickAndPlaceLeavesNoFileWhenTemporalUnitsAreInvalid) {
constexpr bool kInvalidateTemporalUnit = true;
const std::string kOutputIamfFilename = GetAndCleanupOutputFileName(".iamf");
InitObusForOneFrameIaSequence();
@@ -371,7 +374,7 @@
EXPECT_FALSE(std::filesystem::exists(kOutputIamfFilename));
}
-TEST_F(ObuSequencerTest,
+TEST_F(ObuSequencerIamfTest,
PickAndPlaceOnInvalidTemporalUnitFailsWhenOutputFileIsOmitted) {
constexpr bool kInvalidateTemporalUnit = true;
InitObusForOneFrameIaSequence();
@@ -391,5 +394,57 @@
.ok());
}
+TEST_F(ObuSequencerIamfTest,
+ FileContainsUpdatedDescriptorObusAfterUpdateDescriptorObusAndClose) {
+ const ProfileVersion kOriginalProfile = ProfileVersion::kIamfBaseProfile;
+ const ProfileVersion kUpdatedProfile =
+ ProfileVersion::kIamfBaseEnhancedProfile;
+ InitObusForOneFrameIaSequence();
+ parameter_blocks_.clear();
+ ia_sequence_header_obu_ =
+ IASequenceHeaderObu(ObuHeader(), IASequenceHeaderObu::kIaCode,
+ kOriginalProfile, kOriginalProfile);
+ const std::string kOutputIamfFilename = GetAndCleanupOutputFileName(".iamf");
+ ObuSequencerIamf sequencer(kOutputIamfFilename,
+ kDoNotIncludeTemporalDelimiters,
+ *LebGenerator::Create());
+ EXPECT_THAT(sequencer.PushDescriptorObus(
+ *ia_sequence_header_obu_, codec_config_obus_, audio_elements_,
+ mix_presentation_obus_, arbitrary_obus_),
+ IsOk());
+ const auto temporal_unit = TemporalUnitView::Create(
+ parameter_blocks_, audio_frames_, arbitrary_obus_);
+ ASSERT_THAT(temporal_unit, IsOk());
+ EXPECT_THAT(sequencer.PushTemporalUnit(*temporal_unit), IsOk());
+ // As a toy example, we will update the IA Sequence Header.
+ ia_sequence_header_obu_ =
+ IASequenceHeaderObu(ObuHeader(), IASequenceHeaderObu::kIaCode,
+ kUpdatedProfile, kUpdatedProfile);
+
+ // Finalize the descriptor OBUs with a new IA Sequence Header.
+ EXPECT_THAT(sequencer.UpdateDescriptorObusAndClose(
+ *ia_sequence_header_obu_, codec_config_obus_, audio_elements_,
+ mix_presentation_obus_, arbitrary_obus_),
+ IsOk());
+
+ auto read_bit_buffer = FileBasedReadBitBuffer::CreateFromFilePath(
+ kReadBitBufferCapacity, kOutputIamfFilename);
+ ASSERT_NE(read_bit_buffer, nullptr);
+ IASequenceHeaderObu read_ia_sequence_header;
+ absl::flat_hash_map<DecodedUleb128, CodecConfigObu> read_codec_config_obus;
+ absl::flat_hash_map<DecodedUleb128, AudioElementWithData> read_audio_elements;
+ std::list<MixPresentationObu> read_mix_presentation_obus;
+ std::list<AudioFrameWithData> read_audio_frames;
+ std::list<ParameterBlockWithData> read_parameter_blocks;
+ EXPECT_THAT(
+ CollectObusFromIaSequence(*read_bit_buffer, read_ia_sequence_header,
+ read_codec_config_obus, read_audio_elements,
+ read_mix_presentation_obus, read_audio_frames,
+ read_parameter_blocks),
+ IsOk());
+ // Finally we expect to see evidence of the modified IA Sequence Header.
+ EXPECT_EQ(read_ia_sequence_header.GetPrimaryProfile(), kUpdatedProfile);
+}
+
} // namespace
} // namespace iamf_tools
diff --git a/iamf/cli/tests/profile_filter_test.cc b/iamf/cli/tests/profile_filter_test.cc
index 8154681..9b90334 100644
--- a/iamf/cli/tests/profile_filter_test.cc
+++ b/iamf/cli/tests/profile_filter_test.cc
@@ -453,7 +453,6 @@
kCommonMixGainParameterRate;
common_mix_gain_param_definition.param_definition_mode_ = true;
common_mix_gain_param_definition.default_mix_gain_ = 0;
- constexpr DecodedUleb128 kNumAudioElementsPerSubmix = 1;
const RenderingConfig kRenderingConfig = {
.headphones_rendering_mode =
RenderingConfig::kHeadphonesRenderingModeStereo,
@@ -471,30 +470,26 @@
.loudness = {
.info_type = 0, .integrated_loudness = 0, .digital_peak = 0}};
std::vector<MixPresentationSubMix> sub_mixes;
- sub_mixes.push_back({.num_audio_elements = kNumAudioElementsPerSubmix,
- .audio_elements = {{
+ sub_mixes.push_back({.audio_elements = {{
.audio_element_id = kFirstAudioElementId,
.localized_element_annotations = {},
.rendering_config = kRenderingConfig,
.element_mix_gain = common_mix_gain_param_definition,
}},
.output_mix_gain = common_mix_gain_param_definition,
- .num_layouts = 1,
.layouts = {kStereoLayout}});
- sub_mixes.push_back({.num_audio_elements = kNumAudioElementsPerSubmix,
- .audio_elements = {{
+ sub_mixes.push_back({.audio_elements = {{
.audio_element_id = kSecondAudioElementId,
.localized_element_annotations = {},
.rendering_config = kRenderingConfig,
.element_mix_gain = common_mix_gain_param_definition,
}},
.output_mix_gain = common_mix_gain_param_definition,
- .num_layouts = 1,
.layouts = {kStereoLayout}});
- mix_presentation_obus.push_back(MixPresentationObu(
- ObuHeader(), kFirstMixPresentationId,
- /*count_label=*/0, {}, {}, sub_mixes.size(), sub_mixes));
+ mix_presentation_obus.push_back(
+ MixPresentationObu(ObuHeader(), kFirstMixPresentationId,
+ /*count_label=*/0, {}, {}, sub_mixes));
}
TEST(FilterProfilesForMixPresentation,
diff --git a/iamf/cli/tests/temporal_unit_view_test.cc b/iamf/cli/tests/temporal_unit_view_test.cc
index 965aa27..10e3760 100644
--- a/iamf/cli/tests/temporal_unit_view_test.cc
+++ b/iamf/cli/tests/temporal_unit_view_test.cc
@@ -288,6 +288,40 @@
EXPECT_EQ(temporal_unit->arbitrary_obus_, arbitrary_obus_ptrs);
}
+TEST(Create, SetsStartTimestamp) {
+ constexpr InternalTimestamp kExpectedStartTimestamp = 123456789;
+ constexpr InternalTimestamp kEndTimestamp = kExpectedStartTimestamp + 8;
+ absl::flat_hash_map<DecodedUleb128, CodecConfigObu> codec_config_obus;
+ absl::flat_hash_map<uint32_t, AudioElementWithData> audio_elements;
+ std::list<AudioFrameWithData> audio_frames;
+ InitializeOneFrameIaSequence(codec_config_obus, audio_elements, audio_frames);
+ audio_frames.front().start_timestamp = kExpectedStartTimestamp;
+ audio_frames.front().end_timestamp = kEndTimestamp;
+
+ const auto temporal_unit = TemporalUnitView::Create(
+ kNoParameterBlocks, audio_frames, kNoArbitraryObus);
+ ASSERT_THAT(temporal_unit, IsOk());
+
+ EXPECT_EQ(temporal_unit->start_timestamp_, kExpectedStartTimestamp);
+}
+
+TEST(Create, SetsEndTimestamp) {
+ constexpr InternalTimestamp kStartTimestamp = 123456789;
+ constexpr InternalTimestamp kExpectedEndTimestamp = kStartTimestamp + 8;
+ absl::flat_hash_map<DecodedUleb128, CodecConfigObu> codec_config_obus;
+ absl::flat_hash_map<uint32_t, AudioElementWithData> audio_elements;
+ std::list<AudioFrameWithData> audio_frames;
+ InitializeOneFrameIaSequence(codec_config_obus, audio_elements, audio_frames);
+ audio_frames.front().start_timestamp = kStartTimestamp;
+ audio_frames.front().end_timestamp = kExpectedEndTimestamp;
+
+ const auto temporal_unit = TemporalUnitView::Create(
+ kNoParameterBlocks, audio_frames, kNoArbitraryObus);
+ ASSERT_THAT(temporal_unit, IsOk());
+
+ EXPECT_EQ(temporal_unit->end_timestamp_, kExpectedEndTimestamp);
+}
+
TEST(Create, SetsNumSamplesToTrimAtStart) {
absl::flat_hash_map<DecodedUleb128, CodecConfigObu> codec_config_obus;
absl::flat_hash_map<uint32_t, AudioElementWithData> audio_elements;
diff --git a/iamf/cli/wav_writer.cc b/iamf/cli/wav_writer.cc
index e67ef59..2e4269a 100644
--- a/iamf/cli/wav_writer.cc
+++ b/iamf/cli/wav_writer.cc
@@ -225,7 +225,7 @@
std::vector<uint8_t> samples_as_pcm(num_channels * num_ticks * bit_depth_ / 8,
0);
- int write_position = 0;
+ size_t write_position = 0;
for (const auto& tick : time_channel_samples) {
for (const auto& channel_sample : tick) {
RETURN_IF_NOT_OK(WritePcmSample(channel_sample, bit_depth_,
diff --git a/iamf/common/utils/sample_processing_utils.cc b/iamf/common/utils/sample_processing_utils.cc
index bde2910..0f0d13c 100644
--- a/iamf/common/utils/sample_processing_utils.cc
+++ b/iamf/common/utils/sample_processing_utils.cc
@@ -11,6 +11,7 @@
*/
#include "iamf/common/utils/sample_processing_utils.h"
+#include <cstddef>
#include <cstdint>
#include "absl/log/check.h"
@@ -22,16 +23,16 @@
absl::Status WritePcmSample(uint32_t sample, uint8_t sample_size,
bool big_endian, uint8_t* const buffer,
- int& write_position) {
+ size_t& write_position) {
// Validate assumptions of the logic in the `for` loop below.
- if (sample_size % 8 != 0 || sample_size > 32) {
+ if (sample_size % 8 != 0 || sample_size > 32) [[unlikely]] {
return absl::InvalidArgumentError(
absl::StrCat("Invalid sample size: ", sample_size));
}
for (int shift = 32 - sample_size; shift < 32; shift += 8) {
uint8_t byte = 0;
- if (big_endian) {
+ if (big_endian) [[unlikely]] {
byte = (sample >> ((32 - sample_size) + (32 - (shift + 8)))) & 0xff;
} else {
byte = (sample >> shift) & 0xff;
diff --git a/iamf/common/utils/sample_processing_utils.h b/iamf/common/utils/sample_processing_utils.h
index a4de471..d3f9f8a 100644
--- a/iamf/common/utils/sample_processing_utils.h
+++ b/iamf/common/utils/sample_processing_utils.h
@@ -44,7 +44,7 @@
*/
absl::Status WritePcmSample(uint32_t sample, uint8_t sample_size,
bool big_endian, uint8_t* buffer,
- int& write_position);
+ size_t& write_position);
/*!\brief Arranges the input samples by time and channel.
*
diff --git a/iamf/common/utils/tests/sample_processing_utils_test.cc b/iamf/common/utils/tests/sample_processing_utils_test.cc
index c06a260..6af390b 100644
--- a/iamf/common/utils/tests/sample_processing_utils_test.cc
+++ b/iamf/common/utils/tests/sample_processing_utils_test.cc
@@ -33,7 +33,7 @@
TEST(WritePcmSample, LittleEndian32Bits) {
std::vector<uint8_t> buffer(4, 0);
- int write_position = 0;
+ size_t write_position = 0;
EXPECT_THAT(WritePcmSample(0x12345678, 32, /*big_endian=*/false,
buffer.data(), write_position),
IsOk());
@@ -44,7 +44,7 @@
TEST(WritePcmSample, BigEndian32bits) {
std::vector<uint8_t> buffer(4, 0);
- int write_position = 0;
+ size_t write_position = 0;
EXPECT_THAT(WritePcmSample(0x12345678, 32, /*big_endian=*/true, buffer.data(),
write_position),
IsOk());
@@ -55,7 +55,7 @@
TEST(WritePcmSample, LittleEndian24Bits) {
std::vector<uint8_t> buffer(3, 0);
- int write_position = 0;
+ size_t write_position = 0;
EXPECT_THAT(WritePcmSample(0x12345600, 24, /*big_endian=*/false,
buffer.data(), write_position),
IsOk());
@@ -66,7 +66,7 @@
TEST(WritePcmSample, BigEndian24Bits) {
std::vector<uint8_t> buffer(3, 0);
- int write_position = 0;
+ size_t write_position = 0;
EXPECT_THAT(WritePcmSample(0x12345600, 24, /*big_endian=*/true, buffer.data(),
write_position),
IsOk());
@@ -77,7 +77,7 @@
TEST(WritePcmSample, LittleEndian16Bits) {
std::vector<uint8_t> buffer(2, 0);
- int write_position = 0;
+ size_t write_position = 0;
EXPECT_THAT(WritePcmSample(0x12340000, 16, /*big_endian=*/false,
buffer.data(), write_position),
IsOk());
@@ -88,7 +88,7 @@
TEST(WritePcmSample, BigEndian16Bits) {
std::vector<uint8_t> buffer(2, 0);
- int write_position = 0;
+ size_t write_position = 0;
EXPECT_THAT(WritePcmSample(0x12340000, 16, /*big_endian=*/true, buffer.data(),
write_position),
IsOk());
@@ -99,7 +99,7 @@
TEST(WritePcmSample, InvalidOver32Bits) {
std::vector<uint8_t> buffer(5, 0);
- int write_position = 0;
+ size_t write_position = 0;
EXPECT_EQ(WritePcmSample(0x00000000, 40, /*big_endian=*/false, buffer.data(),
write_position)
.code(),
diff --git a/iamf/obu/BUILD b/iamf/obu/BUILD
index 1715813..7ba1922 100644
--- a/iamf/obu/BUILD
+++ b/iamf/obu/BUILD
@@ -119,6 +119,7 @@
"//iamf/common:write_bit_buffer",
"//iamf/common/utils:macros",
"//iamf/common/utils:map_utils",
+ "//iamf/common/utils:numeric_utils",
"//iamf/common/utils:validation_utils",
"@com_google_absl//absl/base:no_destructor",
"@com_google_absl//absl/container:flat_hash_map",
diff --git a/iamf/obu/mix_presentation.cc b/iamf/obu/mix_presentation.cc
index cd25fe7..40b32c5 100644
--- a/iamf/obu/mix_presentation.cc
+++ b/iamf/obu/mix_presentation.cc
@@ -25,6 +25,7 @@
#include "iamf/common/read_bit_buffer.h"
#include "iamf/common/utils/macros.h"
#include "iamf/common/utils/map_utils.h"
+#include "iamf/common/utils/numeric_utils.h"
#include "iamf/common/utils/validation_utils.h"
#include "iamf/common/write_bit_buffer.h"
#include "iamf/obu/obu_header.h"
@@ -137,11 +138,11 @@
layout.loudness.anchored_loudness.anchor_elements));
const AnchoredLoudness& anchored_loudness =
layout.loudness.anchored_loudness;
- RETURN_IF_NOT_OK(
- wb.WriteUnsignedLiteral(anchored_loudness.num_anchored_loudness, 8));
- RETURN_IF_NOT_OK(ValidateContainerSizeEqual(
- "anchor_elements", anchored_loudness.anchor_elements,
- anchored_loudness.num_anchored_loudness));
+ uint8_t num_anchor_elements;
+ RETURN_IF_NOT_OK(StaticCastIfInRange(
+ "num_anchor_elements", anchored_loudness.anchor_elements.size(),
+ num_anchor_elements));
+ RETURN_IF_NOT_OK(wb.WriteUnsignedLiteral(num_anchor_elements, 8));
for (const auto& anchor_element : anchored_loudness.anchor_elements) {
RETURN_IF_NOT_OK(wb.WriteUnsignedLiteral(
static_cast<uint8_t>(anchor_element.anchor_element), 8));
@@ -166,27 +167,25 @@
const MixPresentationSubMix& sub_mix,
WriteBitBuffer& wb) {
// IAMF requires there to be at least one audio element.
- RETURN_IF_NOT_OK(ValidateNotEqual(
- DecodedUleb128{0}, sub_mix.num_audio_elements, "num_audio_elements"));
+ const DecodedUleb128 num_audio_elements = sub_mix.audio_elements.size();
+ RETURN_IF_NOT_OK(ValidateNotEqual(DecodedUleb128{0}, num_audio_elements,
+ "num_audio_elements"));
// Write the main portion of a `MixPresentationSubMix`.
- RETURN_IF_NOT_OK(wb.WriteUleb128(sub_mix.num_audio_elements));
+ RETURN_IF_NOT_OK(wb.WriteUleb128(num_audio_elements));
// Loop to write the `audio_elements` array.
- RETURN_IF_NOT_OK(ValidateContainerSizeEqual(
- "audio_elements", sub_mix.audio_elements, sub_mix.num_audio_elements));
for (const auto& sub_mix_audio_element : sub_mix.audio_elements) {
RETURN_IF_NOT_OK(ValidateAndWriteSubMixAudioElement(
count_label, sub_mix_audio_element, wb));
}
RETURN_IF_NOT_OK(sub_mix.output_mix_gain.ValidateAndWrite(wb));
- RETURN_IF_NOT_OK(wb.WriteUleb128(sub_mix.num_layouts));
+ const DecodedUleb128 num_layouts = sub_mix.layouts.size();
+ RETURN_IF_NOT_OK(wb.WriteUleb128(num_layouts));
// Loop to write the `layouts` array.
bool found_stereo_layout = false;
- RETURN_IF_NOT_OK(ValidateContainerSizeEqual("layouts", sub_mix.layouts,
- sub_mix.num_layouts));
for (const auto& layout : sub_mix.layouts) {
RETURN_IF_NOT_OK(ValidateAndWriteLayout(layout, found_stereo_layout, wb));
}
@@ -246,10 +245,10 @@
}
// Conditionally read `anchored_loudness` based on `info_type`.
if (loudness.info_type & LoudnessInfo::kAnchoredLoudness) {
- RETURN_IF_NOT_OK(rb.ReadUnsignedLiteral(
- 8, loudness.anchored_loudness.num_anchored_loudness));
+ uint8_t num_anchored_loudness;
+ RETURN_IF_NOT_OK(rb.ReadUnsignedLiteral(8, num_anchored_loudness));
- for (int i = 0; i < loudness.anchored_loudness.num_anchored_loudness; ++i) {
+ for (int i = 0; i < num_anchored_loudness; ++i) {
AnchoredLoudnessElement anchor_loudness_element;
uint8_t anchor_element;
RETURN_IF_NOT_OK(rb.ReadUnsignedLiteral(8, anchor_element));
@@ -308,6 +307,7 @@
absl::Status MixPresentationSubMix::ReadAndValidate(const int32_t& count_label,
ReadBitBuffer& rb) {
+ DecodedUleb128 num_audio_elements;
RETURN_IF_NOT_OK(rb.ReadULeb128(num_audio_elements));
// IAMF requires there to be at least one audio element.
RETURN_IF_NOT_OK(ValidateNotEqual(DecodedUleb128{0}, num_audio_elements,
@@ -319,6 +319,7 @@
}
RETURN_IF_NOT_OK(output_mix_gain.ReadAndValidate(rb));
+ DecodedUleb128 num_layouts;
RETURN_IF_NOT_OK(rb.ReadULeb128(num_layouts));
for (int i = 0; i < num_layouts; ++i) {
MixPresentationLayout mix_presentation_layout;
@@ -343,8 +344,9 @@
}
absl::Status MixPresentationTags::ValidateAndWrite(WriteBitBuffer& wb) const {
+ uint8_t num_tags;
+ RETURN_IF_NOT_OK(StaticCastIfInRange("num_tags", tags.size(), num_tags));
RETURN_IF_NOT_OK(wb.WriteUnsignedLiteral(num_tags, 8));
- RETURN_IF_NOT_OK(ValidateContainerSizeEqual("tags", tags, num_tags));
int count_content_language_tag = 0;
@@ -472,13 +474,12 @@
RETURN_IF_NOT_OK(wb.WriteString(localized_presentation_annotation));
}
- RETURN_IF_NOT_OK(wb.WriteUleb128(num_sub_mixes_));
+ const DecodedUleb128 num_sub_mixes = sub_mixes_.size();
+ RETURN_IF_NOT_OK(wb.WriteUleb128(num_sub_mixes));
// Loop to write the `sub_mixes` array.
- RETURN_IF_NOT_OK(ValidateNumSubMixes(num_sub_mixes_));
+ RETURN_IF_NOT_OK(ValidateNumSubMixes(num_sub_mixes));
RETURN_IF_NOT_OK(ValidateUniqueAudioElementIds(sub_mixes_));
- RETURN_IF_NOT_OK(
- ValidateContainerSizeEqual("sub_mixes", sub_mixes_, num_sub_mixes_));
for (const auto& sub_mix : sub_mixes_) {
RETURN_IF_NOT_OK(ValidateAndWriteSubMix(count_label_, sub_mix, wb));
}
@@ -520,10 +521,11 @@
localized_presentation_annotation);
}
- RETURN_IF_NOT_OK(rb.ReadULeb128(num_sub_mixes_));
+ DecodedUleb128 num_sub_mixes;
+ RETURN_IF_NOT_OK(rb.ReadULeb128(num_sub_mixes));
// Loop to read the `sub_mixes` array.
- for (int i = 0; i < num_sub_mixes_; ++i) {
+ for (int i = 0; i < num_sub_mixes; ++i) {
MixPresentationSubMix sub_mix;
RETURN_IF_NOT_OK(sub_mix.ReadAndValidate(count_label_, rb));
sub_mixes_.push_back(sub_mix);
@@ -531,7 +533,7 @@
// TODO(b/329705373): Examine how many bytes were read so far. Use this to
// determine if Mix Presentation Tags should be read.
- RETURN_IF_NOT_OK(ValidateNumSubMixes(num_sub_mixes_));
+ RETURN_IF_NOT_OK(ValidateNumSubMixes(num_sub_mixes));
RETURN_IF_NOT_OK(ValidateUniqueAudioElementIds(sub_mixes_));
return absl::OkStatus();
@@ -560,15 +562,15 @@
LOG(INFO) << " localized_presentation_annotations[" << i << "]= \""
<< localized_presentation_annotations_[i] << "\"";
}
- LOG(INFO) << " num_sub_mixes= " << num_sub_mixes_;
+ LOG(INFO) << " num_sub_mixes= " << sub_mixes_.size();
// Submixes.
- for (int i = 0; i < num_sub_mixes_; ++i) {
+ for (int i = 0; i < sub_mixes_.size(); ++i) {
const auto& sub_mix = sub_mixes_[i];
LOG(INFO) << " // sub_mixes[" << i << "]:";
- LOG(INFO) << " num_audio_elements= " << sub_mix.num_audio_elements;
+ LOG(INFO) << " num_audio_elements= " << sub_mix.audio_elements.size();
// Audio elements.
- for (int j = 0; j < sub_mix.num_audio_elements; ++j) {
+ for (int j = 0; j < sub_mix.audio_elements.size(); ++j) {
const auto& audio_element = sub_mix.audio_elements[j];
LOG(INFO) << " // audio_elements[" << j << "]:";
LOG(INFO) << " audio_element_id= " << audio_element.audio_element_id;
@@ -594,10 +596,10 @@
LOG(INFO) << " output_mix_gain:";
sub_mix.output_mix_gain.Print();
- LOG(INFO) << " num_layouts= " << sub_mix.num_layouts;
+ LOG(INFO) << " num_layouts= " << sub_mix.layouts.size();
// Layouts.
- for (int j = 0; j < sub_mix.num_layouts; j++) {
+ for (int j = 0; j < sub_mix.layouts.size(); j++) {
const auto& layout = sub_mix.layouts[j];
LOG(INFO) << " // layouts[" << j << "]:";
LOG(INFO) << " loudness_layout:";
@@ -634,7 +636,7 @@
const auto& anchored_loudness = loudness.anchored_loudness;
LOG(INFO) << " anchored_loudness: ";
LOG(INFO) << " num_anchored_loudness= "
- << absl::StrCat(anchored_loudness.num_anchored_loudness);
+ << absl::StrCat(anchored_loudness.anchor_elements.size());
for (int i = 0; i < anchored_loudness.anchor_elements.size(); i++) {
LOG(INFO) << " anchor_element[" << i << "]= "
<< absl::StrCat(
@@ -656,6 +658,17 @@
}
}
}
+ if (mix_presentation_tags_.has_value()) {
+ LOG(INFO) << " mix_presentation_tags:";
+ for (int i = 0; i < mix_presentation_tags_->tags.size(); ++i) {
+ const auto& tag = mix_presentation_tags_->tags[i];
+ LOG(INFO) << " tags[" << i << "]:";
+ LOG(INFO) << " tag_name= \"" << tag.tag_name << "\"";
+ LOG(INFO) << " tag_value= \"" << tag.tag_value << "\"";
+ }
+ } else {
+ LOG(INFO) << " No mix_presentation_tags detected.";
+ }
}
} // namespace iamf_tools
diff --git a/iamf/obu/mix_presentation.h b/iamf/obu/mix_presentation.h
index 08a5c22..19b81f8 100644
--- a/iamf/obu/mix_presentation.h
+++ b/iamf/obu/mix_presentation.h
@@ -92,8 +92,8 @@
friend bool operator==(const AnchoredLoudness& lhs,
const AnchoredLoudness& rhs) = default;
- uint8_t num_anchored_loudness = 0;
- // Length `num_anchored_loudness`.
+ // `num_anchored_loudness` is implicit based on the size of
+ // `anchor_elements`.
std::vector<AnchoredLoudnessElement> anchor_elements = {};
};
@@ -293,16 +293,14 @@
*/
absl::Status ReadAndValidate(const int32_t& count_label, ReadBitBuffer& rb);
- DecodedUleb128 num_audio_elements;
- // Length `num_audio_elements`.
+ // `num_audio_elements` is implicit based on the size of `audio_elements`.
std::vector<SubMixAudioElement> audio_elements;
// The gain value to be applied in post-processing the mixed audio signal to
// generate the audio signal for playback.
MixGainParamDefinition output_mix_gain;
- DecodedUleb128 num_layouts;
- // Length `num_layouts`.
+ // `num_layouts` is implicit based on the size of `layouts`.
std::vector<MixPresentationLayout> layouts;
};
@@ -325,7 +323,7 @@
*/
absl::Status ValidateAndWrite(WriteBitBuffer& wb) const;
- uint8_t num_tags;
+ // `num_tags` is implicit based on the size of `tags`.
std::vector<Tag> tags;
};
@@ -366,7 +364,6 @@
* `annotations_language`s in the OBU.
* \param localized_presentation_annotations Vector representing all of the
* `localized_presentation_annotations`s in the OBU.
- * \param num_sub_mixes `num_sub_mixes` in the OBU.
* \param sub_mixes Vector representing all of the sub mixes in the OBU.
*/
MixPresentationObu(
@@ -374,15 +371,14 @@
DecodedUleb128 count_label,
const std::vector<std::string>& annotations_language,
const std::vector<std::string>& localized_presentation_annotations,
- DecodedUleb128 num_sub_mixes,
std::vector<MixPresentationSubMix>& sub_mixes)
: ObuBase(header, kObuIaMixPresentation),
sub_mixes_(std::move(sub_mixes)),
mix_presentation_id_(mix_presentation_id),
count_label_(count_label),
annotations_language_(annotations_language),
- localized_presentation_annotations_(localized_presentation_annotations),
- num_sub_mixes_(num_sub_mixes) {}
+ localized_presentation_annotations_(
+ localized_presentation_annotations) {}
/*!\brief Creates a `MixPresentationObu` from a `ReadBitBuffer`.
*
@@ -426,7 +422,7 @@
return localized_presentation_annotations_;
}
- DecodedUleb128 GetNumSubMixes() const { return num_sub_mixes_; }
+ DecodedUleb128 GetNumSubMixes() const { return sub_mixes_.size(); }
std::vector<MixPresentationSubMix> sub_mixes_;
@@ -442,7 +438,7 @@
// Length `count_label`.
std::vector<std::string> localized_presentation_annotations_;
- DecodedUleb128 num_sub_mixes_;
+ // `num_sub_mixes_` is implicit based on the size of `sub_mixes_`.
// Used only by the factory create function.
explicit MixPresentationObu(const ObuHeader& header)
@@ -451,8 +447,7 @@
mix_presentation_id_(DecodedUleb128()),
count_label_(DecodedUleb128()),
annotations_language_({}),
- localized_presentation_annotations_({}),
- num_sub_mixes_(DecodedUleb128()) {}
+ localized_presentation_annotations_({}) {}
/*!\brief Writes the OBU payload to the buffer.
*
* \param wb Buffer to write to.
diff --git a/iamf/obu/tests/mix_presentation_test.cc b/iamf/obu/tests/mix_presentation_test.cc
index def25da..b046b1c 100644
--- a/iamf/obu/tests/mix_presentation_test.cc
+++ b/iamf/obu/tests/mix_presentation_test.cc
@@ -79,10 +79,8 @@
count_label_(1),
annotations_language_({"en-us"}),
localized_presentation_annotations_({"Mix 1"}),
- num_sub_mixes_(1),
sub_mixes_(
- {{.num_audio_elements = 1,
- .audio_elements = {{
+ {{.audio_elements = {{
.audio_element_id = 11,
.localized_element_annotations = {"Submix 1"},
.rendering_config =
@@ -92,7 +90,6 @@
.rendering_config_extension_size = 0,
.rendering_config_extension_bytes = {}},
}},
- .num_layouts = 1,
.layouts = {{.loudness_layout =
{.layout_type =
Layout::kLayoutTypeLoudspeakersSsConvention,
@@ -143,7 +140,6 @@
// Length `count_label`.
std::vector<std::string> localized_presentation_annotations_;
- DecodedUleb128 num_sub_mixes_;
// Length `num_sub_mixes`.
std::vector<MixPresentationSubMix> sub_mixes_;
@@ -205,7 +201,7 @@
// Construct and transfer ownership of the memory to the OBU.
obu_ = std::make_unique<MixPresentationObu>(
header_, mix_presentation_id_, count_label_, annotations_language_,
- localized_presentation_annotations_, num_sub_mixes_, sub_mixes_);
+ localized_presentation_annotations_, sub_mixes_);
}
};
@@ -252,7 +248,6 @@
}
TEST_F(MixPresentationObuTest, ValidateAndWriteFailsWithInvalidNumSubMixes) {
- num_sub_mixes_ = 0;
sub_mixes_.clear();
dynamic_sub_mix_args_.clear();
@@ -301,7 +296,6 @@
ASSERT_EQ(sub_mixes_.size(), 1);
ASSERT_EQ(sub_mixes_[0].audio_elements.size(), 1);
// Add an extra audio element to sub-mix.
- sub_mixes_[0].num_audio_elements = 2;
sub_mixes_[0].audio_elements.push_back(sub_mixes_[0].audio_elements[0]);
dynamic_sub_mix_args_[0].element_mix_gain_subblocks = {{}, {}};
@@ -320,7 +314,6 @@
ASSERT_EQ(sub_mixes_.size(), 1);
// Reconfigure the sub-mix to have no audio elements and no `element_mix`
// gains which are typically 1:1 with the audio elements.
- sub_mixes_[0].num_audio_elements = 0;
sub_mixes_[0].audio_elements.clear();
dynamic_sub_mix_args_[0].element_mix_gain_subblocks.clear();
@@ -332,7 +325,6 @@
TEST_F(MixPresentationObuTest, TwoAnchorElements) {
sub_mixes_[0].layouts[0].loudness.info_type = LoudnessInfo::kAnchoredLoudness;
sub_mixes_[0].layouts[0].loudness.anchored_loudness = {
- 2,
{{.anchor_element = AnchoredLoudnessElement::kAnchorElementAlbum,
.anchored_loudness = 20},
{.anchor_element = AnchoredLoudnessElement::kAnchorElementDialogue,
@@ -369,7 +361,6 @@
LoudnessInfo::kAnchoredLoudness | LoudnessInfo::kTruePeak;
sub_mixes_[0].layouts[0].loudness.true_peak = 22;
sub_mixes_[0].layouts[0].loudness.anchored_loudness = {
- 1,
{{.anchor_element = AnchoredLoudnessElement::kAnchorElementAlbum,
.anchored_loudness = 20}}};
@@ -405,7 +396,6 @@
ValidateAndWriteFailsWithInvalidNonUniqueAnchorElement) {
sub_mixes_[0].layouts[0].loudness.info_type = LoudnessInfo::kAnchoredLoudness;
sub_mixes_[0].layouts[0].loudness.anchored_loudness = {
- 2,
{{.anchor_element = AnchoredLoudnessElement::kAnchorElementAlbum,
.anchored_loudness = 20},
{.anchor_element = AnchoredLoudnessElement::kAnchorElementAlbum,
@@ -468,7 +458,7 @@
0x80 | 1, 0x00,
// `language_label` and `mix_presentation_annotations`.
'e', 'n', '-', 'u', 's', '\0', 'M', 'i', 'x', ' ', '1', '\0',
- // `num_submixes` is affected by the `LebGenerator`.
+ // `num_sub_mixes` is affected by the `LebGenerator`.
0x80 | 1, 0x00,
// Start Submix 1
// `num_audio_elements` is affected by the `LebGenerator`.
@@ -681,10 +671,8 @@
}
TEST_F(MixPresentationObuTest, MultipleSubmixesAndLayouts) {
- num_sub_mixes_ = 2;
sub_mixes_.push_back(
- {.num_audio_elements = 1,
- .audio_elements = {{
+ {.audio_elements = {{
.audio_element_id = 21,
.localized_element_annotations = {"Submix 2"},
.rendering_config =
@@ -694,8 +682,6 @@
.rendering_config_extension_size = 0,
.rendering_config_extension_bytes = {}},
}},
-
- .num_layouts = 3,
.layouts = {
{.loudness_layout = {.layout_type = Layout::kLayoutTypeReserved0,
.specific_layout =
@@ -818,7 +804,7 @@
};
InitExpectOk();
obu_->mix_presentation_tags_ =
- MixPresentationTags{.num_tags = 1, .tags = {{"tag", "value"}}};
+ MixPresentationTags{.tags = {{"tag", "value"}}};
WriteBitBuffer wb(1024);
EXPECT_THAT(obu_->ValidateAndWriteObu(wb), IsOk());
@@ -943,7 +929,6 @@
ASSERT_FALSE(sub_mixes_.empty());
sub_mixes_[0].layouts.push_back(
MixPresentationLayout{Layout{.layout_type = kBeyondLayoutType}});
- sub_mixes_[0].num_layouts = sub_mixes_[0].layouts.size();
InitExpectOk();
WriteBitBuffer unused_wb(0);
@@ -986,7 +971,7 @@
'e', 'n', '-', 'u', 's', '\0',
// localized_presentation_annotations[0]
'M', 'i', 'x', ' ', '1', '\0',
- // num_submixes
+ // num_sub_mixes
0,
// End Mix OBU.
};
@@ -1014,7 +999,7 @@
'e', 'n', '-', 'u', 's', '\0',
// localized_presentation_annotations[0]
'M', 'i', 'x', ' ', '1', '\0',
- // num_submixes
+ // num_sub_mixes
1,
// Start Submix.
1, 21,
@@ -1069,7 +1054,7 @@
10,
// count_label
0,
- // num_submixes
+ // num_sub_mixes
1,
// Start Submix.
1, 21,
@@ -1119,7 +1104,7 @@
10,
// count_label
0,
- // num_submixes
+ // num_sub_mixes
1,
// Start Submix.
1, 21,
@@ -1217,7 +1202,7 @@
{.sound_system =
LoudspeakersSsConventionLayout::kSoundSystemA_0_2_0}));
EXPECT_EQ(layout.loudness.info_type, LoudnessInfo::kAnchoredLoudness);
- EXPECT_EQ(layout.loudness.anchored_loudness.num_anchored_loudness, 2);
+ EXPECT_EQ(layout.loudness.anchored_loudness.anchor_elements.size(), 2);
EXPECT_EQ(layout.loudness.anchored_loudness.anchor_elements[0].anchor_element,
AnchoredLoudnessElement::kAnchorElementAlbum);
EXPECT_EQ(
@@ -1372,8 +1357,7 @@
TEST(MixPresentationTagsWriteAndValidate, WritesWithZeroTags) {
constexpr uint8_t kZeroNumTags = 0;
- const MixPresentationTags kMixPresentationTagsWithZeroTags = {
- .num_tags = kZeroNumTags};
+ const MixPresentationTags kMixPresentationTagsWithZeroTags = {.tags = {}};
const std::vector<uint8_t> kExpectedBuffer = {
// `num_tags`.
kZeroNumTags,
@@ -1388,7 +1372,7 @@
TEST(MixPresentationTagsWriteAndValidate, WritesContentLanguageTag) {
constexpr uint8_t kOneTag = 1;
const MixPresentationTags kMixPresentationTagsWithContentLanguageTag = {
- .num_tags = kOneTag, .tags = {{"content_language", "eng"}}};
+ .tags = {{"content_language", "eng"}}};
const std::vector<uint8_t> kExpectedBuffer = {
// `num_tags`.
kOneTag,
@@ -1407,9 +1391,8 @@
TEST(MixPresentationTagsWriteAndValidate,
InvalidWhenContentLanguageTagNotThreeCharacters) {
- constexpr uint8_t kOneTag = 1;
const MixPresentationTags kMixPresentationTagsWithContentLanguageTag = {
- .num_tags = kOneTag, .tags = {{"content_language", "en-us"}}};
+ .tags = {{"content_language", "en-us"}}};
WriteBitBuffer wb(0);
@@ -1420,7 +1403,7 @@
TEST(MixPresentationTagsWriteAndValidate, WritesArbitraryTags) {
constexpr uint8_t kNumTags = 1;
const MixPresentationTags kMixPresentationTagsWithArbitraryTag = {
- .num_tags = kNumTags, .tags = {{"ABC", "123"}}};
+ .tags = {{"ABC", "123"}}};
const std::vector<uint8_t> kExpectedBuffer = {// `num_tags`.
kNumTags,
// `tag_name[0]`.
@@ -1438,7 +1421,7 @@
TEST(MixPresentationTagsWriteAndValidate, WritesDuplicateArbitraryTags) {
constexpr uint8_t kTwoTags = 2;
const MixPresentationTags kMixPresentationTagsWithArbitraryTag = {
- .num_tags = kTwoTags, .tags = {{"tag", "value"}, {"tag", "value"}}};
+ .tags = {{"tag", "value"}, {"tag", "value"}}};
const std::vector<uint8_t> kExpectedBuffer = {// `num_tags`.
kTwoTags,
// `tag_name[0]`.
@@ -1458,10 +1441,8 @@
}
TEST(MixPresentationTagsWriteAndValidate, InvalidForDuplicateContentIdTag) {
- constexpr uint8_t kTwoTags = 2;
const MixPresentationTags
kMixPresentationTagsWithDuplicateContentLanguageTag = {
- .num_tags = kTwoTags,
.tags = {{"content_language", "eng"}, {"content_language", "kor"}}};
WriteBitBuffer wb(1024);