Fri, 16 Jan 2015 04:50:19 +0100
Replace accessor implementation with direct member state manipulation, by
request https://trac.torproject.org/projects/tor/ticket/9701#comment:32
michael@0 | 1 | /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/ |
michael@0 | 2 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
michael@0 | 4 | * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 5 | |
michael@0 | 6 | #ifndef MOZILLA_MEDIASTREAMGRAPH_H_ |
michael@0 | 7 | #define MOZILLA_MEDIASTREAMGRAPH_H_ |
michael@0 | 8 | |
michael@0 | 9 | #include "mozilla/Mutex.h" |
michael@0 | 10 | #include "mozilla/LinkedList.h" |
michael@0 | 11 | #include "AudioStream.h" |
michael@0 | 12 | #include "nsTArray.h" |
michael@0 | 13 | #include "nsIRunnable.h" |
michael@0 | 14 | #include "StreamBuffer.h" |
michael@0 | 15 | #include "TimeVarying.h" |
michael@0 | 16 | #include "VideoFrameContainer.h" |
michael@0 | 17 | #include "VideoSegment.h" |
michael@0 | 18 | #include "MainThreadUtils.h" |
michael@0 | 19 | #include "nsAutoRef.h" |
michael@0 | 20 | #include "speex/speex_resampler.h" |
michael@0 | 21 | #include "AudioMixer.h" |
michael@0 | 22 | #include "mozilla/dom/AudioChannelBinding.h" |
michael@0 | 23 | |
michael@0 | 24 | class nsIRunnable; |
michael@0 | 25 | |
michael@0 | 26 | template <> |
michael@0 | 27 | class nsAutoRefTraits<SpeexResamplerState> : public nsPointerRefTraits<SpeexResamplerState> |
michael@0 | 28 | { |
michael@0 | 29 | public: |
michael@0 | 30 | static void Release(SpeexResamplerState* aState) { speex_resampler_destroy(aState); } |
michael@0 | 31 | }; |
michael@0 | 32 | |
michael@0 | 33 | namespace mozilla { |
michael@0 | 34 | |
michael@0 | 35 | class DOMMediaStream; |
michael@0 | 36 | |
michael@0 | 37 | #ifdef PR_LOGGING |
michael@0 | 38 | extern PRLogModuleInfo* gMediaStreamGraphLog; |
michael@0 | 39 | #endif |
michael@0 | 40 | |
michael@0 | 41 | /** |
michael@0 | 42 | * Microseconds relative to the start of the graph timeline. |
michael@0 | 43 | */ |
michael@0 | 44 | typedef int64_t GraphTime; |
michael@0 | 45 | const GraphTime GRAPH_TIME_MAX = MEDIA_TIME_MAX; |
michael@0 | 46 | |
michael@0 | 47 | /* |
michael@0 | 48 | * MediaStreamGraph is a framework for synchronized audio/video processing |
michael@0 | 49 | * and playback. It is designed to be used by other browser components such as |
michael@0 | 50 | * HTML media elements, media capture APIs, real-time media streaming APIs, |
michael@0 | 51 | * multitrack media APIs, and advanced audio APIs. |
michael@0 | 52 | * |
michael@0 | 53 | * The MediaStreamGraph uses a dedicated thread to process media --- the media |
michael@0 | 54 | * graph thread. This ensures that we can process media through the graph |
michael@0 | 55 | * without blocking on main-thread activity. The media graph is only modified |
michael@0 | 56 | * on the media graph thread, to ensure graph changes can be processed without |
michael@0 | 57 | * interfering with media processing. All interaction with the media graph |
michael@0 | 58 | * thread is done with message passing. |
michael@0 | 59 | * |
michael@0 | 60 | * APIs that modify the graph or its properties are described as "control APIs". |
michael@0 | 61 | * These APIs are asynchronous; they queue graph changes internally and |
michael@0 | 62 | * those changes are processed all-at-once by the MediaStreamGraph. The |
michael@0 | 63 | * MediaStreamGraph monitors the main thread event loop via nsIAppShell::RunInStableState |
michael@0 | 64 | * to ensure that graph changes from a single event loop task are always |
michael@0 | 65 | * processed all together. Control APIs should only be used on the main thread, |
michael@0 | 66 | * currently; we may be able to relax that later. |
michael@0 | 67 | * |
michael@0 | 68 | * To allow precise synchronization of times in the control API, the |
michael@0 | 69 | * MediaStreamGraph maintains a "media timeline". Control APIs that take or |
michael@0 | 70 | * return times use that timeline. Those times never advance during |
michael@0 | 71 | * an event loop task. This time is returned by MediaStreamGraph::GetCurrentTime(). |
michael@0 | 72 | * |
michael@0 | 73 | * Media decoding, audio processing and media playback use thread-safe APIs to |
michael@0 | 74 | * the media graph to ensure they can continue while the main thread is blocked. |
michael@0 | 75 | * |
michael@0 | 76 | * When the graph is changed, we may need to throw out buffered data and |
michael@0 | 77 | * reprocess it. This is triggered automatically by the MediaStreamGraph. |
michael@0 | 78 | */ |
michael@0 | 79 | |
michael@0 | 80 | class MediaStreamGraph; |
michael@0 | 81 | |
michael@0 | 82 | /** |
michael@0 | 83 | * This is a base class for media graph thread listener callbacks. |
michael@0 | 84 | * Override methods to be notified of audio or video data or changes in stream |
michael@0 | 85 | * state. |
michael@0 | 86 | * |
michael@0 | 87 | * This can be used by stream recorders or network connections that receive |
michael@0 | 88 | * stream input. It could also be used for debugging. |
michael@0 | 89 | * |
michael@0 | 90 | * All notification methods are called from the media graph thread. Overriders |
michael@0 | 91 | * of these methods are responsible for all synchronization. Beware! |
michael@0 | 92 | * These methods are called without the media graph monitor held, so |
michael@0 | 93 | * reentry into media graph methods is possible, although very much discouraged! |
michael@0 | 94 | * You should do something non-blocking and non-reentrant (e.g. dispatch an |
michael@0 | 95 | * event to some thread) and return. |
michael@0 | 96 | * The listener is not allowed to add/remove any listeners from the stream. |
michael@0 | 97 | * |
michael@0 | 98 | * When a listener is first attached, we guarantee to send a NotifyBlockingChanged |
michael@0 | 99 | * callback to notify of the initial blocking state. Also, if a listener is |
michael@0 | 100 | * attached to a stream that has already finished, we'll call NotifyFinished. |
michael@0 | 101 | */ |
michael@0 | 102 | class MediaStreamListener { |
michael@0 | 103 | protected: |
michael@0 | 104 | // Protected destructor, to discourage deletion outside of Release(): |
michael@0 | 105 | virtual ~MediaStreamListener() {} |
michael@0 | 106 | |
michael@0 | 107 | public: |
michael@0 | 108 | NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaStreamListener) |
michael@0 | 109 | |
michael@0 | 110 | enum Consumption { |
michael@0 | 111 | CONSUMED, |
michael@0 | 112 | NOT_CONSUMED |
michael@0 | 113 | }; |
michael@0 | 114 | /** |
michael@0 | 115 | * Notify that the stream is hooked up and we'd like to start or stop receiving |
michael@0 | 116 | * data on it. Only fires on SourceMediaStreams. |
michael@0 | 117 | * The initial state is assumed to be NOT_CONSUMED. |
michael@0 | 118 | */ |
michael@0 | 119 | virtual void NotifyConsumptionChanged(MediaStreamGraph* aGraph, Consumption aConsuming) {} |
michael@0 | 120 | |
michael@0 | 121 | /** |
michael@0 | 122 | * When a SourceMediaStream has pulling enabled, and the MediaStreamGraph |
michael@0 | 123 | * control loop is ready to pull, this gets called. A NotifyPull implementation |
michael@0 | 124 | * is allowed to call the SourceMediaStream methods that alter track |
michael@0 | 125 | * data. It is not allowed to make other MediaStream API calls, including |
michael@0 | 126 | * calls to add or remove MediaStreamListeners. It is not allowed to block |
michael@0 | 127 | * for any length of time. |
michael@0 | 128 | * aDesiredTime is the stream time we would like to get data up to. Data |
michael@0 | 129 | * beyond this point will not be played until NotifyPull runs again, so there's |
michael@0 | 130 | * not much point in providing it. Note that if the stream is blocked for |
michael@0 | 131 | * some reason, then data before aDesiredTime may not be played immediately. |
michael@0 | 132 | */ |
michael@0 | 133 | virtual void NotifyPull(MediaStreamGraph* aGraph, StreamTime aDesiredTime) {} |
michael@0 | 134 | |
michael@0 | 135 | enum Blocking { |
michael@0 | 136 | BLOCKED, |
michael@0 | 137 | UNBLOCKED |
michael@0 | 138 | }; |
michael@0 | 139 | /** |
michael@0 | 140 | * Notify that the blocking status of the stream changed. The initial state |
michael@0 | 141 | * is assumed to be BLOCKED. |
michael@0 | 142 | */ |
michael@0 | 143 | virtual void NotifyBlockingChanged(MediaStreamGraph* aGraph, Blocking aBlocked) {} |
michael@0 | 144 | |
michael@0 | 145 | /** |
michael@0 | 146 | * Notify that the stream has data in each track |
michael@0 | 147 | * for the stream's current time. Once this state becomes true, it will |
michael@0 | 148 | * always be true since we block stream time from progressing to times where |
michael@0 | 149 | * there isn't data in each track. |
michael@0 | 150 | */ |
michael@0 | 151 | virtual void NotifyHasCurrentData(MediaStreamGraph* aGraph) {} |
michael@0 | 152 | |
michael@0 | 153 | /** |
michael@0 | 154 | * Notify that the stream output is advancing. aCurrentTime is the graph's |
michael@0 | 155 | * current time. MediaStream::GraphTimeToStreamTime can be used to get the |
michael@0 | 156 | * stream time. |
michael@0 | 157 | */ |
michael@0 | 158 | virtual void NotifyOutput(MediaStreamGraph* aGraph, GraphTime aCurrentTime) {} |
michael@0 | 159 | |
michael@0 | 160 | /** |
michael@0 | 161 | * Notify that the stream finished. |
michael@0 | 162 | */ |
michael@0 | 163 | virtual void NotifyFinished(MediaStreamGraph* aGraph) {} |
michael@0 | 164 | |
michael@0 | 165 | /** |
michael@0 | 166 | * Notify that your listener has been removed, either due to RemoveListener(), |
michael@0 | 167 | * or due to the stream being destroyed. You will get no further notifications. |
michael@0 | 168 | */ |
michael@0 | 169 | virtual void NotifyRemoved(MediaStreamGraph* aGraph) {} |
michael@0 | 170 | |
michael@0 | 171 | enum { |
michael@0 | 172 | TRACK_EVENT_CREATED = 0x01, |
michael@0 | 173 | TRACK_EVENT_ENDED = 0x02 |
michael@0 | 174 | }; |
michael@0 | 175 | /** |
michael@0 | 176 | * Notify that changes to one of the stream tracks have been queued. |
michael@0 | 177 | * aTrackEvents can be any combination of TRACK_EVENT_CREATED and |
michael@0 | 178 | * TRACK_EVENT_ENDED. aQueuedMedia is the data being added to the track |
michael@0 | 179 | * at aTrackOffset (relative to the start of the stream). |
michael@0 | 180 | */ |
michael@0 | 181 | virtual void NotifyQueuedTrackChanges(MediaStreamGraph* aGraph, TrackID aID, |
michael@0 | 182 | TrackRate aTrackRate, |
michael@0 | 183 | TrackTicks aTrackOffset, |
michael@0 | 184 | uint32_t aTrackEvents, |
michael@0 | 185 | const MediaSegment& aQueuedMedia) {} |
michael@0 | 186 | }; |
michael@0 | 187 | |
michael@0 | 188 | /** |
michael@0 | 189 | * This is a base class for media graph thread listener direct callbacks |
michael@0 | 190 | * from within AppendToTrack(). Note that your regular listener will |
michael@0 | 191 | * still get NotifyQueuedTrackChanges() callbacks from the MSG thread, so |
michael@0 | 192 | * you must be careful to ignore them if AddDirectListener was successful. |
michael@0 | 193 | */ |
michael@0 | 194 | class MediaStreamDirectListener : public MediaStreamListener |
michael@0 | 195 | { |
michael@0 | 196 | public: |
michael@0 | 197 | virtual ~MediaStreamDirectListener() {} |
michael@0 | 198 | |
michael@0 | 199 | /* |
michael@0 | 200 | * This will be called on any MediaStreamDirectListener added to a |
michael@0 | 201 | * a SourceMediaStream when AppendToTrack() is called. The MediaSegment |
michael@0 | 202 | * will be the RawSegment (unresampled) if available in AppendToTrack(). |
michael@0 | 203 | * Note that NotifyQueuedTrackChanges() calls will also still occur. |
michael@0 | 204 | */ |
michael@0 | 205 | virtual void NotifyRealtimeData(MediaStreamGraph* aGraph, TrackID aID, |
michael@0 | 206 | TrackRate aTrackRate, |
michael@0 | 207 | TrackTicks aTrackOffset, |
michael@0 | 208 | uint32_t aTrackEvents, |
michael@0 | 209 | const MediaSegment& aMedia) {} |
michael@0 | 210 | }; |
michael@0 | 211 | |
michael@0 | 212 | /** |
michael@0 | 213 | * This is a base class for main-thread listener callbacks. |
michael@0 | 214 | * This callback is invoked on the main thread when the main-thread-visible |
michael@0 | 215 | * state of a stream has changed. |
michael@0 | 216 | * |
michael@0 | 217 | * These methods are called with the media graph monitor held, so |
michael@0 | 218 | * reentry into general media graph methods is not possible. |
michael@0 | 219 | * You should do something non-blocking and non-reentrant (e.g. dispatch an |
michael@0 | 220 | * event) and return. DispatchFromMainThreadAfterNextStreamStateUpdate |
michael@0 | 221 | * would be a good choice. |
michael@0 | 222 | * The listener is allowed to synchronously remove itself from the stream, but |
michael@0 | 223 | * not add or remove any other listeners. |
michael@0 | 224 | */ |
michael@0 | 225 | class MainThreadMediaStreamListener { |
michael@0 | 226 | public: |
michael@0 | 227 | virtual void NotifyMainThreadStateChanged() = 0; |
michael@0 | 228 | }; |
michael@0 | 229 | |
michael@0 | 230 | /** |
michael@0 | 231 | * Helper struct used to keep track of memory usage by AudioNodes. |
michael@0 | 232 | */ |
michael@0 | 233 | struct AudioNodeSizes |
michael@0 | 234 | { |
michael@0 | 235 | size_t mDomNode; |
michael@0 | 236 | size_t mStream; |
michael@0 | 237 | size_t mEngine; |
michael@0 | 238 | nsCString mNodeType; |
michael@0 | 239 | }; |
michael@0 | 240 | |
michael@0 | 241 | class MediaStreamGraphImpl; |
michael@0 | 242 | class SourceMediaStream; |
michael@0 | 243 | class ProcessedMediaStream; |
michael@0 | 244 | class MediaInputPort; |
michael@0 | 245 | class AudioNodeEngine; |
michael@0 | 246 | class AudioNodeExternalInputStream; |
michael@0 | 247 | class AudioNodeStream; |
michael@0 | 248 | struct AudioChunk; |
michael@0 | 249 | |
michael@0 | 250 | /** |
michael@0 | 251 | * A stream of synchronized audio and video data. All (not blocked) streams |
michael@0 | 252 | * progress at the same rate --- "real time". Streams cannot seek. The only |
michael@0 | 253 | * operation readers can perform on a stream is to read the next data. |
michael@0 | 254 | * |
michael@0 | 255 | * Consumers of a stream can be reading from it at different offsets, but that |
michael@0 | 256 | * should only happen due to the order in which consumers are being run. |
michael@0 | 257 | * Those offsets must not diverge in the long term, otherwise we would require |
michael@0 | 258 | * unbounded buffering. |
michael@0 | 259 | * |
michael@0 | 260 | * Streams can be in a "blocked" state. While blocked, a stream does not |
michael@0 | 261 | * produce data. A stream can be explicitly blocked via the control API, |
michael@0 | 262 | * or implicitly blocked by whatever's generating it (e.g. an underrun in the |
michael@0 | 263 | * source resource), or implicitly blocked because something consuming it |
michael@0 | 264 | * blocks, or implicitly because it has finished. |
michael@0 | 265 | * |
michael@0 | 266 | * A stream can be in a "finished" state. "Finished" streams are permanently |
michael@0 | 267 | * blocked. |
michael@0 | 268 | * |
michael@0 | 269 | * Transitions into and out of the "blocked" and "finished" states are managed |
michael@0 | 270 | * by the MediaStreamGraph on the media graph thread. |
michael@0 | 271 | * |
michael@0 | 272 | * We buffer media data ahead of the consumers' reading offsets. It is possible |
michael@0 | 273 | * to have buffered data but still be blocked. |
michael@0 | 274 | * |
michael@0 | 275 | * Any stream can have its audio and video playing when requested. The media |
michael@0 | 276 | * stream graph plays audio by constructing audio output streams as necessary. |
michael@0 | 277 | * Video is played by setting video frames into an VideoFrameContainer at the right |
michael@0 | 278 | * time. To ensure video plays in sync with audio, make sure that the same |
michael@0 | 279 | * stream is playing both the audio and video. |
michael@0 | 280 | * |
michael@0 | 281 | * The data in a stream is managed by StreamBuffer. It consists of a set of |
michael@0 | 282 | * tracks of various types that can start and end over time. |
michael@0 | 283 | * |
michael@0 | 284 | * Streams are explicitly managed. The client creates them via |
michael@0 | 285 | * MediaStreamGraph::CreateInput/ProcessedMediaStream, and releases them by calling |
michael@0 | 286 | * Destroy() when no longer needed (actual destruction will be deferred). |
michael@0 | 287 | * The actual object is owned by the MediaStreamGraph. The basic idea is that |
michael@0 | 288 | * main thread objects will keep Streams alive as long as necessary (using the |
michael@0 | 289 | * cycle collector to clean up whenever needed). |
michael@0 | 290 | * |
michael@0 | 291 | * We make them refcounted only so that stream-related messages with MediaStream* |
michael@0 | 292 | * pointers can be sent to the main thread safely. |
michael@0 | 293 | * |
michael@0 | 294 | * The lifetimes of MediaStreams are controlled from the main thread. |
michael@0 | 295 | * For MediaStreams exposed to the DOM, the lifetime is controlled by the DOM |
michael@0 | 296 | * wrapper; the DOM wrappers own their associated MediaStreams. When a DOM |
michael@0 | 297 | * wrapper is destroyed, it sends a Destroy message for the associated |
michael@0 | 298 | * MediaStream and clears its reference (the last main-thread reference to |
michael@0 | 299 | * the object). When the Destroy message is processed on the graph |
michael@0 | 300 | * manager thread we immediately release the affected objects (disentangling them |
michael@0 | 301 | * from other objects as necessary). |
michael@0 | 302 | * |
michael@0 | 303 | * This could cause problems for media processing if a MediaStream is |
michael@0 | 304 | * destroyed while a downstream MediaStream is still using it. Therefore |
michael@0 | 305 | * the DOM wrappers must keep upstream MediaStreams alive as long as they |
michael@0 | 306 | * could be being used in the media graph. |
michael@0 | 307 | * |
michael@0 | 308 | * At any time, however, a set of MediaStream wrappers could be |
michael@0 | 309 | * collected via cycle collection. Destroy messages will be sent |
michael@0 | 310 | * for those objects in arbitrary order and the MediaStreamGraph has to be able |
michael@0 | 311 | * to handle this. |
michael@0 | 312 | */ |
michael@0 | 313 | class MediaStream : public mozilla::LinkedListElement<MediaStream> { |
michael@0 | 314 | public: |
michael@0 | 315 | NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaStream) |
michael@0 | 316 | |
michael@0 | 317 | MediaStream(DOMMediaStream* aWrapper); |
michael@0 | 318 | |
michael@0 | 319 | protected: |
michael@0 | 320 | // Protected destructor, to discourage deletion outside of Release(): |
michael@0 | 321 | virtual ~MediaStream() |
michael@0 | 322 | { |
michael@0 | 323 | MOZ_COUNT_DTOR(MediaStream); |
michael@0 | 324 | NS_ASSERTION(mMainThreadDestroyed, "Should have been destroyed already"); |
michael@0 | 325 | NS_ASSERTION(mMainThreadListeners.IsEmpty(), |
michael@0 | 326 | "All main thread listeners should have been removed"); |
michael@0 | 327 | } |
michael@0 | 328 | |
michael@0 | 329 | public: |
michael@0 | 330 | /** |
michael@0 | 331 | * Returns the graph that owns this stream. |
michael@0 | 332 | */ |
michael@0 | 333 | MediaStreamGraphImpl* GraphImpl(); |
michael@0 | 334 | MediaStreamGraph* Graph(); |
michael@0 | 335 | /** |
michael@0 | 336 | * Sets the graph that owns this stream. Should only be called once. |
michael@0 | 337 | */ |
michael@0 | 338 | void SetGraphImpl(MediaStreamGraphImpl* aGraph); |
michael@0 | 339 | void SetGraphImpl(MediaStreamGraph* aGraph); |
michael@0 | 340 | |
michael@0 | 341 | // Control API. |
michael@0 | 342 | // Since a stream can be played multiple ways, we need to combine independent |
michael@0 | 343 | // volume settings. The aKey parameter is used to keep volume settings |
michael@0 | 344 | // separate. Since the stream is always playing the same contents, only |
michael@0 | 345 | // a single audio output stream is used; the volumes are combined. |
michael@0 | 346 | // Currently only the first enabled audio track is played. |
michael@0 | 347 | // XXX change this so all enabled audio tracks are mixed and played. |
michael@0 | 348 | virtual void AddAudioOutput(void* aKey); |
michael@0 | 349 | virtual void SetAudioOutputVolume(void* aKey, float aVolume); |
michael@0 | 350 | virtual void RemoveAudioOutput(void* aKey); |
michael@0 | 351 | // Since a stream can be played multiple ways, we need to be able to |
michael@0 | 352 | // play to multiple VideoFrameContainers. |
michael@0 | 353 | // Only the first enabled video track is played. |
michael@0 | 354 | virtual void AddVideoOutput(VideoFrameContainer* aContainer); |
michael@0 | 355 | virtual void RemoveVideoOutput(VideoFrameContainer* aContainer); |
michael@0 | 356 | // Explicitly block. Useful for example if a media element is pausing |
michael@0 | 357 | // and we need to stop its stream emitting its buffered data. |
michael@0 | 358 | virtual void ChangeExplicitBlockerCount(int32_t aDelta); |
michael@0 | 359 | // Events will be dispatched by calling methods of aListener. |
michael@0 | 360 | virtual void AddListener(MediaStreamListener* aListener); |
michael@0 | 361 | virtual void RemoveListener(MediaStreamListener* aListener); |
michael@0 | 362 | // A disabled track has video replaced by black, and audio replaced by |
michael@0 | 363 | // silence. |
michael@0 | 364 | void SetTrackEnabled(TrackID aTrackID, bool aEnabled); |
michael@0 | 365 | // Events will be dispatched by calling methods of aListener. It is the |
michael@0 | 366 | // responsibility of the caller to remove aListener before it is destroyed. |
michael@0 | 367 | void AddMainThreadListener(MainThreadMediaStreamListener* aListener) |
michael@0 | 368 | { |
michael@0 | 369 | NS_ASSERTION(NS_IsMainThread(), "Call only on main thread"); |
michael@0 | 370 | mMainThreadListeners.AppendElement(aListener); |
michael@0 | 371 | } |
michael@0 | 372 | // It's safe to call this even if aListener is not currently a listener; |
michael@0 | 373 | // the call will be ignored. |
michael@0 | 374 | void RemoveMainThreadListener(MainThreadMediaStreamListener* aListener) |
michael@0 | 375 | { |
michael@0 | 376 | NS_ASSERTION(NS_IsMainThread(), "Call only on main thread"); |
michael@0 | 377 | mMainThreadListeners.RemoveElement(aListener); |
michael@0 | 378 | } |
michael@0 | 379 | /** |
michael@0 | 380 | * Ensure a runnable will run on the main thread after running all pending |
michael@0 | 381 | * updates that were sent from the graph thread or will be sent before the |
michael@0 | 382 | * graph thread receives the next graph update. |
michael@0 | 383 | * |
michael@0 | 384 | * If the graph has been shut down or destroyed, then the runnable will be |
michael@0 | 385 | * dispatched to the event queue immediately. If the graph is non-realtime |
michael@0 | 386 | * and has not started, then the runnable will be run |
michael@0 | 387 | * synchronously/immediately. (There are no pending updates in these |
michael@0 | 388 | * situations.) |
michael@0 | 389 | * |
michael@0 | 390 | * Main thread only. |
michael@0 | 391 | */ |
michael@0 | 392 | void RunAfterPendingUpdates(nsRefPtr<nsIRunnable> aRunnable); |
michael@0 | 393 | |
michael@0 | 394 | // Signal that the client is done with this MediaStream. It will be deleted later. |
michael@0 | 395 | virtual void Destroy(); |
michael@0 | 396 | // Returns the main-thread's view of how much data has been processed by |
michael@0 | 397 | // this stream. |
michael@0 | 398 | StreamTime GetCurrentTime() |
michael@0 | 399 | { |
michael@0 | 400 | NS_ASSERTION(NS_IsMainThread(), "Call only on main thread"); |
michael@0 | 401 | return mMainThreadCurrentTime; |
michael@0 | 402 | } |
michael@0 | 403 | // Return the main thread's view of whether this stream has finished. |
michael@0 | 404 | bool IsFinished() |
michael@0 | 405 | { |
michael@0 | 406 | NS_ASSERTION(NS_IsMainThread(), "Call only on main thread"); |
michael@0 | 407 | return mMainThreadFinished; |
michael@0 | 408 | } |
michael@0 | 409 | bool IsDestroyed() |
michael@0 | 410 | { |
michael@0 | 411 | NS_ASSERTION(NS_IsMainThread(), "Call only on main thread"); |
michael@0 | 412 | return mMainThreadDestroyed; |
michael@0 | 413 | } |
michael@0 | 414 | |
michael@0 | 415 | friend class MediaStreamGraphImpl; |
michael@0 | 416 | friend class MediaInputPort; |
michael@0 | 417 | friend class AudioNodeExternalInputStream; |
michael@0 | 418 | |
michael@0 | 419 | virtual SourceMediaStream* AsSourceStream() { return nullptr; } |
michael@0 | 420 | virtual ProcessedMediaStream* AsProcessedStream() { return nullptr; } |
michael@0 | 421 | virtual AudioNodeStream* AsAudioNodeStream() { return nullptr; } |
michael@0 | 422 | |
michael@0 | 423 | // media graph thread only |
michael@0 | 424 | void Init(); |
michael@0 | 425 | // These Impl methods perform the core functionality of the control methods |
michael@0 | 426 | // above, on the media graph thread. |
michael@0 | 427 | /** |
michael@0 | 428 | * Stop all stream activity and disconnect it from all inputs and outputs. |
michael@0 | 429 | * This must be idempotent. |
michael@0 | 430 | */ |
michael@0 | 431 | virtual void DestroyImpl(); |
michael@0 | 432 | StreamTime GetBufferEnd() { return mBuffer.GetEnd(); } |
michael@0 | 433 | #ifdef DEBUG |
michael@0 | 434 | void DumpTrackInfo() { return mBuffer.DumpTrackInfo(); } |
michael@0 | 435 | #endif |
michael@0 | 436 | void SetAudioOutputVolumeImpl(void* aKey, float aVolume); |
michael@0 | 437 | void AddAudioOutputImpl(void* aKey) |
michael@0 | 438 | { |
michael@0 | 439 | mAudioOutputs.AppendElement(AudioOutput(aKey)); |
michael@0 | 440 | } |
michael@0 | 441 | void RemoveAudioOutputImpl(void* aKey); |
michael@0 | 442 | void AddVideoOutputImpl(already_AddRefed<VideoFrameContainer> aContainer) |
michael@0 | 443 | { |
michael@0 | 444 | *mVideoOutputs.AppendElement() = aContainer; |
michael@0 | 445 | } |
michael@0 | 446 | void RemoveVideoOutputImpl(VideoFrameContainer* aContainer) |
michael@0 | 447 | { |
michael@0 | 448 | mVideoOutputs.RemoveElement(aContainer); |
michael@0 | 449 | } |
michael@0 | 450 | void ChangeExplicitBlockerCountImpl(GraphTime aTime, int32_t aDelta) |
michael@0 | 451 | { |
michael@0 | 452 | mExplicitBlockerCount.SetAtAndAfter(aTime, mExplicitBlockerCount.GetAt(aTime) + aDelta); |
michael@0 | 453 | } |
michael@0 | 454 | void AddListenerImpl(already_AddRefed<MediaStreamListener> aListener); |
michael@0 | 455 | void RemoveListenerImpl(MediaStreamListener* aListener); |
michael@0 | 456 | void RemoveAllListenersImpl(); |
michael@0 | 457 | void SetTrackEnabledImpl(TrackID aTrackID, bool aEnabled); |
michael@0 | 458 | /** |
michael@0 | 459 | * Returns true when this stream requires the contents of its inputs even if |
michael@0 | 460 | * its own outputs are not being consumed. This is used to signal inputs to |
michael@0 | 461 | * this stream that they are being consumed; when they're not being consumed, |
michael@0 | 462 | * we make some optimizations. |
michael@0 | 463 | */ |
michael@0 | 464 | virtual bool IsIntrinsicallyConsumed() const |
michael@0 | 465 | { |
michael@0 | 466 | return !mAudioOutputs.IsEmpty() || !mVideoOutputs.IsEmpty(); |
michael@0 | 467 | } |
michael@0 | 468 | |
michael@0 | 469 | void AddConsumer(MediaInputPort* aPort) |
michael@0 | 470 | { |
michael@0 | 471 | mConsumers.AppendElement(aPort); |
michael@0 | 472 | } |
michael@0 | 473 | void RemoveConsumer(MediaInputPort* aPort) |
michael@0 | 474 | { |
michael@0 | 475 | mConsumers.RemoveElement(aPort); |
michael@0 | 476 | } |
michael@0 | 477 | uint32_t ConsumerCount() |
michael@0 | 478 | { |
michael@0 | 479 | return mConsumers.Length(); |
michael@0 | 480 | } |
michael@0 | 481 | const StreamBuffer& GetStreamBuffer() { return mBuffer; } |
michael@0 | 482 | GraphTime GetStreamBufferStartTime() { return mBufferStartTime; } |
michael@0 | 483 | /** |
michael@0 | 484 | * Convert graph time to stream time. aTime must be <= mStateComputedTime |
michael@0 | 485 | * to ensure we know exactly how much time this stream will be blocked during |
michael@0 | 486 | * the interval. |
michael@0 | 487 | */ |
michael@0 | 488 | StreamTime GraphTimeToStreamTime(GraphTime aTime); |
michael@0 | 489 | /** |
michael@0 | 490 | * Convert graph time to stream time. aTime can be > mStateComputedTime, |
michael@0 | 491 | * in which case we optimistically assume the stream will not be blocked |
michael@0 | 492 | * after mStateComputedTime. |
michael@0 | 493 | */ |
michael@0 | 494 | StreamTime GraphTimeToStreamTimeOptimistic(GraphTime aTime); |
michael@0 | 495 | /** |
michael@0 | 496 | * Convert stream time to graph time. The result can be > mStateComputedTime, |
michael@0 | 497 | * in which case we did the conversion optimistically assuming the stream |
michael@0 | 498 | * will not be blocked after mStateComputedTime. |
michael@0 | 499 | */ |
michael@0 | 500 | GraphTime StreamTimeToGraphTime(StreamTime aTime); |
michael@0 | 501 | bool IsFinishedOnGraphThread() { return mFinished; } |
michael@0 | 502 | void FinishOnGraphThread(); |
michael@0 | 503 | /** |
michael@0 | 504 | * Identify which graph update index we are currently processing. |
michael@0 | 505 | */ |
michael@0 | 506 | int64_t GetProcessingGraphUpdateIndex(); |
michael@0 | 507 | |
michael@0 | 508 | bool HasCurrentData() { return mHasCurrentData; } |
michael@0 | 509 | |
michael@0 | 510 | StreamBuffer::Track* EnsureTrack(TrackID aTrack, TrackRate aSampleRate); |
michael@0 | 511 | |
michael@0 | 512 | void ApplyTrackDisabling(TrackID aTrackID, MediaSegment* aSegment, MediaSegment* aRawSegment = nullptr); |
michael@0 | 513 | |
michael@0 | 514 | DOMMediaStream* GetWrapper() |
michael@0 | 515 | { |
michael@0 | 516 | NS_ASSERTION(NS_IsMainThread(), "Only use DOMMediaStream on main thread"); |
michael@0 | 517 | return mWrapper; |
michael@0 | 518 | } |
michael@0 | 519 | |
michael@0 | 520 | // Return true if the main thread needs to observe updates from this stream. |
michael@0 | 521 | virtual bool MainThreadNeedsUpdates() const |
michael@0 | 522 | { |
michael@0 | 523 | return true; |
michael@0 | 524 | } |
michael@0 | 525 | |
michael@0 | 526 | virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const; |
michael@0 | 527 | virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const; |
michael@0 | 528 | |
michael@0 | 529 | void SetAudioChannelType(dom::AudioChannel aType) { mAudioChannelType = aType; } |
michael@0 | 530 | |
michael@0 | 531 | protected: |
michael@0 | 532 | virtual void AdvanceTimeVaryingValuesToCurrentTime(GraphTime aCurrentTime, GraphTime aBlockedTime) |
michael@0 | 533 | { |
michael@0 | 534 | mBufferStartTime += aBlockedTime; |
michael@0 | 535 | mGraphUpdateIndices.InsertTimeAtStart(aBlockedTime); |
michael@0 | 536 | mGraphUpdateIndices.AdvanceCurrentTime(aCurrentTime); |
michael@0 | 537 | mExplicitBlockerCount.AdvanceCurrentTime(aCurrentTime); |
michael@0 | 538 | |
michael@0 | 539 | mBuffer.ForgetUpTo(aCurrentTime - mBufferStartTime); |
michael@0 | 540 | } |
michael@0 | 541 | |
michael@0 | 542 | // This state is all initialized on the main thread but |
michael@0 | 543 | // otherwise modified only on the media graph thread. |
michael@0 | 544 | |
michael@0 | 545 | // Buffered data. The start of the buffer corresponds to mBufferStartTime. |
michael@0 | 546 | // Conceptually the buffer contains everything this stream has ever played, |
michael@0 | 547 | // but we forget some prefix of the buffered data to bound the space usage. |
michael@0 | 548 | StreamBuffer mBuffer; |
michael@0 | 549 | // The time when the buffered data could be considered to have started playing. |
michael@0 | 550 | // This increases over time to account for time the stream was blocked before |
michael@0 | 551 | // mCurrentTime. |
michael@0 | 552 | GraphTime mBufferStartTime; |
michael@0 | 553 | |
michael@0 | 554 | // Client-set volume of this stream |
michael@0 | 555 | struct AudioOutput { |
michael@0 | 556 | AudioOutput(void* aKey) : mKey(aKey), mVolume(1.0f) {} |
michael@0 | 557 | void* mKey; |
michael@0 | 558 | float mVolume; |
michael@0 | 559 | }; |
michael@0 | 560 | nsTArray<AudioOutput> mAudioOutputs; |
michael@0 | 561 | nsTArray<nsRefPtr<VideoFrameContainer> > mVideoOutputs; |
michael@0 | 562 | // We record the last played video frame to avoid redundant setting |
michael@0 | 563 | // of the current video frame. |
michael@0 | 564 | VideoFrame mLastPlayedVideoFrame; |
michael@0 | 565 | // The number of times this stream has been explicitly blocked by the control |
michael@0 | 566 | // API, minus the number of times it has been explicitly unblocked. |
michael@0 | 567 | TimeVarying<GraphTime,uint32_t,0> mExplicitBlockerCount; |
michael@0 | 568 | nsTArray<nsRefPtr<MediaStreamListener> > mListeners; |
michael@0 | 569 | nsTArray<MainThreadMediaStreamListener*> mMainThreadListeners; |
michael@0 | 570 | nsTArray<TrackID> mDisabledTrackIDs; |
michael@0 | 571 | |
michael@0 | 572 | // Precomputed blocking status (over GraphTime). |
michael@0 | 573 | // This is only valid between the graph's mCurrentTime and |
michael@0 | 574 | // mStateComputedTime. The stream is considered to have |
michael@0 | 575 | // not been blocked before mCurrentTime (its mBufferStartTime is increased |
michael@0 | 576 | // as necessary to account for that time instead) --- this avoids us having to |
michael@0 | 577 | // record the entire history of the stream's blocking-ness in mBlocked. |
michael@0 | 578 | TimeVarying<GraphTime,bool,5> mBlocked; |
michael@0 | 579 | // Maps graph time to the graph update that affected this stream at that time |
michael@0 | 580 | TimeVarying<GraphTime,int64_t,0> mGraphUpdateIndices; |
michael@0 | 581 | |
michael@0 | 582 | // MediaInputPorts to which this is connected |
michael@0 | 583 | nsTArray<MediaInputPort*> mConsumers; |
michael@0 | 584 | |
michael@0 | 585 | // Where audio output is going. There is one AudioOutputStream per |
michael@0 | 586 | // audio track. |
michael@0 | 587 | struct AudioOutputStream { |
michael@0 | 588 | // When we started audio playback for this track. |
michael@0 | 589 | // Add mStream->GetPosition() to find the current audio playback position. |
michael@0 | 590 | GraphTime mAudioPlaybackStartTime; |
michael@0 | 591 | // Amount of time that we've wanted to play silence because of the stream |
michael@0 | 592 | // blocking. |
michael@0 | 593 | MediaTime mBlockedAudioTime; |
michael@0 | 594 | // Last tick written to the audio output. |
michael@0 | 595 | TrackTicks mLastTickWritten; |
michael@0 | 596 | RefPtr<AudioStream> mStream; |
michael@0 | 597 | TrackID mTrackID; |
michael@0 | 598 | |
michael@0 | 599 | size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const |
michael@0 | 600 | { |
michael@0 | 601 | size_t amount = 0; |
michael@0 | 602 | amount += mStream->SizeOfIncludingThis(aMallocSizeOf); |
michael@0 | 603 | return amount; |
michael@0 | 604 | } |
michael@0 | 605 | }; |
michael@0 | 606 | nsTArray<AudioOutputStream> mAudioOutputStreams; |
michael@0 | 607 | |
michael@0 | 608 | /** |
michael@0 | 609 | * When true, this means the stream will be finished once all |
michael@0 | 610 | * buffered data has been consumed. |
michael@0 | 611 | */ |
michael@0 | 612 | bool mFinished; |
michael@0 | 613 | /** |
michael@0 | 614 | * When true, mFinished is true and we've played all the data in this stream |
michael@0 | 615 | * and fired NotifyFinished notifications. |
michael@0 | 616 | */ |
michael@0 | 617 | bool mNotifiedFinished; |
michael@0 | 618 | /** |
michael@0 | 619 | * When true, the last NotifyBlockingChanged delivered to the listeners |
michael@0 | 620 | * indicated that the stream is blocked. |
michael@0 | 621 | */ |
michael@0 | 622 | bool mNotifiedBlocked; |
michael@0 | 623 | /** |
michael@0 | 624 | * True if some data can be present by this stream if/when it's unblocked. |
michael@0 | 625 | * Set by the stream itself on the MediaStreamGraph thread. Only changes |
michael@0 | 626 | * from false to true once a stream has data, since we won't |
michael@0 | 627 | * unblock it until there's more data. |
michael@0 | 628 | */ |
michael@0 | 629 | bool mHasCurrentData; |
michael@0 | 630 | /** |
michael@0 | 631 | * True if mHasCurrentData is true and we've notified listeners. |
michael@0 | 632 | */ |
michael@0 | 633 | bool mNotifiedHasCurrentData; |
michael@0 | 634 | |
michael@0 | 635 | // Temporary data for ordering streams by dependency graph |
michael@0 | 636 | bool mHasBeenOrdered; |
michael@0 | 637 | bool mIsOnOrderingStack; |
michael@0 | 638 | // True if the stream is being consumed (i.e. has track data being played, |
michael@0 | 639 | // or is feeding into some stream that is being consumed). |
michael@0 | 640 | bool mIsConsumed; |
michael@0 | 641 | // Temporary data for computing blocking status of streams |
michael@0 | 642 | // True if we've added this stream to the set of streams we're computing |
michael@0 | 643 | // blocking for. |
michael@0 | 644 | bool mInBlockingSet; |
michael@0 | 645 | // True if this stream should be blocked in this phase. |
michael@0 | 646 | bool mBlockInThisPhase; |
michael@0 | 647 | |
michael@0 | 648 | // This state is only used on the main thread. |
michael@0 | 649 | DOMMediaStream* mWrapper; |
michael@0 | 650 | // Main-thread views of state |
michael@0 | 651 | StreamTime mMainThreadCurrentTime; |
michael@0 | 652 | bool mMainThreadFinished; |
michael@0 | 653 | bool mMainThreadDestroyed; |
michael@0 | 654 | |
michael@0 | 655 | // Our media stream graph. null if destroyed on the graph thread. |
michael@0 | 656 | MediaStreamGraphImpl* mGraph; |
michael@0 | 657 | |
michael@0 | 658 | dom::AudioChannel mAudioChannelType; |
michael@0 | 659 | }; |
michael@0 | 660 | |
michael@0 | 661 | /** |
michael@0 | 662 | * This is a stream into which a decoder can write audio and video. |
michael@0 | 663 | * |
michael@0 | 664 | * Audio and video can be written on any thread, but you probably want to |
michael@0 | 665 | * always write from the same thread to avoid unexpected interleavings. |
michael@0 | 666 | */ |
michael@0 | 667 | class SourceMediaStream : public MediaStream { |
michael@0 | 668 | public: |
michael@0 | 669 | SourceMediaStream(DOMMediaStream* aWrapper) : |
michael@0 | 670 | MediaStream(aWrapper), |
michael@0 | 671 | mLastConsumptionState(MediaStreamListener::NOT_CONSUMED), |
michael@0 | 672 | mMutex("mozilla::media::SourceMediaStream"), |
michael@0 | 673 | mUpdateKnownTracksTime(0), |
michael@0 | 674 | mPullEnabled(false), |
michael@0 | 675 | mUpdateFinished(false) |
michael@0 | 676 | {} |
michael@0 | 677 | |
michael@0 | 678 | virtual SourceMediaStream* AsSourceStream() { return this; } |
michael@0 | 679 | |
michael@0 | 680 | // Media graph thread only |
michael@0 | 681 | virtual void DestroyImpl(); |
michael@0 | 682 | |
michael@0 | 683 | // Call these on any thread. |
michael@0 | 684 | /** |
michael@0 | 685 | * Enable or disable pulling. When pulling is enabled, NotifyPull |
michael@0 | 686 | * gets called on MediaStreamListeners for this stream during the |
michael@0 | 687 | * MediaStreamGraph control loop. Pulling is initially disabled. |
michael@0 | 688 | * Due to unavoidable race conditions, after a call to SetPullEnabled(false) |
michael@0 | 689 | * it is still possible for a NotifyPull to occur. |
michael@0 | 690 | */ |
michael@0 | 691 | void SetPullEnabled(bool aEnabled); |
michael@0 | 692 | |
michael@0 | 693 | void AddDirectListener(MediaStreamDirectListener* aListener); |
michael@0 | 694 | void RemoveDirectListener(MediaStreamDirectListener* aListener); |
michael@0 | 695 | |
michael@0 | 696 | /** |
michael@0 | 697 | * Add a new track to the stream starting at the given base time (which |
michael@0 | 698 | * must be greater than or equal to the last time passed to |
michael@0 | 699 | * AdvanceKnownTracksTime). Takes ownership of aSegment. aSegment should |
michael@0 | 700 | * contain data starting after aStart. |
michael@0 | 701 | */ |
michael@0 | 702 | void AddTrack(TrackID aID, TrackRate aRate, TrackTicks aStart, |
michael@0 | 703 | MediaSegment* aSegment); |
michael@0 | 704 | |
michael@0 | 705 | /** |
michael@0 | 706 | * Append media data to a track. Ownership of aSegment remains with the caller, |
michael@0 | 707 | * but aSegment is emptied. |
michael@0 | 708 | * Returns false if the data was not appended because no such track exists |
michael@0 | 709 | * or the stream was already finished. |
michael@0 | 710 | */ |
michael@0 | 711 | bool AppendToTrack(TrackID aID, MediaSegment* aSegment, MediaSegment *aRawSegment = nullptr); |
michael@0 | 712 | /** |
michael@0 | 713 | * Returns true if the buffer currently has enough data. |
michael@0 | 714 | * Returns false if there isn't enough data or if no such track exists. |
michael@0 | 715 | */ |
michael@0 | 716 | bool HaveEnoughBuffered(TrackID aID); |
michael@0 | 717 | /** |
michael@0 | 718 | * Ensures that aSignalRunnable will be dispatched to aSignalThread |
michael@0 | 719 | * when we don't have enough buffered data in the track (which could be |
michael@0 | 720 | * immediately). Will dispatch the runnable immediately if the track |
michael@0 | 721 | * does not exist. No op if a runnable is already present for this track. |
michael@0 | 722 | */ |
michael@0 | 723 | void DispatchWhenNotEnoughBuffered(TrackID aID, |
michael@0 | 724 | nsIEventTarget* aSignalThread, nsIRunnable* aSignalRunnable); |
michael@0 | 725 | /** |
michael@0 | 726 | * Indicate that a track has ended. Do not do any more API calls |
michael@0 | 727 | * affecting this track. |
michael@0 | 728 | * Ignored if the track does not exist. |
michael@0 | 729 | */ |
michael@0 | 730 | void EndTrack(TrackID aID); |
michael@0 | 731 | /** |
michael@0 | 732 | * Indicate that no tracks will be added starting before time aKnownTime. |
michael@0 | 733 | * aKnownTime must be >= its value at the last call to AdvanceKnownTracksTime. |
michael@0 | 734 | */ |
michael@0 | 735 | void AdvanceKnownTracksTime(StreamTime aKnownTime); |
michael@0 | 736 | /** |
michael@0 | 737 | * Indicate that this stream should enter the "finished" state. All tracks |
michael@0 | 738 | * must have been ended via EndTrack. The finish time of the stream is |
michael@0 | 739 | * when all tracks have ended. |
michael@0 | 740 | */ |
michael@0 | 741 | void FinishWithLockHeld(); |
michael@0 | 742 | void Finish() |
michael@0 | 743 | { |
michael@0 | 744 | MutexAutoLock lock(mMutex); |
michael@0 | 745 | FinishWithLockHeld(); |
michael@0 | 746 | } |
michael@0 | 747 | |
michael@0 | 748 | // Overriding allows us to hold the mMutex lock while changing the track enable status |
michael@0 | 749 | void SetTrackEnabledImpl(TrackID aTrackID, bool aEnabled) { |
michael@0 | 750 | MutexAutoLock lock(mMutex); |
michael@0 | 751 | MediaStream::SetTrackEnabledImpl(aTrackID, aEnabled); |
michael@0 | 752 | } |
michael@0 | 753 | |
michael@0 | 754 | /** |
michael@0 | 755 | * End all tracks and Finish() this stream. Used to voluntarily revoke access |
michael@0 | 756 | * to a LocalMediaStream. |
michael@0 | 757 | */ |
michael@0 | 758 | void EndAllTrackAndFinish(); |
michael@0 | 759 | |
michael@0 | 760 | /** |
michael@0 | 761 | * Note: Only call from Media Graph thread (eg NotifyPull) |
michael@0 | 762 | * |
michael@0 | 763 | * Returns amount of time (data) that is currently buffered in the track, |
michael@0 | 764 | * assuming playout via PlayAudio or via a TrackUnion - note that |
michael@0 | 765 | * NotifyQueuedTrackChanges() on a SourceMediaStream will occur without |
michael@0 | 766 | * any "extra" buffering, but NotifyQueued TrackChanges() on a TrackUnion |
michael@0 | 767 | * will be buffered. |
michael@0 | 768 | */ |
michael@0 | 769 | TrackTicks GetBufferedTicks(TrackID aID); |
michael@0 | 770 | |
michael@0 | 771 | void RegisterForAudioMixing(); |
michael@0 | 772 | |
michael@0 | 773 | // XXX need a Reset API |
michael@0 | 774 | |
michael@0 | 775 | friend class MediaStreamGraphImpl; |
michael@0 | 776 | |
michael@0 | 777 | protected: |
michael@0 | 778 | struct ThreadAndRunnable { |
michael@0 | 779 | void Init(nsIEventTarget* aTarget, nsIRunnable* aRunnable) |
michael@0 | 780 | { |
michael@0 | 781 | mTarget = aTarget; |
michael@0 | 782 | mRunnable = aRunnable; |
michael@0 | 783 | } |
michael@0 | 784 | |
michael@0 | 785 | nsCOMPtr<nsIEventTarget> mTarget; |
michael@0 | 786 | nsCOMPtr<nsIRunnable> mRunnable; |
michael@0 | 787 | }; |
michael@0 | 788 | enum TrackCommands { |
michael@0 | 789 | TRACK_CREATE = MediaStreamListener::TRACK_EVENT_CREATED, |
michael@0 | 790 | TRACK_END = MediaStreamListener::TRACK_EVENT_ENDED |
michael@0 | 791 | }; |
michael@0 | 792 | /** |
michael@0 | 793 | * Data for each track that hasn't ended. |
michael@0 | 794 | */ |
michael@0 | 795 | struct TrackData { |
michael@0 | 796 | TrackID mID; |
michael@0 | 797 | // Sample rate of the input data. |
michael@0 | 798 | TrackRate mInputRate; |
michael@0 | 799 | // Sample rate of the output data, always equal to the sample rate of the |
michael@0 | 800 | // graph. |
michael@0 | 801 | TrackRate mOutputRate; |
michael@0 | 802 | // Resampler if the rate of the input track does not match the |
michael@0 | 803 | // MediaStreamGraph's. |
michael@0 | 804 | nsAutoRef<SpeexResamplerState> mResampler; |
michael@0 | 805 | TrackTicks mStart; |
michael@0 | 806 | // Each time the track updates are flushed to the media graph thread, |
michael@0 | 807 | // this is cleared. |
michael@0 | 808 | uint32_t mCommands; |
michael@0 | 809 | // Each time the track updates are flushed to the media graph thread, |
michael@0 | 810 | // the segment buffer is emptied. |
michael@0 | 811 | nsAutoPtr<MediaSegment> mData; |
michael@0 | 812 | nsTArray<ThreadAndRunnable> mDispatchWhenNotEnough; |
michael@0 | 813 | bool mHaveEnough; |
michael@0 | 814 | }; |
michael@0 | 815 | |
michael@0 | 816 | bool NeedsMixing(); |
michael@0 | 817 | |
michael@0 | 818 | void ResampleAudioToGraphSampleRate(TrackData* aTrackData, MediaSegment* aSegment); |
michael@0 | 819 | |
michael@0 | 820 | TrackData* FindDataForTrack(TrackID aID) |
michael@0 | 821 | { |
michael@0 | 822 | for (uint32_t i = 0; i < mUpdateTracks.Length(); ++i) { |
michael@0 | 823 | if (mUpdateTracks[i].mID == aID) { |
michael@0 | 824 | return &mUpdateTracks[i]; |
michael@0 | 825 | } |
michael@0 | 826 | } |
michael@0 | 827 | return nullptr; |
michael@0 | 828 | } |
michael@0 | 829 | |
michael@0 | 830 | /** |
michael@0 | 831 | * Notify direct consumers of new data to one of the stream tracks. |
michael@0 | 832 | * The data doesn't have to be resampled (though it may be). This is called |
michael@0 | 833 | * from AppendToTrack on the thread providing the data, and will call |
michael@0 | 834 | * the Listeners on this thread. |
michael@0 | 835 | */ |
michael@0 | 836 | void NotifyDirectConsumers(TrackData *aTrack, |
michael@0 | 837 | MediaSegment *aSegment); |
michael@0 | 838 | |
michael@0 | 839 | // Media stream graph thread only |
michael@0 | 840 | MediaStreamListener::Consumption mLastConsumptionState; |
michael@0 | 841 | |
michael@0 | 842 | // This must be acquired *before* MediaStreamGraphImpl's lock, if they are |
michael@0 | 843 | // held together. |
michael@0 | 844 | Mutex mMutex; |
michael@0 | 845 | // protected by mMutex |
michael@0 | 846 | StreamTime mUpdateKnownTracksTime; |
michael@0 | 847 | nsTArray<TrackData> mUpdateTracks; |
michael@0 | 848 | nsTArray<nsRefPtr<MediaStreamDirectListener> > mDirectListeners; |
michael@0 | 849 | bool mPullEnabled; |
michael@0 | 850 | bool mUpdateFinished; |
michael@0 | 851 | bool mNeedsMixing; |
michael@0 | 852 | }; |
michael@0 | 853 | |
michael@0 | 854 | /** |
michael@0 | 855 | * Represents a connection between a ProcessedMediaStream and one of its |
michael@0 | 856 | * input streams. |
michael@0 | 857 | * We make these refcounted so that stream-related messages with MediaInputPort* |
michael@0 | 858 | * pointers can be sent to the main thread safely. |
michael@0 | 859 | * |
michael@0 | 860 | * When a port's source or destination stream dies, the stream's DestroyImpl |
michael@0 | 861 | * calls MediaInputPort::Disconnect to disconnect the port from |
michael@0 | 862 | * the source and destination streams. |
michael@0 | 863 | * |
michael@0 | 864 | * The lifetimes of MediaInputPort are controlled from the main thread. |
michael@0 | 865 | * The media graph adds a reference to the port. When a MediaInputPort is no |
michael@0 | 866 | * longer needed, main-thread code sends a Destroy message for the port and |
michael@0 | 867 | * clears its reference (the last main-thread reference to the object). When |
michael@0 | 868 | * the Destroy message is processed on the graph manager thread we disconnect |
michael@0 | 869 | * the port and drop the graph's reference, destroying the object. |
michael@0 | 870 | */ |
michael@0 | 871 | class MediaInputPort MOZ_FINAL { |
michael@0 | 872 | private: |
michael@0 | 873 | // Do not call this constructor directly. Instead call aDest->AllocateInputPort. |
michael@0 | 874 | MediaInputPort(MediaStream* aSource, ProcessedMediaStream* aDest, |
michael@0 | 875 | uint32_t aFlags, uint16_t aInputNumber, |
michael@0 | 876 | uint16_t aOutputNumber) |
michael@0 | 877 | : mSource(aSource) |
michael@0 | 878 | , mDest(aDest) |
michael@0 | 879 | , mFlags(aFlags) |
michael@0 | 880 | , mInputNumber(aInputNumber) |
michael@0 | 881 | , mOutputNumber(aOutputNumber) |
michael@0 | 882 | , mGraph(nullptr) |
michael@0 | 883 | { |
michael@0 | 884 | MOZ_COUNT_CTOR(MediaInputPort); |
michael@0 | 885 | } |
michael@0 | 886 | |
michael@0 | 887 | // Private destructor, to discourage deletion outside of Release(): |
michael@0 | 888 | ~MediaInputPort() |
michael@0 | 889 | { |
michael@0 | 890 | MOZ_COUNT_DTOR(MediaInputPort); |
michael@0 | 891 | } |
michael@0 | 892 | |
michael@0 | 893 | public: |
michael@0 | 894 | NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaInputPort) |
michael@0 | 895 | |
michael@0 | 896 | /** |
michael@0 | 897 | * The FLAG_BLOCK_INPUT and FLAG_BLOCK_OUTPUT flags can be used to control |
michael@0 | 898 | * exactly how the blocking statuses of the input and output streams affect |
michael@0 | 899 | * each other. |
michael@0 | 900 | */ |
michael@0 | 901 | enum { |
michael@0 | 902 | // When set, blocking on the output stream forces blocking on the input |
michael@0 | 903 | // stream. |
michael@0 | 904 | FLAG_BLOCK_INPUT = 0x01, |
michael@0 | 905 | // When set, blocking on the input stream forces blocking on the output |
michael@0 | 906 | // stream. |
michael@0 | 907 | FLAG_BLOCK_OUTPUT = 0x02 |
michael@0 | 908 | }; |
michael@0 | 909 | |
michael@0 | 910 | // Called on graph manager thread |
michael@0 | 911 | // Do not call these from outside MediaStreamGraph.cpp! |
michael@0 | 912 | void Init(); |
michael@0 | 913 | // Called during message processing to trigger removal of this stream. |
michael@0 | 914 | void Disconnect(); |
michael@0 | 915 | |
michael@0 | 916 | // Control API |
michael@0 | 917 | /** |
michael@0 | 918 | * Disconnects and destroys the port. The caller must not reference this |
michael@0 | 919 | * object again. |
michael@0 | 920 | */ |
michael@0 | 921 | void Destroy(); |
michael@0 | 922 | |
michael@0 | 923 | // Any thread |
michael@0 | 924 | MediaStream* GetSource() { return mSource; } |
michael@0 | 925 | ProcessedMediaStream* GetDestination() { return mDest; } |
michael@0 | 926 | |
michael@0 | 927 | uint16_t InputNumber() const { return mInputNumber; } |
michael@0 | 928 | uint16_t OutputNumber() const { return mOutputNumber; } |
michael@0 | 929 | |
michael@0 | 930 | // Call on graph manager thread |
michael@0 | 931 | struct InputInterval { |
michael@0 | 932 | GraphTime mStart; |
michael@0 | 933 | GraphTime mEnd; |
michael@0 | 934 | bool mInputIsBlocked; |
michael@0 | 935 | }; |
michael@0 | 936 | // Find the next time interval starting at or after aTime during which |
michael@0 | 937 | // mDest is not blocked and mSource's blocking status does not change. |
michael@0 | 938 | InputInterval GetNextInputInterval(GraphTime aTime); |
michael@0 | 939 | |
michael@0 | 940 | /** |
michael@0 | 941 | * Returns the graph that owns this port. |
michael@0 | 942 | */ |
michael@0 | 943 | MediaStreamGraphImpl* GraphImpl(); |
michael@0 | 944 | MediaStreamGraph* Graph(); |
michael@0 | 945 | /** |
michael@0 | 946 | * Sets the graph that owns this stream. Should only be called once. |
michael@0 | 947 | */ |
michael@0 | 948 | void SetGraphImpl(MediaStreamGraphImpl* aGraph); |
michael@0 | 949 | |
michael@0 | 950 | size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const |
michael@0 | 951 | { |
michael@0 | 952 | size_t amount = 0; |
michael@0 | 953 | |
michael@0 | 954 | // Not owned: |
michael@0 | 955 | // - mSource |
michael@0 | 956 | // - mDest |
michael@0 | 957 | // - mGraph |
michael@0 | 958 | return amount; |
michael@0 | 959 | } |
michael@0 | 960 | |
michael@0 | 961 | size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const |
michael@0 | 962 | { |
michael@0 | 963 | return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); |
michael@0 | 964 | } |
michael@0 | 965 | |
michael@0 | 966 | private: |
michael@0 | 967 | friend class MediaStreamGraphImpl; |
michael@0 | 968 | friend class MediaStream; |
michael@0 | 969 | friend class ProcessedMediaStream; |
michael@0 | 970 | // Never modified after Init() |
michael@0 | 971 | MediaStream* mSource; |
michael@0 | 972 | ProcessedMediaStream* mDest; |
michael@0 | 973 | uint32_t mFlags; |
michael@0 | 974 | // The input and output numbers are optional, and are currently only used by |
michael@0 | 975 | // Web Audio. |
michael@0 | 976 | const uint16_t mInputNumber; |
michael@0 | 977 | const uint16_t mOutputNumber; |
michael@0 | 978 | |
michael@0 | 979 | // Our media stream graph |
michael@0 | 980 | MediaStreamGraphImpl* mGraph; |
michael@0 | 981 | }; |
michael@0 | 982 | |
michael@0 | 983 | /** |
michael@0 | 984 | * This stream processes zero or more input streams in parallel to produce |
michael@0 | 985 | * its output. The details of how the output is produced are handled by |
michael@0 | 986 | * subclasses overriding the ProcessInput method. |
michael@0 | 987 | */ |
michael@0 | 988 | class ProcessedMediaStream : public MediaStream { |
michael@0 | 989 | public: |
michael@0 | 990 | ProcessedMediaStream(DOMMediaStream* aWrapper) |
michael@0 | 991 | : MediaStream(aWrapper), mAutofinish(false), mInCycle(false) |
michael@0 | 992 | {} |
michael@0 | 993 | |
michael@0 | 994 | // Control API. |
michael@0 | 995 | /** |
michael@0 | 996 | * Allocates a new input port attached to source aStream. |
michael@0 | 997 | * This stream can be removed by calling MediaInputPort::Remove(). |
michael@0 | 998 | */ |
michael@0 | 999 | already_AddRefed<MediaInputPort> AllocateInputPort(MediaStream* aStream, |
michael@0 | 1000 | uint32_t aFlags = 0, |
michael@0 | 1001 | uint16_t aInputNumber = 0, |
michael@0 | 1002 | uint16_t aOutputNumber = 0); |
michael@0 | 1003 | /** |
michael@0 | 1004 | * Force this stream into the finished state. |
michael@0 | 1005 | */ |
michael@0 | 1006 | void Finish(); |
michael@0 | 1007 | /** |
michael@0 | 1008 | * Set the autofinish flag on this stream (defaults to false). When this flag |
michael@0 | 1009 | * is set, and all input streams are in the finished state (including if there |
michael@0 | 1010 | * are no input streams), this stream automatically enters the finished state. |
michael@0 | 1011 | */ |
michael@0 | 1012 | void SetAutofinish(bool aAutofinish); |
michael@0 | 1013 | |
michael@0 | 1014 | virtual ProcessedMediaStream* AsProcessedStream() { return this; } |
michael@0 | 1015 | |
michael@0 | 1016 | friend class MediaStreamGraphImpl; |
michael@0 | 1017 | |
michael@0 | 1018 | // Do not call these from outside MediaStreamGraph.cpp! |
michael@0 | 1019 | virtual void AddInput(MediaInputPort* aPort); |
michael@0 | 1020 | virtual void RemoveInput(MediaInputPort* aPort) |
michael@0 | 1021 | { |
michael@0 | 1022 | mInputs.RemoveElement(aPort); |
michael@0 | 1023 | } |
michael@0 | 1024 | bool HasInputPort(MediaInputPort* aPort) |
michael@0 | 1025 | { |
michael@0 | 1026 | return mInputs.Contains(aPort); |
michael@0 | 1027 | } |
michael@0 | 1028 | uint32_t InputPortCount() |
michael@0 | 1029 | { |
michael@0 | 1030 | return mInputs.Length(); |
michael@0 | 1031 | } |
michael@0 | 1032 | virtual void DestroyImpl(); |
michael@0 | 1033 | /** |
michael@0 | 1034 | * This gets called after we've computed the blocking states for all |
michael@0 | 1035 | * streams (mBlocked is up to date up to mStateComputedTime). |
michael@0 | 1036 | * Also, we've produced output for all streams up to this one. If this stream |
michael@0 | 1037 | * is not in a cycle, then all its source streams have produced data. |
michael@0 | 1038 | * Generate output from aFrom to aTo. |
michael@0 | 1039 | * This will be called on streams that have finished. Most stream types should |
michael@0 | 1040 | * just return immediately if IsFinishedOnGraphThread(), but some may wish to |
michael@0 | 1041 | * update internal state (see AudioNodeStream). |
michael@0 | 1042 | * ProcessInput is allowed to call FinishOnGraphThread only if ALLOW_FINISH |
michael@0 | 1043 | * is in aFlags. (This flag will be set when aTo >= mStateComputedTime, i.e. |
michael@0 | 1044 | * when we've producing the last block of data we need to produce.) Otherwise |
michael@0 | 1045 | * we can get into a situation where we've determined the stream should not |
michael@0 | 1046 | * block before mStateComputedTime, but the stream finishes before |
michael@0 | 1047 | * mStateComputedTime, violating the invariant that finished streams are blocked. |
michael@0 | 1048 | */ |
michael@0 | 1049 | enum { |
michael@0 | 1050 | ALLOW_FINISH = 0x01 |
michael@0 | 1051 | }; |
michael@0 | 1052 | virtual void ProcessInput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags) = 0; |
michael@0 | 1053 | void SetAutofinishImpl(bool aAutofinish) { mAutofinish = aAutofinish; } |
michael@0 | 1054 | |
michael@0 | 1055 | /** |
michael@0 | 1056 | * Forward SetTrackEnabled() to the input MediaStream(s) and translate the ID |
michael@0 | 1057 | */ |
michael@0 | 1058 | virtual void ForwardTrackEnabled(TrackID aOutputID, bool aEnabled) {}; |
michael@0 | 1059 | |
michael@0 | 1060 | bool InCycle() const { return mInCycle; } |
michael@0 | 1061 | |
michael@0 | 1062 | virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE |
michael@0 | 1063 | { |
michael@0 | 1064 | size_t amount = MediaStream::SizeOfExcludingThis(aMallocSizeOf); |
michael@0 | 1065 | // Not owned: |
michael@0 | 1066 | // - mInputs elements |
michael@0 | 1067 | amount += mInputs.SizeOfExcludingThis(aMallocSizeOf); |
michael@0 | 1068 | return amount; |
michael@0 | 1069 | } |
michael@0 | 1070 | |
michael@0 | 1071 | virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE |
michael@0 | 1072 | { |
michael@0 | 1073 | return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); |
michael@0 | 1074 | } |
michael@0 | 1075 | |
michael@0 | 1076 | protected: |
michael@0 | 1077 | // This state is all accessed only on the media graph thread. |
michael@0 | 1078 | |
michael@0 | 1079 | // The list of all inputs that are currently enabled or waiting to be enabled. |
michael@0 | 1080 | nsTArray<MediaInputPort*> mInputs; |
michael@0 | 1081 | bool mAutofinish; |
michael@0 | 1082 | // True if and only if this stream is in a cycle. |
michael@0 | 1083 | // Updated by MediaStreamGraphImpl::UpdateStreamOrder. |
michael@0 | 1084 | bool mInCycle; |
michael@0 | 1085 | }; |
michael@0 | 1086 | |
michael@0 | 1087 | /** |
michael@0 | 1088 | * Initially, at least, we will have a singleton MediaStreamGraph per |
michael@0 | 1089 | * process. Each OfflineAudioContext object creates its own MediaStreamGraph |
michael@0 | 1090 | * object too. |
michael@0 | 1091 | */ |
michael@0 | 1092 | class MediaStreamGraph { |
michael@0 | 1093 | public: |
michael@0 | 1094 | // We ensure that the graph current time advances in multiples of |
michael@0 | 1095 | // IdealAudioBlockSize()/AudioStream::PreferredSampleRate(). A stream that |
michael@0 | 1096 | // never blocks and has a track with the ideal audio rate will produce audio |
michael@0 | 1097 | // in multiples of the block size. |
michael@0 | 1098 | |
michael@0 | 1099 | // Main thread only |
michael@0 | 1100 | static MediaStreamGraph* GetInstance(); |
michael@0 | 1101 | static MediaStreamGraph* CreateNonRealtimeInstance(TrackRate aSampleRate); |
michael@0 | 1102 | // Idempotent |
michael@0 | 1103 | static void DestroyNonRealtimeInstance(MediaStreamGraph* aGraph); |
michael@0 | 1104 | |
michael@0 | 1105 | // Control API. |
michael@0 | 1106 | /** |
michael@0 | 1107 | * Create a stream that a media decoder (or some other source of |
michael@0 | 1108 | * media data, such as a camera) can write to. |
michael@0 | 1109 | */ |
michael@0 | 1110 | SourceMediaStream* CreateSourceStream(DOMMediaStream* aWrapper); |
michael@0 | 1111 | /** |
michael@0 | 1112 | * Create a stream that will form the union of the tracks of its input |
michael@0 | 1113 | * streams. |
michael@0 | 1114 | * A TrackUnionStream contains all the tracks of all its input streams. |
michael@0 | 1115 | * Adding a new input stream makes that stream's tracks immediately appear as new |
michael@0 | 1116 | * tracks starting at the time the input stream was added. |
michael@0 | 1117 | * Removing an input stream makes the output tracks corresponding to the |
michael@0 | 1118 | * removed tracks immediately end. |
michael@0 | 1119 | * For each added track, the track ID of the output track is the track ID |
michael@0 | 1120 | * of the input track or one plus the maximum ID of all previously added |
michael@0 | 1121 | * tracks, whichever is greater. |
michael@0 | 1122 | * TODO at some point we will probably need to add API to select |
michael@0 | 1123 | * particular tracks of each input stream. |
michael@0 | 1124 | */ |
michael@0 | 1125 | ProcessedMediaStream* CreateTrackUnionStream(DOMMediaStream* aWrapper); |
michael@0 | 1126 | // Internal AudioNodeStreams can only pass their output to another |
michael@0 | 1127 | // AudioNode, whereas external AudioNodeStreams can pass their output |
michael@0 | 1128 | // to an nsAudioStream for playback. |
michael@0 | 1129 | enum AudioNodeStreamKind { SOURCE_STREAM, INTERNAL_STREAM, EXTERNAL_STREAM }; |
michael@0 | 1130 | /** |
michael@0 | 1131 | * Create a stream that will process audio for an AudioNode. |
michael@0 | 1132 | * Takes ownership of aEngine. aSampleRate is the sampling rate used |
michael@0 | 1133 | * for the stream. If 0 is passed, the sampling rate of the engine's |
michael@0 | 1134 | * node will get used. |
michael@0 | 1135 | */ |
michael@0 | 1136 | AudioNodeStream* CreateAudioNodeStream(AudioNodeEngine* aEngine, |
michael@0 | 1137 | AudioNodeStreamKind aKind, |
michael@0 | 1138 | TrackRate aSampleRate = 0); |
michael@0 | 1139 | |
michael@0 | 1140 | AudioNodeExternalInputStream* |
michael@0 | 1141 | CreateAudioNodeExternalInputStream(AudioNodeEngine* aEngine, |
michael@0 | 1142 | TrackRate aSampleRate = 0); |
michael@0 | 1143 | |
michael@0 | 1144 | bool IsNonRealtime() const; |
michael@0 | 1145 | /** |
michael@0 | 1146 | * Start processing non-realtime for a specific number of ticks. |
michael@0 | 1147 | */ |
michael@0 | 1148 | void StartNonRealtimeProcessing(TrackRate aRate, uint32_t aTicksToProcess); |
michael@0 | 1149 | |
michael@0 | 1150 | /** |
michael@0 | 1151 | * Media graph thread only. |
michael@0 | 1152 | * Dispatches a runnable that will run on the main thread after all |
michael@0 | 1153 | * main-thread stream state has been next updated. |
michael@0 | 1154 | * Should only be called during MediaStreamListener callbacks or during |
michael@0 | 1155 | * ProcessedMediaStream::ProcessInput(). |
michael@0 | 1156 | */ |
michael@0 | 1157 | void DispatchToMainThreadAfterStreamStateUpdate(already_AddRefed<nsIRunnable> aRunnable) |
michael@0 | 1158 | { |
michael@0 | 1159 | *mPendingUpdateRunnables.AppendElement() = aRunnable; |
michael@0 | 1160 | } |
michael@0 | 1161 | |
michael@0 | 1162 | protected: |
michael@0 | 1163 | MediaStreamGraph() |
michael@0 | 1164 | : mNextGraphUpdateIndex(1) |
michael@0 | 1165 | { |
michael@0 | 1166 | MOZ_COUNT_CTOR(MediaStreamGraph); |
michael@0 | 1167 | } |
michael@0 | 1168 | virtual ~MediaStreamGraph() |
michael@0 | 1169 | { |
michael@0 | 1170 | MOZ_COUNT_DTOR(MediaStreamGraph); |
michael@0 | 1171 | } |
michael@0 | 1172 | |
michael@0 | 1173 | // Media graph thread only |
michael@0 | 1174 | nsTArray<nsCOMPtr<nsIRunnable> > mPendingUpdateRunnables; |
michael@0 | 1175 | |
michael@0 | 1176 | // Main thread only |
michael@0 | 1177 | // The number of updates we have sent to the media graph thread + 1. |
michael@0 | 1178 | // We start this at 1 just to ensure that 0 is usable as a special value. |
michael@0 | 1179 | int64_t mNextGraphUpdateIndex; |
michael@0 | 1180 | }; |
michael@0 | 1181 | |
michael@0 | 1182 | } |
michael@0 | 1183 | |
michael@0 | 1184 | #endif /* MOZILLA_MEDIASTREAMGRAPH_H_ */ |