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 "CubebUtils.h"
7 #include "GraphDriver.h"
8 
9 #include "gmock/gmock.h"
10 #include "gtest/gtest-printers.h"
11 #include "gtest/gtest.h"
12 
13 #include "MediaTrackGraphImpl.h"
14 #include "mozilla/Attributes.h"
15 #include "mozilla/UniquePtr.h"
16 #include "nsTArray.h"
17 
18 #include "MockCubeb.h"
19 #include "WaitFor.h"
20 
21 using namespace mozilla;
22 using IterationResult = GraphInterface::IterationResult;
23 using ::testing::NiceMock;
24 
25 class MockGraphInterface : public GraphInterface {
26   NS_DECL_THREADSAFE_ISUPPORTS
MockGraphInterface(TrackRate aSampleRate)27   explicit MockGraphInterface(TrackRate aSampleRate)
28       : mSampleRate(aSampleRate) {}
29   MOCK_METHOD4(NotifyOutputData,
30                void(AudioDataValue*, size_t, TrackRate, uint32_t));
31   MOCK_METHOD0(NotifyInputStopped, void());
32   MOCK_METHOD5(NotifyInputData, void(const AudioDataValue*, size_t, TrackRate,
33                                      uint32_t, uint32_t));
34   MOCK_METHOD0(DeviceChanged, void());
35   /* OneIteration cannot be mocked because IterationResult is non-memmovable and
36    * cannot be passed as a parameter, which GMock does internally. */
OneIteration(GraphTime aStateComputedTime,GraphTime,AudioMixer * aMixer)37   IterationResult OneIteration(GraphTime aStateComputedTime, GraphTime,
38                                AudioMixer* aMixer) {
39     GraphDriver* driver = mCurrentDriver;
40     if (aMixer) {
41       aMixer->StartMixing();
42       aMixer->Mix(nullptr,
43                   driver->AsAudioCallbackDriver()->OutputChannelCount(),
44                   aStateComputedTime - mStateComputedTime, mSampleRate);
45       aMixer->FinishMixing();
46     }
47     if (aStateComputedTime != mStateComputedTime) {
48       mFramesIteratedEvent.Notify(aStateComputedTime - mStateComputedTime);
49       ++mIterationCount;
50     }
51     mStateComputedTime = aStateComputedTime;
52     if (!mKeepProcessing) {
53       return IterationResult::CreateStop(
54           NS_NewRunnableFunction(__func__, [] {}));
55     }
56     GraphDriver* next = mNextDriver.exchange(nullptr);
57     if (next) {
58       return IterationResult::CreateSwitchDriver(
59           next, NS_NewRunnableFunction(__func__, [] {}));
60     }
61     if (mEnsureNextIteration) {
62       driver->EnsureNextIteration();
63     }
64     return IterationResult::CreateStillProcessing();
65   }
SetEnsureNextIteration(bool aEnsure)66   void SetEnsureNextIteration(bool aEnsure) { mEnsureNextIteration = aEnsure; }
67 
68 #ifdef DEBUG
InDriverIteration(const GraphDriver * aDriver) const69   bool InDriverIteration(const GraphDriver* aDriver) const override {
70     return aDriver->OnThread();
71   }
72 #endif
73 
IterationCount() const74   size_t IterationCount() const { return mIterationCount; }
75 
StateComputedTime() const76   GraphTime StateComputedTime() const { return mStateComputedTime; }
SetCurrentDriver(GraphDriver * aDriver)77   void SetCurrentDriver(GraphDriver* aDriver) { mCurrentDriver = aDriver; }
78 
StopIterating()79   void StopIterating() { mKeepProcessing = false; }
80 
SwitchTo(GraphDriver * aDriver)81   void SwitchTo(GraphDriver* aDriver) { mNextDriver = aDriver; }
82   const TrackRate mSampleRate;
83 
FramesIteratedEvent()84   MediaEventSource<uint32_t>& FramesIteratedEvent() {
85     return mFramesIteratedEvent;
86   }
87 
88  protected:
89   Atomic<size_t> mIterationCount{0};
90   Atomic<GraphTime> mStateComputedTime{0};
91   Atomic<GraphDriver*> mCurrentDriver{nullptr};
92   Atomic<bool> mEnsureNextIteration{false};
93   Atomic<bool> mKeepProcessing{true};
94   Atomic<GraphDriver*> mNextDriver{nullptr};
95   MediaEventProducer<uint32_t> mFramesIteratedEvent;
96   virtual ~MockGraphInterface() = default;
97 };
98 
99 NS_IMPL_ISUPPORTS0(MockGraphInterface)
100 
TEST(TestAudioCallbackDriver,StartStop)101 TEST(TestAudioCallbackDriver, StartStop)
102 MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
103   const TrackRate rate = 44100;
104   MockCubeb* cubeb = new MockCubeb();
105   CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());
106 
107   RefPtr<AudioCallbackDriver> driver;
108   auto graph = MakeRefPtr<NiceMock<MockGraphInterface>>(rate);
109   EXPECT_CALL(*graph, NotifyInputStopped).Times(0);
110   ON_CALL(*graph, NotifyOutputData)
111       .WillByDefault([&](AudioDataValue*, size_t, TrackRate, uint32_t) {});
112 
113   driver = MakeRefPtr<AudioCallbackDriver>(graph, nullptr, rate, 2, 0, nullptr,
114                                            nullptr, AudioInputType::Unknown);
115   EXPECT_FALSE(driver->ThreadRunning()) << "Verify thread is not running";
116   EXPECT_FALSE(driver->IsStarted()) << "Verify thread is not started";
117 
118   graph->SetCurrentDriver(driver);
119   driver->Start();
120   // Allow some time to "play" audio.
121   std::this_thread::sleep_for(std::chrono::milliseconds(200));
122   EXPECT_TRUE(driver->ThreadRunning()) << "Verify thread is running";
123   EXPECT_TRUE(driver->IsStarted()) << "Verify thread is started";
124 
125   // This will block untill all events have been executed.
126   MOZ_KnownLive(driver)->Shutdown();
127   EXPECT_FALSE(driver->ThreadRunning()) << "Verify thread is not running";
128   EXPECT_FALSE(driver->IsStarted()) << "Verify thread is not started";
129 }
130 
TestSlowStart(const TrackRate aRate)131 void TestSlowStart(const TrackRate aRate) MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
132   std::cerr << "TestSlowStart with rate " << aRate << std::endl;
133 
134   MockCubeb* cubeb = new MockCubeb();
135   cubeb->SetStreamStartFreezeEnabled(true);
136   auto unforcer = WaitFor(cubeb->ForceAudioThread()).unwrap();
137   Unused << unforcer;
138   CubebUtils::ForceSetCubebContext(cubeb->AsCubebContext());
139 
140   RefPtr<AudioCallbackDriver> driver;
141   auto graph = MakeRefPtr<NiceMock<MockGraphInterface>>(aRate);
142   EXPECT_CALL(*graph, NotifyInputStopped).Times(0);
143 
144   Maybe<int64_t> audioStart;
145   Maybe<uint32_t> alreadyBuffered;
146   int64_t inputFrameCount = 0;
147   int64_t outputFrameCount = 0;
148   int64_t processedFrameCount = 0;
149   ON_CALL(*graph, NotifyInputData)
150       .WillByDefault([&](const AudioDataValue*, size_t aFrames, TrackRate,
151                          uint32_t, uint32_t aAlreadyBuffered) {
152         if (!audioStart) {
153           audioStart = Some(graph->StateComputedTime());
154           alreadyBuffered = Some(aAlreadyBuffered);
155         }
156         EXPECT_NEAR(inputFrameCount,
157                     static_cast<int64_t>(graph->StateComputedTime() -
158                                          *audioStart + *alreadyBuffered),
159                     WEBAUDIO_BLOCK_SIZE)
160             << "Input should be behind state time, due to the delayed start. "
161                "stateComputedTime="
162             << graph->StateComputedTime() << ", audioStartTime=" << *audioStart
163             << ", alreadyBuffered=" << *alreadyBuffered;
164         inputFrameCount += aFrames;
165       });
166   ON_CALL(*graph, NotifyOutputData)
167       .WillByDefault([&](AudioDataValue*, size_t aFrames, TrackRate aRate,
168                          uint32_t) { outputFrameCount += aFrames; });
169 
170   driver = MakeRefPtr<AudioCallbackDriver>(graph, nullptr, aRate, 2, 2, nullptr,
171                                            (void*)1, AudioInputType::Voice);
172   EXPECT_FALSE(driver->ThreadRunning()) << "Verify thread is not running";
173   EXPECT_FALSE(driver->IsStarted()) << "Verify thread is not started";
174 
175   graph->SetCurrentDriver(driver);
176   graph->SetEnsureNextIteration(true);
177 
178   driver->Start();
179   RefPtr<SmartMockCubebStream> stream = WaitFor(cubeb->StreamInitEvent());
180   cubeb->SetStreamStartFreezeEnabled(false);
181 
182   const size_t fallbackIterations = 3;
183   WaitUntil(graph->FramesIteratedEvent(), [&](uint32_t aFrames) {
184     const GraphTime tenMillis = aRate / 100;
185     // An iteration is always rounded upwards to the next full block.
186     const GraphTime tenMillisIteration =
187         MediaTrackGraphImpl::RoundUpToEndOfAudioBlock(tenMillis);
188     // The iteration may be smaller because up to an extra block may have been
189     // processed and buffered.
190     const GraphTime tenMillisMinIteration =
191         tenMillisIteration - WEBAUDIO_BLOCK_SIZE;
192     // An iteration must be at least one audio block.
193     const GraphTime minIteration =
194         std::max<GraphTime>(WEBAUDIO_BLOCK_SIZE, tenMillisMinIteration);
195     EXPECT_GE(aFrames, minIteration)
196         << "Fallback driver iteration >= 10ms, modulo an audio block";
197     EXPECT_LT(aFrames, static_cast<size_t>(aRate))
198         << "Fallback driver iteration <1s (sanity)";
199     return graph->IterationCount() >= fallbackIterations;
200   });
201   stream->Thaw();
202 
203   // Wait for at least 100ms of audio data.
204   WaitUntil(stream->FramesProcessedEvent(), [&](uint32_t aFrames) {
205     processedFrameCount += aFrames;
206     return processedFrameCount >= aRate / 10;
207   });
208 
209   // This will block untill all events have been executed.
210   MOZ_KnownLive(driver)->Shutdown();
211 
212   EXPECT_EQ(inputFrameCount, outputFrameCount);
213   EXPECT_NEAR(graph->StateComputedTime() - *audioStart,
214               inputFrameCount + *alreadyBuffered, WEBAUDIO_BLOCK_SIZE)
215       << "Graph progresses while audio driver runs. stateComputedTime="
216       << graph->StateComputedTime() << ", inputFrameCount=" << inputFrameCount;
217 }
218 
TEST(TestAudioCallbackDriver,SlowStart)219 TEST(TestAudioCallbackDriver, SlowStart)
220 MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
221   TestSlowStart(1000);   // 10ms = 10 <<< 128 samples
222   TestSlowStart(8000);   // 10ms = 80  <  128 samples
223   TestSlowStart(44100);  // 10ms = 441 >  128 samples
224 }
225