diff options
Diffstat (limited to 'dom/media/platforms/agnostic/gmp')
-rw-r--r-- | dom/media/platforms/agnostic/gmp/GMPAudioDecoder.cpp | 306 | ||||
-rw-r--r-- | dom/media/platforms/agnostic/gmp/GMPAudioDecoder.h | 112 | ||||
-rw-r--r-- | dom/media/platforms/agnostic/gmp/GMPDecoderModule.cpp | 172 | ||||
-rw-r--r-- | dom/media/platforms/agnostic/gmp/GMPDecoderModule.h | 57 | ||||
-rw-r--r-- | dom/media/platforms/agnostic/gmp/GMPVideoDecoder.cpp | 387 | ||||
-rw-r--r-- | dom/media/platforms/agnostic/gmp/GMPVideoDecoder.h | 122 | ||||
-rw-r--r-- | dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.cpp | 90 | ||||
-rw-r--r-- | dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.h | 181 | ||||
-rw-r--r-- | dom/media/platforms/agnostic/gmp/moz.build | 24 |
9 files changed, 1451 insertions, 0 deletions
diff --git a/dom/media/platforms/agnostic/gmp/GMPAudioDecoder.cpp b/dom/media/platforms/agnostic/gmp/GMPAudioDecoder.cpp new file mode 100644 index 000000000..d863d44d4 --- /dev/null +++ b/dom/media/platforms/agnostic/gmp/GMPAudioDecoder.cpp @@ -0,0 +1,306 @@ +/* -*- 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 "GMPAudioDecoder.h" +#include "nsServiceManagerUtils.h" +#include "MediaInfo.h" +#include "GMPDecoderModule.h" +#include "nsPrintfCString.h" + +namespace mozilla { + +#if defined(DEBUG) +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 NS_GetCurrentThread() == gmpThread; +} +#endif + +void +AudioCallbackAdapter::Decoded(const nsTArray<int16_t>& aPCM, uint64_t aTimeStamp, uint32_t aChannels, uint32_t aRate) +{ + MOZ_ASSERT(IsOnGMPThread()); + + if (aRate == 0 || aChannels == 0) { + mCallback->Error(MediaResult( + NS_ERROR_DOM_MEDIA_FATAL_ERR, + RESULT_DETAIL( + "Invalid rate or num channels returned on GMP audio samples"))); + return; + } + + size_t numFrames = aPCM.Length() / aChannels; + MOZ_ASSERT((aPCM.Length() % aChannels) == 0); + AlignedAudioBuffer audioData(aPCM.Length()); + if (!audioData) { + mCallback->Error( + MediaResult(NS_ERROR_OUT_OF_MEMORY, + RESULT_DETAIL("Unable to allocate audio buffer"))); + return; + } + + for (size_t i = 0; i < aPCM.Length(); ++i) { + audioData[i] = AudioSampleToFloat(aPCM[i]); + } + + if (mMustRecaptureAudioPosition) { + mAudioFrameSum = 0; + auto timestamp = UsecsToFrames(aTimeStamp, aRate); + if (!timestamp.isValid()) { + mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_OVERFLOW_ERR, + RESULT_DETAIL("Invalid timestamp"))); + return; + } + mAudioFrameOffset = timestamp.value(); + mMustRecaptureAudioPosition = false; + } + + auto timestamp = FramesToUsecs(mAudioFrameOffset + mAudioFrameSum, aRate); + if (!timestamp.isValid()) { + mCallback->Error( + MediaResult(NS_ERROR_DOM_MEDIA_OVERFLOW_ERR, + RESULT_DETAIL("Invalid timestamp on audio samples"))); + return; + } + mAudioFrameSum += numFrames; + + auto duration = FramesToUsecs(numFrames, aRate); + if (!duration.isValid()) { + mCallback->Error( + MediaResult(NS_ERROR_DOM_MEDIA_OVERFLOW_ERR, + RESULT_DETAIL("Invalid duration on audio samples"))); + return; + } + + RefPtr<AudioData> audio(new AudioData(mLastStreamOffset, + timestamp.value(), + duration.value(), + numFrames, + Move(audioData), + aChannels, + aRate)); + +#ifdef LOG_SAMPLE_DECODE + LOG("Decoded audio sample! timestamp=%lld duration=%lld currentLength=%u", + timestamp, duration, currentLength); +#endif + + mCallback->Output(audio); +} + +void +AudioCallbackAdapter::InputDataExhausted() +{ + MOZ_ASSERT(IsOnGMPThread()); + mCallback->InputExhausted(); +} + +void +AudioCallbackAdapter::DrainComplete() +{ + MOZ_ASSERT(IsOnGMPThread()); + mCallback->DrainComplete(); +} + +void +AudioCallbackAdapter::ResetComplete() +{ + MOZ_ASSERT(IsOnGMPThread()); + mMustRecaptureAudioPosition = true; + mCallback->FlushComplete(); +} + +void +AudioCallbackAdapter::Error(GMPErr aErr) +{ + MOZ_ASSERT(IsOnGMPThread()); + mCallback->Error(MediaResult(aErr == GMPDecodeErr + ? NS_ERROR_DOM_MEDIA_DECODE_ERR + : NS_ERROR_DOM_MEDIA_FATAL_ERR, + RESULT_DETAIL("GMPErr:%x", aErr))); +} + +void +AudioCallbackAdapter::Terminated() +{ + mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, + RESULT_DETAIL("Audio GMP decoder terminated."))); +} + +GMPAudioDecoderParams::GMPAudioDecoderParams(const CreateDecoderParams& aParams) + : mConfig(aParams.AudioConfig()) + , mTaskQueue(aParams.mTaskQueue) + , mCallback(nullptr) + , mAdapter(nullptr) + , mCrashHelper(aParams.mCrashHelper) +{} + +GMPAudioDecoderParams& +GMPAudioDecoderParams::WithCallback(MediaDataDecoderProxy* aWrapper) +{ + MOZ_ASSERT(aWrapper); + MOZ_ASSERT(!mCallback); // Should only be called once per instance. + mCallback = aWrapper->Callback(); + mAdapter = nullptr; + return *this; +} + +GMPAudioDecoderParams& +GMPAudioDecoderParams::WithAdapter(AudioCallbackAdapter* aAdapter) +{ + MOZ_ASSERT(aAdapter); + MOZ_ASSERT(!mAdapter); // Should only be called once per instance. + mCallback = aAdapter->Callback(); + mAdapter = aAdapter; + return *this; +} + +GMPAudioDecoder::GMPAudioDecoder(const GMPAudioDecoderParams& aParams) + : mConfig(aParams.mConfig) + , mCallback(aParams.mCallback) + , mGMP(nullptr) + , mAdapter(aParams.mAdapter) + , mCrashHelper(aParams.mCrashHelper) +{ + MOZ_ASSERT(!mAdapter || mCallback == mAdapter->Callback()); + if (!mAdapter) { + mAdapter = new AudioCallbackAdapter(mCallback); + } +} + +void +GMPAudioDecoder::InitTags(nsTArray<nsCString>& aTags) +{ + aTags.AppendElement(NS_LITERAL_CSTRING("aac")); + const Maybe<nsCString> gmp( + GMPDecoderModule::PreferredGMP(NS_LITERAL_CSTRING("audio/mp4a-latm"))); + if (gmp.isSome()) { + aTags.AppendElement(gmp.value()); + } +} + +nsCString +GMPAudioDecoder::GetNodeId() +{ + return SHARED_GMP_DECODING_NODE_ID; +} + +void +GMPAudioDecoder::GMPInitDone(GMPAudioDecoderProxy* aGMP) +{ + MOZ_ASSERT(IsOnGMPThread()); + + if (!aGMP) { + mInitPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); + return; + } + if (mInitPromise.IsEmpty()) { + // GMP must have been shutdown while we were waiting for Init operation + // to complete. + aGMP->Close(); + return; + } + nsTArray<uint8_t> codecSpecific; + codecSpecific.AppendElements(mConfig.mCodecSpecificConfig->Elements(), + mConfig.mCodecSpecificConfig->Length()); + + nsresult rv = aGMP->InitDecode(kGMPAudioCodecAAC, + mConfig.mChannels, + mConfig.mBitDepth, + mConfig.mRate, + codecSpecific, + mAdapter); + if (NS_FAILED(rv)) { + aGMP->Close(); + mInitPromise.Reject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); + return; + } + + mGMP = aGMP; + mInitPromise.Resolve(TrackInfo::kAudioTrack, __func__); +} + +RefPtr<MediaDataDecoder::InitPromise> +GMPAudioDecoder::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<GetGMPAudioDecoderCallback> callback(new GMPInitDoneCallback(this)); + if (NS_FAILED(mMPS->GetGMPAudioDecoder(mCrashHelper, &tags, GetNodeId(), Move(callback)))) { + mInitPromise.Reject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); + } + + return promise; +} + +void +GMPAudioDecoder::Input(MediaRawData* aSample) +{ + MOZ_ASSERT(IsOnGMPThread()); + + RefPtr<MediaRawData> sample(aSample); + if (!mGMP) { + mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, + RESULT_DETAIL("mGMP not initialized"))); + return; + } + + mAdapter->SetLastStreamOffset(sample->mOffset); + + gmp::GMPAudioSamplesImpl samples(sample, mConfig.mChannels, mConfig.mRate); + nsresult rv = mGMP->Decode(samples); + if (NS_FAILED(rv)) { + mCallback->Error(MediaResult(rv, __func__)); + } +} + +void +GMPAudioDecoder::Flush() +{ + MOZ_ASSERT(IsOnGMPThread()); + + if (!mGMP || NS_FAILED(mGMP->Reset())) { + // Abort the flush. + mCallback->FlushComplete(); + } +} + +void +GMPAudioDecoder::Drain() +{ + MOZ_ASSERT(IsOnGMPThread()); + + if (!mGMP || NS_FAILED(mGMP->Drain())) { + mCallback->DrainComplete(); + } +} + +void +GMPAudioDecoder::Shutdown() +{ + mInitPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__); + if (!mGMP) { + return; + } + // Note this unblocks flush and drain operations waiting for callbacks. + mGMP->Close(); + mGMP = nullptr; +} + +} // namespace mozilla diff --git a/dom/media/platforms/agnostic/gmp/GMPAudioDecoder.h b/dom/media/platforms/agnostic/gmp/GMPAudioDecoder.h new file mode 100644 index 000000000..90e3ebdb6 --- /dev/null +++ b/dom/media/platforms/agnostic/gmp/GMPAudioDecoder.h @@ -0,0 +1,112 @@ +/* -*- 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(GMPAudioDecoder_h_) +#define GMPAudioDecoder_h_ + +#include "GMPAudioDecoderProxy.h" +#include "MediaDataDecoderProxy.h" +#include "PlatformDecoderModule.h" +#include "mozIGeckoMediaPluginService.h" +#include "nsAutoPtr.h" + +namespace mozilla { + +class AudioCallbackAdapter : public GMPAudioDecoderCallbackProxy { +public: + explicit AudioCallbackAdapter(MediaDataDecoderCallbackProxy* aCallback) + : mCallback(aCallback) + , mLastStreamOffset(0) + , mAudioFrameSum(0) + , mAudioFrameOffset(0) + , mMustRecaptureAudioPosition(true) + {} + + MediaDataDecoderCallbackProxy* Callback() const { return mCallback; } + + // GMPAudioDecoderCallbackProxy + void Decoded(const nsTArray<int16_t>& aPCM, uint64_t aTimeStamp, uint32_t aChannels, uint32_t aRate) override; + void InputDataExhausted() override; + void DrainComplete() override; + void ResetComplete() override; + void Error(GMPErr aErr) override; + void Terminated() override; + + void SetLastStreamOffset(int64_t aStreamOffset) { + mLastStreamOffset = aStreamOffset; + } + +private: + MediaDataDecoderCallbackProxy* mCallback; + int64_t mLastStreamOffset; + + int64_t mAudioFrameSum; + int64_t mAudioFrameOffset; + bool mMustRecaptureAudioPosition; +}; + +struct GMPAudioDecoderParams { + explicit GMPAudioDecoderParams(const CreateDecoderParams& aParams); + GMPAudioDecoderParams& WithCallback(MediaDataDecoderProxy* aWrapper); + GMPAudioDecoderParams& WithAdapter(AudioCallbackAdapter* aAdapter); + + const AudioInfo& mConfig; + TaskQueue* mTaskQueue; + MediaDataDecoderCallbackProxy* mCallback; + AudioCallbackAdapter* mAdapter; + RefPtr<GMPCrashHelper> mCrashHelper; +}; + +class GMPAudioDecoder : public MediaDataDecoder { +public: + explicit GMPAudioDecoder(const GMPAudioDecoderParams& aParams); + + RefPtr<InitPromise> Init() override; + void Input(MediaRawData* aSample) override; + void Flush() override; + void Drain() override; + void Shutdown() override; + const char* GetDescriptionName() const override + { + return "GMP audio decoder"; + } + +protected: + virtual void InitTags(nsTArray<nsCString>& aTags); + virtual nsCString GetNodeId(); + +private: + + class GMPInitDoneCallback : public GetGMPAudioDecoderCallback + { + public: + explicit GMPInitDoneCallback(GMPAudioDecoder* aDecoder) + : mDecoder(aDecoder) + { + } + + void Done(GMPAudioDecoderProxy* aGMP) override + { + mDecoder->GMPInitDone(aGMP); + } + + private: + RefPtr<GMPAudioDecoder> mDecoder; + }; + void GMPInitDone(GMPAudioDecoderProxy* aGMP); + + const AudioInfo mConfig; + MediaDataDecoderCallbackProxy* mCallback; + nsCOMPtr<mozIGeckoMediaPluginService> mMPS; + GMPAudioDecoderProxy* mGMP; + nsAutoPtr<AudioCallbackAdapter> mAdapter; + MozPromiseHolder<InitPromise> mInitPromise; + RefPtr<GMPCrashHelper> mCrashHelper; +}; + +} // namespace mozilla + +#endif // GMPAudioDecoder_h_ diff --git a/dom/media/platforms/agnostic/gmp/GMPDecoderModule.cpp b/dom/media/platforms/agnostic/gmp/GMPDecoderModule.cpp new file mode 100644 index 000000000..cc53d2c93 --- /dev/null +++ b/dom/media/platforms/agnostic/gmp/GMPDecoderModule.cpp @@ -0,0 +1,172 @@ +/* -*- 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 "GMPAudioDecoder.h" +#include "GMPVideoDecoder.h" +#include "GMPUtils.h" +#include "MediaDataDecoderProxy.h" +#include "MediaPrefs.h" +#include "VideoUtils.h" +#include "mozIGeckoMediaPluginService.h" +#include "nsServiceManagerUtils.h" +#include "mozilla/StaticMutex.h" +#include "gmp-audio-decode.h" +#include "gmp-video-decode.h" +#include "MP4Decoder.h" +#include "VPXDecoder.h" +#ifdef XP_WIN +#include "WMFDecoderModule.h" +#endif + +namespace mozilla { + +GMPDecoderModule::GMPDecoderModule() +{ +} + +GMPDecoderModule::~GMPDecoderModule() +{ +} + +static already_AddRefed<MediaDataDecoderProxy> +CreateDecoderWrapper(MediaDataDecoderCallback* aCallback) +{ + RefPtr<gmp::GeckoMediaPluginService> s(gmp::GeckoMediaPluginService::GetGeckoMediaPluginService()); + if (!s) { + return nullptr; + } + RefPtr<AbstractThread> thread(s->GetAbstractGMPThread()); + if (!thread) { + return nullptr; + } + RefPtr<MediaDataDecoderProxy> decoder(new MediaDataDecoderProxy(thread.forget(), aCallback)); + 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; + } + + if (aParams.mDiagnostics) { + const Maybe<nsCString> preferredGMP = PreferredGMP(aParams.mConfig.mMimeType); + if (preferredGMP.isSome()) { + aParams.mDiagnostics->SetGMP(preferredGMP.value()); + } + } + + RefPtr<MediaDataDecoderProxy> wrapper = CreateDecoderWrapper(aParams.mCallback); + auto params = GMPVideoDecoderParams(aParams).WithCallback(wrapper); + wrapper->SetProxyTarget(new GMPVideoDecoder(params)); + return wrapper.forget(); +} + +already_AddRefed<MediaDataDecoder> +GMPDecoderModule::CreateAudioDecoder(const CreateDecoderParams& aParams) +{ + if (!aParams.mConfig.mMimeType.EqualsLiteral("audio/mp4a-latm")) { + return nullptr; + } + + if (aParams.mDiagnostics) { + const Maybe<nsCString> preferredGMP = PreferredGMP(aParams.mConfig.mMimeType); + if (preferredGMP.isSome()) { + aParams.mDiagnostics->SetGMP(preferredGMP.value()); + } + } + + RefPtr<MediaDataDecoderProxy> wrapper = CreateDecoderWrapper(aParams.mCallback); + auto params = GMPAudioDecoderParams(aParams).WithCallback(wrapper); + wrapper->SetProxyTarget(new GMPAudioDecoder(params)); + return wrapper.forget(); +} + +PlatformDecoderModule::ConversionRequired +GMPDecoderModule::DecoderNeedsConversion(const TrackInfo& aConfig) const +{ + // GMPVideoCodecType::kGMPVideoCodecH264 specifies that encoded frames must be in AVCC format. + if (aConfig.IsVideo() && MP4Decoder::IsH264(aConfig.mMimeType)) { + return ConversionRequired::kNeedAVCC; + } else { + return ConversionRequired::kNeedNone; + } +} + +/* static */ +const Maybe<nsCString> +GMPDecoderModule::PreferredGMP(const nsACString& aMimeType) +{ + Maybe<nsCString> rv; + if (aMimeType.EqualsLiteral("audio/mp4a-latm")) { + switch (MediaPrefs::GMPAACPreferred()) { + case 1: rv.emplace(kEMEKeySystemClearkey); break; + case 2: rv.emplace(kEMEKeySystemPrimetime); break; + default: break; + } + } + + if (MP4Decoder::IsH264(aMimeType)) { + switch (MediaPrefs::GMPH264Preferred()) { + case 1: rv.emplace(kEMEKeySystemClearkey); break; + case 2: rv.emplace(kEMEKeySystemPrimetime); break; + default: break; + } + } + + return rv; +} + +/* static */ +bool +GMPDecoderModule::SupportsMimeType(const nsACString& aMimeType, + const Maybe<nsCString>& aGMP) +{ + if (aGMP.isNothing()) { + return false; + } + + if (MP4Decoder::IsH264(aMimeType)) { + return HaveGMPFor(NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER), + { NS_LITERAL_CSTRING("h264"), aGMP.value()}); + } + + if (VPXDecoder::IsVP9(aMimeType)) { + return HaveGMPFor(NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER), + { NS_LITERAL_CSTRING("vp9"), aGMP.value()}); + } + + if (VPXDecoder::IsVP8(aMimeType)) { + return HaveGMPFor(NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER), + { NS_LITERAL_CSTRING("vp8"), aGMP.value()}); + } + + if (MP4Decoder::IsAAC(aMimeType)) { + return HaveGMPFor(NS_LITERAL_CSTRING(GMP_API_AUDIO_DECODER), + { NS_LITERAL_CSTRING("aac"), aGMP.value()}); + } + + return false; +} + +bool +GMPDecoderModule::SupportsMimeType(const nsACString& aMimeType, + DecoderDoctorDiagnostics* aDiagnostics) const +{ + const Maybe<nsCString> preferredGMP = PreferredGMP(aMimeType); + bool rv = SupportsMimeType(aMimeType, preferredGMP); + if (rv && aDiagnostics && preferredGMP.isSome()) { + aDiagnostics->SetGMP(preferredGMP.value()); + } + return rv; +} + +} // 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 000000000..b501ecb54 --- /dev/null +++ b/dom/media/platforms/agnostic/gmp/GMPDecoderModule.h @@ -0,0 +1,57 @@ +/* -*- 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 NS_LITERAL_CSTRING("gmp-shared-decoding") + +namespace mozilla { + +class GMPDecoderModule : public PlatformDecoderModule { +public: + GMPDecoderModule(); + + virtual ~GMPDecoderModule(); + + // Decode thread. + already_AddRefed<MediaDataDecoder> + CreateVideoDecoder(const CreateDecoderParams& aParams) override; + + // Decode thread. + already_AddRefed<MediaDataDecoder> + CreateAudioDecoder(const CreateDecoderParams& aParams) override; + + ConversionRequired + DecoderNeedsConversion(const TrackInfo& aConfig) const override; + + bool + SupportsMimeType(const nsACString& aMimeType, + DecoderDoctorDiagnostics* aDiagnostics) const override; + + static const Maybe<nsCString> PreferredGMP(const nsACString& aMimeType); + + static bool SupportsMimeType(const nsACString& aMimeType, + const Maybe<nsCString>& aGMP); +}; + +} // 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 000000000..912b88ce1 --- /dev/null +++ b/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.cpp @@ -0,0 +1,387 @@ +/* -*- 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 "GMPVideoHost.h" +#include "mozilla/EndianUtils.h" +#include "prsystem.h" +#include "MediaData.h" +#include "GMPDecoderModule.h" +#include "VPXDecoder.h" + +namespace mozilla { + +#if defined(DEBUG) +extern bool IsOnGMPThread(); +#endif + +void +VideoCallbackAdapter::Decoded(GMPVideoi420Frame* aDecodedFrame) +{ + 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].mOffset = 0; + b.mPlanes[i].mSkip = 0; + } + + gfx::IntRect pictureRegion(0, 0, decodedFrame->Width(), decodedFrame->Height()); + RefPtr<VideoData> v = + VideoData::CreateAndCopyData(mVideoInfo, + mImageContainer, + mLastStreamOffset, + decodedFrame->Timestamp(), + decodedFrame->Duration(), + b, + false, + -1, + pictureRegion); + if (v) { + mCallback->Output(v); + } else { + mCallback->Error(MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__)); + } +} + +void +VideoCallbackAdapter::ReceivedDecodedReferenceFrame(const uint64_t aPictureId) +{ + MOZ_ASSERT(IsOnGMPThread()); +} + +void +VideoCallbackAdapter::ReceivedDecodedFrame(const uint64_t aPictureId) +{ + MOZ_ASSERT(IsOnGMPThread()); +} + +void +VideoCallbackAdapter::InputDataExhausted() +{ + MOZ_ASSERT(IsOnGMPThread()); + mCallback->InputExhausted(); +} + +void +VideoCallbackAdapter::DrainComplete() +{ + MOZ_ASSERT(IsOnGMPThread()); + mCallback->DrainComplete(); +} + +void +VideoCallbackAdapter::ResetComplete() +{ + MOZ_ASSERT(IsOnGMPThread()); + mCallback->FlushComplete(); +} + +void +VideoCallbackAdapter::Error(GMPErr aErr) +{ + MOZ_ASSERT(IsOnGMPThread()); + mCallback->Error(MediaResult(aErr == GMPDecodeErr + ? NS_ERROR_DOM_MEDIA_DECODE_ERR + : NS_ERROR_DOM_MEDIA_FATAL_ERR, + RESULT_DETAIL("GMPErr:%x", aErr))); +} + +void +VideoCallbackAdapter::Terminated() +{ + // Note that this *may* be called from the proxy thread also. + mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, + RESULT_DETAIL("Video GMP decoder terminated."))); +} + +GMPVideoDecoderParams::GMPVideoDecoderParams(const CreateDecoderParams& aParams) + : mConfig(aParams.VideoConfig()) + , mTaskQueue(aParams.mTaskQueue) + , mCallback(nullptr) + , mAdapter(nullptr) + , mImageContainer(aParams.mImageContainer) + , mLayersBackend(aParams.GetLayersBackend()) + , mCrashHelper(aParams.mCrashHelper) +{} + +GMPVideoDecoderParams& +GMPVideoDecoderParams::WithCallback(MediaDataDecoderProxy* aWrapper) +{ + MOZ_ASSERT(aWrapper); + MOZ_ASSERT(!mCallback); // Should only be called once per instance. + mCallback = aWrapper->Callback(); + mAdapter = nullptr; + return *this; +} + +GMPVideoDecoderParams& +GMPVideoDecoderParams::WithAdapter(VideoCallbackAdapter* aAdapter) +{ + MOZ_ASSERT(aAdapter); + MOZ_ASSERT(!mAdapter); // Should only be called once per instance. + mCallback = aAdapter->Callback(); + mAdapter = aAdapter; + return *this; +} + +GMPVideoDecoder::GMPVideoDecoder(const GMPVideoDecoderParams& aParams) + : mConfig(aParams.mConfig) + , mCallback(aParams.mCallback) + , mGMP(nullptr) + , mHost(nullptr) + , mAdapter(aParams.mAdapter) + , mConvertNALUnitLengths(false) + , mCrashHelper(aParams.mCrashHelper) +{ + MOZ_ASSERT(!mAdapter || mCallback == mAdapter->Callback()); + if (!mAdapter) { + mAdapter = new VideoCallbackAdapter(mCallback, + VideoInfo(mConfig.mDisplay.width, + mConfig.mDisplay.height), + aParams.mImageContainer); + } +} + +void +GMPVideoDecoder::InitTags(nsTArray<nsCString>& aTags) +{ + if (MP4Decoder::IsH264(mConfig.mMimeType)) { + aTags.AppendElement(NS_LITERAL_CSTRING("h264")); + const Maybe<nsCString> gmp( + GMPDecoderModule::PreferredGMP(NS_LITERAL_CSTRING("video/avc"))); + if (gmp.isSome()) { + aTags.AppendElement(gmp.value()); + } + } else if (VPXDecoder::IsVP8(mConfig.mMimeType)) { + aTags.AppendElement(NS_LITERAL_CSTRING("vp8")); + } else if (VPXDecoder::IsVP9(mConfig.mMimeType)) { + aTags.AppendElement(NS_LITERAL_CSTRING("vp9")); + } +} + +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)) { + mCallback->Error(MediaResult(NS_ERROR_OUT_OF_MEMORY, + RESULT_DETAIL("Host::CreateFrame:%x", err))); + return nullptr; + } + + GMPUniquePtr<GMPVideoEncodedFrame> frame(static_cast<GMPVideoEncodedFrame*>(ftmp)); + err = frame->CreateEmptyFrame(aSample->Size()); + if (GMP_FAILED(err)) { + mCallback->Error(MediaResult(NS_ERROR_OUT_OF_MEMORY, + RESULT_DETAIL("GMPVideoEncodedFrame::CreateEmptyFrame:%x", 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); + frame->SetCompleteFrame(true); + frame->SetDuration(aSample->mDuration); + 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; + } + + GMPVideoCodec codec; + memset(&codec, 0, sizeof(codec)); + + codec.mGMPApiVersion = kGMPVersion33; + nsTArray<uint8_t> codecSpecific; + if (MP4Decoder::IsH264(mConfig.mMimeType)) { + codec.mCodecType = kGMPVideoCodecH264; + codecSpecific.AppendElement(0); // mPacketizationMode. + codecSpecific.AppendElements(mConfig.mExtraData->Elements(), + mConfig.mExtraData->Length()); + } 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; + + nsresult rv = aGMP->InitDecode(codec, + codecSpecific, + mAdapter, + 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 = mGMP->GetDisplayName().EqualsLiteral("gmpopenh264"); + + 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->GetDecryptingGMPVideoDecoder(mCrashHelper, + &tags, + GetNodeId(), + Move(callback), + DecryptorId()))) { + mInitPromise.Reject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); + } + + return promise; +} + +void +GMPVideoDecoder::Input(MediaRawData* aSample) +{ + MOZ_ASSERT(IsOnGMPThread()); + + RefPtr<MediaRawData> sample(aSample); + if (!mGMP) { + mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, + RESULT_DETAIL("mGMP not initialized"))); + return; + } + + mAdapter->SetLastStreamOffset(sample->mOffset); + + GMPUniquePtr<GMPVideoEncodedFrame> frame = CreateFrame(sample); + if (!frame) { + mCallback->Error(MediaResult(NS_ERROR_OUT_OF_MEMORY, + RESULT_DETAIL("CreateFrame returned null"))); + return; + } + nsTArray<uint8_t> info; // No codec specific per-frame info to pass. + nsresult rv = mGMP->Decode(Move(frame), false, info, 0); + if (NS_FAILED(rv)) { + mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR, + RESULT_DETAIL("mGMP->Decode:%x", rv))); + } +} + +void +GMPVideoDecoder::Flush() +{ + MOZ_ASSERT(IsOnGMPThread()); + + if (!mGMP || NS_FAILED(mGMP->Reset())) { + // Abort the flush. + mCallback->FlushComplete(); + } +} + +void +GMPVideoDecoder::Drain() +{ + MOZ_ASSERT(IsOnGMPThread()); + + if (!mGMP || NS_FAILED(mGMP->Drain())) { + mCallback->DrainComplete(); + } +} + +void +GMPVideoDecoder::Shutdown() +{ + mInitPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__); + // Note that this *may* be called from the proxy thread also. + if (!mGMP) { + return; + } + // Note this unblocks flush and drain operations waiting for callbacks. + mGMP->Close(); + mGMP = nullptr; +} + +} // 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 000000000..900ef4553 --- /dev/null +++ b/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.h @@ -0,0 +1,122 @@ +/* -*- 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(GMPVideoDecoder_h_) +#define GMPVideoDecoder_h_ + +#include "GMPVideoDecoderProxy.h" +#include "ImageContainer.h" +#include "MediaDataDecoderProxy.h" +#include "PlatformDecoderModule.h" +#include "mozIGeckoMediaPluginService.h" +#include "MediaInfo.h" + +namespace mozilla { + +class VideoCallbackAdapter : public GMPVideoDecoderCallbackProxy { +public: + VideoCallbackAdapter(MediaDataDecoderCallbackProxy* aCallback, + VideoInfo aVideoInfo, + layers::ImageContainer* aImageContainer) + : mCallback(aCallback) + , mLastStreamOffset(0) + , mVideoInfo(aVideoInfo) + , mImageContainer(aImageContainer) + {} + + MediaDataDecoderCallbackProxy* Callback() const { return mCallback; } + + // GMPVideoDecoderCallbackProxy + 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; + + void SetLastStreamOffset(int64_t aStreamOffset) { + mLastStreamOffset = aStreamOffset; + } + +private: + MediaDataDecoderCallbackProxy* mCallback; + int64_t mLastStreamOffset; + + VideoInfo mVideoInfo; + RefPtr<layers::ImageContainer> mImageContainer; +}; + +struct GMPVideoDecoderParams { + explicit GMPVideoDecoderParams(const CreateDecoderParams& aParams); + GMPVideoDecoderParams& WithCallback(MediaDataDecoderProxy* aWrapper); + GMPVideoDecoderParams& WithAdapter(VideoCallbackAdapter* aAdapter); + + const VideoInfo& mConfig; + TaskQueue* mTaskQueue; + MediaDataDecoderCallbackProxy* mCallback; + VideoCallbackAdapter* mAdapter; + layers::ImageContainer* mImageContainer; + layers::LayersBackend mLayersBackend; + RefPtr<GMPCrashHelper> mCrashHelper; +}; + +class GMPVideoDecoder : public MediaDataDecoder { +public: + explicit GMPVideoDecoder(const GMPVideoDecoderParams& aParams); + + RefPtr<InitPromise> Init() override; + void Input(MediaRawData* aSample) override; + void Flush() override; + void Drain() override; + void Shutdown() override; + const char* GetDescriptionName() const override + { + return "GMP video decoder"; + } + +protected: + virtual void InitTags(nsTArray<nsCString>& aTags); + virtual nsCString GetNodeId(); + virtual uint32_t DecryptorId() const { return 0; } + virtual GMPUniquePtr<GMPVideoEncodedFrame> CreateFrame(MediaRawData* aSample); + virtual const VideoInfo& GetConfig() const; + +private: + + 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; + MediaDataDecoderCallbackProxy* mCallback; + nsCOMPtr<mozIGeckoMediaPluginService> mMPS; + GMPVideoDecoderProxy* mGMP; + GMPVideoHost* mHost; + nsAutoPtr<VideoCallbackAdapter> mAdapter; + bool mConvertNALUnitLengths; + MozPromiseHolder<InitPromise> mInitPromise; + RefPtr<GMPCrashHelper> mCrashHelper; +}; + +} // namespace mozilla + +#endif // GMPVideoDecoder_h_ diff --git a/dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.cpp b/dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.cpp new file mode 100644 index 000000000..5a196f8e6 --- /dev/null +++ b/dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.cpp @@ -0,0 +1,90 @@ +/* -*- 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 "MediaDataDecoderProxy.h" +#include "MediaData.h" + +namespace mozilla { + +void +MediaDataDecoderCallbackProxy::Error(const MediaResult& aError) +{ + mProxyCallback->Error(aError); +} + +void +MediaDataDecoderCallbackProxy::FlushComplete() +{ + mProxyDecoder->FlushComplete(); +} + +RefPtr<MediaDataDecoder::InitPromise> +MediaDataDecoderProxy::InternalInit() +{ + return mProxyDecoder->Init(); +} + +RefPtr<MediaDataDecoder::InitPromise> +MediaDataDecoderProxy::Init() +{ + MOZ_ASSERT(!mIsShutdown); + + return InvokeAsync(mProxyThread, this, __func__, + &MediaDataDecoderProxy::InternalInit); +} + +void +MediaDataDecoderProxy::Input(MediaRawData* aSample) +{ + MOZ_ASSERT(!IsOnProxyThread()); + MOZ_ASSERT(!mIsShutdown); + + nsCOMPtr<nsIRunnable> task(new InputTask(mProxyDecoder, aSample)); + mProxyThread->Dispatch(task.forget()); +} + +void +MediaDataDecoderProxy::Flush() +{ + MOZ_ASSERT(!IsOnProxyThread()); + MOZ_ASSERT(!mIsShutdown); + + mFlushComplete.Set(false); + + mProxyThread->Dispatch(NewRunnableMethod(mProxyDecoder, &MediaDataDecoder::Flush)); + + mFlushComplete.WaitUntil(true); +} + +void +MediaDataDecoderProxy::Drain() +{ + MOZ_ASSERT(!IsOnProxyThread()); + MOZ_ASSERT(!mIsShutdown); + + mProxyThread->Dispatch(NewRunnableMethod(mProxyDecoder, &MediaDataDecoder::Drain)); +} + +void +MediaDataDecoderProxy::Shutdown() +{ + // Note that this *may* be called from the proxy thread also. + MOZ_ASSERT(!mIsShutdown); +#if defined(DEBUG) + mIsShutdown = true; +#endif + mProxyThread->AsXPCOMThread()->Dispatch(NewRunnableMethod(mProxyDecoder, + &MediaDataDecoder::Shutdown), + NS_DISPATCH_SYNC); +} + +void +MediaDataDecoderProxy::FlushComplete() +{ + mFlushComplete.Set(true); +} + +} // namespace mozilla diff --git a/dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.h b/dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.h new file mode 100644 index 000000000..735b6126e --- /dev/null +++ b/dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.h @@ -0,0 +1,181 @@ +/* -*- 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(MediaDataDecoderProxy_h_) +#define MediaDataDecoderProxy_h_ + +#include "PlatformDecoderModule.h" +#include "mozilla/RefPtr.h" +#include "nsThreadUtils.h" +#include "nscore.h" +#include "GMPService.h" + +namespace mozilla { + +class InputTask : public Runnable { +public: + InputTask(MediaDataDecoder* aDecoder, + MediaRawData* aSample) + : mDecoder(aDecoder) + , mSample(aSample) + {} + + NS_IMETHOD Run() override { + mDecoder->Input(mSample); + return NS_OK; + } + +private: + RefPtr<MediaDataDecoder> mDecoder; + RefPtr<MediaRawData> mSample; +}; + +template<typename T> +class Condition { +public: + explicit Condition(T aValue) + : mMonitor("Condition") + , mCondition(aValue) + {} + + void Set(T aValue) { + MonitorAutoLock mon(mMonitor); + mCondition = aValue; + mon.NotifyAll(); + } + + void WaitUntil(T aValue) { + MonitorAutoLock mon(mMonitor); + while (mCondition != aValue) { + mon.Wait(); + } + } + +private: + Monitor mMonitor; + T mCondition; +}; + +class MediaDataDecoderProxy; + +class MediaDataDecoderCallbackProxy : public MediaDataDecoderCallback { +public: + MediaDataDecoderCallbackProxy(MediaDataDecoderProxy* aProxyDecoder, + MediaDataDecoderCallback* aCallback) + : mProxyDecoder(aProxyDecoder) + , mProxyCallback(aCallback) + { + } + + void Output(MediaData* aData) override { + mProxyCallback->Output(aData); + } + + void Error(const MediaResult& aError) override; + + void InputExhausted() override { + mProxyCallback->InputExhausted(); + } + + void DrainComplete() override { + mProxyCallback->DrainComplete(); + } + + void ReleaseMediaResources() override { + mProxyCallback->ReleaseMediaResources(); + } + + void FlushComplete(); + + bool OnReaderTaskQueue() override + { + return mProxyCallback->OnReaderTaskQueue(); + } + + void WaitingForKey() override + { + mProxyCallback->WaitingForKey(); + } + +private: + MediaDataDecoderProxy* mProxyDecoder; + MediaDataDecoderCallback* mProxyCallback; +}; + +class MediaDataDecoderProxy : public MediaDataDecoder { +public: + MediaDataDecoderProxy(already_AddRefed<AbstractThread> aProxyThread, + MediaDataDecoderCallback* aCallback) + : mProxyThread(aProxyThread) + , mProxyCallback(this, aCallback) + , mFlushComplete(false) +#if defined(DEBUG) + , mIsShutdown(false) +#endif + { + } + + // Ideally, this would return a regular MediaDataDecoderCallback pointer + // to retain the clean abstraction, but until MediaDataDecoderCallback + // supports the FlushComplete interface, this will have to do. When MDDC + // supports FlushComplete, this, the GMP*Decoders, and the + // *CallbackAdapters can be reverted to accepting a regular + // MediaDataDecoderCallback pointer. + MediaDataDecoderCallbackProxy* Callback() + { + return &mProxyCallback; + } + + void SetProxyTarget(MediaDataDecoder* aProxyDecoder) + { + MOZ_ASSERT(aProxyDecoder); + mProxyDecoder = aProxyDecoder; + } + + // These are called from the decoder thread pool. + // Init and Shutdown run synchronously on the proxy thread, all others are + // asynchronously and responded to via the MediaDataDecoderCallback. + // Note: the nsresults returned by the proxied decoder are lost. + RefPtr<InitPromise> Init() override; + void Input(MediaRawData* aSample) override; + void Flush() override; + void Drain() override; + void Shutdown() override; + + const char* GetDescriptionName() const override + { + return "GMP proxy data decoder"; + } + + // Called by MediaDataDecoderCallbackProxy. + void FlushComplete(); + +private: + RefPtr<InitPromise> InternalInit(); + +#ifdef DEBUG + bool IsOnProxyThread() { + return mProxyThread && mProxyThread->IsCurrentThreadIn(); + } +#endif + + friend class InputTask; + friend class InitTask; + + RefPtr<MediaDataDecoder> mProxyDecoder; + RefPtr<AbstractThread> mProxyThread; + + MediaDataDecoderCallbackProxy mProxyCallback; + + Condition<bool> mFlushComplete; +#if defined(DEBUG) + bool mIsShutdown; +#endif +}; + +} // namespace mozilla + +#endif // MediaDataDecoderProxy_h_ diff --git a/dom/media/platforms/agnostic/gmp/moz.build b/dom/media/platforms/agnostic/gmp/moz.build new file mode 100644 index 000000000..eb2738e26 --- /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/. + +EXPORTS += [ + 'GMPAudioDecoder.h', + 'GMPDecoderModule.h', + 'GMPVideoDecoder.h', + 'MediaDataDecoderProxy.h', +] + +UNIFIED_SOURCES += [ + 'GMPAudioDecoder.cpp', + 'GMPDecoderModule.cpp', + 'GMPVideoDecoder.cpp', + 'MediaDataDecoderProxy.cpp', +] + +# GMPVideoEncodedFrameImpl.h needs IPC +include('/ipc/chromium/chromium-config.mozbuild') + +FINAL_LIBRARY = 'xul' |