diff --git a/webrtc/common_audio/BUILD.gn b/webrtc/common_audio/BUILD.gn index ac09f961c6..81df70cd66 100644 --- a/webrtc/common_audio/BUILD.gn +++ b/webrtc/common_audio/BUILD.gn @@ -19,8 +19,12 @@ config("common_audio_config") { source_set("common_audio") { sources = [ + "../modules/audio_processing/channel_buffer.cc", + "../modules/audio_processing/channel_buffer.h", "audio_converter.cc", "audio_converter.h", + "audio_ring_buffer.cc", + "audio_ring_buffer.h", "audio_util.cc", "blocker.cc", "blocker.h", diff --git a/webrtc/common_audio/audio_ring_buffer.cc b/webrtc/common_audio/audio_ring_buffer.cc new file mode 100644 index 0000000000..0ec53a34b8 --- /dev/null +++ b/webrtc/common_audio/audio_ring_buffer.cc @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2015 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/audio_ring_buffer.h" + +#include "webrtc/base/checks.h" +#include "webrtc/common_audio/ring_buffer.h" + +// This is a simple multi-channel wrapper over the ring_buffer.h C interface. + +namespace webrtc { + +AudioRingBuffer::AudioRingBuffer(size_t channels, size_t max_frames) { + for (size_t i = 0; i < channels; ++i) + buffers_.push_back(WebRtc_CreateBuffer(max_frames, sizeof(float))); +} + +AudioRingBuffer::~AudioRingBuffer() { + for (auto buf : buffers_) + WebRtc_FreeBuffer(buf); +} + +void AudioRingBuffer::Write(const float* const* data, size_t channels, + size_t frames) { + DCHECK_EQ(buffers_.size(), channels); + for (size_t i = 0; i < channels; ++i) { + size_t written = WebRtc_WriteBuffer(buffers_[i], data[i], frames); + CHECK_EQ(written, frames); + } +} + +void AudioRingBuffer::Read(float* const* data, size_t channels, size_t frames) { + DCHECK_EQ(buffers_.size(), channels); + for (size_t i = 0; i < channels; ++i) { + size_t read = WebRtc_ReadBuffer(buffers_[i], nullptr, data[i], frames); + CHECK_EQ(read, frames); + } +} + +size_t AudioRingBuffer::ReadFramesAvailable() const { + // All buffers have the same amount available. + return WebRtc_available_read(buffers_[0]); +} + +size_t AudioRingBuffer::WriteFramesAvailable() const { + // All buffers have the same amount available. + return WebRtc_available_write(buffers_[0]); +} + +void AudioRingBuffer::MoveReadPosition(int frames) { + for (auto buf : buffers_) { + int moved = WebRtc_MoveReadPtr(buf, frames); + CHECK_EQ(moved, frames); + } +} + +} // namespace webrtc diff --git a/webrtc/common_audio/audio_ring_buffer.h b/webrtc/common_audio/audio_ring_buffer.h new file mode 100644 index 0000000000..ebfc9d9a9c --- /dev/null +++ b/webrtc/common_audio/audio_ring_buffer.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2015 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 +#include + +struct RingBuffer; + +namespace webrtc { + +// A ring buffer tailored for float deinterleaved audio. Any operation that +// cannot be performed as requested will cause a crash (e.g. insufficient data +// in the buffer to fulfill a read request.) +class AudioRingBuffer final { + public: + // Specify the number of channels and maximum number of frames the buffer will + // contain. + AudioRingBuffer(size_t channels, size_t max_frames); + ~AudioRingBuffer(); + + // Copy |data| to the buffer and advance the write pointer. |channels| must + // be the same as at creation time. + void Write(const float* const* data, size_t channels, size_t frames); + + // Copy from the buffer to |data| and advance the read pointer. |channels| + // must be the same as at creation time. + void Read(float* const* data, size_t channels, size_t frames); + + size_t ReadFramesAvailable() const; + size_t WriteFramesAvailable() const; + + // Positive values advance the read pointer and negative values withdraw + // the read pointer (i.e. flush and stuff the buffer respectively.) + void MoveReadPosition(int frames); + + private: + // We don't use a ScopedVector because it doesn't support a specialized + // deleter (like scoped_ptr for instance.) + std::vector buffers_; +}; + +} // namespace webrtc diff --git a/webrtc/common_audio/audio_ring_buffer_unittest.cc b/webrtc/common_audio/audio_ring_buffer_unittest.cc new file mode 100644 index 0000000000..ee0e60224e --- /dev/null +++ b/webrtc/common_audio/audio_ring_buffer_unittest.cc @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2015 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/audio_ring_buffer.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "webrtc/modules/audio_processing/channel_buffer.h" + +namespace webrtc { + +class AudioRingBufferTest : + public ::testing::TestWithParam< ::testing::tuple > { +}; + +void ReadAndWriteTest(const ChannelBuffer& input, + size_t num_write_chunk_frames, + size_t num_read_chunk_frames, + size_t buffer_frames, + ChannelBuffer* output) { + const size_t num_channels = input.num_channels(); + const size_t total_frames = input.samples_per_channel(); + AudioRingBuffer buf(num_channels, buffer_frames); + scoped_ptr slice(new float*[num_channels]); + + size_t input_pos = 0; + size_t output_pos = 0; + while (input_pos + buf.WriteFramesAvailable() < total_frames) { + // Write until the buffer is as full as possible. + while (buf.WriteFramesAvailable() >= num_write_chunk_frames) { + buf.Write(input.Slice(slice.get(), static_cast(input_pos)), + num_channels, num_write_chunk_frames); + input_pos += num_write_chunk_frames; + } + // Read until the buffer is as empty as possible. + while (buf.ReadFramesAvailable() >= num_read_chunk_frames) { + EXPECT_LT(output_pos, total_frames); + buf.Read(output->Slice(slice.get(), static_cast(output_pos)), + num_channels, num_read_chunk_frames); + output_pos += num_read_chunk_frames; + } + } + + // Write and read the last bit. + if (input_pos < total_frames) + buf.Write(input.Slice(slice.get(), static_cast(input_pos)), + num_channels, total_frames - input_pos); + if (buf.ReadFramesAvailable()) + buf.Read(output->Slice(slice.get(), static_cast(output_pos)), + num_channels, buf.ReadFramesAvailable()); + EXPECT_EQ(0u, buf.ReadFramesAvailable()); +} + +TEST_P(AudioRingBufferTest, ReadDataMatchesWrittenData) { + const size_t kFrames = 5000; + const size_t num_channels = ::testing::get<3>(GetParam()); + + // Initialize the input data to an increasing sequence. + ChannelBuffer input(kFrames, static_cast(num_channels)); + for (size_t i = 0; i < num_channels; ++i) + for (size_t j = 0; j < kFrames; ++j) + input.channels()[i][j] = i * j; + + ChannelBuffer output(kFrames, static_cast(num_channels)); + ReadAndWriteTest(input, + ::testing::get<0>(GetParam()), + ::testing::get<1>(GetParam()), + ::testing::get<2>(GetParam()), + &output); + + // Verify the read data matches the input. + for (size_t i = 0; i < num_channels; ++i) + for (size_t j = 0; j < kFrames; ++j) + EXPECT_EQ(input.channels()[i][j], output.channels()[i][j]); +} + +INSTANTIATE_TEST_CASE_P( + AudioRingBufferTest, AudioRingBufferTest, + ::testing::Combine(::testing::Values(10, 20, 42), // num_write_chunk_frames + ::testing::Values(1, 10, 17), // num_read_chunk_frames + ::testing::Values(100, 256), // buffer_frames + ::testing::Values(1, 4))); // num_channels + +TEST_F(AudioRingBufferTest, MoveReadPosition) { + const size_t kNumChannels = 1; + const float kInputArray[] = {1, 2, 3, 4}; + const size_t kNumFrames = sizeof(kInputArray) / sizeof(*kInputArray); + ChannelBuffer input(kInputArray, kNumFrames, kNumChannels); + AudioRingBuffer buf(kNumChannels, kNumFrames); + buf.Write(input.channels(), kNumChannels, kNumFrames); + + buf.MoveReadPosition(3); + ChannelBuffer output(1, kNumChannels); + buf.Read(output.channels(), kNumChannels, 1); + EXPECT_EQ(4, output.data()[0]); + buf.MoveReadPosition(-3); + buf.Read(output.channels(), kNumChannels, 1); + EXPECT_EQ(2, output.data()[0]); +} + +} // namespace webrtc diff --git a/webrtc/common_audio/blocker.cc b/webrtc/common_audio/blocker.cc index 400db0cae8..7ced460637 100644 --- a/webrtc/common_audio/blocker.cc +++ b/webrtc/common_audio/blocker.cc @@ -110,7 +110,7 @@ Blocker::Blocker(int chunk_size, num_output_channels_(num_output_channels), initial_delay_(block_size_ - gcd(chunk_size, shift_amount)), frame_offset_(0), - input_buffer_(chunk_size_ + initial_delay_, num_input_channels_), + input_buffer_(num_input_channels_, chunk_size_ + initial_delay_), output_buffer_(chunk_size_ + initial_delay_, num_output_channels_), input_block_(block_size_, num_input_channels_), output_block_(block_size_, num_output_channels_), @@ -118,15 +118,8 @@ Blocker::Blocker(int chunk_size, shift_amount_(shift_amount), callback_(callback) { CHECK_LE(num_output_channels_, num_input_channels_); - 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)); + input_buffer_.MoveReadPosition(-initial_delay_); } // When block_size < chunk_size the input and output buffers look like this: @@ -177,25 +170,14 @@ void Blocker::ProcessChunk(const float* const* input, 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_); - + input_buffer_.Write(input, num_input_channels, chunk_size_); 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); + input_buffer_.Read(input_block_.channels(), num_input_channels, + block_size_); + input_buffer_.MoveReadPosition(-block_size_ + shift_amount_); ApplyWindow(window_.get(), block_size_, @@ -231,15 +213,6 @@ void Blocker::ProcessChunk(const float* const* input, output, 0); - // Copy input buffer [chunk_size_, chunk_size_ + initial_delay] - // to input buffer [0, initial_delay] - MoveFrames(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. MoveFrames(output_buffer_.channels(), diff --git a/webrtc/common_audio/blocker.h b/webrtc/common_audio/blocker.h index 68289d57b5..5809d570a8 100644 --- a/webrtc/common_audio/blocker.h +++ b/webrtc/common_audio/blocker.h @@ -11,6 +11,7 @@ #ifndef WEBRTC_INTERNAL_BEAMFORMER_BLOCKER_H_ #define WEBRTC_INTERNAL_BEAMFORMER_BLOCKER_H_ +#include "webrtc/common_audio/audio_ring_buffer.h" #include "webrtc/modules/audio_processing/channel_buffer.h" #include "webrtc/system_wrappers/interface/scoped_ptr.h" @@ -93,8 +94,10 @@ class Blocker { // input and output buffers are responsible for saving those frames between // calls to ProcessChunk(). // - // Both contain |initial delay| + |chunk_size| frames. - ChannelBuffer input_buffer_; + // Both contain |initial delay| + |chunk_size| frames. The input is a fairly + // standard FIFO, but due to the overlap-add it's harder to use an + // AudioRingBuffer for the output. + AudioRingBuffer input_buffer_; ChannelBuffer output_buffer_; // Space for the input block (can't wrap because of windowing). diff --git a/webrtc/common_audio/common_audio.gyp b/webrtc/common_audio/common_audio.gyp index d50c2c557c..55ba05cdcc 100644 --- a/webrtc/common_audio/common_audio.gyp +++ b/webrtc/common_audio/common_audio.gyp @@ -29,8 +29,12 @@ ], }, 'sources': [ + '../modules/audio_processing/channel_buffer.cc', + '../modules/audio_processing/channel_buffer.h', 'audio_converter.cc', 'audio_converter.h', + 'audio_ring_buffer.cc', + 'audio_ring_buffer.h', 'audio_util.cc', 'blocker.cc', 'blocker.h', @@ -228,6 +232,7 @@ ], 'sources': [ 'audio_converter_unittest.cc', + 'audio_ring_buffer_unittest.cc', 'audio_util_unittest.cc', 'blocker_unittest.cc', 'fir_filter_unittest.cc', diff --git a/webrtc/common_audio/ring_buffer.h b/webrtc/common_audio/ring_buffer.h index 861b8ac8fa..4125c48d01 100644 --- a/webrtc/common_audio/ring_buffer.h +++ b/webrtc/common_audio/ring_buffer.h @@ -22,7 +22,7 @@ extern "C" { typedef struct RingBuffer RingBuffer; -// Returns NULL on failure. +// Creates and initializes the buffer. Returns NULL on failure. RingBuffer* WebRtc_CreateBuffer(size_t element_count, size_t element_size); void WebRtc_InitBuffer(RingBuffer* handle); void WebRtc_FreeBuffer(void* handle); diff --git a/webrtc/modules/audio_processing/BUILD.gn b/webrtc/modules/audio_processing/BUILD.gn index 6e968d524a..9707f08c96 100644 --- a/webrtc/modules/audio_processing/BUILD.gn +++ b/webrtc/modules/audio_processing/BUILD.gn @@ -76,8 +76,6 @@ source_set("audio_processing") { "beamformer/covariance_matrix_generator.cc", "beamformer/covariance_matrix_generator.h", "beamformer/matrix.h", - "channel_buffer.cc", - "channel_buffer.h", "common.h", "echo_cancellation_impl.cc", "echo_cancellation_impl.h", diff --git a/webrtc/modules/audio_processing/audio_processing.gypi b/webrtc/modules/audio_processing/audio_processing.gypi index c26054d5eb..3feb9d0e22 100644 --- a/webrtc/modules/audio_processing/audio_processing.gypi +++ b/webrtc/modules/audio_processing/audio_processing.gypi @@ -85,8 +85,6 @@ 'beamformer/covariance_matrix_generator.cc', 'beamformer/covariance_matrix_generator.h', 'beamformer/matrix.h', - 'channel_buffer.cc', - 'channel_buffer.h', 'common.h', 'echo_cancellation_impl.cc', 'echo_cancellation_impl.h', diff --git a/webrtc/modules/audio_processing/channel_buffer.h b/webrtc/modules/audio_processing/channel_buffer.h index 6ecc07ab53..31ebaa2d57 100644 --- a/webrtc/modules/audio_processing/channel_buffer.h +++ b/webrtc/modules/audio_processing/channel_buffer.h @@ -19,7 +19,8 @@ namespace webrtc { // Helper to encapsulate a contiguous data buffer with access to a pointer -// array of the deinterleaved channels. +// array of the deinterleaved channels. The buffer is zero initialized at +// creation. template class ChannelBuffer { public: @@ -74,6 +75,19 @@ class ChannelBuffer { T* const* channels() { return channels_.get(); } const T* const* channels() const { return channels_.get(); } + // Sets the |slice| pointers to the |start_frame| position for each channel. + // Returns |slice| for convenience. + const T* const* Slice(T** slice, int start_frame) const { + DCHECK_LT(start_frame, samples_per_channel_); + for (int i = 0; i < num_channels_; ++i) + slice[i] = &channels_[i][start_frame]; + return slice; + } + T** Slice(T** slice, int start_frame) { + const ChannelBuffer* t = this; + return const_cast(t->Slice(slice, start_frame)); + } + int samples_per_channel() const { return samples_per_channel_; } int num_channels() const { return num_channels_; } int length() const { return samples_per_channel_ * num_channels_; }