diff options
Diffstat (limited to 'dom/media/GraphDriver.h')
-rw-r--r-- | dom/media/GraphDriver.h | 821 |
1 files changed, 821 insertions, 0 deletions
diff --git a/dom/media/GraphDriver.h b/dom/media/GraphDriver.h new file mode 100644 index 0000000000..7c985eeca0 --- /dev/null +++ b/dom/media/GraphDriver.h @@ -0,0 +1,821 @@ +/* -*- 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/SharedThreadPool.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 { + +// A thread pool containing only one thread to execute the cubeb operations. We +// should always use this thread to init, destroy, start, or stop cubeb streams, +// to avoid data racing or deadlock issues across platforms. +#define CUBEB_TASK_THREAD SharedThreadPool::Get("CubebOperation"_ns, 1) + +/** + * 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 when there is new output data for listeners. + * This is the mixed audio output of this MediaTrackGraph. */ + virtual void NotifyOutputData(AudioDataValue* aBuffer, size_t aFrames, + TrackRate aRate, uint32_t aChannels) = 0; + /* 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 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. Output from the graph gets + * mixed into aMixer, if it is non-null. */ + virtual IterationResult OneIteration(GraphTime aStateComputedEnd, + GraphTime aIterationEnd, + AudioMixer* aMixer) = 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; + /* 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; + + /* Implement the switching of the driver and the necessary updates */ + void SwitchToDriver(GraphDriver* aDriver); + + // 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(GraphTime aIterationStart, 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: + // Time of the start of this graph iteration. + GraphTime mIterationStart = 0; + // 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; +}; + +struct TrackAndPromiseForOperation { + TrackAndPromiseForOperation( + MediaTrack* aTrack, dom::AudioContextOperation aOperation, + AbstractThread* aMainThread, + MozPromiseHolder<MediaTrackGraph::AudioContextOperationPromise>&& + aHolder); + TrackAndPromiseForOperation(TrackAndPromiseForOperation&& aOther) noexcept; + RefPtr<MediaTrack> mTrack; + dom::AudioContextOperation mOperation; + RefPtr<AbstractThread> mMainThread; + MozPromiseHolder<MediaTrackGraph::AudioContextOperationPromise> mHolder; +}; + +enum class AsyncCubebOperation { INIT, SHUTDOWN }; +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(AudioCallbackDriver, 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); + + void Start() override; + MOZ_CAN_RUN_SCRIPT void Shutdown() 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(AudioDataValue* aMixedBuffer, AudioSampleFormat aFormat, + uint32_t aChannels, uint32_t aFrames, + 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; + } + + std::thread::id ThreadId() const { return mAudioThreadIdInCb.load(); } + + /* Called when the thread servicing the callback has changed. This can be + * fairly expensive */ + void OnThreadIdChanged(); + /* 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 audio callback driver has successfully started and not + * yet stopped. If the fallback driver is active, this returns false. */ + bool ThreadRunning() const override { + return mAudioStreamState == AudioStreamState::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 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 AsyncCubebTask; + void Init(); + void Stop(); + /** + * 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 aIterationStart, 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 mInitShutdownThread->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; + /* 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; + }; + + /* Shared thread pool with up to one thread for off-main-thread + * initialization and shutdown of the audio stream via AsyncCubebTask. */ + const RefPtr<SharedThreadPool> mInitShutdownThread; + cubeb_device_pref mInputDevicePreference; + /* The mixer that the graph mixes into during an iteration. Audio thread only. + */ + AudioMixer mMixer; + /* 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 AsyncCubebTask exists to INIT a new + * cubeb_stream. */ + None, + /* An AsyncCubebTask 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 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; +#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; +}; + +class AsyncCubebTask : public Runnable { + public: + AsyncCubebTask(AudioCallbackDriver* aDriver, AsyncCubebOperation aOperation); + + nsresult Dispatch(uint32_t aFlags = NS_DISPATCH_NORMAL) { + return mDriver->mInitShutdownThread->Dispatch(this, aFlags); + } + + nsresult DispatchAndSpinEventLoopUntilComplete( + const nsACString& aVeryGoodReasonToDoThis) { + return NS_DispatchAndSpinEventLoopUntilComplete( + aVeryGoodReasonToDoThis, mDriver->mInitShutdownThread, do_AddRef(this)); + } + + protected: + virtual ~AsyncCubebTask(); + + private: + NS_IMETHOD Run() final; + + RefPtr<AudioCallbackDriver> mDriver; + AsyncCubebOperation mOperation; + RefPtr<GraphInterface> mShutdownGrip; +}; + +} // namespace mozilla + +#endif // GRAPHDRIVER_H_ |