import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo

This commit is contained in:
Roy Tam 2018-01-19 03:59:58 +08:00
commit dcd9973243
150858 changed files with 23884658 additions and 0 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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