Bug 1121258 - Add a GMP PDM to allow MP4 playback via OpenH264. r=cpearce

This commit is contained in:
Matthew Gregan 2015-02-02 21:49:00 +13:00
parent a15ce311b8
commit 6e2bccf8bb
15 changed files with 1149 additions and 4 deletions

View File

@ -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;

View File

@ -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;
}

View File

@ -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<PlatformDecoderModule> m(new AVCCDecoderModule(new GMPDecoderModule()));
return m.forget();
}
return nullptr;
}

View File

@ -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

View File

@ -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<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) {
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<AudioDataValue> 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<AudioData> 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<nsCString>& 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<nsCString> tags;
InitTags(tags);
nsresult rv = mMPS->GetGMPAudioDecoder(&tags, GetNodeId(), &mGMP);
NS_ENSURE_SUCCESS(rv, rv);
MOZ_ASSERT(mGMP);
nsTArray<uint8_t> 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<mp4_demuxer::MP4Sample> 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

View File

@ -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<int16_t>& 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<nsCString>& aTags);
virtual nsCString GetNodeId();
private:
const mp4_demuxer::AudioDecoderConfig& mConfig;
MediaDataDecoderCallbackProxy* mCallback;
nsCOMPtr<mozIGeckoMediaPluginService> mMPS;
GMPAudioDecoderProxy* mGMP;
nsAutoPtr<AudioCallbackAdapter> mAdapter;
};
} // namespace mozilla
#endif // GMPAudioDecoder_h_

View File

@ -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<MediaDataDecoderProxy>
CreateDecoderWrapper(MediaDataDecoderCallback* aCallback)
{
nsCOMPtr<mozIGeckoMediaPluginService> gmpService = do_GetService("@mozilla.org/gecko-media-plugin-service;1");
if (!gmpService) {
return nullptr;
}
nsCOMPtr<nsIThread> thread;
nsresult rv = gmpService->GetThread(getter_AddRefs(thread));
if (NS_FAILED(rv)) {
return nullptr;
}
nsRefPtr<MediaDataDecoderProxy> decoder(new MediaDataDecoderProxy(thread, aCallback));
return decoder.forget();
}
already_AddRefed<MediaDataDecoder>
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<MediaDataDecoderProxy> wrapper = CreateDecoderWrapper(aCallback);
wrapper->SetProxyTarget(new GMPVideoDecoder(aConfig,
aLayersBackend,
aImageContainer,
aVideoTaskQueue,
wrapper->Callback()));
return wrapper.forget();
}
already_AddRefed<MediaDataDecoder>
GMPDecoderModule::CreateAudioDecoder(const mp4_demuxer::AudioDecoderConfig& aConfig,
MediaTaskQueue* aAudioTaskQueue,
MediaDataDecoderCallback* aCallback)
{
if (strcmp(aConfig.mime_type, "audio/mp4a-latm") != 0) {
return nullptr;
}
nsRefPtr<MediaDataDecoderProxy> 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

View File

@ -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<MediaDataDecoder>
CreateVideoDecoder(const mp4_demuxer::VideoDecoderConfig& aConfig,
layers::LayersBackend aLayersBackend,
layers::ImageContainer* aImageContainer,
MediaTaskQueue* aVideoTaskQueue,
MediaDataDecoderCallback* aCallback) MOZ_OVERRIDE;
// Decode thread.
virtual already_AddRefed<MediaDataDecoder>
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_

View File

@ -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<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());
nsRefPtr<VideoData> 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<nsCString>& aTags)
{
aTags.AppendElement(NS_LITERAL_CSTRING("h264"));
}
nsCString
GMPVideoDecoder::GetNodeId()
{
return NS_LITERAL_CSTRING("");
}
GMPUniquePtr<GMPVideoEncodedFrame>
GMPVideoDecoder::CreateFrame(mp4_demuxer::MP4Sample* aSample)
{
GMPVideoFrame* ftmp = nullptr;
GMPErr err = mHost->CreateFrame(kGMPEncodedVideoFrame, &ftmp);
if (GMP_FAILED(err)) {
mCallback->Error();
return nullptr;
}
GMPUniquePtr<GMPVideoEncodedFrame> frame(static_cast<GMPVideoEncodedFrame*>(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<nsCString> 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<uint8_t> 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<mp4_demuxer::MP4Sample> sample(aSample);
if (!mGMP) {
mCallback->Error();
return NS_ERROR_FAILURE;
}
mAdapter->SetLastStreamOffset(sample->byte_offset);
GMPUniquePtr<GMPVideoEncodedFrame> frame = CreateFrame(sample);
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();
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

View File

@ -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<layers::ImageContainer> 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<nsCString>& aTags);
virtual nsCString GetNodeId();
virtual GMPUniquePtr<GMPVideoEncodedFrame> CreateFrame(mp4_demuxer::MP4Sample* aSample);
private:
const mp4_demuxer::VideoDecoderConfig& mConfig;
MediaDataDecoderCallbackProxy* mCallback;
nsCOMPtr<mozIGeckoMediaPluginService> mMPS;
GMPVideoDecoderProxy* mGMP;
GMPVideoHost* mHost;
nsAutoPtr<VideoCallbackAdapter> mAdapter;
};
} // namespace mozilla
#endif // GMPVideoDecoder_h_

View File

@ -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<InitTask> 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<nsIRunnable> 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<nsIRunnable> 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<nsIRunnable> 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<nsIRunnable> 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

View File

@ -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<MediaDataDecoder> mDecoder;
nsAutoPtr<mp4_demuxer::MP4Sample> 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<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:
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<MediaDataDecoder> mProxyDecoder;
nsCOMPtr<nsIThread> mProxyThread;
MediaDataDecoderCallbackProxy mProxyCallback;
Condition<bool> mFlushComplete;
#if defined(DEBUG)
bool mIsShutdown;
#endif
};
} // namespace mozilla
#endif // MediaDataDecoderProxy_h_

View File

@ -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

View File

@ -26,6 +26,8 @@ SOURCES += [
'MP4Reader.cpp',
]
DIRS += ['gmp']
if CONFIG['MOZ_WMF']:
DIRS += [ 'wmf' ];

View File

@ -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 <video> elements.
pref("media.fragmented-mp4.exposed", true);