1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
7 #include "DeviceInputTrack.h"
9 #include "gmock/gmock.h"
10 #include "gtest/gtest.h"
12 #include "AudioGenerator.h"
13 #include "MediaTrackGraphImpl.h"
14 #include "MockCubeb.h"
16 #include "mozilla/StaticPrefs_media.h"
17 #include "nsContentUtils.h"
19 using namespace mozilla
;
20 using testing::NiceMock
;
21 using testing::Return
;
24 #define DispatchFunction(f) \
25 NS_DispatchToCurrentThread(NS_NewRunnableFunction(__func__, f))
28 class MockGraphImpl
: public MediaTrackGraphImpl
{
30 MockGraphImpl(TrackRate aRate
, uint32_t aChannels
)
31 : MediaTrackGraphImpl(OFFLINE_THREAD_DRIVER
, DIRECT_DRIVER
, aRate
,
32 aChannels
, nullptr, NS_GetCurrentThread()) {
33 ON_CALL(*this, OnGraphThread
).WillByDefault(Return(true));
34 // We have to call `Destroy()` manually in order to break the reference.
35 // The reason we don't assign a null driver is because we would add a track
36 // to the graph, then it would trigger graph's `EnsureNextIteration()` that
37 // requires a non-null driver.
38 SetCurrentDriver(new NiceMock
<MockDriver
>());
41 MOCK_CONST_METHOD0(OnGraphThread
, bool());
42 MOCK_METHOD1(AppendMessage
, void(UniquePtr
<ControlMessage
>));
45 ~MockGraphImpl() = default;
47 class MockDriver
: public GraphDriver
{
48 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MockDriver
, override
);
50 MockDriver() : GraphDriver(nullptr, nullptr, 0) {
51 ON_CALL(*this, OnThread
).WillByDefault(Return(true));
52 ON_CALL(*this, ThreadRunning
).WillByDefault(Return(true));
55 MOCK_METHOD0(Start
, void());
56 MOCK_METHOD0(Shutdown
, void());
57 MOCK_METHOD0(IterationDuration
, uint32_t());
58 MOCK_METHOD0(EnsureNextIteration
, void());
59 MOCK_CONST_METHOD0(OnThread
, bool());
60 MOCK_CONST_METHOD0(ThreadRunning
, bool());
63 ~MockDriver() = default;
67 class TestDeviceInputTrack
: public testing::Test
{
69 TestDeviceInputTrack() : mChannels(2), mRate(44100) {}
71 void SetUp() override
{
72 mGraph
= MakeRefPtr
<NiceMock
<MockGraphImpl
>>(mRate
, mChannels
);
75 void TearDown() override
{ mGraph
->Destroy(); }
77 const uint32_t mChannels
;
78 const TrackRate mRate
;
79 RefPtr
<MockGraphImpl
> mGraph
;
82 TEST_F(TestDeviceInputTrack
, DeviceInputConsumerTrack
) {
83 class TestDeviceInputConsumerTrack
: public DeviceInputConsumerTrack
{
85 static TestDeviceInputConsumerTrack
* Create(MediaTrackGraph
* aGraph
) {
86 MOZ_ASSERT(NS_IsMainThread());
87 TestDeviceInputConsumerTrack
* track
=
88 new TestDeviceInputConsumerTrack(aGraph
->GraphRate());
89 aGraph
->AddTrack(track
);
94 MOZ_ASSERT(NS_IsMainThread());
95 DisconnectDeviceInput();
96 DeviceInputConsumerTrack::Destroy();
99 void ProcessInput(GraphTime aFrom
, GraphTime aTo
,
100 uint32_t aFlags
) override
{/* Ignored */};
102 uint32_t NumberOfChannels() const override
{
103 if (mInputs
.IsEmpty()) {
106 DeviceInputTrack
* t
= mInputs
[0]->GetSource()->AsDeviceInputTrack();
108 return t
->NumberOfChannels();
112 explicit TestDeviceInputConsumerTrack(TrackRate aSampleRate
)
113 : DeviceInputConsumerTrack(aSampleRate
) {}
116 class TestAudioDataListener
: public AudioDataListener
{
118 TestAudioDataListener(uint32_t aChannelCount
, bool aIsVoice
)
119 : mChannelCount(aChannelCount
), mIsVoice(aIsVoice
) {}
120 // Graph thread APIs: AudioDataListenerInterface implementations.
121 uint32_t RequestedInputChannelCount(MediaTrackGraphImpl
* aGraph
) override
{
122 MOZ_ASSERT(aGraph
->OnGraphThread());
123 return mChannelCount
;
125 bool IsVoiceInput(MediaTrackGraphImpl
* aGraph
) const override
{
128 void DeviceChanged(MediaTrackGraphImpl
* aGraph
) override
{ /* Ignored */
130 void Disconnect(MediaTrackGraphImpl
* aGraph
) override
{/* Ignored */};
133 ~TestAudioDataListener() = default;
135 // Graph thread-only.
136 uint32_t mChannelCount
;
141 const PrincipalHandle testPrincipal
=
142 MakePrincipalHandle(nsContentUtils::GetSystemPrincipal());
144 const CubebUtils::AudioDeviceID device1
= (void*)1;
145 RefPtr
<TestAudioDataListener
> listener1
= new TestAudioDataListener(1, false);
146 RefPtr
<TestDeviceInputConsumerTrack
> track1
=
147 TestDeviceInputConsumerTrack::Create(mGraph
);
148 track1
->ConnectDeviceInput(device1
, listener1
.get(), testPrincipal
);
149 EXPECT_TRUE(track1
->ConnectToNativeDevice());
150 EXPECT_FALSE(track1
->ConnectToNonNativeDevice());
152 const CubebUtils::AudioDeviceID device2
= (void*)2;
153 RefPtr
<TestAudioDataListener
> listener2
= new TestAudioDataListener(2, false);
154 RefPtr
<TestDeviceInputConsumerTrack
> track2
=
155 TestDeviceInputConsumerTrack::Create(mGraph
);
156 track2
->ConnectDeviceInput(device2
, listener2
.get(), testPrincipal
);
157 EXPECT_FALSE(track2
->ConnectToNativeDevice());
158 EXPECT_TRUE(track2
->ConnectToNonNativeDevice());
161 mGraph
->RemoveTrackGraphThread(track2
);
164 mGraph
->RemoveTrackGraphThread(track1
);
167 TEST_F(TestDeviceInputTrack
, NativeInputTrackData
) {
168 const uint32_t flags
= 0;
169 const CubebUtils::AudioDeviceID deviceId
= (void*)1;
171 AudioGenerator
<AudioDataValue
> generator(mChannels
, mRate
);
172 const size_t nrFrames
= 10;
173 const size_t bufferSize
= nrFrames
* mChannels
;
174 nsTArray
<AudioDataValue
> buffer(bufferSize
);
175 buffer
.AppendElements(bufferSize
);
177 const PrincipalHandle testPrincipal
=
178 MakePrincipalHandle(nsContentUtils::GetSystemPrincipal());
180 // Setup: Create a NativeInputTrack and add it to mGraph
181 RefPtr
<NativeInputTrack
> track
=
182 new NativeInputTrack(mGraph
->GraphRate(), deviceId
, testPrincipal
);
183 mGraph
->AddTrack(track
);
187 generator
.GenerateInterleaved(buffer
.Elements(), nrFrames
);
188 track
->NotifyInputData(mGraph
.get(), buffer
.Elements(), nrFrames
, mRate
,
191 track
->ProcessInput(0, WEBAUDIO_BLOCK_SIZE
+ nrFrames
, flags
);
192 EXPECT_EQ(static_cast<size_t>(track
->GetEnd()),
193 static_cast<size_t>(WEBAUDIO_BLOCK_SIZE
) + nrFrames
);
195 // Check pre-buffering: null data with PRINCIPAL_HANDLE_NONE principal
196 AudioSegment preBuffering
;
197 preBuffering
.AppendSlice(*track
->GetData(), 0, WEBAUDIO_BLOCK_SIZE
);
198 EXPECT_TRUE(preBuffering
.IsNull());
199 for (AudioSegment::ConstChunkIterator
iter(preBuffering
); !iter
.IsEnded();
201 const AudioChunk
& chunk
= *iter
;
202 EXPECT_EQ(chunk
.mPrincipalHandle
, PRINCIPAL_HANDLE_NONE
);
205 // Check rest of the data
207 data
.AppendSlice(*track
->GetData(), WEBAUDIO_BLOCK_SIZE
,
208 WEBAUDIO_BLOCK_SIZE
+ nrFrames
);
209 nsTArray
<AudioDataValue
> interleaved
;
210 size_t sampleCount
= data
.WriteToInterleavedBuffer(interleaved
, mChannels
);
211 EXPECT_EQ(sampleCount
, bufferSize
);
212 EXPECT_EQ(interleaved
, buffer
);
214 // Check principal in data
215 for (AudioSegment::ConstChunkIterator
iter(data
); !iter
.IsEnded();
217 const AudioChunk
& chunk
= *iter
;
218 EXPECT_EQ(chunk
.mPrincipalHandle
, testPrincipal
);
221 // Tear down: Destroy the NativeInputTrack and remove it from mGraph.
223 mGraph
->RemoveTrackGraphThread(track
);
226 class MockEventListener
: public AudioInputSource::EventListener
{
228 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MockEventListener
, override
);
229 MOCK_METHOD1(AudioDeviceChanged
, void(AudioInputSource::Id
));
230 MOCK_METHOD2(AudioStateCallback
,
231 void(AudioInputSource::Id
,
232 AudioInputSource::EventListener::State
));
235 ~MockEventListener() = default;
238 TEST_F(TestDeviceInputTrack
, StartAndStop
) {
239 MockCubeb
* cubeb
= new MockCubeb();
240 CubebUtils::ForceSetCubebContext(cubeb
->AsCubebContext());
242 // Non native input settings
243 const AudioInputSource::Id sourceId
= 1;
244 const CubebUtils::AudioDeviceID deviceId
= (CubebUtils::AudioDeviceID
)1;
245 const uint32_t channels
= 2;
246 const PrincipalHandle testPrincipal
=
247 MakePrincipalHandle(nsContentUtils::GetSystemPrincipal());
248 const TrackRate rate
= 48000;
249 const uint32_t bufferingMs
= StaticPrefs::media_clockdrift_buffering();
251 // Setup: Create a NonNativeInputTrack and add it to mGraph.
252 RefPtr
<NonNativeInputTrack
> track
=
253 new NonNativeInputTrack(mGraph
->GraphRate(), deviceId
, testPrincipal
);
254 mGraph
->AddTrack(track
);
258 // Make sure the NonNativeInputTrack can start and stop its audio correctly.
260 auto listener
= MakeRefPtr
<MockEventListener
>();
261 EXPECT_CALL(*listener
,
263 sourceId
, AudioInputSource::EventListener::State::Started
));
264 EXPECT_CALL(*listener
,
266 sourceId
, AudioInputSource::EventListener::State::Stopped
));
268 // No input channels and device preference before start.
269 EXPECT_EQ(track
->NumberOfChannels(), 0U);
270 EXPECT_EQ(track
->DevicePreference(), AudioInputType::Unknown
);
272 DispatchFunction([&] {
273 track
->StartAudio(MakeRefPtr
<AudioInputSource
>(
274 std::move(listener
), sourceId
, deviceId
, channels
, true /* voice */,
275 testPrincipal
, rate
, mGraph
->GraphRate(), bufferingMs
));
278 // Wait for stream creation.
279 RefPtr
<SmartMockCubebStream
> stream
= WaitFor(cubeb
->StreamInitEvent());
281 // Make sure the audio stream and the track's settings are correct.
282 EXPECT_TRUE(stream
->mHasInput
);
283 EXPECT_FALSE(stream
->mHasOutput
);
284 EXPECT_EQ(stream
->GetInputDeviceID(), deviceId
);
285 EXPECT_EQ(stream
->InputChannels(), channels
);
286 EXPECT_EQ(stream
->InputSampleRate(), static_cast<uint32_t>(rate
));
287 EXPECT_EQ(track
->NumberOfChannels(), channels
);
288 EXPECT_EQ(track
->DevicePreference(), AudioInputType::Voice
);
290 // Wait for stream callbacks.
291 Unused
<< WaitFor(stream
->FramesProcessedEvent());
293 DispatchFunction([&] { track
->StopAudio(); });
295 // Wait for stream destroy.
296 Unused
<< WaitFor(cubeb
->StreamDestroyEvent());
298 // No input channels and device preference after stop.
299 EXPECT_EQ(track
->NumberOfChannels(), 0U);
300 EXPECT_EQ(track
->DevicePreference(), AudioInputType::Unknown
);
303 // Make sure the NonNativeInputTrack can restart its audio correctly.
305 auto listener
= MakeRefPtr
<MockEventListener
>();
306 EXPECT_CALL(*listener
,
308 sourceId
, AudioInputSource::EventListener::State::Started
));
309 EXPECT_CALL(*listener
,
311 sourceId
, AudioInputSource::EventListener::State::Stopped
));
313 DispatchFunction([&] {
314 track
->StartAudio(MakeRefPtr
<AudioInputSource
>(
315 std::move(listener
), sourceId
, deviceId
, channels
, true,
316 testPrincipal
, rate
, mGraph
->GraphRate(), bufferingMs
));
319 // Wait for stream creation.
320 RefPtr
<SmartMockCubebStream
> stream
= WaitFor(cubeb
->StreamInitEvent());
321 EXPECT_TRUE(stream
->mHasInput
);
322 EXPECT_FALSE(stream
->mHasOutput
);
323 EXPECT_EQ(stream
->GetInputDeviceID(), deviceId
);
324 EXPECT_EQ(stream
->InputChannels(), channels
);
325 EXPECT_EQ(stream
->InputSampleRate(), static_cast<uint32_t>(rate
));
327 // Wait for stream callbacks.
328 Unused
<< WaitFor(stream
->FramesProcessedEvent());
330 DispatchFunction([&] { track
->StopAudio(); });
332 // Wait for stream destroy.
333 Unused
<< WaitFor(cubeb
->StreamDestroyEvent());
336 // Tear down: Destroy the NativeInputTrack and remove it from mGraph.
338 mGraph
->RemoveTrackGraphThread(track
);
341 TEST_F(TestDeviceInputTrack
, NonNativeInputTrackData
) {
342 MockCubeb
* cubeb
= new MockCubeb();
343 CubebUtils::ForceSetCubebContext(cubeb
->AsCubebContext());
346 const uint32_t flags
= 0;
347 const GraphTime frames
= 440;
349 // Non native input settings
350 const AudioInputSource::Id sourceId
= 1;
351 const CubebUtils::AudioDeviceID deviceId
= (CubebUtils::AudioDeviceID
)1;
352 const uint32_t channels
= 2;
353 const PrincipalHandle testPrincipal
=
354 MakePrincipalHandle(nsContentUtils::GetSystemPrincipal());
355 const TrackRate rate
= 48000;
356 const uint32_t bufferingMs
=
357 static_cast<uint32_t>(StaticPrefs::media_clockdrift_buffering());
359 // Setup: Create a NonNativeInputTrack and add it to mGraph.
360 RefPtr
<NonNativeInputTrack
> track
=
361 new NonNativeInputTrack(mGraph
->GraphRate(), deviceId
, testPrincipal
);
362 mGraph
->AddTrack(track
);
366 // Make sure we get null data if the track is not started yet.
367 GraphTime current
= 0;
368 GraphTime next
= MediaTrackGraphImpl::RoundUpToEndOfAudioBlock(frames
);
369 ASSERT_NE(current
, next
); // Make sure we have data produced in ProcessInput.
371 track
->ProcessInput(current
, next
, flags
);
374 data
.AppendSegment(track
->GetData
<AudioSegment
>());
375 EXPECT_TRUE(data
.IsNull());
378 // Make sure we get the AudioInputSource's data once we start the track.
381 next
= MediaTrackGraphImpl::RoundUpToEndOfAudioBlock(2 * frames
);
382 ASSERT_NE(current
, next
); // Make sure we have data produced in ProcessInput.
384 auto listener
= MakeRefPtr
<MockEventListener
>();
385 EXPECT_CALL(*listener
,
387 sourceId
, AudioInputSource::EventListener::State::Started
));
388 EXPECT_CALL(*listener
,
390 sourceId
, AudioInputSource::EventListener::State::Stopped
));
392 DispatchFunction([&] {
393 track
->StartAudio(MakeRefPtr
<AudioInputSource
>(
394 std::move(listener
), sourceId
, deviceId
, channels
, true, testPrincipal
,
395 rate
, mGraph
->GraphRate(), bufferingMs
));
397 RefPtr
<SmartMockCubebStream
> stream
= WaitFor(cubeb
->StreamInitEvent());
398 EXPECT_TRUE(stream
->mHasInput
);
399 EXPECT_FALSE(stream
->mHasOutput
);
400 EXPECT_EQ(stream
->GetInputDeviceID(), deviceId
);
401 EXPECT_EQ(stream
->InputChannels(), channels
);
402 EXPECT_EQ(stream
->InputSampleRate(), static_cast<uint32_t>(rate
));
405 Unused
<< WaitFor(stream
->FramesProcessedEvent());
406 track
->ProcessInput(current
, next
, flags
);
409 data
.AppendSlice(*track
->GetData
<AudioSegment
>(), current
, next
);
410 EXPECT_FALSE(data
.IsNull());
411 for (AudioSegment::ConstChunkIterator
iter(data
); !iter
.IsEnded();
413 EXPECT_EQ(iter
->mChannelData
.Length(), channels
);
414 EXPECT_EQ(iter
->mPrincipalHandle
, testPrincipal
);
418 // Stop the track and make sure it produces null data again.
420 next
= MediaTrackGraphImpl::RoundUpToEndOfAudioBlock(3 * frames
);
421 ASSERT_NE(current
, next
); // Make sure we have data produced in ProcessInput.
423 DispatchFunction([&] { track
->StopAudio(); });
424 Unused
<< WaitFor(cubeb
->StreamDestroyEvent());
426 track
->ProcessInput(current
, next
, flags
);
429 data
.AppendSlice(*track
->GetData
<AudioSegment
>(), current
, next
);
430 EXPECT_TRUE(data
.IsNull());
433 // Tear down: Destroy the NonNativeInputTrack and remove it from mGraph.
435 mGraph
->RemoveTrackGraphThread(track
);
438 TEST_F(TestDeviceInputTrack
, NonNativeDeviceChangedCallback
) {
439 MockCubeb
* cubeb
= new MockCubeb();
440 CubebUtils::ForceSetCubebContext(cubeb
->AsCubebContext());
442 // Non native input settings
443 const AudioInputSource::Id sourceId
= 1;
444 const CubebUtils::AudioDeviceID deviceId
= (CubebUtils::AudioDeviceID
)1;
445 const uint32_t channels
= 2;
446 const PrincipalHandle testPrincipal
=
447 MakePrincipalHandle(nsContentUtils::GetSystemPrincipal());
448 const TrackRate rate
= 48000;
449 const uint32_t bufferingMs
= StaticPrefs::media_clockdrift_buffering();
451 // Setup: Create a NonNativeInputTrack and add it to mGraph.
452 RefPtr
<NonNativeInputTrack
> track
=
453 new NonNativeInputTrack(mGraph
->GraphRate(), deviceId
, testPrincipal
);
454 mGraph
->AddTrack(track
);
458 auto listener
= MakeRefPtr
<MockEventListener
>();
459 EXPECT_CALL(*listener
, AudioDeviceChanged(sourceId
));
460 EXPECT_CALL(*listener
,
462 sourceId
, AudioInputSource::EventListener::State::Started
));
463 EXPECT_CALL(*listener
,
465 sourceId
, AudioInputSource::EventListener::State::Stopped
));
467 // Launch and start an audio stream.
468 DispatchFunction([&] {
469 track
->StartAudio(MakeRefPtr
<AudioInputSource
>(
470 std::move(listener
), sourceId
, deviceId
, channels
, true, testPrincipal
,
471 rate
, mGraph
->GraphRate(), bufferingMs
));
473 RefPtr
<SmartMockCubebStream
> stream
= WaitFor(cubeb
->StreamInitEvent());
474 EXPECT_TRUE(stream
->mHasInput
);
475 EXPECT_FALSE(stream
->mHasOutput
);
476 EXPECT_EQ(stream
->GetInputDeviceID(), deviceId
);
477 EXPECT_EQ(stream
->InputChannels(), channels
);
478 EXPECT_EQ(stream
->InputSampleRate(), static_cast<uint32_t>(rate
));
480 // Make sure the stream is running.
481 Unused
<< WaitFor(stream
->FramesProcessedEvent());
483 // Fire a device-changed callback.
484 DispatchFunction([&] { stream
->ForceDeviceChanged(); });
485 WaitFor(stream
->DeviceChangeForcedEvent());
487 // Stop and destroy the stream.
488 DispatchFunction([&] { track
->StopAudio(); });
489 Unused
<< WaitFor(cubeb
->StreamDestroyEvent());
491 // Tear down: Destroy the NonNativeInputTrack and remove it from mGraph.
493 mGraph
->RemoveTrackGraphThread(track
);
496 TEST_F(TestDeviceInputTrack
, NonNativeErrorCallback
) {
497 MockCubeb
* cubeb
= new MockCubeb();
498 CubebUtils::ForceSetCubebContext(cubeb
->AsCubebContext());
500 // Non native input settings
501 const AudioInputSource::Id sourceId
= 1;
502 const CubebUtils::AudioDeviceID deviceId
= (CubebUtils::AudioDeviceID
)1;
503 const uint32_t channels
= 2;
504 const PrincipalHandle testPrincipal
=
505 MakePrincipalHandle(nsContentUtils::GetSystemPrincipal());
506 const TrackRate rate
= 48000;
507 const uint32_t bufferingMs
= StaticPrefs::media_clockdrift_buffering();
509 // Setup: Create a NonNativeInputTrack and add it to mGraph.
510 RefPtr
<NonNativeInputTrack
> track
=
511 new NonNativeInputTrack(mGraph
->GraphRate(), deviceId
, testPrincipal
);
512 mGraph
->AddTrack(track
);
516 auto listener
= MakeRefPtr
<MockEventListener
>();
517 EXPECT_CALL(*listener
,
519 sourceId
, AudioInputSource::EventListener::State::Started
));
520 EXPECT_CALL(*listener
,
522 sourceId
, AudioInputSource::EventListener::State::Error
));
524 // Launch and start an audio stream.
525 DispatchFunction([&] {
526 track
->StartAudio(MakeRefPtr
<AudioInputSource
>(
527 std::move(listener
), sourceId
, deviceId
, channels
, true, testPrincipal
,
528 rate
, mGraph
->GraphRate(), bufferingMs
));
530 RefPtr
<SmartMockCubebStream
> stream
= WaitFor(cubeb
->StreamInitEvent());
531 EXPECT_TRUE(stream
->mHasInput
);
532 EXPECT_FALSE(stream
->mHasOutput
);
533 EXPECT_EQ(stream
->GetInputDeviceID(), deviceId
);
534 EXPECT_EQ(stream
->InputChannels(), channels
);
535 EXPECT_EQ(stream
->InputSampleRate(), static_cast<uint32_t>(rate
));
537 // Make sure the stream is running.
538 Unused
<< WaitFor(stream
->FramesProcessedEvent());
540 // Force an error in the MockCubeb.
541 DispatchFunction([&] { stream
->ForceError(); });
542 WaitFor(stream
->ErrorForcedEvent());
544 // Make sure the stream has been stopped by the error-state's backgroud thread
545 // task, to avoid getting a stopped state callback by `track->StopAudio`
547 WaitFor(stream
->ErrorStoppedEvent());
549 // Stop and destroy the stream.
550 DispatchFunction([&] { track
->StopAudio(); });
551 Unused
<< WaitFor(cubeb
->StreamDestroyEvent());
553 // Tear down: Destroy the NonNativeInputTrack and remove it from mGraph.
555 mGraph
->RemoveTrackGraphThread(track
);