| /* |
| * Copyright (c) 2023, 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 "iamf/obu/audio_element.h" |
| |
| #include <algorithm> |
| #include <array> |
| #include <cstdint> |
| #include <cstdlib> |
| #include <sstream> |
| #include <string> |
| #include <utility> |
| #include <variant> |
| #include <vector> |
| |
| #include "absl/container/flat_hash_set.h" |
| #include "absl/log/log.h" |
| #include "absl/status/status.h" |
| #include "absl/strings/str_cat.h" |
| #include "absl/types/span.h" |
| #include "iamf/common/read_bit_buffer.h" |
| #include "iamf/common/utils/macros.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/demixing_param_definition.h" |
| #include "iamf/obu/obu_base.h" |
| #include "iamf/obu/obu_header.h" |
| #include "iamf/obu/param_definitions.h" |
| #include "iamf/obu/types.h" |
| |
| namespace iamf_tools { |
| |
| using absl::OkStatus; |
| |
| namespace { |
| |
| // Returns the number of elements in the demixing_matrix. |
| size_t GetNumDemixingMatrixElements(const AmbisonicsProjectionConfig& config) { |
| const size_t c = static_cast<size_t>(config.output_channel_count); |
| const size_t n = static_cast<size_t>(config.substream_count); |
| const size_t m = static_cast<size_t>(config.coupled_substream_count); |
| |
| return (n + m) * c; |
| } |
| |
| void LogChannelBased(const ScalableChannelLayoutConfig& channel_config) { |
| LOG(INFO) << " scalable_channel_layout_config:"; |
| LOG(INFO) << " num_layers= " << absl::StrCat(channel_config.num_layers); |
| LOG(INFO) << " reserved= " << absl::StrCat(channel_config.reserved); |
| for (int i = 0; i < channel_config.num_layers; ++i) { |
| LOG(INFO) << " channel_audio_layer_configs[" << i << "]:"; |
| const auto& channel_audio_layer_config = |
| channel_config.channel_audio_layer_configs[i]; |
| LOG(INFO) << " loudspeaker_layout= " |
| << absl::StrCat(channel_audio_layer_config.loudspeaker_layout); |
| LOG(INFO) << " output_gain_is_present_flag= " |
| << absl::StrCat( |
| channel_audio_layer_config.output_gain_is_present_flag); |
| LOG(INFO) << " recon_gain_is_present_flag= " |
| << absl::StrCat( |
| channel_audio_layer_config.recon_gain_is_present_flag); |
| LOG(INFO) << " reserved= " |
| << absl::StrCat(channel_audio_layer_config.reserved_a); |
| LOG(INFO) << " substream_count= " |
| << absl::StrCat(channel_audio_layer_config.substream_count); |
| LOG(INFO) << " coupled_substream_count= " |
| << absl::StrCat( |
| channel_audio_layer_config.coupled_substream_count); |
| if (channel_audio_layer_config.output_gain_is_present_flag == 1) { |
| LOG(INFO) << " output_gain_flag= " |
| << absl::StrCat(channel_audio_layer_config.output_gain_flag); |
| LOG(INFO) << " reserved= " |
| << absl::StrCat(channel_audio_layer_config.reserved_b); |
| LOG(INFO) << " output_gain= " |
| << channel_audio_layer_config.output_gain; |
| } |
| if (channel_audio_layer_config.expanded_loudspeaker_layout.has_value()) { |
| LOG(INFO) << " expanded_loudspeaker_layout= " |
| << absl::StrCat( |
| *channel_audio_layer_config.expanded_loudspeaker_layout); |
| } else { |
| LOG(INFO) << " expanded_loudspeaker_layout= Not present."; |
| } |
| } |
| } |
| |
| void LogAmbisonicsMonoConfig(const AmbisonicsMonoConfig& mono_config) { |
| LOG(INFO) << " ambisonics_mono_config:"; |
| LOG(INFO) << " output_channel_count:" |
| << absl::StrCat(mono_config.output_channel_count); |
| LOG(INFO) << " substream_count:" |
| << absl::StrCat(mono_config.substream_count); |
| std::stringstream channel_mapping_stream; |
| for (int c = 0; c < mono_config.output_channel_count; c++) { |
| channel_mapping_stream << absl::StrCat(mono_config.channel_mapping[c]) |
| << ", "; |
| } |
| LOG(INFO) << " channel_mapping: [ " << channel_mapping_stream.str() << "]"; |
| } |
| |
| void LogAmbisonicsProjectionConfig( |
| const AmbisonicsProjectionConfig& projection_config) { |
| LOG(INFO) << " ambisonics_projection_config:"; |
| LOG(INFO) << " output_channel_count:" |
| << absl::StrCat(projection_config.output_channel_count); |
| LOG(INFO) << " substream_count:" |
| << absl::StrCat(projection_config.substream_count); |
| LOG(INFO) << " coupled_substream_count:" |
| << absl::StrCat(projection_config.coupled_substream_count); |
| std::string demixing_matrix_string; |
| for (int i = 0; i < (projection_config.substream_count + |
| projection_config.coupled_substream_count) * |
| projection_config.output_channel_count; |
| i++) { |
| absl::StrAppend(&demixing_matrix_string, |
| projection_config.demixing_matrix[i], ","); |
| } |
| LOG(INFO) << " demixing_matrix: [ " << demixing_matrix_string << "]"; |
| } |
| |
| void LogSceneBased(const AmbisonicsConfig& ambisonics_config) { |
| LOG(INFO) << " ambisonics_config:"; |
| LOG(INFO) << " ambisonics_mode= " |
| << absl::StrCat(ambisonics_config.ambisonics_mode); |
| if (ambisonics_config.ambisonics_mode == |
| AmbisonicsConfig::kAmbisonicsModeMono) { |
| LogAmbisonicsMonoConfig( |
| std::get<AmbisonicsMonoConfig>(ambisonics_config.ambisonics_config)); |
| } else if (ambisonics_config.ambisonics_mode == |
| AmbisonicsConfig::kAmbisonicsModeProjection) { |
| LogAmbisonicsProjectionConfig(std::get<AmbisonicsProjectionConfig>( |
| ambisonics_config.ambisonics_config)); |
| } |
| } |
| |
| // Returns `absl::OkStatus()` if all parameters have a unique |
| // `param_definition_type` in the OBU. `absl::InvalidArgumentError()` |
| // otherwise. |
| absl::Status ValidateUniqueParamDefinitionType( |
| const std::vector<AudioElementParam>& audio_element_params) { |
| std::vector<ParamDefinition::ParameterDefinitionType> |
| collected_param_definition_types; |
| collected_param_definition_types.reserve(audio_element_params.size()); |
| for (const auto& param : audio_element_params) { |
| collected_param_definition_types.push_back(param.GetType()); |
| } |
| |
| return ValidateUnique(collected_param_definition_types.begin(), |
| collected_param_definition_types.end(), |
| "audio_element_params"); |
| } |
| |
| absl::Status ValidateOutputChannelCount(const uint8_t channel_count) { |
| uint8_t next_valid_output_channel_count; |
| RETURN_IF_NOT_OK(AmbisonicsConfig ::GetNextValidOutputChannelCount( |
| channel_count, next_valid_output_channel_count)); |
| |
| if (next_valid_output_channel_count == channel_count) { |
| return absl::OkStatus(); |
| } |
| |
| return absl::InvalidArgumentError(absl::StrCat( |
| "Invalid Ambisonics output channel_count = ", channel_count)); |
| } |
| |
| // Writes an element of the `audio_element_params` array of a scalable channel |
| // `AudioElementObu`. |
| absl::Status ValidateAndWriteAudioElementParam(const AudioElementParam& param, |
| WriteBitBuffer& wb) { |
| const auto param_definition_type = param.GetType(); |
| |
| // Write the main portion of the `AudioElementParam`. |
| RETURN_IF_NOT_OK( |
| wb.WriteUleb128(static_cast<DecodedUleb128>(param_definition_type))); |
| |
| if (param_definition_type == ParamDefinition::kParameterDefinitionMixGain) { |
| return absl::InvalidArgumentError( |
| "Mix Gain parameter type is explicitly forbidden for " |
| "Audio Element OBUs."); |
| } |
| RETURN_IF_NOT_OK(std::visit( |
| [&wb](auto& param_definition) { |
| return param_definition.ValidateAndWrite(wb); |
| }, |
| param.param_definition)); |
| |
| return absl::OkStatus(); |
| } |
| |
| // Writes the `ScalableChannelLayoutConfig` of an `AudioElementObu`. |
| absl::Status ValidateAndWriteScalableChannelLayout( |
| const ScalableChannelLayoutConfig& layout, |
| const DecodedUleb128 num_substreams, WriteBitBuffer& wb) { |
| RETURN_IF_NOT_OK(layout.Validate(num_substreams)); |
| |
| // Write the main portion of the `ScalableChannelLayoutConfig`. |
| RETURN_IF_NOT_OK(wb.WriteUnsignedLiteral(layout.num_layers, 3)); |
| RETURN_IF_NOT_OK(wb.WriteUnsignedLiteral(layout.reserved, 5)); |
| |
| // Loop to write the `channel_audio_layer_configs` array. |
| for (const auto& layer_config : layout.channel_audio_layer_configs) { |
| RETURN_IF_NOT_OK(layer_config.Write(wb)); |
| } |
| |
| return absl::OkStatus(); |
| } |
| |
| // Reads the `ScalableChannelLayoutConfig` of an `AudioElementObu`. |
| absl::Status ReadAndValidateScalableChannelLayout( |
| ScalableChannelLayoutConfig& layout, const DecodedUleb128 num_substreams, |
| ReadBitBuffer& rb) { |
| // Read the main portion of the `ScalableChannelLayoutConfig`. |
| RETURN_IF_NOT_OK(rb.ReadUnsignedLiteral(3, layout.num_layers)); |
| RETURN_IF_NOT_OK(rb.ReadUnsignedLiteral(5, layout.reserved)); |
| |
| for (int i = 0; i < layout.num_layers; ++i) { |
| ChannelAudioLayerConfig layer_config; |
| RETURN_IF_NOT_OK(layer_config.Read(rb)); |
| layout.channel_audio_layer_configs.push_back(layer_config); |
| } |
| |
| RETURN_IF_NOT_OK(layout.Validate(num_substreams)); |
| |
| return absl::OkStatus(); |
| } |
| |
| // Writes the `AmbisonicsMonoConfig` of an ambisonics mono `AudioElementObu`. |
| absl::Status ValidateAndWriteAmbisonicsMono( |
| const AmbisonicsMonoConfig& mono_config, DecodedUleb128 num_substreams, |
| WriteBitBuffer& wb) { |
| RETURN_IF_NOT_OK(mono_config.Validate(num_substreams)); |
| |
| RETURN_IF_NOT_OK( |
| wb.WriteUnsignedLiteral(mono_config.output_channel_count, 8)); |
| RETURN_IF_NOT_OK(wb.WriteUnsignedLiteral(mono_config.substream_count, 8)); |
| |
| RETURN_IF_NOT_OK( |
| wb.WriteUint8Span(absl::MakeConstSpan(mono_config.channel_mapping))); |
| |
| return absl::OkStatus(); |
| } |
| |
| // Writes the `AmbisonicsProjectionConfig` of an ambisonics projection |
| // `AudioElementObu`. |
| absl::Status ValidateAndWriteAmbisonicsProjection( |
| const AmbisonicsProjectionConfig& projection_config, |
| DecodedUleb128 num_substreams, WriteBitBuffer& wb) { |
| RETURN_IF_NOT_OK(projection_config.Validate(num_substreams)); |
| |
| // Write the main portion of the `AmbisonicsProjectionConfig`. |
| RETURN_IF_NOT_OK( |
| wb.WriteUnsignedLiteral(projection_config.output_channel_count, 8)); |
| RETURN_IF_NOT_OK( |
| wb.WriteUnsignedLiteral(projection_config.substream_count, 8)); |
| RETURN_IF_NOT_OK( |
| wb.WriteUnsignedLiteral(projection_config.coupled_substream_count, 8)); |
| |
| // Loop to write the `demixing_matrix`. |
| for (size_t i = 0; i < projection_config.demixing_matrix.size(); i++) { |
| RETURN_IF_NOT_OK(wb.WriteSigned16(projection_config.demixing_matrix[i])); |
| } |
| |
| return absl::OkStatus(); |
| } |
| |
| // Writes the `AmbisonicsConfig` of an ambisonics `AudioElementObu`. |
| absl::Status ValidateAndWriteAmbisonicsConfig(const AmbisonicsConfig& config, |
| DecodedUleb128 num_substreams, |
| WriteBitBuffer& wb) { |
| // Write the main portion of the `AmbisonicsConfig`. |
| RETURN_IF_NOT_OK( |
| wb.WriteUleb128(static_cast<DecodedUleb128>(config.ambisonics_mode))); |
| |
| // Write the specific config based on `ambisonics_mode`. |
| switch (config.ambisonics_mode) { |
| using enum AmbisonicsConfig::AmbisonicsMode; |
| case kAmbisonicsModeMono: |
| return ValidateAndWriteAmbisonicsMono( |
| std::get<AmbisonicsMonoConfig>(config.ambisonics_config), |
| num_substreams, wb); |
| case kAmbisonicsModeProjection: |
| return ValidateAndWriteAmbisonicsProjection( |
| std::get<AmbisonicsProjectionConfig>(config.ambisonics_config), |
| num_substreams, wb); |
| default: |
| return absl::OkStatus(); |
| } |
| } |
| |
| absl::Status ReadAndValidateAmbisonicsProjection( |
| AmbisonicsProjectionConfig& projection_config, |
| DecodedUleb128 num_substreams, ReadBitBuffer& rb) { |
| RETURN_IF_NOT_OK( |
| rb.ReadUnsignedLiteral(8, projection_config.output_channel_count)); |
| RETURN_IF_NOT_OK( |
| rb.ReadUnsignedLiteral(8, projection_config.substream_count)); |
| RETURN_IF_NOT_OK( |
| rb.ReadUnsignedLiteral(8, projection_config.coupled_substream_count)); |
| const size_t demixing_matrix_size = |
| GetNumDemixingMatrixElements(projection_config); |
| for (size_t i = 0; i < demixing_matrix_size; ++i) { |
| int16_t demixing_matrix_value; |
| RETURN_IF_NOT_OK(rb.ReadSigned16(demixing_matrix_value)); |
| projection_config.demixing_matrix.push_back(demixing_matrix_value); |
| } |
| RETURN_IF_NOT_OK(projection_config.Validate(num_substreams)); |
| return OkStatus(); |
| } |
| |
| absl::Status ReadAndValidateAmbisonicsMonoConfig( |
| AmbisonicsMonoConfig& mono_config, DecodedUleb128 num_substreams, |
| ReadBitBuffer& rb) { |
| RETURN_IF_NOT_OK(rb.ReadUnsignedLiteral(8, mono_config.output_channel_count)); |
| RETURN_IF_NOT_OK(rb.ReadUnsignedLiteral(8, mono_config.substream_count)); |
| const size_t channel_mapping_size = mono_config.output_channel_count; |
| mono_config.channel_mapping.resize(channel_mapping_size); |
| RETURN_IF_NOT_OK( |
| rb.ReadUint8Span(absl::MakeSpan(mono_config.channel_mapping))); |
| RETURN_IF_NOT_OK(mono_config.Validate(num_substreams)); |
| return OkStatus(); |
| } |
| |
| // Reads the `AmbisonicsConfig` of an ambisonics `AudioElementObu`. |
| absl::Status ReadAndValidateAmbisonicsConfig(AmbisonicsConfig& config, |
| DecodedUleb128 num_substreams, |
| ReadBitBuffer& rb) { |
| DecodedUleb128 ambisonics_mode; |
| RETURN_IF_NOT_OK(rb.ReadULeb128(ambisonics_mode)); |
| config.ambisonics_mode = |
| static_cast<AmbisonicsConfig::AmbisonicsMode>(ambisonics_mode); |
| switch (config.ambisonics_mode) { |
| using enum AmbisonicsConfig::AmbisonicsMode; |
| case kAmbisonicsModeMono: { |
| config.ambisonics_config = AmbisonicsMonoConfig(); |
| return ReadAndValidateAmbisonicsMonoConfig( |
| std::get<AmbisonicsMonoConfig>(config.ambisonics_config), |
| num_substreams, rb); |
| } |
| case kAmbisonicsModeProjection: { |
| config.ambisonics_config = AmbisonicsProjectionConfig(); |
| return ReadAndValidateAmbisonicsProjection( |
| std::get<AmbisonicsProjectionConfig>(config.ambisonics_config), |
| num_substreams, rb); |
| } |
| default: |
| return OkStatus(); |
| } |
| } |
| |
| } // namespace |
| |
| absl::Status AudioElementParam::ReadAndValidate(uint32_t audio_element_id, |
| ReadBitBuffer& rb) { |
| // Reads the main portion of the `AudioElementParam`. |
| DecodedUleb128 param_definition_type_uleb; |
| RETURN_IF_NOT_OK(rb.ReadULeb128(param_definition_type_uleb)); |
| const auto param_definition_type = |
| static_cast<ParamDefinition::ParameterDefinitionType>( |
| param_definition_type_uleb); |
| |
| switch (param_definition_type) { |
| case ParamDefinition::kParameterDefinitionMixGain: { |
| return absl::InvalidArgumentError( |
| "Mix Gain parameter type is explicitly forbidden for Audio Element " |
| "OBUs."); |
| } |
| case ParamDefinition::kParameterDefinitionReconGain: { |
| auto& recon_gain_param_definition = |
| param_definition.emplace<ReconGainParamDefinition>(audio_element_id); |
| RETURN_IF_NOT_OK(recon_gain_param_definition.ReadAndValidate(rb)); |
| return absl::OkStatus(); |
| } |
| case ParamDefinition::kParameterDefinitionDemixing: { |
| auto& demixing_param_definition = |
| param_definition.emplace<DemixingParamDefinition>(); |
| RETURN_IF_NOT_OK(demixing_param_definition.ReadAndValidate(rb)); |
| return absl::OkStatus(); |
| } |
| default: |
| auto& extended_param_definition = |
| param_definition.emplace<ExtendedParamDefinition>( |
| param_definition_type); |
| RETURN_IF_NOT_OK(extended_param_definition.ReadAndValidate(rb)); |
| return absl::OkStatus(); |
| } |
| } |
| |
| absl::Status ChannelAudioLayerConfig::Write(WriteBitBuffer& wb) const { |
| RETURN_IF_NOT_OK(wb.WriteUnsignedLiteral(loudspeaker_layout, 4)); |
| RETURN_IF_NOT_OK(wb.WriteUnsignedLiteral(output_gain_is_present_flag, 1)); |
| RETURN_IF_NOT_OK(wb.WriteUnsignedLiteral(recon_gain_is_present_flag, 1)); |
| RETURN_IF_NOT_OK(wb.WriteUnsignedLiteral(reserved_a, 2)); |
| RETURN_IF_NOT_OK(wb.WriteUnsignedLiteral(substream_count, 8)); |
| RETURN_IF_NOT_OK(wb.WriteUnsignedLiteral(coupled_substream_count, 8)); |
| |
| if (output_gain_is_present_flag == 1) { |
| RETURN_IF_NOT_OK(wb.WriteUnsignedLiteral(output_gain_flag, 6)); |
| RETURN_IF_NOT_OK(wb.WriteUnsignedLiteral(reserved_b, 2)); |
| RETURN_IF_NOT_OK(wb.WriteSigned16(output_gain)); |
| } |
| |
| if (loudspeaker_layout == kLayoutExpanded) { |
| RETURN_IF_NOT_OK(ValidateHasValue(expanded_loudspeaker_layout, |
| "`expanded_loudspeaker_layout`")); |
| RETURN_IF_NOT_OK(wb.WriteUnsignedLiteral(*expanded_loudspeaker_layout, 8)); |
| } |
| |
| return absl::OkStatus(); |
| } |
| |
| absl::Status ChannelAudioLayerConfig::Read(ReadBitBuffer& rb) { |
| uint8_t loudspeaker_layout_uint8; |
| RETURN_IF_NOT_OK(rb.ReadUnsignedLiteral(4, loudspeaker_layout_uint8)); |
| loudspeaker_layout = static_cast<ChannelAudioLayerConfig::LoudspeakerLayout>( |
| loudspeaker_layout_uint8); |
| RETURN_IF_NOT_OK(rb.ReadUnsignedLiteral(1, output_gain_is_present_flag)); |
| RETURN_IF_NOT_OK(rb.ReadUnsignedLiteral(1, recon_gain_is_present_flag)); |
| RETURN_IF_NOT_OK(rb.ReadUnsignedLiteral(2, reserved_a)); |
| RETURN_IF_NOT_OK(rb.ReadUnsignedLiteral(8, substream_count)); |
| RETURN_IF_NOT_OK(rb.ReadUnsignedLiteral(8, coupled_substream_count)); |
| |
| if (output_gain_is_present_flag == 1) { |
| RETURN_IF_NOT_OK(rb.ReadUnsignedLiteral(6, output_gain_flag)); |
| RETURN_IF_NOT_OK(rb.ReadUnsignedLiteral(2, reserved_b)); |
| RETURN_IF_NOT_OK(rb.ReadSigned16(output_gain)); |
| } |
| |
| if (loudspeaker_layout == kLayoutExpanded) { |
| uint8_t expanded_loudspeaker_layout_uint8; |
| RETURN_IF_NOT_OK( |
| rb.ReadUnsignedLiteral(8, expanded_loudspeaker_layout_uint8)); |
| expanded_loudspeaker_layout = |
| static_cast<ChannelAudioLayerConfig::ExpandedLoudspeakerLayout>( |
| expanded_loudspeaker_layout_uint8); |
| } |
| |
| return absl::OkStatus(); |
| } |
| |
| absl::Status ScalableChannelLayoutConfig::Validate( |
| DecodedUleb128 num_substreams_in_audio_element) const { |
| if (num_layers == 0 || num_layers > 6) { |
| return absl::InvalidArgumentError( |
| absl::StrCat("Expected `num_layers` in [1, 6]; got ", num_layers)); |
| } |
| RETURN_IF_NOT_OK(ValidateContainerSizeEqual( |
| "channel_audio_layer_configs", channel_audio_layer_configs, num_layers)); |
| |
| // Determine whether any binaural layouts are found and the total number of |
| // substreams. |
| DecodedUleb128 cumulative_substream_count = 0; |
| bool has_binaural_layout = false; |
| for (const auto& layer_config : channel_audio_layer_configs) { |
| if (layer_config.loudspeaker_layout == |
| ChannelAudioLayerConfig::kLayoutBinaural) { |
| has_binaural_layout = true; |
| } |
| |
| cumulative_substream_count += |
| static_cast<DecodedUleb128>(layer_config.substream_count); |
| } |
| |
| if (cumulative_substream_count != num_substreams_in_audio_element) { |
| return absl::InvalidArgumentError( |
| "Cumulative substream count from all layers is not equal to " |
| "the `num_substreams` in the OBU."); |
| } |
| |
| if (has_binaural_layout && num_layers != 1) { |
| return absl::InvalidArgumentError( |
| "There must be exactly 1 layer if there is a binaural layout."); |
| } |
| |
| return absl::OkStatus(); |
| } |
| |
| absl::Status AmbisonicsMonoConfig::Validate( |
| DecodedUleb128 num_substreams_in_audio_element) const { |
| MAYBE_RETURN_IF_NOT_OK(ValidateOutputChannelCount(output_channel_count)); |
| RETURN_IF_NOT_OK(ValidateContainerSizeEqual( |
| "channel_mapping", channel_mapping, output_channel_count)); |
| if (substream_count > output_channel_count) { |
| return absl::InvalidArgumentError( |
| absl::StrCat("Expected substream_count=", substream_count, |
| " to be less than or equal to `output_channel_count`=", |
| output_channel_count, ".")); |
| } |
| if (num_substreams_in_audio_element != substream_count) { |
| return absl::InvalidArgumentError( |
| absl::StrCat("Expected substream_count=", substream_count, |
| " to be equal to num_substreams_in_audio_element=", |
| num_substreams_in_audio_element, ".")); |
| } |
| |
| // Track the number of unique substream indices in the mapping. |
| absl::flat_hash_set<uint8_t> unique_substream_indices; |
| for (const auto& substream_index : channel_mapping) { |
| if (substream_index == kInactiveAmbisonicsChannelNumber) { |
| // OK. This implies the nth ambisonics channel number is dropped (i.e. the |
| // user wants mixed-order ambisonics). |
| continue; |
| } |
| if (substream_index >= substream_count) { |
| return absl::InvalidArgumentError(absl::StrCat( |
| "Mapping out of bounds. When substream_count= ", substream_count, |
| " there is no substream_index= ", substream_index, ".")); |
| } |
| |
| unique_substream_indices.insert(substream_index); |
| } |
| |
| if (unique_substream_indices.size() != substream_count) { |
| return absl::InvalidArgumentError(absl::StrCat( |
| "A substream is in limbo; it has no associated ACN. ", |
| "substream_count= ", substream_count, |
| ", unique_substream_indices.size()= ", unique_substream_indices.size(), |
| ".")); |
| } |
| |
| return absl::OkStatus(); |
| } |
| |
| absl::Status AmbisonicsProjectionConfig::Validate( |
| DecodedUleb128 num_substreams_in_audio_element) const { |
| RETURN_IF_NOT_OK(ValidateOutputChannelCount(output_channel_count)); |
| if (coupled_substream_count > substream_count) { |
| return absl::InvalidArgumentError(absl::StrCat( |
| "Expected coupled_substream_count= ", coupled_substream_count, |
| " to be less than or equal to substream_count= ", substream_count)); |
| } |
| |
| if ((static_cast<int>(substream_count) + |
| static_cast<int>(coupled_substream_count)) > output_channel_count) { |
| return absl::InvalidArgumentError(absl::StrCat( |
| "Expected coupled_substream_count= ", coupled_substream_count, |
| " + substream_count= ", substream_count, |
| " to be less than or equal to `output_channel_count`= ", |
| output_channel_count, ".")); |
| } |
| if (num_substreams_in_audio_element != substream_count) { |
| return absl::InvalidArgumentError( |
| absl::StrCat("Expected substream_count= ", substream_count, |
| " to be equal to num_substreams_in_audio_element= ", |
| num_substreams_in_audio_element, ".")); |
| } |
| |
| const size_t expected_num_elements = GetNumDemixingMatrixElements(*this); |
| RETURN_IF_NOT_OK(ValidateContainerSizeEqual( |
| "demixing_matrix", demixing_matrix, expected_num_elements)); |
| |
| return absl::OkStatus(); |
| } |
| |
| absl::Status AmbisonicsConfig::GetNextValidOutputChannelCount( |
| uint8_t requested_output_channel_count, |
| uint8_t& next_valid_output_channel_count) { |
| // Valid values are `(1+n)^2`, for integer `n` in the range [0, 14]. |
| static constexpr auto kValidAmbisonicChannelCounts = []() -> auto { |
| std::array<uint8_t, 15> channel_count_i; |
| for (int i = 0; i < channel_count_i.size(); ++i) { |
| channel_count_i[i] = (i + 1) * (i + 1); |
| } |
| return channel_count_i; |
| }(); |
| |
| // Lookup the next higher or equal valid channel count. |
| auto valid_channel_count_iter = std::lower_bound( |
| kValidAmbisonicChannelCounts.begin(), kValidAmbisonicChannelCounts.end(), |
| requested_output_channel_count); |
| if (valid_channel_count_iter != kValidAmbisonicChannelCounts.end()) { |
| next_valid_output_channel_count = *valid_channel_count_iter; |
| return absl::OkStatus(); |
| } |
| |
| return absl::InvalidArgumentError(absl::StrCat( |
| "Output channel count is too large. requested_output_channel_count= ", |
| requested_output_channel_count, |
| ". Max=", kValidAmbisonicChannelCounts.back(), ".")); |
| } |
| |
| AudioElementObu::AudioElementObu(const ObuHeader& header, |
| DecodedUleb128 audio_element_id, |
| AudioElementType audio_element_type, |
| const uint8_t reserved, |
| DecodedUleb128 codec_config_id) |
| : ObuBase(header, kObuIaAudioElement), |
| num_substreams_(0), |
| num_parameters_(0), |
| audio_element_id_(audio_element_id), |
| audio_element_type_(audio_element_type), |
| reserved_(reserved), |
| codec_config_id_(codec_config_id) {} |
| |
| absl::StatusOr<AudioElementObu> AudioElementObu::CreateFromBuffer( |
| const ObuHeader& header, int64_t payload_size, ReadBitBuffer& rb) { |
| AudioElementObu audio_element_obu(header); |
| RETURN_IF_NOT_OK(audio_element_obu.ReadAndValidatePayload(payload_size, rb)); |
| return audio_element_obu; |
| } |
| |
| void AudioElementObu::InitializeAudioSubstreams(DecodedUleb128 num_substreams) { |
| num_substreams_ = num_substreams; |
| audio_substream_ids_.resize(static_cast<size_t>(num_substreams)); |
| } |
| |
| void AudioElementObu::InitializeParams(const DecodedUleb128 num_parameters) { |
| num_parameters_ = num_parameters; |
| audio_element_params_.reserve(static_cast<size_t>(num_parameters)); |
| } |
| |
| // Initializes the scalable channel portion of an `AudioElementObu`. |
| absl::Status AudioElementObu::InitializeScalableChannelLayout( |
| const uint32_t num_layers, const uint32_t reserved) { |
| // Validate the audio element type is correct. |
| if (audio_element_type_ != kAudioElementChannelBased) { |
| return absl::InvalidArgumentError(absl::StrCat( |
| "`InitializeScalableChannelLayout()` can only be called ", |
| "when `audio_element_type_ == kAudioElementChannelBased`, ", "but got ", |
| audio_element_type_)); |
| } |
| |
| ScalableChannelLayoutConfig config; |
| RETURN_IF_NOT_OK(StaticCastIfInRange<uint32_t, uint8_t>( |
| "ScalableChannelLayoutConfig.num_layers", num_layers, config.num_layers)); |
| RETURN_IF_NOT_OK(StaticCastIfInRange<uint32_t, uint8_t>( |
| "ScalableChannelLayoutConfig.reserved", reserved, config.reserved)); |
| config.channel_audio_layer_configs.resize(num_layers); |
| config_ = config; |
| return absl::OkStatus(); |
| } |
| |
| // Initializes the ambisonics mono portion of an `AudioElementObu`. |
| absl::Status AudioElementObu::InitializeAmbisonicsMono( |
| const uint32_t output_channel_count, const uint32_t substream_count) { |
| // Validate the audio element type and ambisonics mode are correct. |
| if (audio_element_type_ != kAudioElementSceneBased) { |
| return absl::InvalidArgumentError( |
| absl::StrCat("`InitializeAmbisonicsMono()` can only be called ", |
| "when `audio_element_type_ == kAudioElementSceneBased`, ", |
| "but got ", audio_element_type_)); |
| } |
| |
| AmbisonicsConfig config; |
| config.ambisonics_mode = AmbisonicsConfig::kAmbisonicsModeMono; |
| |
| AmbisonicsMonoConfig mono_config; |
| RETURN_IF_NOT_OK(StaticCastIfInRange<uint32_t, uint8_t>( |
| "AmbisonicsMonoConfig.output_channel_count", output_channel_count, |
| mono_config.output_channel_count)); |
| RETURN_IF_NOT_OK(StaticCastIfInRange<uint32_t, uint8_t>( |
| "AmbisonicsMonoConfig.substream_count", substream_count, |
| mono_config.substream_count)); |
| mono_config.channel_mapping.resize(output_channel_count); |
| config.ambisonics_config = mono_config; |
| config_ = config; |
| |
| return absl::OkStatus(); |
| } |
| |
| // Initializes the ambisonics projection portion of an `AudioElementObu`. |
| absl::Status AudioElementObu::InitializeAmbisonicsProjection( |
| const uint32_t output_channel_count, const uint32_t substream_count, |
| const uint32_t coupled_substream_count) { |
| // Validate the audio element type and ambisonics mode are correct. |
| if (audio_element_type_ != kAudioElementSceneBased) { |
| return absl::InvalidArgumentError( |
| absl::StrCat("`InitializeAmbisonicsProjection()` can only be called ", |
| "when `audio_element_type_ == kAudioElementSceneBased`, ", |
| "but got ", audio_element_type_)); |
| } |
| |
| AmbisonicsConfig config; |
| config.ambisonics_mode = AmbisonicsConfig::kAmbisonicsModeProjection; |
| |
| AmbisonicsProjectionConfig projection_config; |
| RETURN_IF_NOT_OK(StaticCastIfInRange<uint32_t, uint8_t>( |
| "AmbisonicsProjectionConfig.output_channel_count", output_channel_count, |
| projection_config.output_channel_count)); |
| RETURN_IF_NOT_OK(StaticCastIfInRange<uint32_t, uint8_t>( |
| "AmbisonicsProjectionConfig.substream_count", substream_count, |
| projection_config.substream_count)); |
| RETURN_IF_NOT_OK(StaticCastIfInRange<uint32_t, uint8_t>( |
| "AmbisonicsProjectionConfig.coupled_substream_count", |
| coupled_substream_count, projection_config.coupled_substream_count)); |
| const size_t num_elements = GetNumDemixingMatrixElements(projection_config); |
| projection_config.demixing_matrix.resize(num_elements); |
| config.ambisonics_config = projection_config; |
| config_ = config; |
| |
| return absl::OkStatus(); |
| } |
| |
| void AudioElementObu::InitializeExtensionConfig( |
| const DecodedUleb128 audio_element_config_size) { |
| config_ = |
| ExtensionConfig{.audio_element_config_size = audio_element_config_size}; |
| } |
| |
| void AudioElementObu::PrintObu() const { |
| LOG(INFO) << "Audio Element OBU:"; |
| LOG(INFO) << " audio_element_id= " << audio_element_id_; |
| LOG(INFO) << " audio_element_type= " << absl::StrCat(audio_element_type_); |
| LOG(INFO) << " reserved= " << absl::StrCat(reserved_); |
| LOG(INFO) << " codec_config_id= " << codec_config_id_; |
| LOG(INFO) << " num_substreams= " << num_substreams_; |
| for (int i = 0; i < num_substreams_; ++i) { |
| const auto& substream_id = audio_substream_ids_[i]; |
| LOG(INFO) << " audio_substream_ids[" << i << "]= " << substream_id; |
| } |
| LOG(INFO) << " num_parameters= " << num_parameters_; |
| for (int i = 0; i < num_parameters_; ++i) { |
| LOG(INFO) << " params[" << i << "]"; |
| std::visit([](const auto& param_definition) { param_definition.Print(); }, |
| audio_element_params_[i].param_definition); |
| } |
| if (audio_element_type_ == kAudioElementChannelBased) { |
| LogChannelBased(std::get<ScalableChannelLayoutConfig>(config_)); |
| } else if (audio_element_type_ == kAudioElementSceneBased) { |
| LogSceneBased(std::get<AmbisonicsConfig>(config_)); |
| } |
| } |
| |
| absl::Status AudioElementObu::ValidateAndWritePayload( |
| WriteBitBuffer& wb) const { |
| RETURN_IF_NOT_OK(ValidateUniqueParamDefinitionType(audio_element_params_)); |
| |
| RETURN_IF_NOT_OK(wb.WriteUleb128(audio_element_id_)); |
| RETURN_IF_NOT_OK(wb.WriteUnsignedLiteral(audio_element_type_, 3)); |
| RETURN_IF_NOT_OK(wb.WriteUnsignedLiteral(reserved_, 5)); |
| RETURN_IF_NOT_OK(wb.WriteUleb128(codec_config_id_)); |
| RETURN_IF_NOT_OK(wb.WriteUleb128(num_substreams_)); |
| |
| // Loop to write the audio substream IDs portion of the obu. |
| RETURN_IF_NOT_OK(ValidateContainerSizeEqual( |
| "audio_substream_ids", audio_substream_ids_, num_substreams_)); |
| for (const auto& audio_substream_id : audio_substream_ids_) { |
| RETURN_IF_NOT_OK(wb.WriteUleb128(audio_substream_id)); |
| } |
| |
| RETURN_IF_NOT_OK(wb.WriteUleb128(num_parameters_)); |
| |
| // Loop to write the parameter portion of the obu. |
| RETURN_IF_NOT_OK(ValidateContainerSizeEqual( |
| "audio_element_params_", audio_element_params_, num_parameters_)); |
| for (const auto& audio_element_param : audio_element_params_) { |
| RETURN_IF_NOT_OK( |
| ValidateAndWriteAudioElementParam(audio_element_param, wb)); |
| } |
| |
| // Write the specific `audio_element_type`'s config. |
| switch (audio_element_type_) { |
| case kAudioElementChannelBased: |
| return ValidateAndWriteScalableChannelLayout( |
| std::get<ScalableChannelLayoutConfig>(config_), num_substreams_, wb); |
| case kAudioElementSceneBased: |
| return ValidateAndWriteAmbisonicsConfig( |
| std::get<AmbisonicsConfig>(config_), num_substreams_, wb); |
| default: { |
| const auto& extension_config = std::get<ExtensionConfig>(config_); |
| RETURN_IF_NOT_OK( |
| wb.WriteUleb128(extension_config.audio_element_config_size)); |
| RETURN_IF_NOT_OK(ValidateContainerSizeEqual( |
| "audio_element_config_bytes", |
| extension_config.audio_element_config_bytes, |
| extension_config.audio_element_config_size)); |
| RETURN_IF_NOT_OK(wb.WriteUint8Span( |
| absl::MakeConstSpan(extension_config.audio_element_config_bytes))); |
| |
| return absl::OkStatus(); |
| } |
| } |
| |
| return absl::OkStatus(); |
| } |
| |
| absl::Status AudioElementObu::ReadAndValidatePayloadDerived( |
| int64_t /*payload_size*/, ReadBitBuffer& rb) { |
| RETURN_IF_NOT_OK(rb.ReadULeb128(audio_element_id_)); |
| uint8_t audio_element_type; |
| RETURN_IF_NOT_OK(rb.ReadUnsignedLiteral(3, audio_element_type)); |
| audio_element_type_ = static_cast<AudioElementType>(audio_element_type); |
| RETURN_IF_NOT_OK(rb.ReadUnsignedLiteral(5, reserved_)); |
| RETURN_IF_NOT_OK(rb.ReadULeb128(codec_config_id_)); |
| RETURN_IF_NOT_OK(rb.ReadULeb128(num_substreams_)); |
| |
| // Loop to read the audio substream IDs portion of the obu. |
| for (int i = 0; i < num_substreams_; ++i) { |
| DecodedUleb128 audio_substream_id; |
| RETURN_IF_NOT_OK(rb.ReadULeb128(audio_substream_id)); |
| audio_substream_ids_.push_back(audio_substream_id); |
| } |
| RETURN_IF_NOT_OK(ValidateContainerSizeEqual( |
| "audio_substream_ids", audio_substream_ids_, num_substreams_)); |
| |
| RETURN_IF_NOT_OK(rb.ReadULeb128(num_parameters_)); |
| |
| // Loop to read the parameter portion of the obu. |
| for (int i = 0; i < num_parameters_; ++i) { |
| AudioElementParam audio_element_param; |
| RETURN_IF_NOT_OK( |
| audio_element_param.ReadAndValidate(audio_element_id_, rb)); |
| audio_element_params_.push_back(std::move(audio_element_param)); |
| } |
| RETURN_IF_NOT_OK(ValidateContainerSizeEqual( |
| "num_parameters", audio_element_params_, num_parameters_)); |
| |
| // Write the specific `audio_element_type`'s config. |
| switch (audio_element_type_) { |
| case kAudioElementChannelBased: |
| config_ = ScalableChannelLayoutConfig(); |
| return ReadAndValidateScalableChannelLayout( |
| std::get<ScalableChannelLayoutConfig>(config_), num_substreams_, rb); |
| case kAudioElementSceneBased: |
| config_ = AmbisonicsConfig(); |
| return ReadAndValidateAmbisonicsConfig( |
| std::get<AmbisonicsConfig>(config_), num_substreams_, rb); |
| default: { |
| ExtensionConfig extension_config; |
| RETURN_IF_NOT_OK( |
| rb.ReadULeb128(extension_config.audio_element_config_size)); |
| for (int i = 0; i < extension_config.audio_element_config_size; ++i) { |
| uint8_t config_bytes; |
| RETURN_IF_NOT_OK(rb.ReadUnsignedLiteral(8, config_bytes)); |
| extension_config.audio_element_config_bytes.push_back(config_bytes); |
| } |
| |
| RETURN_IF_NOT_OK(ValidateContainerSizeEqual( |
| "audio_element_config_bytes", |
| extension_config.audio_element_config_bytes, |
| extension_config.audio_element_config_size)); |
| |
| return absl::OkStatus(); |
| } |
| } |
| RETURN_IF_NOT_OK(ValidateUniqueParamDefinitionType(audio_element_params_)); |
| return absl::OkStatus(); |
| } |
| |
| } // namespace iamf_tools |