/src/mozilla-central/dom/media/encoder/MediaEncoder.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 "MediaEncoder.h" |
7 | | |
8 | | #include <algorithm> |
9 | | #include "AudioNodeEngine.h" |
10 | | #include "AudioNodeStream.h" |
11 | | #include "GeckoProfiler.h" |
12 | | #include "MediaDecoder.h" |
13 | | #include "MediaStreamVideoSink.h" |
14 | | #include "mozilla/dom/AudioNode.h" |
15 | | #include "mozilla/dom/AudioStreamTrack.h" |
16 | | #include "mozilla/dom/MediaStreamTrack.h" |
17 | | #include "mozilla/dom/VideoStreamTrack.h" |
18 | | #include "mozilla/gfx/Point.h" // IntSize |
19 | | #include "mozilla/Logging.h" |
20 | | #include "mozilla/media/MediaUtils.h" |
21 | | #include "mozilla/Preferences.h" |
22 | | #include "mozilla/StaticPrefs.h" |
23 | | #include "mozilla/StaticPtr.h" |
24 | | #include "mozilla/TaskQueue.h" |
25 | | #include "mozilla/Unused.h" |
26 | | #include "nsIPrincipal.h" |
27 | | #include "nsMimeTypes.h" |
28 | | #include "OggWriter.h" |
29 | | #include "OpusTrackEncoder.h" |
30 | | #include "TimeUnits.h" |
31 | | #include "Tracing.h" |
32 | | |
33 | | #ifdef MOZ_WEBM_ENCODER |
34 | | #include "VP8TrackEncoder.h" |
35 | | #include "WebMWriter.h" |
36 | | #endif |
37 | | |
38 | | #ifdef LOG |
39 | | #undef LOG |
40 | | #endif |
41 | | |
42 | | mozilla::LazyLogModule gMediaEncoderLog("MediaEncoder"); |
43 | 0 | #define LOG(type, msg) MOZ_LOG(gMediaEncoderLog, type, msg) |
44 | | |
45 | | namespace mozilla { |
46 | | |
47 | | using namespace dom; |
48 | | using namespace media; |
49 | | |
50 | | class MediaEncoder::AudioTrackListener : public DirectMediaStreamTrackListener |
51 | | { |
52 | | public: |
53 | | AudioTrackListener(AudioTrackEncoder* aEncoder, |
54 | | TaskQueue* aEncoderThread) |
55 | | : mDirectConnected(false) |
56 | | , mInitialized(false) |
57 | | , mRemoved(false) |
58 | | , mEncoder(aEncoder) |
59 | | , mEncoderThread(aEncoderThread) |
60 | 0 | { |
61 | 0 | MOZ_ASSERT(mEncoder); |
62 | 0 | MOZ_ASSERT(mEncoderThread); |
63 | 0 | } |
64 | | |
65 | 0 | void NotifyShutdown() { |
66 | 0 | mShutdown = true; |
67 | 0 | } |
68 | | |
69 | | void NotifyDirectListenerInstalled(InstallationResult aResult) override |
70 | 0 | { |
71 | 0 | if (aResult == InstallationResult::SUCCESS) { |
72 | 0 | LOG(LogLevel::Info, ("Audio track direct listener installed")); |
73 | 0 | mDirectConnected = true; |
74 | 0 | } else { |
75 | 0 | LOG(LogLevel::Info, ("Audio track failed to install direct listener")); |
76 | 0 | MOZ_ASSERT(!mDirectConnected); |
77 | 0 | } |
78 | 0 | } |
79 | | |
80 | | void NotifyDirectListenerUninstalled() override |
81 | 0 | { |
82 | 0 | mDirectConnected = false; |
83 | 0 |
|
84 | 0 | if (mRemoved) { |
85 | 0 | mEncoder = nullptr; |
86 | 0 | mEncoderThread = nullptr; |
87 | 0 | } |
88 | 0 | } |
89 | | |
90 | | void NotifyQueuedChanges(MediaStreamGraph* aGraph, |
91 | | StreamTime aTrackOffset, |
92 | | const MediaSegment& aQueuedMedia) override |
93 | 0 | { |
94 | 0 | TRACE_COMMENT("Encoder %p", mEncoder.get()); |
95 | 0 | MOZ_ASSERT(mEncoder); |
96 | 0 | MOZ_ASSERT(mEncoderThread); |
97 | 0 |
|
98 | 0 | if (mShutdown) { |
99 | 0 | return; |
100 | 0 | } |
101 | 0 | |
102 | 0 | if (!mInitialized) { |
103 | 0 | nsresult rv = |
104 | 0 | mEncoderThread->Dispatch( |
105 | 0 | NewRunnableMethod<StreamTime>( |
106 | 0 | "mozilla::AudioTrackEncoder::SetStartOffset", |
107 | 0 | mEncoder, &AudioTrackEncoder::SetStartOffset, aTrackOffset)); |
108 | 0 | MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); |
109 | 0 | Unused << rv; |
110 | 0 | mInitialized = true; |
111 | 0 | } |
112 | 0 |
|
113 | 0 | if (mDirectConnected) { |
114 | 0 | if (aQueuedMedia.IsNull()) { |
115 | 0 | nsresult rv = |
116 | 0 | mEncoderThread->Dispatch( |
117 | 0 | NewRunnableMethod<StreamTime>( |
118 | 0 | "mozilla::AudioTrackEncoder::AdvanceBlockedInput", |
119 | 0 | mEncoder, &AudioTrackEncoder::AdvanceBlockedInput, |
120 | 0 | aQueuedMedia.GetDuration())); |
121 | 0 | MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); |
122 | 0 | Unused << rv; |
123 | 0 | return; |
124 | 0 | } |
125 | 0 | } else { |
126 | 0 | NotifyRealtimeTrackData(aGraph, aTrackOffset, aQueuedMedia); |
127 | 0 | } |
128 | 0 |
|
129 | 0 | nsresult rv = |
130 | 0 | mEncoderThread->Dispatch( |
131 | 0 | NewRunnableMethod<StreamTime>( |
132 | 0 | "mozilla::AudioTrackEncoder::AdvanceCurrentTime", |
133 | 0 | mEncoder, &AudioTrackEncoder::AdvanceCurrentTime, |
134 | 0 | aQueuedMedia.GetDuration())); |
135 | 0 | MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); |
136 | 0 | Unused << rv; |
137 | 0 | } |
138 | | |
139 | | void NotifyRealtimeTrackData(MediaStreamGraph* aGraph, |
140 | | StreamTime aTrackOffset, |
141 | | const MediaSegment& aMedia) override |
142 | 0 | { |
143 | 0 | TRACE_COMMENT("Encoder %p", mEncoder.get()); |
144 | 0 | MOZ_ASSERT(mEncoder); |
145 | 0 | MOZ_ASSERT(mEncoderThread); |
146 | 0 |
|
147 | 0 | if (mShutdown) { |
148 | 0 | return; |
149 | 0 | } |
150 | 0 | |
151 | 0 | const AudioSegment& audio = static_cast<const AudioSegment&>(aMedia); |
152 | 0 |
|
153 | 0 | AudioSegment copy; |
154 | 0 | copy.AppendSlice(audio, 0, audio.GetDuration()); |
155 | 0 |
|
156 | 0 | nsresult rv = |
157 | 0 | mEncoderThread->Dispatch( |
158 | 0 | NewRunnableMethod<StoreCopyPassByRRef<AudioSegment>>( |
159 | 0 | "mozilla::AudioTrackEncoder::AppendAudioSegment", |
160 | 0 | mEncoder, &AudioTrackEncoder::AppendAudioSegment, std::move(copy))); |
161 | 0 | MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); |
162 | 0 | Unused << rv; |
163 | 0 | } |
164 | | |
165 | | void NotifyEnded() override |
166 | 0 | { |
167 | 0 | MOZ_ASSERT(mEncoder); |
168 | 0 | MOZ_ASSERT(mEncoderThread); |
169 | 0 |
|
170 | 0 | if (mShutdown) { |
171 | 0 | return; |
172 | 0 | } |
173 | 0 | |
174 | 0 | nsresult rv = |
175 | 0 | mEncoderThread->Dispatch( |
176 | 0 | NewRunnableMethod("mozilla::AudioTrackEncoder::NotifyEndOfStream", |
177 | 0 | mEncoder, &AudioTrackEncoder::NotifyEndOfStream)); |
178 | 0 | MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); |
179 | 0 | Unused << rv; |
180 | 0 | } |
181 | | |
182 | | void NotifyRemoved() override |
183 | 0 | { |
184 | 0 | if (!mShutdown) { |
185 | 0 | nsresult rv = |
186 | 0 | mEncoderThread->Dispatch( |
187 | 0 | NewRunnableMethod("mozilla::AudioTrackEncoder::NotifyEndOfStream", |
188 | 0 | mEncoder, &AudioTrackEncoder::NotifyEndOfStream)); |
189 | 0 | MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); |
190 | 0 | Unused << rv; |
191 | 0 | } |
192 | 0 |
|
193 | 0 | mRemoved = true; |
194 | 0 |
|
195 | 0 | if (!mDirectConnected) { |
196 | 0 | mEncoder = nullptr; |
197 | 0 | mEncoderThread = nullptr; |
198 | 0 | } |
199 | 0 | } |
200 | | |
201 | | private: |
202 | | // True when MediaEncoder has shutdown and destroyed the TaskQueue. |
203 | | Atomic<bool> mShutdown; |
204 | | bool mDirectConnected; |
205 | | bool mInitialized; |
206 | | bool mRemoved; |
207 | | RefPtr<AudioTrackEncoder> mEncoder; |
208 | | RefPtr<TaskQueue> mEncoderThread; |
209 | | }; |
210 | | |
211 | | class MediaEncoder::VideoTrackListener : public MediaStreamVideoSink |
212 | | { |
213 | | public: |
214 | | VideoTrackListener(VideoTrackEncoder* aEncoder, |
215 | | TaskQueue* aEncoderThread) |
216 | | : mDirectConnected(false) |
217 | | , mInitialized(false) |
218 | | , mRemoved(false) |
219 | | , mEncoder(aEncoder) |
220 | | , mEncoderThread(aEncoderThread) |
221 | 0 | { |
222 | 0 | MOZ_ASSERT(mEncoder); |
223 | 0 | MOZ_ASSERT(mEncoderThread); |
224 | 0 | } |
225 | | |
226 | 0 | void NotifyShutdown() { |
227 | 0 | mShutdown = true; |
228 | 0 | } |
229 | | |
230 | | void NotifyDirectListenerInstalled(InstallationResult aResult) override |
231 | 0 | { |
232 | 0 | if (aResult == InstallationResult::SUCCESS) { |
233 | 0 | LOG(LogLevel::Info, ("Video track direct listener installed")); |
234 | 0 | mDirectConnected = true; |
235 | 0 | } else { |
236 | 0 | LOG(LogLevel::Info, ("Video track failed to install direct listener")); |
237 | 0 | MOZ_ASSERT(!mDirectConnected); |
238 | 0 | return; |
239 | 0 | } |
240 | 0 | } |
241 | | |
242 | | void NotifyDirectListenerUninstalled() override |
243 | 0 | { |
244 | 0 | mDirectConnected = false; |
245 | 0 |
|
246 | 0 | if (mRemoved) { |
247 | 0 | mEncoder = nullptr; |
248 | 0 | mEncoderThread = nullptr; |
249 | 0 | } |
250 | 0 | } |
251 | | |
252 | | void NotifyQueuedChanges(MediaStreamGraph* aGraph, |
253 | | StreamTime aTrackOffset, |
254 | | const MediaSegment& aQueuedMedia) override |
255 | 0 | { |
256 | 0 | TRACE_COMMENT("Encoder %p", mEncoder.get()); |
257 | 0 | MOZ_ASSERT(mEncoder); |
258 | 0 | MOZ_ASSERT(mEncoderThread); |
259 | 0 |
|
260 | 0 | if (mShutdown) { |
261 | 0 | return; |
262 | 0 | } |
263 | 0 | |
264 | 0 | if (!mInitialized) { |
265 | 0 | nsresult rv = |
266 | 0 | mEncoderThread->Dispatch( |
267 | 0 | NewRunnableMethod<StreamTime>( |
268 | 0 | "mozilla::VideoTrackEncoder::SetStartOffset", |
269 | 0 | mEncoder, &VideoTrackEncoder::SetStartOffset, aTrackOffset)); |
270 | 0 | MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); |
271 | 0 | Unused << rv; |
272 | 0 | mInitialized = true; |
273 | 0 | } |
274 | 0 |
|
275 | 0 | if (aQueuedMedia.IsNull()) { |
276 | 0 | nsresult rv = |
277 | 0 | mEncoderThread->Dispatch( |
278 | 0 | NewRunnableMethod<StreamTime>( |
279 | 0 | "mozilla::VideoTrackEncoder::AdvanceBlockedInput", |
280 | 0 | mEncoder, &VideoTrackEncoder::AdvanceBlockedInput, |
281 | 0 | aQueuedMedia.GetDuration())); |
282 | 0 | MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); |
283 | 0 | Unused << rv; |
284 | 0 | return; |
285 | 0 | } |
286 | 0 | |
287 | 0 | nsresult rv = |
288 | 0 | mEncoderThread->Dispatch( |
289 | 0 | NewRunnableMethod<StreamTime>( |
290 | 0 | "mozilla::VideoTrackEncoder::AdvanceCurrentTime", |
291 | 0 | mEncoder, &VideoTrackEncoder::AdvanceCurrentTime, |
292 | 0 | aQueuedMedia.GetDuration())); |
293 | 0 | MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); |
294 | 0 | Unused << rv; |
295 | 0 | } |
296 | | |
297 | | void SetCurrentFrames(const VideoSegment& aMedia) override |
298 | 0 | { |
299 | 0 | TRACE_COMMENT("Encoder %p", mEncoder.get()); |
300 | 0 | MOZ_ASSERT(mEncoder); |
301 | 0 | MOZ_ASSERT(mEncoderThread); |
302 | 0 |
|
303 | 0 | if (mShutdown) { |
304 | 0 | return; |
305 | 0 | } |
306 | 0 | |
307 | 0 | VideoSegment copy; |
308 | 0 | copy.AppendSlice(aMedia, 0, aMedia.GetDuration()); |
309 | 0 |
|
310 | 0 | nsresult rv = |
311 | 0 | mEncoderThread->Dispatch( |
312 | 0 | NewRunnableMethod<StoreCopyPassByRRef<VideoSegment>>( |
313 | 0 | "mozilla::VideoTrackEncoder::AppendVideoSegment", |
314 | 0 | mEncoder, &VideoTrackEncoder::AppendVideoSegment, std::move(copy))); |
315 | 0 | MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); |
316 | 0 | Unused << rv; |
317 | 0 | } |
318 | | |
319 | 0 | void ClearFrames() override {} |
320 | | |
321 | | void NotifyEnded() override |
322 | 0 | { |
323 | 0 | MOZ_ASSERT(mEncoder); |
324 | 0 | MOZ_ASSERT(mEncoderThread); |
325 | 0 |
|
326 | 0 | if (mShutdown) { |
327 | 0 | return; |
328 | 0 | } |
329 | 0 | |
330 | 0 | nsresult rv = |
331 | 0 | mEncoderThread->Dispatch( |
332 | 0 | NewRunnableMethod("mozilla::VideoTrackEncoder::NotifyEndOfStream", |
333 | 0 | mEncoder, &VideoTrackEncoder::NotifyEndOfStream)); |
334 | 0 | MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); |
335 | 0 | Unused << rv; |
336 | 0 | } |
337 | | |
338 | | void NotifyRemoved() override |
339 | 0 | { |
340 | 0 | if (!mShutdown) { |
341 | 0 | nsresult rv = |
342 | 0 | mEncoderThread->Dispatch( |
343 | 0 | NewRunnableMethod("mozilla::VideoTrackEncoder::NotifyEndOfStream", |
344 | 0 | mEncoder, &VideoTrackEncoder::NotifyEndOfStream)); |
345 | 0 | MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); |
346 | 0 | Unused << rv; |
347 | 0 | } |
348 | 0 |
|
349 | 0 | mRemoved = true; |
350 | 0 |
|
351 | 0 | if (!mDirectConnected) { |
352 | 0 | mEncoder = nullptr; |
353 | 0 | mEncoderThread = nullptr; |
354 | 0 | } |
355 | 0 | } |
356 | | |
357 | | private: |
358 | | // True when MediaEncoder has shutdown and destroyed the TaskQueue. |
359 | | Atomic<bool> mShutdown; |
360 | | bool mDirectConnected; |
361 | | bool mInitialized; |
362 | | bool mRemoved; |
363 | | RefPtr<VideoTrackEncoder> mEncoder; |
364 | | RefPtr<TaskQueue> mEncoderThread; |
365 | | }; |
366 | | |
367 | | class MediaEncoder::EncoderListener : public TrackEncoderListener |
368 | | { |
369 | | public: |
370 | | EncoderListener(TaskQueue* aEncoderThread, MediaEncoder* aEncoder) |
371 | | : mEncoderThread(aEncoderThread) |
372 | | , mEncoder(aEncoder) |
373 | | , mPendingDataAvailable(false) |
374 | 0 | {} |
375 | | |
376 | | void Forget() |
377 | 0 | { |
378 | 0 | MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn()); |
379 | 0 | mEncoder = nullptr; |
380 | 0 | } |
381 | | |
382 | | void Initialized(TrackEncoder* aTrackEncoder) override |
383 | 0 | { |
384 | 0 | MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn()); |
385 | 0 | MOZ_ASSERT(aTrackEncoder->IsInitialized()); |
386 | 0 |
|
387 | 0 | if (!mEncoder) { |
388 | 0 | return; |
389 | 0 | } |
390 | 0 | |
391 | 0 | nsresult rv = |
392 | 0 | mEncoderThread->Dispatch( |
393 | 0 | NewRunnableMethod("mozilla::MediaEncoder::NotifyInitialized", |
394 | 0 | mEncoder, &MediaEncoder::NotifyInitialized)); |
395 | 0 | MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); |
396 | 0 | Unused << rv; |
397 | 0 | } |
398 | | |
399 | | void DataAvailable(TrackEncoder* aTrackEncoder) override |
400 | 0 | { |
401 | 0 | MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn()); |
402 | 0 | MOZ_ASSERT(aTrackEncoder->IsInitialized()); |
403 | 0 |
|
404 | 0 | if (!mEncoder) { |
405 | 0 | return; |
406 | 0 | } |
407 | 0 | |
408 | 0 | if (mPendingDataAvailable) { |
409 | 0 | return; |
410 | 0 | } |
411 | 0 | |
412 | 0 | nsresult rv = |
413 | 0 | mEncoderThread->Dispatch( |
414 | 0 | NewRunnableMethod("mozilla::MediaEncoder::EncoderListener::DataAvailableImpl", |
415 | 0 | this, &EncoderListener::DataAvailableImpl)); |
416 | 0 | MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); |
417 | 0 | Unused << rv; |
418 | 0 |
|
419 | 0 | mPendingDataAvailable = true; |
420 | 0 | } |
421 | | |
422 | | void DataAvailableImpl() |
423 | 0 | { |
424 | 0 | MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn()); |
425 | 0 |
|
426 | 0 | if (!mEncoder) { |
427 | 0 | return; |
428 | 0 | } |
429 | 0 | |
430 | 0 | mEncoder->NotifyDataAvailable(); |
431 | 0 | mPendingDataAvailable = false; |
432 | 0 | } |
433 | | |
434 | | void Error(TrackEncoder* aTrackEncoder) override |
435 | 0 | { |
436 | 0 | MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn()); |
437 | 0 |
|
438 | 0 | if (!mEncoder) { |
439 | 0 | return; |
440 | 0 | } |
441 | 0 | |
442 | 0 | nsresult rv = |
443 | 0 | mEncoderThread->Dispatch( |
444 | 0 | NewRunnableMethod("mozilla::MediaEncoder::SetError", |
445 | 0 | mEncoder, &MediaEncoder::SetError)); |
446 | 0 | MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); |
447 | 0 | Unused << rv; |
448 | 0 | } |
449 | | |
450 | | protected: |
451 | | RefPtr<TaskQueue> mEncoderThread; |
452 | | RefPtr<MediaEncoder> mEncoder; |
453 | | bool mPendingDataAvailable; |
454 | | }; |
455 | | |
456 | | MediaEncoder::MediaEncoder(TaskQueue* aEncoderThread, |
457 | | UniquePtr<ContainerWriter> aWriter, |
458 | | AudioTrackEncoder* aAudioEncoder, |
459 | | VideoTrackEncoder* aVideoEncoder, |
460 | | const nsAString& aMIMEType) |
461 | | : mEncoderThread(aEncoderThread) |
462 | | , mWriter(std::move(aWriter)) |
463 | | , mAudioEncoder(aAudioEncoder) |
464 | | , mVideoEncoder(aVideoEncoder) |
465 | | , mEncoderListener(MakeAndAddRef<EncoderListener>(mEncoderThread, this)) |
466 | | , mStartTime(TimeStamp::Now()) |
467 | | , mMIMEType(aMIMEType) |
468 | | , mInitialized(false) |
469 | | , mMetadataEncoded(false) |
470 | | , mCompleted(false) |
471 | | , mError(false) |
472 | | , mCanceled(false) |
473 | | , mShutdown(false) |
474 | 0 | { |
475 | 0 | if (mAudioEncoder) { |
476 | 0 | mAudioListener = |
477 | 0 | MakeAndAddRef<AudioTrackListener>(mAudioEncoder, mEncoderThread); |
478 | 0 | nsresult rv = |
479 | 0 | mEncoderThread->Dispatch( |
480 | 0 | NewRunnableMethod<RefPtr<EncoderListener>>( |
481 | 0 | "mozilla::AudioTrackEncoder::RegisterListener", |
482 | 0 | mAudioEncoder, &AudioTrackEncoder::RegisterListener, |
483 | 0 | mEncoderListener)); |
484 | 0 | MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); |
485 | 0 | Unused << rv; |
486 | 0 | } |
487 | 0 | if (mVideoEncoder) { |
488 | 0 | mVideoListener = |
489 | 0 | MakeAndAddRef<VideoTrackListener>(mVideoEncoder, mEncoderThread); |
490 | 0 | nsresult rv = |
491 | 0 | mEncoderThread->Dispatch( |
492 | 0 | NewRunnableMethod<RefPtr<EncoderListener>>( |
493 | 0 | "mozilla::VideoTrackEncoder::RegisterListener", |
494 | 0 | mVideoEncoder, &VideoTrackEncoder::RegisterListener, |
495 | 0 | mEncoderListener)); |
496 | 0 | MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); |
497 | 0 | Unused << rv; |
498 | 0 | } |
499 | 0 | } |
500 | | |
501 | | MediaEncoder::~MediaEncoder() |
502 | 0 | { |
503 | 0 | MOZ_ASSERT(mListeners.IsEmpty()); |
504 | 0 | } |
505 | | |
506 | | void |
507 | | MediaEncoder::Suspend(TimeStamp aTime) |
508 | 0 | { |
509 | 0 | auto& ae = mAudioEncoder; |
510 | 0 | auto& ve = mVideoEncoder; |
511 | 0 | nsresult rv = |
512 | 0 | mEncoderThread->Dispatch(NewRunnableFrom([ae, ve, aTime]() { |
513 | 0 | if (ae) { |
514 | 0 | ae->Suspend(aTime); |
515 | 0 | } |
516 | 0 | if (ve) { |
517 | 0 | ve->Suspend(aTime); |
518 | 0 | } |
519 | 0 | return NS_OK; |
520 | 0 | })); |
521 | 0 | MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); |
522 | 0 | Unused << rv; |
523 | 0 | } |
524 | | |
525 | | void |
526 | | MediaEncoder::Resume(TimeStamp aTime) |
527 | 0 | { |
528 | 0 | auto& ae = mAudioEncoder; |
529 | 0 | auto& ve = mVideoEncoder; |
530 | 0 | nsresult rv = |
531 | 0 | mEncoderThread->Dispatch(NewRunnableFrom([ae, ve, aTime]() { |
532 | 0 | if (ae) { |
533 | 0 | ae->Resume(aTime); |
534 | 0 | } |
535 | 0 | if (ve) { |
536 | 0 | ve->Resume(aTime); |
537 | 0 | } |
538 | 0 | return NS_OK; |
539 | 0 | })); |
540 | 0 | MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); |
541 | 0 | Unused << rv; |
542 | 0 | } |
543 | | |
544 | | void |
545 | | MediaEncoder::ConnectAudioNode(AudioNode* aNode, uint32_t aOutput) |
546 | 0 | { |
547 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
548 | 0 |
|
549 | 0 | if (mAudioNode) { |
550 | 0 | MOZ_ASSERT(false, "Only one audio node supported"); |
551 | 0 | return; |
552 | 0 | } |
553 | 0 |
|
554 | 0 | // Only AudioNodeStream of kind EXTERNAL_OUTPUT stores output audio data in |
555 | 0 | // the track (see AudioNodeStream::AdvanceOutputSegment()). That means track |
556 | 0 | // union stream in recorder session won't be able to copy data from the |
557 | 0 | // stream of non-destination node. Create a pipe stream in this case. |
558 | 0 | if (aNode->NumberOfOutputs() > 0) { |
559 | 0 | AudioContext* ctx = aNode->Context(); |
560 | 0 | AudioNodeEngine* engine = new AudioNodeEngine(nullptr); |
561 | 0 | AudioNodeStream::Flags flags = |
562 | 0 | AudioNodeStream::EXTERNAL_OUTPUT | |
563 | 0 | AudioNodeStream::NEED_MAIN_THREAD_FINISHED; |
564 | 0 | mPipeStream = AudioNodeStream::Create(ctx, engine, flags, ctx->Graph()); |
565 | 0 | AudioNodeStream* ns = aNode->GetStream(); |
566 | 0 | if (ns) { |
567 | 0 | mInputPort = |
568 | 0 | mPipeStream->AllocateInputPort(aNode->GetStream(), |
569 | 0 | TRACK_ANY, TRACK_ANY, |
570 | 0 | 0, aOutput); |
571 | 0 | } |
572 | 0 | } |
573 | 0 |
|
574 | 0 | mAudioNode = aNode; |
575 | 0 |
|
576 | 0 | if (mPipeStream) { |
577 | 0 | mPipeStream->AddTrackListener(mAudioListener, AudioNodeStream::AUDIO_TRACK); |
578 | 0 | } else { |
579 | 0 | mAudioNode->GetStream()->AddTrackListener(mAudioListener, AudioNodeStream::AUDIO_TRACK); |
580 | 0 | } |
581 | 0 | } |
582 | | |
583 | | void |
584 | | MediaEncoder::ConnectMediaStreamTrack(MediaStreamTrack* aTrack) |
585 | 0 | { |
586 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
587 | 0 |
|
588 | 0 | if (aTrack->Ended()) { |
589 | 0 | NS_ASSERTION(false, "Cannot connect ended track"); |
590 | 0 | return; |
591 | 0 | } |
592 | 0 |
|
593 | 0 | if (AudioStreamTrack* audio = aTrack->AsAudioStreamTrack()) { |
594 | 0 | if (!mAudioEncoder) { |
595 | 0 | MOZ_ASSERT(false, "No audio encoder for this audio track"); |
596 | 0 | return; |
597 | 0 | } |
598 | 0 | if (mAudioTrack) { |
599 | 0 | MOZ_ASSERT(false, "Only one audio track supported."); |
600 | 0 | return; |
601 | 0 | } |
602 | 0 | if (!mAudioListener) { |
603 | 0 | MOZ_ASSERT(false, "No audio listener for this audio track"); |
604 | 0 | return; |
605 | 0 | } |
606 | 0 |
|
607 | 0 | mAudioTrack = audio; |
608 | 0 | // With full duplex we don't risk having audio come in late to the MSG |
609 | 0 | // so we won't need a direct listener. |
610 | 0 | const bool enableDirectListener = |
611 | 0 | !Preferences::GetBool("media.navigator.audio.full_duplex", false); |
612 | 0 | if (enableDirectListener) { |
613 | 0 | audio->AddDirectListener(mAudioListener); |
614 | 0 | } |
615 | 0 | audio->AddListener(mAudioListener); |
616 | 0 | } else if (VideoStreamTrack* video = aTrack->AsVideoStreamTrack()) { |
617 | 0 | if(!mVideoEncoder) { |
618 | 0 | MOZ_ASSERT(false, "No video encoder for this video track"); |
619 | 0 | return; |
620 | 0 | } |
621 | 0 | if (mVideoTrack) { |
622 | 0 | MOZ_ASSERT(false, "Only one video track supported."); |
623 | 0 | return; |
624 | 0 | } |
625 | 0 | if (!mVideoListener) { |
626 | 0 | MOZ_ASSERT(false, "No video listener for this audio track"); |
627 | 0 | return; |
628 | 0 | } |
629 | 0 |
|
630 | 0 | mVideoTrack = video; |
631 | 0 | video->AddVideoOutput(mVideoListener); |
632 | 0 | video->AddListener(mVideoListener); |
633 | 0 | } else { |
634 | 0 | MOZ_ASSERT(false, "Unknown track type"); |
635 | 0 | } |
636 | 0 | } |
637 | | |
638 | | void |
639 | | MediaEncoder::RemoveMediaStreamTrack(MediaStreamTrack* aTrack) |
640 | 0 | { |
641 | 0 | if (!aTrack) { |
642 | 0 | MOZ_ASSERT(false); |
643 | 0 | return; |
644 | 0 | } |
645 | 0 |
|
646 | 0 | if (AudioStreamTrack* audio = aTrack->AsAudioStreamTrack()) { |
647 | 0 | if (audio != mAudioTrack) { |
648 | 0 | MOZ_ASSERT(false, "Not connected to this audio track"); |
649 | 0 | return; |
650 | 0 | } |
651 | 0 |
|
652 | 0 | if (mAudioListener) { |
653 | 0 | audio->RemoveDirectListener(mAudioListener); |
654 | 0 | audio->RemoveListener(mAudioListener); |
655 | 0 | } |
656 | 0 | mAudioTrack = nullptr; |
657 | 0 | } else if (VideoStreamTrack* video = aTrack->AsVideoStreamTrack()) { |
658 | 0 | if (video != mVideoTrack) { |
659 | 0 | MOZ_ASSERT(false, "Not connected to this video track"); |
660 | 0 | return; |
661 | 0 | } |
662 | 0 |
|
663 | 0 | if (mVideoListener) { |
664 | 0 | video->RemoveVideoOutput(mVideoListener); |
665 | 0 | video->RemoveListener(mVideoListener); |
666 | 0 | } |
667 | 0 | mVideoTrack = nullptr; |
668 | 0 | } |
669 | 0 | } |
670 | | |
671 | | /* static */ |
672 | | already_AddRefed<MediaEncoder> |
673 | | MediaEncoder::CreateEncoder(TaskQueue* aEncoderThread, |
674 | | const nsAString& aMIMEType, |
675 | | uint32_t aAudioBitrate, |
676 | | uint32_t aVideoBitrate, |
677 | | uint8_t aTrackTypes, |
678 | | TrackRate aTrackRate) |
679 | 0 | { |
680 | 0 | AUTO_PROFILER_LABEL("MediaEncoder::CreateEncoder", OTHER); |
681 | 0 |
|
682 | 0 | UniquePtr<ContainerWriter> writer; |
683 | 0 | RefPtr<AudioTrackEncoder> audioEncoder; |
684 | 0 | RefPtr<VideoTrackEncoder> videoEncoder; |
685 | 0 | nsString mimeType; |
686 | 0 |
|
687 | 0 | if (!aTrackTypes) { |
688 | 0 | MOZ_ASSERT(false); |
689 | 0 | LOG(LogLevel::Error, ("No TrackTypes")); |
690 | 0 | return nullptr; |
691 | 0 | } |
692 | 0 | #ifdef MOZ_WEBM_ENCODER |
693 | 0 | else if (MediaEncoder::IsWebMEncoderEnabled() && |
694 | 0 | (aMIMEType.EqualsLiteral(VIDEO_WEBM) || |
695 | 0 | (aTrackTypes & ContainerWriter::CREATE_VIDEO_TRACK))) { |
696 | 0 | if (aTrackTypes & ContainerWriter::CREATE_AUDIO_TRACK && |
697 | 0 | MediaDecoder::IsOpusEnabled()) { |
698 | 0 | audioEncoder = MakeAndAddRef<OpusTrackEncoder>(aTrackRate); |
699 | 0 | NS_ENSURE_TRUE(audioEncoder, nullptr); |
700 | 0 | } |
701 | 0 | if (Preferences::GetBool("media.recorder.video.frame_drops", true)) { |
702 | 0 | videoEncoder = MakeAndAddRef<VP8TrackEncoder>(aTrackRate, FrameDroppingMode::ALLOW); |
703 | 0 | } else { |
704 | 0 | videoEncoder = MakeAndAddRef<VP8TrackEncoder>(aTrackRate, FrameDroppingMode::DISALLOW); |
705 | 0 | } |
706 | 0 | writer = MakeUnique<WebMWriter>(aTrackTypes); |
707 | 0 | NS_ENSURE_TRUE(writer, nullptr); |
708 | 0 | NS_ENSURE_TRUE(videoEncoder, nullptr); |
709 | 0 | mimeType = NS_LITERAL_STRING(VIDEO_WEBM); |
710 | 0 | } |
711 | 0 | #endif //MOZ_WEBM_ENCODER |
712 | 0 | else if (MediaDecoder::IsOggEnabled() && MediaDecoder::IsOpusEnabled() && |
713 | 0 | (aMIMEType.EqualsLiteral(AUDIO_OGG) || |
714 | 0 | (aTrackTypes & ContainerWriter::CREATE_AUDIO_TRACK))) { |
715 | 0 | writer = MakeUnique<OggWriter>(); |
716 | 0 | audioEncoder = MakeAndAddRef<OpusTrackEncoder>(aTrackRate); |
717 | 0 | NS_ENSURE_TRUE(writer, nullptr); |
718 | 0 | NS_ENSURE_TRUE(audioEncoder, nullptr); |
719 | 0 | mimeType = NS_LITERAL_STRING(AUDIO_OGG); |
720 | 0 | } |
721 | 0 | else { |
722 | 0 | LOG(LogLevel::Error, ("Can not find any encoder to record this media stream")); |
723 | 0 | return nullptr; |
724 | 0 | } |
725 | 0 |
|
726 | 0 | LOG(LogLevel::Info, ("Create encoder result:a[%p](%u bps) v[%p](%u bps) w[%p] mimeType = %s.", |
727 | 0 | audioEncoder.get(), aAudioBitrate, |
728 | 0 | videoEncoder.get(), aVideoBitrate, |
729 | 0 | writer.get(), NS_ConvertUTF16toUTF8(mimeType).get())); |
730 | 0 |
|
731 | 0 | if (audioEncoder) { |
732 | 0 | audioEncoder->SetWorkerThread(aEncoderThread); |
733 | 0 | if (aAudioBitrate != 0) { |
734 | 0 | audioEncoder->SetBitrate(aAudioBitrate); |
735 | 0 | } |
736 | 0 | } |
737 | 0 | if (videoEncoder) { |
738 | 0 | videoEncoder->SetWorkerThread(aEncoderThread); |
739 | 0 | if (aVideoBitrate != 0) { |
740 | 0 | videoEncoder->SetBitrate(aVideoBitrate); |
741 | 0 | } |
742 | 0 | } |
743 | 0 | return MakeAndAddRef<MediaEncoder>(aEncoderThread, |
744 | 0 | std::move(writer), |
745 | 0 | audioEncoder, |
746 | 0 | videoEncoder, |
747 | 0 | mimeType); |
748 | 0 | } |
749 | | |
750 | | nsresult |
751 | | MediaEncoder::GetEncodedMetadata(nsTArray<nsTArray<uint8_t>>* aOutputBufs, |
752 | | nsAString& aMIMEType) |
753 | 0 | { |
754 | 0 | AUTO_PROFILER_LABEL("MediaEncoder::GetEncodedMetadata", OTHER); |
755 | 0 |
|
756 | 0 | MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn()); |
757 | 0 |
|
758 | 0 | if (mShutdown) { |
759 | 0 | MOZ_ASSERT(false); |
760 | 0 | return NS_ERROR_FAILURE; |
761 | 0 | } |
762 | 0 |
|
763 | 0 | if (!mInitialized) { |
764 | 0 | MOZ_ASSERT(false); |
765 | 0 | return NS_ERROR_FAILURE; |
766 | 0 | } |
767 | 0 |
|
768 | 0 | if (mMetadataEncoded) { |
769 | 0 | MOZ_ASSERT(false); |
770 | 0 | return NS_ERROR_FAILURE; |
771 | 0 | } |
772 | 0 |
|
773 | 0 | aMIMEType = mMIMEType; |
774 | 0 |
|
775 | 0 | LOG(LogLevel::Verbose, ("GetEncodedMetadata TimeStamp = %f", GetEncodeTimeStamp())); |
776 | 0 |
|
777 | 0 | nsresult rv; |
778 | 0 |
|
779 | 0 | if (mAudioEncoder) { |
780 | 0 | if (!mAudioEncoder->IsInitialized()) { |
781 | 0 | LOG(LogLevel::Error, ("GetEncodedMetadata Audio encoder not initialized")); |
782 | 0 | MOZ_ASSERT(false); |
783 | 0 | return NS_ERROR_FAILURE; |
784 | 0 | } |
785 | 0 | rv = CopyMetadataToMuxer(mAudioEncoder); |
786 | 0 | if (NS_FAILED(rv)) { |
787 | 0 | LOG(LogLevel::Error, ("Failed to Set Audio Metadata")); |
788 | 0 | SetError(); |
789 | 0 | return rv; |
790 | 0 | } |
791 | 0 | } |
792 | 0 | if (mVideoEncoder) { |
793 | 0 | if (!mVideoEncoder->IsInitialized()) { |
794 | 0 | LOG(LogLevel::Error, ("GetEncodedMetadata Video encoder not initialized")); |
795 | 0 | MOZ_ASSERT(false); |
796 | 0 | return NS_ERROR_FAILURE; |
797 | 0 | } |
798 | 0 | rv = CopyMetadataToMuxer(mVideoEncoder.get()); |
799 | 0 | if (NS_FAILED(rv)) { |
800 | 0 | LOG(LogLevel::Error, ("Failed to Set Video Metadata")); |
801 | 0 | SetError(); |
802 | 0 | return rv; |
803 | 0 | } |
804 | 0 | } |
805 | 0 |
|
806 | 0 | rv = mWriter->GetContainerData(aOutputBufs, |
807 | 0 | ContainerWriter::GET_HEADER); |
808 | 0 | if (NS_FAILED(rv)) { |
809 | 0 | LOG(LogLevel::Error,("Writer fail to generate header!")); |
810 | 0 | SetError(); |
811 | 0 | return rv; |
812 | 0 | } |
813 | 0 | LOG(LogLevel::Verbose, ("Finish GetEncodedMetadata TimeStamp = %f", GetEncodeTimeStamp())); |
814 | 0 | mMetadataEncoded = true; |
815 | 0 |
|
816 | 0 | return NS_OK; |
817 | 0 | } |
818 | | |
819 | | nsresult |
820 | | MediaEncoder::GetEncodedData(nsTArray<nsTArray<uint8_t> >* aOutputBufs) |
821 | 0 | { |
822 | 0 | AUTO_PROFILER_LABEL("MediaEncoder::GetEncodedData", OTHER); |
823 | 0 |
|
824 | 0 | MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn()); |
825 | 0 |
|
826 | 0 | if (!mMetadataEncoded) { |
827 | 0 | MOZ_ASSERT(false); |
828 | 0 | return NS_ERROR_FAILURE; |
829 | 0 | } |
830 | 0 |
|
831 | 0 | nsresult rv; |
832 | 0 | LOG(LogLevel::Verbose, ("GetEncodedData TimeStamp = %f", GetEncodeTimeStamp())); |
833 | 0 | EncodedFrameContainer encodedData; |
834 | 0 |
|
835 | 0 | if (mVideoEncoder) { |
836 | 0 | // We're most likely to actually wait for a video frame, so do that first |
837 | 0 | // to minimize capture offset/lipsync issues. |
838 | 0 | rv = WriteEncodedDataToMuxer(mVideoEncoder); |
839 | 0 | LOG(LogLevel::Verbose, ("Video encoded TimeStamp = %f", GetEncodeTimeStamp())); |
840 | 0 | if (NS_FAILED(rv)) { |
841 | 0 | LOG(LogLevel::Warning, ("Failed to write encoded video data to muxer")); |
842 | 0 | return rv; |
843 | 0 | } |
844 | 0 | } |
845 | 0 |
|
846 | 0 | if (mAudioEncoder) { |
847 | 0 | rv = WriteEncodedDataToMuxer(mAudioEncoder); |
848 | 0 | LOG(LogLevel::Verbose, ("Audio encoded TimeStamp = %f", GetEncodeTimeStamp())); |
849 | 0 | if (NS_FAILED(rv)) { |
850 | 0 | LOG(LogLevel::Warning, ("Failed to write encoded audio data to muxer")); |
851 | 0 | return rv; |
852 | 0 | } |
853 | 0 | } |
854 | 0 |
|
855 | 0 | // In audio only or video only case, let unavailable track's flag to be true. |
856 | 0 | bool isAudioCompleted = !mAudioEncoder || mAudioEncoder->IsEncodingComplete(); |
857 | 0 | bool isVideoCompleted = !mVideoEncoder || mVideoEncoder->IsEncodingComplete(); |
858 | 0 | rv = mWriter->GetContainerData(aOutputBufs, |
859 | 0 | isAudioCompleted && isVideoCompleted ? |
860 | 0 | ContainerWriter::FLUSH_NEEDED : 0); |
861 | 0 | if (mWriter->IsWritingComplete()) { |
862 | 0 | mCompleted = true; |
863 | 0 | Shutdown(); |
864 | 0 | } |
865 | 0 |
|
866 | 0 | LOG(LogLevel::Verbose, ("END GetEncodedData TimeStamp=%f " |
867 | 0 | "mCompleted=%d, aComplete=%d, vComplete=%d", |
868 | 0 | GetEncodeTimeStamp(), mCompleted, isAudioCompleted, isVideoCompleted)); |
869 | 0 |
|
870 | 0 | return rv; |
871 | 0 | } |
872 | | |
873 | | void |
874 | | MediaEncoder::Shutdown() |
875 | 0 | { |
876 | 0 | MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn()); |
877 | 0 | if (mShutdown) { |
878 | 0 | return; |
879 | 0 | } |
880 | 0 | mShutdown = true; |
881 | 0 |
|
882 | 0 | LOG(LogLevel::Info, ("MediaEncoder has been shut down.")); |
883 | 0 | if (mAudioEncoder) { |
884 | 0 | mAudioEncoder->UnregisterListener(mEncoderListener); |
885 | 0 | } |
886 | 0 | if (mAudioListener) { |
887 | 0 | mAudioListener->NotifyShutdown(); |
888 | 0 | } |
889 | 0 | if (mVideoEncoder) { |
890 | 0 | mVideoEncoder->UnregisterListener(mEncoderListener); |
891 | 0 | } |
892 | 0 | if (mVideoListener) { |
893 | 0 | mVideoListener->NotifyShutdown(); |
894 | 0 | } |
895 | 0 | mEncoderListener->Forget(); |
896 | 0 |
|
897 | 0 | if (mCanceled) { |
898 | 0 | // Shutting down after being canceled. We cannot use the encoder thread. |
899 | 0 | return; |
900 | 0 | } |
901 | 0 | |
902 | 0 | auto listeners(mListeners); |
903 | 0 | for (auto& l : listeners) { |
904 | 0 | // We dispatch here since this method is typically called from |
905 | 0 | // a DataAvailable() handler. |
906 | 0 | nsresult rv = |
907 | 0 | mEncoderThread->Dispatch( |
908 | 0 | NewRunnableMethod("mozilla::MediaEncoderListener::Shutdown", |
909 | 0 | l, &MediaEncoderListener::Shutdown)); |
910 | 0 | MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); |
911 | 0 | Unused << rv; |
912 | 0 | } |
913 | 0 | } |
914 | | |
915 | | nsresult |
916 | | MediaEncoder::WriteEncodedDataToMuxer(TrackEncoder *aTrackEncoder) |
917 | 0 | { |
918 | 0 | AUTO_PROFILER_LABEL("MediaEncoder::WriteEncodedDataToMuxer", OTHER); |
919 | 0 |
|
920 | 0 | MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn()); |
921 | 0 |
|
922 | 0 | if (!aTrackEncoder) { |
923 | 0 | NS_ERROR("No track encoder to get data from"); |
924 | 0 | return NS_ERROR_FAILURE; |
925 | 0 | } |
926 | 0 |
|
927 | 0 | if (aTrackEncoder->IsEncodingComplete()) { |
928 | 0 | return NS_OK; |
929 | 0 | } |
930 | 0 | |
931 | 0 | EncodedFrameContainer encodedData; |
932 | 0 | nsresult rv = aTrackEncoder->GetEncodedTrack(encodedData); |
933 | 0 | if (NS_FAILED(rv)) { |
934 | 0 | // Encoding might be canceled. |
935 | 0 | LOG(LogLevel::Error, ("Failed to get encoded data from encoder.")); |
936 | 0 | SetError(); |
937 | 0 | return rv; |
938 | 0 | } |
939 | 0 | rv = mWriter->WriteEncodedTrack(encodedData, |
940 | 0 | aTrackEncoder->IsEncodingComplete() ? |
941 | 0 | ContainerWriter::END_OF_STREAM : 0); |
942 | 0 | if (NS_FAILED(rv)) { |
943 | 0 | LOG(LogLevel::Error, ("Failed to write encoded track to the media container.")); |
944 | 0 | SetError(); |
945 | 0 | } |
946 | 0 | return rv; |
947 | 0 | } |
948 | | |
949 | | nsresult |
950 | | MediaEncoder::CopyMetadataToMuxer(TrackEncoder *aTrackEncoder) |
951 | 0 | { |
952 | 0 | AUTO_PROFILER_LABEL("MediaEncoder::CopyMetadataToMuxer", OTHER); |
953 | 0 |
|
954 | 0 | MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn()); |
955 | 0 |
|
956 | 0 | if (!aTrackEncoder) { |
957 | 0 | NS_ERROR("No track encoder to get metadata from"); |
958 | 0 | return NS_ERROR_FAILURE; |
959 | 0 | } |
960 | 0 |
|
961 | 0 | RefPtr<TrackMetadataBase> meta = aTrackEncoder->GetMetadata(); |
962 | 0 | if (meta == nullptr) { |
963 | 0 | LOG(LogLevel::Error, ("metadata == null")); |
964 | 0 | SetError(); |
965 | 0 | return NS_ERROR_ABORT; |
966 | 0 | } |
967 | 0 |
|
968 | 0 | nsresult rv = mWriter->SetMetadata(meta); |
969 | 0 | if (NS_FAILED(rv)) { |
970 | 0 | LOG(LogLevel::Error, ("SetMetadata failed")); |
971 | 0 | SetError(); |
972 | 0 | } |
973 | 0 | return rv; |
974 | 0 | } |
975 | | |
976 | | bool |
977 | | MediaEncoder::IsShutdown() |
978 | 0 | { |
979 | 0 | MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn()); |
980 | 0 | return mShutdown; |
981 | 0 | } |
982 | | |
983 | | void |
984 | | MediaEncoder::Cancel() |
985 | 0 | { |
986 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
987 | 0 |
|
988 | 0 | RefPtr<MediaEncoder> self = this; |
989 | 0 | nsresult rv = |
990 | 0 | mEncoderThread->Dispatch(NewRunnableFrom([self]() mutable { |
991 | 0 | self->mCanceled = true; |
992 | 0 |
|
993 | 0 | if (self->mAudioEncoder) { |
994 | 0 | self->mAudioEncoder->Cancel(); |
995 | 0 | } |
996 | 0 | if (self->mVideoEncoder) { |
997 | 0 | self->mVideoEncoder->Cancel(); |
998 | 0 | } |
999 | 0 | self->Shutdown(); |
1000 | 0 | return NS_OK; |
1001 | 0 | })); |
1002 | 0 | MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); |
1003 | 0 | Unused << rv; |
1004 | 0 | } |
1005 | | |
1006 | | bool |
1007 | | MediaEncoder::HasError() |
1008 | 0 | { |
1009 | 0 | MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn()); |
1010 | 0 | return mError; |
1011 | 0 | } |
1012 | | |
1013 | | void |
1014 | | MediaEncoder::SetError() |
1015 | 0 | { |
1016 | 0 | MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn()); |
1017 | 0 |
|
1018 | 0 | if (mError) { |
1019 | 0 | return; |
1020 | 0 | } |
1021 | 0 | |
1022 | 0 | mError = true; |
1023 | 0 | auto listeners(mListeners); |
1024 | 0 | for (auto& l : listeners) { |
1025 | 0 | l->Error(); |
1026 | 0 | } |
1027 | 0 | } |
1028 | | |
1029 | | void |
1030 | | MediaEncoder::Stop() |
1031 | 0 | { |
1032 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
1033 | 0 |
|
1034 | 0 | if (mAudioNode) { |
1035 | 0 | mAudioNode->GetStream()->RemoveTrackListener(mAudioListener, AudioNodeStream::AUDIO_TRACK); |
1036 | 0 | if (mInputPort) { |
1037 | 0 | mInputPort->Destroy(); |
1038 | 0 | mInputPort = nullptr; |
1039 | 0 | } |
1040 | 0 | if (mPipeStream) { |
1041 | 0 | mPipeStream->RemoveTrackListener(mAudioListener, AudioNodeStream::AUDIO_TRACK); |
1042 | 0 | mPipeStream->Destroy(); |
1043 | 0 | mPipeStream = nullptr; |
1044 | 0 | } |
1045 | 0 | mAudioNode = nullptr; |
1046 | 0 | } |
1047 | 0 |
|
1048 | 0 | if (mAudioTrack) { |
1049 | 0 | RemoveMediaStreamTrack(mAudioTrack); |
1050 | 0 | } |
1051 | 0 |
|
1052 | 0 | if (mVideoTrack) { |
1053 | 0 | RemoveMediaStreamTrack(mVideoTrack); |
1054 | 0 | } |
1055 | 0 | } |
1056 | | |
1057 | | #ifdef MOZ_WEBM_ENCODER |
1058 | | bool |
1059 | | MediaEncoder::IsWebMEncoderEnabled() |
1060 | 0 | { |
1061 | 0 | return StaticPrefs::MediaEncoderWebMEnabled(); |
1062 | 0 | } |
1063 | | #endif |
1064 | | |
1065 | | void |
1066 | | MediaEncoder::NotifyInitialized() |
1067 | 0 | { |
1068 | 0 | MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn()); |
1069 | 0 |
|
1070 | 0 | if (mInitialized) { |
1071 | 0 | // This could happen if an encoder re-inits due to a resolution change. |
1072 | 0 | return; |
1073 | 0 | } |
1074 | 0 | |
1075 | 0 | if (mAudioEncoder && !mAudioEncoder->IsInitialized()) { |
1076 | 0 | return; |
1077 | 0 | } |
1078 | 0 | |
1079 | 0 | if (mVideoEncoder && !mVideoEncoder->IsInitialized()) { |
1080 | 0 | return; |
1081 | 0 | } |
1082 | 0 | |
1083 | 0 | mInitialized = true; |
1084 | 0 |
|
1085 | 0 | auto listeners(mListeners); |
1086 | 0 | for (auto& l : listeners) { |
1087 | 0 | l->Initialized(); |
1088 | 0 | } |
1089 | 0 | } |
1090 | | |
1091 | | void |
1092 | | MediaEncoder::NotifyDataAvailable() |
1093 | 0 | { |
1094 | 0 | MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn()); |
1095 | 0 |
|
1096 | 0 | if (!mInitialized) { |
1097 | 0 | return; |
1098 | 0 | } |
1099 | 0 | |
1100 | 0 | auto listeners(mListeners); |
1101 | 0 | for (auto& l : listeners) { |
1102 | 0 | l->DataAvailable(); |
1103 | 0 | } |
1104 | 0 | } |
1105 | | |
1106 | | void |
1107 | | MediaEncoder::RegisterListener(MediaEncoderListener* aListener) |
1108 | 0 | { |
1109 | 0 | MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn()); |
1110 | 0 | MOZ_ASSERT(!mListeners.Contains(aListener)); |
1111 | 0 | mListeners.AppendElement(aListener); |
1112 | 0 | } |
1113 | | |
1114 | | bool |
1115 | | MediaEncoder::UnregisterListener(MediaEncoderListener* aListener) |
1116 | 0 | { |
1117 | 0 | MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn()); |
1118 | 0 | return mListeners.RemoveElement(aListener); |
1119 | 0 | } |
1120 | | |
1121 | | /* |
1122 | | * SizeOfExcludingThis measures memory being used by the Media Encoder. |
1123 | | * Currently it measures the size of the Encoder buffer and memory occupied |
1124 | | * by mAudioEncoder and mVideoEncoder. |
1125 | | */ |
1126 | | size_t |
1127 | | MediaEncoder::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) |
1128 | 0 | { |
1129 | 0 | MOZ_ASSERT(mEncoderThread->IsCurrentThreadIn()); |
1130 | 0 |
|
1131 | 0 | size_t size = 0; |
1132 | 0 | if (mAudioEncoder) { |
1133 | 0 | size += mAudioEncoder->SizeOfExcludingThis(aMallocSizeOf); |
1134 | 0 | } |
1135 | 0 | if (mVideoEncoder) { |
1136 | 0 | size += mVideoEncoder->SizeOfExcludingThis(aMallocSizeOf); |
1137 | 0 | } |
1138 | 0 | return size; |
1139 | 0 | } |
1140 | | |
1141 | | void |
1142 | | MediaEncoder::SetVideoKeyFrameInterval(int32_t aVideoKeyFrameInterval) |
1143 | 0 | { |
1144 | 0 | if (!mVideoEncoder) { |
1145 | 0 | return; |
1146 | 0 | } |
1147 | 0 | |
1148 | 0 | MOZ_ASSERT(mEncoderThread); |
1149 | 0 | nsresult rv = |
1150 | 0 | mEncoderThread->Dispatch( |
1151 | 0 | NewRunnableMethod<int32_t>( |
1152 | 0 | "mozilla::VideoTrackEncoder::SetKeyFrameInterval", |
1153 | 0 | mVideoEncoder, &VideoTrackEncoder::SetKeyFrameInterval, |
1154 | 0 | aVideoKeyFrameInterval)); |
1155 | 0 | MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); |
1156 | 0 | Unused << rv; |
1157 | 0 | } |
1158 | | |
1159 | | } // namespace mozilla |