/* 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 "WebrtcGmpVideoCodec.h"
#include <utility>
#include <vector>
#include "GMPLog.h"
#include "GMPUtils.h"
#include "MainThreadUtils.h"
#include "VideoConduit.h"
#include "gmp-video-frame-encoded.h"
#include "gmp-video-frame-i420.h"
#include "mozilla/CheckedInt.h"
#include "nsServiceManagerUtils.h"
#include "api/video/video_frame_type.h"
#include "common_video/include/video_frame_buffer.h"
#include "media/base/media_constants.h"
namespace mozilla {
// QP scaling thresholds.
static const int kLowH264QpThreshold = 24;
static const int kHighH264QpThreshold = 37;
// Encoder.
WebrtcGmpVideoEncoder::WebrtcGmpVideoEncoder(
const webrtc::SdpVideoFormat& aFormat, std::string aPCHandle)
: mGMP(nullptr),
mInitting(
false),
mConfiguredBitrateKbps(0),
mHost(nullptr),
mMaxPayloadSize(0),
mFormatParams(aFormat.parameters),
mCallbackMutex(
"WebrtcGmpVideoEncoder encoded callback mutex"),
mCallback(nullptr),
mPCHandle(std::move(aPCHandle)) {
mCodecParams.mCodecType = kGMPVideoCodecInvalid;
mCodecParams.mMode = kGMPCodecModeInvalid;
mCodecParams.mLogLevel = GetGMPLibraryLogLevel();
MOZ_ASSERT(!mPCHandle.empty());
}
WebrtcGmpVideoEncoder::~WebrtcGmpVideoEncoder() {
// We should not have been destroyed if we never closed our GMP
MOZ_ASSERT(!mGMP);
}
static int WebrtcFrameTypeToGmpFrameType(webrtc::VideoFrameType aIn,
GMPVideoFrameType* aOut) {
MOZ_ASSERT(aOut);
switch (aIn) {
case webrtc::VideoFrameType::kVideoFrameKey:
*aOut = kGMPKeyFrame;
break;
case webrtc::VideoFrameType::kVideoFrameDelta:
*aOut = kGMPDeltaFrame;
break;
case webrtc::VideoFrameType::kEmptyFrame:
*aOut = kGMPSkipFrame;
break;
default:
MOZ_CRASH(
"Unexpected webrtc::FrameType");
}
return WEBRTC_VIDEO_CODEC_OK;
}
static int GmpFrameTypeToWebrtcFrameType(GMPVideoFrameType aIn,
webrtc::VideoFrameType* aOut) {
MOZ_ASSERT(aOut);
switch (aIn) {
case kGMPKeyFrame:
*aOut = webrtc::VideoFrameType::kVideoFrameKey;
break;
case kGMPDeltaFrame:
*aOut = webrtc::VideoFrameType::kVideoFrameDelta;
break;
case kGMPSkipFrame:
*aOut = webrtc::VideoFrameType::kEmptyFrame;
break;
default:
MOZ_CRASH(
"Unexpected GMPVideoFrameType");
}
return WEBRTC_VIDEO_CODEC_OK;
}
int32_t WebrtcGmpVideoEncoder::InitEncode(
const webrtc::VideoCodec* aCodecSettings,
const webrtc::VideoEncoder::Settings& aSettings) {
if (!mEncodeQueue) {
mEncodeQueue.emplace(GetCurrentSerialEventTarget());
}
mEncodeQueue->AssertOnCurrentThread();
if (!mMPS) {
mMPS = do_GetService(
"@mozilla.org/gecko-media-plugin-service;1");
}
MOZ_ASSERT(mMPS);
if (!mGMPThread) {
if (NS_WARN_IF(NS_FAILED(mMPS->GetThread(getter_AddRefs(mGMPThread))))) {
return WEBRTC_VIDEO_CODEC_ERROR;
}
}
if (aCodecSettings->numberOfSimulcastStreams > 1) {
// Simulcast not implemented for GMP-H264
return WEBRTC_VIDEO_CODEC_ERR_SIMULCAST_PARAMETERS_NOT_SUPPORTED;
}
GMPVideoCodec codecParams{};
codecParams.mGMPApiVersion = kGMPVersion36;
codecParams.mLogLevel = GetGMPLibraryLogLevel();
codecParams.mStartBitrate = aCodecSettings->startBitrate;
codecParams.mMinBitrate = aCodecSettings->minBitrate;
codecParams.mMaxBitrate = aCodecSettings->maxBitrate;
codecParams.mMaxFramerate = aCodecSettings->maxFramerate;
codecParams.mFrameDroppingOn = aCodecSettings->GetFrameDropEnabled();
codecParams.mTemporalLayerNum =
aCodecSettings->simulcastStream[0].GetNumberOfTemporalLayers();
if (aCodecSettings->mode == webrtc::VideoCodecMode::kScreensharing) {
codecParams.mMode = kGMPScreensharing;
}
else {
codecParams.mMode = kGMPRealtimeVideo;
}
codecParams.mWidth = aCodecSettings->width;
codecParams.mHeight = aCodecSettings->height;
mCodecSpecificInfo.codecType = webrtc::kVideoCodecH264;
mCodecSpecificInfo.codecSpecific = {};
mCodecSpecificInfo.codecSpecific.H264.packetization_mode =
mFormatParams.count(cricket::kH264FmtpPacketizationMode) == 1 &&
mFormatParams.at(cricket::kH264FmtpPacketizationMode) ==
"1"
? webrtc::H264PacketizationMode::NonInterleaved
: webrtc::H264PacketizationMode::SingleNalUnit;
uint32_t maxPayloadSize = aSettings.max_payload_size;
if (mCodecSpecificInfo.codecSpecific.H264.packetization_mode ==
webrtc::H264PacketizationMode::NonInterleaved) {
maxPayloadSize = 0;
// No limit, use FUAs
}
mConfiguredBitrateKbps = codecParams.mMaxBitrate;
MOZ_ALWAYS_SUCCEEDS(
mGMPThread->Dispatch(NewRunnableMethod<GMPVideoCodec, int32_t, uint32_t>(
__func__,
this, &WebrtcGmpVideoEncoder::InitEncode_g, codecParams,
aSettings.number_of_cores, maxPayloadSize)));
// Since init of the GMP encoder is a multi-step async dispatch (including
// dispatches to main), and since this function is invoked on main, there's
// no safe way to block until this init is done. If an error occurs, we'll
// handle it later.
return WEBRTC_VIDEO_CODEC_OK;
}
void WebrtcGmpVideoEncoder::InitEncode_g(
const GMPVideoCodec& aCodecParams,
int32_t aNumberOfCores,
uint32_t aMaxPayloadSize) {
nsTArray<nsCString> tags;
tags.AppendElement(
"h264"_ns);
UniquePtr<GetGMPVideoEncoderCallback> callback(
new InitDoneCallback(
this, aCodecParams));
mInitting =
true;
mMaxPayloadSize = aMaxPayloadSize;
nsresult rv =
mMPS->GetGMPVideoEncoder(nullptr, &tags,
""_ns, std::move(callback));
if (NS_WARN_IF(NS_FAILED(rv))) {
GMP_LOG_DEBUG(
"GMP Encode: GetGMPVideoEncoder failed");
Close_g();
NotifyGmpInitDone(mPCHandle, WEBRTC_VIDEO_CODEC_ERROR,
"GMP Encode: GetGMPVideoEncoder failed");
}
}
int32_t WebrtcGmpVideoEncoder::GmpInitDone_g(GMPVideoEncoderProxy* aGMP,
GMPVideoHost* aHost,
std::string* aErrorOut) {
if (!mInitting || !aGMP || !aHost) {
*aErrorOut =
"GMP Encode: Either init was aborted, "
"or init failed to supply either a GMP Encoder or GMP host.";
if (aGMP) {
// This could destroy us, since aGMP may be the last thing holding a ref
// Return immediately.
aGMP->Close();
}
return WEBRTC_VIDEO_CODEC_ERROR;
}
mInitting =
false;
if (mGMP && mGMP != aGMP) {
Close_g();
}
mGMP = aGMP;
mHost = aHost;
mCachedPluginId = Some(mGMP->GetPluginId());
mInitPluginEvent.Notify(*mCachedPluginId);
return WEBRTC_VIDEO_CODEC_OK;
}
int32_t WebrtcGmpVideoEncoder::GmpInitDone_g(GMPVideoEncoderProxy* aGMP,
GMPVideoHost* aHost,
const GMPVideoCodec& aCodecParams,
std::string* aErrorOut) {
int32_t r = GmpInitDone_g(aGMP, aHost, aErrorOut);
if (r != WEBRTC_VIDEO_CODEC_OK) {
// We might have been destroyed if GmpInitDone failed.
// Return immediately.
return r;
}
mCodecParams = aCodecParams;
return InitEncoderForSize(aCodecParams.mWidth, aCodecParams.mHeight,
aErrorOut);
}
void WebrtcGmpVideoEncoder::Close_g() {
GMPVideoEncoderProxy* gmp(mGMP);
mGMP = nullptr;
mHost = nullptr;
mInitting =
false;
if (mCachedPluginId) {
mReleasePluginEvent.Notify(*mCachedPluginId);
}
mCachedPluginId = Nothing();
if (gmp) {
// Do this last, since this could cause us to be destroyed
gmp->Close();
}
}
int32_t WebrtcGmpVideoEncoder::InitEncoderForSize(
unsigned short aWidth,
unsigned short aHeight,
std::string* aErrorOut) {
mCodecParams.mWidth = aWidth;
mCodecParams.mHeight = aHeight;
// Pass dummy codecSpecific data for now...
nsTArray<uint8_t> codecSpecific;
GMPErr err =
mGMP->InitEncode(mCodecParams, codecSpecific,
this, 1, mMaxPayloadSize);
if (err != GMPNoErr) {
*aErrorOut =
"GMP Encode: InitEncode failed";
return WEBRTC_VIDEO_CODEC_ERROR;
}
return WEBRTC_VIDEO_CODEC_OK;
}
int32_t WebrtcGmpVideoEncoder::Encode(
const webrtc::VideoFrame& aInputImage,
const std::vector<webrtc::VideoFrameType>* aFrameTypes) {
mEncodeQueue->AssertOnCurrentThread();
MOZ_ASSERT(aInputImage.width() >= 0 && aInputImage.height() >= 0);
if (!aFrameTypes) {
return WEBRTC_VIDEO_CODEC_ERROR;
}
if (mConfiguredBitrateKbps == 0) {
GMP_LOG_VERBOSE(
"GMP Encode: not enabled");
MutexAutoLock lock(mCallbackMutex);
if (mCallback) {
mCallback->OnDroppedFrame(
webrtc::EncodedImageCallback::DropReason::kDroppedByEncoder);
}
return WEBRTC_VIDEO_CODEC_OK;
}
// It is safe to copy aInputImage here because the frame buffer is held by
// a refptr.
MOZ_ALWAYS_SUCCEEDS(mGMPThread->Dispatch(
NewRunnableMethod<webrtc::VideoFrame,
std::vector<webrtc::VideoFrameType>>(
__func__,
this, &WebrtcGmpVideoEncoder::Encode_g, aInputImage,
*aFrameTypes)));
return WEBRTC_VIDEO_CODEC_OK;
}
void WebrtcGmpVideoEncoder::RegetEncoderForResolutionChange(uint32_t aWidth,
uint32_t aHeight) {
Close_g();
UniquePtr<GetGMPVideoEncoderCallback> callback(
new InitDoneForResolutionChangeCallback(
this, aWidth, aHeight));
// OpenH264 codec (at least) can't handle dynamic input resolution changes
// re-init the plugin when the resolution changes
// XXX allow codec to indicate it doesn't need re-init!
nsTArray<nsCString> tags;
tags.AppendElement(
"h264"_ns);
mInitting =
true;
if (NS_WARN_IF(NS_FAILED(mMPS->GetGMPVideoEncoder(nullptr, &tags,
""_ns,
std::move(callback))))) {
NotifyGmpInitDone(mPCHandle, WEBRTC_VIDEO_CODEC_ERROR,
"GMP Encode: GetGMPVideoEncoder failed");
}
}
void WebrtcGmpVideoEncoder::Encode_g(
const webrtc::VideoFrame& aInputImage,
std::vector<webrtc::VideoFrameType> aFrameTypes) {
if (!mGMP) {
// destroyed via Terminate(), failed to init, or just not initted yet
GMP_LOG_DEBUG(
"GMP Encode: not initted yet");
return;
}
MOZ_ASSERT(mHost);
if (
static_cast<uint32_t>(aInputImage.width()) != mCodecParams.mWidth ||
static_cast<uint32_t>(aInputImage.height()) != mCodecParams.mHeight) {
GMP_LOG_DEBUG(
"GMP Encode: resolution change from %ux%u to %dx%d",
mCodecParams.mWidth, mCodecParams.mHeight,
aInputImage.width(), aInputImage.height());
RegetEncoderForResolutionChange(aInputImage.width(), aInputImage.height());
if (!mGMP) {
// We needed to go async to re-get the encoder. Bail.
return;
}
}
GMPVideoFrame* ftmp = nullptr;
GMPErr err = mHost->CreateFrame(kGMPI420VideoFrame, &ftmp);
if (err != GMPNoErr) {
GMP_LOG_DEBUG(
"GMP Encode: failed to create frame on host");
return;
}
GMPUniquePtr<GMPVideoi420Frame> frame(
static_cast<GMPVideoi420Frame*>(ftmp));
const webrtc::I420BufferInterface* input_image =
aInputImage.video_frame_buffer()->GetI420();
// check for overflow of stride * height
CheckedInt32 ysize =
CheckedInt32(input_image->StrideY()) * input_image->height();
MOZ_RELEASE_ASSERT(ysize.isValid());
// I will assume that if that doesn't overflow, the others case - YUV
// 4:2:0 has U/V widths <= Y, even with alignment issues.
err = frame->CreateFrame(
ysize.value(), input_image->DataY(),
input_image->StrideU() * ((input_image->height() + 1) / 2),
input_image->DataU(),
input_image->StrideV() * ((input_image->height() + 1) / 2),
input_image->DataV(), input_image->width(), input_image->height(),
input_image->StrideY(), input_image->StrideU(), input_image->StrideV());
if (err != GMPNoErr) {
GMP_LOG_DEBUG(
"GMP Encode: failed to create frame");
return;
}
frame->SetTimestamp((aInputImage.rtp_timestamp() * 1000ll) /
90);
// note: rounds down!
GMPCodecSpecificInfo info{};
info.mCodecType = kGMPVideoCodecH264;
nsTArray<uint8_t> codecSpecificInfo;
codecSpecificInfo.AppendElements((uint8_t*)&info,
sizeof(GMPCodecSpecificInfo));
nsTArray<GMPVideoFrameType> gmp_frame_types;
for (
auto it = aFrameTypes.begin(); it != aFrameTypes.end(); ++it) {
GMPVideoFrameType ft;
int32_t ret = WebrtcFrameTypeToGmpFrameType(*it, &ft);
if (ret != WEBRTC_VIDEO_CODEC_OK) {
GMP_LOG_DEBUG(
"GMP Encode: failed to map webrtc frame type to gmp frame type");
return;
}
gmp_frame_types.AppendElement(ft);
}
MOZ_RELEASE_ASSERT(mInputImageMap.IsEmpty() ||
mInputImageMap.LastElement().rtp_timestamp <
frame->Timestamp());
mInputImageMap.AppendElement(
InputImageData{frame->Timestamp(), aInputImage.timestamp_us()});
GMP_LOG_DEBUG(
"GMP Encode: %" PRIu64, (frame->Timestamp()));
err = mGMP->Encode(std::move(frame), codecSpecificInfo, gmp_frame_types);
if (err != GMPNoErr) {
GMP_LOG_DEBUG(
"GMP Encode: failed to encode frame");
}
}
int32_t WebrtcGmpVideoEncoder::RegisterEncodeCompleteCallback(
webrtc::EncodedImageCallback* aCallback) {
MutexAutoLock lock(mCallbackMutex);
mCallback = aCallback;
return WEBRTC_VIDEO_CODEC_OK;
}
int32_t WebrtcGmpVideoEncoder::Shutdown() {
GMP_LOG_DEBUG(
"GMP Released:");
RegisterEncodeCompleteCallback(nullptr);
if (mGMPThread) {
MOZ_ALWAYS_SUCCEEDS(mGMPThread->Dispatch(
NewRunnableMethod(__func__,
this, &WebrtcGmpVideoEncoder::Close_g)));
}
return WEBRTC_VIDEO_CODEC_OK;
}
int32_t WebrtcGmpVideoEncoder::SetRates(
const webrtc::VideoEncoder::RateControlParameters& aParameters) {
mEncodeQueue->AssertOnCurrentThread();
MOZ_ASSERT(mGMPThread);
MOZ_ASSERT(!aParameters.bitrate.IsSpatialLayerUsed(1),
"No simulcast support for H264");
mConfiguredBitrateKbps = aParameters.bitrate.GetSpatialLayerSum(0) / 1000;
MOZ_ALWAYS_SUCCEEDS(
mGMPThread->Dispatch(NewRunnableMethod<uint32_t, Maybe<
double>>(
__func__,
this, &WebrtcGmpVideoEncoder::SetRates_g,
mConfiguredBitrateKbps,
aParameters.framerate_fps > 0.0 ? Some(aParameters.framerate_fps)
: Nothing())));
return WEBRTC_VIDEO_CODEC_OK;
}
WebrtcVideoEncoder::EncoderInfo WebrtcGmpVideoEncoder::GetEncoderInfo()
const {
WebrtcVideoEncoder::EncoderInfo info;
info.supports_native_handle =
false;
info.implementation_name =
"GMPOpenH264";
info.scaling_settings = WebrtcVideoEncoder::ScalingSettings(
kLowH264QpThreshold, kHighH264QpThreshold);
info.is_hardware_accelerated =
false;
info.supports_simulcast =
false;
return info;
}
int32_t WebrtcGmpVideoEncoder::SetRates_g(uint32_t aNewBitRateKbps,
Maybe<
double> aFrameRate) {
if (!mGMP) {
// destroyed via Terminate()
return WEBRTC_VIDEO_CODEC_ERROR;
}
GMPErr err = mGMP->SetRates(
aNewBitRateKbps, aFrameRate
.map([](
double aFr) {
// Avoid rounding to 0
return std::max(1U,
static_cast<uint32_t>(aFr));
})
.valueOr(mCodecParams.mMaxFramerate));
if (err != GMPNoErr) {
return WEBRTC_VIDEO_CODEC_ERROR;
}
return WEBRTC_VIDEO_CODEC_OK;
}
// GMPVideoEncoderCallback virtual functions.
void WebrtcGmpVideoEncoder::Terminated() {
GMP_LOG_DEBUG(
"GMP Encoder Terminated: %p", (
void*)
this);
GMPVideoEncoderProxy* gmp(mGMP);
mGMP = nullptr;
mHost = nullptr;
mInitting =
false;
if (gmp) {
// Do this last, since this could cause us to be destroyed
gmp->Close();
}
// Could now notify that it's dead
}
void WebrtcGmpVideoEncoder::Encoded(
GMPVideoEncodedFrame* aEncodedFrame,
const nsTArray<uint8_t>& aCodecSpecificInfo) {
MOZ_ASSERT(mGMPThread->IsOnCurrentThread());
webrtc::Timestamp capture_time = webrtc::Timestamp::Micros(0);
auto rtp_comparator = [](
const InputImageData& aA,
const InputImageData& aB) -> int32_t {
const auto& a = aA.rtp_timestamp;
const auto& b = aB.rtp_timestamp;
return a < b ? -1 : a != b;
};
size_t nextIdx = mInputImageMap.IndexOfFirstElementGt(
InputImageData{aEncodedFrame->TimeStamp(), 0}, rtp_comparator);
const size_t numToRemove = nextIdx;
size_t numFramesDropped = numToRemove;
MOZ_ASSERT(nextIdx != 0);
if (nextIdx != 0 && mInputImageMap.ElementAt(nextIdx - 1).rtp_timestamp ==
aEncodedFrame->TimeStamp()) {
--numFramesDropped;
capture_time = webrtc::Timestamp::Micros(
mInputImageMap.ElementAt(nextIdx - 1).timestamp_us);
}
mInputImageMap.RemoveElementsAt(0, numToRemove);
MutexAutoLock lock(mCallbackMutex);
if (!mCallback) {
return;
}
for (size_t i = 0; i < numFramesDropped; ++i) {
mCallback->OnDroppedFrame(
webrtc::EncodedImageCallback::DropReason::kDroppedByEncoder);
}
webrtc::VideoFrameType ft;
GmpFrameTypeToWebrtcFrameType(aEncodedFrame->FrameType(), &ft);
uint64_t timestamp = (aEncodedFrame->TimeStamp() * 90ll + 999) / 1000;
GMP_LOG_DEBUG(
"GMP Encoded: %" PRIu64
", type %d, len %d",
aEncodedFrame->TimeStamp(), aEncodedFrame->BufferType(),
aEncodedFrame->Size());
// Libwebrtc's RtpPacketizerH264 expects a 3- or 4-byte NALU start sequence
// before the start of the NALU payload. {0,0,1} or {0,0,0,1}. We set this
// in-place. Any other length of the length field we reject.
if (NS_WARN_IF(!AdjustOpenH264NALUSequence(aEncodedFrame))) {
return;
}
webrtc::EncodedImage unit;
unit.SetEncodedData(webrtc::EncodedImageBuffer::Create(
aEncodedFrame->Buffer(), aEncodedFrame->Size()));
unit._frameType = ft;
unit.SetRtpTimestamp(timestamp);
unit.capture_time_ms_ = capture_time.ms();
unit._encodedWidth = aEncodedFrame->EncodedWidth();
unit._encodedHeight = aEncodedFrame->EncodedHeight();
if (
int idx = aEncodedFrame->GetTemporalLayerId(); idx >= 0) {
unit.SetTemporalIndex(idx);
}
// Parse QP.
mH264BitstreamParser.ParseBitstream(unit);
unit.qp_ = mH264BitstreamParser.GetLastSliceQp().value_or(-1);
// TODO: Currently the OpenH264 codec does not preserve any codec
// specific info passed into it and just returns default values.
// If this changes in the future, it would be nice to get rid of
// mCodecSpecificInfo.
mCallback->OnEncodedImage(unit, &mCodecSpecificInfo);
}
// Decoder.
WebrtcGmpVideoDecoder::WebrtcGmpVideoDecoder(std::string aPCHandle,
TrackingId aTrackingId)
: mGMP(nullptr),
mInitting(
false),
mHost(nullptr),
mCallbackMutex(
"WebrtcGmpVideoDecoder decoded callback mutex"),
mCallback(nullptr),
mDecoderStatus(GMPNoErr),
mPCHandle(std::move(aPCHandle)),
mTrackingId(std::move(aTrackingId)) {
MOZ_ASSERT(!mPCHandle.empty());
}
WebrtcGmpVideoDecoder::~WebrtcGmpVideoDecoder() {
// We should not have been destroyed if we never closed our GMP
MOZ_ASSERT(!mGMP);
}
bool WebrtcGmpVideoDecoder::Configure(
const webrtc::VideoDecoder::Settings& settings) {
if (!mMPS) {
mMPS = do_GetService(
"@mozilla.org/gecko-media-plugin-service;1");
}
MOZ_ASSERT(mMPS);
if (!mGMPThread) {
if (NS_WARN_IF(NS_FAILED(mMPS->GetThread(getter_AddRefs(mGMPThread))))) {
return false;
}
}
MOZ_ALWAYS_SUCCEEDS(
mGMPThread->Dispatch(NewRunnableMethod<webrtc::VideoDecoder::Settings>(
__func__,
this, &WebrtcGmpVideoDecoder::Configure_g, settings)));
return true;
}
void WebrtcGmpVideoDecoder::Configure_g(
const webrtc::VideoDecoder::Settings& settings) {
nsTArray<nsCString> tags;
tags.AppendElement(
"h264"_ns);
UniquePtr<GetGMPVideoDecoderCallback> callback(
new InitDoneCallback(
this));
mInitting =
true;
nsresult rv =
mMPS->GetGMPVideoDecoder(nullptr, &tags,
""_ns, std::move(callback));
if (NS_WARN_IF(NS_FAILED(rv))) {
GMP_LOG_DEBUG(
"GMP Decode: GetGMPVideoDecoder failed");
Close_g();
NotifyGmpInitDone(mPCHandle, WEBRTC_VIDEO_CODEC_ERROR,
"GMP Decode: GetGMPVideoDecoder failed.");
}
}
int32_t WebrtcGmpVideoDecoder::GmpInitDone_g(GMPVideoDecoderProxy* aGMP,
GMPVideoHost* aHost,
std::string* aErrorOut) {
if (!mInitting || !aGMP || !aHost) {
*aErrorOut =
"GMP Decode: Either init was aborted, "
"or init failed to supply either a GMP decoder or GMP host.";
if (aGMP) {
// This could destroy us, since aGMP may be the last thing holding a ref
// Return immediately.
aGMP->Close();
}
return WEBRTC_VIDEO_CODEC_ERROR;
}
mInitting =
false;
if (mGMP && mGMP != aGMP) {
Close_g();
}
mGMP = aGMP;
mHost = aHost;
mCachedPluginId = Some(mGMP->GetPluginId());
mInitPluginEvent.Notify(*mCachedPluginId);
GMPVideoCodec codec{};
codec.mGMPApiVersion = kGMPVersion34;
codec.mLogLevel = GetGMPLibraryLogLevel();
// XXX this is currently a hack
// GMPVideoCodecUnion codecSpecific;
// memset(&codecSpecific, 0, sizeof(codecSpecific));
nsTArray<uint8_t> codecSpecific;
nsresult rv = mGMP->InitDecode(codec, codecSpecific,
this, 1);
if (NS_FAILED(rv)) {
*aErrorOut =
"GMP Decode: InitDecode failed";
mQueuedFrames.Clear();
return WEBRTC_VIDEO_CODEC_ERROR;
}
// now release any frames that got queued waiting for InitDone
if (!mQueuedFrames.IsEmpty()) {
// So we're safe to call Decode_g(), which asserts it's empty
nsTArray<UniquePtr<GMPDecodeData>> temp = std::move(mQueuedFrames);
for (
auto& queued : temp) {
Decode_g(std::move(queued));
}
}
// This is an ugly solution to asynchronous decoding errors
// from Decode_g() not being returned to the synchronous Decode() method.
// If we don't return an error code at this point, our caller ultimately won't
// know to request a PLI and the video stream will remain frozen unless an IDR
// happens to arrive for other reasons. Bug 1492852 tracks implementing a
// proper solution.
if (mDecoderStatus != GMPNoErr) {
GMP_LOG_ERROR(
"%s: Decoder status is bad (%u)!", __PRETTY_FUNCTION__,
static_cast<
unsigned>(mDecoderStatus));
return WEBRTC_VIDEO_CODEC_ERROR;
}
return WEBRTC_VIDEO_CODEC_OK;
}
void WebrtcGmpVideoDecoder::Close_g() {
GMPVideoDecoderProxy* gmp(mGMP);
mGMP = nullptr;
mHost = nullptr;
mInitting =
false;
if (mCachedPluginId) {
mReleasePluginEvent.Notify(*mCachedPluginId);
}
mCachedPluginId = Nothing();
if (gmp) {
// Do this last, since this could cause us to be destroyed
gmp->Close();
}
}
int32_t WebrtcGmpVideoDecoder::Decode(
const webrtc::EncodedImage& aInputImage,
bool aMissingFrames,
int64_t aRenderTimeMs) {
MOZ_ASSERT(mGMPThread);
MOZ_ASSERT(!NS_IsMainThread());
if (!aInputImage.size()) {
return WEBRTC_VIDEO_CODEC_ERROR;
}
MediaInfoFlag flag = MediaInfoFlag::None;
flag |= (aInputImage._frameType == webrtc::VideoFrameType::kVideoFrameKey
? MediaInfoFlag::KeyFrame
: MediaInfoFlag::NonKeyFrame);
flag |= MediaInfoFlag::SoftwareDecoding;
flag |= MediaInfoFlag::VIDEO_H264;
mPerformanceRecorder.Start((aInputImage.RtpTimestamp() * 1000ll) / 90,
"WebrtcGmpVideoDecoder"_ns, mTrackingId, flag);
// This is an ugly solution to asynchronous decoding errors
// from Decode_g() not being returned to the synchronous Decode() method.
// If we don't return an error code at this point, our caller ultimately won't
// know to request a PLI and the video stream will remain frozen unless an IDR
// happens to arrive for other reasons. Bug 1492852 tracks implementing a
// proper solution.
auto decodeData =
MakeUnique<GMPDecodeData>(aInputImage, aMissingFrames, aRenderTimeMs);
MOZ_ALWAYS_SUCCEEDS(
mGMPThread->Dispatch(NewRunnableMethod<UniquePtr<GMPDecodeData>&&>(
__func__,
this, &WebrtcGmpVideoDecoder::Decode_g,
std::move(decodeData))));
if (mDecoderStatus != GMPNoErr) {
GMP_LOG_ERROR(
"%s: Decoder status is bad (%u)!", __PRETTY_FUNCTION__,
static_cast<
unsigned>(mDecoderStatus));
return WEBRTC_VIDEO_CODEC_ERROR;
}
return WEBRTC_VIDEO_CODEC_OK;
}
void WebrtcGmpVideoDecoder::Decode_g(UniquePtr<GMPDecodeData>&& aDecodeData) {
if (!mGMP) {
if (mInitting) {
// InitDone hasn't been called yet (race)
mQueuedFrames.AppendElement(std::move(aDecodeData));
return;
}
// destroyed via Terminate(), failed to init, or just not initted yet
GMP_LOG_DEBUG(
"GMP Decode: not initted yet");
mDecoderStatus = GMPDecodeErr;
return;
}
MOZ_ASSERT(mQueuedFrames.IsEmpty());
MOZ_ASSERT(mHost);
GMPVideoFrame* ftmp = nullptr;
GMPErr err = mHost->CreateFrame(kGMPEncodedVideoFrame, &ftmp);
if (err != GMPNoErr) {
GMP_LOG_ERROR(
"%s: CreateFrame failed (%u)!", __PRETTY_FUNCTION__,
static_cast<
unsigned>(err));
mDecoderStatus = err;
return;
}
GMPUniquePtr<GMPVideoEncodedFrame> frame(
static_cast<GMPVideoEncodedFrame*>(ftmp));
err = frame->CreateEmptyFrame(aDecodeData->mImage.size());
if (err != GMPNoErr) {
GMP_LOG_ERROR(
"%s: CreateEmptyFrame failed (%u)!", __PRETTY_FUNCTION__,
static_cast<
unsigned>(err));
mDecoderStatus = err;
return;
}
// XXX At this point, we only will get mode1 data (a single length and a
// buffer) Session_info.cc/etc code needs to change to support mode 0.
*(
reinterpret_cast<uint32_t*>(frame->Buffer())) = frame->Size();
// XXX It'd be wonderful not to have to memcpy the encoded data!
memcpy(frame->Buffer() + 4, aDecodeData->mImage.data() + 4,
frame->Size() - 4);
frame->SetEncodedWidth(aDecodeData->mImage._encodedWidth);
frame->SetEncodedHeight(aDecodeData->mImage._encodedHeight);
frame->SetTimeStamp((aDecodeData->mImage.RtpTimestamp() * 1000ll) /
90);
// rounds down
frame->SetCompleteFrame(
true);
// upstream no longer deals with incomplete frames
frame->SetBufferType(GMP_BufferLength32);
GMPVideoFrameType ft;
int32_t ret =
WebrtcFrameTypeToGmpFrameType(aDecodeData->mImage._frameType, &ft);
if (ret != WEBRTC_VIDEO_CODEC_OK) {
GMP_LOG_ERROR(
"%s: WebrtcFrameTypeToGmpFrameType failed (%u)!",
__PRETTY_FUNCTION__,
static_cast<
unsigned>(ret));
mDecoderStatus = GMPDecodeErr;
return;
}
GMPCodecSpecificInfo info{};
info.mCodecType = kGMPVideoCodecH264;
info.mCodecSpecific.mH264.mSimulcastIdx = 0;
nsTArray<uint8_t> codecSpecificInfo;
codecSpecificInfo.AppendElements((uint8_t*)&info,
sizeof(GMPCodecSpecificInfo));
GMP_LOG_DEBUG(
"GMP Decode: %" PRIu64
", len %zu%s", frame->TimeStamp(),
aDecodeData->mImage.size(),
ft == kGMPKeyFrame ?
", KeyFrame" :
"");
nsresult rv = mGMP->Decode(std::move(frame), aDecodeData->mMissingFrames,
codecSpecificInfo, aDecodeData->mRenderTimeMs);
if (NS_FAILED(rv)) {
GMP_LOG_ERROR(
"%s: Decode failed (rv=%u)!", __PRETTY_FUNCTION__,
static_cast<
unsigned>(rv));
mDecoderStatus = GMPDecodeErr;
return;
}
mDecoderStatus = GMPNoErr;
}
int32_t WebrtcGmpVideoDecoder::RegisterDecodeCompleteCallback(
webrtc::DecodedImageCallback* aCallback) {
MutexAutoLock lock(mCallbackMutex);
mCallback = aCallback;
return WEBRTC_VIDEO_CODEC_OK;
}
int32_t WebrtcGmpVideoDecoder::ReleaseGmp() {
GMP_LOG_DEBUG(
"GMP Released:");
RegisterDecodeCompleteCallback(nullptr);
if (mGMPThread) {
MOZ_ALWAYS_SUCCEEDS(mGMPThread->Dispatch(
NewRunnableMethod(__func__,
this, &WebrtcGmpVideoDecoder::Close_g)));
}
return WEBRTC_VIDEO_CODEC_OK;
}
void WebrtcGmpVideoDecoder::Terminated() {
GMP_LOG_DEBUG(
"GMP Decoder Terminated: %p", (
void*)
this);
GMPVideoDecoderProxy* gmp(mGMP);
mGMP = nullptr;
mHost = nullptr;
mInitting =
false;
if (gmp) {
// Do this last, since this could cause us to be destroyed
gmp->Close();
}
// Could now notify that it's dead
}
void WebrtcGmpVideoDecoder::Decoded(GMPVideoi420Frame* aDecodedFrame) {
// we have two choices here: wrap the frame with a callback that frees
// the data later (risking running out of shmems), or copy the data out
// always. Also, we can only Destroy() the frame on the gmp thread, so
// copying is simplest if expensive.
// I420 size including rounding...
CheckedInt32 length =
(CheckedInt32(aDecodedFrame->Stride(kGMPYPlane)) *
aDecodedFrame->Height()) +
(aDecodedFrame->Stride(kGMPVPlane) + aDecodedFrame->Stride(kGMPUPlane)) *
((aDecodedFrame->Height() + 1) / 2);
int32_t size = length.value();
MOZ_RELEASE_ASSERT(length.isValid() && size > 0);
// Don't use MakeUniqueFallible here, because UniquePtr isn't copyable, and
// the closure below in WrapI420Buffer uses std::function which _is_ copyable.
// We'll alloc the buffer here, so we preserve the "fallible" nature, and
// then hand a shared_ptr, which is copyable, to WrapI420Buffer.
auto* falliblebuffer =
new (std::nothrow) uint8_t[size];
if (falliblebuffer) {
auto buffer = std::shared_ptr<uint8_t>(falliblebuffer);
// This is 3 separate buffers currently anyways, no use in trying to
// see if we can use a single memcpy.
uint8_t* buffer_y = buffer.get();
memcpy(buffer_y, aDecodedFrame->Buffer(kGMPYPlane),
aDecodedFrame->Stride(kGMPYPlane) * aDecodedFrame->Height());
// Should this be aligned, making it non-contiguous? Assume no, this is
// already factored into the strides.
uint8_t* buffer_u =
buffer_y + aDecodedFrame->Stride(kGMPYPlane) * aDecodedFrame->Height();
memcpy(buffer_u, aDecodedFrame->Buffer(kGMPUPlane),
aDecodedFrame->Stride(kGMPUPlane) *
((aDecodedFrame->Height() + 1) / 2));
uint8_t* buffer_v = buffer_u + aDecodedFrame->Stride(kGMPUPlane) *
((aDecodedFrame->Height() + 1) / 2);
memcpy(buffer_v, aDecodedFrame->Buffer(kGMPVPlane),
aDecodedFrame->Stride(kGMPVPlane) *
((aDecodedFrame->Height() + 1) / 2));
MutexAutoLock lock(mCallbackMutex);
if (mCallback) {
// Note: the last parameter to WrapI420Buffer is named no_longer_used,
// but is currently called in the destructor of WrappedYuvBuffer when
// the buffer is "no_longer_used".
rtc::scoped_refptr<webrtc::I420BufferInterface> video_frame_buffer =
webrtc::WrapI420Buffer(
aDecodedFrame->Width(), aDecodedFrame->Height(), buffer_y,
aDecodedFrame->Stride(kGMPYPlane), buffer_u,
aDecodedFrame->Stride(kGMPUPlane), buffer_v,
aDecodedFrame->Stride(kGMPVPlane), [buffer] {});
GMP_LOG_DEBUG(
"GMP Decoded: %" PRIu64, aDecodedFrame->Timestamp());
auto videoFrame =
webrtc::VideoFrame::Builder()
.set_video_frame_buffer(video_frame_buffer)
.set_timestamp_rtp(
// round up
(aDecodedFrame->UpdatedTimestamp() * 90ll + 999) / 1000)
.build();
mPerformanceRecorder.Record(
static_cast<int64_t>(aDecodedFrame->Timestamp()),
[&](DecodeStage& aStage) {
aStage.SetImageFormat(DecodeStage::YUV420P);
aStage.SetResolution(aDecodedFrame->Width(),
aDecodedFrame->Height());
aStage.SetColorDepth(gfx::ColorDepth::COLOR_8);
});
mCallback->Decoded(videoFrame);
}
}
aDecodedFrame->Destroy();
}
}
// namespace mozilla