Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/dom/media/gtest/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 135 kB image not shown  

Quelle  TestAudioTrackGraph.cpp

  Sprache: C
 

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */


#include "MediaTrackGraphImpl.h"

#include "gmock/gmock.h"
#include "gtest/gtest-printers.h"
#include "gtest/gtest.h"

#include "CrossGraphPort.h"
#include "DeviceInputTrack.h"
#ifdef MOZ_WEBRTC
#  include "MediaEngineWebRTCAudio.h"
#endif  // MOZ_WEBRTC
#include "MockCubeb.h"
#include "mozilla/gtest/WaitFor.h"
#include "mozilla/Preferences.h"
#include "mozilla/SpinEventLoopUntil.h"
#include "WavDumper.h"

using namespace mozilla;
using testing::AtLeast;
using testing::Eq;
using testing::InSequence;
using testing::MockFunction;
using testing::Return;
using testing::StrEq;
using testing::StrictMock;

// Short-hand for InvokeAsync on the current thread.
#define InvokeAsync(f) InvokeAsync(GetCurrentSerialEventTarget(), __func__, f)

// Short-hand for DispatchToCurrentThread with a function.
#define DispatchFunction(f) \
  NS_DispatchToCurrentThread(NS_NewRunnableFunction(__func__, f))

// Short-hand for DispatchToCurrentThread with a method with arguments
#define DispatchMethod(t, m, args...) \
  NS_DispatchToCurrentThread(NewRunnableMethod(__func__, t, m, ##args))

// Short-hand for draining the current threads event queue, i.e. processing
// those runnables dispatched per above.
#define ProcessEventQueue()                     \
  while (NS_ProcessNextEvent(nullptr, false)) { \
  }

namespace {
#ifdef MOZ_WEBRTC
/*
 * Common ControlMessages
 */

struct StartInputProcessing : public ControlMessage {
  const RefPtr<AudioProcessingTrack> mProcessingTrack;
  const RefPtr<AudioInputProcessing> mInputProcessing;

  StartInputProcessing(AudioProcessingTrack* aTrack,
                       AudioInputProcessing* aInputProcessing)
      : ControlMessage(aTrack),
        mProcessingTrack(aTrack),
        mInputProcessing(aInputProcessing) {}
  void Run() override { mInputProcessing->Start(mTrack->Graph()); }
};

struct StopInputProcessing : public ControlMessage {
  const RefPtr<AudioInputProcessing> mInputProcessing;

  explicit StopInputProcessing(AudioProcessingTrack* aTrack,
                               AudioInputProcessing* aInputProcessing)
      : ControlMessage(aTrack), mInputProcessing(aInputProcessing) {}
  void Run() override { mInputProcessing->Stop(mTrack->Graph()); }
};

void QueueApplySettings(AudioProcessingTrack* aTrack,
                        AudioInputProcessing* aInputProcessing,
                        const MediaEnginePrefs& aSettings) {
  aTrack->QueueControlMessageWithNoShutdown(
      [inputProcessing = RefPtr{aInputProcessing}, aSettings,
       // If the track is not connected to a device then the particular
       // AudioDeviceID (nullptr) passed to ReevaluateInputDevice() is not
       // important.
       deviceId = aTrack->DeviceId().valueOr(nullptr),
       graph = aTrack->Graph()] {
        inputProcessing->ApplySettings(graph, deviceId, aSettings);
      });
}

void QueueExpectIsPassThrough(AudioProcessingTrack* aTrack,
                              AudioInputProcessing* aInputProcessing) {
  aTrack->QueueControlMessageWithNoShutdown(
      [inputProcessing = RefPtr{aInputProcessing}, graph = aTrack->Graph()] {
        EXPECT_EQ(inputProcessing->IsPassThrough(graph), true);
      });
}
#endif  // MOZ_WEBRTC

class GoFaster : public ControlMessage {
  MockCubeb* mCubeb;

 public:
  explicit GoFaster(MockCubeb* aCubeb)
      : ControlMessage(nullptr), mCubeb(aCubeb) {}
  void Run() override { mCubeb->GoFaster(); }
};

struct StartNonNativeInput : public ControlMessage {
  const RefPtr<NonNativeInputTrack> mInputTrack;
  RefPtr<AudioInputSource> mInputSource;

  StartNonNativeInput(NonNativeInputTrack* aInputTrack,
                      RefPtr<AudioInputSource>&& aInputSource)
      : ControlMessage(aInputTrack),
        mInputTrack(aInputTrack),
        mInputSource(std::move(aInputSource)) {}
  void Run() override { mInputTrack->StartAudio(std::move(mInputSource)); }
};

struct StopNonNativeInput : public ControlMessage {
  const RefPtr<NonNativeInputTrack> mInputTrack;

  explicit StopNonNativeInput(NonNativeInputTrack* aInputTrack)
      : ControlMessage(aInputTrack), mInputTrack(aInputTrack) {}
  void Run() override { mInputTrack->StopAudio(); }
};

// Helper for detecting when fallback driver has been switched away, for use
// with RunningMode::Manual.
class OnFallbackListener : public MediaTrackListener {
  const RefPtr<MediaTrack> mTrack;
  Atomic<bool> mOnFallback{true};

 public:
  explicit OnFallbackListener(MediaTrack* aTrack) : mTrack(aTrack) {}

  void Reset() { mOnFallback = true; }
  bool OnFallback() { return mOnFallback; }

  void NotifyOutput(MediaTrackGraph*, TrackTime) override {
    if (auto* ad =
            mTrack->GraphImpl()->CurrentDriver()->AsAudioCallbackDriver()) {
      mOnFallback = ad->OnFallback();
    }
  }
};

class MockAudioDataListener : public AudioDataListener {
 protected:
  ~MockAudioDataListener() = default;

 public:
  MockAudioDataListener() = default;

  MOCK_METHOD(uint32_t, RequestedInputChannelCount, (MediaTrackGraph*),
              (const));
  MOCK_METHOD(cubeb_input_processing_params, RequestedInputProcessingParams,
              (MediaTrackGraph*), (const));
  MOCK_METHOD(bool, IsVoiceInput, (MediaTrackGraph*), (const));
  MOCK_METHOD(void, DeviceChanged, (MediaTrackGraph*));
  MOCK_METHOD(void, Disconnect, (MediaTrackGraph*));
  MOCK_METHOD(void, NotifySetRequestedInputProcessingParams,
              (MediaTrackGraph*, int, cubeb_input_processing_params));
  MOCK_METHOD(void, NotifySetRequestedInputProcessingParamsResult,
              (MediaTrackGraph*, int,
               (const Result<cubeb_input_processing_params, int>&)));
};
}  // namespace

/*
 * The set of tests here are a bit special. In part because they're async and
 * depends on the graph thread to function. In part because they depend on main
 * thread stable state to send messages to the graph.
 *
 * Any message sent from the main thread to the graph through the graph's
 * various APIs are scheduled to run in stable state. Stable state occurs after
 * a task in the main thread eventloop has run to completion.
 *
 * Since gtests are generally sync and on main thread, calling into the graph
 * may schedule a stable state runnable but with no task in the eventloop to
 * trigger stable state. Therefore care must be taken to always call into the
 * graph from a task, typically via InvokeAsync or a dispatch to main thread.
 */


TEST(TestAudioTrackGraph, DifferentDeviceIDs)
{
  MockCubeb* cubeb = new MockCubeb();
  CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());

  MediaTrackGraph* g1 = MediaTrackGraphImpl::GetInstance(
      MediaTrackGraph::AUDIO_THREAD_DRIVER, /*Window ID*/ 1,
      CubebUtils::PreferredSampleRate(/* aShouldResistFingerprinting */ false),
      /*OutputDeviceID*/ nullptr, GetMainThreadSerialEventTarget());

  MediaTrackGraph* g2 = MediaTrackGraphImpl::GetInstance(
      MediaTrackGraph::AUDIO_THREAD_DRIVER, /*Window ID*/ 1,
      CubebUtils::PreferredSampleRate(/* aShouldResistFingerprinting */ false),
      /*OutputDeviceID*/ reinterpret_cast<cubeb_devid>(1),
      GetMainThreadSerialEventTarget());

  MediaTrackGraph* g1_2 = MediaTrackGraphImpl::GetInstance(
      MediaTrackGraph::AUDIO_THREAD_DRIVER, /*Window ID*/ 1,
      CubebUtils::PreferredSampleRate(/* aShouldResistFingerprinting */ false),
      /*OutputDeviceID*/ nullptr, GetMainThreadSerialEventTarget());

  MediaTrackGraph* g2_2 = MediaTrackGraphImpl::GetInstance(
      MediaTrackGraph::AUDIO_THREAD_DRIVER, /*Window ID*/ 1,
      CubebUtils::PreferredSampleRate(/* aShouldResistFingerprinting */ false),
      /*OutputDeviceID*/ reinterpret_cast<cubeb_devid>(1),
      GetMainThreadSerialEventTarget());

  EXPECT_NE(g1, g2) << "Different graphs due to different device ids";
  EXPECT_EQ(g1, g1_2) << "Same graphs for same device ids";
  EXPECT_EQ(g2, g2_2) << "Same graphs for same device ids";

  for (MediaTrackGraph* g : {g1, g2}) {
    // Dummy track to make graph rolling. Add it and remove it to remove the
    // graph from the global hash table and let it shutdown.

    using SourceTrackPromise = MozPromise<SourceMediaTrack*, nsresult, true>;
    auto p = InvokeAsync([g] {
      return SourceTrackPromise::CreateAndResolve(
          g->CreateSourceTrack(MediaSegment::AUDIO), __func__);
    });

    WaitFor(cubeb->StreamInitEvent());
    RefPtr<SourceMediaTrack> dummySource = WaitFor(p).unwrap();

    DispatchMethod(dummySource, &SourceMediaTrack::Destroy);

    WaitFor(cubeb->StreamDestroyEvent());
  }
}

TEST(TestAudioTrackGraph, SetOutputDeviceID)
{
  MockCubeb* cubeb = new MockCubeb();
  CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());

  // Set the output device id in GetInstance method confirm that it is the one
  // used in cubeb_stream_init.
  MediaTrackGraph* graph = MediaTrackGraphImpl::GetInstance(
      MediaTrackGraph::AUDIO_THREAD_DRIVER, /*Window ID*/ 1,
      CubebUtils::PreferredSampleRate(/* aShouldResistFingerprinting */ false),
      /*OutputDeviceID*/ reinterpret_cast<cubeb_devid>(2),
      GetMainThreadSerialEventTarget());

  // Dummy track to make graph rolling. Add it and remove it to remove the
  // graph from the global hash table and let it shutdown.
  RefPtr<SourceMediaTrack> dummySource;
  DispatchFunction(
      [&] { dummySource = graph->CreateSourceTrack(MediaSegment::AUDIO); });

  RefPtr<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent());

  EXPECT_EQ(stream->GetOutputDeviceID(), reinterpret_cast<cubeb_devid>(2))
      << "After init confirm the expected output device id";

  // Test has finished, destroy the track to shutdown the MTG.
  DispatchMethod(dummySource, &SourceMediaTrack::Destroy);
  WaitFor(cubeb->StreamDestroyEvent());
}

TEST(TestAudioTrackGraph, StreamName)
{
  MockCubeb* cubeb = new MockCubeb();
  CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());

  // Initialize a graph with a system thread driver to check that the stream
  // name survives the driver switch.
  MediaTrackGraphImpl* graph = MediaTrackGraphImpl::GetInstance(
      MediaTrackGraph::SYSTEM_THREAD_DRIVER, /*Window ID*/ 1,
      CubebUtils::PreferredSampleRate(/* aShouldResistFingerprinting */ false),
      /*OutputDeviceID*/ reinterpret_cast<cubeb_devid>(1),
      GetMainThreadSerialEventTarget());
  nsLiteralCString name1("name1");
  graph->CurrentDriver()->SetStreamName(name1);

  // Dummy track to start the graph rolling and switch to an
  // AudioCallbackDriver.
  RefPtr<SourceMediaTrack> dummySource;
  DispatchFunction(
      [&] { dummySource = graph->CreateSourceTrack(MediaSegment::AUDIO); });

  RefPtr<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent());
  EXPECT_STREQ(stream->StreamName(), name1.get());

  // Test a name change on an existing stream.
  nsLiteralCString name2("name2");
  DispatchFunction([&] {
    graph->QueueControlMessageWithNoShutdown(
        [&] { graph->CurrentDriver()->SetStreamName(name2); });
  });
  nsCString name = WaitFor(stream->NameSetEvent());
  EXPECT_EQ(name, name2);

  // Test has finished. Destroy the track to shutdown the MTG.
  DispatchMethod(dummySource, &SourceMediaTrack::Destroy);
  WaitFor(cubeb->StreamDestroyEvent());
}

TEST(TestAudioTrackGraph, NotifyDeviceStarted)
{
  MockCubeb* cubeb = new MockCubeb();
  CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());

  MediaTrackGraph* graph = MediaTrackGraphImpl::GetInstance(
      MediaTrackGraph::AUDIO_THREAD_DRIVER, /*Window ID*/ 1,
      CubebUtils::PreferredSampleRate(/* aShouldResistFingerprinting */ false),
      nullptr, GetMainThreadSerialEventTarget());

  RefPtr<SourceMediaTrack> dummySource;
  Unused << WaitFor(InvokeAsync([&] {
    // Dummy track to make graph rolling. Add it and remove it to remove the
    // graph from the global hash table and let it shutdown.
    dummySource = graph->CreateSourceTrack(MediaSegment::AUDIO);

    return graph->NotifyWhenDeviceStarted(nullptr);
  }));

  {
    MediaTrackGraphImpl* graph = dummySource->GraphImpl();
    MonitorAutoLock lock(graph->GetMonitor());
    EXPECT_TRUE(graph->CurrentDriver()->AsAudioCallbackDriver());
    EXPECT_TRUE(graph->CurrentDriver()->ThreadRunning());
  }

  // Test has finished, destroy the track to shutdown the MTG.
  DispatchMethod(dummySource, &SourceMediaTrack::Destroy);
  WaitFor(cubeb->StreamDestroyEvent());
}

TEST(TestAudioTrackGraph, NonNativeInputTrackStartAndStop)
{
  MockCubeb* cubeb = new MockCubeb();
  CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());

  MediaTrackGraph* graph = MediaTrackGraphImpl::GetInstance(
      MediaTrackGraph::SYSTEM_THREAD_DRIVER, /*Window ID*/ 1,
      CubebUtils::PreferredSampleRate(/* aShouldResistFingerprinting */ false),
      nullptr, GetMainThreadSerialEventTarget());

  const CubebUtils::AudioDeviceID deviceId = (CubebUtils::AudioDeviceID)1;

  // Add a NonNativeInputTrack to graph, making graph create an output-only
  // AudioCallbackDriver since NonNativeInputTrack is an audio-type MediaTrack.
  RefPtr<NonNativeInputTrack> track;
  DispatchFunction([&] {
    track = new NonNativeInputTrack(graph->GraphRate(), deviceId,
                                    PRINCIPAL_HANDLE_NONE);
    graph->AddTrack(track);
  });

  RefPtr<SmartMockCubebStream> driverStream = WaitFor(cubeb->StreamInitEvent());
  EXPECT_FALSE(driverStream->mHasInput);
  EXPECT_TRUE(driverStream->mHasOutput);

  // Main test below:
  {
    const AudioInputSource::Id sourceId = 1;
    const uint32_t channels = 2;
    const TrackRate rate = 48000;

    // Start and stop the audio in NonNativeInputTrack.
    {
      struct DeviceInfo {
        uint32_t mChannelCount;
        AudioInputType mType;
      };
      using DeviceQueryPromise =
          MozPromise<DeviceInfo, nsresult, /* IsExclusive = */ true>;

      struct DeviceQueryMessage : public ControlMessage {
        const NonNativeInputTrack* mInputTrack;
        MozPromiseHolder<DeviceQueryPromise> mHolder;

        DeviceQueryMessage(NonNativeInputTrack* aInputTrack,
                           MozPromiseHolder<DeviceQueryPromise>&& aHolder)
            : ControlMessage(aInputTrack),
              mInputTrack(aInputTrack),
              mHolder(std::move(aHolder)) {}
        void Run() override {
          DeviceInfo info = {mInputTrack->NumberOfChannels(),
                             mInputTrack->DevicePreference()};
          // mHolder.Resolve(info, __func__);
          mTrack->GraphImpl()->Dispatch(NS_NewRunnableFunction(
              "TestAudioTrackGraph::DeviceQueryMessage",
              [holder = std::move(mHolder), devInfo = info]() mutable {
                holder.Resolve(devInfo, __func__);
              }));
        }
      };

      // No input channels and device preference before start.
      {
        MozPromiseHolder<DeviceQueryPromise> h;
        RefPtr<DeviceQueryPromise> p = h.Ensure(__func__);
        DispatchFunction([&] {
          track->GraphImpl()->AppendMessage(
              MakeUnique<DeviceQueryMessage>(track.get(), std::move(h)));
        });
        Result<DeviceInfo, nsresult> r = WaitFor(p);
        ASSERT_TRUE(r.isOk());
        DeviceInfo info = r.unwrap();

        EXPECT_EQ(info.mChannelCount, 0U);
        EXPECT_EQ(info.mType, AudioInputType::Unknown);
      }

      DispatchFunction([&] {
        track->GraphImpl()->AppendMessage(MakeUnique<StartNonNativeInput>(
            track.get(), MakeRefPtr<AudioInputSource>(
                             MakeRefPtr<AudioInputSourceListener>(track.get()),
                             sourceId, deviceId, channels, true /* voice */,
                             PRINCIPAL_HANDLE_NONE, rate, graph->GraphRate())));
      });
      RefPtr<SmartMockCubebStream> nonNativeStream =
          WaitFor(cubeb->StreamInitEvent());
      EXPECT_TRUE(nonNativeStream->mHasInput);
      EXPECT_FALSE(nonNativeStream->mHasOutput);
      EXPECT_EQ(nonNativeStream->GetInputDeviceID(), deviceId);
      EXPECT_EQ(nonNativeStream->InputChannels(), channels);
      EXPECT_EQ(nonNativeStream->SampleRate(), static_cast<uint32_t>(rate));

      // Input channels and device preference should be set after start.
      {
        MozPromiseHolder<DeviceQueryPromise> h;
        RefPtr<DeviceQueryPromise> p = h.Ensure(__func__);
        DispatchFunction([&] {
          track->GraphImpl()->AppendMessage(
              MakeUnique<DeviceQueryMessage>(track.get(), std::move(h)));
        });
        Result<DeviceInfo, nsresult> r = WaitFor(p);
        ASSERT_TRUE(r.isOk());
        DeviceInfo info = r.unwrap();

        EXPECT_EQ(info.mChannelCount, channels);
        EXPECT_EQ(info.mType, AudioInputType::Voice);
      }

      Unused << WaitFor(nonNativeStream->FramesProcessedEvent());

      DispatchFunction([&] {
        track->GraphImpl()->AppendMessage(
            MakeUnique<StopNonNativeInput>(track.get()));
      });
      RefPtr<SmartMockCubebStream> destroyedStream =
          WaitFor(cubeb->StreamDestroyEvent());
      EXPECT_EQ(destroyedStream.get(), nonNativeStream.get());

      // No input channels and device preference after stop.
      {
        MozPromiseHolder<DeviceQueryPromise> h;
        RefPtr<DeviceQueryPromise> p = h.Ensure(__func__);
        DispatchFunction([&] {
          track->GraphImpl()->AppendMessage(
              MakeUnique<DeviceQueryMessage>(track.get(), std::move(h)));
        });
        Result<DeviceInfo, nsresult> r = WaitFor(p);
        ASSERT_TRUE(r.isOk());
        DeviceInfo info = r.unwrap();

        EXPECT_EQ(info.mChannelCount, 0U);
        EXPECT_EQ(info.mType, AudioInputType::Unknown);
      }
    }

    // Make sure the NonNativeInputTrack can restart and stop its audio.
    {
      DispatchFunction([&] {
        track->GraphImpl()->AppendMessage(MakeUnique<StartNonNativeInput>(
            track.get(), MakeRefPtr<AudioInputSource>(
                             MakeRefPtr<AudioInputSourceListener>(track.get()),
                             sourceId, deviceId, channels, true,
                             PRINCIPAL_HANDLE_NONE, rate, graph->GraphRate())));
      });
      RefPtr<SmartMockCubebStream> nonNativeStream =
          WaitFor(cubeb->StreamInitEvent());
      EXPECT_TRUE(nonNativeStream->mHasInput);
      EXPECT_FALSE(nonNativeStream->mHasOutput);
      EXPECT_EQ(nonNativeStream->GetInputDeviceID(), deviceId);
      EXPECT_EQ(nonNativeStream->InputChannels(), channels);
      EXPECT_EQ(nonNativeStream->SampleRate(), static_cast<uint32_t>(rate));

      Unused << WaitFor(nonNativeStream->FramesProcessedEvent());

      DispatchFunction([&] {
        track->GraphImpl()->AppendMessage(
            MakeUnique<StopNonNativeInput>(track.get()));
      });
      RefPtr<SmartMockCubebStream> destroyedStream =
          WaitFor(cubeb->StreamDestroyEvent());
      EXPECT_EQ(destroyedStream.get(), nonNativeStream.get());
    }
  }

  // Clean up.
  DispatchFunction([&] { track->Destroy(); });
  RefPtr<SmartMockCubebStream> destroyedStream =
      WaitFor(cubeb->StreamDestroyEvent());
  EXPECT_EQ(destroyedStream.get(), driverStream.get());
}

TEST(TestAudioTrackGraph, NonNativeInputTrackErrorCallback)
{
  MockCubeb* cubeb = new MockCubeb();
  CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());

  MediaTrackGraph* graph = MediaTrackGraphImpl::GetInstance(
      MediaTrackGraph::SYSTEM_THREAD_DRIVER, /*Window ID*/ 1,
      CubebUtils::PreferredSampleRate(/* aShouldResistFingerprinting */ false),
      nullptr, GetMainThreadSerialEventTarget());

  const CubebUtils::AudioDeviceID deviceId = (CubebUtils::AudioDeviceID)1;

  // Add a NonNativeInputTrack to graph, making graph create an output-only
  // AudioCallbackDriver since NonNativeInputTrack is an audio-type MediaTrack.
  RefPtr<NonNativeInputTrack> track;
  DispatchFunction([&] {
    track = new NonNativeInputTrack(graph->GraphRate(), deviceId,
                                    PRINCIPAL_HANDLE_NONE);
    graph->AddTrack(track);
  });

  RefPtr<SmartMockCubebStream> driverStream = WaitFor(cubeb->StreamInitEvent());
  EXPECT_FALSE(driverStream->mHasInput);
  EXPECT_TRUE(driverStream->mHasOutput);

  // Main test below:
  {
    const AudioInputSource::Id sourceId = 1;
    const uint32_t channels = 2;
    const TrackRate rate = 48000;

    // Launch and start the non-native audio stream.
    DispatchFunction([&] {
      track->GraphImpl()->AppendMessage(MakeUnique<StartNonNativeInput>(
          track.get(), MakeRefPtr<AudioInputSource>(
                           MakeRefPtr<AudioInputSourceListener>(track.get()),
                           sourceId, deviceId, channels, true,
                           PRINCIPAL_HANDLE_NONE, rate, graph->GraphRate())));
    });
    RefPtr<SmartMockCubebStream> nonNativeStream =
        WaitFor(cubeb->StreamInitEvent());
    EXPECT_TRUE(nonNativeStream->mHasInput);
    EXPECT_FALSE(nonNativeStream->mHasOutput);
    EXPECT_EQ(nonNativeStream->GetInputDeviceID(), deviceId);
    EXPECT_EQ(nonNativeStream->InputChannels(), channels);
    EXPECT_EQ(nonNativeStream->SampleRate(), static_cast<uint32_t>(rate));

    // Make sure the audio stream is running.
    Unused << WaitFor(nonNativeStream->FramesProcessedEvent());

    // Force an error. This results in the audio stream destroying.
    DispatchFunction([&] { nonNativeStream->ForceError(); });
    WaitFor(nonNativeStream->ErrorForcedEvent());

    RefPtr<SmartMockCubebStream> destroyedStream =
        WaitFor(cubeb->StreamDestroyEvent());
    EXPECT_EQ(destroyedStream.get(), nonNativeStream.get());
  }

  // Make sure it's ok to call audio stop again.
  DispatchFunction([&] {
    track->GraphImpl()->AppendMessage(
        MakeUnique<StopNonNativeInput>(track.get()));
  });

  // Clean up.
  DispatchFunction([&] { track->Destroy(); });
  RefPtr<SmartMockCubebStream> destroyedStream =
      WaitFor(cubeb->StreamDestroyEvent());
  EXPECT_EQ(destroyedStream.get(), driverStream.get());
}

class TestDeviceInputConsumerTrack : public DeviceInputConsumerTrack {
 public:
  static TestDeviceInputConsumerTrack* Create(MediaTrackGraph* aGraph) {
    MOZ_RELEASE_ASSERT(NS_IsMainThread());
    TestDeviceInputConsumerTrack* track =
        new TestDeviceInputConsumerTrack(aGraph->GraphRate());
    aGraph->AddTrack(track);
    return track;
  }

  void Destroy() {
    MOZ_RELEASE_ASSERT(NS_IsMainThread());
    DisconnectDeviceInput();
    DeviceInputConsumerTrack::Destroy();
  }

  void ProcessInput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags) override {
    MOZ_RELEASE_ASSERT(aFrom < aTo);

    if (mInputs.IsEmpty()) {
      GetData<AudioSegment>()->AppendNullData(aTo - aFrom);
    } else {
      MOZ_RELEASE_ASSERT(mInputs.Length() == 1);
      AudioSegment data;
      DeviceInputConsumerTrack::GetInputSourceData(data, aFrom, aTo);
      GetData<AudioSegment>()->AppendFrom(&data);
    }
  };

  uint32_t NumberOfChannels() const override {
    if (mInputs.IsEmpty()) {
      return 0;
    }
    DeviceInputTrack* t = mInputs[0]->GetSource()->AsDeviceInputTrack();
    MOZ_RELEASE_ASSERT(t);
    return t->NumberOfChannels();
  }

 private:
  explicit TestDeviceInputConsumerTrack(TrackRate aSampleRate)
      : DeviceInputConsumerTrack(aSampleRate) {}
};

TEST(TestAudioTrackGraph, DeviceChangedCallback)
{
  MockCubeb* cubeb = new MockCubeb();
  CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());

  MediaTrackGraph* graphImpl = MediaTrackGraphImpl::GetInstance(
      MediaTrackGraph::SYSTEM_THREAD_DRIVER, /*Window ID*/ 1,
      CubebUtils::PreferredSampleRate(/* aShouldResistFingerprinting */ false),
      nullptr, GetMainThreadSerialEventTarget());

  class TestAudioDataListener : public StrictMock<MockAudioDataListener> {
   public:
    TestAudioDataListener(uint32_t aChannelCount, bool aIsVoice) {
      EXPECT_CALL(*this, RequestedInputChannelCount)
          .WillRepeatedly(Return(aChannelCount));
      EXPECT_CALL(*this, RequestedInputProcessingParams)
          .WillRepeatedly(Return(CUBEB_INPUT_PROCESSING_PARAM_NONE));
      EXPECT_CALL(*this, IsVoiceInput).WillRepeatedly(Return(aIsVoice));
      {
        InSequence s;
        EXPECT_CALL(*this, DeviceChanged);
        EXPECT_CALL(*this, Disconnect);
      }
    }

   private:
    ~TestAudioDataListener() = default;
  };

  // Create a full-duplex AudioCallbackDriver by creating a NativeInputTrack.
  const CubebUtils::AudioDeviceID device1 = (CubebUtils::AudioDeviceID)1;
  RefPtr<TestAudioDataListener> listener1 = new TestAudioDataListener(1, false);
  RefPtr<TestDeviceInputConsumerTrack> track1 =
      TestDeviceInputConsumerTrack::Create(graphImpl);
  track1->ConnectDeviceInput(device1, listener1.get(), PRINCIPAL_HANDLE_NONE);

  EXPECT_TRUE(track1->ConnectedToNativeDevice());
  EXPECT_FALSE(track1->ConnectedToNonNativeDevice());
  auto started =
      InvokeAsync([&] { return graphImpl->NotifyWhenDeviceStarted(nullptr); });
  RefPtr<SmartMockCubebStream> stream1 = WaitFor(cubeb->StreamInitEvent());
  EXPECT_TRUE(stream1->mHasInput);
  EXPECT_TRUE(stream1->mHasOutput);
  EXPECT_EQ(stream1->GetInputDeviceID(), device1);
  Unused << WaitFor(started);

  // Create a NonNativeInputTrack, and make sure its DeviceChangeCallback works.
  const CubebUtils::AudioDeviceID device2 = (CubebUtils::AudioDeviceID)2;
  RefPtr<TestAudioDataListener> listener2 = new TestAudioDataListener(2, true);
  RefPtr<TestDeviceInputConsumerTrack> track2 =
      TestDeviceInputConsumerTrack::Create(graphImpl);
  track2->ConnectDeviceInput(device2, listener2.get(), PRINCIPAL_HANDLE_NONE);

  EXPECT_FALSE(track2->ConnectedToNativeDevice());
  EXPECT_TRUE(track2->ConnectedToNonNativeDevice());
  RefPtr<SmartMockCubebStream> stream2 = WaitFor(cubeb->StreamInitEvent());
  EXPECT_TRUE(stream2->mHasInput);
  EXPECT_FALSE(stream2->mHasOutput);
  EXPECT_EQ(stream2->GetInputDeviceID(), device2);

  // Produce a device-changed event for the NonNativeInputTrack.
  DispatchFunction([&] { stream2->ForceDeviceChanged(); });
  WaitFor(stream2->DeviceChangeForcedEvent());

  // Produce a device-changed event for the NativeInputTrack.
  DispatchFunction([&] { stream1->ForceDeviceChanged(); });
  WaitFor(stream1->DeviceChangeForcedEvent());

  // Destroy the NonNativeInputTrack.
  DispatchFunction([&] {
    track2->DisconnectDeviceInput();
    track2->Destroy();
  });
  RefPtr<SmartMockCubebStream> destroyedStream =
      WaitFor(cubeb->StreamDestroyEvent());
  EXPECT_EQ(destroyedStream.get(), stream2.get());

  // Destroy the NativeInputTrack.
  DispatchFunction([&] {
    track1->DisconnectDeviceInput();
    track1->Destroy();
  });
  destroyedStream = WaitFor(cubeb->StreamDestroyEvent());
  EXPECT_EQ(destroyedStream.get(), stream1.get());
}

// The native audio stream (a.k.a. GraphDriver) and the non-native audio stream
// should always be the same as the max requested input channel of its paired
// DeviceInputTracks. This test checks if the audio stream paired with the
// DeviceInputTrack will follow the max requested input channel or not.
//
// The main focus for this test is to make sure DeviceInputTrack::OpenAudio and
// ::CloseAudio works as what we expect. Besides, This test also confirms
// MediaTrackGraph::ReevaluateInputDevice works correctly by using a
// test-only AudioDataListener.
//
// This test is pretty similar to RestartAudioIfProcessingMaxChannelCountChanged
// below, which tests the same thing but using AudioProcessingTrack.
// AudioProcessingTrack is the consumer of the  DeviceInputTrack used in wild.
// It has its own customized AudioDataListener. However, it only tests when
// MOZ_WEBRTC is defined.
TEST(TestAudioTrackGraph, RestartAudioIfMaxChannelCountChanged)
{
  MockCubeb* cubeb = new MockCubeb();
  CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());
  auto unforcer = WaitFor(cubeb->ForceAudioThread()).unwrap();
  Unused << unforcer;

  MediaTrackGraph* graphImpl = MediaTrackGraphImpl::GetInstance(
      MediaTrackGraph::SYSTEM_THREAD_DRIVER, /*Window ID*/ 1,
      CubebUtils::PreferredSampleRate(/* aShouldResistFingerprinting */ false),
      nullptr, GetMainThreadSerialEventTarget());

  // A test-only AudioDataListener that simulates AudioInputProcessing's setter
  // and getter for the input channel count.
  class TestAudioDataListener : public StrictMock<MockAudioDataListener> {
   public:
    TestAudioDataListener(uint32_t aChannelCount, bool aIsVoice) {
      EXPECT_CALL(*this, RequestedInputChannelCount)
          .WillRepeatedly(Return(aChannelCount));
      EXPECT_CALL(*this, RequestedInputProcessingParams)
          .WillRepeatedly(Return(CUBEB_INPUT_PROCESSING_PARAM_NONE));
      EXPECT_CALL(*this, IsVoiceInput).WillRepeatedly(Return(aIsVoice));
      EXPECT_CALL(*this, Disconnect);
    }
    // Main thread API
    void SetInputChannelCount(MediaTrackGraph* aGraph,
                              CubebUtils::AudioDeviceID aDevice,
                              uint32_t aChannelCount) {
      MOZ_RELEASE_ASSERT(NS_IsMainThread());
      static_cast<MediaTrackGraphImpl*>(aGraph)
          ->QueueControlMessageWithNoShutdown(
              [this, self = RefPtr(this), aGraph, aDevice, aChannelCount] {
                EXPECT_CALL(*this, RequestedInputChannelCount)
                    .WillRepeatedly(Return(aChannelCount));
                aGraph->ReevaluateInputDevice(aDevice);
              });
    }

   private:
    ~TestAudioDataListener() = default;
  };

  // Request a new input channel count and expect to have a new stream.
  auto setNewChannelCount = [&](const RefPtr<TestAudioDataListener>&&nbsp;aListener,
                                RefPtr<SmartMockCubebStream>& aStream,
                                uint32_t aChannelCount) {
    ASSERT_TRUE(!!aListener);
    ASSERT_TRUE(!!aStream);
    ASSERT_TRUE(aStream->mHasInput);
    ASSERT_NE(aChannelCount, 0U);

    const CubebUtils::AudioDeviceID device = aStream->GetInputDeviceID();

    bool destroyed = false;
    MediaEventListener destroyListener = cubeb->StreamDestroyEvent().Connect(
        AbstractThread::GetCurrent(),
        [&](const RefPtr<SmartMockCubebStream>& aDestroyed) {
          destroyed = aDestroyed.get() == aStream.get();
        });

    RefPtr<SmartMockCubebStream> newStream;
    MediaEventListener restartListener = cubeb->StreamInitEvent().Connect(
        AbstractThread::GetCurrent(),
        [&](const RefPtr<SmartMockCubebStream>& aCreated) {
          newStream = aCreated;
        });

    DispatchFunction([&] {
      aListener->SetInputChannelCount(graphImpl, device, aChannelCount);
    });

    SpinEventLoopUntil<ProcessFailureBehavior::IgnoreAndContinue>(
        "TEST(TestAudioTrackGraph, RestartAudioIfMaxChannelCountChanged) #1"_ns,
        [&] { return destroyed && newStream; });

    destroyListener.Disconnect();
    restartListener.Disconnect();

    aStream = newStream;
  };

  // Open a new track and expect to have a new stream.
  auto openTrack = [&](RefPtr<SmartMockCubebStream>& aCurrentStream,
                       RefPtr<TestDeviceInputConsumerTrack>& aTrack,
                       const RefPtr<TestAudioDataListener>& aListener,
                       CubebUtils::AudioDeviceID aDevice) {
    ASSERT_TRUE(!!aCurrentStream);
    ASSERT_TRUE(aCurrentStream->mHasInput);
    ASSERT_TRUE(!aTrack);
    ASSERT_TRUE(!!aListener);

    bool destroyed = false;
    MediaEventListener destroyListener = cubeb->StreamDestroyEvent().Connect(
        AbstractThread::GetCurrent(),
        [&](const RefPtr<SmartMockCubebStream>& aDestroyed) {
          destroyed = aDestroyed.get() == aCurrentStream.get();
        });

    RefPtr<SmartMockCubebStream> newStream;
    MediaEventListener restartListener = cubeb->StreamInitEvent().Connect(
        AbstractThread::GetCurrent(),
        [&](const RefPtr<SmartMockCubebStream>& aCreated) {
          newStream = aCreated;
        });

    aTrack = TestDeviceInputConsumerTrack::Create(graphImpl);
    aTrack->ConnectDeviceInput(aDevice, aListener.get(), PRINCIPAL_HANDLE_NONE);

    SpinEventLoopUntil<ProcessFailureBehavior::IgnoreAndContinue>(
        "TEST(TestAudioTrackGraph, RestartAudioIfMaxChannelCountChanged) #2"_ns,
        [&] { return destroyed && newStream; });

    destroyListener.Disconnect();
    restartListener.Disconnect();

    aCurrentStream = newStream;
  };

  // Test for the native input device first then non-native device. The
  // non-native device will be destroyed before the native device in case of
  // causing a driver switching.

  // Test for the native device.
  const CubebUtils::AudioDeviceID nativeDevice = (CubebUtils::AudioDeviceID)1;
  RefPtr<TestDeviceInputConsumerTrack> track1;
  RefPtr<TestAudioDataListener> listener1;
  RefPtr<SmartMockCubebStream> nativeStream;
  RefPtr<TestDeviceInputConsumerTrack> track2;
  RefPtr<TestAudioDataListener> listener2;
  {
    // Open a 1-channel NativeInputTrack.
    listener1 = new TestAudioDataListener(1, false);
    track1 = TestDeviceInputConsumerTrack::Create(graphImpl);
    track1->ConnectDeviceInput(nativeDevice, listener1.get(),
                               PRINCIPAL_HANDLE_NONE);

    EXPECT_TRUE(track1->ConnectedToNativeDevice());
    EXPECT_FALSE(track1->ConnectedToNonNativeDevice());
    auto started = InvokeAsync(
        [&] { return graphImpl->NotifyWhenDeviceStarted(nullptr); });
    nativeStream = WaitFor(cubeb->StreamInitEvent());
    EXPECT_TRUE(nativeStream->mHasInput);
    EXPECT_TRUE(nativeStream->mHasOutput);
    EXPECT_EQ(nativeStream->GetInputDeviceID(), nativeDevice);
    Unused << WaitFor(started);

    // Open a 2-channel NativeInputTrack and wait for a new driver since the
    // max-channel for the native device becomes 2 now.
    listener2 = new TestAudioDataListener(2, false);
    openTrack(nativeStream, track2, listener2, nativeDevice);
    EXPECT_EQ(nativeStream->InputChannels(), 2U);

    // Set the second NativeInputTrack to 1-channel and wait for a new driver
    // since the max-channel for the native device becomes 1 now.
    setNewChannelCount(listener2, nativeStream, 1);
    EXPECT_EQ(nativeStream->InputChannels(), 1U);

    // Set the first NativeInputTrack to 2-channel and wait for a new driver
    // since the max input channel for the native device becomes 2 now.
    setNewChannelCount(listener1, nativeStream, 2);
    EXPECT_EQ(nativeStream->InputChannels(), 2U);
  }

  // Test for the non-native device.
  {
    const CubebUtils::AudioDeviceID nonNativeDevice =
        (CubebUtils::AudioDeviceID)2;

    // Open a 1-channel NonNativeInputTrack.
    RefPtr<TestAudioDataListener> listener3 =
        new TestAudioDataListener(1, false);
    RefPtr<TestDeviceInputConsumerTrack> track3 =
        TestDeviceInputConsumerTrack::Create(graphImpl);
    track3->ConnectDeviceInput(nonNativeDevice, listener3.get(),
                               PRINCIPAL_HANDLE_NONE);
    EXPECT_FALSE(track3->ConnectedToNativeDevice());
    EXPECT_TRUE(track3->ConnectedToNonNativeDevice());

    RefPtr<SmartMockCubebStream> nonNativeStream =
        WaitFor(cubeb->StreamInitEvent());
    EXPECT_TRUE(nonNativeStream->mHasInput);
    EXPECT_FALSE(nonNativeStream->mHasOutput);
    EXPECT_EQ(nonNativeStream->GetInputDeviceID(), nonNativeDevice);
    EXPECT_EQ(nonNativeStream->InputChannels(), 1U);

    // Open a 2-channel NonNativeInputTrack and wait for a new stream since
    // the max-channel for the non-native device becomes 2 now.
    RefPtr<TestAudioDataListener> listener4 =
        new TestAudioDataListener(2, false);
    RefPtr<TestDeviceInputConsumerTrack> track4;
    openTrack(nonNativeStream, track4, listener4, nonNativeDevice);
    EXPECT_EQ(nonNativeStream->InputChannels(), 2U);
    EXPECT_EQ(nonNativeStream->GetInputDeviceID(), nonNativeDevice);

    // Set the second NonNativeInputTrack to 1-channel and wait for a new
    // driver since the max-channel for the non-native device becomes 1 now.
    setNewChannelCount(listener4, nonNativeStream, 1);
    EXPECT_EQ(nonNativeStream->InputChannels(), 1U);

    // Set the first NonNativeInputTrack to 2-channel and wait for a new
    // driver since the max input channel for the non-native device becomes 2
    // now.
    setNewChannelCount(listener3, nonNativeStream, 2);
    EXPECT_EQ(nonNativeStream->InputChannels(), 2U);

    // Close the second NonNativeInputTrack (1-channel) then the first one
    // (2-channel) so we won't result in another stream creation.
    DispatchFunction([&] {
      track4->DisconnectDeviceInput();
      track4->Destroy();
    });
    DispatchFunction([&] {
      track3->DisconnectDeviceInput();
      track3->Destroy();
    });
    RefPtr<SmartMockCubebStream> destroyedStream =
        WaitFor(cubeb->StreamDestroyEvent());
    EXPECT_EQ(destroyedStream.get(), nonNativeStream.get());
  }

  // Tear down for the native device.
  {
    // Close the second NativeInputTrack (1-channel) then the first one
    // (2-channel) so we won't have driver switching.
    DispatchFunction([&] {
      track2->DisconnectDeviceInput();
      track2->Destroy();
    });
    DispatchFunction([&] {
      track1->DisconnectDeviceInput();
      track1->Destroy();
    });
    RefPtr<SmartMockCubebStream> destroyedStream =
        WaitFor(cubeb->StreamDestroyEvent());
    EXPECT_EQ(destroyedStream.get(), nativeStream.get());
  }
}

// This test is pretty similar to SwitchNativeAudioProcessingTrack below, which
// tests the same thing but using AudioProcessingTrack. AudioProcessingTrack is
// the consumer of the  DeviceInputTrack used in wild. It has its own customized
// AudioDataListener. However, it only tests when MOZ_WEBRTC is defined.
TEST(TestAudioTrackGraph, SwitchNativeInputDevice)
{
  class TestAudioDataListener : public StrictMock<MockAudioDataListener> {
   public:
    TestAudioDataListener(uint32_t aChannelCount, bool aIsVoice) {
      EXPECT_CALL(*this, RequestedInputChannelCount)
          .WillRepeatedly(Return(aChannelCount));
      EXPECT_CALL(*this, RequestedInputProcessingParams)
          .WillRepeatedly(Return(CUBEB_INPUT_PROCESSING_PARAM_NONE));
      EXPECT_CALL(*this, IsVoiceInput).WillRepeatedly(Return(aIsVoice));
    }

   private:
    ~TestAudioDataListener() = default;
  };

  MockCubeb* cubeb = new MockCubeb();
  CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());

  MediaTrackGraph* graph = MediaTrackGraphImpl::GetInstance(
      MediaTrackGraph::SYSTEM_THREAD_DRIVER, /*Window ID*/ 1,
      CubebUtils::PreferredSampleRate(/* aShouldResistFingerprinting */ false),
      nullptr, GetMainThreadSerialEventTarget());

  auto switchNativeDevice =
      [&](RefPtr<SmartMockCubebStream>&& aCurrentNativeStream,
          RefPtr<TestDeviceInputConsumerTrack>& aCurrentNativeTrack,
          RefPtr<SmartMockCubebStream>& aNextNativeStream,
          RefPtr<TestDeviceInputConsumerTrack>& aNextNativeTrack) {
        ASSERT_TRUE(aCurrentNativeStream->mHasInput);
        ASSERT_TRUE(aCurrentNativeStream->mHasOutput);
        ASSERT_TRUE(aNextNativeStream->mHasInput);
        ASSERT_FALSE(aNextNativeStream->mHasOutput);

        std::cerr << "Switching native input from device "
                  << aCurrentNativeStream->GetInputDeviceID() << " to "
                  << aNextNativeStream->GetInputDeviceID() << std::endl;

        uint32_t destroyed = 0;
        MediaEventListener destroyListener =
            cubeb->StreamDestroyEvent().Connect(
                AbstractThread::GetCurrent(),
                [&](const RefPtr<SmartMockCubebStream>& aDestroyed) {
                  if (aDestroyed.get() == aCurrentNativeStream.get() ||
                      aDestroyed.get() == aNextNativeStream.get()) {
                    std::cerr << "cubeb stream " << aDestroyed.get()
                              << " (device " << aDestroyed->GetInputDeviceID()
                              << ") has been destroyed" << std::endl;
                    destroyed += 1;
                  }
                });

        RefPtr<SmartMockCubebStream> newStream;
        MediaEventListener restartListener = cubeb->StreamInitEvent().Connect(
            AbstractThread::GetCurrent(),
            [&](const RefPtr<SmartMockCubebStream>& aCreated) {
              // Make sure new stream has input, to prevent from getting a
              // temporary output-only AudioCallbackDriver after closing current
              // native device but before setting a new native input.
              if (aCreated->mHasInput) {
                ASSERT_TRUE(aCreated->mHasOutput);
                newStream = aCreated;
              }
            });

        std::cerr << "Close device " << aCurrentNativeStream->GetInputDeviceID()
                  << std::endl;
        DispatchFunction([&] {
          aCurrentNativeTrack->DisconnectDeviceInput();
          aCurrentNativeTrack->Destroy();
        });

        std::cerr << "Wait for the switching" << std::endl;
        SpinEventLoopUntil<ProcessFailureBehavior::IgnoreAndContinue>(
            "TEST(TestAudioTrackGraph, SwitchNativeInputDevice)"_ns,
            [&] { return destroyed >= 2 && newStream; });

        destroyListener.Disconnect();
        restartListener.Disconnect();

        aCurrentNativeStream = nullptr;
        aNextNativeStream = newStream;

        std::cerr << "Now the native input is device "
                  << aNextNativeStream->GetInputDeviceID() << std::endl;
      };

  // Open a DeviceInputConsumerTrack for device 1.
  const CubebUtils::AudioDeviceID device1 = (CubebUtils::AudioDeviceID)1;
  RefPtr<TestDeviceInputConsumerTrack> track1 =
      TestDeviceInputConsumerTrack::Create(graph);
  RefPtr<TestAudioDataListener> listener1 = new TestAudioDataListener(1, false);
  EXPECT_CALL(*listener1, Disconnect);
  track1->ConnectDeviceInput(device1, listener1, PRINCIPAL_HANDLE_NONE);
  EXPECT_EQ(track1->DeviceId().value(), device1);

  auto started =
      InvokeAsync([&] { return graph->NotifyWhenDeviceStarted(nullptr); });

  RefPtr<SmartMockCubebStream> stream1 = WaitFor(cubeb->StreamInitEvent());
  EXPECT_TRUE(stream1->mHasInput);
  EXPECT_TRUE(stream1->mHasOutput);
  EXPECT_EQ(stream1->InputChannels(), 1U);
  EXPECT_EQ(stream1->GetInputDeviceID(), device1);
  Unused << WaitFor(started);
  std::cerr << "Device " << device1 << " is opened (stream " << stream1.get()
            << ")" << std::endl;

  // Open a DeviceInputConsumerTrack for device 2.
  const CubebUtils::AudioDeviceID device2 = (CubebUtils::AudioDeviceID)2;
  RefPtr<TestDeviceInputConsumerTrack> track2 =
      TestDeviceInputConsumerTrack::Create(graph);
  RefPtr<TestAudioDataListener> listener2 = new TestAudioDataListener(2, false);
  EXPECT_CALL(*listener2, Disconnect).Times(2);
  track2->ConnectDeviceInput(device2, listener2, PRINCIPAL_HANDLE_NONE);
  EXPECT_EQ(track2->DeviceId().value(), device2);

  RefPtr<SmartMockCubebStream> stream2 = WaitFor(cubeb->StreamInitEvent());
  EXPECT_TRUE(stream2->mHasInput);
  EXPECT_FALSE(stream2->mHasOutput);
  EXPECT_EQ(stream2->InputChannels(), 2U);
  EXPECT_EQ(stream2->GetInputDeviceID(), device2);
  std::cerr << "Device " << device2 << " is opened (stream " << stream2.get()
            << ")" << std::endl;

  // Open a DeviceInputConsumerTrack for device 3.
  const CubebUtils::AudioDeviceID device3 = (CubebUtils::AudioDeviceID)3;
  RefPtr<TestDeviceInputConsumerTrack> track3 =
      TestDeviceInputConsumerTrack::Create(graph);
  RefPtr<TestAudioDataListener> listener3 = new TestAudioDataListener(1, false);
  EXPECT_CALL(*listener3, Disconnect).Times(2);
  track3->ConnectDeviceInput(device3, listener3, PRINCIPAL_HANDLE_NONE);
  EXPECT_EQ(track3->DeviceId().value(), device3);

  RefPtr<SmartMockCubebStream> stream3 = WaitFor(cubeb->StreamInitEvent());
  EXPECT_TRUE(stream3->mHasInput);
  EXPECT_FALSE(stream3->mHasOutput);
  EXPECT_EQ(stream3->InputChannels(), 1U);
  EXPECT_EQ(stream3->GetInputDeviceID(), device3);
  std::cerr << "Device " << device3 << " is opened (stream " << stream3.get()
            << ")" << std::endl;

  // Close device 1, so the native input device is switched from device 1 to
  // device 2.
  switchNativeDevice(std::move(stream1), track1, stream2, track2);
  EXPECT_TRUE(stream2->mHasInput);
  EXPECT_TRUE(stream2->mHasOutput);
  EXPECT_EQ(stream2->InputChannels(), 2U);
  EXPECT_EQ(stream2->GetInputDeviceID(), device2);
  {
    NativeInputTrack* native = track2->Graph()->GetNativeInputTrackMainThread();
    ASSERT_TRUE(!!native);
    EXPECT_EQ(native->mDeviceId, device2);
  }

  // Close device 2, so the native input device is switched from device 2 to
  // device 3.
  switchNativeDevice(std::move(stream2), track2, stream3, track3);
  EXPECT_TRUE(stream3->mHasInput);
  EXPECT_TRUE(stream3->mHasOutput);
  EXPECT_EQ(stream3->InputChannels(), 1U);
  EXPECT_EQ(stream3->GetInputDeviceID(), device3);
  {
    NativeInputTrack* native = track3->Graph()->GetNativeInputTrackMainThread();
    ASSERT_TRUE(!!native);
    EXPECT_EQ(native->mDeviceId, device3);
  }

  // Clean up.
  std::cerr << "Close device " << device3 << std::endl;
  DispatchFunction([&] {
    track3->DisconnectDeviceInput();
    track3->Destroy();
  });
  RefPtr<SmartMockCubebStream> destroyedStream =
      WaitFor(cubeb->StreamDestroyEvent());
  EXPECT_EQ(destroyedStream.get(), stream3.get());
  {
    NativeInputTrack* native = graph->GetNativeInputTrackMainThread();
    ASSERT_TRUE(!native);
  }
  std::cerr << "No native input now" << std::endl;
}

#ifdef MOZ_WEBRTC
TEST(TestAudioTrackGraph, ErrorCallback)
{
  MockCubeb* cubeb = new MockCubeb();
  CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());

  MediaTrackGraph* graph = MediaTrackGraphImpl::GetInstance(
      MediaTrackGraph::SYSTEM_THREAD_DRIVER, /*Window ID*/ 1,
      CubebUtils::PreferredSampleRate(/* aShouldResistFingerprinting */ false),
      nullptr, GetMainThreadSerialEventTarget());

  const CubebUtils::AudioDeviceID deviceId = (CubebUtils::AudioDeviceID)1;

  // Dummy track to make graph rolling. Add it and remove it to remove the
  // graph from the global hash table and let it shutdown.
  //
  // We open an input through this track so that there's something triggering
  // EnsureNextIteration on the fallback driver after the callback driver has
  // gotten the error, and to check that a replacement cubeb_stream receives
  // output from the graph.
  RefPtr<AudioProcessingTrack> processingTrack;
  RefPtr<AudioInputProcessing> listener;
  auto started = InvokeAsync([&] {
    processingTrack = AudioProcessingTrack::Create(graph);
    listener = new AudioInputProcessing(2);
    QueueExpectIsPassThrough(processingTrack, listener);
    processingTrack->SetInputProcessing(listener);
    processingTrack->GraphImpl()->AppendMessage(
        MakeUnique<StartInputProcessing>(processingTrack, listener));
    processingTrack->ConnectDeviceInput(deviceId, listener,
                                        PRINCIPAL_HANDLE_NONE);
    EXPECT_EQ(processingTrack->DeviceId().value(), deviceId);
    processingTrack->AddAudioOutput(reinterpret_cast<void*>(1), nullptr);
    return graph->NotifyWhenDeviceStarted(nullptr);
  });

  RefPtr<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent());
  Result<bool, nsresult> rv = WaitFor(started);
  EXPECT_TRUE(rv.unwrapOr(false));

  // Force a cubeb state_callback error and see that we don't crash.
  DispatchFunction([&] { stream->ForceError(); });

  // Wait for the error to take effect, and the driver to restart and receive
  // output.
  bool errored = false;
  MediaEventListener errorListener = stream->ErrorForcedEvent().Connect(
      AbstractThread::GetCurrent(), [&] { errored = true; });
  stream = WaitFor(cubeb->StreamInitEvent());
  WaitFor(stream->FramesVerifiedEvent());
  // The error event is notified after CUBEB_STATE_ERROR triggers other
  // threads to init a new cubeb_stream, so there is a theoretical chance that
  // `errored` might not be set when `stream` is set.
  errorListener.Disconnect();
  EXPECT_TRUE(errored);

  // Clean up.
  DispatchFunction([&] {
    processingTrack->GraphImpl()->AppendMessage(
        MakeUnique<StopInputProcessing>(processingTrack, listener));
    processingTrack->DisconnectDeviceInput();
    processingTrack->Destroy();
  });
  WaitFor(cubeb->StreamDestroyEvent());
}

TEST(TestAudioTrackGraph, AudioProcessingTrack)
{
  MockCubeb* cubeb = new MockCubeb();
  CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());
  auto unforcer = WaitFor(cubeb->ForceAudioThread()).unwrap();
  Unused << unforcer;

  // Start on a system clock driver, then switch to full-duplex in one go. If we
  // did output-then-full-duplex we'd risk a second NotifyWhenDeviceStarted
  // resolving early after checking the first audio driver only.
  MediaTrackGraph* graph = MediaTrackGraphImpl::GetInstance(
      MediaTrackGraph::SYSTEM_THREAD_DRIVER, /*Window ID*/ 1,
      CubebUtils::PreferredSampleRate(/* aShouldResistFingerprinting */ false),
      nullptr, GetMainThreadSerialEventTarget());

  const CubebUtils::AudioDeviceID deviceId = (CubebUtils::AudioDeviceID)1;

  RefPtr<AudioProcessingTrack> processingTrack;
  RefPtr<ProcessedMediaTrack> outputTrack;
  RefPtr<MediaInputPort> port;
  RefPtr<AudioInputProcessing> listener;
  auto p = InvokeAsync([&] {
    processingTrack = AudioProcessingTrack::Create(graph);
    outputTrack = graph->CreateForwardedInputTrack(MediaSegment::AUDIO);
    outputTrack->QueueSetAutoend(false);
    outputTrack->AddAudioOutput(reinterpret_cast<void*>(1), nullptr);
    port = outputTrack->AllocateInputPort(processingTrack);
    /* Primary graph: Open Audio Input through SourceMediaTrack */
    listener = new AudioInputProcessing(2);
    QueueExpectIsPassThrough(processingTrack, listener);
    processingTrack->SetInputProcessing(listener);
    processingTrack->GraphImpl()->AppendMessage(
        MakeUnique<StartInputProcessing>(processingTrack, listener));
    // Device id does not matter. Ignore.
    processingTrack->ConnectDeviceInput(deviceId, listener,
                                        PRINCIPAL_HANDLE_NONE);
    return graph->NotifyWhenDeviceStarted(nullptr);
  });

  RefPtr<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent());
  EXPECT_TRUE(stream->mHasInput);
  Unused << WaitFor(p);

  // Wait for a second worth of audio data. GoFaster is dispatched through a
  // ControlMessage so that it is called in the first audio driver iteration.
  // Otherwise the audio driver might be going very fast while the fallback
  // system clock driver is still in an iteration.
  DispatchFunction([&] {
    processingTrack->GraphImpl()->AppendMessage(MakeUnique<GoFaster>(cubeb));
  });
  uint32_t totalFrames = 0;
  WaitUntil(stream->FramesVerifiedEvent(), [&](uint32_t aFrames) {
    totalFrames += aFrames;
    return totalFrames > static_cast<uint32_t>(graph->GraphRate());
  });
  cubeb->DontGoFaster();

  // Clean up.
  DispatchFunction([&] {
    outputTrack->RemoveAudioOutput((void*)1);
    outputTrack->Destroy();
    port->Destroy();
    processingTrack->GraphImpl()->AppendMessage(
        MakeUnique<StopInputProcessing>(processingTrack, listener));
    processingTrack->DisconnectDeviceInput();
    processingTrack->Destroy();
  });

  uint32_t inputRate = stream->SampleRate();
  uint32_t inputFrequency = stream->InputFrequency();
  uint64_t preSilenceSamples;
  uint32_t estimatedFreq;
  uint32_t nrDiscontinuities;
  std::tie(preSilenceSamples, estimatedFreq, nrDiscontinuities) =
      WaitFor(stream->OutputVerificationEvent());

  EXPECT_EQ(estimatedFreq, inputFrequency);
  std::cerr << "PreSilence: " << preSilenceSamples << std::endl;
  // We buffer 128 frames. See NativeInputTrack::NotifyInputData.
  EXPECT_GE(preSilenceSamples, 128U);
  // If the fallback system clock driver is doing a graph iteration before the
  // first audio driver iteration comes in, that iteration is ignored and
  // results in zeros. It takes one fallback driver iteration *after* the audio
  // driver has started to complete the switch, *usually* resulting two
  // 10ms-iterations of silence; sometimes only one.
  EXPECT_LE(preSilenceSamples, 128U + 2 * inputRate / 100 /* 2*10ms */);
  // The waveform from AudioGenerator starts at 0, but we don't control its
  // ending, so we expect a discontinuity there.
  EXPECT_LE(nrDiscontinuities, 1U);
}

TEST(TestAudioTrackGraph, ReConnectDeviceInput)
{
  MockCubeb* cubeb = new MockCubeb(MockCubeb::RunningMode::Manual);
  CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());

  // 48k is a native processing rate, and avoids a resampling pass compared
  // to 44.1k. The resampler may add take a few frames to stabilize, which show
  // as unexected discontinuities in the test.
  const TrackRate rate = 48000;
  // Use a drift factor so that we don't dont produce perfect 10ms-chunks.
  // This will exercise whatever buffers are in the audio processing pipeline,
  // and the bookkeeping surrounding them.
  const long step = 10 * rate * 1111 / 1000 / PR_MSEC_PER_SEC;

  MediaTrackGraph* graph = MediaTrackGraphImpl::GetInstance(
      MediaTrackGraph::SYSTEM_THREAD_DRIVER, /*Window ID*/ 1, rate, nullptr,
      GetMainThreadSerialEventTarget());

  const CubebUtils::AudioDeviceID deviceId = (CubebUtils::AudioDeviceID)1;

  RefPtr<AudioProcessingTrack> processingTrack;
  RefPtr<ProcessedMediaTrack> outputTrack;
  RefPtr<MediaInputPort> port;
  RefPtr<AudioInputProcessing> listener;
  RefPtr<OnFallbackListener> fallbackListener;
  DispatchFunction([&] {
    processingTrack = AudioProcessingTrack::Create(graph);
    outputTrack = graph->CreateForwardedInputTrack(MediaSegment::AUDIO);
    outputTrack->QueueSetAutoend(false);
    outputTrack->AddAudioOutput(reinterpret_cast<void*>(1), nullptr);
    port = outputTrack->AllocateInputPort(processingTrack);

    const int32_t channelCount = 2;
    listener = new AudioInputProcessing(channelCount);
    processingTrack->SetInputProcessing(listener);
    processingTrack->GraphImpl()->AppendMessage(
        MakeUnique<StartInputProcessing>(processingTrack, listener));
    processingTrack->ConnectDeviceInput(deviceId, listener,
                                        PRINCIPAL_HANDLE_NONE);
    MediaEnginePrefs settings;
    settings.mChannels = channelCount;
    settings.mAgcOn = true;  // Turn off pass-through.
    // AGC1 Mode 0 interferes with AudioVerifier's frequency estimation
    // through zero-crossing counts.
    settings.mAgc2Forced = true;
    QueueApplySettings(processingTrack, listener, settings);

    fallbackListener = new OnFallbackListener(processingTrack);
    processingTrack->AddListener(fallbackListener);
  });

  RefPtr<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent());
  EXPECT_TRUE(stream->mHasInput);

  while (
      stream->State()
          .map([](cubeb_state aState) { return aState != CUBEB_STATE_STARTED; })
          .valueOr(true)) {
    std::this_thread::sleep_for(std::chrono::milliseconds(1));
  }

  // Wait for the AudioCallbackDriver to come into effect.
  while (fallbackListener->OnFallback()) {
    EXPECT_EQ(stream->ManualDataCallback(0),
              MockCubebStream::KeepProcessing::Yes);
    std::this_thread::sleep_for(std::chrono::milliseconds(1));
  }

  // Iterate for a second worth of audio data.
  for (long frames = 0; frames < graph->GraphRate(); frames += step) {
    stream->ManualDataCallback(step);
  }

  // Close the input to see that no asserts go off due to bad state.
  DispatchFunction([&] { processingTrack->DisconnectDeviceInput(); });

  // Dispatch the disconnect message.
  ProcessEventQueue();
  // Run the disconnect message.
  EXPECT_EQ(stream->ManualDataCallback(0),
            MockCubebStream::KeepProcessing::Yes);
  // Switch driver.
  auto initPromise = TakeN(cubeb->StreamInitEvent(), 1);
  EXPECT_EQ(stream->ManualDataCallback(0), MockCubebStream::KeepProcessing::No);
  std::tie(stream) = WaitFor(initPromise).unwrap()[0];
  EXPECT_FALSE(stream->mHasInput);

  while (
      stream->State()
          .map([](cubeb_state aState) { return aState != CUBEB_STATE_STARTED; })
          .valueOr(true)) {
    std::this_thread::sleep_for(std::chrono::milliseconds(1));
  }

  // Wait for the new AudioCallbackDriver to come into effect.
  fallbackListener->Reset();
  while (fallbackListener->OnFallback()) {
    EXPECT_EQ(stream->ManualDataCallback(0),
              MockCubebStream::KeepProcessing::Yes);
    std::this_thread::sleep_for(std::chrono::milliseconds(1));
  }

  // Output-only. Iterate for another second before unmuting.
  for (long frames = 0; frames < graph->GraphRate(); frames += step) {
    stream->ManualDataCallback(step);
  }

  // Re-open the input to again see that no asserts go off due to bad state.
  DispatchFunction([&] {
    // Device id does not matter. Ignore.
    processingTrack->ConnectDeviceInput(deviceId, listener,
                                        PRINCIPAL_HANDLE_NONE);
  });
  // Dispatch the connect message.
  ProcessEventQueue();
  // Run the connect message.
  EXPECT_EQ(stream->ManualDataCallback(0),
            MockCubebStream::KeepProcessing::Yes);
  // Switch driver.
  initPromise = TakeN(cubeb->StreamInitEvent(), 1);
  EXPECT_EQ(stream->ManualDataCallback(0), MockCubebStream::KeepProcessing::No);
  std::tie(stream) = WaitFor(initPromise).unwrap()[0];
  EXPECT_TRUE(stream->mHasInput);

  while (
      stream->State()
          .map([](cubeb_state aState) { return aState != CUBEB_STATE_STARTED; })
          .valueOr(true)) {
    std::this_thread::sleep_for(std::chrono::milliseconds(1));
  }

  // Wait for the new AudioCallbackDriver to come into effect.
  fallbackListener->Reset();
  while (fallbackListener->OnFallback()) {
    EXPECT_EQ(stream->ManualDataCallback(0),
              MockCubebStream::KeepProcessing::Yes);
    std::this_thread::sleep_for(std::chrono::milliseconds(1));
  }

  // Full-duplex. Iterate for another second before finishing.
  for (long frames = 0; frames < graph->GraphRate(); frames += step) {
    stream->ManualDataCallback(step);
  }

  // Clean up.
  DispatchFunction([&] {
    processingTrack->RemoveListener(fallbackListener);
    outputTrack->RemoveAudioOutput((void*)1);
    outputTrack->Destroy();
    port->Destroy();
    processingTrack->GraphImpl()->AppendMessage(
        MakeUnique<StopInputProcessing>(processingTrack, listener));
    processingTrack->DisconnectDeviceInput();
    processingTrack->Destroy();
  });

  // Dispatch the clean-up messages.
  ProcessEventQueue();
  // Run the clean-up messages.
  EXPECT_EQ(stream->ManualDataCallback(0),
            MockCubebStream::KeepProcessing::Yes);
  // Shut down driver.
  EXPECT_EQ(stream->ManualDataCallback(0), MockCubebStream::KeepProcessing::No);

  uint32_t inputFrequency = stream->InputFrequency();
  uint64_t preSilenceSamples;
  uint32_t estimatedFreq;
  uint32_t nrDiscontinuities;
  std::tie(preSilenceSamples, estimatedFreq, nrDiscontinuities) =
      WaitFor(stream->OutputVerificationEvent());

  EXPECT_EQ(estimatedFreq, inputFrequency);
  std::cerr << "PreSilence: " << preSilenceSamples << "\n";
  // We buffer 128 frames. See NativeInputTrack::NotifyInputData.
  // When not in passthrough the AudioInputProcessing packetizer also buffers
  // 10ms of silence, pulled in from NativeInputTrack when being run by the
  // fallback SystemClockDriver.
  EXPECT_EQ(preSilenceSamples, WEBAUDIO_BLOCK_SIZE + rate / 100);
  // The waveform from AudioGenerator starts at 0, but we don't control its
  // ending, so we expect a discontinuity there. Note that this check is only
  // for the waveform on the stream *after* re-opening the input.
  EXPECT_LE(nrDiscontinuities, 1U);
}

// Sum the signal to mono and compute the root mean square, in float32,
// regardless of the input format.
float rmsf32(AudioDataValue* aSamples, uint32_t aChannels, uint32_t aFrames) {
  float downmixed;
  float rms = 0.;
  uint32_t readIdx = 0;
  for (uint32_t i = 0; i < aFrames; i++) {
    downmixed = 0.;
    for (uint32_t j = 0; j < aChannels; j++) {
      downmixed += ConvertAudioSample<float>(aSamples[readIdx++]);
    }
    rms += downmixed * downmixed;
  }
  rms = rms / aFrames;
  return sqrt(rms);
}

TEST(TestAudioTrackGraph, AudioProcessingTrackDisabling)
{
  MockCubeb* cubeb = new MockCubeb(MockCubeb::RunningMode::Manual);
  CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());

  MediaTrackGraph* graph = MediaTrackGraphImpl::GetInstance(
      MediaTrackGraph::SYSTEM_THREAD_DRIVER, /*Window ID*/ 1,
      CubebUtils::PreferredSampleRate(/* aShouldResistFingerprinting */ false),
      nullptr, GetMainThreadSerialEventTarget());

  const CubebUtils::AudioDeviceID deviceId = (CubebUtils::AudioDeviceID)1;

  RefPtr<AudioProcessingTrack> processingTrack;
  RefPtr<ProcessedMediaTrack> outputTrack;
  RefPtr<MediaInputPort> port;
  RefPtr<AudioInputProcessing> listener;
  RefPtr<OnFallbackListener> fallbackListener;
  DispatchFunction([&] {
    processingTrack = AudioProcessingTrack::Create(graph);
    outputTrack = graph->CreateForwardedInputTrack(MediaSegment::AUDIO);
    outputTrack->QueueSetAutoend(false);
    outputTrack->AddAudioOutput(reinterpret_cast<void*>(1), nullptr);
    port = outputTrack->AllocateInputPort(processingTrack);
    /* Primary graph: Open Audio Input through SourceMediaTrack */
    listener = new AudioInputProcessing(2);
    QueueExpectIsPassThrough(processingTrack, listener);
    processingTrack->SetInputProcessing(listener);
    processingTrack->ConnectDeviceInput(deviceId, listener,
                                        PRINCIPAL_HANDLE_NONE);
    processingTrack->GraphImpl()->AppendMessage(
        MakeUnique<StartInputProcessing>(processingTrack, listener));
    fallbackListener = new OnFallbackListener(processingTrack);
    processingTrack->AddListener(fallbackListener);
  });

  ProcessEventQueue();

  RefPtr<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent());
  EXPECT_TRUE(stream->mHasInput);

  while (
      stream->State()
          .map([](cubeb_state aState) { return aState != CUBEB_STATE_STARTED; })
          .valueOr(true)) {
    std::this_thread::sleep_for(std::chrono::milliseconds(1));
  }

  // Wait for the AudioCallbackDriver to come into effect.
  while (fallbackListener->OnFallback()) {
    EXPECT_EQ(stream->ManualDataCallback(0),
              MockCubebStream::KeepProcessing::Yes);
    std::this_thread::sleep_for(std::chrono::milliseconds(1));
  }

  stream->SetOutputRecordingEnabled(true);

  // Wait for a second worth of audio data.
  const long step = graph->GraphRate() / 100;  // 10ms
  for (long frames = 0; frames < graph->GraphRate(); frames += step) {
    stream->ManualDataCallback(step);
  }

  const uint32_t ITERATION_COUNT = 5;
  uint32_t iterations = ITERATION_COUNT;
  DisabledTrackMode nextMode = DisabledTrackMode::SILENCE_BLACK;
  while (iterations--) {
    // toggle the track enabled mode, wait a second, do this ITERATION_COUNT
    // times
    DispatchFunction([&] {
      processingTrack->SetDisabledTrackMode(nextMode);
      if (nextMode == DisabledTrackMode::SILENCE_BLACK) {
        nextMode = DisabledTrackMode::ENABLED;
      } else {
        nextMode = DisabledTrackMode::SILENCE_BLACK;
      }
    });

    ProcessEventQueue();

    for (long frames = 0; frames < graph->GraphRate(); frames += step) {
      stream->ManualDataCallback(step);
    }
  }

  // Clean up.
  DispatchFunction([&] {
    outputTrack->RemoveAudioOutput((void*)1);
    outputTrack->Destroy();
    port->Destroy();
    processingTrack->GraphImpl()->AppendMessage(
        MakeUnique<StopInputProcessing>(processingTrack, listener));
    processingTrack->RemoveListener(fallbackListener);
    processingTrack->DisconnectDeviceInput();
    processingTrack->Destroy();
  });

  ProcessEventQueue();

  // Close the input and switch driver.
  while (stream->ManualDataCallback(0) != MockCubebStream::KeepProcessing::No) {
    std::cerr << "Waiting for switch...\n";
  }

  uint64_t preSilenceSamples;
  uint32_t estimatedFreq;
  uint32_t nrDiscontinuities;
  std::tie(preSilenceSamples, estimatedFreq, nrDiscontinuities) =
      WaitFor(stream->OutputVerificationEvent());

  auto data = stream->TakeRecordedOutput();

  // check that there is non-silence and silence at the expected time in the
  // stereo recording, while allowing for a bit of scheduling uncertainty, by
  // checking half a second after the theoretical muting/unmuting.
  // non-silence starts around: 0s, 2s, 4s
  // silence start around: 1s, 3s, 5s
  // To detect silence or non-silence, we compute the RMS of the signal for
  // 100ms.
  float noisyTime_s[] = {0.5, 2.5, 4.5};
  float silenceTime_s[] = {1.5, 3.5, 5.5};

  uint32_t rate = graph->GraphRate();
  for (float& time : noisyTime_s) {
    uint32_t startIdx = time * rate * 2 /* stereo */;
    EXPECT_NE(rmsf32(&(data[startIdx]), 2, rate / 10), 0.0);
  }

  for (float& time : silenceTime_s) {
    uint32_t startIdx = time * rate * 2 /* stereo */;
    EXPECT_EQ(rmsf32(&(data[startIdx]), 2, rate / 10), 0.0);
  }
}

TEST(TestAudioTrackGraph, SetRequestedInputChannelCount)
{
  MockCubeb* cubeb = new MockCubeb();
  CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());

  MediaTrackGraph* graph = MediaTrackGraphImpl::GetInstance(
      MediaTrackGraph::SYSTEM_THREAD_DRIVER, /*Window ID*/ 1,
      CubebUtils::PreferredSampleRate(/* aShouldResistFingerprinting */ false),
      nullptr, GetMainThreadSerialEventTarget());

  // Open a 2-channel native input stream.
  const CubebUtils::AudioDeviceID device1 = (CubebUtils::AudioDeviceID)1;
  RefPtr<AudioProcessingTrack> track1 = AudioProcessingTrack::Create(graph);
  RefPtr<AudioInputProcessing> listener1 = new AudioInputProcessing(2);
  track1->SetInputProcessing(listener1);
  QueueExpectIsPassThrough(track1, listener1);
  track1->GraphImpl()->AppendMessage(
      MakeUnique<StartInputProcessing>(track1, listener1));
  track1->ConnectDeviceInput(device1, listener1, PRINCIPAL_HANDLE_NONE);
  EXPECT_EQ(track1->DeviceId().value(), device1);

  auto started =
      InvokeAsync([&] { return graph->NotifyWhenDeviceStarted(nullptr); });

  RefPtr<SmartMockCubebStream> stream1 = WaitFor(cubeb->StreamInitEvent());
  EXPECT_TRUE(stream1->mHasInput);
  EXPECT_TRUE(stream1->mHasOutput);
  EXPECT_EQ(stream1->InputChannels(), 2U);
  EXPECT_EQ(stream1->GetInputDeviceID(), device1);
  Unused << WaitFor(started);

  // Open a 1-channel non-native input stream.
  const CubebUtils::AudioDeviceID device2 = (CubebUtils::AudioDeviceID)2;
  RefPtr<AudioProcessingTrack> track2 = AudioProcessingTrack::Create(graph);
  RefPtr<AudioInputProcessing> listener2 = new AudioInputProcessing(1);
  track2->SetInputProcessing(listener2);
  QueueExpectIsPassThrough(track2, listener2);
  track2->GraphImpl()->AppendMessage(
      MakeUnique<StartInputProcessing>(track2, listener2));
  track2->ConnectDeviceInput(device2, listener2, PRINCIPAL_HANDLE_NONE);
  EXPECT_EQ(track2->DeviceId().value(), device2);

  RefPtr<SmartMockCubebStream> stream2 = WaitFor(cubeb->StreamInitEvent());
  EXPECT_TRUE(stream2->mHasInput);
  EXPECT_FALSE(stream2->mHasOutput);
  EXPECT_EQ(stream2->InputChannels(), 1U);
  EXPECT_EQ(stream2->GetInputDeviceID(), device2);

  // Request a new input channel count. This should re-create new input stream
  // accordingly.
  auto setNewChannelCount = [&](const RefPtr<AudioProcessingTrack> aTrack,
                                const RefPtr<AudioInputProcessing>& aListener,
                                RefPtr<SmartMockCubebStream>& aStream,
                                int32_t aChannelCount) {
    bool destroyed = false;
    MediaEventListener destroyListener = cubeb->StreamDestroyEvent().Connect(
        AbstractThread::GetCurrent(),
        [&](const RefPtr<SmartMockCubebStream>& aDestroyed) {
          destroyed = aDestroyed.get() == aStream.get();
        });

    RefPtr<SmartMockCubebStream> newStream;
    MediaEventListener restartListener = cubeb->StreamInitEvent().Connect(
        AbstractThread::GetCurrent(),
        [&](const RefPtr<SmartMockCubebStream>& aCreated) {
          newStream = aCreated;
        });

    MediaEnginePrefs settings;
    settings.mChannels = aChannelCount;
    QueueApplySettings(aTrack, aListener, settings);

    SpinEventLoopUntil<ProcessFailureBehavior::IgnoreAndContinue>(
        "TEST(TestAudioTrackGraph, SetRequestedInputChannelCount)"_ns,
        [&] { return destroyed && newStream; });

    destroyListener.Disconnect();
    restartListener.Disconnect();

    aStream = newStream;
  };

  // Set the native input stream's input channel count to 1.
  setNewChannelCount(track1, listener1, stream1, 1);
  EXPECT_TRUE(stream1->mHasInput);
  EXPECT_TRUE(stream1->mHasOutput);
  EXPECT_EQ(stream1->InputChannels(), 1U);
  EXPECT_EQ(stream1->GetInputDeviceID(), device1);

  // Set the non-native input stream's input channel count to 2.
  setNewChannelCount(track2, listener2, stream2, 2);
  EXPECT_TRUE(stream2->mHasInput);
  EXPECT_FALSE(stream2->mHasOutput);
  EXPECT_EQ(stream2->InputChannels(), 2U);
  EXPECT_EQ(stream2->GetInputDeviceID(), device2);

  // Close the non-native input stream.
  DispatchFunction([&] {
    track2->GraphImpl()->AppendMessage(
        MakeUnique<StopInputProcessing>(track2, listener2));
    track2->DisconnectDeviceInput();
    track2->Destroy();
  });
  RefPtr<SmartMockCubebStream> destroyed = WaitFor(cubeb->StreamDestroyEvent());
  EXPECT_EQ(destroyed.get(), stream2.get());

  // Close the native input stream.
  DispatchFunction([&] {
    track1->GraphImpl()->AppendMessage(
        MakeUnique<StopInputProcessing>(track1, listener1));
    track1->DisconnectDeviceInput();
    track1->Destroy();
  });
  destroyed = WaitFor(cubeb->StreamDestroyEvent());
  EXPECT_EQ(destroyed.get(), stream1.get());
}

// The native audio stream (a.k.a. GraphDriver) and the non-native audio stream
// should always be the same as the max requested input channel of its paired
// AudioProcessingTracks. This test checks if the audio stream paired with the
// AudioProcessingTrack will follow the max requested input channel or not.
//
// This test is pretty similar to RestartAudioIfMaxChannelCountChanged above,
// which makes sure the related DeviceInputTrack operations for the test here
// works correctly. Instead of using a test-only AudioDataListener, we use
// AudioInputProcessing here to simulate the real world use case.
TEST(TestAudioTrackGraph, RestartAudioIfProcessingMaxChannelCountChanged)
{
  MockCubeb* cubeb = new MockCubeb();
  CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());
  auto unforcer = WaitFor(cubeb->ForceAudioThread()).unwrap();
  Unused << unforcer;

  MediaTrackGraph* graph = MediaTrackGraphImpl::GetInstance(
      MediaTrackGraph::SYSTEM_THREAD_DRIVER, /*Window ID*/ 1,
      CubebUtils::PreferredSampleRate(/* aShouldResistFingerprinting */ false),
      nullptr, GetMainThreadSerialEventTarget());

  // Request a new input channel count and expect to have a new stream.
  auto setNewChannelCount = [&](const RefPtr<AudioProcessingTrack>&&nbsp;aTrack,
                                const RefPtr<AudioInputProcessing>& aListener,
                                RefPtr<SmartMockCubebStream>& aStream,
                                int32_t aChannelCount) {
    ASSERT_TRUE(!!aTrack);
    ASSERT_TRUE(!!aListener);
    ASSERT_TRUE(!!aStream);
    ASSERT_TRUE(aStream->mHasInput);
    ASSERT_NE(aChannelCount, 0);

    bool destroyed = false;
    MediaEventListener destroyListener = cubeb->StreamDestroyEvent().Connect(
        AbstractThread::GetCurrent(),
        [&](const RefPtr<SmartMockCubebStream>& aDestroyed) {
          destroyed = aDestroyed.get() == aStream.get();
        });

    RefPtr<SmartMockCubebStream> newStream;
    MediaEventListener restartListener = cubeb->StreamInitEvent().Connect(
        AbstractThread::GetCurrent(),
        [&](const RefPtr<SmartMockCubebStream>& aCreated) {
          newStream = aCreated;
        });

    MediaEnginePrefs settings;
    settings.mChannels = aChannelCount;
    QueueApplySettings(aTrack, aListener, settings);

    SpinEventLoopUntil<ProcessFailureBehavior::IgnoreAndContinue>(
        "TEST(TestAudioTrackGraph, RestartAudioIfProcessingMaxChannelCountChanged) #1"_ns,
        [&] { return destroyed && newStream; });

    destroyListener.Disconnect();
    restartListener.Disconnect();

    aStream = newStream;
  };

  // Open a new track and expect to have a new stream.
  auto openTrack = [&](RefPtr<SmartMockCubebStream>& aCurrentStream,
                       RefPtr<AudioProcessingTrack>& aTrack,
                       RefPtr<AudioInputProcessing>& aListener,
                       CubebUtils::AudioDeviceID aDevice,
                       uint32_t aChannelCount) {
    ASSERT_TRUE(!!aCurrentStream);
    ASSERT_TRUE(aCurrentStream->mHasInput);
    ASSERT_TRUE(aChannelCount > aCurrentStream->InputChannels());
    ASSERT_TRUE(!aTrack);
    ASSERT_TRUE(!aListener);

    bool destroyed = false;
    MediaEventListener destroyListener = cubeb->StreamDestroyEvent().Connect(
        AbstractThread::GetCurrent(),
        [&](const RefPtr<SmartMockCubebStream>& aDestroyed) {
          destroyed = aDestroyed.get() == aCurrentStream.get();
        });

    RefPtr<SmartMockCubebStream> newStream;
    MediaEventListener restartListener = cubeb->StreamInitEvent().Connect(
        AbstractThread::GetCurrent(),
        [&](const RefPtr<SmartMockCubebStream>& aCreated) {
          newStream = aCreated;
        });

    aTrack = AudioProcessingTrack::Create(graph);
    aListener = new AudioInputProcessing(aChannelCount);
    aTrack->SetInputProcessing(aListener);
    QueueExpectIsPassThrough(aTrack, aListener);
    aTrack->GraphImpl()->AppendMessage(
        MakeUnique<StartInputProcessing>(aTrack, aListener));

    DispatchFunction([&] {
      aTrack->ConnectDeviceInput(aDevice, aListener, PRINCIPAL_HANDLE_NONE);
    });

    SpinEventLoopUntil<ProcessFailureBehavior::IgnoreAndContinue>(
        "TEST(TestAudioTrackGraph, RestartAudioIfProcessingMaxChannelCountChanged) #2"_ns,
        [&] { return destroyed && newStream; });

    destroyListener.Disconnect();
    restartListener.Disconnect();

    aCurrentStream = newStream;
  };

  // Test for the native input device first then non-native device. The
  // non-native device will be destroyed before the native device in case of
  // causing a native-device-switching.

  // Test for the native device.
  const CubebUtils::AudioDeviceID nativeDevice = (CubebUtils::AudioDeviceID)1;
  RefPtr<AudioProcessingTrack> track1;
  RefPtr<AudioInputProcessing> listener1;
  RefPtr<SmartMockCubebStream> nativeStream;
  RefPtr<AudioProcessingTrack> track2;
  RefPtr<AudioInputProcessing> listener2;
  {
    // Open a 1-channel AudioProcessingTrack for the native device.
    track1 = AudioProcessingTrack::Create(graph);
    listener1 = new AudioInputProcessing(1);
    track1->SetInputProcessing(listener1);
    QueueExpectIsPassThrough(track1, listener1);
    track1->GraphImpl()->AppendMessage(
        MakeUnique<StartInputProcessing>(track1, listener1));
    track1->ConnectDeviceInput(nativeDevice, listener1, PRINCIPAL_HANDLE_NONE);
    EXPECT_EQ(track1->DeviceId().value(), nativeDevice);

    auto started =
        InvokeAsync([&] { return graph->NotifyWhenDeviceStarted(nullptr); });

    nativeStream = WaitFor(cubeb->StreamInitEvent());
    EXPECT_TRUE(nativeStream->mHasInput);
    EXPECT_TRUE(nativeStream->mHasOutput);
    EXPECT_EQ(nativeStream->InputChannels(), 1U);
    EXPECT_EQ(nativeStream->GetInputDeviceID(), nativeDevice);
    Unused << WaitFor(started);

    // Open a 2-channel AudioProcessingTrack for the native device and wait for
    // a new driver since the max-channel for the native device becomes 2 now.
    openTrack(nativeStream, track2, listener2, nativeDevice, 2);
    EXPECT_EQ(nativeStream->InputChannels(), 2U);

    // Set the second AudioProcessingTrack for the native device to 1-channel
    // and wait for a new driver since the max-channel for the native device
    // becomes 1 now.
    setNewChannelCount(track2, listener2, nativeStream, 1);
    EXPECT_EQ(nativeStream->InputChannels(), 1U);

    // Set the first AudioProcessingTrack for the native device to 2-channel and
    // wait for a new driver since the max input channel for the native device
    // becomes 2 now.
    setNewChannelCount(track1, listener1, nativeStream, 2);
    EXPECT_EQ(nativeStream->InputChannels(), 2U);
  }

  // Test for the non-native device.
  {
    const CubebUtils::AudioDeviceID nonNativeDevice =
        (CubebUtils::AudioDeviceID)2;

    // Open a 1-channel AudioProcessingTrack for the non-native device.
    RefPtr<AudioProcessingTrack> track3 = AudioProcessingTrack::Create(graph);
    RefPtr<AudioInputProcessing> listener3 = new AudioInputProcessing(1);
    track3->SetInputProcessing(listener3);
    QueueExpectIsPassThrough(track3, listener3);
    track3->GraphImpl()->AppendMessage(
        MakeUnique<StartInputProcessing>(track3, listener3));
    track3->ConnectDeviceInput(nonNativeDevice, listener3,
                               PRINCIPAL_HANDLE_NONE);
    EXPECT_EQ(track3->DeviceId().value(), nonNativeDevice);

    RefPtr<SmartMockCubebStream> nonNativeStream =
        WaitFor(cubeb->StreamInitEvent());
    EXPECT_TRUE(nonNativeStream->mHasInput);
    EXPECT_FALSE(nonNativeStream->mHasOutput);
    EXPECT_EQ(nonNativeStream->InputChannels(), 1U);
    EXPECT_EQ(nonNativeStream->GetInputDeviceID(), nonNativeDevice);

    // Open a 2-channel AudioProcessingTrack for the non-native device and wait
    // for a new stream since the max-channel for the non-native device becomes
    // 2 now.
    RefPtr<AudioProcessingTrack> track4;
    RefPtr<AudioInputProcessing> listener4;
    openTrack(nonNativeStream, track4, listener4, nonNativeDevice, 2);
    EXPECT_EQ(nonNativeStream->InputChannels(), 2U);
    EXPECT_EQ(nonNativeStream->GetInputDeviceID(), nonNativeDevice);

    // Set the second AudioProcessingTrack for the non-native to 1-channel and
    // wait for a new driver since the max-channel for the non-native device
    // becomes 1 now.
    setNewChannelCount(track4, listener4, nonNativeStream, 1);
    EXPECT_EQ(nonNativeStream->InputChannels(), 1U);
    EXPECT_EQ(nonNativeStream->GetInputDeviceID(), nonNativeDevice);

    // Set the first AudioProcessingTrack for the non-native device to 2-channel
    // and wait for a new driver since the max input channel for the non-native
    // device becomes 2 now.
    setNewChannelCount(track3, listener3, nonNativeStream, 2);
    EXPECT_EQ(nonNativeStream->InputChannels(), 2U);
    EXPECT_EQ(nonNativeStream->GetInputDeviceID(), nonNativeDevice);

    // Close the second AudioProcessingTrack (1-channel) for the non-native
    // device then the first one (2-channel) so we won't result in another
    // stream creation.
    DispatchFunction([&] {
      track4->GraphImpl()->AppendMessage(
          MakeUnique<StopInputProcessing>(track4, listener4));
      track4->DisconnectDeviceInput();
      track4->Destroy();
    });
    DispatchFunction([&] {
      track3->GraphImpl()->AppendMessage(
          MakeUnique<StopInputProcessing>(track3, listener3));
      track3->DisconnectDeviceInput();
      track3->Destroy();
    });
    RefPtr<SmartMockCubebStream> destroyedStream =
        WaitFor(cubeb->StreamDestroyEvent());
    EXPECT_EQ(destroyedStream.get(), nonNativeStream.get());
  }

  // Tear down for the native device.
  {
    // Close the second AudioProcessingTrack (1-channel) for the native device
    // then the first one (2-channel) so we won't have driver switching.
    DispatchFunction([&] {
      track2->GraphImpl()->AppendMessage(
          MakeUnique<StopInputProcessing>(track2, listener2));
      track2->DisconnectDeviceInput();
      track2->Destroy();
    });
    DispatchFunction([&] {
      track1->GraphImpl()->AppendMessage(
          MakeUnique<StopInputProcessing>(track1, listener1));
      track1->DisconnectDeviceInput();
      track1->Destroy();
    });
    RefPtr<SmartMockCubebStream> destroyedStream =
        WaitFor(cubeb->StreamDestroyEvent());
    EXPECT_EQ(destroyedStream.get(), nativeStream.get());
  }
}

TEST(TestAudioTrackGraph, SetInputChannelCountBeforeAudioCallbackDriver)
{
  MockCubeb* cubeb = new MockCubeb();
  CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());

  MediaTrackGraph* graph = MediaTrackGraphImpl::GetInstance(
      MediaTrackGraph::SYSTEM_THREAD_DRIVER, /*Window ID*/ 1,
      CubebUtils::PreferredSampleRate(/* aShouldResistFingerprinting */ false),
      nullptr, GetMainThreadSerialEventTarget());

  // Set the input channel count of AudioInputProcessing, which will force
  // MediaTrackGraph to re-evaluate input device, when the MediaTrackGraph is
  // driven by the SystemClockDriver.

  const CubebUtils::AudioDeviceID deviceId = (CubebUtils::AudioDeviceID)1;
  RefPtr<AudioProcessingTrack> track;
  RefPtr<AudioInputProcessing> listener;
  DispatchFunction([&] {
    track = AudioProcessingTrack::Create(graph);
    listener = new AudioInputProcessing(2);
    QueueExpectIsPassThrough(track, listener);
    track->SetInputProcessing(listener);

    MediaEnginePrefs settings;
    settings.mChannels = 1;
    QueueApplySettings(track, listener, settings);
  });

  // Wait for AudioCallbackDriver to init output-only stream.
  RefPtr<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent());
  EXPECT_FALSE(stream->mHasInput);
  EXPECT_TRUE(stream->mHasOutput);

  // Open a full-duplex AudioCallbackDriver.
  RefPtr<MediaInputPort> port;
  DispatchFunction([&] {
    track->GraphImpl()->AppendMessage(
        MakeUnique<StartInputProcessing>(track, listener));
    track->ConnectDeviceInput(deviceId, listener, PRINCIPAL_HANDLE_NONE);
  });

  stream = WaitFor(cubeb->StreamInitEvent());
  EXPECT_TRUE(stream->mHasInput);
  EXPECT_TRUE(stream->mHasOutput);
  EXPECT_EQ(stream->InputChannels(), 1U);

  Unused << WaitFor(
      InvokeAsync([&] { return graph->NotifyWhenDeviceStarted(nullptr); }));

  // Clean up.
  DispatchFunction([&] {
    track->GraphImpl()->AppendMessage(
        MakeUnique<StopInputProcessing>(track, listener));
    track->DisconnectDeviceInput();
    track->Destroy();
  });
  Unused << WaitFor(cubeb->StreamDestroyEvent());
}

TEST(TestAudioTrackGraph, StartAudioDeviceBeforeStartingAudioProcessing)
{
  MockCubeb* cubeb = new MockCubeb();
  CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());

  MediaTrackGraph* graph = MediaTrackGraphImpl::GetInstance(
      MediaTrackGraph::SYSTEM_THREAD_DRIVER, /*Window ID*/ 1,
      CubebUtils::PreferredSampleRate(/* aShouldResistFingerprinting */ false),
      nullptr, GetMainThreadSerialEventTarget());

  // Create a duplex AudioCallbackDriver
  const CubebUtils::AudioDeviceID deviceId = (CubebUtils::AudioDeviceID)1;
  RefPtr<AudioProcessingTrack> track;
  RefPtr<AudioInputProcessing> listener;
  DispatchFunction([&] {
    track = AudioProcessingTrack::Create(graph);
    listener = new AudioInputProcessing(2);
    QueueExpectIsPassThrough(track, listener);
    track->SetInputProcessing(listener);
    // Start audio device without starting audio processing.
    track->ConnectDeviceInput(deviceId, listener, PRINCIPAL_HANDLE_NONE);
  });

  RefPtr<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent());
  EXPECT_TRUE(stream->mHasInput);
  EXPECT_TRUE(stream->mHasOutput);

  // Wait for a second to make sure audio output callback has been fired.
  DispatchFunction(
      [&] { track->GraphImpl()->AppendMessage(MakeUnique<GoFaster>(cubeb)); });
  {
    uint32_t totalFrames = 0;
    WaitUntil(stream->FramesProcessedEvent(), [&](uint32_t aFrames) {
      totalFrames += aFrames;
      return totalFrames > static_cast<uint32_t>(graph->GraphRate());
    });
  }
  cubeb->DontGoFaster();

  // Start the audio processing.
  DispatchFunction([&] {
    track->GraphImpl()->AppendMessage(
        MakeUnique<StartInputProcessing>(track, listener));
  });

  // Wait for a second to make sure audio output callback has been fired.
  DispatchFunction(
      [&] { track->GraphImpl()->AppendMessage(MakeUnique<GoFaster>(cubeb)); });
  {
    uint32_t totalFrames = 0;
    WaitUntil(stream->FramesProcessedEvent(), [&](uint32_t aFrames) {
      totalFrames += aFrames;
      return totalFrames > static_cast<uint32_t>(graph->GraphRate());
    });
  }
  cubeb->DontGoFaster();

  // Clean up.
  DispatchFunction([&] {
    track->DisconnectDeviceInput();
    track->Destroy();
  });
  Unused << WaitFor(cubeb->StreamDestroyEvent());
}

TEST(TestAudioTrackGraph, StopAudioProcessingBeforeStoppingAudioDevice)
{
  MockCubeb* cubeb = new MockCubeb();
  CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());

  MediaTrackGraph* graph = MediaTrackGraphImpl::GetInstance(
      MediaTrackGraph::SYSTEM_THREAD_DRIVER, /*Window ID*/ 1,
      CubebUtils::PreferredSampleRate(/* aShouldResistFingerprinting */ false),
      nullptr, GetMainThreadSerialEventTarget());

  // Create a duplex AudioCallbackDriver
  const CubebUtils::AudioDeviceID deviceId = (CubebUtils::AudioDeviceID)1;
  RefPtr<AudioProcessingTrack> track;
  RefPtr<AudioInputProcessing> listener;
  DispatchFunction([&] {
    track = AudioProcessingTrack::Create(graph);
    listener = new AudioInputProcessing(2);
    QueueExpectIsPassThrough(track, listener);
    track->SetInputProcessing(listener);
    track->GraphImpl()->AppendMessage(
        MakeUnique<StartInputProcessing>(track, listener));
    track->ConnectDeviceInput(deviceId, listener, PRINCIPAL_HANDLE_NONE);
  });

  RefPtr<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent());
  EXPECT_TRUE(stream->mHasInput);
  EXPECT_TRUE(stream->mHasOutput);

  // Wait for a second to make sure audio output callback has been fired.
  DispatchFunction(
      [&] { track->GraphImpl()->AppendMessage(MakeUnique<GoFaster>(cubeb)); });
  {
    uint32_t totalFrames = 0;
    WaitUntil(stream->FramesProcessedEvent(), [&](uint32_t aFrames) {
      totalFrames += aFrames;
      return totalFrames > static_cast<uint32_t>(graph->GraphRate());
    });
  }
  cubeb->DontGoFaster();

  // Stop the audio processing
  DispatchFunction([&] {
    track->GraphImpl()->AppendMessage(
        MakeUnique<StopInputProcessing>(track, listener));
  });

  // Wait for a second to make sure audio output callback has been fired.
  DispatchFunction(
      [&] { track->GraphImpl()->AppendMessage(MakeUnique<GoFaster>(cubeb)); });
  {
    uint32_t totalFrames = 0;
    WaitUntil(stream->FramesProcessedEvent(), [&](uint32_t aFrames) {
      totalFrames += aFrames;
      return totalFrames > static_cast<uint32_t>(graph->GraphRate());
    });
  }
  cubeb->DontGoFaster();

  // Clean up.
  DispatchFunction([&] {
    track->DisconnectDeviceInput();
    track->Destroy();
  });
  Unused << WaitFor(cubeb->StreamDestroyEvent());
}

// This test is pretty similar to SwitchNativeInputDevice above, which makes
// sure the related DeviceInputTrack operations for the test here works
// correctly. Instead of using a test-only DeviceInputTrack consumer, we use
// AudioProcessingTrack here to simulate the real world use case.
TEST(TestAudioTrackGraph, SwitchNativeAudioProcessingTrack)
{
  MockCubeb* cubeb = new MockCubeb();
  CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());

  MediaTrackGraph* graph = MediaTrackGraphImpl::GetInstance(
      MediaTrackGraph::SYSTEM_THREAD_DRIVER, /*Window ID*/ 1,
      CubebUtils::PreferredSampleRate(/* aShouldResistFingerprinting */ false),
      nullptr, GetMainThreadSerialEventTarget());

  auto switchNativeDevice =
      [&](RefPtr<SmartMockCubebStream>&& aCurrentNativeStream,
          RefPtr<AudioProcessingTrack>& aCurrentNativeTrack,
          RefPtr<AudioInputProcessing>& aCurrentNativeListener,
          RefPtr<SmartMockCubebStream>& aNextNativeStream,
          RefPtr<AudioProcessingTrack>& aNextNativeTrack) {
        ASSERT_TRUE(aCurrentNativeStream->mHasInput);
        ASSERT_TRUE(aCurrentNativeStream->mHasOutput);
        ASSERT_TRUE(aNextNativeStream->mHasInput);
        ASSERT_FALSE(aNextNativeStream->mHasOutput);

        std::cerr << "Switching native input from device "
                  << aCurrentNativeStream->GetInputDeviceID() << " to "
                  << aNextNativeStream->GetInputDeviceID() << std::endl;

        uint32_t destroyed = 0;
        MediaEventListener destroyListener =
            cubeb->StreamDestroyEvent().Connect(
                AbstractThread::GetCurrent(),
                [&](const RefPtr<SmartMockCubebStream>& aDestroyed) {
                  if (aDestroyed.get() == aCurrentNativeStream.get() ||
                      aDestroyed.get() == aNextNativeStream.get()) {
                    std::cerr << "cubeb stream " << aDestroyed.get()
                              << " (device " << aDestroyed->GetInputDeviceID()
                              << ") has been destroyed" << std::endl;
                    destroyed += 1;
                  }
                });

        RefPtr<SmartMockCubebStream> newStream;
        MediaEventListener restartListener = cubeb->StreamInitEvent().Connect(
            AbstractThread::GetCurrent(),
            [&](const RefPtr<SmartMockCubebStream>& aCreated) {
              // Make sure new stream has input, to prevent from getting a
              // temporary output-only AudioCallbackDriver after closing current
              // native device but before setting a new native input.
              if (aCreated->mHasInput) {
                ASSERT_TRUE(aCreated->mHasOutput);
                newStream = aCreated;
              }
            });

        std::cerr << "Close device " << aCurrentNativeStream->GetInputDeviceID()
                  << std::endl;
        DispatchFunction([&] {
          aCurrentNativeTrack->GraphImpl()->AppendMessage(
              MakeUnique<StopInputProcessing>(aCurrentNativeTrack,
                                              aCurrentNativeListener));
          aCurrentNativeTrack->DisconnectDeviceInput();
          aCurrentNativeTrack->Destroy();
        });

        std::cerr << "Wait for the switching" << std::endl;
        SpinEventLoopUntil<ProcessFailureBehavior::IgnoreAndContinue>(
            "TEST(TestAudioTrackGraph, SwitchNativeAudioProcessingTrack)"_ns,
            [&] { return destroyed >= 2 && newStream; });

        destroyListener.Disconnect();
        restartListener.Disconnect();

        aCurrentNativeStream = nullptr;
        aNextNativeStream = newStream;

        std::cerr << "Now the native input is device "
                  << aNextNativeStream->GetInputDeviceID() << std::endl;
      };

  // Open a AudioProcessingTrack for device 1.
  const CubebUtils::AudioDeviceID device1 = (CubebUtils::AudioDeviceID)1;
  RefPtr<AudioProcessingTrack> track1 = AudioProcessingTrack::Create(graph);
  RefPtr<AudioInputProcessing> listener1 = new AudioInputProcessing(1);
  track1->SetInputProcessing(listener1);
  QueueExpectIsPassThrough(track1, listener1);
  track1->GraphImpl()->AppendMessage(
      MakeUnique<StartInputProcessing>(track1, listener1));
  track1->ConnectDeviceInput(device1, listener1, PRINCIPAL_HANDLE_NONE);
  EXPECT_EQ(track1->DeviceId().value(), device1);

  auto started =
      InvokeAsync([&] { return graph->NotifyWhenDeviceStarted(nullptr); });

  RefPtr<SmartMockCubebStream> stream1 = WaitFor(cubeb->StreamInitEvent());
  EXPECT_TRUE(stream1->mHasInput);
  EXPECT_TRUE(stream1->mHasOutput);
  EXPECT_EQ(stream1->InputChannels(), 1U);
  EXPECT_EQ(stream1->GetInputDeviceID(), device1);
  Unused << WaitFor(started);
  std::cerr << "Device " << device1 << " is opened (stream " << stream1.get()
            << ")" << std::endl;

  // Open a AudioProcessingTrack for device 2.
  const CubebUtils::AudioDeviceID device2 = (CubebUtils::AudioDeviceID)2;
  RefPtr<AudioProcessingTrack> track2 = AudioProcessingTrack::Create(graph);
  RefPtr<AudioInputProcessing> listener2 = new AudioInputProcessing(2);
  track2->SetInputProcessing(listener2);
  QueueExpectIsPassThrough(track2, listener2);
  track2->GraphImpl()->AppendMessage(
      MakeUnique<StartInputProcessing>(track2, listener2));
  track2->ConnectDeviceInput(device2, listener2, PRINCIPAL_HANDLE_NONE);
  EXPECT_EQ(track2->DeviceId().value(), device2);

  RefPtr<SmartMockCubebStream> stream2 = WaitFor(cubeb->StreamInitEvent());
  EXPECT_TRUE(stream2->mHasInput);
  EXPECT_FALSE(stream2->mHasOutput);
  EXPECT_EQ(stream2->InputChannels(), 2U);
  EXPECT_EQ(stream2->GetInputDeviceID(), device2);
  std::cerr << "Device " << device2 << " is opened (stream " << stream2.get()
            << ")" << std::endl;

  // Open a AudioProcessingTrack for device 3.
  const CubebUtils::AudioDeviceID device3 = (CubebUtils::AudioDeviceID)3;
  RefPtr<AudioProcessingTrack> track3 = AudioProcessingTrack::Create(graph);
  RefPtr<AudioInputProcessing> listener3 = new AudioInputProcessing(1);
  track3->SetInputProcessing(listener3);
  QueueExpectIsPassThrough(track3, listener3);
  track3->GraphImpl()->AppendMessage(
      MakeUnique<StartInputProcessing>(track3, listener3));
  track3->ConnectDeviceInput(device3, listener3, PRINCIPAL_HANDLE_NONE);
  EXPECT_EQ(track3->DeviceId().value(), device3);

  RefPtr<SmartMockCubebStream> stream3 = WaitFor(cubeb->StreamInitEvent());
  EXPECT_TRUE(stream3->mHasInput);
  EXPECT_FALSE(stream3->mHasOutput);
  EXPECT_EQ(stream3->InputChannels(), 1U);
  EXPECT_EQ(stream3->GetInputDeviceID(), device3);
  std::cerr << "Device " << device3 << " is opened (stream " << stream3.get()
            << ")" << std::endl;

  // Close device 1, so the native input device is switched from device 1 to
  // device 2.
  switchNativeDevice(std::move(stream1), track1, listener1, stream2, track2);
  EXPECT_TRUE(stream2->mHasInput);
  EXPECT_TRUE(stream2->mHasOutput);
  EXPECT_EQ(stream2->InputChannels(), 2U);
  EXPECT_EQ(stream2->GetInputDeviceID(), device2);
  {
    NativeInputTrack* native = track2->Graph()->GetNativeInputTrackMainThread();
    ASSERT_TRUE(!!native);
    EXPECT_EQ(native->mDeviceId, device2);
  }

  // Close device 2, so the native input device is switched from device 2 to
  // device 3.
  switchNativeDevice(std::move(stream2), track2, listener2, stream3, track3);
  EXPECT_TRUE(stream3->mHasInput);
  EXPECT_TRUE(stream3->mHasOutput);
  EXPECT_EQ(stream3->InputChannels(), 1U);
  EXPECT_EQ(stream3->GetInputDeviceID(), device3);
  {
    NativeInputTrack* native = track3->Graph()->GetNativeInputTrackMainThread();
    ASSERT_TRUE(!!native);
    EXPECT_EQ(native->mDeviceId, device3);
  }

  // Clean up.
  std::cerr << "Close device " << device3 << std::endl;
  DispatchFunction([&] {
    track3->GraphImpl()->AppendMessage(
        MakeUnique<StopInputProcessing>(track3, listener3));
    track3->DisconnectDeviceInput();
    track3->Destroy();
  });
  RefPtr<SmartMockCubebStream> destroyedStream =
      WaitFor(cubeb->StreamDestroyEvent());
  EXPECT_EQ(destroyedStream.get(), stream3.get());
  {
    NativeInputTrack* native = graph->GetNativeInputTrackMainThread();
    ASSERT_TRUE(!native);
  }
  std::cerr << "No native input now" << std::endl;
}

void TestCrossGraphPort(uint32_t aInputRate, uint32_t aOutputRate,
                        float aDriftFactor, uint32_t aRunTimeSeconds = 10,
                        uint32_t aNumExpectedUnderruns = 0) {
  std::cerr << "TestCrossGraphPort input: " << aInputRate
            << ", output: " << aOutputRate << ", driftFactor: " << aDriftFactor
            << std::endl;

  MockCubeb* cubeb = new MockCubeb(MockCubeb::RunningMode::Manual);
  CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());

  /* Primary graph: Create the graph. */
  MediaTrackGraph* primary = MediaTrackGraphImpl::GetInstance(
      MediaTrackGraph::SYSTEM_THREAD_DRIVER,
      /*Window ID*/ 1, aInputRate, nullptr, GetMainThreadSerialEventTarget());

  /* Partner graph: Create the graph. */
  MediaTrackGraph* partner = MediaTrackGraphImpl::GetInstance(
      MediaTrackGraph::SYSTEM_THREAD_DRIVER, /*Window ID*/ 1, aOutputRate,
      /*OutputDeviceID*/ reinterpret_cast<cubeb_devid>(1),
      GetMainThreadSerialEventTarget());

  const CubebUtils::AudioDeviceID inputDeviceId = (CubebUtils::AudioDeviceID)1;

  RefPtr<AudioProcessingTrack> processingTrack;
  RefPtr<AudioInputProcessing> listener;
  RefPtr<OnFallbackListener> primaryFallbackListener;
  DispatchFunction([&] {
    /* Primary graph: Create input track and open it */
    processingTrack = AudioProcessingTrack::Create(primary);
    listener = new AudioInputProcessing(2);
    QueueExpectIsPassThrough(processingTrack, listener);
    processingTrack->SetInputProcessing(listener);
    processingTrack->GraphImpl()->AppendMessage(
        MakeUnique<StartInputProcessing>(processingTrack, listener));
    processingTrack->ConnectDeviceInput(inputDeviceId, listener,
                                        PRINCIPAL_HANDLE_NONE);
    primaryFallbackListener = new OnFallbackListener(processingTrack);
    processingTrack->AddListener(primaryFallbackListener);
  });

  RefPtr<SmartMockCubebStream> inputStream = WaitFor(cubeb->StreamInitEvent());

  while (
      inputStream->State()
          .map([](cubeb_state aState) { return aState != CUBEB_STATE_STARTED; })
          .valueOr(true)) {
    std::this_thread::sleep_for(std::chrono::milliseconds(1));
  }

  // Wait for the primary AudioCallbackDriver to come into effect.
  while (primaryFallbackListener->OnFallback()) {
    EXPECT_EQ(inputStream->ManualDataCallback(0),
              MockCubebStream::KeepProcessing::Yes);
    std::this_thread::sleep_for(std::chrono::milliseconds(1));
  }

  RefPtr<CrossGraphTransmitter> transmitter;
  RefPtr<MediaInputPort> port;
  RefPtr<CrossGraphReceiver> receiver;
  RefPtr<OnFallbackListener> partnerFallbackListener;
  DispatchFunction([&] {
    processingTrack->RemoveListener(primaryFallbackListener);

    /* Partner graph: Create CrossGraphReceiver */
    receiver = partner->CreateCrossGraphReceiver(primary->GraphRate());

    /* Primary graph: Create CrossGraphTransmitter */
    transmitter = primary->CreateCrossGraphTransmitter(receiver);

    /* How the input track connects to another ProcessedMediaTrack.
     * Check in MediaManager how it is connected to AudioStreamTrack. */

    port = transmitter->AllocateInputPort(processingTrack);
    receiver->AddAudioOutput((void*)1, partner->PrimaryOutputDeviceID(), 0);

    partnerFallbackListener = new OnFallbackListener(receiver);
    receiver->AddListener(partnerFallbackListener);
  });

  RefPtr<SmartMockCubebStream> partnerStream =
      WaitFor(cubeb->StreamInitEvent());

  while (
      partnerStream->State()
          .map([](cubeb_state aState) { return aState != CUBEB_STATE_STARTED; })
          .valueOr(true)) {
    std::this_thread::sleep_for(std::chrono::milliseconds(1));
  }

  // Process the CrossGraphTransmitter on the primary graph.
  EXPECT_EQ(inputStream->ManualDataCallback(0),
            MockCubebStream::KeepProcessing::Yes);

  // Wait for the partner AudioCallbackDriver to come into effect.
  while (partnerFallbackListener->OnFallback()) {
    EXPECT_EQ(partnerStream->ManualDataCallback(0),
              MockCubebStream::KeepProcessing::Yes);
    std::this_thread::sleep_for(std::chrono::milliseconds(1));
  }

  DispatchFunction([&] { receiver->RemoveListener(partnerFallbackListener); });
  ProcessEventQueue();

  nsIThread* currentThread = NS_GetCurrentThread();
  cubeb_state inputState = CUBEB_STATE_STARTED;
  MediaEventListener inputStateListener = inputStream->StateEvent().Connect(
      currentThread, [&](cubeb_state aState) { inputState = aState; });
  cubeb_state partnerState = CUBEB_STATE_STARTED;
  MediaEventListener partnerStateListener = partnerStream->StateEvent().Connect(
      currentThread, [&](cubeb_state aState) { partnerState = aState; });

  const media::TimeUnit runtime = media::TimeUnit::FromSeconds(aRunTimeSeconds);
  // 10ms per iteration.
  const media::TimeUnit step = media::TimeUnit::FromSeconds(0.01);
  {
    media::TimeUnit pos = media::TimeUnit::Zero();
    long inputFrames = 0;
    long outputFrames = 0;
    while (pos < runtime) {
      pos += step;
      const long newInputFrames = pos.ToTicksAtRate(aInputRate);
      const long newOutputFrames =
          (pos.MultDouble(aDriftFactor)).ToTicksAtRate(aOutputRate);
      EXPECT_EQ(inputStream->ManualDataCallback(newInputFrames - inputFrames),
                MockCubebStream::KeepProcessing::Yes);
      EXPECT_EQ(
          partnerStream->ManualDataCallback(newOutputFrames - outputFrames),
          MockCubebStream::KeepProcessing::Yes);

      inputFrames = newInputFrames;
      outputFrames = newOutputFrames;
    }
  }

  DispatchFunction([&] {
    // Clean up on MainThread
    receiver->RemoveAudioOutput((void*)1);
    receiver->Destroy();
    transmitter->Destroy();
    port->Destroy();
    processingTrack->GraphImpl()->AppendMessage(
        MakeUnique<StopInputProcessing>(processingTrack, listener));
    processingTrack->DisconnectDeviceInput();
    processingTrack->Destroy();
  });

  ProcessEventQueue();

  EXPECT_EQ(inputStream->ManualDataCallback(0),
            MockCubebStream::KeepProcessing::Yes);
  EXPECT_EQ(partnerStream->ManualDataCallback(0),
            MockCubebStream::KeepProcessing::Yes);

  EXPECT_EQ(inputStream->ManualDataCallback(128),
            MockCubebStream::KeepProcessing::No);
  EXPECT_EQ(partnerStream->ManualDataCallback(128),
            MockCubebStream::KeepProcessing::No);

  uint32_t inputFrequency = inputStream->InputFrequency();

  uint64_t preSilenceSamples;
  float estimatedFreq;
  uint32_t nrDiscontinuities;
  std::tie(preSilenceSamples, estimatedFreq, nrDiscontinuities) =
      WaitFor(partnerStream->OutputVerificationEvent());

  EXPECT_NEAR(estimatedFreq, inputFrequency / aDriftFactor, 5);
  // Note that pre-silence is in the output rate. The buffering is on the input
  // side. There is one block buffered in NativeInputTrack. Then
  // AudioDriftCorrection sets its pre-buffering so that *after* the first
  // resample of real input data, the buffer contains enough data to match the
  // desired level, which is initially 50ms. I.e. silence = buffering -
  // inputStep + outputStep. Note that the steps here are rounded up to block
  // size.
  const media::TimeUnit inputBuffering(WEBAUDIO_BLOCK_SIZE, aInputRate);
  const media::TimeUnit buffering =
      media::TimeUnit::FromSeconds(0.05).ToBase(aInputRate);
  const media::TimeUnit inputStepSize(
      MediaTrackGraphImpl::RoundUpToEndOfAudioBlock(
          step.ToTicksAtRate(aInputRate)),
      aInputRate);
  const media::TimeUnit outputStepSize =
      media::TimeUnit(MediaTrackGraphImpl::RoundUpToEndOfAudioBlock(
                          step.ToBase(aOutputRate)
                              .MultDouble(aDriftFactor)
                              .ToTicksAtRate(aOutputRate)),
                      aOutputRate)
          .ToBase(aInputRate);
  const uint32_t expectedPreSilence =
      (outputStepSize + inputBuffering + buffering - inputStepSize)
          .ToBase(aInputRate)
          .ToBase<media::TimeUnit::CeilingPolicy>(aOutputRate)
          .ToTicksAtRate(aOutputRate);
  // Use a margin of 0.1% of the expected pre-silence, since the resampler is
  // adapting to drift and will process the pre-silence frames. Because of
  // rounding errors, we don't use a margin lower than 1.
  const uint32_t margin = std::max(1U, expectedPreSilence / 1000);
  EXPECT_NEAR(preSilenceSamples, expectedPreSilence, margin);
  // The waveform from AudioGenerator starts at 0, but we don't control its
  // ending, so we expect a discontinuity there. For each expected underrun
  // there could be an additional 2 discontinuities (start and end of the silent
  // period).
  EXPECT_LE(nrDiscontinuities, 1U + 2 * aNumExpectedUnderruns);

  SpinEventLoopUntil("streams have stopped"_ns, [&] {
    return inputState == CUBEB_STATE_STOPPED &&
           partnerState == CUBEB_STATE_STOPPED;
  });
  inputStateListener.Disconnect();
  partnerStateListener.Disconnect();
}

TEST(TestAudioTrackGraph, CrossGraphPort)
{
  TestCrossGraphPort(44100, 44100, 1);
  TestCrossGraphPort(44100, 44100, 1.006);
  TestCrossGraphPort(44100, 44100, 0.994);

  TestCrossGraphPort(48000, 44100, 1);
  TestCrossGraphPort(48000, 44100, 1.006);
  TestCrossGraphPort(48000, 44100, 0.994);

  TestCrossGraphPort(44100, 48000, 1);
  TestCrossGraphPort(44100, 48000, 1.006);
  TestCrossGraphPort(44100, 48000, 0.994);

  TestCrossGraphPort(52110, 17781, 1);
  TestCrossGraphPort(52110, 17781, 1.006);
  TestCrossGraphPort(52110, 17781, 0.994);
}

TEST(TestAudioTrackGraph, CrossGraphPortUnderrun)
{
  TestCrossGraphPort(44100, 44100, 1.01, 30, 1);
  TestCrossGraphPort(44100, 44100, 1.03, 40, 3);

  TestCrossGraphPort(48000, 44100, 1.01, 30, 1);
  TestCrossGraphPort(48000, 44100, 1.03, 40, 3);

  TestCrossGraphPort(44100, 48000, 1.01, 30, 1);
  TestCrossGraphPort(44100, 48000, 1.03, 40, 3);

  TestCrossGraphPort(52110, 17781, 1.01, 30, 1);
  TestCrossGraphPort(52110, 17781, 1.03, 40, 3);
}

TEST(TestAudioTrackGraph, SecondaryOutputDevice)
{
  MockCubeb* cubeb = new MockCubeb();
  CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());

  const TrackRate primaryRate = 48000;
  const TrackRate secondaryRate = 44100;  // for secondary output device

  MediaTrackGraph* graph = MediaTrackGraphImpl::GetInstance(
      MediaTrackGraph::SYSTEM_THREAD_DRIVER,
      /*Window ID*/ 1, primaryRate, nullptr, GetMainThreadSerialEventTarget());

  RefPtr<AudioProcessingTrack> processingTrack;
  RefPtr<AudioInputProcessing> listener;
  DispatchFunction([&] {
    /* Create an input track and connect it to a device */
    processingTrack = AudioProcessingTrack::Create(graph);
    listener = new AudioInputProcessing(2);
    QueueExpectIsPassThrough(processingTrack, listener);
    processingTrack->SetInputProcessing(listener);
    processingTrack->GraphImpl()->AppendMessage(
        MakeUnique<StartInputProcessing>(processingTrack, listener));
    processingTrack->ConnectDeviceInput(nullptr, listener,
                                        PRINCIPAL_HANDLE_NONE);
  });
  RefPtr<SmartMockCubebStream> primaryStream =
      WaitFor(cubeb->StreamInitEvent());

  const void* secondaryDeviceID = CubebUtils::AudioDeviceID(2);
  DispatchFunction([&] {
    processingTrack->AddAudioOutput(nullptr, secondaryDeviceID, secondaryRate);
    processingTrack->SetAudioOutputVolume(nullptr, 0.f);
  });
  RefPtr<SmartMockCubebStream> secondaryStream =
      WaitFor(cubeb->StreamInitEvent());
  EXPECT_EQ(secondaryStream->GetOutputDeviceID(), secondaryDeviceID);
  EXPECT_EQ(static_cast<TrackRate>(secondaryStream->SampleRate()),
            secondaryRate);

  nsIThread* currentThread = NS_GetCurrentThread();
  uint32_t audioFrames = 0;  // excludes pre-silence
  MediaEventListener audioListener =
      secondaryStream->FramesVerifiedEvent().Connect(
          currentThread, [&](uint32_t aFrames) { audioFrames += aFrames; });

  // Wait for 100ms of pre-silence to verify that SetAudioOutputVolume() is
  // effective.
  uint32_t processedFrames = 0;
  WaitUntil(secondaryStream->FramesProcessedEvent(), [&](uint32_t aFrames) {
    processedFrames += aFrames;
    return processedFrames > static_cast<uint32_t>(secondaryRate / 10);
  });
  EXPECT_EQ(audioFrames, 0U) << "audio frames at zero volume";

  secondaryStream->SetOutputRecordingEnabled(true);
  DispatchFunction(
      [&] { processingTrack->SetAudioOutputVolume(nullptr, 1.f); });

  // Wait for enough audio after initial silence to check the frequency.
  SpinEventLoopUntil("200ms of audio"_ns, [&] {
    return audioFrames > static_cast<uint32_t>(secondaryRate / 5);
  });
  audioListener.Disconnect();

  // Stop recording now so as not to record the discontinuity when the
  // CrossGraphReceiver is removed from the secondary graph before its
  // AudioCallbackDriver is stopped.
  secondaryStream->SetOutputRecordingEnabled(false);

  DispatchFunction([&] { processingTrack->RemoveAudioOutput(nullptr); });
  WaitFor(secondaryStream->OutputVerificationEvent());
  // The frequency from OutputVerificationEvent() is estimated by
  // AudioVerifier from a zero-crossing count.  When the discontinuity from
  // the volume change is resampled, the discontinuity presents as
  // oscillations, which increase the zero-crossing count and corrupt the
  // frequency estimate.  Trim off sufficient leading from the output to
  // remove this discontinuity.
  uint32_t channelCount = secondaryStream->OutputChannels();
  nsTArray<AudioDataValue> output = secondaryStream->TakeRecordedOutput();
  size_t leadingIndex = 0;
  for (; leadingIndex < output.Length() && output[leadingIndex] == 0.f;
       leadingIndex += channelCount) {
  };
  leadingIndex += 10 * channelCount;  // skip discontinuity oscillations
  EXPECT_LT(leadingIndex, output.Length());
  auto trimmed = Span(output).From(std::min(leadingIndex, output.Length()));
  size_t frameCount = trimmed.Length() / channelCount;
  uint32_t inputFrequency = primaryStream->InputFrequency();
  AudioVerifier<AudioDataValue> verifier(secondaryRate, inputFrequency);
  verifier.AppendDataInterleaved(trimmed.Elements(), frameCount, channelCount);
  EXPECT_EQ(verifier.EstimatedFreq(), inputFrequency);
  // AudioVerifier considers the previous value before the initial sample to
  // be zero and so considers any initial sample >> 0 to be a discontinuity.
  EXPECT_EQ(verifier.CountDiscontinuities(), 1U);

  DispatchFunction([&] {
    // Clean up
    processingTrack->GraphImpl()->AppendMessage(
        MakeUnique<StopInputProcessing>(processingTrack, listener));
    processingTrack->DisconnectDeviceInput();
    processingTrack->Destroy();
  });
  WaitFor(primaryStream->OutputVerificationEvent());
}

// Test when AudioInputProcessing expects clock drift
TEST(TestAudioInputProcessing, ClockDriftExpectation)
{
  MockCubeb* cubeb = new MockCubeb();
  CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());

  const TrackRate rate = 44100;

  MediaTrackGraph* graph = MediaTrackGraphImpl::GetInstance(
      MediaTrackGraph::SYSTEM_THREAD_DRIVER,
      /*Window ID*/ 1, rate, nullptr, GetMainThreadSerialEventTarget());

  auto createInputProcessing =
      [&](CubebUtils::AudioDeviceID aDeviceID,
          RefPtr<AudioProcessingTrack>* aProcessingTrack,
          RefPtr<AudioInputProcessing>* aInputProcessing) {
        /* Create an input track and connect it to a device */
        const int32_t channelCount = 2;
        RefPtr processingTrack = AudioProcessingTrack::Create(graph);
        RefPtr inputProcessing = new AudioInputProcessing(channelCount);
        processingTrack->SetInputProcessing(inputProcessing);
        MediaEnginePrefs settings;
        settings.mChannels = channelCount;
        settings.mAecOn = true;
        QueueApplySettings(processingTrack, inputProcessing, settings);
        processingTrack->GraphImpl()->AppendMessage(
            MakeUnique<StartInputProcessing>(processingTrack, inputProcessing));
        processingTrack->ConnectDeviceInput(aDeviceID, inputProcessing,
                                            PRINCIPAL_HANDLE_NONE);
        aProcessingTrack->swap(processingTrack);
        aInputProcessing->swap(inputProcessing);
      };

  // Native input, which uses a duplex stream
  RefPtr<AudioProcessingTrack> processingTrack1;
  RefPtr<AudioInputProcessing> inputProcessing1;
  DispatchFunction([&] {
    createInputProcessing(nullptr, &processingTrack1, &inputProcessing1);
  });
  // Non-native input
  const auto* nonNativeInputDeviceID = CubebUtils::AudioDeviceID(1);
  RefPtr<AudioProcessingTrack> processingTrack2;
  RefPtr<AudioInputProcessing> inputProcessing2;
  DispatchFunction([&] {
    createInputProcessing(nonNativeInputDeviceID, &processingTrack2,
                          &inputProcessing2);
    processingTrack2->AddAudioOutput(nullptr, nullptr, rate);
  });

  RefPtr<SmartMockCubebStream> primaryStream;
  RefPtr<SmartMockCubebStream> nonNativeInputStream;
  WaitUntil(cubeb->StreamInitEvent(),
            [&](RefPtr<SmartMockCubebStream>&& stream) {
              if (stream->OutputChannels() > 0) {
                primaryStream = std::move(stream);
                return false;
              }
              nonNativeInputStream = std::move(stream);
              return true;
            });
  EXPECT_EQ(nonNativeInputStream->GetInputDeviceID(), nonNativeInputDeviceID);

  // Wait until non-native input signal reaches the output, when input
  // processing has run and so has been configured.
  WaitFor(primaryStream->FramesVerifiedEvent());

  const void* secondaryOutputDeviceID = CubebUtils::AudioDeviceID(2);
  DispatchFunction([&] {
    // Check input processing config with output to primary device.
    processingTrack1->QueueControlMessageWithNoShutdown([&] {
      EXPECT_FALSE(inputProcessing1->HadAECAndDrift());
      EXPECT_TRUE(inputProcessing2->HadAECAndDrift());
    });

    // Switch output to a secondary device.
    processingTrack2->RemoveAudioOutput(nullptr);
    processingTrack2->AddAudioOutput(nullptr, secondaryOutputDeviceID, rate);
  });

  RefPtr<SmartMockCubebStream> secondaryOutputStream =
      WaitFor(cubeb->StreamInitEvent());
  EXPECT_EQ(secondaryOutputStream->GetOutputDeviceID(),
            secondaryOutputDeviceID);

  WaitFor(secondaryOutputStream->FramesVerifiedEvent());
  DispatchFunction([&] {
    // Check input processing config with output to secondary device.
    processingTrack1->QueueControlMessageWithNoShutdown([&] {
      EXPECT_TRUE(inputProcessing1->HadAECAndDrift());
      EXPECT_TRUE(inputProcessing2->HadAECAndDrift());
    });
  });

  auto destroyInputProcessing = [&](AudioProcessingTrack* aProcessingTrack,
                                    AudioInputProcessing* aInputProcessing) {
    aProcessingTrack->GraphImpl()->AppendMessage(
        MakeUnique<StopInputProcessing>(aProcessingTrack, aInputProcessing));
    aProcessingTrack->DisconnectDeviceInput();
    aProcessingTrack->Destroy();
  };

  DispatchFunction([&] {
    // Clean up
    destroyInputProcessing(processingTrack1, inputProcessing1);
    destroyInputProcessing(processingTrack2, inputProcessing2);
  });
  // Wait for stream stop to ensure that expectations have been checked.
  WaitFor(nonNativeInputStream->OutputVerificationEvent());
}
#endif  // MOZ_WEBRTC

TEST(TestAudioTrackGraph, PlatformProcessing)
{
  constexpr cubeb_input_processing_params allParams =
      CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION |
      CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION |
      CUBEB_INPUT_PROCESSING_PARAM_AUTOMATIC_GAIN_CONTROL |
      CUBEB_INPUT_PROCESSING_PARAM_VOICE_ISOLATION;
  MockCubeb* cubeb = new MockCubeb(MockCubeb::RunningMode::Manual);
  cubeb->SetSupportedInputProcessingParams(allParams, CUBEB_OK);
  CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());

  MediaTrackGraph* graph = MediaTrackGraphImpl::GetInstance(
      MediaTrackGraph::SYSTEM_THREAD_DRIVER, /*Window ID*/ 1,
      CubebUtils::PreferredSampleRate(/* aShouldResistFingerprinting */ false),
      nullptr, GetMainThreadSerialEventTarget());

  const CubebUtils::AudioDeviceID device = (CubebUtils::AudioDeviceID)1;

  // Set up mock listener.
  RefPtr<MockAudioDataListener> listener = MakeRefPtr<MockAudioDataListener>();
  EXPECT_CALL(*listener, IsVoiceInput).WillRepeatedly(Return(true));
  EXPECT_CALL(*listener, RequestedInputChannelCount).WillRepeatedly(Return(1));
  EXPECT_CALL(*listener, RequestedInputProcessingParams)
      .WillRepeatedly(Return(CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION));
  EXPECT_CALL(*listener, Disconnect);

  // Expectations.
  const Result<cubeb_input_processing_params, int> notSupportedResult(
      Err(CUBEB_ERROR_NOT_SUPPORTED));
  const Result<cubeb_input_processing_params, int> errorResult(
      Err(CUBEB_ERROR));
  const Result<cubeb_input_processing_params, int> noneResult(
      CUBEB_INPUT_PROCESSING_PARAM_NONE);
  const Result<cubeb_input_processing_params, int> echoResult(
      CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION);
  const Result<cubeb_input_processing_params, int> noiseResult(
      CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION);
  Atomic<int> numProcessingParamsResults(0);
  {
    InSequence s;
    // On first driver start.
    EXPECT_CALL(*listener,
                NotifySetRequestedInputProcessingParams(
                    graph, 1, CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION));
    EXPECT_CALL(*listener, NotifySetRequestedInputProcessingParamsResult(
                               graph, 1, Eq(std::ref(echoResult))))
        .WillOnce([&] { ++numProcessingParamsResults; });
    // After requesting something else.
    EXPECT_CALL(*listener,
                NotifySetRequestedInputProcessingParams(
                    graph, 2, CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION));
    EXPECT_CALL(*listener, NotifySetRequestedInputProcessingParamsResult(
                               graph, 2, Eq(std::ref(noiseResult))))
        .WillOnce([&] { ++numProcessingParamsResults; });
    // After error request.
    EXPECT_CALL(*listener,
                NotifySetRequestedInputProcessingParams(
                    graph, 3, CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION));
    EXPECT_CALL(*listener, NotifySetRequestedInputProcessingParamsResult(
                               graph, 3, Eq(std::ref(errorResult))))
        .WillOnce([&] { ++numProcessingParamsResults; });
    // After requesting None, because of the previous error.
    EXPECT_CALL(*listener, NotifySetRequestedInputProcessingParams(
                               graph, 4, CUBEB_INPUT_PROCESSING_PARAM_NONE));
    EXPECT_CALL(*listener, NotifySetRequestedInputProcessingParamsResult(
                               graph, 4, Eq(std::ref(noneResult))))
        .WillOnce([&] { ++numProcessingParamsResults; });
    // After driver switch.
    EXPECT_CALL(*listener, NotifySetRequestedInputProcessingParamsResult(
                               graph, 4, Eq(std::ref(noneResult))))
        .WillOnce([&] { ++numProcessingParamsResults; });
    // After requesting something not supported.
    EXPECT_CALL(*listener,
                NotifySetRequestedInputProcessingParams(
                    graph, 5, CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION));
    EXPECT_CALL(*listener, NotifySetRequestedInputProcessingParamsResult(
                               graph, 5, Eq(std::ref(noneResult))))
        .WillOnce([&] { ++numProcessingParamsResults; });
    // After requesting something with backend not supporting processing params.
    EXPECT_CALL(*listener,
                NotifySetRequestedInputProcessingParams(
                    graph, 6, CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION));
    EXPECT_CALL(*listener, NotifySetRequestedInputProcessingParamsResult(
                               graph, 6, Eq(std::ref(notSupportedResult))))
        .WillOnce([&] { ++numProcessingParamsResults; });
  }

  // Open a device.
  RefPtr<TestDeviceInputConsumerTrack> track;
  RefPtr<OnFallbackListener> fallbackListener;
  DispatchFunction([&] {
    track = TestDeviceInputConsumerTrack::Create(graph);
    track->ConnectDeviceInput(device, listener, PRINCIPAL_HANDLE_NONE);
    fallbackListener = new OnFallbackListener(track);
    track->AddListener(fallbackListener);
  });

  RefPtr<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent());
  EXPECT_TRUE(stream->mHasInput);
  EXPECT_TRUE(stream->mHasOutput);
  EXPECT_EQ(stream->InputChannels(), 1U);
  EXPECT_EQ(stream->GetInputDeviceID(), device);

  while (
      stream->State()
          .map([](cubeb_state aState) { return aState != CUBEB_STATE_STARTED; })
          .valueOr(true)) {
    std::this_thread::sleep_for(std::chrono::milliseconds(1));
  }

  const auto waitForResult = [&](int aNumResult) {
    while (numProcessingParamsResults < aNumResult) {
      EXPECT_EQ(stream->ManualDataCallback(0),
                MockCubebStream::KeepProcessing::Yes);
      NS_ProcessNextEvent();
      std::this_thread::sleep_for(std::chrono::milliseconds(1));
    }
  };

  // Wait for the AudioCallbackDriver to come into effect.
  while (fallbackListener->OnFallback()) {
    EXPECT_EQ(stream->ManualDataCallback(0),
              MockCubebStream::KeepProcessing::Yes);
    std::this_thread::sleep_for(std::chrono::milliseconds(1));
  }

  // Wait for the first result after driver creation.
  waitForResult(1);

  // Request new processing params.
  EXPECT_CALL(*listener, RequestedInputProcessingParams)
      .WillRepeatedly(Return(CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION));
  waitForResult(2);

  // Test with returning error on new request.
  cubeb->SetInputProcessingApplyRv(CUBEB_ERROR);
  EXPECT_CALL(*listener, RequestedInputProcessingParams)
      .WillRepeatedly(Return(CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION));
  waitForResult(3);

  // Test unsetting all params.
  cubeb->SetInputProcessingApplyRv(CUBEB_OK);
  EXPECT_CALL(*listener, RequestedInputProcessingParams)
      .WillRepeatedly(Return(CUBEB_INPUT_PROCESSING_PARAM_NONE));
  waitForResult(4);

  // Switch driver.
  EXPECT_CALL(*listener, RequestedInputChannelCount).WillRepeatedly(Return(2));
  DispatchFunction([&] {
    track->QueueControlMessageWithNoShutdown(
        [&] { graph->ReevaluateInputDevice(device); });
  });
  ProcessEventQueue();
  // Process the reevaluation message.
  EXPECT_EQ(stream->ManualDataCallback(0),
            MockCubebStream::KeepProcessing::Yes);
  // Perform the switch.
  auto initPromise = TakeN(cubeb->StreamInitEvent(), 1);
  EXPECT_EQ(stream->ManualDataCallback(0), MockCubebStream::KeepProcessing::No);
  std::tie(stream) = WaitFor(initPromise).unwrap()[0];
  EXPECT_TRUE(stream->mHasInput);
  EXPECT_TRUE(stream->mHasOutput);
  EXPECT_EQ(stream->InputChannels(), 2U);
  EXPECT_EQ(stream->GetInputDeviceID(), device);

  while (
      stream->State()
          .map([](cubeb_state aState) { return aState != CUBEB_STATE_STARTED; })
          .valueOr(true)) {
    std::this_thread::sleep_for(std::chrono::milliseconds(1));
  }

  // Wait for the new AudioCallbackDriver to come into effect.
  fallbackListener->Reset();
  while (fallbackListener->OnFallback()) {
    EXPECT_EQ(stream->ManualDataCallback(0),
              MockCubebStream::KeepProcessing::Yes);
    std::this_thread::sleep_for(std::chrono::milliseconds(1));
  }

  // Wait for the first result after driver creation.
  waitForResult(5);

  // Test requesting something not supported.
  cubeb->SetSupportedInputProcessingParams(
      CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION |
          CUBEB_INPUT_PROCESSING_PARAM_AUTOMATIC_GAIN_CONTROL |
          CUBEB_INPUT_PROCESSING_PARAM_VOICE_ISOLATION,
      CUBEB_OK);
  EXPECT_CALL(*listener, RequestedInputProcessingParams)
      .WillRepeatedly(Return(CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION));
  waitForResult(6);

  // Test requesting something when unsupported by backend.
  cubeb->SetSupportedInputProcessingParams(CUBEB_INPUT_PROCESSING_PARAM_NONE,
                                           CUBEB_ERROR_NOT_SUPPORTED);
  EXPECT_CALL(*listener, RequestedInputProcessingParams)
      .WillRepeatedly(Return(CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION));
  waitForResult(7);

  // Clean up.
  DispatchFunction([&] {
    track->RemoveListener(fallbackListener);
    track->Destroy();
  });
  ProcessEventQueue();
  // Process the destroy message.
  EXPECT_EQ(stream->ManualDataCallback(0),
            MockCubebStream::KeepProcessing::Yes);
  // Shut down.
  EXPECT_EQ(stream->ManualDataCallback(0), MockCubebStream::KeepProcessing::No);
  RefPtr<SmartMockCubebStream> destroyedStream =
      WaitFor(cubeb->StreamDestroyEvent());
  EXPECT_EQ(destroyedStream.get(), stream.get());
  {
    NativeInputTrack* native = graph->GetNativeInputTrackMainThread();
    ASSERT_TRUE(!native);
  }
}

TEST(TestAudioTrackGraph, PlatformProcessingNonNativeToNativeSwitch)
{
  constexpr cubeb_input_processing_params allParams =
      CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION |
      CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION |
      CUBEB_INPUT_PROCESSING_PARAM_AUTOMATIC_GAIN_CONTROL |
      CUBEB_INPUT_PROCESSING_PARAM_VOICE_ISOLATION;
  MockCubeb* cubeb = new MockCubeb(MockCubeb::RunningMode::Manual);
  cubeb->SetSupportedInputProcessingParams(allParams, CUBEB_OK);
  CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());

  MediaTrackGraph* graph = MediaTrackGraphImpl::GetInstance(
      MediaTrackGraph::SYSTEM_THREAD_DRIVER, /*Window ID*/ 1,
      CubebUtils::PreferredSampleRate(/* aShouldResistFingerprinting */ false),
      nullptr, GetMainThreadSerialEventTarget());

  const CubebUtils::AudioDeviceID firstDevice = (CubebUtils::AudioDeviceID)1;
  const CubebUtils::AudioDeviceID secondDevice = (CubebUtils::AudioDeviceID)2;

  // Set up mock listeners.
  RefPtr<MockAudioDataListener> firstListener =
      MakeRefPtr<MockAudioDataListener>();
  EXPECT_CALL(*firstListener, IsVoiceInput).WillRepeatedly(Return(true));
  EXPECT_CALL(*firstListener, RequestedInputChannelCount)
      .WillRepeatedly(Return(1));
  EXPECT_CALL(*firstListener, RequestedInputProcessingParams)
      .WillRepeatedly(Return(CUBEB_INPUT_PROCESSING_PARAM_NONE));

  RefPtr<MockAudioDataListener> secondListener =
      MakeRefPtr<MockAudioDataListener>();
  EXPECT_CALL(*secondListener, IsVoiceInput).WillRepeatedly(Return(true));
  EXPECT_CALL(*secondListener, RequestedInputChannelCount)
      .WillRepeatedly(Return(1));
  EXPECT_CALL(*secondListener, RequestedInputProcessingParams)
      .WillRepeatedly(Return(CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION));

  // Expectations.
  const Result<cubeb_input_processing_params, int> noneResult(
      CUBEB_INPUT_PROCESSING_PARAM_NONE);
  const Result<cubeb_input_processing_params, int> echoResult(
      CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION);
  const Result<cubeb_input_processing_params, int> noiseResult(
      CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION);
  Atomic<int> numProcessingParamsResults(0);
  {
    InSequence s;
    // On first driver start.
    EXPECT_CALL(*firstListener, NotifySetRequestedInputProcessingParamsResult(
                                    graph, 0, Eq(std::ref(noneResult))))
        .WillOnce([&] { ++numProcessingParamsResults; });
    // After param update.
    EXPECT_CALL(*firstListener,
                NotifySetRequestedInputProcessingParams(
                    graph, 1, CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION));
    EXPECT_CALL(*firstListener, NotifySetRequestedInputProcessingParamsResult(
                                    graph, 1, Eq(std::ref(noiseResult))))
        .WillOnce([&] { ++numProcessingParamsResults; });
    // After second param update.
    EXPECT_CALL(*firstListener,
                NotifySetRequestedInputProcessingParams(
                    graph, 2, CUBEB_INPUT_PROCESSING_PARAM_NONE));
    EXPECT_CALL(*firstListener, NotifySetRequestedInputProcessingParamsResult(
                                    graph, 2, Eq(std::ref(noneResult))))
        .WillOnce([&] { ++numProcessingParamsResults; });
    // On non-native device's start.
    EXPECT_CALL(*secondListener,
                NotifySetRequestedInputProcessingParams(
                    graph, 1, CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION));
    EXPECT_CALL(*secondListener, NotifySetRequestedInputProcessingParamsResult(
                                     graph, 1, Eq(std::ref(echoResult))))
        .WillOnce([&] { ++numProcessingParamsResults; });
    // After switch to native device for second device.
    EXPECT_CALL(*firstListener, Disconnect);
    EXPECT_CALL(*secondListener, Disconnect);
    EXPECT_CALL(*secondListener,
                NotifySetRequestedInputProcessingParams(
                    graph, 1, CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION));
    EXPECT_CALL(*secondListener, NotifySetRequestedInputProcessingParamsResult(
                                     graph, 1, Eq(std::ref(echoResult))))
        .WillOnce([&] { ++numProcessingParamsResults; });
    // After param update.
    EXPECT_CALL(*secondListener,
                NotifySetRequestedInputProcessingParams(
                    graph, 2, CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION));
    EXPECT_CALL(*secondListener, NotifySetRequestedInputProcessingParamsResult(
                                     graph, 2, Eq(std::ref(noiseResult))))
        .WillOnce([&] { ++numProcessingParamsResults; });
    EXPECT_CALL(*secondListener, Disconnect);
  }

  // Open the first input device. It will be the native device of the graph.
  RefPtr<TestDeviceInputConsumerTrack> firstTrack;
  RefPtr<OnFallbackListener> fallbackListener;
  DispatchFunction([&] {
    firstTrack = TestDeviceInputConsumerTrack::Create(graph);
    firstTrack->ConnectDeviceInput(firstDevice, firstListener,
                                   PRINCIPAL_HANDLE_NONE);
    fallbackListener = new OnFallbackListener(firstTrack);
    firstTrack->AddListener(fallbackListener);
  });

  RefPtr<SmartMockCubebStream> nativeStream = WaitFor(cubeb->StreamInitEvent());
  RefPtr<SmartMockCubebStream> nonNativeStream;
  EXPECT_TRUE(nativeStream->mHasInput);
  EXPECT_TRUE(nativeStream->mHasOutput);
  EXPECT_EQ(nativeStream->InputChannels(), 1U);
  EXPECT_EQ(nativeStream->GetInputDeviceID(), firstDevice);

  while (
      nativeStream->State()
          .map([](cubeb_state aState) { return aState != CUBEB_STATE_STARTED; })
          .valueOr(true)) {
    std::this_thread::sleep_for(std::chrono::milliseconds(1));
  }

  // Wait for the AudioCallbackDriver to come into effect.
  while (fallbackListener->OnFallback()) {
    EXPECT_EQ(nativeStream->ManualDataCallback(0),
              MockCubebStream::KeepProcessing::Yes);
    std::this_thread::sleep_for(std::chrono::milliseconds(1));
  }

  const auto waitForResult = [&](int aNumResult) {
    while (numProcessingParamsResults < aNumResult) {
      EXPECT_EQ(nativeStream->ManualDataCallback(0),
                MockCubebStream::KeepProcessing::Yes);
      if (nonNativeStream) {
        EXPECT_EQ(nonNativeStream->ManualDataCallback(0),
                  MockCubebStream::KeepProcessing::Yes);
      }
      NS_ProcessNextEvent();
      std::this_thread::sleep_for(std::chrono::milliseconds(1));
    }
  };

  // Wait for the first result after driver creation.
  waitForResult(1);

  // Request new processing params.
  EXPECT_CALL(*firstListener, RequestedInputProcessingParams)
      .WillRepeatedly(Return(CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION));
  waitForResult(2);

  // Again request new processing params, to move the processing params
  // generation of the first device higher so as to avoid ambiguity with the
  // processing params generation of the second device.
  EXPECT_CALL(*firstListener, RequestedInputProcessingParams)
      .WillRepeatedly(Return(CUBEB_INPUT_PROCESSING_PARAM_NONE));
  waitForResult(3);

  // Open the second input device. It will be a non-native device of the graph.
  RefPtr<TestDeviceInputConsumerTrack> secondTrack;
  DispatchFunction([&] {
    secondTrack = TestDeviceInputConsumerTrack::Create(graph);
    secondTrack->ConnectDeviceInput(secondDevice, secondListener,
                                    PRINCIPAL_HANDLE_NONE);
    secondTrack->AddListener(fallbackListener);
  });

  auto initPromise = TakeN(cubeb->StreamInitEvent(), 1);
  DispatchFunction([&] {
    // TakeN dispatches a task that runs SpinEventLoopUntil while blocking the
    // caller. Make sure any event loop spinning that needs to be intertwined
    // with driving the graph runs in a nested task, so as to not block the test
    // flow. I.e. if the caller that gets blocked on the TakeN task is WaitFor
    // below, we will deadlock, because the graph must be iterated to unblock
    // the TakeN task.
    EXPECT_EQ(nativeStream->ManualDataCallback(0),
              MockCubebStream::KeepProcessing::Yes);
    ProcessEventQueue();
    EXPECT_EQ(nativeStream->ManualDataCallback(0),
              MockCubebStream::KeepProcessing::Yes);
  });

  std::tie(nonNativeStream) = WaitFor(initPromise).unwrap()[0];
  EXPECT_TRUE(nonNativeStream->mHasInput);
  EXPECT_FALSE(nonNativeStream->mHasOutput);
  EXPECT_EQ(nonNativeStream->InputChannels(), 1U);
  EXPECT_EQ(nonNativeStream->GetInputDeviceID(), secondDevice);

  while (
      nonNativeStream->State()
          .map([](cubeb_state aState) { return aState != CUBEB_STATE_STARTED; })
          .valueOr(true)) {
    std::this_thread::sleep_for(std::chrono::milliseconds(1));
  }

  // Wait for the first result after non-native device input creation.
  waitForResult(4);

  // Disconnect native input. First non-native input should be promoted to
  // native, and processing param generation should increase monotonically
  // through the switch.
  DispatchFunction([&] {
    firstTrack->RemoveListener(fallbackListener);
    secondTrack->AddListener(fallbackListener);
    firstTrack->DisconnectDeviceInput();
  });
  ProcessEventQueue();
  initPromise = TakeN(cubeb->StreamInitEvent(), 1);
  // Process the disconnect message, and check that the second device is now
  // used with the new graph driver.
  EXPECT_EQ(nativeStream->ManualDataCallback(0),
            MockCubebStream::KeepProcessing::Yes);
  // Perform the switch.
  EXPECT_EQ(nativeStream->ManualDataCallback(0),
            MockCubebStream::KeepProcessing::No);
  std::tie(nativeStream) = WaitFor(initPromise).unwrap()[0];
  EXPECT_TRUE(nativeStream->mHasInput);
  EXPECT_TRUE(nativeStream->mHasOutput);
  EXPECT_EQ(nativeStream->InputChannels(), 1U);
  EXPECT_EQ(nativeStream->GetInputDeviceID(), secondDevice);

  while (
      nativeStream->State()
          .map([](cubeb_state aState) { return aState != CUBEB_STATE_STARTED; })
          .valueOr(true)) {
    std::this_thread::sleep_for(std::chrono::milliseconds(1));
  }

  // Wait for the new AudioCallbackDriver to come into effect.
  fallbackListener->Reset();
  while (fallbackListener->OnFallback()) {
    EXPECT_EQ(nativeStream->ManualDataCallback(0),
              MockCubebStream::KeepProcessing::Yes);
    std::this_thread::sleep_for(std::chrono::milliseconds(1));
  }

  // So that waitForResult doesn't make callbacks to it anymore.
  nonNativeStream = nullptr;

  // Wait for the first result after driver creation.
  waitForResult(5);

  // Request new processing params.
  EXPECT_CALL(*secondListener, RequestedInputProcessingParams)
      .WillRepeatedly(Return(CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION));
  waitForResult(6);

  // Clean up.
  DispatchFunction([&] {
    firstTrack->Destroy();
    secondTrack->RemoveListener(fallbackListener);
    secondTrack->Destroy();
  });
  auto destroyPromise = TakeN(cubeb->StreamDestroyEvent(), 1);
  DispatchFunction([&] {
    ProcessEventQueue();
    // Process the destroy message.
    EXPECT_EQ(nativeStream->ManualDataCallback(0),
              MockCubebStream::KeepProcessing::Yes);
    // Shut down native.
    EXPECT_EQ(nativeStream->ManualDataCallback(0),
              MockCubebStream::KeepProcessing::No);
  });
  RefPtr<SmartMockCubebStream> destroyedStream;
  std::tie(destroyedStream) = WaitFor(destroyPromise).unwrap()[0];
  EXPECT_EQ(destroyedStream, nativeStream);
  {
    NativeInputTrack* native = graph->GetNativeInputTrackMainThread();
    ASSERT_TRUE(!native);
  }
}

class MockProcessedMediaTrack : public ProcessedMediaTrack {
 public:
  explicit MockProcessedMediaTrack(TrackRate aRate)
      : ProcessedMediaTrack(aRate, MediaSegment::AUDIO, new AudioSegment()) {
    ON_CALL(*this, ProcessInput)
        .WillByDefault([segment = GetData<AudioSegment>()](
                           GraphTime aFrom, GraphTime aTo, uint32_t aFlags) {
          segment->AppendNullData(aTo - aFrom);
        });
  }

  MOCK_METHOD(void, ProcessInput,
              (GraphTime aFrom, GraphTime aTo, uint32_t aFlags), (override));

  uint32_t NumberOfChannels() const override { return 2; };
};

TEST(TestAudioTrackGraph, EmptyProcessingInterval)
{
  MockCubeb* cubeb = new MockCubeb(MockCubeb::RunningMode::Manual);
  CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());

  MediaTrackGraph* graph = MediaTrackGraphImpl::GetInstance(
      MediaTrackGraph::AUDIO_THREAD_DRIVER, /*Window ID*/ 1,
      CubebUtils::PreferredSampleRate(/* aShouldResistFingerprinting */ false),
      nullptr, GetMainThreadSerialEventTarget());

  RefPtr processedTrack = new MockProcessedMediaTrack(graph->GraphRate());
  RefPtr fallbackListener = new OnFallbackListener(processedTrack);
  MockFunction<void(const char* name)> checkpoint;
  {
    InSequence s;
    EXPECT_CALL(*processedTrack, ProcessInput).Times(AtLeast(1));
    EXPECT_CALL(checkpoint, Call(StrEq("before single iteration")));
    EXPECT_CALL(*processedTrack, ProcessInput).Times(1);
    EXPECT_CALL(checkpoint, Call(StrEq("before empty iteration")));
    EXPECT_CALL(checkpoint, Call(StrEq("after empty iteration")));
  }
  DispatchFunction([&] {
    graph->AddTrack(processedTrack);
    processedTrack->AddAudioOutput(reinterpret_cast<void*>(1), nullptr);
    processedTrack->AddListener(fallbackListener);
  });

  RefPtr<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent());
  while (stream->State().isNothing()) {
    std::this_thread::sleep_for(std::chrono::milliseconds(1));
  }
  EXPECT_EQ(*stream->State(), CUBEB_STATE_STARTED);
  // Wait for the AudioCallbackDriver to come into effect.
  while (fallbackListener->OnFallback()) {
    EXPECT_EQ(stream->ManualDataCallback(1),
              MockCubebStream::KeepProcessing::Yes);
    std::this_thread::sleep_for(std::chrono::milliseconds(1));
  }
  // The graph is now run by ManualDataCallback().
  GraphTime processedUpTo0 = processedTrack->GetEnd();
  EXPECT_GT(processedUpTo0, 0u);
  checkpoint.Call("before single iteration");
  // 128 matches the block size used by MediaTrackGraph and so is expected to
  // trigger another block of processing.
  EXPECT_EQ(stream->ManualDataCallback(128),
            MockCubebStream::KeepProcessing::Yes);
  GraphTime processedUpTo1 = processedTrack->GetEnd();
  EXPECT_EQ(processedUpTo1, processedUpTo0 + 128);
  // Test that ProcessInput() is not called in a graph iteration with no
  // frames to be rendered.
  checkpoint.Call("before empty iteration");
  EXPECT_EQ(stream->ManualDataCallback(0),
            MockCubebStream::KeepProcessing::Yes);
  checkpoint.Call("after empty iteration");
  EXPECT_EQ(processedTrack->GetEnd(), processedUpTo1);

  DispatchFunction([&] { processedTrack->Destroy(); });
  ProcessEventQueue();
  // Process the destroy message and drain the stream.
  auto destroyPromise = TakeN(cubeb->StreamDestroyEvent(), 1);
  while (stream->ManualDataCallback(0) ==
         MockCubebStream::KeepProcessing::Yes) {
  }
  // Ensure the stream is no longer used by its MockCubeb before releasing our
  // reference, and before the next test might ForceSetCubebContext() to
  // destroy our cubeb.
  (void)WaitFor(destroyPromise).unwrap()[0];
}

#undef Invoke
#undef DispatchFunction
#undef DispatchMethod

Messung V0.5 in Prozent
C=87 H=86 G=86

¤ Dauer der Verarbeitung: 0.52 Sekunden  (vorverarbeitet am  2026-04-26) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.