summaryrefslogtreecommitdiffstats
path: root/dom/media/platforms/agnostic/gmp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/platforms/agnostic/gmp')
-rw-r--r--dom/media/platforms/agnostic/gmp/GMPDecoderModule.cpp94
-rw-r--r--dom/media/platforms/agnostic/gmp/GMPDecoderModule.h58
-rw-r--r--dom/media/platforms/agnostic/gmp/GMPVideoDecoder.cpp489
-rw-r--r--dom/media/platforms/agnostic/gmp/GMPVideoDecoder.h129
-rw-r--r--dom/media/platforms/agnostic/gmp/moz.build24
5 files changed, 794 insertions, 0 deletions
diff --git a/dom/media/platforms/agnostic/gmp/GMPDecoderModule.cpp b/dom/media/platforms/agnostic/gmp/GMPDecoderModule.cpp
new file mode 100644
index 0000000000..a389c9ad0b
--- /dev/null
+++ b/dom/media/platforms/agnostic/gmp/GMPDecoderModule.cpp
@@ -0,0 +1,94 @@
+/* -*- 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 "GMPDecoderModule.h"
+
+#include "DecoderDoctorDiagnostics.h"
+#include "GMPService.h"
+#include "GMPUtils.h"
+#include "GMPVideoDecoder.h"
+#include "MP4Decoder.h"
+#include "MediaDataDecoderProxy.h"
+#include "VPXDecoder.h"
+#include "VideoUtils.h"
+#include "gmp-video-decode.h"
+#include "mozilla/StaticMutex.h"
+#include "nsServiceManagerUtils.h"
+#ifdef XP_WIN
+# include "WMFDecoderModule.h"
+#endif
+
+namespace mozilla {
+
+static already_AddRefed<MediaDataDecoderProxy> CreateDecoderWrapper(
+ GMPVideoDecoderParams&& aParams) {
+ RefPtr<gmp::GeckoMediaPluginService> s(
+ gmp::GeckoMediaPluginService::GetGeckoMediaPluginService());
+ if (!s) {
+ return nullptr;
+ }
+ nsCOMPtr<nsISerialEventTarget> thread(s->GetGMPThread());
+ if (!thread) {
+ return nullptr;
+ }
+
+ RefPtr<MediaDataDecoderProxy> decoder(new MediaDataDecoderProxy(
+ do_AddRef(new GMPVideoDecoder(std::move(aParams))), thread.forget()));
+ return decoder.forget();
+}
+
+already_AddRefed<MediaDataDecoder> GMPDecoderModule::CreateVideoDecoder(
+ const CreateDecoderParams& aParams) {
+ if (!MP4Decoder::IsH264(aParams.mConfig.mMimeType) &&
+ !VPXDecoder::IsVP8(aParams.mConfig.mMimeType) &&
+ !VPXDecoder::IsVP9(aParams.mConfig.mMimeType)) {
+ return nullptr;
+ }
+
+ return CreateDecoderWrapper(GMPVideoDecoderParams(aParams));
+}
+
+already_AddRefed<MediaDataDecoder> GMPDecoderModule::CreateAudioDecoder(
+ const CreateDecoderParams& aParams) {
+ return nullptr;
+}
+
+/* static */
+media::DecodeSupportSet GMPDecoderModule::SupportsMimeType(
+ const nsACString& aMimeType, const nsACString& aApi,
+ const Maybe<nsCString>& aKeySystem) {
+ AutoTArray<nsCString, 2> tags;
+ if (MP4Decoder::IsH264(aMimeType)) {
+ tags.AppendElement("h264"_ns);
+ } else if (VPXDecoder::IsVP9(aMimeType)) {
+ tags.AppendElement("vp9"_ns);
+ } else if (VPXDecoder::IsVP8(aMimeType)) {
+ tags.AppendElement("vp8"_ns);
+ } else {
+ return media::DecodeSupport::Unsupported;
+ }
+
+ // Optional tag for EME GMP plugins.
+ if (aKeySystem) {
+ tags.AppendElement(*aKeySystem);
+ }
+
+ // GMP plugins are always software based.
+ return HaveGMPFor(aApi, tags) ? media::DecodeSupport::SoftwareDecode
+ : media::DecodeSupport::Unsupported;
+}
+
+media::DecodeSupportSet GMPDecoderModule::SupportsMimeType(
+ const nsACString& aMimeType, DecoderDoctorDiagnostics* aDiagnostics) const {
+ return SupportsMimeType(aMimeType, "decode-video"_ns, Nothing());
+}
+
+/* static */
+already_AddRefed<PlatformDecoderModule> GMPDecoderModule::Create() {
+ return MakeAndAddRef<GMPDecoderModule>();
+}
+
+} // namespace mozilla
diff --git a/dom/media/platforms/agnostic/gmp/GMPDecoderModule.h b/dom/media/platforms/agnostic/gmp/GMPDecoderModule.h
new file mode 100644
index 0000000000..1a131dc154
--- /dev/null
+++ b/dom/media/platforms/agnostic/gmp/GMPDecoderModule.h
@@ -0,0 +1,58 @@
+/* -*- 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/. */
+
+#if !defined(GMPDecoderModule_h_)
+# define GMPDecoderModule_h_
+
+# include "PlatformDecoderModule.h"
+# include "mozilla/Maybe.h"
+
+// The special NodeId we use when doing unencrypted decoding using the GMP's
+// decoder. This ensures that each GMP MediaDataDecoder we create doesn't
+// require spinning up a new process, but instead we run all instances of
+// GMP decoders in the one process, to reduce overhead.
+//
+// Note: GMP storage is isolated by NodeId, and non persistent for this
+// special NodeId, and the only way a GMP can communicate with the outside
+// world is through the EME GMP APIs, and we never run EME with this NodeID
+// (because NodeIds are random strings which can't contain the '-' character),
+// so there's no way a malicious GMP can harvest, store, and then report any
+// privacy sensitive data about what users are watching.
+# define SHARED_GMP_DECODING_NODE_ID "gmp-shared-decoding"_ns
+
+namespace mozilla {
+
+class GMPDecoderModule : public PlatformDecoderModule {
+ template <typename T, typename... Args>
+ friend already_AddRefed<T> MakeAndAddRef(Args&&...);
+
+ public:
+ static already_AddRefed<PlatformDecoderModule> Create();
+
+ // Decode thread.
+ already_AddRefed<MediaDataDecoder> CreateVideoDecoder(
+ const CreateDecoderParams& aParams) override;
+
+ // Decode thread.
+ already_AddRefed<MediaDataDecoder> CreateAudioDecoder(
+ const CreateDecoderParams& aParams) override;
+
+ media::DecodeSupportSet SupportsMimeType(
+ const nsACString& aMimeType,
+ DecoderDoctorDiagnostics* aDiagnostics) const override;
+
+ static media::DecodeSupportSet SupportsMimeType(
+ const nsACString& aMimeType, const nsACString& aApi,
+ const Maybe<nsCString>& aKeySystem);
+
+ private:
+ GMPDecoderModule() = default;
+ virtual ~GMPDecoderModule() = default;
+};
+
+} // namespace mozilla
+
+#endif // GMPDecoderModule_h_
diff --git a/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.cpp b/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.cpp
new file mode 100644
index 0000000000..47798fafb0
--- /dev/null
+++ b/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.cpp
@@ -0,0 +1,489 @@
+/* -*- 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 "GMPVideoDecoder.h"
+#include "GMPDecoderModule.h"
+#include "GMPVideoHost.h"
+#include "GMPLog.h"
+#include "MediaData.h"
+#include "mozilla/EndianUtils.h"
+#include "mozilla/StaticPrefs_media.h"
+#include "nsServiceManagerUtils.h"
+#include "AnnexB.h"
+#include "H264.h"
+#include "MP4Decoder.h"
+#include "prsystem.h"
+#include "VPXDecoder.h"
+#include "VideoUtils.h"
+
+namespace mozilla {
+
+#if defined(DEBUG)
+static bool IsOnGMPThread() {
+ nsCOMPtr<mozIGeckoMediaPluginService> mps =
+ do_GetService("@mozilla.org/gecko-media-plugin-service;1");
+ MOZ_ASSERT(mps);
+
+ nsCOMPtr<nsIThread> gmpThread;
+ nsresult rv = mps->GetThread(getter_AddRefs(gmpThread));
+ MOZ_ASSERT(NS_SUCCEEDED(rv) && gmpThread);
+ return gmpThread->IsOnCurrentThread();
+}
+#endif
+
+GMPVideoDecoderParams::GMPVideoDecoderParams(const CreateDecoderParams& aParams)
+ : mConfig(aParams.VideoConfig()),
+ mImageContainer(aParams.mImageContainer),
+ mCrashHelper(aParams.mCrashHelper),
+ mKnowsCompositor(aParams.mKnowsCompositor),
+ mTrackingId(aParams.mTrackingId) {}
+
+nsCString GMPVideoDecoder::GetCodecName() const {
+ if (MP4Decoder::IsH264(mConfig.mMimeType)) {
+ return "h264"_ns;
+ } else if (VPXDecoder::IsVP8(mConfig.mMimeType)) {
+ return "vp8"_ns;
+ } else if (VPXDecoder::IsVP9(mConfig.mMimeType)) {
+ return "vp9"_ns;
+ }
+ return "unknown"_ns;
+}
+
+void GMPVideoDecoder::Decoded(GMPVideoi420Frame* aDecodedFrame) {
+ GMP_LOG_DEBUG("GMPVideoDecoder::Decoded");
+
+ GMPUniquePtr<GMPVideoi420Frame> decodedFrame(aDecodedFrame);
+ MOZ_ASSERT(IsOnGMPThread());
+
+ VideoData::YCbCrBuffer b;
+ for (int i = 0; i < kGMPNumOfPlanes; ++i) {
+ b.mPlanes[i].mData = decodedFrame->Buffer(GMPPlaneType(i));
+ b.mPlanes[i].mStride = decodedFrame->Stride(GMPPlaneType(i));
+ if (i == kGMPYPlane) {
+ b.mPlanes[i].mWidth = decodedFrame->Width();
+ b.mPlanes[i].mHeight = decodedFrame->Height();
+ } else {
+ b.mPlanes[i].mWidth = (decodedFrame->Width() + 1) / 2;
+ b.mPlanes[i].mHeight = (decodedFrame->Height() + 1) / 2;
+ }
+ b.mPlanes[i].mSkip = 0;
+ }
+
+ b.mChromaSubsampling = gfx::ChromaSubsampling::HALF_WIDTH_AND_HEIGHT;
+ b.mYUVColorSpace =
+ DefaultColorSpace({decodedFrame->Width(), decodedFrame->Height()});
+
+ UniquePtr<SampleMetadata> sampleData;
+ if (auto entryHandle = mSamples.Lookup(decodedFrame->Timestamp())) {
+ sampleData = std::move(entryHandle.Data());
+ entryHandle.Remove();
+ } else {
+ GMP_LOG_DEBUG(
+ "GMPVideoDecoder::Decoded(this=%p) missing sample metadata for "
+ "time %" PRIu64,
+ this, decodedFrame->Timestamp());
+ if (mSamples.IsEmpty()) {
+ // If we have no remaining samples in the table, then we have processed
+ // all outstanding decode requests.
+ ProcessReorderQueue(mDecodePromise, __func__);
+ }
+ return;
+ }
+
+ MOZ_ASSERT(sampleData);
+
+ gfx::IntRect pictureRegion(0, 0, decodedFrame->Width(),
+ decodedFrame->Height());
+ RefPtr<VideoData> v = VideoData::CreateAndCopyData(
+ mConfig, mImageContainer, sampleData->mOffset,
+ media::TimeUnit::FromMicroseconds(decodedFrame->UpdatedTimestamp()),
+ media::TimeUnit::FromMicroseconds(decodedFrame->Duration()), b,
+ sampleData->mKeyframe, media::TimeUnit::FromMicroseconds(-1),
+ pictureRegion, mKnowsCompositor);
+ RefPtr<GMPVideoDecoder> self = this;
+ if (v) {
+ mPerformanceRecorder.Record(static_cast<int64_t>(decodedFrame->Timestamp()),
+ [&](DecodeStage& aStage) {
+ aStage.SetImageFormat(DecodeStage::YUV420P);
+ aStage.SetResolution(decodedFrame->Width(),
+ decodedFrame->Height());
+ aStage.SetYUVColorSpace(b.mYUVColorSpace);
+ aStage.SetColorDepth(b.mColorDepth);
+ aStage.SetColorRange(b.mColorRange);
+ });
+
+ if (mReorderFrames) {
+ mReorderQueue.Push(std::move(v));
+ } else {
+ mUnorderedData.AppendElement(std::move(v));
+ }
+
+ if (mSamples.IsEmpty()) {
+ // If we have no remaining samples in the table, then we have processed
+ // all outstanding decode requests.
+ ProcessReorderQueue(mDecodePromise, __func__);
+ }
+ } else {
+ mReorderQueue.Clear();
+ mUnorderedData.Clear();
+ mSamples.Clear();
+ mDecodePromise.RejectIfExists(
+ MediaResult(NS_ERROR_OUT_OF_MEMORY,
+ RESULT_DETAIL("CallBack::CreateAndCopyData")),
+ __func__);
+ }
+}
+
+void GMPVideoDecoder::ReceivedDecodedReferenceFrame(const uint64_t aPictureId) {
+ GMP_LOG_DEBUG("GMPVideoDecoder::ReceivedDecodedReferenceFrame");
+ MOZ_ASSERT(IsOnGMPThread());
+}
+
+void GMPVideoDecoder::ReceivedDecodedFrame(const uint64_t aPictureId) {
+ GMP_LOG_DEBUG("GMPVideoDecoder::ReceivedDecodedFrame");
+ MOZ_ASSERT(IsOnGMPThread());
+}
+
+void GMPVideoDecoder::InputDataExhausted() {
+ GMP_LOG_DEBUG("GMPVideoDecoder::InputDataExhausted");
+ MOZ_ASSERT(IsOnGMPThread());
+ mSamples.Clear();
+ ProcessReorderQueue(mDecodePromise, __func__);
+}
+
+void GMPVideoDecoder::DrainComplete() {
+ GMP_LOG_DEBUG("GMPVideoDecoder::DrainComplete");
+ MOZ_ASSERT(IsOnGMPThread());
+ mSamples.Clear();
+
+ if (mDrainPromise.IsEmpty()) {
+ return;
+ }
+
+ DecodedData results;
+ if (mReorderFrames) {
+ results.SetCapacity(mReorderQueue.Length());
+ while (!mReorderQueue.IsEmpty()) {
+ results.AppendElement(mReorderQueue.Pop());
+ }
+ } else {
+ results = std::move(mUnorderedData);
+ }
+
+ mDrainPromise.Resolve(std::move(results), __func__);
+}
+
+void GMPVideoDecoder::ResetComplete() {
+ GMP_LOG_DEBUG("GMPVideoDecoder::ResetComplete");
+ MOZ_ASSERT(IsOnGMPThread());
+ mPerformanceRecorder.Record(std::numeric_limits<int64_t>::max());
+ mFlushPromise.ResolveIfExists(true, __func__);
+}
+
+void GMPVideoDecoder::Error(GMPErr aErr) {
+ GMP_LOG_DEBUG("GMPVideoDecoder::Error");
+ MOZ_ASSERT(IsOnGMPThread());
+ auto error = MediaResult(aErr == GMPDecodeErr ? NS_ERROR_DOM_MEDIA_DECODE_ERR
+ : NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ RESULT_DETAIL("GMPErr:%x", aErr));
+ mDecodePromise.RejectIfExists(error, __func__);
+ mDrainPromise.RejectIfExists(error, __func__);
+ mFlushPromise.RejectIfExists(error, __func__);
+}
+
+void GMPVideoDecoder::Terminated() {
+ GMP_LOG_DEBUG("GMPVideoDecoder::Terminated");
+ MOZ_ASSERT(IsOnGMPThread());
+ Error(GMPErr::GMPAbortedErr);
+}
+
+void GMPVideoDecoder::ProcessReorderQueue(
+ MozPromiseHolder<DecodePromise>& aPromise, const char* aMethodName) {
+ if (aPromise.IsEmpty()) {
+ return;
+ }
+
+ if (!mReorderFrames) {
+ aPromise.Resolve(std::move(mUnorderedData), aMethodName);
+ return;
+ }
+
+ DecodedData results;
+ size_t availableFrames = mReorderQueue.Length();
+ if (availableFrames > mMaxRefFrames) {
+ size_t resolvedFrames = availableFrames - mMaxRefFrames;
+ results.SetCapacity(resolvedFrames);
+ do {
+ results.AppendElement(mReorderQueue.Pop());
+ } while (--resolvedFrames > 0);
+ }
+
+ aPromise.Resolve(std::move(results), aMethodName);
+}
+
+GMPVideoDecoder::GMPVideoDecoder(const GMPVideoDecoderParams& aParams)
+ : mConfig(aParams.mConfig),
+ mGMP(nullptr),
+ mHost(nullptr),
+ mConvertNALUnitLengths(false),
+ mCrashHelper(aParams.mCrashHelper),
+ mImageContainer(aParams.mImageContainer),
+ mKnowsCompositor(aParams.mKnowsCompositor),
+ mTrackingId(aParams.mTrackingId),
+ mCanDecodeBatch(StaticPrefs::media_gmp_decoder_decode_batch()),
+ mReorderFrames(StaticPrefs::media_gmp_decoder_reorder_frames()) {}
+
+void GMPVideoDecoder::InitTags(nsTArray<nsCString>& aTags) {
+ if (MP4Decoder::IsH264(mConfig.mMimeType)) {
+ aTags.AppendElement("h264"_ns);
+ } else if (VPXDecoder::IsVP8(mConfig.mMimeType)) {
+ aTags.AppendElement("vp8"_ns);
+ } else if (VPXDecoder::IsVP9(mConfig.mMimeType)) {
+ aTags.AppendElement("vp9"_ns);
+ }
+}
+
+nsCString GMPVideoDecoder::GetNodeId() { return SHARED_GMP_DECODING_NODE_ID; }
+
+GMPUniquePtr<GMPVideoEncodedFrame> GMPVideoDecoder::CreateFrame(
+ MediaRawData* aSample) {
+ GMPVideoFrame* ftmp = nullptr;
+ GMPErr err = mHost->CreateFrame(kGMPEncodedVideoFrame, &ftmp);
+ if (GMP_FAILED(err)) {
+ return nullptr;
+ }
+
+ GMPUniquePtr<GMPVideoEncodedFrame> frame(
+ static_cast<GMPVideoEncodedFrame*>(ftmp));
+ err = frame->CreateEmptyFrame(aSample->Size());
+ if (GMP_FAILED(err)) {
+ return nullptr;
+ }
+
+ memcpy(frame->Buffer(), aSample->Data(), frame->Size());
+
+ // Convert 4-byte NAL unit lengths to host-endian 4-byte buffer lengths to
+ // suit the GMP API.
+ if (mConvertNALUnitLengths) {
+ const int kNALLengthSize = 4;
+ uint8_t* buf = frame->Buffer();
+ while (buf < frame->Buffer() + frame->Size() - kNALLengthSize) {
+ uint32_t length = BigEndian::readUint32(buf) + kNALLengthSize;
+ *reinterpret_cast<uint32_t*>(buf) = length;
+ buf += length;
+ }
+ }
+
+ frame->SetBufferType(GMP_BufferLength32);
+
+ frame->SetEncodedWidth(mConfig.mDisplay.width);
+ frame->SetEncodedHeight(mConfig.mDisplay.height);
+ frame->SetTimeStamp(aSample->mTime.ToMicroseconds());
+ frame->SetCompleteFrame(true);
+ frame->SetDuration(aSample->mDuration.ToMicroseconds());
+ frame->SetFrameType(aSample->mKeyframe ? kGMPKeyFrame : kGMPDeltaFrame);
+
+ return frame;
+}
+
+const VideoInfo& GMPVideoDecoder::GetConfig() const { return mConfig; }
+
+void GMPVideoDecoder::GMPInitDone(GMPVideoDecoderProxy* aGMP,
+ GMPVideoHost* aHost) {
+ MOZ_ASSERT(IsOnGMPThread());
+
+ if (!aGMP) {
+ mInitPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
+ return;
+ }
+ MOZ_ASSERT(aHost);
+
+ if (mInitPromise.IsEmpty()) {
+ // GMP must have been shutdown while we were waiting for Init operation
+ // to complete.
+ aGMP->Close();
+ return;
+ }
+
+ bool isOpenH264 = aGMP->GetPluginType() == GMPPluginType::OpenH264;
+
+ GMPVideoCodec codec;
+ memset(&codec, 0, sizeof(codec));
+
+ codec.mGMPApiVersion = kGMPVersion34;
+ nsTArray<uint8_t> codecSpecific;
+ if (MP4Decoder::IsH264(mConfig.mMimeType)) {
+ codec.mCodecType = kGMPVideoCodecH264;
+ codecSpecific.AppendElement(0); // mPacketizationMode.
+ codecSpecific.AppendElements(mConfig.mExtraData->Elements(),
+ mConfig.mExtraData->Length());
+ // OpenH264 expects pseudo-AVCC, but others must be passed
+ // AnnexB for H264.
+ mConvertToAnnexB = !isOpenH264;
+ mMaxRefFrames = H264::ComputeMaxRefFrames(mConfig.mExtraData);
+ } else if (VPXDecoder::IsVP8(mConfig.mMimeType)) {
+ codec.mCodecType = kGMPVideoCodecVP8;
+ } else if (VPXDecoder::IsVP9(mConfig.mMimeType)) {
+ codec.mCodecType = kGMPVideoCodecVP9;
+ } else {
+ // Unrecognized mime type
+ aGMP->Close();
+ mInitPromise.Reject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
+ return;
+ }
+ codec.mWidth = mConfig.mImage.width;
+ codec.mHeight = mConfig.mImage.height;
+ codec.mUseThreadedDecode = StaticPrefs::media_gmp_decoder_multithreaded();
+ codec.mLogLevel = GetGMPLibraryLogLevel();
+
+ nsresult rv =
+ aGMP->InitDecode(codec, codecSpecific, this, PR_GetNumberOfProcessors());
+ if (NS_FAILED(rv)) {
+ aGMP->Close();
+ mInitPromise.Reject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
+ return;
+ }
+
+ mGMP = aGMP;
+ mHost = aHost;
+
+ // GMP implementations have interpreted the meaning of GMP_BufferLength32
+ // differently. The OpenH264 GMP expects GMP_BufferLength32 to behave as
+ // specified in the GMP API, where each buffer is prefixed by a 32-bit
+ // host-endian buffer length that includes the size of the buffer length
+ // field. Other existing GMPs currently expect GMP_BufferLength32 (when
+ // combined with kGMPVideoCodecH264) to mean "like AVCC but restricted to
+ // 4-byte NAL lengths" (i.e. buffer lengths are specified in big-endian
+ // and do not include the length of the buffer length field.
+ mConvertNALUnitLengths = isOpenH264;
+
+ mInitPromise.Resolve(TrackInfo::kVideoTrack, __func__);
+}
+
+RefPtr<MediaDataDecoder::InitPromise> GMPVideoDecoder::Init() {
+ MOZ_ASSERT(IsOnGMPThread());
+
+ mMPS = do_GetService("@mozilla.org/gecko-media-plugin-service;1");
+ MOZ_ASSERT(mMPS);
+
+ RefPtr<InitPromise> promise(mInitPromise.Ensure(__func__));
+
+ nsTArray<nsCString> tags;
+ InitTags(tags);
+ UniquePtr<GetGMPVideoDecoderCallback> callback(new GMPInitDoneCallback(this));
+ if (NS_FAILED(mMPS->GetGMPVideoDecoder(mCrashHelper, &tags, GetNodeId(),
+ std::move(callback)))) {
+ mInitPromise.Reject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
+ }
+
+ return promise;
+}
+
+RefPtr<MediaDataDecoder::DecodePromise> GMPVideoDecoder::Decode(
+ MediaRawData* aSample) {
+ MOZ_ASSERT(IsOnGMPThread());
+
+ RefPtr<MediaRawData> sample(aSample);
+ if (!mGMP) {
+ return DecodePromise::CreateAndReject(
+ MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ RESULT_DETAIL("mGMP not initialized")),
+ __func__);
+ }
+
+ if (mTrackingId) {
+ MediaInfoFlag flag = MediaInfoFlag::None;
+ flag |= (aSample->mKeyframe ? MediaInfoFlag::KeyFrame
+ : MediaInfoFlag::NonKeyFrame);
+ if (mGMP->GetPluginType() == GMPPluginType::OpenH264) {
+ flag |= MediaInfoFlag::SoftwareDecoding;
+ }
+ if (MP4Decoder::IsH264(mConfig.mMimeType)) {
+ flag |= MediaInfoFlag::VIDEO_H264;
+ } else if (VPXDecoder::IsVP8(mConfig.mMimeType)) {
+ flag |= MediaInfoFlag::VIDEO_VP8;
+ } else if (VPXDecoder::IsVP9(mConfig.mMimeType)) {
+ flag |= MediaInfoFlag::VIDEO_VP9;
+ }
+ mPerformanceRecorder.Start(aSample->mTime.ToMicroseconds(),
+ "GMPVideoDecoder"_ns, *mTrackingId, flag);
+ }
+
+ GMPUniquePtr<GMPVideoEncodedFrame> frame = CreateFrame(sample);
+ if (!frame) {
+ return DecodePromise::CreateAndReject(
+ MediaResult(NS_ERROR_OUT_OF_MEMORY,
+ RESULT_DETAIL("CreateFrame returned null")),
+ __func__);
+ }
+
+ uint64_t frameTimestamp = frame->TimeStamp();
+ RefPtr<DecodePromise> p = mDecodePromise.Ensure(__func__);
+ nsTArray<uint8_t> info; // No codec specific per-frame info to pass.
+ nsresult rv = mGMP->Decode(std::move(frame), false, info, 0);
+ if (NS_FAILED(rv)) {
+ mDecodePromise.Reject(MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR,
+ RESULT_DETAIL("mGMP->Decode:%" PRIx32,
+ static_cast<uint32_t>(rv))),
+ __func__);
+ }
+
+ // If we have multiple outstanding frames, we need to track which offset
+ // belongs to which frame. During seek, it is possible to get the same frame
+ // requested twice, if the old frame is still outstanding. We will simply drop
+ // the extra decoded frame and request more input if the last outstanding.
+ mSamples.WithEntryHandle(frameTimestamp, [&](auto entryHandle) {
+ auto sampleData = MakeUnique<SampleMetadata>(sample);
+ entryHandle.InsertOrUpdate(std::move(sampleData));
+ });
+
+ return p;
+}
+
+RefPtr<MediaDataDecoder::FlushPromise> GMPVideoDecoder::Flush() {
+ MOZ_ASSERT(IsOnGMPThread());
+
+ mDecodePromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
+ mDrainPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
+
+ RefPtr<FlushPromise> p = mFlushPromise.Ensure(__func__);
+ if (!mGMP || NS_FAILED(mGMP->Reset())) {
+ // Abort the flush.
+ mPerformanceRecorder.Record(std::numeric_limits<int64_t>::max());
+ mFlushPromise.Resolve(true, __func__);
+ }
+ return p;
+}
+
+RefPtr<MediaDataDecoder::DecodePromise> GMPVideoDecoder::Drain() {
+ MOZ_ASSERT(IsOnGMPThread());
+
+ MOZ_ASSERT(mDecodePromise.IsEmpty(), "Must wait for decoding to complete");
+
+ RefPtr<DecodePromise> p = mDrainPromise.Ensure(__func__);
+ if (!mGMP || NS_FAILED(mGMP->Drain())) {
+ mDrainPromise.Resolve(DecodedData(), __func__);
+ }
+
+ return p;
+}
+
+RefPtr<ShutdownPromise> GMPVideoDecoder::Shutdown() {
+ mInitPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
+ mFlushPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
+
+ // Note that this *may* be called from the proxy thread also.
+ // TODO: If that's the case, then this code is racy.
+ if (!mGMP) {
+ return ShutdownPromise::CreateAndResolve(true, __func__);
+ }
+ // Note this unblocks flush and drain operations waiting for callbacks.
+ mGMP->Close();
+ mGMP = nullptr;
+ return ShutdownPromise::CreateAndResolve(true, __func__);
+}
+
+} // namespace mozilla
diff --git a/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.h b/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.h
new file mode 100644
index 0000000000..1f0f59c685
--- /dev/null
+++ b/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.h
@@ -0,0 +1,129 @@
+/* -*- 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 "mozilla/layers/KnowsCompositor.h"
+#if !defined(GMPVideoDecoder_h_)
+# define GMPVideoDecoder_h_
+
+# include "GMPVideoDecoderProxy.h"
+# include "ImageContainer.h"
+# include "MediaDataDecoderProxy.h"
+# include "MediaInfo.h"
+# include "PerformanceRecorder.h"
+# include "PlatformDecoderModule.h"
+# include "ReorderQueue.h"
+# include "mozIGeckoMediaPluginService.h"
+# include "nsClassHashtable.h"
+
+namespace mozilla {
+
+struct MOZ_STACK_CLASS GMPVideoDecoderParams {
+ explicit GMPVideoDecoderParams(const CreateDecoderParams& aParams);
+
+ const VideoInfo& mConfig;
+ layers::ImageContainer* mImageContainer;
+ GMPCrashHelper* mCrashHelper;
+ layers::KnowsCompositor* mKnowsCompositor;
+ const Maybe<TrackingId> mTrackingId;
+};
+
+DDLoggedTypeDeclNameAndBase(GMPVideoDecoder, MediaDataDecoder);
+
+class GMPVideoDecoder final : public MediaDataDecoder,
+ public GMPVideoDecoderCallbackProxy,
+ public DecoderDoctorLifeLogger<GMPVideoDecoder> {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GMPVideoDecoder, final);
+
+ explicit GMPVideoDecoder(const GMPVideoDecoderParams& aParams);
+
+ RefPtr<InitPromise> Init() override;
+ RefPtr<DecodePromise> Decode(MediaRawData* aSample) override;
+ RefPtr<DecodePromise> Drain() override;
+ RefPtr<FlushPromise> Flush() override;
+ RefPtr<ShutdownPromise> Shutdown() override;
+ nsCString GetDescriptionName() const override {
+ return "gmp video decoder"_ns;
+ }
+ nsCString GetCodecName() const override;
+ ConversionRequired NeedsConversion() const override {
+ return mConvertToAnnexB ? ConversionRequired::kNeedAnnexB
+ : ConversionRequired::kNeedAVCC;
+ }
+ bool CanDecodeBatch() const override { return mCanDecodeBatch; }
+
+ // GMPVideoDecoderCallbackProxy
+ // All those methods are called on the GMP thread.
+ void Decoded(GMPVideoi420Frame* aDecodedFrame) override;
+ void ReceivedDecodedReferenceFrame(const uint64_t aPictureId) override;
+ void ReceivedDecodedFrame(const uint64_t aPictureId) override;
+ void InputDataExhausted() override;
+ void DrainComplete() override;
+ void ResetComplete() override;
+ void Error(GMPErr aErr) override;
+ void Terminated() override;
+
+ protected:
+ virtual void InitTags(nsTArray<nsCString>& aTags);
+ virtual nsCString GetNodeId();
+ virtual GMPUniquePtr<GMPVideoEncodedFrame> CreateFrame(MediaRawData* aSample);
+ virtual const VideoInfo& GetConfig() const;
+ void ProcessReorderQueue(MozPromiseHolder<DecodePromise>& aPromise,
+ const char* aMethodName);
+
+ private:
+ ~GMPVideoDecoder() = default;
+
+ class GMPInitDoneCallback : public GetGMPVideoDecoderCallback {
+ public:
+ explicit GMPInitDoneCallback(GMPVideoDecoder* aDecoder)
+ : mDecoder(aDecoder) {}
+
+ void Done(GMPVideoDecoderProxy* aGMP, GMPVideoHost* aHost) override {
+ mDecoder->GMPInitDone(aGMP, aHost);
+ }
+
+ private:
+ RefPtr<GMPVideoDecoder> mDecoder;
+ };
+ void GMPInitDone(GMPVideoDecoderProxy* aGMP, GMPVideoHost* aHost);
+
+ const VideoInfo mConfig;
+ nsCOMPtr<mozIGeckoMediaPluginService> mMPS;
+ GMPVideoDecoderProxy* mGMP;
+ GMPVideoHost* mHost;
+ bool mConvertNALUnitLengths;
+ MozPromiseHolder<InitPromise> mInitPromise;
+ RefPtr<GMPCrashHelper> mCrashHelper;
+
+ struct SampleMetadata {
+ explicit SampleMetadata(MediaRawData* aSample)
+ : mOffset(aSample->mOffset), mKeyframe(aSample->mKeyframe) {}
+ int64_t mOffset;
+ bool mKeyframe;
+ };
+
+ nsClassHashtable<nsUint64HashKey, SampleMetadata> mSamples;
+ RefPtr<layers::ImageContainer> mImageContainer;
+ RefPtr<layers::KnowsCompositor> mKnowsCompositor;
+ PerformanceRecorderMulti<DecodeStage> mPerformanceRecorder;
+ const Maybe<TrackingId> mTrackingId;
+
+ uint32_t mMaxRefFrames = 0;
+ ReorderQueue mReorderQueue;
+ DecodedData mUnorderedData;
+
+ MozPromiseHolder<DecodePromise> mDecodePromise;
+ MozPromiseHolder<DecodePromise> mDrainPromise;
+ MozPromiseHolder<FlushPromise> mFlushPromise;
+ bool mConvertToAnnexB = false;
+ bool mCanDecodeBatch = false;
+ bool mReorderFrames = true;
+};
+
+} // namespace mozilla
+
+#endif // GMPVideoDecoder_h_
diff --git a/dom/media/platforms/agnostic/gmp/moz.build b/dom/media/platforms/agnostic/gmp/moz.build
new file mode 100644
index 0000000000..8e49f4a0e8
--- /dev/null
+++ b/dom/media/platforms/agnostic/gmp/moz.build
@@ -0,0 +1,24 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+LOCAL_INCLUDES += [
+ "/dom/media/gmp", # for GMPLog.h,
+]
+
+EXPORTS += [
+ "GMPDecoderModule.h",
+ "GMPVideoDecoder.h",
+]
+
+UNIFIED_SOURCES += [
+ "GMPDecoderModule.cpp",
+ "GMPVideoDecoder.cpp",
+]
+
+# GMPVideoEncodedFrameImpl.h needs IPC
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"