blob: 5e6e554b9b39a77ea97e2038e91d0823e89c5e01 [file] [log] [blame]
/*
* 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 <array>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <list>
#include <string>
#include <utility>
#include <variant>
#include <vector>
#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<DecodedUleb128, ParamDefinitionVariant>&
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 =
[&param_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<ReconGainParamDefinition::ReconGainAuxiliaryData>& aux_data) {
const auto& channel_config =
std::get<ScalableChannelLayoutConfig>(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<MixPresentationSubMix>& 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<DecodedUleb128, AudioElementWithData>&
audio_elements,
const std::list<MixPresentationObu>& mix_presentation_obus,
absl::flat_hash_map<DecodedUleb128, ParamDefinitionVariant>&
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<DemixingParamDefinition>(
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<ReconGainParamDefinition>(
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<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() * 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<uint32_t>(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<uint32_t>& sample_rates,
const absl::flat_hash_set<uint8_t>& 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<uint32_t, CodecConfigObu>& 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