[media][effects] Add additional checks when adding an Effect.

This validates that an added effects parameters match what the
processor requires. This is a simplified implementation that requires
all Effects do in-place processing so all Effects will have the same
sample rate and channel configuration.

We also make the EffectsProcessor movable and expand on some
class-level documentation.

Test: fx run-test effexts_loader_unittests
Change-Id: Ia5e0318af6749e0a697feb30b411b4a28a213418
diff --git a/src/media/audio/lib/effects_loader/effects_processor.cc b/src/media/audio/lib/effects_loader/effects_processor.cc
index 7c808c9..da10664 100644
--- a/src/media/audio/lib/effects_loader/effects_processor.cc
+++ b/src/media/audio/lib/effects_loader/effects_processor.cc
@@ -8,10 +8,54 @@
 
 namespace media::audio {
 
+EffectsProcessor::EffectsProcessor(EffectsProcessor&& o) noexcept
+    : effects_chain_(std::move(o.effects_chain_)),
+      channels_in_(o.channels_in_),
+      channels_out_(o.channels_out_) {
+  o.channels_in_ = 0;
+  o.channels_out_ = 0;
+}
+
+EffectsProcessor& EffectsProcessor::operator=(EffectsProcessor&& o) noexcept {
+  effects_chain_ = std::move(o.effects_chain_);
+  channels_in_ = o.channels_in_;
+  channels_out_ = o.channels_out_;
+  o.channels_in_ = 0;
+  o.channels_out_ = 0;
+  return *this;
+}
+
 // Insert an effect instance at the end of the chain.
-void EffectsProcessor::AddEffect(Effect e) {
+zx_status_t EffectsProcessor::AddEffect(Effect e) {
   FXL_DCHECK(e);
+
+  fuchsia_audio_effects_parameters params;
+  zx_status_t status = e.GetParameters(&params);
+  if (status != ZX_OK) {
+    return status;
+  }
+
+  // For now we only support in-place processors.
+  if (params.channels_in != params.channels_out) {
+    FXL_LOG(ERROR) << "Can't add effect; only in-place effects are currently supported.";
+    return ZX_ERR_INVALID_ARGS;
+  }
+
+  if (effects_chain_.empty()) {
+    // This is the first effect; the processors input channels will be whatever this effect
+    // accepts.
+    channels_in_ = params.channels_in;
+  } else if (params.channels_in != channels_out_) {
+    // We have existing effects and this effect excepts different channelization than what we're
+    // currently producing.
+    FXL_LOG(ERROR) << "Can't add effect; needs " << params.channels_in << " channels but have "
+                   << channels_out_ << " channels";
+    return ZX_ERR_INVALID_ARGS;
+  }
+
+  channels_out_ = params.channels_out;
   effects_chain_.emplace_back(std::move(e));
+  return ZX_OK;
 }
 
 // Aborts if position is out-of-range.
diff --git a/src/media/audio/lib/effects_loader/effects_processor.h b/src/media/audio/lib/effects_loader/effects_processor.h
index 807e69e..7591591 100644
--- a/src/media/audio/lib/effects_loader/effects_processor.h
+++ b/src/media/audio/lib/effects_loader/effects_processor.h
@@ -13,17 +13,54 @@
 
 namespace media::audio {
 
-// EffectsProcessor represents a chain of active effect instances and manages chaining calls of
+// EffectsProcessor represents a queue of active effect instances and manages chaining calls of
 // Process/ProcessInPlace through a chain of effects.
 //
 // This class is designed to be used synchronously and is not explicitly multi-thread-safe.
 class EffectsProcessor {
  public:
-  void AddEffect(Effect e);
+  // Creates a new, empty effects processor.
+  EffectsProcessor() = default;
+
+  // Allow move.
+  EffectsProcessor(EffectsProcessor&& o) noexcept;
+  EffectsProcessor& operator=(EffectsProcessor&& o) noexcept;
+
+  // Disallow copy.
+  EffectsProcessor(const EffectsProcessor&) = delete;
+  EffectsProcessor& operator=(const EffectsProcessor&) = delete;
+
+  // Adds an `Effect` to the end of the queue of effects included in this processor.
+  //
+  // When the first `Effect` is added, that effects input channels becomes the input to the entire
+  // processor. Likewise that effects output channels becomes the processors output channels.
+  //
+  // When subsequent effects are added, the new effects input channels must match exactly the out-
+  // put channels of last added effect. The output channels for the processor will be updated to
+  // match the output channels of the newly added effect.
+  //
+  // Currently AddEffect will fail if an Effect has channels_in != channels_out (ex: all effects
+  // must do in-place processing). This is a short-term limitation that will be lifted in the
+  // future.
+  //
+  // Aborts if `e` is an invalid `Effect`.
+  zx_status_t AddEffect(Effect e);
 
   // Returns the number of active instances in the enclosed effect chain.
   [[nodiscard]] uint16_t size() const { return effects_chain_.size(); }
 
+  // Returns the number of input channels for this effect. This will be the number of channels
+  // expected for input frames to `ProcessInPlace`.
+  //
+  // Returns 0 if this processor has no effects.
+  [[nodiscard]] uint32_t channels_in() const { return channels_in_; }
+
+  // Returns the number of output channels for this effect. Currently this always equals
+  // `channels_in()`.
+  //
+  // Returns 0 if this processor has no effects.
+  [[nodiscard]] uint32_t channels_out() const { return channels_out_; }
+
   [[nodiscard]] auto begin() { return effects_chain_.begin(); }
   [[nodiscard]] auto end() { return effects_chain_.end(); }
   [[nodiscard]] auto cbegin() const { return effects_chain_.cbegin(); }
@@ -38,6 +75,8 @@
 
  private:
   std::vector<Effect> effects_chain_;
+  uint32_t channels_in_ = 0;
+  uint32_t channels_out_ = 0;
 };
 
 }  // namespace media::audio
diff --git a/src/media/audio/lib/effects_loader/effects_processor_unittest.cc b/src/media/audio/lib/effects_loader/effects_processor_unittest.cc
index 7472cc0..adaaefb 100644
--- a/src/media/audio/lib/effects_loader/effects_processor_unittest.cc
+++ b/src/media/audio/lib/effects_loader/effects_processor_unittest.cc
@@ -46,10 +46,10 @@
   // Create processor
   {
     EffectsProcessor processor;
-    processor.AddEffect(std::move(effect3));
-    processor.AddEffect(std::move(effect1));
-    processor.AddEffect(std::move(effect2));
-    processor.AddEffect(std::move(effect4));
+    EXPECT_EQ(processor.AddEffect(std::move(effect3)), ZX_OK);
+    EXPECT_EQ(processor.AddEffect(std::move(effect1)), ZX_OK);
+    EXPECT_EQ(processor.AddEffect(std::move(effect2)), ZX_OK);
+    EXPECT_EQ(processor.AddEffect(std::move(effect4)), ZX_OK);
     EXPECT_EQ(processor.size(), 4);
 
     EXPECT_EQ(effects_handle3, processor.GetEffectAt(0).get());
@@ -65,6 +65,75 @@
   test_effects()->clear_effects();
 }
 
+TEST_F(EffectsProcessorTest, AddEffectWithMismatchedChannelConfig) {
+  ASSERT_EQ(ZX_OK, test_effects()->add_effect({{"assign_to_1.0", FUCHSIA_AUDIO_EFFECTS_CHANNELS_ANY,
+                                                FUCHSIA_AUDIO_EFFECTS_CHANNELS_SAME_AS_IN},
+                                               TEST_EFFECTS_ACTION_ASSIGN,
+                                               1.0}));
+  Effect single_channel_effect1 = effects_loader()->CreateEffect(0, 1, 1, 1, {});
+  Effect single_channel_effect2 = effects_loader()->CreateEffect(0, 1, 1, 1, {});
+  Effect two_channel_effect = effects_loader()->CreateEffect(0, 1, 2, 2, {});
+
+  EffectsProcessor processor;
+  EXPECT_EQ(processor.channels_in(), 0u);
+  EXPECT_EQ(processor.channels_out(), 0u);
+
+  // Add a single channel effect (chans in == chans out == 1).
+  EXPECT_EQ(processor.AddEffect(std::move(single_channel_effect1)), ZX_OK);
+  EXPECT_EQ(processor.channels_in(), 1u);
+  EXPECT_EQ(processor.channels_out(), 1u);
+
+  // Add a second single channel effect.
+  EXPECT_EQ(processor.AddEffect(std::move(single_channel_effect2)), ZX_OK);
+  EXPECT_EQ(processor.channels_in(), 1u);
+  EXPECT_EQ(processor.channels_out(), 1u);
+
+  // Add a two channel effect. This should fail as the processor is currently producing single
+  // channel audio out of the last effect.
+  EXPECT_EQ(processor.AddEffect(std::move(two_channel_effect)), ZX_ERR_INVALID_ARGS);
+}
+
+TEST_F(EffectsProcessorTest, MoveProcessor) {
+  ASSERT_EQ(ZX_OK, test_effects()->add_effect({{"assign_to_1.0", FUCHSIA_AUDIO_EFFECTS_CHANNELS_ANY,
+                                                FUCHSIA_AUDIO_EFFECTS_CHANNELS_SAME_AS_IN},
+                                               TEST_EFFECTS_ACTION_ASSIGN,
+                                               1.0}));
+  EffectsProcessor p1;
+  p1.AddEffect(effects_loader()->CreateEffect(0, 1, 1, 1, {}));
+  p1.AddEffect(effects_loader()->CreateEffect(0, 1, 1, 1, {}));
+
+  EXPECT_EQ(p1.size(), 2u);
+  EXPECT_EQ(p1.channels_in(), 1u);
+  EXPECT_EQ(p1.channels_out(), 1u);
+
+  EffectsProcessor p2;
+  EXPECT_EQ(p2.size(), 0u);
+  EXPECT_EQ(p2.channels_in(), 0u);
+  EXPECT_EQ(p2.channels_out(), 0u);
+
+  // Move assignment.
+  p2 = std::move(p1);
+  // p1 is now empty.
+  EXPECT_EQ(p1.size(), 0u);
+  EXPECT_EQ(p1.channels_in(), 0u);
+  EXPECT_EQ(p1.channels_out(), 0u);
+  // p2 has effects.
+  EXPECT_EQ(p2.size(), 2u);
+  EXPECT_EQ(p2.channels_in(), 1u);
+  EXPECT_EQ(p2.channels_out(), 1u);
+
+  // Move construction.
+  EffectsProcessor p3(std::move(p2));
+  // p2 is now empty.
+  EXPECT_EQ(p2.size(), 0u);
+  EXPECT_EQ(p2.channels_in(), 0u);
+  EXPECT_EQ(p2.channels_out(), 0u);
+  // p3 has effects.
+  EXPECT_EQ(p3.size(), 2u);
+  EXPECT_EQ(p3.channels_in(), 1u);
+  EXPECT_EQ(p3.channels_out(), 1u);
+}
+
 // Verify (at a VERY Basic level) the methods that handle data flow.
 TEST_F(EffectsProcessorTest, ProcessInPlaceFlush) {
   ASSERT_EQ(ZX_OK,
@@ -106,10 +175,10 @@
   Effect effect4 = effects_loader()->CreateEffect(3, 1, 1, 1, {});
   ASSERT_TRUE(effect1 && effect2 && effect3 && effect4);
 
-  processor.AddEffect(std::move(effect1));
-  processor.AddEffect(std::move(effect2));
-  processor.AddEffect(std::move(effect3));
-  processor.AddEffect(std::move(effect4));
+  EXPECT_EQ(processor.AddEffect(std::move(effect1)), ZX_OK);
+  EXPECT_EQ(processor.AddEffect(std::move(effect2)), ZX_OK);
+  EXPECT_EQ(processor.AddEffect(std::move(effect3)), ZX_OK);
+  EXPECT_EQ(processor.AddEffect(std::move(effect4)), ZX_OK);
   EXPECT_EQ(4u, test_effects()->num_instances());
 
   // The first 2 processors will mutate data, but this will be clobbered by the 3rd processor which