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