/* -*- 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/. */
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_METHOD(void, NotifyInputStopped, ());
MOCK_METHOD(void, NotifyInputData,
(const AudioDataValue*, size_t, TrackRate, uint32_t, uint32_t));
MOCK_METHOD(void, NotifySetRequestedInputProcessingParamsResult,
(AudioCallbackDriver*, int,
(Result<cubeb_input_processing_params, int>&&)));
MOCK_METHOD(void, DeviceChanged, ()); #ifdef DEBUG
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. */
IterationResult OneIteration(GraphTime aStateComputedTime, GraphTime,
MixerCallbackReceiver* aMixerReceiver) {
GraphDriver* driver = mCurrentDriver; if (aMixerReceiver) {
mMixer.StartMixing();
mMixer.Mix(nullptr, driver->AsAudioCallbackDriver()->OutputChannelCount(),
aStateComputedTime - mStateComputedTime, mSampleRate);
aMixerReceiver->MixerCallback(mMixer.MixedChunk(), mSampleRate);
} if (aStateComputedTime != mStateComputedTime) {
mFramesIteratedEvent.Notify(aStateComputedTime - mStateComputedTime);
++mIterationCount;
}
mStateComputedTime = aStateComputedTime; if (!mKeepProcessing) { return IterationResult::CreateStop(
NS_NewRunnableFunction(__func__, [] {}));
} if (auto guard = mNextDriver.Lock(); guard->isSome()) { auto tup = guard->extract(); constauto& [driver, switchedRunnable] = tup; return IterationResult::CreateSwitchDriver(driver, switchedRunnable);
} if (mEnsureNextIteration) {
driver->EnsureNextIteration();
} return IterationResult::CreateStillProcessing();
} void SetEnsureNextIteration(bool aEnsure) { mEnsureNextIteration = aEnsure; }
RefPtr<AudioCallbackDriver> driver; auto graph = MakeRefPtr<NiceMock<MockGraphInterface>>(rate);
EXPECT_CALL(*graph, NotifyInputStopped).Times(0);
driver = MakeRefPtr<AudioCallbackDriver>(
graph, nullptr, rate, 2, 0, nullptr, nullptr, AudioInputType::Unknown,
Some<AudioInputProcessingParamsRequest>(
{0, 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);
driver->Start(); // Allow some time to "play" audio.
std::this_thread::sleep_for(std::chrono::milliseconds(200));
EXPECT_TRUE(driver->ThreadRunning()) << "Verify thread is running";
EXPECT_TRUE(driver->IsStarted()) << "Verify thread is started";
// 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";
}
auto initPromise = TakeN(cubeb->StreamInitEvent(), 1);
driver->Start(); auto [stream] = WaitFor(initPromise).unwrap()[0];
cubeb->SetStreamStartFreezeEnabled(false);
const size_t fallbackIterations = 3;
WaitUntil(graph->FramesIteratedEvent(), [&](uint32_t aFrames) { const GraphTime tenMillis = aRate / 100; // An iteration is always rounded upwards to the next full block. const GraphTime tenMillisIteration =
MediaTrackGraphImpl::RoundUpToEndOfAudioBlock(tenMillis); // The iteration may be smaller because up to an extra block may have been // processed and buffered. const GraphTime tenMillisMinIteration =
tenMillisIteration - WEBAUDIO_BLOCK_SIZE; // An iteration must be at least one audio block. const GraphTime minIteration =
std::max<GraphTime>(WEBAUDIO_BLOCK_SIZE, tenMillisMinIteration);
EXPECT_GE(aFrames, minIteration)
<< "Fallback driver iteration >= 10ms, modulo an audio block";
EXPECT_LT(aFrames, static_cast<size_t>(aRate))
<< "Fallback driver iteration <1s (sanity)"; return graph->IterationCount() >= fallbackIterations;
});
SpinEventLoopUntil( "processed at least 100ms of audio data from stream callback"_ns,
[&] { return processedFrameCount >= aRate / 10; });
// This will block until all events have been queued.
MOZ_KnownLive(driver)->Shutdown(); // Process processListener events.
NS_ProcessPendingEvents(mainThread);
processedListener.Disconnect();
graph->SetCurrentDriver(driver);
graph->SetEnsureNextIteration(true); // This starts the fallback 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.
EXPECT_EQ(stream->ManualDataCallback(0),
MockCubebStream::KeepProcessing::Yes);
{ #ifdef DEBUG
AutoSetter as(threadInDriverIteration, std::this_thread::get_id()); #endif while (driver->OnFallback()) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}
// Flag that the stream should force a devicechange event.
stream->NotifyDeviceChangedNow();
// The audio driver should now have switched on the fallback driver again.
{ #ifdef DEBUG
AutoSetter as(threadInDriverIteration, std::this_thread::get_id()); #endif
EXPECT_TRUE(driver->OnFallback());
}
// Make sure that the audio driver can handle (and ignore) data callbacks for // a little while after the devicechange callback. Cubeb does not provide // ordering guarantees here. auto start = TimeStamp::Now(); while (start + TimeDuration::FromMilliseconds(5) > TimeStamp::Now()) {
EXPECT_EQ(stream->ManualDataCallback(ignoredFrameCount),
MockCubebStream::KeepProcessing::Yes);
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
// Let the fallback driver start and spin for one second.
std::this_thread::sleep_for(std::chrono::seconds(1));
// Tell the fallback driver to hand over to the audio driver which has // finished changing devices.
EXPECT_EQ(stream->ManualDataCallback(0),
MockCubebStream::KeepProcessing::Yes);
// Wait for the fallback to stop.
{ #ifdef DEBUG
AutoSetter as(threadInDriverIteration, std::this_thread::get_id()); #endif while (driver->OnFallback()) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}
auto wallClockDuration =
media::TimeUnit::FromTimeDuration(wallClockEnd - wallClockStart); auto graphClockDuration =
media::TimeUnit(CheckedInt64(graphClockEnd) - graphClockStart, rate);
// Check that the time while we switched devices was accounted for by the // fallback driver.
EXPECT_NEAR(
wallClockDuration.ToSeconds(), graphClockDuration.ToSeconds(), #ifdef XP_MACOSX // SystemClockDriver on macOS in CI is underrunning, i.e. the driver // thread when waiting for the next iteration waits too long. Therefore // the graph clock is unable to keep up with wall clock.
wallClockDuration.ToSeconds() * 0.8 #else
0.1 #endif
); // Check that each fallback driver was of reasonable cadence. It's a thread // that tries to run a task every 10ms. Check that the average callback // interval i falls in 8ms ≤ i ≤ 40ms. auto fallbackCadence =
graphClockDuration / static_cast<int64_t>(iterationCountEnd - iterationCountStart);
EXPECT_LE(8, fallbackCadence.ToMilliseconds());
EXPECT_LE(fallbackCadence.ToMilliseconds(), 40.0);
// This will block until all events have been queued.
MOZ_KnownLive(driver)->Shutdown(); // Drain the event queue.
NS_ProcessPendingEvents(nullptr);
}
auto graph = MakeRefPtr<MockGraphInterface>(rate); auto driver = MakeRefPtr<AudioCallbackDriver>(
graph, nullptr, rate, 2, 1, nullptr, (void*)1, AudioInputType::Voice,
Some<AudioInputProcessingParamsRequest>(
{99, 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,
Some<AudioInputProcessingParamsRequest>(
{99, 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()};
EXPECT_CALL(*graph, InDriverIteration(_)).WillRepeatedly([&] { return std::this_thread::get_id() == threadInDriverIteration;
}); #endif
EXPECT_CALL(*graph, NotifyInputData(_, 0, rate, 1, _)).Times(AnyNumber()); // 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(), 99, Eq(std::ref(expected))));
EXPECT_CALL(*graph, NotifySetRequestedInputProcessingParamsResult(
newDriver.get(), 99, 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;
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 =
CubebUtils::GetCubebOperationThread();
MOZ_ALWAYS_SUCCEEDS(SyncRunnable::DispatchToThread(
cubebOpThread, NS_NewRunnableFunction(__func__, [] {})));
// This marks the audio driver as running.
EXPECT_EQ(stream->ManualDataCallback(0),
MockCubebStream::KeepProcessing::Yes);
// 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()); #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);
}
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));
}
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";
}
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.
// Not supported by backend.
cubeb->SetSupportedInputProcessingParams(CUBEB_INPUT_PROCESSING_PARAM_NONE,
CUBEB_ERROR_NOT_SUPPORTED);
setParams(101, CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION);
waitForSignal(101);
// Not supported by params.
cubeb->SetSupportedInputProcessingParams(CUBEB_INPUT_PROCESSING_PARAM_NONE,
CUBEB_OK);
setParams(102, CUBEB_INPUT_PROCESSING_PARAM_ECHO_CANCELLATION);
waitForSignal(102);
// Successful all.
cubeb->SetSupportedInputProcessingParams(allParams, CUBEB_OK);
setParams(103, allParams);
waitForSignal(103);
// Not supported by stream.
cubeb->SetInputProcessingApplyRv(applyError);
setParams(105, CUBEB_INPUT_PROCESSING_PARAM_NOISE_SUPPRESSION);
waitForSignal(105);
// 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
Messung V0.5
¤ Dauer der Verarbeitung: 0.15 Sekunden
(vorverarbeitet)
¤
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.