/src/mozilla-central/dom/media/GraphDriver.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* vim: set ts=8 sts=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 file, |
5 | | * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
6 | | |
7 | | #include <MediaStreamGraphImpl.h> |
8 | | #include "mozilla/dom/AudioContext.h" |
9 | | #include "mozilla/dom/AudioDeviceInfo.h" |
10 | | #include "mozilla/SharedThreadPool.h" |
11 | | #include "mozilla/ClearOnShutdown.h" |
12 | | #include "mozilla/Unused.h" |
13 | | #include "CubebUtils.h" |
14 | | #include "Tracing.h" |
15 | | |
16 | | #ifdef MOZ_WEBRTC |
17 | | #include "webrtc/MediaEngineWebRTC.h" |
18 | | #endif |
19 | | |
20 | | #ifdef XP_MACOSX |
21 | | #include <sys/sysctl.h> |
22 | | #endif |
23 | | |
24 | | extern mozilla::LazyLogModule gMediaStreamGraphLog; |
25 | | #ifdef LOG |
26 | | #undef LOG |
27 | | #endif // LOG |
28 | 0 | #define LOG(type, msg) MOZ_LOG(gMediaStreamGraphLog, type, msg) |
29 | | |
30 | | namespace mozilla { |
31 | | |
32 | | GraphDriver::GraphDriver(MediaStreamGraphImpl* aGraphImpl) |
33 | | : mIterationStart(0), |
34 | | mIterationEnd(0), |
35 | | mGraphImpl(aGraphImpl), |
36 | | mCurrentTimeStamp(TimeStamp::Now()), |
37 | | mPreviousDriver(nullptr), |
38 | | mNextDriver(nullptr) |
39 | 0 | { } |
40 | | |
41 | | void GraphDriver::SetGraphTime(GraphDriver* aPreviousDriver, |
42 | | GraphTime aLastSwitchNextIterationStart, |
43 | | GraphTime aLastSwitchNextIterationEnd) |
44 | 0 | { |
45 | 0 | MOZ_ASSERT(OnThread() || !ThreadRunning()); |
46 | 0 | GraphImpl()->GetMonitor().AssertCurrentThreadOwns(); |
47 | 0 | // We set mIterationEnd here, because the first thing a driver do when it |
48 | 0 | // does an iteration is to update graph times, so we are in fact setting |
49 | 0 | // mIterationStart of the next iteration by setting the end of the previous |
50 | 0 | // iteration. |
51 | 0 | mIterationStart = aLastSwitchNextIterationStart; |
52 | 0 | mIterationEnd = aLastSwitchNextIterationEnd; |
53 | 0 |
|
54 | 0 | MOZ_ASSERT(!PreviousDriver()); |
55 | 0 | MOZ_ASSERT(aPreviousDriver); |
56 | 0 | MOZ_DIAGNOSTIC_ASSERT(GraphImpl()->CurrentDriver() == aPreviousDriver); |
57 | 0 |
|
58 | 0 | LOG(LogLevel::Debug, |
59 | 0 | ("%p: Setting previous driver: %p (%s)", |
60 | 0 | GraphImpl(), |
61 | 0 | aPreviousDriver, |
62 | 0 | aPreviousDriver->AsAudioCallbackDriver() ? "AudioCallbackDriver" |
63 | 0 | : "SystemClockDriver")); |
64 | 0 |
|
65 | 0 | SetPreviousDriver(aPreviousDriver); |
66 | 0 | } |
67 | | |
68 | | void GraphDriver::SwitchAtNextIteration(GraphDriver* aNextDriver) |
69 | 0 | { |
70 | 0 | MOZ_ASSERT(OnThread()); |
71 | 0 | MOZ_ASSERT(aNextDriver); |
72 | 0 | GraphImpl()->GetMonitor().AssertCurrentThreadOwns(); |
73 | 0 |
|
74 | 0 | LOG(LogLevel::Debug, |
75 | 0 | ("%p: Switching to new driver: %p (%s)", |
76 | 0 | GraphImpl(), |
77 | 0 | aNextDriver, |
78 | 0 | aNextDriver->AsAudioCallbackDriver() ? "AudioCallbackDriver" |
79 | 0 | : "SystemClockDriver")); |
80 | 0 | if (mNextDriver && |
81 | 0 | mNextDriver != GraphImpl()->CurrentDriver()) { |
82 | 0 | LOG(LogLevel::Debug, |
83 | 0 | ("%p: Discarding previous next driver: %p (%s)", |
84 | 0 | GraphImpl(), |
85 | 0 | mNextDriver.get(), |
86 | 0 | mNextDriver->AsAudioCallbackDriver() ? "AudioCallbackDriver" |
87 | 0 | : "SystemClockDriver")); |
88 | 0 | } |
89 | 0 | SetNextDriver(aNextDriver); |
90 | 0 | } |
91 | | |
92 | | GraphTime |
93 | | GraphDriver::StateComputedTime() const |
94 | 0 | { |
95 | 0 | return GraphImpl()->mStateComputedTime; |
96 | 0 | } |
97 | | |
98 | | void GraphDriver::EnsureNextIteration() |
99 | 0 | { |
100 | 0 | GraphImpl()->EnsureNextIteration(); |
101 | 0 | } |
102 | | |
103 | | bool GraphDriver::Switching() |
104 | 0 | { |
105 | 0 | MOZ_ASSERT(OnThread()); |
106 | 0 | GraphImpl()->GetMonitor().AssertCurrentThreadOwns(); |
107 | 0 | return mNextDriver || mPreviousDriver; |
108 | 0 | } |
109 | | |
110 | | void GraphDriver::SwitchToNextDriver() |
111 | 0 | { |
112 | 0 | MOZ_ASSERT(OnThread() || !ThreadRunning()); |
113 | 0 | GraphImpl()->GetMonitor().AssertCurrentThreadOwns(); |
114 | 0 | MOZ_ASSERT(NextDriver()); |
115 | 0 |
|
116 | 0 | NextDriver()->SetGraphTime(this, mIterationStart, mIterationEnd); |
117 | 0 | GraphImpl()->SetCurrentDriver(NextDriver()); |
118 | 0 | NextDriver()->Start(); |
119 | 0 | SetNextDriver(nullptr); |
120 | 0 | } |
121 | | |
122 | | GraphDriver* GraphDriver::NextDriver() |
123 | 0 | { |
124 | 0 | MOZ_ASSERT(OnThread() || !ThreadRunning()); |
125 | 0 | GraphImpl()->GetMonitor().AssertCurrentThreadOwns(); |
126 | 0 | return mNextDriver; |
127 | 0 | } |
128 | | |
129 | | GraphDriver* GraphDriver::PreviousDriver() |
130 | 0 | { |
131 | 0 | MOZ_ASSERT(OnThread() || !ThreadRunning()); |
132 | 0 | GraphImpl()->GetMonitor().AssertCurrentThreadOwns(); |
133 | 0 | return mPreviousDriver; |
134 | 0 | } |
135 | | |
136 | | void GraphDriver::SetNextDriver(GraphDriver* aNextDriver) |
137 | 0 | { |
138 | 0 | MOZ_ASSERT(OnThread() || !ThreadRunning()); |
139 | 0 | GraphImpl()->GetMonitor().AssertCurrentThreadOwns(); |
140 | 0 | MOZ_ASSERT(aNextDriver != this); |
141 | 0 | MOZ_ASSERT(aNextDriver != mNextDriver); |
142 | 0 |
|
143 | 0 | if (mNextDriver && |
144 | 0 | mNextDriver != GraphImpl()->CurrentDriver()) { |
145 | 0 | LOG(LogLevel::Debug, |
146 | 0 | ("Discarding previous next driver: %p (%s)", |
147 | 0 | mNextDriver.get(), |
148 | 0 | mNextDriver->AsAudioCallbackDriver() ? "AudioCallbackDriver" |
149 | 0 | : "SystemClockDriver")); |
150 | 0 | } |
151 | 0 |
|
152 | 0 | mNextDriver = aNextDriver; |
153 | 0 | } |
154 | | |
155 | | void GraphDriver::SetPreviousDriver(GraphDriver* aPreviousDriver) |
156 | 0 | { |
157 | 0 | MOZ_ASSERT(OnThread() || !ThreadRunning()); |
158 | 0 | GraphImpl()->GetMonitor().AssertCurrentThreadOwns(); |
159 | 0 | mPreviousDriver = aPreviousDriver; |
160 | 0 | } |
161 | | |
162 | | ThreadedDriver::ThreadedDriver(MediaStreamGraphImpl* aGraphImpl) |
163 | | : GraphDriver(aGraphImpl) |
164 | | , mThreadRunning(false) |
165 | 0 | { } |
166 | | |
167 | | class MediaStreamGraphShutdownThreadRunnable : public Runnable { |
168 | | public: |
169 | | explicit MediaStreamGraphShutdownThreadRunnable( |
170 | | already_AddRefed<nsIThread> aThread) |
171 | | : Runnable("MediaStreamGraphShutdownThreadRunnable") |
172 | | , mThread(aThread) |
173 | 0 | { |
174 | 0 | } |
175 | | NS_IMETHOD Run() override |
176 | 0 | { |
177 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
178 | 0 | MOZ_ASSERT(mThread); |
179 | 0 |
|
180 | 0 | mThread->Shutdown(); |
181 | 0 | mThread = nullptr; |
182 | 0 | return NS_OK; |
183 | 0 | } |
184 | | private: |
185 | | nsCOMPtr<nsIThread> mThread; |
186 | | }; |
187 | | |
188 | | ThreadedDriver::~ThreadedDriver() |
189 | 0 | { |
190 | 0 | if (mThread) { |
191 | 0 | nsCOMPtr<nsIRunnable> event = |
192 | 0 | new MediaStreamGraphShutdownThreadRunnable(mThread.forget()); |
193 | 0 | SystemGroup::Dispatch(TaskCategory::Other, event.forget()); |
194 | 0 | } |
195 | 0 | } |
196 | | |
197 | | class MediaStreamGraphInitThreadRunnable : public Runnable { |
198 | | public: |
199 | | explicit MediaStreamGraphInitThreadRunnable(ThreadedDriver* aDriver) |
200 | | : Runnable("MediaStreamGraphInitThreadRunnable") |
201 | | , mDriver(aDriver) |
202 | 0 | { |
203 | 0 | } |
204 | | NS_IMETHOD Run() override |
205 | 0 | { |
206 | 0 | MOZ_ASSERT(!mDriver->ThreadRunning()); |
207 | 0 | LOG(LogLevel::Debug, |
208 | 0 | ("Starting a new system driver for graph %p", |
209 | 0 | mDriver->mGraphImpl.get())); |
210 | 0 |
|
211 | 0 | RefPtr<GraphDriver> previousDriver; |
212 | 0 | { |
213 | 0 | MonitorAutoLock mon(mDriver->mGraphImpl->GetMonitor()); |
214 | 0 | previousDriver = mDriver->PreviousDriver(); |
215 | 0 | } |
216 | 0 | if (previousDriver) { |
217 | 0 | LOG(LogLevel::Debug, |
218 | 0 | ("%p releasing an AudioCallbackDriver(%p), for graph %p", |
219 | 0 | mDriver.get(), |
220 | 0 | previousDriver.get(), |
221 | 0 | mDriver->GraphImpl())); |
222 | 0 | MOZ_ASSERT(!mDriver->AsAudioCallbackDriver()); |
223 | 0 | RefPtr<AsyncCubebTask> releaseEvent = |
224 | 0 | new AsyncCubebTask(previousDriver->AsAudioCallbackDriver(), AsyncCubebOperation::SHUTDOWN); |
225 | 0 | releaseEvent->Dispatch(); |
226 | 0 |
|
227 | 0 | MonitorAutoLock mon(mDriver->mGraphImpl->GetMonitor()); |
228 | 0 | mDriver->SetPreviousDriver(nullptr); |
229 | 0 | } else { |
230 | 0 | MonitorAutoLock mon(mDriver->mGraphImpl->GetMonitor()); |
231 | 0 | MOZ_ASSERT(mDriver->mGraphImpl->MessagesQueued() || |
232 | 0 | mDriver->mGraphImpl->mForceShutDown, "Don't start a graph without messages queued."); |
233 | 0 | mDriver->mGraphImpl->SwapMessageQueues(); |
234 | 0 | } |
235 | 0 |
|
236 | 0 | mDriver->RunThread(); |
237 | 0 | return NS_OK; |
238 | 0 | } |
239 | | private: |
240 | | RefPtr<ThreadedDriver> mDriver; |
241 | | }; |
242 | | |
243 | | void |
244 | | ThreadedDriver::Start() |
245 | 0 | { |
246 | 0 | MOZ_ASSERT(!ThreadRunning()); |
247 | 0 | LOG(LogLevel::Debug, |
248 | 0 | ("Starting thread for a SystemClockDriver %p", mGraphImpl.get())); |
249 | 0 | Unused << NS_WARN_IF(mThread); |
250 | 0 | MOZ_ASSERT(!mThread); // Ensure we haven't already started it |
251 | 0 |
|
252 | 0 | nsCOMPtr<nsIRunnable> event = new MediaStreamGraphInitThreadRunnable(this); |
253 | 0 | // Note: mThread may be null during event->Run() if we pass to NewNamedThread! See AudioInitTask |
254 | 0 | nsresult rv = NS_NewNamedThread("MediaStreamGrph", getter_AddRefs(mThread)); |
255 | 0 | if (NS_SUCCEEDED(rv)) { |
256 | 0 | mThread->EventTarget()->Dispatch(event.forget(), NS_DISPATCH_NORMAL); |
257 | 0 | } |
258 | 0 | } |
259 | | |
260 | | void |
261 | | ThreadedDriver::Revive() |
262 | 0 | { |
263 | 0 | MOZ_ASSERT(NS_IsMainThread() && !ThreadRunning()); |
264 | 0 | // Note: only called on MainThread, without monitor |
265 | 0 | // We know were weren't in a running state |
266 | 0 | LOG(LogLevel::Debug, ("AudioCallbackDriver reviving.")); |
267 | 0 | // If we were switching, switch now. Otherwise, tell thread to run the main |
268 | 0 | // loop again. |
269 | 0 | MonitorAutoLock mon(mGraphImpl->GetMonitor()); |
270 | 0 | if (NextDriver()) { |
271 | 0 | SwitchToNextDriver(); |
272 | 0 | } else { |
273 | 0 | nsCOMPtr<nsIRunnable> event = new MediaStreamGraphInitThreadRunnable(this); |
274 | 0 | mThread->EventTarget()->Dispatch(event.forget(), NS_DISPATCH_NORMAL); |
275 | 0 | } |
276 | 0 | } |
277 | | |
278 | | void |
279 | | ThreadedDriver::Shutdown() |
280 | 0 | { |
281 | 0 | NS_ASSERTION(NS_IsMainThread(), "Must be called on main thread"); |
282 | 0 | // mGraph's thread is not running so it's OK to do whatever here |
283 | 0 | LOG(LogLevel::Debug, ("Stopping threads for MediaStreamGraph %p", this)); |
284 | 0 |
|
285 | 0 | if (mThread) { |
286 | 0 | LOG(LogLevel::Debug, ("%p: Stopping ThreadedDriver's %p thread", GraphImpl(), this)); |
287 | 0 | mThread->Shutdown(); |
288 | 0 | mThread = nullptr; |
289 | 0 | } |
290 | 0 | } |
291 | | |
292 | | SystemClockDriver::SystemClockDriver(MediaStreamGraphImpl* aGraphImpl) |
293 | | : ThreadedDriver(aGraphImpl), |
294 | | mInitialTimeStamp(TimeStamp::Now()), |
295 | | mLastTimeStamp(TimeStamp::Now()), |
296 | | mWaitState(WAITSTATE_RUNNING), |
297 | | mIsFallback(false) |
298 | 0 | {} |
299 | | |
300 | | SystemClockDriver::~SystemClockDriver() |
301 | | { } |
302 | | |
303 | | void |
304 | | SystemClockDriver::MarkAsFallback() |
305 | 0 | { |
306 | 0 | mIsFallback = true; |
307 | 0 | } |
308 | | |
309 | | bool |
310 | | SystemClockDriver::IsFallback() |
311 | 0 | { |
312 | 0 | return mIsFallback; |
313 | 0 | } |
314 | | |
315 | | void |
316 | | ThreadedDriver::RunThread() |
317 | 0 | { |
318 | 0 | mThreadRunning = true; |
319 | 0 | while (true) { |
320 | 0 | mIterationStart = IterationEnd(); |
321 | 0 | mIterationEnd += GetIntervalForIteration(); |
322 | 0 |
|
323 | 0 | GraphTime stateComputedTime = StateComputedTime(); |
324 | 0 | if (stateComputedTime < mIterationEnd) { |
325 | 0 | LOG(LogLevel::Warning, ("%p: Global underrun detected", GraphImpl())); |
326 | 0 | mIterationEnd = stateComputedTime; |
327 | 0 | } |
328 | 0 |
|
329 | 0 | if (mIterationStart >= mIterationEnd) { |
330 | 0 | NS_ASSERTION(mIterationStart == mIterationEnd , |
331 | 0 | "Time can't go backwards!"); |
332 | 0 | // This could happen due to low clock resolution, maybe? |
333 | 0 | LOG(LogLevel::Debug, ("%p: Time did not advance", GraphImpl())); |
334 | 0 | } |
335 | 0 |
|
336 | 0 | GraphTime nextStateComputedTime = |
337 | 0 | GraphImpl()->RoundUpToEndOfAudioBlock( |
338 | 0 | mIterationEnd + GraphImpl()->MillisecondsToMediaTime(AUDIO_TARGET_MS)); |
339 | 0 | if (nextStateComputedTime < stateComputedTime) { |
340 | 0 | // A previous driver may have been processing further ahead of |
341 | 0 | // iterationEnd. |
342 | 0 | LOG(LogLevel::Warning, |
343 | 0 | ("%p: Prevent state from going backwards. interval[%ld; %ld] state[%ld; " |
344 | 0 | "%ld]", |
345 | 0 | GraphImpl(), |
346 | 0 | (long)mIterationStart, |
347 | 0 | (long)mIterationEnd, |
348 | 0 | (long)stateComputedTime, |
349 | 0 | (long)nextStateComputedTime)); |
350 | 0 | nextStateComputedTime = stateComputedTime; |
351 | 0 | } |
352 | 0 | LOG(LogLevel::Verbose, |
353 | 0 | ("%p: interval[%ld; %ld] state[%ld; %ld]", |
354 | 0 | GraphImpl(), |
355 | 0 | (long)mIterationStart, |
356 | 0 | (long)mIterationEnd, |
357 | 0 | (long)stateComputedTime, |
358 | 0 | (long)nextStateComputedTime)); |
359 | 0 |
|
360 | 0 | bool stillProcessing = GraphImpl()->OneIteration(nextStateComputedTime); |
361 | 0 |
|
362 | 0 | if (!stillProcessing) { |
363 | 0 | // Enter shutdown mode. The stable-state handler will detect this |
364 | 0 | // and complete shutdown if the graph does not get restarted. |
365 | 0 | GraphImpl()->SignalMainThreadCleanup(); |
366 | 0 | break; |
367 | 0 | } |
368 | 0 | MonitorAutoLock lock(GraphImpl()->GetMonitor()); |
369 | 0 | if (NextDriver()) { |
370 | 0 | LOG(LogLevel::Debug, ("%p: Switching to AudioCallbackDriver", GraphImpl())); |
371 | 0 | SwitchToNextDriver(); |
372 | 0 | break; |
373 | 0 | } |
374 | 0 | } |
375 | 0 | mThreadRunning = false; |
376 | 0 | } |
377 | | |
378 | | MediaTime |
379 | | SystemClockDriver::GetIntervalForIteration() |
380 | 0 | { |
381 | 0 | TimeStamp now = TimeStamp::Now(); |
382 | 0 | MediaTime interval = |
383 | 0 | GraphImpl()->SecondsToMediaTime((now - mCurrentTimeStamp).ToSeconds()); |
384 | 0 | mCurrentTimeStamp = now; |
385 | 0 |
|
386 | 0 | MOZ_LOG(gMediaStreamGraphLog, LogLevel::Verbose, |
387 | 0 | ("%p: Updating current time to %f (real %f, StateComputedTime() %f)", |
388 | 0 | GraphImpl(), |
389 | 0 | GraphImpl()->MediaTimeToSeconds(IterationEnd() + interval), |
390 | 0 | (now - mInitialTimeStamp).ToSeconds(), |
391 | 0 | GraphImpl()->MediaTimeToSeconds(StateComputedTime()))); |
392 | 0 |
|
393 | 0 | return interval; |
394 | 0 | } |
395 | | |
396 | | TimeStamp |
397 | | OfflineClockDriver::GetCurrentTimeStamp() |
398 | 0 | { |
399 | 0 | MOZ_CRASH("This driver does not support getting the current timestamp."); |
400 | 0 | return TimeStamp(); |
401 | 0 | } |
402 | | |
403 | | void |
404 | | SystemClockDriver::WaitForNextIteration() |
405 | 0 | { |
406 | 0 | GraphImpl()->GetMonitor().AssertCurrentThreadOwns(); |
407 | 0 |
|
408 | 0 | TimeDuration timeout = TimeDuration::Forever(); |
409 | 0 | TimeStamp now = TimeStamp::Now(); |
410 | 0 |
|
411 | 0 | // This lets us avoid hitting the Atomic twice when we know we won't sleep |
412 | 0 | bool another = GraphImpl()->mNeedAnotherIteration; // atomic |
413 | 0 | if (!another) { |
414 | 0 | GraphImpl()->mGraphDriverAsleep = true; // atomic |
415 | 0 | mWaitState = WAITSTATE_WAITING_INDEFINITELY; |
416 | 0 | } |
417 | 0 | // NOTE: mNeedAnotherIteration while also atomic may have changed before |
418 | 0 | // we could set mGraphDriverAsleep, so we must re-test it. |
419 | 0 | // (EnsureNextIteration sets mNeedAnotherIteration, then tests |
420 | 0 | // mGraphDriverAsleep |
421 | 0 | if (another || GraphImpl()->mNeedAnotherIteration) { // atomic |
422 | 0 | int64_t timeoutMS = MEDIA_GRAPH_TARGET_PERIOD_MS - |
423 | 0 | int64_t((now - mCurrentTimeStamp).ToMilliseconds()); |
424 | 0 | // Make sure timeoutMS doesn't overflow 32 bits by waking up at |
425 | 0 | // least once a minute, if we need to wake up at all |
426 | 0 | timeoutMS = std::max<int64_t>(0, std::min<int64_t>(timeoutMS, 60*1000)); |
427 | 0 | timeout = TimeDuration::FromMilliseconds(timeoutMS); |
428 | 0 | LOG(LogLevel::Verbose, |
429 | 0 | ("%p: Waiting for next iteration; at %f, timeout=%f", |
430 | 0 | GraphImpl(), |
431 | 0 | (now - mInitialTimeStamp).ToSeconds(), |
432 | 0 | timeoutMS / 1000.0)); |
433 | 0 | if (mWaitState == WAITSTATE_WAITING_INDEFINITELY) { |
434 | 0 | GraphImpl()->mGraphDriverAsleep = false; // atomic |
435 | 0 | } |
436 | 0 | mWaitState = WAITSTATE_WAITING_FOR_NEXT_ITERATION; |
437 | 0 | } |
438 | 0 | if (!timeout.IsZero()) { |
439 | 0 | GraphImpl()->GetMonitor().Wait(timeout); |
440 | 0 | LOG(LogLevel::Verbose, |
441 | 0 | ("%p: Resuming after timeout; at %f, elapsed=%f", |
442 | 0 | GraphImpl(), |
443 | 0 | (TimeStamp::Now() - mInitialTimeStamp).ToSeconds(), |
444 | 0 | (TimeStamp::Now() - now).ToSeconds())); |
445 | 0 | } |
446 | 0 |
|
447 | 0 | if (mWaitState == WAITSTATE_WAITING_INDEFINITELY) { |
448 | 0 | GraphImpl()->mGraphDriverAsleep = false; // atomic |
449 | 0 | } |
450 | 0 | // Note: this can race against the EnsureNextIteration setting |
451 | 0 | // WAITSTATE_RUNNING and setting mGraphDriverAsleep to false, so you can |
452 | 0 | // have an iteration with WAITSTATE_WAKING_UP instead of RUNNING. |
453 | 0 | mWaitState = WAITSTATE_RUNNING; |
454 | 0 | GraphImpl()->mNeedAnotherIteration = false; // atomic |
455 | 0 | } |
456 | | |
457 | | void SystemClockDriver::WakeUp() |
458 | 0 | { |
459 | 0 | GraphImpl()->GetMonitor().AssertCurrentThreadOwns(); |
460 | 0 | // Note: this can race against the thread setting WAITSTATE_RUNNING and |
461 | 0 | // setting mGraphDriverAsleep to false, so you can have an iteration |
462 | 0 | // with WAITSTATE_WAKING_UP instead of RUNNING. |
463 | 0 | mWaitState = WAITSTATE_WAKING_UP; |
464 | 0 | GraphImpl()->mGraphDriverAsleep = false; // atomic |
465 | 0 | GraphImpl()->GetMonitor().Notify(); |
466 | 0 | } |
467 | | |
468 | | OfflineClockDriver::OfflineClockDriver(MediaStreamGraphImpl* aGraphImpl, GraphTime aSlice) |
469 | | : ThreadedDriver(aGraphImpl), |
470 | | mSlice(aSlice) |
471 | 0 | { |
472 | 0 |
|
473 | 0 | } |
474 | | |
475 | | OfflineClockDriver::~OfflineClockDriver() |
476 | | { |
477 | | } |
478 | | |
479 | | MediaTime |
480 | | OfflineClockDriver::GetIntervalForIteration() |
481 | 0 | { |
482 | 0 | return GraphImpl()->MillisecondsToMediaTime(mSlice); |
483 | 0 | } |
484 | | |
485 | | void |
486 | | OfflineClockDriver::WaitForNextIteration() |
487 | 0 | { |
488 | 0 | // No op: we want to go as fast as possible when we are offline |
489 | 0 | } |
490 | | |
491 | | void |
492 | | OfflineClockDriver::WakeUp() |
493 | 0 | { |
494 | 0 | MOZ_ASSERT(false, "An offline graph should not have to wake up."); |
495 | 0 | } |
496 | | |
497 | | AsyncCubebTask::AsyncCubebTask(AudioCallbackDriver* aDriver, |
498 | | AsyncCubebOperation aOperation) |
499 | | : Runnable("AsyncCubebTask") |
500 | | , mDriver(aDriver) |
501 | | , mOperation(aOperation) |
502 | | , mShutdownGrip(aDriver->GraphImpl()) |
503 | 0 | { |
504 | 0 | NS_WARNING_ASSERTION(mDriver->mAudioStream || aOperation == INIT, |
505 | 0 | "No audio stream!"); |
506 | 0 | } |
507 | | |
508 | | AsyncCubebTask::~AsyncCubebTask() |
509 | 0 | { |
510 | 0 | } |
511 | | |
512 | | NS_IMETHODIMP |
513 | | AsyncCubebTask::Run() |
514 | 0 | { |
515 | 0 | MOZ_ASSERT(mDriver); |
516 | 0 |
|
517 | 0 | switch(mOperation) { |
518 | 0 | case AsyncCubebOperation::INIT: { |
519 | 0 | LOG(LogLevel::Debug, |
520 | 0 | ("%p: AsyncCubebOperation::INIT driver=%p", mDriver->GraphImpl(), mDriver.get())); |
521 | 0 | if (!mDriver->Init()) { |
522 | 0 | LOG(LogLevel::Warning, |
523 | 0 | ("AsyncCubebOperation::INIT failed for driver=%p", mDriver.get())); |
524 | 0 | return NS_ERROR_FAILURE; |
525 | 0 | } |
526 | 0 | mDriver->CompleteAudioContextOperations(mOperation); |
527 | 0 | break; |
528 | 0 | } |
529 | 0 | case AsyncCubebOperation::SHUTDOWN: { |
530 | 0 | LOG(LogLevel::Debug, |
531 | 0 | ("%p: AsyncCubebOperation::SHUTDOWN driver=%p", mDriver->GraphImpl(), mDriver.get())); |
532 | 0 | mDriver->Stop(); |
533 | 0 |
|
534 | 0 | mDriver->CompleteAudioContextOperations(mOperation); |
535 | 0 |
|
536 | 0 | mDriver = nullptr; |
537 | 0 | mShutdownGrip = nullptr; |
538 | 0 | break; |
539 | 0 | } |
540 | 0 | default: |
541 | 0 | MOZ_CRASH("Operation not implemented."); |
542 | 0 | } |
543 | 0 |
|
544 | 0 | // The thread will kill itself after a bit |
545 | 0 | return NS_OK; |
546 | 0 | } |
547 | | |
548 | | StreamAndPromiseForOperation::StreamAndPromiseForOperation(MediaStream* aStream, |
549 | | void* aPromise, |
550 | | dom::AudioContextOperation aOperation) |
551 | | : mStream(aStream) |
552 | | , mPromise(aPromise) |
553 | | , mOperation(aOperation) |
554 | 0 | { |
555 | 0 | // MOZ_ASSERT(aPromise); |
556 | 0 | } |
557 | | |
558 | | AudioCallbackDriver::AudioCallbackDriver(MediaStreamGraphImpl* aGraphImpl, uint32_t aInputChannelCount) |
559 | | : GraphDriver(aGraphImpl) |
560 | | , mOutputChannels(0) |
561 | | , mSampleRate(0) |
562 | | , mInputChannelCount(aInputChannelCount) |
563 | | , mIterationDurationMS(MEDIA_GRAPH_TARGET_PERIOD_MS) |
564 | | , mStarted(false) |
565 | | , mInitShutdownThread(SharedThreadPool::Get(NS_LITERAL_CSTRING("CubebOperation"), 1)) |
566 | | , mAddedMixer(false) |
567 | | , mAudioThreadId(std::thread::id()) |
568 | | , mAudioThreadRunning(false) |
569 | | , mShouldFallbackIfError(false) |
570 | | , mFromFallback(false) |
571 | 0 | { |
572 | 0 | LOG(LogLevel::Debug, ("%p: AudioCallbackDriver ctor", GraphImpl())); |
573 | 0 |
|
574 | 0 | const uint32_t kIdleThreadTimeoutMs = 2000; |
575 | 0 | mInitShutdownThread-> |
576 | 0 | SetIdleThreadTimeout(PR_MillisecondsToInterval(kIdleThreadTimeoutMs)); |
577 | 0 |
|
578 | | #if defined(XP_WIN) |
579 | | if (XRE_IsContentProcess()) { |
580 | | audio::AudioNotificationReceiver::Register(this); |
581 | | } |
582 | | #endif |
583 | | } |
584 | | |
585 | | AudioCallbackDriver::~AudioCallbackDriver() |
586 | 0 | { |
587 | 0 | MOZ_ASSERT(mPromisesForOperation.IsEmpty()); |
588 | 0 | MOZ_ASSERT(!mAddedMixer); |
589 | | #if defined(XP_WIN) |
590 | | if (XRE_IsContentProcess()) { |
591 | | audio::AudioNotificationReceiver::Unregister(this); |
592 | | } |
593 | | #endif |
594 | | } |
595 | | |
596 | | bool IsMacbookOrMacbookAir() |
597 | 0 | { |
598 | | #ifdef XP_MACOSX |
599 | | size_t len = 0; |
600 | | sysctlbyname("hw.model", NULL, &len, NULL, 0); |
601 | | if (len) { |
602 | | UniquePtr<char[]> model(new char[len]); |
603 | | // This string can be |
604 | | // MacBook%d,%d for a normal MacBook |
605 | | // MacBookPro%d,%d for a MacBook Pro |
606 | | // MacBookAir%d,%d for a Macbook Air |
607 | | sysctlbyname("hw.model", model.get(), &len, NULL, 0); |
608 | | char* substring = strstr(model.get(), "MacBook"); |
609 | | if (substring) { |
610 | | const size_t offset = strlen("MacBook"); |
611 | | if (!strncmp(model.get() + offset, "Air", 3) || |
612 | | isdigit(model[offset + 1])) { |
613 | | return true; |
614 | | } |
615 | | } |
616 | | // Bug 1477200, we're temporarily capping the latency to 512 here to help |
617 | | // with audio quality. |
618 | | return true; |
619 | | } |
620 | | #endif |
621 | | return false; |
622 | 0 | } |
623 | | |
624 | | bool |
625 | | AudioCallbackDriver::Init() |
626 | 0 | { |
627 | 0 | cubeb* cubebContext = CubebUtils::GetCubebContext(); |
628 | 0 | if (!cubebContext) { |
629 | 0 | NS_WARNING("Could not get cubeb context."); |
630 | 0 | LOG(LogLevel::Warning, ("%s: Could not get cubeb context", __func__)); |
631 | 0 | if (!mFromFallback) { |
632 | 0 | CubebUtils::ReportCubebStreamInitFailure(true); |
633 | 0 | } |
634 | 0 | MonitorAutoLock lock(GraphImpl()->GetMonitor()); |
635 | 0 | FallbackToSystemClockDriver(); |
636 | 0 | return true; |
637 | 0 | } |
638 | 0 |
|
639 | 0 | cubeb_stream_params output; |
640 | 0 | cubeb_stream_params input; |
641 | 0 | bool firstStream = CubebUtils::GetFirstStream(); |
642 | 0 |
|
643 | 0 | MOZ_ASSERT(!NS_IsMainThread(), |
644 | 0 | "This is blocking and should never run on the main thread."); |
645 | 0 |
|
646 | 0 | mSampleRate = output.rate = mGraphImpl->GraphRate(); |
647 | 0 |
|
648 | 0 | if (AUDIO_OUTPUT_FORMAT == AUDIO_FORMAT_S16) { |
649 | 0 | output.format = CUBEB_SAMPLE_S16NE; |
650 | 0 | } else { |
651 | 0 | output.format = CUBEB_SAMPLE_FLOAT32NE; |
652 | 0 | } |
653 | 0 |
|
654 | 0 | // Query and set the number of channels this AudioCallbackDriver will use. |
655 | 0 | mOutputChannels = GraphImpl()->AudioOutputChannelCount(); |
656 | 0 | if (!mOutputChannels) { |
657 | 0 | LOG(LogLevel::Warning, ("Output number of channels is 0.")); |
658 | 0 | MonitorAutoLock lock(GraphImpl()->GetMonitor()); |
659 | 0 | FallbackToSystemClockDriver(); |
660 | 0 | return true; |
661 | 0 | } |
662 | 0 |
|
663 | 0 | CubebUtils::AudioDeviceID forcedOutputDeviceId = nullptr; |
664 | 0 |
|
665 | 0 | char* forcedOutputDeviceName = CubebUtils::GetForcedOutputDevice(); |
666 | 0 | if (forcedOutputDeviceName) { |
667 | 0 | nsTArray<RefPtr<AudioDeviceInfo>> deviceInfos; |
668 | 0 | GetDeviceCollection(deviceInfos, CubebUtils::Output); |
669 | 0 | for (const auto& device : deviceInfos) { |
670 | 0 | const nsString& name = device->Name(); |
671 | 0 | if (name.Equals(NS_ConvertUTF8toUTF16(forcedOutputDeviceName))) { |
672 | 0 | if (device->DeviceID()) { |
673 | 0 | forcedOutputDeviceId = device->DeviceID(); |
674 | 0 | } |
675 | 0 | } |
676 | 0 | } |
677 | 0 | } |
678 | 0 |
|
679 | 0 | mBuffer = AudioCallbackBufferWrapper<AudioDataValue>(mOutputChannels); |
680 | 0 | mScratchBuffer = SpillBuffer<AudioDataValue, WEBAUDIO_BLOCK_SIZE * 2>(mOutputChannels); |
681 | 0 |
|
682 | 0 | output.channels = mOutputChannels; |
683 | 0 | output.layout = CUBEB_LAYOUT_UNDEFINED; |
684 | 0 | output.prefs = CubebUtils::GetDefaultStreamPrefs(); |
685 | 0 |
|
686 | 0 | uint32_t latency_frames = CubebUtils::GetCubebMSGLatencyInFrames(&output); |
687 | 0 |
|
688 | 0 | // Macbook and MacBook air don't have enough CPU to run very low latency |
689 | 0 | // MediaStreamGraphs, cap the minimal latency to 512 frames int this case. |
690 | 0 | if (IsMacbookOrMacbookAir()) { |
691 | 0 | latency_frames = std::max((uint32_t) 512, latency_frames); |
692 | 0 | } |
693 | 0 |
|
694 | 0 | input = output; |
695 | 0 | input.channels = mInputChannelCount; |
696 | 0 | input.layout = CUBEB_LAYOUT_UNDEFINED; |
697 | 0 |
|
698 | 0 | cubeb_stream* stream = nullptr; |
699 | 0 | bool inputWanted = mInputChannelCount > 0; |
700 | 0 | CubebUtils::AudioDeviceID output_id = GraphImpl()->mOutputDeviceID; |
701 | 0 | CubebUtils::AudioDeviceID input_id = GraphImpl()->mInputDeviceID; |
702 | 0 |
|
703 | 0 | // XXX Only pass input input if we have an input listener. Always |
704 | 0 | // set up output because it's easier, and it will just get silence. |
705 | 0 | if (cubeb_stream_init(cubebContext, |
706 | 0 | &stream, |
707 | 0 | "AudioCallbackDriver", |
708 | 0 | input_id, |
709 | 0 | inputWanted ? &input : nullptr, |
710 | 0 | forcedOutputDeviceId ? forcedOutputDeviceId : output_id, |
711 | 0 | &output, |
712 | 0 | latency_frames, |
713 | 0 | DataCallback_s, |
714 | 0 | StateCallback_s, |
715 | 0 | this) == CUBEB_OK) { |
716 | 0 | mAudioStream.own(stream); |
717 | 0 | DebugOnly<int> rv = |
718 | 0 | cubeb_stream_set_volume(mAudioStream, CubebUtils::GetVolumeScale()); |
719 | 0 | NS_WARNING_ASSERTION( |
720 | 0 | rv == CUBEB_OK, |
721 | 0 | "Could not set the audio stream volume in GraphDriver.cpp"); |
722 | 0 | CubebUtils::ReportCubebBackendUsed(); |
723 | 0 | } else { |
724 | 0 | NS_WARNING("Could not create a cubeb stream for MediaStreamGraph, falling " |
725 | 0 | "back to a SystemClockDriver"); |
726 | 0 | // Only report failures when we're not coming from a driver that was |
727 | 0 | // created itself as a fallback driver because of a previous audio driver |
728 | 0 | // failure. |
729 | 0 | if (!mFromFallback) { |
730 | 0 | CubebUtils::ReportCubebStreamInitFailure(firstStream); |
731 | 0 | } |
732 | 0 | MonitorAutoLock lock(GraphImpl()->GetMonitor()); |
733 | 0 | FallbackToSystemClockDriver(); |
734 | 0 | return true; |
735 | 0 | } |
736 | 0 |
|
737 | | #ifdef XP_MACOSX |
738 | | PanOutputIfNeeded(inputWanted); |
739 | | #endif |
740 | | |
741 | 0 | cubeb_stream_register_device_changed_callback( |
742 | 0 | mAudioStream, AudioCallbackDriver::DeviceChangedCallback_s); |
743 | 0 |
|
744 | 0 | if (!StartStream()) { |
745 | 0 | LOG(LogLevel::Warning, ("%p: AudioCallbackDriver couldn't start a cubeb stream.", GraphImpl())); |
746 | 0 | return false; |
747 | 0 | } |
748 | 0 |
|
749 | 0 | LOG(LogLevel::Debug, ("%p: AudioCallbackDriver started.", GraphImpl())); |
750 | 0 | return true; |
751 | 0 | } |
752 | | |
753 | | void |
754 | | AudioCallbackDriver::Start() |
755 | 0 | { |
756 | 0 | MOZ_ASSERT(!IsStarted()); |
757 | 0 | MOZ_ASSERT(NS_IsMainThread() || OnCubebOperationThread() || |
758 | 0 | (PreviousDriver() && PreviousDriver()->OnThread())); |
759 | 0 | if (mPreviousDriver) { |
760 | 0 | if (mPreviousDriver->AsAudioCallbackDriver()) { |
761 | 0 | LOG(LogLevel::Debug, ("Releasing audio driver off main thread.")); |
762 | 0 | RefPtr<AsyncCubebTask> releaseEvent = |
763 | 0 | new AsyncCubebTask(mPreviousDriver->AsAudioCallbackDriver(), |
764 | 0 | AsyncCubebOperation::SHUTDOWN); |
765 | 0 | releaseEvent->Dispatch(); |
766 | 0 | mPreviousDriver = nullptr; |
767 | 0 | } else { |
768 | 0 | LOG(LogLevel::Debug, |
769 | 0 | ("Dropping driver reference for SystemClockDriver.")); |
770 | 0 | MOZ_ASSERT(mPreviousDriver->AsSystemClockDriver()); |
771 | 0 | mFromFallback = mPreviousDriver->AsSystemClockDriver()->IsFallback(); |
772 | 0 | mPreviousDriver = nullptr; |
773 | 0 | } |
774 | 0 | } |
775 | 0 |
|
776 | 0 | LOG(LogLevel::Debug, |
777 | 0 | ("Starting new audio driver off main thread, " |
778 | 0 | "to ensure it runs after previous shutdown.")); |
779 | 0 | RefPtr<AsyncCubebTask> initEvent = |
780 | 0 | new AsyncCubebTask(AsAudioCallbackDriver(), AsyncCubebOperation::INIT); |
781 | 0 | initEvent->Dispatch(); |
782 | 0 | } |
783 | | |
784 | | bool |
785 | | AudioCallbackDriver::StartStream() |
786 | 0 | { |
787 | 0 | MOZ_ASSERT(!IsStarted() && OnCubebOperationThread()); |
788 | 0 | mShouldFallbackIfError = true; |
789 | 0 | if (cubeb_stream_start(mAudioStream) != CUBEB_OK) { |
790 | 0 | NS_WARNING("Could not start cubeb stream for MSG."); |
791 | 0 | return false; |
792 | 0 | } |
793 | 0 |
|
794 | 0 | mStarted = true; |
795 | 0 | return true; |
796 | 0 | } |
797 | | |
798 | | void |
799 | | AudioCallbackDriver::Stop() |
800 | 0 | { |
801 | 0 | MOZ_ASSERT(OnCubebOperationThread()); |
802 | 0 | if (cubeb_stream_stop(mAudioStream) != CUBEB_OK) { |
803 | 0 | NS_WARNING("Could not stop cubeb stream for MSG."); |
804 | 0 | } |
805 | 0 | } |
806 | | |
807 | | void |
808 | | AudioCallbackDriver::Revive() |
809 | 0 | { |
810 | 0 | MOZ_ASSERT(NS_IsMainThread() && !ThreadRunning()); |
811 | 0 | // Note: only called on MainThread, without monitor |
812 | 0 | // We know were weren't in a running state |
813 | 0 | LOG(LogLevel::Debug, ("%p: AudioCallbackDriver reviving.", GraphImpl())); |
814 | 0 | // If we were switching, switch now. Otherwise, start the audio thread again. |
815 | 0 | MonitorAutoLock mon(GraphImpl()->GetMonitor()); |
816 | 0 | if (NextDriver()) { |
817 | 0 | SwitchToNextDriver(); |
818 | 0 | } else { |
819 | 0 | LOG(LogLevel::Debug, |
820 | 0 | ("Starting audio threads for MediaStreamGraph %p from a new thread.", |
821 | 0 | mGraphImpl.get())); |
822 | 0 | RefPtr<AsyncCubebTask> initEvent = |
823 | 0 | new AsyncCubebTask(this, AsyncCubebOperation::INIT); |
824 | 0 | initEvent->Dispatch(); |
825 | 0 | } |
826 | 0 | } |
827 | | |
828 | | void |
829 | | AudioCallbackDriver::RemoveMixerCallback() |
830 | 0 | { |
831 | 0 | MOZ_ASSERT(OnThread() || !ThreadRunning()); |
832 | 0 |
|
833 | 0 | if (mAddedMixer) { |
834 | 0 | GraphImpl()->mMixer.RemoveCallback(this); |
835 | 0 | mAddedMixer = false; |
836 | 0 | } |
837 | 0 | } |
838 | | |
839 | | void |
840 | | AudioCallbackDriver::AddMixerCallback() |
841 | 0 | { |
842 | 0 | MOZ_ASSERT(OnThread()); |
843 | 0 |
|
844 | 0 | if (!mAddedMixer) { |
845 | 0 | mGraphImpl->mMixer.AddCallback(this); |
846 | 0 | mAddedMixer = true; |
847 | 0 | } |
848 | 0 | } |
849 | | |
850 | | void |
851 | | AudioCallbackDriver::WaitForNextIteration() |
852 | 0 | { |
853 | 0 | } |
854 | | |
855 | | void |
856 | | AudioCallbackDriver::WakeUp() |
857 | 0 | { |
858 | 0 | mGraphImpl->GetMonitor().AssertCurrentThreadOwns(); |
859 | 0 | mGraphImpl->GetMonitor().Notify(); |
860 | 0 | } |
861 | | |
862 | | void |
863 | | AudioCallbackDriver::Shutdown() |
864 | 0 | { |
865 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
866 | 0 | LOG(LogLevel::Debug, |
867 | 0 | ("%p: Releasing audio driver off main thread (GraphDriver::Shutdown).", GraphImpl())); |
868 | 0 | RefPtr<AsyncCubebTask> releaseEvent = |
869 | 0 | new AsyncCubebTask(this, AsyncCubebOperation::SHUTDOWN); |
870 | 0 | releaseEvent->Dispatch(NS_DISPATCH_SYNC); |
871 | 0 | } |
872 | | |
873 | | #if defined(XP_WIN) |
874 | | void |
875 | | AudioCallbackDriver::ResetDefaultDevice() |
876 | | { |
877 | | if (cubeb_stream_reset_default_device(mAudioStream) != CUBEB_OK) { |
878 | | NS_WARNING("Could not reset cubeb stream to default output device."); |
879 | | } |
880 | | } |
881 | | #endif |
882 | | |
883 | | /* static */ long |
884 | | AudioCallbackDriver::DataCallback_s(cubeb_stream* aStream, |
885 | | void* aUser, |
886 | | const void* aInputBuffer, |
887 | | void* aOutputBuffer, |
888 | | long aFrames) |
889 | 0 | { |
890 | 0 | AudioCallbackDriver* driver = reinterpret_cast<AudioCallbackDriver*>(aUser); |
891 | 0 | return driver->DataCallback(static_cast<const AudioDataValue*>(aInputBuffer), |
892 | 0 | static_cast<AudioDataValue*>(aOutputBuffer), aFrames); |
893 | 0 | } |
894 | | |
895 | | /* static */ void |
896 | | AudioCallbackDriver::StateCallback_s(cubeb_stream* aStream, void * aUser, |
897 | | cubeb_state aState) |
898 | 0 | { |
899 | 0 | AudioCallbackDriver* driver = reinterpret_cast<AudioCallbackDriver*>(aUser); |
900 | 0 | driver->StateCallback(aState); |
901 | 0 | } |
902 | | |
903 | | /* static */ void |
904 | | AudioCallbackDriver::DeviceChangedCallback_s(void* aUser) |
905 | 0 | { |
906 | 0 | AudioCallbackDriver* driver = reinterpret_cast<AudioCallbackDriver*>(aUser); |
907 | 0 | driver->DeviceChangedCallback(); |
908 | 0 | } |
909 | | |
910 | | AudioCallbackDriver::AutoInCallback::AutoInCallback(AudioCallbackDriver* aDriver) |
911 | | : mDriver(aDriver) |
912 | 0 | { |
913 | 0 | mDriver->mAudioThreadId = std::this_thread::get_id(); |
914 | 0 | } |
915 | | |
916 | 0 | AudioCallbackDriver::AutoInCallback::~AutoInCallback() { |
917 | 0 | mDriver->mAudioThreadId = std::thread::id(); |
918 | 0 | } |
919 | | |
920 | | long |
921 | | AudioCallbackDriver::DataCallback(const AudioDataValue* aInputBuffer, |
922 | | AudioDataValue* aOutputBuffer, long aFrames) |
923 | 0 | { |
924 | 0 | TRACE_AUDIO_CALLBACK_BUDGET(aFrames, mSampleRate); |
925 | 0 | TRACE_AUDIO_CALLBACK(); |
926 | 0 |
|
927 | | #ifdef DEBUG |
928 | | AutoInCallback aic(this); |
929 | | #endif |
930 | |
|
931 | 0 | // Don't add the callback until we're inited and ready |
932 | 0 | if (!mAddedMixer) { |
933 | 0 | GraphImpl()->mMixer.AddCallback(this); |
934 | 0 | mAddedMixer = true; |
935 | 0 | } |
936 | 0 |
|
937 | 0 | GraphTime stateComputedTime = StateComputedTime(); |
938 | 0 | if (stateComputedTime == 0) { |
939 | 0 | MonitorAutoLock mon(GraphImpl()->GetMonitor()); |
940 | 0 | // Because this function is called during cubeb_stream_init (to prefill the |
941 | 0 | // audio buffers), it can be that we don't have a message here (because this |
942 | 0 | // driver is the first one for this graph), and the graph would exit. Simply |
943 | 0 | // return here until we have messages. |
944 | 0 | if (!GraphImpl()->MessagesQueued()) { |
945 | 0 | PodZero(aOutputBuffer, aFrames * mOutputChannels); |
946 | 0 | return aFrames; |
947 | 0 | } |
948 | 0 | GraphImpl()->SwapMessageQueues(); |
949 | 0 | } |
950 | 0 |
|
951 | 0 | uint32_t durationMS = aFrames * 1000 / mSampleRate; |
952 | 0 |
|
953 | 0 | // For now, simply average the duration with the previous |
954 | 0 | // duration so there is some damping against sudden changes. |
955 | 0 | if (!mIterationDurationMS) { |
956 | 0 | mIterationDurationMS = durationMS; |
957 | 0 | } else { |
958 | 0 | mIterationDurationMS = (mIterationDurationMS*3) + durationMS; |
959 | 0 | mIterationDurationMS /= 4; |
960 | 0 | } |
961 | 0 |
|
962 | 0 | mBuffer.SetBuffer(aOutputBuffer, aFrames); |
963 | 0 | // fill part or all with leftover data from last iteration (since we |
964 | 0 | // align to Audio blocks) |
965 | 0 | mScratchBuffer.Empty(mBuffer); |
966 | 0 |
|
967 | 0 | // State computed time is decided by the audio callback's buffer length. We |
968 | 0 | // compute the iteration start and end from there, trying to keep the amount |
969 | 0 | // of buffering in the graph constant. |
970 | 0 | GraphTime nextStateComputedTime = |
971 | 0 | GraphImpl()->RoundUpToEndOfAudioBlock( |
972 | 0 | stateComputedTime + mBuffer.Available()); |
973 | 0 |
|
974 | 0 | mIterationStart = mIterationEnd; |
975 | 0 | // inGraph is the number of audio frames there is between the state time and |
976 | 0 | // the current time, i.e. the maximum theoretical length of the interval we |
977 | 0 | // could use as [mIterationStart; mIterationEnd]. |
978 | 0 | GraphTime inGraph = stateComputedTime - mIterationStart; |
979 | 0 | // We want the interval [mIterationStart; mIterationEnd] to be before the |
980 | 0 | // interval [stateComputedTime; nextStateComputedTime]. We also want |
981 | 0 | // the distance between these intervals to be roughly equivalent each time, to |
982 | 0 | // ensure there is no clock drift between current time and state time. Since |
983 | 0 | // we can't act on the state time because we have to fill the audio buffer, we |
984 | 0 | // reclock the current time against the state time, here. |
985 | 0 | mIterationEnd = mIterationStart + 0.8 * inGraph; |
986 | 0 |
|
987 | 0 | LOG(LogLevel::Verbose, |
988 | 0 | ("%p: interval[%ld; %ld] state[%ld; %ld] (frames: %ld) (durationMS: %u) " |
989 | 0 | "(duration ticks: %ld)", |
990 | 0 | GraphImpl(), |
991 | 0 | (long)mIterationStart, |
992 | 0 | (long)mIterationEnd, |
993 | 0 | (long)stateComputedTime, |
994 | 0 | (long)nextStateComputedTime, |
995 | 0 | (long)aFrames, |
996 | 0 | (uint32_t)durationMS, |
997 | 0 | (long)(nextStateComputedTime - stateComputedTime))); |
998 | 0 |
|
999 | 0 | mCurrentTimeStamp = TimeStamp::Now(); |
1000 | 0 |
|
1001 | 0 | if (stateComputedTime < mIterationEnd) { |
1002 | 0 | LOG(LogLevel::Error, ("%p: Media graph global underrun detected", GraphImpl())); |
1003 | 0 | MOZ_ASSERT_UNREACHABLE("We should not underrun in full duplex"); |
1004 | 0 | mIterationEnd = stateComputedTime; |
1005 | 0 | } |
1006 | 0 |
|
1007 | 0 | // Process mic data if any/needed |
1008 | 0 | if (aInputBuffer && mInputChannelCount > 0) { |
1009 | 0 | GraphImpl()->NotifyInputData(aInputBuffer, static_cast<size_t>(aFrames), |
1010 | 0 | mSampleRate, mInputChannelCount); |
1011 | 0 | } |
1012 | 0 |
|
1013 | 0 | bool stillProcessing; |
1014 | 0 | if (mBuffer.Available()) { |
1015 | 0 | // We totally filled the buffer (and mScratchBuffer isn't empty). |
1016 | 0 | // We don't need to run an iteration and if we do so we may overflow. |
1017 | 0 | stillProcessing = GraphImpl()->OneIteration(nextStateComputedTime); |
1018 | 0 | } else { |
1019 | 0 | LOG(LogLevel::Verbose, |
1020 | 0 | ("%p: DataCallback buffer filled entirely from scratch " |
1021 | 0 | "buffer, skipping iteration.", GraphImpl())); |
1022 | 0 | stillProcessing = true; |
1023 | 0 | } |
1024 | 0 |
|
1025 | 0 | mBuffer.BufferFilled(); |
1026 | 0 |
|
1027 | 0 | // Callback any observers for the AEC speaker data. Note that one |
1028 | 0 | // (maybe) of these will be full-duplex, the others will get their input |
1029 | 0 | // data off separate cubeb callbacks. Take care with how stuff is |
1030 | 0 | // removed/added to this list and TSAN issues, but input and output will |
1031 | 0 | // use separate callback methods. |
1032 | 0 | GraphImpl()->NotifyOutputData(aOutputBuffer, static_cast<size_t>(aFrames), |
1033 | 0 | mSampleRate, mOutputChannels); |
1034 | 0 |
|
1035 | 0 | if (!stillProcessing) { |
1036 | 0 | // About to hand over control of the graph. Do not start a new driver if |
1037 | 0 | // StateCallback() receives an error for this stream while the main thread |
1038 | 0 | // or another driver has control of the graph. |
1039 | 0 | mShouldFallbackIfError = false; |
1040 | 0 | // Enter shutdown mode. The stable-state handler will detect this |
1041 | 0 | // and complete shutdown if the graph does not get restarted. |
1042 | 0 | mGraphImpl->SignalMainThreadCleanup(); |
1043 | 0 | RemoveMixerCallback(); |
1044 | 0 | // Update the flag before go to drain |
1045 | 0 | mAudioThreadRunning = false; |
1046 | 0 | return aFrames - 1; |
1047 | 0 | } |
1048 | 0 | |
1049 | 0 | bool switching = false; |
1050 | 0 | { |
1051 | 0 | MonitorAutoLock mon(GraphImpl()->GetMonitor()); |
1052 | 0 | switching = !!NextDriver(); |
1053 | 0 | } |
1054 | 0 |
|
1055 | 0 | if (switching) { |
1056 | 0 | mShouldFallbackIfError = false; |
1057 | 0 | // If the audio stream has not been started by the previous driver or |
1058 | 0 | // the graph itself, keep it alive. |
1059 | 0 | MonitorAutoLock mon(GraphImpl()->GetMonitor()); |
1060 | 0 | if (!IsStarted()) { |
1061 | 0 | return aFrames; |
1062 | 0 | } |
1063 | 0 | LOG(LogLevel::Debug, ("%p: Switching to system driver.", GraphImpl())); |
1064 | 0 | RemoveMixerCallback(); |
1065 | 0 | SwitchToNextDriver(); |
1066 | 0 | mAudioThreadRunning = false; |
1067 | 0 | // Returning less than aFrames starts the draining and eventually stops the |
1068 | 0 | // audio thread. This function will never get called again. |
1069 | 0 | return aFrames - 1; |
1070 | 0 | } |
1071 | 0 | |
1072 | 0 | return aFrames; |
1073 | 0 | } |
1074 | | |
1075 | | void |
1076 | | AudioCallbackDriver::StateCallback(cubeb_state aState) |
1077 | 0 | { |
1078 | 0 | MOZ_ASSERT(!OnThread()); |
1079 | 0 | LOG(LogLevel::Debug, ("AudioCallbackDriver State: %d", aState)); |
1080 | 0 |
|
1081 | 0 | // Clear the flag for the not running |
1082 | 0 | // states: stopped, drained, error. |
1083 | 0 | mAudioThreadRunning = (aState == CUBEB_STATE_STARTED); |
1084 | 0 |
|
1085 | 0 | if (aState == CUBEB_STATE_ERROR && mShouldFallbackIfError) { |
1086 | 0 | MOZ_ASSERT(!ThreadRunning()); |
1087 | 0 | mShouldFallbackIfError = false; |
1088 | 0 | MonitorAutoLock lock(GraphImpl()->GetMonitor()); |
1089 | 0 | RemoveMixerCallback(); |
1090 | 0 | FallbackToSystemClockDriver(); |
1091 | 0 | } else if (aState == CUBEB_STATE_STOPPED) { |
1092 | 0 | MOZ_ASSERT(!ThreadRunning()); |
1093 | 0 | RemoveMixerCallback(); |
1094 | 0 | } |
1095 | 0 | } |
1096 | | |
1097 | | void |
1098 | | AudioCallbackDriver::MixerCallback(AudioDataValue* aMixedBuffer, |
1099 | | AudioSampleFormat aFormat, |
1100 | | uint32_t aChannels, |
1101 | | uint32_t aFrames, |
1102 | | uint32_t aSampleRate) |
1103 | 0 | { |
1104 | 0 | MOZ_ASSERT(OnThread()); |
1105 | 0 | uint32_t toWrite = mBuffer.Available(); |
1106 | 0 |
|
1107 | 0 | if (!mBuffer.Available()) { |
1108 | 0 | NS_WARNING("DataCallback buffer full, expect frame drops."); |
1109 | 0 | } |
1110 | 0 |
|
1111 | 0 | MOZ_ASSERT(mBuffer.Available() <= aFrames); |
1112 | 0 |
|
1113 | 0 | mBuffer.WriteFrames(aMixedBuffer, mBuffer.Available()); |
1114 | 0 | MOZ_ASSERT(mBuffer.Available() == 0, "Missing frames to fill audio callback's buffer."); |
1115 | 0 |
|
1116 | 0 | DebugOnly<uint32_t> written = mScratchBuffer.Fill(aMixedBuffer + toWrite * aChannels, aFrames - toWrite); |
1117 | 0 | NS_WARNING_ASSERTION(written == aFrames - toWrite, "Dropping frames."); |
1118 | 0 | }; |
1119 | | |
1120 | | void AudioCallbackDriver::PanOutputIfNeeded(bool aMicrophoneActive) |
1121 | 0 | { |
1122 | | #ifdef XP_MACOSX |
1123 | | cubeb_device* out; |
1124 | | int rv; |
1125 | | char name[128]; |
1126 | | size_t length = sizeof(name); |
1127 | | |
1128 | | rv = sysctlbyname("hw.model", name, &length, NULL, 0); |
1129 | | if (rv) { |
1130 | | return; |
1131 | | } |
1132 | | |
1133 | | if (!strncmp(name, "MacBookPro", 10)) { |
1134 | | if (cubeb_stream_get_current_device(mAudioStream, &out) == CUBEB_OK) { |
1135 | | // Check if we are currently outputing sound on external speakers. |
1136 | | if (!strcmp(out->output_name, "ispk")) { |
1137 | | // Pan everything to the right speaker. |
1138 | | if (aMicrophoneActive) { |
1139 | | if (cubeb_stream_set_panning(mAudioStream, 1.0) != CUBEB_OK) { |
1140 | | NS_WARNING("Could not pan audio output to the right."); |
1141 | | } |
1142 | | } else { |
1143 | | if (cubeb_stream_set_panning(mAudioStream, 0.0) != CUBEB_OK) { |
1144 | | NS_WARNING("Could not pan audio output to the center."); |
1145 | | } |
1146 | | } |
1147 | | } else { |
1148 | | if (cubeb_stream_set_panning(mAudioStream, 0.0) != CUBEB_OK) { |
1149 | | NS_WARNING("Could not pan audio output to the center."); |
1150 | | } |
1151 | | } |
1152 | | cubeb_stream_device_destroy(mAudioStream, out); |
1153 | | } |
1154 | | } |
1155 | | #endif |
1156 | | } |
1157 | | |
1158 | | void |
1159 | | AudioCallbackDriver::DeviceChangedCallback() |
1160 | 0 | { |
1161 | 0 | MOZ_ASSERT(!OnThread()); |
1162 | 0 | // Tell the audio engine the device has changed, it might want to reset some |
1163 | 0 | // state. |
1164 | 0 | MonitorAutoLock mon(mGraphImpl->GetMonitor()); |
1165 | 0 | GraphImpl()->DeviceChanged(); |
1166 | | #ifdef XP_MACOSX |
1167 | | PanOutputIfNeeded(mInputChannelCount); |
1168 | | #endif |
1169 | | } |
1170 | | |
1171 | | uint32_t |
1172 | | AudioCallbackDriver::IterationDuration() |
1173 | 0 | { |
1174 | 0 | MOZ_ASSERT(OnThread()); |
1175 | 0 | // The real fix would be to have an API in cubeb to give us the number. Short |
1176 | 0 | // of that, we approximate it here. bug 1019507 |
1177 | 0 | return mIterationDurationMS; |
1178 | 0 | } |
1179 | | |
1180 | | bool |
1181 | 0 | AudioCallbackDriver::IsStarted() { |
1182 | 0 | return mStarted; |
1183 | 0 | } |
1184 | | |
1185 | | void |
1186 | | AudioCallbackDriver::EnqueueStreamAndPromiseForOperation(MediaStream* aStream, |
1187 | | void* aPromise, |
1188 | | dom::AudioContextOperation aOperation) |
1189 | 0 | { |
1190 | 0 | MOZ_ASSERT(OnThread() || !ThreadRunning()); |
1191 | 0 | MonitorAutoLock mon(mGraphImpl->GetMonitor()); |
1192 | 0 | mPromisesForOperation.AppendElement(StreamAndPromiseForOperation(aStream, |
1193 | 0 | aPromise, |
1194 | 0 | aOperation)); |
1195 | 0 | } |
1196 | | |
1197 | | void AudioCallbackDriver::CompleteAudioContextOperations(AsyncCubebOperation aOperation) |
1198 | 0 | { |
1199 | 0 | MOZ_ASSERT(OnCubebOperationThread()); |
1200 | 0 | AutoTArray<StreamAndPromiseForOperation, 1> array; |
1201 | 0 |
|
1202 | 0 | // We can't lock for the whole function because AudioContextOperationCompleted |
1203 | 0 | // will grab the monitor |
1204 | 0 | { |
1205 | 0 | MonitorAutoLock mon(GraphImpl()->GetMonitor()); |
1206 | 0 | array.SwapElements(mPromisesForOperation); |
1207 | 0 | } |
1208 | 0 |
|
1209 | 0 | for (uint32_t i = 0; i < array.Length(); i++) { |
1210 | 0 | StreamAndPromiseForOperation& s = array[i]; |
1211 | 0 | if ((aOperation == AsyncCubebOperation::INIT && |
1212 | 0 | s.mOperation == dom::AudioContextOperation::Resume) || |
1213 | 0 | (aOperation == AsyncCubebOperation::SHUTDOWN && |
1214 | 0 | s.mOperation != dom::AudioContextOperation::Resume)) { |
1215 | 0 |
|
1216 | 0 | GraphImpl()->AudioContextOperationCompleted(s.mStream, |
1217 | 0 | s.mPromise, |
1218 | 0 | s.mOperation); |
1219 | 0 | array.RemoveElementAt(i); |
1220 | 0 | i--; |
1221 | 0 | } |
1222 | 0 | } |
1223 | 0 |
|
1224 | 0 | if (!array.IsEmpty()) { |
1225 | 0 | MonitorAutoLock mon(GraphImpl()->GetMonitor()); |
1226 | 0 | mPromisesForOperation.AppendElements(array); |
1227 | 0 | } |
1228 | 0 | } |
1229 | | |
1230 | | void AudioCallbackDriver::FallbackToSystemClockDriver() |
1231 | 0 | { |
1232 | 0 | MOZ_ASSERT(!ThreadRunning()); |
1233 | 0 | GraphImpl()->GetMonitor().AssertCurrentThreadOwns(); |
1234 | 0 | SystemClockDriver* nextDriver = new SystemClockDriver(GraphImpl()); |
1235 | 0 | nextDriver->MarkAsFallback(); |
1236 | 0 | SetNextDriver(nextDriver); |
1237 | 0 | // We're not using SwitchAtNextIteration here, because there |
1238 | 0 | // won't be a next iteration if we don't restart things manually: |
1239 | 0 | // the audio stream just signaled that it's in error state. |
1240 | 0 | SwitchToNextDriver(); |
1241 | 0 | } |
1242 | | |
1243 | | } // namespace mozilla |
1244 | | |
1245 | | // avoid redefined macro in unified build |
1246 | | #undef LOG |