/* * 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 #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/field_trial.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(input_layout + 1)) { for (ChannelLayout output_layout = CHANNEL_LAYOUT_MONO; output_layout <= CHANNEL_LAYOUT_MAX; output_layout = static_cast(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> 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(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> 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> 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, MonoToTwoOneWithoutVoIPAdjustments) { test::ScopedFieldTrials field_trials( "WebRTC-VoIPChannelRemixingAdjustmentKillSwitch/Enabled/"); 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> 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, MonoToTwoOneWithVoIPAdjustments) { 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> matrix; bool remapping = matrix_builder.CreateTransformationMatrix(&matrix); // Input: mono // CENTER // Output: 2.1 FRONT_LEFT 1 // FRONT_RIGHT 1 // BACK_CENTER 0 // EXPECT_TRUE(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, MonoToFiveOneWithoutVoIPAdjustments) { test::ScopedFieldTrials field_trials( "WebRTC-VoIPChannelRemixingAdjustmentKillSwitch/Enabled/"); ChannelLayout input_layout = CHANNEL_LAYOUT_MONO; 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> matrix; bool remapping = matrix_builder.CreateTransformationMatrix(&matrix); // Input: mono // CENTER // Output: 5.1 LEFT 0 // RIGHT 0 // CENTER 1 // LFE 0 // SIDE_LEFT 0 // SIDE_RIGHT 0 // EXPECT_TRUE(remapping); EXPECT_EQ(static_cast(output_channels), matrix.size()); for (int n = 0; n < output_channels; n++) { EXPECT_EQ(static_cast(input_channels), matrix[n].size()); if (n == CENTER) { EXPECT_EQ(1.0f, matrix[CENTER][0]); } else { EXPECT_EQ(0.0f, matrix[n][0]); } } } TEST(ChannelMixingMatrixTest, MonoToFiveOneWithVoIPAdjustments) { ChannelLayout input_layout = CHANNEL_LAYOUT_MONO; 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> matrix; bool remapping = matrix_builder.CreateTransformationMatrix(&matrix); // Input: mono // CENTER // Output: 5.1 LEFT 1 // RIGHT 1 // CENTER 0 // LFE 0 // SIDE_LEFT 0 // SIDE_RIGHT 0 // EXPECT_TRUE(remapping); EXPECT_EQ(static_cast(output_channels), matrix.size()); for (int n = 0; n < output_channels; n++) { EXPECT_EQ(static_cast(input_channels), matrix[n].size()); if (n == LEFT || n == RIGHT) { EXPECT_EQ(1.0f, matrix[n][0]); } else { EXPECT_EQ(0.0f, matrix[n][0]); } } } TEST(ChannelMixingMatrixTest, MonoToSevenOneWithoutVoIPAdjustments) { test::ScopedFieldTrials field_trials( "WebRTC-VoIPChannelRemixingAdjustmentKillSwitch/Enabled/"); ChannelLayout input_layout = CHANNEL_LAYOUT_MONO; 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> matrix; bool remapping = matrix_builder.CreateTransformationMatrix(&matrix); // Input: mono // CENTER // Output: 7.1 LEFT 0 // RIGHT 0 // CENTER 1 // LFE 0 // SIDE_LEFT 0 // SIDE_RIGHT 0 // BACK_LEFT 0 // BACK_RIGHT 0 // EXPECT_TRUE(remapping); EXPECT_EQ(static_cast(output_channels), matrix.size()); for (int n = 0; n < output_channels; n++) { EXPECT_EQ(static_cast(input_channels), matrix[n].size()); if (n == CENTER) { EXPECT_EQ(1.0f, matrix[CENTER][0]); } else { EXPECT_EQ(0.0f, matrix[n][0]); } } } TEST(ChannelMixingMatrixTest, MonoToSevenOneWithVoIPAdjustments) { ChannelLayout input_layout = CHANNEL_LAYOUT_MONO; 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> matrix; bool remapping = matrix_builder.CreateTransformationMatrix(&matrix); // Input: mono // CENTER // Output: 7.1 LEFT 1 // RIGHT 1 // CENTER 0 // LFE 0 // SIDE_LEFT 0 // SIDE_RIGHT 0 // BACK_LEFT 0 // BACK_RIGHT 0 // EXPECT_TRUE(remapping); EXPECT_EQ(static_cast(output_channels), matrix.size()); for (int n = 0; n < output_channels; n++) { EXPECT_EQ(static_cast(input_channels), matrix[n].size()); if (n == LEFT || n == RIGHT) { EXPECT_EQ(1.0f, matrix[n][0]); } else { EXPECT_EQ(0.0f, matrix[n][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> 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> 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(output_channels), matrix.size()); EXPECT_EQ(static_cast(input_channels), matrix[LEFT].size()); EXPECT_EQ(static_cast(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> 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(output_channels), matrix.size()); for (int i = 0; i < output_channels; i++) { EXPECT_EQ(static_cast(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> 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(output_channels), matrix.size()); for (int n = 0; n < output_channels; n++) { EXPECT_EQ(static_cast(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> matrix; bool remapping = matrix_builder.CreateTransformationMatrix(&matrix); EXPECT_TRUE(remapping); EXPECT_EQ(static_cast(output_channels), matrix.size()); for (int i = 0; i < output_channels; i++) { EXPECT_EQ(static_cast(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