Import LappedTransform and friends.
Add code for doing block-based frequency domain processing. Developed and reviewed in isolation. Corresponding export CL: https://chromereviews.googleplex.com/95187013/ R=bercic@google.com, kjellander@webrtc.org, turaj@webrtc.org Review URL: https://webrtc-codereview.appspot.com/31539004 git-svn-id: http://webrtc.googlecode.com/svn/trunk@7359 4adac7df-926f-26a2-2b94-8c16560cd09d
This commit is contained in:
parent
593c3a0868
commit
325cff01b4
@ -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",
|
||||
|
220
webrtc/common_audio/blocker.cc
Normal file
220
webrtc/common_audio/blocker.cc
Normal file
@ -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 <string.h>
|
||||
|
||||
#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
|
119
webrtc/common_audio/blocker.h
Normal file
119
webrtc/common_audio/blocker.h
Normal file
@ -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<float> input_buffer_;
|
||||
ChannelBuffer<float> output_buffer_;
|
||||
|
||||
// Space for the input block (can't wrap because of windowing).
|
||||
ChannelBuffer<float> input_block_;
|
||||
|
||||
// Space for the output block (can't wrap because of overlap/add).
|
||||
ChannelBuffer<float> output_block_;
|
||||
|
||||
scoped_ptr<float[]> 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_
|
245
webrtc/common_audio/blocker_unittest.cc
Normal file
245
webrtc/common_audio/blocker_unittest.cc
Normal file
@ -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<float> 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<float> expected_output_cb(
|
||||
kExpectedOutput[0], kNumFrames, kNumInputChannels);
|
||||
|
||||
const float kWindow[kBlockSize] = {2.f, 2.f, 2.f, 2.f};
|
||||
|
||||
ChannelBuffer<float> actual_output_cb(kNumFrames, kNumOutputChannels);
|
||||
ChannelBuffer<float> input_chunk_cb(kChunkSize, kNumInputChannels);
|
||||
ChannelBuffer<float> 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<float> 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<float> expected_output_cb(
|
||||
kExpectedOutput[0], kNumFrames, kNumInputChannels);
|
||||
|
||||
const float kWindow[kBlockSize] = {2.f, 2.f, 2.f, 2.f};
|
||||
|
||||
ChannelBuffer<float> actual_output_cb(kNumFrames, kNumOutputChannels);
|
||||
ChannelBuffer<float> input_chunk_cb(kChunkSize, kNumInputChannels);
|
||||
ChannelBuffer<float> 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<float> 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<float> expected_output_cb(
|
||||
kExpectedOutput[0], kNumFrames, kNumInputChannels);
|
||||
|
||||
const float kWindow[kBlockSize] = {2.f, 2.f, 2.f, 2.f};
|
||||
|
||||
ChannelBuffer<float> actual_output_cb(kNumFrames, kNumOutputChannels);
|
||||
ChannelBuffer<float> input_chunk_cb(kChunkSize, kNumInputChannels);
|
||||
ChannelBuffer<float> 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
|
@ -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',
|
||||
|
107
webrtc/common_audio/lapped_transform.cc
Normal file
107
webrtc/common_audio/lapped_transform.cc
Normal file
@ -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 <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
#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<float*>(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
|
||||
|
101
webrtc/common_audio/lapped_transform.h
Normal file
101
webrtc/common_audio/lapped_transform.h
Normal file
@ -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 <complex>
|
||||
|
||||
#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<float>* const* in_block,
|
||||
int in_channels, int frames,
|
||||
int out_channels,
|
||||
std::complex<float>* 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> blocker_;
|
||||
|
||||
RealFourier fft_;
|
||||
int cplx_length_;
|
||||
AlignedArray<float> real_buf_;
|
||||
AlignedArray<std::complex<float> > cplx_pre_;
|
||||
AlignedArray<std::complex<float> > cplx_post_;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
||||
#endif // WEBRTC_COMMON_AUDIO_LAPPED_TRANSFORM_H_
|
||||
|
174
webrtc/common_audio/lapped_transform_unittest.cc
Normal file
174
webrtc/common_audio/lapped_transform_unittest.cc
Normal file
@ -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 <cmath>
|
||||
#include <cstring>
|
||||
|
||||
#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<float>* const* in_block,
|
||||
int in_channels, int frames, int out_channels,
|
||||
complex<float>* 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<float>* const* in_block,
|
||||
int in_channels, int frames, int out_channels,
|
||||
complex<float>* 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
|
||||
|
88
webrtc/common_audio/real_fourier.cc
Normal file
88
webrtc/common_audio/real_fourier.cc
Normal file
@ -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 <cstdlib>
|
||||
|
||||
#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<float*>(
|
||||
AlignedMalloc(sizeof(float) * count, kFftBufferAlignment)));
|
||||
}
|
||||
|
||||
RealFourier::fft_cplx_scoper RealFourier::AllocCplxBuffer(int count) {
|
||||
return fft_cplx_scoper(static_cast<complex<float>*>(
|
||||
AlignedMalloc(sizeof(complex<float>) * count, kFftBufferAlignment)));
|
||||
}
|
||||
|
||||
void RealFourier::Forward(const float* src, complex<float>* dest) const {
|
||||
OMXResult r;
|
||||
r = omxSP_FFTFwd_RToCCS_F32(src, reinterpret_cast<OMX_F32*>(dest), omx_spec_);
|
||||
CHECK_EQ(r, OMX_Sts_NoErr);
|
||||
}
|
||||
|
||||
void RealFourier::Inverse(const complex<float>* src, float* dest) const {
|
||||
OMXResult r;
|
||||
r = omxSP_FFTInv_CCSToR_F32(reinterpret_cast<const OMX_F32*>(src), dest,
|
||||
omx_spec_);
|
||||
CHECK_EQ(r, OMX_Sts_NoErr);
|
||||
}
|
||||
|
||||
} // namespace webrtc
|
||||
|
85
webrtc/common_audio/real_fourier.h
Normal file
85
webrtc/common_audio/real_fourier.h
Normal file
@ -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 <complex>
|
||||
|
||||
#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<float[], AlignedFreeDeleter> fft_real_scoper;
|
||||
typedef scoped_ptr<std::complex<float>[], 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<float>* dest) const;
|
||||
|
||||
// Inverse transform. Same input format as output above, conjugate pairs
|
||||
// not needed.
|
||||
void Inverse(const std::complex<float>* 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_
|
||||
|
105
webrtc/common_audio/real_fourier_unittest.cc
Normal file
105
webrtc/common_audio/real_fourier_unittest.cc
Normal file
@ -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 <stdlib.h>
|
||||
|
||||
#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<int64_t>(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<int64_t>(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<float>(10.0f, 0.0f);
|
||||
cplx_buffer_[1] = complex<float>(-2.0f, 2.0f);
|
||||
cplx_buffer_[2] = complex<float>(-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
|
||||
|
72
webrtc/common_audio/window_generator.cc
Normal file
72
webrtc/common_audio/window_generator.cc
Normal file
@ -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 <cmath>
|
||||
#include <complex>
|
||||
|
||||
#include "webrtc/base/checks.h"
|
||||
|
||||
using std::complex;
|
||||
|
||||
namespace {
|
||||
|
||||
// Modified Bessel function of order 0 for complex inputs.
|
||||
complex<float> I0(complex<float> x) {
|
||||
complex<float> 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<float>(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<float> r = (4.0f * i) / length - 1.0f;
|
||||
sum += I0(static_cast<float>(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
|
||||
|
31
webrtc/common_audio/window_generator.h
Normal file
31
webrtc/common_audio/window_generator.h
Normal file
@ -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_
|
||||
|
92
webrtc/common_audio/window_generator_unittest.cc
Normal file
92
webrtc/common_audio/window_generator_unittest.cc
Normal file
@ -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 <cstring>
|
||||
|
||||
#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
|
||||
|
@ -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" ]
|
||||
|
89
webrtc/system_wrappers/interface/aligned_array.h
Normal file
89
webrtc/system_wrappers/interface/aligned_array.h
Normal file
@ -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<typename T> class AlignedArray {
|
||||
public:
|
||||
AlignedArray(int rows, int cols, int alignment)
|
||||
: rows_(rows),
|
||||
cols_(cols),
|
||||
alignment_(alignment) {
|
||||
CHECK_GT(alignment_, 0);
|
||||
head_row_ = static_cast<T**>(AlignedMalloc(rows_ * sizeof(*head_row_),
|
||||
alignment_));
|
||||
for (int i = 0; i < rows_; ++i) {
|
||||
head_row_[i] = static_cast<T*>(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_
|
||||
|
55
webrtc/system_wrappers/source/aligned_array_unittest.cc
Normal file
55
webrtc/system_wrappers/source/aligned_array_unittest.cc
Normal file
@ -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 <stdint.h>
|
||||
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
|
||||
namespace {
|
||||
|
||||
bool IsAligned(const void* ptr, int alignment) {
|
||||
return reinterpret_cast<uintptr_t>(ptr) % alignment == 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
TEST(AlignedArrayTest, CheckAlignment) {
|
||||
AlignedArray<bool> 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<int> 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
|
||||
|
@ -25,6 +25,7 @@
|
||||
],
|
||||
},
|
||||
'sources': [
|
||||
'../interface/aligned_array.h',
|
||||
'../interface/aligned_malloc.h',
|
||||
'../interface/atomic32.h',
|
||||
'../interface/clock.h',
|
||||
|
@ -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',
|
||||
|
Loading…
x
Reference in New Issue
Block a user