summaryrefslogtreecommitdiffstats
path: root/dom/media/gtest/TestAudioCallbackDriver.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/gtest/TestAudioCallbackDriver.cpp')
-rw-r--r--dom/media/gtest/TestAudioCallbackDriver.cpp457
1 files changed, 400 insertions, 57 deletions
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<cubeb_input_processing_params, int>&&)));
+ 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<Runnable> 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<AudioCallbackDriver>(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<AudioCallbackDriver>(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<SmartMockCubebStream> 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<T> 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<MockGraphInterface>(rate);
auto driver = MakeRefPtr<AudioCallbackDriver>(
- 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<std::thread::id> threadInDriverIteration((std::thread::id()));
+ std::atomic<std::thread::id> 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<cubeb_input_processing_params, int> 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<SmartMockCubebStream> 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<nsIEventTarget> cubebOpThread = CUBEB_TASK_THREAD;
+ nsCOMPtr<nsIEventTarget> cubebOpThread =
+ CubebUtils::GetCubebOperationThread();
MOZ_ALWAYS_SUCCEEDS(SyncRunnable::DispatchToThread(
cubebOpThread, NS_NewRunnableFunction(__func__, [] {})));
@@ -393,85 +409,412 @@ MOZ_CAN_RUN_SCRIPT_BOUNDARY {
auto graph = MakeRefPtr<MockGraphInterface>(rate);
auto driver = MakeRefPtr<AudioCallbackDriver>(
- 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<AudioCallbackDriver>(
- 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<std::thread::id> threadInDriverIteration(
- (std::this_thread::get_id()));
+ std::atomic<std::thread::id> 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<cubeb_input_processing_params, int> 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<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent());
+ RefPtr<SmartMockCubebStream> 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<nsIEventTarget> cubebOpThread = CUBEB_TASK_THREAD;
+ nsCOMPtr<nsIEventTarget> 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<cubeb_input_processing_params, int>& aExpected)
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY {
+ const TrackRate rate = 44100;
+
+ auto graph = MakeRefPtr<NiceMock<MockGraphInterface>>(rate);
+ auto driver = MakeRefPtr<AudioCallbackDriver>(
+ 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<nsIEventTarget> 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<NiceMock<MockGraphInterface>>(rate);
+ auto driver = MakeRefPtr<AudioCallbackDriver>(
+ 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<cubeb_input_processing_params, int> noneResult =
+ CUBEB_INPUT_PROCESSING_PARAM_NONE;
+ const Result<cubeb_input_processing_params, int> aecResult =
+ CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION;
+ const Result<cubeb_input_processing_params, int> allResult = allParams;
+ const Result<cubeb_input_processing_params, int> notSupportedResult =
+ Err(CUBEB_ERROR_NOT_SUPPORTED);
+ const Result<cubeb_input_processing_params, int> 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<nsIEventTarget> 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