Two new classes are added to WebRTC from Chrome: ChannelMixer and ChannelMixingMatrix but they are not yet utilized in the audio path for WebRTC. The idea is to utilize these new classes when adding support for multi- channel encoding/decoding in WebRTC/Chrome. Adds support for a new enumerator call webrtc::ChannelLayout and some helper methods which maps between channel layout and number of channels. These parts are also copied from Chrome. Minor (cosmetic) changes are also done on the AudioFrame to prepare for upcoming work. Bug: webrtc:10783 Change-Id: I6cd7a13a3bc1c8bbfa19bc974c7a011d22d19197 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/141674 Commit-Queue: Henrik Andreassson <henrika@webrtc.org> Reviewed-by: Henrik Lundin <henrik.lundin@webrtc.org> Cr-Commit-Position: refs/heads/master@{#28482}
323 lines
13 KiB
C++
323 lines
13 KiB
C++
/*
|
|
* Copyright (c) 2019 The WebRTC project authors. All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by a BSD-style license
|
|
* that can be found in the LICENSE file in the root of the source
|
|
* tree. An additional intellectual property rights grant can be found
|
|
* in the file PATENTS. All contributing project authors may
|
|
* be found in the AUTHORS file in the root of the source tree.
|
|
*/
|
|
|
|
#include "audio/utility/channel_mixing_matrix.h"
|
|
|
|
#include <stddef.h>
|
|
|
|
#include "audio/utility/channel_mixer.h"
|
|
#include "rtc_base/arraysize.h"
|
|
#include "rtc_base/logging.h"
|
|
#include "rtc_base/strings/string_builder.h"
|
|
#include "test/gtest.h"
|
|
|
|
namespace webrtc {
|
|
|
|
// Test all possible layout conversions can be constructed and mixed.
|
|
// Also ensure that the channel matrix fulfill certain conditions when remapping
|
|
// is supported.
|
|
TEST(ChannelMixingMatrixTest, ConstructAllPossibleLayouts) {
|
|
for (ChannelLayout input_layout = CHANNEL_LAYOUT_MONO;
|
|
input_layout <= CHANNEL_LAYOUT_MAX;
|
|
input_layout = static_cast<ChannelLayout>(input_layout + 1)) {
|
|
for (ChannelLayout output_layout = CHANNEL_LAYOUT_MONO;
|
|
output_layout <= CHANNEL_LAYOUT_MAX;
|
|
output_layout = static_cast<ChannelLayout>(output_layout + 1)) {
|
|
// DISCRETE, BITSTREAM can't be tested here based on the current approach.
|
|
// CHANNEL_LAYOUT_STEREO_AND_KEYBOARD_MIC is not mixable.
|
|
// Stereo down mix should never be the output layout.
|
|
if (input_layout == CHANNEL_LAYOUT_BITSTREAM ||
|
|
input_layout == CHANNEL_LAYOUT_DISCRETE ||
|
|
input_layout == CHANNEL_LAYOUT_STEREO_AND_KEYBOARD_MIC ||
|
|
output_layout == CHANNEL_LAYOUT_BITSTREAM ||
|
|
output_layout == CHANNEL_LAYOUT_DISCRETE ||
|
|
output_layout == CHANNEL_LAYOUT_STEREO_AND_KEYBOARD_MIC ||
|
|
output_layout == CHANNEL_LAYOUT_STEREO_DOWNMIX) {
|
|
continue;
|
|
}
|
|
|
|
rtc::StringBuilder ss;
|
|
ss << "Input Layout: " << input_layout
|
|
<< ", Output Layout: " << output_layout;
|
|
SCOPED_TRACE(ss.str());
|
|
ChannelMixingMatrix matrix_builder(
|
|
input_layout, ChannelLayoutToChannelCount(input_layout),
|
|
output_layout, ChannelLayoutToChannelCount(output_layout));
|
|
const int input_channels = ChannelLayoutToChannelCount(input_layout);
|
|
const int output_channels = ChannelLayoutToChannelCount(output_layout);
|
|
std::vector<std::vector<float>> matrix;
|
|
bool remapping = matrix_builder.CreateTransformationMatrix(&matrix);
|
|
|
|
if (remapping) {
|
|
// Also ensure that (when remapping can take place), a maximum of one
|
|
// input channel is included per output. This knowledge will simplify
|
|
// the channel mixing algorithm since it allows us to find the only
|
|
// scale factor which equals 1.0 and copy that input to its
|
|
// corresponding output. If no such factor can be found, the
|
|
// corresponding output can be set to zero.
|
|
for (int i = 0; i < output_channels; i++) {
|
|
EXPECT_EQ(static_cast<size_t>(input_channels), matrix[i].size());
|
|
int num_input_channels_accounted_for_per_output = 0;
|
|
for (int j = 0; j < input_channels; j++) {
|
|
float scale = matrix[i][j];
|
|
if (scale > 0) {
|
|
EXPECT_EQ(scale, 1.0f);
|
|
num_input_channels_accounted_for_per_output++;
|
|
}
|
|
}
|
|
// Each output channel shall contain contribution from one or less
|
|
// input channels.
|
|
EXPECT_LE(num_input_channels_accounted_for_per_output, 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Verify channels are mixed and scaled correctly.
|
|
TEST(ChannelMixingMatrixTest, StereoToMono) {
|
|
ChannelLayout input_layout = CHANNEL_LAYOUT_STEREO;
|
|
ChannelLayout output_layout = CHANNEL_LAYOUT_MONO;
|
|
ChannelMixingMatrix matrix_builder(
|
|
input_layout, ChannelLayoutToChannelCount(input_layout), output_layout,
|
|
ChannelLayoutToChannelCount(output_layout));
|
|
std::vector<std::vector<float>> matrix;
|
|
bool remapping = matrix_builder.CreateTransformationMatrix(&matrix);
|
|
|
|
// Input: stereo
|
|
// LEFT RIGHT
|
|
// Output: mono CENTER 0.5 0.5
|
|
//
|
|
EXPECT_FALSE(remapping);
|
|
EXPECT_EQ(1u, matrix.size());
|
|
EXPECT_EQ(2u, matrix[0].size());
|
|
EXPECT_EQ(0.5f, matrix[0][0]);
|
|
EXPECT_EQ(0.5f, matrix[0][1]);
|
|
}
|
|
|
|
TEST(ChannelMixingMatrixTest, MonoToStereo) {
|
|
ChannelLayout input_layout = CHANNEL_LAYOUT_MONO;
|
|
ChannelLayout output_layout = CHANNEL_LAYOUT_STEREO;
|
|
ChannelMixingMatrix matrix_builder(
|
|
input_layout, ChannelLayoutToChannelCount(input_layout), output_layout,
|
|
ChannelLayoutToChannelCount(output_layout));
|
|
std::vector<std::vector<float>> matrix;
|
|
bool remapping = matrix_builder.CreateTransformationMatrix(&matrix);
|
|
|
|
// Input: mono
|
|
// CENTER
|
|
// Output: stereo LEFT 1
|
|
// RIGHT 1
|
|
//
|
|
EXPECT_TRUE(remapping);
|
|
EXPECT_EQ(2u, matrix.size());
|
|
EXPECT_EQ(1u, matrix[0].size());
|
|
EXPECT_EQ(1.0f, matrix[0][0]);
|
|
EXPECT_EQ(1u, matrix[1].size());
|
|
EXPECT_EQ(1.0f, matrix[1][0]);
|
|
}
|
|
|
|
TEST(ChannelMixingMatrixTest, MonoToTwoOne) {
|
|
ChannelLayout input_layout = CHANNEL_LAYOUT_MONO;
|
|
ChannelLayout output_layout = CHANNEL_LAYOUT_2_1;
|
|
ChannelMixingMatrix matrix_builder(
|
|
input_layout, ChannelLayoutToChannelCount(input_layout), output_layout,
|
|
ChannelLayoutToChannelCount(output_layout));
|
|
std::vector<std::vector<float>> matrix;
|
|
bool remapping = matrix_builder.CreateTransformationMatrix(&matrix);
|
|
|
|
// Input: mono
|
|
// CENTER
|
|
// Output: 2.1 FRONT_LEFT 1
|
|
// FRONT_RIGHT 1
|
|
// BACK_CENTER 0
|
|
//
|
|
EXPECT_FALSE(remapping);
|
|
EXPECT_EQ(3u, matrix.size());
|
|
EXPECT_EQ(1u, matrix[0].size());
|
|
EXPECT_EQ(1.0f, matrix[0][0]);
|
|
EXPECT_EQ(1.0f, matrix[1][0]);
|
|
EXPECT_EQ(0.0f, matrix[2][0]);
|
|
}
|
|
|
|
TEST(ChannelMixingMatrixTest, FiveOneToMono) {
|
|
ChannelLayout input_layout = CHANNEL_LAYOUT_5_1;
|
|
ChannelLayout output_layout = CHANNEL_LAYOUT_MONO;
|
|
ChannelMixingMatrix matrix_builder(
|
|
input_layout, ChannelLayoutToChannelCount(input_layout), output_layout,
|
|
ChannelLayoutToChannelCount(output_layout));
|
|
std::vector<std::vector<float>> matrix;
|
|
bool remapping = matrix_builder.CreateTransformationMatrix(&matrix);
|
|
|
|
// Note: 1/sqrt(2) is shown as 0.707.
|
|
//
|
|
// Input: 5.1
|
|
// LEFT RIGHT CENTER LFE SIDE_LEFT SIDE_RIGHT
|
|
// Output: mono CENTER 0.707 0.707 1 0.707 0.707 0.707
|
|
//
|
|
EXPECT_FALSE(remapping);
|
|
EXPECT_EQ(1u, matrix.size());
|
|
EXPECT_EQ(6u, matrix[0].size());
|
|
EXPECT_FLOAT_EQ(ChannelMixer::kHalfPower, matrix[0][0]);
|
|
EXPECT_FLOAT_EQ(ChannelMixer::kHalfPower, matrix[0][1]);
|
|
// The center channel will be mixed at scale 1.
|
|
EXPECT_EQ(1.0f, matrix[0][2]);
|
|
EXPECT_FLOAT_EQ(ChannelMixer::kHalfPower, matrix[0][3]);
|
|
EXPECT_FLOAT_EQ(ChannelMixer::kHalfPower, matrix[0][4]);
|
|
EXPECT_FLOAT_EQ(ChannelMixer::kHalfPower, matrix[0][5]);
|
|
}
|
|
|
|
TEST(ChannelMixingMatrixTest, FiveOneBackToStereo) {
|
|
// Front L, Front R, Front C, LFE, Back L, Back R
|
|
ChannelLayout input_layout = CHANNEL_LAYOUT_5_1_BACK;
|
|
ChannelLayout output_layout = CHANNEL_LAYOUT_STEREO;
|
|
const int input_channels = ChannelLayoutToChannelCount(input_layout);
|
|
const int output_channels = ChannelLayoutToChannelCount(output_layout);
|
|
ChannelMixingMatrix matrix_builder(input_layout, input_channels,
|
|
output_layout, output_channels);
|
|
std::vector<std::vector<float>> matrix;
|
|
bool remapping = matrix_builder.CreateTransformationMatrix(&matrix);
|
|
|
|
// Note: 1/sqrt(2) is shown as 0.707.
|
|
// Note: The Channels enumerator is given by {LEFT = 0, RIGHT, CENTER, LFE,
|
|
// BACK_LEFT, BACK_RIGHT,...}, hence we can use the enumerator values as
|
|
// indexes in the matrix when verifying the scaling factors.
|
|
//
|
|
// Input: 5.1
|
|
// LEFT RIGHT CENTER LFE BACK_LEFT BACK_RIGHT
|
|
// Output: stereo LEFT 1 0 0.707 0.707 0.707 0
|
|
// RIGHT 0 1 0.707 0.707 0 0.707
|
|
//
|
|
EXPECT_FALSE(remapping);
|
|
EXPECT_EQ(static_cast<size_t>(output_channels), matrix.size());
|
|
EXPECT_EQ(static_cast<size_t>(input_channels), matrix[LEFT].size());
|
|
EXPECT_EQ(static_cast<size_t>(input_channels), matrix[RIGHT].size());
|
|
EXPECT_EQ(1.0f, matrix[LEFT][LEFT]);
|
|
EXPECT_EQ(1.0f, matrix[RIGHT][RIGHT]);
|
|
EXPECT_EQ(0.0f, matrix[LEFT][RIGHT]);
|
|
EXPECT_EQ(0.0f, matrix[RIGHT][LEFT]);
|
|
EXPECT_EQ(0.0f, matrix[LEFT][BACK_RIGHT]);
|
|
EXPECT_EQ(0.0f, matrix[RIGHT][BACK_LEFT]);
|
|
EXPECT_FLOAT_EQ(ChannelMixer::kHalfPower, matrix[LEFT][CENTER]);
|
|
EXPECT_FLOAT_EQ(ChannelMixer::kHalfPower, matrix[LEFT][LFE]);
|
|
EXPECT_FLOAT_EQ(ChannelMixer::kHalfPower, matrix[LEFT][BACK_LEFT]);
|
|
EXPECT_FLOAT_EQ(ChannelMixer::kHalfPower, matrix[RIGHT][CENTER]);
|
|
EXPECT_FLOAT_EQ(ChannelMixer::kHalfPower, matrix[RIGHT][LFE]);
|
|
EXPECT_FLOAT_EQ(ChannelMixer::kHalfPower, matrix[RIGHT][BACK_RIGHT]);
|
|
}
|
|
|
|
TEST(ChannelMixingMatrixTest, FiveOneToSevenOne) {
|
|
// Front L, Front R, Front C, LFE, Side L, Side R
|
|
ChannelLayout input_layout = CHANNEL_LAYOUT_5_1;
|
|
// Front L, Front R, Front C, LFE, Side L, Side R, Back L, Back R
|
|
ChannelLayout output_layout = CHANNEL_LAYOUT_7_1;
|
|
const int input_channels = ChannelLayoutToChannelCount(input_layout);
|
|
const int output_channels = ChannelLayoutToChannelCount(output_layout);
|
|
ChannelMixingMatrix matrix_builder(input_layout, input_channels,
|
|
output_layout, output_channels);
|
|
std::vector<std::vector<float>> matrix;
|
|
bool remapping = matrix_builder.CreateTransformationMatrix(&matrix);
|
|
|
|
// Input: 5.1
|
|
// LEFT RIGHT CENTER LFE SIDE_LEFT SIDE_RIGHT
|
|
// Output: 7.1 LEFT 1 0 0 0 0 0
|
|
// RIGHT 0 1 0 0 0 0
|
|
// CENTER 0 0 1 0 0 0
|
|
// LFE 0 0 0 1 0 0
|
|
// SIDE_LEFT 0 0 0 0 1 0
|
|
// SIDE_RIGHT 0 0 0 0 0 1
|
|
// BACK_LEFT 0 0 0 0 0 0
|
|
// BACK_RIGHT 0 0 0 0 0 0
|
|
//
|
|
EXPECT_TRUE(remapping);
|
|
EXPECT_EQ(static_cast<size_t>(output_channels), matrix.size());
|
|
for (int i = 0; i < output_channels; i++) {
|
|
EXPECT_EQ(static_cast<size_t>(input_channels), matrix[i].size());
|
|
for (int j = 0; j < input_channels; j++) {
|
|
if (i == j) {
|
|
EXPECT_EQ(1.0f, matrix[i][j]);
|
|
} else {
|
|
EXPECT_EQ(0.0f, matrix[i][j]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST(ChannelMixingMatrixTest, StereoToFiveOne) {
|
|
ChannelLayout input_layout = CHANNEL_LAYOUT_STEREO;
|
|
ChannelLayout output_layout = CHANNEL_LAYOUT_5_1;
|
|
const int input_channels = ChannelLayoutToChannelCount(input_layout);
|
|
const int output_channels = ChannelLayoutToChannelCount(output_layout);
|
|
ChannelMixingMatrix matrix_builder(input_layout, input_channels,
|
|
output_layout, output_channels);
|
|
std::vector<std::vector<float>> matrix;
|
|
bool remapping = matrix_builder.CreateTransformationMatrix(&matrix);
|
|
|
|
// Input: Stereo
|
|
// LEFT RIGHT
|
|
// Output: 5.1 LEFT 1 0
|
|
// RIGHT 0 1
|
|
// CENTER 0 0
|
|
// LFE 0 0
|
|
// SIDE_LEFT 0 0
|
|
// SIDE_RIGHT 0 0
|
|
//
|
|
EXPECT_TRUE(remapping);
|
|
EXPECT_EQ(static_cast<size_t>(output_channels), matrix.size());
|
|
for (int n = 0; n < output_channels; n++) {
|
|
EXPECT_EQ(static_cast<size_t>(input_channels), matrix[n].size());
|
|
if (n == LEFT) {
|
|
EXPECT_EQ(1.0f, matrix[LEFT][LEFT]);
|
|
EXPECT_EQ(0.0f, matrix[LEFT][RIGHT]);
|
|
} else if (n == RIGHT) {
|
|
EXPECT_EQ(0.0f, matrix[RIGHT][LEFT]);
|
|
EXPECT_EQ(1.0f, matrix[RIGHT][RIGHT]);
|
|
} else {
|
|
EXPECT_EQ(0.0f, matrix[n][LEFT]);
|
|
EXPECT_EQ(0.0f, matrix[n][RIGHT]);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST(ChannelMixingMatrixTest, DiscreteToDiscrete) {
|
|
const struct {
|
|
int input_channels;
|
|
int output_channels;
|
|
} test_case[] = {
|
|
{2, 2},
|
|
{2, 5},
|
|
{5, 2},
|
|
};
|
|
|
|
for (size_t n = 0; n < arraysize(test_case); n++) {
|
|
int input_channels = test_case[n].input_channels;
|
|
int output_channels = test_case[n].output_channels;
|
|
ChannelMixingMatrix matrix_builder(CHANNEL_LAYOUT_DISCRETE, input_channels,
|
|
CHANNEL_LAYOUT_DISCRETE,
|
|
output_channels);
|
|
std::vector<std::vector<float>> matrix;
|
|
bool remapping = matrix_builder.CreateTransformationMatrix(&matrix);
|
|
EXPECT_TRUE(remapping);
|
|
EXPECT_EQ(static_cast<size_t>(output_channels), matrix.size());
|
|
for (int i = 0; i < output_channels; i++) {
|
|
EXPECT_EQ(static_cast<size_t>(input_channels), matrix[i].size());
|
|
for (int j = 0; j < input_channels; j++) {
|
|
if (i == j) {
|
|
EXPECT_EQ(1.0f, matrix[i][j]);
|
|
} else {
|
|
EXPECT_EQ(0.0f, matrix[i][j]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace webrtc
|