2014-04-25 18:09:30 +04:00
|
|
|
/* -*- 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_
|
|
|
|
|
2014-08-26 19:01:33 +04:00
|
|
|
#include "nsAutoRef.h"
|
2020-11-23 19:21:38 +03:00
|
|
|
#include "nsIThread.h"
|
2014-08-26 19:01:33 +04:00
|
|
|
#include "AudioBufferUtils.h"
|
|
|
|
#include "AudioMixer.h"
|
|
|
|
#include "AudioSegment.h"
|
Bug 1085356 - Better handling of OSX audio output devices switching when SourceMediaStream are present in the MSG. r=jesup
On OSX, when the audio output device changes, the OS will call the audio
callbacks in weird patterns, if at all, during a period of ~1s. If
real-time SourceMediaStreams are present in the MediaStreamGraph, this means
buffering will occur, and the overall latency between the MediaStreamGraph
insertion time, and the actual output time will grow.
To fix this, we detect when the output device changes, and we switch temporarily
to a SystemClockDriver, that will pull from the SourceMediaStream, and simply
discard all input data. Then, when we get audio callbacks called reliably
(basically, when OSX is done switching to the other output), we switch back to
the previous AudioCallbackDriver.
We keep the previous AudioCallbackDriver alive using a self-reference. If an
AudioCallbackDriver has a self-reference, that means it's in a state when a
device is switching, so it's not linked to an MSG per se.
2014-10-22 18:12:29 +04:00
|
|
|
#include "SelfRef.h"
|
2014-09-28 20:07:24 +04:00
|
|
|
#include "mozilla/Atomics.h"
|
2019-05-14 19:00:22 +03:00
|
|
|
#include "mozilla/dom/AudioContext.h"
|
2019-12-19 01:50:52 +03:00
|
|
|
#include "mozilla/DataMutex.h"
|
2016-01-21 19:51:36 +03:00
|
|
|
#include "mozilla/SharedThreadPool.h"
|
|
|
|
#include "mozilla/StaticPtr.h"
|
2020-04-07 17:33:57 +03:00
|
|
|
#include "WavDumper.h"
|
2014-08-26 19:01:33 +04:00
|
|
|
|
2018-05-07 20:36:14 +03:00
|
|
|
#include <thread>
|
|
|
|
|
2017-08-29 12:45:44 +03:00
|
|
|
#if defined(XP_WIN)
|
|
|
|
# include "mozilla/audio/AudioNotificationReceiver.h"
|
|
|
|
#endif
|
|
|
|
|
2014-08-26 19:01:33 +04:00
|
|
|
struct cubeb_stream;
|
|
|
|
|
2014-09-15 10:09:00 +04:00
|
|
|
template <>
|
|
|
|
class nsAutoRefTraits<cubeb_stream> : public nsPointerRefTraits<cubeb_stream> {
|
|
|
|
public:
|
|
|
|
static void Release(cubeb_stream* aStream) { cubeb_stream_destroy(aStream); }
|
|
|
|
};
|
2014-08-26 19:01:33 +04:00
|
|
|
|
2014-09-15 10:09:00 +04:00
|
|
|
namespace mozilla {
|
2014-09-17 22:39:17 +04:00
|
|
|
|
2014-08-26 19:01:33 +04:00
|
|
|
/**
|
2019-10-02 13:23:02 +03:00
|
|
|
* Assume we can run an iteration of the MediaTrackGraph loop in this much time
|
2014-08-26 19:01:33 +04:00
|
|
|
* or less.
|
|
|
|
* We try to run the control loop at this rate.
|
|
|
|
*/
|
|
|
|
static const int MEDIA_GRAPH_TARGET_PERIOD_MS = 10;
|
|
|
|
|
|
|
|
/**
|
2019-10-02 13:23:02 +03:00
|
|
|
* Assume that we might miss our scheduled wakeup of the MediaTrackGraph by
|
2014-08-26 19:01:33 +04:00
|
|
|
* this much.
|
|
|
|
*/
|
|
|
|
static const int SCHEDULE_SAFETY_MARGIN_MS = 10;
|
|
|
|
|
2014-04-25 18:09:30 +04:00
|
|
|
/**
|
2014-08-26 19:01:33 +04:00
|
|
|
* 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.
|
2014-04-25 18:09:30 +04:00
|
|
|
*/
|
2014-08-26 19:01:33 +04:00
|
|
|
static const int AUDIO_TARGET_MS =
|
|
|
|
2 * MEDIA_GRAPH_TARGET_PERIOD_MS + SCHEDULE_SAFETY_MARGIN_MS;
|
|
|
|
|
2019-12-19 01:53:46 +03:00
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
|
2014-08-26 19:01:33 +04:00
|
|
|
class AudioCallbackDriver;
|
2019-12-19 01:51:57 +03:00
|
|
|
class GraphDriver;
|
|
|
|
class MediaTrack;
|
2014-10-20 20:06:10 +04:00
|
|
|
class OfflineClockDriver;
|
2016-07-27 16:18:17 +03:00
|
|
|
class SystemClockDriver;
|
2014-08-26 19:01:33 +04:00
|
|
|
|
2019-03-06 23:12:25 +03:00
|
|
|
namespace dom {
|
|
|
|
enum class AudioContextOperation;
|
|
|
|
}
|
|
|
|
|
2020-03-03 21:16:21 +03:00
|
|
|
struct GraphInterface : public nsISupports {
|
2019-12-19 01:51:05 +03:00
|
|
|
/**
|
|
|
|
* Object returned from OneIteration() instructing the iterating GraphDriver
|
|
|
|
* what to do.
|
2019-12-19 01:51:12 +03:00
|
|
|
*
|
|
|
|
* - 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.
|
2019-12-19 01:51:25 +03:00
|
|
|
* - 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().
|
2019-12-19 01:51:05 +03:00
|
|
|
*/
|
|
|
|
class IterationResult final {
|
|
|
|
struct Undefined {};
|
|
|
|
struct StillProcessing {};
|
2019-12-19 01:51:12 +03:00
|
|
|
struct Stop {
|
|
|
|
explicit Stop(RefPtr<Runnable> aStoppedRunnable)
|
|
|
|
: mStoppedRunnable(std::move(aStoppedRunnable)) {}
|
|
|
|
Stop(const Stop&) = delete;
|
2019-12-19 16:48:09 +03:00
|
|
|
Stop(Stop&& aOther) noexcept
|
2019-12-19 01:51:12 +03:00
|
|
|
: mStoppedRunnable(std::move(aOther.mStoppedRunnable)) {}
|
|
|
|
~Stop() { MOZ_ASSERT(!mStoppedRunnable); }
|
|
|
|
RefPtr<Runnable> mStoppedRunnable;
|
|
|
|
void Stopped() {
|
|
|
|
mStoppedRunnable->Run();
|
|
|
|
mStoppedRunnable = nullptr;
|
|
|
|
}
|
|
|
|
};
|
2019-12-19 01:51:25 +03:00
|
|
|
struct SwitchDriver {
|
|
|
|
SwitchDriver(RefPtr<GraphDriver> aDriver,
|
|
|
|
RefPtr<Runnable> aSwitchedRunnable)
|
|
|
|
: mDriver(std::move(aDriver)),
|
|
|
|
mSwitchedRunnable(std::move(aSwitchedRunnable)) {}
|
|
|
|
SwitchDriver(const SwitchDriver&) = delete;
|
2019-12-19 16:48:09 +03:00
|
|
|
SwitchDriver(SwitchDriver&& aOther) noexcept
|
2019-12-19 01:51:25 +03:00
|
|
|
: 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;
|
2019-12-19 01:51:05 +03:00
|
|
|
|
|
|
|
explicit IterationResult(StillProcessing&& aArg)
|
|
|
|
: mResult(std::move(aArg)) {}
|
|
|
|
explicit IterationResult(Stop&& aArg) : mResult(std::move(aArg)) {}
|
2019-12-19 01:51:25 +03:00
|
|
|
explicit IterationResult(SwitchDriver&& aArg) : mResult(std::move(aArg)) {}
|
2019-12-19 01:51:05 +03:00
|
|
|
|
|
|
|
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());
|
|
|
|
}
|
2019-12-19 01:51:12 +03:00
|
|
|
static IterationResult CreateStop(RefPtr<Runnable> aStoppedRunnable) {
|
|
|
|
return IterationResult(Stop(std::move(aStoppedRunnable)));
|
|
|
|
}
|
2019-12-19 01:51:25 +03:00
|
|
|
static IterationResult CreateSwitchDriver(
|
|
|
|
RefPtr<GraphDriver> aDriver, RefPtr<Runnable> aSwitchedRunnable) {
|
|
|
|
return IterationResult(
|
|
|
|
SwitchDriver(std::move(aDriver), std::move(aSwitchedRunnable)));
|
|
|
|
}
|
2019-12-19 01:51:05 +03:00
|
|
|
|
|
|
|
bool IsStillProcessing() const { return mResult.is<StillProcessing>(); }
|
|
|
|
bool IsStop() const { return mResult.is<Stop>(); }
|
2019-12-19 01:51:25 +03:00
|
|
|
bool IsSwitchDriver() const { return mResult.is<SwitchDriver>(); }
|
2019-12-19 01:51:12 +03:00
|
|
|
|
|
|
|
void Stopped() {
|
|
|
|
MOZ_ASSERT(IsStop());
|
|
|
|
mResult.as<Stop>().Stopped();
|
|
|
|
}
|
2019-12-19 01:51:25 +03:00
|
|
|
|
|
|
|
GraphDriver* NextDriver() const {
|
|
|
|
if (!IsSwitchDriver()) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
return mResult.as<SwitchDriver>().mDriver;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Switched() {
|
|
|
|
MOZ_ASSERT(IsSwitchDriver());
|
|
|
|
mResult.as<SwitchDriver>().Switched();
|
|
|
|
}
|
2019-12-19 01:51:05 +03:00
|
|
|
};
|
|
|
|
|
2019-12-19 01:51:57 +03:00
|
|
|
/* 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;
|
2020-11-05 19:43:24 +03:00
|
|
|
/* Called on the graph thread after an AudioCallbackDriver with an input
|
|
|
|
* stream has stopped. */
|
|
|
|
virtual void NotifyInputStopped() = 0;
|
2019-12-19 01:51:57 +03:00
|
|
|
/* 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,
|
2020-11-05 19:44:00 +03:00
|
|
|
TrackRate aRate, uint32_t aChannels,
|
|
|
|
uint32_t aAlreadyBuffered) = 0;
|
2019-12-19 01:51:57 +03:00
|
|
|
/* 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. */
|
2019-12-19 01:52:24 +03:00
|
|
|
virtual IterationResult OneIteration(GraphTime aStateComputedEnd,
|
|
|
|
GraphTime aIterationEnd,
|
2019-12-19 01:51:57 +03:00
|
|
|
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. */
|
2020-11-30 17:16:57 +03:00
|
|
|
virtual bool InDriverIteration(const GraphDriver* aDriver) const = 0;
|
2019-12-19 01:51:57 +03:00
|
|
|
#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
|
2019-12-19 01:53:35 +03:00
|
|
|
* OfflineClockDriver, if the graph is offline, or a SystemClockDriver or an
|
|
|
|
* AudioCallbackDriver, if the graph is real time.
|
2019-12-19 01:51:57 +03:00
|
|
|
* 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:
|
2019-12-19 01:53:35 +03:00
|
|
|
* - 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.
|
2019-12-19 01:51:57 +03:00
|
|
|
* - 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.
|
2019-12-19 01:53:35 +03:00
|
|
|
* - 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.
|
2019-12-19 01:51:57 +03:00
|
|
|
*
|
|
|
|
* Switching from an AudioCallbackDriver to a SystemClockDriver:
|
2019-12-19 01:53:35 +03:00
|
|
|
* - 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.
|
2019-12-19 01:51:57 +03:00
|
|
|
* - When starting, the SystemClockDriver checks if it has been switched from an
|
|
|
|
* AudioCallbackDriver. If yes, it creates a new temporary thread to release
|
2019-12-19 01:53:35 +03:00
|
|
|
* the cubeb_streams. This temporary thread closes the cubeb_stream, and then
|
|
|
|
* dispatches a message to the main thread to be terminated.
|
2019-12-19 01:51:57 +03:00
|
|
|
* - The graph now runs off a normal thread.
|
|
|
|
*
|
|
|
|
* Two drivers cannot run at the same time for the same graph. The thread safety
|
2019-12-19 01:53:35 +03:00
|
|
|
* of the different members of drivers, and their access pattern is documented
|
2019-12-19 01:51:57 +03:00
|
|
|
* next to the members themselves.
|
|
|
|
*/
|
|
|
|
class GraphDriver {
|
|
|
|
public:
|
|
|
|
using IterationResult = GraphInterface::IterationResult;
|
|
|
|
|
|
|
|
GraphDriver(GraphInterface* aGraphInterface, GraphDriver* aPreviousDriver,
|
2019-12-19 01:51:25 +03:00
|
|
|
uint32_t aSampleRate);
|
2014-04-25 18:09:30 +04:00
|
|
|
|
2019-12-19 01:52:33 +03:00
|
|
|
NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING
|
|
|
|
|
2018-05-25 11:58:58 +03:00
|
|
|
/* Start the graph, init the driver, start the thread.
|
|
|
|
* A driver cannot be started twice, it must be shutdown
|
|
|
|
* before being started again. */
|
2014-04-25 20:03:04 +04:00
|
|
|
virtual void Start() = 0;
|
2016-01-22 10:39:42 +03:00
|
|
|
/* Shutdown GraphDriver (synchronously) */
|
2019-11-21 10:33:46 +03:00
|
|
|
MOZ_CAN_RUN_SCRIPT virtual void Shutdown() = 0;
|
2014-08-26 19:01:33 +04:00
|
|
|
/* 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;
|
2019-12-19 01:49:52 +03:00
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
2019-12-19 01:52:33 +03:00
|
|
|
virtual void EnsureNextIteration() = 0;
|
2014-08-26 19:01:33 +04:00
|
|
|
|
2018-05-07 20:35:56 +03:00
|
|
|
/* Implement the switching of the driver and the necessary updates */
|
2019-12-19 01:51:25 +03:00
|
|
|
void SwitchToDriver(GraphDriver* aDriver);
|
2015-12-01 13:47:31 +03:00
|
|
|
|
2019-12-19 01:51:25 +03:00
|
|
|
// Those are simply for accessing the associated pointer. Graph thread only,
|
|
|
|
// or if one is not running, main thread.
|
2015-12-01 13:47:31 +03:00
|
|
|
GraphDriver* PreviousDriver();
|
|
|
|
void SetPreviousDriver(GraphDriver* aPreviousDriver);
|
2015-07-13 20:16:53 +03:00
|
|
|
|
2014-08-26 19:01:33 +04:00
|
|
|
virtual AudioCallbackDriver* AsAudioCallbackDriver() { return nullptr; }
|
2020-11-30 17:16:57 +03:00
|
|
|
virtual const AudioCallbackDriver* AsAudioCallbackDriver() const {
|
|
|
|
return nullptr;
|
|
|
|
}
|
2014-08-26 19:01:33 +04:00
|
|
|
|
2014-10-20 20:06:10 +04:00
|
|
|
virtual OfflineClockDriver* AsOfflineClockDriver() { return nullptr; }
|
2020-11-30 17:16:57 +03:00
|
|
|
virtual const OfflineClockDriver* AsOfflineClockDriver() const {
|
|
|
|
return nullptr;
|
|
|
|
}
|
2014-10-20 20:06:10 +04:00
|
|
|
|
2016-07-27 16:18:17 +03:00
|
|
|
virtual SystemClockDriver* AsSystemClockDriver() { return nullptr; }
|
2020-11-30 17:16:57 +03:00
|
|
|
virtual const SystemClockDriver* AsSystemClockDriver() const {
|
|
|
|
return nullptr;
|
|
|
|
}
|
2016-07-27 16:18:17 +03:00
|
|
|
|
2014-08-26 19:01:33 +04:00
|
|
|
/**
|
2019-12-19 01:49:30 +03:00
|
|
|
* Set the state of the driver so it can start at the right point in time,
|
|
|
|
* after switching from another driver.
|
2014-08-26 19:01:33 +04:00
|
|
|
*/
|
2019-12-19 01:51:25 +03:00
|
|
|
void SetState(GraphTime aIterationStart, GraphTime aIterationEnd,
|
|
|
|
GraphTime aStateComputedTime);
|
2014-04-25 18:09:30 +04:00
|
|
|
|
2019-12-19 01:51:57 +03:00
|
|
|
GraphInterface* Graph() const { return mGraphInterface; }
|
2014-08-31 16:19:48 +04:00
|
|
|
|
2019-03-06 23:12:25 +03:00
|
|
|
#ifdef DEBUG
|
2019-12-19 01:51:45 +03:00
|
|
|
// True if the current thread is currently iterating the MTG.
|
2020-11-30 17:16:57 +03:00
|
|
|
bool InIteration() const;
|
2019-03-06 23:12:25 +03:00
|
|
|
#endif
|
2018-05-22 19:51:42 +03:00
|
|
|
// True if the current thread is the GraphDriver's thread.
|
2020-11-30 17:16:57 +03:00
|
|
|
virtual bool OnThread() const = 0;
|
2018-05-22 19:51:42 +03:00
|
|
|
// GraphDriver's thread has started and the thread is running.
|
2020-11-30 17:16:57 +03:00
|
|
|
virtual bool ThreadRunning() const = 0;
|
2014-09-28 20:07:24 +04:00
|
|
|
|
2019-12-19 01:50:45 +03:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2014-04-25 18:09:30 +04:00
|
|
|
protected:
|
2019-12-19 01:52:24 +03:00
|
|
|
// Time of the start of this graph iteration.
|
2019-12-19 01:49:30 +03:00
|
|
|
GraphTime mIterationStart = 0;
|
2019-12-19 01:52:24 +03:00
|
|
|
// Time of the end of this graph iteration.
|
2019-12-19 01:49:30 +03:00
|
|
|
GraphTime mIterationEnd = 0;
|
|
|
|
// Time until which the graph has processed data.
|
|
|
|
GraphTime mStateComputedTime = 0;
|
2019-12-19 01:51:57 +03:00
|
|
|
// The GraphInterface this driver is currently iterating.
|
|
|
|
const RefPtr<GraphInterface> mGraphInterface;
|
2019-12-19 01:50:12 +03:00
|
|
|
// The sample rate for the graph, and in case of an audio driver, also for the
|
|
|
|
// cubeb stream.
|
|
|
|
const uint32_t mSampleRate;
|
2014-04-25 18:09:30 +04:00
|
|
|
|
2014-08-26 19:01:33 +04:00
|
|
|
// 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).
|
2015-12-01 13:48:06 +03:00
|
|
|
//
|
|
|
|
// 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.
|
2015-10-18 08:24:48 +03:00
|
|
|
RefPtr<GraphDriver> mPreviousDriver;
|
2019-12-19 01:51:25 +03:00
|
|
|
|
2019-12-19 01:49:30 +03:00
|
|
|
virtual ~GraphDriver() = default;
|
2014-04-25 18:09:30 +04:00
|
|
|
};
|
|
|
|
|
2019-10-02 13:23:02 +03:00
|
|
|
class MediaTrackGraphInitThreadRunnable;
|
2014-08-25 17:26:21 +04:00
|
|
|
|
2014-04-25 20:04:53 +04:00
|
|
|
/**
|
|
|
|
* This class is a driver that manages its own thread.
|
|
|
|
*/
|
|
|
|
class ThreadedDriver : public GraphDriver {
|
2019-12-19 01:49:52 +03:00
|
|
|
class IterationWaitHelper {
|
|
|
|
Monitor mMonitor;
|
|
|
|
// 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();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2014-04-25 20:04:53 +04:00
|
|
|
public:
|
2019-12-19 01:52:33 +03:00
|
|
|
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ThreadedDriver, override);
|
|
|
|
|
2019-12-19 01:51:57 +03:00
|
|
|
ThreadedDriver(GraphInterface* aGraphInterface, GraphDriver* aPreviousDriver,
|
2019-12-19 01:51:25 +03:00
|
|
|
uint32_t aSampleRate);
|
2019-12-19 01:49:52 +03:00
|
|
|
|
|
|
|
void EnsureNextIteration() override;
|
2016-01-18 06:50:29 +03:00
|
|
|
void Start() override;
|
2019-11-21 10:33:46 +03:00
|
|
|
MOZ_CAN_RUN_SCRIPT void Shutdown() override;
|
2014-08-26 19:01:33 +04:00
|
|
|
/**
|
|
|
|
* Runs main control loop on the graph thread. Normally a single invocation
|
|
|
|
* of this runs for the entire lifetime of the graph thread.
|
|
|
|
*/
|
2020-03-03 21:16:54 +03:00
|
|
|
virtual void RunThread();
|
2019-10-02 13:23:02 +03:00
|
|
|
friend class MediaTrackGraphInitThreadRunnable;
|
2018-04-30 17:01:56 +03:00
|
|
|
uint32_t IterationDuration() override { return MEDIA_GRAPH_TARGET_PERIOD_MS; }
|
2014-09-28 20:07:24 +04:00
|
|
|
|
2020-11-30 17:16:57 +03:00
|
|
|
nsIThread* Thread() const { return mThread; }
|
2019-03-06 23:12:25 +03:00
|
|
|
|
2020-11-30 17:16:57 +03:00
|
|
|
bool OnThread() const override {
|
2018-04-17 18:11:13 +03:00
|
|
|
return !mThread || mThread->EventTarget()->IsOnCurrentThread();
|
2018-05-22 19:51:42 +03:00
|
|
|
}
|
|
|
|
|
2020-11-30 17:16:57 +03:00
|
|
|
bool ThreadRunning() const override { return mThreadRunning; }
|
2019-12-19 01:49:52 +03:00
|
|
|
|
|
|
|
protected:
|
|
|
|
/* Waits until it's time to process more data. */
|
|
|
|
void WaitForNextIteration();
|
|
|
|
/* Implementation dependent time the ThreadedDriver should wait between
|
|
|
|
* iterations. */
|
2018-10-30 12:48:08 +03:00
|
|
|
virtual TimeDuration WaitInterval() = 0;
|
2015-07-23 02:30:02 +03:00
|
|
|
/* When the graph wakes up to do an iteration, implementations return the
|
2015-07-23 08:15:49 +03:00
|
|
|
* 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;
|
2018-11-19 16:25:37 +03:00
|
|
|
|
2019-12-19 01:52:33 +03:00
|
|
|
virtual ~ThreadedDriver();
|
|
|
|
|
2014-04-25 20:04:53 +04:00
|
|
|
nsCOMPtr<nsIThread> mThread;
|
2018-11-19 16:25:37 +03:00
|
|
|
|
2018-05-22 19:51:42 +03:00
|
|
|
private:
|
|
|
|
// This is true if the thread is running. It is false
|
|
|
|
// before starting the thread and after stopping it.
|
|
|
|
Atomic<bool> mThreadRunning;
|
2019-12-19 01:49:52 +03:00
|
|
|
|
|
|
|
// Any thread.
|
|
|
|
IterationWaitHelper mWaitHelper;
|
2014-04-25 20:04:53 +04:00
|
|
|
};
|
|
|
|
|
2014-04-25 18:09:30 +04:00
|
|
|
/**
|
2019-12-19 01:51:57 +03:00
|
|
|
* A SystemClockDriver drives a GraphInterface using a system clock, and waits
|
2014-04-25 18:09:30 +04:00
|
|
|
* using a monitor, between each iteration.
|
|
|
|
*/
|
2014-04-25 20:04:53 +04:00
|
|
|
class SystemClockDriver : public ThreadedDriver {
|
2014-04-25 18:09:30 +04:00
|
|
|
public:
|
2019-12-19 01:51:57 +03:00
|
|
|
SystemClockDriver(GraphInterface* aGraphInterface,
|
2019-12-19 01:52:33 +03:00
|
|
|
GraphDriver* aPreviousDriver, uint32_t aSampleRate);
|
2014-04-25 18:09:30 +04:00
|
|
|
virtual ~SystemClockDriver();
|
2016-07-28 14:17:46 +03:00
|
|
|
SystemClockDriver* AsSystemClockDriver() override { return this; }
|
2020-11-30 17:16:57 +03:00
|
|
|
const SystemClockDriver* AsSystemClockDriver() const override { return this; }
|
2014-04-25 20:03:04 +04:00
|
|
|
|
2019-12-19 01:49:52 +03:00
|
|
|
protected:
|
|
|
|
/* Return the TimeDuration to wait before the next rendering iteration. */
|
|
|
|
TimeDuration WaitInterval() override;
|
|
|
|
MediaTime GetIntervalForIteration() override;
|
|
|
|
|
2014-04-25 18:09:30 +04:00
|
|
|
private:
|
2015-12-01 13:48:06 +03:00
|
|
|
// Those are only modified (after initialization) on the graph thread. The
|
|
|
|
// graph thread does not run during the initialization.
|
2014-04-25 18:09:30 +04:00
|
|
|
TimeStamp mInitialTimeStamp;
|
2019-03-22 14:41:46 +03:00
|
|
|
TimeStamp mCurrentTimeStamp;
|
2014-04-25 18:09:30 +04:00
|
|
|
TimeStamp mLastTimeStamp;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* An OfflineClockDriver runs the graph as fast as possible, without waiting
|
|
|
|
* between iteration.
|
|
|
|
*/
|
2014-04-25 20:04:53 +04:00
|
|
|
class OfflineClockDriver : public ThreadedDriver {
|
2014-04-25 18:09:30 +04:00
|
|
|
public:
|
2019-12-19 01:51:57 +03:00
|
|
|
OfflineClockDriver(GraphInterface* aGraphInterface, uint32_t aSampleRate,
|
2019-12-19 01:50:12 +03:00
|
|
|
GraphTime aSlice);
|
2014-04-25 18:09:30 +04:00
|
|
|
virtual ~OfflineClockDriver();
|
2016-01-18 06:50:29 +03:00
|
|
|
OfflineClockDriver* AsOfflineClockDriver() override { return this; }
|
2020-11-30 17:16:57 +03:00
|
|
|
const OfflineClockDriver* AsOfflineClockDriver() const override {
|
|
|
|
return this;
|
|
|
|
}
|
2014-04-25 18:09:30 +04:00
|
|
|
|
2020-03-03 21:16:54 +03:00
|
|
|
void RunThread() override;
|
|
|
|
|
2019-12-19 01:49:52 +03:00
|
|
|
protected:
|
2019-12-19 16:48:09 +03:00
|
|
|
TimeDuration WaitInterval() override { return TimeDuration(); }
|
2019-12-19 01:49:52 +03:00
|
|
|
MediaTime GetIntervalForIteration() override;
|
|
|
|
|
2014-04-25 18:09:30 +04:00
|
|
|
private:
|
|
|
|
// Time, in GraphTime, for each iteration
|
|
|
|
GraphTime mSlice;
|
|
|
|
};
|
|
|
|
|
2019-10-02 13:23:02 +03:00
|
|
|
struct TrackAndPromiseForOperation {
|
2019-12-19 01:50:52 +03:00
|
|
|
TrackAndPromiseForOperation(
|
|
|
|
MediaTrack* aTrack, dom::AudioContextOperation aOperation,
|
|
|
|
AbstractThread* aMainThread,
|
|
|
|
MozPromiseHolder<MediaTrackGraph::AudioContextOperationPromise>&&
|
|
|
|
aHolder);
|
|
|
|
TrackAndPromiseForOperation(TrackAndPromiseForOperation&& aOther) noexcept;
|
2019-10-02 13:23:02 +03:00
|
|
|
RefPtr<MediaTrack> mTrack;
|
Bug 1094764 - Implement AudioContext.suspend and friends. r=roc,ehsan
- Relevant spec text:
- http://webaudio.github.io/web-audio-api/#widl-AudioContext-suspend-Promise
- http://webaudio.github.io/web-audio-api/#widl-AudioContext-resume-Promise
- http://webaudio.github.io/web-audio-api/#widl-AudioContext-close-Promise
- http://webaudio.github.io/web-audio-api/#widl-AudioContext-state
- http://webaudio.github.io/web-audio-api/#widl-AudioContext-onstatechange
- In a couple words, the behavior we want:
- Closed context cannot have new nodes created, but can do decodeAudioData,
and create buffers, and such.
- OfflineAudioContexts don't support those methods, transitions happen at
startRendering and at the end of processing. onstatechange is used to make
this observable.
- (regular) AudioContexts support those methods. The promises and
onstatechange should be resolved/called when the operation has actually
completed on the rendering thread. Once a context has been closed, it
cannot transition back to "running". An AudioContext switches to "running"
when the audio callback start running, this allow authors to know how long
the audio stack takes to start running.
- MediaStreams that feed in/go out of a suspended graph should respectively
not buffer at the graph input, and output silence
- suspended context should not be doing much on the CPU, and we should try
to pause audio streams if we can (this behaviour is the main reason we need
this in the first place, for saving battery on mobile, and CPU on all
platforms)
- Now, the implementation:
- AudioNodeStreams are now tagged with a context id, to be able to operate
on all the streams of a given AudioContext on the Graph thread without
having to go and lock everytime to touch the AudioContext. This happens in
the AudioNodeStream ctor. IDs are of course constant for the lifetime of the
node.
- When an AudioContext goes into suspended mode, streams for this
AudioContext are moved out of the mStreams array to a second array,
mSuspendedStreams. Streams in mSuspendedStream are not ordered, and are not
processed.
- The MSG will automatically switch to a SystemClockDriver when it finds
that there are no more AudioNodeStream/Stream with an audio track. This is
how pausing the audio subsystem and saving battery works. Subsequently, when
the MSG finds that there are only streams in mSuspendedStreams, it will go
to sleep (block on a monitor), so we save CPU, but it does not shut itself
down. This is mostly not a new behaviour (this is what the MSG does since
the refactoring), but is important to note.
- Promises are gripped (addref-ed) on the main thread, and then shepherd
down other threads and to the GraphDriver, if needed (sometimes we can
resolve them right away). They move between threads as void* to prevent
calling methods on them, as they are not thread safe. Then, the driver
executes the operation, and when it's done (initializing and closing audio
streams can take some time), we send the promise back to the main thread,
and resolve it, casting back to Promise* after asserting we're back on the
main thread. This way, we can send them back on the main thread once an
operation has complete (suspending an audio stream, starting it again on
resume(), etc.), without having to do bookkeeping between suspend calls and
their result. Promises are not thread safe, so we can't move them around
AddRef-ed.
- The stream destruction logic now takes into account that a stream can be
destroyed while not being in mStreams.
- A graph can now switch GraphDriver twice or more per iteration, for
example if an author goes suspend()/resume()/suspend() in the same script.
- Some operation have to be done on suspended stream, so we now use double
for-loop around mSuspendedStreams and mStreams in some places in
MediaStreamGraph.cpp.
- A tricky part was making sure everything worked at AudioContext
boundaries. TrackUnionStream that have one of their input stream suspended
append null ticks instead.
- The graph ordering algorithm had to be altered to not include suspended
streams.
- There are some edge cases (adding a stream on a suspended graph, calling
suspend/resume when a graph has just been close()d).
2015-02-27 20:22:05 +03:00
|
|
|
dom::AudioContextOperation mOperation;
|
2019-12-19 01:50:52 +03:00
|
|
|
RefPtr<AbstractThread> mMainThread;
|
|
|
|
MozPromiseHolder<MediaTrackGraph::AudioContextOperationPromise> mHolder;
|
Bug 1094764 - Implement AudioContext.suspend and friends. r=roc,ehsan
- Relevant spec text:
- http://webaudio.github.io/web-audio-api/#widl-AudioContext-suspend-Promise
- http://webaudio.github.io/web-audio-api/#widl-AudioContext-resume-Promise
- http://webaudio.github.io/web-audio-api/#widl-AudioContext-close-Promise
- http://webaudio.github.io/web-audio-api/#widl-AudioContext-state
- http://webaudio.github.io/web-audio-api/#widl-AudioContext-onstatechange
- In a couple words, the behavior we want:
- Closed context cannot have new nodes created, but can do decodeAudioData,
and create buffers, and such.
- OfflineAudioContexts don't support those methods, transitions happen at
startRendering and at the end of processing. onstatechange is used to make
this observable.
- (regular) AudioContexts support those methods. The promises and
onstatechange should be resolved/called when the operation has actually
completed on the rendering thread. Once a context has been closed, it
cannot transition back to "running". An AudioContext switches to "running"
when the audio callback start running, this allow authors to know how long
the audio stack takes to start running.
- MediaStreams that feed in/go out of a suspended graph should respectively
not buffer at the graph input, and output silence
- suspended context should not be doing much on the CPU, and we should try
to pause audio streams if we can (this behaviour is the main reason we need
this in the first place, for saving battery on mobile, and CPU on all
platforms)
- Now, the implementation:
- AudioNodeStreams are now tagged with a context id, to be able to operate
on all the streams of a given AudioContext on the Graph thread without
having to go and lock everytime to touch the AudioContext. This happens in
the AudioNodeStream ctor. IDs are of course constant for the lifetime of the
node.
- When an AudioContext goes into suspended mode, streams for this
AudioContext are moved out of the mStreams array to a second array,
mSuspendedStreams. Streams in mSuspendedStream are not ordered, and are not
processed.
- The MSG will automatically switch to a SystemClockDriver when it finds
that there are no more AudioNodeStream/Stream with an audio track. This is
how pausing the audio subsystem and saving battery works. Subsequently, when
the MSG finds that there are only streams in mSuspendedStreams, it will go
to sleep (block on a monitor), so we save CPU, but it does not shut itself
down. This is mostly not a new behaviour (this is what the MSG does since
the refactoring), but is important to note.
- Promises are gripped (addref-ed) on the main thread, and then shepherd
down other threads and to the GraphDriver, if needed (sometimes we can
resolve them right away). They move between threads as void* to prevent
calling methods on them, as they are not thread safe. Then, the driver
executes the operation, and when it's done (initializing and closing audio
streams can take some time), we send the promise back to the main thread,
and resolve it, casting back to Promise* after asserting we're back on the
main thread. This way, we can send them back on the main thread once an
operation has complete (suspending an audio stream, starting it again on
resume(), etc.), without having to do bookkeeping between suspend calls and
their result. Promises are not thread safe, so we can't move them around
AddRef-ed.
- The stream destruction logic now takes into account that a stream can be
destroyed while not being in mStreams.
- A graph can now switch GraphDriver twice or more per iteration, for
example if an author goes suspend()/resume()/suspend() in the same script.
- Some operation have to be done on suspended stream, so we now use double
for-loop around mSuspendedStreams and mStreams in some places in
MediaStreamGraph.cpp.
- A tricky part was making sure everything worked at AudioContext
boundaries. TrackUnionStream that have one of their input stream suspended
append null ticks instead.
- The graph ordering algorithm had to be altered to not include suspended
streams.
- There are some edge cases (adding a stream on a suspended graph, calling
suspend/resume when a graph has just been close()d).
2015-02-27 20:22:05 +03:00
|
|
|
};
|
|
|
|
|
2019-08-28 13:05:22 +03:00
|
|
|
enum class AsyncCubebOperation { INIT, SHUTDOWN };
|
2019-04-16 18:42:38 +03:00
|
|
|
enum class AudioInputType { Unknown, Voice };
|
Bug 1094764 - Implement AudioContext.suspend and friends. r=roc,ehsan
- Relevant spec text:
- http://webaudio.github.io/web-audio-api/#widl-AudioContext-suspend-Promise
- http://webaudio.github.io/web-audio-api/#widl-AudioContext-resume-Promise
- http://webaudio.github.io/web-audio-api/#widl-AudioContext-close-Promise
- http://webaudio.github.io/web-audio-api/#widl-AudioContext-state
- http://webaudio.github.io/web-audio-api/#widl-AudioContext-onstatechange
- In a couple words, the behavior we want:
- Closed context cannot have new nodes created, but can do decodeAudioData,
and create buffers, and such.
- OfflineAudioContexts don't support those methods, transitions happen at
startRendering and at the end of processing. onstatechange is used to make
this observable.
- (regular) AudioContexts support those methods. The promises and
onstatechange should be resolved/called when the operation has actually
completed on the rendering thread. Once a context has been closed, it
cannot transition back to "running". An AudioContext switches to "running"
when the audio callback start running, this allow authors to know how long
the audio stack takes to start running.
- MediaStreams that feed in/go out of a suspended graph should respectively
not buffer at the graph input, and output silence
- suspended context should not be doing much on the CPU, and we should try
to pause audio streams if we can (this behaviour is the main reason we need
this in the first place, for saving battery on mobile, and CPU on all
platforms)
- Now, the implementation:
- AudioNodeStreams are now tagged with a context id, to be able to operate
on all the streams of a given AudioContext on the Graph thread without
having to go and lock everytime to touch the AudioContext. This happens in
the AudioNodeStream ctor. IDs are of course constant for the lifetime of the
node.
- When an AudioContext goes into suspended mode, streams for this
AudioContext are moved out of the mStreams array to a second array,
mSuspendedStreams. Streams in mSuspendedStream are not ordered, and are not
processed.
- The MSG will automatically switch to a SystemClockDriver when it finds
that there are no more AudioNodeStream/Stream with an audio track. This is
how pausing the audio subsystem and saving battery works. Subsequently, when
the MSG finds that there are only streams in mSuspendedStreams, it will go
to sleep (block on a monitor), so we save CPU, but it does not shut itself
down. This is mostly not a new behaviour (this is what the MSG does since
the refactoring), but is important to note.
- Promises are gripped (addref-ed) on the main thread, and then shepherd
down other threads and to the GraphDriver, if needed (sometimes we can
resolve them right away). They move between threads as void* to prevent
calling methods on them, as they are not thread safe. Then, the driver
executes the operation, and when it's done (initializing and closing audio
streams can take some time), we send the promise back to the main thread,
and resolve it, casting back to Promise* after asserting we're back on the
main thread. This way, we can send them back on the main thread once an
operation has complete (suspending an audio stream, starting it again on
resume(), etc.), without having to do bookkeeping between suspend calls and
their result. Promises are not thread safe, so we can't move them around
AddRef-ed.
- The stream destruction logic now takes into account that a stream can be
destroyed while not being in mStreams.
- A graph can now switch GraphDriver twice or more per iteration, for
example if an author goes suspend()/resume()/suspend() in the same script.
- Some operation have to be done on suspended stream, so we now use double
for-loop around mSuspendedStreams and mStreams in some places in
MediaStreamGraph.cpp.
- A tricky part was making sure everything worked at AudioContext
boundaries. TrackUnionStream that have one of their input stream suspended
append null ticks instead.
- The graph ordering algorithm had to be altered to not include suspended
streams.
- There are some edge cases (adding a stream on a suspended graph, calling
suspend/resume when a graph has just been close()d).
2015-02-27 20:22:05 +03:00
|
|
|
|
2014-08-26 19:01:33 +04:00
|
|
|
/**
|
|
|
|
* 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
|
2017-08-29 12:45:44 +03:00
|
|
|
#if defined(XP_WIN)
|
|
|
|
,
|
|
|
|
public audio::DeviceChangeListener
|
|
|
|
#endif
|
2014-08-26 19:01:33 +04:00
|
|
|
{
|
2019-12-19 01:52:33 +03:00
|
|
|
using IterationResult = GraphInterface::IterationResult;
|
2019-12-19 01:53:28 +03:00
|
|
|
enum class FallbackDriverState;
|
2019-12-19 01:52:33 +03:00
|
|
|
class FallbackWrapper;
|
|
|
|
|
2014-08-26 19:01:33 +04:00
|
|
|
public:
|
2019-12-19 01:52:33 +03:00
|
|
|
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AudioCallbackDriver, override);
|
|
|
|
|
2018-04-30 17:01:56 +03:00
|
|
|
/** If aInputChannelCount is zero, then this driver is output-only. */
|
2019-12-19 01:51:57 +03:00
|
|
|
AudioCallbackDriver(GraphInterface* aGraphInterface,
|
2019-12-19 01:51:25 +03:00
|
|
|
GraphDriver* aPreviousDriver, uint32_t aSampleRate,
|
2019-11-19 21:22:46 +03:00
|
|
|
uint32_t aOutputChannelCount, uint32_t aInputChannelCount,
|
2019-12-19 01:50:25 +03:00
|
|
|
CubebUtils::AudioDeviceID aOutputDeviceID,
|
|
|
|
CubebUtils::AudioDeviceID aInputDeviceID,
|
2019-04-16 18:42:38 +03:00
|
|
|
AudioInputType aAudioInputType);
|
2014-08-26 19:01:33 +04:00
|
|
|
|
2016-01-18 06:50:29 +03:00
|
|
|
void Start() override;
|
2019-11-21 10:33:46 +03:00
|
|
|
MOZ_CAN_RUN_SCRIPT void Shutdown() override;
|
2017-08-29 12:45:44 +03:00
|
|
|
#if defined(XP_WIN)
|
|
|
|
void ResetDefaultDevice() override;
|
|
|
|
#endif
|
2014-08-26 19:01:33 +04:00
|
|
|
|
|
|
|
/* Static wrapper function cubeb calls back. */
|
|
|
|
static long DataCallback_s(cubeb_stream* aStream, void* aUser,
|
2016-01-21 19:51:36 +03:00
|
|
|
const void* aInputBuffer, void* aOutputBuffer,
|
2014-08-26 19:01:33 +04:00
|
|
|
long aFrames);
|
2018-04-30 17:01:56 +03:00
|
|
|
static void StateCallback_s(cubeb_stream* aStream, void* aUser,
|
|
|
|
cubeb_state aState);
|
2014-08-26 19:02:31 +04:00
|
|
|
static void DeviceChangedCallback_s(void* aUser);
|
2019-12-19 01:52:33 +03:00
|
|
|
|
2014-08-26 19:01:33 +04:00
|
|
|
/* 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. */
|
2018-04-30 17:01:56 +03:00
|
|
|
long DataCallback(const AudioDataValue* aInputBuffer,
|
|
|
|
AudioDataValue* aOutputBuffer, long aFrames);
|
2014-08-26 19:01:33 +04:00
|
|
|
/* 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. */
|
2016-01-18 06:50:29 +03:00
|
|
|
uint32_t IterationDuration() override;
|
2019-12-19 01:52:33 +03:00
|
|
|
/* 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;
|
2014-08-26 19:01:33 +04:00
|
|
|
|
|
|
|
/* This function gets called when the graph has produced the audio frames for
|
|
|
|
* this iteration. */
|
2016-01-18 06:50:29 +03:00
|
|
|
void MixerCallback(AudioDataValue* aMixedBuffer, AudioSampleFormat aFormat,
|
|
|
|
uint32_t aChannels, uint32_t aFrames,
|
|
|
|
uint32_t aSampleRate) override;
|
2014-08-26 19:01:33 +04:00
|
|
|
|
2016-01-18 06:50:29 +03:00
|
|
|
AudioCallbackDriver* AsAudioCallbackDriver() override { return this; }
|
2020-11-30 17:16:57 +03:00
|
|
|
const AudioCallbackDriver* AsAudioCallbackDriver() const override {
|
|
|
|
return this;
|
|
|
|
}
|
2014-08-26 19:01:33 +04:00
|
|
|
|
2020-04-07 17:33:57 +03:00
|
|
|
uint32_t OutputChannelCount() { return mOutputChannelCount; }
|
2017-08-31 18:14:34 +03:00
|
|
|
|
2018-04-30 17:01:56 +03:00
|
|
|
uint32_t InputChannelCount() { return mInputChannelCount; }
|
|
|
|
|
2019-04-16 18:42:38 +03:00
|
|
|
AudioInputType InputDevicePreference() {
|
|
|
|
if (mInputDevicePreference == CUBEB_DEVICE_PREF_VOICE) {
|
|
|
|
return AudioInputType::Voice;
|
|
|
|
}
|
|
|
|
return AudioInputType::Unknown;
|
|
|
|
}
|
|
|
|
|
2020-11-30 17:16:57 +03:00
|
|
|
std::thread::id ThreadId() const { return mAudioThreadIdInCb.load(); }
|
2020-06-12 16:32:49 +03:00
|
|
|
|
|
|
|
/* 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. */
|
2020-06-12 16:33:04 +03:00
|
|
|
bool CheckThreadIdChanged();
|
2019-03-06 23:12:25 +03:00
|
|
|
|
2020-11-30 17:16:57 +03:00
|
|
|
bool OnThread() const override {
|
2020-06-12 16:32:49 +03:00
|
|
|
return mAudioThreadIdInCb.load() == std::this_thread::get_id();
|
2018-05-07 20:36:14 +03:00
|
|
|
}
|
2014-09-28 20:07:24 +04:00
|
|
|
|
2019-12-19 01:52:33 +03:00
|
|
|
/* Returns true if this audio callback driver has successfully started and not
|
|
|
|
* yet stopped. If the fallback driver is active, this returns false. */
|
2020-11-30 17:16:57 +03:00
|
|
|
bool ThreadRunning() const override {
|
2019-12-19 01:53:28 +03:00
|
|
|
return mAudioStreamState == AudioStreamState::Running;
|
|
|
|
}
|
2018-05-22 19:51:42 +03:00
|
|
|
|
2014-08-26 19:02:31 +04:00
|
|
|
/* Whether the underlying cubeb stream has been started. See comment for
|
|
|
|
* mStarted for details. */
|
2014-08-26 19:01:33 +04:00
|
|
|
bool IsStarted();
|
2014-08-26 19:02:31 +04:00
|
|
|
|
2019-07-12 14:28:03 +03:00
|
|
|
// Returns the output latency for the current audio output stream.
|
|
|
|
TimeDuration AudioOutputLatency();
|
|
|
|
|
2020-11-30 17:16:57 +03:00
|
|
|
/* Returns true if this driver is currently driven by the fallback driver. */
|
|
|
|
bool OnFallback() const;
|
|
|
|
|
2014-08-26 19:01:33 +04:00
|
|
|
private:
|
2014-08-26 19:02:31 +04:00
|
|
|
/**
|
|
|
|
* 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();
|
2014-08-26 19:02:30 +04:00
|
|
|
/* Start the cubeb stream */
|
2017-01-11 22:51:23 +03:00
|
|
|
bool StartStream();
|
2014-08-26 19:02:07 +04:00
|
|
|
friend class AsyncCubebTask;
|
2019-12-19 16:17:32 +03:00
|
|
|
void Init();
|
2017-09-28 05:05:36 +03:00
|
|
|
void Stop();
|
2017-11-28 13:57:02 +03:00
|
|
|
/**
|
|
|
|
* Fall back to a SystemClockDriver using a normal thread. If needed,
|
|
|
|
* the graph will try to re-open an audio stream later. */
|
|
|
|
void FallbackToSystemClockDriver();
|
2019-12-19 01:53:28 +03:00
|
|
|
/* 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. */
|
2019-12-19 01:52:33 +03:00
|
|
|
void FallbackDriverStopped(GraphTime aIterationStart, GraphTime aIterationEnd,
|
2019-12-19 01:53:28 +03:00
|
|
|
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();
|
2017-09-28 05:05:36 +03:00
|
|
|
|
2018-05-25 11:58:45 +03:00
|
|
|
/* This is true when the method is executed on CubebOperation thread pool. */
|
|
|
|
bool OnCubebOperationThread() {
|
|
|
|
return mInitShutdownThread->IsOnCurrentThreadInfallible();
|
|
|
|
}
|
|
|
|
|
2019-10-02 13:23:02 +03:00
|
|
|
/* MediaTrackGraphs are always down/up mixed to output channels. */
|
2020-04-07 17:33:57 +03:00
|
|
|
const uint32_t mOutputChannelCount;
|
2014-08-26 19:01:33 +04:00
|
|
|
/* 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
|
2015-12-01 13:48:06 +03:00
|
|
|
* up to block boundaries during an iteration.
|
|
|
|
* This is only ever accessed on the audio callback thread. */
|
2017-08-28 17:16:20 +03:00
|
|
|
SpillBuffer<AudioDataValue, WEBAUDIO_BLOCK_SIZE * 2> mScratchBuffer;
|
2014-08-26 19:01:33 +04:00
|
|
|
/* Wrapper to ensure we write exactly the number of frames we need in the
|
2015-12-01 13:48:06 +03:00
|
|
|
* audio buffer cubeb passes us. This is only ever accessed on the audio
|
|
|
|
* callback thread. */
|
2017-08-28 17:16:20 +03:00
|
|
|
AudioCallbackBufferWrapper<AudioDataValue> mBuffer;
|
2019-12-19 01:52:33 +03:00
|
|
|
/* cubeb stream for this graph. This is non-null after a successful
|
|
|
|
* cubeb_stream_init(). CubebOperation thread only. */
|
2014-08-26 19:01:33 +04:00
|
|
|
nsAutoRef<cubeb_stream> mAudioStream;
|
2018-04-30 17:01:56 +03:00
|
|
|
/* 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;
|
2019-12-19 01:50:25 +03:00
|
|
|
/**
|
|
|
|
* Devices to use for cubeb input & output, or nullptr for default device.
|
|
|
|
*/
|
|
|
|
const CubebUtils::AudioDeviceID mOutputDeviceID;
|
|
|
|
const CubebUtils::AudioDeviceID mInputDeviceID;
|
2014-08-26 19:01:33 +04:00
|
|
|
/* Approximation of the time between two callbacks. This is used to schedule
|
2015-12-01 13:48:06 +03:00
|
|
|
* video frames. This is in milliseconds. Only even used (after
|
|
|
|
* inizatialization) on the audio callback thread. */
|
2014-08-26 19:01:33 +04:00
|
|
|
uint32_t mIterationDurationMS;
|
|
|
|
/* cubeb_stream_init calls the audio callback to prefill the buffers. The
|
|
|
|
* previous driver has to be kept alive until the audio stream has been
|
|
|
|
* started, because it is responsible to call cubeb_stream_start, so we delay
|
|
|
|
* the cleanup of the previous driver until it has started the audio stream.
|
|
|
|
* Otherwise, there is a race where we kill the previous driver thread
|
|
|
|
* between cubeb_stream_init and cubeb_stream_start,
|
|
|
|
* and callbacks after the prefill never get called.
|
|
|
|
* This is written on the previous driver's thread (if switching) or main
|
|
|
|
* thread (if this driver is the first one).
|
|
|
|
* This is read on previous driver's thread (during callbacks from
|
|
|
|
* cubeb_stream_init) and the audio thread (when switching away from this
|
|
|
|
* driver back to a SystemClockDriver).
|
|
|
|
* */
|
2018-05-07 20:36:45 +03:00
|
|
|
Atomic<bool> mStarted;
|
2014-08-26 19:01:35 +04:00
|
|
|
|
|
|
|
struct AutoInCallback {
|
2014-09-01 07:50:23 +04:00
|
|
|
explicit AutoInCallback(AudioCallbackDriver* aDriver);
|
2014-08-26 19:01:35 +04:00
|
|
|
~AutoInCallback();
|
|
|
|
AudioCallbackDriver* mDriver;
|
|
|
|
};
|
|
|
|
|
2017-09-26 07:28:17 +03:00
|
|
|
/* Shared thread pool with up to one thread for off-main-thread
|
|
|
|
* initialization and shutdown of the audio stream via AsyncCubebTask. */
|
2018-05-25 11:58:45 +03:00
|
|
|
const RefPtr<SharedThreadPool> mInitShutdownThread;
|
2019-04-16 18:42:38 +03:00
|
|
|
cubeb_device_pref mInputDevicePreference;
|
2019-12-19 01:50:00 +03:00
|
|
|
/* The mixer that the graph mixes into during an iteration. Audio thread only.
|
|
|
|
*/
|
|
|
|
AudioMixer mMixer;
|
2020-06-23 19:43:40 +03:00
|
|
|
/* Contains the id of the audio thread, from profiler_get_thread_id. */
|
|
|
|
std::atomic<int> mAudioThreadId;
|
2020-06-12 16:32:49 +03:00
|
|
|
/* 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;
|
2019-12-19 01:53:28 +03:00
|
|
|
/* State of the audio stream, see inline comments. */
|
|
|
|
enum class AudioStreamState {
|
|
|
|
/* There is no AudioStream and no pending AsyncCubebTask to INIT one. */
|
|
|
|
None,
|
|
|
|
/* There is no AudioStream but an AsyncCubebTask to INIT one is pending. */
|
|
|
|
Pending,
|
|
|
|
/* There is a running AudioStream. */
|
|
|
|
Running,
|
|
|
|
/* There is an AudioStream that is draining, and will soon stop. */
|
2020-10-28 03:58:15 +03:00
|
|
|
Stopping
|
2019-12-19 01:53:28 +03:00
|
|
|
};
|
|
|
|
Atomic<AudioStreamState> mAudioStreamState;
|
|
|
|
/* 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,
|
|
|
|
};
|
2020-06-12 04:02:18 +03:00
|
|
|
Atomic<FallbackDriverState> mFallbackDriverState{FallbackDriverState::None};
|
2019-12-19 01:52:33 +03:00
|
|
|
/* SystemClockDriver used as fallback if this AudioCallbackDriver fails to
|
|
|
|
* init or start. */
|
|
|
|
DataMutex<RefPtr<FallbackWrapper>> mFallback;
|
2019-12-19 01:53:46 +03:00
|
|
|
/* 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;
|
2019-10-11 18:55:02 +03:00
|
|
|
#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. */
|
2019-10-11 18:45:18 +03:00
|
|
|
Atomic<bool> mNeedsPanning;
|
2019-10-11 18:55:02 +03:00
|
|
|
#endif
|
2019-12-19 01:52:33 +03:00
|
|
|
|
2020-04-07 17:33:57 +03:00
|
|
|
WavDumper mInputStreamFile;
|
|
|
|
WavDumper mOutputStreamFile;
|
|
|
|
|
2019-12-19 01:52:33 +03:00
|
|
|
virtual ~AudioCallbackDriver();
|
2020-06-12 16:33:04 +03:00
|
|
|
const bool mSandboxed = false;
|
2014-08-26 19:02:30 +04:00
|
|
|
};
|
|
|
|
|
2016-04-26 03:23:21 +03:00
|
|
|
class AsyncCubebTask : public Runnable {
|
2014-08-26 19:02:30 +04:00
|
|
|
public:
|
2014-09-03 17:52:43 +04:00
|
|
|
AsyncCubebTask(AudioCallbackDriver* aDriver, AsyncCubebOperation aOperation);
|
2014-08-26 19:02:30 +04:00
|
|
|
|
2016-01-22 10:39:42 +03:00
|
|
|
nsresult Dispatch(uint32_t aFlags = NS_DISPATCH_NORMAL) {
|
2018-05-25 11:58:45 +03:00
|
|
|
return mDriver->mInitShutdownThread->Dispatch(this, aFlags);
|
2014-08-26 19:02:30 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
protected:
|
2014-09-03 17:52:43 +04:00
|
|
|
virtual ~AsyncCubebTask();
|
2014-08-26 19:02:30 +04:00
|
|
|
|
|
|
|
private:
|
2018-02-06 09:50:00 +03:00
|
|
|
NS_IMETHOD Run() final;
|
2017-09-26 07:28:17 +03:00
|
|
|
|
2015-10-18 08:24:48 +03:00
|
|
|
RefPtr<AudioCallbackDriver> mDriver;
|
2014-08-26 19:02:30 +04:00
|
|
|
AsyncCubebOperation mOperation;
|
2019-12-19 01:51:57 +03:00
|
|
|
RefPtr<GraphInterface> mShutdownGrip;
|
2014-08-26 19:01:33 +04:00
|
|
|
};
|
|
|
|
|
2015-07-13 18:25:42 +03:00
|
|
|
} // namespace mozilla
|
2014-04-25 18:09:30 +04:00
|
|
|
|
|
|
|
#endif // GRAPHDRIVER_H_
|