/* -*- 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/. */
usingnamespace 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)) { \
}
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);
});
}
// 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};
/* * 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());
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__);
});
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); });
// 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); });
// 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());
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);
// 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());
// 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);
});
// 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();
// 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);
});
// 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());
// Make sure it's ok to call audio stop again.
DispatchFunction([&] {
track->GraphImpl()->AppendMessage(
MakeUnique<StopNonNativeInput>(track.get()));
});
// 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());
// 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;
// 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>& aListener,
RefPtr<SmartMockCubebStream>& aStream,
uint32_t aChannelCount) {
ASSERT_TRUE(!!aListener);
ASSERT_TRUE(!!aStream);
ASSERT_TRUE(aStream->mHasInput);
ASSERT_NE(aChannelCount, 0U);
// 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);
// 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);
// 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());
// 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());
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;
}
});
// 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);
});
// 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);
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());
// 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();
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. constlong step = 10 * rate * 1111 / 1000 / PR_MSEC_PER_SEC;
// 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);
// 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);
// 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);
}
// 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);
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());
// 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. constlong 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;
}
});
// Close the input and switch driver. while (stream->ManualDataCallback(0) != MockCubebStream::KeepProcessing::No) {
std::cerr << "Waiting for switch...\n";
}
// 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());
// 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;
// Request a new input channel count and expect to have a new stream. auto setNewChannelCount = [&](const RefPtr<AudioProcessingTrack>& 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);
// 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);
// 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); });
// 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);
// 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());
// Set the input channel count of AudioInputProcessing, which will force // MediaTrackGraph to re-evaluate input device, when the MediaTrackGraph is // driven by the SystemClockDriver.
// 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());
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;
}
});
// 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));
}
/* 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);
});
// 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));
}
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);
// 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);
// Wait until non-native input signal reaches the output, when input // processing has run and so has been configured.
WaitFor(primaryStream->FramesVerifiedEvent());
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
// 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);
// 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);
// 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);
});
// 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));
}
// 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);
});
// 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);
// 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);
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];
}
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.