Add an AudioRingBuffer class wrapper for the ring_buffer.h C interface.

Integrate it in Blocker to demonstrate use.

TEST=beamforming sounds good.
R=aluebs@webrtc.org, mgraczyk@chromium.org, sahark@google.com

Review URL: https://webrtc-codereview.appspot.com/36799004

git-svn-id: http://webrtc.googlecode.com/svn/trunk@8157 4adac7df-926f-26a2-2b94-8c16560cd09d
diff --git a/webrtc/common_audio/BUILD.gn b/webrtc/common_audio/BUILD.gn
index ac09f96..81df70c 100644
--- a/webrtc/common_audio/BUILD.gn
+++ b/webrtc/common_audio/BUILD.gn
@@ -19,8 +19,12 @@
 
 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 0000000..0ec53a3
--- /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 0000000..ebfc9d9
--- /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 <stddef.h>
+#include <vector>
+
+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<RingBuffer*> 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 0000000..ee0e602
--- /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<int, int, int, int> > {
+};
+
+void ReadAndWriteTest(const ChannelBuffer<float>& input,
+                      size_t num_write_chunk_frames,
+                      size_t num_read_chunk_frames,
+                      size_t buffer_frames,
+                      ChannelBuffer<float>* 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<float*[]> 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<int>(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<int>(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<int>(input_pos)),
+              num_channels, total_frames - input_pos);
+  if (buf.ReadFramesAvailable())
+    buf.Read(output->Slice(slice.get(), static_cast<int>(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<float> input(kFrames, static_cast<int>(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<float> output(kFrames, static_cast<int>(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<float> input(kInputArray, kNumFrames, kNumChannels);
+  AudioRingBuffer buf(kNumChannels, kNumFrames);
+  buf.Write(input.channels(), kNumChannels, kNumFrames);
+
+  buf.MoveReadPosition(3);
+  ChannelBuffer<float> 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 400db0c..7ced460 100644
--- a/webrtc/common_audio/blocker.cc
+++ b/webrtc/common_audio/blocker.cc
@@ -110,7 +110,7 @@
       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 @@
       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 @@
   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 @@
              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 68289d5..5809d57 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 @@
   // 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_;
+  // 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<float> 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 d50c2c5..55ba05c 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 861b8ac..4125c48 100644
--- a/webrtc/common_audio/ring_buffer.h
+++ b/webrtc/common_audio/ring_buffer.h
@@ -22,7 +22,7 @@
 
 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 6e968d5..9707f08 100644
--- a/webrtc/modules/audio_processing/BUILD.gn
+++ b/webrtc/modules/audio_processing/BUILD.gn
@@ -76,8 +76,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/audio_processing.gypi b/webrtc/modules/audio_processing/audio_processing.gypi
index c26054d..3feb9d0 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 6ecc07a..31ebaa2 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 <typename T>
 class ChannelBuffer {
  public:
@@ -74,6 +75,19 @@
   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>* t = this;
+    return const_cast<T**>(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_; }