From 59203c63bb777a3bacec32fb8830fba33540e809 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 12 Jun 2024 07:35:29 +0200 Subject: Adding upstream version 127.0. Signed-off-by: Daniel Baumann --- dom/media/gtest/TestAudioCallbackDriver.cpp | 457 ++++++++++++++++++++++++---- 1 file changed, 400 insertions(+), 57 deletions(-) (limited to 'dom/media/gtest/TestAudioCallbackDriver.cpp') diff --git a/dom/media/gtest/TestAudioCallbackDriver.cpp b/dom/media/gtest/TestAudioCallbackDriver.cpp index 050395fa44..9c8c9bd107 100644 --- a/dom/media/gtest/TestAudioCallbackDriver.cpp +++ b/dom/media/gtest/TestAudioCallbackDriver.cpp @@ -9,34 +9,39 @@ #include "GraphDriver.h" #include "gmock/gmock.h" -#include "gtest/gtest-printers.h" #include "gtest/gtest.h" #include "MediaTrackGraphImpl.h" #include "mozilla/gtest/WaitFor.h" #include "mozilla/Attributes.h" #include "mozilla/SyncRunnable.h" -#include "mozilla/UniquePtr.h" #include "nsTArray.h" #include "MockCubeb.h" -using namespace mozilla; +namespace mozilla { + using IterationResult = GraphInterface::IterationResult; using ::testing::_; using ::testing::AnyNumber; +using ::testing::AtMost; +using ::testing::Eq; +using ::testing::InSequence; using ::testing::NiceMock; class MockGraphInterface : public GraphInterface { NS_DECL_THREADSAFE_ISUPPORTS explicit MockGraphInterface(TrackRate aSampleRate) : mSampleRate(aSampleRate) {} - MOCK_METHOD0(NotifyInputStopped, void()); - MOCK_METHOD5(NotifyInputData, void(const AudioDataValue*, size_t, TrackRate, - uint32_t, uint32_t)); - MOCK_METHOD0(DeviceChanged, void()); + MOCK_METHOD(void, NotifyInputStopped, ()); + MOCK_METHOD(void, NotifyInputData, + (const AudioDataValue*, size_t, TrackRate, uint32_t, uint32_t)); + MOCK_METHOD(void, NotifySetRequestedInputProcessingParamsResult, + (AudioCallbackDriver*, cubeb_input_processing_params, + (Result&&))); + MOCK_METHOD(void, DeviceChanged, ()); #ifdef DEBUG - MOCK_CONST_METHOD1(InDriverIteration, bool(const GraphDriver*)); + MOCK_METHOD(bool, InDriverIteration, (const GraphDriver*), (const)); #endif /* OneIteration cannot be mocked because IterationResult is non-memmovable and * cannot be passed as a parameter, which GMock does internally. */ @@ -81,7 +86,7 @@ class MockGraphInterface : public GraphInterface { RefPtr aSwitchedRunnable = NS_NewRunnableFunction( "DefaultNoopSwitchedRunnable", [] {})) { auto guard = mNextDriver.Lock(); - MOZ_ASSERT(guard->isNothing()); + MOZ_RELEASE_ASSERT(guard->isNothing()); *guard = Some(std::make_tuple(std::move(aDriver), std::move(aSwitchedRunnable))); } @@ -108,7 +113,7 @@ class MockGraphInterface : public GraphInterface { NS_IMPL_ISUPPORTS0(MockGraphInterface) TEST(TestAudioCallbackDriver, StartStop) -MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION { +MOZ_CAN_RUN_SCRIPT_BOUNDARY { const TrackRate rate = 44100; MockCubeb* cubeb = new MockCubeb(); CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext()); @@ -118,7 +123,8 @@ MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION { EXPECT_CALL(*graph, NotifyInputStopped).Times(0); driver = MakeRefPtr(graph, nullptr, rate, 2, 0, nullptr, - nullptr, AudioInputType::Unknown); + nullptr, AudioInputType::Unknown, + CUBEB_INPUT_PROCESSING_PARAM_NONE); EXPECT_FALSE(driver->ThreadRunning()) << "Verify thread is not running"; EXPECT_FALSE(driver->IsStarted()) << "Verify thread is not started"; @@ -135,7 +141,7 @@ MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION { EXPECT_FALSE(driver->IsStarted()) << "Verify thread is not started"; } -void TestSlowStart(const TrackRate aRate) MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION { +void TestSlowStart(const TrackRate aRate) MOZ_CAN_RUN_SCRIPT_BOUNDARY { std::cerr << "TestSlowStart with rate " << aRate << std::endl; MockCubeb* cubeb = new MockCubeb(); @@ -177,15 +183,17 @@ void TestSlowStart(const TrackRate aRate) MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION { }); driver = MakeRefPtr(graph, nullptr, aRate, 2, 2, nullptr, - (void*)1, AudioInputType::Voice); + (void*)1, AudioInputType::Voice, + CUBEB_INPUT_PROCESSING_PARAM_NONE); EXPECT_FALSE(driver->ThreadRunning()) << "Verify thread is not running"; EXPECT_FALSE(driver->IsStarted()) << "Verify thread is not started"; graph->SetCurrentDriver(driver); graph->SetEnsureNextIteration(true); + auto initPromise = TakeN(cubeb->StreamInitEvent(), 1); driver->Start(); - RefPtr stream = WaitFor(cubeb->StreamInitEvent()); + auto [stream] = WaitFor(initPromise).unwrap()[0]; cubeb->SetStreamStartFreezeEnabled(false); const size_t fallbackIterations = 3; @@ -234,7 +242,7 @@ void TestSlowStart(const TrackRate aRate) MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION { } TEST(TestAudioCallbackDriver, SlowStart) -MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION { +{ TestSlowStart(1000); // 10ms = 10 <<< 128 samples TestSlowStart(8000); // 10ms = 80 < 128 samples TestSlowStart(44100); // 10ms = 441 > 128 samples @@ -252,7 +260,7 @@ class MOZ_STACK_CLASS AutoSetter { : mVal(aVal), mNew(aNew), mOld(mVal.exchange(aNew)) {} ~AutoSetter() { DebugOnly oldNew = mVal.exchange(mOld); - MOZ_ASSERT(oldNew == mNew); + MOZ_RELEASE_ASSERT(oldNew == mNew); } }; #endif @@ -265,12 +273,13 @@ MOZ_CAN_RUN_SCRIPT_BOUNDARY { auto graph = MakeRefPtr(rate); auto driver = MakeRefPtr( - graph, nullptr, rate, 2, 1, nullptr, (void*)1, AudioInputType::Voice); + graph, nullptr, rate, 2, 1, nullptr, (void*)1, AudioInputType::Voice, + CUBEB_INPUT_PROCESSING_PARAM_NONE); EXPECT_FALSE(driver->ThreadRunning()) << "Verify thread is not running"; EXPECT_FALSE(driver->IsStarted()) << "Verify thread is not started"; #ifdef DEBUG - std::atomic threadInDriverIteration((std::thread::id())); + std::atomic threadInDriverIteration{std::thread::id()}; EXPECT_CALL(*graph, InDriverIteration(driver.get())).WillRepeatedly([&] { return std::this_thread::get_id() == threadInDriverIteration; }); @@ -279,17 +288,24 @@ MOZ_CAN_RUN_SCRIPT_BOUNDARY { EXPECT_CALL(*graph, NotifyInputData(_, 0, rate, 1, _)).Times(AnyNumber()); EXPECT_CALL(*graph, NotifyInputData(_, ignoredFrameCount, _, _, _)).Times(0); EXPECT_CALL(*graph, DeviceChanged); + Result expected = + Err(CUBEB_ERROR_NOT_SUPPORTED); + EXPECT_CALL(*graph, NotifySetRequestedInputProcessingParamsResult( + driver.get(), CUBEB_INPUT_PROCESSING_PARAM_NONE, + Eq(std::ref(expected)))); graph->SetCurrentDriver(driver); graph->SetEnsureNextIteration(true); // This starts the fallback driver. + auto initPromise = TakeN(cubeb->StreamInitEvent(), 1); driver->Start(); - RefPtr stream = WaitFor(cubeb->StreamInitEvent()); + auto [stream] = WaitFor(initPromise).unwrap()[0]; // Wait for the audio driver to have started the stream before running data // callbacks. driver->Start() does a dispatch to the cubeb operation thread // and starts the stream there. - nsCOMPtr cubebOpThread = CUBEB_TASK_THREAD; + nsCOMPtr cubebOpThread = + CubebUtils::GetCubebOperationThread(); MOZ_ALWAYS_SUCCEEDS(SyncRunnable::DispatchToThread( cubebOpThread, NS_NewRunnableFunction(__func__, [] {}))); @@ -393,85 +409,412 @@ MOZ_CAN_RUN_SCRIPT_BOUNDARY { auto graph = MakeRefPtr(rate); auto driver = MakeRefPtr( - graph, nullptr, rate, 2, 1, nullptr, (void*)1, AudioInputType::Voice); + graph, nullptr, rate, 2, 1, nullptr, (void*)1, AudioInputType::Voice, + CUBEB_INPUT_PROCESSING_PARAM_NONE); EXPECT_FALSE(driver->ThreadRunning()) << "Verify thread is not running"; EXPECT_FALSE(driver->IsStarted()) << "Verify thread is not started"; auto newDriver = MakeRefPtr( - graph, nullptr, rate, 2, 1, nullptr, (void*)1, AudioInputType::Voice); + graph, nullptr, rate, 2, 1, nullptr, (void*)1, AudioInputType::Voice, + CUBEB_INPUT_PROCESSING_PARAM_NONE); EXPECT_FALSE(newDriver->ThreadRunning()) << "Verify thread is not running"; EXPECT_FALSE(newDriver->IsStarted()) << "Verify thread is not started"; #ifdef DEBUG - std::atomic threadInDriverIteration( - (std::this_thread::get_id())); + std::atomic threadInDriverIteration{ + std::this_thread::get_id()}; EXPECT_CALL(*graph, InDriverIteration(_)).WillRepeatedly([&] { return std::this_thread::get_id() == threadInDriverIteration; }); #endif EXPECT_CALL(*graph, NotifyInputData(_, 0, rate, 1, _)).Times(AnyNumber()); - EXPECT_CALL(*graph, DeviceChanged); + // This only happens if the first fallback driver is stopped by the audio + // driver handover rather than the driver switch. It happens when the + // subsequent audio callback performs the switch. + EXPECT_CALL(*graph, NotifyInputStopped()).Times(AtMost(1)); + Result expected = + Err(CUBEB_ERROR_NOT_SUPPORTED); + EXPECT_CALL(*graph, NotifySetRequestedInputProcessingParamsResult( + driver.get(), CUBEB_INPUT_PROCESSING_PARAM_NONE, + Eq(std::ref(expected)))); + EXPECT_CALL(*graph, NotifySetRequestedInputProcessingParamsResult( + newDriver.get(), CUBEB_INPUT_PROCESSING_PARAM_NONE, + Eq(std::ref(expected)))); graph->SetCurrentDriver(driver); graph->SetEnsureNextIteration(true); + auto initPromise = TakeN(cubeb->StreamInitEvent(), 1); // This starts the fallback driver. driver->Start(); - RefPtr stream = WaitFor(cubeb->StreamInitEvent()); + RefPtr stream; + std::tie(stream) = WaitFor(initPromise).unwrap()[0]; // Wait for the audio driver to have started or the DeviceChanged event will // be ignored. driver->Start() does a dispatch to the cubeb operation thread // and starts the stream there. - nsCOMPtr cubebOpThread = CUBEB_TASK_THREAD; + nsCOMPtr cubebOpThread = + CubebUtils::GetCubebOperationThread(); MOZ_ALWAYS_SUCCEEDS(SyncRunnable::DispatchToThread( cubebOpThread, NS_NewRunnableFunction(__func__, [] {}))); -#ifdef DEBUG - AutoSetter as(threadInDriverIteration, std::this_thread::get_id()); -#endif + initPromise = TakeN(cubeb->StreamInitEvent(), 1); + Monitor mon(__func__); + bool canContinueToStartNextDriver = false; + bool continued = false; // This marks the audio driver as running. EXPECT_EQ(stream->ManualDataCallback(0), MockCubebStream::KeepProcessing::Yes); - // If a fallback driver callback happens between the audio callback above, and - // the SwitchTo below, the audio driver will perform the switch instead of the - // fallback since the fallback will have stopped. This test may therefore - // intermittently take different code paths. - - // Stop the fallback driver by switching audio driver in the graph. - { - Monitor mon(__func__); - MonitorAutoLock lock(mon); - bool switched = false; - graph->SwitchTo(newDriver, NS_NewRunnableFunction(__func__, [&] { - MonitorAutoLock lock(mon); - switched = true; - lock.Notify(); - })); - while (!switched) { - lock.Wait(); - } + // To satisfy TSAN's lock-order-inversion checking we avoid locking stream's + // mMutex (by calling ManualDataCallback) under mon. The SwitchTo runnable + // below already locks mon under stream's mMutex. + MonitorAutoLock lock(mon); + + // If a fallback driver callback happens between the audio callback + // above, and the SwitchTo below, the driver will enter + // `FallbackDriverState::None`, relying on the audio driver to + // iterate the graph, including performing the driver switch. This + // test may therefore intermittently take different code paths. + // Note however that the fallback driver runs every ~10ms while the + // time from the manual callback above to telling the mock graph to + // switch drivers below is much much shorter. The vast majority of + // test runs will exercise the intended code path. + + // Make the fallback driver enter FallbackDriverState::Stopped by + // switching audio driver in the graph. + graph->SwitchTo(newDriver, NS_NewRunnableFunction(__func__, [&] { + MonitorAutoLock lock(mon); + // Block the fallback driver on its thread until + // the test on main thread has finished testing + // what it needs. + while (!canContinueToStartNextDriver) { + lock.Wait(); + } + // Notify the test that it can take these + // variables off the stack now. + continued = true; + lock.Notify(); + })); + + // Wait for the fallback driver to stop running. + while (driver->OnFallback()) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); } - { + if (driver->HasFallback()) { + // Driver entered FallbackDriverState::Stopped as desired. + // Proceed with a DeviceChangedCallback. + + EXPECT_CALL(*graph, DeviceChanged); + + { #ifdef DEBUG - AutoSetter as(threadInDriverIteration, std::thread::id()); + AutoSetter as(threadInDriverIteration, std::thread::id()); #endif - // After stopping the fallback driver, but before newDriver has stopped the - // old audio driver, fire a DeviceChanged event to ensure it is handled - // properly. - AudioCallbackDriver::DeviceChangedCallback_s(driver); + // After stopping the fallback driver, but before newDriver has + // stopped the old audio driver, fire a DeviceChanged event to + // ensure it is handled properly. + AudioCallbackDriver::DeviceChangedCallback_s(driver); + } + + EXPECT_FALSE(driver->OnFallback()) + << "DeviceChangedCallback after stopping must not start the " + "fallback driver again"; } + // Iterate the audio driver on a background thread in case the fallback + // driver completed the handover to the audio driver before the switch + // above. Doing the switch would deadlock as the switch runnable waits on + // mon. + NS_DispatchBackgroundTask(NS_NewRunnableFunction( + "DeviceChangeAfterStop::postSwitchManualAudioCallback", [stream] { + // An audio callback after switching must tell the stream to stop. + EXPECT_EQ(stream->ManualDataCallback(0), + MockCubebStream::KeepProcessing::No); + })); + + // Unblock the fallback driver. + canContinueToStartNextDriver = true; + lock.Notify(); + + // Wait for the fallback driver to continue, so we can clear the + // stack. + while (!continued) { + lock.Wait(); + } + + // Wait for newDriver's cubeb stream to init. + std::tie(stream) = WaitFor(initPromise).unwrap()[0]; + graph->StopIterating(); newDriver->EnsureNextIteration(); while (newDriver->OnFallback()) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); } - // This will block until all events have been queued. - MOZ_KnownLive(driver)->Shutdown(); - MOZ_KnownLive(newDriver)->Shutdown(); + { +#ifdef DEBUG + AutoSetter as(threadInDriverIteration, std::thread::id()); +#endif + EXPECT_EQ(stream->ManualDataCallback(0), + MockCubebStream::KeepProcessing::No); + } + // Drain the event queue. NS_ProcessPendingEvents(nullptr); } + +void TestInputProcessingOnStart( + MockCubeb* aCubeb, cubeb_input_processing_params aRequested, + const Result& aExpected) + MOZ_CAN_RUN_SCRIPT_BOUNDARY { + const TrackRate rate = 44100; + + auto graph = MakeRefPtr>(rate); + auto driver = MakeRefPtr( + graph, nullptr, rate, 2, 1, nullptr, nullptr, AudioInputType::Voice, + aRequested); + EXPECT_FALSE(driver->ThreadRunning()) << "Verify thread is not running"; + EXPECT_FALSE(driver->IsStarted()) << "Verify thread is not started"; + +#ifdef DEBUG + std::atomic_bool inGraphIteration{false}; + ON_CALL(*graph, InDriverIteration(_)).WillByDefault([&] { + return inGraphIteration.load() && NS_IsMainThread(); + }); +#endif + bool notified = false; + EXPECT_CALL(*graph, NotifyInputStopped).Times(0); + EXPECT_CALL(*graph, NotifySetRequestedInputProcessingParamsResult( + driver.get(), aRequested, Eq(std::ref(aExpected)))) + .WillOnce([&] { notified = true; }); + + graph->SetCurrentDriver(driver); + auto initPromise = TakeN(aCubeb->StreamInitEvent(), 1); + driver->Start(); + auto [stream] = WaitFor(initPromise).unwrap()[0]; + + // Wait for the audio driver to have started the stream before running data + // callbacks. driver->Start() does a dispatch to the cubeb operation thread + // and starts the stream there. + nsCOMPtr cubebOpThread = + CubebUtils::GetCubebOperationThread(); + MOZ_ALWAYS_SUCCEEDS(SyncRunnable::DispatchToThread( + cubebOpThread, NS_NewRunnableFunction(__func__, [] {}))); + + // This makes the fallback driver stop on its next callback. + { +#ifdef DEBUG + AutoSetter as(inGraphIteration, true); +#endif + while (driver->OnFallback()) { + stream->ManualDataCallback(0); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + } + + while (!notified) { + NS_ProcessNextEvent(); + } + + // This will block untill all events have been executed. + MOZ_KnownLive(driver)->Shutdown(); + EXPECT_FALSE(driver->ThreadRunning()) << "Verify thread is not running"; + EXPECT_FALSE(driver->IsStarted()) << "Verify thread is not started"; +} + +TEST(TestAudioCallbackDriver, InputProcessingOnStart) +{ + constexpr cubeb_input_processing_params allParams = + CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION | + CUBEB_INPUT_PROCESSING_PARAM_AUTOMATIC_GAIN_CONTROL | + CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION | + CUBEB_INPUT_PROCESSING_PARAM_VOICE_ISOLATION; + + MockCubeb* cubeb = new MockCubeb(MockCubeb::RunningMode::Manual); + CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext()); + + // Not supported by backend. + cubeb->SetSupportedInputProcessingParams(CUBEB_INPUT_PROCESSING_PARAM_NONE, + CUBEB_ERROR_NOT_SUPPORTED); + TestInputProcessingOnStart(cubeb, + CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION, + Err(CUBEB_ERROR_NOT_SUPPORTED)); + + // Not supported by params. + cubeb->SetSupportedInputProcessingParams(CUBEB_INPUT_PROCESSING_PARAM_NONE, + CUBEB_OK); + TestInputProcessingOnStart(cubeb, + CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION, + CUBEB_INPUT_PROCESSING_PARAM_NONE); + + // Successful all. + cubeb->SetSupportedInputProcessingParams(allParams, CUBEB_OK); + TestInputProcessingOnStart(cubeb, allParams, allParams); + + // Successful partial. + TestInputProcessingOnStart(cubeb, + CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION, + CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION); + + // Not supported by stream. + cubeb->SetInputProcessingApplyRv(CUBEB_ERROR); + TestInputProcessingOnStart( + cubeb, CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION, Err(CUBEB_ERROR)); +} + +TEST(TestAudioCallbackDriver, InputProcessingWhileRunning) +MOZ_CAN_RUN_SCRIPT_BOUNDARY { + constexpr TrackRate rate = 44100; + constexpr cubeb_input_processing_params allParams = + CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION | + CUBEB_INPUT_PROCESSING_PARAM_AUTOMATIC_GAIN_CONTROL | + CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION | + CUBEB_INPUT_PROCESSING_PARAM_VOICE_ISOLATION; + constexpr int applyError = 99; + + int numNotifications = 0; + const auto signal = [&]() mutable { + MOZ_ASSERT(NS_IsMainThread()); + ++numNotifications; + }; + const auto waitForSignal = [&](int aNotification) { + while (numNotifications < aNotification) { + NS_ProcessNextEvent(); + } + }; + MockCubeb* cubeb = new MockCubeb(MockCubeb::RunningMode::Manual); + CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext()); + + auto graph = MakeRefPtr>(rate); + auto driver = MakeRefPtr( + graph, nullptr, rate, 2, 1, nullptr, nullptr, AudioInputType::Voice, + CUBEB_INPUT_PROCESSING_PARAM_NONE); + EXPECT_FALSE(driver->ThreadRunning()) << "Verify thread is not running"; + EXPECT_FALSE(driver->IsStarted()) << "Verify thread is not started"; + + EXPECT_CALL(*graph, NotifyInputStopped).Times(0); + // Expectations + const Result noneResult = + CUBEB_INPUT_PROCESSING_PARAM_NONE; + const Result aecResult = + CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION; + const Result allResult = allParams; + const Result notSupportedResult = + Err(CUBEB_ERROR_NOT_SUPPORTED); + const Result applyErrorResult = + Err(applyError); + { + InSequence s; + + // Notified on start. + EXPECT_CALL(*graph, NotifySetRequestedInputProcessingParamsResult( + driver.get(), CUBEB_INPUT_PROCESSING_PARAM_NONE, + Eq(std::ref(notSupportedResult)))) + .WillOnce(signal); + // Not supported by backend. + EXPECT_CALL(*graph, NotifySetRequestedInputProcessingParamsResult( + driver.get(), + CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION, + Eq(std::ref(notSupportedResult)))) + .WillOnce(signal); + // Not supported by params. + EXPECT_CALL(*graph, NotifySetRequestedInputProcessingParamsResult( + driver.get(), + CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION, + Eq(std::ref(noneResult)))) + .WillOnce(signal); + // Successful all. + EXPECT_CALL(*graph, NotifySetRequestedInputProcessingParamsResult( + driver.get(), allParams, Eq(std::ref(allResult)))) + .WillOnce(signal); + // Successful partial. + EXPECT_CALL(*graph, NotifySetRequestedInputProcessingParamsResult( + driver.get(), + CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION, + Eq(std::ref(aecResult)))) + .WillOnce(signal); + // Not supported by stream. + EXPECT_CALL(*graph, NotifySetRequestedInputProcessingParamsResult( + driver.get(), + CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION, + Eq(std::ref(applyErrorResult)))) + .WillOnce(signal); + } + +#ifdef DEBUG + std::atomic_bool inGraphIteration{false}; + ON_CALL(*graph, InDriverIteration(_)).WillByDefault([&] { + return inGraphIteration.load() && NS_IsMainThread(); + }); +#endif + + const auto setParams = [&](cubeb_input_processing_params aParams) { + { +#ifdef DEBUG + AutoSetter as(inGraphIteration, true); +#endif + driver->SetRequestedInputProcessingParams(aParams); + } + }; + + graph->SetCurrentDriver(driver); + auto initPromise = TakeN(cubeb->StreamInitEvent(), 1); + driver->Start(); + auto [stream] = WaitFor(initPromise).unwrap()[0]; + + // Wait for the audio driver to have started the stream before running data + // callbacks. driver->Start() does a dispatch to the cubeb operation thread + // and starts the stream there. + nsCOMPtr cubebOpThread = + CubebUtils::GetCubebOperationThread(); + MOZ_ALWAYS_SUCCEEDS(SyncRunnable::DispatchToThread( + cubebOpThread, NS_NewRunnableFunction(__func__, [] {}))); + + // This makes the fallback driver stop on its next callback. + + { +#ifdef DEBUG + AutoSetter as(inGraphIteration, true); +#endif + while (driver->OnFallback()) { + stream->ManualDataCallback(0); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + } + waitForSignal(1); + + // Not supported by backend. + cubeb->SetSupportedInputProcessingParams(CUBEB_INPUT_PROCESSING_PARAM_NONE, + CUBEB_ERROR_NOT_SUPPORTED); + setParams(CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION); + waitForSignal(2); + + // Not supported by params. + cubeb->SetSupportedInputProcessingParams(CUBEB_INPUT_PROCESSING_PARAM_NONE, + CUBEB_OK); + setParams(CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION); + waitForSignal(3); + + // Successful all. + cubeb->SetSupportedInputProcessingParams(allParams, CUBEB_OK); + setParams(allParams); + waitForSignal(4); + + // Successful partial. + setParams(CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION); + waitForSignal(5); + + // Not supported by stream. + cubeb->SetInputProcessingApplyRv(applyError); + setParams(CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION); + waitForSignal(6); + + // This will block untill all events have been executed. + MOZ_KnownLive(driver)->Shutdown(); + EXPECT_FALSE(driver->ThreadRunning()) << "Verify thread is not running"; + EXPECT_FALSE(driver->IsStarted()) << "Verify thread is not started"; +} + +} // namespace mozilla -- cgit v1.2.3