diff options
author | jrummell@chromium.org <jrummell@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-03-26 08:36:41 +0000 |
---|---|---|
committer | jrummell@chromium.org <jrummell@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-03-26 08:36:41 +0000 |
commit | 9f3b46d0a7413e88907ee8d89de0a6a648548fda (patch) | |
tree | 1778c88bda8146137a9fe752e2db4cd3e33b1726 /media/base | |
parent | 1dc8ff9ddc29df8a60e9246f99ce9fa20e50cbd4 (diff) | |
download | chromium_src-9f3b46d0a7413e88907ee8d89de0a6a648548fda.zip chromium_src-9f3b46d0a7413e88907ee8d89de0a6a648548fda.tar.gz chromium_src-9f3b46d0a7413e88907ee8d89de0a6a648548fda.tar.bz2 |
Refactor creation of the transform matrix in channel_mixer
BUG=
Review URL: https://chromiumcodereview.appspot.com/12880008
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@190598 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media/base')
-rw-r--r-- | media/base/channel_mixer.cc | 149 | ||||
-rw-r--r-- | media/base/channel_mixer.h | 27 | ||||
-rw-r--r-- | media/base/channel_mixer_unittest.cc | 72 |
3 files changed, 165 insertions, 83 deletions
diff --git a/media/base/channel_mixer.cc b/media/base/channel_mixer.cc index 9f8f57a..3de63fe 100644 --- a/media/base/channel_mixer.cc +++ b/media/base/channel_mixer.cc @@ -53,6 +53,65 @@ static void ValidateLayout(ChannelLayout layout) { return; } +class MatrixBuilder { + public: + MatrixBuilder(ChannelLayout input_layout, int input_channels, + ChannelLayout output_layout, int output_channels) + : input_layout_(input_layout), + input_channels_(input_channels), + output_layout_(output_layout), + output_channels_(output_channels) { + // Special case for 5.0, 5.1 with back channels when upmixed to 7.0, 7.1, + // which should map the back LR to side LR. + if (input_layout_ == CHANNEL_LAYOUT_5_0_BACK && + output_layout_ == CHANNEL_LAYOUT_7_0) { + input_layout_ = CHANNEL_LAYOUT_5_0; + } else if (input_layout_ == CHANNEL_LAYOUT_5_1_BACK && + output_layout_ == CHANNEL_LAYOUT_7_1) { + input_layout_ = CHANNEL_LAYOUT_5_1; + } + } + + ~MatrixBuilder() { } + + // Create the transformation matrix of input channels to output channels. + // Updates the empty matrix with the transformation, and returns true + // if the transformation is just a remapping of channels (no mixing). + bool CreateTransformationMatrix(std::vector< std::vector<float> >* matrix); + + private: + // Result transformation of input channels to output channels + std::vector< std::vector<float> >* matrix_; + + // Input and output channel layout provided during construction. + ChannelLayout input_layout_; + int input_channels_; + ChannelLayout output_layout_; + int output_channels_; + + // Helper variable for tracking which inputs are currently unaccounted, + // should be empty after construction completes. + std::vector<Channels> unaccounted_inputs_; + + // Helper methods for managing unaccounted input channels. + void AccountFor(Channels ch); + bool IsUnaccounted(Channels ch); + + // Helper methods for checking if |ch| exists in either |input_layout_| or + // |output_layout_| respectively. + bool HasInputChannel(Channels ch); + bool HasOutputChannel(Channels ch); + + // Helper methods for updating |matrix_| with the proper value for + // mixing |input_ch| into |output_ch|. MixWithoutAccounting() does not + // remove the channel from |unaccounted_inputs_|. + void Mix(Channels input_ch, Channels output_ch, float scale); + void MixWithoutAccounting(Channels input_ch, Channels output_ch, + float scale); + + DISALLOW_COPY_AND_ASSIGN(MatrixBuilder); +}; + ChannelMixer::ChannelMixer(ChannelLayout input_layout, ChannelLayout output_layout) { Initialize(input_layout, @@ -72,22 +131,29 @@ ChannelMixer::ChannelMixer( void ChannelMixer::Initialize( ChannelLayout input_layout, int input_channels, ChannelLayout output_layout, int output_channels) { - input_layout_ = input_layout; - output_layout_ = output_layout; - remapping_ = false; - // Stereo down mix should never be the output layout. - CHECK_NE(output_layout_, CHANNEL_LAYOUT_STEREO_DOWNMIX); + CHECK_NE(output_layout, CHANNEL_LAYOUT_STEREO_DOWNMIX); + + // Verify that the layouts are supported + if (input_layout != CHANNEL_LAYOUT_DISCRETE) + ValidateLayout(input_layout); + if (output_layout != CHANNEL_LAYOUT_DISCRETE) + ValidateLayout(output_layout); + + // Create the transformation matrix + MatrixBuilder matrix_builder(input_layout, input_channels, + output_layout, output_channels); + remapping_ = matrix_builder.CreateTransformationMatrix(&matrix_); +} - if (input_layout_ != CHANNEL_LAYOUT_DISCRETE) - ValidateLayout(input_layout_); - if (output_layout_ != CHANNEL_LAYOUT_DISCRETE) - ValidateLayout(output_layout_); +bool MatrixBuilder::CreateTransformationMatrix( + std::vector< std::vector<float> >* matrix) { + matrix_ = matrix; // Size out the initial matrix. - matrix_.reserve(output_channels); - for (int output_ch = 0; output_ch < output_channels; ++output_ch) - matrix_.push_back(std::vector<float>(input_channels, 0)); + matrix_->reserve(output_channels_); + for (int output_ch = 0; output_ch < output_channels_; ++output_ch) + matrix_->push_back(std::vector<float>(input_channels_, 0)); // First check for discrete case. if (input_layout_ == CHANNEL_LAYOUT_DISCRETE || @@ -96,49 +162,36 @@ void ChannelMixer::Initialize( // copy as many as we can then drop the remaining input channels. // If the number of input channels is less than output channels, then // copy them all, then zero out the remaining output channels. - int passthrough_channels = std::min(input_channels, output_channels); + int passthrough_channels = std::min(input_channels_, output_channels_); for (int i = 0; i < passthrough_channels; ++i) - matrix_[i][i] = 1; - - remapping_ = true; - return; - } + (*matrix_)[i][i] = 1; - // Special case for 5.0, 5.1 with back channels when upmixed to 7.0, 7.1, - // which should map the back LR to side LR. - if (input_layout_ == CHANNEL_LAYOUT_5_0_BACK && - output_layout_ == CHANNEL_LAYOUT_7_0) { - input_layout_ = CHANNEL_LAYOUT_5_0; - } else if (input_layout_ == CHANNEL_LAYOUT_5_1_BACK && - output_layout_ == CHANNEL_LAYOUT_7_1) { - input_layout_ = CHANNEL_LAYOUT_5_1; + return true; } // Route matching channels and figure out which ones aren't accounted for. for (Channels ch = LEFT; ch < CHANNELS_MAX; ch = static_cast<Channels>(ch + 1)) { int input_ch_index = ChannelOrder(input_layout_, ch); - int output_ch_index = ChannelOrder(output_layout_, ch); - if (input_ch_index < 0) continue; + int output_ch_index = ChannelOrder(output_layout_, ch); if (output_ch_index < 0) { unaccounted_inputs_.push_back(ch); continue; } - DCHECK_LT(static_cast<size_t>(output_ch_index), matrix_.size()); + DCHECK_LT(static_cast<size_t>(output_ch_index), matrix_->size()); DCHECK_LT(static_cast<size_t>(input_ch_index), - matrix_[output_ch_index].size()); - matrix_[output_ch_index][input_ch_index] = 1; + (*matrix_)[output_ch_index].size()); + (*matrix_)[output_ch_index][input_ch_index] = 1; } // If all input channels are accounted for, there's nothing left to do. if (unaccounted_inputs_.empty()) { // Since all output channels map directly to inputs we can optimize. - remapping_ = true; - return; + return true; } // Mix front LR into center. @@ -147,7 +200,7 @@ void ChannelMixer::Initialize( // stereo mixes. Scaling by 1 / sqrt(2) here will likely lead to clipping // so we use 1 / 2 instead. float scale = - (output_layout_ == CHANNEL_LAYOUT_MONO && input_channels == 2) ? + (output_layout_ == CHANNEL_LAYOUT_MONO && input_channels_ == 2) ? 0.5 : kEqualPowerScale; Mix(LEFT, CENTER, scale); Mix(RIGHT, CENTER, scale); @@ -261,18 +314,18 @@ void ChannelMixer::Initialize( // See if the output |matrix_| is simply a remapping matrix. If each input // channel maps to a single output channel we can simply remap. Doing this // programmatically is less fragile than logic checks on channel mappings. - for (int output_ch = 0; output_ch < output_channels; ++output_ch) { + for (int output_ch = 0; output_ch < output_channels_; ++output_ch) { int input_mappings = 0; - for (int input_ch = 0; input_ch < input_channels; ++input_ch) { + for (int input_ch = 0; input_ch < input_channels_; ++input_ch) { // We can only remap if each row contains a single scale of 1. I.e., each // output channel is mapped from a single unscaled input channel. - if (matrix_[output_ch][input_ch] != 1 || ++input_mappings > 1) - return; + if ((*matrix_)[output_ch][input_ch] != 1 || ++input_mappings > 1) + return false; } } // If we've gotten here, |matrix_| is simply a remapping. - remapping_ = true; + return true; } ChannelMixer::~ChannelMixer() {} @@ -314,31 +367,31 @@ void ChannelMixer::Transform(const AudioBus* input, AudioBus* output) { } } -void ChannelMixer::AccountFor(Channels ch) { +void MatrixBuilder::AccountFor(Channels ch) { unaccounted_inputs_.erase(std::find( unaccounted_inputs_.begin(), unaccounted_inputs_.end(), ch)); } -bool ChannelMixer::IsUnaccounted(Channels ch) { +bool MatrixBuilder::IsUnaccounted(Channels ch) { return std::find(unaccounted_inputs_.begin(), unaccounted_inputs_.end(), ch) != unaccounted_inputs_.end(); } -bool ChannelMixer::HasInputChannel(Channels ch) { +bool MatrixBuilder::HasInputChannel(Channels ch) { return ChannelOrder(input_layout_, ch) >= 0; } -bool ChannelMixer::HasOutputChannel(Channels ch) { +bool MatrixBuilder::HasOutputChannel(Channels ch) { return ChannelOrder(output_layout_, ch) >= 0; } -void ChannelMixer::Mix(Channels input_ch, Channels output_ch, float scale) { +void MatrixBuilder::Mix(Channels input_ch, Channels output_ch, float scale) { MixWithoutAccounting(input_ch, output_ch, scale); AccountFor(input_ch); } -void ChannelMixer::MixWithoutAccounting(Channels input_ch, Channels output_ch, - float scale) { +void MatrixBuilder::MixWithoutAccounting(Channels input_ch, Channels output_ch, + float scale) { int input_ch_index = ChannelOrder(input_layout_, input_ch); int output_ch_index = ChannelOrder(output_layout_, output_ch); @@ -346,8 +399,8 @@ void ChannelMixer::MixWithoutAccounting(Channels input_ch, Channels output_ch, DCHECK_GE(input_ch_index, 0); DCHECK_GE(output_ch_index, 0); - DCHECK_EQ(matrix_[output_ch_index][input_ch_index], 0); - matrix_[output_ch_index][input_ch_index] = scale; + DCHECK_EQ((*matrix_)[output_ch_index][input_ch_index], 0); + (*matrix_)[output_ch_index][input_ch_index] = scale; } } // namespace media diff --git a/media/base/channel_mixer.h b/media/base/channel_mixer.h index c88669d..ea3cbf81 100644 --- a/media/base/channel_mixer.h +++ b/media/base/channel_mixer.h @@ -28,35 +28,12 @@ class MEDIA_EXPORT ChannelMixer { ChannelMixer(const AudioParameters& input, const AudioParameters& output); ~ChannelMixer(); - void Initialize(ChannelLayout input_layout, int input_channels, - ChannelLayout output_layout, int output_channels); - // Transforms all channels from |input| into |output| channels. void Transform(const AudioBus* input, AudioBus* output); private: - // Constructor helper methods for managing unaccounted input channels. - void AccountFor(Channels ch); - bool IsUnaccounted(Channels ch); - - // Helper methods for checking if |ch| exists in either |input_layout_| or - // |output_layout_| respectively. - bool HasInputChannel(Channels ch); - bool HasOutputChannel(Channels ch); - - // Constructor helper methods for updating |matrix_| with the proper value for - // mixing |input_ch| into |output_ch|. MixWithoutAccounting() does not remove - // the channel from |unaccounted_inputs_|. - void Mix(Channels input_ch, Channels output_ch, float scale); - void MixWithoutAccounting(Channels input_ch, Channels output_ch, float scale); - - // Input and output channel layout provided during construction. - ChannelLayout input_layout_; - ChannelLayout output_layout_; - - // Helper variable for tracking which inputs are currently unaccounted, should - // be empty after construction completes. - std::vector<Channels> unaccounted_inputs_; + void Initialize(ChannelLayout input_layout, int input_channels, + ChannelLayout output_layout, int output_channels); // 2D matrix of output channels to input channels. std::vector< std::vector<float> > matrix_; diff --git a/media/base/channel_mixer_unittest.cc b/media/base/channel_mixer_unittest.cc index 3e44409..935d060 100644 --- a/media/base/channel_mixer_unittest.cc +++ b/media/base/channel_mixer_unittest.cc @@ -8,6 +8,7 @@ #include <cmath> #include "base/stringprintf.h" +#include "media/audio/audio_parameters.h" #include "media/base/audio_bus.h" #include "media/base/channel_mixer.h" #include "testing/gtest/include/gtest/gtest.h" @@ -54,6 +55,20 @@ struct ChannelMixerTestData { channel_values(channel_values), num_channel_values(num_channel_values), scale(scale) { + input_channels = ChannelLayoutToChannelCount(input_layout); + output_channels = ChannelLayoutToChannelCount(output_layout); + } + + ChannelMixerTestData(ChannelLayout input_layout, int input_channels, + ChannelLayout output_layout, int output_channels, + float* channel_values, int num_channel_values) + : input_layout(input_layout), + input_channels(input_channels), + output_layout(output_layout), + output_channels(output_channels), + channel_values(channel_values), + num_channel_values(num_channel_values), + scale(1.0f) { } std::string DebugString() const { @@ -63,7 +78,9 @@ struct ChannelMixerTestData { } ChannelLayout input_layout; + int input_channels; ChannelLayout output_layout; + int output_channels; float* channel_values; int num_channel_values; float scale; @@ -79,13 +96,24 @@ class ChannelMixerTest : public testing::TestWithParam<ChannelMixerTestData> {}; // output channels have the same value. TEST_P(ChannelMixerTest, Mixing) { ChannelLayout input_layout = GetParam().input_layout; - ChannelLayout output_layout = GetParam().output_layout; + int input_channels = GetParam().input_channels; + scoped_ptr<AudioBus> input_bus = AudioBus::Create(input_channels, kFrames); + AudioParameters input_audio(AudioParameters::AUDIO_PCM_LINEAR, + input_layout, + AudioParameters::kAudioCDSampleRate, 16, + kFrames); + if (input_layout == CHANNEL_LAYOUT_DISCRETE) + input_audio.SetDiscreteChannels(input_channels); - ChannelMixer mixer(input_layout, output_layout); - scoped_ptr<AudioBus> input_bus = AudioBus::Create( - ChannelLayoutToChannelCount(input_layout), kFrames); - scoped_ptr<AudioBus> output_bus = AudioBus::Create( - ChannelLayoutToChannelCount(output_layout), kFrames); + ChannelLayout output_layout = GetParam().output_layout; + int output_channels = GetParam().output_channels; + scoped_ptr<AudioBus> output_bus = AudioBus::Create(output_channels, kFrames); + AudioParameters output_audio(AudioParameters::AUDIO_PCM_LINEAR, + output_layout, + AudioParameters::kAudioCDSampleRate, 16, + kFrames); + if (output_layout == CHANNEL_LAYOUT_DISCRETE) + output_audio.SetDiscreteChannels(output_channels); const float* channel_values = GetParam().channel_values; ASSERT_EQ(input_bus->channels(), GetParam().num_channel_values); @@ -98,11 +126,25 @@ TEST_P(ChannelMixerTest, Mixing) { expected_value += channel_values[ch] * scale; } + ChannelMixer mixer(input_audio, output_audio); mixer.Transform(input_bus.get(), output_bus.get()); - for (int ch = 0; ch < output_bus->channels(); ++ch) { - for (int frame = 0; frame < output_bus->frames(); ++frame) { - ASSERT_FLOAT_EQ(output_bus->channel(ch)[frame], expected_value); + // Validate the output channel + if (input_layout != CHANNEL_LAYOUT_DISCRETE) { + for (int ch = 0; ch < output_bus->channels(); ++ch) { + for (int frame = 0; frame < output_bus->frames(); ++frame) { + ASSERT_FLOAT_EQ(output_bus->channel(ch)[frame], expected_value); + } + } + } else { + // Processing discrete mixing. If there is a matching input channel, + // then the output channel should be set. If no input channel, + // output channel should be 0 + for (int ch = 0; ch < output_bus->channels(); ++ch) { + expected_value = (ch < input_channels) ? channel_values[ch] : 0; + for (int frame = 0; frame < output_bus->frames(); ++frame) { + ASSERT_FLOAT_EQ(output_bus->channel(ch)[frame], expected_value); + } } } } @@ -111,6 +153,7 @@ static float kStereoToMonoValues[] = { 0.5f, 0.75f }; static float kMonoToStereoValues[] = { 0.5f }; // Zero the center channel since it will be mixed at scale 1 vs M_SQRT1_2. static float kFiveOneToMonoValues[] = { 0.1f, 0.2f, 0.0f, 0.4f, 0.5f, 0.6f }; +static float kFiveDiscreteValues[] = { 0.1f, 0.2f, 0.3f, 0.4f, 0.5f }; // Run through basic sanity tests for some common conversions. INSTANTIATE_TEST_CASE_P(ChannelMixerTest, ChannelMixerTest, testing::Values( @@ -122,7 +165,16 @@ INSTANTIATE_TEST_CASE_P(ChannelMixerTest, ChannelMixerTest, testing::Values( 1.0f), ChannelMixerTestData(CHANNEL_LAYOUT_5_1, CHANNEL_LAYOUT_MONO, kFiveOneToMonoValues, arraysize(kFiveOneToMonoValues), - static_cast<float>(M_SQRT1_2)) + static_cast<float>(M_SQRT1_2)), + ChannelMixerTestData(CHANNEL_LAYOUT_DISCRETE, 2, + CHANNEL_LAYOUT_DISCRETE, 2, + kStereoToMonoValues, arraysize(kStereoToMonoValues)), + ChannelMixerTestData(CHANNEL_LAYOUT_DISCRETE, 2, + CHANNEL_LAYOUT_DISCRETE, 5, + kStereoToMonoValues, arraysize(kStereoToMonoValues)), + ChannelMixerTestData(CHANNEL_LAYOUT_DISCRETE, 5, + CHANNEL_LAYOUT_DISCRETE, 2, + kFiveDiscreteValues, arraysize(kFiveDiscreteValues)) )); } // namespace media |