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