diff --git a/webrtc/common_audio/BUILD.gn b/webrtc/common_audio/BUILD.gn index adee4b29cc..36740e87ce 100644 --- a/webrtc/common_audio/BUILD.gn +++ b/webrtc/common_audio/BUILD.gn @@ -20,6 +20,8 @@ config("common_audio_config") { source_set("common_audio") { sources = [ "audio_util.cc", + "blocker.cc", + "blocker.h", "fir_filter.cc", "fir_filter.h", "fir_filter_neon.h", @@ -82,10 +84,28 @@ source_set("common_audio") { "wav_header.h", "wav_writer.cc", "wav_writer.h", + "window_generator.cc", + "window_generator.h", ] deps = [ "../system_wrappers" ] + # TODO(ajm): Enable when GN support for openmax_dl is added. + # See: crbug.com/419206 + # Not needed immediately, since nothing built by GN depends on these bits. + # TODO(ajm): Workaround until openmax_dl has non-Android ARM support. + # See: crbug.com/415393 + #if (cpu_arch != "arm" or (cpu_arch == "arm" and is_android)) { + # 'sources' += [ + # 'lapped_transform.cc', + # 'lapped_transform.h', + # 'real_fourier.cc', + # 'real_fourier.h', + # ] + # + # deps += [ "//third_party/openmax_dl/dl" ] + #} + if (cpu_arch == "arm") { sources += [ "signal_processing/complex_bit_reverse_arm.S", diff --git a/webrtc/common_audio/blocker.cc b/webrtc/common_audio/blocker.cc new file mode 100644 index 0000000000..d207c1bb12 --- /dev/null +++ b/webrtc/common_audio/blocker.cc @@ -0,0 +1,220 @@ +/* + * Copyright (c) 2014 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 "webrtc/common_audio/blocker.h" + +#include + +#include "webrtc/base/checks.h" + +namespace { + +// Adds |a| and |b| frame by frame into |result| (basically matrix addition). +void AddFrames(const float* const* a, + int a_start_index, + const float* const* b, + int b_start_index, + int num_frames, + int num_channels, + float* const* result, + int result_start_index) { + for (int i = 0; i < num_channels; ++i) { + for (int j = 0; j < num_frames; ++j) { + result[i][j + result_start_index] = + a[i][j + a_start_index] + b[i][j + b_start_index]; + } + } +} + +// Copies |src| into |dst| channel by channel. +void CopyFrames(const float* const* src, + int src_start_index, + int num_frames, + int num_channels, + float* const* dst, + int dst_start_index) { + for (int i = 0; i < num_channels; ++i) { + memcpy(&dst[i][dst_start_index], + &src[i][src_start_index], + num_frames * sizeof(float)); + } +} + +void ZeroOut(float* const* buffer, + int starting_idx, + int num_frames, + int num_channels) { + for (int i = 0; i < num_channels; ++i) { + memset(&buffer[i][starting_idx], 0, num_frames * sizeof(float)); + } +} + +// Pointwise multiplies each channel of |frames| with |window|. Results are +// stored in |frames|. +void ApplyWindow(const float* window, + int num_frames, + int num_channels, + float* const* frames) { + for (int i = 0; i < num_channels; ++i) { + for (int j = 0; j < num_frames; ++j) { + frames[i][j] = frames[i][j] * window[j]; + } + } +} + +} // namespace + +namespace webrtc { + +Blocker::Blocker(int chunk_size, + int block_size, + int num_input_channels, + int num_output_channels, + const float* window, + int shift_amount, + BlockerCallback* callback) + : chunk_size_(chunk_size), + block_size_(block_size), + num_input_channels_(num_input_channels), + num_output_channels_(num_output_channels), + initial_delay_(block_size_), + frame_offset_(0), + input_buffer_(chunk_size_ + initial_delay_, num_input_channels_), + output_buffer_(chunk_size_ + initial_delay_, num_output_channels_), + input_block_(block_size_, num_input_channels_), + output_block_(block_size_, num_output_channels_), + window_(new float[block_size_]), + shift_amount_(shift_amount), + callback_(callback) { + CHECK_LE(num_output_channels_, num_input_channels_); + CHECK_GE(chunk_size_, block_size_); + + memcpy(window_.get(), window, block_size_ * sizeof(float)); + size_t buffer_size = chunk_size_ + initial_delay_; + memset(input_buffer_.channels()[0], + 0, + buffer_size * num_input_channels_ * sizeof(float)); + memset(output_buffer_.channels()[0], + 0, + buffer_size * num_output_channels_ * sizeof(float)); +} + +// Both the input and output buffers look like this: +// +// delay* chunk_size chunk_size + delay* +// buffer: <-------------|---------------------|---------------|> +// _a_ _b_ _c_ +// +// On each call to ProcessChunk(): +// 1. New input gets read into sections _b_ and _c_ of the input buffer. +// 2. We block starting from frame_offset. +// 3. We block until we reach a block |bl| that doesn't contain any frames +// from sections _a_ or _b_ of the input buffer. +// 4. We window the current block, fire the callback for processing, window +// again, and overlap/add to the output buffer. +// 5. We copy sections _a_ and _b_ of the output buffer into output. +// 6. For both the input and the output buffers, we copy section c into +// section a. +// 7. We set the new frame_offset to be the difference between the first frame +// of |bl| and the border between sections _b_ and _c_. +// +// * delay here refers to inintial_delay_ +// +// TODO(claguna): Look at using ring buffers to eliminate some copies. +void Blocker::ProcessChunk(const float* const* input, + int chunk_size, + int num_input_channels, + int num_output_channels, + float* const* output) { + CHECK_EQ(chunk_size, chunk_size_); + CHECK_EQ(num_input_channels, num_input_channels_); + CHECK_EQ(num_output_channels, num_output_channels_); + + // Copy new data into input buffer at + // [|initial_delay_|, |chunk_size_| + |initial_delay_|]. + CopyFrames(input, + 0, + chunk_size_, + num_input_channels_, + input_buffer_.channels(), + initial_delay_); + + int first_frame_in_block = frame_offset_; + + // Loop through blocks. + while (first_frame_in_block < chunk_size_) { + CopyFrames(input_buffer_.channels(), + first_frame_in_block, + block_size_, + num_input_channels_, + input_block_.channels(), + 0); + + ApplyWindow(window_.get(), + block_size_, + num_input_channels_, + input_block_.channels()); + callback_->ProcessBlock(input_block_.channels(), + block_size_, + num_input_channels_, + num_output_channels_, + output_block_.channels()); + ApplyWindow(window_.get(), + block_size_, + num_output_channels_, + output_block_.channels()); + + AddFrames(output_buffer_.channels(), + first_frame_in_block, + output_block_.channels(), + 0, + block_size_, + num_output_channels_, + output_buffer_.channels(), + first_frame_in_block); + + first_frame_in_block += shift_amount_; + } + + // Copy output buffer to output + CopyFrames(output_buffer_.channels(), + 0, + chunk_size_, + num_output_channels_, + output, + 0); + + // Copy input buffer [chunk_size_, chunk_size_ + initial_delay] + // to input buffer [0, initial_delay] + CopyFrames(input_buffer_.channels(), + chunk_size, + initial_delay_, + num_input_channels_, + input_buffer_.channels(), + 0); + + // Copy output buffer [chunk_size_, chunk_size_ + initial_delay] + // to output buffer [0, initial_delay], zero the rest. + CopyFrames(output_buffer_.channels(), + chunk_size, + initial_delay_, + num_output_channels_, + output_buffer_.channels(), + 0); + ZeroOut(output_buffer_.channels(), + initial_delay_, + chunk_size_, + num_output_channels_); + + // Calculate new starting frames. + frame_offset_ = first_frame_in_block - chunk_size_; +} + +} // namespace webrtc diff --git a/webrtc/common_audio/blocker.h b/webrtc/common_audio/blocker.h new file mode 100644 index 0000000000..f3f99cd31e --- /dev/null +++ b/webrtc/common_audio/blocker.h @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2014 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. + */ + +#ifndef WEBRTC_INTERNAL_BEAMFORMER_BLOCKER_H_ +#define WEBRTC_INTERNAL_BEAMFORMER_BLOCKER_H_ + +#include "webrtc/modules/audio_processing/common.h" +#include "webrtc/system_wrappers/interface/scoped_ptr.h" + +namespace webrtc { + +// The callback function to process audio in the time domain. Input has already +// been windowed, and output will be windowed. The number of input channels +// must be >= the number of output channels. +class BlockerCallback { + public: + virtual ~BlockerCallback() {} + + virtual void ProcessBlock(const float* const* input, + int num_frames, + int num_input_channels, + int num_output_channels, + float* const* output) = 0; +}; + +// The main purpose of Blocker is to abstract away the fact that often we +// receive a different number of audio frames than our transform takes. For +// example, most FFTs work best when the fft-size is a power of 2, but suppose +// we receive 20ms of audio at a sample rate of 48000. That comes to 960 frames +// of audio, which is not a power of 2. Blocker allows us to specify the +// transform and all other necessary processing via the Process() callback +// function without any constraints on the transform-size +// (read: |block_size_|) or received-audio-size (read: |chunk_size_|). +// We handle this for the multichannel audio case, allowing for different +// numbers of input and output channels (for example, beamforming takes 2 or +// more input channels and returns 1 output channel). Audio signals are +// represented as deinterleaved floats in the range [-1, 1]. +// +// Blocker is responsible for: +// - blocking audio while handling potential discontinuities on the edges +// of chunks +// - windowing blocks before sending them to Process() +// - windowing processed blocks, and overlap-adding them together before +// sending back a processed chunk +// +// To use blocker: +// 1. Impelment a BlockerCallback object |bc|. +// 2. Instantiate a Blocker object |b|, passing in |bc|. +// 3. As you receive audio, call b.ProcessChunk() to get processed audio. +// +// A small amount of delay is added to the first received chunk to deal with +// the difference in chunk/block sizes. This delay is <= chunk_size. +class Blocker { + public: + Blocker(int chunk_size, + int block_size, + int num_input_channels, + int num_output_channels, + const float* window, + int shift_amount, + BlockerCallback* callback); + + void ProcessChunk(const float* const* input, + int num_frames, + int num_input_channels, + int num_output_channels, + float* const* output); + + private: + const int chunk_size_; + const int block_size_; + const int num_input_channels_; + const int num_output_channels_; + + // The number of frames of delay to add at the beginning of the first chunk. + // + // TODO(claguna): find a lower cap for this than |block_size_|. + const int initial_delay_; + + // The frame index into the input buffer where the first block should be read + // from. This is necessary because shift_amount_ is not necessarily a + // multiple of chunk_size_, so blocks won't line up at the start of the + // buffer. + int frame_offset_; + + // Since blocks nearly always overlap, there are certain blocks that require + // frames from the end of one chunk and the beginning of the next chunk. The + // input and output buffers are responsible for saving those frames between + // calls to ProcessChunk(). + // + // Both contain |initial delay| + |chunk_size| frames. + ChannelBuffer input_buffer_; + ChannelBuffer output_buffer_; + + // Space for the input block (can't wrap because of windowing). + ChannelBuffer input_block_; + + // Space for the output block (can't wrap because of overlap/add). + ChannelBuffer output_block_; + + scoped_ptr window_; + + // The amount of frames between the start of contiguous blocks. For example, + // |shift_amount_| = |block_size_| / 2 for a Hann window. + int shift_amount_; + + BlockerCallback* callback_; +}; + +} // namespace webrtc + +#endif // WEBRTC_INTERNAL_BEAMFORMER_BLOCKER_H_ diff --git a/webrtc/common_audio/blocker_unittest.cc b/webrtc/common_audio/blocker_unittest.cc new file mode 100644 index 0000000000..5009c0eed8 --- /dev/null +++ b/webrtc/common_audio/blocker_unittest.cc @@ -0,0 +1,245 @@ +/* + * Copyright (c) 2014 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 "webrtc/common_audio/blocker.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +// Callback Function to add 3 to every sample in the signal. +class SimpleBlockerCallback : public webrtc::BlockerCallback { + public: + virtual void ProcessBlock(const float* const* input, + int num_frames, + int num_input_channels, + int num_output_channels, + float* const* output) OVERRIDE { + for (int i = 0; i < num_output_channels; ++i) { + for (int j = 0; j < num_frames; ++j) { + output[i][j] = input[i][j] + 3; + } + } + } +}; + +} // namespace + +namespace webrtc { + +// Tests blocking with a window that multiplies the signal by 2, a callback +// that adds 3 to each sample in the signal, and different combinations of chunk +// size, block size, and shift amount. +class BlockerTest : public ::testing::Test { + protected: + void RunTest(Blocker* blocker, + int chunk_size, + int num_frames, + const float* const* input, + float* const* input_chunk, + float* const* output, + float* const* output_chunk, + int num_input_channels, + int num_output_channels) { + int start = 0; + int end = chunk_size - 1; + while (end < num_frames) { + CopyTo(input_chunk, 0, start, num_input_channels, chunk_size, input); + blocker->ProcessChunk(input_chunk, + chunk_size, + num_input_channels, + num_output_channels, + output_chunk); + CopyTo(output, start, 0, num_output_channels, chunk_size, output_chunk); + + start = start + chunk_size; + end = end + chunk_size; + } + } + + void ValidateSignalEquality(const float* const* expected, + const float* const* actual, + int num_channels, + int num_frames) { + for (int i = 0; i < num_channels; ++i) { + for (int j = 0; j < num_frames; ++j) { + EXPECT_FLOAT_EQ(expected[i][j], actual[i][j]); + } + } + } + + static void CopyTo(float* const* dst, + int start_index_dst, + int start_index_src, + int num_channels, + int num_frames, + const float* const* src) { + for (int i = 0; i < num_channels; ++i) { + memcpy(&dst[i][start_index_dst], + &src[i][start_index_src], + num_frames * sizeof(float)); + } + } +}; + +TEST_F(BlockerTest, TestBlockerMutuallyPrimeChunkandBlockSize) { + const int kNumInputChannels = 3; + const int kNumOutputChannels = 2; + const int kNumFrames = 10; + const int kBlockSize = 4; + const int kChunkSize = 5; + const int kShiftAmount = 2; + + const float kInput[kNumInputChannels][kNumFrames] = { + {1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, + {2, 2, 2, 2, 2, 2, 2, 2, 2, 2}, + {3, 3, 3, 3, 3, 3, 3, 3, 3, 3}}; + const ChannelBuffer input_cb(kInput[0], kNumFrames, kNumInputChannels); + + const float kExpectedOutput[kNumInputChannels][kNumFrames] = { + {6, 6, 12, 12, 20, 20, 20, 20, 20, 20}, + {6, 6, 12, 12, 28, 28, 28, 28, 28, 28}}; + const ChannelBuffer expected_output_cb( + kExpectedOutput[0], kNumFrames, kNumInputChannels); + + const float kWindow[kBlockSize] = {2.f, 2.f, 2.f, 2.f}; + + ChannelBuffer actual_output_cb(kNumFrames, kNumOutputChannels); + ChannelBuffer input_chunk_cb(kChunkSize, kNumInputChannels); + ChannelBuffer output_chunk_cb(kChunkSize, kNumOutputChannels); + + SimpleBlockerCallback callback; + Blocker blocker(kChunkSize, + kBlockSize, + kNumInputChannels, + kNumOutputChannels, + kWindow, + kShiftAmount, + &callback); + + RunTest(&blocker, + kChunkSize, + kNumFrames, + input_cb.channels(), + input_chunk_cb.channels(), + actual_output_cb.channels(), + output_chunk_cb.channels(), + kNumInputChannels, + kNumOutputChannels); + + ValidateSignalEquality(expected_output_cb.channels(), + actual_output_cb.channels(), + kNumOutputChannels, + kNumFrames); +} + +TEST_F(BlockerTest, TestBlockerMutuallyPrimeShiftAndBlockSize) { + const int kNumInputChannels = 3; + const int kNumOutputChannels = 2; + const int kNumFrames = 12; + const int kBlockSize = 4; + const int kChunkSize = 6; + const int kShiftAmount = 3; + + const float kInput[kNumInputChannels][kNumFrames] = { + {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, + {2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2}, + {3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3}}; + const ChannelBuffer input_cb(kInput[0], kNumFrames, kNumInputChannels); + + const float kExpectedOutput[kNumInputChannels][kNumFrames] = { + {6, 6, 6, 12, 10, 10, 20, 10, 10, 20, 10, 10}, + {6, 6, 6, 12, 14, 14, 28, 14, 14, 28, 14, 14}}; + const ChannelBuffer expected_output_cb( + kExpectedOutput[0], kNumFrames, kNumInputChannels); + + const float kWindow[kBlockSize] = {2.f, 2.f, 2.f, 2.f}; + + ChannelBuffer actual_output_cb(kNumFrames, kNumOutputChannels); + ChannelBuffer input_chunk_cb(kChunkSize, kNumInputChannels); + ChannelBuffer output_chunk_cb(kChunkSize, kNumOutputChannels); + + SimpleBlockerCallback callback; + Blocker blocker(kChunkSize, + kBlockSize, + kNumInputChannels, + kNumOutputChannels, + kWindow, + kShiftAmount, + &callback); + + RunTest(&blocker, + kChunkSize, + kNumFrames, + input_cb.channels(), + input_chunk_cb.channels(), + actual_output_cb.channels(), + output_chunk_cb.channels(), + kNumInputChannels, + kNumOutputChannels); + + ValidateSignalEquality(expected_output_cb.channels(), + actual_output_cb.channels(), + kNumOutputChannels, + kNumFrames); +} + +TEST_F(BlockerTest, TestBlockerNoOverlap) { + const int kNumInputChannels = 3; + const int kNumOutputChannels = 2; + const int kNumFrames = 12; + const int kBlockSize = 4; + const int kChunkSize = 4; + const int kShiftAmount = 4; + + const float kInput[kNumInputChannels][kNumFrames] = { + {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, + {2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2}, + {3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3}}; + const ChannelBuffer input_cb(kInput[0], kNumFrames, kNumInputChannels); + + const float kExpectedOutput[kNumInputChannels][kNumFrames] = { + {6, 6, 6, 6, 10, 10, 10, 10, 10, 10, 10, 10}, + {6, 6, 6, 6, 14, 14, 14, 14, 14, 14, 14, 14}}; + const ChannelBuffer expected_output_cb( + kExpectedOutput[0], kNumFrames, kNumInputChannels); + + const float kWindow[kBlockSize] = {2.f, 2.f, 2.f, 2.f}; + + ChannelBuffer actual_output_cb(kNumFrames, kNumOutputChannels); + ChannelBuffer input_chunk_cb(kChunkSize, kNumInputChannels); + ChannelBuffer output_chunk_cb(kChunkSize, kNumOutputChannels); + + SimpleBlockerCallback callback; + Blocker blocker(kChunkSize, + kBlockSize, + kNumInputChannels, + kNumOutputChannels, + kWindow, + kShiftAmount, + &callback); + + RunTest(&blocker, + kChunkSize, + kNumFrames, + input_cb.channels(), + input_chunk_cb.channels(), + actual_output_cb.channels(), + output_chunk_cb.channels(), + kNumInputChannels, + kNumOutputChannels); + + ValidateSignalEquality(expected_output_cb.channels(), + actual_output_cb.channels(), + kNumOutputChannels, + kNumFrames); +} + +} // namespace webrtc diff --git a/webrtc/common_audio/common_audio.gyp b/webrtc/common_audio/common_audio.gyp index e9abf8222f..4581f5886e 100644 --- a/webrtc/common_audio/common_audio.gyp +++ b/webrtc/common_audio/common_audio.gyp @@ -30,6 +30,8 @@ }, 'sources': [ 'audio_util.cc', + 'blocker.cc', + 'blocker.h', 'fir_filter.cc', 'fir_filter.h', 'fir_filter_neon.h', @@ -96,8 +98,23 @@ 'wav_header.h', 'wav_writer.cc', 'wav_writer.h', + 'window_generator.cc', + 'window_generator.h', ], 'conditions': [ + # TODO(ajm): Workaround until openmax_dl has non-Android ARM support. + # See: crbug.com/415393 + ['target_arch!="arm" or (target_arch=="arm" and OS=="android")', { + 'sources': [ + 'lapped_transform.cc', + 'lapped_transform.h', + 'real_fourier.cc', + 'real_fourier.h', + ], + 'dependencies': [ + '<(DEPTH)/third_party/openmax_dl/dl/dl.gyp:openmax_dl', + ], + }], ['target_arch=="ia32" or target_arch=="x64"', { 'dependencies': ['common_audio_sse2',], }], @@ -209,6 +226,7 @@ ], 'sources': [ 'audio_util_unittest.cc', + 'blocker_unittest.cc', 'fir_filter_unittest.cc', 'resampler/resampler_unittest.cc', 'resampler/push_resampler_unittest.cc', @@ -226,8 +244,17 @@ 'vad/vad_unittest.h', 'wav_header_unittest.cc', 'wav_writer_unittest.cc', + 'window_generator_unittest.cc', ], 'conditions': [ + # TODO(ajm): Workaround until openmax_dl has non-Android ARM + # support. See: crbug.com/415393 + ['target_arch!="arm" or (target_arch=="arm" and OS=="android")', { + 'sources': [ + 'lapped_transform_unittest.cc', + 'real_fourier_unittest.cc', + ], + }], ['OS=="android"', { 'dependencies': [ '<(DEPTH)/testing/android/native_test.gyp:native_test_native_code', diff --git a/webrtc/common_audio/lapped_transform.cc b/webrtc/common_audio/lapped_transform.cc new file mode 100644 index 0000000000..ed4610fc18 --- /dev/null +++ b/webrtc/common_audio/lapped_transform.cc @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2014 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 "webrtc/common_audio/lapped_transform.h" + +#include +#include + +#include "webrtc/base/checks.h" +#include "webrtc/common_audio/real_fourier.h" + +namespace webrtc { + +void LappedTransform::BlockThunk::ProcessBlock(const float* const* input, + int num_frames, + int num_input_channels, + int num_output_channels, + float* const* output) { + CHECK_EQ(num_input_channels, parent_->in_channels_); + CHECK_EQ(num_output_channels, parent_->out_channels_); + CHECK_EQ(parent_->block_length_, num_frames); + + for (int i = 0; i < num_input_channels; ++i) { + memcpy(parent_->real_buf_.Row(i), input[i], + num_frames * sizeof(*input[0])); + parent_->fft_.Forward(parent_->real_buf_.Row(i), parent_->cplx_pre_.Row(i)); + } + + int block_length = RealFourier::ComplexLength( + RealFourier::FftOrder(num_frames)); + CHECK_EQ(parent_->cplx_length_, block_length); + parent_->block_processor_->ProcessAudioBlock(parent_->cplx_pre_.Array(), + num_input_channels, + parent_->cplx_length_, + num_output_channels, + parent_->cplx_post_.Array()); + + for (int i = 0; i < num_output_channels; ++i) { + parent_->fft_.Inverse(parent_->cplx_post_.Row(i), + parent_->real_buf_.Row(i)); + memcpy(output[i], parent_->real_buf_.Row(i), + num_frames * sizeof(*input[0])); + } +} + +LappedTransform::LappedTransform(int in_channels, int out_channels, + int chunk_length, const float* window, + int block_length, int shift_amount, + Callback* callback) + : blocker_callback_(this), + in_channels_(in_channels), + out_channels_(out_channels), + window_(window), + own_window_(false), + window_shift_amount_(shift_amount), + block_length_(block_length), + chunk_length_(chunk_length), + block_processor_(callback), + blocker_(nullptr), + fft_(RealFourier::FftOrder(block_length_)), + cplx_length_(RealFourier::ComplexLength(fft_.order())), + real_buf_(in_channels, block_length, RealFourier::kFftBufferAlignment), + cplx_pre_(in_channels, cplx_length_, RealFourier::kFftBufferAlignment), + cplx_post_(out_channels, cplx_length_, RealFourier::kFftBufferAlignment) { + CHECK(in_channels_ > 0 && out_channels_ > 0); + CHECK_GT(block_length_, 0); + CHECK_GT(chunk_length_, 0); + CHECK(block_processor_); + CHECK_EQ(0, block_length & (block_length - 1)); // block_length_ power of 2? + + if (!window_) { + own_window_ = true; + window_ = new float[block_length_]; + CHECK(window_ != nullptr); + window_shift_amount_ = block_length_; + float* temp = const_cast(window_); + for (int i = 0; i < block_length_; ++i) { + temp[i] = 1.0f; + } + } + + blocker_.reset(new Blocker(chunk_length_, block_length_, in_channels_, + out_channels_, window_, window_shift_amount_, + &blocker_callback_)); +} + +LappedTransform::~LappedTransform() { + if (own_window_) { + delete [] window_; + } +} + +void LappedTransform::ProcessChunk(const float* const* in_chunk, + float* const* out_chunk) { + blocker_->ProcessChunk(in_chunk, chunk_length_, in_channels_, out_channels_, + out_chunk); +} + +} // namespace webrtc + diff --git a/webrtc/common_audio/lapped_transform.h b/webrtc/common_audio/lapped_transform.h new file mode 100644 index 0000000000..330886a283 --- /dev/null +++ b/webrtc/common_audio/lapped_transform.h @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2014 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. + */ + +#ifndef WEBRTC_COMMON_AUDIO_LAPPED_TRANSFORM_H_ +#define WEBRTC_COMMON_AUDIO_LAPPED_TRANSFORM_H_ + +#include + +#include "webrtc/base/checks.h" +#include "webrtc/common_audio/blocker.h" +#include "webrtc/common_audio/real_fourier.h" +#include "webrtc/system_wrappers/interface/aligned_array.h" +#include "webrtc/system_wrappers/interface/scoped_ptr.h" + +namespace webrtc { + +// Helper class for audio processing modules which operate on frequency domain +// input derived from the windowed time domain audio stream. +// +// The input audio chunk is sliced into possibly overlapping blocks, multiplied +// by a window and transformed with an FFT implementation. The transformed data +// is supplied to the given callback for processing. The processed output is +// then inverse transformed into the time domain and spliced back into a chunk +// which constitutes the final output of this processing module. +class LappedTransform { + public: + class Callback { + public: + virtual ~Callback() {} + + virtual void ProcessAudioBlock(const std::complex* const* in_block, + int in_channels, int frames, + int out_channels, + std::complex* const* out_block) = 0; + }; + + // Construct a transform instance. |chunk_length| is the number of samples in + // each channel. |window| defines the window, owned by the caller (a copy is + // made internally); can be NULL to disable windowing entirely. + // |block_length| defines the length of a block, in samples, even when + // windowing is disabled. |shift_length| is in samples. |callback| is the + // caller-owned audio processing function called for each block of the input + // chunk. + LappedTransform(int in_channels, int out_channels, int chunk_length, + const float* window, int block_length, int shift_amount, + Callback* callback); + ~LappedTransform(); + + // Main audio processing helper method. Internally slices |in_chunk| into + // blocks, transforms them to frequency domain, calls the callback for each + // block and returns a de-blocked time domain chunk of audio through + // |out_chunk|. Both buffers are caller-owned. + void ProcessChunk(const float* const* in_chunk, float* const* out_chunk); + + private: + // Internal middleware callback, given to the blocker. Transforms each block + // and hands it over to the processing method given at construction time. + friend class BlockThunk; + class BlockThunk : public BlockerCallback { + public: + explicit BlockThunk(LappedTransform* parent) : parent_(parent) {} + virtual ~BlockThunk() {} + + virtual void ProcessBlock(const float* const* input, int num_frames, + int num_input_channels, int num_output_channels, + float* const* output); + + private: + LappedTransform* parent_; + } blocker_callback_; + + int in_channels_; + int out_channels_; + + const float* window_; + bool own_window_; + int window_shift_amount_; + + int block_length_; + int chunk_length_; + Callback* block_processor_; + scoped_ptr blocker_; + + RealFourier fft_; + int cplx_length_; + AlignedArray real_buf_; + AlignedArray > cplx_pre_; + AlignedArray > cplx_post_; +}; + +} // namespace webrtc + +#endif // WEBRTC_COMMON_AUDIO_LAPPED_TRANSFORM_H_ + diff --git a/webrtc/common_audio/lapped_transform_unittest.cc b/webrtc/common_audio/lapped_transform_unittest.cc new file mode 100644 index 0000000000..905238229f --- /dev/null +++ b/webrtc/common_audio/lapped_transform_unittest.cc @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2014 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 "webrtc/common_audio/lapped_transform.h" + +#include +#include + +#include "testing/gtest/include/gtest/gtest.h" + +using std::complex; + +namespace { + +class NoopCallback : public webrtc::LappedTransform::Callback { + public: + NoopCallback() : block_num_(0) {} + + virtual void ProcessAudioBlock(const complex* const* in_block, + int in_channels, int frames, int out_channels, + complex* const* out_block) { + CHECK_EQ(in_channels, out_channels); + for (int i = 0; i < out_channels; ++i) { + memcpy(out_block[i], in_block[i], sizeof(**in_block) * frames); + } + ++block_num_; + } + + int block_num() { + return block_num_; + } + + private: + int block_num_; +}; + +class FftCheckerCallback : public webrtc::LappedTransform::Callback { + public: + FftCheckerCallback() : block_num_(0) {} + + virtual void ProcessAudioBlock(const complex* const* in_block, + int in_channels, int frames, int out_channels, + complex* const* out_block) { + CHECK_EQ(in_channels, out_channels); + + float full_length = (frames - 1) * 2; + ++block_num_; + + if (block_num_ == 1) { + for (int i = 0; i < frames; ++i) { + ASSERT_NEAR(in_block[0][i].real(), 0.0f, 1e-5f); + ASSERT_NEAR(in_block[0][i].imag(), 0.0f, 1e-5f); + } + } else { + ASSERT_NEAR(in_block[0][0].real(), full_length, 1e-5f); + ASSERT_NEAR(in_block[0][0].imag(), 0.0f, 1e-5f); + for (int i = 1; i < frames; ++i) { + ASSERT_NEAR(in_block[0][i].real(), 0.0f, 1e-5f); + ASSERT_NEAR(in_block[0][i].imag(), 0.0f, 1e-5f); + } + } + } + + int block_num() { + return block_num_; + } + + private: + int block_num_; +}; + +void SetFloatArray(float value, int rows, int cols, float* const* array) { + for (int i = 0; i < rows; ++i) { + for (int j = 0; j < cols; ++j) { + array[i][j] = value; + } + } +} + +} // namespace + +namespace webrtc { + +TEST(LappedTransformTest, Windowless) { + const int kChannels = 3; + const int kChunkLength = 512; + const int kBlockLength = 64; + const int kShiftAmount = 32; + NoopCallback noop; + LappedTransform trans(kChannels, kChannels, kChunkLength, nullptr, + kBlockLength, kShiftAmount, &noop); + float in_buffer[kChannels][kChunkLength]; + float* in_chunk[kChannels]; + float out_buffer[kChannels][kChunkLength]; + float* out_chunk[kChannels]; + + in_chunk[0] = in_buffer[0]; + in_chunk[1] = in_buffer[1]; + in_chunk[2] = in_buffer[2]; + out_chunk[0] = out_buffer[0]; + out_chunk[1] = out_buffer[1]; + out_chunk[2] = out_buffer[2]; + SetFloatArray(2.0f, kChannels, kChunkLength, in_chunk); + SetFloatArray(-1.0f, kChannels, kChunkLength, out_chunk); + + trans.ProcessChunk(in_chunk, out_chunk); + + for (int i = 0; i < kChannels; ++i) { + for (int j = 0; j < kChunkLength; ++j) { + ASSERT_NEAR(out_chunk[i][j], (j < kBlockLength) ? 0.0f : 2.0f, 1e-5f); + } + } + + ASSERT_EQ(kChunkLength / kBlockLength, noop.block_num()); +} + +TEST(LappedTransformTest, IdentityProcessor) { + const int kChunkLength = 512; + const int kBlockLength = 64; + const int kShiftAmount = 32; + NoopCallback noop; + float window[kBlockLength]; + float* window_ptr = window; + + // Identity window for |overlap = block_size / 2|. + SetFloatArray(sqrtf(0.5f), 1, kBlockLength, &window_ptr); + + LappedTransform trans(1, 1, kChunkLength, window, kBlockLength, kShiftAmount, + &noop); + float in_buffer[kChunkLength]; + float* in_chunk = in_buffer; + float out_buffer[kChunkLength]; + float* out_chunk = out_buffer; + + SetFloatArray(2.0f, 1, kChunkLength, &in_chunk); + SetFloatArray(-1.0f, 1, kChunkLength, &out_chunk); + + trans.ProcessChunk(&in_chunk, &out_chunk); + + for (int i = 0; i < kChunkLength; ++i) { + ASSERT_NEAR(out_chunk[i], (i < kBlockLength) ? 0.0f : 2.0f, 1e-5f); + } + + ASSERT_EQ(kChunkLength / kShiftAmount, noop.block_num()); +} + +TEST(LappedTransformTest, Callbacks) { + const int kChunkLength = 512; + const int kBlockLength = 64; + FftCheckerCallback call; + LappedTransform trans(1, 1, kChunkLength, nullptr, kBlockLength, + kBlockLength, &call); + float in_buffer[kChunkLength]; + float* in_chunk = in_buffer; + float out_buffer[kChunkLength]; + float* out_chunk = out_buffer; + + SetFloatArray(1.0f, 1, kChunkLength, &in_chunk); + SetFloatArray(-1.0f, 1, kChunkLength, &out_chunk); + + trans.ProcessChunk(&in_chunk, &out_chunk); + + ASSERT_EQ(kChunkLength / kBlockLength, call.block_num()); +} + +} // namespace webrtc + diff --git a/webrtc/common_audio/real_fourier.cc b/webrtc/common_audio/real_fourier.cc new file mode 100644 index 0000000000..6933ea7ad2 --- /dev/null +++ b/webrtc/common_audio/real_fourier.cc @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2014 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 "webrtc/common_audio/real_fourier.h" + +#include + +#include "third_party/openmax_dl/dl/sp/api/omxSP.h" +#include "webrtc/base/checks.h" + +namespace webrtc { + +using std::complex; + +// The omx implementation uses this macro to check order validity. +const int RealFourier::kMaxFftOrder = TWIDDLE_TABLE_ORDER; +const int RealFourier::kFftBufferAlignment = 32; + +RealFourier::RealFourier(int fft_order) + : order_(fft_order), + omx_spec_(nullptr) { + CHECK_GE(order_, 1); + CHECK_LE(order_, kMaxFftOrder); + + OMX_INT buffer_size; + OMXResult r; + + r = omxSP_FFTGetBufSize_R_F32(order_, &buffer_size); + CHECK_EQ(r, OMX_Sts_NoErr); + + omx_spec_ = malloc(buffer_size); + DCHECK(omx_spec_); + + r = omxSP_FFTInit_R_F32(omx_spec_, order_); + CHECK_EQ(r, OMX_Sts_NoErr); +} + +RealFourier::~RealFourier() { + free(omx_spec_); +} + +int RealFourier::FftOrder(int length) { + for (int order = 0; order <= kMaxFftOrder; order++) { + if ((1 << order) >= length) { + return order; + } + } + return -1; +} + +int RealFourier::ComplexLength(int order) { + CHECK_LE(order, kMaxFftOrder); + CHECK_GT(order, 0); + return (1 << order) / 2 + 1; +} + +RealFourier::fft_real_scoper RealFourier::AllocRealBuffer(int count) { + return fft_real_scoper(static_cast( + AlignedMalloc(sizeof(float) * count, kFftBufferAlignment))); +} + +RealFourier::fft_cplx_scoper RealFourier::AllocCplxBuffer(int count) { + return fft_cplx_scoper(static_cast*>( + AlignedMalloc(sizeof(complex) * count, kFftBufferAlignment))); +} + +void RealFourier::Forward(const float* src, complex* dest) const { + OMXResult r; + r = omxSP_FFTFwd_RToCCS_F32(src, reinterpret_cast(dest), omx_spec_); + CHECK_EQ(r, OMX_Sts_NoErr); +} + +void RealFourier::Inverse(const complex* src, float* dest) const { + OMXResult r; + r = omxSP_FFTInv_CCSToR_F32(reinterpret_cast(src), dest, + omx_spec_); + CHECK_EQ(r, OMX_Sts_NoErr); +} + +} // namespace webrtc + diff --git a/webrtc/common_audio/real_fourier.h b/webrtc/common_audio/real_fourier.h new file mode 100644 index 0000000000..0ca962c94f --- /dev/null +++ b/webrtc/common_audio/real_fourier.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2014 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. + */ + +#ifndef WEBRTC_COMMON_AUDIO_REAL_FOURIER_H_ +#define WEBRTC_COMMON_AUDIO_REAL_FOURIER_H_ + +#include + +#include "webrtc/system_wrappers/interface/aligned_malloc.h" +#include "webrtc/system_wrappers/interface/scoped_ptr.h" + +// Uniform interface class for the real DFT and its inverse, for power-of-2 +// input lengths. Also contains helper functions for buffer allocation, taking +// care of any memory alignment requirements the underlying library might have. + +namespace webrtc { + +class RealFourier { + public: + // Shorthand typenames for the scopers used by the buffer allocation helpers. + typedef scoped_ptr fft_real_scoper; + typedef scoped_ptr[], AlignedFreeDeleter> fft_cplx_scoper; + + // The maximum input order supported by this implementation. + static const int kMaxFftOrder; + + // The alignment required for all input and output buffers, in bytes. + static const int kFftBufferAlignment; + + // Construct a wrapper instance for the given input order, which must be + // between 1 and kMaxFftOrder, inclusively. + explicit RealFourier(int fft_order); + ~RealFourier(); + + // Short helper to compute the smallest FFT order (a power of 2) which will + // contain the given input length. Returns -1 if the order would have been + // too big for the implementation. + static int FftOrder(int length); + + // Short helper to compute the exact length, in complex floats, of the + // transform output (i.e. |2^order / 2 + 1|). + static int ComplexLength(int order); + + // Buffer allocation helpers. The buffers are large enough to hold |count| + // floats/complexes and suitably aligned for use by the implementation. + // The returned scopers are set up with proper deleters; the caller owns + // the allocated memory. + static fft_real_scoper AllocRealBuffer(int count); + static fft_cplx_scoper AllocCplxBuffer(int count); + + // Main forward transform interface. The output array need only be big + // enough for |2^order / 2 + 1| elements - the conjugate pairs are not + // returned. Input and output must be properly aligned (e.g. through + // AllocRealBuffer and AllocCplxBuffer) and input length must be + // |2^order| (same as given at construction time). + void Forward(const float* src, std::complex* dest) const; + + // Inverse transform. Same input format as output above, conjugate pairs + // not needed. + void Inverse(const std::complex* src, float* dest) const; + + int order() const { + return order_; + } + + private: + // Basically a forward declare of OMXFFTSpec_R_F32. To get rid of the + // dependency on openmax. + typedef void OMXFFTSpec_R_F32_; + const int order_; + + OMXFFTSpec_R_F32_* omx_spec_; +}; + +} // namespace webrtc + +#endif // WEBRTC_COMMON_AUDIO_REAL_FOURIER_H_ + diff --git a/webrtc/common_audio/real_fourier_unittest.cc b/webrtc/common_audio/real_fourier_unittest.cc new file mode 100644 index 0000000000..8660d4d72b --- /dev/null +++ b/webrtc/common_audio/real_fourier_unittest.cc @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2014 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 "webrtc/common_audio/real_fourier.h" + +#include + +#include "testing/gtest/include/gtest/gtest.h" + +namespace webrtc { + +using std::complex; + +TEST(RealFourierStaticsTest, AllocatorAlignment) { + { + RealFourier::fft_real_scoper real; + real = RealFourier::AllocRealBuffer(3); + ASSERT_TRUE(real.get() != nullptr); + int64_t ptr_value = reinterpret_cast(real.get()); + ASSERT_EQ(ptr_value % RealFourier::kFftBufferAlignment, 0); + } + { + RealFourier::fft_cplx_scoper cplx; + cplx = RealFourier::AllocCplxBuffer(3); + ASSERT_TRUE(cplx.get() != nullptr); + int64_t ptr_value = reinterpret_cast(cplx.get()); + ASSERT_EQ(ptr_value % RealFourier::kFftBufferAlignment, 0); + } +} + +TEST(RealFourierStaticsTest, OrderComputation) { + ASSERT_EQ(RealFourier::FftOrder(2000000), -1); + ASSERT_EQ(RealFourier::FftOrder((1 << RealFourier::kMaxFftOrder) + 1), -1); + ASSERT_EQ(RealFourier::FftOrder(1 << RealFourier::kMaxFftOrder), + RealFourier::kMaxFftOrder); + ASSERT_EQ(RealFourier::FftOrder(13), 4); + ASSERT_EQ(RealFourier::FftOrder(32), 5); + ASSERT_EQ(RealFourier::FftOrder(2), 1); + ASSERT_EQ(RealFourier::FftOrder(1), 0); + ASSERT_EQ(RealFourier::FftOrder(0), 0); +} + +TEST(RealFourierStaticsTest, ComplexLengthComputation) { + ASSERT_EQ(RealFourier::ComplexLength(1), 2); + ASSERT_EQ(RealFourier::ComplexLength(2), 3); + ASSERT_EQ(RealFourier::ComplexLength(3), 5); + ASSERT_EQ(RealFourier::ComplexLength(4), 9); + ASSERT_EQ(RealFourier::ComplexLength(5), 17); + ASSERT_EQ(RealFourier::ComplexLength(7), 65); +} + +class RealFourierTest : public ::testing::Test { + protected: + RealFourierTest() + : rf_(new RealFourier(2)), + real_buffer_(RealFourier::AllocRealBuffer(4)), + cplx_buffer_(RealFourier::AllocCplxBuffer(3)) {} + + ~RealFourierTest() { + delete rf_; + } + + const RealFourier* rf_; + const RealFourier::fft_real_scoper real_buffer_; + const RealFourier::fft_cplx_scoper cplx_buffer_; +}; + +TEST_F(RealFourierTest, SimpleForwardTransform) { + real_buffer_[0] = 1.0f; + real_buffer_[1] = 2.0f; + real_buffer_[2] = 3.0f; + real_buffer_[3] = 4.0f; + + rf_->Forward(real_buffer_.get(), cplx_buffer_.get()); + + ASSERT_NEAR(cplx_buffer_[0].real(), 10.0f, 1e-8f); + ASSERT_NEAR(cplx_buffer_[0].imag(), 0.0f, 1e-8f); + ASSERT_NEAR(cplx_buffer_[1].real(), -2.0f, 1e-8f); + ASSERT_NEAR(cplx_buffer_[1].imag(), 2.0f, 1e-8f); + ASSERT_NEAR(cplx_buffer_[2].real(), -2.0f, 1e-8f); + ASSERT_NEAR(cplx_buffer_[2].imag(), 0.0f, 1e-8f); +} + +TEST_F(RealFourierTest, SimpleBackwardTransform) { + cplx_buffer_[0] = complex(10.0f, 0.0f); + cplx_buffer_[1] = complex(-2.0f, 2.0f); + cplx_buffer_[2] = complex(-2.0f, 0.0f); + + rf_->Inverse(cplx_buffer_.get(), real_buffer_.get()); + + ASSERT_NEAR(real_buffer_[0], 1.0f, 1e-8f); + ASSERT_NEAR(real_buffer_[1], 2.0f, 1e-8f); + ASSERT_NEAR(real_buffer_[2], 3.0f, 1e-8f); + ASSERT_NEAR(real_buffer_[3], 4.0f, 1e-8f); +} + +} // namespace webrtc + diff --git a/webrtc/common_audio/window_generator.cc b/webrtc/common_audio/window_generator.cc new file mode 100644 index 0000000000..1d61368c19 --- /dev/null +++ b/webrtc/common_audio/window_generator.cc @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2014 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. + */ + +#define _USE_MATH_DEFINES + +#include "webrtc/common_audio/window_generator.h" + +#include +#include + +#include "webrtc/base/checks.h" + +using std::complex; + +namespace { + +// Modified Bessel function of order 0 for complex inputs. +complex I0(complex x) { + complex y = x / 3.75f; + y *= y; + return 1.0f + y * ( + 3.5156229f + y * ( + 3.0899424f + y * ( + 1.2067492f + y * ( + 0.2659732f + y * ( + 0.360768e-1f + y * 0.45813e-2f))))); +} + +} // namespace + +namespace webrtc { + +void WindowGenerator::Hanning(int length, float* window) { + CHECK_GT(length, 1); + CHECK(window != nullptr); + for (int i = 0; i < length; ++i) { + window[i] = 0.5f * (1 - cosf(2 * static_cast(M_PI) * i / + (length - 1))); + } +} + +void WindowGenerator::KaiserBesselDerived(float alpha, int length, + float* window) { + CHECK_GT(length, 1); + CHECK(window != nullptr); + + const int half = (length + 1) / 2; + float sum = 0.0f; + + for (int i = 0; i <= half; ++i) { + complex r = (4.0f * i) / length - 1.0f; + sum += I0(static_cast(M_PI) * alpha * sqrt(1.0f - r * r)).real(); + window[i] = sum; + } + for (int i = length - 1; i >= half; --i) { + window[length - i - 1] = sqrtf(window[length - i - 1] / sum); + window[i] = window[length - i - 1]; + } + if (length % 2 == 1) { + window[half - 1] = sqrtf(window[half - 1] / sum); + } +} + +} // namespace webrtc + diff --git a/webrtc/common_audio/window_generator.h b/webrtc/common_audio/window_generator.h new file mode 100644 index 0000000000..ee0acada52 --- /dev/null +++ b/webrtc/common_audio/window_generator.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2014 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. + */ + +#ifndef WEBRTC_COMMON_AUDIO_WINDOW_GENERATOR_H_ +#define WEBRTC_COMMON_AUDIO_WINDOW_GENERATOR_H_ + +#include "webrtc/base/constructormagic.h" + +namespace webrtc { + +// Helper class with generators for various signal transform windows. +class WindowGenerator { + public: + static void Hanning(int length, float* window); + static void KaiserBesselDerived(float alpha, int length, float* window); + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(WindowGenerator); +}; + +} // namespace webrtc + +#endif // WEBRTC_COMMON_AUDIO_WINDOW_GENERATOR_H_ + diff --git a/webrtc/common_audio/window_generator_unittest.cc b/webrtc/common_audio/window_generator_unittest.cc new file mode 100644 index 0000000000..124b301df6 --- /dev/null +++ b/webrtc/common_audio/window_generator_unittest.cc @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2014 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 "webrtc/common_audio/window_generator.h" + +#include + +#include "testing/gtest/include/gtest/gtest.h" + +namespace webrtc { + +TEST(WindowGeneratorTest, KaiserBesselDerived) { + float window[7]; + + memset(window, 0, sizeof(window)); + + WindowGenerator::KaiserBesselDerived(0.397856f, 2, window); + ASSERT_NEAR(window[0], 0.707106f, 1e-6f); + ASSERT_NEAR(window[1], 0.707106f, 1e-6f); + ASSERT_NEAR(window[2], 0.0f, 1e-6f); + ASSERT_NEAR(window[3], 0.0f, 1e-6f); + ASSERT_NEAR(window[4], 0.0f, 1e-6f); + ASSERT_NEAR(window[5], 0.0f, 1e-6f); + ASSERT_NEAR(window[6], 0.0f, 1e-6f); + + WindowGenerator::KaiserBesselDerived(0.397856f, 3, window); + ASSERT_NEAR(window[0], 0.598066f, 1e-6f); + ASSERT_NEAR(window[1], 0.922358f, 1e-6f); + ASSERT_NEAR(window[2], 0.598066f, 1e-6f); + ASSERT_NEAR(window[3], 0.0f, 1e-6f); + ASSERT_NEAR(window[4], 0.0f, 1e-6f); + ASSERT_NEAR(window[5], 0.0f, 1e-6f); + ASSERT_NEAR(window[6], 0.0f, 1e-6f); + + WindowGenerator::KaiserBesselDerived(0.397856f, 6, window); + ASSERT_NEAR(window[0], 0.458495038865344f, 1e-6f); + ASSERT_NEAR(window[1], 0.707106781186548f, 1e-6f); + ASSERT_NEAR(window[2], 0.888696967101760f, 1e-6f); + ASSERT_NEAR(window[3], 0.888696967101760f, 1e-6f); + ASSERT_NEAR(window[4], 0.707106781186548f, 1e-6f); + ASSERT_NEAR(window[5], 0.458495038865344f, 1e-6f); + ASSERT_NEAR(window[6], 0.0f, 1e-6f); +} + +TEST(WindowGeneratorTest, Hanning) { + float window[7]; + + memset(window, 0, sizeof(window)); + + window[0] = -1.0f; + window[1] = -1.0f; + WindowGenerator::Hanning(2, window); + ASSERT_NEAR(window[0], 0.0f, 1e-6f); + ASSERT_NEAR(window[1], 0.0f, 1e-6f); + ASSERT_NEAR(window[2], 0.0f, 1e-6f); + ASSERT_NEAR(window[3], 0.0f, 1e-6f); + ASSERT_NEAR(window[4], 0.0f, 1e-6f); + ASSERT_NEAR(window[5], 0.0f, 1e-6f); + ASSERT_NEAR(window[6], 0.0f, 1e-6f); + + window[0] = -1.0f; + window[2] = -1.0f; + WindowGenerator::Hanning(3, window); + ASSERT_NEAR(window[0], 0.0f, 1e-6f); + ASSERT_NEAR(window[1], 1.0f, 1e-6f); + ASSERT_NEAR(window[2], 0.0f, 1e-6f); + ASSERT_NEAR(window[3], 0.0f, 1e-6f); + ASSERT_NEAR(window[4], 0.0f, 1e-6f); + ASSERT_NEAR(window[5], 0.0f, 1e-6f); + ASSERT_NEAR(window[6], 0.0f, 1e-6f); + + window[0] = -1.0f; + window[5] = -1.0f; + WindowGenerator::Hanning(6, window); + ASSERT_NEAR(window[0], 0.0f, 1e-6f); + ASSERT_NEAR(window[1], 0.345491f, 1e-6f); + ASSERT_NEAR(window[2], 0.904508f, 1e-6f); + ASSERT_NEAR(window[3], 0.904508f, 1e-6f); + ASSERT_NEAR(window[4], 0.345491f, 1e-6f); + ASSERT_NEAR(window[5], 0.0f, 1e-6f); + ASSERT_NEAR(window[6], 0.0f, 1e-6f); +} + +} // namespace webrtc + diff --git a/webrtc/system_wrappers/BUILD.gn b/webrtc/system_wrappers/BUILD.gn index 14deab9914..c193c28b77 100644 --- a/webrtc/system_wrappers/BUILD.gn +++ b/webrtc/system_wrappers/BUILD.gn @@ -17,6 +17,7 @@ config("system_wrappers_inherited_config") { static_library("system_wrappers") { sources = [ + "interface/aligned_array.h", "interface/aligned_malloc.h", "interface/atomic32.h", "interface/clock.h", @@ -107,7 +108,7 @@ static_library("system_wrappers") { configs += [ "..:common_config" ] - if (is_clang) { + if (is_clang) { # Suppress warnings from Chrome's Clang plugins. # See http://code.google.com/p/webrtc/issues/detail?id=163 for details. configs -= [ "//build/config/clang:find_bad_constructs" ] diff --git a/webrtc/system_wrappers/interface/aligned_array.h b/webrtc/system_wrappers/interface/aligned_array.h new file mode 100644 index 0000000000..4b5c276d43 --- /dev/null +++ b/webrtc/system_wrappers/interface/aligned_array.h @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2014 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. + */ + +#ifndef WEBRTC_SYSTEM_WRAPPERS_INTERFACE_ALIGNED_ARRAY_ +#define WEBRTC_SYSTEM_WRAPPERS_INTERFACE_ALIGNED_ARRAY_ + +#include "webrtc/base/checks.h" +#include "webrtc/system_wrappers/interface/aligned_malloc.h" + +namespace webrtc { + +// Wrapper class for aligned arrays. Every row (and the first dimension) are +// aligned to the given byte alignment. +template class AlignedArray { + public: + AlignedArray(int rows, int cols, int alignment) + : rows_(rows), + cols_(cols), + alignment_(alignment) { + CHECK_GT(alignment_, 0); + head_row_ = static_cast(AlignedMalloc(rows_ * sizeof(*head_row_), + alignment_)); + for (int i = 0; i < rows_; ++i) { + head_row_[i] = static_cast(AlignedMalloc(cols_ * sizeof(**head_row_), + alignment_)); + } + } + + ~AlignedArray() { + for (int i = 0; i < rows_; ++i) { + AlignedFree(head_row_[i]); + } + AlignedFree(head_row_); + } + + T* const* Array() { + return head_row_; + } + + const T* const* Array() const { + return head_row_; + } + + T* Row(int row) { + CHECK_LE(row, rows_); + return head_row_[row]; + } + + const T* Row(int row) const { + CHECK_LE(row, rows_); + return head_row_[row]; + } + + T& At(int row, int col) { + CHECK_LE(col, cols_); + return Row(row)[col]; + } + + const T& At(int row, int col) const { + CHECK_LE(col, cols_); + return Row(row)[col]; + } + + int rows() const { + return rows_; + } + + int cols() const { + return cols_; + } + + private: + int rows_; + int cols_; + int alignment_; + T** head_row_; +}; + +} // namespace webrtc + +#endif // WEBRTC_SYSTEM_WRAPPERS_INTERFACE_ALIGNED_ARRAY_ + diff --git a/webrtc/system_wrappers/source/aligned_array_unittest.cc b/webrtc/system_wrappers/source/aligned_array_unittest.cc new file mode 100644 index 0000000000..e5e556dff5 --- /dev/null +++ b/webrtc/system_wrappers/source/aligned_array_unittest.cc @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2014 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 "webrtc/system_wrappers/interface/aligned_array.h" + +#include + +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +bool IsAligned(const void* ptr, int alignment) { + return reinterpret_cast(ptr) % alignment == 0; +} + +} // namespace + +namespace webrtc { + +TEST(AlignedArrayTest, CheckAlignment) { + AlignedArray arr(10, 7, 128); + ASSERT_TRUE(IsAligned(arr.Array(), 128)); + for (int i = 0; i < 10; ++i) { + ASSERT_TRUE(IsAligned(arr.Row(i), 128)); + ASSERT_EQ(arr.Row(i), arr.Array()[i]); + } +} + +TEST(AlignedArrayTest, CheckOverlap) { + AlignedArray arr(10, 7, 128); + + for (int i = 0; i < 10; ++i) { + for (int j = 0; j < 7; ++j) { + arr.At(i, j) = 20 * i + j; + } + } + + for (int i = 0; i < 10; ++i) { + for (int j = 0; j < 7; ++j) { + ASSERT_EQ(arr.At(i, j), 20 * i + j); + ASSERT_EQ(arr.Row(i)[j], 20 * i + j); + ASSERT_EQ(arr.Array()[i][j], 20 * i + j); + } + } +} + +} // namespace webrtc + diff --git a/webrtc/system_wrappers/source/system_wrappers.gyp b/webrtc/system_wrappers/source/system_wrappers.gyp index e6c4216da7..870d88a40e 100644 --- a/webrtc/system_wrappers/source/system_wrappers.gyp +++ b/webrtc/system_wrappers/source/system_wrappers.gyp @@ -25,6 +25,7 @@ ], }, 'sources': [ + '../interface/aligned_array.h', '../interface/aligned_malloc.h', '../interface/atomic32.h', '../interface/clock.h', diff --git a/webrtc/system_wrappers/source/system_wrappers_tests.gyp b/webrtc/system_wrappers/source/system_wrappers_tests.gyp index 75081b9dc4..f77b985a22 100644 --- a/webrtc/system_wrappers/source/system_wrappers_tests.gyp +++ b/webrtc/system_wrappers/source/system_wrappers_tests.gyp @@ -18,6 +18,7 @@ '<(webrtc_root)/test/test.gyp:test_support_main', ], 'sources': [ + 'aligned_array_unittest.cc', 'aligned_malloc_unittest.cc', 'clock_unittest.cc', 'condition_variable_unittest.cc',