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/. */
6 #include "MediaEncoder.h"
9 #include "AudioNodeEngine.h"
10 #include "AudioNodeTrack.h"
11 #include "DriftCompensation.h"
12 #include "MediaDecoder.h"
13 #include "MediaTrackGraph.h"
14 #include "MediaTrackListener.h"
15 #include "mozilla/dom/AudioNode.h"
16 #include "mozilla/dom/AudioStreamTrack.h"
17 #include "mozilla/dom/Blob.h"
18 #include "mozilla/dom/BlobImpl.h"
19 #include "mozilla/dom/MediaStreamTrack.h"
20 #include "mozilla/dom/MutableBlobStorage.h"
21 #include "mozilla/dom/VideoStreamTrack.h"
22 #include "mozilla/gfx/Point.h" // IntSize
23 #include "mozilla/Logging.h"
24 #include "mozilla/Preferences.h"
25 #include "mozilla/ProfilerLabels.h"
26 #include "mozilla/StaticPrefs_media.h"
27 #include "mozilla/StaticPtr.h"
28 #include "mozilla/TaskQueue.h"
29 #include "mozilla/Unused.h"
31 #include "nsMimeTypes.h"
32 #include "nsThreadUtils.h"
33 #include "OggWriter.h"
34 #include "OpusTrackEncoder.h"
35 #include "TimeUnits.h"
38 #include "VP8TrackEncoder.h"
39 #include "WebMWriter.h"
41 mozilla::LazyLogModule
gMediaEncoderLog("MediaEncoder");
42 #define LOG(type, msg) MOZ_LOG(gMediaEncoderLog, type, msg)
47 using namespace media
;
50 class BlobStorer
: public MutableBlobStorageCallback
{
51 MozPromiseHolder
<MediaEncoder::BlobPromise
> mHolder
;
53 virtual ~BlobStorer() = default;
56 BlobStorer() = default;
58 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(BlobStorer
, override
)
60 void BlobStoreCompleted(MutableBlobStorage
*, BlobImpl
* aBlobImpl
,
61 nsresult aRv
) override
{
62 MOZ_ASSERT(NS_IsMainThread());
64 mHolder
.Reject(aRv
, __func__
);
68 mHolder
.Resolve(aBlobImpl
, __func__
);
71 RefPtr
<MediaEncoder::BlobPromise
> Promise() {
72 return mHolder
.Ensure(__func__
);
77 class MediaEncoder::AudioTrackListener
: public DirectMediaTrackListener
{
79 AudioTrackListener(RefPtr
<DriftCompensator
> aDriftCompensator
,
80 RefPtr
<MediaEncoder
> aMediaEncoder
)
81 : mDirectConnected(false),
84 mDriftCompensator(std::move(aDriftCompensator
)),
85 mMediaEncoder(std::move(aMediaEncoder
)),
86 mEncoderThread(mMediaEncoder
->mEncoderThread
),
87 mShutdownPromise(mShutdownHolder
.Ensure(__func__
)) {
88 MOZ_ASSERT(mMediaEncoder
);
89 MOZ_ASSERT(mMediaEncoder
->mAudioEncoder
);
90 MOZ_ASSERT(mEncoderThread
);
93 void NotifyDirectListenerInstalled(InstallationResult aResult
) override
{
94 if (aResult
== InstallationResult::SUCCESS
) {
95 LOG(LogLevel::Info
, ("Audio track direct listener installed"));
96 mDirectConnected
= true;
98 LOG(LogLevel::Info
, ("Audio track failed to install direct listener"));
99 MOZ_ASSERT(!mDirectConnected
);
103 void NotifyDirectListenerUninstalled() override
{
104 mDirectConnected
= false;
107 mMediaEncoder
= nullptr;
108 mEncoderThread
= nullptr;
112 void NotifyQueuedChanges(MediaTrackGraph
* aGraph
, TrackTime aTrackOffset
,
113 const MediaSegment
& aQueuedMedia
) override
{
114 TRACE_COMMENT("MediaEncoder::NotifyQueuedChanges", "%p",
115 mMediaEncoder
.get());
116 MOZ_ASSERT(mMediaEncoder
);
117 MOZ_ASSERT(mEncoderThread
);
120 mDriftCompensator
->NotifyAudioStart(TimeStamp::Now());
124 mDriftCompensator
->NotifyAudio(aQueuedMedia
.GetDuration());
126 const AudioSegment
& audio
= static_cast<const AudioSegment
&>(aQueuedMedia
);
129 copy
.AppendSlice(audio
, 0, audio
.GetDuration());
131 nsresult rv
= mEncoderThread
->Dispatch(NS_NewRunnableFunction(
132 "mozilla::AudioTrackEncoder::AppendAudioSegment",
133 [encoder
= mMediaEncoder
, copy
= std::move(copy
)]() mutable {
134 encoder
->mAudioEncoder
->AppendAudioSegment(std::move(copy
));
136 MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv
));
140 void NotifyEnded(MediaTrackGraph
* aGraph
) override
{
141 MOZ_ASSERT(mMediaEncoder
);
142 MOZ_ASSERT(mMediaEncoder
->mAudioEncoder
);
143 MOZ_ASSERT(mEncoderThread
);
145 nsresult rv
= mEncoderThread
->Dispatch(
146 NS_NewRunnableFunction("mozilla::AudioTrackEncoder::NotifyEndOfStream",
147 [encoder
= mMediaEncoder
] {
148 encoder
->mAudioEncoder
->NotifyEndOfStream();
150 MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv
));
154 void NotifyRemoved(MediaTrackGraph
* aGraph
) override
{
155 nsresult rv
= mEncoderThread
->Dispatch(
156 NS_NewRunnableFunction("mozilla::AudioTrackEncoder::NotifyEndOfStream",
157 [encoder
= mMediaEncoder
] {
158 encoder
->mAudioEncoder
->NotifyEndOfStream();
160 MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv
));
165 if (!mDirectConnected
) {
166 mMediaEncoder
= nullptr;
167 mEncoderThread
= nullptr;
170 mShutdownHolder
.Resolve(true, __func__
);
173 const RefPtr
<GenericNonExclusivePromise
>& OnShutdown() const {
174 return mShutdownPromise
;
178 bool mDirectConnected
;
181 const RefPtr
<DriftCompensator
> mDriftCompensator
;
182 RefPtr
<MediaEncoder
> mMediaEncoder
;
183 RefPtr
<TaskQueue
> mEncoderThread
;
184 MozPromiseHolder
<GenericNonExclusivePromise
> mShutdownHolder
;
185 const RefPtr
<GenericNonExclusivePromise
> mShutdownPromise
;
188 class MediaEncoder::VideoTrackListener
: public DirectMediaTrackListener
{
190 explicit VideoTrackListener(RefPtr
<MediaEncoder
> aMediaEncoder
)
191 : mDirectConnected(false),
194 mPendingAdvanceCurrentTime(false),
195 mMediaEncoder(std::move(aMediaEncoder
)),
196 mEncoderThread(mMediaEncoder
->mEncoderThread
),
197 mShutdownPromise(mShutdownHolder
.Ensure(__func__
)) {
198 MOZ_ASSERT(mMediaEncoder
);
199 MOZ_ASSERT(mEncoderThread
);
202 void NotifyDirectListenerInstalled(InstallationResult aResult
) override
{
203 if (aResult
== InstallationResult::SUCCESS
) {
204 LOG(LogLevel::Info
, ("Video track direct listener installed"));
205 mDirectConnected
= true;
207 LOG(LogLevel::Info
, ("Video track failed to install direct listener"));
208 MOZ_ASSERT(!mDirectConnected
);
213 void NotifyDirectListenerUninstalled() override
{
214 mDirectConnected
= false;
217 mMediaEncoder
= nullptr;
218 mEncoderThread
= nullptr;
222 void NotifyQueuedChanges(MediaTrackGraph
* aGraph
, TrackTime aTrackOffset
,
223 const MediaSegment
& aQueuedMedia
) override
{
224 TRACE_COMMENT("MediaEncoder::NotifyQueuedChanges", "%p",
225 mMediaEncoder
.get());
226 MOZ_ASSERT(mMediaEncoder
);
227 MOZ_ASSERT(mMediaEncoder
->mVideoEncoder
);
228 MOZ_ASSERT(mEncoderThread
);
230 mCurrentTime
= TimeStamp::Now();
232 nsresult rv
= mEncoderThread
->Dispatch(
233 NS_NewRunnableFunction("mozilla::VideoTrackEncoder::SetStartOffset",
234 [encoder
= mMediaEncoder
, now
= mCurrentTime
] {
235 encoder
->mVideoEncoder
->SetStartOffset(now
);
237 MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv
));
242 if (!mPendingAdvanceCurrentTime
) {
243 mPendingAdvanceCurrentTime
= true;
244 nsresult rv
= mEncoderThread
->Dispatch(NS_NewRunnableFunction(
245 "mozilla::VideoTrackEncoder::AdvanceCurrentTime",
246 [encoder
= mMediaEncoder
, now
= mCurrentTime
] {
247 encoder
->mVideoListener
->mPendingAdvanceCurrentTime
= false;
248 encoder
->mVideoEncoder
->AdvanceCurrentTime(now
);
250 MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv
));
255 void NotifyRealtimeTrackData(MediaTrackGraph
* aGraph
, TrackTime aTrackOffset
,
256 const MediaSegment
& aMedia
) override
{
257 TRACE_COMMENT("MediaEncoder::NotifyRealtimeTrackData", "%p",
258 mMediaEncoder
.get());
259 MOZ_ASSERT(mMediaEncoder
);
260 MOZ_ASSERT(mMediaEncoder
->mVideoEncoder
);
261 MOZ_ASSERT(mEncoderThread
);
262 MOZ_ASSERT(aMedia
.GetType() == MediaSegment::VIDEO
);
264 const VideoSegment
& video
= static_cast<const VideoSegment
&>(aMedia
);
266 for (VideoSegment::ConstChunkIterator
iter(video
); !iter
.IsEnded();
268 copy
.AppendFrame(do_AddRef(iter
->mFrame
.GetImage()),
269 iter
->mFrame
.GetIntrinsicSize(),
270 iter
->mFrame
.GetPrincipalHandle(),
271 iter
->mFrame
.GetForceBlack(), iter
->mTimeStamp
);
274 nsresult rv
= mEncoderThread
->Dispatch(NS_NewRunnableFunction(
275 "mozilla::VideoTrackEncoder::AppendVideoSegment",
276 [encoder
= mMediaEncoder
, copy
= std::move(copy
)]() mutable {
277 encoder
->mVideoEncoder
->AppendVideoSegment(std::move(copy
));
279 MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv
));
283 void NotifyEnabledStateChanged(MediaTrackGraph
* aGraph
,
284 bool aEnabled
) override
{
285 MOZ_ASSERT(mMediaEncoder
);
286 MOZ_ASSERT(mMediaEncoder
->mVideoEncoder
);
287 MOZ_ASSERT(mEncoderThread
);
291 rv
= mEncoderThread
->Dispatch(NS_NewRunnableFunction(
292 "mozilla::VideoTrackEncoder::Enable",
293 [encoder
= mMediaEncoder
, now
= TimeStamp::Now()] {
294 encoder
->mVideoEncoder
->Enable(now
);
297 rv
= mEncoderThread
->Dispatch(NS_NewRunnableFunction(
298 "mozilla::VideoTrackEncoder::Disable",
299 [encoder
= mMediaEncoder
, now
= TimeStamp::Now()] {
300 encoder
->mVideoEncoder
->Disable(now
);
303 MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv
));
307 void NotifyEnded(MediaTrackGraph
* aGraph
) override
{
308 MOZ_ASSERT(mMediaEncoder
);
309 MOZ_ASSERT(mMediaEncoder
->mVideoEncoder
);
310 MOZ_ASSERT(mEncoderThread
);
312 nsresult rv
= mEncoderThread
->Dispatch(NS_NewRunnableFunction(
313 "mozilla::VideoTrackEncoder::NotifyEndOfStream",
314 [encoder
= mMediaEncoder
, now
= mCurrentTime
] {
316 encoder
->mVideoEncoder
->AdvanceCurrentTime(now
);
318 encoder
->mVideoEncoder
->NotifyEndOfStream();
320 MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv
));
324 void NotifyRemoved(MediaTrackGraph
* aGraph
) override
{
325 nsresult rv
= mEncoderThread
->Dispatch(NS_NewRunnableFunction(
326 "mozilla::VideoTrackEncoder::NotifyEndOfStream",
327 [encoder
= mMediaEncoder
, now
= mCurrentTime
] {
329 encoder
->mVideoEncoder
->AdvanceCurrentTime(now
);
331 encoder
->mVideoEncoder
->NotifyEndOfStream();
333 MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv
));
338 if (!mDirectConnected
) {
339 mMediaEncoder
= nullptr;
340 mEncoderThread
= nullptr;
343 mShutdownHolder
.Resolve(true, __func__
);
346 const RefPtr
<GenericNonExclusivePromise
>& OnShutdown() const {
347 return mShutdownPromise
;
351 bool mDirectConnected
;
354 TimeStamp mCurrentTime
;
355 Atomic
<bool> mPendingAdvanceCurrentTime
;
356 RefPtr
<MediaEncoder
> mMediaEncoder
;
357 RefPtr
<TaskQueue
> mEncoderThread
;
358 MozPromiseHolder
<GenericNonExclusivePromise
> mShutdownHolder
;
359 const RefPtr
<GenericNonExclusivePromise
> mShutdownPromise
;
362 class MediaEncoder::EncoderListener
: public TrackEncoderListener
{
364 EncoderListener(TaskQueue
* aEncoderThread
, MediaEncoder
* aEncoder
)
365 : mEncoderThread(aEncoderThread
), mEncoder(aEncoder
) {}
368 MOZ_ASSERT(mEncoderThread
->IsCurrentThreadIn());
372 void Initialized(TrackEncoder
* aTrackEncoder
) override
{
373 MOZ_ASSERT(mEncoderThread
->IsCurrentThreadIn());
374 MOZ_ASSERT(aTrackEncoder
->IsInitialized());
380 mEncoder
->UpdateInitialized();
383 void Started(TrackEncoder
* aTrackEncoder
) override
{
384 MOZ_ASSERT(mEncoderThread
->IsCurrentThreadIn());
385 MOZ_ASSERT(aTrackEncoder
->IsStarted());
391 mEncoder
->UpdateStarted();
394 void Error(TrackEncoder
* aTrackEncoder
) override
{
395 MOZ_ASSERT(mEncoderThread
->IsCurrentThreadIn());
401 mEncoder
->SetError();
405 RefPtr
<TaskQueue
> mEncoderThread
;
406 RefPtr
<MediaEncoder
> mEncoder
;
409 MediaEncoder::MediaEncoder(
410 RefPtr
<TaskQueue
> aEncoderThread
,
411 RefPtr
<DriftCompensator
> aDriftCompensator
,
412 UniquePtr
<ContainerWriter
> aWriter
,
413 UniquePtr
<AudioTrackEncoder
> aAudioEncoder
,
414 UniquePtr
<VideoTrackEncoder
> aVideoEncoder
,
415 UniquePtr
<MediaQueue
<EncodedFrame
>> aEncodedAudioQueue
,
416 UniquePtr
<MediaQueue
<EncodedFrame
>> aEncodedVideoQueue
,
417 TrackRate aTrackRate
, const nsAString
& aMimeType
, uint64_t aMaxMemory
,
418 TimeDuration aTimeslice
)
419 : mMainThread(GetMainThreadSerialEventTarget()),
420 mEncoderThread(std::move(aEncoderThread
)),
421 mEncodedAudioQueue(std::move(aEncodedAudioQueue
)),
422 mEncodedVideoQueue(std::move(aEncodedVideoQueue
)),
423 mMuxer(MakeUnique
<Muxer
>(std::move(aWriter
), *mEncodedAudioQueue
,
424 *mEncodedVideoQueue
)),
425 mAudioEncoder(std::move(aAudioEncoder
)),
426 mAudioListener(mAudioEncoder
? MakeAndAddRef
<AudioTrackListener
>(
427 std::move(aDriftCompensator
), this)
429 mVideoEncoder(std::move(aVideoEncoder
)),
430 mVideoListener(mVideoEncoder
? MakeAndAddRef
<VideoTrackListener
>(this)
432 mEncoderListener(MakeAndAddRef
<EncoderListener
>(mEncoderThread
, this)),
433 mMimeType(aMimeType
),
434 mMaxMemory(aMaxMemory
),
435 mTimeslice(aTimeslice
),
436 mStartTime(TimeStamp::Now()),
441 if (!mAudioEncoder
) {
442 mMuxedAudioEndTime
= TimeUnit::FromInfinity();
443 mEncodedAudioQueue
->Finish();
445 if (!mVideoEncoder
) {
446 mMuxedVideoEndTime
= TimeUnit::FromInfinity();
447 mEncodedVideoQueue
->Finish();
451 void MediaEncoder::RegisterListeners() {
453 mAudioPushListener
= mEncodedAudioQueue
->PushEvent().Connect(
454 mEncoderThread
, this, &MediaEncoder::OnEncodedAudioPushed
);
455 mAudioFinishListener
= mEncodedAudioQueue
->FinishEvent().Connect(
456 mEncoderThread
, this, &MediaEncoder::MaybeShutdown
);
457 MOZ_ALWAYS_SUCCEEDS(mEncoderThread
->Dispatch(NS_NewRunnableFunction(
458 "mozilla::AudioTrackEncoder::RegisterListener",
459 [self
= RefPtr
<MediaEncoder
>(this), this] {
460 mAudioEncoder
->RegisterListener(mEncoderListener
);
464 mVideoPushListener
= mEncodedVideoQueue
->PushEvent().Connect(
465 mEncoderThread
, this, &MediaEncoder::OnEncodedVideoPushed
);
466 mVideoFinishListener
= mEncodedVideoQueue
->FinishEvent().Connect(
467 mEncoderThread
, this, &MediaEncoder::MaybeShutdown
);
468 MOZ_ALWAYS_SUCCEEDS(mEncoderThread
->Dispatch(NS_NewRunnableFunction(
469 "mozilla::VideoTrackEncoder::RegisterListener",
470 [self
= RefPtr
<MediaEncoder
>(this), this] {
471 mVideoEncoder
->RegisterListener(mEncoderListener
);
476 MediaEncoder::~MediaEncoder() {
477 MOZ_ASSERT(!mAudioTrack
);
478 MOZ_ASSERT(!mVideoTrack
);
479 MOZ_ASSERT(!mAudioNode
);
480 MOZ_ASSERT(!mInputPort
);
481 MOZ_ASSERT(!mPipeTrack
);
484 void MediaEncoder::EnsureGraphTrackFrom(MediaTrack
* aTrack
) {
488 MOZ_DIAGNOSTIC_ASSERT(!aTrack
->IsDestroyed());
489 mGraphTrack
= MakeAndAddRef
<SharedDummyTrack
>(
490 aTrack
->Graph()->CreateSourceTrack(MediaSegment::VIDEO
));
493 void MediaEncoder::Suspend() {
494 mGraphTrack
->mTrack
->QueueControlMessageWithNoShutdown(
495 [self
= RefPtr
<MediaEncoder
>(this), this] {
496 TRACE("MediaEncoder::Suspend (graph)");
497 if (NS_FAILED(mEncoderThread
->Dispatch(
498 NS_NewRunnableFunction("MediaEncoder::Suspend (encoder)",
499 [self
, this, now
= TimeStamp::Now()] {
501 mAudioEncoder
->Suspend();
504 mVideoEncoder
->Suspend(now
);
507 // QueueControlMessageWithNoShutdown added an extra async step, and
508 // now `thread` has shut down.
514 void MediaEncoder::Resume() {
515 mGraphTrack
->mTrack
->QueueControlMessageWithNoShutdown(
516 [self
= RefPtr
<MediaEncoder
>(this), this] {
517 TRACE("MediaEncoder::Resume (graph)");
518 if (NS_FAILED(mEncoderThread
->Dispatch(
519 NS_NewRunnableFunction("MediaEncoder::Resume (encoder)",
520 [self
, this, now
= TimeStamp::Now()] {
522 mAudioEncoder
->Resume();
525 mVideoEncoder
->Resume(now
);
528 // QueueControlMessageWithNoShutdown added an extra async step, and
529 // now `thread` has shut down.
535 void MediaEncoder::ConnectAudioNode(AudioNode
* aNode
, uint32_t aOutput
) {
536 MOZ_ASSERT(NS_IsMainThread());
539 MOZ_ASSERT(false, "Only one audio node supported");
543 // Only AudioNodeTrack of kind EXTERNAL_OUTPUT stores output audio data in
544 // the track (see AudioNodeTrack::AdvanceOutputSegment()). That means
545 // forwarding input track in recorder session won't be able to copy data from
546 // the track of non-destination node. Create a pipe track in this case.
547 if (aNode
->NumberOfOutputs() > 0) {
548 AudioContext
* ctx
= aNode
->Context();
549 AudioNodeEngine
* engine
= new AudioNodeEngine(nullptr);
550 AudioNodeTrack::Flags flags
= AudioNodeTrack::EXTERNAL_OUTPUT
|
551 AudioNodeTrack::NEED_MAIN_THREAD_ENDED
;
552 mPipeTrack
= AudioNodeTrack::Create(ctx
, engine
, flags
, ctx
->Graph());
553 AudioNodeTrack
* ns
= aNode
->GetTrack();
555 mInputPort
= mPipeTrack
->AllocateInputPort(aNode
->GetTrack(), 0, aOutput
);
562 mPipeTrack
->AddListener(mAudioListener
);
563 EnsureGraphTrackFrom(mPipeTrack
);
565 mAudioNode
->GetTrack()->AddListener(mAudioListener
);
566 EnsureGraphTrackFrom(mAudioNode
->GetTrack());
570 void MediaEncoder::ConnectMediaStreamTrack(MediaStreamTrack
* aTrack
) {
571 MOZ_ASSERT(NS_IsMainThread());
573 if (aTrack
->Ended()) {
574 MOZ_ASSERT_UNREACHABLE("Cannot connect ended track");
578 EnsureGraphTrackFrom(aTrack
->GetTrack());
580 if (AudioStreamTrack
* audio
= aTrack
->AsAudioStreamTrack()) {
581 if (!mAudioEncoder
) {
582 // No audio encoder for this audio track. It could be disabled.
583 LOG(LogLevel::Warning
, ("Cannot connect to audio track - no encoder"));
587 MOZ_ASSERT(!mAudioTrack
, "Only one audio track supported.");
588 MOZ_ASSERT(mAudioListener
, "No audio listener for this audio track");
590 LOG(LogLevel::Info
, ("Connected to audio track %p", aTrack
));
593 audio
->AddListener(mAudioListener
);
594 } else if (VideoStreamTrack
* video
= aTrack
->AsVideoStreamTrack()) {
595 if (!mVideoEncoder
) {
596 // No video encoder for this video track. It could be disabled.
597 LOG(LogLevel::Warning
, ("Cannot connect to video track - no encoder"));
601 MOZ_ASSERT(!mVideoTrack
, "Only one video track supported.");
602 MOZ_ASSERT(mVideoListener
, "No video listener for this video track");
604 LOG(LogLevel::Info
, ("Connected to video track %p", aTrack
));
607 video
->AddDirectListener(mVideoListener
);
608 video
->AddListener(mVideoListener
);
610 MOZ_ASSERT(false, "Unknown track type");
614 void MediaEncoder::RemoveMediaStreamTrack(MediaStreamTrack
* aTrack
) {
620 if (AudioStreamTrack
* audio
= aTrack
->AsAudioStreamTrack()) {
621 if (audio
!= mAudioTrack
) {
622 MOZ_ASSERT(false, "Not connected to this audio track");
626 if (mAudioListener
) {
627 audio
->RemoveDirectListener(mAudioListener
);
628 audio
->RemoveListener(mAudioListener
);
630 mAudioTrack
= nullptr;
631 } else if (VideoStreamTrack
* video
= aTrack
->AsVideoStreamTrack()) {
632 if (video
!= mVideoTrack
) {
633 MOZ_ASSERT(false, "Not connected to this video track");
637 if (mVideoListener
) {
638 video
->RemoveDirectListener(mVideoListener
);
639 video
->RemoveListener(mVideoListener
);
641 mVideoTrack
= nullptr;
646 already_AddRefed
<MediaEncoder
> MediaEncoder::CreateEncoder(
647 RefPtr
<TaskQueue
> aEncoderThread
, const nsAString
& aMimeType
,
648 uint32_t aAudioBitrate
, uint32_t aVideoBitrate
, uint8_t aTrackTypes
,
649 TrackRate aTrackRate
, uint64_t aMaxMemory
, TimeDuration aTimeslice
) {
650 AUTO_PROFILER_LABEL("MediaEncoder::CreateEncoder", OTHER
);
652 UniquePtr
<ContainerWriter
> writer
;
653 UniquePtr
<AudioTrackEncoder
> audioEncoder
;
654 UniquePtr
<VideoTrackEncoder
> videoEncoder
;
655 auto encodedAudioQueue
= MakeUnique
<MediaQueue
<EncodedFrame
>>();
656 auto encodedVideoQueue
= MakeUnique
<MediaQueue
<EncodedFrame
>>();
657 auto driftCompensator
=
658 MakeRefPtr
<DriftCompensator
>(aEncoderThread
, aTrackRate
);
660 Maybe
<MediaContainerType
> mimeType
= MakeMediaContainerType(aMimeType
);
665 for (const auto& codec
: mimeType
->ExtendedType().Codecs().Range()) {
666 if (codec
.EqualsLiteral("opus")) {
667 MOZ_ASSERT(!audioEncoder
);
669 MakeUnique
<OpusTrackEncoder
>(aTrackRate
, *encodedAudioQueue
);
670 } else if (codec
.EqualsLiteral("vp8") || codec
.EqualsLiteral("vp8.0")) {
671 MOZ_ASSERT(!videoEncoder
);
672 if (Preferences::GetBool("media.recorder.video.frame_drops", true)) {
673 videoEncoder
= MakeUnique
<VP8TrackEncoder
>(driftCompensator
, aTrackRate
,
675 FrameDroppingMode::ALLOW
);
677 videoEncoder
= MakeUnique
<VP8TrackEncoder
>(driftCompensator
, aTrackRate
,
679 FrameDroppingMode::DISALLOW
);
682 MOZ_CRASH("Unknown codec");
686 if (mimeType
->Type() == MEDIAMIMETYPE(VIDEO_WEBM
) ||
687 mimeType
->Type() == MEDIAMIMETYPE(AUDIO_WEBM
)) {
688 MOZ_ASSERT_IF(mimeType
->Type() == MEDIAMIMETYPE(AUDIO_WEBM
), !videoEncoder
);
689 writer
= MakeUnique
<WebMWriter
>();
690 } else if (mimeType
->Type() == MEDIAMIMETYPE(AUDIO_OGG
)) {
691 MOZ_ASSERT(audioEncoder
);
692 MOZ_ASSERT(!videoEncoder
);
693 writer
= MakeUnique
<OggWriter
>();
695 NS_ENSURE_TRUE(writer
, nullptr);
698 ("Create encoder result:a[%p](%u bps) v[%p](%u bps) w[%p] mimeType = "
700 audioEncoder
.get(), aAudioBitrate
, videoEncoder
.get(), aVideoBitrate
,
701 writer
.get(), NS_ConvertUTF16toUTF8(aMimeType
).get()));
704 audioEncoder
->SetWorkerThread(aEncoderThread
);
705 if (aAudioBitrate
!= 0) {
706 audioEncoder
->SetBitrate(aAudioBitrate
);
710 videoEncoder
->SetWorkerThread(aEncoderThread
);
711 if (aVideoBitrate
!= 0) {
712 videoEncoder
->SetBitrate(aVideoBitrate
);
715 RefPtr
<MediaEncoder
> encoder
= new MediaEncoder(
716 std::move(aEncoderThread
), std::move(driftCompensator
), std::move(writer
),
717 std::move(audioEncoder
), std::move(videoEncoder
),
718 std::move(encodedAudioQueue
), std::move(encodedVideoQueue
), aTrackRate
,
719 aMimeType
, aMaxMemory
, aTimeslice
);
721 encoder
->RegisterListeners();
723 return encoder
.forget();
726 nsresult
MediaEncoder::GetEncodedData(
727 nsTArray
<nsTArray
<uint8_t>>* aOutputBufs
) {
728 AUTO_PROFILER_LABEL("MediaEncoder::GetEncodedData", OTHER
);
730 MOZ_ASSERT(mEncoderThread
->IsCurrentThreadIn());
732 LOG(LogLevel::Verbose
,
733 ("GetEncodedData TimeStamp = %f", GetEncodeTimeStamp()));
736 return NS_ERROR_NOT_INITIALIZED
;
739 nsresult rv
= mMuxer
->GetData(aOutputBufs
);
740 if (mMuxer
->IsFinished()) {
744 LOG(LogLevel::Verbose
,
745 ("END GetEncodedData TimeStamp=%f "
746 "mCompleted=%d, aComplete=%d, vComplete=%d",
747 GetEncodeTimeStamp(), mCompleted
,
748 !mAudioEncoder
|| mAudioEncoder
->IsEncodingComplete(),
749 !mVideoEncoder
|| mVideoEncoder
->IsEncodingComplete()));
754 void MediaEncoder::MaybeShutdown() {
755 MOZ_ASSERT(mEncoderThread
->IsCurrentThreadIn());
756 if (!mEncodedAudioQueue
->IsFinished()) {
758 ("MediaEncoder %p not shutting down, audio is still live", this));
762 if (!mEncodedVideoQueue
->IsFinished()) {
764 ("MediaEncoder %p not shutting down, video is still live", this));
768 mShutdownEvent
.Notify();
770 // Stop will Shutdown() gracefully.
771 Unused
<< InvokeAsync(mMainThread
, this, __func__
, &MediaEncoder::Stop
);
774 RefPtr
<GenericNonExclusivePromise
> MediaEncoder::Shutdown() {
775 MOZ_ASSERT(mEncoderThread
->IsCurrentThreadIn());
776 if (mShutdownPromise
) {
777 return mShutdownPromise
;
780 LOG(LogLevel::Info
, ("MediaEncoder is shutting down."));
782 AutoTArray
<RefPtr
<GenericNonExclusivePromise
>, 2> shutdownPromises
;
783 if (mAudioListener
) {
784 shutdownPromises
.AppendElement(mAudioListener
->OnShutdown());
786 if (mVideoListener
) {
787 shutdownPromises
.AppendElement(mVideoListener
->OnShutdown());
791 GenericNonExclusivePromise::All(mEncoderThread
, shutdownPromises
)
792 ->Then(mEncoderThread
, __func__
,
793 [](const GenericNonExclusivePromise::AllPromiseType::
794 ResolveOrRejectValue
& aValue
) {
795 if (aValue
.IsResolve()) {
796 return GenericNonExclusivePromise::CreateAndResolve(
799 return GenericNonExclusivePromise::CreateAndReject(
800 aValue
.RejectValue(), __func__
);
803 mShutdownPromise
->Then(
804 mEncoderThread
, __func__
, [self
= RefPtr
<MediaEncoder
>(this), this] {
806 mAudioEncoder
->UnregisterListener(mEncoderListener
);
809 mVideoEncoder
->UnregisterListener(mEncoderListener
);
811 mEncoderListener
->Forget();
812 mMuxer
->Disconnect();
813 mAudioPushListener
.DisconnectIfExists();
814 mAudioFinishListener
.DisconnectIfExists();
815 mVideoPushListener
.DisconnectIfExists();
816 mVideoFinishListener
.DisconnectIfExists();
819 return mShutdownPromise
;
822 RefPtr
<GenericNonExclusivePromise
> MediaEncoder::Stop() {
823 MOZ_ASSERT(NS_IsMainThread());
825 LOG(LogLevel::Info
, ("MediaEncoder %p Stop", this));
829 return InvokeAsync(mEncoderThread
, this, __func__
, &MediaEncoder::Shutdown
);
832 RefPtr
<GenericNonExclusivePromise
> MediaEncoder::Cancel() {
833 MOZ_ASSERT(NS_IsMainThread());
835 LOG(LogLevel::Info
, ("MediaEncoder %p Cancel", this));
839 return InvokeAsync(mEncoderThread
, __func__
,
840 [self
= RefPtr
<MediaEncoder
>(this), this]() {
842 mAudioEncoder
->Cancel();
845 mVideoEncoder
->Cancel();
851 bool MediaEncoder::HasError() {
852 MOZ_ASSERT(mEncoderThread
->IsCurrentThreadIn());
856 void MediaEncoder::SetError() {
857 MOZ_ASSERT(mEncoderThread
->IsCurrentThreadIn());
864 mErrorEvent
.Notify();
867 auto MediaEncoder::RequestData() -> RefPtr
<BlobPromise
> {
868 MOZ_ASSERT(mEncoderThread
->IsCurrentThreadIn());
869 TimeUnit muxedEndTime
= std::min(mMuxedAudioEndTime
, mMuxedVideoEndTime
);
870 mLastBlobTime
= muxedEndTime
;
871 mLastExtractTime
= muxedEndTime
;
872 return Extract()->Then(
873 mMainThread
, __func__
,
874 [this, self
= RefPtr
<MediaEncoder
>(this)](
875 const GenericPromise::ResolveOrRejectValue
& aValue
) {
876 // Even if rejected, we want to gather what has already been
877 // extracted into the current blob and expose that.
878 Unused
<< NS_WARN_IF(aValue
.IsReject());
883 void MediaEncoder::MaybeCreateMutableBlobStorage() {
884 MOZ_ASSERT(NS_IsMainThread());
885 if (!mMutableBlobStorage
) {
886 mMutableBlobStorage
= new MutableBlobStorage(
887 MutableBlobStorage::eCouldBeInTemporaryFile
, nullptr, mMaxMemory
);
891 void MediaEncoder::OnEncodedAudioPushed(const RefPtr
<EncodedFrame
>& aFrame
) {
892 MOZ_ASSERT(mEncoderThread
->IsCurrentThreadIn());
893 mMuxedAudioEndTime
= aFrame
->GetEndTime();
894 MaybeExtractOrGatherBlob();
897 void MediaEncoder::OnEncodedVideoPushed(const RefPtr
<EncodedFrame
>& aFrame
) {
898 MOZ_ASSERT(mEncoderThread
->IsCurrentThreadIn());
899 mMuxedVideoEndTime
= aFrame
->GetEndTime();
900 MaybeExtractOrGatherBlob();
903 void MediaEncoder::MaybeExtractOrGatherBlob() {
904 MOZ_ASSERT(mEncoderThread
->IsCurrentThreadIn());
906 TimeUnit muxedEndTime
= std::min(mMuxedAudioEndTime
, mMuxedVideoEndTime
);
907 if ((muxedEndTime
- mLastBlobTime
).ToTimeDuration() >= mTimeslice
) {
908 LOG(LogLevel::Verbose
, ("MediaEncoder %p Muxed %.2fs of data since last "
909 "blob. Issuing new blob.",
910 this, (muxedEndTime
- mLastBlobTime
).ToSeconds()));
911 RequestData()->Then(mEncoderThread
, __func__
,
912 [this, self
= RefPtr
<MediaEncoder
>(this)](
913 const BlobPromise::ResolveOrRejectValue
& aValue
) {
914 if (aValue
.IsReject()) {
918 RefPtr
<BlobImpl
> blob
= aValue
.ResolveValue();
919 mDataAvailableEvent
.Notify(std::move(blob
));
923 if (muxedEndTime
- mLastExtractTime
> TimeUnit::FromSeconds(1)) {
924 // Extract data from the muxer at least every second.
925 LOG(LogLevel::Verbose
,
926 ("MediaEncoder %p Muxed %.2fs of data since last "
927 "extract. Extracting more data into blob.",
928 this, (muxedEndTime
- mLastExtractTime
).ToSeconds()));
929 mLastExtractTime
= muxedEndTime
;
934 // Pull encoded media data from MediaEncoder and put into MutableBlobStorage.
935 RefPtr
<GenericPromise
> MediaEncoder::Extract() {
936 MOZ_ASSERT(mEncoderThread
->IsCurrentThreadIn());
938 LOG(LogLevel::Debug
, ("MediaEncoder %p Extract", this));
940 AUTO_PROFILER_LABEL("MediaEncoder::Extract", OTHER
);
942 // Pull encoded media data from MediaEncoder
943 nsTArray
<nsTArray
<uint8_t>> buffer
;
944 nsresult rv
= GetEncodedData(&buffer
);
945 MOZ_ASSERT(rv
!= NS_ERROR_INVALID_ARG
, "Invalid args can be prevented.");
947 MOZ_RELEASE_ASSERT(buffer
.IsEmpty());
948 // Even if we failed to encode more data, it might be time to push a blob
949 // with already encoded data.
952 // To ensure Extract() promises are resolved in calling order, we always
953 // invoke the main thread. Even when the encoded buffer is empty.
955 mMainThread
, __func__
,
956 [self
= RefPtr
<MediaEncoder
>(this), this, buffer
= std::move(buffer
)] {
957 MaybeCreateMutableBlobStorage();
958 for (const auto& part
: buffer
) {
959 if (part
.IsEmpty()) {
964 mMutableBlobStorage
->Append(part
.Elements(), part
.Length());
965 if (NS_WARN_IF(NS_FAILED(rv
))) {
966 return GenericPromise::CreateAndReject(rv
, __func__
);
969 return GenericPromise::CreateAndResolve(true, __func__
);
973 auto MediaEncoder::GatherBlob() -> RefPtr
<BlobPromise
> {
974 MOZ_ASSERT(NS_IsMainThread());
976 return mBlobPromise
= GatherBlobImpl();
978 return mBlobPromise
= mBlobPromise
->Then(mMainThread
, __func__
,
979 [self
= RefPtr
<MediaEncoder
>(this)] {
980 return self
->GatherBlobImpl();
984 auto MediaEncoder::GatherBlobImpl() -> RefPtr
<BlobPromise
> {
985 RefPtr
<BlobStorer
> storer
= MakeAndAddRef
<BlobStorer
>();
986 MaybeCreateMutableBlobStorage();
987 mMutableBlobStorage
->GetBlobImplWhenReady(NS_ConvertUTF16toUTF8(mMimeType
),
989 mMutableBlobStorage
= nullptr;
991 storer
->Promise()->Then(
992 mMainThread
, __func__
,
993 [self
= RefPtr
<MediaEncoder
>(this), p
= storer
->Promise()] {
994 if (self
->mBlobPromise
== p
) {
995 // Reset BlobPromise.
996 self
->mBlobPromise
= nullptr;
1000 return storer
->Promise();
1003 void MediaEncoder::DisconnectTracks() {
1004 MOZ_ASSERT(NS_IsMainThread());
1007 mAudioNode
->GetTrack()->RemoveListener(mAudioListener
);
1009 mInputPort
->Destroy();
1010 mInputPort
= nullptr;
1013 mPipeTrack
->RemoveListener(mAudioListener
);
1014 mPipeTrack
->Destroy();
1015 mPipeTrack
= nullptr;
1017 mAudioNode
= nullptr;
1021 RemoveMediaStreamTrack(mAudioTrack
);
1025 RemoveMediaStreamTrack(mVideoTrack
);
1029 bool MediaEncoder::IsWebMEncoderEnabled() {
1030 return StaticPrefs::media_encoder_webm_enabled();
1033 void MediaEncoder::UpdateInitialized() {
1034 MOZ_ASSERT(mEncoderThread
->IsCurrentThreadIn());
1037 // This could happen if an encoder re-inits due to a resolution change.
1041 if (mAudioEncoder
&& !mAudioEncoder
->IsInitialized()) {
1042 LOG(LogLevel::Debug
,
1043 ("MediaEncoder %p UpdateInitialized waiting for audio", this));
1047 if (mVideoEncoder
&& !mVideoEncoder
->IsInitialized()) {
1048 LOG(LogLevel::Debug
,
1049 ("MediaEncoder %p UpdateInitialized waiting for video", this));
1053 MOZ_ASSERT(mMuxer
->NeedsMetadata());
1054 nsTArray
<RefPtr
<TrackMetadataBase
>> meta
;
1055 if (mAudioEncoder
&& !*meta
.AppendElement(mAudioEncoder
->GetMetadata())) {
1056 LOG(LogLevel::Error
, ("Audio metadata is null"));
1060 if (mVideoEncoder
&& !*meta
.AppendElement(mVideoEncoder
->GetMetadata())) {
1061 LOG(LogLevel::Error
, ("Video metadata is null"));
1066 if (NS_FAILED(mMuxer
->SetMetadata(meta
))) {
1067 LOG(LogLevel::Error
, ("SetMetadata failed"));
1073 ("MediaEncoder %p UpdateInitialized set metadata in muxer", this));
1075 mInitialized
= true;
1078 void MediaEncoder::UpdateStarted() {
1079 MOZ_ASSERT(mEncoderThread
->IsCurrentThreadIn());
1085 if (mAudioEncoder
&& !mAudioEncoder
->IsStarted()) {
1089 if (mVideoEncoder
&& !mVideoEncoder
->IsStarted()) {
1095 // Start issuing timeslice-based blobs.
1096 MOZ_ASSERT(mLastBlobTime
== TimeUnit::Zero());
1098 mStartedEvent
.Notify();
1102 * SizeOfExcludingThis measures memory being used by the Media Encoder.
1103 * Currently it measures the size of the Encoder buffer and memory occupied
1104 * by mAudioEncoder, mVideoEncoder, and any current blob storage.
1106 auto MediaEncoder::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf
)
1107 -> RefPtr
<SizeOfPromise
> {
1108 MOZ_ASSERT(NS_IsMainThread());
1109 size_t blobStorageSize
=
1110 mMutableBlobStorage
? mMutableBlobStorage
->SizeOfCurrentMemoryBuffer()
1114 mEncoderThread
, __func__
,
1115 [self
= RefPtr
<MediaEncoder
>(this), this, blobStorageSize
,
1118 if (mAudioEncoder
) {
1119 size
+= mAudioEncoder
->SizeOfExcludingThis(aMallocSizeOf
);
1121 if (mVideoEncoder
) {
1122 size
+= mVideoEncoder
->SizeOfExcludingThis(aMallocSizeOf
);
1124 return SizeOfPromise::CreateAndResolve(blobStorageSize
+ size
,
1129 } // namespace mozilla