/src/mozilla-central/dom/media/MediaStreamGraph.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/ |
2 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
3 | | * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
4 | | * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
5 | | |
6 | | #include "MediaStreamGraphImpl.h" |
7 | | #include "mozilla/MathAlgorithms.h" |
8 | | #include "mozilla/Unused.h" |
9 | | |
10 | | #include "AudioSegment.h" |
11 | | #include "VideoSegment.h" |
12 | | #include "nsContentUtils.h" |
13 | | #include "nsIObserver.h" |
14 | | #include "nsPrintfCString.h" |
15 | | #include "nsServiceManagerUtils.h" |
16 | | #include "prerror.h" |
17 | | #include "mozilla/Logging.h" |
18 | | #include "mozilla/Attributes.h" |
19 | | #include "TrackUnionStream.h" |
20 | | #include "ImageContainer.h" |
21 | | #include "AudioCaptureStream.h" |
22 | | #include "AudioNodeStream.h" |
23 | | #include "AudioNodeExternalInputStream.h" |
24 | | #include "MediaStreamListener.h" |
25 | | #include "MediaStreamVideoSink.h" |
26 | | #include "mozilla/dom/BaseAudioContextBinding.h" |
27 | | #include "mozilla/media/MediaUtils.h" |
28 | | #include <algorithm> |
29 | | #include "GeckoProfiler.h" |
30 | | #include "VideoFrameContainer.h" |
31 | | #include "mozilla/AbstractThread.h" |
32 | | #include "mozilla/Unused.h" |
33 | | #include "mtransport/runnable_utils.h" |
34 | | #include "VideoUtils.h" |
35 | | #include "Tracing.h" |
36 | | |
37 | | #include "webaudio/blink/DenormalDisabler.h" |
38 | | #include "webaudio/blink/HRTFDatabaseLoader.h" |
39 | | |
40 | | using namespace mozilla::layers; |
41 | | using namespace mozilla::dom; |
42 | | using namespace mozilla::gfx; |
43 | | using namespace mozilla::media; |
44 | | |
45 | | mozilla::AsyncLogger gMSGTraceLogger("MSGTracing"); |
46 | | |
47 | | namespace mozilla { |
48 | | |
49 | | LazyLogModule gMediaStreamGraphLog("MediaStreamGraph"); |
50 | | #ifdef LOG |
51 | | #undef LOG |
52 | | #endif // LOG |
53 | 0 | #define LOG(type, msg) MOZ_LOG(gMediaStreamGraphLog, type, msg) |
54 | | |
55 | | enum SourceMediaStream::TrackCommands : uint32_t { |
56 | | TRACK_CREATE = TrackEventCommand::TRACK_EVENT_CREATED, |
57 | | TRACK_END = TrackEventCommand::TRACK_EVENT_ENDED, |
58 | | TRACK_UNUSED = TrackEventCommand::TRACK_EVENT_UNUSED, |
59 | | }; |
60 | | |
61 | | /** |
62 | | * A hash table containing the graph instances, one per document. |
63 | | * |
64 | | * The key is a hash of nsPIDOMWindowInner, see `WindowToHash`. |
65 | | */ |
66 | | static nsDataHashtable<nsUint32HashKey, MediaStreamGraphImpl*> gGraphs; |
67 | | |
68 | | MediaStreamGraphImpl::~MediaStreamGraphImpl() |
69 | 0 | { |
70 | 0 | MOZ_ASSERT(mStreams.IsEmpty() && mSuspendedStreams.IsEmpty(), |
71 | 0 | "All streams should have been destroyed by messages from the main thread"); |
72 | 0 | LOG(LogLevel::Debug, ("MediaStreamGraph %p destroyed", this)); |
73 | 0 | LOG(LogLevel::Debug, ("MediaStreamGraphImpl::~MediaStreamGraphImpl")); |
74 | 0 |
|
75 | 0 | #ifdef TRACING |
76 | 0 | gMSGTraceLogger.Stop(); |
77 | 0 | #endif |
78 | 0 | } |
79 | | |
80 | | void |
81 | | MediaStreamGraphImpl::AddStreamGraphThread(MediaStream* aStream) |
82 | 0 | { |
83 | 0 | MOZ_ASSERT(OnGraphThreadOrNotRunning()); |
84 | 0 | aStream->mTracksStartTime = mProcessedTime; |
85 | 0 |
|
86 | 0 | if (aStream->AsSourceStream()) { |
87 | 0 | SourceMediaStream* source = aStream->AsSourceStream(); |
88 | 0 | TimeStamp currentTimeStamp = CurrentDriver()->GetCurrentTimeStamp(); |
89 | 0 | TimeStamp processedTimeStamp = currentTimeStamp + |
90 | 0 | TimeDuration::FromSeconds(MediaTimeToSeconds(mProcessedTime - IterationEnd())); |
91 | 0 | source->SetStreamTracksStartTimeStamp(processedTimeStamp); |
92 | 0 | } |
93 | 0 |
|
94 | 0 | if (aStream->IsSuspended()) { |
95 | 0 | mSuspendedStreams.AppendElement(aStream); |
96 | 0 | LOG(LogLevel::Debug, |
97 | 0 | ("%p: Adding media stream %p, in the suspended stream array", |
98 | 0 | this, aStream)); |
99 | 0 | } else { |
100 | 0 | mStreams.AppendElement(aStream); |
101 | 0 | LOG(LogLevel::Debug, |
102 | 0 | ("%p: Adding media stream %p, count %zu", |
103 | 0 | this, |
104 | 0 | aStream, |
105 | 0 | mStreams.Length())); |
106 | 0 | } |
107 | 0 |
|
108 | 0 | SetStreamOrderDirty(); |
109 | 0 | } |
110 | | |
111 | | void |
112 | | MediaStreamGraphImpl::RemoveStreamGraphThread(MediaStream* aStream) |
113 | 0 | { |
114 | 0 | MOZ_ASSERT(OnGraphThreadOrNotRunning()); |
115 | 0 | // Remove references in mStreamUpdates before we allow aStream to die. |
116 | 0 | // Pending updates are not needed (since the main thread has already given |
117 | 0 | // up the stream) so we will just drop them. |
118 | 0 | { |
119 | 0 | MonitorAutoLock lock(mMonitor); |
120 | 0 | for (uint32_t i = 0; i < mStreamUpdates.Length(); ++i) { |
121 | 0 | if (mStreamUpdates[i].mStream == aStream) { |
122 | 0 | mStreamUpdates[i].mStream = nullptr; |
123 | 0 | } |
124 | 0 | } |
125 | 0 | } |
126 | 0 |
|
127 | 0 | // Ensure that mFirstCycleBreaker and mMixer are updated when necessary. |
128 | 0 | SetStreamOrderDirty(); |
129 | 0 |
|
130 | 0 | if (aStream->IsSuspended()) { |
131 | 0 | mSuspendedStreams.RemoveElement(aStream); |
132 | 0 | } else { |
133 | 0 | mStreams.RemoveElement(aStream); |
134 | 0 | } |
135 | 0 |
|
136 | 0 | LOG(LogLevel::Debug, |
137 | 0 | ("%p: Removed media stream %p, count %zu", |
138 | 0 | this, |
139 | 0 | aStream, |
140 | 0 | mStreams.Length())); |
141 | 0 |
|
142 | 0 | NS_RELEASE(aStream); // probably destroying it |
143 | 0 | } |
144 | | |
145 | | StreamTime |
146 | | MediaStreamGraphImpl::GraphTimeToStreamTimeWithBlocking(const MediaStream* aStream, |
147 | | GraphTime aTime) const |
148 | 0 | { |
149 | 0 | MOZ_ASSERT(aTime <= mStateComputedTime, |
150 | 0 | "Don't ask about times where we haven't made blocking decisions yet"); |
151 | 0 | return std::max<StreamTime>(0, |
152 | 0 | std::min(aTime, aStream->mStartBlocking) - aStream->mTracksStartTime); |
153 | 0 | } |
154 | | |
155 | | GraphTime |
156 | | MediaStreamGraphImpl::IterationEnd() const |
157 | 0 | { |
158 | 0 | MOZ_ASSERT(OnGraphThreadOrNotRunning()); |
159 | 0 | return CurrentDriver()->IterationEnd(); |
160 | 0 | } |
161 | | |
162 | | void |
163 | | MediaStreamGraphImpl::UpdateCurrentTimeForStreams(GraphTime aPrevCurrentTime) |
164 | 0 | { |
165 | 0 | MOZ_ASSERT(OnGraphThread()); |
166 | 0 | for (MediaStream* stream : AllStreams()) { |
167 | 0 | bool isAnyBlocked = stream->mStartBlocking < mStateComputedTime; |
168 | 0 | bool isAnyUnblocked = stream->mStartBlocking > aPrevCurrentTime; |
169 | 0 |
|
170 | 0 | // Calculate blocked time and fire Blocked/Unblocked events |
171 | 0 | GraphTime blockedTime = mStateComputedTime - stream->mStartBlocking; |
172 | 0 | NS_ASSERTION(blockedTime >= 0, "Error in blocking time"); |
173 | 0 | stream->AdvanceTimeVaryingValuesToCurrentTime(mStateComputedTime, |
174 | 0 | blockedTime); |
175 | 0 | LOG(LogLevel::Verbose, |
176 | 0 | ("%p: MediaStream %p bufferStartTime=%f blockedTime=%f", |
177 | 0 | this, |
178 | 0 | stream, |
179 | 0 | MediaTimeToSeconds(stream->mTracksStartTime), |
180 | 0 | MediaTimeToSeconds(blockedTime))); |
181 | 0 | stream->mStartBlocking = mStateComputedTime; |
182 | 0 |
|
183 | 0 | if (isAnyUnblocked && stream->mNotifiedBlocked) { |
184 | 0 | for (uint32_t j = 0; j < stream->mListeners.Length(); ++j) { |
185 | 0 | MediaStreamListener* l = stream->mListeners[j]; |
186 | 0 | l->NotifyBlockingChanged(this, MediaStreamListener::UNBLOCKED); |
187 | 0 | } |
188 | 0 | stream->mNotifiedBlocked = false; |
189 | 0 | } |
190 | 0 | if (isAnyBlocked && !stream->mNotifiedBlocked) { |
191 | 0 | for (uint32_t j = 0; j < stream->mListeners.Length(); ++j) { |
192 | 0 | MediaStreamListener* l = stream->mListeners[j]; |
193 | 0 | l->NotifyBlockingChanged(this, MediaStreamListener::BLOCKED); |
194 | 0 | } |
195 | 0 | stream->mNotifiedBlocked = true; |
196 | 0 | } |
197 | 0 |
|
198 | 0 | if (isAnyUnblocked) { |
199 | 0 | NS_ASSERTION(!stream->mNotifiedFinished, |
200 | 0 | "Shouldn't have already notified of finish *and* have output!"); |
201 | 0 | for (uint32_t j = 0; j < stream->mListeners.Length(); ++j) { |
202 | 0 | MediaStreamListener* l = stream->mListeners[j]; |
203 | 0 | l->NotifyOutput(this, mProcessedTime); |
204 | 0 | } |
205 | 0 | } |
206 | 0 |
|
207 | 0 | // The stream is fully finished when all of its track data has been played |
208 | 0 | // out. |
209 | 0 | if (stream->mFinished && !stream->mNotifiedFinished && |
210 | 0 | mProcessedTime >= |
211 | 0 | stream->StreamTimeToGraphTime(stream->GetStreamTracks().GetAllTracksEnd())) { |
212 | 0 | stream->mNotifiedFinished = true; |
213 | 0 | SetStreamOrderDirty(); |
214 | 0 | for (uint32_t j = 0; j < stream->mListeners.Length(); ++j) { |
215 | 0 | MediaStreamListener* l = stream->mListeners[j]; |
216 | 0 | l->NotifyEvent(this, MediaStreamGraphEvent::EVENT_FINISHED); |
217 | 0 | } |
218 | 0 | } |
219 | 0 | } |
220 | 0 | } |
221 | | |
222 | | template<typename C, typename Chunk> |
223 | | void |
224 | | MediaStreamGraphImpl::ProcessChunkMetadataForInterval(MediaStream* aStream, |
225 | | TrackID aTrackID, |
226 | | C& aSegment, |
227 | | StreamTime aStart, |
228 | | StreamTime aEnd) |
229 | 0 | { |
230 | 0 | MOZ_ASSERT(OnGraphThreadOrNotRunning()); |
231 | 0 | MOZ_ASSERT(aStream); |
232 | 0 | MOZ_ASSERT(IsTrackIDExplicit(aTrackID)); |
233 | 0 |
|
234 | 0 | StreamTime offset = 0; |
235 | 0 | for (typename C::ConstChunkIterator chunk(aSegment); |
236 | 0 | !chunk.IsEnded(); chunk.Next()) { |
237 | 0 | if (offset >= aEnd) { |
238 | 0 | break; |
239 | 0 | } |
240 | 0 | offset += chunk->GetDuration(); |
241 | 0 | if (chunk->IsNull() || offset < aStart) { |
242 | 0 | continue; |
243 | 0 | } |
244 | 0 | const PrincipalHandle& principalHandle = chunk->GetPrincipalHandle(); |
245 | 0 | if (principalHandle != aSegment.GetLastPrincipalHandle()) { |
246 | 0 | aSegment.SetLastPrincipalHandle(principalHandle); |
247 | 0 | LOG(LogLevel::Debug, |
248 | 0 | ("%p: MediaStream %p track %d, principalHandle " |
249 | 0 | "changed in %sChunk with duration %lld", |
250 | 0 | this, |
251 | 0 | aStream, |
252 | 0 | aTrackID, |
253 | 0 | aSegment.GetType() == MediaSegment::AUDIO ? "Audio" : "Video", |
254 | 0 | (long long)chunk->GetDuration())); |
255 | 0 | for (const TrackBound<MediaStreamTrackListener>& listener : |
256 | 0 | aStream->mTrackListeners) { |
257 | 0 | if (listener.mTrackID == aTrackID) { |
258 | 0 | listener.mListener->NotifyPrincipalHandleChanged(this, principalHandle); |
259 | 0 | } |
260 | 0 | } |
261 | 0 | } |
262 | 0 | } |
263 | 0 | } Unexecuted instantiation: void mozilla::MediaStreamGraphImpl::ProcessChunkMetadataForInterval<mozilla::AudioSegment, mozilla::AudioChunk>(mozilla::MediaStream*, int, mozilla::AudioSegment&, long, long) Unexecuted instantiation: void mozilla::MediaStreamGraphImpl::ProcessChunkMetadataForInterval<mozilla::VideoSegment, mozilla::VideoChunk>(mozilla::MediaStream*, int, mozilla::VideoSegment&, long, long) |
264 | | |
265 | | void |
266 | | MediaStreamGraphImpl::ProcessChunkMetadata(GraphTime aPrevCurrentTime) |
267 | 0 | { |
268 | 0 | MOZ_ASSERT(OnGraphThreadOrNotRunning()); |
269 | 0 | for (MediaStream* stream : AllStreams()) { |
270 | 0 | StreamTime iterationStart = stream->GraphTimeToStreamTime(aPrevCurrentTime); |
271 | 0 | StreamTime iterationEnd = stream->GraphTimeToStreamTime(mProcessedTime); |
272 | 0 | for (StreamTracks::TrackIter tracks(stream->mTracks); |
273 | 0 | !tracks.IsEnded(); tracks.Next()) { |
274 | 0 | MediaSegment* segment = tracks->GetSegment(); |
275 | 0 | if (!segment) { |
276 | 0 | continue; |
277 | 0 | } |
278 | 0 | if (tracks->GetType() == MediaSegment::AUDIO) { |
279 | 0 | AudioSegment* audio = static_cast<AudioSegment*>(segment); |
280 | 0 | ProcessChunkMetadataForInterval<AudioSegment, AudioChunk>( |
281 | 0 | stream, tracks->GetID(), *audio, iterationStart, iterationEnd); |
282 | 0 | } else if (tracks->GetType() == MediaSegment::VIDEO) { |
283 | 0 | VideoSegment* video = static_cast<VideoSegment*>(segment); |
284 | 0 | ProcessChunkMetadataForInterval<VideoSegment, VideoChunk>( |
285 | 0 | stream, tracks->GetID(), *video, iterationStart, iterationEnd); |
286 | 0 | } else { |
287 | 0 | MOZ_CRASH("Unknown track type"); |
288 | 0 | } |
289 | 0 | } |
290 | 0 | } |
291 | 0 | } |
292 | | |
293 | | GraphTime |
294 | | MediaStreamGraphImpl::WillUnderrun(MediaStream* aStream, |
295 | | GraphTime aEndBlockingDecisions) |
296 | 0 | { |
297 | 0 | // Finished streams can't underrun. ProcessedMediaStreams also can't cause |
298 | 0 | // underrun currently, since we'll always be able to produce data for them |
299 | 0 | // unless they block on some other stream. |
300 | 0 | if (aStream->mFinished || aStream->AsProcessedStream()) { |
301 | 0 | return aEndBlockingDecisions; |
302 | 0 | } |
303 | 0 | // This stream isn't finished or suspended. We don't need to call |
304 | 0 | // StreamTimeToGraphTime since an underrun is the only thing that can block |
305 | 0 | // it. |
306 | 0 | GraphTime bufferEnd = aStream->GetTracksEnd() + aStream->mTracksStartTime; |
307 | | #ifdef DEBUG |
308 | | if (bufferEnd < mProcessedTime) { |
309 | | LOG(LogLevel::Error, |
310 | | ("%p: MediaStream %p underrun, " |
311 | | "bufferEnd %f < mProcessedTime %f (%" PRId64 " < %" PRId64 |
312 | | "), Streamtime %" PRId64, |
313 | | this, |
314 | | aStream, |
315 | | MediaTimeToSeconds(bufferEnd), |
316 | | MediaTimeToSeconds(mProcessedTime), |
317 | | bufferEnd, |
318 | | mProcessedTime, |
319 | | aStream->GetTracksEnd())); |
320 | | aStream->DumpTrackInfo(); |
321 | | NS_ASSERTION(bufferEnd >= mProcessedTime, "Buffer underran"); |
322 | | } |
323 | | #endif |
324 | | return std::min(bufferEnd, aEndBlockingDecisions); |
325 | 0 | } |
326 | | |
327 | | namespace { |
328 | | // Value of mCycleMarker for unvisited streams in cycle detection. |
329 | | const uint32_t NOT_VISITED = UINT32_MAX; |
330 | | // Value of mCycleMarker for ordered streams in muted cycles. |
331 | | const uint32_t IN_MUTED_CYCLE = 1; |
332 | | } // namespace |
333 | | |
334 | | bool |
335 | | MediaStreamGraphImpl::AudioTrackPresent() |
336 | 0 | { |
337 | 0 | MOZ_ASSERT(OnGraphThreadOrNotRunning()); |
338 | 0 |
|
339 | 0 | bool audioTrackPresent = false; |
340 | 0 | for (MediaStream* stream : mStreams) { |
341 | 0 | if (stream->AsAudioNodeStream()) { |
342 | 0 | audioTrackPresent = true; |
343 | 0 | break; |
344 | 0 | } |
345 | 0 | |
346 | 0 | if (!StreamTracks::TrackIter( |
347 | 0 | stream->GetStreamTracks(), |
348 | 0 | MediaSegment::AUDIO |
349 | 0 | ).IsEnded()) { |
350 | 0 | audioTrackPresent = true; |
351 | 0 | break; |
352 | 0 | } |
353 | 0 | |
354 | 0 | if (SourceMediaStream* source = stream->AsSourceStream()) { |
355 | 0 | audioTrackPresent = source->HasPendingAudioTrack(); |
356 | 0 | } |
357 | 0 |
|
358 | 0 | if (audioTrackPresent) { |
359 | 0 | break; |
360 | 0 | } |
361 | 0 | } |
362 | 0 |
|
363 | 0 | // XXX For some reason, there are race conditions when starting an audio input where |
364 | 0 | // we find no active audio tracks. In any case, if we have an active audio input we |
365 | 0 | // should not allow a switch back to a SystemClockDriver |
366 | 0 | if (!audioTrackPresent && mInputDeviceUsers.Count() != 0) { |
367 | 0 | NS_WARNING("No audio tracks, but full-duplex audio is enabled!!!!!"); |
368 | 0 | audioTrackPresent = true; |
369 | 0 | } |
370 | 0 |
|
371 | 0 | return audioTrackPresent; |
372 | 0 | } |
373 | | |
374 | | void |
375 | | MediaStreamGraphImpl::UpdateStreamOrder() |
376 | 0 | { |
377 | 0 | MOZ_ASSERT(OnGraphThread()); |
378 | 0 | bool audioTrackPresent = AudioTrackPresent(); |
379 | 0 |
|
380 | 0 | // Note that this looks for any audio streams, input or output, and switches to a |
381 | 0 | // SystemClockDriver if there are none. However, if another is already pending, let that |
382 | 0 | // switch happen. |
383 | 0 |
|
384 | 0 | if (!audioTrackPresent && mRealtime && |
385 | 0 | CurrentDriver()->AsAudioCallbackDriver()) { |
386 | 0 | MonitorAutoLock mon(mMonitor); |
387 | 0 | if (CurrentDriver()->AsAudioCallbackDriver()->IsStarted() && |
388 | 0 | !(CurrentDriver()->Switching())) { |
389 | 0 | if (LifecycleStateRef() == LIFECYCLE_RUNNING) { |
390 | 0 | SystemClockDriver* driver = new SystemClockDriver(this); |
391 | 0 | CurrentDriver()->SwitchAtNextIteration(driver); |
392 | 0 | } |
393 | 0 | } |
394 | 0 | } |
395 | 0 |
|
396 | 0 | bool switching = false; |
397 | 0 | { |
398 | 0 | MonitorAutoLock mon(mMonitor); |
399 | 0 | switching = CurrentDriver()->Switching(); |
400 | 0 | } |
401 | 0 |
|
402 | 0 | if (audioTrackPresent && mRealtime && |
403 | 0 | !CurrentDriver()->AsAudioCallbackDriver() && |
404 | 0 | !switching) { |
405 | 0 | MonitorAutoLock mon(mMonitor); |
406 | 0 | if (LifecycleStateRef() == LIFECYCLE_RUNNING) { |
407 | 0 | AudioCallbackDriver* driver = new AudioCallbackDriver(this, AudioInputChannelCount()); |
408 | 0 | CurrentDriver()->SwitchAtNextIteration(driver); |
409 | 0 | } |
410 | 0 | } |
411 | 0 |
|
412 | 0 | if (!mStreamOrderDirty) { |
413 | 0 | return; |
414 | 0 | } |
415 | 0 | |
416 | 0 | mStreamOrderDirty = false; |
417 | 0 |
|
418 | 0 | // The algorithm for finding cycles is based on Tim Leslie's iterative |
419 | 0 | // implementation [1][2] of Pearce's variant [3] of Tarjan's strongly |
420 | 0 | // connected components (SCC) algorithm. There are variations (a) to |
421 | 0 | // distinguish whether streams in SCCs of size 1 are in a cycle and (b) to |
422 | 0 | // re-run the algorithm over SCCs with breaks at DelayNodes. |
423 | 0 | // |
424 | 0 | // [1] http://www.timl.id.au/?p=327 |
425 | 0 | // [2] https://github.com/scipy/scipy/blob/e2c502fca/scipy/sparse/csgraph/_traversal.pyx#L582 |
426 | 0 | // [3] http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.102.1707 |
427 | 0 | // |
428 | 0 | // There are two stacks. One for the depth-first search (DFS), |
429 | 0 | mozilla::LinkedList<MediaStream> dfsStack; |
430 | 0 | // and another for streams popped from the DFS stack, but still being |
431 | 0 | // considered as part of SCCs involving streams on the stack. |
432 | 0 | mozilla::LinkedList<MediaStream> sccStack; |
433 | 0 |
|
434 | 0 | // An index into mStreams for the next stream found with no unsatisfied |
435 | 0 | // upstream dependencies. |
436 | 0 | uint32_t orderedStreamCount = 0; |
437 | 0 |
|
438 | 0 | for (uint32_t i = 0; i < mStreams.Length(); ++i) { |
439 | 0 | MediaStream* s = mStreams[i]; |
440 | 0 | ProcessedMediaStream* ps = s->AsProcessedStream(); |
441 | 0 | if (ps) { |
442 | 0 | // The dfsStack initially contains a list of all processed streams in |
443 | 0 | // unchanged order. |
444 | 0 | dfsStack.insertBack(s); |
445 | 0 | ps->mCycleMarker = NOT_VISITED; |
446 | 0 | } else { |
447 | 0 | // SourceMediaStreams have no inputs and so can be ordered now. |
448 | 0 | mStreams[orderedStreamCount] = s; |
449 | 0 | ++orderedStreamCount; |
450 | 0 | } |
451 | 0 | } |
452 | 0 |
|
453 | 0 | // mNextStackMarker corresponds to "index" in Tarjan's algorithm. It is a |
454 | 0 | // counter to label mCycleMarker on the next visited stream in the DFS |
455 | 0 | // uniquely in the set of visited streams that are still being considered. |
456 | 0 | // |
457 | 0 | // In this implementation, the counter descends so that the values are |
458 | 0 | // strictly greater than the values that mCycleMarker takes when the stream |
459 | 0 | // has been ordered (0 or IN_MUTED_CYCLE). |
460 | 0 | // |
461 | 0 | // Each new stream labelled, as the DFS searches upstream, receives a value |
462 | 0 | // less than those used for all other streams being considered. |
463 | 0 | uint32_t nextStackMarker = NOT_VISITED - 1; |
464 | 0 | // Reset list of DelayNodes in cycles stored at the tail of mStreams. |
465 | 0 | mFirstCycleBreaker = mStreams.Length(); |
466 | 0 |
|
467 | 0 | // Rearrange dfsStack order as required to DFS upstream and pop streams |
468 | 0 | // in processing order to place in mStreams. |
469 | 0 | while (auto ps = static_cast<ProcessedMediaStream*>(dfsStack.getFirst())) { |
470 | 0 | const auto& inputs = ps->mInputs; |
471 | 0 | MOZ_ASSERT(ps->AsProcessedStream()); |
472 | 0 | if (ps->mCycleMarker == NOT_VISITED) { |
473 | 0 | // Record the position on the visited stack, so that any searches |
474 | 0 | // finding this stream again know how much of the stack is in the cycle. |
475 | 0 | ps->mCycleMarker = nextStackMarker; |
476 | 0 | --nextStackMarker; |
477 | 0 | // Not-visited input streams should be processed first. |
478 | 0 | // SourceMediaStreams have already been ordered. |
479 | 0 | for (uint32_t i = inputs.Length(); i--; ) { |
480 | 0 | if (inputs[i]->mSource->IsSuspended()) { |
481 | 0 | continue; |
482 | 0 | } |
483 | 0 | auto input = inputs[i]->mSource->AsProcessedStream(); |
484 | 0 | if (input && input->mCycleMarker == NOT_VISITED) { |
485 | 0 | // It can be that this stream has an input which is from a suspended |
486 | 0 | // AudioContext. |
487 | 0 | if (input->isInList()) { |
488 | 0 | input->remove(); |
489 | 0 | dfsStack.insertFront(input); |
490 | 0 | } |
491 | 0 | } |
492 | 0 | } |
493 | 0 | continue; |
494 | 0 | } |
495 | 0 |
|
496 | 0 | // Returning from DFS. Pop from dfsStack. |
497 | 0 | ps->remove(); |
498 | 0 |
|
499 | 0 | // cycleStackMarker keeps track of the highest marker value on any |
500 | 0 | // upstream stream, if any, found receiving input, directly or indirectly, |
501 | 0 | // from the visited stack (and so from |ps|, making a cycle). In a |
502 | 0 | // variation from Tarjan's SCC algorithm, this does not include |ps| |
503 | 0 | // unless it is part of the cycle. |
504 | 0 | uint32_t cycleStackMarker = 0; |
505 | 0 | for (uint32_t i = inputs.Length(); i--; ) { |
506 | 0 | if (inputs[i]->mSource->IsSuspended()) { |
507 | 0 | continue; |
508 | 0 | } |
509 | 0 | auto input = inputs[i]->mSource->AsProcessedStream(); |
510 | 0 | if (input) { |
511 | 0 | cycleStackMarker = std::max(cycleStackMarker, input->mCycleMarker); |
512 | 0 | } |
513 | 0 | } |
514 | 0 |
|
515 | 0 | if (cycleStackMarker <= IN_MUTED_CYCLE) { |
516 | 0 | // All inputs have been ordered and their stack markers have been removed. |
517 | 0 | // This stream is not part of a cycle. It can be processed next. |
518 | 0 | ps->mCycleMarker = 0; |
519 | 0 | mStreams[orderedStreamCount] = ps; |
520 | 0 | ++orderedStreamCount; |
521 | 0 | continue; |
522 | 0 | } |
523 | 0 | |
524 | 0 | // A cycle has been found. Record this stream for ordering when all |
525 | 0 | // streams in this SCC have been popped from the DFS stack. |
526 | 0 | sccStack.insertFront(ps); |
527 | 0 |
|
528 | 0 | if (cycleStackMarker > ps->mCycleMarker) { |
529 | 0 | // Cycles have been found that involve streams that remain on the stack. |
530 | 0 | // Leave mCycleMarker indicating the most downstream (last) stream on |
531 | 0 | // the stack known to be part of this SCC. In this way, any searches on |
532 | 0 | // other paths that find |ps| will know (without having to traverse from |
533 | 0 | // this stream again) that they are part of this SCC (i.e. part of an |
534 | 0 | // intersecting cycle). |
535 | 0 | ps->mCycleMarker = cycleStackMarker; |
536 | 0 | continue; |
537 | 0 | } |
538 | 0 | |
539 | 0 | // |ps| is the root of an SCC involving no other streams on dfsStack, the |
540 | 0 | // complete SCC has been recorded, and streams in this SCC are part of at |
541 | 0 | // least one cycle. |
542 | 0 | MOZ_ASSERT(cycleStackMarker == ps->mCycleMarker); |
543 | 0 | // If there are DelayNodes in this SCC, then they may break the cycles. |
544 | 0 | bool haveDelayNode = false; |
545 | 0 | auto next = sccStack.getFirst(); |
546 | 0 | // Streams in this SCC are identified by mCycleMarker <= cycleStackMarker. |
547 | 0 | // (There may be other streams later in sccStack from other incompletely |
548 | 0 | // searched SCCs, involving streams still on dfsStack.) |
549 | 0 | // |
550 | 0 | // DelayNodes in cycles must behave differently from those not in cycles, |
551 | 0 | // so all DelayNodes in the SCC must be identified. |
552 | 0 | while (next && static_cast<ProcessedMediaStream*>(next)-> |
553 | 0 | mCycleMarker <= cycleStackMarker) { |
554 | 0 | auto ns = next->AsAudioNodeStream(); |
555 | 0 | // Get next before perhaps removing from list below. |
556 | 0 | next = next->getNext(); |
557 | 0 | if (ns && ns->Engine()->AsDelayNodeEngine()) { |
558 | 0 | haveDelayNode = true; |
559 | 0 | // DelayNodes break cycles by producing their output in a |
560 | 0 | // preprocessing phase; they do not need to be ordered before their |
561 | 0 | // consumers. Order them at the tail of mStreams so that they can be |
562 | 0 | // handled specially. Do so now, so that DFS ignores them. |
563 | 0 | ns->remove(); |
564 | 0 | ns->mCycleMarker = 0; |
565 | 0 | --mFirstCycleBreaker; |
566 | 0 | mStreams[mFirstCycleBreaker] = ns; |
567 | 0 | } |
568 | 0 | } |
569 | 0 | auto after_scc = next; |
570 | 0 | while ((next = sccStack.getFirst()) != after_scc) { |
571 | 0 | next->remove(); |
572 | 0 | auto removed = static_cast<ProcessedMediaStream*>(next); |
573 | 0 | if (haveDelayNode) { |
574 | 0 | // Return streams to the DFS stack again (to order and detect cycles |
575 | 0 | // without delayNodes). Any of these streams that are still inputs |
576 | 0 | // for streams on the visited stack must be returned to the front of |
577 | 0 | // the stack to be ordered before their dependents. We know that none |
578 | 0 | // of these streams need input from streams on the visited stack, so |
579 | 0 | // they can all be searched and ordered before the current stack head |
580 | 0 | // is popped. |
581 | 0 | removed->mCycleMarker = NOT_VISITED; |
582 | 0 | dfsStack.insertFront(removed); |
583 | 0 | } else { |
584 | 0 | // Streams in cycles without any DelayNodes must be muted, and so do |
585 | 0 | // not need input and can be ordered now. They must be ordered before |
586 | 0 | // their consumers so that their muted output is available. |
587 | 0 | removed->mCycleMarker = IN_MUTED_CYCLE; |
588 | 0 | mStreams[orderedStreamCount] = removed; |
589 | 0 | ++orderedStreamCount; |
590 | 0 | } |
591 | 0 | } |
592 | 0 | } |
593 | 0 |
|
594 | 0 | MOZ_ASSERT(orderedStreamCount == mFirstCycleBreaker); |
595 | 0 | } |
596 | | |
597 | | void |
598 | | MediaStreamGraphImpl::NotifyHasCurrentData(MediaStream* aStream) |
599 | 0 | { |
600 | 0 | if (!aStream->mNotifiedHasCurrentData && aStream->mHasCurrentData) { |
601 | 0 | for (uint32_t j = 0; j < aStream->mListeners.Length(); ++j) { |
602 | 0 | MediaStreamListener* l = aStream->mListeners[j]; |
603 | 0 | l->NotifyHasCurrentData(this); |
604 | 0 | } |
605 | 0 | aStream->mNotifiedHasCurrentData = true; |
606 | 0 | } |
607 | 0 | } |
608 | | |
609 | | void |
610 | | MediaStreamGraphImpl::CreateOrDestroyAudioStreams(MediaStream* aStream) |
611 | 0 | { |
612 | 0 | MOZ_ASSERT(OnGraphThread()); |
613 | 0 | MOZ_ASSERT(mRealtime, "Should only attempt to create audio streams in real-time mode"); |
614 | 0 |
|
615 | 0 | if (aStream->mAudioOutputs.IsEmpty()) { |
616 | 0 | aStream->mAudioOutputStreams.Clear(); |
617 | 0 | return; |
618 | 0 | } |
619 | 0 | |
620 | 0 | if (!aStream->GetStreamTracks().GetAndResetTracksDirty() && |
621 | 0 | !aStream->mAudioOutputStreams.IsEmpty()) { |
622 | 0 | return; |
623 | 0 | } |
624 | 0 | |
625 | 0 | LOG(LogLevel::Debug, |
626 | 0 | ("%p: Updating AudioOutputStreams for MediaStream %p", this, aStream)); |
627 | 0 |
|
628 | 0 | AutoTArray<bool,2> audioOutputStreamsFound; |
629 | 0 | for (uint32_t i = 0; i < aStream->mAudioOutputStreams.Length(); ++i) { |
630 | 0 | audioOutputStreamsFound.AppendElement(false); |
631 | 0 | } |
632 | 0 |
|
633 | 0 | for (StreamTracks::TrackIter tracks(aStream->GetStreamTracks(), MediaSegment::AUDIO); |
634 | 0 | !tracks.IsEnded(); tracks.Next()) { |
635 | 0 | uint32_t i; |
636 | 0 | for (i = 0; i < audioOutputStreamsFound.Length(); ++i) { |
637 | 0 | if (aStream->mAudioOutputStreams[i].mTrackID == tracks->GetID()) { |
638 | 0 | break; |
639 | 0 | } |
640 | 0 | } |
641 | 0 | if (i < audioOutputStreamsFound.Length()) { |
642 | 0 | audioOutputStreamsFound[i] = true; |
643 | 0 | } else { |
644 | 0 | MediaStream::AudioOutputStream* audioOutputStream = |
645 | 0 | aStream->mAudioOutputStreams.AppendElement(); |
646 | 0 | audioOutputStream->mAudioPlaybackStartTime = mProcessedTime; |
647 | 0 | audioOutputStream->mBlockedAudioTime = 0; |
648 | 0 | audioOutputStream->mLastTickWritten = 0; |
649 | 0 | audioOutputStream->mTrackID = tracks->GetID(); |
650 | 0 |
|
651 | 0 | bool switching = false; |
652 | 0 |
|
653 | 0 | { |
654 | 0 | MonitorAutoLock lock(mMonitor); |
655 | 0 | switching = CurrentDriver()->Switching(); |
656 | 0 | } |
657 | 0 |
|
658 | 0 | if (!CurrentDriver()->AsAudioCallbackDriver() && |
659 | 0 | !switching) { |
660 | 0 | MonitorAutoLock mon(mMonitor); |
661 | 0 | if (LifecycleStateRef() == LIFECYCLE_RUNNING) { |
662 | 0 | AudioCallbackDriver* driver = new AudioCallbackDriver(this, AudioInputChannelCount()); |
663 | 0 | CurrentDriver()->SwitchAtNextIteration(driver); |
664 | 0 | } |
665 | 0 | } |
666 | 0 | } |
667 | 0 | } |
668 | 0 |
|
669 | 0 | for (int32_t i = audioOutputStreamsFound.Length() - 1; i >= 0; --i) { |
670 | 0 | if (!audioOutputStreamsFound[i]) { |
671 | 0 | aStream->mAudioOutputStreams.RemoveElementAt(i); |
672 | 0 | } |
673 | 0 | } |
674 | 0 | } |
675 | | |
676 | | StreamTime |
677 | | MediaStreamGraphImpl::PlayAudio(MediaStream* aStream) |
678 | 0 | { |
679 | 0 | MOZ_ASSERT(OnGraphThread()); |
680 | 0 | MOZ_ASSERT(mRealtime, "Should only attempt to play audio in realtime mode"); |
681 | 0 |
|
682 | 0 | float volume = 0.0f; |
683 | 0 | for (uint32_t i = 0; i < aStream->mAudioOutputs.Length(); ++i) { |
684 | 0 | volume += aStream->mAudioOutputs[i].mVolume * mGlobalVolume; |
685 | 0 | } |
686 | 0 |
|
687 | 0 | StreamTime ticksWritten = 0; |
688 | 0 |
|
689 | 0 | for (uint32_t i = 0; i < aStream->mAudioOutputStreams.Length(); ++i) { |
690 | 0 | ticksWritten = 0; |
691 | 0 |
|
692 | 0 | MediaStream::AudioOutputStream& audioOutput = aStream->mAudioOutputStreams[i]; |
693 | 0 | StreamTracks::Track* track = aStream->mTracks.FindTrack(audioOutput.mTrackID); |
694 | 0 | AudioSegment* audio = track->Get<AudioSegment>(); |
695 | 0 | AudioSegment output; |
696 | 0 |
|
697 | 0 | StreamTime offset = aStream->GraphTimeToStreamTime(mProcessedTime); |
698 | 0 |
|
699 | 0 | // We don't update aStream->mTracksStartTime here to account for time spent |
700 | 0 | // blocked. Instead, we'll update it in UpdateCurrentTimeForStreams after |
701 | 0 | // the blocked period has completed. But we do need to make sure we play |
702 | 0 | // from the right offsets in the stream buffer, even if we've already |
703 | 0 | // written silence for some amount of blocked time after the current time. |
704 | 0 | GraphTime t = mProcessedTime; |
705 | 0 | while (t < mStateComputedTime) { |
706 | 0 | bool blocked = t >= aStream->mStartBlocking; |
707 | 0 | GraphTime end = blocked ? mStateComputedTime : aStream->mStartBlocking; |
708 | 0 | NS_ASSERTION(end <= mStateComputedTime, "mStartBlocking is wrong!"); |
709 | 0 |
|
710 | 0 | // Check how many ticks of sound we can provide if we are blocked some |
711 | 0 | // time in the middle of this cycle. |
712 | 0 | StreamTime toWrite = end - t; |
713 | 0 |
|
714 | 0 | if (blocked) { |
715 | 0 | output.InsertNullDataAtStart(toWrite); |
716 | 0 | ticksWritten += toWrite; |
717 | 0 | LOG(LogLevel::Verbose, |
718 | 0 | ("%p: MediaStream %p writing %" PRId64 " blocking-silence samples for " |
719 | 0 | "%f to %f (%" PRId64 " to %" PRId64 ")", |
720 | 0 | this, |
721 | 0 | aStream, |
722 | 0 | toWrite, |
723 | 0 | MediaTimeToSeconds(t), |
724 | 0 | MediaTimeToSeconds(end), |
725 | 0 | offset, |
726 | 0 | offset + toWrite)); |
727 | 0 | } else { |
728 | 0 | StreamTime endTicksNeeded = offset + toWrite; |
729 | 0 | StreamTime endTicksAvailable = audio->GetDuration(); |
730 | 0 |
|
731 | 0 | if (endTicksNeeded <= endTicksAvailable) { |
732 | 0 | LOG(LogLevel::Verbose, |
733 | 0 | ("%p: MediaStream %p writing %" PRId64 " samples for %f to %f " |
734 | 0 | "(samples %" PRId64 " to %" PRId64 ")", |
735 | 0 | this, |
736 | 0 | aStream, |
737 | 0 | toWrite, |
738 | 0 | MediaTimeToSeconds(t), |
739 | 0 | MediaTimeToSeconds(end), |
740 | 0 | offset, |
741 | 0 | endTicksNeeded)); |
742 | 0 | output.AppendSlice(*audio, offset, endTicksNeeded); |
743 | 0 | ticksWritten += toWrite; |
744 | 0 | offset = endTicksNeeded; |
745 | 0 | } else { |
746 | 0 | // MOZ_ASSERT(track->IsEnded(), "Not enough data, and track not ended."); |
747 | 0 | // If we are at the end of the track, maybe write the remaining |
748 | 0 | // samples, and pad with/output silence. |
749 | 0 | if (endTicksNeeded > endTicksAvailable && |
750 | 0 | offset < endTicksAvailable) { |
751 | 0 | output.AppendSlice(*audio, offset, endTicksAvailable); |
752 | 0 | LOG(LogLevel::Verbose, |
753 | 0 | ("%p: MediaStream %p writing %" PRId64 " samples for %f to %f " |
754 | 0 | "(samples %" PRId64 " to %" PRId64 ")", |
755 | 0 | this, |
756 | 0 | aStream, |
757 | 0 | toWrite, |
758 | 0 | MediaTimeToSeconds(t), |
759 | 0 | MediaTimeToSeconds(end), |
760 | 0 | offset, |
761 | 0 | endTicksNeeded)); |
762 | 0 | uint32_t available = endTicksAvailable - offset; |
763 | 0 | ticksWritten += available; |
764 | 0 | toWrite -= available; |
765 | 0 | offset = endTicksAvailable; |
766 | 0 | } |
767 | 0 | output.AppendNullData(toWrite); |
768 | 0 | LOG(LogLevel::Verbose, |
769 | 0 | ("%p MediaStream %p writing %" PRId64 " padding slsamples for %f to " |
770 | 0 | "%f (samples %" PRId64 " to %" PRId64 ")", |
771 | 0 | this, |
772 | 0 | aStream, |
773 | 0 | toWrite, |
774 | 0 | MediaTimeToSeconds(t), |
775 | 0 | MediaTimeToSeconds(end), |
776 | 0 | offset, |
777 | 0 | endTicksNeeded)); |
778 | 0 | ticksWritten += toWrite; |
779 | 0 | } |
780 | 0 | output.ApplyVolume(volume); |
781 | 0 | } |
782 | 0 | t = end; |
783 | 0 | } |
784 | 0 | audioOutput.mLastTickWritten = offset; |
785 | 0 |
|
786 | 0 | // Need unique id for stream & track - and we want it to match the inserter |
787 | 0 | output.WriteTo(LATENCY_STREAM_ID(aStream, track->GetID()), |
788 | 0 | mMixer, |
789 | 0 | AudioOutputChannelCount(), |
790 | 0 | mSampleRate); |
791 | 0 | } |
792 | 0 | return ticksWritten; |
793 | 0 | } |
794 | | |
795 | | void |
796 | | MediaStreamGraphImpl::OpenAudioInputImpl(CubebUtils::AudioDeviceID aID, |
797 | | AudioDataListener* aListener) |
798 | 0 | { |
799 | 0 | MOZ_ASSERT(OnGraphThread()); |
800 | 0 | // Only allow one device per MSG (hence, per document), but allow opening a |
801 | 0 | // device multiple times |
802 | 0 | nsTArray<RefPtr<AudioDataListener>>& listeners = mInputDeviceUsers.GetOrInsert(aID); |
803 | 0 | if (listeners.IsEmpty() && mInputDeviceUsers.Count() > 1) { |
804 | 0 | // We don't support opening multiple input device in a graph for now. |
805 | 0 | listeners.RemoveElement(aID); |
806 | 0 | return; |
807 | 0 | } |
808 | 0 | |
809 | 0 | MOZ_ASSERT(!listeners.Contains(aListener), "Don't add a listener twice."); |
810 | 0 |
|
811 | 0 | listeners.AppendElement(aListener); |
812 | 0 |
|
813 | 0 | if (listeners.Length() == 1) { // first open for this device |
814 | 0 | mInputDeviceID = aID; |
815 | 0 | // Switch Drivers since we're adding input (to input-only or full-duplex) |
816 | 0 | MonitorAutoLock mon(mMonitor); |
817 | 0 | if (LifecycleStateRef() == LIFECYCLE_RUNNING) { |
818 | 0 | AudioCallbackDriver* driver = new AudioCallbackDriver(this, AudioInputChannelCount()); |
819 | 0 | LOG( |
820 | 0 | LogLevel::Debug, |
821 | 0 | ("%p OpenAudioInput: starting new AudioCallbackDriver(input) %p", this, driver)); |
822 | 0 | CurrentDriver()->SwitchAtNextIteration(driver); |
823 | 0 | } else { |
824 | 0 | LOG(LogLevel::Error, ("OpenAudioInput in shutdown!")); |
825 | 0 | MOZ_ASSERT_UNREACHABLE("Can't open cubeb inputs in shutdown"); |
826 | 0 | } |
827 | 0 | } |
828 | 0 | } |
829 | | |
830 | | nsresult |
831 | | MediaStreamGraphImpl::OpenAudioInput(CubebUtils::AudioDeviceID aID, |
832 | | AudioDataListener* aListener) |
833 | 0 | { |
834 | 0 | // So, so, so annoying. Can't AppendMessage except on Mainthread |
835 | 0 | if (!NS_IsMainThread()) { |
836 | 0 | RefPtr<nsIRunnable> runnable = |
837 | 0 | WrapRunnable(this, |
838 | 0 | &MediaStreamGraphImpl::OpenAudioInput, |
839 | 0 | aID, |
840 | 0 | RefPtr<AudioDataListener>(aListener)); |
841 | 0 | mAbstractMainThread->Dispatch(runnable.forget()); |
842 | 0 | return NS_OK; |
843 | 0 | } |
844 | 0 | class Message : public ControlMessage { |
845 | 0 | public: |
846 | 0 | Message(MediaStreamGraphImpl *aGraph, CubebUtils::AudioDeviceID aID, |
847 | 0 | AudioDataListener* aListener) : |
848 | 0 | ControlMessage(nullptr), mGraph(aGraph), mID(aID), mListener(aListener) {} |
849 | 0 | void Run() override |
850 | 0 | { |
851 | 0 | mGraph->OpenAudioInputImpl(mID, mListener); |
852 | 0 | } |
853 | 0 | MediaStreamGraphImpl *mGraph; |
854 | 0 | CubebUtils::AudioDeviceID mID; |
855 | 0 | RefPtr<AudioDataListener> mListener; |
856 | 0 | }; |
857 | 0 | // XXX Check not destroyed! |
858 | 0 | this->AppendMessage(MakeUnique<Message>(this, aID, aListener)); |
859 | 0 | return NS_OK; |
860 | 0 | } |
861 | | |
862 | | void |
863 | | MediaStreamGraphImpl::CloseAudioInputImpl(Maybe<CubebUtils::AudioDeviceID>& aID, AudioDataListener* aListener) |
864 | 0 | { |
865 | 0 | MOZ_ASSERT(OnGraphThreadOrNotRunning()); |
866 | 0 | // It is possible to not know the ID here, find it first. |
867 | 0 | if (aID.isNothing()) { |
868 | 0 | for (auto iter = mInputDeviceUsers.Iter(); !iter.Done(); iter.Next()) { |
869 | 0 | if (iter.Data().Contains(aListener)) { |
870 | 0 | aID = Some(iter.Key()); |
871 | 0 | } |
872 | 0 | } |
873 | 0 | MOZ_ASSERT(aID.isSome(), "Closing an audio input that was not opened."); |
874 | 0 | } |
875 | 0 |
|
876 | 0 | nsTArray<RefPtr<AudioDataListener>>* listeners = mInputDeviceUsers.GetValue(aID.value()); |
877 | 0 |
|
878 | 0 | MOZ_ASSERT(listeners); |
879 | 0 | DebugOnly<bool> wasPresent = listeners->RemoveElement(aListener); |
880 | 0 | MOZ_ASSERT(wasPresent); |
881 | 0 |
|
882 | 0 | // Breaks the cycle between the MSG and the listener. |
883 | 0 | aListener->Disconnect(this); |
884 | 0 |
|
885 | 0 | if (!listeners->IsEmpty()) { |
886 | 0 | // There is still a consumer for this audio input device |
887 | 0 | return; |
888 | 0 | } |
889 | 0 | |
890 | 0 | mInputDeviceID = nullptr; // reset to default |
891 | 0 | mInputDeviceUsers.Remove(aID.value()); |
892 | 0 |
|
893 | 0 | // Switch Drivers since we're adding or removing an input (to nothing/system or output only) |
894 | 0 | bool audioTrackPresent = AudioTrackPresent(); |
895 | 0 |
|
896 | 0 | MonitorAutoLock mon(mMonitor); |
897 | 0 | if (LifecycleStateRef() == LIFECYCLE_RUNNING) { |
898 | 0 | GraphDriver* driver; |
899 | 0 | if (audioTrackPresent) { |
900 | 0 | // We still have audio output |
901 | 0 | LOG(LogLevel::Debug, ("%p: CloseInput: output present (AudioCallback)", this)); |
902 | 0 |
|
903 | 0 | driver = new AudioCallbackDriver(this, AudioInputChannelCount()); |
904 | 0 | CurrentDriver()->SwitchAtNextIteration(driver); |
905 | 0 | } else if (CurrentDriver()->AsAudioCallbackDriver()) { |
906 | 0 | LOG(LogLevel::Debug, |
907 | 0 | ("%p: CloseInput: no output present (SystemClockCallback)", this)); |
908 | 0 |
|
909 | 0 | driver = new SystemClockDriver(this); |
910 | 0 | CurrentDriver()->SwitchAtNextIteration(driver); |
911 | 0 | } // else SystemClockDriver->SystemClockDriver, no switch |
912 | 0 | } |
913 | 0 | } |
914 | | |
915 | | void |
916 | | MediaStreamGraphImpl::CloseAudioInput(Maybe<CubebUtils::AudioDeviceID>& aID, AudioDataListener* aListener) |
917 | 0 | { |
918 | 0 | // So, so, so annoying. Can't AppendMessage except on Mainthread |
919 | 0 | if (!NS_IsMainThread()) { |
920 | 0 | RefPtr<nsIRunnable> runnable = |
921 | 0 | WrapRunnable(this, |
922 | 0 | &MediaStreamGraphImpl::CloseAudioInput, |
923 | 0 | aID, |
924 | 0 | RefPtr<AudioDataListener>(aListener)); |
925 | 0 | mAbstractMainThread->Dispatch(runnable.forget()); |
926 | 0 | return; |
927 | 0 | } |
928 | 0 | class Message : public ControlMessage { |
929 | 0 | public: |
930 | 0 | Message(MediaStreamGraphImpl *aGraph, |
931 | 0 | Maybe<CubebUtils::AudioDeviceID>& aID, |
932 | 0 | AudioDataListener* aListener) |
933 | 0 | : ControlMessage(nullptr), |
934 | 0 | mGraph(aGraph), |
935 | 0 | mID(aID), |
936 | 0 | mListener(aListener) |
937 | 0 | {} |
938 | 0 | void Run() override |
939 | 0 | { |
940 | 0 | mGraph->CloseAudioInputImpl(mID, mListener); |
941 | 0 | } |
942 | 0 | MediaStreamGraphImpl *mGraph; |
943 | 0 | Maybe<CubebUtils::AudioDeviceID> mID; |
944 | 0 | RefPtr<AudioDataListener> mListener; |
945 | 0 | }; |
946 | 0 | this->AppendMessage(MakeUnique<Message>(this, aID, aListener)); |
947 | 0 | } |
948 | | |
949 | | // All AudioInput listeners get the same speaker data (at least for now). |
950 | | void |
951 | | MediaStreamGraphImpl::NotifyOutputData(AudioDataValue* aBuffer, size_t aFrames, |
952 | | TrackRate aRate, uint32_t aChannels) |
953 | 0 | { |
954 | | #ifdef ANDROID |
955 | | // On Android, mInputDeviceID is always null and represents the default |
956 | | // device. |
957 | | // The absence of an input consumer is enough to know we need to bail out |
958 | | // here. |
959 | | if (!mInputDeviceUsers.GetValue(mInputDeviceID)) { |
960 | | return; |
961 | | } |
962 | | #else |
963 | 0 | if (!mInputDeviceID) { |
964 | 0 | return; |
965 | 0 | } |
966 | 0 | #endif |
967 | 0 | // When/if we decide to support multiple input devices per graph, this needs |
968 | 0 | // to loop over them. |
969 | 0 | nsTArray<RefPtr<AudioDataListener>>* listeners = mInputDeviceUsers.GetValue(mInputDeviceID); |
970 | 0 | MOZ_ASSERT(listeners); |
971 | 0 | for (auto& listener : *listeners) { |
972 | 0 | listener->NotifyOutputData(this, aBuffer, aFrames, aRate, aChannels); |
973 | 0 | } |
974 | 0 | } |
975 | | |
976 | | void |
977 | | MediaStreamGraphImpl::NotifyInputData(const AudioDataValue* aBuffer, size_t aFrames, |
978 | | TrackRate aRate, uint32_t aChannels) |
979 | 0 | { |
980 | | #ifdef ANDROID |
981 | | if (!mInputDeviceUsers.GetValue(mInputDeviceID)) { |
982 | | return; |
983 | | } |
984 | | #else |
985 | | #ifdef DEBUG |
986 | | { |
987 | | MonitorAutoLock lock(mMonitor); |
988 | | // Either we have an audio input device, or we just removed the audio input |
989 | | // this iteration, and we're switching back to an output-only driver next |
990 | | // iteration. |
991 | | MOZ_ASSERT(mInputDeviceID || CurrentDriver()->Switching()); |
992 | | } |
993 | | #endif |
994 | 0 | if (!mInputDeviceID) { |
995 | 0 | return; |
996 | 0 | } |
997 | 0 | #endif |
998 | 0 | nsTArray<RefPtr<AudioDataListener>>* listeners = mInputDeviceUsers.GetValue(mInputDeviceID); |
999 | 0 | MOZ_ASSERT(listeners); |
1000 | 0 | for (auto& listener : *listeners) { |
1001 | 0 | listener->NotifyInputData(this, aBuffer, aFrames, aRate, aChannels); |
1002 | 0 | } |
1003 | 0 | } |
1004 | | |
1005 | | void MediaStreamGraphImpl::DeviceChangedImpl() |
1006 | 0 | { |
1007 | 0 | MOZ_ASSERT(OnGraphThread()); |
1008 | 0 |
|
1009 | | #ifdef ANDROID |
1010 | | if (!mInputDeviceUsers.GetValue(mInputDeviceID)) { |
1011 | | return; |
1012 | | } |
1013 | | #else |
1014 | 0 | if (!mInputDeviceID) { |
1015 | 0 | return; |
1016 | 0 | } |
1017 | 0 | #endif |
1018 | 0 | |
1019 | 0 | nsTArray<RefPtr<AudioDataListener>>* listeners = |
1020 | 0 | mInputDeviceUsers.GetValue(mInputDeviceID); |
1021 | 0 | for (auto& listener : *listeners) { |
1022 | 0 | listener->DeviceChanged(this); |
1023 | 0 | } |
1024 | 0 | } |
1025 | | |
1026 | | void MediaStreamGraphImpl::DeviceChanged() |
1027 | 0 | { |
1028 | 0 | // This is safe to be called from any thread: this message comes from an |
1029 | 0 | // underlying platform API, and we don't have much guarantees. If it is not |
1030 | 0 | // called from the main thread (and it probably will rarely be), it will post |
1031 | 0 | // itself to the main thread, and the actual device change message will be ran |
1032 | 0 | // and acted upon on the graph thread. |
1033 | 0 | if (!NS_IsMainThread()) { |
1034 | 0 | RefPtr<nsIRunnable> runnable = |
1035 | 0 | WrapRunnable(this, |
1036 | 0 | &MediaStreamGraphImpl::DeviceChanged); |
1037 | 0 | mAbstractMainThread->Dispatch(runnable.forget()); |
1038 | 0 | return; |
1039 | 0 | } |
1040 | 0 | |
1041 | 0 | class Message : public ControlMessage { |
1042 | 0 | public: |
1043 | 0 | explicit Message(MediaStreamGraph* aGraph) |
1044 | 0 | : ControlMessage(nullptr) |
1045 | 0 | , mGraphImpl(static_cast<MediaStreamGraphImpl*>(aGraph)) |
1046 | 0 | {} |
1047 | 0 | void Run() override |
1048 | 0 | { |
1049 | 0 | mGraphImpl->DeviceChangedImpl(); |
1050 | 0 | } |
1051 | 0 | // We know that this is valid, because the graph can't shutdown if it has |
1052 | 0 | // messages. |
1053 | 0 | MediaStreamGraphImpl* mGraphImpl; |
1054 | 0 | }; |
1055 | 0 |
|
1056 | 0 | AppendMessage(MakeUnique<Message>(this)); |
1057 | 0 | } |
1058 | | |
1059 | | void MediaStreamGraphImpl::ReevaluateInputDevice() |
1060 | 0 | { |
1061 | 0 | MOZ_ASSERT(OnGraphThread()); |
1062 | 0 | bool needToSwitch = false; |
1063 | 0 |
|
1064 | 0 | if (CurrentDriver()->AsAudioCallbackDriver()) { |
1065 | 0 | AudioCallbackDriver* audioCallbackDriver = CurrentDriver()->AsAudioCallbackDriver(); |
1066 | 0 | if (audioCallbackDriver->InputChannelCount() != AudioInputChannelCount()) { |
1067 | 0 | needToSwitch = true; |
1068 | 0 | } |
1069 | 0 | } else { |
1070 | 0 | // We're already in the process of switching to a audio callback driver, |
1071 | 0 | // which will happen at the next iteration. |
1072 | 0 | // However, maybe it's not the correct number of channels. Re-query the |
1073 | 0 | // correct channel amount at this time. |
1074 | | #ifdef DEBUG |
1075 | | MonitorAutoLock lock(mMonitor); |
1076 | | MOZ_ASSERT(CurrentDriver()->Switching()); |
1077 | | #endif |
1078 | | needToSwitch = true; |
1079 | 0 | } |
1080 | 0 | if (needToSwitch) { |
1081 | 0 | AudioCallbackDriver* newDriver = new AudioCallbackDriver(this, AudioInputChannelCount()); |
1082 | 0 | { |
1083 | 0 | MonitorAutoLock lock(mMonitor); |
1084 | 0 | CurrentDriver()->SwitchAtNextIteration(newDriver); |
1085 | 0 | } |
1086 | 0 | } |
1087 | 0 | } |
1088 | | |
1089 | | bool |
1090 | | MediaStreamGraph::OnGraphThreadOrNotRunning() const |
1091 | 0 | { |
1092 | 0 | // either we're on the right thread (and calling CurrentDriver() is safe), |
1093 | 0 | // or we're going to fail the assert anyway, so don't cross-check |
1094 | 0 | // via CurrentDriver(). |
1095 | 0 | MediaStreamGraphImpl const * graph = |
1096 | 0 | static_cast<MediaStreamGraphImpl const *>(this); |
1097 | 0 | return graph->mDetectedNotRunning ? |
1098 | 0 | NS_IsMainThread() : graph->mDriver->OnThread(); |
1099 | 0 | } |
1100 | | |
1101 | | bool |
1102 | | MediaStreamGraph::OnGraphThread() const |
1103 | 0 | { |
1104 | 0 | // we're on the right thread (and calling mDriver is safe), |
1105 | 0 | MediaStreamGraphImpl const * graph = |
1106 | 0 | static_cast<MediaStreamGraphImpl const *>(this); |
1107 | 0 | MOZ_ASSERT(graph->mDriver); |
1108 | 0 | return graph->mDriver->OnThread(); |
1109 | 0 | } |
1110 | | |
1111 | | bool |
1112 | | MediaStreamGraphImpl::ShouldUpdateMainThread() |
1113 | 0 | { |
1114 | 0 | MOZ_ASSERT(OnGraphThreadOrNotRunning()); |
1115 | 0 | if (mRealtime) { |
1116 | 0 | return true; |
1117 | 0 | } |
1118 | 0 | |
1119 | 0 | TimeStamp now = TimeStamp::Now(); |
1120 | 0 | if ((now - mLastMainThreadUpdate).ToMilliseconds() > CurrentDriver()->IterationDuration()) { |
1121 | 0 | mLastMainThreadUpdate = now; |
1122 | 0 | return true; |
1123 | 0 | } |
1124 | 0 | return false; |
1125 | 0 | } |
1126 | | |
1127 | | void |
1128 | | MediaStreamGraphImpl::PrepareUpdatesToMainThreadState(bool aFinalUpdate) |
1129 | 0 | { |
1130 | 0 | MOZ_ASSERT(OnGraphThreadOrNotRunning()); |
1131 | 0 | mMonitor.AssertCurrentThreadOwns(); |
1132 | 0 |
|
1133 | 0 | // We don't want to frequently update the main thread about timing update |
1134 | 0 | // when we are not running in realtime. |
1135 | 0 | if (aFinalUpdate || ShouldUpdateMainThread()) { |
1136 | 0 | // Strip updates that will be obsoleted below, so as to keep the length of |
1137 | 0 | // mStreamUpdates sane. |
1138 | 0 | size_t keptUpdateCount = 0; |
1139 | 0 | for (size_t i = 0; i < mStreamUpdates.Length(); ++i) { |
1140 | 0 | MediaStream* stream = mStreamUpdates[i].mStream; |
1141 | 0 | // RemoveStreamGraphThread() clears mStream in updates for |
1142 | 0 | // streams that are removed from the graph. |
1143 | 0 | MOZ_ASSERT(!stream || stream->GraphImpl() == this); |
1144 | 0 | if (!stream || stream->MainThreadNeedsUpdates()) { |
1145 | 0 | // Discard this update as it has either been cleared when the stream |
1146 | 0 | // was destroyed or there will be a newer update below. |
1147 | 0 | continue; |
1148 | 0 | } |
1149 | 0 | if (keptUpdateCount != i) { |
1150 | 0 | mStreamUpdates[keptUpdateCount] = std::move(mStreamUpdates[i]); |
1151 | 0 | MOZ_ASSERT(!mStreamUpdates[i].mStream); |
1152 | 0 | } |
1153 | 0 | ++keptUpdateCount; |
1154 | 0 | } |
1155 | 0 | mStreamUpdates.TruncateLength(keptUpdateCount); |
1156 | 0 |
|
1157 | 0 | mStreamUpdates.SetCapacity(mStreamUpdates.Length() + mStreams.Length() + |
1158 | 0 | mSuspendedStreams.Length()); |
1159 | 0 | for (MediaStream* stream : AllStreams()) { |
1160 | 0 | if (!stream->MainThreadNeedsUpdates()) { |
1161 | 0 | continue; |
1162 | 0 | } |
1163 | 0 | StreamUpdate* update = mStreamUpdates.AppendElement(); |
1164 | 0 | update->mStream = stream; |
1165 | 0 | // No blocking to worry about here, since we've passed |
1166 | 0 | // UpdateCurrentTimeForStreams. |
1167 | 0 | update->mNextMainThreadCurrentTime = |
1168 | 0 | stream->GraphTimeToStreamTime(mProcessedTime); |
1169 | 0 | update->mNextMainThreadFinished = stream->mNotifiedFinished; |
1170 | 0 | } |
1171 | 0 | if (!mPendingUpdateRunnables.IsEmpty()) { |
1172 | 0 | mUpdateRunnables.AppendElements(std::move(mPendingUpdateRunnables)); |
1173 | 0 | } |
1174 | 0 | } |
1175 | 0 |
|
1176 | 0 | // If this is the final update, then a stable state event will soon be |
1177 | 0 | // posted just before this thread finishes, and so there is no need to also |
1178 | 0 | // post here. |
1179 | 0 | if (!aFinalUpdate && |
1180 | 0 | // Don't send the message to the main thread if it's not going to have |
1181 | 0 | // any work to do. |
1182 | 0 | !(mUpdateRunnables.IsEmpty() && mStreamUpdates.IsEmpty())) { |
1183 | 0 | EnsureStableStateEventPosted(); |
1184 | 0 | } |
1185 | 0 | } |
1186 | | |
1187 | | GraphTime |
1188 | | MediaStreamGraphImpl::RoundUpToEndOfAudioBlock(GraphTime aTime) |
1189 | 0 | { |
1190 | 0 | if (aTime % WEBAUDIO_BLOCK_SIZE == 0) { |
1191 | 0 | return aTime; |
1192 | 0 | } |
1193 | 0 | return RoundUpToNextAudioBlock(aTime); |
1194 | 0 | } |
1195 | | |
1196 | | GraphTime |
1197 | | MediaStreamGraphImpl::RoundUpToNextAudioBlock(GraphTime aTime) |
1198 | 0 | { |
1199 | 0 | uint64_t block = aTime >> WEBAUDIO_BLOCK_SIZE_BITS; |
1200 | 0 | uint64_t nextBlock = block + 1; |
1201 | 0 | GraphTime nextTime = nextBlock << WEBAUDIO_BLOCK_SIZE_BITS; |
1202 | 0 | return nextTime; |
1203 | 0 | } |
1204 | | |
1205 | | void |
1206 | | MediaStreamGraphImpl::ProduceDataForStreamsBlockByBlock(uint32_t aStreamIndex, |
1207 | | TrackRate aSampleRate) |
1208 | 0 | { |
1209 | 0 | MOZ_ASSERT(OnGraphThread()); |
1210 | 0 | MOZ_ASSERT(aStreamIndex <= mFirstCycleBreaker, |
1211 | 0 | "Cycle breaker is not AudioNodeStream?"); |
1212 | 0 | GraphTime t = mProcessedTime; |
1213 | 0 | while (t < mStateComputedTime) { |
1214 | 0 | GraphTime next = RoundUpToNextAudioBlock(t); |
1215 | 0 | for (uint32_t i = mFirstCycleBreaker; i < mStreams.Length(); ++i) { |
1216 | 0 | auto ns = static_cast<AudioNodeStream*>(mStreams[i]); |
1217 | 0 | MOZ_ASSERT(ns->AsAudioNodeStream()); |
1218 | 0 | ns->ProduceOutputBeforeInput(t); |
1219 | 0 | } |
1220 | 0 | for (uint32_t i = aStreamIndex; i < mStreams.Length(); ++i) { |
1221 | 0 | ProcessedMediaStream* ps = mStreams[i]->AsProcessedStream(); |
1222 | 0 | if (ps) { |
1223 | 0 | ps->ProcessInput(t, next, |
1224 | 0 | (next == mStateComputedTime) ? ProcessedMediaStream::ALLOW_FINISH : 0); |
1225 | 0 | } |
1226 | 0 | } |
1227 | 0 | t = next; |
1228 | 0 | } |
1229 | 0 | NS_ASSERTION(t == mStateComputedTime, |
1230 | 0 | "Something went wrong with rounding to block boundaries"); |
1231 | 0 | } |
1232 | | |
1233 | | bool |
1234 | | MediaStreamGraphImpl::AllFinishedStreamsNotified() |
1235 | 0 | { |
1236 | 0 | MOZ_ASSERT(OnGraphThread()); |
1237 | 0 | for (MediaStream* stream : AllStreams()) { |
1238 | 0 | if (stream->mFinished && !stream->mNotifiedFinished) { |
1239 | 0 | return false; |
1240 | 0 | } |
1241 | 0 | } |
1242 | 0 | return true; |
1243 | 0 | } |
1244 | | |
1245 | | void |
1246 | | MediaStreamGraphImpl::RunMessageAfterProcessing(UniquePtr<ControlMessage> aMessage) |
1247 | 0 | { |
1248 | 0 | MOZ_ASSERT(OnGraphThread()); |
1249 | 0 |
|
1250 | 0 | if (mFrontMessageQueue.IsEmpty()) { |
1251 | 0 | mFrontMessageQueue.AppendElement(); |
1252 | 0 | } |
1253 | 0 |
|
1254 | 0 | // Only one block is used for messages from the graph thread. |
1255 | 0 | MOZ_ASSERT(mFrontMessageQueue.Length() == 1); |
1256 | 0 | mFrontMessageQueue[0].mMessages.AppendElement(std::move(aMessage)); |
1257 | 0 | } |
1258 | | |
1259 | | void |
1260 | | MediaStreamGraphImpl::RunMessagesInQueue() |
1261 | 0 | { |
1262 | 0 | TRACE_AUDIO_CALLBACK(); |
1263 | 0 | MOZ_ASSERT(OnGraphThread()); |
1264 | 0 | // Calculate independent action times for each batch of messages (each |
1265 | 0 | // batch corresponding to an event loop task). This isolates the performance |
1266 | 0 | // of different scripts to some extent. |
1267 | 0 | for (uint32_t i = 0; i < mFrontMessageQueue.Length(); ++i) { |
1268 | 0 | nsTArray<UniquePtr<ControlMessage>>& messages = mFrontMessageQueue[i].mMessages; |
1269 | 0 |
|
1270 | 0 | for (uint32_t j = 0; j < messages.Length(); ++j) { |
1271 | 0 | messages[j]->Run(); |
1272 | 0 | } |
1273 | 0 | } |
1274 | 0 | mFrontMessageQueue.Clear(); |
1275 | 0 | } |
1276 | | |
1277 | | void |
1278 | | MediaStreamGraphImpl::UpdateGraph(GraphTime aEndBlockingDecisions) |
1279 | 0 | { |
1280 | 0 | TRACE_AUDIO_CALLBACK(); |
1281 | 0 | MOZ_ASSERT(OnGraphThread()); |
1282 | 0 | MOZ_ASSERT(aEndBlockingDecisions >= mProcessedTime); |
1283 | 0 | // The next state computed time can be the same as the previous: it |
1284 | 0 | // means the driver would have been blocking indefinitly, but the graph has |
1285 | 0 | // been woken up right after having been to sleep. |
1286 | 0 | MOZ_ASSERT(aEndBlockingDecisions >= mStateComputedTime); |
1287 | 0 |
|
1288 | 0 | UpdateStreamOrder(); |
1289 | 0 |
|
1290 | 0 | bool ensureNextIteration = false; |
1291 | 0 |
|
1292 | 0 | for (MediaStream* stream : mStreams) { |
1293 | 0 | if (SourceMediaStream* is = stream->AsSourceStream()) { |
1294 | 0 | ensureNextIteration |= is->PullNewData(aEndBlockingDecisions); |
1295 | 0 | is->ExtractPendingInput(); |
1296 | 0 | } |
1297 | 0 | if (stream->mFinished) { |
1298 | 0 | // The stream's not suspended, and since it's finished, underruns won't |
1299 | 0 | // stop it playing out. So there's no blocking other than what we impose |
1300 | 0 | // here. |
1301 | 0 | GraphTime endTime = stream->GetStreamTracks().GetAllTracksEnd() + |
1302 | 0 | stream->mTracksStartTime; |
1303 | 0 | if (endTime <= mStateComputedTime) { |
1304 | 0 | LOG(LogLevel::Verbose, |
1305 | 0 | ("%p: MediaStream %p is blocked due to being finished", this, stream)); |
1306 | 0 | stream->mStartBlocking = mStateComputedTime; |
1307 | 0 | } else { |
1308 | 0 | LOG(LogLevel::Verbose, |
1309 | 0 | ("%p: MediaStream %p is finished, but not blocked yet (end at %f, with " |
1310 | 0 | "blocking at %f)", |
1311 | 0 | this, |
1312 | 0 | stream, |
1313 | 0 | MediaTimeToSeconds(stream->GetTracksEnd()), |
1314 | 0 | MediaTimeToSeconds(endTime))); |
1315 | 0 | // Data can't be added to a finished stream, so underruns are irrelevant. |
1316 | 0 | stream->mStartBlocking = std::min(endTime, aEndBlockingDecisions); |
1317 | 0 | } |
1318 | 0 | } else { |
1319 | 0 | stream->mStartBlocking = WillUnderrun(stream, aEndBlockingDecisions); |
1320 | 0 |
|
1321 | 0 | SourceMediaStream* s = stream->AsSourceStream(); |
1322 | 0 | if (s && s->mPullEnabled) { |
1323 | 0 | for (StreamTracks::TrackIter i(s->mTracks); !i.IsEnded(); i.Next()) { |
1324 | 0 | if (i->IsEnded()) { |
1325 | 0 | continue; |
1326 | 0 | } |
1327 | 0 | if (i->GetEnd() < stream->GraphTimeToStreamTime(aEndBlockingDecisions)) { |
1328 | 0 | LOG(LogLevel::Error, |
1329 | 0 | ("%p: SourceMediaStream %p track %u (%s) is live and pulled, but wasn't fed " |
1330 | 0 | "enough data. Listeners=%zu. Track-end=%f, Iteration-end=%f", |
1331 | 0 | this, |
1332 | 0 | stream, |
1333 | 0 | i->GetID(), |
1334 | 0 | (i->GetType() == MediaSegment::AUDIO ? "audio" : "video"), |
1335 | 0 | stream->mListeners.Length(), |
1336 | 0 | MediaTimeToSeconds(i->GetEnd()), |
1337 | 0 | MediaTimeToSeconds(stream->GraphTimeToStreamTime(aEndBlockingDecisions)))); |
1338 | 0 | MOZ_DIAGNOSTIC_ASSERT(false, |
1339 | 0 | "A non-finished SourceMediaStream wasn't fed " |
1340 | 0 | "enough data by NotifyPull"); |
1341 | 0 | } |
1342 | 0 | } |
1343 | 0 | } |
1344 | 0 | } |
1345 | 0 | } |
1346 | 0 |
|
1347 | 0 | for (MediaStream* stream : mSuspendedStreams) { |
1348 | 0 | stream->mStartBlocking = mStateComputedTime; |
1349 | 0 | } |
1350 | 0 |
|
1351 | 0 | // The loop is woken up so soon that IterationEnd() barely advances and we |
1352 | 0 | // end up having aEndBlockingDecision == mStateComputedTime. |
1353 | 0 | // Since stream blocking is computed in the interval of |
1354 | 0 | // [mStateComputedTime, aEndBlockingDecision), it won't be computed at all. |
1355 | 0 | // We should ensure next iteration so that pending blocking changes will be |
1356 | 0 | // computed in next loop. |
1357 | 0 | if (ensureNextIteration || |
1358 | 0 | aEndBlockingDecisions == mStateComputedTime) { |
1359 | 0 | EnsureNextIteration(); |
1360 | 0 | } |
1361 | 0 | } |
1362 | | |
1363 | | void |
1364 | | MediaStreamGraphImpl::Process() |
1365 | 0 | { |
1366 | 0 | TRACE_AUDIO_CALLBACK(); |
1367 | 0 | MOZ_ASSERT(OnGraphThread()); |
1368 | 0 | // Play stream contents. |
1369 | 0 | bool allBlockedForever = true; |
1370 | 0 | // True when we've done ProcessInput for all processed streams. |
1371 | 0 | bool doneAllProducing = false; |
1372 | 0 | // This is the number of frame that are written to the AudioStreams, for |
1373 | 0 | // this cycle. |
1374 | 0 | StreamTime ticksPlayed = 0; |
1375 | 0 |
|
1376 | 0 | mMixer.StartMixing(); |
1377 | 0 |
|
1378 | 0 | // Figure out what each stream wants to do |
1379 | 0 | for (uint32_t i = 0; i < mStreams.Length(); ++i) { |
1380 | 0 | MediaStream* stream = mStreams[i]; |
1381 | 0 | if (!doneAllProducing) { |
1382 | 0 | ProcessedMediaStream* ps = stream->AsProcessedStream(); |
1383 | 0 | if (ps) { |
1384 | 0 | AudioNodeStream* n = stream->AsAudioNodeStream(); |
1385 | 0 | if (n) { |
1386 | | #ifdef DEBUG |
1387 | | // Verify that the sampling rate for all of the following streams is the same |
1388 | | for (uint32_t j = i + 1; j < mStreams.Length(); ++j) { |
1389 | | AudioNodeStream* nextStream = mStreams[j]->AsAudioNodeStream(); |
1390 | | if (nextStream) { |
1391 | | MOZ_ASSERT(n->SampleRate() == nextStream->SampleRate(), |
1392 | | "All AudioNodeStreams in the graph must have the same sampling rate"); |
1393 | | } |
1394 | | } |
1395 | | #endif |
1396 | | // Since an AudioNodeStream is present, go ahead and |
1397 | 0 | // produce audio block by block for all the rest of the streams. |
1398 | 0 | ProduceDataForStreamsBlockByBlock(i, n->SampleRate()); |
1399 | 0 | doneAllProducing = true; |
1400 | 0 | } else { |
1401 | 0 | ps->ProcessInput(mProcessedTime, mStateComputedTime, |
1402 | 0 | ProcessedMediaStream::ALLOW_FINISH); |
1403 | 0 | NS_ASSERTION(stream->mTracks.GetEnd() >= |
1404 | 0 | GraphTimeToStreamTimeWithBlocking(stream, mStateComputedTime), |
1405 | 0 | "Stream did not produce enough data"); |
1406 | 0 | } |
1407 | 0 | } |
1408 | 0 | } |
1409 | 0 | NotifyHasCurrentData(stream); |
1410 | 0 | // Only playback audio and video in real-time mode |
1411 | 0 | if (mRealtime) { |
1412 | 0 | CreateOrDestroyAudioStreams(stream); |
1413 | 0 | if (CurrentDriver()->AsAudioCallbackDriver()) { |
1414 | 0 | StreamTime ticksPlayedForThisStream = PlayAudio(stream); |
1415 | 0 | if (!ticksPlayed) { |
1416 | 0 | ticksPlayed = ticksPlayedForThisStream; |
1417 | 0 | } else { |
1418 | 0 | MOZ_ASSERT(!ticksPlayedForThisStream || ticksPlayedForThisStream == ticksPlayed, |
1419 | 0 | "Each stream should have the same number of frame."); |
1420 | 0 | } |
1421 | 0 | } |
1422 | 0 | } |
1423 | 0 | if (stream->mStartBlocking > mProcessedTime) { |
1424 | 0 | allBlockedForever = false; |
1425 | 0 | } |
1426 | 0 | } |
1427 | 0 |
|
1428 | 0 | if (CurrentDriver()->AsAudioCallbackDriver()) { |
1429 | 0 | if (!ticksPlayed) { |
1430 | 0 | // Nothing was played, so the mixer doesn't know how many frames were |
1431 | 0 | // processed. We still tell it so AudioCallbackDriver knows how much has |
1432 | 0 | // been processed. (bug 1406027) |
1433 | 0 | mMixer.Mix(nullptr, |
1434 | 0 | CurrentDriver()->AsAudioCallbackDriver()->OutputChannelCount(), |
1435 | 0 | mStateComputedTime - mProcessedTime, |
1436 | 0 | mSampleRate); |
1437 | 0 | } |
1438 | 0 | mMixer.FinishMixing(); |
1439 | 0 | } |
1440 | 0 |
|
1441 | 0 | if (!allBlockedForever) { |
1442 | 0 | EnsureNextIteration(); |
1443 | 0 | } |
1444 | 0 | } |
1445 | | |
1446 | | bool |
1447 | | MediaStreamGraphImpl::UpdateMainThreadState() |
1448 | 0 | { |
1449 | 0 | MOZ_ASSERT(OnGraphThread()); |
1450 | 0 | MonitorAutoLock lock(mMonitor); |
1451 | 0 | bool finalUpdate = mForceShutDown || |
1452 | 0 | (mProcessedTime >= mEndTime && AllFinishedStreamsNotified()) || |
1453 | 0 | (IsEmpty() && mBackMessageQueue.IsEmpty()); |
1454 | 0 | PrepareUpdatesToMainThreadState(finalUpdate); |
1455 | 0 | if (finalUpdate) { |
1456 | 0 | // Enter shutdown mode when this iteration is completed. |
1457 | 0 | // No need to Destroy streams here. The main-thread owner of each |
1458 | 0 | // stream is responsible for calling Destroy on them. |
1459 | 0 | return false; |
1460 | 0 | } |
1461 | 0 | |
1462 | 0 | CurrentDriver()->WaitForNextIteration(); |
1463 | 0 |
|
1464 | 0 | SwapMessageQueues(); |
1465 | 0 | return true; |
1466 | 0 | } |
1467 | | |
1468 | | bool |
1469 | | MediaStreamGraphImpl::OneIteration(GraphTime aStateEnd) |
1470 | 0 | { |
1471 | 0 | TRACE_AUDIO_CALLBACK(); |
1472 | 0 | // Changes to LIFECYCLE_RUNNING occur before starting or reviving the graph |
1473 | 0 | // thread, and so the monitor need not be held to check mLifecycleState. |
1474 | 0 | // LIFECYCLE_THREAD_NOT_STARTED is possible when shutting down offline |
1475 | 0 | // graphs that have not started. |
1476 | 0 | MOZ_DIAGNOSTIC_ASSERT(mLifecycleState <= LIFECYCLE_RUNNING); |
1477 | 0 | MOZ_ASSERT(OnGraphThread()); |
1478 | 0 | WebCore::DenormalDisabler disabler; |
1479 | 0 |
|
1480 | 0 | // Process graph message from the main thread for this iteration. |
1481 | 0 | RunMessagesInQueue(); |
1482 | 0 |
|
1483 | 0 | GraphTime stateEnd = std::min(aStateEnd, GraphTime(mEndTime)); |
1484 | 0 | UpdateGraph(stateEnd); |
1485 | 0 |
|
1486 | 0 | mStateComputedTime = stateEnd; |
1487 | 0 |
|
1488 | 0 | Process(); |
1489 | 0 |
|
1490 | 0 | GraphTime oldProcessedTime = mProcessedTime; |
1491 | 0 | mProcessedTime = stateEnd; |
1492 | 0 |
|
1493 | 0 | UpdateCurrentTimeForStreams(oldProcessedTime); |
1494 | 0 |
|
1495 | 0 | ProcessChunkMetadata(oldProcessedTime); |
1496 | 0 |
|
1497 | 0 | // Process graph messages queued from RunMessageAfterProcessing() on this |
1498 | 0 | // thread during the iteration. |
1499 | 0 | RunMessagesInQueue(); |
1500 | 0 |
|
1501 | 0 | return UpdateMainThreadState(); |
1502 | 0 | } |
1503 | | |
1504 | | void |
1505 | | MediaStreamGraphImpl::ApplyStreamUpdate(StreamUpdate* aUpdate) |
1506 | 0 | { |
1507 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
1508 | 0 | mMonitor.AssertCurrentThreadOwns(); |
1509 | 0 |
|
1510 | 0 | MediaStream* stream = aUpdate->mStream; |
1511 | 0 | if (!stream) |
1512 | 0 | return; |
1513 | 0 | stream->mMainThreadCurrentTime = aUpdate->mNextMainThreadCurrentTime; |
1514 | 0 | stream->mMainThreadFinished = aUpdate->mNextMainThreadFinished; |
1515 | 0 |
|
1516 | 0 | if (stream->ShouldNotifyStreamFinished()) { |
1517 | 0 | stream->NotifyMainThreadListeners(); |
1518 | 0 | } |
1519 | 0 | } |
1520 | | |
1521 | | void |
1522 | | MediaStreamGraphImpl::ForceShutDown(media::ShutdownTicket* aShutdownTicket) |
1523 | 0 | { |
1524 | 0 | MOZ_ASSERT(NS_IsMainThread(), "Must be called on main thread"); |
1525 | 0 | LOG(LogLevel::Debug, ("%p: MediaStreamGraph::ForceShutdown", this)); |
1526 | 0 |
|
1527 | 0 | if (aShutdownTicket) { |
1528 | 0 | MOZ_ASSERT(!mForceShutdownTicket); |
1529 | 0 | // Avoid waiting forever for a graph to shut down |
1530 | 0 | // synchronously. Reports are that some 3rd-party audio drivers |
1531 | 0 | // occasionally hang in shutdown (both for us and Chrome). |
1532 | 0 | NS_NewTimerWithCallback(getter_AddRefs(mShutdownTimer), |
1533 | 0 | this, |
1534 | 0 | MediaStreamGraph::AUDIO_CALLBACK_DRIVER_SHUTDOWN_TIMEOUT, |
1535 | 0 | nsITimer::TYPE_ONE_SHOT); |
1536 | 0 | } |
1537 | 0 | mForceShutdownTicket = aShutdownTicket; |
1538 | 0 | MonitorAutoLock lock(mMonitor); |
1539 | 0 | mForceShutDown = true; |
1540 | 0 | if (IsNonRealtime()) { |
1541 | 0 | // Start the graph if it has not been started already, but don't produce anything. |
1542 | 0 | // This method will return early if the graph is already started. |
1543 | 0 | StartNonRealtimeProcessing(0); |
1544 | 0 | } else if (LifecycleStateRef() == LIFECYCLE_THREAD_NOT_STARTED) { |
1545 | 0 | // We *could* have just sent this a message to start up, so don't |
1546 | 0 | // yank the rug out from under it. Tell it to startup and let it |
1547 | 0 | // shut down. |
1548 | 0 | RefPtr<GraphDriver> driver = CurrentDriver(); |
1549 | 0 | MonitorAutoUnlock unlock(mMonitor); |
1550 | 0 | driver->Start(); |
1551 | 0 | } |
1552 | 0 | EnsureNextIterationLocked(); |
1553 | 0 | } |
1554 | | |
1555 | | NS_IMETHODIMP |
1556 | | MediaStreamGraphImpl::Notify(nsITimer* aTimer) |
1557 | 0 | { |
1558 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
1559 | 0 | NS_ASSERTION(!mForceShutdownTicket, "MediaStreamGraph took too long to shut down!"); |
1560 | 0 | // Sigh, graph took too long to shut down. Stop blocking system |
1561 | 0 | // shutdown and hope all is well. |
1562 | 0 | mForceShutdownTicket = nullptr; |
1563 | 0 | return NS_OK; |
1564 | 0 | } |
1565 | | |
1566 | | NS_IMETHODIMP |
1567 | | MediaStreamGraphImpl::GetName(nsACString& aName) |
1568 | 0 | { |
1569 | 0 | aName.AssignLiteral("MediaStreamGraphImpl"); |
1570 | 0 | return NS_OK; |
1571 | 0 | } |
1572 | | |
1573 | | /* static */ StaticRefPtr<nsIAsyncShutdownBlocker> gMediaStreamGraphShutdownBlocker; |
1574 | | |
1575 | | namespace { |
1576 | | |
1577 | | class MediaStreamGraphShutDownRunnable : public Runnable { |
1578 | | public: |
1579 | | explicit MediaStreamGraphShutDownRunnable(MediaStreamGraphImpl* aGraph) |
1580 | | : Runnable("MediaStreamGraphShutDownRunnable") |
1581 | | , mGraph(aGraph) |
1582 | 0 | {} |
1583 | | NS_IMETHOD Run() override |
1584 | 0 | { |
1585 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
1586 | 0 | MOZ_ASSERT(mGraph->mDetectedNotRunning && mGraph->mDriver, |
1587 | 0 | "We should know the graph thread control loop isn't running!"); |
1588 | 0 |
|
1589 | 0 | LOG(LogLevel::Debug, ("%p: Shutting down graph", mGraph.get())); |
1590 | 0 |
|
1591 | 0 | // We've asserted the graph isn't running. Use mDriver instead of CurrentDriver |
1592 | 0 | // to avoid thread-safety checks |
1593 | | #if 0 // AudioCallbackDrivers are released asynchronously anyways |
1594 | | // XXX a better test would be have setting mDetectedNotRunning make sure |
1595 | | // any current callback has finished and block future ones -- or just |
1596 | | // handle it all in Shutdown()! |
1597 | | if (mGraph->mDriver->AsAudioCallbackDriver()) { |
1598 | | MOZ_ASSERT(!mGraph->mDriver->AsAudioCallbackDriver()->InCallback()); |
1599 | | } |
1600 | | #endif |
1601 | |
|
1602 | 0 | mGraph->mDriver->Shutdown(); // This will wait until it's shutdown since |
1603 | 0 | // we'll start tearing down the graph after this |
1604 | 0 |
|
1605 | 0 | // Release the driver now so that an AudioCallbackDriver will release its |
1606 | 0 | // SharedThreadPool reference. Each SharedThreadPool reference must be |
1607 | 0 | // released before SharedThreadPool::SpinUntilEmpty() runs on |
1608 | 0 | // xpcom-shutdown-threads. Don't wait for GC/CC to release references to |
1609 | 0 | // objects owning streams, or for expiration of mGraph->mShutdownTimer, |
1610 | 0 | // which won't otherwise release its reference on the graph until |
1611 | 0 | // nsTimerImpl::Shutdown(), which runs after xpcom-shutdown-threads. |
1612 | 0 | { |
1613 | 0 | MonitorAutoLock mon(mGraph->mMonitor); |
1614 | 0 | mGraph->SetCurrentDriver(nullptr); |
1615 | 0 | } |
1616 | 0 |
|
1617 | 0 | // Safe to access these without the monitor since the graph isn't running. |
1618 | 0 | // We may be one of several graphs. Drop ticket to eventually unblock shutdown. |
1619 | 0 | if (mGraph->mShutdownTimer && !mGraph->mForceShutdownTicket) { |
1620 | 0 | MOZ_ASSERT(false, |
1621 | 0 | "AudioCallbackDriver took too long to shut down and we let shutdown" |
1622 | 0 | " continue - freezing and leaking"); |
1623 | 0 |
|
1624 | 0 | // The timer fired, so we may be deeper in shutdown now. Block any further |
1625 | 0 | // teardown and just leak, for safety. |
1626 | 0 | return NS_OK; |
1627 | 0 | } |
1628 | 0 |
|
1629 | 0 | // mGraph's thread is not running so it's OK to do whatever here |
1630 | 0 | for (MediaStream* stream : mGraph->AllStreams()) { |
1631 | 0 | // Clean up all MediaSegments since we cannot release Images too |
1632 | 0 | // late during shutdown. Also notify listeners that they were removed |
1633 | 0 | // so they can clean up any gfx resources. |
1634 | 0 | if (SourceMediaStream* source = stream->AsSourceStream()) { |
1635 | 0 | // Finishing a SourceStream prevents new data from being appended. |
1636 | 0 | source->FinishOnGraphThread(); |
1637 | 0 | } |
1638 | 0 | stream->GetStreamTracks().Clear(); |
1639 | 0 | stream->RemoveAllListenersImpl(); |
1640 | 0 | } |
1641 | 0 |
|
1642 | 0 | mGraph->mForceShutdownTicket = nullptr; |
1643 | 0 |
|
1644 | 0 | // We can't block past the final LIFECYCLE_WAITING_FOR_STREAM_DESTRUCTION |
1645 | 0 | // stage, since completion of that stage requires all streams to be freed, |
1646 | 0 | // which requires shutdown to proceed. |
1647 | 0 |
|
1648 | 0 | if (mGraph->IsEmpty()) { |
1649 | 0 | // mGraph is no longer needed, so delete it. |
1650 | 0 | mGraph->Destroy(); |
1651 | 0 | } else { |
1652 | 0 | // The graph is not empty. We must be in a forced shutdown, or a |
1653 | 0 | // non-realtime graph that has finished processing. Some later |
1654 | 0 | // AppendMessage will detect that the graph has been emptied, and |
1655 | 0 | // delete it. |
1656 | 0 | NS_ASSERTION(mGraph->mForceShutDown || !mGraph->mRealtime, |
1657 | 0 | "Not in forced shutdown?"); |
1658 | 0 | mGraph->LifecycleStateRef() = |
1659 | 0 | MediaStreamGraphImpl::LIFECYCLE_WAITING_FOR_STREAM_DESTRUCTION; |
1660 | 0 | } |
1661 | 0 | return NS_OK; |
1662 | 0 | } |
1663 | | private: |
1664 | | RefPtr<MediaStreamGraphImpl> mGraph; |
1665 | | }; |
1666 | | |
1667 | | class MediaStreamGraphStableStateRunnable : public Runnable { |
1668 | | public: |
1669 | | explicit MediaStreamGraphStableStateRunnable(MediaStreamGraphImpl* aGraph, |
1670 | | bool aSourceIsMSG) |
1671 | | : Runnable("MediaStreamGraphStableStateRunnable") |
1672 | | , mGraph(aGraph) |
1673 | | , mSourceIsMSG(aSourceIsMSG) |
1674 | 0 | { |
1675 | 0 | } |
1676 | | NS_IMETHOD Run() override |
1677 | 0 | { |
1678 | 0 | TRACE(); |
1679 | 0 | if (mGraph) { |
1680 | 0 | mGraph->RunInStableState(mSourceIsMSG); |
1681 | 0 | } |
1682 | 0 | return NS_OK; |
1683 | 0 | } |
1684 | | private: |
1685 | | RefPtr<MediaStreamGraphImpl> mGraph; |
1686 | | bool mSourceIsMSG; |
1687 | | }; |
1688 | | |
1689 | | /* |
1690 | | * Control messages forwarded from main thread to graph manager thread |
1691 | | */ |
1692 | | class CreateMessage : public ControlMessage { |
1693 | | public: |
1694 | 0 | explicit CreateMessage(MediaStream* aStream) : ControlMessage(aStream) {} |
1695 | | void Run() override |
1696 | 0 | { |
1697 | 0 | mStream->GraphImpl()->AddStreamGraphThread(mStream); |
1698 | 0 | } |
1699 | | void RunDuringShutdown() override |
1700 | 0 | { |
1701 | 0 | // Make sure to run this message during shutdown too, to make sure |
1702 | 0 | // that we balance the number of streams registered with the graph |
1703 | 0 | // as they're destroyed during shutdown. |
1704 | 0 | Run(); |
1705 | 0 | } |
1706 | | }; |
1707 | | |
1708 | | } // namespace |
1709 | | |
1710 | | void |
1711 | | MediaStreamGraphImpl::RunInStableState(bool aSourceIsMSG) |
1712 | 0 | { |
1713 | 0 | MOZ_ASSERT(NS_IsMainThread(), "Must be called on main thread"); |
1714 | 0 |
|
1715 | 0 | nsTArray<nsCOMPtr<nsIRunnable> > runnables; |
1716 | 0 | // When we're doing a forced shutdown, pending control messages may be |
1717 | 0 | // run on the main thread via RunDuringShutdown. Those messages must |
1718 | 0 | // run without the graph monitor being held. So, we collect them here. |
1719 | 0 | nsTArray<UniquePtr<ControlMessage>> controlMessagesToRunDuringShutdown; |
1720 | 0 |
|
1721 | 0 | { |
1722 | 0 | MonitorAutoLock lock(mMonitor); |
1723 | 0 | if (aSourceIsMSG) { |
1724 | 0 | MOZ_ASSERT(mPostedRunInStableStateEvent); |
1725 | 0 | mPostedRunInStableStateEvent = false; |
1726 | 0 | } |
1727 | 0 |
|
1728 | 0 | // This should be kept in sync with the LifecycleState enum in |
1729 | 0 | // MediaStreamGraphImpl.h |
1730 | 0 | const char* LifecycleState_str[] = { |
1731 | 0 | "LIFECYCLE_THREAD_NOT_STARTED", |
1732 | 0 | "LIFECYCLE_RUNNING", |
1733 | 0 | "LIFECYCLE_WAITING_FOR_MAIN_THREAD_CLEANUP", |
1734 | 0 | "LIFECYCLE_WAITING_FOR_THREAD_SHUTDOWN", |
1735 | 0 | "LIFECYCLE_WAITING_FOR_STREAM_DESTRUCTION" |
1736 | 0 | }; |
1737 | 0 |
|
1738 | 0 | if (LifecycleStateRef() != LIFECYCLE_RUNNING) { |
1739 | 0 | LOG(LogLevel::Debug, |
1740 | 0 | ("%p: Running stable state callback. Current state: %s", |
1741 | 0 | this, |
1742 | 0 | LifecycleState_str[LifecycleStateRef()])); |
1743 | 0 | } |
1744 | 0 |
|
1745 | 0 | runnables.SwapElements(mUpdateRunnables); |
1746 | 0 | for (uint32_t i = 0; i < mStreamUpdates.Length(); ++i) { |
1747 | 0 | StreamUpdate* update = &mStreamUpdates[i]; |
1748 | 0 | if (update->mStream) { |
1749 | 0 | ApplyStreamUpdate(update); |
1750 | 0 | } |
1751 | 0 | } |
1752 | 0 | mStreamUpdates.Clear(); |
1753 | 0 |
|
1754 | 0 | if (mCurrentTaskMessageQueue.IsEmpty()) { |
1755 | 0 | if (LifecycleStateRef() == LIFECYCLE_WAITING_FOR_MAIN_THREAD_CLEANUP && IsEmpty()) { |
1756 | 0 | // Complete shutdown. First, ensure that this graph is no longer used. |
1757 | 0 | // A new graph graph will be created if one is needed. |
1758 | 0 | // Asynchronously clean up old graph. We don't want to do this |
1759 | 0 | // synchronously because it spins the event loop waiting for threads |
1760 | 0 | // to shut down, and we don't want to do that in a stable state handler. |
1761 | 0 | LifecycleStateRef() = LIFECYCLE_WAITING_FOR_THREAD_SHUTDOWN; |
1762 | 0 | LOG(LogLevel::Debug, |
1763 | 0 | ("%p: Sending MediaStreamGraphShutDownRunnable", this)); |
1764 | 0 | nsCOMPtr<nsIRunnable> event = new MediaStreamGraphShutDownRunnable(this ); |
1765 | 0 | mAbstractMainThread->Dispatch(event.forget()); |
1766 | 0 |
|
1767 | 0 | LOG(LogLevel::Debug, ("%p: Disconnecting MediaStreamGraph", this)); |
1768 | 0 |
|
1769 | 0 | // Find the graph in the hash table and remove it. |
1770 | 0 | for (auto iter = gGraphs.Iter(); !iter.Done(); iter.Next()) { |
1771 | 0 | if (iter.UserData() == this) { |
1772 | 0 | iter.Remove(); |
1773 | 0 | break; |
1774 | 0 | } |
1775 | 0 | } |
1776 | 0 | } |
1777 | 0 | } else { |
1778 | 0 | if (LifecycleStateRef() <= LIFECYCLE_WAITING_FOR_MAIN_THREAD_CLEANUP) { |
1779 | 0 | MessageBlock* block = mBackMessageQueue.AppendElement(); |
1780 | 0 | block->mMessages.SwapElements(mCurrentTaskMessageQueue); |
1781 | 0 | EnsureNextIterationLocked(); |
1782 | 0 | } |
1783 | 0 |
|
1784 | 0 | // If the MediaStreamGraph has more messages going to it, try to revive |
1785 | 0 | // it to process those messages. Don't do this if we're in a forced |
1786 | 0 | // shutdown or it's a non-realtime graph that has already terminated |
1787 | 0 | // processing. |
1788 | 0 | if (LifecycleStateRef() == LIFECYCLE_WAITING_FOR_MAIN_THREAD_CLEANUP && |
1789 | 0 | mRealtime && !mForceShutDown) { |
1790 | 0 | LifecycleStateRef() = LIFECYCLE_RUNNING; |
1791 | 0 | // Revive the MediaStreamGraph since we have more messages going to it. |
1792 | 0 | // Note that we need to put messages into its queue before reviving it, |
1793 | 0 | // or it might exit immediately. |
1794 | 0 | { |
1795 | 0 | LOG(LogLevel::Debug, |
1796 | 0 | ("%p: Reviving this graph! %s", |
1797 | 0 | this, |
1798 | 0 | CurrentDriver()->AsAudioCallbackDriver() ? "AudioCallbackDriver" |
1799 | 0 | : "SystemClockDriver")); |
1800 | 0 | RefPtr<GraphDriver> driver = CurrentDriver(); |
1801 | 0 | MonitorAutoUnlock unlock(mMonitor); |
1802 | 0 | driver->Revive(); |
1803 | 0 | } |
1804 | 0 | } |
1805 | 0 | } |
1806 | 0 |
|
1807 | 0 | // Don't start the thread for a non-realtime graph until it has been |
1808 | 0 | // explicitly started by StartNonRealtimeProcessing. |
1809 | 0 | if (LifecycleStateRef() == LIFECYCLE_THREAD_NOT_STARTED && |
1810 | 0 | (mRealtime || mNonRealtimeProcessing)) { |
1811 | 0 | LifecycleStateRef() = LIFECYCLE_RUNNING; |
1812 | 0 | // Start the thread now. We couldn't start it earlier because |
1813 | 0 | // the graph might exit immediately on finding it has no streams. The |
1814 | 0 | // first message for a new graph must create a stream. |
1815 | 0 | { |
1816 | 0 | // We should exit the monitor for now, because starting a stream might |
1817 | 0 | // take locks, and we don't want to deadlock. |
1818 | 0 | LOG(LogLevel::Debug, |
1819 | 0 | ("%p: Starting a graph with a %s", |
1820 | 0 | this, |
1821 | 0 | CurrentDriver()->AsAudioCallbackDriver() ? "AudioCallbackDriver" |
1822 | 0 | : "SystemClockDriver")); |
1823 | 0 | RefPtr<GraphDriver> driver = CurrentDriver(); |
1824 | 0 | MonitorAutoUnlock unlock(mMonitor); |
1825 | 0 | driver->Start(); |
1826 | 0 | // It's not safe to Shutdown() a thread from StableState, and |
1827 | 0 | // releasing this may shutdown a SystemClockDriver thread. |
1828 | 0 | // Proxy the release to outside of StableState. |
1829 | 0 | NS_ReleaseOnMainThreadSystemGroup( |
1830 | 0 | "MediaStreamGraphImpl::CurrentDriver", driver.forget(), |
1831 | 0 | true); // always proxy |
1832 | 0 | } |
1833 | 0 | } |
1834 | 0 |
|
1835 | 0 | if ((mForceShutDown || !mRealtime) && |
1836 | 0 | LifecycleStateRef() == LIFECYCLE_WAITING_FOR_MAIN_THREAD_CLEANUP) { |
1837 | 0 | // Defer calls to RunDuringShutdown() to happen while mMonitor is not held. |
1838 | 0 | for (uint32_t i = 0; i < mBackMessageQueue.Length(); ++i) { |
1839 | 0 | MessageBlock& mb = mBackMessageQueue[i]; |
1840 | 0 | controlMessagesToRunDuringShutdown.AppendElements(std::move(mb.mMessages)); |
1841 | 0 | } |
1842 | 0 | mBackMessageQueue.Clear(); |
1843 | 0 | MOZ_ASSERT(mCurrentTaskMessageQueue.IsEmpty()); |
1844 | 0 | // Stop MediaStreamGraph threads. Do not clear gGraph since |
1845 | 0 | // we have outstanding DOM objects that may need it. |
1846 | 0 | LifecycleStateRef() = LIFECYCLE_WAITING_FOR_THREAD_SHUTDOWN; |
1847 | 0 | nsCOMPtr<nsIRunnable> event = new MediaStreamGraphShutDownRunnable(this); |
1848 | 0 | mAbstractMainThread->Dispatch(event.forget()); |
1849 | 0 | } |
1850 | 0 |
|
1851 | 0 | mDetectedNotRunning = LifecycleStateRef() > LIFECYCLE_RUNNING; |
1852 | 0 | } |
1853 | 0 |
|
1854 | 0 | // Make sure we get a new current time in the next event loop task |
1855 | 0 | if (!aSourceIsMSG) { |
1856 | 0 | MOZ_ASSERT(mPostedRunInStableState); |
1857 | 0 | mPostedRunInStableState = false; |
1858 | 0 | } |
1859 | 0 |
|
1860 | 0 | for (uint32_t i = 0; i < controlMessagesToRunDuringShutdown.Length(); ++i) { |
1861 | 0 | controlMessagesToRunDuringShutdown[i]->RunDuringShutdown(); |
1862 | 0 | } |
1863 | 0 |
|
1864 | | #ifdef DEBUG |
1865 | | mCanRunMessagesSynchronously = mDetectedNotRunning && |
1866 | | LifecycleStateRef() >= LIFECYCLE_WAITING_FOR_THREAD_SHUTDOWN; |
1867 | | #endif |
1868 | |
|
1869 | 0 | for (uint32_t i = 0; i < runnables.Length(); ++i) { |
1870 | 0 | runnables[i]->Run(); |
1871 | 0 | } |
1872 | 0 | } |
1873 | | |
1874 | | |
1875 | | void |
1876 | | MediaStreamGraphImpl::EnsureRunInStableState() |
1877 | 0 | { |
1878 | 0 | MOZ_ASSERT(NS_IsMainThread(), "main thread only"); |
1879 | 0 |
|
1880 | 0 | if (mPostedRunInStableState) |
1881 | 0 | return; |
1882 | 0 | mPostedRunInStableState = true; |
1883 | 0 | nsCOMPtr<nsIRunnable> event = new MediaStreamGraphStableStateRunnable(this, false); |
1884 | 0 | nsContentUtils::RunInStableState(event.forget()); |
1885 | 0 | } |
1886 | | |
1887 | | void |
1888 | | MediaStreamGraphImpl::EnsureStableStateEventPosted() |
1889 | 0 | { |
1890 | 0 | MOZ_ASSERT(OnGraphThread()); |
1891 | 0 | mMonitor.AssertCurrentThreadOwns(); |
1892 | 0 |
|
1893 | 0 | if (mPostedRunInStableStateEvent) |
1894 | 0 | return; |
1895 | 0 | mPostedRunInStableStateEvent = true; |
1896 | 0 | nsCOMPtr<nsIRunnable> event = new MediaStreamGraphStableStateRunnable(this, true); |
1897 | 0 | mAbstractMainThread->Dispatch(event.forget()); |
1898 | 0 | } |
1899 | | |
1900 | | void |
1901 | | MediaStreamGraphImpl::SignalMainThreadCleanup() |
1902 | 0 | { |
1903 | 0 | MOZ_ASSERT(mDriver->OnThread()); |
1904 | 0 |
|
1905 | 0 | MonitorAutoLock lock(mMonitor); |
1906 | 0 | // LIFECYCLE_THREAD_NOT_STARTED is possible when shutting down offline |
1907 | 0 | // graphs that have not started. |
1908 | 0 | MOZ_DIAGNOSTIC_ASSERT(mLifecycleState <= LIFECYCLE_RUNNING); |
1909 | 0 | LOG(LogLevel::Debug, |
1910 | 0 | ("%p: MediaStreamGraph waiting for main thread cleanup", this)); |
1911 | 0 | LifecycleStateRef() = |
1912 | 0 | MediaStreamGraphImpl::LIFECYCLE_WAITING_FOR_MAIN_THREAD_CLEANUP; |
1913 | 0 | EnsureStableStateEventPosted(); |
1914 | 0 | } |
1915 | | |
1916 | | void |
1917 | | MediaStreamGraphImpl::AppendMessage(UniquePtr<ControlMessage> aMessage) |
1918 | 0 | { |
1919 | 0 | MOZ_ASSERT(NS_IsMainThread(), "main thread only"); |
1920 | 0 | MOZ_ASSERT(!aMessage->GetStream() || |
1921 | 0 | !aMessage->GetStream()->IsDestroyed(), |
1922 | 0 | "Stream already destroyed"); |
1923 | 0 |
|
1924 | 0 | if (mDetectedNotRunning && |
1925 | 0 | LifecycleStateRef() > LIFECYCLE_WAITING_FOR_MAIN_THREAD_CLEANUP) { |
1926 | 0 | // The graph control loop is not running and main thread cleanup has |
1927 | 0 | // happened. From now on we can't append messages to mCurrentTaskMessageQueue, |
1928 | 0 | // because that will never be processed again, so just RunDuringShutdown |
1929 | 0 | // this message. |
1930 | 0 | // This should only happen during forced shutdown, or after a non-realtime |
1931 | 0 | // graph has finished processing. |
1932 | | #ifdef DEBUG |
1933 | | MOZ_ASSERT(mCanRunMessagesSynchronously); |
1934 | | mCanRunMessagesSynchronously = false; |
1935 | | #endif |
1936 | | aMessage->RunDuringShutdown(); |
1937 | | #ifdef DEBUG |
1938 | | mCanRunMessagesSynchronously = true; |
1939 | | #endif |
1940 | 0 | if (IsEmpty() && |
1941 | 0 | LifecycleStateRef() >= LIFECYCLE_WAITING_FOR_STREAM_DESTRUCTION) { |
1942 | 0 |
|
1943 | 0 | // Find the graph in the hash table and remove it. |
1944 | 0 | for (auto iter = gGraphs.Iter(); !iter.Done(); iter.Next()) { |
1945 | 0 | if (iter.UserData() == this) { |
1946 | 0 | iter.Remove(); |
1947 | 0 | break; |
1948 | 0 | } |
1949 | 0 | } |
1950 | 0 |
|
1951 | 0 | Destroy(); |
1952 | 0 | } |
1953 | 0 | return; |
1954 | 0 | } |
1955 | 0 |
|
1956 | 0 | mCurrentTaskMessageQueue.AppendElement(std::move(aMessage)); |
1957 | 0 | EnsureRunInStableState(); |
1958 | 0 | } |
1959 | | |
1960 | | void |
1961 | | MediaStreamGraphImpl::Dispatch(already_AddRefed<nsIRunnable>&& aRunnable) |
1962 | 0 | { |
1963 | 0 | mAbstractMainThread->Dispatch(std::move(aRunnable)); |
1964 | 0 | } |
1965 | | |
1966 | | MediaStream::MediaStream() |
1967 | | : mTracksStartTime(0) |
1968 | | , mStartBlocking(GRAPH_TIME_MAX) |
1969 | | , mSuspendedCount(0) |
1970 | | , mFinished(false) |
1971 | | , mNotifiedFinished(false) |
1972 | | , mNotifiedBlocked(false) |
1973 | | , mHasCurrentData(false) |
1974 | | , mNotifiedHasCurrentData(false) |
1975 | | , mMainThreadCurrentTime(0) |
1976 | | , mMainThreadFinished(false) |
1977 | | , mFinishedNotificationSent(false) |
1978 | | , mMainThreadDestroyed(false) |
1979 | | , mNrOfMainThreadUsers(0) |
1980 | | , mGraph(nullptr) |
1981 | 0 | { |
1982 | 0 | MOZ_COUNT_CTOR(MediaStream); |
1983 | 0 | } |
1984 | | |
1985 | | MediaStream::~MediaStream() |
1986 | 0 | { |
1987 | 0 | MOZ_COUNT_DTOR(MediaStream); |
1988 | 0 | NS_ASSERTION(mMainThreadDestroyed, "Should have been destroyed already"); |
1989 | 0 | NS_ASSERTION(mMainThreadListeners.IsEmpty(), |
1990 | 0 | "All main thread listeners should have been removed"); |
1991 | 0 | } |
1992 | | |
1993 | | size_t |
1994 | | MediaStream::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const |
1995 | 0 | { |
1996 | 0 | size_t amount = 0; |
1997 | 0 |
|
1998 | 0 | // Not owned: |
1999 | 0 | // - mGraph - Not reported here |
2000 | 0 | // - mConsumers - elements |
2001 | 0 | // Future: |
2002 | 0 | // - mVideoOutputs - elements |
2003 | 0 | // - mLastPlayedVideoFrame |
2004 | 0 | // - mListeners - elements |
2005 | 0 | // - mAudioOutputStream - elements |
2006 | 0 |
|
2007 | 0 | amount += mTracks.SizeOfExcludingThis(aMallocSizeOf); |
2008 | 0 | amount += mAudioOutputs.ShallowSizeOfExcludingThis(aMallocSizeOf); |
2009 | 0 | amount += mVideoOutputs.ShallowSizeOfExcludingThis(aMallocSizeOf); |
2010 | 0 | amount += mListeners.ShallowSizeOfExcludingThis(aMallocSizeOf); |
2011 | 0 | amount += mMainThreadListeners.ShallowSizeOfExcludingThis(aMallocSizeOf); |
2012 | 0 | amount += mDisabledTracks.ShallowSizeOfExcludingThis(aMallocSizeOf); |
2013 | 0 | amount += mConsumers.ShallowSizeOfExcludingThis(aMallocSizeOf); |
2014 | 0 |
|
2015 | 0 | return amount; |
2016 | 0 | } |
2017 | | |
2018 | | size_t |
2019 | | MediaStream::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const |
2020 | 0 | { |
2021 | 0 | return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); |
2022 | 0 | } |
2023 | | |
2024 | | void |
2025 | | MediaStream::IncrementSuspendCount() |
2026 | 0 | { |
2027 | 0 | ++mSuspendedCount; |
2028 | 0 | if (mSuspendedCount == 1) { |
2029 | 0 | for (uint32_t i = 0; i < mConsumers.Length(); ++i) { |
2030 | 0 | mConsumers[i]->Suspended(); |
2031 | 0 | } |
2032 | 0 | } |
2033 | 0 | } |
2034 | | |
2035 | | void |
2036 | | MediaStream::DecrementSuspendCount() |
2037 | 0 | { |
2038 | 0 | NS_ASSERTION(mSuspendedCount > 0, "Suspend count underrun"); |
2039 | 0 | --mSuspendedCount; |
2040 | 0 |
|
2041 | 0 | if (mSuspendedCount == 0) { |
2042 | 0 | for (uint32_t i = 0; i < mConsumers.Length(); ++i) { |
2043 | 0 | mConsumers[i]->Resumed(); |
2044 | 0 | } |
2045 | 0 | } |
2046 | 0 | } |
2047 | | |
2048 | | MediaStreamGraphImpl* |
2049 | | MediaStream::GraphImpl() |
2050 | 0 | { |
2051 | 0 | return mGraph; |
2052 | 0 | } |
2053 | | |
2054 | | const MediaStreamGraphImpl* |
2055 | | MediaStream::GraphImpl() const |
2056 | 0 | { |
2057 | 0 | return mGraph; |
2058 | 0 | } |
2059 | | |
2060 | | MediaStreamGraph* |
2061 | | MediaStream::Graph() |
2062 | 0 | { |
2063 | 0 | return mGraph; |
2064 | 0 | } |
2065 | | |
2066 | | void |
2067 | | MediaStream::SetGraphImpl(MediaStreamGraphImpl* aGraph) |
2068 | 0 | { |
2069 | 0 | MOZ_ASSERT(!mGraph, "Should only be called once"); |
2070 | 0 | mGraph = aGraph; |
2071 | 0 | mTracks.InitGraphRate(aGraph->GraphRate()); |
2072 | 0 | } |
2073 | | |
2074 | | void |
2075 | | MediaStream::SetGraphImpl(MediaStreamGraph* aGraph) |
2076 | 0 | { |
2077 | 0 | MediaStreamGraphImpl* graph = static_cast<MediaStreamGraphImpl*>(aGraph); |
2078 | 0 | SetGraphImpl(graph); |
2079 | 0 | } |
2080 | | |
2081 | | StreamTime |
2082 | | MediaStream::GraphTimeToStreamTime(GraphTime aTime) const |
2083 | 0 | { |
2084 | 0 | NS_ASSERTION(mStartBlocking == GraphImpl()->mStateComputedTime || |
2085 | 0 | aTime <= mStartBlocking, |
2086 | 0 | "Incorrectly ignoring blocking!"); |
2087 | 0 | return aTime - mTracksStartTime; |
2088 | 0 | } |
2089 | | |
2090 | | GraphTime |
2091 | | MediaStream::StreamTimeToGraphTime(StreamTime aTime) const |
2092 | 0 | { |
2093 | 0 | NS_ASSERTION(mStartBlocking == GraphImpl()->mStateComputedTime || |
2094 | 0 | aTime + mTracksStartTime <= mStartBlocking, |
2095 | 0 | "Incorrectly ignoring blocking!"); |
2096 | 0 | return aTime + mTracksStartTime; |
2097 | 0 | } |
2098 | | |
2099 | | StreamTime |
2100 | | MediaStream::GraphTimeToStreamTimeWithBlocking(GraphTime aTime) const |
2101 | 0 | { |
2102 | 0 | return GraphImpl()->GraphTimeToStreamTimeWithBlocking(this, aTime); |
2103 | 0 | } |
2104 | | |
2105 | | void |
2106 | | MediaStream::FinishOnGraphThread() |
2107 | 0 | { |
2108 | 0 | if (mFinished) { |
2109 | 0 | return; |
2110 | 0 | } |
2111 | 0 | LOG(LogLevel::Debug, ("MediaStream %p will finish", this)); |
2112 | | #ifdef DEBUG |
2113 | | if (!mGraph->mForceShutDown) { |
2114 | | // All tracks must be ended by the source before the stream finishes. |
2115 | | // The exception is in forced shutdown, where we finish all streams as is. |
2116 | | for (StreamTracks::TrackIter track(mTracks); !track.IsEnded(); track.Next()) { |
2117 | | if (!track->IsEnded()) { |
2118 | | LOG(LogLevel::Error, |
2119 | | ("MediaStream %p will finish, but track %d has not ended.", |
2120 | | this, |
2121 | | track->GetID())); |
2122 | | NS_ASSERTION(false, "Finished stream cannot contain live track"); |
2123 | | } |
2124 | | } |
2125 | | } |
2126 | | #endif |
2127 | | mFinished = true; |
2128 | 0 | mTracks.AdvanceKnownTracksTime(STREAM_TIME_MAX); |
2129 | 0 |
|
2130 | 0 | // Let the MSG knows that this stream can be destroyed if necessary to avoid |
2131 | 0 | // unnecessarily processing it in the future. |
2132 | 0 | GraphImpl()->SetStreamOrderDirty(); |
2133 | 0 | } |
2134 | | |
2135 | | StreamTracks::Track* |
2136 | | MediaStream::FindTrack(TrackID aID) const |
2137 | 0 | { |
2138 | 0 | return mTracks.FindTrack(aID); |
2139 | 0 | } |
2140 | | |
2141 | | StreamTracks::Track* |
2142 | | MediaStream::EnsureTrack(TrackID aTrackId) |
2143 | 0 | { |
2144 | 0 | StreamTracks::Track* track = mTracks.FindTrack(aTrackId); |
2145 | 0 | if (!track) { |
2146 | 0 | nsAutoPtr<MediaSegment> segment(new AudioSegment()); |
2147 | 0 | for (uint32_t j = 0; j < mListeners.Length(); ++j) { |
2148 | 0 | MediaStreamListener* l = mListeners[j]; |
2149 | 0 | l->NotifyQueuedTrackChanges(Graph(), aTrackId, 0, |
2150 | 0 | TrackEventCommand::TRACK_EVENT_CREATED, |
2151 | 0 | *segment); |
2152 | 0 | // TODO If we ever need to ensure several tracks at once, we will have to |
2153 | 0 | // change this. |
2154 | 0 | l->NotifyFinishedTrackCreation(Graph()); |
2155 | 0 | } |
2156 | 0 | track = &mTracks.AddTrack(aTrackId, 0, segment.forget()); |
2157 | 0 | } |
2158 | 0 | return track; |
2159 | 0 | } |
2160 | | |
2161 | | void |
2162 | | MediaStream::RemoveAllListenersImpl() |
2163 | 0 | { |
2164 | 0 | GraphImpl()->AssertOnGraphThreadOrNotRunning(); |
2165 | 0 |
|
2166 | 0 | auto streamListeners(mListeners); |
2167 | 0 | for (auto& l : streamListeners) { |
2168 | 0 | l->NotifyEvent(GraphImpl(), MediaStreamGraphEvent::EVENT_REMOVED); |
2169 | 0 | } |
2170 | 0 | mListeners.Clear(); |
2171 | 0 |
|
2172 | 0 | auto trackListeners(mTrackListeners); |
2173 | 0 | for (auto& l : trackListeners) { |
2174 | 0 | l.mListener->NotifyRemoved(); |
2175 | 0 | } |
2176 | 0 | mTrackListeners.Clear(); |
2177 | 0 |
|
2178 | 0 | RemoveAllDirectListenersImpl(); |
2179 | 0 |
|
2180 | 0 | auto videoOutputs(mVideoOutputs); |
2181 | 0 | for (auto& l : videoOutputs) { |
2182 | 0 | l.mListener->NotifyRemoved(); |
2183 | 0 | } |
2184 | 0 | mVideoOutputs.Clear(); |
2185 | 0 | } |
2186 | | |
2187 | | void |
2188 | | MediaStream::DestroyImpl() |
2189 | 0 | { |
2190 | 0 | for (int32_t i = mConsumers.Length() - 1; i >= 0; --i) { |
2191 | 0 | mConsumers[i]->Disconnect(); |
2192 | 0 | } |
2193 | 0 | mTracks.Clear(); |
2194 | 0 | mGraph = nullptr; |
2195 | 0 | } |
2196 | | |
2197 | | void |
2198 | | MediaStream::Destroy() |
2199 | 0 | { |
2200 | 0 | NS_ASSERTION(mNrOfMainThreadUsers == 0, |
2201 | 0 | "Do not mix Destroy() and RegisterUser()/UnregisterUser()"); |
2202 | 0 | // Keep this stream alive until we leave this method |
2203 | 0 | RefPtr<MediaStream> kungFuDeathGrip = this; |
2204 | 0 |
|
2205 | 0 | class Message : public ControlMessage { |
2206 | 0 | public: |
2207 | 0 | explicit Message(MediaStream* aStream) : ControlMessage(aStream) {} |
2208 | 0 | void Run() override |
2209 | 0 | { |
2210 | 0 | mStream->RemoveAllListenersImpl(); |
2211 | 0 | auto graph = mStream->GraphImpl(); |
2212 | 0 | mStream->DestroyImpl(); |
2213 | 0 | graph->RemoveStreamGraphThread(mStream); |
2214 | 0 | } |
2215 | 0 | void RunDuringShutdown() override |
2216 | 0 | { Run(); } |
2217 | 0 | }; |
2218 | 0 | GraphImpl()->AppendMessage(MakeUnique<Message>(this)); |
2219 | 0 | // Message::RunDuringShutdown may have removed this stream from the graph, |
2220 | 0 | // but our kungFuDeathGrip above will have kept this stream alive if |
2221 | 0 | // necessary. |
2222 | 0 | mMainThreadDestroyed = true; |
2223 | 0 | } |
2224 | | |
2225 | | void |
2226 | | MediaStream::RegisterUser() |
2227 | 0 | { |
2228 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
2229 | 0 | ++mNrOfMainThreadUsers; |
2230 | 0 | } |
2231 | | |
2232 | | void |
2233 | | MediaStream::UnregisterUser() |
2234 | 0 | { |
2235 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
2236 | 0 |
|
2237 | 0 | --mNrOfMainThreadUsers; |
2238 | 0 | NS_ASSERTION(mNrOfMainThreadUsers >= 0, "Double-removal of main thread user"); |
2239 | 0 | NS_ASSERTION(!IsDestroyed(), "Do not mix Destroy() and RegisterUser()/UnregisterUser()"); |
2240 | 0 | if (mNrOfMainThreadUsers == 0) { |
2241 | 0 | Destroy(); |
2242 | 0 | } |
2243 | 0 | } |
2244 | | |
2245 | | void |
2246 | | MediaStream::AddAudioOutput(void* aKey) |
2247 | 0 | { |
2248 | 0 | class Message : public ControlMessage { |
2249 | 0 | public: |
2250 | 0 | Message(MediaStream* aStream, void* aKey) : ControlMessage(aStream), mKey(aKey) {} |
2251 | 0 | void Run() override |
2252 | 0 | { |
2253 | 0 | mStream->AddAudioOutputImpl(mKey); |
2254 | 0 | } |
2255 | 0 | void* mKey; |
2256 | 0 | }; |
2257 | 0 | GraphImpl()->AppendMessage(MakeUnique<Message>(this, aKey)); |
2258 | 0 | } |
2259 | | |
2260 | | void |
2261 | | MediaStream::SetAudioOutputVolumeImpl(void* aKey, float aVolume) |
2262 | 0 | { |
2263 | 0 | for (uint32_t i = 0; i < mAudioOutputs.Length(); ++i) { |
2264 | 0 | if (mAudioOutputs[i].mKey == aKey) { |
2265 | 0 | mAudioOutputs[i].mVolume = aVolume; |
2266 | 0 | return; |
2267 | 0 | } |
2268 | 0 | } |
2269 | 0 | NS_ERROR("Audio output key not found"); |
2270 | 0 | } |
2271 | | |
2272 | | void |
2273 | | MediaStream::SetAudioOutputVolume(void* aKey, float aVolume) |
2274 | 0 | { |
2275 | 0 | class Message : public ControlMessage { |
2276 | 0 | public: |
2277 | 0 | Message(MediaStream* aStream, void* aKey, float aVolume) : |
2278 | 0 | ControlMessage(aStream), mKey(aKey), mVolume(aVolume) {} |
2279 | 0 | void Run() override |
2280 | 0 | { |
2281 | 0 | mStream->SetAudioOutputVolumeImpl(mKey, mVolume); |
2282 | 0 | } |
2283 | 0 | void* mKey; |
2284 | 0 | float mVolume; |
2285 | 0 | }; |
2286 | 0 | GraphImpl()->AppendMessage(MakeUnique<Message>(this, aKey, aVolume)); |
2287 | 0 | } |
2288 | | |
2289 | | void |
2290 | | MediaStream::AddAudioOutputImpl(void* aKey) |
2291 | 0 | { |
2292 | 0 | LOG(LogLevel::Info, |
2293 | 0 | ("MediaStream %p Adding AudioOutput for key %p", this, aKey)); |
2294 | 0 | mAudioOutputs.AppendElement(AudioOutput(aKey)); |
2295 | 0 | } |
2296 | | |
2297 | | void |
2298 | | MediaStream::RemoveAudioOutputImpl(void* aKey) |
2299 | 0 | { |
2300 | 0 | LOG(LogLevel::Info, |
2301 | 0 | ("MediaStream %p Removing AudioOutput for key %p", this, aKey)); |
2302 | 0 | for (uint32_t i = 0; i < mAudioOutputs.Length(); ++i) { |
2303 | 0 | if (mAudioOutputs[i].mKey == aKey) { |
2304 | 0 | mAudioOutputs.RemoveElementAt(i); |
2305 | 0 | return; |
2306 | 0 | } |
2307 | 0 | } |
2308 | 0 | NS_ERROR("Audio output key not found"); |
2309 | 0 | } |
2310 | | |
2311 | | void |
2312 | | MediaStream::RemoveAudioOutput(void* aKey) |
2313 | 0 | { |
2314 | 0 | class Message : public ControlMessage { |
2315 | 0 | public: |
2316 | 0 | Message(MediaStream* aStream, void* aKey) : |
2317 | 0 | ControlMessage(aStream), mKey(aKey) {} |
2318 | 0 | void Run() override |
2319 | 0 | { |
2320 | 0 | mStream->RemoveAudioOutputImpl(mKey); |
2321 | 0 | } |
2322 | 0 | void* mKey; |
2323 | 0 | }; |
2324 | 0 | GraphImpl()->AppendMessage(MakeUnique<Message>(this, aKey)); |
2325 | 0 | } |
2326 | | |
2327 | | void |
2328 | | MediaStream::AddVideoOutputImpl(already_AddRefed<MediaStreamVideoSink> aSink, |
2329 | | TrackID aID) |
2330 | 0 | { |
2331 | 0 | RefPtr<MediaStreamVideoSink> sink = aSink; |
2332 | 0 | LOG(LogLevel::Info, |
2333 | 0 | ("MediaStream %p Adding MediaStreamVideoSink %p as output", |
2334 | 0 | this, |
2335 | 0 | sink.get())); |
2336 | 0 | MOZ_ASSERT(aID != TRACK_NONE); |
2337 | 0 | for (auto entry : mVideoOutputs) { |
2338 | 0 | if (entry.mListener == sink && |
2339 | 0 | (entry.mTrackID == TRACK_ANY || entry.mTrackID == aID)) { |
2340 | 0 | return; |
2341 | 0 | } |
2342 | 0 | } |
2343 | 0 | TrackBound<MediaStreamVideoSink>* l = mVideoOutputs.AppendElement(); |
2344 | 0 | l->mListener = sink; |
2345 | 0 | l->mTrackID = aID; |
2346 | 0 |
|
2347 | 0 | AddDirectTrackListenerImpl(sink.forget(), aID); |
2348 | 0 | } |
2349 | | |
2350 | | void |
2351 | | MediaStream::RemoveVideoOutputImpl(MediaStreamVideoSink* aSink, |
2352 | | TrackID aID) |
2353 | 0 | { |
2354 | 0 | LOG( |
2355 | 0 | LogLevel::Info, |
2356 | 0 | ("MediaStream %p Removing MediaStreamVideoSink %p as output", this, aSink)); |
2357 | 0 | MOZ_ASSERT(aID != TRACK_NONE); |
2358 | 0 |
|
2359 | 0 | // Ensure that any frames currently queued for playback by the compositor |
2360 | 0 | // are removed. |
2361 | 0 | aSink->ClearFrames(); |
2362 | 0 | for (size_t i = 0; i < mVideoOutputs.Length(); ++i) { |
2363 | 0 | if (mVideoOutputs[i].mListener == aSink && |
2364 | 0 | (mVideoOutputs[i].mTrackID == TRACK_ANY || |
2365 | 0 | mVideoOutputs[i].mTrackID == aID)) { |
2366 | 0 | mVideoOutputs.RemoveElementAt(i); |
2367 | 0 | } |
2368 | 0 | } |
2369 | 0 |
|
2370 | 0 | RemoveDirectTrackListenerImpl(aSink, aID); |
2371 | 0 | } |
2372 | | |
2373 | | void |
2374 | | MediaStream::AddVideoOutput(MediaStreamVideoSink* aSink, TrackID aID) |
2375 | 0 | { |
2376 | 0 | class Message : public ControlMessage { |
2377 | 0 | public: |
2378 | 0 | Message(MediaStream* aStream, MediaStreamVideoSink* aSink, TrackID aID) : |
2379 | 0 | ControlMessage(aStream), mSink(aSink), mID(aID) {} |
2380 | 0 | void Run() override |
2381 | 0 | { |
2382 | 0 | mStream->AddVideoOutputImpl(mSink.forget(), mID); |
2383 | 0 | } |
2384 | 0 | RefPtr<MediaStreamVideoSink> mSink; |
2385 | 0 | TrackID mID; |
2386 | 0 | }; |
2387 | 0 | GraphImpl()->AppendMessage(MakeUnique<Message>(this, aSink, aID)); |
2388 | 0 | } |
2389 | | |
2390 | | void |
2391 | | MediaStream::RemoveVideoOutput(MediaStreamVideoSink* aSink, TrackID aID) |
2392 | 0 | { |
2393 | 0 | class Message : public ControlMessage { |
2394 | 0 | public: |
2395 | 0 | Message(MediaStream* aStream, MediaStreamVideoSink* aSink, TrackID aID) : |
2396 | 0 | ControlMessage(aStream), mSink(aSink), mID(aID) {} |
2397 | 0 | void Run() override |
2398 | 0 | { |
2399 | 0 | mStream->RemoveVideoOutputImpl(mSink, mID); |
2400 | 0 | } |
2401 | 0 | RefPtr<MediaStreamVideoSink> mSink; |
2402 | 0 | TrackID mID; |
2403 | 0 | }; |
2404 | 0 | GraphImpl()->AppendMessage(MakeUnique<Message>(this, aSink, aID)); |
2405 | 0 | } |
2406 | | |
2407 | | void |
2408 | | MediaStream::Suspend() |
2409 | 0 | { |
2410 | 0 | class Message : public ControlMessage { |
2411 | 0 | public: |
2412 | 0 | explicit Message(MediaStream* aStream) : |
2413 | 0 | ControlMessage(aStream) {} |
2414 | 0 | void Run() override |
2415 | 0 | { |
2416 | 0 | mStream->GraphImpl()->IncrementSuspendCount(mStream); |
2417 | 0 | } |
2418 | 0 | }; |
2419 | 0 |
|
2420 | 0 | // This can happen if this method has been called asynchronously, and the |
2421 | 0 | // stream has been destroyed since then. |
2422 | 0 | if (mMainThreadDestroyed) { |
2423 | 0 | return; |
2424 | 0 | } |
2425 | 0 | GraphImpl()->AppendMessage(MakeUnique<Message>(this)); |
2426 | 0 | } |
2427 | | |
2428 | | void |
2429 | | MediaStream::Resume() |
2430 | 0 | { |
2431 | 0 | class Message : public ControlMessage { |
2432 | 0 | public: |
2433 | 0 | explicit Message(MediaStream* aStream) : |
2434 | 0 | ControlMessage(aStream) {} |
2435 | 0 | void Run() override |
2436 | 0 | { |
2437 | 0 | mStream->GraphImpl()->DecrementSuspendCount(mStream); |
2438 | 0 | } |
2439 | 0 | }; |
2440 | 0 |
|
2441 | 0 | // This can happen if this method has been called asynchronously, and the |
2442 | 0 | // stream has been destroyed since then. |
2443 | 0 | if (mMainThreadDestroyed) { |
2444 | 0 | return; |
2445 | 0 | } |
2446 | 0 | GraphImpl()->AppendMessage(MakeUnique<Message>(this)); |
2447 | 0 | } |
2448 | | |
2449 | | void |
2450 | | MediaStream::AddListenerImpl(already_AddRefed<MediaStreamListener> aListener) |
2451 | 0 | { |
2452 | 0 | MediaStreamListener* listener = *mListeners.AppendElement() = aListener; |
2453 | 0 | listener->NotifyBlockingChanged(GraphImpl(), |
2454 | 0 | mNotifiedBlocked ? MediaStreamListener::BLOCKED : MediaStreamListener::UNBLOCKED); |
2455 | 0 |
|
2456 | 0 | for (StreamTracks::TrackIter it(mTracks); !it.IsEnded(); it.Next()) { |
2457 | 0 | MediaStream* inputStream = nullptr; |
2458 | 0 | TrackID inputTrackID = TRACK_INVALID; |
2459 | 0 | if (ProcessedMediaStream* ps = AsProcessedStream()) { |
2460 | 0 | // The only ProcessedMediaStream where we should have listeners is |
2461 | 0 | // TrackUnionStream - it's what's used as owned stream in DOMMediaStream, |
2462 | 0 | // the only main-thread exposed stream type. |
2463 | 0 | // TrackUnionStream guarantees that each of its tracks has an input track. |
2464 | 0 | // Other types do not implement GetInputStreamFor() and will return null. |
2465 | 0 | inputStream = ps->GetInputStreamFor(it->GetID()); |
2466 | 0 | if (!inputStream && it->IsEnded()) { |
2467 | 0 | // If this track has no input anymore we assume there's no data for the |
2468 | 0 | // current iteration either and thus no need to expose it to a listener. |
2469 | 0 | continue; |
2470 | 0 | } |
2471 | 0 | MOZ_ASSERT(inputStream); |
2472 | 0 | inputTrackID = ps->GetInputTrackIDFor(it->GetID()); |
2473 | 0 | MOZ_ASSERT(IsTrackIDExplicit(inputTrackID)); |
2474 | 0 | } |
2475 | 0 |
|
2476 | 0 | uint32_t flags = TrackEventCommand::TRACK_EVENT_CREATED; |
2477 | 0 | if (it->IsEnded()) { |
2478 | 0 | flags |= TrackEventCommand::TRACK_EVENT_ENDED; |
2479 | 0 | } |
2480 | 0 | nsAutoPtr<MediaSegment> segment(it->GetSegment()->CreateEmptyClone()); |
2481 | 0 | listener->NotifyQueuedTrackChanges(Graph(), it->GetID(), it->GetEnd(), |
2482 | 0 | static_cast<TrackEventCommand>(flags), *segment, |
2483 | 0 | inputStream, inputTrackID); |
2484 | 0 | } |
2485 | 0 | if (mNotifiedFinished) { |
2486 | 0 | listener->NotifyEvent(GraphImpl(), MediaStreamGraphEvent::EVENT_FINISHED); |
2487 | 0 | } |
2488 | 0 | if (mNotifiedHasCurrentData) { |
2489 | 0 | listener->NotifyHasCurrentData(GraphImpl()); |
2490 | 0 | } |
2491 | 0 | } |
2492 | | |
2493 | | void |
2494 | | MediaStream::AddListener(MediaStreamListener* aListener) |
2495 | 0 | { |
2496 | 0 | class Message : public ControlMessage { |
2497 | 0 | public: |
2498 | 0 | Message(MediaStream* aStream, MediaStreamListener* aListener) : |
2499 | 0 | ControlMessage(aStream), mListener(aListener) {} |
2500 | 0 | void Run() override |
2501 | 0 | { |
2502 | 0 | mStream->AddListenerImpl(mListener.forget()); |
2503 | 0 | } |
2504 | 0 | RefPtr<MediaStreamListener> mListener; |
2505 | 0 | }; |
2506 | 0 | GraphImpl()->AppendMessage(MakeUnique<Message>(this, aListener)); |
2507 | 0 | } |
2508 | | |
2509 | | void |
2510 | | MediaStream::RemoveListenerImpl(MediaStreamListener* aListener) |
2511 | 0 | { |
2512 | 0 | // wouldn't need this if we could do it in the opposite order |
2513 | 0 | RefPtr<MediaStreamListener> listener(aListener); |
2514 | 0 | mListeners.RemoveElement(aListener); |
2515 | 0 | listener->NotifyEvent(GraphImpl(), MediaStreamGraphEvent::EVENT_REMOVED); |
2516 | 0 | } |
2517 | | |
2518 | | void |
2519 | | MediaStream::RemoveListener(MediaStreamListener* aListener) |
2520 | 0 | { |
2521 | 0 | class Message : public ControlMessage { |
2522 | 0 | public: |
2523 | 0 | Message(MediaStream* aStream, MediaStreamListener* aListener) : |
2524 | 0 | ControlMessage(aStream), mListener(aListener) {} |
2525 | 0 | void Run() override |
2526 | 0 | { |
2527 | 0 | mStream->RemoveListenerImpl(mListener); |
2528 | 0 | } |
2529 | 0 | RefPtr<MediaStreamListener> mListener; |
2530 | 0 | }; |
2531 | 0 | // If the stream is destroyed the Listeners have or will be |
2532 | 0 | // removed. |
2533 | 0 | if (!IsDestroyed()) { |
2534 | 0 | GraphImpl()->AppendMessage(MakeUnique<Message>(this, aListener)); |
2535 | 0 | } |
2536 | 0 | } |
2537 | | |
2538 | | void |
2539 | | MediaStream::AddTrackListenerImpl(already_AddRefed<MediaStreamTrackListener> aListener, |
2540 | | TrackID aTrackID) |
2541 | 0 | { |
2542 | 0 | TrackBound<MediaStreamTrackListener>* l = mTrackListeners.AppendElement(); |
2543 | 0 | l->mListener = aListener; |
2544 | 0 | l->mTrackID = aTrackID; |
2545 | 0 |
|
2546 | 0 | StreamTracks::Track* track = FindTrack(aTrackID); |
2547 | 0 | if (!track) { |
2548 | 0 | return; |
2549 | 0 | } |
2550 | 0 | PrincipalHandle lastPrincipalHandle = |
2551 | 0 | track->GetSegment()->GetLastPrincipalHandle(); |
2552 | 0 | l->mListener->NotifyPrincipalHandleChanged(Graph(), lastPrincipalHandle); |
2553 | 0 | if (track->IsEnded() && |
2554 | 0 | track->GetEnd() <= GraphTimeToStreamTime(GraphImpl()->mStateComputedTime)) { |
2555 | 0 | l->mListener->NotifyEnded(); |
2556 | 0 | } |
2557 | 0 | } |
2558 | | |
2559 | | void |
2560 | | MediaStream::AddTrackListener(MediaStreamTrackListener* aListener, |
2561 | | TrackID aTrackID) |
2562 | 0 | { |
2563 | 0 | class Message : public ControlMessage { |
2564 | 0 | public: |
2565 | 0 | Message(MediaStream* aStream, MediaStreamTrackListener* aListener, |
2566 | 0 | TrackID aTrackID) : |
2567 | 0 | ControlMessage(aStream), mListener(aListener), mTrackID(aTrackID) {} |
2568 | 0 | void Run() override |
2569 | 0 | { |
2570 | 0 | mStream->AddTrackListenerImpl(mListener.forget(), mTrackID); |
2571 | 0 | } |
2572 | 0 | RefPtr<MediaStreamTrackListener> mListener; |
2573 | 0 | TrackID mTrackID; |
2574 | 0 | }; |
2575 | 0 | GraphImpl()->AppendMessage(MakeUnique<Message>(this, aListener, aTrackID)); |
2576 | 0 | } |
2577 | | |
2578 | | void |
2579 | | MediaStream::RemoveTrackListenerImpl(MediaStreamTrackListener* aListener, |
2580 | | TrackID aTrackID) |
2581 | 0 | { |
2582 | 0 | for (size_t i = 0; i < mTrackListeners.Length(); ++i) { |
2583 | 0 | if (mTrackListeners[i].mListener == aListener && |
2584 | 0 | mTrackListeners[i].mTrackID == aTrackID) { |
2585 | 0 | mTrackListeners[i].mListener->NotifyRemoved(); |
2586 | 0 | mTrackListeners.RemoveElementAt(i); |
2587 | 0 | return; |
2588 | 0 | } |
2589 | 0 | } |
2590 | 0 | } |
2591 | | |
2592 | | void |
2593 | | MediaStream::RemoveTrackListener(MediaStreamTrackListener* aListener, |
2594 | | TrackID aTrackID) |
2595 | 0 | { |
2596 | 0 | class Message : public ControlMessage { |
2597 | 0 | public: |
2598 | 0 | Message(MediaStream* aStream, MediaStreamTrackListener* aListener, |
2599 | 0 | TrackID aTrackID) : |
2600 | 0 | ControlMessage(aStream), mListener(aListener), mTrackID(aTrackID) {} |
2601 | 0 | void Run() override |
2602 | 0 | { |
2603 | 0 | mStream->RemoveTrackListenerImpl(mListener, mTrackID); |
2604 | 0 | } |
2605 | 0 | void RunDuringShutdown() override |
2606 | 0 | { |
2607 | 0 | // During shutdown we still want the listener's NotifyRemoved to be |
2608 | 0 | // called, since not doing that might block shutdown of other modules. |
2609 | 0 | Run(); |
2610 | 0 | } |
2611 | 0 | RefPtr<MediaStreamTrackListener> mListener; |
2612 | 0 | TrackID mTrackID; |
2613 | 0 | }; |
2614 | 0 | GraphImpl()->AppendMessage(MakeUnique<Message>(this, aListener, aTrackID)); |
2615 | 0 | } |
2616 | | |
2617 | | void |
2618 | | MediaStream::AddDirectTrackListenerImpl(already_AddRefed<DirectMediaStreamTrackListener> aListener, |
2619 | | TrackID aTrackID) |
2620 | 0 | { |
2621 | 0 | // Base implementation, for streams that don't support direct track listeners. |
2622 | 0 | RefPtr<DirectMediaStreamTrackListener> listener = aListener; |
2623 | 0 | listener->NotifyDirectListenerInstalled( |
2624 | 0 | DirectMediaStreamTrackListener::InstallationResult::STREAM_NOT_SUPPORTED); |
2625 | 0 | } |
2626 | | |
2627 | | void |
2628 | | MediaStream::AddDirectTrackListener(DirectMediaStreamTrackListener* aListener, |
2629 | | TrackID aTrackID) |
2630 | 0 | { |
2631 | 0 | class Message : public ControlMessage { |
2632 | 0 | public: |
2633 | 0 | Message(MediaStream* aStream, DirectMediaStreamTrackListener* aListener, |
2634 | 0 | TrackID aTrackID) : |
2635 | 0 | ControlMessage(aStream), mListener(aListener), mTrackID(aTrackID) {} |
2636 | 0 | void Run() override |
2637 | 0 | { |
2638 | 0 | mStream->AddDirectTrackListenerImpl(mListener.forget(), mTrackID); |
2639 | 0 | } |
2640 | 0 | RefPtr<DirectMediaStreamTrackListener> mListener; |
2641 | 0 | TrackID mTrackID; |
2642 | 0 | }; |
2643 | 0 | GraphImpl()->AppendMessage(MakeUnique<Message>(this, aListener, aTrackID)); |
2644 | 0 | } |
2645 | | |
2646 | | void |
2647 | | MediaStream::RemoveDirectTrackListenerImpl(DirectMediaStreamTrackListener* aListener, |
2648 | | TrackID aTrackID) |
2649 | 0 | { |
2650 | 0 | // Base implementation, the listener was never added so nothing to do. |
2651 | 0 | RefPtr<DirectMediaStreamTrackListener> listener = aListener; |
2652 | 0 | } |
2653 | | |
2654 | | void |
2655 | | MediaStream::RemoveDirectTrackListener(DirectMediaStreamTrackListener* aListener, |
2656 | | TrackID aTrackID) |
2657 | 0 | { |
2658 | 0 | class Message : public ControlMessage { |
2659 | 0 | public: |
2660 | 0 | Message(MediaStream* aStream, DirectMediaStreamTrackListener* aListener, |
2661 | 0 | TrackID aTrackID) : |
2662 | 0 | ControlMessage(aStream), mListener(aListener), mTrackID(aTrackID) {} |
2663 | 0 | void Run() override |
2664 | 0 | { |
2665 | 0 | mStream->RemoveDirectTrackListenerImpl(mListener, mTrackID); |
2666 | 0 | } |
2667 | 0 | void RunDuringShutdown() override |
2668 | 0 | { |
2669 | 0 | // During shutdown we still want the listener's |
2670 | 0 | // NotifyDirectListenerUninstalled to be called, since not doing that |
2671 | 0 | // might block shutdown of other modules. |
2672 | 0 | Run(); |
2673 | 0 | } |
2674 | 0 | RefPtr<DirectMediaStreamTrackListener> mListener; |
2675 | 0 | TrackID mTrackID; |
2676 | 0 | }; |
2677 | 0 | GraphImpl()->AppendMessage(MakeUnique<Message>(this, aListener, aTrackID)); |
2678 | 0 | } |
2679 | | |
2680 | | void |
2681 | | MediaStream::RunAfterPendingUpdates(already_AddRefed<nsIRunnable> aRunnable) |
2682 | 0 | { |
2683 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
2684 | 0 | MediaStreamGraphImpl* graph = GraphImpl(); |
2685 | 0 | nsCOMPtr<nsIRunnable> runnable(aRunnable); |
2686 | 0 |
|
2687 | 0 | // Special case when a non-realtime graph has not started, to ensure the |
2688 | 0 | // runnable will run in finite time. |
2689 | 0 | if (!(graph->mRealtime || graph->mNonRealtimeProcessing)) { |
2690 | 0 | runnable->Run(); |
2691 | 0 | return; |
2692 | 0 | } |
2693 | 0 | |
2694 | 0 | class Message : public ControlMessage { |
2695 | 0 | public: |
2696 | 0 | Message(MediaStream* aStream, already_AddRefed<nsIRunnable> aRunnable) |
2697 | 0 | : ControlMessage(aStream) |
2698 | 0 | , mRunnable(aRunnable) |
2699 | 0 | {} |
2700 | 0 | void Run() override |
2701 | 0 | { |
2702 | 0 | mStream->Graph()->DispatchToMainThreadAfterStreamStateUpdate( |
2703 | 0 | mRunnable.forget()); |
2704 | 0 | } |
2705 | 0 | void RunDuringShutdown() override |
2706 | 0 | { |
2707 | 0 | // Don't run mRunnable now as it may call AppendMessage() which would |
2708 | 0 | // assume that there are no remaining controlMessagesToRunDuringShutdown. |
2709 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
2710 | 0 | mStream->GraphImpl()->Dispatch(mRunnable.forget()); |
2711 | 0 | } |
2712 | 0 | private: |
2713 | 0 | nsCOMPtr<nsIRunnable> mRunnable; |
2714 | 0 | }; |
2715 | 0 |
|
2716 | 0 | graph->AppendMessage(MakeUnique<Message>(this, runnable.forget())); |
2717 | 0 | } |
2718 | | |
2719 | | void |
2720 | | MediaStream::SetTrackEnabledImpl(TrackID aTrackID, DisabledTrackMode aMode) |
2721 | 0 | { |
2722 | 0 | if (aMode == DisabledTrackMode::ENABLED) { |
2723 | 0 | for (int32_t i = mDisabledTracks.Length() - 1; i >= 0; --i) { |
2724 | 0 | if (aTrackID == mDisabledTracks[i].mTrackID) { |
2725 | 0 | mDisabledTracks.RemoveElementAt(i); |
2726 | 0 | return; |
2727 | 0 | } |
2728 | 0 | } |
2729 | 0 | } else { |
2730 | 0 | for (const DisabledTrack& t : mDisabledTracks) { |
2731 | 0 | if (aTrackID == t.mTrackID) { |
2732 | 0 | NS_ERROR("Changing disabled track mode for a track is not allowed"); |
2733 | 0 | return; |
2734 | 0 | } |
2735 | 0 | } |
2736 | 0 | mDisabledTracks.AppendElement(DisabledTrack(aTrackID, aMode)); |
2737 | 0 | } |
2738 | 0 | } |
2739 | | |
2740 | | DisabledTrackMode |
2741 | | MediaStream::GetDisabledTrackMode(TrackID aTrackID) |
2742 | 0 | { |
2743 | 0 | for (const DisabledTrack& t : mDisabledTracks) { |
2744 | 0 | if (t.mTrackID == aTrackID) { |
2745 | 0 | return t.mMode; |
2746 | 0 | } |
2747 | 0 | } |
2748 | 0 | return DisabledTrackMode::ENABLED; |
2749 | 0 | } |
2750 | | |
2751 | | void |
2752 | | MediaStream::SetTrackEnabled(TrackID aTrackID, DisabledTrackMode aMode) |
2753 | 0 | { |
2754 | 0 | class Message : public ControlMessage { |
2755 | 0 | public: |
2756 | 0 | Message(MediaStream* aStream, TrackID aTrackID, DisabledTrackMode aMode) : |
2757 | 0 | ControlMessage(aStream), |
2758 | 0 | mTrackID(aTrackID), |
2759 | 0 | mMode(aMode) {} |
2760 | 0 | void Run() override |
2761 | 0 | { |
2762 | 0 | mStream->SetTrackEnabledImpl(mTrackID, mMode); |
2763 | 0 | } |
2764 | 0 | TrackID mTrackID; |
2765 | 0 | DisabledTrackMode mMode; |
2766 | 0 | }; |
2767 | 0 | GraphImpl()->AppendMessage(MakeUnique<Message>(this, aTrackID, aMode)); |
2768 | 0 | } |
2769 | | |
2770 | | void |
2771 | | MediaStream::ApplyTrackDisabling(TrackID aTrackID, MediaSegment* aSegment, MediaSegment* aRawSegment) |
2772 | 0 | { |
2773 | 0 | DisabledTrackMode mode = GetDisabledTrackMode(aTrackID); |
2774 | 0 | if (mode == DisabledTrackMode::ENABLED) { |
2775 | 0 | return; |
2776 | 0 | } |
2777 | 0 | if (mode == DisabledTrackMode::SILENCE_BLACK) { |
2778 | 0 | aSegment->ReplaceWithDisabled(); |
2779 | 0 | if (aRawSegment) { |
2780 | 0 | aRawSegment->ReplaceWithDisabled(); |
2781 | 0 | } |
2782 | 0 | } else if (mode == DisabledTrackMode::SILENCE_FREEZE) { |
2783 | 0 | aSegment->ReplaceWithNull(); |
2784 | 0 | if (aRawSegment) { |
2785 | 0 | aRawSegment->ReplaceWithNull(); |
2786 | 0 | } |
2787 | 0 | } else { |
2788 | 0 | MOZ_CRASH("Unsupported mode"); |
2789 | 0 | } |
2790 | 0 | } |
2791 | | |
2792 | | void |
2793 | | MediaStream::AddMainThreadListener(MainThreadMediaStreamListener* aListener) |
2794 | 0 | { |
2795 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
2796 | 0 | MOZ_ASSERT(aListener); |
2797 | 0 | MOZ_ASSERT(!mMainThreadListeners.Contains(aListener)); |
2798 | 0 |
|
2799 | 0 | mMainThreadListeners.AppendElement(aListener); |
2800 | 0 |
|
2801 | 0 | // If it is not yet time to send the notification, then finish here. |
2802 | 0 | if (!mFinishedNotificationSent) { |
2803 | 0 | return; |
2804 | 0 | } |
2805 | 0 | |
2806 | 0 | class NotifyRunnable final : public Runnable |
2807 | 0 | { |
2808 | 0 | public: |
2809 | 0 | explicit NotifyRunnable(MediaStream* aStream) |
2810 | 0 | : Runnable("MediaStream::NotifyRunnable") |
2811 | 0 | , mStream(aStream) |
2812 | 0 | {} |
2813 | 0 |
|
2814 | 0 | NS_IMETHOD Run() override |
2815 | 0 | { |
2816 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
2817 | 0 | mStream->NotifyMainThreadListeners(); |
2818 | 0 | return NS_OK; |
2819 | 0 | } |
2820 | 0 |
|
2821 | 0 | private: |
2822 | 0 | ~NotifyRunnable() {} |
2823 | 0 |
|
2824 | 0 | RefPtr<MediaStream> mStream; |
2825 | 0 | }; |
2826 | 0 |
|
2827 | 0 | nsCOMPtr<nsIRunnable> runnable = new NotifyRunnable(this); |
2828 | 0 | GraphImpl()->Dispatch(runnable.forget()); |
2829 | 0 | } |
2830 | | |
2831 | | SourceMediaStream::SourceMediaStream() |
2832 | | : MediaStream() |
2833 | | , mMutex("mozilla::media::SourceMediaStream") |
2834 | | , mUpdateKnownTracksTime(0) |
2835 | | , mPullEnabled(false) |
2836 | | , mFinishPending(false) |
2837 | | , mNeedsMixing(false) |
2838 | 0 | { |
2839 | 0 | } |
2840 | | |
2841 | | nsresult |
2842 | | SourceMediaStream::OpenAudioInput(CubebUtils::AudioDeviceID aID, |
2843 | | AudioDataListener *aListener) |
2844 | 0 | { |
2845 | 0 | MOZ_ASSERT(GraphImpl()); |
2846 | 0 | mInputListener = aListener; |
2847 | 0 | return GraphImpl()->OpenAudioInput(aID, aListener); |
2848 | 0 | } |
2849 | | |
2850 | | void |
2851 | | SourceMediaStream::CloseAudioInput(Maybe<CubebUtils::AudioDeviceID>& aID, |
2852 | | AudioDataListener* aListener) |
2853 | 0 | { |
2854 | 0 | MOZ_ASSERT(mInputListener == aListener); |
2855 | 0 | // Destroy() may have run already and cleared this |
2856 | 0 | if (GraphImpl() && mInputListener) { |
2857 | 0 | GraphImpl()->CloseAudioInput(aID, aListener); |
2858 | 0 | } |
2859 | 0 | mInputListener = nullptr; |
2860 | 0 | } |
2861 | | |
2862 | | void |
2863 | | SourceMediaStream::DestroyImpl() |
2864 | 0 | { |
2865 | 0 | Maybe<CubebUtils::AudioDeviceID> id = Nothing(); |
2866 | 0 | CloseAudioInput(id, mInputListener); |
2867 | 0 |
|
2868 | 0 | GraphImpl()->AssertOnGraphThreadOrNotRunning(); |
2869 | 0 | for (int32_t i = mConsumers.Length() - 1; i >= 0; --i) { |
2870 | 0 | // Disconnect before we come under mMutex's lock since it can call back |
2871 | 0 | // through RemoveDirectTrackListenerImpl() and deadlock. |
2872 | 0 | mConsumers[i]->Disconnect(); |
2873 | 0 | } |
2874 | 0 |
|
2875 | 0 | // Hold mMutex while mGraph is reset so that other threads holding mMutex |
2876 | 0 | // can null-check know that the graph will not destroyed. |
2877 | 0 | MutexAutoLock lock(mMutex); |
2878 | 0 | MediaStream::DestroyImpl(); |
2879 | 0 | } |
2880 | | |
2881 | | void |
2882 | | SourceMediaStream::SetPullEnabled(bool aEnabled) |
2883 | 0 | { |
2884 | 0 | class Message : public ControlMessage { |
2885 | 0 | public: |
2886 | 0 | Message(SourceMediaStream* aStream, bool aEnabled) |
2887 | 0 | : ControlMessage(nullptr) |
2888 | 0 | , mStream(aStream) |
2889 | 0 | , mEnabled(aEnabled) |
2890 | 0 | {} |
2891 | 0 | void Run() override |
2892 | 0 | { |
2893 | 0 | MutexAutoLock lock(mStream->mMutex); |
2894 | 0 | mStream->mPullEnabled = mEnabled; |
2895 | 0 | } |
2896 | 0 | SourceMediaStream* mStream; |
2897 | 0 | bool mEnabled; |
2898 | 0 | }; |
2899 | 0 | GraphImpl()->AppendMessage(MakeUnique<Message>(this, aEnabled)); |
2900 | 0 | } |
2901 | | |
2902 | | bool |
2903 | | SourceMediaStream::PullNewData(StreamTime aDesiredUpToTime) |
2904 | 0 | { |
2905 | 0 | TRACE_AUDIO_CALLBACK_COMMENT("SourceMediaStream %p", this); |
2906 | 0 | MutexAutoLock lock(mMutex); |
2907 | 0 | if (!mPullEnabled || mFinished) { |
2908 | 0 | return false; |
2909 | 0 | } |
2910 | 0 | // Compute how much stream time we'll need assuming we don't block |
2911 | 0 | // the stream at all. |
2912 | 0 | StreamTime t = GraphTimeToStreamTime(aDesiredUpToTime); |
2913 | 0 | StreamTime current = mTracks.GetEnd(); |
2914 | 0 | LOG(LogLevel::Verbose, |
2915 | 0 | ("%p: Calling NotifyPull aStream=%p t=%f current end=%f", |
2916 | 0 | GraphImpl(), |
2917 | 0 | this, |
2918 | 0 | GraphImpl()->MediaTimeToSeconds(t), |
2919 | 0 | GraphImpl()->MediaTimeToSeconds(current))); |
2920 | 0 | if (t <= current) { |
2921 | 0 | return false; |
2922 | 0 | } |
2923 | 0 | for (uint32_t j = 0; j < mListeners.Length(); ++j) { |
2924 | 0 | MediaStreamListener* l = mListeners[j]; |
2925 | 0 | { |
2926 | 0 | MutexAutoUnlock unlock(mMutex); |
2927 | 0 | l->NotifyPull(GraphImpl(), t); |
2928 | 0 | } |
2929 | 0 | } |
2930 | 0 | return true; |
2931 | 0 | } |
2932 | | |
2933 | | void |
2934 | | SourceMediaStream::ExtractPendingInput() |
2935 | 0 | { |
2936 | 0 | MutexAutoLock lock(mMutex); |
2937 | 0 |
|
2938 | 0 | bool finished = mFinishPending; |
2939 | 0 | bool shouldNotifyTrackCreated = false; |
2940 | 0 |
|
2941 | 0 | for (int32_t i = mUpdateTracks.Length() - 1; i >= 0; --i) { |
2942 | 0 | SourceMediaStream::TrackData* data = &mUpdateTracks[i]; |
2943 | 0 | ApplyTrackDisabling(data->mID, data->mData); |
2944 | 0 | // Dealing with NotifyQueuedTrackChanges and NotifyQueuedAudioData part. |
2945 | 0 |
|
2946 | 0 | // The logic is different from the manipulating of aStream->mTracks part. |
2947 | 0 | // So it is not combined with the manipulating of aStream->mTracks part. |
2948 | 0 | StreamTime offset = |
2949 | 0 | (data->mCommands & SourceMediaStream::TRACK_CREATE) |
2950 | 0 | ? data->mStart |
2951 | 0 | : mTracks.FindTrack(data->mID)->GetSegment()->GetDuration(); |
2952 | 0 |
|
2953 | 0 | // Audio case. |
2954 | 0 | if (data->mData->GetType() == MediaSegment::AUDIO) { |
2955 | 0 | if (data->mCommands) { |
2956 | 0 | MOZ_ASSERT(!(data->mCommands & SourceMediaStream::TRACK_UNUSED)); |
2957 | 0 | for (MediaStreamListener* l : mListeners) { |
2958 | 0 | if (data->mCommands & SourceMediaStream::TRACK_END) { |
2959 | 0 | l->NotifyQueuedAudioData( |
2960 | 0 | GraphImpl(), |
2961 | 0 | data->mID, |
2962 | 0 | offset, |
2963 | 0 | *(static_cast<AudioSegment*>(data->mData.get()))); |
2964 | 0 | } |
2965 | 0 | l->NotifyQueuedTrackChanges( |
2966 | 0 | GraphImpl(), |
2967 | 0 | data->mID, |
2968 | 0 | offset, |
2969 | 0 | static_cast<TrackEventCommand>(data->mCommands), |
2970 | 0 | *data->mData); |
2971 | 0 | if (data->mCommands & SourceMediaStream::TRACK_CREATE) { |
2972 | 0 | l->NotifyQueuedAudioData( |
2973 | 0 | GraphImpl(), |
2974 | 0 | data->mID, |
2975 | 0 | offset, |
2976 | 0 | *(static_cast<AudioSegment*>(data->mData.get()))); |
2977 | 0 | } |
2978 | 0 | } |
2979 | 0 | } else { |
2980 | 0 | for (MediaStreamListener* l : mListeners) { |
2981 | 0 | l->NotifyQueuedAudioData( |
2982 | 0 | GraphImpl(), |
2983 | 0 | data->mID, |
2984 | 0 | offset, |
2985 | 0 | *(static_cast<AudioSegment*>(data->mData.get()))); |
2986 | 0 | } |
2987 | 0 | } |
2988 | 0 | } |
2989 | 0 |
|
2990 | 0 | // Video case. |
2991 | 0 | if (data->mData->GetType() == MediaSegment::VIDEO) { |
2992 | 0 | if (data->mCommands) { |
2993 | 0 | MOZ_ASSERT(!(data->mCommands & SourceMediaStream::TRACK_UNUSED)); |
2994 | 0 | for (MediaStreamListener* l : mListeners) { |
2995 | 0 | l->NotifyQueuedTrackChanges( |
2996 | 0 | GraphImpl(), |
2997 | 0 | data->mID, |
2998 | 0 | offset, |
2999 | 0 | static_cast<TrackEventCommand>(data->mCommands), |
3000 | 0 | *data->mData); |
3001 | 0 | } |
3002 | 0 | } |
3003 | 0 | } |
3004 | 0 |
|
3005 | 0 | for (TrackBound<MediaStreamTrackListener>& b : mTrackListeners) { |
3006 | 0 | if (b.mTrackID != data->mID) { |
3007 | 0 | continue; |
3008 | 0 | } |
3009 | 0 | b.mListener->NotifyQueuedChanges(GraphImpl(), offset, *data->mData); |
3010 | 0 | if (data->mCommands & SourceMediaStream::TRACK_END) { |
3011 | 0 | b.mListener->NotifyEnded(); |
3012 | 0 | } |
3013 | 0 | } |
3014 | 0 | if (data->mCommands & SourceMediaStream::TRACK_CREATE) { |
3015 | 0 | MediaSegment* segment = data->mData.forget(); |
3016 | 0 | LOG(LogLevel::Debug, |
3017 | 0 | ("%p: SourceMediaStream %p creating track %d, start %" PRId64 |
3018 | 0 | ", initial end %" PRId64, |
3019 | 0 | GraphImpl(), |
3020 | 0 | this, |
3021 | 0 | data->mID, |
3022 | 0 | int64_t(data->mStart), |
3023 | 0 | int64_t(segment->GetDuration()))); |
3024 | 0 |
|
3025 | 0 | data->mEndOfFlushedData += segment->GetDuration(); |
3026 | 0 | mTracks.AddTrack(data->mID, data->mStart, segment); |
3027 | 0 | // The track has taken ownership of data->mData, so let's replace |
3028 | 0 | // data->mData with an empty clone. |
3029 | 0 | data->mData = segment->CreateEmptyClone(); |
3030 | 0 | data->mCommands &= ~SourceMediaStream::TRACK_CREATE; |
3031 | 0 | shouldNotifyTrackCreated = true; |
3032 | 0 | } else if (data->mData->GetDuration() > 0) { |
3033 | 0 | MediaSegment* dest = mTracks.FindTrack(data->mID)->GetSegment(); |
3034 | 0 | LOG(LogLevel::Verbose, |
3035 | 0 | ("%p: SourceMediaStream %p track %d, advancing end from %" PRId64 |
3036 | 0 | " to %" PRId64, |
3037 | 0 | GraphImpl(), |
3038 | 0 | this, |
3039 | 0 | data->mID, |
3040 | 0 | int64_t(dest->GetDuration()), |
3041 | 0 | int64_t(dest->GetDuration() + data->mData->GetDuration()))); |
3042 | 0 | data->mEndOfFlushedData += data->mData->GetDuration(); |
3043 | 0 | dest->AppendFrom(data->mData); |
3044 | 0 | } |
3045 | 0 | if (data->mCommands & SourceMediaStream::TRACK_END) { |
3046 | 0 | mTracks.FindTrack(data->mID)->SetEnded(); |
3047 | 0 | mUpdateTracks.RemoveElementAt(i); |
3048 | 0 | } |
3049 | 0 | } |
3050 | 0 | if (shouldNotifyTrackCreated) { |
3051 | 0 | for (MediaStreamListener* l : mListeners) { |
3052 | 0 | l->NotifyFinishedTrackCreation(GraphImpl()); |
3053 | 0 | } |
3054 | 0 | } |
3055 | 0 | if (!mFinished) { |
3056 | 0 | mTracks.AdvanceKnownTracksTime(mUpdateKnownTracksTime); |
3057 | 0 | } |
3058 | 0 |
|
3059 | 0 | if (mTracks.GetEnd() > 0) { |
3060 | 0 | mHasCurrentData = true; |
3061 | 0 | } |
3062 | 0 |
|
3063 | 0 | if (finished) { |
3064 | 0 | FinishOnGraphThread(); |
3065 | 0 | } |
3066 | 0 | } |
3067 | | |
3068 | | void |
3069 | | SourceMediaStream::AddTrackInternal(TrackID aID, TrackRate aRate, StreamTime aStart, |
3070 | | MediaSegment* aSegment, uint32_t aFlags) |
3071 | 0 | { |
3072 | 0 | MutexAutoLock lock(mMutex); |
3073 | 0 | nsTArray<TrackData> *track_data = (aFlags & ADDTRACK_QUEUED) ? |
3074 | 0 | &mPendingTracks : &mUpdateTracks; |
3075 | 0 | TrackData* data = track_data->AppendElement(); |
3076 | 0 | LOG(LogLevel::Debug, |
3077 | 0 | ("%p: AddTrackInternal: %lu/%lu", |
3078 | 0 | GraphImpl(), |
3079 | 0 | (long)mPendingTracks.Length(), |
3080 | 0 | (long)mUpdateTracks.Length())); |
3081 | 0 | data->mID = aID; |
3082 | 0 | data->mInputRate = aRate; |
3083 | 0 | data->mResamplerChannelCount = 0; |
3084 | 0 | data->mStart = aStart; |
3085 | 0 | data->mEndOfFlushedData = aStart; |
3086 | 0 | data->mCommands = TRACK_CREATE; |
3087 | 0 | data->mData = aSegment; |
3088 | 0 | ResampleAudioToGraphSampleRate(data, aSegment); |
3089 | 0 | if (!(aFlags & ADDTRACK_QUEUED) && GraphImpl()) { |
3090 | 0 | GraphImpl()->EnsureNextIteration(); |
3091 | 0 | } |
3092 | 0 | } |
3093 | | |
3094 | | void |
3095 | | SourceMediaStream::AddAudioTrack(TrackID aID, TrackRate aRate, StreamTime aStart, |
3096 | | AudioSegment* aSegment, uint32_t aFlags) |
3097 | 0 | { |
3098 | 0 | AddTrackInternal(aID, aRate, aStart, aSegment, aFlags); |
3099 | 0 | } |
3100 | | |
3101 | | void |
3102 | | SourceMediaStream::FinishAddTracks() |
3103 | 0 | { |
3104 | 0 | MutexAutoLock lock(mMutex); |
3105 | 0 | mUpdateTracks.AppendElements(std::move(mPendingTracks)); |
3106 | 0 | LOG(LogLevel::Debug, |
3107 | 0 | ("%p: FinishAddTracks: %lu/%lu", |
3108 | 0 | GraphImpl(), |
3109 | 0 | (long)mPendingTracks.Length(), |
3110 | 0 | (long)mUpdateTracks.Length())); |
3111 | 0 | if (GraphImpl()) { |
3112 | 0 | GraphImpl()->EnsureNextIteration(); |
3113 | 0 | } |
3114 | 0 | } |
3115 | | |
3116 | | void |
3117 | | SourceMediaStream::ResampleAudioToGraphSampleRate(TrackData* aTrackData, MediaSegment* aSegment) |
3118 | 0 | { |
3119 | 0 | if (aSegment->GetType() != MediaSegment::AUDIO || |
3120 | 0 | aTrackData->mInputRate == GraphImpl()->GraphRate()) { |
3121 | 0 | return; |
3122 | 0 | } |
3123 | 0 | AudioSegment* segment = static_cast<AudioSegment*>(aSegment); |
3124 | 0 | int channels = segment->ChannelCount(); |
3125 | 0 |
|
3126 | 0 | // If this segment is just silence, we delay instanciating the resampler. We |
3127 | 0 | // also need to recreate the resampler if the channel count changes. |
3128 | 0 | if (channels && aTrackData->mResamplerChannelCount != channels) { |
3129 | 0 | SpeexResamplerState* state = speex_resampler_init(channels, |
3130 | 0 | aTrackData->mInputRate, |
3131 | 0 | GraphImpl()->GraphRate(), |
3132 | 0 | SPEEX_RESAMPLER_QUALITY_MIN, |
3133 | 0 | nullptr); |
3134 | 0 | if (!state) { |
3135 | 0 | return; |
3136 | 0 | } |
3137 | 0 | aTrackData->mResampler.own(state); |
3138 | 0 | aTrackData->mResamplerChannelCount = channels; |
3139 | 0 | } |
3140 | 0 | segment->ResampleChunks(aTrackData->mResampler, aTrackData->mInputRate, GraphImpl()->GraphRate()); |
3141 | 0 | } |
3142 | | |
3143 | | void |
3144 | | SourceMediaStream::AdvanceTimeVaryingValuesToCurrentTime(GraphTime aCurrentTime, |
3145 | | GraphTime aBlockedTime) |
3146 | 0 | { |
3147 | 0 | MutexAutoLock lock(mMutex); |
3148 | 0 | mTracksStartTime += aBlockedTime; |
3149 | 0 | mStreamTracksStartTimeStamp += TimeDuration::FromSeconds(GraphImpl()->MediaTimeToSeconds(aBlockedTime)); |
3150 | 0 | mTracks.ForgetUpTo(aCurrentTime - mTracksStartTime); |
3151 | 0 | } |
3152 | | |
3153 | | bool |
3154 | | SourceMediaStream::AppendToTrack(TrackID aID, MediaSegment* aSegment, MediaSegment *aRawSegment) |
3155 | 0 | { |
3156 | 0 | MutexAutoLock lock(mMutex); |
3157 | 0 | // ::EndAllTrackAndFinished() can end these before the sources notice |
3158 | 0 | bool appended = false; |
3159 | 0 | auto graph = GraphImpl(); |
3160 | 0 | if (!mFinished && graph) { |
3161 | 0 | TrackData *track = FindDataForTrack(aID); |
3162 | 0 | if (track) { |
3163 | 0 | // Data goes into mData, and on the next iteration of the MSG moves |
3164 | 0 | // into the track's segment after NotifyQueuedTrackChanges(). This adds |
3165 | 0 | // 0-10ms of delay before data gets to direct listeners. |
3166 | 0 | // Indirect listeners (via subsequent TrackUnion nodes) are synced to |
3167 | 0 | // playout time, and so can be delayed by buffering. |
3168 | 0 |
|
3169 | 0 | // Apply track disabling before notifying any consumers directly |
3170 | 0 | // or inserting into the graph |
3171 | 0 | ApplyTrackDisabling(aID, aSegment, aRawSegment); |
3172 | 0 |
|
3173 | 0 | ResampleAudioToGraphSampleRate(track, aSegment); |
3174 | 0 |
|
3175 | 0 | // Must notify first, since AppendFrom() will empty out aSegment |
3176 | 0 | NotifyDirectConsumers(track, aRawSegment ? aRawSegment : aSegment); |
3177 | 0 | track->mData->AppendFrom(aSegment); // note: aSegment is now dead |
3178 | 0 | appended = true; |
3179 | 0 | GraphImpl()->EnsureNextIteration(); |
3180 | 0 | } else { |
3181 | 0 | aSegment->Clear(); |
3182 | 0 | } |
3183 | 0 | } |
3184 | 0 | return appended; |
3185 | 0 | } |
3186 | | |
3187 | | void |
3188 | | SourceMediaStream::NotifyDirectConsumers(TrackData *aTrack, |
3189 | | MediaSegment *aSegment) |
3190 | 0 | { |
3191 | 0 | mMutex.AssertCurrentThreadOwns(); |
3192 | 0 | MOZ_ASSERT(aTrack); |
3193 | 0 |
|
3194 | 0 | for (const TrackBound<DirectMediaStreamTrackListener>& source |
3195 | 0 | : mDirectTrackListeners) { |
3196 | 0 | if (aTrack->mID != source.mTrackID) { |
3197 | 0 | continue; |
3198 | 0 | } |
3199 | 0 | StreamTime offset = 0; // FIX! need a separate StreamTime.... or the end of the internal buffer |
3200 | 0 | source.mListener->NotifyRealtimeTrackDataAndApplyTrackDisabling(Graph(), offset, *aSegment); |
3201 | 0 | } |
3202 | 0 | } |
3203 | | |
3204 | | // These handle notifying all the listeners of an event |
3205 | | void |
3206 | | SourceMediaStream::NotifyListenersEventImpl(MediaStreamGraphEvent aEvent) |
3207 | 0 | { |
3208 | 0 | for (uint32_t j = 0; j < mListeners.Length(); ++j) { |
3209 | 0 | MediaStreamListener* l = mListeners[j]; |
3210 | 0 | l->NotifyEvent(GraphImpl(), aEvent); |
3211 | 0 | } |
3212 | 0 | } |
3213 | | |
3214 | | void |
3215 | | SourceMediaStream::NotifyListenersEvent(MediaStreamGraphEvent aNewEvent) |
3216 | 0 | { |
3217 | 0 | class Message : public ControlMessage { |
3218 | 0 | public: |
3219 | 0 | Message(SourceMediaStream* aStream, MediaStreamGraphEvent aEvent) : |
3220 | 0 | ControlMessage(aStream), mEvent(aEvent) {} |
3221 | 0 | void Run() override |
3222 | 0 | { |
3223 | 0 | mStream->AsSourceStream()->NotifyListenersEventImpl(mEvent); |
3224 | 0 | } |
3225 | 0 | MediaStreamGraphEvent mEvent; |
3226 | 0 | }; |
3227 | 0 | GraphImpl()->AppendMessage(MakeUnique<Message>(this, aNewEvent)); |
3228 | 0 | } |
3229 | | |
3230 | | void |
3231 | | SourceMediaStream::AddDirectTrackListenerImpl(already_AddRefed<DirectMediaStreamTrackListener> aListener, |
3232 | | TrackID aTrackID) |
3233 | 0 | { |
3234 | 0 | MOZ_ASSERT(IsTrackIDExplicit(aTrackID)); |
3235 | 0 | MutexAutoLock lock(mMutex); |
3236 | 0 |
|
3237 | 0 | RefPtr<DirectMediaStreamTrackListener> listener = aListener; |
3238 | 0 | LOG(LogLevel::Debug, |
3239 | 0 | ("%p: Adding direct track listener %p bound to track %d to source stream %p", |
3240 | 0 | GraphImpl(), |
3241 | 0 | listener.get(), |
3242 | 0 | aTrackID, |
3243 | 0 | this)); |
3244 | 0 |
|
3245 | 0 | StreamTracks::Track* track = FindTrack(aTrackID); |
3246 | 0 |
|
3247 | 0 | if (!track) { |
3248 | 0 | LOG(LogLevel::Warning, |
3249 | 0 | ("%p: Couldn't find source track for direct track listener %p", |
3250 | 0 | GraphImpl(), |
3251 | 0 | listener.get())); |
3252 | 0 | listener->NotifyDirectListenerInstalled( |
3253 | 0 | DirectMediaStreamTrackListener::InstallationResult::TRACK_NOT_FOUND_AT_SOURCE); |
3254 | 0 | return; |
3255 | 0 | } |
3256 | 0 |
|
3257 | 0 | bool isAudio = track->GetType() == MediaSegment::AUDIO; |
3258 | 0 | bool isVideo = track->GetType() == MediaSegment::VIDEO; |
3259 | 0 | if (!isAudio && !isVideo) { |
3260 | 0 | LOG( |
3261 | 0 | LogLevel::Warning, |
3262 | 0 | ("%p: Source track for direct track listener %p is unknown", GraphImpl(), listener.get())); |
3263 | 0 | MOZ_ASSERT(false); |
3264 | 0 | return; |
3265 | 0 | } |
3266 | 0 |
|
3267 | 0 | for (auto entry : mDirectTrackListeners) { |
3268 | 0 | if (entry.mListener == listener && |
3269 | 0 | (entry.mTrackID == TRACK_ANY || entry.mTrackID == aTrackID)) { |
3270 | 0 | listener->NotifyDirectListenerInstalled( |
3271 | 0 | DirectMediaStreamTrackListener::InstallationResult::ALREADY_EXISTS); |
3272 | 0 | return; |
3273 | 0 | } |
3274 | 0 | } |
3275 | 0 |
|
3276 | 0 | TrackBound<DirectMediaStreamTrackListener>* sourceListener = |
3277 | 0 | mDirectTrackListeners.AppendElement(); |
3278 | 0 | sourceListener->mListener = listener; |
3279 | 0 | sourceListener->mTrackID = aTrackID; |
3280 | 0 |
|
3281 | 0 | LOG(LogLevel::Debug, ("%p: Added direct track listener %p", GraphImpl(), listener.get())); |
3282 | 0 | listener->NotifyDirectListenerInstalled( |
3283 | 0 | DirectMediaStreamTrackListener::InstallationResult::SUCCESS); |
3284 | 0 |
|
3285 | 0 | // Pass buffered data to the listener |
3286 | 0 | AudioSegment bufferedAudio; |
3287 | 0 | VideoSegment bufferedVideo; |
3288 | 0 | MediaSegment& bufferedData = |
3289 | 0 | isAudio ? static_cast<MediaSegment&>(bufferedAudio) |
3290 | 0 | : static_cast<MediaSegment&>(bufferedVideo); |
3291 | 0 |
|
3292 | 0 | MediaSegment& trackSegment = *track->GetSegment(); |
3293 | 0 | if (mTracks.GetForgottenDuration() < trackSegment.GetDuration()) { |
3294 | 0 | bufferedData.AppendSlice(trackSegment, |
3295 | 0 | mTracks.GetForgottenDuration(), |
3296 | 0 | trackSegment.GetDuration()); |
3297 | 0 | } |
3298 | 0 |
|
3299 | 0 | if (TrackData* updateData = FindDataForTrack(aTrackID)) { |
3300 | 0 | bufferedData.AppendSlice(*updateData->mData, 0, updateData->mData->GetDuration()); |
3301 | 0 | } |
3302 | 0 |
|
3303 | 0 | if (bufferedData.GetDuration() != 0) { |
3304 | 0 | listener->NotifyRealtimeTrackData(Graph(), 0, bufferedData); |
3305 | 0 | } |
3306 | 0 | } |
3307 | | |
3308 | | void |
3309 | | SourceMediaStream::RemoveDirectTrackListenerImpl(DirectMediaStreamTrackListener* aListener, |
3310 | | TrackID aTrackID) |
3311 | 0 | { |
3312 | 0 | MutexAutoLock lock(mMutex); |
3313 | 0 | for (int32_t i = mDirectTrackListeners.Length() - 1; i >= 0; --i) { |
3314 | 0 | const TrackBound<DirectMediaStreamTrackListener>& source = |
3315 | 0 | mDirectTrackListeners[i]; |
3316 | 0 | if (source.mListener == aListener && source.mTrackID == aTrackID) { |
3317 | 0 | aListener->NotifyDirectListenerUninstalled(); |
3318 | 0 | mDirectTrackListeners.RemoveElementAt(i); |
3319 | 0 | } |
3320 | 0 | } |
3321 | 0 | } |
3322 | | |
3323 | | StreamTime |
3324 | | SourceMediaStream::GetEndOfAppendedData(TrackID aID) |
3325 | 0 | { |
3326 | 0 | MutexAutoLock lock(mMutex); |
3327 | 0 | TrackData *track = FindDataForTrack(aID); |
3328 | 0 | if (track) { |
3329 | 0 | return track->mEndOfFlushedData + track->mData->GetDuration(); |
3330 | 0 | } |
3331 | 0 | NS_ERROR("Track not found"); |
3332 | 0 | return 0; |
3333 | 0 | } |
3334 | | |
3335 | | void |
3336 | | SourceMediaStream::EndTrack(TrackID aID) |
3337 | 0 | { |
3338 | 0 | MutexAutoLock lock(mMutex); |
3339 | 0 | TrackData *track = FindDataForTrack(aID); |
3340 | 0 | if (track) { |
3341 | 0 | track->mCommands |= TrackEventCommand::TRACK_EVENT_ENDED; |
3342 | 0 | } |
3343 | 0 | if (auto graph = GraphImpl()) { |
3344 | 0 | graph->EnsureNextIteration(); |
3345 | 0 | } |
3346 | 0 | } |
3347 | | |
3348 | | void |
3349 | | SourceMediaStream::AdvanceKnownTracksTime(StreamTime aKnownTime) |
3350 | 0 | { |
3351 | 0 | MutexAutoLock lock(mMutex); |
3352 | 0 | MOZ_ASSERT(aKnownTime >= mUpdateKnownTracksTime); |
3353 | 0 | mUpdateKnownTracksTime = aKnownTime; |
3354 | 0 | if (auto graph = GraphImpl()) { |
3355 | 0 | graph->EnsureNextIteration(); |
3356 | 0 | } |
3357 | 0 | } |
3358 | | |
3359 | | void |
3360 | | SourceMediaStream::FinishPendingWithLockHeld() |
3361 | 0 | { |
3362 | 0 | mMutex.AssertCurrentThreadOwns(); |
3363 | 0 | mFinishPending = true; |
3364 | 0 | if (auto graph = GraphImpl()) { |
3365 | 0 | graph->EnsureNextIteration(); |
3366 | 0 | } |
3367 | 0 | } |
3368 | | |
3369 | | void |
3370 | | SourceMediaStream::SetTrackEnabledImpl(TrackID aTrackID, DisabledTrackMode aMode) |
3371 | 0 | { |
3372 | 0 | { |
3373 | 0 | MutexAutoLock lock(mMutex); |
3374 | 0 | for (TrackBound<DirectMediaStreamTrackListener>& l: mDirectTrackListeners) { |
3375 | 0 | if (l.mTrackID != aTrackID) { |
3376 | 0 | continue; |
3377 | 0 | } |
3378 | 0 | DisabledTrackMode oldMode = GetDisabledTrackMode(aTrackID); |
3379 | 0 | bool oldEnabled = oldMode == DisabledTrackMode::ENABLED; |
3380 | 0 | if (!oldEnabled && aMode == DisabledTrackMode::ENABLED) { |
3381 | 0 | LOG(LogLevel::Debug, |
3382 | 0 | ("%p: SourceMediaStream %p track %d setting " |
3383 | 0 | "direct listener enabled", |
3384 | 0 | GraphImpl(), |
3385 | 0 | this, |
3386 | 0 | aTrackID)); |
3387 | 0 | l.mListener->DecreaseDisabled(oldMode); |
3388 | 0 | } else if (oldEnabled && aMode != DisabledTrackMode::ENABLED) { |
3389 | 0 | LOG(LogLevel::Debug, |
3390 | 0 | ("%p: SourceMediaStream %p track %d setting " |
3391 | 0 | "direct listener disabled", |
3392 | 0 | GraphImpl(), |
3393 | 0 | this, |
3394 | 0 | aTrackID)); |
3395 | 0 | l.mListener->IncreaseDisabled(aMode); |
3396 | 0 | } |
3397 | 0 | } |
3398 | 0 | } |
3399 | 0 | MediaStream::SetTrackEnabledImpl(aTrackID, aMode); |
3400 | 0 | } |
3401 | | |
3402 | | void |
3403 | | SourceMediaStream::EndAllTrackAndFinish() |
3404 | 0 | { |
3405 | 0 | MutexAutoLock lock(mMutex); |
3406 | 0 | for (uint32_t i = 0; i < mUpdateTracks.Length(); ++i) { |
3407 | 0 | SourceMediaStream::TrackData* data = &mUpdateTracks[i]; |
3408 | 0 | data->mCommands |= TrackEventCommand::TRACK_EVENT_ENDED; |
3409 | 0 | } |
3410 | 0 | mPendingTracks.Clear(); |
3411 | 0 | FinishPendingWithLockHeld(); |
3412 | 0 | // we will call NotifyEvent() to let GetUserMedia know |
3413 | 0 | } |
3414 | | |
3415 | | void |
3416 | | SourceMediaStream::RemoveAllDirectListenersImpl() |
3417 | 0 | { |
3418 | 0 | GraphImpl()->AssertOnGraphThreadOrNotRunning(); |
3419 | 0 |
|
3420 | 0 | auto directListeners(mDirectTrackListeners); |
3421 | 0 | for (auto& l : directListeners) { |
3422 | 0 | l.mListener->NotifyDirectListenerUninstalled(); |
3423 | 0 | } |
3424 | 0 | mDirectTrackListeners.Clear(); |
3425 | 0 | } |
3426 | | |
3427 | | SourceMediaStream::~SourceMediaStream() |
3428 | 0 | { |
3429 | 0 | } |
3430 | | |
3431 | | void |
3432 | | SourceMediaStream::RegisterForAudioMixing() |
3433 | 0 | { |
3434 | 0 | MutexAutoLock lock(mMutex); |
3435 | 0 | mNeedsMixing = true; |
3436 | 0 | } |
3437 | | |
3438 | | bool |
3439 | | SourceMediaStream::NeedsMixing() |
3440 | 0 | { |
3441 | 0 | MutexAutoLock lock(mMutex); |
3442 | 0 | return mNeedsMixing; |
3443 | 0 | } |
3444 | | |
3445 | | bool |
3446 | | SourceMediaStream::HasPendingAudioTrack() |
3447 | 0 | { |
3448 | 0 | MutexAutoLock lock(mMutex); |
3449 | 0 | bool audioTrackPresent = false; |
3450 | 0 |
|
3451 | 0 | for (auto& data : mPendingTracks) { |
3452 | 0 | if (data.mData->GetType() == MediaSegment::AUDIO) { |
3453 | 0 | audioTrackPresent = true; |
3454 | 0 | break; |
3455 | 0 | } |
3456 | 0 | } |
3457 | 0 |
|
3458 | 0 | return audioTrackPresent; |
3459 | 0 | } |
3460 | | |
3461 | | void |
3462 | | MediaInputPort::Init() |
3463 | 0 | { |
3464 | 0 | LOG(LogLevel::Debug, |
3465 | 0 | ("%p: Adding MediaInputPort %p (from %p to %p)", |
3466 | 0 | mSource->GraphImpl(), |
3467 | 0 | this, |
3468 | 0 | mSource, |
3469 | 0 | mDest)); |
3470 | 0 | mSource->AddConsumer(this); |
3471 | 0 | mDest->AddInput(this); |
3472 | 0 | // mPortCount decremented via MediaInputPort::Destroy's message |
3473 | 0 | ++mDest->GraphImpl()->mPortCount; |
3474 | 0 | } |
3475 | | |
3476 | | void |
3477 | | MediaInputPort::Disconnect() |
3478 | 0 | { |
3479 | 0 | GraphImpl()->AssertOnGraphThreadOrNotRunning(); |
3480 | 0 | NS_ASSERTION(!mSource == !mDest, |
3481 | 0 | "mSource must either both be null or both non-null"); |
3482 | 0 | if (!mSource) |
3483 | 0 | return; |
3484 | 0 | |
3485 | 0 | mSource->RemoveConsumer(this); |
3486 | 0 | mDest->RemoveInput(this); |
3487 | 0 | mSource = nullptr; |
3488 | 0 | mDest = nullptr; |
3489 | 0 |
|
3490 | 0 | GraphImpl()->SetStreamOrderDirty(); |
3491 | 0 | } |
3492 | | |
3493 | | MediaInputPort::InputInterval |
3494 | | MediaInputPort::GetNextInputInterval(GraphTime aTime) const |
3495 | 0 | { |
3496 | 0 | InputInterval result = { GRAPH_TIME_MAX, GRAPH_TIME_MAX, false }; |
3497 | 0 | if (aTime >= mDest->mStartBlocking) { |
3498 | 0 | return result; |
3499 | 0 | } |
3500 | 0 | result.mStart = aTime; |
3501 | 0 | result.mEnd = mDest->mStartBlocking; |
3502 | 0 | result.mInputIsBlocked = aTime >= mSource->mStartBlocking; |
3503 | 0 | if (!result.mInputIsBlocked) { |
3504 | 0 | result.mEnd = std::min(result.mEnd, mSource->mStartBlocking); |
3505 | 0 | } |
3506 | 0 | return result; |
3507 | 0 | } |
3508 | | |
3509 | | void |
3510 | | MediaInputPort::Suspended() |
3511 | 0 | { |
3512 | 0 | mDest->InputSuspended(this); |
3513 | 0 | } |
3514 | | |
3515 | | void |
3516 | | MediaInputPort::Resumed() |
3517 | 0 | { |
3518 | 0 | mDest->InputResumed(this); |
3519 | 0 | } |
3520 | | |
3521 | | void |
3522 | | MediaInputPort::Destroy() |
3523 | 0 | { |
3524 | 0 | class Message : public ControlMessage { |
3525 | 0 | public: |
3526 | 0 | explicit Message(MediaInputPort* aPort) |
3527 | 0 | : ControlMessage(nullptr), mPort(aPort) {} |
3528 | 0 | void Run() override |
3529 | 0 | { |
3530 | 0 | mPort->Disconnect(); |
3531 | 0 | --mPort->GraphImpl()->mPortCount; |
3532 | 0 | mPort->SetGraphImpl(nullptr); |
3533 | 0 | NS_RELEASE(mPort); |
3534 | 0 | } |
3535 | 0 | void RunDuringShutdown() override |
3536 | 0 | { |
3537 | 0 | Run(); |
3538 | 0 | } |
3539 | 0 | MediaInputPort* mPort; |
3540 | 0 | }; |
3541 | 0 | GraphImpl()->AppendMessage(MakeUnique<Message>(this)); |
3542 | 0 | } |
3543 | | |
3544 | | MediaStreamGraphImpl* |
3545 | | MediaInputPort::GraphImpl() |
3546 | 0 | { |
3547 | 0 | return mGraph; |
3548 | 0 | } |
3549 | | |
3550 | | MediaStreamGraph* |
3551 | | MediaInputPort::Graph() |
3552 | 0 | { |
3553 | 0 | return mGraph; |
3554 | 0 | } |
3555 | | |
3556 | | void |
3557 | | MediaInputPort::SetGraphImpl(MediaStreamGraphImpl* aGraph) |
3558 | 0 | { |
3559 | 0 | MOZ_ASSERT(!mGraph || !aGraph, "Should only be set once"); |
3560 | 0 | mGraph = aGraph; |
3561 | 0 | } |
3562 | | |
3563 | | void |
3564 | | MediaInputPort::BlockSourceTrackIdImpl(TrackID aTrackId, BlockingMode aBlockingMode) |
3565 | 0 | { |
3566 | 0 | mBlockedTracks.AppendElement(Pair<TrackID, BlockingMode>(aTrackId, aBlockingMode)); |
3567 | 0 | } |
3568 | | |
3569 | | already_AddRefed<Pledge<bool>> |
3570 | | MediaInputPort::BlockSourceTrackId(TrackID aTrackId, BlockingMode aBlockingMode) |
3571 | | { |
3572 | | class Message : public ControlMessage { |
3573 | | public: |
3574 | | Message(MediaInputPort* aPort, |
3575 | | TrackID aTrackId, |
3576 | | BlockingMode aBlockingMode, |
3577 | | already_AddRefed<nsIRunnable> aRunnable) |
3578 | | : ControlMessage(aPort->GetDestination()) |
3579 | | , mPort(aPort) |
3580 | | , mTrackId(aTrackId) |
3581 | | , mBlockingMode(aBlockingMode) |
3582 | | , mRunnable(aRunnable) |
3583 | 0 | { |
3584 | 0 | } |
3585 | | void Run() override |
3586 | 0 | { |
3587 | 0 | mPort->BlockSourceTrackIdImpl(mTrackId, mBlockingMode); |
3588 | 0 | if (mRunnable) { |
3589 | 0 | mStream->Graph()->DispatchToMainThreadAfterStreamStateUpdate( |
3590 | 0 | mRunnable.forget()); |
3591 | 0 | } |
3592 | 0 | } |
3593 | | void RunDuringShutdown() override |
3594 | 0 | { |
3595 | 0 | Run(); |
3596 | 0 | } |
3597 | | RefPtr<MediaInputPort> mPort; |
3598 | | TrackID mTrackId; |
3599 | | BlockingMode mBlockingMode; |
3600 | | nsCOMPtr<nsIRunnable> mRunnable; |
3601 | | }; |
3602 | | |
3603 | | MOZ_ASSERT(IsTrackIDExplicit(aTrackId), |
3604 | | "Only explicit TrackID is allowed"); |
3605 | | |
3606 | | auto pledge = MakeRefPtr<Pledge<bool>>(); |
3607 | 0 | nsCOMPtr<nsIRunnable> runnable = NewRunnableFrom([pledge]() { |
3608 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
3609 | 0 | pledge->Resolve(true); |
3610 | 0 | return NS_OK; |
3611 | 0 | }); |
3612 | | GraphImpl()->AppendMessage( |
3613 | | MakeUnique<Message>(this, aTrackId, aBlockingMode, runnable.forget())); |
3614 | | return pledge.forget(); |
3615 | | } |
3616 | | |
3617 | | already_AddRefed<MediaInputPort> |
3618 | | ProcessedMediaStream::AllocateInputPort(MediaStream* aStream, TrackID aTrackID, |
3619 | | TrackID aDestTrackID, |
3620 | | uint16_t aInputNumber, uint16_t aOutputNumber, |
3621 | | nsTArray<TrackID>* aBlockedTracks) |
3622 | 0 | { |
3623 | 0 | // This method creates two references to the MediaInputPort: one for |
3624 | 0 | // the main thread, and one for the MediaStreamGraph. |
3625 | 0 | class Message : public ControlMessage { |
3626 | 0 | public: |
3627 | 0 | explicit Message(MediaInputPort* aPort) |
3628 | 0 | : ControlMessage(aPort->GetDestination()), |
3629 | 0 | mPort(aPort) {} |
3630 | 0 | void Run() override |
3631 | 0 | { |
3632 | 0 | mPort->Init(); |
3633 | 0 | // The graph holds its reference implicitly |
3634 | 0 | mPort->GraphImpl()->SetStreamOrderDirty(); |
3635 | 0 | Unused << mPort.forget(); |
3636 | 0 | } |
3637 | 0 | void RunDuringShutdown() override |
3638 | 0 | { |
3639 | 0 | Run(); |
3640 | 0 | } |
3641 | 0 | RefPtr<MediaInputPort> mPort; |
3642 | 0 | }; |
3643 | 0 |
|
3644 | 0 | MOZ_ASSERT(aStream->GraphImpl() == GraphImpl()); |
3645 | 0 | MOZ_ASSERT(aTrackID == TRACK_ANY || IsTrackIDExplicit(aTrackID), |
3646 | 0 | "Only TRACK_ANY and explicit ID are allowed for source track"); |
3647 | 0 | MOZ_ASSERT(aDestTrackID == TRACK_ANY || IsTrackIDExplicit(aDestTrackID), |
3648 | 0 | "Only TRACK_ANY and explicit ID are allowed for destination track"); |
3649 | 0 | MOZ_ASSERT(aTrackID != TRACK_ANY || aDestTrackID == TRACK_ANY, |
3650 | 0 | "Generic MediaInputPort cannot produce a single destination track"); |
3651 | 0 | RefPtr<MediaInputPort> port = new MediaInputPort( |
3652 | 0 | aStream, aTrackID, this, aDestTrackID, aInputNumber, aOutputNumber); |
3653 | 0 | if (aBlockedTracks) { |
3654 | 0 | for (TrackID trackID : *aBlockedTracks) { |
3655 | 0 | port->BlockSourceTrackIdImpl(trackID, BlockingMode::CREATION); |
3656 | 0 | } |
3657 | 0 | } |
3658 | 0 | port->SetGraphImpl(GraphImpl()); |
3659 | 0 | GraphImpl()->AppendMessage(MakeUnique<Message>(port)); |
3660 | 0 | return port.forget(); |
3661 | 0 | } |
3662 | | |
3663 | | void |
3664 | | ProcessedMediaStream::QueueFinish() |
3665 | 0 | { |
3666 | 0 | class Message : public ControlMessage { |
3667 | 0 | public: |
3668 | 0 | explicit Message(ProcessedMediaStream* aStream) |
3669 | 0 | : ControlMessage(aStream) {} |
3670 | 0 | void Run() override |
3671 | 0 | { |
3672 | 0 | mStream->FinishOnGraphThread(); |
3673 | 0 | } |
3674 | 0 | }; |
3675 | 0 | GraphImpl()->AppendMessage(MakeUnique<Message>(this)); |
3676 | 0 | } |
3677 | | |
3678 | | void |
3679 | | ProcessedMediaStream::QueueSetAutofinish(bool aAutofinish) |
3680 | 0 | { |
3681 | 0 | class Message : public ControlMessage { |
3682 | 0 | public: |
3683 | 0 | Message(ProcessedMediaStream* aStream, bool aAutofinish) |
3684 | 0 | : ControlMessage(aStream), mAutofinish(aAutofinish) {} |
3685 | 0 | void Run() override |
3686 | 0 | { |
3687 | 0 | static_cast<ProcessedMediaStream*>(mStream)->SetAutofinishImpl(mAutofinish); |
3688 | 0 | } |
3689 | 0 | bool mAutofinish; |
3690 | 0 | }; |
3691 | 0 | GraphImpl()->AppendMessage(MakeUnique<Message>(this, aAutofinish)); |
3692 | 0 | } |
3693 | | |
3694 | | void |
3695 | | ProcessedMediaStream::DestroyImpl() |
3696 | 0 | { |
3697 | 0 | for (int32_t i = mInputs.Length() - 1; i >= 0; --i) { |
3698 | 0 | mInputs[i]->Disconnect(); |
3699 | 0 | } |
3700 | 0 |
|
3701 | 0 | for (int32_t i = mSuspendedInputs.Length() - 1; i >= 0; --i) { |
3702 | 0 | mSuspendedInputs[i]->Disconnect(); |
3703 | 0 | } |
3704 | 0 |
|
3705 | 0 | MediaStream::DestroyImpl(); |
3706 | 0 | // The stream order is only important if there are connections, in which |
3707 | 0 | // case MediaInputPort::Disconnect() called SetStreamOrderDirty(). |
3708 | 0 | // MediaStreamGraphImpl::RemoveStreamGraphThread() will also call |
3709 | 0 | // SetStreamOrderDirty(), for other reasons. |
3710 | 0 | } |
3711 | | |
3712 | | MediaStreamGraphImpl::MediaStreamGraphImpl(GraphDriverType aDriverRequested, |
3713 | | TrackRate aSampleRate, |
3714 | | AbstractThread* aMainThread) |
3715 | | : MediaStreamGraph(aSampleRate) |
3716 | | , mFirstCycleBreaker(0) |
3717 | | , mPortCount(0) |
3718 | | , mInputDeviceID(nullptr) |
3719 | | , mOutputDeviceID(nullptr) |
3720 | | , mNeedAnotherIteration(false) |
3721 | | , mGraphDriverAsleep(false) |
3722 | | , mMonitor("MediaStreamGraphImpl") |
3723 | | , mLifecycleState(LIFECYCLE_THREAD_NOT_STARTED) |
3724 | | , mEndTime(GRAPH_TIME_MAX) |
3725 | | , mForceShutDown(false) |
3726 | | , mPostedRunInStableStateEvent(false) |
3727 | | , mDetectedNotRunning(false) |
3728 | | , mPostedRunInStableState(false) |
3729 | | , mRealtime(aDriverRequested != OFFLINE_THREAD_DRIVER) |
3730 | | , mNonRealtimeProcessing(false) |
3731 | | , mStreamOrderDirty(false) |
3732 | | , mLatencyLog(AsyncLatencyLogger::Get()) |
3733 | | , mAbstractMainThread(aMainThread) |
3734 | | , mSelfRef(this) |
3735 | | , mOutputChannels(std::min<uint32_t>(8, CubebUtils::MaxNumberOfChannels())) |
3736 | | , mGlobalVolume(CubebUtils::GetVolumeScale()) |
3737 | | #ifdef DEBUG |
3738 | | , mCanRunMessagesSynchronously(false) |
3739 | | #endif |
3740 | 0 | { |
3741 | 0 | if (mRealtime) { |
3742 | 0 | if (aDriverRequested == AUDIO_THREAD_DRIVER) { |
3743 | 0 | // Always start with zero input channels. |
3744 | 0 | mDriver = new AudioCallbackDriver(this, 0); |
3745 | 0 | } else { |
3746 | 0 | mDriver = new SystemClockDriver(this); |
3747 | 0 | } |
3748 | 0 |
|
3749 | 0 | #ifdef TRACING |
3750 | 0 | // This is a noop if the logger has not been enabled. |
3751 | 0 | gMSGTraceLogger.Start(); |
3752 | 0 | gMSGTraceLogger.Log("["); |
3753 | 0 | #endif |
3754 | 0 | } else { |
3755 | 0 | mDriver = new OfflineClockDriver(this, MEDIA_GRAPH_TARGET_PERIOD_MS); |
3756 | 0 | } |
3757 | 0 |
|
3758 | 0 | mLastMainThreadUpdate = TimeStamp::Now(); |
3759 | 0 |
|
3760 | 0 | RegisterWeakAsyncMemoryReporter(this); |
3761 | 0 | } |
3762 | | |
3763 | | AbstractThread* |
3764 | | MediaStreamGraph::AbstractMainThread() |
3765 | 0 | { |
3766 | 0 | MOZ_ASSERT(static_cast<MediaStreamGraphImpl*>(this)->mAbstractMainThread); |
3767 | 0 | return static_cast<MediaStreamGraphImpl*>(this)->mAbstractMainThread; |
3768 | 0 | } |
3769 | | |
3770 | | void |
3771 | | MediaStreamGraphImpl::Destroy() |
3772 | 0 | { |
3773 | 0 | // First unregister from memory reporting. |
3774 | 0 | UnregisterWeakMemoryReporter(this); |
3775 | 0 |
|
3776 | 0 | // Clear the self reference which will destroy this instance if all |
3777 | 0 | // associated GraphDrivers are destroyed. |
3778 | 0 | mSelfRef = nullptr; |
3779 | 0 | } |
3780 | | |
3781 | | static |
3782 | | uint32_t WindowToHash(nsPIDOMWindowInner* aWindow, |
3783 | | TrackRate aSampleRate) |
3784 | 0 | { |
3785 | 0 | uint32_t hashkey = 0; |
3786 | 0 |
|
3787 | 0 | hashkey = AddToHash(hashkey, aWindow); |
3788 | 0 | hashkey = AddToHash(hashkey, aSampleRate); |
3789 | 0 |
|
3790 | 0 | return hashkey; |
3791 | 0 | } |
3792 | | |
3793 | | MediaStreamGraph* |
3794 | | MediaStreamGraph::GetInstanceIfExists(nsPIDOMWindowInner* aWindow, |
3795 | | TrackRate aSampleRate) |
3796 | 0 | { |
3797 | 0 | MOZ_ASSERT(NS_IsMainThread(), "Main thread only"); |
3798 | 0 |
|
3799 | 0 | TrackRate sampleRate = aSampleRate ? aSampleRate : CubebUtils::PreferredSampleRate(); |
3800 | 0 | uint32_t hashkey = WindowToHash(aWindow, sampleRate); |
3801 | 0 |
|
3802 | 0 | MediaStreamGraphImpl* graph = nullptr; |
3803 | 0 | gGraphs.Get(hashkey, &graph); |
3804 | 0 | return graph; |
3805 | 0 | } |
3806 | | |
3807 | | MediaStreamGraph* |
3808 | | MediaStreamGraph::GetInstance(MediaStreamGraph::GraphDriverType aGraphDriverRequested, |
3809 | | nsPIDOMWindowInner* aWindow, |
3810 | | TrackRate aSampleRate) |
3811 | 0 | { |
3812 | 0 | MOZ_ASSERT(NS_IsMainThread(), "Main thread only"); |
3813 | 0 |
|
3814 | 0 | TrackRate sampleRate = aSampleRate ? aSampleRate : CubebUtils::PreferredSampleRate(); |
3815 | 0 | MediaStreamGraphImpl* graph = |
3816 | 0 | static_cast<MediaStreamGraphImpl*>(GetInstanceIfExists(aWindow, sampleRate)); |
3817 | 0 |
|
3818 | 0 | if (!graph) { |
3819 | 0 | if (!gMediaStreamGraphShutdownBlocker) { |
3820 | 0 |
|
3821 | 0 | class Blocker : public media::ShutdownBlocker |
3822 | 0 | { |
3823 | 0 | public: |
3824 | 0 | Blocker() |
3825 | 0 | : media::ShutdownBlocker(NS_LITERAL_STRING( |
3826 | 0 | "MediaStreamGraph shutdown: blocking on msg thread")) |
3827 | 0 | {} |
3828 | 0 |
|
3829 | 0 | NS_IMETHOD |
3830 | 0 | BlockShutdown(nsIAsyncShutdownClient* aProfileBeforeChange) override |
3831 | 0 | { |
3832 | 0 | // Distribute the global async shutdown blocker in a ticket. If there |
3833 | 0 | // are zero graphs then shutdown is unblocked when we go out of scope. |
3834 | 0 | auto ticket = MakeRefPtr<media::ShutdownTicket>( |
3835 | 0 | gMediaStreamGraphShutdownBlocker.get()); |
3836 | 0 | gMediaStreamGraphShutdownBlocker = nullptr; |
3837 | 0 |
|
3838 | 0 | for (auto iter = gGraphs.Iter(); !iter.Done(); iter.Next()) { |
3839 | 0 | iter.UserData()->ForceShutDown(ticket); |
3840 | 0 | } |
3841 | 0 | return NS_OK; |
3842 | 0 | } |
3843 | 0 | }; |
3844 | 0 |
|
3845 | 0 | gMediaStreamGraphShutdownBlocker = new Blocker(); |
3846 | 0 | nsCOMPtr<nsIAsyncShutdownClient> barrier = media::GetShutdownBarrier(); |
3847 | 0 | nsresult rv = barrier-> |
3848 | 0 | AddBlocker(gMediaStreamGraphShutdownBlocker, |
3849 | 0 | NS_LITERAL_STRING(__FILE__), __LINE__, |
3850 | 0 | NS_LITERAL_STRING("MediaStreamGraph shutdown")); |
3851 | 0 | MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv)); |
3852 | 0 | } |
3853 | 0 |
|
3854 | 0 | AbstractThread* mainThread; |
3855 | 0 | if (aWindow) { |
3856 | 0 | mainThread = aWindow->AsGlobal()->AbstractMainThreadFor(TaskCategory::Other); |
3857 | 0 | } else { |
3858 | 0 | // Uncommon case, only for some old configuration of webspeech. |
3859 | 0 | mainThread = AbstractThread::MainThread(); |
3860 | 0 | } |
3861 | 0 | graph = new MediaStreamGraphImpl(aGraphDriverRequested, |
3862 | 0 | sampleRate, |
3863 | 0 | mainThread); |
3864 | 0 |
|
3865 | 0 | uint32_t hashkey = WindowToHash(aWindow, sampleRate); |
3866 | 0 | gGraphs.Put(hashkey, graph); |
3867 | 0 |
|
3868 | 0 | LOG(LogLevel::Debug, |
3869 | 0 | ("Starting up MediaStreamGraph %p for window %p", graph, aWindow)); |
3870 | 0 | } |
3871 | 0 |
|
3872 | 0 | return graph; |
3873 | 0 | } |
3874 | | |
3875 | | MediaStreamGraph* |
3876 | | MediaStreamGraph::CreateNonRealtimeInstance(TrackRate aSampleRate, |
3877 | | nsPIDOMWindowInner* aWindow) |
3878 | 0 | { |
3879 | 0 | MOZ_ASSERT(NS_IsMainThread(), "Main thread only"); |
3880 | 0 |
|
3881 | 0 | MediaStreamGraphImpl* graph = new MediaStreamGraphImpl( |
3882 | 0 | OFFLINE_THREAD_DRIVER, |
3883 | 0 | aSampleRate, |
3884 | 0 | aWindow->AsGlobal()->AbstractMainThreadFor(TaskCategory::Other)); |
3885 | 0 |
|
3886 | 0 | LOG(LogLevel::Debug, ("Starting up Offline MediaStreamGraph %p", graph)); |
3887 | 0 |
|
3888 | 0 | return graph; |
3889 | 0 | } |
3890 | | |
3891 | | void |
3892 | | MediaStreamGraph::DestroyNonRealtimeInstance(MediaStreamGraph* aGraph) |
3893 | 0 | { |
3894 | 0 | MOZ_ASSERT(NS_IsMainThread(), "Main thread only"); |
3895 | 0 | MOZ_ASSERT(aGraph->IsNonRealtime(), "Should not destroy the global graph here"); |
3896 | 0 |
|
3897 | 0 | MediaStreamGraphImpl* graph = static_cast<MediaStreamGraphImpl*>(aGraph); |
3898 | 0 |
|
3899 | 0 | if (!graph->mNonRealtimeProcessing) { |
3900 | 0 | // Start the graph, but don't produce anything |
3901 | 0 | graph->StartNonRealtimeProcessing(0); |
3902 | 0 | } |
3903 | 0 |
|
3904 | 0 | graph->ForceShutDown(nullptr); |
3905 | 0 | } |
3906 | | |
3907 | | NS_IMPL_ISUPPORTS(MediaStreamGraphImpl, nsIMemoryReporter, nsITimerCallback, |
3908 | | nsINamed) |
3909 | | |
3910 | | NS_IMETHODIMP |
3911 | | MediaStreamGraphImpl::CollectReports(nsIHandleReportCallback* aHandleReport, |
3912 | | nsISupports* aData, bool aAnonymize) |
3913 | 0 | { |
3914 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
3915 | 0 | { |
3916 | 0 | MonitorAutoLock mon(mMonitor); |
3917 | 0 | if (LifecycleStateRef() >= LIFECYCLE_WAITING_FOR_THREAD_SHUTDOWN) { |
3918 | 0 | // Shutting down, nothing to report. |
3919 | 0 | FinishCollectReports(aHandleReport, aData, nsTArray<AudioNodeSizes>()); |
3920 | 0 | return NS_OK; |
3921 | 0 | } |
3922 | 0 | } |
3923 | 0 | |
3924 | 0 | class Message final : public ControlMessage { |
3925 | 0 | public: |
3926 | 0 | Message(MediaStreamGraphImpl *aGraph, |
3927 | 0 | nsIHandleReportCallback* aHandleReport, |
3928 | 0 | nsISupports *aHandlerData) |
3929 | 0 | : ControlMessage(nullptr) |
3930 | 0 | , mGraph(aGraph) |
3931 | 0 | , mHandleReport(aHandleReport) |
3932 | 0 | , mHandlerData(aHandlerData) {} |
3933 | 0 | void Run() override |
3934 | 0 | { |
3935 | 0 | mGraph->CollectSizesForMemoryReport(mHandleReport.forget(), |
3936 | 0 | mHandlerData.forget()); |
3937 | 0 | } |
3938 | 0 | void RunDuringShutdown() override |
3939 | 0 | { |
3940 | 0 | // Run this message during shutdown too, so that endReports is called. |
3941 | 0 | Run(); |
3942 | 0 | } |
3943 | 0 | MediaStreamGraphImpl *mGraph; |
3944 | 0 | // nsMemoryReporterManager keeps the callback and data alive only if it |
3945 | 0 | // does not time out. |
3946 | 0 | nsCOMPtr<nsIHandleReportCallback> mHandleReport; |
3947 | 0 | nsCOMPtr<nsISupports> mHandlerData; |
3948 | 0 | }; |
3949 | 0 |
|
3950 | 0 | // When a non-realtime graph has not started, there is no thread yet, so |
3951 | 0 | // collect sizes on this thread. |
3952 | 0 | if (!(mRealtime || mNonRealtimeProcessing)) { |
3953 | 0 | CollectSizesForMemoryReport(do_AddRef(aHandleReport), do_AddRef(aData)); |
3954 | 0 | return NS_OK; |
3955 | 0 | } |
3956 | 0 | |
3957 | 0 | AppendMessage(MakeUnique<Message>(this, aHandleReport, aData)); |
3958 | 0 |
|
3959 | 0 | return NS_OK; |
3960 | 0 | } |
3961 | | |
3962 | | void |
3963 | | MediaStreamGraphImpl::CollectSizesForMemoryReport( |
3964 | | already_AddRefed<nsIHandleReportCallback> aHandleReport, |
3965 | | already_AddRefed<nsISupports> aHandlerData) |
3966 | 0 | { |
3967 | 0 | class FinishCollectRunnable final : public Runnable |
3968 | 0 | { |
3969 | 0 | public: |
3970 | 0 | explicit FinishCollectRunnable( |
3971 | 0 | already_AddRefed<nsIHandleReportCallback> aHandleReport, |
3972 | 0 | already_AddRefed<nsISupports> aHandlerData) |
3973 | 0 | : mozilla::Runnable("FinishCollectRunnable") |
3974 | 0 | , mHandleReport(aHandleReport) |
3975 | 0 | , mHandlerData(aHandlerData) |
3976 | 0 | {} |
3977 | 0 |
|
3978 | 0 | NS_IMETHOD Run() override |
3979 | 0 | { |
3980 | 0 | MediaStreamGraphImpl::FinishCollectReports(mHandleReport, mHandlerData, |
3981 | 0 | std::move(mAudioStreamSizes)); |
3982 | 0 | return NS_OK; |
3983 | 0 | } |
3984 | 0 |
|
3985 | 0 | nsTArray<AudioNodeSizes> mAudioStreamSizes; |
3986 | 0 |
|
3987 | 0 | private: |
3988 | 0 | ~FinishCollectRunnable() {} |
3989 | 0 |
|
3990 | 0 | // Avoiding nsCOMPtr because NSCAP_ASSERT_NO_QUERY_NEEDED in its |
3991 | 0 | // constructor modifies the ref-count, which cannot be done off main |
3992 | 0 | // thread. |
3993 | 0 | RefPtr<nsIHandleReportCallback> mHandleReport; |
3994 | 0 | RefPtr<nsISupports> mHandlerData; |
3995 | 0 | }; |
3996 | 0 |
|
3997 | 0 | RefPtr<FinishCollectRunnable> runnable = |
3998 | 0 | new FinishCollectRunnable(std::move(aHandleReport), std::move(aHandlerData)); |
3999 | 0 |
|
4000 | 0 | auto audioStreamSizes = &runnable->mAudioStreamSizes; |
4001 | 0 |
|
4002 | 0 | for (MediaStream* s : AllStreams()) { |
4003 | 0 | AudioNodeStream* stream = s->AsAudioNodeStream(); |
4004 | 0 | if (stream) { |
4005 | 0 | AudioNodeSizes* usage = audioStreamSizes->AppendElement(); |
4006 | 0 | stream->SizeOfAudioNodesIncludingThis(MallocSizeOf, *usage); |
4007 | 0 | } |
4008 | 0 | } |
4009 | 0 |
|
4010 | 0 | mAbstractMainThread->Dispatch(runnable.forget()); |
4011 | 0 | } |
4012 | | |
4013 | | void |
4014 | | MediaStreamGraphImpl:: |
4015 | | FinishCollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData, |
4016 | | const nsTArray<AudioNodeSizes>& aAudioStreamSizes) |
4017 | 0 | { |
4018 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
4019 | 0 |
|
4020 | 0 | nsCOMPtr<nsIMemoryReporterManager> manager = |
4021 | 0 | do_GetService("@mozilla.org/memory-reporter-manager;1"); |
4022 | 0 |
|
4023 | 0 | if (!manager) |
4024 | 0 | return; |
4025 | 0 | |
4026 | 0 | #define REPORT(_path, _amount, _desc) \ |
4027 | 0 | aHandleReport->Callback(EmptyCString(), _path, KIND_HEAP, UNITS_BYTES, \ |
4028 | 0 | _amount, NS_LITERAL_CSTRING(_desc), aData); |
4029 | 0 |
|
4030 | 0 | for (size_t i = 0; i < aAudioStreamSizes.Length(); i++) { |
4031 | 0 | const AudioNodeSizes& usage = aAudioStreamSizes[i]; |
4032 | 0 | const char* const nodeType = |
4033 | 0 | usage.mNodeType ? usage.mNodeType : "<unknown>"; |
4034 | 0 |
|
4035 | 0 | nsPrintfCString enginePath("explicit/webaudio/audio-node/%s/engine-objects", |
4036 | 0 | nodeType); |
4037 | 0 | REPORT(enginePath, usage.mEngine, |
4038 | 0 | "Memory used by AudioNode engine objects (Web Audio)."); |
4039 | 0 |
|
4040 | 0 | nsPrintfCString streamPath("explicit/webaudio/audio-node/%s/stream-objects", |
4041 | 0 | nodeType); |
4042 | 0 | REPORT(streamPath, usage.mStream, |
4043 | 0 | "Memory used by AudioNode stream objects (Web Audio)."); |
4044 | 0 |
|
4045 | 0 | } |
4046 | 0 |
|
4047 | 0 | size_t hrtfLoaders = WebCore::HRTFDatabaseLoader::sizeOfLoaders(MallocSizeOf); |
4048 | 0 | if (hrtfLoaders) { |
4049 | 0 | REPORT(NS_LITERAL_CSTRING( |
4050 | 0 | "explicit/webaudio/audio-node/PannerNode/hrtf-databases"), |
4051 | 0 | hrtfLoaders, |
4052 | 0 | "Memory used by PannerNode databases (Web Audio)."); |
4053 | 0 | } |
4054 | 0 |
|
4055 | 0 | #undef REPORT |
4056 | 0 |
|
4057 | 0 | manager->EndReport(); |
4058 | 0 | } |
4059 | | |
4060 | | SourceMediaStream* |
4061 | | MediaStreamGraph::CreateSourceStream() |
4062 | 0 | { |
4063 | 0 | SourceMediaStream* stream = new SourceMediaStream(); |
4064 | 0 | AddStream(stream); |
4065 | 0 | return stream; |
4066 | 0 | } |
4067 | | |
4068 | | ProcessedMediaStream* |
4069 | | MediaStreamGraph::CreateTrackUnionStream() |
4070 | 0 | { |
4071 | 0 | TrackUnionStream* stream = new TrackUnionStream(); |
4072 | 0 | AddStream(stream); |
4073 | 0 | return stream; |
4074 | 0 | } |
4075 | | |
4076 | | ProcessedMediaStream* |
4077 | | MediaStreamGraph::CreateAudioCaptureStream(TrackID aTrackId) |
4078 | 0 | { |
4079 | 0 | AudioCaptureStream* stream = new AudioCaptureStream(aTrackId); |
4080 | 0 | AddStream(stream); |
4081 | 0 | return stream; |
4082 | 0 | } |
4083 | | |
4084 | | void |
4085 | | MediaStreamGraph::AddStream(MediaStream* aStream) |
4086 | 0 | { |
4087 | 0 | NS_ADDREF(aStream); |
4088 | 0 | MediaStreamGraphImpl* graph = static_cast<MediaStreamGraphImpl*>(this); |
4089 | 0 | aStream->SetGraphImpl(graph); |
4090 | 0 | graph->AppendMessage(MakeUnique<CreateMessage>(aStream)); |
4091 | 0 | } |
4092 | | |
4093 | | class GraphStartedRunnable final : public Runnable |
4094 | | { |
4095 | | public: |
4096 | | GraphStartedRunnable(AudioNodeStream* aStream, MediaStreamGraph* aGraph) |
4097 | | : Runnable("GraphStartedRunnable") |
4098 | | , mStream(aStream) |
4099 | | , mGraph(aGraph) |
4100 | 0 | { } |
4101 | | |
4102 | 0 | NS_IMETHOD Run() override { |
4103 | 0 | mGraph->NotifyWhenGraphStarted(mStream); |
4104 | 0 | return NS_OK; |
4105 | 0 | } |
4106 | | |
4107 | | private: |
4108 | | RefPtr<AudioNodeStream> mStream; |
4109 | | MediaStreamGraph* mGraph; |
4110 | | }; |
4111 | | |
4112 | | void |
4113 | | MediaStreamGraph::NotifyWhenGraphStarted(AudioNodeStream* aStream) |
4114 | 0 | { |
4115 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
4116 | 0 |
|
4117 | 0 | class GraphStartedNotificationControlMessage : public ControlMessage |
4118 | 0 | { |
4119 | 0 | public: |
4120 | 0 | explicit GraphStartedNotificationControlMessage(AudioNodeStream* aStream) |
4121 | 0 | : ControlMessage(aStream) |
4122 | 0 | { |
4123 | 0 | } |
4124 | 0 | void Run() override |
4125 | 0 | { |
4126 | 0 | // This runs on the graph thread, so when this runs, and the current |
4127 | 0 | // driver is an AudioCallbackDriver, we know the audio hardware is |
4128 | 0 | // started. If not, we are going to switch soon, keep reposting this |
4129 | 0 | // ControlMessage. |
4130 | 0 | MediaStreamGraphImpl* graphImpl = mStream->GraphImpl(); |
4131 | 0 | if (graphImpl->CurrentDriver()->AsAudioCallbackDriver()) { |
4132 | 0 | nsCOMPtr<nsIRunnable> event = new dom::StateChangeTask( |
4133 | 0 | mStream->AsAudioNodeStream(), nullptr, AudioContextState::Running); |
4134 | 0 | graphImpl->Dispatch(event.forget()); |
4135 | 0 | } else { |
4136 | 0 | nsCOMPtr<nsIRunnable> event = new GraphStartedRunnable( |
4137 | 0 | mStream->AsAudioNodeStream(), mStream->Graph()); |
4138 | 0 | graphImpl->Dispatch(event.forget()); |
4139 | 0 | } |
4140 | 0 | } |
4141 | 0 | void RunDuringShutdown() override |
4142 | 0 | { |
4143 | 0 | } |
4144 | 0 | }; |
4145 | 0 |
|
4146 | 0 | if (!aStream->IsDestroyed()) { |
4147 | 0 | MediaStreamGraphImpl* graphImpl = static_cast<MediaStreamGraphImpl*>(this); |
4148 | 0 | graphImpl->AppendMessage(MakeUnique<GraphStartedNotificationControlMessage>(aStream)); |
4149 | 0 | } |
4150 | 0 | } |
4151 | | |
4152 | | void |
4153 | | MediaStreamGraphImpl::IncrementSuspendCount(MediaStream* aStream) |
4154 | 0 | { |
4155 | 0 | MOZ_ASSERT(OnGraphThreadOrNotRunning()); |
4156 | 0 | if (!aStream->IsSuspended()) { |
4157 | 0 | MOZ_ASSERT(mStreams.Contains(aStream)); |
4158 | 0 | mStreams.RemoveElement(aStream); |
4159 | 0 | mSuspendedStreams.AppendElement(aStream); |
4160 | 0 | SetStreamOrderDirty(); |
4161 | 0 | } |
4162 | 0 | aStream->IncrementSuspendCount(); |
4163 | 0 | } |
4164 | | |
4165 | | void |
4166 | | MediaStreamGraphImpl::DecrementSuspendCount(MediaStream* aStream) |
4167 | 0 | { |
4168 | 0 | MOZ_ASSERT(OnGraphThreadOrNotRunning()); |
4169 | 0 | bool wasSuspended = aStream->IsSuspended(); |
4170 | 0 | aStream->DecrementSuspendCount(); |
4171 | 0 | if (wasSuspended && !aStream->IsSuspended()) { |
4172 | 0 | MOZ_ASSERT(mSuspendedStreams.Contains(aStream)); |
4173 | 0 | mSuspendedStreams.RemoveElement(aStream); |
4174 | 0 | mStreams.AppendElement(aStream); |
4175 | 0 | ProcessedMediaStream* ps = aStream->AsProcessedStream(); |
4176 | 0 | if (ps) { |
4177 | 0 | ps->mCycleMarker = NOT_VISITED; |
4178 | 0 | } |
4179 | 0 | SetStreamOrderDirty(); |
4180 | 0 | } |
4181 | 0 | } |
4182 | | |
4183 | | void |
4184 | | MediaStreamGraphImpl::SuspendOrResumeStreams(AudioContextOperation aAudioContextOperation, |
4185 | | const nsTArray<MediaStream*>& aStreamSet) |
4186 | 0 | { |
4187 | 0 | MOZ_ASSERT(OnGraphThreadOrNotRunning()); |
4188 | 0 | // For our purpose, Suspend and Close are equivalent: we want to remove the |
4189 | 0 | // streams from the set of streams that are going to be processed. |
4190 | 0 | for (MediaStream* stream : aStreamSet) { |
4191 | 0 | if (aAudioContextOperation == AudioContextOperation::Resume) { |
4192 | 0 | DecrementSuspendCount(stream); |
4193 | 0 | } else { |
4194 | 0 | IncrementSuspendCount(stream); |
4195 | 0 | } |
4196 | 0 | } |
4197 | 0 | LOG(LogLevel::Debug, |
4198 | 0 | ("Moving streams between suspended and running" |
4199 | 0 | "state: mStreams: %zu, mSuspendedStreams: %zu", |
4200 | 0 | mStreams.Length(), |
4201 | 0 | mSuspendedStreams.Length())); |
4202 | | #ifdef DEBUG |
4203 | | // The intersection of the two arrays should be null. |
4204 | | for (uint32_t i = 0; i < mStreams.Length(); i++) { |
4205 | | for (uint32_t j = 0; j < mSuspendedStreams.Length(); j++) { |
4206 | | MOZ_ASSERT( |
4207 | | mStreams[i] != mSuspendedStreams[j], |
4208 | | "The suspended stream set and running stream set are not disjoint."); |
4209 | | } |
4210 | | } |
4211 | | #endif |
4212 | | } |
4213 | | |
4214 | | void |
4215 | | MediaStreamGraphImpl::AudioContextOperationCompleted(MediaStream* aStream, |
4216 | | void* aPromise, |
4217 | | AudioContextOperation aOperation) |
4218 | 0 | { |
4219 | 0 | // This can be called from the thread created to do cubeb operation, or the |
4220 | 0 | // MSG thread. The pointers passed back here are refcounted, so are still |
4221 | 0 | // alive. |
4222 | 0 | AudioContextState state; |
4223 | 0 | switch (aOperation) { |
4224 | 0 | case AudioContextOperation::Suspend: |
4225 | 0 | state = AudioContextState::Suspended; |
4226 | 0 | break; |
4227 | 0 | case AudioContextOperation::Resume: |
4228 | 0 | state = AudioContextState::Running; |
4229 | 0 | break; |
4230 | 0 | case AudioContextOperation::Close: |
4231 | 0 | state = AudioContextState::Closed; |
4232 | 0 | break; |
4233 | 0 | default: MOZ_CRASH("Not handled."); |
4234 | 0 | } |
4235 | 0 |
|
4236 | 0 | nsCOMPtr<nsIRunnable> event = new dom::StateChangeTask( |
4237 | 0 | aStream->AsAudioNodeStream(), aPromise, state); |
4238 | 0 | mAbstractMainThread->Dispatch(event.forget()); |
4239 | 0 | } |
4240 | | |
4241 | | void |
4242 | | MediaStreamGraphImpl::ApplyAudioContextOperationImpl( |
4243 | | MediaStream* aDestinationStream, const nsTArray<MediaStream*>& aStreams, |
4244 | | AudioContextOperation aOperation, void* aPromise) |
4245 | 0 | { |
4246 | 0 | MOZ_ASSERT(OnGraphThread()); |
4247 | 0 |
|
4248 | 0 | SuspendOrResumeStreams(aOperation, aStreams); |
4249 | 0 |
|
4250 | 0 | bool switching = false; |
4251 | 0 | GraphDriver* nextDriver = nullptr; |
4252 | 0 | { |
4253 | 0 | MonitorAutoLock lock(mMonitor); |
4254 | 0 | switching = CurrentDriver()->Switching(); |
4255 | 0 | if (switching) { |
4256 | 0 | nextDriver = CurrentDriver()->NextDriver(); |
4257 | 0 | } |
4258 | 0 | } |
4259 | 0 |
|
4260 | 0 | // If we have suspended the last AudioContext, and we don't have other |
4261 | 0 | // streams that have audio, this graph will automatically switch to a |
4262 | 0 | // SystemCallbackDriver, because it can't find a MediaStream that has an audio |
4263 | 0 | // track. When resuming, force switching to an AudioCallbackDriver (if we're |
4264 | 0 | // not already switching). It would have happened at the next iteration |
4265 | 0 | // anyways, but doing this now save some time. |
4266 | 0 | if (aOperation == AudioContextOperation::Resume) { |
4267 | 0 | if (!CurrentDriver()->AsAudioCallbackDriver()) { |
4268 | 0 | AudioCallbackDriver* driver; |
4269 | 0 | if (switching) { |
4270 | 0 | MOZ_ASSERT(nextDriver->AsAudioCallbackDriver()); |
4271 | 0 | driver = nextDriver->AsAudioCallbackDriver(); |
4272 | 0 | } else { |
4273 | 0 | driver = new AudioCallbackDriver(this, AudioInputChannelCount()); |
4274 | 0 | MonitorAutoLock lock(mMonitor); |
4275 | 0 | CurrentDriver()->SwitchAtNextIteration(driver); |
4276 | 0 | } |
4277 | 0 | driver->EnqueueStreamAndPromiseForOperation(aDestinationStream, |
4278 | 0 | aPromise, aOperation); |
4279 | 0 | } else { |
4280 | 0 | // We are resuming a context, but we are already using an |
4281 | 0 | // AudioCallbackDriver, we can resolve the promise now. |
4282 | 0 | AudioContextOperationCompleted(aDestinationStream, aPromise, aOperation); |
4283 | 0 | } |
4284 | 0 | } |
4285 | 0 | // Close, suspend: check if we are going to switch to a |
4286 | 0 | // SystemAudioCallbackDriver, and pass the promise to the AudioCallbackDriver |
4287 | 0 | // if that's the case, so it can notify the content. |
4288 | 0 | // This is the same logic as in UpdateStreamOrder, but it's simpler to have it |
4289 | 0 | // here as well so we don't have to store the Promise(s) on the Graph. |
4290 | 0 | if (aOperation != AudioContextOperation::Resume) { |
4291 | 0 | bool audioTrackPresent = AudioTrackPresent(); |
4292 | 0 |
|
4293 | 0 | if (!audioTrackPresent && CurrentDriver()->AsAudioCallbackDriver()) { |
4294 | 0 | CurrentDriver()->AsAudioCallbackDriver()-> |
4295 | 0 | EnqueueStreamAndPromiseForOperation(aDestinationStream, aPromise, |
4296 | 0 | aOperation); |
4297 | 0 |
|
4298 | 0 | SystemClockDriver* driver; |
4299 | 0 | if (nextDriver) { |
4300 | 0 | MOZ_ASSERT(!nextDriver->AsAudioCallbackDriver()); |
4301 | 0 | } else { |
4302 | 0 | driver = new SystemClockDriver(this); |
4303 | 0 | MonitorAutoLock lock(mMonitor); |
4304 | 0 | CurrentDriver()->SwitchAtNextIteration(driver); |
4305 | 0 | } |
4306 | 0 | // We are closing or suspending an AudioContext, but we just got resumed. |
4307 | 0 | // Queue the operation on the next driver so that the ordering is |
4308 | 0 | // preserved. |
4309 | 0 | } else if (!audioTrackPresent && switching) { |
4310 | 0 | MOZ_ASSERT(nextDriver->AsAudioCallbackDriver() || |
4311 | 0 | nextDriver->AsSystemClockDriver()->IsFallback()); |
4312 | 0 | if (nextDriver->AsAudioCallbackDriver()) { |
4313 | 0 | nextDriver->AsAudioCallbackDriver()-> |
4314 | 0 | EnqueueStreamAndPromiseForOperation(aDestinationStream, aPromise, |
4315 | 0 | aOperation); |
4316 | 0 | } else { |
4317 | 0 | // If this is not an AudioCallbackDriver, this means we failed opening an |
4318 | 0 | // AudioCallbackDriver in the past, and we're constantly trying to re-open |
4319 | 0 | // an new audio stream, but are running this graph that has an audio track |
4320 | 0 | // off a SystemClockDriver for now to keep things moving. This is the |
4321 | 0 | // case where we're trying to switch an an system driver (because suspend |
4322 | 0 | // or close have been called on an AudioContext, or we've closed the |
4323 | 0 | // page), but we're already running one. We can just resolve the promise |
4324 | 0 | // now: we're already running off a system thread. |
4325 | 0 | AudioContextOperationCompleted(aDestinationStream, aPromise, aOperation); |
4326 | 0 | } |
4327 | 0 | } else { |
4328 | 0 | // We are closing or suspending an AudioContext, but something else is |
4329 | 0 | // using the audio stream, we can resolve the promise now. |
4330 | 0 | AudioContextOperationCompleted(aDestinationStream, aPromise, aOperation); |
4331 | 0 | } |
4332 | 0 | } |
4333 | 0 | } |
4334 | | |
4335 | | void |
4336 | | MediaStreamGraph::ApplyAudioContextOperation(MediaStream* aDestinationStream, |
4337 | | const nsTArray<MediaStream*>& aStreams, |
4338 | | AudioContextOperation aOperation, |
4339 | | void* aPromise) |
4340 | 0 | { |
4341 | 0 | class AudioContextOperationControlMessage : public ControlMessage |
4342 | 0 | { |
4343 | 0 | public: |
4344 | 0 | AudioContextOperationControlMessage(MediaStream* aDestinationStream, |
4345 | 0 | const nsTArray<MediaStream*>& aStreams, |
4346 | 0 | AudioContextOperation aOperation, |
4347 | 0 | void* aPromise) |
4348 | 0 | : ControlMessage(aDestinationStream) |
4349 | 0 | , mStreams(aStreams) |
4350 | 0 | , mAudioContextOperation(aOperation) |
4351 | 0 | , mPromise(aPromise) |
4352 | 0 | { |
4353 | 0 | } |
4354 | 0 | void Run() override |
4355 | 0 | { |
4356 | 0 | mStream->GraphImpl()->ApplyAudioContextOperationImpl(mStream, |
4357 | 0 | mStreams, mAudioContextOperation, mPromise); |
4358 | 0 | } |
4359 | 0 | void RunDuringShutdown() override |
4360 | 0 | { |
4361 | 0 | MOZ_ASSERT(mAudioContextOperation == AudioContextOperation::Close, |
4362 | 0 | "We should be reviving the graph?"); |
4363 | 0 | } |
4364 | 0 |
|
4365 | 0 | private: |
4366 | 0 | // We don't need strong references here for the same reason ControlMessage |
4367 | 0 | // doesn't. |
4368 | 0 | nsTArray<MediaStream*> mStreams; |
4369 | 0 | AudioContextOperation mAudioContextOperation; |
4370 | 0 | void* mPromise; |
4371 | 0 | }; |
4372 | 0 |
|
4373 | 0 | MediaStreamGraphImpl* graphImpl = static_cast<MediaStreamGraphImpl*>(this); |
4374 | 0 | graphImpl->AppendMessage( |
4375 | 0 | MakeUnique<AudioContextOperationControlMessage>(aDestinationStream, aStreams, |
4376 | 0 | aOperation, aPromise)); |
4377 | 0 | } |
4378 | | |
4379 | | bool |
4380 | | MediaStreamGraph::IsNonRealtime() const |
4381 | 0 | { |
4382 | 0 | return !static_cast<const MediaStreamGraphImpl*>(this)->mRealtime; |
4383 | 0 | } |
4384 | | |
4385 | | void |
4386 | | MediaStreamGraph::StartNonRealtimeProcessing(uint32_t aTicksToProcess) |
4387 | 0 | { |
4388 | 0 | MOZ_ASSERT(NS_IsMainThread(), "main thread only"); |
4389 | 0 |
|
4390 | 0 | MediaStreamGraphImpl* graph = static_cast<MediaStreamGraphImpl*>(this); |
4391 | 0 | NS_ASSERTION(!graph->mRealtime, "non-realtime only"); |
4392 | 0 |
|
4393 | 0 | if (graph->mNonRealtimeProcessing) |
4394 | 0 | return; |
4395 | 0 | |
4396 | 0 | graph->mEndTime = |
4397 | 0 | graph->RoundUpToEndOfAudioBlock(graph->mStateComputedTime + |
4398 | 0 | aTicksToProcess); |
4399 | 0 | graph->mNonRealtimeProcessing = true; |
4400 | 0 | graph->EnsureRunInStableState(); |
4401 | 0 | } |
4402 | | |
4403 | | void |
4404 | | ProcessedMediaStream::AddInput(MediaInputPort* aPort) |
4405 | 0 | { |
4406 | 0 | MediaStream* s = aPort->GetSource(); |
4407 | 0 | if (!s->IsSuspended()) { |
4408 | 0 | mInputs.AppendElement(aPort); |
4409 | 0 | } else { |
4410 | 0 | mSuspendedInputs.AppendElement(aPort); |
4411 | 0 | } |
4412 | 0 | GraphImpl()->SetStreamOrderDirty(); |
4413 | 0 | } |
4414 | | |
4415 | | void |
4416 | | ProcessedMediaStream::InputSuspended(MediaInputPort* aPort) |
4417 | 0 | { |
4418 | 0 | GraphImpl()->AssertOnGraphThreadOrNotRunning(); |
4419 | 0 | mInputs.RemoveElement(aPort); |
4420 | 0 | mSuspendedInputs.AppendElement(aPort); |
4421 | 0 | GraphImpl()->SetStreamOrderDirty(); |
4422 | 0 | } |
4423 | | |
4424 | | void |
4425 | | ProcessedMediaStream::InputResumed(MediaInputPort* aPort) |
4426 | 0 | { |
4427 | 0 | GraphImpl()->AssertOnGraphThreadOrNotRunning(); |
4428 | 0 | mSuspendedInputs.RemoveElement(aPort); |
4429 | 0 | mInputs.AppendElement(aPort); |
4430 | 0 | GraphImpl()->SetStreamOrderDirty(); |
4431 | 0 | } |
4432 | | |
4433 | | void |
4434 | | MediaStreamGraph::RegisterCaptureStreamForWindow( |
4435 | | uint64_t aWindowId, ProcessedMediaStream* aCaptureStream) |
4436 | 0 | { |
4437 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
4438 | 0 | MediaStreamGraphImpl* graphImpl = static_cast<MediaStreamGraphImpl*>(this); |
4439 | 0 | graphImpl->RegisterCaptureStreamForWindow(aWindowId, aCaptureStream); |
4440 | 0 | } |
4441 | | |
4442 | | void |
4443 | | MediaStreamGraphImpl::RegisterCaptureStreamForWindow( |
4444 | | uint64_t aWindowId, ProcessedMediaStream* aCaptureStream) |
4445 | 0 | { |
4446 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
4447 | 0 | WindowAndStream winAndStream; |
4448 | 0 | winAndStream.mWindowId = aWindowId; |
4449 | 0 | winAndStream.mCaptureStreamSink = aCaptureStream; |
4450 | 0 | mWindowCaptureStreams.AppendElement(winAndStream); |
4451 | 0 | } |
4452 | | |
4453 | | void |
4454 | | MediaStreamGraph::UnregisterCaptureStreamForWindow(uint64_t aWindowId) |
4455 | 0 | { |
4456 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
4457 | 0 | MediaStreamGraphImpl* graphImpl = static_cast<MediaStreamGraphImpl*>(this); |
4458 | 0 | graphImpl->UnregisterCaptureStreamForWindow(aWindowId); |
4459 | 0 | } |
4460 | | |
4461 | | void |
4462 | | MediaStreamGraphImpl::UnregisterCaptureStreamForWindow(uint64_t aWindowId) |
4463 | 0 | { |
4464 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
4465 | 0 | for (int32_t i = mWindowCaptureStreams.Length() - 1; i >= 0; i--) { |
4466 | 0 | if (mWindowCaptureStreams[i].mWindowId == aWindowId) { |
4467 | 0 | mWindowCaptureStreams.RemoveElementAt(i); |
4468 | 0 | } |
4469 | 0 | } |
4470 | 0 | } |
4471 | | |
4472 | | already_AddRefed<MediaInputPort> |
4473 | | MediaStreamGraph::ConnectToCaptureStream(uint64_t aWindowId, |
4474 | | MediaStream* aMediaStream) |
4475 | 0 | { |
4476 | 0 | return aMediaStream->GraphImpl()->ConnectToCaptureStream(aWindowId, |
4477 | 0 | aMediaStream); |
4478 | 0 | } |
4479 | | |
4480 | | already_AddRefed<MediaInputPort> |
4481 | | MediaStreamGraphImpl::ConnectToCaptureStream(uint64_t aWindowId, |
4482 | | MediaStream* aMediaStream) |
4483 | 0 | { |
4484 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
4485 | 0 | for (uint32_t i = 0; i < mWindowCaptureStreams.Length(); i++) { |
4486 | 0 | if (mWindowCaptureStreams[i].mWindowId == aWindowId) { |
4487 | 0 | ProcessedMediaStream* sink = mWindowCaptureStreams[i].mCaptureStreamSink; |
4488 | 0 | return sink->AllocateInputPort(aMediaStream); |
4489 | 0 | } |
4490 | 0 | } |
4491 | 0 | return nullptr; |
4492 | 0 | } |
4493 | | |
4494 | | void |
4495 | | MediaStreamGraph::DispatchToMainThreadAfterStreamStateUpdate( |
4496 | | already_AddRefed<nsIRunnable> aRunnable) |
4497 | 0 | { |
4498 | 0 | AssertOnGraphThreadOrNotRunning(); |
4499 | 0 | *mPendingUpdateRunnables.AppendElement() = |
4500 | 0 | AbstractMainThread()->CreateDirectTaskDrainer(std::move(aRunnable)); |
4501 | 0 | } |
4502 | | |
4503 | | } // namespace mozilla |