/* * 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/cli/cli_util.h" #include #include #include #include #include #include #include #include #include #include "absl/container/flat_hash_map.h" #include "absl/container/flat_hash_set.h" #include "absl/log/log.h" #include "absl/status/status.h" #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" #include "iamf/cli/audio_element_with_data.h" #include "iamf/common/utils/macros.h" #include "iamf/common/utils/sample_processing_utils.h" #include "iamf/obu/audio_element.h" #include "iamf/obu/codec_config.h" #include "iamf/obu/demixing_param_definition.h" #include "iamf/obu/mix_presentation.h" #include "iamf/obu/param_definition_variant.h" #include "iamf/obu/param_definitions.h" #include "iamf/obu/types.h" namespace iamf_tools { namespace { absl::Status InsertParamDefinitionAndCheckEquivalence( const ParamDefinitionVariant& param_definition_variant_to_insert, absl::flat_hash_map& param_definition_variants) { const auto parameter_id = std::visit( [](const auto& param_definition) { return param_definition.parameter_id_; }, param_definition_variant_to_insert); const auto [existing_param_definition_iter, inserted] = param_definition_variants.insert( {parameter_id, param_definition_variant_to_insert}); // Use double dispatch to check equivalence. Note this automatically returns // false when the two variants do not hold the same type of objects. const auto equivalent_to_param_definition_variant_to_insert = [¶m_definition_variant_to_insert](const auto& rhs) { return std::visit([&rhs](const auto& lhs) { return (lhs == rhs); }, param_definition_variant_to_insert); }; if (!inserted && !std::visit(equivalent_to_param_definition_variant_to_insert, existing_param_definition_iter->second)) { return absl::InvalidArgumentError(absl::StrCat( "Inequivalent `param_definition` for id = ", parameter_id)); } return absl::OkStatus(); }; void FillReconGainAuxiliaryData( const AudioElementWithData& audio_element, std::vector& aux_data) { const auto& channel_config = std::get(audio_element.obu.config_); aux_data.resize(channel_config.num_layers); for (int l = 0; l < channel_config.num_layers; l++) { aux_data[l].recon_gain_is_present_flag = (channel_config.channel_audio_layer_configs[l] .recon_gain_is_present_flag == 1); aux_data[l].channel_numbers_for_layer = audio_element.channel_numbers_for_layers[l]; } } } // namespace bool IsStereoLayout(const Layout& layout) { const Layout kStereoLayout = { .layout_type = Layout::kLayoutTypeLoudspeakersSsConvention, .specific_layout = LoudspeakersSsConventionLayout{ .sound_system = LoudspeakersSsConventionLayout::kSoundSystemA_0_2_0}}; return layout == kStereoLayout; } absl::Status GetIndicesForLayout( const std::vector& mix_presentation_sub_mixes, 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.layouts.size(); l++) { const auto& mix_presentation_layout = sub_mix.layouts[l]; if (layout == mix_presentation_layout.loudness_layout) { output_submix_index = s; output_layout_index = l; return absl::OkStatus(); } } } return absl::InvalidArgumentError( "No match found in the mix presentation submixes for the desired " "layout."); } absl::Status CollectAndValidateParamDefinitions( const absl::flat_hash_map& audio_elements, const std::list& mix_presentation_obus, absl::flat_hash_map& param_definition_variants) { param_definition_variants.clear(); // Collect all `param_definition`s in Audio Element and Mix Presentation // OBUs. for (const auto& [audio_element_id_for_debugging, audio_element] : audio_elements) { for (const auto& audio_element_param : audio_element.obu.audio_element_params_) { const auto param_definition_type = audio_element_param.GetType(); switch (param_definition_type) { case ParamDefinition::kParameterDefinitionDemixing: RETURN_IF_NOT_OK(InsertParamDefinitionAndCheckEquivalence( std::get( audio_element_param.param_definition), param_definition_variants)); break; case ParamDefinition::kParameterDefinitionReconGain: { // Make a copy, which will be modified. ReconGainParamDefinition recon_gain_param_definition = std::get( audio_element_param.param_definition); FillReconGainAuxiliaryData(audio_element, recon_gain_param_definition.aux_data_); RETURN_IF_NOT_OK(InsertParamDefinitionAndCheckEquivalence( recon_gain_param_definition, param_definition_variants)); break; } default: LOG(WARNING) << "Ignoring parameter definition of type= " << param_definition_type << " in audio element= " << audio_element_id_for_debugging; continue; } } } for (const auto& mix_presentation_obu : mix_presentation_obus) { for (const auto& sub_mix : mix_presentation_obu.sub_mixes_) { for (const auto& audio_element : sub_mix.audio_elements) { RETURN_IF_NOT_OK(InsertParamDefinitionAndCheckEquivalence( audio_element.element_mix_gain, param_definition_variants)); } RETURN_IF_NOT_OK(InsertParamDefinitionAndCheckEquivalence( sub_mix.output_mix_gain, param_definition_variants)); } } return absl::OkStatus(); } absl::Status CompareTimestamps(InternalTimestamp expected_timestamp, InternalTimestamp actual_timestamp, absl::string_view prompt) { if (expected_timestamp != actual_timestamp) { return absl::InvalidArgumentError( absl::StrCat(prompt, "Expected timestamp != actual timestamp: (", expected_timestamp, " vs ", actual_timestamp, ")")); } return absl::OkStatus(); } absl::Status WritePcmFrameToBuffer( const std::vector>& frame, uint8_t bit_depth, bool big_endian, std::vector& 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() * 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. 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(frame[t][c]); RETURN_IF_NOT_OK(WritePcmSample(sample, bit_depth, big_endian, buffer.data(), write_position)); } } return absl::OkStatus(); } absl::Status GetCommonSampleRateAndBitDepth( const absl::flat_hash_set& sample_rates, const absl::flat_hash_set& bit_depths, uint32_t& common_sample_rate, uint8_t& common_bit_depth, bool& requires_resampling) { requires_resampling = false; if (sample_rates.empty() || bit_depths.empty()) { return absl::InvalidArgumentError( "Expected at least one sample rate and bit depth."); } if (sample_rates.size() == 1) { common_sample_rate = *sample_rates.begin(); } else { // No common sample rate. The spec recommends the rendering output to be // resampled to 48000 Hz. common_sample_rate = 48000; requires_resampling = true; } if (bit_depths.size() == 1) { common_bit_depth = *bit_depths.begin(); } else { // No common bit-depth. The spec recommends the rendering output to be // resampled to 16-bits. common_bit_depth = 16; requires_resampling = true; } return absl::OkStatus(); } absl::Status GetCommonSamplesPerFrame( const absl::flat_hash_map& codec_config_obus, uint32_t& common_samples_per_frame) { bool first = true; for (const auto& [unused_id, codec_config_obu] : codec_config_obus) { if (first) { common_samples_per_frame = codec_config_obu.GetNumSamplesPerFrame(); first = false; continue; } if (common_samples_per_frame != codec_config_obu.GetNumSamplesPerFrame()) { return absl::UnknownError( "The encoder does not support Codec Config OBUs with a different " "number of samples per frame yet."); } } return absl::OkStatus(); } void LogChannelNumbers(const std::string& name, const ChannelNumbers& channel_numbers) { LOG(INFO) << name << ": [" << channel_numbers.surround << "." << channel_numbers.lfe << "." << channel_numbers.height << "]"; } } // namespace iamf_tools