Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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 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);
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;
}
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 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 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();
/* 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;
/* 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_