Bug 1677520 - Add gtest for turning a live audio input off and on again. r=padenot
authorAndreas Pehrson <apehrson@mozilla.com>
Wed, 18 Nov 2020 09:21:42 +0000
changeset 557786 fbc4d44a65c4ec6b9d1946f214d66d52a0b25373
parent 557785 e820d352df4cb1cb1ebaed4ca6d3cb95f3c886d8
child 557787 aa0b71302d8a8008437e2554b3c50dee275d5b5f
push id37961
push userccoroiu@mozilla.com
push dateWed, 18 Nov 2020 16:05:35 +0000
treeherdermozilla-central@2f08ec7e57c3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspadenot
bugs1677520
milestone85.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1677520 - Add gtest for turning a live audio input off and on again. r=padenot Differential Revision: https://phabricator.services.mozilla.com/D97308
dom/media/gtest/TestAudioCallbackDriver.cpp
dom/media/gtest/TestAudioTrackGraph.cpp
--- a/dom/media/gtest/TestAudioCallbackDriver.cpp
+++ b/dom/media/gtest/TestAudioCallbackDriver.cpp
@@ -27,30 +27,40 @@ class MockGraphInterface : public GraphI
                void(AudioDataValue*, size_t, TrackRate, uint32_t));
   MOCK_METHOD0(NotifyInputStopped, void());
   MOCK_METHOD5(NotifyInputData, void(const AudioDataValue*, size_t, TrackRate,
                                      uint32_t, uint32_t));
   MOCK_METHOD0(DeviceChanged, void());
   /* OneIteration cannot be mocked because IterationResult is non-memmovable and
    * cannot be passed as a parameter, which GMock does internally. */
   IterationResult OneIteration(GraphTime, GraphTime, AudioMixer*) {
-    return mKeepProcessing ? IterationResult::CreateStillProcessing()
-                           : IterationResult::CreateStop(
-                                 NS_NewRunnableFunction(__func__, [] {}));
+    if (!mKeepProcessing) {
+      return IterationResult::CreateStop(
+          NS_NewRunnableFunction(__func__, [] {}));
+    }
+    GraphDriver* next = mNextDriver.exchange(nullptr);
+    if (next) {
+      return IterationResult::CreateSwitchDriver(
+          next, NS_NewRunnableFunction(__func__, [] {}));
+    }
+    return IterationResult::CreateStillProcessing();
   }
 #ifdef DEBUG
   bool InDriverIteration(GraphDriver* aDriver) override {
     return aDriver->OnThread();
   }
 #endif
 
   void StopIterating() { mKeepProcessing = false; }
 
+  void SwitchTo(GraphDriver* aDriver) { mNextDriver = aDriver; }
+
  protected:
   Atomic<bool> mKeepProcessing{true};
+  Atomic<GraphDriver*> mNextDriver{nullptr};
   virtual ~MockGraphInterface() = default;
 };
 
 NS_IMPL_ISUPPORTS0(MockGraphInterface)
 
 TEST(TestAudioCallbackDriver, StartStop)
 MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
   MockCubeb* cubeb = new MockCubeb();
--- a/dom/media/gtest/TestAudioTrackGraph.cpp
+++ b/dom/media/gtest/TestAudioTrackGraph.cpp
@@ -421,16 +421,159 @@ TEST(TestAudioTrackGraph, AudioInputTrac
   // 10ms-iterations of silence; sometimes only one.
   EXPECT_LE(preSilenceSamples, 128U + 2 * inputRate / 100 /* 2*10ms */);
   // Waveform may start after the beginning. In this case, there is a gap
   // at the beginning and the end which is counted as discontinuity.
   EXPECT_GE(nrDiscontinuities, 0U);
   EXPECT_LE(nrDiscontinuities, 2U);
 }
 
+TEST(TestAudioTrackGraph, ReOpenAudioInput)
+{
+  MockCubeb* cubeb = new MockCubeb();
+  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;
+
+  MediaTrackGraph* graph = MediaTrackGraph::GetInstance(
+      MediaTrackGraph::SYSTEM_THREAD_DRIVER, /*window*/ nullptr, rate, nullptr);
+
+  RefPtr<AudioInputTrack> inputTrack;
+  RefPtr<ProcessedMediaTrack> outputTrack;
+  RefPtr<MediaInputPort> port;
+  RefPtr<AudioInputProcessing> listener;
+  auto p = Invoke([&] {
+    inputTrack = AudioInputTrack::Create(graph);
+    outputTrack = graph->CreateForwardedInputTrack(MediaSegment::AUDIO);
+    outputTrack->QueueSetAutoend(false);
+    outputTrack->AddAudioOutput(reinterpret_cast<void*>(1));
+    port = outputTrack->AllocateInputPort(inputTrack);
+    listener = new AudioInputProcessing(2, PRINCIPAL_HANDLE_NONE);
+    inputTrack->SetInputProcessing(listener);
+    inputTrack->GraphImpl()->AppendMessage(
+        MakeUnique<StartInputProcessing>(inputTrack, listener));
+    inputTrack->OpenAudioInput((void*)1, listener);
+    return graph->NotifyWhenDeviceStarted(inputTrack);
+  });
+
+  MockCubebStream* stream = WaitFor(cubeb->StreamInitEvent());
+  EXPECT_TRUE(stream->mHasInput);
+  Unused << WaitFor(p);
+
+  // Set 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.
+  stream->SetDriftFactor(1.111);
+
+  // 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([&] {
+    inputTrack->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();
+
+  // Close the input to see that no asserts go off due to bad state.
+  DispatchFunction([&] {
+    // Device id does not matter. Ignore.
+    auto id = Some((CubebUtils::AudioDeviceID)1);
+    inputTrack->CloseAudioInput(id);
+  });
+
+  stream = WaitFor(cubeb->StreamInitEvent());
+  EXPECT_FALSE(stream->mHasInput);
+  Unused << WaitFor(
+      Invoke([&] { return graph->NotifyWhenDeviceStarted(inputTrack); }));
+
+  // Output-only. Wait for another second before unmuting.
+  DispatchFunction([&] {
+    inputTrack->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();
+
+  // Re-open the input to again see that no asserts go off due to bad state.
+  DispatchFunction([&] {
+    // Device id does not matter. Ignore.
+    inputTrack->OpenAudioInput((void*)1, listener);
+  });
+
+  stream = WaitFor(cubeb->StreamInitEvent());
+  EXPECT_TRUE(stream->mHasInput);
+  Unused << WaitFor(
+      Invoke([&] { return graph->NotifyWhenDeviceStarted(inputTrack); }));
+
+  // Full-duplex. Wait for another second before finishing.
+  DispatchFunction([&] {
+    inputTrack->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([&] {
+    outputTrack->RemoveAudioOutput((void*)1);
+    outputTrack->Destroy();
+    port->Destroy();
+    inputTrack->GraphImpl()->AppendMessage(
+        MakeUnique<StopInputProcessing>(listener));
+    Maybe<CubebUtils::AudioDeviceID> id =
+        Some(reinterpret_cast<CubebUtils::AudioDeviceID>(1));
+    inputTrack->CloseAudioInput(id);
+    inputTrack->Destroy();
+  });
+
+  uint32_t inputRate = stream->InputSampleRate();
+  uint32_t inputFrequency = stream->InputFrequency();
+  uint64_t preSilenceSamples;
+  uint32_t estimatedFreq;
+  uint32_t nrDiscontinuities;
+  Tie(preSilenceSamples, estimatedFreq, nrDiscontinuities) =
+      WaitFor(stream->OutputVerificationEvent());
+
+  EXPECT_EQ(estimatedFreq, inputFrequency);
+  std::cerr << "PreSilence: " << preSilenceSamples << std::endl;
+  // We buffer 10ms worth of frames in non-passthrough mode, plus up to 128
+  // frames as we round up to the nearest block. See AudioInputProcessing::Pull.
+  EXPECT_GE(preSilenceSamples, 128U + inputRate / 100);
+  // 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 + 3 * inputRate / 100 /* 3*10ms */);
+  // 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);
+}
+
 void TestCrossGraphPort(uint32_t aInputRate, uint32_t aOutputRate,
                         float aDriftFactor, uint32_t aBufferMs = 50) {
   std::cerr << "TestCrossGraphPort input: " << aInputRate
             << ", output: " << aOutputRate << ", driftFactor: " << aDriftFactor
             << std::endl;
 
   MockCubeb* cubeb = new MockCubeb();
   CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());