From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- dom/media/platforms/apple/AppleVTEncoder.cpp | 628 +++++++++++++++++++++++++++ 1 file changed, 628 insertions(+) create mode 100644 dom/media/platforms/apple/AppleVTEncoder.cpp (limited to 'dom/media/platforms/apple/AppleVTEncoder.cpp') diff --git a/dom/media/platforms/apple/AppleVTEncoder.cpp b/dom/media/platforms/apple/AppleVTEncoder.cpp new file mode 100644 index 0000000000..af91d99bcb --- /dev/null +++ b/dom/media/platforms/apple/AppleVTEncoder.cpp @@ -0,0 +1,628 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "AppleVTEncoder.h" + +#include +#include +#include + +#include "ImageContainer.h" +#include "AnnexB.h" +#include "H264.h" + +#include "libyuv.h" + +#include "AppleUtils.h" + +namespace mozilla { +extern LazyLogModule sPEMLog; +#define VTENC_LOGE(fmt, ...) \ + MOZ_LOG(sPEMLog, mozilla::LogLevel::Error, \ + ("[AppleVTEncoder] %s: " fmt, __func__, ##__VA_ARGS__)) +#define VTENC_LOGD(fmt, ...) \ + MOZ_LOG(sPEMLog, mozilla::LogLevel::Debug, \ + ("[AppleVTEncoder] %s: " fmt, __func__, ##__VA_ARGS__)) + +static CFDictionaryRef BuildEncoderSpec(const bool aHardwareNotAllowed) { + const void* keys[] = { + kVTVideoEncoderSpecification_EnableHardwareAcceleratedVideoEncoder}; + const void* values[] = {aHardwareNotAllowed ? kCFBooleanFalse + : kCFBooleanTrue}; + + static_assert(ArrayLength(keys) == ArrayLength(values), + "Non matching keys/values array size"); + return CFDictionaryCreate(kCFAllocatorDefault, keys, values, + ArrayLength(keys), &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); +} + +static void FrameCallback(void* aEncoder, void* aFrameRefCon, OSStatus aStatus, + VTEncodeInfoFlags aInfoFlags, + CMSampleBufferRef aSampleBuffer) { + if (aStatus != noErr || !aSampleBuffer) { + VTENC_LOGE("VideoToolbox encoder returned no data status=%d sample=%p", + aStatus, aSampleBuffer); + aSampleBuffer = nullptr; + } else if (aInfoFlags & kVTEncodeInfo_FrameDropped) { + VTENC_LOGE("frame tagged as dropped"); + return; + } + (static_cast(aEncoder))->OutputFrame(aSampleBuffer); +} + +static bool SetAverageBitrate(VTCompressionSessionRef& aSession, + MediaDataEncoder::Rate aBitsPerSec) { + int64_t bps(aBitsPerSec); + AutoCFRelease bitrate( + CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt64Type, &bps)); + return VTSessionSetProperty(aSession, + kVTCompressionPropertyKey_AverageBitRate, + bitrate) == noErr; +} + +static bool SetRealtimeProperties(VTCompressionSessionRef& aSession) { + return VTSessionSetProperty(aSession, kVTCompressionPropertyKey_RealTime, + kCFBooleanTrue) == noErr && + VTSessionSetProperty(aSession, + kVTCompressionPropertyKey_AllowFrameReordering, + kCFBooleanFalse) == noErr; +} + +static bool SetProfileLevel(VTCompressionSessionRef& aSession, + AppleVTEncoder::H264Specific::ProfileLevel aValue) { + CFStringRef profileLevel = nullptr; + switch (aValue) { + case AppleVTEncoder::H264Specific::ProfileLevel::BaselineAutoLevel: + profileLevel = kVTProfileLevel_H264_Baseline_AutoLevel; + break; + case AppleVTEncoder::H264Specific::ProfileLevel::MainAutoLevel: + profileLevel = kVTProfileLevel_H264_Main_AutoLevel; + break; + } + + return profileLevel ? VTSessionSetProperty( + aSession, kVTCompressionPropertyKey_ProfileLevel, + profileLevel) == noErr + : false; +} + +RefPtr AppleVTEncoder::Init() { + MOZ_ASSERT(!mInited, "Cannot initialize encoder again without shutting down"); + + if (mConfig.mSize.width == 0 || mConfig.mSize.height == 0) { + return InitPromise::CreateAndReject(NS_ERROR_ILLEGAL_VALUE, __func__); + } + + AutoCFRelease spec(BuildEncoderSpec(mHardwareNotAllowed)); + AutoCFRelease srcBufferAttr( + BuildSourceImageBufferAttributes()); + if (!srcBufferAttr) { + return InitPromise::CreateAndReject( + MediaResult(NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR, + "fail to create source buffer attributes"), + __func__); + } + + OSStatus status = VTCompressionSessionCreate( + kCFAllocatorDefault, mConfig.mSize.width, mConfig.mSize.height, + kCMVideoCodecType_H264, spec, srcBufferAttr, kCFAllocatorDefault, + &FrameCallback, this /* outputCallbackRefCon */, &mSession); + + if (status != noErr) { + return InitPromise::CreateAndReject( + MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, + "fail to create encoder session"), + __func__); + } + + if (!SetAverageBitrate(mSession, mConfig.mBitsPerSec)) { + return InitPromise::CreateAndReject( + MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, + "fail to configurate average bitrate"), + __func__); + } + + if (mConfig.mUsage == Usage::Realtime && !SetRealtimeProperties(mSession)) { + VTENC_LOGE("fail to configurate realtime properties"); + return InitPromise::CreateAndReject( + MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, + "fail to configurate average bitrate"), + __func__); + } + + int64_t interval = + mConfig.mKeyframeInterval > std::numeric_limits::max() + ? std::numeric_limits::max() + : mConfig.mKeyframeInterval; + AutoCFRelease cf( + CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt64Type, &interval)); + if (VTSessionSetProperty(mSession, + kVTCompressionPropertyKey_MaxKeyFrameInterval, + cf) != noErr) { + return InitPromise::CreateAndReject( + MediaResult( + NS_ERROR_DOM_MEDIA_FATAL_ERR, + nsPrintfCString("fail to configurate keyframe interval:%" PRId64, + interval)), + __func__); + } + + if (mConfig.mCodecSpecific) { + const H264Specific& specific = mConfig.mCodecSpecific.ref(); + if (!SetProfileLevel(mSession, specific.mProfileLevel)) { + return InitPromise::CreateAndReject( + MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, + nsPrintfCString("fail to configurate profile level:%d", + specific.mProfileLevel)), + __func__); + } + } + + CFBooleanRef isUsingHW = nullptr; + status = VTSessionCopyProperty( + mSession, kVTCompressionPropertyKey_UsingHardwareAcceleratedVideoEncoder, + kCFAllocatorDefault, &isUsingHW); + mIsHardwareAccelerated = status == noErr && isUsingHW == kCFBooleanTrue; + if (isUsingHW) { + CFRelease(isUsingHW); + } + + mError = NS_OK; + return InitPromise::CreateAndResolve(TrackInfo::TrackType::kVideoTrack, + __func__); +} + +static Maybe MapPixelFormat(MediaDataEncoder::PixelFormat aFormat) { + switch (aFormat) { + case MediaDataEncoder::PixelFormat::RGBA32: + case MediaDataEncoder::PixelFormat::BGRA32: + return Some(kCVPixelFormatType_32BGRA); + case MediaDataEncoder::PixelFormat::RGB24: + return Some(kCVPixelFormatType_24RGB); + case MediaDataEncoder::PixelFormat::BGR24: + return Some(kCVPixelFormatType_24BGR); + case MediaDataEncoder::PixelFormat::GRAY8: + return Some(kCVPixelFormatType_OneComponent8); + case MediaDataEncoder::PixelFormat::YUV444P: + return Some(kCVPixelFormatType_444YpCbCr8); + case MediaDataEncoder::PixelFormat::YUV420P: + return Some(kCVPixelFormatType_420YpCbCr8PlanarFullRange); + case MediaDataEncoder::PixelFormat::YUV420SP_NV12: + return Some(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange); + default: + return Nothing(); + } +} + +CFDictionaryRef AppleVTEncoder::BuildSourceImageBufferAttributes() { + Maybe fmt = MapPixelFormat(mConfig.mSourcePixelFormat); + if (fmt.isNothing()) { + VTENC_LOGE("unsupported source pixel format"); + return nullptr; + } + + // Source image buffer attributes + const void* keys[] = {kCVPixelBufferOpenGLCompatibilityKey, // TODO + kCVPixelBufferIOSurfacePropertiesKey, // TODO + kCVPixelBufferPixelFormatTypeKey}; + + AutoCFRelease ioSurfaceProps(CFDictionaryCreate( + kCFAllocatorDefault, nullptr, nullptr, 0, &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks)); + AutoCFRelease pixelFormat( + CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &fmt)); + const void* values[] = {kCFBooleanTrue, ioSurfaceProps, pixelFormat}; + + MOZ_ASSERT(ArrayLength(keys) == ArrayLength(values), + "Non matching keys/values array size"); + + return CFDictionaryCreate(kCFAllocatorDefault, keys, values, + ArrayLength(keys), &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); +} + +static bool IsKeyframe(CMSampleBufferRef aSample) { + CFArrayRef attachments = CMSampleBufferGetSampleAttachmentsArray(aSample, 0); + if (attachments == nullptr || CFArrayGetCount(attachments) == 0) { + return false; + } + + return !CFDictionaryContainsKey( + static_cast(CFArrayGetValueAtIndex(attachments, 0)), + kCMSampleAttachmentKey_NotSync); +} + +static size_t GetNumParamSets(CMFormatDescriptionRef aDescription) { + size_t numParamSets = 0; + OSStatus status = CMVideoFormatDescriptionGetH264ParameterSetAtIndex( + aDescription, 0, nullptr, nullptr, &numParamSets, nullptr); + if (status != noErr) { + VTENC_LOGE("Cannot get number of parameter sets from format description"); + } + + return numParamSets; +} + +static const uint8_t kNALUStart[4] = {0, 0, 0, 1}; + +static size_t GetParamSet(CMFormatDescriptionRef aDescription, size_t aIndex, + const uint8_t** aDataPtr) { + size_t length = 0; + int headerSize = 0; + if (CMVideoFormatDescriptionGetH264ParameterSetAtIndex( + aDescription, aIndex, aDataPtr, &length, nullptr, &headerSize) != + noErr) { + VTENC_LOGE("fail to get parameter set from format description"); + return 0; + } + MOZ_ASSERT(headerSize == sizeof(kNALUStart), "Only support 4 byte header"); + + return length; +} + +static bool WriteSPSPPS(MediaRawData* aDst, + CMFormatDescriptionRef aDescription) { + // Get SPS/PPS + const size_t numParamSets = GetNumParamSets(aDescription); + UniquePtr writer(aDst->CreateWriter()); + for (size_t i = 0; i < numParamSets; i++) { + const uint8_t* data = nullptr; + size_t length = GetParamSet(aDescription, i, &data); + if (length == 0) { + return false; + } + if (!writer->Append(kNALUStart, sizeof(kNALUStart))) { + VTENC_LOGE("Cannot write NAL unit start code"); + return false; + } + if (!writer->Append(data, length)) { + VTENC_LOGE("Cannot write parameter set"); + return false; + } + } + return true; +} + +static RefPtr extractAvcc( + CMFormatDescriptionRef aDescription) { + CFPropertyListRef list = CMFormatDescriptionGetExtension( + aDescription, + kCMFormatDescriptionExtension_SampleDescriptionExtensionAtoms); + if (!list) { + VTENC_LOGE("fail to get atoms"); + return nullptr; + } + CFDataRef avcC = static_cast( + CFDictionaryGetValue(static_cast(list), CFSTR("avcC"))); + if (!avcC) { + VTENC_LOGE("fail to extract avcC"); + return nullptr; + } + CFIndex length = CFDataGetLength(avcC); + const UInt8* bytes = CFDataGetBytePtr(avcC); + if (length <= 0 || !bytes) { + VTENC_LOGE("empty avcC"); + return nullptr; + } + + RefPtr config = new MediaByteBuffer(length); + config->AppendElements(bytes, length); + return config; +} + +bool AppleVTEncoder::WriteExtraData(MediaRawData* aDst, CMSampleBufferRef aSrc, + const bool aAsAnnexB) { + if (!IsKeyframe(aSrc)) { + return true; + } + + aDst->mKeyframe = true; + CMFormatDescriptionRef desc = CMSampleBufferGetFormatDescription(aSrc); + if (!desc) { + VTENC_LOGE("fail to get format description from sample"); + return false; + } + + if (aAsAnnexB) { + return WriteSPSPPS(aDst, desc); + } + + RefPtr avcc = extractAvcc(desc); + if (!avcc) { + return false; + } + + if (!mAvcc || !H264::CompareExtraData(avcc, mAvcc)) { + mAvcc = avcc; + aDst->mExtraData = mAvcc; + } + + return avcc != nullptr; +} + +static bool WriteNALUs(MediaRawData* aDst, CMSampleBufferRef aSrc, + bool aAsAnnexB = false) { + size_t srcRemaining = CMSampleBufferGetTotalSampleSize(aSrc); + CMBlockBufferRef block = CMSampleBufferGetDataBuffer(aSrc); + if (!block) { + VTENC_LOGE("Cannot get block buffer frome sample"); + return false; + } + UniquePtr writer(aDst->CreateWriter()); + size_t writtenLength = aDst->Size(); + // Ensure capacity. + if (!writer->SetSize(writtenLength + srcRemaining)) { + VTENC_LOGE("Cannot allocate buffer"); + return false; + } + size_t readLength = 0; + while (srcRemaining > 0) { + // Extract the size of next NAL unit + uint8_t unitSizeBytes[4]; + MOZ_ASSERT(srcRemaining > sizeof(unitSizeBytes)); + if (CMBlockBufferCopyDataBytes(block, readLength, sizeof(unitSizeBytes), + reinterpret_cast( + unitSizeBytes)) != kCMBlockBufferNoErr) { + VTENC_LOGE("Cannot copy unit size bytes"); + return false; + } + size_t unitSize = + CFSwapInt32BigToHost(*reinterpret_cast(unitSizeBytes)); + + if (aAsAnnexB) { + // Replace unit size bytes with NALU start code. + PodCopy(writer->Data() + writtenLength, kNALUStart, sizeof(kNALUStart)); + readLength += sizeof(unitSizeBytes); + srcRemaining -= sizeof(unitSizeBytes); + writtenLength += sizeof(kNALUStart); + } else { + // Copy unit size bytes + data. + unitSize += sizeof(unitSizeBytes); + } + MOZ_ASSERT(writtenLength + unitSize <= aDst->Size()); + // Copy NAL unit data + if (CMBlockBufferCopyDataBytes(block, readLength, unitSize, + writer->Data() + writtenLength) != + kCMBlockBufferNoErr) { + VTENC_LOGE("Cannot copy unit data"); + return false; + } + readLength += unitSize; + srcRemaining -= unitSize; + writtenLength += unitSize; + } + MOZ_ASSERT(writtenLength == aDst->Size()); + return true; +} + +void AppleVTEncoder::OutputFrame(CMSampleBufferRef aBuffer) { + RefPtr output(new MediaRawData()); + + bool asAnnexB = mConfig.mUsage == Usage::Realtime; + bool succeeded = WriteExtraData(output, aBuffer, asAnnexB) && + WriteNALUs(output, aBuffer, asAnnexB); + + output->mTime = media::TimeUnit::FromSeconds( + CMTimeGetSeconds(CMSampleBufferGetPresentationTimeStamp(aBuffer))); + output->mDuration = media::TimeUnit::FromSeconds( + CMTimeGetSeconds(CMSampleBufferGetOutputDuration(aBuffer))); + ProcessOutput(succeeded ? std::move(output) : nullptr); +} + +void AppleVTEncoder::ProcessOutput(RefPtr&& aOutput) { + if (!mTaskQueue->IsCurrentThreadIn()) { + nsresult rv = mTaskQueue->Dispatch(NewRunnableMethod>( + "AppleVTEncoder::ProcessOutput", this, &AppleVTEncoder::ProcessOutput, + std::move(aOutput))); + MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv)); + Unused << rv; + return; + } + AssertOnTaskQueue(); + + if (aOutput) { + mEncodedData.AppendElement(std::move(aOutput)); + } else { + mError = NS_ERROR_DOM_MEDIA_FATAL_ERR; + } +} + +RefPtr AppleVTEncoder::Encode( + const MediaData* aSample) { + MOZ_ASSERT(aSample != nullptr); + RefPtr sample(aSample->As()); + + return InvokeAsync>(mTaskQueue, this, __func__, + &AppleVTEncoder::ProcessEncode, + std::move(sample)); +} + +RefPtr AppleVTEncoder::ProcessEncode( + RefPtr aSample) { + AssertOnTaskQueue(); + MOZ_ASSERT(mSession); + + if (NS_FAILED(mError)) { + return EncodePromise::CreateAndReject(mError, __func__); + } + + AutoCVBufferRelease buffer( + CreateCVPixelBuffer(aSample->mImage)); + if (!buffer) { + return EncodePromise::CreateAndReject(NS_ERROR_OUT_OF_MEMORY, __func__); + } + + CFDictionaryRef frameProps = nullptr; + if (aSample->mKeyframe) { + CFTypeRef keys[] = {kVTEncodeFrameOptionKey_ForceKeyFrame}; + CFTypeRef values[] = {kCFBooleanTrue}; + MOZ_ASSERT(ArrayLength(keys) == ArrayLength(values)); + frameProps = CFDictionaryCreate( + kCFAllocatorDefault, keys, values, ArrayLength(keys), + &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + }; + + VTEncodeInfoFlags info; + OSStatus status = VTCompressionSessionEncodeFrame( + mSession, buffer, + CMTimeMake(aSample->mTime.ToMicroseconds(), USECS_PER_S), + CMTimeMake(aSample->mDuration.ToMicroseconds(), USECS_PER_S), frameProps, + nullptr /* sourceFrameRefcon */, &info); + if (status != noErr) { + return EncodePromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, + __func__); + } + + return EncodePromise::CreateAndResolve(std::move(mEncodedData), __func__); +} + +static size_t NumberOfPlanes(MediaDataEncoder::PixelFormat aPixelFormat) { + switch (aPixelFormat) { + case MediaDataEncoder::PixelFormat::RGBA32: + case MediaDataEncoder::PixelFormat::BGRA32: + case MediaDataEncoder::PixelFormat::RGB24: + case MediaDataEncoder::PixelFormat::BGR24: + case MediaDataEncoder::PixelFormat::GRAY8: + return 1; + case MediaDataEncoder::PixelFormat::YUV444P: + case MediaDataEncoder::PixelFormat::YUV420P: + return 3; + case MediaDataEncoder::PixelFormat::YUV420SP_NV12: + return 2; + default: + VTENC_LOGE("Unsupported input pixel format"); + return 0; + } +} + +using namespace layers; + +static void ReleaseImage(void* aImageGrip, const void* aDataPtr, + size_t aDataSize, size_t aNumOfPlanes, + const void** aPlanes) { + (static_cast(aImageGrip))->Release(); +} + +CVPixelBufferRef AppleVTEncoder::CreateCVPixelBuffer(const Image* aSource) { + AssertOnTaskQueue(); + + // TODO: support types other than YUV + PlanarYCbCrImage* image = const_cast(aSource)->AsPlanarYCbCrImage(); + if (!image || !image->GetData()) { + return nullptr; + } + + OSType format = MapPixelFormat(mConfig.mSourcePixelFormat).ref(); + size_t numPlanes = NumberOfPlanes(mConfig.mSourcePixelFormat); + const PlanarYCbCrImage::Data* yuv = image->GetData(); + if (!yuv) { + return nullptr; + } + auto ySize = yuv->YDataSize(); + auto cbcrSize = yuv->CbCrDataSize(); + void* addresses[3] = {}; + size_t widths[3] = {}; + size_t heights[3] = {}; + size_t strides[3] = {}; + switch (numPlanes) { + case 3: + addresses[2] = yuv->mCrChannel; + widths[2] = cbcrSize.width; + heights[2] = cbcrSize.height; + strides[2] = yuv->mCbCrStride; + [[fallthrough]]; + case 2: + addresses[1] = yuv->mCbChannel; + widths[1] = cbcrSize.width; + heights[1] = cbcrSize.height; + strides[1] = yuv->mCbCrStride; + [[fallthrough]]; + case 1: + addresses[0] = yuv->mYChannel; + widths[0] = ySize.width; + heights[0] = ySize.height; + strides[0] = yuv->mYStride; + break; + default: + return nullptr; + } + + CVPixelBufferRef buffer = nullptr; + image->AddRef(); // Grip input buffers. + CVReturn rv = CVPixelBufferCreateWithPlanarBytes( + kCFAllocatorDefault, yuv->mPictureRect.width, yuv->mPictureRect.height, + format, nullptr /* dataPtr */, 0 /* dataSize */, numPlanes, addresses, + widths, heights, strides, ReleaseImage /* releaseCallback */, + image /* releaseRefCon */, nullptr /* pixelBufferAttributes */, &buffer); + if (rv == kCVReturnSuccess) { + return buffer; + // |image| will be released in |ReleaseImage()|. + } else { + image->Release(); + return nullptr; + } +} + +RefPtr AppleVTEncoder::Drain() { + return InvokeAsync(mTaskQueue, this, __func__, &AppleVTEncoder::ProcessDrain); +} + +RefPtr AppleVTEncoder::ProcessDrain() { + AssertOnTaskQueue(); + MOZ_ASSERT(mSession); + + if (mFramesCompleted) { + MOZ_DIAGNOSTIC_ASSERT(mEncodedData.IsEmpty()); + return EncodePromise::CreateAndResolve(EncodedData(), __func__); + } + + OSStatus status = + VTCompressionSessionCompleteFrames(mSession, kCMTimeIndefinite); + if (status != noErr) { + return EncodePromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, + __func__); + } + mFramesCompleted = true; + // VTCompressionSessionCompleteFrames() could have queued multiple tasks with + // the new drained frames. Dispatch a task after them to resolve the promise + // with those frames. + RefPtr self = this; + return InvokeAsync(mTaskQueue, __func__, [self]() { + EncodedData pendingFrames(std::move(self->mEncodedData)); + self->mEncodedData = EncodedData(); + return EncodePromise::CreateAndResolve(std::move(pendingFrames), __func__); + }); +} + +RefPtr AppleVTEncoder::Shutdown() { + return InvokeAsync(mTaskQueue, this, __func__, + &AppleVTEncoder::ProcessShutdown); +} + +RefPtr AppleVTEncoder::ProcessShutdown() { + if (mSession) { + VTCompressionSessionInvalidate(mSession); + CFRelease(mSession); + mSession = nullptr; + mInited = false; + } + return ShutdownPromise::CreateAndResolve(true, __func__); +} + +RefPtr AppleVTEncoder::SetBitrate( + MediaDataEncoder::Rate aBitsPerSec) { + RefPtr self = this; + return InvokeAsync(mTaskQueue, __func__, [self, aBitsPerSec]() { + MOZ_ASSERT(self->mSession); + return SetAverageBitrate(self->mSession, aBitsPerSec) + ? GenericPromise::CreateAndResolve(true, __func__) + : GenericPromise::CreateAndReject( + NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR, __func__); + }); +} + +} // namespace mozilla -- cgit v1.2.3