| /* |
| * 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 "iamf/cli/ambisonic_encoder/ambisonic_encoder.h" |
| |
| #include <algorithm> |
| #include <cmath> |
| #include <cstddef> |
| #include <cstdlib> |
| #include <vector> |
| |
| #include "Eigen/Core" |
| #include "absl/log/check.h" |
| #include "iamf/cli/ambisonic_encoder/ambisonic_utils.h" |
| |
| namespace iamf_tools { |
| |
| AmbisonicEncoder::AmbisonicEncoder(size_t buffer_size_per_channel, |
| size_t number_of_input_channels, |
| size_t ambisonic_order) |
| : buffer_size_per_channel_(buffer_size_per_channel), |
| number_of_input_channels_(number_of_input_channels), |
| number_of_output_channels_((ambisonic_order + 1) * (ambisonic_order + 1)), |
| ambisonic_order_(ambisonic_order), |
| alp_generator_(static_cast<int>(ambisonic_order), false, false) { |
| CHECK_GE(number_of_input_channels_, 0); |
| CHECK_GE(ambisonic_order_, 0); |
| |
| // Initialize the encoding matrix. |
| encoding_matrix_ = |
| Eigen::MatrixXf::Zero(static_cast<int>(number_of_output_channels_), |
| static_cast<int>(number_of_input_channels_)); |
| } |
| |
| void AmbisonicEncoder::SetSource(size_t input_channel, float gain, |
| float azimuth, float elevation, |
| float distance) { |
| CHECK_NE(number_of_input_channels_, 0); |
| CHECK_NE(number_of_output_channels_, 0); |
| CHECK_LT(input_channel, number_of_input_channels_); |
| |
| // Check if the key exists in the map. |
| if (sources_.find(input_channel) == sources_.end()) { |
| // Add the source to the map. |
| sources_.insert({input_channel, {0.0, 0.0, 0.0, 0.0}}); |
| } |
| |
| // Check if the source is already set to these properties. |
| if (sources_.at(input_channel).gain == gain && |
| sources_.at(input_channel).azimuth == azimuth && |
| sources_.at(input_channel).elevation == elevation && |
| sources_.at(input_channel).distance == distance) { |
| return; |
| } |
| |
| // Update the gain, azimuth, elevation and distance of the source. |
| sources_.at(input_channel) = {gain, azimuth, elevation, distance}; |
| |
| // Calculate the overall gain for the source. Limit the minimum distance to |
| // 0.5 m. |
| float overall_gain = gain / std::max(distance, 0.5f); |
| |
| // Mute the source if the overall gain is less than -120 dB. |
| if (overall_gain < 0.000001) { |
| encoding_matrix_.col(static_cast<int>(input_channel)).setZero(); |
| return; |
| } |
| |
| // Get the spherical harmonic coefficients for the given azimuth and |
| // elevation. |
| std::vector<float> sh_coeffs(number_of_output_channels_); |
| GetShCoeffs(azimuth, elevation, ambisonic_order_, sh_coeffs); |
| |
| // Scale the spherical harmonic coefficients by the overall gain and update |
| // the encoding matrix. |
| for (size_t i = 0; i < number_of_output_channels_; i++) { |
| encoding_matrix_(static_cast<int>(i), static_cast<int>(input_channel)) = |
| sh_coeffs.at(i) * overall_gain; |
| } |
| } |
| |
| void AmbisonicEncoder::RemoveSource(size_t input_channel) { |
| // Remove the source from the map. |
| sources_.erase(input_channel); |
| |
| // Mute the input channel in the encoding matrix. |
| encoding_matrix_.col(static_cast<int>(input_channel)).setZero(); |
| } |
| |
| void AmbisonicEncoder::ProcessPlanarAudioData( |
| const std::vector<float>& input_buffer, |
| std::vector<float>& output_buffer) const { |
| // Check if the input buffer size matches the declared buffer size and number |
| // of input channels. |
| CHECK_EQ(input_buffer.size(), |
| number_of_input_channels_ * buffer_size_per_channel_); |
| |
| // Check if the output buffer size matches the declared buffer size and number |
| // of output channels. |
| CHECK_EQ(output_buffer.size(), |
| number_of_output_channels_ * buffer_size_per_channel_); |
| |
| // Create Eigen map for the input buffer. |
| const Eigen::Map<const Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, |
| Eigen::RowMajor>> |
| input_matrix(input_buffer.data(), |
| static_cast<int>(number_of_input_channels_), |
| static_cast<int>(buffer_size_per_channel_)); |
| |
| // Create Eigen map for the output buffer. |
| Eigen::Map< |
| Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>> |
| output_matrix(output_buffer.data(), |
| static_cast<int>(number_of_output_channels_), |
| static_cast<int>(buffer_size_per_channel_)); |
| |
| // Perform Ambisonic encoding. |
| output_matrix = encoding_matrix_ * input_matrix; |
| } |
| |
| void AmbisonicEncoder::GetShCoeffs(float azimuth, float elevation, |
| size_t ambisonic_order, |
| std::vector<float>& coeffs) { |
| float azimuth_rad = azimuth * kRadiansFromDegrees; |
| float elevation_rad = elevation * kRadiansFromDegrees; |
| |
| std::vector<float> associated_legendre_polynomials_temp_ = |
| alp_generator_.Generate(std::sin(elevation_rad)); |
| // Compute the actual spherical harmonics using the generated polynomials. |
| for (int degree = 0; degree <= ambisonic_order; degree++) { |
| for (int order = -degree; order <= degree; order++) { |
| const int row = AcnSequence(degree, order); |
| if (row == -1) { |
| // Skip this spherical harmonic. |
| continue; |
| } |
| |
| const float last_term = |
| (order >= 0) ? std::cos(static_cast<float>(order) * azimuth_rad) |
| : std::sin(static_cast<float>(-order) * azimuth_rad); |
| |
| coeffs.at(row) = |
| Sn3dNormalization(degree, order) * |
| associated_legendre_polynomials_temp_[alp_generator_.GetIndex( |
| degree, std::abs(order))] * |
| last_term; |
| } |
| } |
| } |
| |
| } // namespace iamf_tools |