1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
4 * You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6 #include "MediaTrackGraphImpl.h"
7
8 #include "gmock/gmock.h"
9 #include "gtest/gtest-printers.h"
10 #include "gtest/gtest.h"
11
12 #include "CrossGraphPort.h"
13 #ifdef MOZ_WEBRTC
14 # include "MediaEngineWebRTCAudio.h"
15 #endif // MOZ_WEBRTC
16 #include "MockCubeb.h"
17 #include "mozilla/Preferences.h"
18 #include "mozilla/SpinEventLoopUntil.h"
19 #include "WaitFor.h"
20 #include "WavDumper.h"
21
22 #define DRIFT_BUFFERING_PREF "media.clockdrift.buffering"
23
24 using namespace mozilla;
25
26 namespace {
27 // Short-hand for InvokeAsync on the current thread.
28 #define Invoke(f) InvokeAsync(GetCurrentSerialEventTarget(), __func__, f)
29
30 // Short-hand for DispatchToCurrentThread with a function.
31 #define DispatchFunction(f) \
32 NS_DispatchToCurrentThread(NS_NewRunnableFunction(__func__, f))
33
34 // Short-hand for DispatchToCurrentThread with a method with arguments
35 #define DispatchMethod(t, m, args...) \
36 NS_DispatchToCurrentThread(NewRunnableMethod(__func__, t, m, ##args))
37
38 #ifdef MOZ_WEBRTC
39 /*
40 * Common ControlMessages
41 */
42 struct StartInputProcessing : public ControlMessage {
43 const RefPtr<AudioInputTrack> mInputTrack;
44 const RefPtr<AudioInputProcessing> mInputProcessing;
45
StartInputProcessing__anon955068c90111::StartInputProcessing46 StartInputProcessing(AudioInputTrack* aTrack,
47 AudioInputProcessing* aInputProcessing)
48 : ControlMessage(aTrack),
49 mInputTrack(aTrack),
50 mInputProcessing(aInputProcessing) {}
Run__anon955068c90111::StartInputProcessing51 void Run() override { mInputProcessing->Start(); }
52 };
53
54 struct StopInputProcessing : public ControlMessage {
55 const RefPtr<AudioInputProcessing> mInputProcessing;
56
StopInputProcessing__anon955068c90111::StopInputProcessing57 explicit StopInputProcessing(AudioInputProcessing* aInputProcessing)
58 : ControlMessage(nullptr), mInputProcessing(aInputProcessing) {}
Run__anon955068c90111::StopInputProcessing59 void Run() override { mInputProcessing->Stop(); }
60 };
61
62 struct SetPassThrough : public ControlMessage {
63 const RefPtr<AudioInputProcessing> mInputProcessing;
64 const bool mPassThrough;
65
SetPassThrough__anon955068c90111::SetPassThrough66 SetPassThrough(MediaTrack* aTrack, AudioInputProcessing* aInputProcessing,
67 bool aPassThrough)
68 : ControlMessage(aTrack),
69 mInputProcessing(aInputProcessing),
70 mPassThrough(aPassThrough) {}
Run__anon955068c90111::SetPassThrough71 void Run() override {
72 EXPECT_EQ(mInputProcessing->PassThrough(mTrack->GraphImpl()),
73 !mPassThrough);
74 mInputProcessing->SetPassThrough(mTrack->GraphImpl(), mPassThrough);
75 }
76 };
77 #endif // MOZ_WEBRTC
78
79 class GoFaster : public ControlMessage {
80 MockCubeb* mCubeb;
81
82 public:
GoFaster(MockCubeb * aCubeb)83 explicit GoFaster(MockCubeb* aCubeb)
84 : ControlMessage(nullptr), mCubeb(aCubeb) {}
Run()85 void Run() override { mCubeb->GoFaster(); }
86 };
87
88 } // namespace
89
90 /*
91 * The set of tests here are a bit special. In part because they're async and
92 * depends on the graph thread to function. In part because they depend on main
93 * thread stable state to send messages to the graph.
94 *
95 * Any message sent from the main thread to the graph through the graph's
96 * various APIs are scheduled to run in stable state. Stable state occurs after
97 * a task in the main thread eventloop has run to completion.
98 *
99 * Since gtests are generally sync and on main thread, calling into the graph
100 * may schedule a stable state runnable but with no task in the eventloop to
101 * trigger stable state. Therefore care must be taken to always call into the
102 * graph from a task, typically via InvokeAsync or a dispatch to main thread.
103 */
104
TEST(TestAudioTrackGraph,DifferentDeviceIDs)105 TEST(TestAudioTrackGraph, DifferentDeviceIDs)
106 {
107 MockCubeb* cubeb = new MockCubeb();
108 CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());
109
110 MediaTrackGraph* g1 = MediaTrackGraph::GetInstance(
111 MediaTrackGraph::AUDIO_THREAD_DRIVER, /*window*/ nullptr,
112 MediaTrackGraph::REQUEST_DEFAULT_SAMPLE_RATE,
113 /*OutputDeviceID*/ nullptr);
114
115 MediaTrackGraph* g2 = MediaTrackGraph::GetInstance(
116 MediaTrackGraph::AUDIO_THREAD_DRIVER, /*window*/ nullptr,
117 MediaTrackGraph::REQUEST_DEFAULT_SAMPLE_RATE,
118 /*OutputDeviceID*/ reinterpret_cast<cubeb_devid>(1));
119
120 MediaTrackGraph* g1_2 = MediaTrackGraph::GetInstance(
121 MediaTrackGraph::AUDIO_THREAD_DRIVER, /*window*/ nullptr,
122 MediaTrackGraph::REQUEST_DEFAULT_SAMPLE_RATE,
123 /*OutputDeviceID*/ nullptr);
124
125 MediaTrackGraph* g2_2 = MediaTrackGraph::GetInstance(
126 MediaTrackGraph::AUDIO_THREAD_DRIVER, /*window*/ nullptr,
127 MediaTrackGraph::REQUEST_DEFAULT_SAMPLE_RATE,
128 /*OutputDeviceID*/ reinterpret_cast<cubeb_devid>(1));
129
130 EXPECT_NE(g1, g2) << "Different graphs due to different device ids";
131 EXPECT_EQ(g1, g1_2) << "Same graphs for same device ids";
132 EXPECT_EQ(g2, g2_2) << "Same graphs for same device ids";
133
134 for (MediaTrackGraph* g : {g1, g2}) {
135 // Dummy track to make graph rolling. Add it and remove it to remove the
136 // graph from the global hash table and let it shutdown.
137
138 using SourceTrackPromise = MozPromise<SourceMediaTrack*, nsresult, true>;
139 auto p = Invoke([g] {
140 return SourceTrackPromise::CreateAndResolve(
141 g->CreateSourceTrack(MediaSegment::AUDIO), __func__);
142 });
143
144 WaitFor(cubeb->StreamInitEvent());
145 RefPtr<SourceMediaTrack> dummySource = WaitFor(p).unwrap();
146
147 DispatchMethod(dummySource, &SourceMediaTrack::Destroy);
148
149 WaitFor(cubeb->StreamDestroyEvent());
150 }
151 }
152
TEST(TestAudioTrackGraph,SetOutputDeviceID)153 TEST(TestAudioTrackGraph, SetOutputDeviceID)
154 {
155 MockCubeb* cubeb = new MockCubeb();
156 CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());
157
158 // Set the output device id in GetInstance method confirm that it is the one
159 // used in cubeb_stream_init.
160 MediaTrackGraph* graph = MediaTrackGraph::GetInstance(
161 MediaTrackGraph::AUDIO_THREAD_DRIVER, /*window*/ nullptr,
162 MediaTrackGraph::REQUEST_DEFAULT_SAMPLE_RATE,
163 /*OutputDeviceID*/ reinterpret_cast<cubeb_devid>(2));
164
165 // Dummy track to make graph rolling. Add it and remove it to remove the
166 // graph from the global hash table and let it shutdown.
167 RefPtr<SourceMediaTrack> dummySource;
168 DispatchFunction(
169 [&] { dummySource = graph->CreateSourceTrack(MediaSegment::AUDIO); });
170
171 RefPtr<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent());
172
173 EXPECT_EQ(stream->GetOutputDeviceID(), reinterpret_cast<cubeb_devid>(2))
174 << "After init confirm the expected output device id";
175
176 // Test has finished, destroy the track to shutdown the MTG.
177 DispatchMethod(dummySource, &SourceMediaTrack::Destroy);
178 WaitFor(cubeb->StreamDestroyEvent());
179 }
180
TEST(TestAudioTrackGraph,NotifyDeviceStarted)181 TEST(TestAudioTrackGraph, NotifyDeviceStarted)
182 {
183 MockCubeb* cubeb = new MockCubeb();
184 CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());
185
186 MediaTrackGraph* graph = MediaTrackGraph::GetInstance(
187 MediaTrackGraph::AUDIO_THREAD_DRIVER, /*window*/ nullptr,
188 MediaTrackGraph::REQUEST_DEFAULT_SAMPLE_RATE, nullptr);
189
190 RefPtr<SourceMediaTrack> dummySource;
191 Unused << WaitFor(Invoke([&] {
192 // Dummy track to make graph rolling. Add it and remove it to remove the
193 // graph from the global hash table and let it shutdown.
194 dummySource = graph->CreateSourceTrack(MediaSegment::AUDIO);
195
196 return graph->NotifyWhenDeviceStarted(dummySource);
197 }));
198
199 {
200 MediaTrackGraphImpl* graph = dummySource->GraphImpl();
201 MonitorAutoLock lock(graph->GetMonitor());
202 EXPECT_TRUE(graph->CurrentDriver()->AsAudioCallbackDriver());
203 EXPECT_TRUE(graph->CurrentDriver()->ThreadRunning());
204 }
205
206 // Test has finished, destroy the track to shutdown the MTG.
207 DispatchMethod(dummySource, &SourceMediaTrack::Destroy);
208 WaitFor(cubeb->StreamDestroyEvent());
209 }
210
211 #ifdef MOZ_WEBRTC
TEST(TestAudioTrackGraph,ErrorCallback)212 TEST(TestAudioTrackGraph, ErrorCallback)
213 {
214 MockCubeb* cubeb = new MockCubeb();
215 CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());
216
217 MediaTrackGraph* graph = MediaTrackGraph::GetInstance(
218 MediaTrackGraph::SYSTEM_THREAD_DRIVER, /*window*/ nullptr,
219 MediaTrackGraph::REQUEST_DEFAULT_SAMPLE_RATE, nullptr);
220
221 const CubebUtils::AudioDeviceID deviceId = (void*)1;
222
223 // Dummy track to make graph rolling. Add it and remove it to remove the
224 // graph from the global hash table and let it shutdown.
225 //
226 // We open an input through this track so that there's something triggering
227 // EnsureNextIteration on the fallback driver after the callback driver has
228 // gotten the error.
229 RefPtr<AudioInputTrack> inputTrack;
230 RefPtr<AudioInputProcessing> listener;
231 auto started = Invoke([&] {
232 inputTrack = AudioInputTrack::Create(graph);
233 listener = new AudioInputProcessing(2, PRINCIPAL_HANDLE_NONE);
234 inputTrack->GraphImpl()->AppendMessage(
235 MakeUnique<SetPassThrough>(inputTrack, listener, true));
236 inputTrack->SetInputProcessing(listener);
237 inputTrack->GraphImpl()->AppendMessage(
238 MakeUnique<StartInputProcessing>(inputTrack, listener));
239 inputTrack->OpenAudioInput(deviceId, listener);
240 EXPECT_EQ(inputTrack->DeviceId().value(), deviceId);
241 return graph->NotifyWhenDeviceStarted(inputTrack);
242 });
243
244 RefPtr<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent());
245 Result<bool, nsresult> rv = WaitFor(started);
246 EXPECT_TRUE(rv.unwrapOr(false));
247
248 // Force a cubeb state_callback error and see that we don't crash.
249 DispatchFunction([&] { stream->ForceError(); });
250
251 // Wait for both the error to take effect, and the driver to restart.
252 bool errored = false, init = false;
253 MediaEventListener errorListener = stream->ErrorForcedEvent().Connect(
254 AbstractThread::GetCurrent(), [&] { errored = true; });
255 MediaEventListener initListener = cubeb->StreamInitEvent().Connect(
256 AbstractThread::GetCurrent(), [&] { init = true; });
257 SpinEventLoopUntil<ProcessFailureBehavior::IgnoreAndContinue>(
258 [&] { return errored && init; });
259 errorListener.Disconnect();
260 initListener.Disconnect();
261
262 // Clean up.
263 DispatchFunction([&] {
264 inputTrack->GraphImpl()->AppendMessage(
265 MakeUnique<StopInputProcessing>(listener));
266 inputTrack->CloseAudioInput();
267 inputTrack->Destroy();
268 });
269 WaitFor(cubeb->StreamDestroyEvent());
270 }
271
TEST(TestAudioTrackGraph,AudioInputTrack)272 TEST(TestAudioTrackGraph, AudioInputTrack)
273 {
274 MockCubeb* cubeb = new MockCubeb();
275 CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());
276 auto unforcer = WaitFor(cubeb->ForceAudioThread()).unwrap();
277 Unused << unforcer;
278
279 // Start on a system clock driver, then switch to full-duplex in one go. If we
280 // did output-then-full-duplex we'd risk a second NotifyWhenDeviceStarted
281 // resolving early after checking the first audio driver only.
282 MediaTrackGraph* graph = MediaTrackGraph::GetInstance(
283 MediaTrackGraph::SYSTEM_THREAD_DRIVER, /*window*/ nullptr,
284 MediaTrackGraph::REQUEST_DEFAULT_SAMPLE_RATE, nullptr);
285
286 const CubebUtils::AudioDeviceID deviceId = (void*)1;
287
288 RefPtr<AudioInputTrack> inputTrack;
289 RefPtr<ProcessedMediaTrack> outputTrack;
290 RefPtr<MediaInputPort> port;
291 RefPtr<AudioInputProcessing> listener;
292 auto p = Invoke([&] {
293 inputTrack = AudioInputTrack::Create(graph);
294 outputTrack = graph->CreateForwardedInputTrack(MediaSegment::AUDIO);
295 outputTrack->QueueSetAutoend(false);
296 outputTrack->AddAudioOutput(reinterpret_cast<void*>(1));
297 port = outputTrack->AllocateInputPort(inputTrack);
298 /* Primary graph: Open Audio Input through SourceMediaTrack */
299 listener = new AudioInputProcessing(2, PRINCIPAL_HANDLE_NONE);
300 inputTrack->GraphImpl()->AppendMessage(
301 MakeUnique<SetPassThrough>(inputTrack, listener, true));
302 inputTrack->SetInputProcessing(listener);
303 inputTrack->GraphImpl()->AppendMessage(
304 MakeUnique<StartInputProcessing>(inputTrack, listener));
305 // Device id does not matter. Ignore.
306 inputTrack->OpenAudioInput(deviceId, listener);
307 return graph->NotifyWhenDeviceStarted(inputTrack);
308 });
309
310 RefPtr<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent());
311 EXPECT_TRUE(stream->mHasInput);
312 Unused << WaitFor(p);
313
314 // Wait for a second worth of audio data. GoFaster is dispatched through a
315 // ControlMessage so that it is called in the first audio driver iteration.
316 // Otherwise the audio driver might be going very fast while the fallback
317 // system clock driver is still in an iteration.
318 DispatchFunction([&] {
319 inputTrack->GraphImpl()->AppendMessage(MakeUnique<GoFaster>(cubeb));
320 });
321 uint32_t totalFrames = 0;
322 WaitUntil(stream->FramesVerifiedEvent(), [&](uint32_t aFrames) {
323 totalFrames += aFrames;
324 return totalFrames > static_cast<uint32_t>(graph->GraphRate());
325 });
326 cubeb->DontGoFaster();
327
328 // Clean up.
329 DispatchFunction([&] {
330 outputTrack->RemoveAudioOutput((void*)1);
331 outputTrack->Destroy();
332 port->Destroy();
333 inputTrack->GraphImpl()->AppendMessage(
334 MakeUnique<StopInputProcessing>(listener));
335 inputTrack->CloseAudioInput();
336 inputTrack->Destroy();
337 });
338
339 uint32_t inputRate = stream->InputSampleRate();
340 uint32_t inputFrequency = stream->InputFrequency();
341 uint64_t preSilenceSamples;
342 uint32_t estimatedFreq;
343 uint32_t nrDiscontinuities;
344 Tie(preSilenceSamples, estimatedFreq, nrDiscontinuities) =
345 WaitFor(stream->OutputVerificationEvent());
346
347 EXPECT_EQ(estimatedFreq, inputFrequency);
348 std::cerr << "PreSilence: " << preSilenceSamples << std::endl;
349 // We buffer 128 frames in passthrough mode. See AudioInputProcessing::Pull.
350 EXPECT_GE(preSilenceSamples, 128U);
351 // If the fallback system clock driver is doing a graph iteration before the
352 // first audio driver iteration comes in, that iteration is ignored and
353 // results in zeros. It takes one fallback driver iteration *after* the audio
354 // driver has started to complete the switch, *usually* resulting two
355 // 10ms-iterations of silence; sometimes only one.
356 EXPECT_LE(preSilenceSamples, 128U + 2 * inputRate / 100 /* 2*10ms */);
357 // The waveform from AudioGenerator starts at 0, but we don't control its
358 // ending, so we expect a discontinuity there.
359 EXPECT_LE(nrDiscontinuities, 1U);
360 }
361
TEST(TestAudioTrackGraph,ReOpenAudioInput)362 TEST(TestAudioTrackGraph, ReOpenAudioInput)
363 {
364 MockCubeb* cubeb = new MockCubeb();
365 CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());
366
367 // 48k is a native processing rate, and avoids a resampling pass compared
368 // to 44.1k. The resampler may add take a few frames to stabilize, which show
369 // as unexected discontinuities in the test.
370 const TrackRate rate = 48000;
371
372 MediaTrackGraph* graph = MediaTrackGraph::GetInstance(
373 MediaTrackGraph::SYSTEM_THREAD_DRIVER, /*window*/ nullptr, rate, nullptr);
374
375 const CubebUtils::AudioDeviceID deviceId = (void*)1;
376
377 RefPtr<AudioInputTrack> inputTrack;
378 RefPtr<ProcessedMediaTrack> outputTrack;
379 RefPtr<MediaInputPort> port;
380 RefPtr<AudioInputProcessing> listener;
381 auto p = Invoke([&] {
382 inputTrack = AudioInputTrack::Create(graph);
383 outputTrack = graph->CreateForwardedInputTrack(MediaSegment::AUDIO);
384 outputTrack->QueueSetAutoend(false);
385 outputTrack->AddAudioOutput(reinterpret_cast<void*>(1));
386 port = outputTrack->AllocateInputPort(inputTrack);
387 listener = new AudioInputProcessing(2, PRINCIPAL_HANDLE_NONE);
388 inputTrack->SetInputProcessing(listener);
389 inputTrack->GraphImpl()->AppendMessage(
390 MakeUnique<StartInputProcessing>(inputTrack, listener));
391 inputTrack->OpenAudioInput(deviceId, listener);
392 return graph->NotifyWhenDeviceStarted(inputTrack);
393 });
394
395 RefPtr<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent());
396 EXPECT_TRUE(stream->mHasInput);
397 Unused << WaitFor(p);
398
399 // Set a drift factor so that we don't dont produce perfect 10ms-chunks. This
400 // will exercise whatever buffers are in the audio processing pipeline, and
401 // the bookkeeping surrounding them.
402 stream->SetDriftFactor(1.111);
403
404 // Wait for a second worth of audio data. GoFaster is dispatched through a
405 // ControlMessage so that it is called in the first audio driver iteration.
406 // Otherwise the audio driver might be going very fast while the fallback
407 // system clock driver is still in an iteration.
408 DispatchFunction([&] {
409 inputTrack->GraphImpl()->AppendMessage(MakeUnique<GoFaster>(cubeb));
410 });
411 {
412 uint32_t totalFrames = 0;
413 WaitUntil(stream->FramesProcessedEvent(), [&](uint32_t aFrames) {
414 totalFrames += aFrames;
415 return totalFrames > static_cast<uint32_t>(graph->GraphRate());
416 });
417 }
418 cubeb->DontGoFaster();
419
420 // Close the input to see that no asserts go off due to bad state.
421 DispatchFunction([&] { inputTrack->CloseAudioInput(); });
422
423 stream = WaitFor(cubeb->StreamInitEvent());
424 EXPECT_FALSE(stream->mHasInput);
425 Unused << WaitFor(
426 Invoke([&] { return graph->NotifyWhenDeviceStarted(inputTrack); }));
427
428 // Output-only. Wait for another second before unmuting.
429 DispatchFunction([&] {
430 inputTrack->GraphImpl()->AppendMessage(MakeUnique<GoFaster>(cubeb));
431 });
432 {
433 uint32_t totalFrames = 0;
434 WaitUntil(stream->FramesProcessedEvent(), [&](uint32_t aFrames) {
435 totalFrames += aFrames;
436 return totalFrames > static_cast<uint32_t>(graph->GraphRate());
437 });
438 }
439 cubeb->DontGoFaster();
440
441 // Re-open the input to again see that no asserts go off due to bad state.
442 DispatchFunction([&] {
443 // Device id does not matter. Ignore.
444 inputTrack->OpenAudioInput(deviceId, listener);
445 });
446
447 stream = WaitFor(cubeb->StreamInitEvent());
448 EXPECT_TRUE(stream->mHasInput);
449 Unused << WaitFor(
450 Invoke([&] { return graph->NotifyWhenDeviceStarted(inputTrack); }));
451
452 // Full-duplex. Wait for another second before finishing.
453 DispatchFunction([&] {
454 inputTrack->GraphImpl()->AppendMessage(MakeUnique<GoFaster>(cubeb));
455 });
456 {
457 uint32_t totalFrames = 0;
458 WaitUntil(stream->FramesProcessedEvent(), [&](uint32_t aFrames) {
459 totalFrames += aFrames;
460 return totalFrames > static_cast<uint32_t>(graph->GraphRate());
461 });
462 }
463 cubeb->DontGoFaster();
464
465 // Clean up.
466 DispatchFunction([&] {
467 outputTrack->RemoveAudioOutput((void*)1);
468 outputTrack->Destroy();
469 port->Destroy();
470 inputTrack->GraphImpl()->AppendMessage(
471 MakeUnique<StopInputProcessing>(listener));
472 inputTrack->CloseAudioInput();
473 inputTrack->Destroy();
474 });
475
476 uint32_t inputRate = stream->InputSampleRate();
477 uint32_t inputFrequency = stream->InputFrequency();
478 uint64_t preSilenceSamples;
479 uint32_t estimatedFreq;
480 uint32_t nrDiscontinuities;
481 Tie(preSilenceSamples, estimatedFreq, nrDiscontinuities) =
482 WaitFor(stream->OutputVerificationEvent());
483
484 EXPECT_EQ(estimatedFreq, inputFrequency);
485 std::cerr << "PreSilence: " << preSilenceSamples << std::endl;
486 // We buffer 10ms worth of frames in non-passthrough mode, plus up to 128
487 // frames as we round up to the nearest block. See AudioInputProcessing::Pull.
488 EXPECT_GE(preSilenceSamples, 128U + inputRate / 100);
489 // If the fallback system clock driver is doing a graph iteration before the
490 // first audio driver iteration comes in, that iteration is ignored and
491 // results in zeros. It takes one fallback driver iteration *after* the audio
492 // driver has started to complete the switch, *usually* resulting two
493 // 10ms-iterations of silence; sometimes only one.
494 EXPECT_LE(preSilenceSamples, 128U + 3 * inputRate / 100 /* 3*10ms */);
495 // The waveform from AudioGenerator starts at 0, but we don't control its
496 // ending, so we expect a discontinuity there. Note that this check is only
497 // for the waveform on the stream *after* re-opening the input.
498 EXPECT_LE(nrDiscontinuities, 1U);
499 }
500
501 // Sum the signal to mono and compute the root mean square, in float32,
502 // regardless of the input format.
rmsf32(AudioDataValue * aSamples,uint32_t aChannels,uint32_t aFrames)503 float rmsf32(AudioDataValue* aSamples, uint32_t aChannels, uint32_t aFrames) {
504 float downmixed;
505 float rms = 0.;
506 uint32_t readIdx = 0;
507 for (uint32_t i = 0; i < aFrames; i++) {
508 downmixed = 0.;
509 for (uint32_t j = 0; j < aChannels; j++) {
510 downmixed += AudioSampleToFloat(aSamples[readIdx++]);
511 }
512 rms += downmixed * downmixed;
513 }
514 rms = rms / aFrames;
515 return sqrt(rms);
516 }
517
TEST(TestAudioTrackGraph,AudioInputTrackDisabling)518 TEST(TestAudioTrackGraph, AudioInputTrackDisabling)
519 {
520 MockCubeb* cubeb = new MockCubeb();
521 CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());
522
523 MediaTrackGraph* graph = MediaTrackGraph::GetInstance(
524 MediaTrackGraph::SYSTEM_THREAD_DRIVER, /*window*/ nullptr,
525 MediaTrackGraph::REQUEST_DEFAULT_SAMPLE_RATE, nullptr);
526
527 const CubebUtils::AudioDeviceID deviceId = (void*)1;
528
529 RefPtr<AudioInputTrack> inputTrack;
530 RefPtr<ProcessedMediaTrack> outputTrack;
531 RefPtr<MediaInputPort> port;
532 RefPtr<AudioInputProcessing> listener;
533 auto p = Invoke([&] {
534 inputTrack = AudioInputTrack::Create(graph);
535 outputTrack = graph->CreateForwardedInputTrack(MediaSegment::AUDIO);
536 outputTrack->QueueSetAutoend(false);
537 outputTrack->AddAudioOutput(reinterpret_cast<void*>(1));
538 port = outputTrack->AllocateInputPort(inputTrack);
539 /* Primary graph: Open Audio Input through SourceMediaTrack */
540 listener = new AudioInputProcessing(2, PRINCIPAL_HANDLE_NONE);
541 inputTrack->GraphImpl()->AppendMessage(
542 MakeUnique<SetPassThrough>(inputTrack, listener, true));
543 inputTrack->SetInputProcessing(listener);
544 inputTrack->OpenAudioInput(deviceId, listener);
545 inputTrack->GraphImpl()->AppendMessage(
546 MakeUnique<StartInputProcessing>(inputTrack, listener));
547 return graph->NotifyWhenDeviceStarted(inputTrack);
548 });
549
550 RefPtr<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent());
551 EXPECT_TRUE(stream->mHasInput);
552 Unused << WaitFor(p);
553
554 stream->SetOutputRecordingEnabled(true);
555
556 // Wait for a second worth of audio data.
557 uint32_t totalFrames = 0;
558 WaitUntil(stream->FramesProcessedEvent(), [&](uint32_t aFrames) {
559 totalFrames += aFrames;
560 return totalFrames > static_cast<uint32_t>(graph->GraphRate());
561 });
562
563 const uint32_t ITERATION_COUNT = 5;
564 uint32_t iterations = ITERATION_COUNT;
565 DisabledTrackMode currentMode = DisabledTrackMode::SILENCE_BLACK;
566 while (iterations--) {
567 // toggle the track enabled mode, wait a second, do this ITERATION_COUNT
568 // times
569 DispatchFunction([&] {
570 inputTrack->SetDisabledTrackMode(currentMode);
571 if (currentMode == DisabledTrackMode::SILENCE_BLACK) {
572 currentMode = DisabledTrackMode::ENABLED;
573 } else {
574 currentMode = DisabledTrackMode::SILENCE_BLACK;
575 }
576 });
577
578 totalFrames = 0;
579 WaitUntil(stream->FramesProcessedEvent(), [&](uint32_t aFrames) {
580 totalFrames += aFrames;
581 return totalFrames > static_cast<uint32_t>(graph->GraphRate());
582 });
583 }
584
585 // Clean up.
586 DispatchFunction([&] {
587 outputTrack->RemoveAudioOutput((void*)1);
588 outputTrack->Destroy();
589 port->Destroy();
590 inputTrack->GraphImpl()->AppendMessage(
591 MakeUnique<StopInputProcessing>(listener));
592 inputTrack->CloseAudioInput();
593 inputTrack->Destroy();
594 });
595
596 uint64_t preSilenceSamples;
597 uint32_t estimatedFreq;
598 uint32_t nrDiscontinuities;
599 Tie(preSilenceSamples, estimatedFreq, nrDiscontinuities) =
600 WaitFor(stream->OutputVerificationEvent());
601
602 auto data = stream->TakeRecordedOutput();
603
604 // check that there is non-silence and silence at the expected time in the
605 // stereo recording, while allowing for a bit of scheduling uncertainty, by
606 // checking half a second after the theoretical muting/unmuting.
607 // non-silence starts around: 0s, 2s, 4s
608 // silence start around: 1s, 3s, 5s
609 // To detect silence or non-silence, we compute the RMS of the signal for
610 // 100ms.
611 float noisyTime_s[] = {0.5, 2.5, 4.5};
612 float silenceTime_s[] = {1.5, 3.5, 5.5};
613
614 uint32_t rate = graph->GraphRate();
615 for (float& time : noisyTime_s) {
616 uint32_t startIdx = time * rate * 2 /* stereo */;
617 EXPECT_NE(rmsf32(&(data[startIdx]), 2, rate / 10), 0.0);
618 }
619
620 for (float& time : silenceTime_s) {
621 uint32_t startIdx = time * rate * 2 /* stereo */;
622 EXPECT_EQ(rmsf32(&(data[startIdx]), 2, rate / 10), 0.0);
623 }
624 }
625
TestCrossGraphPort(uint32_t aInputRate,uint32_t aOutputRate,float aDriftFactor,uint32_t aBufferMs=50)626 void TestCrossGraphPort(uint32_t aInputRate, uint32_t aOutputRate,
627 float aDriftFactor, uint32_t aBufferMs = 50) {
628 std::cerr << "TestCrossGraphPort input: " << aInputRate
629 << ", output: " << aOutputRate << ", driftFactor: " << aDriftFactor
630 << std::endl;
631
632 MockCubeb* cubeb = new MockCubeb();
633 CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());
634 auto unforcer = WaitFor(cubeb->ForceAudioThread()).unwrap();
635 Unused << unforcer;
636
637 cubeb->SetStreamStartFreezeEnabled(true);
638
639 /* Primary graph: Create the graph. */
640 MediaTrackGraph* primary =
641 MediaTrackGraph::GetInstance(MediaTrackGraph::SYSTEM_THREAD_DRIVER,
642 /*window*/ nullptr, aInputRate, nullptr);
643
644 /* Partner graph: Create the graph. */
645 MediaTrackGraph* partner = MediaTrackGraph::GetInstance(
646 MediaTrackGraph::SYSTEM_THREAD_DRIVER, /*window*/ nullptr, aOutputRate,
647 /*OutputDeviceID*/ reinterpret_cast<cubeb_devid>(1));
648
649 const CubebUtils::AudioDeviceID deviceId = (void*)1;
650
651 RefPtr<AudioInputTrack> inputTrack;
652 RefPtr<AudioInputProcessing> listener;
653 auto primaryStarted = Invoke([&] {
654 /* Primary graph: Create input track and open it */
655 inputTrack = AudioInputTrack::Create(primary);
656 listener = new AudioInputProcessing(2, PRINCIPAL_HANDLE_NONE);
657 inputTrack->GraphImpl()->AppendMessage(
658 MakeUnique<SetPassThrough>(inputTrack, listener, true));
659 inputTrack->SetInputProcessing(listener);
660 inputTrack->GraphImpl()->AppendMessage(
661 MakeUnique<StartInputProcessing>(inputTrack, listener));
662 inputTrack->OpenAudioInput(deviceId, listener);
663 return primary->NotifyWhenDeviceStarted(inputTrack);
664 });
665
666 RefPtr<SmartMockCubebStream> inputStream = WaitFor(cubeb->StreamInitEvent());
667
668 RefPtr<CrossGraphTransmitter> transmitter;
669 RefPtr<MediaInputPort> port;
670 RefPtr<CrossGraphReceiver> receiver;
671 auto partnerStarted = Invoke([&] {
672 /* Partner graph: Create CrossGraphReceiver */
673 receiver = partner->CreateCrossGraphReceiver(primary->GraphRate());
674
675 /* Primary graph: Create CrossGraphTransmitter */
676 transmitter = primary->CreateCrossGraphTransmitter(receiver);
677
678 /* How the input track connects to another ProcessedMediaTrack.
679 * Check in MediaManager how it is connected to AudioStreamTrack. */
680 port = transmitter->AllocateInputPort(inputTrack);
681 receiver->AddAudioOutput((void*)1);
682 return partner->NotifyWhenDeviceStarted(receiver);
683 });
684
685 RefPtr<SmartMockCubebStream> partnerStream =
686 WaitFor(cubeb->StreamInitEvent());
687 partnerStream->SetDriftFactor(aDriftFactor);
688
689 cubeb->SetStreamStartFreezeEnabled(false);
690
691 // One source of non-determinism in this type of test is that inputStream
692 // and partnerStream are started in sequence by the CubebOperation thread pool
693 // (of size 1). To minimize the chance that the stream that starts first sees
694 // an iteration before the other has started - this is a source of pre-silence
695 // - we freeze both on start and thaw them together here.
696 // Note that another source of non-determinism is the fallback driver. Handing
697 // over from the fallback to the audio driver requires first an audio callback
698 // (deterministic with the fake audio thread), then a fallback driver
699 // iteration (non-deterministic, since each graph has its own fallback driver,
700 // each with its own dedicated thread, which we have no control over). This
701 // non-determinism is worrisome, but both fallback drivers are likely to
702 // exhibit similar characteristics, hopefully keeping the level of
703 // non-determinism down sufficiently for this test to pass.
704 inputStream->Thaw();
705 partnerStream->Thaw();
706
707 Unused << WaitFor(primaryStarted);
708 Unused << WaitFor(partnerStarted);
709
710 // Wait for 3s worth of audio data on the receiver stream.
711 DispatchFunction([&] {
712 inputTrack->GraphImpl()->AppendMessage(MakeUnique<GoFaster>(cubeb));
713 });
714 uint32_t totalFrames = 0;
715 WaitUntil(partnerStream->FramesVerifiedEvent(), [&](uint32_t aFrames) {
716 totalFrames += aFrames;
717 return totalFrames > static_cast<uint32_t>(partner->GraphRate() * 3);
718 });
719 cubeb->DontGoFaster();
720
721 DispatchFunction([&] {
722 // Clean up on MainThread
723 receiver->RemoveAudioOutput((void*)1);
724 receiver->Destroy();
725 transmitter->Destroy();
726 port->Destroy();
727 inputTrack->GraphImpl()->AppendMessage(
728 MakeUnique<StopInputProcessing>(listener));
729 inputTrack->CloseAudioInput();
730 inputTrack->Destroy();
731 });
732
733 uint32_t inputFrequency = inputStream->InputFrequency();
734 uint32_t partnerRate = partnerStream->InputSampleRate();
735
736 uint64_t preSilenceSamples;
737 float estimatedFreq;
738 uint32_t nrDiscontinuities;
739 Tie(preSilenceSamples, estimatedFreq, nrDiscontinuities) =
740 WaitFor(partnerStream->OutputVerificationEvent());
741
742 EXPECT_NEAR(estimatedFreq, inputFrequency / aDriftFactor, 5);
743 uint32_t expectedPreSilence =
744 static_cast<uint32_t>(partnerRate * aDriftFactor / 1000 * aBufferMs);
745 uint32_t margin = partnerRate / 20 /* +/- 50ms */;
746 EXPECT_NEAR(preSilenceSamples, expectedPreSilence, margin);
747 // The waveform from AudioGenerator starts at 0, but we don't control its
748 // ending, so we expect a discontinuity there.
749 EXPECT_LE(nrDiscontinuities, 1U);
750 }
751
TEST(TestAudioTrackGraph,CrossGraphPort)752 TEST(TestAudioTrackGraph, CrossGraphPort)
753 {
754 TestCrossGraphPort(44100, 44100, 1);
755 TestCrossGraphPort(44100, 44100, 1.08);
756 TestCrossGraphPort(44100, 44100, 0.92);
757
758 TestCrossGraphPort(48000, 44100, 1);
759 TestCrossGraphPort(48000, 44100, 1.08);
760 TestCrossGraphPort(48000, 44100, 0.92);
761
762 TestCrossGraphPort(44100, 48000, 1);
763 TestCrossGraphPort(44100, 48000, 1.08);
764 TestCrossGraphPort(44100, 48000, 0.92);
765
766 TestCrossGraphPort(52110, 17781, 1);
767 TestCrossGraphPort(52110, 17781, 1.08);
768 TestCrossGraphPort(52110, 17781, 0.92);
769 }
770
TEST(TestAudioTrackGraph,CrossGraphPortLargeBuffer)771 TEST(TestAudioTrackGraph, CrossGraphPortLargeBuffer)
772 {
773 const int32_t oldBuffering = Preferences::GetInt(DRIFT_BUFFERING_PREF);
774 const int32_t longBuffering = 5000;
775 Preferences::SetInt(DRIFT_BUFFERING_PREF, longBuffering);
776
777 TestCrossGraphPort(44100, 44100, 1.02, longBuffering);
778 TestCrossGraphPort(48000, 44100, 1.08, longBuffering);
779 TestCrossGraphPort(44100, 48000, 0.95, longBuffering);
780 TestCrossGraphPort(52110, 17781, 0.92, longBuffering);
781
782 Preferences::SetInt(DRIFT_BUFFERING_PREF, oldBuffering);
783 }
784 #endif // MOZ_WEBRTC
785