зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
61d788c585
Коммит
9fcad09de8
|
@ -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);
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче