1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
|
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef GRAPHDRIVER_H_
#define GRAPHDRIVER_H_
#include "nsAutoRef.h"
#include "nsIThread.h"
#include "AudioBufferUtils.h"
#include "AudioMixer.h"
#include "AudioSegment.h"
#include "SelfRef.h"
#include "mozilla/Atomics.h"
#include "mozilla/dom/AudioContext.h"
#include "mozilla/DataMutex.h"
#include "mozilla/TaskQueue.h"
#include "mozilla/StaticPtr.h"
#include "WavDumper.h"
#include <thread>
struct cubeb_stream;
template <>
class nsAutoRefTraits<cubeb_stream> : public nsPointerRefTraits<cubeb_stream> {
public:
static void Release(cubeb_stream* aStream) { cubeb_stream_destroy(aStream); }
};
namespace mozilla {
/**
* Assume we can run an iteration of the MediaTrackGraph loop in this much time
* or less.
* We try to run the control loop at this rate.
*/
static const int MEDIA_GRAPH_TARGET_PERIOD_MS = 10;
/**
* Assume that we might miss our scheduled wakeup of the MediaTrackGraph by
* this much.
*/
static const int SCHEDULE_SAFETY_MARGIN_MS = 10;
/**
* Try have this much audio buffered in streams and queued to the hardware.
* The maximum delay to the end of the next control loop
* is 2*MEDIA_GRAPH_TARGET_PERIOD_MS + SCHEDULE_SAFETY_MARGIN_MS.
* There is no point in buffering more audio than this in a stream at any
* given time (until we add processing).
* This is not optimal yet.
*/
static const int AUDIO_TARGET_MS =
2 * MEDIA_GRAPH_TARGET_PERIOD_MS + SCHEDULE_SAFETY_MARGIN_MS;
/**
* After starting a fallback driver, wait this long before attempting to re-init
* the audio stream the first time.
*/
static const int AUDIO_INITIAL_FALLBACK_BACKOFF_STEP_MS = 10;
/**
* The backoff step duration for when to next attempt to re-init the audio
* stream is capped at this value.
*/
static const int AUDIO_MAX_FALLBACK_BACKOFF_STEP_MS = 1000;
class AudioCallbackDriver;
class GraphDriver;
class MediaTrack;
class OfflineClockDriver;
class SystemClockDriver;
namespace dom {
enum class AudioContextOperation : uint8_t;
}
struct GraphInterface : public nsISupports {
/**
* Object returned from OneIteration() instructing the iterating GraphDriver
* what to do.
*
* - If the result is StillProcessing: keep the iterations coming.
* - If the result is Stop: the driver potentially updates its internal state
* and interacts with the graph (e.g., NotifyOutputData), then it must call
* Stopped() exactly once.
* - If the result is SwitchDriver: the driver updates internal state as for
* the Stop result, then it must call Switched() exactly once and start
* NextDriver().
*/
class IterationResult final {
struct Undefined {};
struct StillProcessing {};
struct Stop {
explicit Stop(RefPtr<Runnable> aStoppedRunnable)
: mStoppedRunnable(std::move(aStoppedRunnable)) {}
Stop(const Stop&) = delete;
Stop(Stop&& aOther) noexcept
: mStoppedRunnable(std::move(aOther.mStoppedRunnable)) {}
~Stop() { MOZ_ASSERT(!mStoppedRunnable); }
RefPtr<Runnable> mStoppedRunnable;
void Stopped() {
mStoppedRunnable->Run();
mStoppedRunnable = nullptr;
}
};
struct SwitchDriver {
SwitchDriver(RefPtr<GraphDriver> aDriver,
RefPtr<Runnable> aSwitchedRunnable)
: mDriver(std::move(aDriver)),
mSwitchedRunnable(std::move(aSwitchedRunnable)) {}
SwitchDriver(const SwitchDriver&) = delete;
SwitchDriver(SwitchDriver&& aOther) noexcept
: mDriver(std::move(aOther.mDriver)),
mSwitchedRunnable(std::move(aOther.mSwitchedRunnable)) {}
~SwitchDriver() { MOZ_ASSERT(!mSwitchedRunnable); }
RefPtr<GraphDriver> mDriver;
RefPtr<Runnable> mSwitchedRunnable;
void Switched() {
mSwitchedRunnable->Run();
mSwitchedRunnable = nullptr;
}
};
Variant<Undefined, StillProcessing, Stop, SwitchDriver> mResult;
explicit IterationResult(StillProcessing&& aArg)
: mResult(std::move(aArg)) {}
explicit IterationResult(Stop&& aArg) : mResult(std::move(aArg)) {}
explicit IterationResult(SwitchDriver&& aArg) : mResult(std::move(aArg)) {}
public:
IterationResult() : mResult(Undefined()) {}
IterationResult(const IterationResult&) = delete;
IterationResult(IterationResult&&) = default;
IterationResult& operator=(const IterationResult&) = delete;
IterationResult& operator=(IterationResult&&) = default;
static IterationResult CreateStillProcessing() {
return IterationResult(StillProcessing());
}
static IterationResult CreateStop(RefPtr<Runnable> aStoppedRunnable) {
return IterationResult(Stop(std::move(aStoppedRunnable)));
}
static IterationResult CreateSwitchDriver(
RefPtr<GraphDriver> aDriver, RefPtr<Runnable> aSwitchedRunnable) {
return IterationResult(
SwitchDriver(std::move(aDriver), std::move(aSwitchedRunnable)));
}
bool IsStillProcessing() const { return mResult.is<StillProcessing>(); }
bool IsStop() const { return mResult.is<Stop>(); }
bool IsSwitchDriver() const { return mResult.is<SwitchDriver>(); }
void Stopped() {
MOZ_ASSERT(IsStop());
mResult.as<Stop>().Stopped();
}
GraphDriver* NextDriver() const {
if (!IsSwitchDriver()) {
return nullptr;
}
return mResult.as<SwitchDriver>().mDriver;
}
void Switched() {
MOZ_ASSERT(IsSwitchDriver());
mResult.as<SwitchDriver>().Switched();
}
};
/* Called on the graph thread after an AudioCallbackDriver with an input
* stream has stopped. */
virtual void NotifyInputStopped() = 0;
/* Called on the graph thread when there is new input data for listeners. This
* is the raw audio input for this MediaTrackGraph. */
virtual void NotifyInputData(const AudioDataValue* aBuffer, size_t aFrames,
TrackRate aRate, uint32_t aChannels,
uint32_t aAlreadyBuffered) = 0;
/* Called on the main thread after an AudioCallbackDriver has attempted an
* operation to set aRequestedParams on the cubeb stream. */
virtual void NotifySetRequestedInputProcessingParamsResult(
AudioCallbackDriver* aDriver,
cubeb_input_processing_params aRequestedParams,
Result<cubeb_input_processing_params, int>&& aResult) = 0;
/* Called every time there are changes to input/output audio devices like
* plug/unplug etc. This can be called on any thread, and posts a message to
* the main thread so that it can post a message to the graph thread. */
virtual void DeviceChanged() = 0;
/* Called by GraphDriver to iterate the graph. Mixed audio output from the
* graph is passed into aMixerReceiver, if it is non-null. */
virtual IterationResult OneIteration(
GraphTime aStateComputedEnd, GraphTime aIterationEnd,
MixerCallbackReceiver* aMixerReceiver) = 0;
#ifdef DEBUG
/* True if we're on aDriver's thread, or if we're on mGraphRunner's thread
* and mGraphRunner is currently run by aDriver. */
virtual bool InDriverIteration(const GraphDriver* aDriver) const = 0;
#endif
};
/**
* A driver is responsible for the scheduling of the processing, the thread
* management, and give the different clocks to a MediaTrackGraph. This is an
* abstract base class. A MediaTrackGraph can be driven by an
* OfflineClockDriver, if the graph is offline, or a SystemClockDriver or an
* AudioCallbackDriver, if the graph is real time.
* A MediaTrackGraph holds an owning reference to its driver.
*
* The lifetime of drivers is a complicated affair. Here are the different
* scenarii that can happen:
*
* Starting a MediaTrackGraph with an AudioCallbackDriver
* - A new thread T is created, from the main thread.
* - On this thread T, cubeb is initialized if needed, and a cubeb_stream is
* created and started
* - The thread T posts a message to the main thread to terminate itself.
* - The graph runs off the audio thread
*
* Starting a MediaTrackGraph with a SystemClockDriver:
* - A new thread T is created from the main thread.
* - The graph runs off this thread.
*
* Switching from a SystemClockDriver to an AudioCallbackDriver:
* - At the end of the MTG iteration, the graph tells the current driver to
* switch to an AudioCallbackDriver, which is created and initialized on the
* graph thread.
* - At the end of the MTG iteration, the SystemClockDriver transfers its timing
* info and a reference to itself to the AudioCallbackDriver. It then starts
* the AudioCallbackDriver.
* - When the AudioCallbackDriver starts, it:
* - Starts a fallback SystemClockDriver that runs until the
* AudioCallbackDriver is running, in case it takes a long time to start (it
* could block on I/O, e.g., negotiating a bluetooth connection).
* - Checks if it has been switched from a SystemClockDriver, and if that is
* the case, sends a message to the main thread to shut the
* SystemClockDriver thread down.
* - When the AudioCallbackDriver is running, data callbacks are blocked. The
* fallback driver detects this in its callback and stops itself. The first
* DataCallback after the fallback driver had stopped goes through.
* - The graph now runs off an audio callback.
*
* Switching from an AudioCallbackDriver to a SystemClockDriver:
* - At the end of the MTG iteration, the graph tells the current driver to
* switch to a SystemClockDriver.
* - the AudioCallbackDriver transfers its timing info and a reference to itself
* to the SystemClockDriver. A new SystemClockDriver is started from the
* current audio thread.
* - When starting, the SystemClockDriver checks if it has been switched from an
* AudioCallbackDriver. If yes, it creates a new temporary thread to release
* the cubeb_streams. This temporary thread closes the cubeb_stream, and then
* dispatches a message to the main thread to be terminated.
* - The graph now runs off a normal thread.
*
* Two drivers cannot run at the same time for the same graph. The thread safety
* of the different members of drivers, and their access pattern is documented
* next to the members themselves.
*/
class GraphDriver {
public:
using IterationResult = GraphInterface::IterationResult;
GraphDriver(GraphInterface* aGraphInterface, GraphDriver* aPreviousDriver,
uint32_t aSampleRate);
NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING
/* Start the graph, init the driver, start the thread.
* A driver cannot be started twice, it must be shutdown
* before being started again. */
virtual void Start() = 0;
/* Shutdown GraphDriver */
MOZ_CAN_RUN_SCRIPT virtual void Shutdown() = 0;
/* Set the UTF-8 name for system audio streams.
* Graph thread, or main thread if the graph is not running. */
virtual void SetStreamName(const nsACString& aStreamName);
/* Rate at which the GraphDriver runs, in ms. This can either be user
* controlled (because we are using a {System,Offline}ClockDriver, and decide
* how often we want to wakeup/how much we want to process per iteration), or
* it can be indirectly set by the latency of the audio backend, and the
* number of buffers of this audio backend: say we have four buffers, and 40ms
* latency, we will get a callback approximately every 10ms. */
virtual uint32_t IterationDuration() = 0;
/*
* Signaled by the graph when it needs another iteration. Goes unhandled for
* GraphDrivers that are not able to sleep indefinitely (i.e., all drivers but
* ThreadedDriver). Can be called on any thread.
*/
virtual void EnsureNextIteration() = 0;
// Those are simply for accessing the associated pointer. Graph thread only,
// or if one is not running, main thread.
GraphDriver* PreviousDriver();
void SetPreviousDriver(GraphDriver* aPreviousDriver);
virtual AudioCallbackDriver* AsAudioCallbackDriver() { return nullptr; }
virtual const AudioCallbackDriver* AsAudioCallbackDriver() const {
return nullptr;
}
virtual OfflineClockDriver* AsOfflineClockDriver() { return nullptr; }
virtual const OfflineClockDriver* AsOfflineClockDriver() const {
return nullptr;
}
virtual SystemClockDriver* AsSystemClockDriver() { return nullptr; }
virtual const SystemClockDriver* AsSystemClockDriver() const {
return nullptr;
}
/**
* Set the state of the driver so it can start at the right point in time,
* after switching from another driver.
*/
void SetState(const nsACString& aStreamName, GraphTime aIterationEnd,
GraphTime aStateComputedTime);
GraphInterface* Graph() const { return mGraphInterface; }
#ifdef DEBUG
// True if the current thread is currently iterating the MTG.
bool InIteration() const;
#endif
// True if the current thread is the GraphDriver's thread.
virtual bool OnThread() const = 0;
// GraphDriver's thread has started and the thread is running.
virtual bool ThreadRunning() const = 0;
double MediaTimeToSeconds(GraphTime aTime) const {
NS_ASSERTION(aTime > -TRACK_TIME_MAX && aTime <= TRACK_TIME_MAX,
"Bad time");
return static_cast<double>(aTime) / mSampleRate;
}
GraphTime SecondsToMediaTime(double aS) const {
NS_ASSERTION(0 <= aS && aS <= TRACK_TICKS_MAX / TRACK_RATE_MAX,
"Bad seconds");
return mSampleRate * aS;
}
GraphTime MillisecondsToMediaTime(int32_t aMS) const {
return RateConvertTicksRoundDown(mSampleRate, 1000, aMS);
}
protected:
// The UTF-8 name for system audio streams. Graph thread.
nsCString mStreamName;
// Time of the end of this graph iteration.
GraphTime mIterationEnd = 0;
// Time until which the graph has processed data.
GraphTime mStateComputedTime = 0;
// The GraphInterface this driver is currently iterating.
const RefPtr<GraphInterface> mGraphInterface;
// The sample rate for the graph, and in case of an audio driver, also for the
// cubeb stream.
const uint32_t mSampleRate;
// This is non-null only when this driver has recently switched from an other
// driver, and has not cleaned it up yet (for example because the audio stream
// is currently calling the callback during initialization).
//
// This is written to when changing driver, from the previous driver's thread,
// or a thread created for the occasion. This is read each time we need to
// check whether we're changing driver (in Switching()), from the graph
// thread.
// This must be accessed using the {Set,Get}PreviousDriver methods.
RefPtr<GraphDriver> mPreviousDriver;
virtual ~GraphDriver() = default;
};
class MediaTrackGraphInitThreadRunnable;
/**
* This class is a driver that manages its own thread.
*/
class ThreadedDriver : public GraphDriver {
class IterationWaitHelper {
Monitor mMonitor MOZ_UNANNOTATED;
// The below members are guarded by mMonitor.
bool mNeedAnotherIteration = false;
TimeStamp mWakeTime;
public:
IterationWaitHelper() : mMonitor("IterationWaitHelper::mMonitor") {}
/**
* If another iteration is needed we wait for aDuration, otherwise we wait
* for a wake-up. If a wake-up occurs before aDuration time has passed, we
* wait for aDuration nonetheless.
*/
void WaitForNextIterationAtLeast(TimeDuration aDuration) {
MonitorAutoLock lock(mMonitor);
TimeStamp now = TimeStamp::Now();
mWakeTime = now + aDuration;
while (true) {
if (mNeedAnotherIteration && now >= mWakeTime) {
break;
}
if (mNeedAnotherIteration) {
lock.Wait(mWakeTime - now);
} else {
lock.Wait(TimeDuration::Forever());
}
now = TimeStamp::Now();
}
mWakeTime = TimeStamp();
mNeedAnotherIteration = false;
}
/**
* Sets mNeedAnotherIteration to true and notifies the monitor, in case a
* driver is currently waiting.
*/
void EnsureNextIteration() {
MonitorAutoLock lock(mMonitor);
mNeedAnotherIteration = true;
lock.Notify();
}
};
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ThreadedDriver, override);
ThreadedDriver(GraphInterface* aGraphInterface, GraphDriver* aPreviousDriver,
uint32_t aSampleRate);
void EnsureNextIteration() override;
void Start() override;
MOZ_CAN_RUN_SCRIPT void Shutdown() override;
/**
* Runs main control loop on the graph thread. Normally a single invocation
* of this runs for the entire lifetime of the graph thread.
*/
virtual void RunThread();
friend class MediaTrackGraphInitThreadRunnable;
uint32_t IterationDuration() override { return MEDIA_GRAPH_TARGET_PERIOD_MS; }
nsIThread* Thread() const { return mThread; }
bool OnThread() const override {
return !mThread || mThread->IsOnCurrentThread();
}
bool ThreadRunning() const override { return mThreadRunning; }
protected:
/* Waits until it's time to process more data. */
void WaitForNextIteration();
/* Implementation dependent time the ThreadedDriver should wait between
* iterations. */
virtual TimeDuration WaitInterval() = 0;
/* When the graph wakes up to do an iteration, implementations return the
* range of time that will be processed. This is called only once per
* iteration; it may determine the interval from state in a previous
* call. */
virtual MediaTime GetIntervalForIteration() = 0;
virtual ~ThreadedDriver();
nsCOMPtr<nsIThread> mThread;
private:
// This is true if the thread is running. It is false
// before starting the thread and after stopping it.
Atomic<bool> mThreadRunning;
// Any thread.
IterationWaitHelper mWaitHelper;
};
/**
* A SystemClockDriver drives a GraphInterface using a system clock, and waits
* using a monitor, between each iteration.
*/
class SystemClockDriver : public ThreadedDriver {
public:
SystemClockDriver(GraphInterface* aGraphInterface,
GraphDriver* aPreviousDriver, uint32_t aSampleRate);
virtual ~SystemClockDriver();
SystemClockDriver* AsSystemClockDriver() override { return this; }
const SystemClockDriver* AsSystemClockDriver() const override { return this; }
protected:
/* Return the TimeDuration to wait before the next rendering iteration. */
TimeDuration WaitInterval() override;
MediaTime GetIntervalForIteration() override;
private:
// Those are only modified (after initialization) on the graph thread. The
// graph thread does not run during the initialization.
TimeStamp mInitialTimeStamp;
TimeStamp mCurrentTimeStamp;
TimeStamp mLastTimeStamp;
};
/**
* An OfflineClockDriver runs the graph as fast as possible, without waiting
* between iteration.
*/
class OfflineClockDriver : public ThreadedDriver {
public:
OfflineClockDriver(GraphInterface* aGraphInterface, uint32_t aSampleRate,
GraphTime aSlice);
virtual ~OfflineClockDriver();
OfflineClockDriver* AsOfflineClockDriver() override { return this; }
const OfflineClockDriver* AsOfflineClockDriver() const override {
return this;
}
void RunThread() override;
protected:
TimeDuration WaitInterval() override { return TimeDuration(); }
MediaTime GetIntervalForIteration() override;
private:
// Time, in GraphTime, for each iteration
GraphTime mSlice;
};
enum class AudioInputType { Unknown, Voice };
/**
* This is a graph driver that is based on callback functions called by the
* audio api. This ensures minimal audio latency, because it means there is no
* buffering happening: the audio is generated inside the callback.
*
* This design is less flexible than running our own thread:
* - We have no control over the thread:
* - It cannot block, and it has to run for a shorter amount of time than the
* buffer it is going to fill, or an under-run is going to occur (short burst
* of silence in the final audio output).
* - We can't know for sure when the callback function is going to be called
* (although we compute an estimation so we can schedule video frames)
* - Creating and shutting the thread down is a blocking operation, that can
* take _seconds_ in some cases (because IPC has to be set up, and
* sometimes hardware components are involved and need to be warmed up)
* - We have no control on how much audio we generate, we have to return exactly
* the number of frames asked for by the callback. Since for the Web Audio
* API, we have to do block processing at 128 frames per block, we need to
* keep a little spill buffer to store the extra frames.
*/
class AudioCallbackDriver : public GraphDriver, public MixerCallbackReceiver {
using IterationResult = GraphInterface::IterationResult;
enum class FallbackDriverState;
class FallbackWrapper;
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DELETE_ON_EVENT_TARGET(
AudioCallbackDriver, mCubebOperationThread, override);
/** If aInputChannelCount is zero, then this driver is output-only. */
AudioCallbackDriver(
GraphInterface* aGraphInterface, GraphDriver* aPreviousDriver,
uint32_t aSampleRate, uint32_t aOutputChannelCount,
uint32_t aInputChannelCount, CubebUtils::AudioDeviceID aOutputDeviceID,
CubebUtils::AudioDeviceID aInputDeviceID, AudioInputType aAudioInputType,
cubeb_input_processing_params aRequestedInputProcessingParams);
void Start() override;
MOZ_CAN_RUN_SCRIPT void Shutdown() override;
void SetStreamName(const nsACString& aStreamName) override;
/* Static wrapper function cubeb calls back. */
static long DataCallback_s(cubeb_stream* aStream, void* aUser,
const void* aInputBuffer, void* aOutputBuffer,
long aFrames);
static void StateCallback_s(cubeb_stream* aStream, void* aUser,
cubeb_state aState);
static void DeviceChangedCallback_s(void* aUser);
/* This function is called by the underlying audio backend when a refill is
* needed. This is what drives the whole graph when it is used to output
* audio. If the return value is exactly aFrames, this function will get
* called again. If it is less than aFrames, the stream will go in draining
* mode, and this function will not be called again. */
long DataCallback(const AudioDataValue* aInputBuffer,
AudioDataValue* aOutputBuffer, long aFrames);
/* This function is called by the underlying audio backend, but is only used
* for informational purposes at the moment. */
void StateCallback(cubeb_state aState);
/* This is an approximation of the number of millisecond there are between two
* iterations of the graph. */
uint32_t IterationDuration() override;
/* If the audio stream has started, this does nothing. There will be another
* iteration. If there is an active fallback driver, we forward the call so it
* can wake up. */
void EnsureNextIteration() override;
/* This function gets called when the graph has produced the audio frames for
* this iteration. */
void MixerCallback(AudioChunk* aMixedBuffer, uint32_t aSampleRate) override;
AudioCallbackDriver* AsAudioCallbackDriver() override { return this; }
const AudioCallbackDriver* AsAudioCallbackDriver() const override {
return this;
}
uint32_t OutputChannelCount() { return mOutputChannelCount; }
uint32_t InputChannelCount() { return mInputChannelCount; }
AudioInputType InputDevicePreference() {
if (mInputDevicePreference == CUBEB_DEVICE_PREF_VOICE) {
return AudioInputType::Voice;
}
return AudioInputType::Unknown;
}
/* Get the input processing params requested from this driver, so that an
* external caller can decide whether it is necessary to call the setter,
* since it may allocate or dispatch. */
cubeb_input_processing_params RequestedInputProcessingParams() const;
/* Set the input processing params requested from this driver. */
void SetRequestedInputProcessingParams(cubeb_input_processing_params aParams);
std::thread::id ThreadId() const { return mAudioThreadIdInCb.load(); }
/* Called at the beginning of the audio callback to check if the thread id has
* changed. */
bool CheckThreadIdChanged();
bool OnThread() const override {
return mAudioThreadIdInCb.load() == std::this_thread::get_id();
}
/* Returns true if this driver has started (perhaps with a fallback driver)
* and not yet stopped. */
bool ThreadRunning() const override {
return mAudioStreamState == AudioStreamState::Running ||
mFallbackDriverState == FallbackDriverState::Running;
}
/* Whether the underlying cubeb stream has been started and has not stopped
* or errored. */
bool IsStarted() { return mAudioStreamState > AudioStreamState::Starting; };
// Returns the output latency for the current audio output stream.
TimeDuration AudioOutputLatency();
/* Returns true if this driver has a fallback driver and handover to the audio
* callback has not been completed. */
bool HasFallback() const;
/* Returns true if this driver is currently driven by the fallback driver. */
bool OnFallback() const;
private:
/**
* On certain MacBookPro, the microphone is located near the left speaker.
* We need to pan the sound output to the right speaker if we are using the
* mic and the built-in speaker, or we will have terrible echo. */
void PanOutputIfNeeded(bool aMicrophoneActive);
/**
* This is called when the output device used by the cubeb stream changes. */
void DeviceChangedCallback();
/* Start the cubeb stream */
bool StartStream();
friend class MediaTrackGraphInitThreadRunnable;
void Init(const nsCString& aStreamName);
void SetCubebStreamName(const nsCString& aStreamName);
void Stop();
/* After the requested input processing params has changed, this applies them
* on the cubeb stream. */
void SetInputProcessingParams(cubeb_input_processing_params aParams);
/* Calls FallbackToSystemClockDriver() if in FallbackDriverState::None.
* Returns Ok(true) if the fallback driver was started, or the old
* FallbackDriverState in an Err otherwise. */
Result<bool, FallbackDriverState> TryStartingFallbackDriver();
/* Fall back to a SystemClockDriver using a normal thread. If needed, the
* graph will try to re-open an audio stream later. */
void FallbackToSystemClockDriver();
/* Called by the fallback driver when it has fully stopped, after finishing
* its last iteration. If it stopped after the audio stream started, aState
* will be None. If it stopped after the graph told it to stop, or switch,
* aState will be Stopped. Hands over state to the audio driver that may
* iterate the graph after this has been called. */
void FallbackDriverStopped(GraphTime aIterationEnd,
GraphTime aStateComputedTime,
FallbackDriverState aState);
/* Called at the end of the fallback driver's iteration to see whether we
* should attempt to start the AudioStream again. */
void MaybeStartAudioStream();
/* This is true when the method is executed on CubebOperation thread pool. */
bool OnCubebOperationThread() {
return mCubebOperationThread->IsOnCurrentThreadInfallible();
}
/* MediaTrackGraphs are always down/up mixed to output channels. */
const uint32_t mOutputChannelCount;
/* The size of this buffer comes from the fact that some audio backends can
* call back with a number of frames lower than one block (128 frames), so we
* need to keep at most two block in the SpillBuffer, because we always round
* up to block boundaries during an iteration.
* This is only ever accessed on the audio callback thread. */
SpillBuffer<AudioDataValue, WEBAUDIO_BLOCK_SIZE * 2> mScratchBuffer;
/* Wrapper to ensure we write exactly the number of frames we need in the
* audio buffer cubeb passes us. This is only ever accessed on the audio
* callback thread. */
AudioCallbackBufferWrapper<AudioDataValue> mBuffer;
// mAudioStream (a cubeb_stream) has a bare pointer to the cubeb context, so
// we hold a strong reference on its behalf.
RefPtr<CubebUtils::CubebHandle> mCubeb;
/* cubeb stream for this graph. This is non-null after a successful
* cubeb_stream_init(). CubebOperation thread only. */
nsAutoRef<cubeb_stream> mAudioStream;
/* The number of input channels from cubeb. Set before opening cubeb. If it is
* zero then the driver is output-only. */
const uint32_t mInputChannelCount;
/**
* Devices to use for cubeb input & output, or nullptr for default device.
*/
const CubebUtils::AudioDeviceID mOutputDeviceID;
const CubebUtils::AudioDeviceID mInputDeviceID;
/* Approximation of the time between two callbacks. This is used to schedule
* video frames. This is in milliseconds. Only even used (after
* inizatialization) on the audio callback thread. */
uint32_t mIterationDurationMS;
struct AutoInCallback {
explicit AutoInCallback(AudioCallbackDriver* aDriver);
~AutoInCallback();
AudioCallbackDriver* mDriver;
};
static already_AddRefed<TaskQueue> CreateTaskQueue();
/* Shared thread pool with up to one thread for off-main-thread
* initialization and shutdown of the audio stream and for other tasks that
* must run serially for access to mAudioStream. */
const RefPtr<TaskQueue> mCubebOperationThread;
cubeb_device_pref mInputDevicePreference;
/* Params that have been attempted to set on mAudioStream, after filtering by
* supported processing params. Cubeb operation thread only. */
cubeb_input_processing_params mConfiguredInputProcessingParams =
CUBEB_INPUT_PROCESSING_PARAM_NONE;
/* The input processing params requested from this audio driver. Once started,
* audio callback thread only. */
cubeb_input_processing_params mRequestedInputProcessingParams;
/* Contains the id of the audio thread, from profiler_current_thread_id. */
std::atomic<ProfilerThreadId> mAudioThreadId;
/* This allows implementing AutoInCallback. This is equal to the current
* thread id when in an audio callback, and is an invalid thread id otherwise.
*/
std::atomic<std::thread::id> mAudioThreadIdInCb;
/* State of the audio stream, see inline comments. */
enum class AudioStreamState {
/* There is no cubeb_stream or mAudioStream is in CUBEB_STATE_ERROR or
* CUBEB_STATE_STOPPED and no pending task exists to Init() a new
* cubeb_stream. */
None,
/* A task to Init() a new cubeb_stream is pending. */
Pending,
/* cubeb_start_stream() is about to be or has been called on mAudioStream.
* Any previous cubeb_streams have been destroyed. */
Starting,
/* mAudioStream has advertised it will change device. In this state we
ignore all data callbacks until the fallback driver has started. */
ChangingDevice,
/* mAudioStream is running. */
Running,
/* mAudioStream is draining, and will soon stop. */
Stopping
};
Atomic<AudioStreamState> mAudioStreamState{AudioStreamState::None};
/* State of the fallback driver, see inline comments. */
enum class FallbackDriverState {
/* There is no fallback driver. */
None,
/* There is a fallback driver trying to iterate us. */
Running,
/* There was a fallback driver and the graph stopped it. No audio callback
may iterate the graph. */
Stopped,
};
Atomic<FallbackDriverState> mFallbackDriverState{FallbackDriverState::None};
/* SystemClockDriver used as fallback if this AudioCallbackDriver fails to
* init or start. */
DataMutex<RefPtr<FallbackWrapper>> mFallback;
/* If using a fallback driver, this is the duration to wait after failing to
* start it before attempting to start it again. */
TimeDuration mNextReInitBackoffStep;
/* If using a fallback driver, this is the next time we'll try to start the
* audio stream. */
TimeStamp mNextReInitAttempt;
/* The time mAudioStreamState was changed to ChangingDevice.
* Synchronized by the mAudioStreamState atomic, i.e. written *before* writing
* the atomic, and read *after* reading the atomic. */
TimeStamp mChangingDeviceStartTime;
#ifdef XP_MACOSX
/* When using the built-in speakers on macbook pro (13 and 15, all models),
* it's best to hard pan the audio on the right, to avoid feedback into the
* microphone that is located next to the left speaker. */
Atomic<bool> mNeedsPanning;
#endif
WavDumper mInputStreamFile;
WavDumper mOutputStreamFile;
virtual ~AudioCallbackDriver();
const bool mSandboxed = false;
};
} // namespace mozilla
#endif // GRAPHDRIVER_H_
|