Bug 1238038 - Start non-native audio in NonNativeInputTrack r=padenot

This patch implements all the functionalities allowing us to use a
AudioInputSource to generate audio in NonNativeInputTrack.

Depends on D137911

Differential Revision: https://phabricator.services.mozilla.com/D138189
This commit is contained in:
Chun-Min Chang 2022-04-18 18:45:36 +00:00
Родитель 61d788c585
Коммит 9fcad09de8
5 изменённых файлов: 662 добавлений и 80 удалений

Просмотреть файл

@ -54,11 +54,11 @@ class AudioInputSource : public CubebInputStream::Listener {
// The following functions should always be called in the same thread: They
// are always run on MediaTrackGraph's graph thread.
// Starts producing audio data.
virtual void Start();
void Start();
// Stops producing audio data.
virtual void Stop();
void Stop();
// Returns the AudioSegment with aDuration of data inside.
virtual AudioSegment GetAudioSegment(TrackTime aDuration);
AudioSegment GetAudioSegment(TrackTime aDuration);
// CubebInputStream::Listener interface: These are used only for the
// underlying audio stream. No user should call these APIs.

Просмотреть файл

@ -402,6 +402,92 @@ void NonNativeInputTrack::NotifyInputStopped(uint32_t aSourceId) {
mAudioSource->Stop();
}
AudioInputSourceListener::AudioInputSourceListener(NonNativeInputTrack* aOwner)
: mOwner(aOwner) {}
void AudioInputSourceListener::AudioDeviceChanged(
AudioInputSource::Id aSourceId) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mOwner);
if (mOwner->IsDestroyed()) {
LOG("NonNativeInputTrack %p has been destroyed. No need to forward the "
"audio device-changed notification",
mOwner.get());
return;
}
class DeviceChangedMessage : public ControlMessage {
public:
DeviceChangedMessage(NonNativeInputTrack* aInputTrack,
AudioInputSource::Id aSourceId)
: ControlMessage(nullptr),
mInputTrack(aInputTrack),
mSourceId(aSourceId) {
MOZ_ASSERT(mInputTrack);
}
void Run() override {
TRACE("NonNativeInputTrack::AudioDeviceChanged ControlMessage");
mInputTrack->NotifyDeviceChanged(mSourceId);
}
RefPtr<NonNativeInputTrack> mInputTrack;
AudioInputSource::Id mSourceId;
};
MOZ_DIAGNOSTIC_ASSERT(mOwner->GraphImpl());
mOwner->GraphImpl()->AppendMessage(
MakeUnique<DeviceChangedMessage>(mOwner.get(), aSourceId));
}
void AudioInputSourceListener::AudioStateCallback(
AudioInputSource::Id aSourceId,
AudioInputSource::EventListener::State aState) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mOwner);
const char* state =
aState == AudioInputSource::EventListener::State::Started ? "started"
: aState == AudioInputSource::EventListener::State::Stopped ? "stopped"
: aState == AudioInputSource::EventListener::State::Drained ? "drained"
: "error";
if (mOwner->IsDestroyed()) {
LOG("NonNativeInputTrack %p has been destroyed. No need to forward the "
"audio state-changed(%s) notification",
mOwner.get(), state);
return;
}
if (aState == AudioInputSource::EventListener::State::Started) {
LOG("We can ignore %s notification for NonNativeInputTrack %p", state,
mOwner.get());
return;
}
LOG("Notify audio stopped due to entering %s state", state);
class InputStoppedMessage : public ControlMessage {
public:
InputStoppedMessage(NonNativeInputTrack* aInputTrack,
AudioInputSource::Id aSourceId)
: ControlMessage(nullptr),
mInputTrack(aInputTrack),
mSourceId(aSourceId) {
MOZ_ASSERT(mInputTrack);
}
void Run() override {
TRACE("NonNativeInputTrack::AudioStateCallback ControlMessage");
mInputTrack->NotifyInputStopped(mSourceId);
}
RefPtr<NonNativeInputTrack> mInputTrack;
AudioInputSource::Id mSourceId;
};
MOZ_DIAGNOSTIC_ASSERT(mOwner->GraphImpl());
mOwner->GraphImpl()->AppendMessage(
MakeUnique<InputStoppedMessage>(mOwner.get(), aSourceId));
}
#undef LOG_INTERNAL
#undef LOG
#undef LOGE

Просмотреть файл

@ -165,6 +165,23 @@ class NonNativeInputTrack final : public DeviceInputTrack {
RefPtr<AudioInputSource> mAudioSource;
};
class AudioInputSourceListener : public AudioInputSource::EventListener {
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AudioInputSourceListener, override);
explicit AudioInputSourceListener(NonNativeInputTrack* aOwner);
// Main thread APIs:
void AudioDeviceChanged(AudioInputSource::Id aSourceId) override;
void AudioStateCallback(
AudioInputSource::Id aSourceId,
AudioInputSource::EventListener::State aState) override;
private:
~AudioInputSourceListener() = default;
const RefPtr<NonNativeInputTrack> mOwner;
};
} // namespace mozilla
#endif // DOM_MEDIA_DEVICEINPUTTRACK_H_

Просмотреть файл

@ -10,12 +10,14 @@
#include "gtest/gtest.h"
#include "CrossGraphPort.h"
#include "DeviceInputTrack.h"
#ifdef MOZ_WEBRTC
# include "MediaEngineWebRTCAudio.h"
#endif // MOZ_WEBRTC
#include "MockCubeb.h"
#include "mozilla/Preferences.h"
#include "mozilla/SpinEventLoopUntil.h"
#include "mozilla/StaticPrefs_media.h"
#include "WaitFor.h"
#include "WavDumper.h"
@ -102,6 +104,26 @@ class GoFaster : public ControlMessage {
void Run() override { mCubeb->GoFaster(); }
};
struct StartNonNativeInput : public ControlMessage {
const RefPtr<NonNativeInputTrack> mInputTrack;
RefPtr<AudioInputSource> mInputSource;
StartNonNativeInput(NonNativeInputTrack* aInputTrack,
RefPtr<AudioInputSource>&& aInputSource)
: ControlMessage(aInputTrack),
mInputTrack(aInputTrack),
mInputSource(std::move(aInputSource)) {}
void Run() override { mInputTrack->StartAudio(std::move(mInputSource)); }
};
struct StopNonNativeInput : public ControlMessage {
const RefPtr<NonNativeInputTrack> mInputTrack;
explicit StopNonNativeInput(NonNativeInputTrack* aInputTrack)
: ControlMessage(aInputTrack), mInputTrack(aInputTrack) {}
void Run() override { mInputTrack->StopAudio(); }
};
} // namespace
/*
@ -229,6 +251,262 @@ TEST(TestAudioTrackGraph, NotifyDeviceStarted)
WaitFor(cubeb->StreamDestroyEvent());
}
TEST(TestAudioTrackGraph, NonNativeInputTrackStartAndStop)
{
MockCubeb* cubeb = new MockCubeb();
CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());
MediaTrackGraph* graph = MediaTrackGraphImpl::GetInstance(
MediaTrackGraph::SYSTEM_THREAD_DRIVER, /*Window ID*/ 1,
MediaTrackGraph::REQUEST_DEFAULT_SAMPLE_RATE, nullptr,
GetMainThreadSerialEventTarget());
const CubebUtils::AudioDeviceID deviceId = (CubebUtils::AudioDeviceID)1;
// Add a NonNativeInputTrack to graph, making graph create an output-only
// AudioCallbackDriver since NonNativeInputTrack is an audio-type MediaTrack.
RefPtr<NonNativeInputTrack> track;
auto started = Invoke([&] {
track = new NonNativeInputTrack(graph->GraphRate(), deviceId,
PRINCIPAL_HANDLE_NONE);
graph->AddTrack(track);
return graph->NotifyWhenDeviceStarted(track);
});
RefPtr<SmartMockCubebStream> driverStream = WaitFor(cubeb->StreamInitEvent());
Result<bool, nsresult> rv = WaitFor(started);
EXPECT_TRUE(rv.unwrapOr(false));
EXPECT_FALSE(driverStream->mHasInput);
EXPECT_TRUE(driverStream->mHasOutput);
// Main test below:
{
const AudioInputSource::Id sourceId = 1;
const uint32_t channels = 2;
const TrackRate rate = 48000;
const uint32_t bufferingMs = StaticPrefs::media_clockdrift_buffering();
// Start and stop the audio in NonNativeInputTrack.
{
struct DeviceInfo {
uint32_t mChannelCount;
AudioInputType mType;
};
using DeviceQueryPromise =
MozPromise<DeviceInfo, nsresult, /* IsExclusive = */ true>;
struct DeviceQueryMessage : public ControlMessage {
const NonNativeInputTrack* mInputTrack;
MozPromiseHolder<DeviceQueryPromise> mHolder;
DeviceQueryMessage(NonNativeInputTrack* aInputTrack,
MozPromiseHolder<DeviceQueryPromise>&& aHolder)
: ControlMessage(aInputTrack),
mInputTrack(aInputTrack),
mHolder(std::move(aHolder)) {}
void Run() override {
DeviceInfo info = {mInputTrack->NumberOfChannels(),
mInputTrack->DevicePreference()};
// mHolder.Resolve(info, __func__);
mTrack->GraphImpl()->Dispatch(NS_NewRunnableFunction(
"TestAudioTrackGraph::DeviceQueryMessage",
[holder = std::move(mHolder), devInfo = info]() mutable {
holder.Resolve(devInfo, __func__);
}));
}
};
// No input channels and device preference before 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();
EXPECT_EQ(info.mChannelCount, 0U);
EXPECT_EQ(info.mType, AudioInputType::Unknown);
}
DispatchFunction([&] {
track->GraphImpl()->AppendMessage(MakeUnique<StartNonNativeInput>(
track.get(),
MakeRefPtr<AudioInputSource>(
MakeRefPtr<AudioInputSourceListener>(track.get()), sourceId,
deviceId, channels, true /* voice */, PRINCIPAL_HANDLE_NONE,
rate, graph->GraphRate(), bufferingMs)));
});
RefPtr<SmartMockCubebStream> nonNativeStream =
WaitFor(cubeb->StreamInitEvent());
EXPECT_TRUE(nonNativeStream->mHasInput);
EXPECT_FALSE(nonNativeStream->mHasOutput);
EXPECT_EQ(nonNativeStream->GetInputDeviceID(), deviceId);
EXPECT_EQ(nonNativeStream->InputChannels(), channels);
EXPECT_EQ(nonNativeStream->InputSampleRate(),
static_cast<uint32_t>(rate));
// 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();
EXPECT_EQ(info.mChannelCount, channels);
EXPECT_EQ(info.mType, AudioInputType::Voice);
}
Unused << WaitFor(nonNativeStream->FramesProcessedEvent());
DispatchFunction([&] {
track->GraphImpl()->AppendMessage(
MakeUnique<StopNonNativeInput>(track.get()));
});
RefPtr<SmartMockCubebStream> destroyedStream =
WaitFor(cubeb->StreamDestroyEvent());
EXPECT_EQ(destroyedStream.get(), nonNativeStream.get());
// No input channels and device preference after stop.
{
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();
EXPECT_EQ(info.mChannelCount, 0U);
EXPECT_EQ(info.mType, AudioInputType::Unknown);
}
}
// Make sure the NonNativeInputTrack can restart and stop its audio.
{
DispatchFunction([&] {
track->GraphImpl()->AppendMessage(MakeUnique<StartNonNativeInput>(
track.get(),
MakeRefPtr<AudioInputSource>(
MakeRefPtr<AudioInputSourceListener>(track.get()), sourceId,
deviceId, channels, true, PRINCIPAL_HANDLE_NONE, rate,
graph->GraphRate(), bufferingMs)));
});
RefPtr<SmartMockCubebStream> nonNativeStream =
WaitFor(cubeb->StreamInitEvent());
EXPECT_TRUE(nonNativeStream->mHasInput);
EXPECT_FALSE(nonNativeStream->mHasOutput);
EXPECT_EQ(nonNativeStream->GetInputDeviceID(), deviceId);
EXPECT_EQ(nonNativeStream->InputChannels(), channels);
EXPECT_EQ(nonNativeStream->InputSampleRate(),
static_cast<uint32_t>(rate));
Unused << WaitFor(nonNativeStream->FramesProcessedEvent());
DispatchFunction([&] {
track->GraphImpl()->AppendMessage(
MakeUnique<StopNonNativeInput>(track.get()));
});
RefPtr<SmartMockCubebStream> destroyedStream =
WaitFor(cubeb->StreamDestroyEvent());
EXPECT_EQ(destroyedStream.get(), nonNativeStream.get());
}
}
// Clean up.
DispatchFunction([&] { track->Destroy(); });
RefPtr<SmartMockCubebStream> destroyedStream =
WaitFor(cubeb->StreamDestroyEvent());
EXPECT_EQ(destroyedStream.get(), driverStream.get());
}
TEST(TestAudioTrackGraph, NonNativeInputTrackErrorCallback)
{
MockCubeb* cubeb = new MockCubeb();
CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());
MediaTrackGraph* graph = MediaTrackGraphImpl::GetInstance(
MediaTrackGraph::SYSTEM_THREAD_DRIVER, /*Window ID*/ 1,
MediaTrackGraph::REQUEST_DEFAULT_SAMPLE_RATE, nullptr,
GetMainThreadSerialEventTarget());
const CubebUtils::AudioDeviceID deviceId = (CubebUtils::AudioDeviceID)1;
// Add a NonNativeInputTrack to graph, making graph create an output-only
// AudioCallbackDriver since NonNativeInputTrack is an audio-type MediaTrack.
RefPtr<NonNativeInputTrack> track;
auto started = Invoke([&] {
track = new NonNativeInputTrack(graph->GraphRate(), deviceId,
PRINCIPAL_HANDLE_NONE);
graph->AddTrack(track);
return graph->NotifyWhenDeviceStarted(track);
});
RefPtr<SmartMockCubebStream> driverStream = WaitFor(cubeb->StreamInitEvent());
Result<bool, nsresult> rv = WaitFor(started);
EXPECT_TRUE(rv.unwrapOr(false));
EXPECT_FALSE(driverStream->mHasInput);
EXPECT_TRUE(driverStream->mHasOutput);
// Main test below:
{
const AudioInputSource::Id sourceId = 1;
const uint32_t channels = 2;
const TrackRate rate = 48000;
const uint32_t bufferingMs = StaticPrefs::media_clockdrift_buffering();
// Launch and start the non-native audio stream.
DispatchFunction([&] {
track->GraphImpl()->AppendMessage(MakeUnique<StartNonNativeInput>(
track.get(),
MakeRefPtr<AudioInputSource>(
MakeRefPtr<AudioInputSourceListener>(track.get()), sourceId,
deviceId, channels, true, PRINCIPAL_HANDLE_NONE, rate,
graph->GraphRate(), bufferingMs)));
});
RefPtr<SmartMockCubebStream> nonNativeStream =
WaitFor(cubeb->StreamInitEvent());
EXPECT_TRUE(nonNativeStream->mHasInput);
EXPECT_FALSE(nonNativeStream->mHasOutput);
EXPECT_EQ(nonNativeStream->GetInputDeviceID(), deviceId);
EXPECT_EQ(nonNativeStream->InputChannels(), channels);
EXPECT_EQ(nonNativeStream->InputSampleRate(), static_cast<uint32_t>(rate));
// 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());
RefPtr<SmartMockCubebStream> destroyedStream =
WaitFor(cubeb->StreamDestroyEvent());
EXPECT_EQ(destroyedStream.get(), nonNativeStream.get());
}
// Make sure it's ok to call audio stop again.
DispatchFunction([&] {
track->GraphImpl()->AppendMessage(
MakeUnique<StopNonNativeInput>(track.get()));
});
// Clean up.
DispatchFunction([&] { track->Destroy(); });
RefPtr<SmartMockCubebStream> destroyedStream =
WaitFor(cubeb->StreamDestroyEvent());
EXPECT_EQ(destroyedStream.get(), driverStream.get());
}
#ifdef MOZ_WEBRTC
TEST(TestAudioTrackGraph, ErrorCallback)
{

Просмотреть файл

@ -11,12 +11,20 @@
#include "AudioGenerator.h"
#include "MediaTrackGraphImpl.h"
#include "MockCubeb.h"
#include "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:
MockGraphImpl(TrackRate aRate, uint32_t aChannels)
@ -150,25 +158,140 @@ TEST_F(TestDeviceInputTrack, OpenTwiceWithoutCloseFirst) {
NativeInputTrack::CloseAudio(std::move(track1), nullptr);
}
// TODO: Use AudioInputSource with MockCubeb for the test
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;
const uint32_t bufferingMs = StaticPrefs::media_clockdrift_buffering();
// 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));
// 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(), bufferingMs));
});
// 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->InputSampleRate(), 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));
DispatchFunction([&] {
track->StartAudio(MakeRefPtr<AudioInputSource>(
std::move(listener), sourceId, deviceId, channels, true,
testPrincipal, rate, mGraph->GraphRate(), bufferingMs));
});
// 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->InputSampleRate(), 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 uint32_t channelCount = 2;
const TrackRate rate = 48000;
// Declare Buffer here to make sure it lives longer than the TestAudioSource
// we are going to use below.
AudioSegment buffer;
const CubebUtils::AudioDeviceID deviceId = (void*)1;
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;
const uint32_t bufferingMs =
static_cast<uint32_t>(StaticPrefs::media_clockdrift_buffering());
// Setup: Create a NonNativeInputTrack and add it to mGraph
// Setup: Create a NonNativeInputTrack and add it to mGraph.
RefPtr<NonNativeInputTrack> track =
new NonNativeInputTrack(mGraph->GraphRate(), deviceId, testPrincipal);
mGraph->AddTrack(track);
@ -188,82 +311,43 @@ TEST_F(TestDeviceInputTrack, NonNativeInputTrackData) {
}
// 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.
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));
auto listener = MakeRefPtr<MockEventListener>();
EXPECT_CALL(*listener,
AudioStateCallback(
sourceId, AudioInputSource::EventListener::State::Started));
EXPECT_CALL(*listener,
AudioStateCallback(
sourceId, AudioInputSource::EventListener::State::Stopped));
private:
~MockEventListener() = default;
};
DispatchFunction([&] {
track->StartAudio(MakeRefPtr<AudioInputSource>(
std::move(listener), sourceId, deviceId, channels, true, testPrincipal,
rate, mGraph->GraphRate(), bufferingMs));
});
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->InputSampleRate(), static_cast<uint32_t>(rate));
class TestAudioSource final : public AudioInputSource {
public:
TestAudioSource(RefPtr<EventListener>&& aListener, Id aSourceId,
CubebUtils::AudioDeviceID aDeviceId, uint32_t aChannelCount,
bool aIsVoice, const PrincipalHandle& aPrincipalHandle,
TrackRate aSourceRate, TrackRate aTargetRate,
uint32_t aBufferMs, AudioSegment& aCurrentData)
: AudioInputSource(std::move(aListener), aSourceId, aDeviceId,
aChannelCount, aIsVoice, aPrincipalHandle,
aSourceRate, aTargetRate, aBufferMs),
mGenerator(mChannelCount, aTargetRate),
mCurrentData(aCurrentData) {}
void Start() override {}
void Stop() override {}
AudioSegment GetAudioSegment(TrackTime aDuration) override {
mCurrentData.Clear();
mGenerator.Generate(mCurrentData, static_cast<uint32_t>(aDuration));
AudioSegment data;
data.AppendSegment(&mCurrentData);
return data;
}
const AudioSegment& GetCurrentData() { return mCurrentData; }
private:
~TestAudioSource() = default;
AudioGenerator<AudioDataValue> mGenerator;
AudioSegment& mCurrentData;
};
// We need to make sure the buffer lives longer than the TestAudioSource here
// since TestAudioSource will access buffer by reference.
track->StartAudio(MakeRefPtr<TestAudioSource>(
MakeRefPtr<MockEventListener>(), 1 /* Ignored */, deviceId, channelCount,
true /* Ignored*/, testPrincipal, rate, mGraph->GraphRate(),
50 /* Ignored */, buffer));
// Check audio data.
Unused << WaitFor(stream->FramesProcessedEvent());
track->ProcessInput(current, next, flags);
{
AudioSegment data;
data.AppendSlice(*track->GetData<AudioSegment>(), current, next);
TrackTime duration = next - current;
EXPECT_EQ(data.GetDuration(), duration);
EXPECT_EQ(data.GetDuration(), buffer.GetDuration());
// The data we get here should be same as the data in buffer. We don't
// implement == operator for AudioSegment so we convert the data into
// interleaved one in nsTArray, which implements the == operator, to do the
// comparison.
nsTArray<AudioDataValue> actual;
size_t actualSampleCount =
data.WriteToInterleavedBuffer(actual, channelCount);
nsTArray<AudioDataValue> expected;
size_t expectedSampleCount =
buffer.WriteToInterleavedBuffer(expected, channelCount);
EXPECT_EQ(actualSampleCount, expectedSampleCount);
EXPECT_EQ(actualSampleCount, static_cast<size_t>(duration) *
static_cast<size_t>(channelCount));
EXPECT_EQ(actual, expected);
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.
@ -271,7 +355,9 @@ TEST_F(TestDeviceInputTrack, NonNativeInputTrackData) {
next = MediaTrackGraphImpl::RoundUpToEndOfAudioBlock(3 * frames);
ASSERT_NE(current, next); // Make sure we have data produced in ProcessInput.
track->StopAudio();
DispatchFunction([&] { track->StopAudio(); });
Unused << WaitFor(cubeb->StreamDestroyEvent());
track->ProcessInput(current, next, flags);
{
AudioSegment data;
@ -283,3 +369,118 @@ TEST_F(TestDeviceInputTrack, NonNativeInputTrackData) {
track->Destroy();
mGraph->RemoveTrackGraphThread(track);
}
TEST_F(TestDeviceInputTrack, DeviceChangedCallback) {
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;
const uint32_t bufferingMs = StaticPrefs::media_clockdrift_buffering();
// 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));
// Launch and start an audio stream.
DispatchFunction([&] {
track->StartAudio(MakeRefPtr<AudioInputSource>(
std::move(listener), sourceId, deviceId, channels, true, testPrincipal,
rate, mGraph->GraphRate(), bufferingMs));
});
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->InputSampleRate(), 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 NativeInputTrack and remove it from mGraph.
track->Destroy();
mGraph->RemoveTrackGraphThread(track);
}
TEST_F(TestDeviceInputTrack, ErrorCallback) {
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;
const uint32_t bufferingMs = StaticPrefs::media_clockdrift_buffering();
// 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));
// Launch and start an audio stream.
DispatchFunction([&] {
track->StartAudio(MakeRefPtr<AudioInputSource>(
std::move(listener), sourceId, deviceId, channels, true, testPrincipal,
rate, mGraph->GraphRate(), bufferingMs));
});
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->InputSampleRate(), 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 NativeInputTrack and remove it from mGraph.
track->Destroy();
mGraph->RemoveTrackGraphThread(track);
}