diff --git a/dom/media/MediaInfo.h b/dom/media/MediaInfo.h index 37a280c43edc..aad07ff05329 100644 --- a/dom/media/MediaInfo.h +++ b/dom/media/MediaInfo.h @@ -36,12 +36,18 @@ struct TrackInfo { // Stores info relevant to presenting media frames. class VideoInfo { +private: + VideoInfo(int32_t aWidth, int32_t aHeight, bool aHasVideo) + : mDisplay(aWidth, aHeight) + , mStereoMode(StereoMode::MONO) + , mHasVideo(aHasVideo) + , mIsHardwareAccelerated(false) + { + } + public: VideoInfo() - : mDisplay(0,0) - , mStereoMode(StereoMode::MONO) - , mHasVideo(false) - , mIsHardwareAccelerated(false) + : VideoInfo(0, 0, false) { // TODO: TrackInfo should be initialized by its specific codec decoder. // This following call should be removed once we have that implemented. @@ -49,6 +55,11 @@ public: EmptyString(), EmptyString(), true); } + VideoInfo(int32_t aWidth, int32_t aHeight) + : VideoInfo(aWidth, aHeight, true) + { + } + // Size in pixels at which the video is rendered. This is after it has // been scaled by its aspect ratio. nsIntSize mDisplay; diff --git a/dom/media/fmp4/MP4Decoder.cpp b/dom/media/fmp4/MP4Decoder.cpp index ba7a2ac275aa..2e8d0dc29fda 100644 --- a/dom/media/fmp4/MP4Decoder.cpp +++ b/dom/media/fmp4/MP4Decoder.cpp @@ -212,6 +212,12 @@ IsGonkMP4DecoderAvailable() return Preferences::GetBool("media.fragmented-mp4.gonk.enabled", false); } +static bool +IsGMPDecoderAvailable() +{ + return Preferences::GetBool("media.fragmented-mp4.gmp.enabled", false); +} + static bool HavePlatformMPEGDecoders() { @@ -224,6 +230,7 @@ HavePlatformMPEGDecoders() IsFFmpegAvailable() || IsAppleAvailable() || IsGonkMP4DecoderAvailable() || + IsGMPDecoderAvailable() || // TODO: Other platforms... false; } diff --git a/dom/media/fmp4/PlatformDecoderModule.cpp b/dom/media/fmp4/PlatformDecoderModule.cpp index 2c857958d9e2..a20365179f6e 100644 --- a/dom/media/fmp4/PlatformDecoderModule.cpp +++ b/dom/media/fmp4/PlatformDecoderModule.cpp @@ -22,6 +22,7 @@ #ifdef MOZ_WIDGET_ANDROID #include "AndroidDecoderModule.h" #endif +#include "GMPDecoderModule.h" #include "mozilla/Preferences.h" #ifdef MOZ_EME @@ -40,6 +41,7 @@ bool PlatformDecoderModule::sFFmpegDecoderEnabled = false; bool PlatformDecoderModule::sGonkDecoderEnabled = false; bool PlatformDecoderModule::sAndroidMCDecoderEnabled = false; bool PlatformDecoderModule::sAndroidMCDecoderPreferred = false; +bool PlatformDecoderModule::sGMPDecoderEnabled = false; /* static */ void @@ -68,6 +70,9 @@ PlatformDecoderModule::Init() "media.fragmented-mp4.android-media-codec.preferred", false); #endif + Preferences::AddBoolVarCache(&sGMPDecoderEnabled, + "media.fragmented-mp4.gmp.enabled", false); + #ifdef XP_WIN WMFDecoderModule::Init(); #endif @@ -167,6 +172,10 @@ PlatformDecoderModule::CreatePDM() return m.forget(); } #endif + if (sGMPDecoderEnabled) { + nsRefPtr m(new AVCCDecoderModule(new GMPDecoderModule())); + return m.forget(); + } return nullptr; } diff --git a/dom/media/fmp4/PlatformDecoderModule.h b/dom/media/fmp4/PlatformDecoderModule.h index 0f5366c53449..52624de286bd 100644 --- a/dom/media/fmp4/PlatformDecoderModule.h +++ b/dom/media/fmp4/PlatformDecoderModule.h @@ -145,6 +145,7 @@ protected: static bool sGonkDecoderEnabled; static bool sAndroidMCDecoderPreferred; static bool sAndroidMCDecoderEnabled; + static bool sGMPDecoderEnabled; }; // A callback used by MediaDataDecoder to return output/errors to the diff --git a/dom/media/fmp4/gmp/GMPAudioDecoder.cpp b/dom/media/fmp4/gmp/GMPAudioDecoder.cpp new file mode 100644 index 000000000000..98c65c627625 --- /dev/null +++ b/dom/media/fmp4/gmp/GMPAudioDecoder.cpp @@ -0,0 +1,224 @@ +/* -*- 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" + +namespace mozilla { + +#if defined(DEBUG) +static bool IsOnGMPThread() +{ + nsCOMPtr mps = do_GetService("@mozilla.org/gecko-media-plugin-service;1"); + MOZ_ASSERT(mps); + + nsCOMPtr gmpThread; + nsresult rv = mps->GetThread(getter_AddRefs(gmpThread)); + MOZ_ASSERT(NS_SUCCEEDED(rv) && gmpThread); + return NS_GetCurrentThread() == gmpThread; +} +#endif + +void +AudioCallbackAdapter::Decoded(const nsTArray& aPCM, uint64_t aTimeStamp, uint32_t aChannels, uint32_t aRate) +{ + MOZ_ASSERT(IsOnGMPThread()); + + if (aRate == 0 || aChannels == 0) { + NS_WARNING("Invalid rate or num channels returned on GMP audio samples"); + mCallback->Error(); + return; + } + + size_t numFrames = aPCM.Length() / aChannels; + MOZ_ASSERT((aPCM.Length() % aChannels) == 0); + nsAutoArrayPtr audioData(new AudioDataValue[aPCM.Length()]); + + 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()) { + NS_WARNING("Invalid timestamp"); + mCallback->Error(); + return; + } + mAudioFrameOffset = timestamp.value(); + MOZ_ASSERT(mAudioFrameOffset >= 0); + mMustRecaptureAudioPosition = false; + } + + auto timestamp = FramesToUsecs(mAudioFrameOffset + mAudioFrameSum, aRate); + if (!timestamp.isValid()) { + NS_WARNING("Invalid timestamp on audio samples"); + mCallback->Error(); + return; + } + mAudioFrameSum += numFrames; + + auto duration = FramesToUsecs(numFrames, aRate); + if (!duration.isValid()) { + NS_WARNING("Invalid duration on audio samples"); + mCallback->Error(); + return; + } + + nsRefPtr audio(new AudioData(mLastStreamOffset, + timestamp.value(), + duration.value(), + numFrames, + audioData.forget(), + 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(); +} + +void +AudioCallbackAdapter::Terminated() +{ + NS_WARNING("AAC GMP decoder terminated."); + mCallback->Error(); +} + +void +GMPAudioDecoder::InitTags(nsTArray& aTags) +{ + aTags.AppendElement(NS_LITERAL_CSTRING("aac")); +} + +nsCString +GMPAudioDecoder::GetNodeId() +{ + return NS_LITERAL_CSTRING(""); +} + +nsresult +GMPAudioDecoder::Init() +{ + MOZ_ASSERT(IsOnGMPThread()); + + mMPS = do_GetService("@mozilla.org/gecko-media-plugin-service;1"); + MOZ_ASSERT(mMPS); + + nsTArray tags; + InitTags(tags); + nsresult rv = mMPS->GetGMPAudioDecoder(&tags, GetNodeId(), &mGMP); + NS_ENSURE_SUCCESS(rv, rv); + MOZ_ASSERT(mGMP); + + nsTArray codecSpecific; + codecSpecific.AppendElements(mConfig.audio_specific_config->Elements(), + mConfig.audio_specific_config->Length()); + + rv = mGMP->InitDecode(kGMPAudioCodecAAC, + mConfig.channel_count, + mConfig.bits_per_sample, + mConfig.samples_per_second, + codecSpecific, + mAdapter); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +nsresult +GMPAudioDecoder::Input(mp4_demuxer::MP4Sample* aSample) +{ + MOZ_ASSERT(IsOnGMPThread()); + + nsAutoPtr sample(aSample); + if (!mGMP) { + mCallback->Error(); + return NS_ERROR_FAILURE; + } + + mAdapter->SetLastStreamOffset(sample->byte_offset); + + gmp::GMPAudioSamplesImpl samples(sample, mConfig.channel_count, mConfig.samples_per_second); + nsresult rv = mGMP->Decode(samples); + if (NS_FAILED(rv)) { + mCallback->Error(); + return rv; + } + + return NS_OK; +} + +nsresult +GMPAudioDecoder::Flush() +{ + MOZ_ASSERT(IsOnGMPThread()); + + if (!mGMP || NS_FAILED(mGMP->Reset())) { + // Abort the flush. + mCallback->FlushComplete(); + } + + return NS_OK; +} + +nsresult +GMPAudioDecoder::Drain() +{ + MOZ_ASSERT(IsOnGMPThread()); + + if (!mGMP || NS_FAILED(mGMP->Drain())) { + mCallback->DrainComplete(); + } + + return NS_OK; +} + +nsresult +GMPAudioDecoder::Shutdown() +{ + if (!mGMP) { + return NS_ERROR_FAILURE; + } + mGMP->Close(); + mGMP = nullptr; + + return NS_OK; +} + +} // namespace mozilla diff --git a/dom/media/fmp4/gmp/GMPAudioDecoder.h b/dom/media/fmp4/gmp/GMPAudioDecoder.h new file mode 100644 index 000000000000..3e17bb15c6aa --- /dev/null +++ b/dom/media/fmp4/gmp/GMPAudioDecoder.h @@ -0,0 +1,89 @@ +/* -*- 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" + +namespace mozilla { + +class AudioCallbackAdapter : public GMPAudioDecoderCallbackProxy { +public: + explicit AudioCallbackAdapter(MediaDataDecoderCallbackProxy* aCallback) + : mCallback(aCallback) + , mLastStreamOffset(0) + , mAudioFrameSum(0) + , mAudioFrameOffset(0) + , mMustRecaptureAudioPosition(true) + {} + + // GMPAudioDecoderCallbackProxy + virtual void Decoded(const nsTArray& aPCM, uint64_t aTimeStamp, uint32_t aChannels, uint32_t aRate) MOZ_OVERRIDE; + virtual void InputDataExhausted() MOZ_OVERRIDE; + virtual void DrainComplete() MOZ_OVERRIDE; + virtual void ResetComplete() MOZ_OVERRIDE; + virtual void Error(GMPErr aErr) MOZ_OVERRIDE; + virtual void Terminated() MOZ_OVERRIDE; + + void SetLastStreamOffset(int64_t aStreamOffset) { + mLastStreamOffset = aStreamOffset; + } + +private: + MediaDataDecoderCallbackProxy* mCallback; + int64_t mLastStreamOffset; + + int64_t mAudioFrameSum; + int64_t mAudioFrameOffset; + bool mMustRecaptureAudioPosition; +}; + +class GMPAudioDecoder : public MediaDataDecoder { +protected: + GMPAudioDecoder(const mp4_demuxer::AudioDecoderConfig& aConfig, + MediaTaskQueue* aTaskQueue, + MediaDataDecoderCallbackProxy* aCallback, + AudioCallbackAdapter* aAdapter) + : mConfig(aConfig) + , mCallback(aCallback) + , mGMP(nullptr) + , mAdapter(aAdapter) + { + } + +public: + GMPAudioDecoder(const mp4_demuxer::AudioDecoderConfig& aConfig, + MediaTaskQueue* aTaskQueue, + MediaDataDecoderCallbackProxy* aCallback) + : GMPAudioDecoder(aConfig, aTaskQueue, aCallback, new AudioCallbackAdapter(aCallback)) + { + } + + virtual nsresult Init() MOZ_OVERRIDE; + virtual nsresult Input(mp4_demuxer::MP4Sample* aSample) MOZ_OVERRIDE; + virtual nsresult Flush() MOZ_OVERRIDE; + virtual nsresult Drain() MOZ_OVERRIDE; + virtual nsresult Shutdown() MOZ_OVERRIDE; + +protected: + virtual void InitTags(nsTArray& aTags); + virtual nsCString GetNodeId(); + +private: + const mp4_demuxer::AudioDecoderConfig& mConfig; + MediaDataDecoderCallbackProxy* mCallback; + nsCOMPtr mMPS; + GMPAudioDecoderProxy* mGMP; + nsAutoPtr mAdapter; +}; + +} // namespace mozilla + +#endif // GMPAudioDecoder_h_ diff --git a/dom/media/fmp4/gmp/GMPDecoderModule.cpp b/dom/media/fmp4/gmp/GMPDecoderModule.cpp new file mode 100644 index 000000000000..ccfaf6b4b508 --- /dev/null +++ b/dom/media/fmp4/gmp/GMPDecoderModule.cpp @@ -0,0 +1,91 @@ +/* -*- 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 "GMPAudioDecoder.h" +#include "GMPVideoDecoder.h" +#include "MediaDataDecoderProxy.h" +#include "mozIGeckoMediaPluginService.h" +#include "nsServiceManagerUtils.h" + +namespace mozilla { + +GMPDecoderModule::GMPDecoderModule() +{ +} + +GMPDecoderModule::~GMPDecoderModule() +{ +} + +nsresult +GMPDecoderModule::Shutdown() +{ + return NS_OK; +} + +static already_AddRefed +CreateDecoderWrapper(MediaDataDecoderCallback* aCallback) +{ + nsCOMPtr gmpService = do_GetService("@mozilla.org/gecko-media-plugin-service;1"); + if (!gmpService) { + return nullptr; + } + + nsCOMPtr thread; + nsresult rv = gmpService->GetThread(getter_AddRefs(thread)); + if (NS_FAILED(rv)) { + return nullptr; + } + + nsRefPtr decoder(new MediaDataDecoderProxy(thread, aCallback)); + return decoder.forget(); +} + +already_AddRefed +GMPDecoderModule::CreateVideoDecoder(const mp4_demuxer::VideoDecoderConfig& aConfig, + layers::LayersBackend aLayersBackend, + layers::ImageContainer* aImageContainer, + MediaTaskQueue* aVideoTaskQueue, + MediaDataDecoderCallback* aCallback) +{ + if (strcmp(aConfig.mime_type, "video/avc") != 0) { + return nullptr; + } + + nsRefPtr wrapper = CreateDecoderWrapper(aCallback); + wrapper->SetProxyTarget(new GMPVideoDecoder(aConfig, + aLayersBackend, + aImageContainer, + aVideoTaskQueue, + wrapper->Callback())); + return wrapper.forget(); +} + +already_AddRefed +GMPDecoderModule::CreateAudioDecoder(const mp4_demuxer::AudioDecoderConfig& aConfig, + MediaTaskQueue* aAudioTaskQueue, + MediaDataDecoderCallback* aCallback) +{ + if (strcmp(aConfig.mime_type, "audio/mp4a-latm") != 0) { + return nullptr; + } + + nsRefPtr wrapper = CreateDecoderWrapper(aCallback); + wrapper->SetProxyTarget(new GMPAudioDecoder(aConfig, + aAudioTaskQueue, + wrapper->Callback())); + return wrapper.forget(); +} + +bool +GMPDecoderModule::DecoderNeedsAVCC(const mp4_demuxer::VideoDecoderConfig& aConfig) +{ + // GMPVideoCodecType::kGMPVideoCodecH264 specifies that encoded frames must be in AVCC format. + return true; +} + +} // namespace mozilla diff --git a/dom/media/fmp4/gmp/GMPDecoderModule.h b/dom/media/fmp4/gmp/GMPDecoderModule.h new file mode 100644 index 000000000000..82819993b4a6 --- /dev/null +++ b/dom/media/fmp4/gmp/GMPDecoderModule.h @@ -0,0 +1,42 @@ +/* -*- 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" + +namespace mozilla { + +class GMPDecoderModule : public PlatformDecoderModule { +public: + GMPDecoderModule(); + + virtual ~GMPDecoderModule(); + + // Called when the decoders have shutdown. Main thread only. + virtual nsresult Shutdown() MOZ_OVERRIDE; + + // Decode thread. + virtual already_AddRefed + CreateVideoDecoder(const mp4_demuxer::VideoDecoderConfig& aConfig, + layers::LayersBackend aLayersBackend, + layers::ImageContainer* aImageContainer, + MediaTaskQueue* aVideoTaskQueue, + MediaDataDecoderCallback* aCallback) MOZ_OVERRIDE; + + // Decode thread. + virtual already_AddRefed + CreateAudioDecoder(const mp4_demuxer::AudioDecoderConfig& aConfig, + MediaTaskQueue* aAudioTaskQueue, + MediaDataDecoderCallback* aCallback) MOZ_OVERRIDE; + + virtual bool DecoderNeedsAVCC(const mp4_demuxer::VideoDecoderConfig& aConfig) MOZ_OVERRIDE; +}; + +} // namespace mozilla + +#endif // GMPDecoderModule_h_ diff --git a/dom/media/fmp4/gmp/GMPVideoDecoder.cpp b/dom/media/fmp4/gmp/GMPVideoDecoder.cpp new file mode 100644 index 000000000000..d764055eab9a --- /dev/null +++ b/dom/media/fmp4/gmp/GMPVideoDecoder.cpp @@ -0,0 +1,245 @@ +/* -*- 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 "prsystem.h" + +namespace mozilla { + +#if defined(DEBUG) +static bool IsOnGMPThread(); +#endif + +void +VideoCallbackAdapter::Decoded(GMPVideoi420Frame* aDecodedFrame) +{ + GMPUniquePtr 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()); + nsRefPtr v = VideoData::Create(mVideoInfo, + mImageContainer, + mLastStreamOffset, + decodedFrame->Timestamp(), + decodedFrame->Duration(), + b, + false, + -1, + pictureRegion); + if (v) { + mCallback->Output(v); + } else { + mCallback->Error(); + } +} + +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(); +} + +void +VideoCallbackAdapter::Terminated() +{ + // Note that this *may* be called from the proxy thread also. + NS_WARNING("H.264 GMP decoder terminated."); + mCallback->Error(); +} + +void +GMPVideoDecoder::InitTags(nsTArray& aTags) +{ + aTags.AppendElement(NS_LITERAL_CSTRING("h264")); +} + +nsCString +GMPVideoDecoder::GetNodeId() +{ + return NS_LITERAL_CSTRING(""); +} + +GMPUniquePtr +GMPVideoDecoder::CreateFrame(mp4_demuxer::MP4Sample* aSample) +{ + GMPVideoFrame* ftmp = nullptr; + GMPErr err = mHost->CreateFrame(kGMPEncodedVideoFrame, &ftmp); + if (GMP_FAILED(err)) { + mCallback->Error(); + return nullptr; + } + + GMPUniquePtr frame(static_cast(ftmp)); + err = frame->CreateEmptyFrame(aSample->size); + if (GMP_FAILED(err)) { + mCallback->Error(); + return nullptr; + } + + memcpy(frame->Buffer(), aSample->data, frame->Size()); + + frame->SetEncodedWidth(mConfig.display_width); + frame->SetEncodedHeight(mConfig.display_height); + frame->SetTimeStamp(aSample->composition_timestamp); + frame->SetCompleteFrame(true); + frame->SetDuration(aSample->duration); + frame->SetFrameType(aSample->is_sync_point ? kGMPKeyFrame : kGMPDeltaFrame); + frame->SetBufferType(GMP_BufferLength32); + + return frame; +} + +nsresult +GMPVideoDecoder::Init() +{ + MOZ_ASSERT(IsOnGMPThread()); + + mMPS = do_GetService("@mozilla.org/gecko-media-plugin-service;1"); + MOZ_ASSERT(mMPS); + + nsTArray tags; + InitTags(tags); + nsresult rv = mMPS->GetGMPVideoDecoder(&tags, GetNodeId(), &mHost, &mGMP); + NS_ENSURE_SUCCESS(rv, rv); + MOZ_ASSERT(mHost && mGMP); + + GMPVideoCodec codec; + memset(&codec, 0, sizeof(codec)); + + codec.mGMPApiVersion = kGMPVersion33; + + codec.mCodecType = kGMPVideoCodecH264; + codec.mWidth = mConfig.display_width; + codec.mHeight = mConfig.display_height; + + nsTArray codecSpecific; + codecSpecific.AppendElement(0); // mPacketizationMode. + codecSpecific.AppendElements(mConfig.extra_data->Elements(), + mConfig.extra_data->Length()); + + rv = mGMP->InitDecode(codec, + codecSpecific, + mAdapter, + PR_GetNumberOfProcessors()); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +nsresult +GMPVideoDecoder::Input(mp4_demuxer::MP4Sample* aSample) +{ + MOZ_ASSERT(IsOnGMPThread()); + + nsAutoPtr sample(aSample); + if (!mGMP) { + mCallback->Error(); + return NS_ERROR_FAILURE; + } + + mAdapter->SetLastStreamOffset(sample->byte_offset); + + GMPUniquePtr frame = CreateFrame(sample); + nsTArray info; // No codec specific per-frame info to pass. + nsresult rv = mGMP->Decode(Move(frame), false, info, 0); + if (NS_FAILED(rv)) { + mCallback->Error(); + return rv; + } + + return NS_OK; +} + +nsresult +GMPVideoDecoder::Flush() +{ + MOZ_ASSERT(IsOnGMPThread()); + + if (!mGMP || NS_FAILED(mGMP->Reset())) { + // Abort the flush. + mCallback->FlushComplete(); + } + + return NS_OK; +} + +nsresult +GMPVideoDecoder::Drain() +{ + MOZ_ASSERT(IsOnGMPThread()); + + if (!mGMP || NS_FAILED(mGMP->Drain())) { + mCallback->DrainComplete(); + } + + return NS_OK; +} + +nsresult +GMPVideoDecoder::Shutdown() +{ + // Note that this *may* be called from the proxy thread also. + if (!mGMP) { + return NS_ERROR_FAILURE; + } + mGMP->Close(); + mGMP = nullptr; + + return NS_OK; +} + +} // namespace mozilla diff --git a/dom/media/fmp4/gmp/GMPVideoDecoder.h b/dom/media/fmp4/gmp/GMPVideoDecoder.h new file mode 100644 index 000000000000..f95f24444fca --- /dev/null +++ b/dom/media/fmp4/gmp/GMPVideoDecoder.h @@ -0,0 +1,105 @@ +/* -*- 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 "mp4_demuxer/DecoderData.h" + +namespace mozilla { + +class VideoCallbackAdapter : public GMPVideoDecoderCallbackProxy { +public: + VideoCallbackAdapter(MediaDataDecoderCallbackProxy* aCallback, + VideoInfo aVideoInfo, + layers::ImageContainer* aImageContainer) + : mCallback(aCallback) + , mLastStreamOffset(0) + , mVideoInfo(aVideoInfo) + , mImageContainer(aImageContainer) + {} + + // GMPVideoDecoderCallbackProxy + virtual void Decoded(GMPVideoi420Frame* aDecodedFrame) MOZ_OVERRIDE; + virtual void ReceivedDecodedReferenceFrame(const uint64_t aPictureId) MOZ_OVERRIDE; + virtual void ReceivedDecodedFrame(const uint64_t aPictureId) MOZ_OVERRIDE; + virtual void InputDataExhausted() MOZ_OVERRIDE; + virtual void DrainComplete() MOZ_OVERRIDE; + virtual void ResetComplete() MOZ_OVERRIDE; + virtual void Error(GMPErr aErr) MOZ_OVERRIDE; + virtual void Terminated() MOZ_OVERRIDE; + + void SetLastStreamOffset(int64_t aStreamOffset) { + mLastStreamOffset = aStreamOffset; + } + +private: + MediaDataDecoderCallbackProxy* mCallback; + int64_t mLastStreamOffset; + + VideoInfo mVideoInfo; + nsRefPtr mImageContainer; +}; + +class GMPVideoDecoder : public MediaDataDecoder { +protected: + GMPVideoDecoder(const mp4_demuxer::VideoDecoderConfig& aConfig, + layers::LayersBackend aLayersBackend, + layers::ImageContainer* aImageContainer, + MediaTaskQueue* aTaskQueue, + MediaDataDecoderCallbackProxy* aCallback, + VideoCallbackAdapter* aAdapter) + : mConfig(aConfig) + , mCallback(aCallback) + , mGMP(nullptr) + , mHost(nullptr) + , mAdapter(aAdapter) + { + } + +public: + GMPVideoDecoder(const mp4_demuxer::VideoDecoderConfig& aConfig, + layers::LayersBackend aLayersBackend, + layers::ImageContainer* aImageContainer, + MediaTaskQueue* aTaskQueue, + MediaDataDecoderCallbackProxy* aCallback) + : GMPVideoDecoder(aConfig, aLayersBackend, aImageContainer, aTaskQueue, aCallback, + new VideoCallbackAdapter(aCallback, + VideoInfo(aConfig.display_width, + aConfig.display_height), + aImageContainer)) + { + } + + virtual nsresult Init() MOZ_OVERRIDE; + virtual nsresult Input(mp4_demuxer::MP4Sample* aSample) MOZ_OVERRIDE; + virtual nsresult Flush() MOZ_OVERRIDE; + virtual nsresult Drain() MOZ_OVERRIDE; + virtual nsresult Shutdown() MOZ_OVERRIDE; + +protected: + virtual void InitTags(nsTArray& aTags); + virtual nsCString GetNodeId(); + virtual GMPUniquePtr CreateFrame(mp4_demuxer::MP4Sample* aSample); + +private: + const mp4_demuxer::VideoDecoderConfig& mConfig; + MediaDataDecoderCallbackProxy* mCallback; + nsCOMPtr mMPS; + GMPVideoDecoderProxy* mGMP; + GMPVideoHost* mHost; + nsAutoPtr mAdapter; +}; + + +} // namespace mozilla + +#endif // GMPVideoDecoder_h_ diff --git a/dom/media/fmp4/gmp/MediaDataDecoderProxy.cpp b/dom/media/fmp4/gmp/MediaDataDecoderProxy.cpp new file mode 100644 index 000000000000..24a1558b2e5d --- /dev/null +++ b/dom/media/fmp4/gmp/MediaDataDecoderProxy.cpp @@ -0,0 +1,101 @@ +/* -*- 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" + +namespace mozilla { + +void +MediaDataDecoderCallbackProxy::Error() +{ + mProxyCallback->Error(); + mProxyDecoder->Shutdown(); +} + +void +MediaDataDecoderCallbackProxy::FlushComplete() +{ + mProxyDecoder->FlushComplete(); +} + +nsresult +MediaDataDecoderProxy::Init() +{ + MOZ_ASSERT(!mIsShutdown); + nsRefPtr task(new InitTask(mProxyDecoder)); + nsresult rv = mProxyThread->Dispatch(task, NS_DISPATCH_SYNC); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_SUCCESS(task->Result(), task->Result()); + + return NS_OK; +} + +nsresult +MediaDataDecoderProxy::Input(mp4_demuxer::MP4Sample* aSample) +{ + MOZ_ASSERT(!IsOnProxyThread()); + MOZ_ASSERT(!mIsShutdown); + + nsRefPtr task(new InputTask(mProxyDecoder, aSample)); + nsresult rv = mProxyThread->Dispatch(task, NS_DISPATCH_NORMAL); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +nsresult +MediaDataDecoderProxy::Flush() +{ + MOZ_ASSERT(!IsOnProxyThread()); + MOZ_ASSERT(!mIsShutdown); + + mFlushComplete.Set(false); + + nsRefPtr task; + task = NS_NewRunnableMethod(mProxyDecoder, &MediaDataDecoder::Flush); + nsresult rv = mProxyThread->Dispatch(task, NS_DISPATCH_NORMAL); + NS_ENSURE_SUCCESS(rv, rv); + + mFlushComplete.WaitUntil(true); + + return NS_OK; +} + +nsresult +MediaDataDecoderProxy::Drain() +{ + MOZ_ASSERT(!IsOnProxyThread()); + MOZ_ASSERT(!mIsShutdown); + + nsRefPtr task; + task = NS_NewRunnableMethod(mProxyDecoder, &MediaDataDecoder::Drain); + nsresult rv = mProxyThread->Dispatch(task, NS_DISPATCH_NORMAL); + NS_ENSURE_SUCCESS(rv, rv); + return NS_OK; +} + +nsresult +MediaDataDecoderProxy::Shutdown() +{ + // Note that this *may* be called from the proxy thread also. + MOZ_ASSERT(!mIsShutdown); +#if defined(DEBUG) + mIsShutdown = true; +#endif + nsRefPtr task; + task = NS_NewRunnableMethod(mProxyDecoder, &MediaDataDecoder::Shutdown); + nsresult rv = mProxyThread->Dispatch(task, NS_DISPATCH_SYNC); + NS_ENSURE_SUCCESS(rv, rv); + return NS_OK; +} + +void +MediaDataDecoderProxy::FlushComplete() +{ + mFlushComplete.Set(true); +} + +} // namespace mozilla diff --git a/dom/media/fmp4/gmp/MediaDataDecoderProxy.h b/dom/media/fmp4/gmp/MediaDataDecoderProxy.h new file mode 100644 index 000000000000..bc93af17ef81 --- /dev/null +++ b/dom/media/fmp4/gmp/MediaDataDecoderProxy.h @@ -0,0 +1,191 @@ +/* -*- 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 "mp4_demuxer/DecoderData.h" +#include "nsAutoPtr.h" +#include "nsRefPtr.h" +#include "nsThreadUtils.h" +#include "nscore.h" + +namespace mozilla { + +class InputTask : public nsRunnable { +public: + InputTask(MediaDataDecoder* aDecoder, + mp4_demuxer::MP4Sample* aSample) + : mDecoder(aDecoder) + , mSample(aSample) + {} + + NS_IMETHOD Run() { + mDecoder->Input(mSample.forget()); + return NS_OK; + } + +private: + nsRefPtr mDecoder; + nsAutoPtr mSample; +}; + +class InitTask : public nsRunnable { +public: + explicit InitTask(MediaDataDecoder* aDecoder) + : mDecoder(aDecoder) + , mResultValid(false) + {} + + NS_IMETHOD Run() { + mResult = mDecoder->Init(); + mResultValid = true; + return NS_OK; + } + + nsresult Result() { + MOZ_ASSERT(mResultValid); + return mResult; + } + +private: + MediaDataDecoder* mDecoder; + nsresult mResult; + bool mResultValid; +}; + +template +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: + explicit MediaDataDecoderCallbackProxy(MediaDataDecoderProxy* aProxyDecoder, MediaDataDecoderCallback* aCallback) + : mProxyDecoder(aProxyDecoder) + , mProxyCallback(aCallback) + { + } + + virtual void Output(MediaData* aData) MOZ_OVERRIDE { + mProxyCallback->Output(aData); + } + + virtual void Error() MOZ_OVERRIDE; + + virtual void InputExhausted() MOZ_OVERRIDE { + mProxyCallback->InputExhausted(); + } + + virtual void DrainComplete() MOZ_OVERRIDE { + mProxyCallback->DrainComplete(); + } + + virtual void NotifyResourcesStatusChanged() MOZ_OVERRIDE { + mProxyCallback->NotifyResourcesStatusChanged(); + } + + virtual void ReleaseMediaResources() MOZ_OVERRIDE { + mProxyCallback->ReleaseMediaResources(); + } + + virtual void FlushComplete(); + +private: + MediaDataDecoderProxy* mProxyDecoder; + MediaDataDecoderCallback* mProxyCallback; +}; + +class MediaDataDecoderProxy : public MediaDataDecoder { +public: + MediaDataDecoderProxy(nsIThread* 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. + virtual nsresult Init() MOZ_OVERRIDE; + virtual nsresult Input(mp4_demuxer::MP4Sample* aSample) MOZ_OVERRIDE; + virtual nsresult Flush() MOZ_OVERRIDE; + virtual nsresult Drain() MOZ_OVERRIDE; + virtual nsresult Shutdown() MOZ_OVERRIDE; + + // Called by MediaDataDecoderCallbackProxy. + void FlushComplete(); + +private: +#ifdef DEBUG + bool IsOnProxyThread() { + return NS_GetCurrentThread() == mProxyThread; + } +#endif + + friend class InputTask; + friend class InitTask; + + nsRefPtr mProxyDecoder; + nsCOMPtr mProxyThread; + + MediaDataDecoderCallbackProxy mProxyCallback; + + Condition mFlushComplete; +#if defined(DEBUG) + bool mIsShutdown; +#endif +}; + +} // namespace mozilla + +#endif // MediaDataDecoderProxy_h_ diff --git a/dom/media/fmp4/gmp/moz.build b/dom/media/fmp4/gmp/moz.build new file mode 100644 index 000000000000..8678834f9e04 --- /dev/null +++ b/dom/media/fmp4/gmp/moz.build @@ -0,0 +1,26 @@ +# -*- Mode: python; c-basic-offset: 4; 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' + +FAIL_ON_WARNINGS = True diff --git a/dom/media/fmp4/moz.build b/dom/media/fmp4/moz.build index 4c52952ce605..2b74fad737c9 100644 --- a/dom/media/fmp4/moz.build +++ b/dom/media/fmp4/moz.build @@ -26,6 +26,8 @@ SOURCES += [ 'MP4Reader.cpp', ] +DIRS += ['gmp'] + if CONFIG['MOZ_WMF']: DIRS += [ 'wmf' ]; diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index 52371321cc63..1511f7ffa82a 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -280,6 +280,7 @@ pref("media.directshow.enabled", true); #ifdef MOZ_FMP4 pref("media.fragmented-mp4.enabled", true); pref("media.fragmented-mp4.ffmpeg.enabled", false); +pref("media.fragmented-mp4.gmp.enabled", false); #if defined(XP_WIN) && defined(MOZ_WMF) || defined(XP_MACOSX) || defined(MOZ_WIDGET_GONK) // Denotes that the fragmented MP4 parser can be created by