572 lines
20 KiB
C++
572 lines
20 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=2 et sw=2 tw=80: */
|
|
/* 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 https://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "DeviceInputTrack.h"
|
|
|
|
#include "gmock/gmock.h"
|
|
#include "gtest/gtest.h"
|
|
|
|
#include "AudioGenerator.h"
|
|
#include "MediaTrackGraphImpl.h"
|
|
#include "MockCubeb.h"
|
|
#include "mozilla/gtest/WaitFor.h"
|
|
#include "mozilla/StaticPrefs_media.h"
|
|
#include "nsContentUtils.h"
|
|
|
|
using namespace mozilla;
|
|
using testing::NiceMock;
|
|
using testing::Return;
|
|
|
|
namespace {
|
|
#define DispatchFunction(f) \
|
|
NS_DispatchToCurrentThread(NS_NewRunnableFunction(__func__, f))
|
|
} // namespace
|
|
|
|
class MockGraphImpl : public MediaTrackGraphImpl {
|
|
public:
|
|
explicit MockGraphImpl(TrackRate aRate)
|
|
: MediaTrackGraphImpl(0, aRate, nullptr, NS_GetCurrentThread()) {
|
|
ON_CALL(*this, OnGraphThread).WillByDefault(Return(true));
|
|
}
|
|
|
|
void Init(uint32_t aChannels) {
|
|
MediaTrackGraphImpl::Init(OFFLINE_THREAD_DRIVER, DIRECT_DRIVER, aChannels);
|
|
// We have to call `Destroy()` manually in order to break the reference.
|
|
// The reason we don't assign a null driver is because we would add a track
|
|
// to the graph, then it would trigger graph's `EnsureNextIteration()` that
|
|
// requires a non-null driver.
|
|
SetCurrentDriver(new NiceMock<MockDriver>());
|
|
}
|
|
|
|
MOCK_CONST_METHOD0(OnGraphThread, bool());
|
|
MOCK_METHOD1(AppendMessage, void(UniquePtr<ControlMessageInterface>));
|
|
|
|
protected:
|
|
~MockGraphImpl() = default;
|
|
|
|
class MockDriver : public GraphDriver {
|
|
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MockDriver, override);
|
|
|
|
MockDriver() : GraphDriver(nullptr, nullptr, 0) {
|
|
ON_CALL(*this, OnThread).WillByDefault(Return(true));
|
|
ON_CALL(*this, ThreadRunning).WillByDefault(Return(true));
|
|
}
|
|
|
|
MOCK_METHOD0(Start, void());
|
|
MOCK_METHOD0(Shutdown, void());
|
|
MOCK_METHOD0(IterationDuration, uint32_t());
|
|
MOCK_METHOD0(EnsureNextIteration, void());
|
|
MOCK_CONST_METHOD0(OnThread, bool());
|
|
MOCK_CONST_METHOD0(ThreadRunning, bool());
|
|
|
|
protected:
|
|
~MockDriver() = default;
|
|
};
|
|
};
|
|
|
|
class TestDeviceInputTrack : public testing::Test {
|
|
protected:
|
|
TestDeviceInputTrack() : mChannels(2), mRate(44100) {}
|
|
|
|
void SetUp() override {
|
|
mGraph = MakeRefPtr<NiceMock<MockGraphImpl>>(mRate);
|
|
mGraph->Init(mChannels);
|
|
}
|
|
|
|
void TearDown() override { mGraph->Destroy(); }
|
|
|
|
const uint32_t mChannels;
|
|
const TrackRate mRate;
|
|
RefPtr<MockGraphImpl> mGraph;
|
|
};
|
|
|
|
TEST_F(TestDeviceInputTrack, DeviceInputConsumerTrack) {
|
|
class TestDeviceInputConsumerTrack : public DeviceInputConsumerTrack {
|
|
public:
|
|
static TestDeviceInputConsumerTrack* Create(MediaTrackGraph* aGraph) {
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
|
TestDeviceInputConsumerTrack* track =
|
|
new TestDeviceInputConsumerTrack(aGraph->GraphRate());
|
|
aGraph->AddTrack(track);
|
|
return track;
|
|
}
|
|
|
|
void Destroy() {
|
|
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
|
DisconnectDeviceInput();
|
|
DeviceInputConsumerTrack::Destroy();
|
|
}
|
|
|
|
void ProcessInput(GraphTime aFrom, GraphTime aTo,
|
|
uint32_t aFlags) override { /* Ignored */ };
|
|
|
|
uint32_t NumberOfChannels() const override {
|
|
if (mInputs.IsEmpty()) {
|
|
return 0;
|
|
}
|
|
DeviceInputTrack* t = mInputs[0]->GetSource()->AsDeviceInputTrack();
|
|
MOZ_RELEASE_ASSERT(t);
|
|
return t->NumberOfChannels();
|
|
}
|
|
|
|
private:
|
|
explicit TestDeviceInputConsumerTrack(TrackRate aSampleRate)
|
|
: DeviceInputConsumerTrack(aSampleRate) {}
|
|
};
|
|
|
|
class TestAudioDataListener : public AudioDataListener {
|
|
public:
|
|
TestAudioDataListener(uint32_t aChannelCount, bool aIsVoice)
|
|
: mChannelCount(aChannelCount), mIsVoice(aIsVoice) {}
|
|
// Graph thread APIs: AudioDataListenerInterface implementations.
|
|
uint32_t RequestedInputChannelCount(
|
|
MediaTrackGraph* aGraph) const override {
|
|
aGraph->AssertOnGraphThread();
|
|
return mChannelCount;
|
|
}
|
|
cubeb_input_processing_params RequestedInputProcessingParams(
|
|
MediaTrackGraph*) const override {
|
|
return CUBEB_INPUT_PROCESSING_PARAM_NONE;
|
|
}
|
|
bool IsVoiceInput(MediaTrackGraph* aGraph) const override {
|
|
return mIsVoice;
|
|
};
|
|
void DeviceChanged(MediaTrackGraph* aGraph) override { /* Ignored */ }
|
|
void Disconnect(MediaTrackGraph* aGraph) override { /* Ignored */ };
|
|
void NotifySetRequestedInputProcessingParams(
|
|
MediaTrackGraph* aGraph, int aGeneration,
|
|
cubeb_input_processing_params aRequestedParams) override {
|
|
/* Ignored */
|
|
}
|
|
void NotifySetRequestedInputProcessingParamsResult(
|
|
MediaTrackGraph* aGraph, int aGeneration,
|
|
const Result<cubeb_input_processing_params, int>& aResult) override {
|
|
/* Ignored */
|
|
}
|
|
|
|
private:
|
|
~TestAudioDataListener() = default;
|
|
|
|
// Graph thread-only.
|
|
uint32_t mChannelCount;
|
|
// Any thread.
|
|
const bool mIsVoice;
|
|
};
|
|
|
|
const PrincipalHandle testPrincipal =
|
|
MakePrincipalHandle(nsContentUtils::GetSystemPrincipal());
|
|
|
|
const CubebUtils::AudioDeviceID device1 = (void*)1;
|
|
RefPtr<TestAudioDataListener> listener1 = new TestAudioDataListener(1, false);
|
|
RefPtr<TestDeviceInputConsumerTrack> track1 =
|
|
TestDeviceInputConsumerTrack::Create(mGraph);
|
|
track1->ConnectDeviceInput(device1, listener1.get(), testPrincipal);
|
|
EXPECT_TRUE(track1->ConnectedToNativeDevice());
|
|
EXPECT_FALSE(track1->ConnectedToNonNativeDevice());
|
|
|
|
const CubebUtils::AudioDeviceID device2 = (void*)2;
|
|
RefPtr<TestAudioDataListener> listener2 = new TestAudioDataListener(2, false);
|
|
RefPtr<TestDeviceInputConsumerTrack> track2 =
|
|
TestDeviceInputConsumerTrack::Create(mGraph);
|
|
track2->ConnectDeviceInput(device2, listener2.get(), testPrincipal);
|
|
EXPECT_FALSE(track2->ConnectedToNativeDevice());
|
|
EXPECT_TRUE(track2->ConnectedToNonNativeDevice());
|
|
|
|
track2->Destroy();
|
|
mGraph->RemoveTrackGraphThread(track2);
|
|
|
|
track1->Destroy();
|
|
mGraph->RemoveTrackGraphThread(track1);
|
|
}
|
|
|
|
TEST_F(TestDeviceInputTrack, NativeInputTrackData) {
|
|
const uint32_t flags = 0;
|
|
const CubebUtils::AudioDeviceID deviceId = (void*)1;
|
|
|
|
AudioGenerator<AudioDataValue> generator(mChannels, mRate);
|
|
const size_t nrFrames = 10;
|
|
const size_t bufferSize = nrFrames * mChannels;
|
|
nsTArray<AudioDataValue> buffer(bufferSize);
|
|
buffer.AppendElements(bufferSize);
|
|
|
|
const PrincipalHandle testPrincipal =
|
|
MakePrincipalHandle(nsContentUtils::GetSystemPrincipal());
|
|
|
|
// Setup: Create a NativeInputTrack and add it to mGraph
|
|
RefPtr<NativeInputTrack> track =
|
|
new NativeInputTrack(mGraph->GraphRate(), deviceId, testPrincipal);
|
|
mGraph->AddTrack(track);
|
|
|
|
// Main test below:
|
|
|
|
generator.GenerateInterleaved(buffer.Elements(), nrFrames);
|
|
track->NotifyInputData(mGraph.get(), buffer.Elements(), nrFrames, mRate,
|
|
mChannels, 0);
|
|
|
|
track->ProcessInput(0, WEBAUDIO_BLOCK_SIZE + nrFrames, flags);
|
|
EXPECT_EQ(static_cast<size_t>(track->GetEnd()),
|
|
static_cast<size_t>(WEBAUDIO_BLOCK_SIZE) + nrFrames);
|
|
|
|
// Check pre-buffering: null data with PRINCIPAL_HANDLE_NONE principal
|
|
AudioSegment preBuffering;
|
|
preBuffering.AppendSlice(*track->GetData(), 0, WEBAUDIO_BLOCK_SIZE);
|
|
EXPECT_TRUE(preBuffering.IsNull());
|
|
for (AudioSegment::ConstChunkIterator iter(preBuffering); !iter.IsEnded();
|
|
iter.Next()) {
|
|
const AudioChunk& chunk = *iter;
|
|
EXPECT_EQ(chunk.mPrincipalHandle, PRINCIPAL_HANDLE_NONE);
|
|
}
|
|
|
|
// Check rest of the data
|
|
AudioSegment data;
|
|
data.AppendSlice(*track->GetData(), WEBAUDIO_BLOCK_SIZE,
|
|
WEBAUDIO_BLOCK_SIZE + nrFrames);
|
|
nsTArray<AudioDataValue> interleaved;
|
|
size_t sampleCount = data.WriteToInterleavedBuffer(interleaved, mChannels);
|
|
EXPECT_EQ(sampleCount, bufferSize);
|
|
EXPECT_EQ(interleaved, buffer);
|
|
|
|
// Check principal in data
|
|
for (AudioSegment::ConstChunkIterator iter(data); !iter.IsEnded();
|
|
iter.Next()) {
|
|
const AudioChunk& chunk = *iter;
|
|
EXPECT_EQ(chunk.mPrincipalHandle, testPrincipal);
|
|
}
|
|
|
|
// Tear down: Destroy the NativeInputTrack and remove it from mGraph.
|
|
track->Destroy();
|
|
mGraph->RemoveTrackGraphThread(track);
|
|
}
|
|
|
|
class MockEventListener : public AudioInputSource::EventListener {
|
|
public:
|
|
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MockEventListener, override);
|
|
MOCK_METHOD1(AudioDeviceChanged, void(AudioInputSource::Id));
|
|
MOCK_METHOD2(AudioStateCallback,
|
|
void(AudioInputSource::Id,
|
|
AudioInputSource::EventListener::State));
|
|
|
|
private:
|
|
~MockEventListener() = default;
|
|
};
|
|
|
|
TEST_F(TestDeviceInputTrack, StartAndStop) {
|
|
MockCubeb* cubeb = new MockCubeb();
|
|
CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());
|
|
|
|
// Non native input settings
|
|
const AudioInputSource::Id sourceId = 1;
|
|
const CubebUtils::AudioDeviceID deviceId = (CubebUtils::AudioDeviceID)1;
|
|
const uint32_t channels = 2;
|
|
const PrincipalHandle testPrincipal =
|
|
MakePrincipalHandle(nsContentUtils::GetSystemPrincipal());
|
|
const TrackRate rate = 48000;
|
|
|
|
// Setup: Create a NonNativeInputTrack and add it to mGraph.
|
|
RefPtr<NonNativeInputTrack> track =
|
|
new NonNativeInputTrack(mGraph->GraphRate(), deviceId, testPrincipal);
|
|
mGraph->AddTrack(track);
|
|
|
|
// Main test below:
|
|
|
|
// Make sure the NonNativeInputTrack can start and stop its audio correctly.
|
|
{
|
|
auto listener = MakeRefPtr<MockEventListener>();
|
|
EXPECT_CALL(*listener,
|
|
AudioStateCallback(
|
|
sourceId, AudioInputSource::EventListener::State::Started));
|
|
EXPECT_CALL(*listener,
|
|
AudioStateCallback(
|
|
sourceId, AudioInputSource::EventListener::State::Stopped))
|
|
.Times(2);
|
|
|
|
// No input channels and device preference before start.
|
|
EXPECT_EQ(track->NumberOfChannels(), 0U);
|
|
EXPECT_EQ(track->DevicePreference(), AudioInputType::Unknown);
|
|
|
|
DispatchFunction([&] {
|
|
track->StartAudio(MakeRefPtr<AudioInputSource>(
|
|
std::move(listener), sourceId, deviceId, channels, true /* voice */,
|
|
testPrincipal, rate, mGraph->GraphRate()));
|
|
});
|
|
|
|
// Wait for stream creation.
|
|
RefPtr<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent());
|
|
|
|
// Make sure the audio stream and the track's settings are correct.
|
|
EXPECT_TRUE(stream->mHasInput);
|
|
EXPECT_FALSE(stream->mHasOutput);
|
|
EXPECT_EQ(stream->GetInputDeviceID(), deviceId);
|
|
EXPECT_EQ(stream->InputChannels(), channels);
|
|
EXPECT_EQ(stream->SampleRate(), static_cast<uint32_t>(rate));
|
|
EXPECT_EQ(track->NumberOfChannels(), channels);
|
|
EXPECT_EQ(track->DevicePreference(), AudioInputType::Voice);
|
|
|
|
// Wait for stream callbacks.
|
|
Unused << WaitFor(stream->FramesProcessedEvent());
|
|
|
|
DispatchFunction([&] { track->StopAudio(); });
|
|
|
|
// Wait for stream destroy.
|
|
Unused << WaitFor(cubeb->StreamDestroyEvent());
|
|
|
|
// No input channels and device preference after stop.
|
|
EXPECT_EQ(track->NumberOfChannels(), 0U);
|
|
EXPECT_EQ(track->DevicePreference(), AudioInputType::Unknown);
|
|
}
|
|
|
|
// Make sure the NonNativeInputTrack can restart its audio correctly.
|
|
{
|
|
auto listener = MakeRefPtr<MockEventListener>();
|
|
EXPECT_CALL(*listener,
|
|
AudioStateCallback(
|
|
sourceId, AudioInputSource::EventListener::State::Started));
|
|
EXPECT_CALL(*listener,
|
|
AudioStateCallback(
|
|
sourceId, AudioInputSource::EventListener::State::Stopped))
|
|
.Times(2);
|
|
|
|
DispatchFunction([&] {
|
|
track->StartAudio(MakeRefPtr<AudioInputSource>(
|
|
std::move(listener), sourceId, deviceId, channels, true,
|
|
testPrincipal, rate, mGraph->GraphRate()));
|
|
});
|
|
|
|
// Wait for stream creation.
|
|
RefPtr<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent());
|
|
EXPECT_TRUE(stream->mHasInput);
|
|
EXPECT_FALSE(stream->mHasOutput);
|
|
EXPECT_EQ(stream->GetInputDeviceID(), deviceId);
|
|
EXPECT_EQ(stream->InputChannels(), channels);
|
|
EXPECT_EQ(stream->SampleRate(), static_cast<uint32_t>(rate));
|
|
|
|
// Wait for stream callbacks.
|
|
Unused << WaitFor(stream->FramesProcessedEvent());
|
|
|
|
DispatchFunction([&] { track->StopAudio(); });
|
|
|
|
// Wait for stream destroy.
|
|
Unused << WaitFor(cubeb->StreamDestroyEvent());
|
|
}
|
|
|
|
// Tear down: Destroy the NativeInputTrack and remove it from mGraph.
|
|
track->Destroy();
|
|
mGraph->RemoveTrackGraphThread(track);
|
|
}
|
|
|
|
TEST_F(TestDeviceInputTrack, NonNativeInputTrackData) {
|
|
MockCubeb* cubeb = new MockCubeb();
|
|
CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());
|
|
|
|
// Graph settings
|
|
const uint32_t flags = 0;
|
|
const GraphTime frames = 440;
|
|
|
|
// Non native input settings
|
|
const AudioInputSource::Id sourceId = 1;
|
|
const CubebUtils::AudioDeviceID deviceId = (CubebUtils::AudioDeviceID)1;
|
|
const uint32_t channels = 2;
|
|
const PrincipalHandle testPrincipal =
|
|
MakePrincipalHandle(nsContentUtils::GetSystemPrincipal());
|
|
const TrackRate rate = 48000;
|
|
|
|
// Setup: Create a NonNativeInputTrack and add it to mGraph.
|
|
RefPtr<NonNativeInputTrack> track =
|
|
new NonNativeInputTrack(mGraph->GraphRate(), deviceId, testPrincipal);
|
|
mGraph->AddTrack(track);
|
|
|
|
// Main test below:
|
|
|
|
// Make sure we get null data if the track is not started yet.
|
|
GraphTime current = 0;
|
|
GraphTime next = MediaTrackGraphImpl::RoundUpToEndOfAudioBlock(frames);
|
|
ASSERT_NE(current, next); // Make sure we have data produced in ProcessInput.
|
|
|
|
track->ProcessInput(current, next, flags);
|
|
{
|
|
AudioSegment data;
|
|
data.AppendSegment(track->GetData<AudioSegment>());
|
|
EXPECT_TRUE(data.IsNull());
|
|
}
|
|
|
|
// Make sure we get the AudioInputSource's data once we start the track.
|
|
|
|
current = next;
|
|
next = MediaTrackGraphImpl::RoundUpToEndOfAudioBlock(2 * frames);
|
|
ASSERT_NE(current, next); // Make sure we have data produced in ProcessInput.
|
|
|
|
auto listener = MakeRefPtr<MockEventListener>();
|
|
EXPECT_CALL(*listener,
|
|
AudioStateCallback(
|
|
sourceId, AudioInputSource::EventListener::State::Started));
|
|
EXPECT_CALL(*listener,
|
|
AudioStateCallback(
|
|
sourceId, AudioInputSource::EventListener::State::Stopped))
|
|
.Times(2);
|
|
|
|
DispatchFunction([&] {
|
|
track->StartAudio(MakeRefPtr<AudioInputSource>(
|
|
std::move(listener), sourceId, deviceId, channels, true, testPrincipal,
|
|
rate, mGraph->GraphRate()));
|
|
});
|
|
RefPtr<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent());
|
|
EXPECT_TRUE(stream->mHasInput);
|
|
EXPECT_FALSE(stream->mHasOutput);
|
|
EXPECT_EQ(stream->GetInputDeviceID(), deviceId);
|
|
EXPECT_EQ(stream->InputChannels(), channels);
|
|
EXPECT_EQ(stream->SampleRate(), static_cast<uint32_t>(rate));
|
|
|
|
// Check audio data.
|
|
Unused << WaitFor(stream->FramesProcessedEvent());
|
|
track->ProcessInput(current, next, flags);
|
|
{
|
|
AudioSegment data;
|
|
data.AppendSlice(*track->GetData<AudioSegment>(), current, next);
|
|
EXPECT_FALSE(data.IsNull());
|
|
for (AudioSegment::ConstChunkIterator iter(data); !iter.IsEnded();
|
|
iter.Next()) {
|
|
EXPECT_EQ(iter->mChannelData.Length(), channels);
|
|
EXPECT_EQ(iter->mPrincipalHandle, testPrincipal);
|
|
}
|
|
}
|
|
|
|
// Stop the track and make sure it produces null data again.
|
|
current = next;
|
|
next = MediaTrackGraphImpl::RoundUpToEndOfAudioBlock(3 * frames);
|
|
ASSERT_NE(current, next); // Make sure we have data produced in ProcessInput.
|
|
|
|
DispatchFunction([&] { track->StopAudio(); });
|
|
Unused << WaitFor(cubeb->StreamDestroyEvent());
|
|
|
|
track->ProcessInput(current, next, flags);
|
|
{
|
|
AudioSegment data;
|
|
data.AppendSlice(*track->GetData<AudioSegment>(), current, next);
|
|
EXPECT_TRUE(data.IsNull());
|
|
}
|
|
|
|
// Tear down: Destroy the NonNativeInputTrack and remove it from mGraph.
|
|
track->Destroy();
|
|
mGraph->RemoveTrackGraphThread(track);
|
|
}
|
|
|
|
TEST_F(TestDeviceInputTrack, NonNativeDeviceChangedCallback) {
|
|
MockCubeb* cubeb = new MockCubeb();
|
|
CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());
|
|
|
|
// Non native input settings
|
|
const AudioInputSource::Id sourceId = 1;
|
|
const CubebUtils::AudioDeviceID deviceId = (CubebUtils::AudioDeviceID)1;
|
|
const uint32_t channels = 2;
|
|
const PrincipalHandle testPrincipal =
|
|
MakePrincipalHandle(nsContentUtils::GetSystemPrincipal());
|
|
const TrackRate rate = 48000;
|
|
|
|
// Setup: Create a NonNativeInputTrack and add it to mGraph.
|
|
RefPtr<NonNativeInputTrack> track =
|
|
new NonNativeInputTrack(mGraph->GraphRate(), deviceId, testPrincipal);
|
|
mGraph->AddTrack(track);
|
|
|
|
// Main test below:
|
|
|
|
auto listener = MakeRefPtr<MockEventListener>();
|
|
EXPECT_CALL(*listener, AudioDeviceChanged(sourceId));
|
|
EXPECT_CALL(*listener,
|
|
AudioStateCallback(
|
|
sourceId, AudioInputSource::EventListener::State::Started));
|
|
EXPECT_CALL(*listener,
|
|
AudioStateCallback(
|
|
sourceId, AudioInputSource::EventListener::State::Stopped))
|
|
.Times(2);
|
|
|
|
// Launch and start an audio stream.
|
|
DispatchFunction([&] {
|
|
track->StartAudio(MakeRefPtr<AudioInputSource>(
|
|
std::move(listener), sourceId, deviceId, channels, true, testPrincipal,
|
|
rate, mGraph->GraphRate()));
|
|
});
|
|
RefPtr<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent());
|
|
EXPECT_TRUE(stream->mHasInput);
|
|
EXPECT_FALSE(stream->mHasOutput);
|
|
EXPECT_EQ(stream->GetInputDeviceID(), deviceId);
|
|
EXPECT_EQ(stream->InputChannels(), channels);
|
|
EXPECT_EQ(stream->SampleRate(), static_cast<uint32_t>(rate));
|
|
|
|
// Make sure the stream is running.
|
|
Unused << WaitFor(stream->FramesProcessedEvent());
|
|
|
|
// Fire a device-changed callback.
|
|
DispatchFunction([&] { stream->ForceDeviceChanged(); });
|
|
WaitFor(stream->DeviceChangeForcedEvent());
|
|
|
|
// Stop and destroy the stream.
|
|
DispatchFunction([&] { track->StopAudio(); });
|
|
Unused << WaitFor(cubeb->StreamDestroyEvent());
|
|
|
|
// Tear down: Destroy the NonNativeInputTrack and remove it from mGraph.
|
|
track->Destroy();
|
|
mGraph->RemoveTrackGraphThread(track);
|
|
}
|
|
|
|
TEST_F(TestDeviceInputTrack, NonNativeErrorCallback) {
|
|
MockCubeb* cubeb = new MockCubeb();
|
|
CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());
|
|
|
|
// Non native input settings
|
|
const AudioInputSource::Id sourceId = 1;
|
|
const CubebUtils::AudioDeviceID deviceId = (CubebUtils::AudioDeviceID)1;
|
|
const uint32_t channels = 2;
|
|
const PrincipalHandle testPrincipal =
|
|
MakePrincipalHandle(nsContentUtils::GetSystemPrincipal());
|
|
const TrackRate rate = 48000;
|
|
|
|
// Setup: Create a NonNativeInputTrack and add it to mGraph.
|
|
RefPtr<NonNativeInputTrack> track =
|
|
new NonNativeInputTrack(mGraph->GraphRate(), deviceId, testPrincipal);
|
|
mGraph->AddTrack(track);
|
|
|
|
// Main test below:
|
|
|
|
auto listener = MakeRefPtr<MockEventListener>();
|
|
EXPECT_CALL(*listener,
|
|
AudioStateCallback(
|
|
sourceId, AudioInputSource::EventListener::State::Started));
|
|
EXPECT_CALL(*listener,
|
|
AudioStateCallback(
|
|
sourceId, AudioInputSource::EventListener::State::Error));
|
|
EXPECT_CALL(*listener,
|
|
AudioStateCallback(
|
|
sourceId, AudioInputSource::EventListener::State::Stopped))
|
|
.Times(2);
|
|
|
|
// Launch and start an audio stream.
|
|
DispatchFunction([&] {
|
|
track->StartAudio(MakeRefPtr<AudioInputSource>(
|
|
std::move(listener), sourceId, deviceId, channels, true, testPrincipal,
|
|
rate, mGraph->GraphRate()));
|
|
});
|
|
RefPtr<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent());
|
|
EXPECT_TRUE(stream->mHasInput);
|
|
EXPECT_FALSE(stream->mHasOutput);
|
|
EXPECT_EQ(stream->GetInputDeviceID(), deviceId);
|
|
EXPECT_EQ(stream->InputChannels(), channels);
|
|
EXPECT_EQ(stream->SampleRate(), static_cast<uint32_t>(rate));
|
|
|
|
// Make sure the stream is running.
|
|
Unused << WaitFor(stream->FramesProcessedEvent());
|
|
|
|
// Force an error in the MockCubeb.
|
|
DispatchFunction([&] { stream->ForceError(); });
|
|
WaitFor(stream->ErrorForcedEvent());
|
|
|
|
// Stop and destroy the stream.
|
|
DispatchFunction([&] { track->StopAudio(); });
|
|
Unused << WaitFor(cubeb->StreamDestroyEvent());
|
|
|
|
// Tear down: Destroy the NonNativeInputTrack and remove it from mGraph.
|
|
track->Destroy();
|
|
mGraph->RemoveTrackGraphThread(track);
|
|
}
|