From 5f8de423f190bbb79a62f804151bc24824fa32d8 Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Fri, 2 Feb 2018 04:16:08 -0500 Subject: Add m-esr52 at 52.6.0 --- dom/media/MediaStreamGraph.h | 1407 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1407 insertions(+) create mode 100644 dom/media/MediaStreamGraph.h (limited to 'dom/media/MediaStreamGraph.h') diff --git a/dom/media/MediaStreamGraph.h b/dom/media/MediaStreamGraph.h new file mode 100644 index 000000000..a6d77880a --- /dev/null +++ b/dom/media/MediaStreamGraph.h @@ -0,0 +1,1407 @@ +/* -*- 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 MOZILLA_MEDIASTREAMGRAPH_H_ +#define MOZILLA_MEDIASTREAMGRAPH_H_ + +#include "mozilla/LinkedList.h" +#include "mozilla/Mutex.h" +#include "mozilla/TaskQueue.h" + +#include "mozilla/dom/AudioChannelBinding.h" + +#include "AudioStream.h" +#include "nsTArray.h" +#include "nsIRunnable.h" +#include "VideoSegment.h" +#include "StreamTracks.h" +#include "MainThreadUtils.h" +#include "StreamTracks.h" +#include "nsAutoPtr.h" +#include "nsAutoRef.h" +#include + +class nsIRunnable; + +template <> +class nsAutoRefTraits : public nsPointerRefTraits +{ + public: + static void Release(SpeexResamplerState* aState) { speex_resampler_destroy(aState); } +}; + +namespace mozilla { + +extern LazyLogModule gMediaStreamGraphLog; + +namespace dom { + enum class AudioContextOperation; +} + +namespace media { + template class Pledge; +} + +/* + * MediaStreamGraph is a framework for synchronized audio/video processing + * and playback. It is designed to be used by other browser components such as + * HTML media elements, media capture APIs, real-time media streaming APIs, + * multitrack media APIs, and advanced audio APIs. + * + * The MediaStreamGraph uses a dedicated thread to process media --- the media + * graph thread. This ensures that we can process media through the graph + * without blocking on main-thread activity. The media graph is only modified + * on the media graph thread, to ensure graph changes can be processed without + * interfering with media processing. All interaction with the media graph + * thread is done with message passing. + * + * APIs that modify the graph or its properties are described as "control APIs". + * These APIs are asynchronous; they queue graph changes internally and + * those changes are processed all-at-once by the MediaStreamGraph. The + * MediaStreamGraph monitors the main thread event loop via nsIAppShell::RunInStableState + * to ensure that graph changes from a single event loop task are always + * processed all together. Control APIs should only be used on the main thread, + * currently; we may be able to relax that later. + * + * To allow precise synchronization of times in the control API, the + * MediaStreamGraph maintains a "media timeline". Control APIs that take or + * return times use that timeline. Those times never advance during + * an event loop task. This time is returned by MediaStreamGraph::GetCurrentTime(). + * + * Media decoding, audio processing and media playback use thread-safe APIs to + * the media graph to ensure they can continue while the main thread is blocked. + * + * When the graph is changed, we may need to throw out buffered data and + * reprocess it. This is triggered automatically by the MediaStreamGraph. + */ + +class AudioNodeEngine; +class AudioNodeExternalInputStream; +class AudioNodeStream; +class MediaInputPort; +class MediaStream; +class MediaStreamGraph; +class MediaStreamGraphImpl; +class ProcessedMediaStream; +class SourceMediaStream; + +class AudioDataListenerInterface { +protected: + // Protected destructor, to discourage deletion outside of Release(): + virtual ~AudioDataListenerInterface() {} + +public: + /* These are for cubeb audio input & output streams: */ + /** + * Output data to speakers, for use as the "far-end" data for echo + * cancellation. This is not guaranteed to be in any particular size + * chunks. + */ + virtual void NotifyOutputData(MediaStreamGraph* aGraph, + AudioDataValue* aBuffer, size_t aFrames, + TrackRate aRate, uint32_t aChannels) = 0; + /** + * Input data from a microphone (or other audio source. This is not + * guaranteed to be in any particular size chunks. + */ + virtual void NotifyInputData(MediaStreamGraph* aGraph, + const AudioDataValue* aBuffer, size_t aFrames, + TrackRate aRate, uint32_t aChannels) = 0; + + /** + * Called when the underlying audio device has changed. + */ + virtual void DeviceChanged() = 0; +}; + +class AudioDataListener : public AudioDataListenerInterface { +protected: + // Protected destructor, to discourage deletion outside of Release(): + virtual ~AudioDataListener() {} + +public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AudioDataListener) +}; + +/** + * This is a base class for main-thread listener callbacks. + * This callback is invoked on the main thread when the main-thread-visible + * state of a stream has changed. + * + * These methods are called with the media graph monitor held, so + * reentry into general media graph methods is not possible. + * You should do something non-blocking and non-reentrant (e.g. dispatch an + * event) and return. DispatchFromMainThreadAfterNextStreamStateUpdate + * would be a good choice. + * The listener is allowed to synchronously remove itself from the stream, but + * not add or remove any other listeners. + */ +class MainThreadMediaStreamListener { +public: + virtual void NotifyMainThreadStreamFinished() = 0; +}; + +/** + * Helper struct used to keep track of memory usage by AudioNodes. + */ +struct AudioNodeSizes +{ + AudioNodeSizes() : mStream(0), mEngine(0), mNodeType() {} + size_t mStream; + size_t mEngine; + const char* mNodeType; +}; + +class AudioNodeEngine; +class AudioNodeExternalInputStream; +class AudioNodeStream; +class AudioSegment; +class DirectMediaStreamListener; +class DirectMediaStreamTrackListener; +class MediaInputPort; +class MediaStreamGraphImpl; +class MediaStreamListener; +class MediaStreamTrackListener; +class MediaStreamVideoSink; +class ProcessedMediaStream; +class SourceMediaStream; +class TrackUnionStream; + +enum MediaStreamGraphEvent : uint32_t; +enum TrackEventCommand : uint32_t; + +/** + * Helper struct for binding a track listener to a specific TrackID. + */ +template +struct TrackBound +{ + RefPtr mListener; + TrackID mTrackID; +}; + +/** + * Describes how a track should be disabled. + * + * ENABLED Not disabled. + * SILENCE_BLACK Audio data is turned into silence, video frames are made black. + * SILENCE_FREEZE Audio data is turned into silence, video freezes at last frame. + */ +enum class DisabledTrackMode +{ + ENABLED, SILENCE_BLACK, SILENCE_FREEZE +}; +struct DisabledTrack { + DisabledTrack(TrackID aTrackID, DisabledTrackMode aMode) + : mTrackID(aTrackID), mMode(aMode) {} + TrackID mTrackID; + DisabledTrackMode mMode; +}; + +/** + * A stream of synchronized audio and video data. All (not blocked) streams + * progress at the same rate --- "real time". Streams cannot seek. The only + * operation readers can perform on a stream is to read the next data. + * + * Consumers of a stream can be reading from it at different offsets, but that + * should only happen due to the order in which consumers are being run. + * Those offsets must not diverge in the long term, otherwise we would require + * unbounded buffering. + * + * Streams can be in a "blocked" state. While blocked, a stream does not + * produce data. A stream can be explicitly blocked via the control API, + * or implicitly blocked by whatever's generating it (e.g. an underrun in the + * source resource), or implicitly blocked because something consuming it + * blocks, or implicitly because it has finished. + * + * A stream can be in a "finished" state. "Finished" streams are permanently + * blocked. + * + * Transitions into and out of the "blocked" and "finished" states are managed + * by the MediaStreamGraph on the media graph thread. + * + * We buffer media data ahead of the consumers' reading offsets. It is possible + * to have buffered data but still be blocked. + * + * Any stream can have its audio and video playing when requested. The media + * stream graph plays audio by constructing audio output streams as necessary. + * Video is played by setting video frames into an MediaStreamVideoSink at the right + * time. To ensure video plays in sync with audio, make sure that the same + * stream is playing both the audio and video. + * + * The data in a stream is managed by StreamTracks. It consists of a set of + * tracks of various types that can start and end over time. + * + * Streams are explicitly managed. The client creates them via + * MediaStreamGraph::CreateInput/ProcessedMediaStream, and releases them by calling + * Destroy() when no longer needed (actual destruction will be deferred). + * The actual object is owned by the MediaStreamGraph. The basic idea is that + * main thread objects will keep Streams alive as long as necessary (using the + * cycle collector to clean up whenever needed). + * + * We make them refcounted only so that stream-related messages with MediaStream* + * pointers can be sent to the main thread safely. + * + * The lifetimes of MediaStreams are controlled from the main thread. + * For MediaStreams exposed to the DOM, the lifetime is controlled by the DOM + * wrapper; the DOM wrappers own their associated MediaStreams. When a DOM + * wrapper is destroyed, it sends a Destroy message for the associated + * MediaStream and clears its reference (the last main-thread reference to + * the object). When the Destroy message is processed on the graph manager + * thread we immediately release the affected objects (disentangling them + * from other objects as necessary). + * + * This could cause problems for media processing if a MediaStream is + * destroyed while a downstream MediaStream is still using it. Therefore + * the DOM wrappers must keep upstream MediaStreams alive as long as they + * could be being used in the media graph. + * + * At any time, however, a set of MediaStream wrappers could be + * collected via cycle collection. Destroy messages will be sent + * for those objects in arbitrary order and the MediaStreamGraph has to be able + * to handle this. + */ + +// GetCurrentTime is defined in winbase.h as zero argument macro forwarding to +// GetTickCount() and conflicts with MediaStream::GetCurrentTime. +#ifdef GetCurrentTime +#undef GetCurrentTime +#endif + +class MediaStream : public mozilla::LinkedListElement +{ +public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaStream) + + MediaStream(); + +protected: + // Protected destructor, to discourage deletion outside of Release(): + virtual ~MediaStream(); + +public: + /** + * Returns the graph that owns this stream. + */ + MediaStreamGraphImpl* GraphImpl(); + MediaStreamGraph* Graph(); + /** + * Sets the graph that owns this stream. Should only be called once. + */ + void SetGraphImpl(MediaStreamGraphImpl* aGraph); + void SetGraphImpl(MediaStreamGraph* aGraph); + + /** + * Returns sample rate of the graph. + */ + TrackRate GraphRate() { return mTracks.GraphRate(); } + + // Control API. + // Since a stream can be played multiple ways, we need to combine independent + // volume settings. The aKey parameter is used to keep volume settings + // separate. Since the stream is always playing the same contents, only + // a single audio output stream is used; the volumes are combined. + // Currently only the first enabled audio track is played. + // XXX change this so all enabled audio tracks are mixed and played. + virtual void AddAudioOutput(void* aKey); + virtual void SetAudioOutputVolume(void* aKey, float aVolume); + virtual void RemoveAudioOutput(void* aKey); + // Since a stream can be played multiple ways, we need to be able to + // play to multiple MediaStreamVideoSinks. + // Only the first enabled video track is played. + virtual void AddVideoOutput(MediaStreamVideoSink* aSink, + TrackID aID = TRACK_ANY); + virtual void RemoveVideoOutput(MediaStreamVideoSink* aSink, + TrackID aID = TRACK_ANY); + // Explicitly suspend. Useful for example if a media element is pausing + // and we need to stop its stream emitting its buffered data. As soon as the + // Suspend message reaches the graph, the stream stops processing. It + // ignores its inputs and produces silence/no video until Resumed. Its + // current time does not advance. + virtual void Suspend(); + virtual void Resume(); + // Events will be dispatched by calling methods of aListener. + virtual void AddListener(MediaStreamListener* aListener); + virtual void RemoveListener(MediaStreamListener* aListener); + virtual void AddTrackListener(MediaStreamTrackListener* aListener, + TrackID aTrackID); + virtual void RemoveTrackListener(MediaStreamTrackListener* aListener, + TrackID aTrackID); + + /** + * Adds aListener to the source stream of track aTrackID in this stream. + * When the MediaStreamGraph processes the added listener, it will traverse + * the graph and add it to the track's source stream (remapping the TrackID + * along the way). + * Note that the listener will be notified on the MediaStreamGraph thread + * with whether the installation of it at the source was successful or not. + */ + virtual void AddDirectTrackListener(DirectMediaStreamTrackListener* aListener, + TrackID aTrackID); + + /** + * Removes aListener from the source stream of track aTrackID in this stream. + * Note that the listener has already been removed if the link between the + * source of track aTrackID and this stream has been broken (and made track + * aTrackID end). The caller doesn't have to care about this, removing when + * the source cannot be found, or when the listener had already been removed + * does nothing. + */ + virtual void RemoveDirectTrackListener(DirectMediaStreamTrackListener* aListener, + TrackID aTrackID); + + // A disabled track has video replaced by black, and audio replaced by + // silence. + void SetTrackEnabled(TrackID aTrackID, DisabledTrackMode aMode); + + // Finish event will be notified by calling methods of aListener. It is the + // responsibility of the caller to remove aListener before it is destroyed. + void AddMainThreadListener(MainThreadMediaStreamListener* aListener); + // It's safe to call this even if aListener is not currently a listener; + // the call will be ignored. + void RemoveMainThreadListener(MainThreadMediaStreamListener* aListener) + { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aListener); + mMainThreadListeners.RemoveElement(aListener); + } + + /** + * Ensure a runnable will run on the main thread after running all pending + * updates that were sent from the graph thread or will be sent before the + * graph thread receives the next graph update. + * + * If the graph has been shut down or destroyed, then the runnable will be + * dispatched to the event queue immediately. If the graph is non-realtime + * and has not started, then the runnable will be run + * synchronously/immediately. (There are no pending updates in these + * situations.) + * + * Main thread only. + */ + void RunAfterPendingUpdates(already_AddRefed aRunnable); + + // Signal that the client is done with this MediaStream. It will be deleted + // later. Do not mix usage of Destroy() with RegisterUser()/UnregisterUser(). + // That will cause the MediaStream to be destroyed twice, which will cause + // some assertions to fail. + virtual void Destroy(); + // Signal that a client is using this MediaStream. Useful to not have to + // explicitly manage ownership (responsibility to Destroy()) when there are + // multiple clients using a MediaStream. + void RegisterUser(); + // Signal that a client no longer needs this MediaStream. When the number of + // clients using this MediaStream reaches 0, it will be destroyed. + void UnregisterUser(); + + // Returns the main-thread's view of how much data has been processed by + // this stream. + StreamTime GetCurrentTime() + { + NS_ASSERTION(NS_IsMainThread(), "Call only on main thread"); + return mMainThreadCurrentTime; + } + // Return the main thread's view of whether this stream has finished. + bool IsFinished() + { + NS_ASSERTION(NS_IsMainThread(), "Call only on main thread"); + return mMainThreadFinished; + } + + bool IsDestroyed() + { + NS_ASSERTION(NS_IsMainThread(), "Call only on main thread"); + return mMainThreadDestroyed; + } + + friend class MediaStreamGraphImpl; + friend class MediaInputPort; + friend class AudioNodeExternalInputStream; + + virtual SourceMediaStream* AsSourceStream() { return nullptr; } + virtual ProcessedMediaStream* AsProcessedStream() { return nullptr; } + virtual AudioNodeStream* AsAudioNodeStream() { return nullptr; } + virtual TrackUnionStream* AsTrackUnionStream() { return nullptr; } + + // These Impl methods perform the core functionality of the control methods + // above, on the media graph thread. + /** + * Stop all stream activity and disconnect it from all inputs and outputs. + * This must be idempotent. + */ + virtual void DestroyImpl(); + StreamTime GetTracksEnd() { return mTracks.GetEnd(); } +#ifdef DEBUG + void DumpTrackInfo() { return mTracks.DumpTrackInfo(); } +#endif + void SetAudioOutputVolumeImpl(void* aKey, float aVolume); + void AddAudioOutputImpl(void* aKey); + // Returns true if this stream has an audio output. + bool HasAudioOutput() + { + return !mAudioOutputs.IsEmpty(); + } + void RemoveAudioOutputImpl(void* aKey); + void AddVideoOutputImpl(already_AddRefed aSink, + TrackID aID); + void RemoveVideoOutputImpl(MediaStreamVideoSink* aSink, TrackID aID); + void AddListenerImpl(already_AddRefed aListener); + void RemoveListenerImpl(MediaStreamListener* aListener); + void RemoveAllListenersImpl(); + virtual void AddTrackListenerImpl(already_AddRefed aListener, + TrackID aTrackID); + virtual void RemoveTrackListenerImpl(MediaStreamTrackListener* aListener, + TrackID aTrackID); + virtual void AddDirectTrackListenerImpl(already_AddRefed aListener, + TrackID aTrackID); + virtual void RemoveDirectTrackListenerImpl(DirectMediaStreamTrackListener* aListener, + TrackID aTrackID); + virtual void SetTrackEnabledImpl(TrackID aTrackID, DisabledTrackMode aMode); + DisabledTrackMode GetDisabledTrackMode(TrackID aTrackID); + + void AddConsumer(MediaInputPort* aPort) + { + mConsumers.AppendElement(aPort); + } + void RemoveConsumer(MediaInputPort* aPort) + { + mConsumers.RemoveElement(aPort); + } + uint32_t ConsumerCount() + { + return mConsumers.Length(); + } + StreamTracks& GetStreamTracks() { return mTracks; } + GraphTime GetStreamTracksStartTime() { return mTracksStartTime; } + + double StreamTimeToSeconds(StreamTime aTime) + { + NS_ASSERTION(0 <= aTime && aTime <= STREAM_TIME_MAX, "Bad time"); + return static_cast(aTime)/mTracks.GraphRate(); + } + int64_t StreamTimeToMicroseconds(StreamTime aTime) + { + NS_ASSERTION(0 <= aTime && aTime <= STREAM_TIME_MAX, "Bad time"); + return (aTime*1000000)/mTracks.GraphRate(); + } + StreamTime SecondsToNearestStreamTime(double aSeconds) + { + NS_ASSERTION(0 <= aSeconds && aSeconds <= TRACK_TICKS_MAX/TRACK_RATE_MAX, + "Bad seconds"); + return mTracks.GraphRate() * aSeconds + 0.5; + } + StreamTime MicrosecondsToStreamTimeRoundDown(int64_t aMicroseconds) { + return (aMicroseconds*mTracks.GraphRate())/1000000; + } + + TrackTicks TimeToTicksRoundUp(TrackRate aRate, StreamTime aTime) + { + return RateConvertTicksRoundUp(aRate, mTracks.GraphRate(), aTime); + } + StreamTime TicksToTimeRoundDown(TrackRate aRate, TrackTicks aTicks) + { + return RateConvertTicksRoundDown(mTracks.GraphRate(), aRate, aTicks); + } + /** + * Convert graph time to stream time. aTime must be <= mStateComputedTime + * to ensure we know exactly how much time this stream will be blocked during + * the interval. + */ + StreamTime GraphTimeToStreamTimeWithBlocking(GraphTime aTime); + /** + * Convert graph time to stream time. This assumes there is no blocking time + * to take account of, which is always true except between a stream + * having its blocking time calculated in UpdateGraph and its blocking time + * taken account of in UpdateCurrentTimeForStreams. + */ + StreamTime GraphTimeToStreamTime(GraphTime aTime); + /** + * Convert stream time to graph time. This assumes there is no blocking time + * to take account of, which is always true except between a stream + * having its blocking time calculated in UpdateGraph and its blocking time + * taken account of in UpdateCurrentTimeForStreams. + */ + GraphTime StreamTimeToGraphTime(StreamTime aTime); + + bool IsFinishedOnGraphThread() { return mFinished; } + void FinishOnGraphThread(); + + bool HasCurrentData() { return mHasCurrentData; } + + /** + * Find track by track id. + */ + StreamTracks::Track* FindTrack(TrackID aID); + + StreamTracks::Track* EnsureTrack(TrackID aTrack); + + virtual void ApplyTrackDisabling(TrackID aTrackID, MediaSegment* aSegment, MediaSegment* aRawSegment = nullptr); + + // Return true if the main thread needs to observe updates from this stream. + virtual bool MainThreadNeedsUpdates() const + { + return true; + } + + virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const; + virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const; + + void SetAudioChannelType(dom::AudioChannel aType) { mAudioChannelType = aType; } + dom::AudioChannel AudioChannelType() const { return mAudioChannelType; } + + bool IsSuspended() { return mSuspendedCount > 0; } + void IncrementSuspendCount() { ++mSuspendedCount; } + void DecrementSuspendCount() + { + NS_ASSERTION(mSuspendedCount > 0, "Suspend count underrun"); + --mSuspendedCount; + } + +protected: + // |AdvanceTimeVaryingValuesToCurrentTime| will be override in SourceMediaStream. + virtual void AdvanceTimeVaryingValuesToCurrentTime(GraphTime aCurrentTime, + GraphTime aBlockedTime) + { + mTracksStartTime += aBlockedTime; + mTracks.ForgetUpTo(aCurrentTime - mTracksStartTime); + } + + void NotifyMainThreadListeners() + { + NS_ASSERTION(NS_IsMainThread(), "Call only on main thread"); + + for (int32_t i = mMainThreadListeners.Length() - 1; i >= 0; --i) { + mMainThreadListeners[i]->NotifyMainThreadStreamFinished(); + } + mMainThreadListeners.Clear(); + } + + bool ShouldNotifyStreamFinished() + { + NS_ASSERTION(NS_IsMainThread(), "Call only on main thread"); + if (!mMainThreadFinished || mFinishedNotificationSent) { + return false; + } + + mFinishedNotificationSent = true; + return true; + } + + // This state is all initialized on the main thread but + // otherwise modified only on the media graph thread. + + // Buffered data. The start of the buffer corresponds to mTracksStartTime. + // Conceptually the buffer contains everything this stream has ever played, + // but we forget some prefix of the buffered data to bound the space usage. + StreamTracks mTracks; + // The time when the buffered data could be considered to have started playing. + // This increases over time to account for time the stream was blocked before + // mCurrentTime. + GraphTime mTracksStartTime; + + // Client-set volume of this stream + struct AudioOutput { + explicit AudioOutput(void* aKey) : mKey(aKey), mVolume(1.0f) {} + void* mKey; + float mVolume; + }; + nsTArray mAudioOutputs; + nsTArray> mVideoOutputs; + // We record the last played video frame to avoid playing the frame again + // with a different frame id. + VideoFrame mLastPlayedVideoFrame; + nsTArray > mListeners; + nsTArray> mTrackListeners; + nsTArray mMainThreadListeners; + // List of disabled TrackIDs and their associated disabled mode. + // They can either by disabled by frames being replaced by black, or by + // retaining the previous frame. + nsTArray mDisabledTracks; + + // GraphTime at which this stream starts blocking. + // This is only valid up to mStateComputedTime. The stream is considered to + // have not been blocked before mCurrentTime (its mTracksStartTime is increased + // as necessary to account for that time instead). + GraphTime mStartBlocking; + + // MediaInputPorts to which this is connected + nsTArray mConsumers; + + // Where audio output is going. There is one AudioOutputStream per + // audio track. + struct AudioOutputStream + { + // When we started audio playback for this track. + // Add mStream->GetPosition() to find the current audio playback position. + GraphTime mAudioPlaybackStartTime; + // Amount of time that we've wanted to play silence because of the stream + // blocking. + MediaTime mBlockedAudioTime; + // Last tick written to the audio output. + StreamTime mLastTickWritten; + TrackID mTrackID; + }; + nsTArray mAudioOutputStreams; + + /** + * Number of outstanding suspend operations on this stream. Stream is + * suspended when this is > 0. + */ + int32_t mSuspendedCount; + + /** + * When true, this means the stream will be finished once all + * buffered data has been consumed. + */ + bool mFinished; + /** + * When true, mFinished is true and we've played all the data in this stream + * and fired NotifyFinished notifications. + */ + bool mNotifiedFinished; + /** + * When true, the last NotifyBlockingChanged delivered to the listeners + * indicated that the stream is blocked. + */ + bool mNotifiedBlocked; + /** + * True if some data can be present by this stream if/when it's unblocked. + * Set by the stream itself on the MediaStreamGraph thread. Only changes + * from false to true once a stream has data, since we won't + * unblock it until there's more data. + */ + bool mHasCurrentData; + /** + * True if mHasCurrentData is true and we've notified listeners. + */ + bool mNotifiedHasCurrentData; + + // Main-thread views of state + StreamTime mMainThreadCurrentTime; + bool mMainThreadFinished; + bool mFinishedNotificationSent; + bool mMainThreadDestroyed; + int mNrOfMainThreadUsers; + + // Our media stream graph. null if destroyed on the graph thread. + MediaStreamGraphImpl* mGraph; + + dom::AudioChannel mAudioChannelType; +}; + +/** + * This is a stream into which a decoder can write audio and video. + * + * Audio and video can be written on any thread, but you probably want to + * always write from the same thread to avoid unexpected interleavings. + */ +class SourceMediaStream : public MediaStream +{ +public: + explicit SourceMediaStream(); + + SourceMediaStream* AsSourceStream() override { return this; } + + // Media graph thread only + + // Users of audio inputs go through the stream so it can track when the + // last stream referencing an input goes away, so it can close the cubeb + // input. Also note: callable on any thread (though it bounces through + // MainThread to set the command if needed). + nsresult OpenAudioInput(int aID, + AudioDataListener *aListener); + // Note: also implied when Destroy() happens + void CloseAudioInput(); + + void DestroyImpl() override; + + // Call these on any thread. + /** + * Enable or disable pulling. When pulling is enabled, NotifyPull + * gets called on MediaStreamListeners for this stream during the + * MediaStreamGraph control loop. Pulling is initially disabled. + * Due to unavoidable race conditions, after a call to SetPullEnabled(false) + * it is still possible for a NotifyPull to occur. + */ + void SetPullEnabled(bool aEnabled); + + /** + * These add/remove DirectListeners, which allow bypassing the graph and any + * synchronization delays for e.g. PeerConnection, which wants the data ASAP + * and lets the far-end handle sync and playout timing. + */ + void NotifyListenersEventImpl(MediaStreamGraphEvent aEvent); + void NotifyListenersEvent(MediaStreamGraphEvent aEvent); + void AddDirectListener(DirectMediaStreamListener* aListener); + void RemoveDirectListener(DirectMediaStreamListener* aListener); + + enum { + ADDTRACK_QUEUED = 0x01 // Queue track add until FinishAddTracks() + }; + /** + * Add a new track to the stream starting at the given base time (which + * must be greater than or equal to the last time passed to + * AdvanceKnownTracksTime). Takes ownership of aSegment. aSegment should + * contain data starting after aStart. + */ + void AddTrack(TrackID aID, StreamTime aStart, MediaSegment* aSegment, + uint32_t aFlags = 0) + { + AddTrackInternal(aID, GraphRate(), aStart, aSegment, aFlags); + } + + /** + * Like AddTrack, but resamples audio from aRate to the graph rate. + */ + void AddAudioTrack(TrackID aID, TrackRate aRate, StreamTime aStart, + AudioSegment* aSegment, uint32_t aFlags = 0); + + /** + * Call after a series of AddTrack or AddAudioTrack calls to implement + * any pending track adds. + */ + void FinishAddTracks(); + + /** + * Append media data to a track. Ownership of aSegment remains with the caller, + * but aSegment is emptied. + * Returns false if the data was not appended because no such track exists + * or the stream was already finished. + */ + bool AppendToTrack(TrackID aID, MediaSegment* aSegment, MediaSegment *aRawSegment = nullptr); + /** + * Get the stream time of the end of the data that has been appended so far. + * Can be called from any thread but won't be useful if it can race with + * an AppendToTrack call, so should probably just be called from the thread + * that also calls AppendToTrack. + */ + StreamTime GetEndOfAppendedData(TrackID aID); + /** + * Indicate that a track has ended. Do not do any more API calls + * affecting this track. + * Ignored if the track does not exist. + */ + void EndTrack(TrackID aID); + /** + * Indicate that no tracks will be added starting before time aKnownTime. + * aKnownTime must be >= its value at the last call to AdvanceKnownTracksTime. + */ + void AdvanceKnownTracksTime(StreamTime aKnownTime); + /** + * Indicate that this stream should enter the "finished" state. All tracks + * must have been ended via EndTrack. The finish time of the stream is + * when all tracks have ended. + */ + void FinishWithLockHeld(); + void Finish() + { + MutexAutoLock lock(mMutex); + FinishWithLockHeld(); + } + + // Overriding allows us to hold the mMutex lock while changing the track enable status + void SetTrackEnabledImpl(TrackID aTrackID, DisabledTrackMode aMode) override; + + // Overriding allows us to ensure mMutex is locked while changing the track enable status + void + ApplyTrackDisabling(TrackID aTrackID, MediaSegment* aSegment, + MediaSegment* aRawSegment = nullptr) override { + mMutex.AssertCurrentThreadOwns(); + MediaStream::ApplyTrackDisabling(aTrackID, aSegment, aRawSegment); + } + + /** + * End all tracks and Finish() this stream. Used to voluntarily revoke access + * to a LocalMediaStream. + */ + void EndAllTrackAndFinish(); + + void RegisterForAudioMixing(); + + /** + * Returns true if this SourceMediaStream contains at least one audio track + * that is in pending state. + * This is thread safe, and takes the SourceMediaStream mutex. + */ + bool HasPendingAudioTrack(); + + TimeStamp GetStreamTracksStrartTimeStamp() { + MutexAutoLock lock(mMutex); + return mStreamTracksStartTimeStamp; + } + + // XXX need a Reset API + + friend class MediaStreamGraphImpl; + +protected: + enum TrackCommands : uint32_t; + + virtual ~SourceMediaStream(); + + /** + * Data for each track that hasn't ended. + */ + struct TrackData { + TrackID mID; + // Sample rate of the input data. + TrackRate mInputRate; + // Resampler if the rate of the input track does not match the + // MediaStreamGraph's. + nsAutoRef mResampler; + int mResamplerChannelCount; + StreamTime mStart; + // End-time of data already flushed to the track (excluding mData) + StreamTime mEndOfFlushedData; + // Each time the track updates are flushed to the media graph thread, + // the segment buffer is emptied. + nsAutoPtr mData; + // Each time the track updates are flushed to the media graph thread, + // this is cleared. + uint32_t mCommands; + }; + + bool NeedsMixing(); + + void ResampleAudioToGraphSampleRate(TrackData* aTrackData, MediaSegment* aSegment); + + void AddDirectTrackListenerImpl(already_AddRefed aListener, + TrackID aTrackID) override; + void RemoveDirectTrackListenerImpl(DirectMediaStreamTrackListener* aListener, + TrackID aTrackID) override; + + void AddTrackInternal(TrackID aID, TrackRate aRate, + StreamTime aStart, MediaSegment* aSegment, + uint32_t aFlags); + + TrackData* FindDataForTrack(TrackID aID) + { + mMutex.AssertCurrentThreadOwns(); + for (uint32_t i = 0; i < mUpdateTracks.Length(); ++i) { + if (mUpdateTracks[i].mID == aID) { + return &mUpdateTracks[i]; + } + } + return nullptr; + } + + /** + * Notify direct consumers of new data to one of the stream tracks. + * The data doesn't have to be resampled (though it may be). This is called + * from AppendToTrack on the thread providing the data, and will call + * the Listeners on this thread. + */ + void NotifyDirectConsumers(TrackData *aTrack, + MediaSegment *aSegment); + + virtual void + AdvanceTimeVaryingValuesToCurrentTime(GraphTime aCurrentTime, + GraphTime aBlockedTime) override; + void SetStreamTracksStartTimeStamp(const TimeStamp& aTimeStamp) + { + MutexAutoLock lock(mMutex); + mStreamTracksStartTimeStamp = aTimeStamp; + } + + // Only accessed on the MSG thread. Used so to ask the MSGImpl to usecount + // users of a specific input. + // XXX Should really be a CubebUtils::AudioDeviceID, but they aren't + // copyable (opaque pointers) + RefPtr mInputListener; + + // This must be acquired *before* MediaStreamGraphImpl's lock, if they are + // held together. + Mutex mMutex; + // protected by mMutex + StreamTime mUpdateKnownTracksTime; + // This time stamp will be updated in adding and blocked SourceMediaStream, + // |AddStreamGraphThread| and |AdvanceTimeVaryingValuesToCurrentTime| in + // particularly. + TimeStamp mStreamTracksStartTimeStamp; + nsTArray mUpdateTracks; + nsTArray mPendingTracks; + nsTArray> mDirectListeners; + nsTArray> mDirectTrackListeners; + bool mPullEnabled; + bool mUpdateFinished; + bool mNeedsMixing; +}; + +/** + * The blocking mode decides how a track should be blocked in a MediaInputPort. + */ +enum class BlockingMode +{ + /** + * BlockingMode CREATION blocks the source track from being created + * in the destination. It'll end if it already exists. + */ + CREATION, + /** + * BlockingMode END_EXISTING allows a track to be created in the destination + * but will end it before any data has been passed through. + */ + END_EXISTING, +}; + +/** + * Represents a connection between a ProcessedMediaStream and one of its + * input streams. + * We make these refcounted so that stream-related messages with MediaInputPort* + * pointers can be sent to the main thread safely. + * + * A port can be locked to a specific track in the source stream, in which case + * only this track will be forwarded to the destination stream. TRACK_ANY + * can used to signal that all tracks shall be forwarded. + * + * When a port is locked to a specific track in the source stream, it may also + * indicate a TrackID to map this source track to in the destination stream + * by setting aDestTrack to an explicit ID. When we do this, we must know + * that this TrackID in the destination stream is available. We assert during + * processing that the ID is available and that there are no generic input + * ports already attached to the destination stream. + * Note that this is currently only handled by TrackUnionStreams. + * + * When a port's source or destination stream dies, the stream's DestroyImpl + * calls MediaInputPort::Disconnect to disconnect the port from + * the source and destination streams. + * + * The lifetimes of MediaInputPort are controlled from the main thread. + * The media graph adds a reference to the port. When a MediaInputPort is no + * longer needed, main-thread code sends a Destroy message for the port and + * clears its reference (the last main-thread reference to the object). When + * the Destroy message is processed on the graph manager thread we disconnect + * the port and drop the graph's reference, destroying the object. + */ +class MediaInputPort final +{ +private: + // Do not call this constructor directly. Instead call aDest->AllocateInputPort. + MediaInputPort(MediaStream* aSource, TrackID& aSourceTrack, + ProcessedMediaStream* aDest, TrackID& aDestTrack, + uint16_t aInputNumber, uint16_t aOutputNumber) + : mSource(aSource) + , mSourceTrack(aSourceTrack) + , mDest(aDest) + , mDestTrack(aDestTrack) + , mInputNumber(aInputNumber) + , mOutputNumber(aOutputNumber) + , mGraph(nullptr) + { + MOZ_COUNT_CTOR(MediaInputPort); + } + + // Private destructor, to discourage deletion outside of Release(): + ~MediaInputPort() + { + MOZ_COUNT_DTOR(MediaInputPort); + } + +public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaInputPort) + + // Called on graph manager thread + // Do not call these from outside MediaStreamGraph.cpp! + void Init(); + // Called during message processing to trigger removal of this stream. + void Disconnect(); + + // Control API + /** + * Disconnects and destroys the port. The caller must not reference this + * object again. + */ + void Destroy(); + + // Any thread + MediaStream* GetSource() { return mSource; } + TrackID GetSourceTrackId() { return mSourceTrack; } + ProcessedMediaStream* GetDestination() { return mDest; } + TrackID GetDestinationTrackId() { return mDestTrack; } + + /** + * Block aTrackId in the source stream from being passed through the port. + * Consumers will interpret this track as ended. + * Returns a pledge that resolves on the main thread after the track block has + * been applied by the MSG. + */ + already_AddRefed> BlockSourceTrackId(TrackID aTrackId, + BlockingMode aBlockingMode); +private: + void BlockSourceTrackIdImpl(TrackID aTrackId, BlockingMode aBlockingMode); + +public: + // Returns true if aTrackId has not been blocked for any reason and this port + // has not been locked to another track. + bool PassTrackThrough(TrackID aTrackId) { + bool blocked = false; + for (auto pair : mBlockedTracks) { + if (pair.first() == aTrackId && + (pair.second() == BlockingMode::CREATION || + pair.second() == BlockingMode::END_EXISTING)) { + blocked = true; + break; + } + } + return !blocked && (mSourceTrack == TRACK_ANY || mSourceTrack == aTrackId); + } + + // Returns true if aTrackId has not been blocked for track creation and this + // port has not been locked to another track. + bool AllowCreationOf(TrackID aTrackId) { + bool blocked = false; + for (auto pair : mBlockedTracks) { + if (pair.first() == aTrackId && + pair.second() == BlockingMode::CREATION) { + blocked = true; + break; + } + } + return !blocked && (mSourceTrack == TRACK_ANY || mSourceTrack == aTrackId); + } + + uint16_t InputNumber() const { return mInputNumber; } + uint16_t OutputNumber() const { return mOutputNumber; } + + // Call on graph manager thread + struct InputInterval { + GraphTime mStart; + GraphTime mEnd; + bool mInputIsBlocked; + }; + // Find the next time interval starting at or after aTime during which + // mDest is not blocked and mSource's blocking status does not change. + InputInterval GetNextInputInterval(GraphTime aTime); + + /** + * Returns the graph that owns this port. + */ + MediaStreamGraphImpl* GraphImpl(); + MediaStreamGraph* Graph(); + /** + * Sets the graph that owns this stream. Should only be called once. + */ + void SetGraphImpl(MediaStreamGraphImpl* aGraph); + + size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const + { + size_t amount = 0; + + // Not owned: + // - mSource + // - mDest + // - mGraph + return amount; + } + + size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const + { + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); + } + +private: + friend class MediaStreamGraphImpl; + friend class MediaStream; + friend class ProcessedMediaStream; + // Never modified after Init() + MediaStream* mSource; + TrackID mSourceTrack; + ProcessedMediaStream* mDest; + TrackID mDestTrack; + // The input and output numbers are optional, and are currently only used by + // Web Audio. + const uint16_t mInputNumber; + const uint16_t mOutputNumber; + + typedef Pair BlockedTrack; + nsTArray mBlockedTracks; + + // Our media stream graph + MediaStreamGraphImpl* mGraph; +}; + +/** + * This stream processes zero or more input streams in parallel to produce + * its output. The details of how the output is produced are handled by + * subclasses overriding the ProcessInput method. + */ +class ProcessedMediaStream : public MediaStream +{ +public: + explicit ProcessedMediaStream() + : MediaStream(), mAutofinish(false), mCycleMarker(0) + {} + + // Control API. + /** + * Allocates a new input port attached to source aStream. + * This stream can be removed by calling MediaInputPort::Remove(). + * + * The input port is tied to aTrackID in the source stream. + * aTrackID can be set to TRACK_ANY to automatically forward all tracks from + * aStream. + * + * If aTrackID is an explicit ID, aDestTrackID can also be made explicit + * to ensure that the track is assigned this ID in the destination stream. + * To avoid intermittent TrackID collisions the destination stream may not + * have any existing generic input ports (with TRACK_ANY source track) when + * you allocate an input port with a destination TrackID. + * + * To end a track in the destination stream forwarded with TRACK_ANY, + * it can be blocked in the input port through MediaInputPort::BlockTrackId(). + * + * Tracks in aBlockedTracks will be blocked in the input port initially. This + * ensures that they don't get created by the MSG-thread before we can + * BlockTrackId() on the main thread. + */ + already_AddRefed + AllocateInputPort(MediaStream* aStream, + TrackID aTrackID = TRACK_ANY, + TrackID aDestTrackID = TRACK_ANY, + uint16_t aInputNumber = 0, + uint16_t aOutputNumber = 0, + nsTArray* aBlockedTracks = nullptr); + /** + * Force this stream into the finished state. + */ + void Finish(); + /** + * Set the autofinish flag on this stream (defaults to false). When this flag + * is set, and all input streams are in the finished state (including if there + * are no input streams), this stream automatically enters the finished state. + */ + void SetAutofinish(bool aAutofinish); + + ProcessedMediaStream* AsProcessedStream() override { return this; } + + friend class MediaStreamGraphImpl; + + // Do not call these from outside MediaStreamGraph.cpp! + virtual void AddInput(MediaInputPort* aPort); + virtual void RemoveInput(MediaInputPort* aPort) + { + mInputs.RemoveElement(aPort); + } + bool HasInputPort(MediaInputPort* aPort) + { + return mInputs.Contains(aPort); + } + uint32_t InputPortCount() + { + return mInputs.Length(); + } + virtual MediaStream* GetInputStreamFor(TrackID aTrackID) { return nullptr; } + virtual TrackID GetInputTrackIDFor(TrackID aTrackID) { return TRACK_NONE; } + void DestroyImpl() override; + /** + * This gets called after we've computed the blocking states for all + * streams (mBlocked is up to date up to mStateComputedTime). + * Also, we've produced output for all streams up to this one. If this stream + * is not in a cycle, then all its source streams have produced data. + * Generate output from aFrom to aTo. + * This will be called on streams that have finished. Most stream types should + * just return immediately if IsFinishedOnGraphThread(), but some may wish to + * update internal state (see AudioNodeStream). + * ProcessInput is allowed to call FinishOnGraphThread only if ALLOW_FINISH + * is in aFlags. (This flag will be set when aTo >= mStateComputedTime, i.e. + * when we've producing the last block of data we need to produce.) Otherwise + * we can get into a situation where we've determined the stream should not + * block before mStateComputedTime, but the stream finishes before + * mStateComputedTime, violating the invariant that finished streams are blocked. + */ + enum { + ALLOW_FINISH = 0x01 + }; + virtual void ProcessInput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags) = 0; + void SetAutofinishImpl(bool aAutofinish) { mAutofinish = aAutofinish; } + + // Only valid after MediaStreamGraphImpl::UpdateStreamOrder() has run. + // A DelayNode is considered to break a cycle and so this will not return + // true for echo loops, only for muted cycles. + bool InMutedCycle() const { return mCycleMarker; } + + size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override + { + size_t amount = MediaStream::SizeOfExcludingThis(aMallocSizeOf); + // Not owned: + // - mInputs elements + amount += mInputs.ShallowSizeOfExcludingThis(aMallocSizeOf); + return amount; + } + + size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override + { + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); + } + +protected: + // This state is all accessed only on the media graph thread. + + // The list of all inputs that are currently enabled or waiting to be enabled. + nsTArray mInputs; + bool mAutofinish; + // After UpdateStreamOrder(), mCycleMarker is either 0 or 1 to indicate + // whether this stream is in a muted cycle. During ordering it can contain + // other marker values - see MediaStreamGraphImpl::UpdateStreamOrder(). + uint32_t mCycleMarker; +}; + +/** + * There can be multiple MediaStreamGraph per process: one per AudioChannel. + * Additionaly, each OfflineAudioContext object creates its own MediaStreamGraph + * object too.. + */ +class MediaStreamGraph +{ +public: + + // We ensure that the graph current time advances in multiples of + // IdealAudioBlockSize()/AudioStream::PreferredSampleRate(). A stream that + // never blocks and has a track with the ideal audio rate will produce audio + // in multiples of the block size. + + // Initializing an graph that outputs audio can be quite long on some + // platforms. Code that want to output audio at some point can express the + // fact that they will need an audio stream at some point by passing + // AUDIO_THREAD_DRIVER when getting an instance of MediaStreamGraph, so that + // the graph starts with the right driver. + enum GraphDriverType { + AUDIO_THREAD_DRIVER, + SYSTEM_THREAD_DRIVER, + OFFLINE_THREAD_DRIVER + }; + static const uint32_t AUDIO_CALLBACK_DRIVER_SHUTDOWN_TIMEOUT = 20*1000; + + // Main thread only + static MediaStreamGraph* GetInstance(GraphDriverType aGraphDriverRequested, + dom::AudioChannel aChannel); + static MediaStreamGraph* CreateNonRealtimeInstance(TrackRate aSampleRate); + // Idempotent + static void DestroyNonRealtimeInstance(MediaStreamGraph* aGraph); + + virtual nsresult OpenAudioInput(int aID, + AudioDataListener *aListener) { + return NS_ERROR_FAILURE; + } + virtual void CloseAudioInput(AudioDataListener *aListener) {} + + // Control API. + /** + * Create a stream that a media decoder (or some other source of + * media data, such as a camera) can write to. + */ + SourceMediaStream* CreateSourceStream(); + /** + * Create a stream that will form the union of the tracks of its input + * streams. + * A TrackUnionStream contains all the tracks of all its input streams. + * Adding a new input stream makes that stream's tracks immediately appear as new + * tracks starting at the time the input stream was added. + * Removing an input stream makes the output tracks corresponding to the + * removed tracks immediately end. + * For each added track, the track ID of the output track is the track ID + * of the input track or one plus the maximum ID of all previously added + * tracks, whichever is greater. + * TODO at some point we will probably need to add API to select + * particular tracks of each input stream. + */ + ProcessedMediaStream* CreateTrackUnionStream(); + /** + * Create a stream that will mix all its audio input. + */ + ProcessedMediaStream* CreateAudioCaptureStream(TrackID aTrackId); + + /** + * Add a new stream to the graph. Main thread. + */ + void AddStream(MediaStream* aStream); + + /* From the main thread, ask the MSG to send back an event when the graph + * thread is running, and audio is being processed. */ + void NotifyWhenGraphStarted(AudioNodeStream* aNodeStream); + /* From the main thread, suspend, resume or close an AudioContext. + * aStreams are the streams of all the AudioNodes of the AudioContext that + * need to be suspended or resumed. This can be empty if this is a second + * consecutive suspend call and all the nodes are already suspended. + * + * This can possibly pause the graph thread, releasing system resources, if + * all streams have been suspended/closed. + * + * When the operation is complete, aPromise is resolved. + */ + void ApplyAudioContextOperation(MediaStream* aDestinationStream, + const nsTArray& aStreams, + dom::AudioContextOperation aState, + void* aPromise); + + bool IsNonRealtime() const; + /** + * Start processing non-realtime for a specific number of ticks. + */ + void StartNonRealtimeProcessing(uint32_t aTicksToProcess); + + /** + * Media graph thread only. + * Dispatches a runnable that will run on the main thread after all + * main-thread stream state has been next updated. + * Should only be called during MediaStreamListener callbacks or during + * ProcessedMediaStream::ProcessInput(). + */ + virtual void DispatchToMainThreadAfterStreamStateUpdate(already_AddRefed aRunnable) + { + AssertOnGraphThreadOrNotRunning(); + *mPendingUpdateRunnables.AppendElement() = aRunnable; + } + + /** + * Returns graph sample rate in Hz. + */ + TrackRate GraphRate() const { return mSampleRate; } + + void RegisterCaptureStreamForWindow(uint64_t aWindowId, + ProcessedMediaStream* aCaptureStream); + void UnregisterCaptureStreamForWindow(uint64_t aWindowId); + already_AddRefed ConnectToCaptureStream( + uint64_t aWindowId, MediaStream* aMediaStream); + + /** + * Data going to the speakers from the GraphDriver's DataCallback + * to notify any listeners (for echo cancellation). + */ + void NotifyOutputData(AudioDataValue* aBuffer, size_t aFrames, + TrackRate aRate, uint32_t aChannels); + + void AssertOnGraphThreadOrNotRunning() const; + +protected: + explicit MediaStreamGraph(TrackRate aSampleRate) + : mSampleRate(aSampleRate) + { + MOZ_COUNT_CTOR(MediaStreamGraph); + } + virtual ~MediaStreamGraph() + { + MOZ_COUNT_DTOR(MediaStreamGraph); + } + + // Media graph thread only + nsTArray > mPendingUpdateRunnables; + + /** + * Sample rate at which this graph runs. For real time graphs, this is + * the rate of the audio mixer. For offline graphs, this is the rate specified + * at construction. + */ + TrackRate mSampleRate; + + /** + * CloseAudioInput is async, so hold a reference here. + */ + nsTArray> mAudioInputs; +}; + +} // namespace mozilla + +#endif /* MOZILLA_MEDIASTREAMGRAPH_H_ */ -- cgit v1.2.3