[mixer_service] Move ChannelStrip to lib/processing

With minor cleanup in the moved code respectively.

Bug: 87651

Multiply: audio-libprocessing-unittests
Multiply: audio_core_unittests
Multiply: audio_mixer_unittests
Multiply: audio-core-api-pipeline-tests
Multiply: audio-core-fidelity-test
Multiply: audio_fidelity_tests
Change-Id: I90706c998c134b63852a9136a09c733252871d0e
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/693795
Commit-Queue: Alper Gungormusler <alperg@google.com>
Reviewed-by: Tom Bergan <tombergan@google.com>
diff --git a/src/media/audio/audio_core/mixer/BUILD.gn b/src/media/audio/audio_core/mixer/BUILD.gn
index c0f5b57..5c66849 100644
--- a/src/media/audio/audio_core/mixer/BUILD.gn
+++ b/src/media/audio/audio_core/mixer/BUILD.gn
@@ -11,7 +11,6 @@
 
 source_set("mixer") {
   sources = [
-    "channel_strip.h",
     "gain.cc",
     "gain.h",
     "intersect.cc",
@@ -84,7 +83,6 @@
 
   sources = [
     "bookkeeping_unittest.cc",
-    "channel_strip_unittest.cc",
     "gain_unittest.cc",
     "intersect_unittest.cc",
     "no_op_unittest.cc",
diff --git a/src/media/audio/audio_core/mixer/channel_strip.h b/src/media/audio/audio_core/mixer/channel_strip.h
deleted file mode 100644
index 0065173..0000000
--- a/src/media/audio/audio_core/mixer/channel_strip.h
+++ /dev/null
@@ -1,99 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
-
-#ifndef SRC_MEDIA_AUDIO_AUDIO_CORE_MIXER_CHANNEL_STRIP_H_
-#define SRC_MEDIA_AUDIO_AUDIO_CORE_MIXER_CHANNEL_STRIP_H_
-
-#include <lib/syslog/cpp/macros.h>
-
-#include <algorithm>
-#include <cstring>
-#include <vector>
-
-namespace media::audio::mixer {
-
-// ChannelStrip lightly manages sections of single-channel audio, useful when processing audio one
-// channel at a time. ChannelStrip is essentially a vector-of-vectors, but also contains convenience
-// methods to shift audio (either all-channels or a single channel) within each channel's "strip".
-class ChannelStrip {
- public:
-  ChannelStrip(int32_t num_channels, int32_t length)
-      : data_(num_channels), num_channels_(num_channels), len_(length) {
-    FX_DCHECK(num_channels > 0);
-    FX_DCHECK(length > 0);
-
-    for (auto& channel : data_) {
-      channel.resize(len_, 0.0f);
-    }
-  }
-
-  ChannelStrip() : ChannelStrip(1, 1) {}
-  ChannelStrip(const ChannelStrip& not_ctor_copyable) = delete;
-  ChannelStrip& operator=(const ChannelStrip& not_copyable) = delete;
-  ChannelStrip(ChannelStrip&& not_ctor_movable) = delete;
-  ChannelStrip& operator=(ChannelStrip&& not_movable) = delete;
-
-  // Used for debugging purposes only
-  static inline std::string ToString(const ChannelStrip& channels);
-
-  // Zero out all channels, leaving each strip (vector) at the specified length
-  void Clear() {
-    for (auto& channel : data_) {
-      std::memset(channel.data(), 0, channel.size() * sizeof(channel[0]));
-    }
-  }
-
-  // Shift the audio in all channels, by the specified amount
-  void ShiftBy(size_t shift_by) {
-    shift_by = std::min(shift_by, static_cast<size_t>(len_));
-
-    for (auto& channel : data_) {
-      memmove(channel.data(), channel.data() + shift_by, (len_ - shift_by) * sizeof(channel[0]));
-      memset(channel.data() + (len_ - shift_by), 0, shift_by * sizeof(channel[0]));
-    }
-  }
-
-  // This returns a vector containing audio data for a single channel. ChannelStrip is essentially a
-  // vector-of-vector and is not "jagged": all channels have the same amount of audio data (all
-  // vectors are the same length).
-  // For this reason, one MUST NOT perform the following to a channel vector returned by []operator:
-  //  - change the underlying vector: ctor, dtor, operator=, assign, swap
-  //  - change the vector's size: clear, emplace[_back], erase[_if], insert, pop, push, resize,
-  //    or operator[] to set a value at index len_ or greater
-  // Only the following can be performed on the vector returned by []operator:
-  //  - read/write existing audio samples: operator[] < len, at, back, data, front, iterators
-  //  - query size/capacity (allowed but unneeded): capacity, empty, max_size, reserve, size, ...
-  std::vector<float>& operator[](size_t index) { return data_[index]; }
-
-  int32_t num_channels() const { return num_channels_; }
-  int64_t length() const { return len_; }
-
- private:
-  std::vector<std::vector<float>> data_;
-  int32_t num_channels_;
-  int64_t len_;
-};
-
-// declared static, used for debugging purposes only
-// Log the contents of the channel strip, channel by channel.
-inline std::string ChannelStrip::ToString(const ChannelStrip& channels) {
-  std::string strip("ChannelStrip: chans ");
-  strip += std::to_string(channels.num_channels_) + ", len " + std::to_string(channels.len_) + "\n";
-
-  for (auto chan = 0; chan < channels.num_channels_; ++chan) {
-    strip += "\tChannel " + std::to_string(chan);
-
-    for (auto idx = 0; idx < channels.len_; ++idx) {
-      if (idx % 16 == 0) {
-        strip += "\n[ " + std::to_string(idx) + "\t]";
-      }
-      strip += "\t" + std::to_string(channels.data_[chan][idx]);
-    }
-    strip += "\n";
-  }
-  return strip;
-}
-
-}  // namespace media::audio::mixer
-
-#endif  // SRC_MEDIA_AUDIO_AUDIO_CORE_MIXER_CHANNEL_STRIP_H_
diff --git a/src/media/audio/audio_core/mixer/channel_strip_unittest.cc b/src/media/audio/audio_core/mixer/channel_strip_unittest.cc
deleted file mode 100644
index e48bdd0..0000000
--- a/src/media/audio/audio_core/mixer/channel_strip_unittest.cc
+++ /dev/null
@@ -1,138 +0,0 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "src/media/audio/audio_core/mixer/channel_strip.h"
-
-#include <gtest/gtest.h>
-
-namespace media::audio {
-namespace {
-
-void ValidateConstruction(mixer::ChannelStrip* strip, int32_t num_channels, int32_t length) {
-  ASSERT_NE(strip, nullptr);
-  ASSERT_GT(num_channels, 0);
-  ASSERT_GT(length, 0);
-
-  EXPECT_EQ(strip->num_channels(), num_channels);
-  EXPECT_EQ(strip->length(), length);
-
-  for (auto chan = 0; chan < num_channels; ++chan) {
-    EXPECT_EQ((*strip)[chan].size(), static_cast<size_t>(length));
-    for (auto idx = 0; idx < length; ++idx) {
-      EXPECT_EQ((*strip)[chan][idx], 0.0f);
-    }
-  }
-}
-
-// Validate ctor default and parameters
-TEST(ChannelStripTest, Construction) {
-  mixer::ChannelStrip data;
-  auto num_chans = 1;
-  auto strip_len = 1;
-  ValidateConstruction(&data, num_chans, strip_len);
-
-  num_chans = 1;
-  strip_len = 3;
-  mixer::ChannelStrip data2(num_chans, strip_len);
-  ValidateConstruction(&data2, num_chans, strip_len);
-
-  num_chans = 4;
-  strip_len = 2;
-  mixer::ChannelStrip data3(num_chans, strip_len);
-  ValidateConstruction(&data3, num_chans, strip_len);
-}
-
-// Sanity test for [] operator
-TEST(ChannelStripTest, SetValues) {
-  mixer::ChannelStrip data(2, 3);
-
-  auto& channel_0 = data[0];
-  auto& channel_0_watcher = data[0];
-  auto& channel_1 = data[1];
-  auto& channel_1_watcher = data[1];
-  auto& data_watcher = data;
-
-  data[0][0] = 1.0f;
-  channel_0[1] = 2.0f;
-  channel_1[0] = 3.0f;
-  data[1][1] = 4.0f;
-
-  EXPECT_EQ(data_watcher[0][0], 1.0f);
-  EXPECT_EQ(channel_0_watcher[1], 2.0f);
-  EXPECT_EQ(channel_1_watcher[0], 3.0f);
-  EXPECT_EQ(data_watcher[1][1], 4.0f);
-}
-
-// Clear should zero-out the entire length of each chan, regardless of num_chans/length.
-TEST(ChannelStripTest, Clear) {
-  mixer::ChannelStrip data(2, 2);
-  auto& channel_1 = data[1];
-
-  data[0][0] = 1.0f;
-  data[0][1] = 2.0f;
-  channel_1[0] = -3.0f;
-  channel_1[1] = -4.0f;
-
-  data.Clear();
-
-  EXPECT_EQ(data[0][0], 0.0f);
-  EXPECT_EQ(data[0][1], 0.0f);
-  EXPECT_EQ(data[1][0], 0.0f);
-  EXPECT_EQ(data[1][1], 0.0f);
-  EXPECT_EQ(channel_1[0], 0.0f);
-  EXPECT_EQ(channel_1[1], 0.0f);
-}
-
-// Test shifting by various conditions. Shift-by-0 should lead to no change. Shift-by-length (or
-// more) should clear the strip: all values are shifted out, replaced by shifted-in zeroes.
-TEST(ChannelStripTest, ShiftBy) {
-  mixer::ChannelStrip data(2, 2);
-
-  data[0][0] = 1.0f;
-  data[0][1] = 2.0f;
-
-  data[1][0] = -1.0f;
-  data[1][1] = -2.0f;
-
-  // Shift by 0 -- channels unchanged
-  data.ShiftBy(0);
-
-  EXPECT_EQ(data[0][0], 1.0f);
-  EXPECT_EQ(data[0][1], 2.0f);
-  EXPECT_EQ(data[1][0], -1.0f);
-  EXPECT_EQ(data[1][1], -2.0f);
-
-  // Shift by 1 - channels shift left, adding a zero
-  data.ShiftBy(1);
-
-  EXPECT_EQ(data[0][0], 2.0f);
-  EXPECT_EQ(data[0][1], 0.0f);
-  EXPECT_EQ(data[1][0], -2.0f);
-  EXPECT_EQ(data[1][1], 0.0f);
-
-  // Shift by 2 (len_), all data "shifted out", entirely zero
-  data[0][1] = 3.0f;
-  data[1][1] = -3.0f;
-  data.ShiftBy(2);
-
-  EXPECT_EQ(data[0][0], 0.0f);
-  EXPECT_EQ(data[0][1], 0.0f);
-  EXPECT_EQ(data[1][0], 0.0f);
-  EXPECT_EQ(data[1][1], 0.0f);
-
-  // Shift by 3 (more than len_), allowed and same as shifting by len_ (all zeros)
-  data[0][0] = 4.0f;
-  data[0][1] = 5.0f;
-  data[1][0] = -4.0f;
-  data[1][1] = -5.0f;
-  data.ShiftBy(3);
-
-  EXPECT_EQ(data[0][0], 0.0f);
-  EXPECT_EQ(data[0][1], 0.0f);
-  EXPECT_EQ(data[1][0], 0.0f);
-  EXPECT_EQ(data[1][1], 0.0f);
-}
-
-}  // namespace
-}  // namespace media::audio
diff --git a/src/media/audio/audio_core/mixer/sinc_sampler.cc b/src/media/audio/audio_core/mixer/sinc_sampler.cc
index 840871f..c88eb0d 100644
--- a/src/media/audio/audio_core/mixer/sinc_sampler.cc
+++ b/src/media/audio/audio_core/mixer/sinc_sampler.cc
@@ -9,14 +9,15 @@
 #include <algorithm>
 #include <limits>
 
-#include "src/media/audio/audio_core/mixer/channel_strip.h"
 #include "src/media/audio/audio_core/mixer/constants.h"
 #include "src/media/audio/audio_core/mixer/position_manager.h"
+#include "src/media/audio/lib/processing/channel_strip.h"
 #include "src/media/audio/lib/processing/filter.h"
 #include "src/media/audio/lib/processing/sampler.h"
 
 namespace media::audio::mixer {
 
+using ::media_audio::ChannelStrip;
 using ::media_audio::SincFilter;
 
 template <int32_t DestChanCount, typename SourceSampleType, int32_t SourceChanCount>
diff --git a/src/media/audio/lib/processing/BUILD.gn b/src/media/audio/lib/processing/BUILD.gn
index 9070960..a4a060c 100644
--- a/src/media/audio/lib/processing/BUILD.gn
+++ b/src/media/audio/lib/processing/BUILD.gn
@@ -38,6 +38,7 @@
 
 source_set("sampler") {
   sources = [
+    "channel_strip.h",
     "coefficient_table_cache.h",
     "filter.cc",
     "filter.h",
@@ -116,6 +117,7 @@
   output_name = "audio-libprocessing-unittests"
 
   sources = [
+    "channel_strip_unittest.cc",
     "coefficient_table_cache_unittest.cc",
     "coefficient_table_unittest.cc",
     "filter_unittest.cc",
diff --git a/src/media/audio/lib/processing/channel_strip.h b/src/media/audio/lib/processing/channel_strip.h
new file mode 100644
index 0000000..479aae5
--- /dev/null
+++ b/src/media/audio/lib/processing/channel_strip.h
@@ -0,0 +1,91 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SRC_MEDIA_AUDIO_LIB_PROCESSING_CHANNEL_STRIP_H_
+#define SRC_MEDIA_AUDIO_LIB_PROCESSING_CHANNEL_STRIP_H_
+
+#include <lib/stdcompat/span.h>
+#include <lib/syslog/cpp/macros.h>
+
+#include <algorithm>
+#include <cstring>
+#include <sstream>
+#include <string>
+
+namespace media_audio {
+
+// Class that manages planar audio data. This is useful when processing audio one channel at a time.
+class ChannelStrip {
+ public:
+  ChannelStrip(int64_t channel_count, int64_t frame_count)
+      : data_(new float[channel_count * frame_count]{}),
+        channel_count_(channel_count),
+        frame_count_(frame_count) {
+    FX_DCHECK(channel_count > 0);
+    FX_DCHECK(frame_count > 0);
+  }
+  ~ChannelStrip() { delete[] data_; }
+
+  // Non-copyable and non-movable.
+  ChannelStrip(const ChannelStrip& other) = delete;
+  ChannelStrip& operator=(const ChannelStrip& other) = delete;
+  ChannelStrip(ChannelStrip&& other) = delete;
+  ChannelStrip& operator=(ChannelStrip&& other) = delete;
+
+  // Logs the contents of the channel strip, channel by channel.
+  // Used for debugging purposes only.
+  static std::string ToString(const ChannelStrip& channel_strip) {
+    std::stringstream ss;
+    ss << "ChannelStrip: chans ";
+    ss << channel_strip.channel_count_ << ", len " << channel_strip.frame_count_ << "\n";
+    for (auto chan = 0; chan < channel_strip.channel_count_; ++chan) {
+      ss << "\tChannel " << chan;
+      for (auto i = 0; i < channel_strip.frame_count_; ++i) {
+        if (i % 16 == 0) {
+          ss << "\n[ " << i << "\t]";
+        }
+        ss << "\t" << channel_strip.data_[chan * channel_strip.frame_count_ + i];
+      }
+      ss << "\n";
+    }
+    return ss.str();
+  }
+
+  // Zeroes out all channels.
+  void Clear() { std::fill_n(data_, channel_count_ * frame_count_, 0.0f); }
+
+  // Shifts the audio data in all channels, by `shift_by` amount.
+  void ShiftBy(size_t shift_by) {
+    shift_by = std::min(shift_by, static_cast<size_t>(frame_count_));
+    for (auto chan = 0; chan < channel_count_; ++chan) {
+      float* channel_data = data_ + chan * frame_count_;
+      std::memmove(channel_data, channel_data + shift_by,
+                   (frame_count_ - shift_by) * sizeof(data_[0]));
+      std::fill_n(channel_data + (frame_count_ - shift_by), shift_by, 0.0f);
+    }
+  }
+
+  // Returns a span containing audio data for a given `channel`.
+  cpp20::span<float> operator[](size_t channel) {
+    return cpp20::span{data_ + channel * frame_count_, static_cast<size_t>(frame_count_)};
+  }
+  cpp20::span<const float> operator[](size_t channel) const {
+    return cpp20::span{data_ + channel * frame_count_, static_cast<size_t>(frame_count_)};
+  }
+
+  // Returns the number of channels.
+  int64_t channel_count() const { return channel_count_; }
+
+  // Returns the number of frames (i.e., length of each channel).
+  int64_t frame_count() const { return frame_count_; }
+
+ private:
+  float* data_;
+  int64_t channel_count_;
+  int64_t frame_count_;
+};
+
+}  // namespace media_audio
+
+#endif  // SRC_MEDIA_AUDIO_AUDIO_CORE_MIXER_CHANNEL_STRIP_H_
diff --git a/src/media/audio/lib/processing/channel_strip_unittest.cc b/src/media/audio/lib/processing/channel_strip_unittest.cc
new file mode 100644
index 0000000..1a6dea4
--- /dev/null
+++ b/src/media/audio/lib/processing/channel_strip_unittest.cc
@@ -0,0 +1,148 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "src/media/audio/lib/processing/channel_strip.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+namespace media_audio {
+namespace {
+
+using ::testing::AllOf;
+using ::testing::Each;
+using ::testing::SizeIs;
+
+void ValidateConstruction(const ChannelStrip& strip, int64_t channel_count, int64_t frame_count) {
+  ASSERT_GT(channel_count, 0);
+  ASSERT_GT(frame_count, 0);
+
+  EXPECT_EQ(strip.channel_count(), channel_count);
+  EXPECT_EQ(strip.frame_count(), frame_count);
+  for (auto chan = 0; chan < channel_count; ++chan) {
+    EXPECT_THAT(strip[chan], AllOf(SizeIs(frame_count), Each(0.0f)));
+  }
+}
+
+TEST(ChannelStripTest, Construction) {
+  auto num_chans = 1;
+  auto strip_len = 1;
+  ChannelStrip data(num_chans, strip_len);
+  ValidateConstruction(data, num_chans, strip_len);
+
+  num_chans = 1;
+  strip_len = 3;
+  ChannelStrip data2(num_chans, strip_len);
+  ValidateConstruction(data2, num_chans, strip_len);
+
+  num_chans = 4;
+  strip_len = 2;
+  ChannelStrip data3(num_chans, strip_len);
+  ValidateConstruction(data3, num_chans, strip_len);
+}
+
+TEST(ChannelStripTest, SetValues) {
+  ChannelStrip data(2, 3);
+  ValidateConstruction(data, 2, 3);
+
+  auto channel_0 = data[0];
+  auto channel_1 = data[1];
+
+  data[0][0] = 1.0f;
+  channel_0[1] = 2.0f;
+  channel_1[0] = 3.0f;
+  data[1][1] = 4.0f;
+
+  EXPECT_FLOAT_EQ(channel_0[1], 2.0f);
+  EXPECT_FLOAT_EQ(channel_1[0], 3.0f);
+  EXPECT_FLOAT_EQ(data[0][0], 1.0f);
+  EXPECT_FLOAT_EQ(data[0][1], 2.0f);
+  EXPECT_FLOAT_EQ(data[1][0], 3.0f);
+  EXPECT_FLOAT_EQ(data[1][1], 4.0f);
+}
+
+TEST(ChannelStripTest, Clear) {
+  ChannelStrip data(2, 2);
+  ValidateConstruction(data, 2, 2);
+
+  auto channel_1 = data[1];
+
+  data[0][0] = 1.0f;
+  data[0][1] = 2.0f;
+  channel_1[0] = -3.0f;
+  channel_1[1] = -4.0f;
+
+  EXPECT_FLOAT_EQ(channel_1[0], -3.0f);
+  EXPECT_FLOAT_EQ(channel_1[1], -4.0f);
+  EXPECT_FLOAT_EQ(data[0][0], 1.0f);
+  EXPECT_FLOAT_EQ(data[0][1], 2.0f);
+  EXPECT_FLOAT_EQ(data[1][0], -3.0f);
+  EXPECT_FLOAT_EQ(data[1][1], -4.0f);
+
+  data.Clear();
+
+  EXPECT_FLOAT_EQ(channel_1[0], 0.0f);
+  EXPECT_FLOAT_EQ(channel_1[1], 0.0f);
+  EXPECT_FLOAT_EQ(data[0][0], 0.0f);
+  EXPECT_FLOAT_EQ(data[0][1], 0.0f);
+  EXPECT_FLOAT_EQ(data[1][0], 0.0f);
+  EXPECT_FLOAT_EQ(data[1][1], 0.0f);
+}
+
+TEST(ChannelStripTest, ShiftBy) {
+  ChannelStrip data(2, 2);
+  ValidateConstruction(data, 2, 2);
+
+  data[0][0] = 1.0f;
+  data[0][1] = 2.0f;
+  data[1][0] = -1.0f;
+  data[1][1] = -2.0f;
+
+  EXPECT_FLOAT_EQ(data[0][0], 1.0f);
+  EXPECT_FLOAT_EQ(data[0][1], 2.0f);
+  EXPECT_FLOAT_EQ(data[1][0], -1.0f);
+  EXPECT_FLOAT_EQ(data[1][1], -2.0f);
+
+  // Shift by 0, which should have no effect.
+  data.ShiftBy(0);
+
+  EXPECT_FLOAT_EQ(data[0][0], 1.0f);
+  EXPECT_FLOAT_EQ(data[0][1], 2.0f);
+  EXPECT_FLOAT_EQ(data[1][0], -1.0f);
+  EXPECT_FLOAT_EQ(data[1][1], -2.0f);
+
+  // Shift by 1, which should shift channels left, adding a single zero at the end.
+  data.ShiftBy(1);
+
+  EXPECT_FLOAT_EQ(data[0][0], 2.0f);
+  EXPECT_FLOAT_EQ(data[0][1], 0.0f);
+  EXPECT_FLOAT_EQ(data[1][0], -2.0f);
+  EXPECT_FLOAT_EQ(data[1][1], 0.0f);
+
+  // Reset shifted data, and shift by frame count, which should entirely zero out all channels.
+  data[0][1] = 3.0f;
+  data[1][1] = -3.0f;
+  data.ShiftBy(2);
+
+  EXPECT_FLOAT_EQ(data[0][0], 0.0f);
+  EXPECT_FLOAT_EQ(data[0][1], 0.0f);
+  EXPECT_FLOAT_EQ(data[1][0], 0.0f);
+  EXPECT_FLOAT_EQ(data[1][1], 0.0f);
+
+  // Reset shifted data, and shift by more than frame count, which should again entirely zero out
+  // all channels.
+  data[0][0] = 4.0f;
+  data[0][1] = 5.0f;
+  data[1][0] = -4.0f;
+  data[1][1] = -5.0f;
+  data.ShiftBy(3);
+
+  EXPECT_FLOAT_EQ(data[0][0], 0.0f);
+  EXPECT_FLOAT_EQ(data[0][1], 0.0f);
+  EXPECT_FLOAT_EQ(data[1][0], 0.0f);
+  EXPECT_FLOAT_EQ(data[1][1], 0.0f);
+}
+
+}  // namespace
+}  // namespace media_audio