summaryrefslogtreecommitdiffstats
path: root/dom/media/platforms/agnostic
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/platforms/agnostic')
-rw-r--r--dom/media/platforms/agnostic/AgnosticDecoderModule.cpp63
-rw-r--r--dom/media/platforms/agnostic/AgnosticDecoderModule.h34
-rw-r--r--dom/media/platforms/agnostic/BlankDecoderModule.cpp278
-rw-r--r--dom/media/platforms/agnostic/OpusDecoder.cpp356
-rw-r--r--dom/media/platforms/agnostic/OpusDecoder.h76
-rw-r--r--dom/media/platforms/agnostic/TheoraDecoder.cpp240
-rw-r--r--dom/media/platforms/agnostic/TheoraDecoder.h64
-rw-r--r--dom/media/platforms/agnostic/VPXDecoder.cpp253
-rw-r--r--dom/media/platforms/agnostic/VPXDecoder.h69
-rw-r--r--dom/media/platforms/agnostic/VorbisDecoder.cpp349
-rw-r--r--dom/media/platforms/agnostic/VorbisDecoder.h66
-rw-r--r--dom/media/platforms/agnostic/WAVDecoder.cpp158
-rw-r--r--dom/media/platforms/agnostic/WAVDecoder.h41
-rw-r--r--dom/media/platforms/agnostic/eme/EMEAudioDecoder.cpp44
-rw-r--r--dom/media/platforms/agnostic/eme/EMEAudioDecoder.h37
-rw-r--r--dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp307
-rw-r--r--dom/media/platforms/agnostic/eme/EMEDecoderModule.h52
-rw-r--r--dom/media/platforms/agnostic/eme/EMEVideoDecoder.cpp68
-rw-r--r--dom/media/platforms/agnostic/eme/EMEVideoDecoder.h45
-rw-r--r--dom/media/platforms/agnostic/eme/SamplesWaitingForKey.cpp86
-rw-r--r--dom/media/platforms/agnostic/eme/SamplesWaitingForKey.h58
-rw-r--r--dom/media/platforms/agnostic/eme/moz.build23
-rw-r--r--dom/media/platforms/agnostic/gmp/GMPAudioDecoder.cpp306
-rw-r--r--dom/media/platforms/agnostic/gmp/GMPAudioDecoder.h112
-rw-r--r--dom/media/platforms/agnostic/gmp/GMPDecoderModule.cpp172
-rw-r--r--dom/media/platforms/agnostic/gmp/GMPDecoderModule.h57
-rw-r--r--dom/media/platforms/agnostic/gmp/GMPVideoDecoder.cpp387
-rw-r--r--dom/media/platforms/agnostic/gmp/GMPVideoDecoder.h122
-rw-r--r--dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.cpp90
-rw-r--r--dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.h181
-rw-r--r--dom/media/platforms/agnostic/gmp/moz.build24
31 files changed, 4218 insertions, 0 deletions
diff --git a/dom/media/platforms/agnostic/AgnosticDecoderModule.cpp b/dom/media/platforms/agnostic/AgnosticDecoderModule.cpp
new file mode 100644
index 000000000..7bd75b7fe
--- /dev/null
+++ b/dom/media/platforms/agnostic/AgnosticDecoderModule.cpp
@@ -0,0 +1,63 @@
+/* -*- 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 "AgnosticDecoderModule.h"
+#include "mozilla/Logging.h"
+#include "OpusDecoder.h"
+#include "VorbisDecoder.h"
+#include "VPXDecoder.h"
+#include "WAVDecoder.h"
+#include "TheoraDecoder.h"
+
+namespace mozilla {
+
+bool
+AgnosticDecoderModule::SupportsMimeType(const nsACString& aMimeType,
+ DecoderDoctorDiagnostics* aDiagnostics) const
+{
+ bool supports =
+ VPXDecoder::IsVPX(aMimeType) ||
+ OpusDataDecoder::IsOpus(aMimeType) ||
+ VorbisDataDecoder::IsVorbis(aMimeType) ||
+ WaveDataDecoder::IsWave(aMimeType) ||
+ TheoraDecoder::IsTheora(aMimeType);
+ MOZ_LOG(sPDMLog, LogLevel::Debug, ("Agnostic decoder %s requested type",
+ supports ? "supports" : "rejects"));
+ return supports;
+}
+
+already_AddRefed<MediaDataDecoder>
+AgnosticDecoderModule::CreateVideoDecoder(const CreateDecoderParams& aParams)
+{
+ RefPtr<MediaDataDecoder> m;
+
+ if (VPXDecoder::IsVPX(aParams.mConfig.mMimeType)) {
+ m = new VPXDecoder(aParams);
+ } else if (TheoraDecoder::IsTheora(aParams.mConfig.mMimeType)) {
+ m = new TheoraDecoder(aParams);
+ }
+
+ return m.forget();
+}
+
+already_AddRefed<MediaDataDecoder>
+AgnosticDecoderModule::CreateAudioDecoder(const CreateDecoderParams& aParams)
+{
+ RefPtr<MediaDataDecoder> m;
+
+ const TrackInfo& config = aParams.mConfig;
+ if (VorbisDataDecoder::IsVorbis(config.mMimeType)) {
+ m = new VorbisDataDecoder(aParams);
+ } else if (OpusDataDecoder::IsOpus(config.mMimeType)) {
+ m = new OpusDataDecoder(aParams);
+ } else if (WaveDataDecoder::IsWave(config.mMimeType)) {
+ m = new WaveDataDecoder(aParams);
+ }
+
+ return m.forget();
+}
+
+} // namespace mozilla
diff --git a/dom/media/platforms/agnostic/AgnosticDecoderModule.h b/dom/media/platforms/agnostic/AgnosticDecoderModule.h
new file mode 100644
index 000000000..d944b84ab
--- /dev/null
+++ b/dom/media/platforms/agnostic/AgnosticDecoderModule.h
@@ -0,0 +1,34 @@
+#if !defined(AgnosticDecoderModule_h_)
+#define AgnosticDecoderModule_h_
+
+#include "PlatformDecoderModule.h"
+
+namespace mozilla {
+
+class AgnosticDecoderModule : public PlatformDecoderModule {
+public:
+ AgnosticDecoderModule() = default;
+ virtual ~AgnosticDecoderModule() = default;
+
+ bool SupportsMimeType(const nsACString& aMimeType,
+ DecoderDoctorDiagnostics* aDiagnostics) const override;
+
+ ConversionRequired
+ DecoderNeedsConversion(const TrackInfo& aConfig) const override
+ {
+ return ConversionRequired::kNeedNone;
+ }
+
+protected:
+ // Decode thread.
+ already_AddRefed<MediaDataDecoder>
+ CreateVideoDecoder(const CreateDecoderParams& aParams) override;
+
+ // Decode thread.
+ already_AddRefed<MediaDataDecoder>
+ CreateAudioDecoder(const CreateDecoderParams& aParams) override;
+};
+
+} // namespace mozilla
+
+#endif /* AgnosticDecoderModule_h_ */
diff --git a/dom/media/platforms/agnostic/BlankDecoderModule.cpp b/dom/media/platforms/agnostic/BlankDecoderModule.cpp
new file mode 100644
index 000000000..7d7bae721
--- /dev/null
+++ b/dom/media/platforms/agnostic/BlankDecoderModule.cpp
@@ -0,0 +1,278 @@
+/* -*- 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 "ImageContainer.h"
+#include "MediaDecoderReader.h"
+#include "MediaInfo.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/mozalloc.h" // for operator new, and new (fallible)
+#include "mozilla/RefPtr.h"
+#include "mozilla/TaskQueue.h"
+#include "mp4_demuxer/AnnexB.h"
+#include "mp4_demuxer/H264.h"
+#include "MP4Decoder.h"
+#include "nsAutoPtr.h"
+#include "nsRect.h"
+#include "PlatformDecoderModule.h"
+#include "ReorderQueue.h"
+#include "TimeUnits.h"
+#include "VideoUtils.h"
+
+namespace mozilla {
+
+// Decoder that uses a passed in object's Create function to create blank
+// MediaData objects.
+template<class BlankMediaDataCreator>
+class BlankMediaDataDecoder : public MediaDataDecoder {
+public:
+
+ BlankMediaDataDecoder(BlankMediaDataCreator* aCreator,
+ const CreateDecoderParams& aParams)
+ : mCreator(aCreator)
+ , mCallback(aParams.mCallback)
+ , mMaxRefFrames(aParams.mConfig.GetType() == TrackInfo::kVideoTrack &&
+ MP4Decoder::IsH264(aParams.mConfig.mMimeType)
+ ? mp4_demuxer::AnnexB::HasSPS(aParams.VideoConfig().mExtraData)
+ ? mp4_demuxer::H264::ComputeMaxRefFrames(aParams.VideoConfig().mExtraData)
+ : 16
+ : 0)
+ , mType(aParams.mConfig.GetType())
+ {
+ }
+
+ RefPtr<InitPromise> Init() override {
+ return InitPromise::CreateAndResolve(mType, __func__);
+ }
+
+ void Shutdown() override {}
+
+ void Input(MediaRawData* aSample) override
+ {
+ RefPtr<MediaData> data =
+ mCreator->Create(media::TimeUnit::FromMicroseconds(aSample->mTime),
+ media::TimeUnit::FromMicroseconds(aSample->mDuration),
+ aSample->mOffset);
+
+ OutputFrame(data);
+ }
+
+ void Flush() override
+ {
+ mReorderQueue.Clear();
+ }
+
+ void Drain() override
+ {
+ while (!mReorderQueue.IsEmpty()) {
+ mCallback->Output(mReorderQueue.Pop().get());
+ }
+
+ mCallback->DrainComplete();
+ }
+
+ const char* GetDescriptionName() const override
+ {
+ return "blank media data decoder";
+ }
+
+private:
+ void OutputFrame(MediaData* aData)
+ {
+ if (!aData) {
+ mCallback->Error(MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__));
+ return;
+ }
+
+ // Frames come out in DTS order but we need to output them in PTS order.
+ mReorderQueue.Push(aData);
+
+ while (mReorderQueue.Length() > mMaxRefFrames) {
+ mCallback->Output(mReorderQueue.Pop().get());
+ }
+ mCallback->InputExhausted();
+ }
+
+private:
+ nsAutoPtr<BlankMediaDataCreator> mCreator;
+ MediaDataDecoderCallback* mCallback;
+ const uint32_t mMaxRefFrames;
+ ReorderQueue mReorderQueue;
+ TrackInfo::TrackType mType;
+};
+
+class BlankVideoDataCreator {
+public:
+ BlankVideoDataCreator(uint32_t aFrameWidth,
+ uint32_t aFrameHeight,
+ layers::ImageContainer* aImageContainer)
+ : mFrameWidth(aFrameWidth)
+ , mFrameHeight(aFrameHeight)
+ , mImageContainer(aImageContainer)
+ {
+ mInfo.mDisplay = nsIntSize(mFrameWidth, mFrameHeight);
+ mPicture = gfx::IntRect(0, 0, mFrameWidth, mFrameHeight);
+ }
+
+ already_AddRefed<MediaData>
+ Create(const media::TimeUnit& aDTS, const media::TimeUnit& aDuration, int64_t aOffsetInStream)
+ {
+ // Create a fake YUV buffer in a 420 format. That is, an 8bpp Y plane,
+ // with a U and V plane that are half the size of the Y plane, i.e 8 bit,
+ // 2x2 subsampled.
+ const int sizeY = mFrameWidth * mFrameHeight;
+ const int sizeCbCr = ((mFrameWidth + 1) / 2) * ((mFrameHeight + 1) / 2);
+ auto frame = MakeUnique<uint8_t[]>(sizeY + sizeCbCr);
+ VideoData::YCbCrBuffer buffer;
+
+ // Y plane.
+ buffer.mPlanes[0].mData = frame.get();
+ buffer.mPlanes[0].mStride = mFrameWidth;
+ buffer.mPlanes[0].mHeight = mFrameHeight;
+ buffer.mPlanes[0].mWidth = mFrameWidth;
+ buffer.mPlanes[0].mOffset = 0;
+ buffer.mPlanes[0].mSkip = 0;
+
+ // Cb plane.
+ buffer.mPlanes[1].mData = frame.get() + sizeY;
+ buffer.mPlanes[1].mStride = mFrameWidth / 2;
+ buffer.mPlanes[1].mHeight = mFrameHeight / 2;
+ buffer.mPlanes[1].mWidth = mFrameWidth / 2;
+ buffer.mPlanes[1].mOffset = 0;
+ buffer.mPlanes[1].mSkip = 0;
+
+ // Cr plane.
+ buffer.mPlanes[2].mData = frame.get() + sizeY;
+ buffer.mPlanes[2].mStride = mFrameWidth / 2;
+ buffer.mPlanes[2].mHeight = mFrameHeight / 2;
+ buffer.mPlanes[2].mWidth = mFrameWidth / 2;
+ buffer.mPlanes[2].mOffset = 0;
+ buffer.mPlanes[2].mSkip = 0;
+
+ // Set to color white.
+ memset(buffer.mPlanes[0].mData, 255, sizeY);
+ memset(buffer.mPlanes[1].mData, 128, sizeCbCr);
+
+ return VideoData::CreateAndCopyData(mInfo,
+ mImageContainer,
+ aOffsetInStream,
+ aDTS.ToMicroseconds(),
+ aDuration.ToMicroseconds(),
+ buffer,
+ true,
+ aDTS.ToMicroseconds(),
+ mPicture);
+ }
+
+private:
+ VideoInfo mInfo;
+ gfx::IntRect mPicture;
+ uint32_t mFrameWidth;
+ uint32_t mFrameHeight;
+ RefPtr<layers::ImageContainer> mImageContainer;
+};
+
+class BlankAudioDataCreator {
+public:
+ BlankAudioDataCreator(uint32_t aChannelCount, uint32_t aSampleRate)
+ : mFrameSum(0), mChannelCount(aChannelCount), mSampleRate(aSampleRate)
+ {
+ }
+
+ MediaData* Create(const media::TimeUnit& aDTS,
+ const media::TimeUnit& aDuration,
+ int64_t aOffsetInStream)
+ {
+ // Convert duration to frames. We add 1 to duration to account for
+ // rounding errors, so we get a consistent tone.
+ CheckedInt64 frames =
+ UsecsToFrames(aDuration.ToMicroseconds()+1, mSampleRate);
+ if (!frames.isValid() ||
+ !mChannelCount ||
+ !mSampleRate ||
+ frames.value() > (UINT32_MAX / mChannelCount)) {
+ return nullptr;
+ }
+ AlignedAudioBuffer samples(frames.value() * mChannelCount);
+ if (!samples) {
+ return nullptr;
+ }
+ // Fill the sound buffer with an A4 tone.
+ static const float pi = 3.14159265f;
+ static const float noteHz = 440.0f;
+ for (int i = 0; i < frames.value(); i++) {
+ float f = sin(2 * pi * noteHz * mFrameSum / mSampleRate);
+ for (unsigned c = 0; c < mChannelCount; c++) {
+ samples[i * mChannelCount + c] = AudioDataValue(f);
+ }
+ mFrameSum++;
+ }
+ return new AudioData(aOffsetInStream,
+ aDTS.ToMicroseconds(),
+ aDuration.ToMicroseconds(),
+ uint32_t(frames.value()),
+ Move(samples),
+ mChannelCount,
+ mSampleRate);
+ }
+
+private:
+ int64_t mFrameSum;
+ uint32_t mChannelCount;
+ uint32_t mSampleRate;
+};
+
+class BlankDecoderModule : public PlatformDecoderModule {
+public:
+
+ // Decode thread.
+ already_AddRefed<MediaDataDecoder>
+ CreateVideoDecoder(const CreateDecoderParams& aParams) override {
+ const VideoInfo& config = aParams.VideoConfig();
+ BlankVideoDataCreator* creator = new BlankVideoDataCreator(
+ config.mDisplay.width, config.mDisplay.height, aParams.mImageContainer);
+ RefPtr<MediaDataDecoder> decoder =
+ new BlankMediaDataDecoder<BlankVideoDataCreator>(creator, aParams);
+ return decoder.forget();
+ }
+
+ // Decode thread.
+ already_AddRefed<MediaDataDecoder>
+ CreateAudioDecoder(const CreateDecoderParams& aParams) override {
+ const AudioInfo& config = aParams.AudioConfig();
+ BlankAudioDataCreator* creator = new BlankAudioDataCreator(
+ config.mChannels, config.mRate);
+
+ RefPtr<MediaDataDecoder> decoder =
+ new BlankMediaDataDecoder<BlankAudioDataCreator>(creator, aParams);
+ return decoder.forget();
+ }
+
+ bool
+ SupportsMimeType(const nsACString& aMimeType,
+ DecoderDoctorDiagnostics* aDiagnostics) const override
+ {
+ return true;
+ }
+
+ ConversionRequired
+ DecoderNeedsConversion(const TrackInfo& aConfig) const override
+ {
+ if (aConfig.IsVideo() && MP4Decoder::IsH264(aConfig.mMimeType)) {
+ return ConversionRequired::kNeedAVCC;
+ } else {
+ return ConversionRequired::kNeedNone;
+ }
+ }
+
+};
+
+already_AddRefed<PlatformDecoderModule> CreateBlankDecoderModule()
+{
+ RefPtr<PlatformDecoderModule> pdm = new BlankDecoderModule();
+ return pdm.forget();
+}
+
+} // namespace mozilla
diff --git a/dom/media/platforms/agnostic/OpusDecoder.cpp b/dom/media/platforms/agnostic/OpusDecoder.cpp
new file mode 100644
index 000000000..9163ed058
--- /dev/null
+++ b/dom/media/platforms/agnostic/OpusDecoder.cpp
@@ -0,0 +1,356 @@
+/* -*- 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 "OpusDecoder.h"
+#include "OpusParser.h"
+#include "TimeUnits.h"
+#include "VorbisUtils.h"
+#include "VorbisDecoder.h" // For VorbisLayout
+#include "mozilla/EndianUtils.h"
+#include "mozilla/PodOperations.h"
+#include "mozilla/SyncRunnable.h"
+
+#include <inttypes.h> // For PRId64
+
+#include "opus/opus.h"
+extern "C" {
+#include "opus/opus_multistream.h"
+}
+
+#define OPUS_DEBUG(arg, ...) MOZ_LOG(sPDMLog, mozilla::LogLevel::Debug, \
+ ("OpusDataDecoder(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
+
+namespace mozilla {
+
+OpusDataDecoder::OpusDataDecoder(const CreateDecoderParams& aParams)
+ : mInfo(aParams.AudioConfig())
+ , mTaskQueue(aParams.mTaskQueue)
+ , mCallback(aParams.mCallback)
+ , mOpusDecoder(nullptr)
+ , mSkip(0)
+ , mDecodedHeader(false)
+ , mPaddingDiscarded(false)
+ , mFrames(0)
+ , mIsFlushing(false)
+{
+}
+
+OpusDataDecoder::~OpusDataDecoder()
+{
+ if (mOpusDecoder) {
+ opus_multistream_decoder_destroy(mOpusDecoder);
+ mOpusDecoder = nullptr;
+ }
+}
+
+void
+OpusDataDecoder::Shutdown()
+{
+}
+
+void
+OpusDataDecoder::AppendCodecDelay(MediaByteBuffer* config, uint64_t codecDelayUS)
+{
+ uint8_t buffer[sizeof(uint64_t)];
+ BigEndian::writeUint64(buffer, codecDelayUS);
+ config->AppendElements(buffer, sizeof(uint64_t));
+}
+
+RefPtr<MediaDataDecoder::InitPromise>
+OpusDataDecoder::Init()
+{
+ size_t length = mInfo.mCodecSpecificConfig->Length();
+ uint8_t *p = mInfo.mCodecSpecificConfig->Elements();
+ if (length < sizeof(uint64_t)) {
+ OPUS_DEBUG("CodecSpecificConfig too short to read codecDelay!");
+ return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
+ }
+ int64_t codecDelay = BigEndian::readUint64(p);
+ length -= sizeof(uint64_t);
+ p += sizeof(uint64_t);
+ if (NS_FAILED(DecodeHeader(p, length))) {
+ OPUS_DEBUG("Error decoding header!");
+ return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
+ }
+
+ int r;
+ mOpusDecoder = opus_multistream_decoder_create(mOpusParser->mRate,
+ mOpusParser->mChannels,
+ mOpusParser->mStreams,
+ mOpusParser->mCoupledStreams,
+ mMappingTable,
+ &r);
+ mSkip = mOpusParser->mPreSkip;
+ mPaddingDiscarded = false;
+
+ if (codecDelay != FramesToUsecs(mOpusParser->mPreSkip,
+ mOpusParser->mRate).value()) {
+ NS_WARNING("Invalid Opus header: CodecDelay and pre-skip do not match!");
+ return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
+ }
+
+ if (mInfo.mRate != (uint32_t)mOpusParser->mRate) {
+ NS_WARNING("Invalid Opus header: container and codec rate do not match!");
+ }
+ if (mInfo.mChannels != (uint32_t)mOpusParser->mChannels) {
+ NS_WARNING("Invalid Opus header: container and codec channels do not match!");
+ }
+
+ return r == OPUS_OK ? InitPromise::CreateAndResolve(TrackInfo::kAudioTrack, __func__)
+ : InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
+}
+
+nsresult
+OpusDataDecoder::DecodeHeader(const unsigned char* aData, size_t aLength)
+{
+ MOZ_ASSERT(!mOpusParser);
+ MOZ_ASSERT(!mOpusDecoder);
+ MOZ_ASSERT(!mDecodedHeader);
+ mDecodedHeader = true;
+
+ mOpusParser = new OpusParser;
+ if (!mOpusParser->DecodeHeader(const_cast<unsigned char*>(aData), aLength)) {
+ return NS_ERROR_FAILURE;
+ }
+ int channels = mOpusParser->mChannels;
+
+ AudioConfig::ChannelLayout layout(channels);
+ if (!layout.IsValid()) {
+ OPUS_DEBUG("Invalid channel mapping. Source is %d channels", channels);
+ return NS_ERROR_FAILURE;
+ }
+
+ AudioConfig::ChannelLayout vorbisLayout(
+ channels, VorbisDataDecoder::VorbisLayout(channels));
+ AudioConfig::ChannelLayout smpteLayout(channels);
+ static_assert(sizeof(mOpusParser->mMappingTable) / sizeof(mOpusParser->mMappingTable[0]) >= MAX_AUDIO_CHANNELS,
+ "Invalid size set");
+ uint8_t map[sizeof(mOpusParser->mMappingTable) / sizeof(mOpusParser->mMappingTable[0])];
+ if (vorbisLayout.MappingTable(smpteLayout, map)) {
+ for (int i = 0; i < channels; i++) {
+ mMappingTable[i] = mOpusParser->mMappingTable[map[i]];
+ }
+ } else {
+ // Should never get here as vorbis layout is always convertible to SMPTE
+ // default layout.
+ PodCopy(mMappingTable, mOpusParser->mMappingTable, MAX_AUDIO_CHANNELS);
+ }
+
+ return NS_OK;
+}
+
+void
+OpusDataDecoder::Input(MediaRawData* aSample)
+{
+ mTaskQueue->Dispatch(NewRunnableMethod<RefPtr<MediaRawData>>(
+ this, &OpusDataDecoder::ProcessDecode, aSample));
+}
+
+void
+OpusDataDecoder::ProcessDecode(MediaRawData* aSample)
+{
+ if (mIsFlushing) {
+ return;
+ }
+
+ MediaResult rv = DoDecode(aSample);
+ if (NS_FAILED(rv)) {
+ mCallback->Error(rv);
+ return;
+ }
+ mCallback->InputExhausted();
+}
+
+MediaResult
+OpusDataDecoder::DoDecode(MediaRawData* aSample)
+{
+ uint32_t channels = mOpusParser->mChannels;
+
+ if (mPaddingDiscarded) {
+ // Discard padding should be used only on the final packet, so
+ // decoding after a padding discard is invalid.
+ OPUS_DEBUG("Opus error, discard padding on interstitial packet");
+ return MediaResult(
+ NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ RESULT_DETAIL("Discard padding on interstitial packet"));
+ }
+
+ if (!mLastFrameTime || mLastFrameTime.ref() != aSample->mTime) {
+ // We are starting a new block.
+ mFrames = 0;
+ mLastFrameTime = Some(aSample->mTime);
+ }
+
+ // Maximum value is 63*2880, so there's no chance of overflow.
+ int frames_number =
+ opus_packet_get_nb_frames(aSample->Data(), aSample->Size());
+ if (frames_number <= 0) {
+ OPUS_DEBUG("Invalid packet header: r=%d length=%u",
+ frames_number, uint32_t(aSample->Size()));
+ return MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR,
+ RESULT_DETAIL("Invalid packet header: r=%d length=%u",
+ frames_number, uint32_t(aSample->Size())));
+ }
+
+ int samples = opus_packet_get_samples_per_frame(
+ aSample->Data(), opus_int32(mOpusParser->mRate));
+
+
+ // A valid Opus packet must be between 2.5 and 120 ms long (48kHz).
+ CheckedInt32 totalFrames =
+ CheckedInt32(frames_number) * CheckedInt32(samples);
+ if (!totalFrames.isValid()) {
+ return MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR,
+ RESULT_DETAIL("Frames count overflow"));
+ }
+
+ int frames = totalFrames.value();
+ if (frames < 120 || frames > 5760) {
+ OPUS_DEBUG("Invalid packet frames: %d", frames);
+ return MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR,
+ RESULT_DETAIL("Invalid packet frames:%d", frames));
+ }
+
+ AlignedAudioBuffer buffer(frames * channels);
+ if (!buffer) {
+ return MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__);
+ }
+
+ // Decode to the appropriate sample type.
+#ifdef MOZ_SAMPLE_TYPE_FLOAT32
+ int ret = opus_multistream_decode_float(mOpusDecoder,
+ aSample->Data(), aSample->Size(),
+ buffer.get(), frames, false);
+#else
+ int ret = opus_multistream_decode(mOpusDecoder,
+ aSample->Data(), aSample->Size(),
+ buffer.get(), frames, false);
+#endif
+ if (ret < 0) {
+ return MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR,
+ RESULT_DETAIL("Opus decoding error:%d", ret));
+ }
+ NS_ASSERTION(ret == frames, "Opus decoded too few audio samples");
+ CheckedInt64 startTime = aSample->mTime;
+
+ // Trim the initial frames while the decoder is settling.
+ if (mSkip > 0) {
+ int32_t skipFrames = std::min<int32_t>(mSkip, frames);
+ int32_t keepFrames = frames - skipFrames;
+ OPUS_DEBUG(
+ "Opus decoder skipping %d of %d frames", skipFrames, frames);
+ PodMove(buffer.get(),
+ buffer.get() + skipFrames * channels,
+ keepFrames * channels);
+ startTime = startTime + FramesToUsecs(skipFrames, mOpusParser->mRate);
+ frames = keepFrames;
+ mSkip -= skipFrames;
+ }
+
+ if (aSample->mDiscardPadding > 0) {
+ OPUS_DEBUG("Opus decoder discarding %u of %d frames",
+ aSample->mDiscardPadding, frames);
+ // Padding discard is only supposed to happen on the final packet.
+ // Record the discard so we can return an error if another packet is
+ // decoded.
+ if (aSample->mDiscardPadding > uint32_t(frames)) {
+ // Discarding more than the entire packet is invalid.
+ OPUS_DEBUG("Opus error, discard padding larger than packet");
+ return MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ RESULT_DETAIL("Discard padding larger than packet"));
+ }
+
+ mPaddingDiscarded = true;
+ frames = frames - aSample->mDiscardPadding;
+ }
+
+ // Apply the header gain if one was specified.
+#ifdef MOZ_SAMPLE_TYPE_FLOAT32
+ if (mOpusParser->mGain != 1.0f) {
+ float gain = mOpusParser->mGain;
+ uint32_t samples = frames * channels;
+ for (uint32_t i = 0; i < samples; i++) {
+ buffer[i] *= gain;
+ }
+ }
+#else
+ if (mOpusParser->mGain_Q16 != 65536) {
+ int64_t gain_Q16 = mOpusParser->mGain_Q16;
+ uint32_t samples = frames * channels;
+ for (uint32_t i = 0; i < samples; i++) {
+ int32_t val = static_cast<int32_t>((gain_Q16*buffer[i] + 32768)>>16);
+ buffer[i] = static_cast<AudioDataValue>(MOZ_CLIP_TO_15(val));
+ }
+ }
+#endif
+
+ CheckedInt64 duration = FramesToUsecs(frames, mOpusParser->mRate);
+ if (!duration.isValid()) {
+ return MediaResult(
+ NS_ERROR_DOM_MEDIA_OVERFLOW_ERR,
+ RESULT_DETAIL("Overflow converting WebM audio duration"));
+ }
+ CheckedInt64 time =
+ startTime - FramesToUsecs(mOpusParser->mPreSkip, mOpusParser->mRate) +
+ FramesToUsecs(mFrames, mOpusParser->mRate);
+ if (!time.isValid()) {
+ return MediaResult(
+ NS_ERROR_DOM_MEDIA_OVERFLOW_ERR,
+ RESULT_DETAIL("Overflow shifting tstamp by codec delay"));
+ };
+
+ mCallback->Output(new AudioData(aSample->mOffset,
+ time.value(),
+ duration.value(),
+ frames,
+ Move(buffer),
+ mOpusParser->mChannels,
+ mOpusParser->mRate));
+ mFrames += frames;
+ return NS_OK;
+}
+
+void
+OpusDataDecoder::ProcessDrain()
+{
+ mCallback->DrainComplete();
+}
+
+void
+OpusDataDecoder::Drain()
+{
+ mTaskQueue->Dispatch(NewRunnableMethod(this, &OpusDataDecoder::ProcessDrain));
+}
+
+void
+OpusDataDecoder::Flush()
+{
+ if (!mOpusDecoder) {
+ return;
+ }
+ mIsFlushing = true;
+ RefPtr<OpusDataDecoder> self = this;
+ nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction([self] () {
+ MOZ_ASSERT(self->mOpusDecoder);
+ // Reset the decoder.
+ opus_multistream_decoder_ctl(self->mOpusDecoder, OPUS_RESET_STATE);
+ self->mSkip = self->mOpusParser->mPreSkip;
+ self->mPaddingDiscarded = false;
+ self->mLastFrameTime.reset();
+ });
+ SyncRunnable::DispatchToThread(mTaskQueue, runnable);
+ mIsFlushing = false;
+}
+
+/* static */
+bool
+OpusDataDecoder::IsOpus(const nsACString& aMimeType)
+{
+ return aMimeType.EqualsLiteral("audio/opus");
+}
+
+} // namespace mozilla
+#undef OPUS_DEBUG
diff --git a/dom/media/platforms/agnostic/OpusDecoder.h b/dom/media/platforms/agnostic/OpusDecoder.h
new file mode 100644
index 000000000..6d82ae502
--- /dev/null
+++ b/dom/media/platforms/agnostic/OpusDecoder.h
@@ -0,0 +1,76 @@
+/* -*- 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(OpusDecoder_h_)
+#define OpusDecoder_h_
+
+#include "PlatformDecoderModule.h"
+
+#include "mozilla/Maybe.h"
+#include "nsAutoPtr.h"
+
+struct OpusMSDecoder;
+
+namespace mozilla {
+
+class OpusParser;
+
+class OpusDataDecoder : public MediaDataDecoder
+{
+public:
+ explicit OpusDataDecoder(const CreateDecoderParams& aParams);
+ ~OpusDataDecoder();
+
+ RefPtr<InitPromise> Init() override;
+ void Input(MediaRawData* aSample) override;
+ void Flush() override;
+ void Drain() override;
+ void Shutdown() override;
+ const char* GetDescriptionName() const override
+ {
+ return "opus audio decoder";
+ }
+
+ // Return true if mimetype is Opus
+ static bool IsOpus(const nsACString& aMimeType);
+
+ // Pack pre-skip/CodecDelay, given in microseconds, into a
+ // MediaByteBuffer. The decoder expects this value to come
+ // from the container (if any) and to precede the OpusHead
+ // block in the CodecSpecificConfig buffer to verify the
+ // values match.
+ static void AppendCodecDelay(MediaByteBuffer* config, uint64_t codecDelayUS);
+
+private:
+ nsresult DecodeHeader(const unsigned char* aData, size_t aLength);
+
+ void ProcessDecode(MediaRawData* aSample);
+ MediaResult DoDecode(MediaRawData* aSample);
+ void ProcessDrain();
+
+ const AudioInfo& mInfo;
+ const RefPtr<TaskQueue> mTaskQueue;
+ MediaDataDecoderCallback* mCallback;
+
+ // Opus decoder state
+ nsAutoPtr<OpusParser> mOpusParser;
+ OpusMSDecoder* mOpusDecoder;
+
+ uint16_t mSkip; // Samples left to trim before playback.
+ bool mDecodedHeader;
+
+ // Opus padding should only be discarded on the final packet. Once this
+ // is set to true, if the reader attempts to decode any further packets it
+ // will raise an error so we can indicate that the file is invalid.
+ bool mPaddingDiscarded;
+ int64_t mFrames;
+ Maybe<int64_t> mLastFrameTime;
+ uint8_t mMappingTable[MAX_AUDIO_CHANNELS]; // Channel mapping table.
+
+ Atomic<bool> mIsFlushing;
+};
+
+} // namespace mozilla
+#endif
diff --git a/dom/media/platforms/agnostic/TheoraDecoder.cpp b/dom/media/platforms/agnostic/TheoraDecoder.cpp
new file mode 100644
index 000000000..b216791e4
--- /dev/null
+++ b/dom/media/platforms/agnostic/TheoraDecoder.cpp
@@ -0,0 +1,240 @@
+/* -*- 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 "TheoraDecoder.h"
+#include "XiphExtradata.h"
+#include "gfx2DGlue.h"
+#include "nsError.h"
+#include "TimeUnits.h"
+#include "mozilla/PodOperations.h"
+
+#include <algorithm>
+
+#undef LOG
+#define LOG(arg, ...) MOZ_LOG(gMediaDecoderLog, mozilla::LogLevel::Debug, ("TheoraDecoder(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
+
+namespace mozilla {
+
+using namespace gfx;
+using namespace layers;
+
+extern LazyLogModule gMediaDecoderLog;
+
+ogg_packet InitTheoraPacket(const unsigned char* aData, size_t aLength,
+ bool aBOS, bool aEOS,
+ int64_t aGranulepos, int64_t aPacketNo)
+{
+ ogg_packet packet;
+ packet.packet = const_cast<unsigned char*>(aData);
+ packet.bytes = aLength;
+ packet.b_o_s = aBOS;
+ packet.e_o_s = aEOS;
+ packet.granulepos = aGranulepos;
+ packet.packetno = aPacketNo;
+ return packet;
+}
+
+TheoraDecoder::TheoraDecoder(const CreateDecoderParams& aParams)
+ : mImageContainer(aParams.mImageContainer)
+ , mTaskQueue(aParams.mTaskQueue)
+ , mCallback(aParams.mCallback)
+ , mIsFlushing(false)
+ , mTheoraSetupInfo(nullptr)
+ , mTheoraDecoderContext(nullptr)
+ , mPacketCount(0)
+ , mInfo(aParams.VideoConfig())
+{
+ MOZ_COUNT_CTOR(TheoraDecoder);
+}
+
+TheoraDecoder::~TheoraDecoder()
+{
+ MOZ_COUNT_DTOR(TheoraDecoder);
+ th_setup_free(mTheoraSetupInfo);
+ th_comment_clear(&mTheoraComment);
+ th_info_clear(&mTheoraInfo);
+}
+
+void
+TheoraDecoder::Shutdown()
+{
+ if (mTheoraDecoderContext) {
+ th_decode_free(mTheoraDecoderContext);
+ mTheoraDecoderContext = nullptr;
+ }
+}
+
+RefPtr<MediaDataDecoder::InitPromise>
+TheoraDecoder::Init()
+{
+ th_comment_init(&mTheoraComment);
+ th_info_init(&mTheoraInfo);
+
+ nsTArray<unsigned char*> headers;
+ nsTArray<size_t> headerLens;
+ if (!XiphExtradataToHeaders(headers, headerLens,
+ mInfo.mCodecSpecificConfig->Elements(),
+ mInfo.mCodecSpecificConfig->Length())) {
+ return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
+ }
+ for (size_t i = 0; i < headers.Length(); i++) {
+ if (NS_FAILED(DoDecodeHeader(headers[i], headerLens[i]))) {
+ return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
+ }
+ }
+ if (mPacketCount != 3) {
+ return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
+ }
+
+ mTheoraDecoderContext = th_decode_alloc(&mTheoraInfo, mTheoraSetupInfo);
+ if (mTheoraDecoderContext) {
+ return InitPromise::CreateAndResolve(TrackInfo::kVideoTrack, __func__);
+ } else {
+ return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
+ }
+
+}
+
+void
+TheoraDecoder::Flush()
+{
+ MOZ_ASSERT(mCallback->OnReaderTaskQueue());
+ mIsFlushing = true;
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([this] () {
+ // nothing to do for now.
+ });
+ SyncRunnable::DispatchToThread(mTaskQueue, r);
+ mIsFlushing = false;
+}
+
+nsresult
+TheoraDecoder::DoDecodeHeader(const unsigned char* aData, size_t aLength)
+{
+ bool bos = mPacketCount == 0;
+ ogg_packet pkt = InitTheoraPacket(aData, aLength, bos, false, 0, mPacketCount++);
+
+ int r = th_decode_headerin(&mTheoraInfo,
+ &mTheoraComment,
+ &mTheoraSetupInfo,
+ &pkt);
+ return r > 0 ? NS_OK : NS_ERROR_FAILURE;
+}
+
+MediaResult
+TheoraDecoder::DoDecode(MediaRawData* aSample)
+{
+ MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
+
+ const unsigned char* aData = aSample->Data();
+ size_t aLength = aSample->Size();
+
+ bool bos = mPacketCount == 0;
+ ogg_packet pkt = InitTheoraPacket(aData, aLength, bos, false, aSample->mTimecode, mPacketCount++);
+
+ int ret = th_decode_packetin(mTheoraDecoderContext, &pkt, nullptr);
+ if (ret == 0 || ret == TH_DUPFRAME) {
+ th_ycbcr_buffer ycbcr;
+ th_decode_ycbcr_out(mTheoraDecoderContext, ycbcr);
+
+ int hdec = !(mTheoraInfo.pixel_fmt & 1);
+ int vdec = !(mTheoraInfo.pixel_fmt & 2);
+
+ VideoData::YCbCrBuffer b;
+ b.mPlanes[0].mData = ycbcr[0].data;
+ b.mPlanes[0].mStride = ycbcr[0].stride;
+ b.mPlanes[0].mHeight = mTheoraInfo.frame_height;
+ b.mPlanes[0].mWidth = mTheoraInfo.frame_width;
+ b.mPlanes[0].mOffset = b.mPlanes[0].mSkip = 0;
+
+ b.mPlanes[1].mData = ycbcr[1].data;
+ b.mPlanes[1].mStride = ycbcr[1].stride;
+ b.mPlanes[1].mHeight = mTheoraInfo.frame_height >> vdec;
+ b.mPlanes[1].mWidth = mTheoraInfo.frame_width >> hdec;
+ b.mPlanes[1].mOffset = b.mPlanes[1].mSkip = 0;
+
+ b.mPlanes[2].mData = ycbcr[2].data;
+ b.mPlanes[2].mStride = ycbcr[2].stride;
+ b.mPlanes[2].mHeight = mTheoraInfo.frame_height >> vdec;
+ b.mPlanes[2].mWidth = mTheoraInfo.frame_width >> hdec;
+ b.mPlanes[2].mOffset = b.mPlanes[2].mSkip = 0;
+
+ IntRect pictureArea(mTheoraInfo.pic_x, mTheoraInfo.pic_y,
+ mTheoraInfo.pic_width, mTheoraInfo.pic_height);
+
+ VideoInfo info;
+ info.mDisplay = mInfo.mDisplay;
+ RefPtr<VideoData> v =
+ VideoData::CreateAndCopyData(info,
+ mImageContainer,
+ aSample->mOffset,
+ aSample->mTime,
+ aSample->mDuration,
+ b,
+ aSample->mKeyframe,
+ aSample->mTimecode,
+ mInfo.ScaledImageRect(mTheoraInfo.frame_width,
+ mTheoraInfo.frame_height));
+ if (!v) {
+ LOG("Image allocation error source %ldx%ld display %ldx%ld picture %ldx%ld",
+ mTheoraInfo.frame_width, mTheoraInfo.frame_height, mInfo.mDisplay.width, mInfo.mDisplay.height,
+ mInfo.mImage.width, mInfo.mImage.height);
+ return MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__);
+ }
+ mCallback->Output(v);
+ return NS_OK;
+ } else {
+ LOG("Theora Decode error: %d", ret);
+ return MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR,
+ RESULT_DETAIL("Theora decode error:%d", ret));
+ }
+}
+
+void
+TheoraDecoder::ProcessDecode(MediaRawData* aSample)
+{
+ MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
+ if (mIsFlushing) {
+ return;
+ }
+ MediaResult rv = DoDecode(aSample);
+ if (NS_FAILED(rv)) {
+ mCallback->Error(rv);
+ } else {
+ mCallback->InputExhausted();
+ }
+}
+
+void
+TheoraDecoder::Input(MediaRawData* aSample)
+{
+ MOZ_ASSERT(mCallback->OnReaderTaskQueue());
+ mTaskQueue->Dispatch(NewRunnableMethod<RefPtr<MediaRawData>>(
+ this, &TheoraDecoder::ProcessDecode, aSample));
+}
+
+void
+TheoraDecoder::ProcessDrain()
+{
+ MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
+ mCallback->DrainComplete();
+}
+
+void
+TheoraDecoder::Drain()
+{
+ MOZ_ASSERT(mCallback->OnReaderTaskQueue());
+ mTaskQueue->Dispatch(NewRunnableMethod(this, &TheoraDecoder::ProcessDrain));
+}
+
+/* static */
+bool
+TheoraDecoder::IsTheora(const nsACString& aMimeType)
+{
+ return aMimeType.EqualsLiteral("video/theora");
+}
+
+} // namespace mozilla
+#undef LOG
diff --git a/dom/media/platforms/agnostic/TheoraDecoder.h b/dom/media/platforms/agnostic/TheoraDecoder.h
new file mode 100644
index 000000000..4a5daebb9
--- /dev/null
+++ b/dom/media/platforms/agnostic/TheoraDecoder.h
@@ -0,0 +1,64 @@
+/* -*- 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(TheoraDecoder_h_)
+#define TheoraDecoder_h_
+
+#include "PlatformDecoderModule.h"
+
+#include <stdint.h>
+#include "ogg/ogg.h"
+#include "theora/theoradec.h"
+
+namespace mozilla {
+
+ using namespace layers;
+
+class TheoraDecoder : public MediaDataDecoder
+{
+public:
+ explicit TheoraDecoder(const CreateDecoderParams& aParams);
+
+ ~TheoraDecoder();
+
+ RefPtr<InitPromise> Init() override;
+ void Input(MediaRawData* aSample) override;
+ void Flush() override;
+ void Drain() override;
+ void Shutdown() override;
+
+ // Return true if mimetype is a Theora codec
+ static bool IsTheora(const nsACString& aMimeType);
+
+ const char* GetDescriptionName() const override
+ {
+ return "theora video decoder";
+ }
+
+private:
+ nsresult DoDecodeHeader(const unsigned char* aData, size_t aLength);
+
+ void ProcessDecode(MediaRawData* aSample);
+ MediaResult DoDecode(MediaRawData* aSample);
+ void ProcessDrain();
+
+ RefPtr<ImageContainer> mImageContainer;
+ RefPtr<TaskQueue> mTaskQueue;
+ MediaDataDecoderCallback* mCallback;
+ Atomic<bool> mIsFlushing;
+
+ // Theora header & decoder state
+ th_info mTheoraInfo;
+ th_comment mTheoraComment;
+ th_setup_info *mTheoraSetupInfo;
+ th_dec_ctx *mTheoraDecoderContext;
+ int mPacketCount;
+
+ const VideoInfo& mInfo;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/platforms/agnostic/VPXDecoder.cpp b/dom/media/platforms/agnostic/VPXDecoder.cpp
new file mode 100644
index 000000000..77c81b51b
--- /dev/null
+++ b/dom/media/platforms/agnostic/VPXDecoder.cpp
@@ -0,0 +1,253 @@
+/* -*- 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 "VPXDecoder.h"
+#include "gfx2DGlue.h"
+#include "nsError.h"
+#include "TimeUnits.h"
+#include "mozilla/PodOperations.h"
+#include "mozilla/SyncRunnable.h"
+#include "prsystem.h"
+
+#include <algorithm>
+
+#undef LOG
+#define LOG(arg, ...) MOZ_LOG(sPDMLog, mozilla::LogLevel::Debug, ("VPXDecoder(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
+
+namespace mozilla {
+
+using namespace gfx;
+using namespace layers;
+
+static int MimeTypeToCodec(const nsACString& aMimeType)
+{
+ if (aMimeType.EqualsLiteral("video/webm; codecs=vp8")) {
+ return VPXDecoder::Codec::VP8;
+ } else if (aMimeType.EqualsLiteral("video/webm; codecs=vp9")) {
+ return VPXDecoder::Codec::VP9;
+ } else if (aMimeType.EqualsLiteral("video/vp9")) {
+ return VPXDecoder::Codec::VP9;
+ }
+ return -1;
+}
+
+VPXDecoder::VPXDecoder(const CreateDecoderParams& aParams)
+ : mImageContainer(aParams.mImageContainer)
+ , mTaskQueue(aParams.mTaskQueue)
+ , mCallback(aParams.mCallback)
+ , mIsFlushing(false)
+ , mInfo(aParams.VideoConfig())
+ , mCodec(MimeTypeToCodec(aParams.VideoConfig().mMimeType))
+{
+ MOZ_COUNT_CTOR(VPXDecoder);
+ PodZero(&mVPX);
+}
+
+VPXDecoder::~VPXDecoder()
+{
+ MOZ_COUNT_DTOR(VPXDecoder);
+}
+
+void
+VPXDecoder::Shutdown()
+{
+ vpx_codec_destroy(&mVPX);
+}
+
+RefPtr<MediaDataDecoder::InitPromise>
+VPXDecoder::Init()
+{
+ int decode_threads = 2;
+
+ vpx_codec_iface_t* dx = nullptr;
+ if (mCodec == Codec::VP8) {
+ dx = vpx_codec_vp8_dx();
+ } else if (mCodec == Codec::VP9) {
+ dx = vpx_codec_vp9_dx();
+ if (mInfo.mDisplay.width >= 2048) {
+ decode_threads = 8;
+ } else if (mInfo.mDisplay.width >= 1024) {
+ decode_threads = 4;
+ }
+ }
+ decode_threads = std::min(decode_threads, PR_GetNumberOfProcessors());
+
+ vpx_codec_dec_cfg_t config;
+ config.threads = decode_threads;
+ config.w = config.h = 0; // set after decode
+
+ if (!dx || vpx_codec_dec_init(&mVPX, dx, &config, 0)) {
+ return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
+ }
+ return InitPromise::CreateAndResolve(TrackInfo::kVideoTrack, __func__);
+}
+
+void
+VPXDecoder::Flush()
+{
+ MOZ_ASSERT(mCallback->OnReaderTaskQueue());
+ mIsFlushing = true;
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([this] () {
+ // nothing to do for now.
+ });
+ SyncRunnable::DispatchToThread(mTaskQueue, r);
+ mIsFlushing = false;
+}
+
+MediaResult
+VPXDecoder::DoDecode(MediaRawData* aSample)
+{
+ MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
+#if defined(DEBUG)
+ vpx_codec_stream_info_t si;
+ PodZero(&si);
+ si.sz = sizeof(si);
+ if (mCodec == Codec::VP8) {
+ vpx_codec_peek_stream_info(vpx_codec_vp8_dx(), aSample->Data(), aSample->Size(), &si);
+ } else if (mCodec == Codec::VP9) {
+ vpx_codec_peek_stream_info(vpx_codec_vp9_dx(), aSample->Data(), aSample->Size(), &si);
+ }
+ NS_ASSERTION(bool(si.is_kf) == aSample->mKeyframe,
+ "VPX Decode Keyframe error sample->mKeyframe and si.si_kf out of sync");
+#endif
+
+ if (vpx_codec_err_t r = vpx_codec_decode(&mVPX, aSample->Data(), aSample->Size(), nullptr, 0)) {
+ LOG("VPX Decode error: %s", vpx_codec_err_to_string(r));
+ return MediaResult(
+ NS_ERROR_DOM_MEDIA_DECODE_ERR,
+ RESULT_DETAIL("VPX error: %s", vpx_codec_err_to_string(r)));
+ }
+
+ vpx_codec_iter_t iter = nullptr;
+ vpx_image_t *img;
+
+ while ((img = vpx_codec_get_frame(&mVPX, &iter))) {
+ NS_ASSERTION(img->fmt == VPX_IMG_FMT_I420 ||
+ img->fmt == VPX_IMG_FMT_I444,
+ "WebM image format not I420 or I444");
+
+ // Chroma shifts are rounded down as per the decoding examples in the SDK
+ VideoData::YCbCrBuffer b;
+ b.mPlanes[0].mData = img->planes[0];
+ b.mPlanes[0].mStride = img->stride[0];
+ b.mPlanes[0].mHeight = img->d_h;
+ b.mPlanes[0].mWidth = img->d_w;
+ b.mPlanes[0].mOffset = b.mPlanes[0].mSkip = 0;
+
+ b.mPlanes[1].mData = img->planes[1];
+ b.mPlanes[1].mStride = img->stride[1];
+ b.mPlanes[1].mOffset = b.mPlanes[1].mSkip = 0;
+
+ b.mPlanes[2].mData = img->planes[2];
+ b.mPlanes[2].mStride = img->stride[2];
+ b.mPlanes[2].mOffset = b.mPlanes[2].mSkip = 0;
+
+ if (img->fmt == VPX_IMG_FMT_I420) {
+ b.mPlanes[1].mHeight = (img->d_h + 1) >> img->y_chroma_shift;
+ b.mPlanes[1].mWidth = (img->d_w + 1) >> img->x_chroma_shift;
+
+ b.mPlanes[2].mHeight = (img->d_h + 1) >> img->y_chroma_shift;
+ b.mPlanes[2].mWidth = (img->d_w + 1) >> img->x_chroma_shift;
+ } else if (img->fmt == VPX_IMG_FMT_I444) {
+ b.mPlanes[1].mHeight = img->d_h;
+ b.mPlanes[1].mWidth = img->d_w;
+
+ b.mPlanes[2].mHeight = img->d_h;
+ b.mPlanes[2].mWidth = img->d_w;
+ } else {
+ LOG("VPX Unknown image format");
+ return MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR,
+ RESULT_DETAIL("VPX Unknown image format"));
+ }
+
+ RefPtr<VideoData> v =
+ VideoData::CreateAndCopyData(mInfo,
+ mImageContainer,
+ aSample->mOffset,
+ aSample->mTime,
+ aSample->mDuration,
+ b,
+ aSample->mKeyframe,
+ aSample->mTimecode,
+ mInfo.ScaledImageRect(img->d_w,
+ img->d_h));
+
+ if (!v) {
+ LOG("Image allocation error source %ldx%ld display %ldx%ld picture %ldx%ld",
+ img->d_w, img->d_h, mInfo.mDisplay.width, mInfo.mDisplay.height,
+ mInfo.mImage.width, mInfo.mImage.height);
+ return MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__);
+ }
+ mCallback->Output(v);
+ }
+ return NS_OK;
+}
+
+void
+VPXDecoder::ProcessDecode(MediaRawData* aSample)
+{
+ MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
+ if (mIsFlushing) {
+ return;
+ }
+ MediaResult rv = DoDecode(aSample);
+ if (NS_FAILED(rv)) {
+ mCallback->Error(rv);
+ } else {
+ mCallback->InputExhausted();
+ }
+}
+
+void
+VPXDecoder::Input(MediaRawData* aSample)
+{
+ MOZ_ASSERT(mCallback->OnReaderTaskQueue());
+ mTaskQueue->Dispatch(NewRunnableMethod<RefPtr<MediaRawData>>(
+ this, &VPXDecoder::ProcessDecode, aSample));
+}
+
+void
+VPXDecoder::ProcessDrain()
+{
+ MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
+ mCallback->DrainComplete();
+}
+
+void
+VPXDecoder::Drain()
+{
+ MOZ_ASSERT(mCallback->OnReaderTaskQueue());
+ mTaskQueue->Dispatch(NewRunnableMethod(this, &VPXDecoder::ProcessDrain));
+}
+
+/* static */
+bool
+VPXDecoder::IsVPX(const nsACString& aMimeType, uint8_t aCodecMask)
+{
+ return ((aCodecMask & VPXDecoder::VP8) &&
+ aMimeType.EqualsLiteral("video/webm; codecs=vp8")) ||
+ ((aCodecMask & VPXDecoder::VP9) &&
+ aMimeType.EqualsLiteral("video/webm; codecs=vp9")) ||
+ ((aCodecMask & VPXDecoder::VP9) &&
+ aMimeType.EqualsLiteral("video/vp9"));
+}
+
+/* static */
+bool
+VPXDecoder::IsVP8(const nsACString& aMimeType)
+{
+ return IsVPX(aMimeType, VPXDecoder::VP8);
+}
+
+/* static */
+bool
+VPXDecoder::IsVP9(const nsACString& aMimeType)
+{
+ return IsVPX(aMimeType, VPXDecoder::VP9);
+}
+
+} // namespace mozilla
+#undef LOG
diff --git a/dom/media/platforms/agnostic/VPXDecoder.h b/dom/media/platforms/agnostic/VPXDecoder.h
new file mode 100644
index 000000000..d420ec069
--- /dev/null
+++ b/dom/media/platforms/agnostic/VPXDecoder.h
@@ -0,0 +1,69 @@
+/* -*- 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(VPXDecoder_h_)
+#define VPXDecoder_h_
+
+#include "PlatformDecoderModule.h"
+
+#include <stdint.h>
+#define VPX_DONT_DEFINE_STDINT_TYPES
+#include "vpx/vp8dx.h"
+#include "vpx/vpx_codec.h"
+#include "vpx/vpx_decoder.h"
+
+namespace mozilla {
+
+using namespace layers;
+
+class VPXDecoder : public MediaDataDecoder
+{
+public:
+ explicit VPXDecoder(const CreateDecoderParams& aParams);
+ ~VPXDecoder();
+
+ RefPtr<InitPromise> Init() override;
+ void Input(MediaRawData* aSample) override;
+ void Flush() override;
+ void Drain() override;
+ void Shutdown() override;
+ const char* GetDescriptionName() const override
+ {
+ return "libvpx video decoder";
+ }
+
+ enum Codec: uint8_t {
+ VP8 = 1 << 0,
+ VP9 = 1 << 1
+ };
+
+ // Return true if aMimeType is a one of the strings used by our demuxers to
+ // identify VPX of the specified type. Does not parse general content type
+ // strings, i.e. white space matters.
+ static bool IsVPX(const nsACString& aMimeType, uint8_t aCodecMask=VP8|VP9);
+ static bool IsVP8(const nsACString& aMimeType);
+ static bool IsVP9(const nsACString& aMimeType);
+
+private:
+ void ProcessDecode(MediaRawData* aSample);
+ MediaResult DoDecode(MediaRawData* aSample);
+ void ProcessDrain();
+
+ const RefPtr<ImageContainer> mImageContainer;
+ const RefPtr<TaskQueue> mTaskQueue;
+ MediaDataDecoderCallback* mCallback;
+ Atomic<bool> mIsFlushing;
+
+ // VPx decoder state
+ vpx_codec_ctx_t mVPX;
+
+ const VideoInfo& mInfo;
+
+ const int mCodec;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/platforms/agnostic/VorbisDecoder.cpp b/dom/media/platforms/agnostic/VorbisDecoder.cpp
new file mode 100644
index 000000000..ed8b90dbd
--- /dev/null
+++ b/dom/media/platforms/agnostic/VorbisDecoder.cpp
@@ -0,0 +1,349 @@
+/* -*- 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 "VorbisDecoder.h"
+#include "VorbisUtils.h"
+#include "XiphExtradata.h"
+
+#include "mozilla/PodOperations.h"
+#include "mozilla/SyncRunnable.h"
+
+#undef LOG
+#define LOG(type, msg) MOZ_LOG(sPDMLog, type, msg)
+
+namespace mozilla {
+
+ogg_packet InitVorbisPacket(const unsigned char* aData, size_t aLength,
+ bool aBOS, bool aEOS,
+ int64_t aGranulepos, int64_t aPacketNo)
+{
+ ogg_packet packet;
+ packet.packet = const_cast<unsigned char*>(aData);
+ packet.bytes = aLength;
+ packet.b_o_s = aBOS;
+ packet.e_o_s = aEOS;
+ packet.granulepos = aGranulepos;
+ packet.packetno = aPacketNo;
+ return packet;
+}
+
+VorbisDataDecoder::VorbisDataDecoder(const CreateDecoderParams& aParams)
+ : mInfo(aParams.AudioConfig())
+ , mTaskQueue(aParams.mTaskQueue)
+ , mCallback(aParams.mCallback)
+ , mPacketCount(0)
+ , mFrames(0)
+ , mIsFlushing(false)
+{
+ // Zero these member vars to avoid crashes in Vorbis clear functions when
+ // destructor is called before |Init|.
+ PodZero(&mVorbisBlock);
+ PodZero(&mVorbisDsp);
+ PodZero(&mVorbisInfo);
+ PodZero(&mVorbisComment);
+}
+
+VorbisDataDecoder::~VorbisDataDecoder()
+{
+ vorbis_block_clear(&mVorbisBlock);
+ vorbis_dsp_clear(&mVorbisDsp);
+ vorbis_info_clear(&mVorbisInfo);
+ vorbis_comment_clear(&mVorbisComment);
+}
+
+void
+VorbisDataDecoder::Shutdown()
+{
+}
+
+RefPtr<MediaDataDecoder::InitPromise>
+VorbisDataDecoder::Init()
+{
+ vorbis_info_init(&mVorbisInfo);
+ vorbis_comment_init(&mVorbisComment);
+ PodZero(&mVorbisDsp);
+ PodZero(&mVorbisBlock);
+
+ AutoTArray<unsigned char*,4> headers;
+ AutoTArray<size_t,4> headerLens;
+ if (!XiphExtradataToHeaders(headers, headerLens,
+ mInfo.mCodecSpecificConfig->Elements(),
+ mInfo.mCodecSpecificConfig->Length())) {
+ return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
+ }
+ for (size_t i = 0; i < headers.Length(); i++) {
+ if (NS_FAILED(DecodeHeader(headers[i], headerLens[i]))) {
+ return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
+ }
+ }
+
+ MOZ_ASSERT(mPacketCount == 3);
+
+ int r = vorbis_synthesis_init(&mVorbisDsp, &mVorbisInfo);
+ if (r) {
+ return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
+ }
+
+ r = vorbis_block_init(&mVorbisDsp, &mVorbisBlock);
+ if (r) {
+ return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
+ }
+
+ if (mInfo.mRate != (uint32_t)mVorbisDsp.vi->rate) {
+ LOG(LogLevel::Warning,
+ ("Invalid Vorbis header: container and codec rate do not match!"));
+ }
+ if (mInfo.mChannels != (uint32_t)mVorbisDsp.vi->channels) {
+ LOG(LogLevel::Warning,
+ ("Invalid Vorbis header: container and codec channels do not match!"));
+ }
+
+ AudioConfig::ChannelLayout layout(mVorbisDsp.vi->channels);
+ if (!layout.IsValid()) {
+ return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
+ }
+
+ return InitPromise::CreateAndResolve(TrackInfo::kAudioTrack, __func__);
+}
+
+nsresult
+VorbisDataDecoder::DecodeHeader(const unsigned char* aData, size_t aLength)
+{
+ bool bos = mPacketCount == 0;
+ ogg_packet pkt = InitVorbisPacket(aData, aLength, bos, false, 0, mPacketCount++);
+ MOZ_ASSERT(mPacketCount <= 3);
+
+ int r = vorbis_synthesis_headerin(&mVorbisInfo,
+ &mVorbisComment,
+ &pkt);
+ return r == 0 ? NS_OK : NS_ERROR_FAILURE;
+}
+
+void
+VorbisDataDecoder::Input(MediaRawData* aSample)
+{
+ MOZ_ASSERT(mCallback->OnReaderTaskQueue());
+ mTaskQueue->Dispatch(NewRunnableMethod<RefPtr<MediaRawData>>(
+ this, &VorbisDataDecoder::ProcessDecode, aSample));
+}
+
+void
+VorbisDataDecoder::ProcessDecode(MediaRawData* aSample)
+{
+ MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
+ if (mIsFlushing) {
+ return;
+ }
+
+ MediaResult rv = DoDecode(aSample);
+ if (NS_FAILED(rv)) {
+ mCallback->Error(rv);
+ } else {
+ mCallback->InputExhausted();
+ }
+}
+
+MediaResult
+VorbisDataDecoder::DoDecode(MediaRawData* aSample)
+{
+ MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
+
+ const unsigned char* aData = aSample->Data();
+ size_t aLength = aSample->Size();
+ int64_t aOffset = aSample->mOffset;
+ int64_t aTstampUsecs = aSample->mTime;
+ int64_t aTotalFrames = 0;
+
+ MOZ_ASSERT(mPacketCount >= 3);
+
+ if (!mLastFrameTime || mLastFrameTime.ref() != aSample->mTime) {
+ // We are starting a new block.
+ mFrames = 0;
+ mLastFrameTime = Some(aSample->mTime);
+ }
+
+ ogg_packet pkt = InitVorbisPacket(aData, aLength, false, aSample->mEOS,
+ aSample->mTimecode, mPacketCount++);
+
+ int err = vorbis_synthesis(&mVorbisBlock, &pkt);
+ if (err) {
+ return MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR,
+ RESULT_DETAIL("vorbis_synthesis:%d", err));
+ }
+
+ err = vorbis_synthesis_blockin(&mVorbisDsp, &mVorbisBlock);
+ if (err) {
+ return MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR,
+ RESULT_DETAIL("vorbis_synthesis_blockin:%d", err));
+ }
+
+ VorbisPCMValue** pcm = 0;
+ int32_t frames = vorbis_synthesis_pcmout(&mVorbisDsp, &pcm);
+ if (frames == 0) {
+ return NS_OK;
+ }
+ while (frames > 0) {
+ uint32_t channels = mVorbisDsp.vi->channels;
+ uint32_t rate = mVorbisDsp.vi->rate;
+ AlignedAudioBuffer buffer(frames*channels);
+ if (!buffer) {
+ return MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__);
+ }
+ for (uint32_t j = 0; j < channels; ++j) {
+ VorbisPCMValue* channel = pcm[j];
+ for (uint32_t i = 0; i < uint32_t(frames); ++i) {
+ buffer[i*channels + j] = MOZ_CONVERT_VORBIS_SAMPLE(channel[i]);
+ }
+ }
+
+ CheckedInt64 duration = FramesToUsecs(frames, rate);
+ if (!duration.isValid()) {
+ return MediaResult(NS_ERROR_DOM_MEDIA_OVERFLOW_ERR,
+ RESULT_DETAIL("Overflow converting audio duration"));
+ }
+ CheckedInt64 total_duration = FramesToUsecs(mFrames, rate);
+ if (!total_duration.isValid()) {
+ return MediaResult(
+ NS_ERROR_DOM_MEDIA_OVERFLOW_ERR,
+ RESULT_DETAIL("Overflow converting audio total_duration"));
+ }
+
+ CheckedInt64 time = total_duration + aTstampUsecs;
+ if (!time.isValid()) {
+ return MediaResult(
+ NS_ERROR_DOM_MEDIA_OVERFLOW_ERR,
+ RESULT_DETAIL("Overflow adding total_duration and aTstampUsecs"));
+ };
+
+ if (!mAudioConverter) {
+ AudioConfig in(AudioConfig::ChannelLayout(channels, VorbisLayout(channels)),
+ rate);
+ AudioConfig out(channels, rate);
+ if (!in.IsValid() || !out.IsValid()) {
+ return MediaResult(
+ NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ RESULT_DETAIL("Invalid channel layout:%u", channels));
+ }
+ mAudioConverter = MakeUnique<AudioConverter>(in, out);
+ }
+ MOZ_ASSERT(mAudioConverter->CanWorkInPlace());
+ AudioSampleBuffer data(Move(buffer));
+ data = mAudioConverter->Process(Move(data));
+
+ aTotalFrames += frames;
+ mCallback->Output(new AudioData(aOffset,
+ time.value(),
+ duration.value(),
+ frames,
+ data.Forget(),
+ channels,
+ rate));
+ mFrames += frames;
+ err = vorbis_synthesis_read(&mVorbisDsp, frames);
+ if (err) {
+ return MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR,
+ RESULT_DETAIL("vorbis_synthesis_read:%d", err));
+ }
+
+ frames = vorbis_synthesis_pcmout(&mVorbisDsp, &pcm);
+ }
+
+ return NS_OK;
+}
+
+void
+VorbisDataDecoder::ProcessDrain()
+{
+ MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
+ mCallback->DrainComplete();
+}
+
+void
+VorbisDataDecoder::Drain()
+{
+ MOZ_ASSERT(mCallback->OnReaderTaskQueue());
+ mTaskQueue->Dispatch(NewRunnableMethod(this, &VorbisDataDecoder::ProcessDrain));
+}
+
+void
+VorbisDataDecoder::Flush()
+{
+ MOZ_ASSERT(mCallback->OnReaderTaskQueue());
+ mIsFlushing = true;
+ RefPtr<VorbisDataDecoder> self = this;
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([self] () {
+ // Ignore failed results from vorbis_synthesis_restart. They
+ // aren't fatal and it fails when ResetDecode is called at a
+ // time when no vorbis data has been read.
+ vorbis_synthesis_restart(&self->mVorbisDsp);
+ self->mLastFrameTime.reset();
+ });
+ SyncRunnable::DispatchToThread(mTaskQueue, r);
+ mIsFlushing = false;
+}
+
+/* static */
+bool
+VorbisDataDecoder::IsVorbis(const nsACString& aMimeType)
+{
+ return aMimeType.EqualsLiteral("audio/vorbis");
+}
+
+/* static */ const AudioConfig::Channel*
+VorbisDataDecoder::VorbisLayout(uint32_t aChannels)
+{
+ // From https://www.xiph.org/vorbis/doc/Vorbis_I_spec.html
+ // Section 4.3.9.
+ typedef AudioConfig::Channel Channel;
+
+ switch (aChannels) {
+ case 1: // the stream is monophonic
+ {
+ static const Channel config[] = { AudioConfig::CHANNEL_MONO };
+ return config;
+ }
+ case 2: // the stream is stereo. channel order: left, right
+ {
+ static const Channel config[] = { AudioConfig::CHANNEL_LEFT, AudioConfig::CHANNEL_RIGHT };
+ return config;
+ }
+ case 3: // the stream is a 1d-surround encoding. channel order: left, center, right
+ {
+ static const Channel config[] = { AudioConfig::CHANNEL_LEFT, AudioConfig::CHANNEL_CENTER, AudioConfig::CHANNEL_RIGHT };
+ return config;
+ }
+ case 4: // the stream is quadraphonic surround. channel order: front left, front right, rear left, rear right
+ {
+ static const Channel config[] = { AudioConfig::CHANNEL_LEFT, AudioConfig::CHANNEL_RIGHT, AudioConfig::CHANNEL_LS, AudioConfig::CHANNEL_RS };
+ return config;
+ }
+ case 5: // the stream is five-channel surround. channel order: front left, center, front right, rear left, rear right
+ {
+ static const Channel config[] = { AudioConfig::CHANNEL_LEFT, AudioConfig::CHANNEL_CENTER, AudioConfig::CHANNEL_RIGHT, AudioConfig::CHANNEL_LS, AudioConfig::CHANNEL_RS };
+ return config;
+ }
+ case 6: // the stream is 5.1 surround. channel order: front left, center, front right, rear left, rear right, LFE
+ {
+ static const Channel config[] = { AudioConfig::CHANNEL_LEFT, AudioConfig::CHANNEL_CENTER, AudioConfig::CHANNEL_RIGHT, AudioConfig::CHANNEL_LS, AudioConfig::CHANNEL_RS, AudioConfig::CHANNEL_LFE };
+ return config;
+ }
+ case 7: // surround. channel order: front left, center, front right, side left, side right, rear center, LFE
+ {
+ static const Channel config[] = { AudioConfig::CHANNEL_LEFT, AudioConfig::CHANNEL_CENTER, AudioConfig::CHANNEL_RIGHT, AudioConfig::CHANNEL_LS, AudioConfig::CHANNEL_RS, AudioConfig::CHANNEL_RCENTER, AudioConfig::CHANNEL_LFE };
+ return config;
+ }
+ case 8: // the stream is 7.1 surround. channel order: front left, center, front right, side left, side right, rear left, rear right, LFE
+ {
+ static const Channel config[] = { AudioConfig::CHANNEL_LEFT, AudioConfig::CHANNEL_CENTER, AudioConfig::CHANNEL_RIGHT, AudioConfig::CHANNEL_LS, AudioConfig::CHANNEL_RS, AudioConfig::CHANNEL_RLS, AudioConfig::CHANNEL_RRS, AudioConfig::CHANNEL_LFE };
+ return config;
+ }
+ default:
+ return nullptr;
+ }
+}
+
+} // namespace mozilla
+#undef LOG
diff --git a/dom/media/platforms/agnostic/VorbisDecoder.h b/dom/media/platforms/agnostic/VorbisDecoder.h
new file mode 100644
index 000000000..0ed7bb645
--- /dev/null
+++ b/dom/media/platforms/agnostic/VorbisDecoder.h
@@ -0,0 +1,66 @@
+/* -*- 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(VorbisDecoder_h_)
+#define VorbisDecoder_h_
+
+#include "PlatformDecoderModule.h"
+#include "mozilla/Maybe.h"
+#include "AudioConverter.h"
+
+#ifdef MOZ_TREMOR
+#include "tremor/ivorbiscodec.h"
+#else
+#include "vorbis/codec.h"
+#endif
+
+namespace mozilla {
+
+class VorbisDataDecoder : public MediaDataDecoder
+{
+public:
+ explicit VorbisDataDecoder(const CreateDecoderParams& aParams);
+ ~VorbisDataDecoder();
+
+ RefPtr<InitPromise> Init() override;
+ void Input(MediaRawData* aSample) override;
+ void Flush() override;
+ void Drain() override;
+ void Shutdown() override;
+ const char* GetDescriptionName() const override
+ {
+ return "vorbis audio decoder";
+ }
+
+ // Return true if mimetype is Vorbis
+ static bool IsVorbis(const nsACString& aMimeType);
+ static const AudioConfig::Channel* VorbisLayout(uint32_t aChannels);
+
+private:
+ nsresult DecodeHeader(const unsigned char* aData, size_t aLength);
+
+ void ProcessDecode(MediaRawData* aSample);
+ MediaResult DoDecode(MediaRawData* aSample);
+ void ProcessDrain();
+
+ const AudioInfo& mInfo;
+ const RefPtr<TaskQueue> mTaskQueue;
+ MediaDataDecoderCallback* mCallback;
+
+ // Vorbis decoder state
+ vorbis_info mVorbisInfo;
+ vorbis_comment mVorbisComment;
+ vorbis_dsp_state mVorbisDsp;
+ vorbis_block mVorbisBlock;
+
+ int64_t mPacketCount;
+ int64_t mFrames;
+ Maybe<int64_t> mLastFrameTime;
+ UniquePtr<AudioConverter> mAudioConverter;
+ Atomic<bool> mIsFlushing;
+};
+
+} // namespace mozilla
+#endif
diff --git a/dom/media/platforms/agnostic/WAVDecoder.cpp b/dom/media/platforms/agnostic/WAVDecoder.cpp
new file mode 100644
index 000000000..67a045287
--- /dev/null
+++ b/dom/media/platforms/agnostic/WAVDecoder.cpp
@@ -0,0 +1,158 @@
+/* -*- 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 "WAVDecoder.h"
+#include "AudioSampleFormat.h"
+#include "mozilla/SyncRunnable.h"
+
+using mp4_demuxer::ByteReader;
+
+namespace mozilla {
+
+int16_t
+DecodeALawSample(uint8_t aValue)
+{
+ aValue = aValue ^ 0x55;
+ int8_t sign = (aValue & 0x80) ? -1 : 1;
+ uint8_t exponent = (aValue & 0x70) >> 4;
+ uint8_t mantissa = aValue & 0x0F;
+ int16_t sample = mantissa << 4;
+ switch (exponent) {
+ case 0:
+ sample += 8;
+ break;
+ case 1:
+ sample += 0x108;
+ break;
+ default:
+ sample += 0x108;
+ sample <<= exponent - 1;
+ }
+ return sign * sample;
+}
+
+int16_t
+DecodeULawSample(uint8_t aValue)
+{
+ aValue = aValue ^ 0xFF;
+ int8_t sign = (aValue & 0x80) ? -1 : 1;
+ uint8_t exponent = (aValue & 0x70) >> 4;
+ uint8_t mantissa = aValue & 0x0F;
+ int16_t sample = (33 + 2 * mantissa) * (2 << (exponent + 1)) - 33;
+ return sign * sample;
+}
+
+WaveDataDecoder::WaveDataDecoder(const CreateDecoderParams& aParams)
+ : mInfo(aParams.AudioConfig())
+ , mCallback(aParams.mCallback)
+{
+}
+
+void
+WaveDataDecoder::Shutdown()
+{
+}
+
+RefPtr<MediaDataDecoder::InitPromise>
+WaveDataDecoder::Init()
+{
+ return InitPromise::CreateAndResolve(TrackInfo::kAudioTrack, __func__);
+}
+
+void
+WaveDataDecoder::Input(MediaRawData* aSample)
+{
+ MediaResult rv = DoDecode(aSample);
+ if (NS_FAILED(rv)) {
+ mCallback->Error(rv);
+ } else {
+ mCallback->InputExhausted();
+ }
+}
+
+MediaResult
+WaveDataDecoder::DoDecode(MediaRawData* aSample)
+{
+ size_t aLength = aSample->Size();
+ ByteReader aReader(aSample->Data(), aLength);
+ int64_t aOffset = aSample->mOffset;
+ uint64_t aTstampUsecs = aSample->mTime;
+
+ int32_t frames = aLength * 8 / mInfo.mBitDepth / mInfo.mChannels;
+
+ AlignedAudioBuffer buffer(frames * mInfo.mChannels);
+ if (!buffer) {
+ return MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__);
+ }
+ for (int i = 0; i < frames; ++i) {
+ for (unsigned int j = 0; j < mInfo.mChannels; ++j) {
+ if (mInfo.mProfile == 6) { //ALAW Data
+ uint8_t v = aReader.ReadU8();
+ int16_t decoded = DecodeALawSample(v);
+ buffer[i * mInfo.mChannels + j] =
+ IntegerToAudioSample<AudioDataValue>(decoded);
+ } else if (mInfo.mProfile == 7) { //ULAW Data
+ uint8_t v = aReader.ReadU8();
+ int16_t decoded = DecodeULawSample(v);
+ buffer[i * mInfo.mChannels + j] =
+ IntegerToAudioSample<AudioDataValue>(decoded);
+ } else { //PCM Data
+ if (mInfo.mBitDepth == 8) {
+ uint8_t v = aReader.ReadU8();
+ buffer[i * mInfo.mChannels + j] =
+ UInt8bitToAudioSample<AudioDataValue>(v);
+ } else if (mInfo.mBitDepth == 16) {
+ int16_t v = aReader.ReadLE16();
+ buffer[i * mInfo.mChannels + j] =
+ IntegerToAudioSample<AudioDataValue>(v);
+ } else if (mInfo.mBitDepth == 24) {
+ int32_t v = aReader.ReadLE24();
+ buffer[i * mInfo.mChannels + j] =
+ Int24bitToAudioSample<AudioDataValue>(v);
+ }
+ }
+ }
+ }
+
+ int64_t duration = frames / mInfo.mRate;
+
+ mCallback->Output(new AudioData(aOffset,
+ aTstampUsecs,
+ duration,
+ frames,
+ Move(buffer),
+ mInfo.mChannels,
+ mInfo.mRate));
+
+ return NS_OK;
+}
+
+void
+WaveDataDecoder::Drain()
+{
+ mCallback->DrainComplete();
+}
+
+void
+WaveDataDecoder::Flush()
+{
+}
+
+/* static */
+bool
+WaveDataDecoder::IsWave(const nsACString& aMimeType)
+{
+ // Some WebAudio uses "audio/x-wav",
+ // WAVdemuxer uses "audio/wave; codecs=aNum".
+ return aMimeType.EqualsLiteral("audio/x-wav") ||
+ aMimeType.EqualsLiteral("audio/wave; codecs=1") ||
+ aMimeType.EqualsLiteral("audio/wave; codecs=6") ||
+ aMimeType.EqualsLiteral("audio/wave; codecs=7") ||
+ aMimeType.EqualsLiteral("audio/wave; codecs=65534");
+}
+
+} // namespace mozilla
+#undef LOG
diff --git a/dom/media/platforms/agnostic/WAVDecoder.h b/dom/media/platforms/agnostic/WAVDecoder.h
new file mode 100644
index 000000000..dea980168
--- /dev/null
+++ b/dom/media/platforms/agnostic/WAVDecoder.h
@@ -0,0 +1,41 @@
+/* -*- 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(WaveDecoder_h_)
+#define WaveDecoder_h_
+
+#include "PlatformDecoderModule.h"
+#include "mp4_demuxer/ByteReader.h"
+
+namespace mozilla {
+
+class WaveDataDecoder : public MediaDataDecoder
+{
+public:
+ explicit WaveDataDecoder(const CreateDecoderParams& aParams);
+
+ // Return true if mimetype is Wave
+ static bool IsWave(const nsACString& aMimeType);
+
+ RefPtr<InitPromise> Init() override;
+ void Input(MediaRawData* aSample) override;
+ void Flush() override;
+ void Drain() override;
+ void Shutdown() override;
+ const char* GetDescriptionName() const override
+ {
+ return "wave audio decoder";
+ }
+
+private:
+ MediaResult DoDecode(MediaRawData* aSample);
+
+ const AudioInfo& mInfo;
+ MediaDataDecoderCallback* mCallback;
+};
+
+} // namespace mozilla
+#endif
diff --git a/dom/media/platforms/agnostic/eme/EMEAudioDecoder.cpp b/dom/media/platforms/agnostic/eme/EMEAudioDecoder.cpp
new file mode 100644
index 000000000..25e502025
--- /dev/null
+++ b/dom/media/platforms/agnostic/eme/EMEAudioDecoder.cpp
@@ -0,0 +1,44 @@
+/* -*- 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 "EMEAudioDecoder.h"
+#include "mozilla/CDMProxy.h"
+
+namespace mozilla {
+
+void
+EMEAudioCallbackAdapter::Error(GMPErr aErr)
+{
+ if (aErr == GMPNoKeyErr) {
+ // The GMP failed to decrypt a frame due to not having a key. This can
+ // happen if a key expires or a session is closed during playback.
+ NS_WARNING("GMP failed to decrypt due to lack of key");
+ return;
+ }
+ AudioCallbackAdapter::Error(aErr);
+}
+
+EMEAudioDecoder::EMEAudioDecoder(CDMProxy* aProxy,
+ const GMPAudioDecoderParams& aParams)
+ : GMPAudioDecoder(GMPAudioDecoderParams(aParams).WithAdapter(
+ new EMEAudioCallbackAdapter(aParams.mCallback)))
+ , mProxy(aProxy)
+{}
+
+void
+EMEAudioDecoder::InitTags(nsTArray<nsCString>& aTags)
+{
+ aTags.AppendElement(NS_LITERAL_CSTRING("aac"));
+ aTags.AppendElement(NS_ConvertUTF16toUTF8(mProxy->KeySystem()));
+}
+
+nsCString
+EMEAudioDecoder::GetNodeId()
+{
+ return mProxy->GetNodeId();
+}
+
+} // namespace mozilla
diff --git a/dom/media/platforms/agnostic/eme/EMEAudioDecoder.h b/dom/media/platforms/agnostic/eme/EMEAudioDecoder.h
new file mode 100644
index 000000000..c5944cf8c
--- /dev/null
+++ b/dom/media/platforms/agnostic/eme/EMEAudioDecoder.h
@@ -0,0 +1,37 @@
+/* -*- 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/. */
+
+#ifndef EMEAudioDecoder_h_
+#define EMEAudioDecoder_h_
+
+#include "GMPAudioDecoder.h"
+#include "PlatformDecoderModule.h"
+
+namespace mozilla {
+
+class EMEAudioCallbackAdapter : public AudioCallbackAdapter {
+public:
+ explicit EMEAudioCallbackAdapter(MediaDataDecoderCallbackProxy* aCallback)
+ : AudioCallbackAdapter(aCallback)
+ {}
+
+ void Error(GMPErr aErr) override;
+};
+
+class EMEAudioDecoder : public GMPAudioDecoder {
+public:
+ EMEAudioDecoder(CDMProxy* aProxy, const GMPAudioDecoderParams& aParams);
+
+private:
+ void InitTags(nsTArray<nsCString>& aTags) override;
+ nsCString GetNodeId() override;
+
+ RefPtr<CDMProxy> mProxy;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp b/dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp
new file mode 100644
index 000000000..7d523d926
--- /dev/null
+++ b/dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp
@@ -0,0 +1,307 @@
+/* -*- 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 "EMEDecoderModule.h"
+#include "EMEAudioDecoder.h"
+#include "EMEVideoDecoder.h"
+#include "MediaDataDecoderProxy.h"
+#include "mozIGeckoMediaPluginService.h"
+#include "mozilla/CDMProxy.h"
+#include "mozilla/Unused.h"
+#include "nsAutoPtr.h"
+#include "nsServiceManagerUtils.h"
+#include "MediaInfo.h"
+#include "nsClassHashtable.h"
+#include "GMPDecoderModule.h"
+#include "MP4Decoder.h"
+
+namespace mozilla {
+
+typedef MozPromiseRequestHolder<CDMProxy::DecryptPromise> DecryptPromiseRequestHolder;
+
+class EMEDecryptor : public MediaDataDecoder {
+
+public:
+
+ EMEDecryptor(MediaDataDecoder* aDecoder,
+ MediaDataDecoderCallback* aCallback,
+ CDMProxy* aProxy,
+ TaskQueue* aDecodeTaskQueue)
+ : mDecoder(aDecoder)
+ , mCallback(aCallback)
+ , mTaskQueue(aDecodeTaskQueue)
+ , mProxy(aProxy)
+ , mSamplesWaitingForKey(new SamplesWaitingForKey(this, this->mCallback,
+ mTaskQueue, mProxy))
+ , mIsShutdown(false)
+ {
+ }
+
+ RefPtr<InitPromise> Init() override {
+ MOZ_ASSERT(!mIsShutdown);
+ return mDecoder->Init();
+ }
+
+ void Input(MediaRawData* aSample) override {
+ MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
+ if (mIsShutdown) {
+ NS_WARNING("EME encrypted sample arrived after shutdown");
+ return;
+ }
+ if (mSamplesWaitingForKey->WaitIfKeyNotUsable(aSample)) {
+ return;
+ }
+
+ nsAutoPtr<MediaRawDataWriter> writer(aSample->CreateWriter());
+ mProxy->GetSessionIdsForKeyId(aSample->mCrypto.mKeyId,
+ writer->mCrypto.mSessionIds);
+
+ mDecrypts.Put(aSample, new DecryptPromiseRequestHolder());
+ mDecrypts.Get(aSample)->Begin(mProxy->Decrypt(aSample)->Then(
+ mTaskQueue, __func__, this,
+ &EMEDecryptor::Decrypted,
+ &EMEDecryptor::Decrypted));
+ return;
+ }
+
+ void Decrypted(const DecryptResult& aDecrypted) {
+ MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
+ MOZ_ASSERT(aDecrypted.mSample);
+
+ nsAutoPtr<DecryptPromiseRequestHolder> holder;
+ mDecrypts.RemoveAndForget(aDecrypted.mSample, holder);
+ if (holder) {
+ holder->Complete();
+ } else {
+ // Decryption is not in the list of decrypt operations waiting
+ // for a result. It must have been flushed or drained. Ignore result.
+ return;
+ }
+
+ if (mIsShutdown) {
+ NS_WARNING("EME decrypted sample arrived after shutdown");
+ return;
+ }
+
+ if (aDecrypted.mStatus == NoKeyErr) {
+ // Key became unusable after we sent the sample to CDM to decrypt.
+ // Call Input() again, so that the sample is enqueued for decryption
+ // if the key becomes usable again.
+ Input(aDecrypted.mSample);
+ } else if (aDecrypted.mStatus != Ok) {
+ if (mCallback) {
+ mCallback->Error(MediaResult(
+ NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ RESULT_DETAIL("decrypted.mStatus=%u", uint32_t(aDecrypted.mStatus))));
+ }
+ } else {
+ MOZ_ASSERT(!mIsShutdown);
+ // The Adobe GMP AAC decoder gets confused if we pass it non-encrypted
+ // samples with valid crypto data. So clear the crypto data, since the
+ // sample should be decrypted now anyway. If we don't do this and we're
+ // using the Adobe GMP for unencrypted decoding of data that is decrypted
+ // by gmp-clearkey, decoding will fail.
+ UniquePtr<MediaRawDataWriter> writer(aDecrypted.mSample->CreateWriter());
+ writer->mCrypto = CryptoSample();
+ mDecoder->Input(aDecrypted.mSample);
+ }
+ }
+
+ void Flush() override {
+ MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
+ MOZ_ASSERT(!mIsShutdown);
+ for (auto iter = mDecrypts.Iter(); !iter.Done(); iter.Next()) {
+ nsAutoPtr<DecryptPromiseRequestHolder>& holder = iter.Data();
+ holder->DisconnectIfExists();
+ iter.Remove();
+ }
+ mDecoder->Flush();
+ mSamplesWaitingForKey->Flush();
+ }
+
+ void Drain() override {
+ MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
+ MOZ_ASSERT(!mIsShutdown);
+ for (auto iter = mDecrypts.Iter(); !iter.Done(); iter.Next()) {
+ nsAutoPtr<DecryptPromiseRequestHolder>& holder = iter.Data();
+ holder->DisconnectIfExists();
+ iter.Remove();
+ }
+ mDecoder->Drain();
+ }
+
+ void Shutdown() override {
+ MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
+ MOZ_ASSERT(!mIsShutdown);
+ mIsShutdown = true;
+ mDecoder->Shutdown();
+ mSamplesWaitingForKey->BreakCycles();
+ mSamplesWaitingForKey = nullptr;
+ mDecoder = nullptr;
+ mProxy = nullptr;
+ mCallback = nullptr;
+ }
+
+ const char* GetDescriptionName() const override {
+ return mDecoder->GetDescriptionName();
+ }
+
+private:
+
+ RefPtr<MediaDataDecoder> mDecoder;
+ MediaDataDecoderCallback* mCallback;
+ RefPtr<TaskQueue> mTaskQueue;
+ RefPtr<CDMProxy> mProxy;
+ nsClassHashtable<nsRefPtrHashKey<MediaRawData>, DecryptPromiseRequestHolder> mDecrypts;
+ RefPtr<SamplesWaitingForKey> mSamplesWaitingForKey;
+ bool mIsShutdown;
+};
+
+class EMEMediaDataDecoderProxy : public MediaDataDecoderProxy {
+public:
+ EMEMediaDataDecoderProxy(already_AddRefed<AbstractThread> aProxyThread,
+ MediaDataDecoderCallback* aCallback,
+ CDMProxy* aProxy,
+ TaskQueue* aTaskQueue)
+ : MediaDataDecoderProxy(Move(aProxyThread), aCallback)
+ , mSamplesWaitingForKey(new SamplesWaitingForKey(this, aCallback,
+ aTaskQueue, aProxy))
+ , mProxy(aProxy)
+ {
+ }
+
+ void Input(MediaRawData* aSample) override;
+ void Shutdown() override;
+
+private:
+ RefPtr<SamplesWaitingForKey> mSamplesWaitingForKey;
+ RefPtr<CDMProxy> mProxy;
+};
+
+void
+EMEMediaDataDecoderProxy::Input(MediaRawData* aSample)
+{
+ if (mSamplesWaitingForKey->WaitIfKeyNotUsable(aSample)) {
+ return;
+ }
+
+ nsAutoPtr<MediaRawDataWriter> writer(aSample->CreateWriter());
+ mProxy->GetSessionIdsForKeyId(aSample->mCrypto.mKeyId,
+ writer->mCrypto.mSessionIds);
+
+ MediaDataDecoderProxy::Input(aSample);
+}
+
+void
+EMEMediaDataDecoderProxy::Shutdown()
+{
+ MediaDataDecoderProxy::Shutdown();
+
+ mSamplesWaitingForKey->BreakCycles();
+ mSamplesWaitingForKey = nullptr;
+ mProxy = nullptr;
+}
+
+EMEDecoderModule::EMEDecoderModule(CDMProxy* aProxy, PDMFactory* aPDM)
+ : mProxy(aProxy)
+ , mPDM(aPDM)
+{
+}
+
+EMEDecoderModule::~EMEDecoderModule()
+{
+}
+
+static already_AddRefed<MediaDataDecoderProxy>
+CreateDecoderWrapper(MediaDataDecoderCallback* aCallback, CDMProxy* aProxy, TaskQueue* aTaskQueue)
+{
+ RefPtr<gmp::GeckoMediaPluginService> s(gmp::GeckoMediaPluginService::GetGeckoMediaPluginService());
+ if (!s) {
+ return nullptr;
+ }
+ RefPtr<AbstractThread> thread(s->GetAbstractGMPThread());
+ if (!thread) {
+ return nullptr;
+ }
+ RefPtr<MediaDataDecoderProxy> decoder(
+ new EMEMediaDataDecoderProxy(thread.forget(), aCallback, aProxy, aTaskQueue));
+ return decoder.forget();
+}
+
+already_AddRefed<MediaDataDecoder>
+EMEDecoderModule::CreateVideoDecoder(const CreateDecoderParams& aParams)
+{
+ MOZ_ASSERT(aParams.mConfig.mCrypto.mValid);
+
+ if (SupportsMimeType(aParams.mConfig.mMimeType, nullptr)) {
+ // GMP decodes. Assume that means it can decrypt too.
+ RefPtr<MediaDataDecoderProxy> wrapper =
+ CreateDecoderWrapper(aParams.mCallback, mProxy, aParams.mTaskQueue);
+ auto params = GMPVideoDecoderParams(aParams).WithCallback(wrapper);
+ wrapper->SetProxyTarget(new EMEVideoDecoder(mProxy, params));
+ return wrapper.forget();
+ }
+
+ MOZ_ASSERT(mPDM);
+ RefPtr<MediaDataDecoder> decoder(mPDM->CreateDecoder(aParams));
+ if (!decoder) {
+ return nullptr;
+ }
+
+ RefPtr<MediaDataDecoder> emeDecoder(new EMEDecryptor(decoder,
+ aParams.mCallback,
+ mProxy,
+ AbstractThread::GetCurrent()->AsTaskQueue()));
+ return emeDecoder.forget();
+}
+
+already_AddRefed<MediaDataDecoder>
+EMEDecoderModule::CreateAudioDecoder(const CreateDecoderParams& aParams)
+{
+ MOZ_ASSERT(aParams.mConfig.mCrypto.mValid);
+
+ if (SupportsMimeType(aParams.mConfig.mMimeType, nullptr)) {
+ // GMP decodes. Assume that means it can decrypt too.
+ RefPtr<MediaDataDecoderProxy> wrapper =
+ CreateDecoderWrapper(aParams.mCallback, mProxy, aParams.mTaskQueue);
+ auto gmpParams = GMPAudioDecoderParams(aParams).WithCallback(wrapper);
+ wrapper->SetProxyTarget(new EMEAudioDecoder(mProxy, gmpParams));
+ return wrapper.forget();
+ }
+
+ MOZ_ASSERT(mPDM);
+ RefPtr<MediaDataDecoder> decoder(mPDM->CreateDecoder(aParams));
+ if (!decoder) {
+ return nullptr;
+ }
+
+ RefPtr<MediaDataDecoder> emeDecoder(new EMEDecryptor(decoder,
+ aParams.mCallback,
+ mProxy,
+ AbstractThread::GetCurrent()->AsTaskQueue()));
+ return emeDecoder.forget();
+}
+
+PlatformDecoderModule::ConversionRequired
+EMEDecoderModule::DecoderNeedsConversion(const TrackInfo& aConfig) const
+{
+ if (aConfig.IsVideo() && MP4Decoder::IsH264(aConfig.mMimeType)) {
+ return ConversionRequired::kNeedAVCC;
+ } else {
+ return ConversionRequired::kNeedNone;
+ }
+}
+
+bool
+EMEDecoderModule::SupportsMimeType(const nsACString& aMimeType,
+ DecoderDoctorDiagnostics* aDiagnostics) const
+{
+ Maybe<nsCString> gmp;
+ gmp.emplace(NS_ConvertUTF16toUTF8(mProxy->KeySystem()));
+ return GMPDecoderModule::SupportsMimeType(aMimeType, gmp);
+}
+
+} // namespace mozilla
diff --git a/dom/media/platforms/agnostic/eme/EMEDecoderModule.h b/dom/media/platforms/agnostic/eme/EMEDecoderModule.h
new file mode 100644
index 000000000..32074ae8c
--- /dev/null
+++ b/dom/media/platforms/agnostic/eme/EMEDecoderModule.h
@@ -0,0 +1,52 @@
+/* -*- 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(EMEDecoderModule_h_)
+#define EMEDecoderModule_h_
+
+#include "PlatformDecoderModule.h"
+#include "PDMFactory.h"
+#include "gmp-decryption.h"
+
+namespace mozilla {
+
+class CDMProxy;
+
+class EMEDecoderModule : public PlatformDecoderModule {
+private:
+
+public:
+ EMEDecoderModule(CDMProxy* aProxy, PDMFactory* aPDM);
+
+ virtual ~EMEDecoderModule();
+
+protected:
+ // 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;
+
+private:
+ RefPtr<CDMProxy> mProxy;
+ // Will be null if CDM has decoding capability.
+ RefPtr<PDMFactory> mPDM;
+ // We run the PDM on its own task queue.
+ RefPtr<TaskQueue> mTaskQueue;
+};
+
+} // namespace mozilla
+
+#endif // EMEDecoderModule_h_
diff --git a/dom/media/platforms/agnostic/eme/EMEVideoDecoder.cpp b/dom/media/platforms/agnostic/eme/EMEVideoDecoder.cpp
new file mode 100644
index 000000000..bddb89660
--- /dev/null
+++ b/dom/media/platforms/agnostic/eme/EMEVideoDecoder.cpp
@@ -0,0 +1,68 @@
+/* -*- 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 "EMEVideoDecoder.h"
+#include "GMPVideoEncodedFrameImpl.h"
+#include "mozilla/CDMProxy.h"
+#include "MediaData.h"
+#include "MP4Decoder.h"
+#include "VPXDecoder.h"
+
+namespace mozilla {
+
+void
+EMEVideoCallbackAdapter::Error(GMPErr aErr)
+{
+ if (aErr == GMPNoKeyErr) {
+ // The GMP failed to decrypt a frame due to not having a key. This can
+ // happen if a key expires or a session is closed during playback.
+ NS_WARNING("GMP failed to decrypt due to lack of key");
+ return;
+ }
+ VideoCallbackAdapter::Error(aErr);
+}
+
+EMEVideoDecoder::EMEVideoDecoder(CDMProxy* aProxy,
+ const GMPVideoDecoderParams& aParams)
+ : GMPVideoDecoder(GMPVideoDecoderParams(aParams).WithAdapter(
+ new EMEVideoCallbackAdapter(aParams.mCallback,
+ VideoInfo(aParams.mConfig.mDisplay),
+ aParams.mImageContainer)))
+ , mProxy(aProxy)
+ , mDecryptorId(aProxy->GetDecryptorId())
+{}
+
+void
+EMEVideoDecoder::InitTags(nsTArray<nsCString>& aTags)
+{
+ VideoInfo config = GetConfig();
+ if (MP4Decoder::IsH264(config.mMimeType)) {
+ aTags.AppendElement(NS_LITERAL_CSTRING("h264"));
+ } else if (VPXDecoder::IsVP8(config.mMimeType)) {
+ aTags.AppendElement(NS_LITERAL_CSTRING("vp8"));
+ } else if (VPXDecoder::IsVP9(config.mMimeType)) {
+ aTags.AppendElement(NS_LITERAL_CSTRING("vp9"));
+ }
+ aTags.AppendElement(NS_ConvertUTF16toUTF8(mProxy->KeySystem()));
+}
+
+nsCString
+EMEVideoDecoder::GetNodeId()
+{
+ return mProxy->GetNodeId();
+}
+
+GMPUniquePtr<GMPVideoEncodedFrame>
+EMEVideoDecoder::CreateFrame(MediaRawData* aSample)
+{
+ GMPUniquePtr<GMPVideoEncodedFrame> frame = GMPVideoDecoder::CreateFrame(aSample);
+ if (frame && aSample->mCrypto.mValid) {
+ static_cast<gmp::GMPVideoEncodedFrameImpl*>(frame.get())->InitCrypto(aSample->mCrypto);
+ }
+ return frame;
+}
+
+} // namespace mozilla
diff --git a/dom/media/platforms/agnostic/eme/EMEVideoDecoder.h b/dom/media/platforms/agnostic/eme/EMEVideoDecoder.h
new file mode 100644
index 000000000..a0f23f867
--- /dev/null
+++ b/dom/media/platforms/agnostic/eme/EMEVideoDecoder.h
@@ -0,0 +1,45 @@
+/* -*- 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/. */
+
+#ifndef EMEVideoDecoder_h_
+#define EMEVideoDecoder_h_
+
+#include "GMPVideoDecoder.h"
+#include "PlatformDecoderModule.h"
+
+namespace mozilla {
+
+class CDMProxy;
+class TaskQueue;
+
+class EMEVideoCallbackAdapter : public VideoCallbackAdapter {
+public:
+ EMEVideoCallbackAdapter(MediaDataDecoderCallbackProxy* aCallback,
+ VideoInfo aVideoInfo,
+ layers::ImageContainer* aImageContainer)
+ : VideoCallbackAdapter(aCallback, aVideoInfo, aImageContainer)
+ {}
+
+ void Error(GMPErr aErr) override;
+};
+
+class EMEVideoDecoder : public GMPVideoDecoder {
+public:
+ EMEVideoDecoder(CDMProxy* aProxy, const GMPVideoDecoderParams& aParams);
+
+private:
+ void InitTags(nsTArray<nsCString>& aTags) override;
+ nsCString GetNodeId() override;
+ uint32_t DecryptorId() const override { return mDecryptorId; }
+ GMPUniquePtr<GMPVideoEncodedFrame> CreateFrame(MediaRawData* aSample) override;
+
+ RefPtr<CDMProxy> mProxy;
+ uint32_t mDecryptorId;
+};
+
+} // namespace mozilla
+
+#endif // EMEVideoDecoder_h_
diff --git a/dom/media/platforms/agnostic/eme/SamplesWaitingForKey.cpp b/dom/media/platforms/agnostic/eme/SamplesWaitingForKey.cpp
new file mode 100644
index 000000000..58098c2b3
--- /dev/null
+++ b/dom/media/platforms/agnostic/eme/SamplesWaitingForKey.cpp
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "SamplesWaitingForKey.h"
+#include "mozilla/CDMProxy.h"
+#include "mozilla/CDMCaps.h"
+#include "MediaData.h"
+
+namespace mozilla {
+
+SamplesWaitingForKey::SamplesWaitingForKey(MediaDataDecoder* aDecoder,
+ MediaDataDecoderCallback* aCallback,
+ TaskQueue* aTaskQueue,
+ CDMProxy* aProxy)
+ : mMutex("SamplesWaitingForKey")
+ , mDecoder(aDecoder)
+ , mDecoderCallback(aCallback)
+ , mTaskQueue(aTaskQueue)
+ , mProxy(aProxy)
+{
+}
+
+SamplesWaitingForKey::~SamplesWaitingForKey()
+{
+}
+
+bool
+SamplesWaitingForKey::WaitIfKeyNotUsable(MediaRawData* aSample)
+{
+ if (!aSample || !aSample->mCrypto.mValid || !mProxy) {
+ return false;
+ }
+ CDMCaps::AutoLock caps(mProxy->Capabilites());
+ const auto& keyid = aSample->mCrypto.mKeyId;
+ if (!caps.IsKeyUsable(keyid)) {
+ {
+ MutexAutoLock lock(mMutex);
+ mSamples.AppendElement(aSample);
+ }
+ mDecoderCallback->WaitingForKey();
+ caps.NotifyWhenKeyIdUsable(aSample->mCrypto.mKeyId, this);
+ return true;
+ }
+ return false;
+}
+
+void
+SamplesWaitingForKey::NotifyUsable(const CencKeyId& aKeyId)
+{
+ MutexAutoLock lock(mMutex);
+ size_t i = 0;
+ while (i < mSamples.Length()) {
+ if (aKeyId == mSamples[i]->mCrypto.mKeyId) {
+ RefPtr<nsIRunnable> task;
+ task = NewRunnableMethod<RefPtr<MediaRawData>>(mDecoder,
+ &MediaDataDecoder::Input,
+ RefPtr<MediaRawData>(mSamples[i]));
+ mSamples.RemoveElementAt(i);
+ mTaskQueue->Dispatch(task.forget());
+ } else {
+ i++;
+ }
+ }
+}
+
+void
+SamplesWaitingForKey::Flush()
+{
+ MutexAutoLock lock(mMutex);
+ mSamples.Clear();
+}
+
+void
+SamplesWaitingForKey::BreakCycles()
+{
+ MutexAutoLock lock(mMutex);
+ mDecoder = nullptr;
+ mTaskQueue = nullptr;
+ mProxy = nullptr;
+ mSamples.Clear();
+}
+
+} // namespace mozilla
diff --git a/dom/media/platforms/agnostic/eme/SamplesWaitingForKey.h b/dom/media/platforms/agnostic/eme/SamplesWaitingForKey.h
new file mode 100644
index 000000000..65bb14403
--- /dev/null
+++ b/dom/media/platforms/agnostic/eme/SamplesWaitingForKey.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef SamplesWaitingForKey_h_
+#define SamplesWaitingForKey_h_
+
+#include "mozilla/TaskQueue.h"
+
+#include "PlatformDecoderModule.h"
+
+namespace mozilla {
+
+typedef nsTArray<uint8_t> CencKeyId;
+
+class CDMProxy;
+
+// Encapsulates the task of waiting for the CDMProxy to have the necessary
+// keys to decrypt a given sample.
+class SamplesWaitingForKey {
+public:
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SamplesWaitingForKey)
+
+ explicit SamplesWaitingForKey(MediaDataDecoder* aDecoder,
+ MediaDataDecoderCallback* aCallback,
+ TaskQueue* aTaskQueue,
+ CDMProxy* aProxy);
+
+ // Returns true if we need to wait for a key to become usable.
+ // Will callback MediaDataDecoder::Input(aSample) on mDecoder once the
+ // sample is ready to be decrypted. The order of input samples is
+ // preserved.
+ bool WaitIfKeyNotUsable(MediaRawData* aSample);
+
+ void NotifyUsable(const CencKeyId& aKeyId);
+
+ void Flush();
+
+ void BreakCycles();
+
+protected:
+ ~SamplesWaitingForKey();
+
+private:
+ Mutex mMutex;
+ RefPtr<MediaDataDecoder> mDecoder;
+ MediaDataDecoderCallback* mDecoderCallback;
+ RefPtr<TaskQueue> mTaskQueue;
+ RefPtr<CDMProxy> mProxy;
+ nsTArray<RefPtr<MediaRawData>> mSamples;
+};
+
+} // namespace mozilla
+
+#endif // SamplesWaitingForKey_h_
diff --git a/dom/media/platforms/agnostic/eme/moz.build b/dom/media/platforms/agnostic/eme/moz.build
new file mode 100644
index 000000000..78b76d4d4
--- /dev/null
+++ b/dom/media/platforms/agnostic/eme/moz.build
@@ -0,0 +1,23 @@
+# -*- 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 += [
+ 'EMEAudioDecoder.h',
+ 'EMEDecoderModule.h',
+ 'EMEVideoDecoder.h',
+ 'SamplesWaitingForKey.h',
+]
+
+UNIFIED_SOURCES += [
+ 'EMEAudioDecoder.cpp',
+ 'EMEDecoderModule.cpp',
+ 'EMEVideoDecoder.cpp',
+ 'SamplesWaitingForKey.cpp',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
diff --git a/dom/media/platforms/agnostic/gmp/GMPAudioDecoder.cpp b/dom/media/platforms/agnostic/gmp/GMPAudioDecoder.cpp
new file mode 100644
index 000000000..d863d44d4
--- /dev/null
+++ b/dom/media/platforms/agnostic/gmp/GMPAudioDecoder.cpp
@@ -0,0 +1,306 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GMPAudioDecoder.h"
+#include "nsServiceManagerUtils.h"
+#include "MediaInfo.h"
+#include "GMPDecoderModule.h"
+#include "nsPrintfCString.h"
+
+namespace mozilla {
+
+#if defined(DEBUG)
+bool IsOnGMPThread()
+{
+ nsCOMPtr<mozIGeckoMediaPluginService> mps = do_GetService("@mozilla.org/gecko-media-plugin-service;1");
+ MOZ_ASSERT(mps);
+
+ nsCOMPtr<nsIThread> gmpThread;
+ nsresult rv = mps->GetThread(getter_AddRefs(gmpThread));
+ MOZ_ASSERT(NS_SUCCEEDED(rv) && gmpThread);
+ return NS_GetCurrentThread() == gmpThread;
+}
+#endif
+
+void
+AudioCallbackAdapter::Decoded(const nsTArray<int16_t>& aPCM, uint64_t aTimeStamp, uint32_t aChannels, uint32_t aRate)
+{
+ MOZ_ASSERT(IsOnGMPThread());
+
+ if (aRate == 0 || aChannels == 0) {
+ mCallback->Error(MediaResult(
+ NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ RESULT_DETAIL(
+ "Invalid rate or num channels returned on GMP audio samples")));
+ return;
+ }
+
+ size_t numFrames = aPCM.Length() / aChannels;
+ MOZ_ASSERT((aPCM.Length() % aChannels) == 0);
+ AlignedAudioBuffer audioData(aPCM.Length());
+ if (!audioData) {
+ mCallback->Error(
+ MediaResult(NS_ERROR_OUT_OF_MEMORY,
+ RESULT_DETAIL("Unable to allocate audio buffer")));
+ return;
+ }
+
+ for (size_t i = 0; i < aPCM.Length(); ++i) {
+ audioData[i] = AudioSampleToFloat(aPCM[i]);
+ }
+
+ if (mMustRecaptureAudioPosition) {
+ mAudioFrameSum = 0;
+ auto timestamp = UsecsToFrames(aTimeStamp, aRate);
+ if (!timestamp.isValid()) {
+ mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_OVERFLOW_ERR,
+ RESULT_DETAIL("Invalid timestamp")));
+ return;
+ }
+ mAudioFrameOffset = timestamp.value();
+ mMustRecaptureAudioPosition = false;
+ }
+
+ auto timestamp = FramesToUsecs(mAudioFrameOffset + mAudioFrameSum, aRate);
+ if (!timestamp.isValid()) {
+ mCallback->Error(
+ MediaResult(NS_ERROR_DOM_MEDIA_OVERFLOW_ERR,
+ RESULT_DETAIL("Invalid timestamp on audio samples")));
+ return;
+ }
+ mAudioFrameSum += numFrames;
+
+ auto duration = FramesToUsecs(numFrames, aRate);
+ if (!duration.isValid()) {
+ mCallback->Error(
+ MediaResult(NS_ERROR_DOM_MEDIA_OVERFLOW_ERR,
+ RESULT_DETAIL("Invalid duration on audio samples")));
+ return;
+ }
+
+ RefPtr<AudioData> audio(new AudioData(mLastStreamOffset,
+ timestamp.value(),
+ duration.value(),
+ numFrames,
+ Move(audioData),
+ aChannels,
+ aRate));
+
+#ifdef LOG_SAMPLE_DECODE
+ LOG("Decoded audio sample! timestamp=%lld duration=%lld currentLength=%u",
+ timestamp, duration, currentLength);
+#endif
+
+ mCallback->Output(audio);
+}
+
+void
+AudioCallbackAdapter::InputDataExhausted()
+{
+ MOZ_ASSERT(IsOnGMPThread());
+ mCallback->InputExhausted();
+}
+
+void
+AudioCallbackAdapter::DrainComplete()
+{
+ MOZ_ASSERT(IsOnGMPThread());
+ mCallback->DrainComplete();
+}
+
+void
+AudioCallbackAdapter::ResetComplete()
+{
+ MOZ_ASSERT(IsOnGMPThread());
+ mMustRecaptureAudioPosition = true;
+ mCallback->FlushComplete();
+}
+
+void
+AudioCallbackAdapter::Error(GMPErr aErr)
+{
+ MOZ_ASSERT(IsOnGMPThread());
+ mCallback->Error(MediaResult(aErr == GMPDecodeErr
+ ? NS_ERROR_DOM_MEDIA_DECODE_ERR
+ : NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ RESULT_DETAIL("GMPErr:%x", aErr)));
+}
+
+void
+AudioCallbackAdapter::Terminated()
+{
+ mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ RESULT_DETAIL("Audio GMP decoder terminated.")));
+}
+
+GMPAudioDecoderParams::GMPAudioDecoderParams(const CreateDecoderParams& aParams)
+ : mConfig(aParams.AudioConfig())
+ , mTaskQueue(aParams.mTaskQueue)
+ , mCallback(nullptr)
+ , mAdapter(nullptr)
+ , mCrashHelper(aParams.mCrashHelper)
+{}
+
+GMPAudioDecoderParams&
+GMPAudioDecoderParams::WithCallback(MediaDataDecoderProxy* aWrapper)
+{
+ MOZ_ASSERT(aWrapper);
+ MOZ_ASSERT(!mCallback); // Should only be called once per instance.
+ mCallback = aWrapper->Callback();
+ mAdapter = nullptr;
+ return *this;
+}
+
+GMPAudioDecoderParams&
+GMPAudioDecoderParams::WithAdapter(AudioCallbackAdapter* aAdapter)
+{
+ MOZ_ASSERT(aAdapter);
+ MOZ_ASSERT(!mAdapter); // Should only be called once per instance.
+ mCallback = aAdapter->Callback();
+ mAdapter = aAdapter;
+ return *this;
+}
+
+GMPAudioDecoder::GMPAudioDecoder(const GMPAudioDecoderParams& aParams)
+ : mConfig(aParams.mConfig)
+ , mCallback(aParams.mCallback)
+ , mGMP(nullptr)
+ , mAdapter(aParams.mAdapter)
+ , mCrashHelper(aParams.mCrashHelper)
+{
+ MOZ_ASSERT(!mAdapter || mCallback == mAdapter->Callback());
+ if (!mAdapter) {
+ mAdapter = new AudioCallbackAdapter(mCallback);
+ }
+}
+
+void
+GMPAudioDecoder::InitTags(nsTArray<nsCString>& aTags)
+{
+ aTags.AppendElement(NS_LITERAL_CSTRING("aac"));
+ const Maybe<nsCString> gmp(
+ GMPDecoderModule::PreferredGMP(NS_LITERAL_CSTRING("audio/mp4a-latm")));
+ if (gmp.isSome()) {
+ aTags.AppendElement(gmp.value());
+ }
+}
+
+nsCString
+GMPAudioDecoder::GetNodeId()
+{
+ return SHARED_GMP_DECODING_NODE_ID;
+}
+
+void
+GMPAudioDecoder::GMPInitDone(GMPAudioDecoderProxy* aGMP)
+{
+ MOZ_ASSERT(IsOnGMPThread());
+
+ if (!aGMP) {
+ mInitPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
+ return;
+ }
+ if (mInitPromise.IsEmpty()) {
+ // GMP must have been shutdown while we were waiting for Init operation
+ // to complete.
+ aGMP->Close();
+ return;
+ }
+ nsTArray<uint8_t> codecSpecific;
+ codecSpecific.AppendElements(mConfig.mCodecSpecificConfig->Elements(),
+ mConfig.mCodecSpecificConfig->Length());
+
+ nsresult rv = aGMP->InitDecode(kGMPAudioCodecAAC,
+ mConfig.mChannels,
+ mConfig.mBitDepth,
+ mConfig.mRate,
+ codecSpecific,
+ mAdapter);
+ if (NS_FAILED(rv)) {
+ aGMP->Close();
+ mInitPromise.Reject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
+ return;
+ }
+
+ mGMP = aGMP;
+ mInitPromise.Resolve(TrackInfo::kAudioTrack, __func__);
+}
+
+RefPtr<MediaDataDecoder::InitPromise>
+GMPAudioDecoder::Init()
+{
+ MOZ_ASSERT(IsOnGMPThread());
+
+ mMPS = do_GetService("@mozilla.org/gecko-media-plugin-service;1");
+ MOZ_ASSERT(mMPS);
+
+ RefPtr<InitPromise> promise(mInitPromise.Ensure(__func__));
+
+ nsTArray<nsCString> tags;
+ InitTags(tags);
+ UniquePtr<GetGMPAudioDecoderCallback> callback(new GMPInitDoneCallback(this));
+ if (NS_FAILED(mMPS->GetGMPAudioDecoder(mCrashHelper, &tags, GetNodeId(), Move(callback)))) {
+ mInitPromise.Reject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
+ }
+
+ return promise;
+}
+
+void
+GMPAudioDecoder::Input(MediaRawData* aSample)
+{
+ MOZ_ASSERT(IsOnGMPThread());
+
+ RefPtr<MediaRawData> sample(aSample);
+ if (!mGMP) {
+ mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ RESULT_DETAIL("mGMP not initialized")));
+ return;
+ }
+
+ mAdapter->SetLastStreamOffset(sample->mOffset);
+
+ gmp::GMPAudioSamplesImpl samples(sample, mConfig.mChannels, mConfig.mRate);
+ nsresult rv = mGMP->Decode(samples);
+ if (NS_FAILED(rv)) {
+ mCallback->Error(MediaResult(rv, __func__));
+ }
+}
+
+void
+GMPAudioDecoder::Flush()
+{
+ MOZ_ASSERT(IsOnGMPThread());
+
+ if (!mGMP || NS_FAILED(mGMP->Reset())) {
+ // Abort the flush.
+ mCallback->FlushComplete();
+ }
+}
+
+void
+GMPAudioDecoder::Drain()
+{
+ MOZ_ASSERT(IsOnGMPThread());
+
+ if (!mGMP || NS_FAILED(mGMP->Drain())) {
+ mCallback->DrainComplete();
+ }
+}
+
+void
+GMPAudioDecoder::Shutdown()
+{
+ mInitPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
+ if (!mGMP) {
+ return;
+ }
+ // Note this unblocks flush and drain operations waiting for callbacks.
+ mGMP->Close();
+ mGMP = nullptr;
+}
+
+} // namespace mozilla
diff --git a/dom/media/platforms/agnostic/gmp/GMPAudioDecoder.h b/dom/media/platforms/agnostic/gmp/GMPAudioDecoder.h
new file mode 100644
index 000000000..90e3ebdb6
--- /dev/null
+++ b/dom/media/platforms/agnostic/gmp/GMPAudioDecoder.h
@@ -0,0 +1,112 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#if !defined(GMPAudioDecoder_h_)
+#define GMPAudioDecoder_h_
+
+#include "GMPAudioDecoderProxy.h"
+#include "MediaDataDecoderProxy.h"
+#include "PlatformDecoderModule.h"
+#include "mozIGeckoMediaPluginService.h"
+#include "nsAutoPtr.h"
+
+namespace mozilla {
+
+class AudioCallbackAdapter : public GMPAudioDecoderCallbackProxy {
+public:
+ explicit AudioCallbackAdapter(MediaDataDecoderCallbackProxy* aCallback)
+ : mCallback(aCallback)
+ , mLastStreamOffset(0)
+ , mAudioFrameSum(0)
+ , mAudioFrameOffset(0)
+ , mMustRecaptureAudioPosition(true)
+ {}
+
+ MediaDataDecoderCallbackProxy* Callback() const { return mCallback; }
+
+ // GMPAudioDecoderCallbackProxy
+ void Decoded(const nsTArray<int16_t>& aPCM, uint64_t aTimeStamp, uint32_t aChannels, uint32_t aRate) override;
+ void InputDataExhausted() override;
+ void DrainComplete() override;
+ void ResetComplete() override;
+ void Error(GMPErr aErr) override;
+ void Terminated() override;
+
+ void SetLastStreamOffset(int64_t aStreamOffset) {
+ mLastStreamOffset = aStreamOffset;
+ }
+
+private:
+ MediaDataDecoderCallbackProxy* mCallback;
+ int64_t mLastStreamOffset;
+
+ int64_t mAudioFrameSum;
+ int64_t mAudioFrameOffset;
+ bool mMustRecaptureAudioPosition;
+};
+
+struct GMPAudioDecoderParams {
+ explicit GMPAudioDecoderParams(const CreateDecoderParams& aParams);
+ GMPAudioDecoderParams& WithCallback(MediaDataDecoderProxy* aWrapper);
+ GMPAudioDecoderParams& WithAdapter(AudioCallbackAdapter* aAdapter);
+
+ const AudioInfo& mConfig;
+ TaskQueue* mTaskQueue;
+ MediaDataDecoderCallbackProxy* mCallback;
+ AudioCallbackAdapter* mAdapter;
+ RefPtr<GMPCrashHelper> mCrashHelper;
+};
+
+class GMPAudioDecoder : public MediaDataDecoder {
+public:
+ explicit GMPAudioDecoder(const GMPAudioDecoderParams& aParams);
+
+ RefPtr<InitPromise> Init() override;
+ void Input(MediaRawData* aSample) override;
+ void Flush() override;
+ void Drain() override;
+ void Shutdown() override;
+ const char* GetDescriptionName() const override
+ {
+ return "GMP audio decoder";
+ }
+
+protected:
+ virtual void InitTags(nsTArray<nsCString>& aTags);
+ virtual nsCString GetNodeId();
+
+private:
+
+ class GMPInitDoneCallback : public GetGMPAudioDecoderCallback
+ {
+ public:
+ explicit GMPInitDoneCallback(GMPAudioDecoder* aDecoder)
+ : mDecoder(aDecoder)
+ {
+ }
+
+ void Done(GMPAudioDecoderProxy* aGMP) override
+ {
+ mDecoder->GMPInitDone(aGMP);
+ }
+
+ private:
+ RefPtr<GMPAudioDecoder> mDecoder;
+ };
+ void GMPInitDone(GMPAudioDecoderProxy* aGMP);
+
+ const AudioInfo mConfig;
+ MediaDataDecoderCallbackProxy* mCallback;
+ nsCOMPtr<mozIGeckoMediaPluginService> mMPS;
+ GMPAudioDecoderProxy* mGMP;
+ nsAutoPtr<AudioCallbackAdapter> mAdapter;
+ MozPromiseHolder<InitPromise> mInitPromise;
+ RefPtr<GMPCrashHelper> mCrashHelper;
+};
+
+} // namespace mozilla
+
+#endif // GMPAudioDecoder_h_
diff --git a/dom/media/platforms/agnostic/gmp/GMPDecoderModule.cpp b/dom/media/platforms/agnostic/gmp/GMPDecoderModule.cpp
new file mode 100644
index 000000000..cc53d2c93
--- /dev/null
+++ b/dom/media/platforms/agnostic/gmp/GMPDecoderModule.cpp
@@ -0,0 +1,172 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GMPDecoderModule.h"
+#include "DecoderDoctorDiagnostics.h"
+#include "GMPAudioDecoder.h"
+#include "GMPVideoDecoder.h"
+#include "GMPUtils.h"
+#include "MediaDataDecoderProxy.h"
+#include "MediaPrefs.h"
+#include "VideoUtils.h"
+#include "mozIGeckoMediaPluginService.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/StaticMutex.h"
+#include "gmp-audio-decode.h"
+#include "gmp-video-decode.h"
+#include "MP4Decoder.h"
+#include "VPXDecoder.h"
+#ifdef XP_WIN
+#include "WMFDecoderModule.h"
+#endif
+
+namespace mozilla {
+
+GMPDecoderModule::GMPDecoderModule()
+{
+}
+
+GMPDecoderModule::~GMPDecoderModule()
+{
+}
+
+static already_AddRefed<MediaDataDecoderProxy>
+CreateDecoderWrapper(MediaDataDecoderCallback* aCallback)
+{
+ RefPtr<gmp::GeckoMediaPluginService> s(gmp::GeckoMediaPluginService::GetGeckoMediaPluginService());
+ if (!s) {
+ return nullptr;
+ }
+ RefPtr<AbstractThread> thread(s->GetAbstractGMPThread());
+ if (!thread) {
+ return nullptr;
+ }
+ RefPtr<MediaDataDecoderProxy> decoder(new MediaDataDecoderProxy(thread.forget(), aCallback));
+ return decoder.forget();
+}
+
+already_AddRefed<MediaDataDecoder>
+GMPDecoderModule::CreateVideoDecoder(const CreateDecoderParams& aParams)
+{
+ if (!MP4Decoder::IsH264(aParams.mConfig.mMimeType) &&
+ !VPXDecoder::IsVP8(aParams.mConfig.mMimeType) &&
+ !VPXDecoder::IsVP9(aParams.mConfig.mMimeType)) {
+ return nullptr;
+ }
+
+ if (aParams.mDiagnostics) {
+ const Maybe<nsCString> preferredGMP = PreferredGMP(aParams.mConfig.mMimeType);
+ if (preferredGMP.isSome()) {
+ aParams.mDiagnostics->SetGMP(preferredGMP.value());
+ }
+ }
+
+ RefPtr<MediaDataDecoderProxy> wrapper = CreateDecoderWrapper(aParams.mCallback);
+ auto params = GMPVideoDecoderParams(aParams).WithCallback(wrapper);
+ wrapper->SetProxyTarget(new GMPVideoDecoder(params));
+ return wrapper.forget();
+}
+
+already_AddRefed<MediaDataDecoder>
+GMPDecoderModule::CreateAudioDecoder(const CreateDecoderParams& aParams)
+{
+ if (!aParams.mConfig.mMimeType.EqualsLiteral("audio/mp4a-latm")) {
+ return nullptr;
+ }
+
+ if (aParams.mDiagnostics) {
+ const Maybe<nsCString> preferredGMP = PreferredGMP(aParams.mConfig.mMimeType);
+ if (preferredGMP.isSome()) {
+ aParams.mDiagnostics->SetGMP(preferredGMP.value());
+ }
+ }
+
+ RefPtr<MediaDataDecoderProxy> wrapper = CreateDecoderWrapper(aParams.mCallback);
+ auto params = GMPAudioDecoderParams(aParams).WithCallback(wrapper);
+ wrapper->SetProxyTarget(new GMPAudioDecoder(params));
+ return wrapper.forget();
+}
+
+PlatformDecoderModule::ConversionRequired
+GMPDecoderModule::DecoderNeedsConversion(const TrackInfo& aConfig) const
+{
+ // GMPVideoCodecType::kGMPVideoCodecH264 specifies that encoded frames must be in AVCC format.
+ if (aConfig.IsVideo() && MP4Decoder::IsH264(aConfig.mMimeType)) {
+ return ConversionRequired::kNeedAVCC;
+ } else {
+ return ConversionRequired::kNeedNone;
+ }
+}
+
+/* static */
+const Maybe<nsCString>
+GMPDecoderModule::PreferredGMP(const nsACString& aMimeType)
+{
+ Maybe<nsCString> rv;
+ if (aMimeType.EqualsLiteral("audio/mp4a-latm")) {
+ switch (MediaPrefs::GMPAACPreferred()) {
+ case 1: rv.emplace(kEMEKeySystemClearkey); break;
+ case 2: rv.emplace(kEMEKeySystemPrimetime); break;
+ default: break;
+ }
+ }
+
+ if (MP4Decoder::IsH264(aMimeType)) {
+ switch (MediaPrefs::GMPH264Preferred()) {
+ case 1: rv.emplace(kEMEKeySystemClearkey); break;
+ case 2: rv.emplace(kEMEKeySystemPrimetime); break;
+ default: break;
+ }
+ }
+
+ return rv;
+}
+
+/* static */
+bool
+GMPDecoderModule::SupportsMimeType(const nsACString& aMimeType,
+ const Maybe<nsCString>& aGMP)
+{
+ if (aGMP.isNothing()) {
+ return false;
+ }
+
+ if (MP4Decoder::IsH264(aMimeType)) {
+ return HaveGMPFor(NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER),
+ { NS_LITERAL_CSTRING("h264"), aGMP.value()});
+ }
+
+ if (VPXDecoder::IsVP9(aMimeType)) {
+ return HaveGMPFor(NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER),
+ { NS_LITERAL_CSTRING("vp9"), aGMP.value()});
+ }
+
+ if (VPXDecoder::IsVP8(aMimeType)) {
+ return HaveGMPFor(NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER),
+ { NS_LITERAL_CSTRING("vp8"), aGMP.value()});
+ }
+
+ if (MP4Decoder::IsAAC(aMimeType)) {
+ return HaveGMPFor(NS_LITERAL_CSTRING(GMP_API_AUDIO_DECODER),
+ { NS_LITERAL_CSTRING("aac"), aGMP.value()});
+ }
+
+ return false;
+}
+
+bool
+GMPDecoderModule::SupportsMimeType(const nsACString& aMimeType,
+ DecoderDoctorDiagnostics* aDiagnostics) const
+{
+ const Maybe<nsCString> preferredGMP = PreferredGMP(aMimeType);
+ bool rv = SupportsMimeType(aMimeType, preferredGMP);
+ if (rv && aDiagnostics && preferredGMP.isSome()) {
+ aDiagnostics->SetGMP(preferredGMP.value());
+ }
+ return rv;
+}
+
+} // namespace mozilla
diff --git a/dom/media/platforms/agnostic/gmp/GMPDecoderModule.h b/dom/media/platforms/agnostic/gmp/GMPDecoderModule.h
new file mode 100644
index 000000000..b501ecb54
--- /dev/null
+++ b/dom/media/platforms/agnostic/gmp/GMPDecoderModule.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#if !defined(GMPDecoderModule_h_)
+#define GMPDecoderModule_h_
+
+#include "PlatformDecoderModule.h"
+#include "mozilla/Maybe.h"
+
+// The special NodeId we use when doing unencrypted decoding using the GMP's
+// decoder. This ensures that each GMP MediaDataDecoder we create doesn't
+// require spinning up a new process, but instead we run all instances of
+// GMP decoders in the one process, to reduce overhead.
+//
+// Note: GMP storage is isolated by NodeId, and non persistent for this
+// special NodeId, and the only way a GMP can communicate with the outside
+// world is through the EME GMP APIs, and we never run EME with this NodeID
+// (because NodeIds are random strings which can't contain the '-' character),
+// so there's no way a malicious GMP can harvest, store, and then report any
+// privacy sensitive data about what users are watching.
+#define SHARED_GMP_DECODING_NODE_ID NS_LITERAL_CSTRING("gmp-shared-decoding")
+
+namespace mozilla {
+
+class GMPDecoderModule : public PlatformDecoderModule {
+public:
+ GMPDecoderModule();
+
+ virtual ~GMPDecoderModule();
+
+ // Decode thread.
+ already_AddRefed<MediaDataDecoder>
+ CreateVideoDecoder(const CreateDecoderParams& aParams) override;
+
+ // Decode thread.
+ already_AddRefed<MediaDataDecoder>
+ CreateAudioDecoder(const CreateDecoderParams& aParams) override;
+
+ ConversionRequired
+ DecoderNeedsConversion(const TrackInfo& aConfig) const override;
+
+ bool
+ SupportsMimeType(const nsACString& aMimeType,
+ DecoderDoctorDiagnostics* aDiagnostics) const override;
+
+ static const Maybe<nsCString> PreferredGMP(const nsACString& aMimeType);
+
+ static bool SupportsMimeType(const nsACString& aMimeType,
+ const Maybe<nsCString>& aGMP);
+};
+
+} // namespace mozilla
+
+#endif // GMPDecoderModule_h_
diff --git a/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.cpp b/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.cpp
new file mode 100644
index 000000000..912b88ce1
--- /dev/null
+++ b/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.cpp
@@ -0,0 +1,387 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GMPVideoDecoder.h"
+#include "GMPVideoHost.h"
+#include "mozilla/EndianUtils.h"
+#include "prsystem.h"
+#include "MediaData.h"
+#include "GMPDecoderModule.h"
+#include "VPXDecoder.h"
+
+namespace mozilla {
+
+#if defined(DEBUG)
+extern bool IsOnGMPThread();
+#endif
+
+void
+VideoCallbackAdapter::Decoded(GMPVideoi420Frame* aDecodedFrame)
+{
+ GMPUniquePtr<GMPVideoi420Frame> decodedFrame(aDecodedFrame);
+
+ MOZ_ASSERT(IsOnGMPThread());
+
+ VideoData::YCbCrBuffer b;
+ for (int i = 0; i < kGMPNumOfPlanes; ++i) {
+ b.mPlanes[i].mData = decodedFrame->Buffer(GMPPlaneType(i));
+ b.mPlanes[i].mStride = decodedFrame->Stride(GMPPlaneType(i));
+ if (i == kGMPYPlane) {
+ b.mPlanes[i].mWidth = decodedFrame->Width();
+ b.mPlanes[i].mHeight = decodedFrame->Height();
+ } else {
+ b.mPlanes[i].mWidth = (decodedFrame->Width() + 1) / 2;
+ b.mPlanes[i].mHeight = (decodedFrame->Height() + 1) / 2;
+ }
+ b.mPlanes[i].mOffset = 0;
+ b.mPlanes[i].mSkip = 0;
+ }
+
+ gfx::IntRect pictureRegion(0, 0, decodedFrame->Width(), decodedFrame->Height());
+ RefPtr<VideoData> v =
+ VideoData::CreateAndCopyData(mVideoInfo,
+ mImageContainer,
+ mLastStreamOffset,
+ decodedFrame->Timestamp(),
+ decodedFrame->Duration(),
+ b,
+ false,
+ -1,
+ pictureRegion);
+ if (v) {
+ mCallback->Output(v);
+ } else {
+ mCallback->Error(MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__));
+ }
+}
+
+void
+VideoCallbackAdapter::ReceivedDecodedReferenceFrame(const uint64_t aPictureId)
+{
+ MOZ_ASSERT(IsOnGMPThread());
+}
+
+void
+VideoCallbackAdapter::ReceivedDecodedFrame(const uint64_t aPictureId)
+{
+ MOZ_ASSERT(IsOnGMPThread());
+}
+
+void
+VideoCallbackAdapter::InputDataExhausted()
+{
+ MOZ_ASSERT(IsOnGMPThread());
+ mCallback->InputExhausted();
+}
+
+void
+VideoCallbackAdapter::DrainComplete()
+{
+ MOZ_ASSERT(IsOnGMPThread());
+ mCallback->DrainComplete();
+}
+
+void
+VideoCallbackAdapter::ResetComplete()
+{
+ MOZ_ASSERT(IsOnGMPThread());
+ mCallback->FlushComplete();
+}
+
+void
+VideoCallbackAdapter::Error(GMPErr aErr)
+{
+ MOZ_ASSERT(IsOnGMPThread());
+ mCallback->Error(MediaResult(aErr == GMPDecodeErr
+ ? NS_ERROR_DOM_MEDIA_DECODE_ERR
+ : NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ RESULT_DETAIL("GMPErr:%x", aErr)));
+}
+
+void
+VideoCallbackAdapter::Terminated()
+{
+ // Note that this *may* be called from the proxy thread also.
+ mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ RESULT_DETAIL("Video GMP decoder terminated.")));
+}
+
+GMPVideoDecoderParams::GMPVideoDecoderParams(const CreateDecoderParams& aParams)
+ : mConfig(aParams.VideoConfig())
+ , mTaskQueue(aParams.mTaskQueue)
+ , mCallback(nullptr)
+ , mAdapter(nullptr)
+ , mImageContainer(aParams.mImageContainer)
+ , mLayersBackend(aParams.GetLayersBackend())
+ , mCrashHelper(aParams.mCrashHelper)
+{}
+
+GMPVideoDecoderParams&
+GMPVideoDecoderParams::WithCallback(MediaDataDecoderProxy* aWrapper)
+{
+ MOZ_ASSERT(aWrapper);
+ MOZ_ASSERT(!mCallback); // Should only be called once per instance.
+ mCallback = aWrapper->Callback();
+ mAdapter = nullptr;
+ return *this;
+}
+
+GMPVideoDecoderParams&
+GMPVideoDecoderParams::WithAdapter(VideoCallbackAdapter* aAdapter)
+{
+ MOZ_ASSERT(aAdapter);
+ MOZ_ASSERT(!mAdapter); // Should only be called once per instance.
+ mCallback = aAdapter->Callback();
+ mAdapter = aAdapter;
+ return *this;
+}
+
+GMPVideoDecoder::GMPVideoDecoder(const GMPVideoDecoderParams& aParams)
+ : mConfig(aParams.mConfig)
+ , mCallback(aParams.mCallback)
+ , mGMP(nullptr)
+ , mHost(nullptr)
+ , mAdapter(aParams.mAdapter)
+ , mConvertNALUnitLengths(false)
+ , mCrashHelper(aParams.mCrashHelper)
+{
+ MOZ_ASSERT(!mAdapter || mCallback == mAdapter->Callback());
+ if (!mAdapter) {
+ mAdapter = new VideoCallbackAdapter(mCallback,
+ VideoInfo(mConfig.mDisplay.width,
+ mConfig.mDisplay.height),
+ aParams.mImageContainer);
+ }
+}
+
+void
+GMPVideoDecoder::InitTags(nsTArray<nsCString>& aTags)
+{
+ if (MP4Decoder::IsH264(mConfig.mMimeType)) {
+ aTags.AppendElement(NS_LITERAL_CSTRING("h264"));
+ const Maybe<nsCString> gmp(
+ GMPDecoderModule::PreferredGMP(NS_LITERAL_CSTRING("video/avc")));
+ if (gmp.isSome()) {
+ aTags.AppendElement(gmp.value());
+ }
+ } else if (VPXDecoder::IsVP8(mConfig.mMimeType)) {
+ aTags.AppendElement(NS_LITERAL_CSTRING("vp8"));
+ } else if (VPXDecoder::IsVP9(mConfig.mMimeType)) {
+ aTags.AppendElement(NS_LITERAL_CSTRING("vp9"));
+ }
+}
+
+nsCString
+GMPVideoDecoder::GetNodeId()
+{
+ return SHARED_GMP_DECODING_NODE_ID;
+}
+
+GMPUniquePtr<GMPVideoEncodedFrame>
+GMPVideoDecoder::CreateFrame(MediaRawData* aSample)
+{
+ GMPVideoFrame* ftmp = nullptr;
+ GMPErr err = mHost->CreateFrame(kGMPEncodedVideoFrame, &ftmp);
+ if (GMP_FAILED(err)) {
+ mCallback->Error(MediaResult(NS_ERROR_OUT_OF_MEMORY,
+ RESULT_DETAIL("Host::CreateFrame:%x", err)));
+ return nullptr;
+ }
+
+ GMPUniquePtr<GMPVideoEncodedFrame> frame(static_cast<GMPVideoEncodedFrame*>(ftmp));
+ err = frame->CreateEmptyFrame(aSample->Size());
+ if (GMP_FAILED(err)) {
+ mCallback->Error(MediaResult(NS_ERROR_OUT_OF_MEMORY,
+ RESULT_DETAIL("GMPVideoEncodedFrame::CreateEmptyFrame:%x", err)));
+ return nullptr;
+ }
+
+ memcpy(frame->Buffer(), aSample->Data(), frame->Size());
+
+ // Convert 4-byte NAL unit lengths to host-endian 4-byte buffer lengths to
+ // suit the GMP API.
+ if (mConvertNALUnitLengths) {
+ const int kNALLengthSize = 4;
+ uint8_t* buf = frame->Buffer();
+ while (buf < frame->Buffer() + frame->Size() - kNALLengthSize) {
+ uint32_t length = BigEndian::readUint32(buf) + kNALLengthSize;
+ *reinterpret_cast<uint32_t *>(buf) = length;
+ buf += length;
+ }
+ }
+
+ frame->SetBufferType(GMP_BufferLength32);
+
+ frame->SetEncodedWidth(mConfig.mDisplay.width);
+ frame->SetEncodedHeight(mConfig.mDisplay.height);
+ frame->SetTimeStamp(aSample->mTime);
+ frame->SetCompleteFrame(true);
+ frame->SetDuration(aSample->mDuration);
+ frame->SetFrameType(aSample->mKeyframe ? kGMPKeyFrame : kGMPDeltaFrame);
+
+ return frame;
+}
+
+const VideoInfo&
+GMPVideoDecoder::GetConfig() const
+{
+ return mConfig;
+}
+
+void
+GMPVideoDecoder::GMPInitDone(GMPVideoDecoderProxy* aGMP, GMPVideoHost* aHost)
+{
+ MOZ_ASSERT(IsOnGMPThread());
+
+ if (!aGMP) {
+ mInitPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
+ return;
+ }
+ MOZ_ASSERT(aHost);
+
+ if (mInitPromise.IsEmpty()) {
+ // GMP must have been shutdown while we were waiting for Init operation
+ // to complete.
+ aGMP->Close();
+ return;
+ }
+
+ GMPVideoCodec codec;
+ memset(&codec, 0, sizeof(codec));
+
+ codec.mGMPApiVersion = kGMPVersion33;
+ nsTArray<uint8_t> codecSpecific;
+ if (MP4Decoder::IsH264(mConfig.mMimeType)) {
+ codec.mCodecType = kGMPVideoCodecH264;
+ codecSpecific.AppendElement(0); // mPacketizationMode.
+ codecSpecific.AppendElements(mConfig.mExtraData->Elements(),
+ mConfig.mExtraData->Length());
+ } else if (VPXDecoder::IsVP8(mConfig.mMimeType)) {
+ codec.mCodecType = kGMPVideoCodecVP8;
+ } else if (VPXDecoder::IsVP9(mConfig.mMimeType)) {
+ codec.mCodecType = kGMPVideoCodecVP9;
+ } else {
+ // Unrecognized mime type
+ aGMP->Close();
+ mInitPromise.Reject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
+ return;
+ }
+ codec.mWidth = mConfig.mImage.width;
+ codec.mHeight = mConfig.mImage.height;
+
+ nsresult rv = aGMP->InitDecode(codec,
+ codecSpecific,
+ mAdapter,
+ PR_GetNumberOfProcessors());
+ if (NS_FAILED(rv)) {
+ aGMP->Close();
+ mInitPromise.Reject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
+ return;
+ }
+
+ mGMP = aGMP;
+ mHost = aHost;
+
+ // GMP implementations have interpreted the meaning of GMP_BufferLength32
+ // differently. The OpenH264 GMP expects GMP_BufferLength32 to behave as
+ // specified in the GMP API, where each buffer is prefixed by a 32-bit
+ // host-endian buffer length that includes the size of the buffer length
+ // field. Other existing GMPs currently expect GMP_BufferLength32 (when
+ // combined with kGMPVideoCodecH264) to mean "like AVCC but restricted to
+ // 4-byte NAL lengths" (i.e. buffer lengths are specified in big-endian
+ // and do not include the length of the buffer length field.
+ mConvertNALUnitLengths = mGMP->GetDisplayName().EqualsLiteral("gmpopenh264");
+
+ mInitPromise.Resolve(TrackInfo::kVideoTrack, __func__);
+}
+
+RefPtr<MediaDataDecoder::InitPromise>
+GMPVideoDecoder::Init()
+{
+ MOZ_ASSERT(IsOnGMPThread());
+
+ mMPS = do_GetService("@mozilla.org/gecko-media-plugin-service;1");
+ MOZ_ASSERT(mMPS);
+
+ RefPtr<InitPromise> promise(mInitPromise.Ensure(__func__));
+
+ nsTArray<nsCString> tags;
+ InitTags(tags);
+ UniquePtr<GetGMPVideoDecoderCallback> callback(new GMPInitDoneCallback(this));
+ if (NS_FAILED(mMPS->GetDecryptingGMPVideoDecoder(mCrashHelper,
+ &tags,
+ GetNodeId(),
+ Move(callback),
+ DecryptorId()))) {
+ mInitPromise.Reject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
+ }
+
+ return promise;
+}
+
+void
+GMPVideoDecoder::Input(MediaRawData* aSample)
+{
+ MOZ_ASSERT(IsOnGMPThread());
+
+ RefPtr<MediaRawData> sample(aSample);
+ if (!mGMP) {
+ mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ RESULT_DETAIL("mGMP not initialized")));
+ return;
+ }
+
+ mAdapter->SetLastStreamOffset(sample->mOffset);
+
+ GMPUniquePtr<GMPVideoEncodedFrame> frame = CreateFrame(sample);
+ if (!frame) {
+ mCallback->Error(MediaResult(NS_ERROR_OUT_OF_MEMORY,
+ RESULT_DETAIL("CreateFrame returned null")));
+ return;
+ }
+ nsTArray<uint8_t> info; // No codec specific per-frame info to pass.
+ nsresult rv = mGMP->Decode(Move(frame), false, info, 0);
+ if (NS_FAILED(rv)) {
+ mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR,
+ RESULT_DETAIL("mGMP->Decode:%x", rv)));
+ }
+}
+
+void
+GMPVideoDecoder::Flush()
+{
+ MOZ_ASSERT(IsOnGMPThread());
+
+ if (!mGMP || NS_FAILED(mGMP->Reset())) {
+ // Abort the flush.
+ mCallback->FlushComplete();
+ }
+}
+
+void
+GMPVideoDecoder::Drain()
+{
+ MOZ_ASSERT(IsOnGMPThread());
+
+ if (!mGMP || NS_FAILED(mGMP->Drain())) {
+ mCallback->DrainComplete();
+ }
+}
+
+void
+GMPVideoDecoder::Shutdown()
+{
+ mInitPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
+ // Note that this *may* be called from the proxy thread also.
+ if (!mGMP) {
+ return;
+ }
+ // Note this unblocks flush and drain operations waiting for callbacks.
+ mGMP->Close();
+ mGMP = nullptr;
+}
+
+} // namespace mozilla
diff --git a/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.h b/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.h
new file mode 100644
index 000000000..900ef4553
--- /dev/null
+++ b/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.h
@@ -0,0 +1,122 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#if !defined(GMPVideoDecoder_h_)
+#define GMPVideoDecoder_h_
+
+#include "GMPVideoDecoderProxy.h"
+#include "ImageContainer.h"
+#include "MediaDataDecoderProxy.h"
+#include "PlatformDecoderModule.h"
+#include "mozIGeckoMediaPluginService.h"
+#include "MediaInfo.h"
+
+namespace mozilla {
+
+class VideoCallbackAdapter : public GMPVideoDecoderCallbackProxy {
+public:
+ VideoCallbackAdapter(MediaDataDecoderCallbackProxy* aCallback,
+ VideoInfo aVideoInfo,
+ layers::ImageContainer* aImageContainer)
+ : mCallback(aCallback)
+ , mLastStreamOffset(0)
+ , mVideoInfo(aVideoInfo)
+ , mImageContainer(aImageContainer)
+ {}
+
+ MediaDataDecoderCallbackProxy* Callback() const { return mCallback; }
+
+ // GMPVideoDecoderCallbackProxy
+ void Decoded(GMPVideoi420Frame* aDecodedFrame) override;
+ void ReceivedDecodedReferenceFrame(const uint64_t aPictureId) override;
+ void ReceivedDecodedFrame(const uint64_t aPictureId) override;
+ void InputDataExhausted() override;
+ void DrainComplete() override;
+ void ResetComplete() override;
+ void Error(GMPErr aErr) override;
+ void Terminated() override;
+
+ void SetLastStreamOffset(int64_t aStreamOffset) {
+ mLastStreamOffset = aStreamOffset;
+ }
+
+private:
+ MediaDataDecoderCallbackProxy* mCallback;
+ int64_t mLastStreamOffset;
+
+ VideoInfo mVideoInfo;
+ RefPtr<layers::ImageContainer> mImageContainer;
+};
+
+struct GMPVideoDecoderParams {
+ explicit GMPVideoDecoderParams(const CreateDecoderParams& aParams);
+ GMPVideoDecoderParams& WithCallback(MediaDataDecoderProxy* aWrapper);
+ GMPVideoDecoderParams& WithAdapter(VideoCallbackAdapter* aAdapter);
+
+ const VideoInfo& mConfig;
+ TaskQueue* mTaskQueue;
+ MediaDataDecoderCallbackProxy* mCallback;
+ VideoCallbackAdapter* mAdapter;
+ layers::ImageContainer* mImageContainer;
+ layers::LayersBackend mLayersBackend;
+ RefPtr<GMPCrashHelper> mCrashHelper;
+};
+
+class GMPVideoDecoder : public MediaDataDecoder {
+public:
+ explicit GMPVideoDecoder(const GMPVideoDecoderParams& aParams);
+
+ RefPtr<InitPromise> Init() override;
+ void Input(MediaRawData* aSample) override;
+ void Flush() override;
+ void Drain() override;
+ void Shutdown() override;
+ const char* GetDescriptionName() const override
+ {
+ return "GMP video decoder";
+ }
+
+protected:
+ virtual void InitTags(nsTArray<nsCString>& aTags);
+ virtual nsCString GetNodeId();
+ virtual uint32_t DecryptorId() const { return 0; }
+ virtual GMPUniquePtr<GMPVideoEncodedFrame> CreateFrame(MediaRawData* aSample);
+ virtual const VideoInfo& GetConfig() const;
+
+private:
+
+ class GMPInitDoneCallback : public GetGMPVideoDecoderCallback
+ {
+ public:
+ explicit GMPInitDoneCallback(GMPVideoDecoder* aDecoder)
+ : mDecoder(aDecoder)
+ {
+ }
+
+ void Done(GMPVideoDecoderProxy* aGMP, GMPVideoHost* aHost) override
+ {
+ mDecoder->GMPInitDone(aGMP, aHost);
+ }
+
+ private:
+ RefPtr<GMPVideoDecoder> mDecoder;
+ };
+ void GMPInitDone(GMPVideoDecoderProxy* aGMP, GMPVideoHost* aHost);
+
+ const VideoInfo mConfig;
+ MediaDataDecoderCallbackProxy* mCallback;
+ nsCOMPtr<mozIGeckoMediaPluginService> mMPS;
+ GMPVideoDecoderProxy* mGMP;
+ GMPVideoHost* mHost;
+ nsAutoPtr<VideoCallbackAdapter> mAdapter;
+ bool mConvertNALUnitLengths;
+ MozPromiseHolder<InitPromise> mInitPromise;
+ RefPtr<GMPCrashHelper> mCrashHelper;
+};
+
+} // namespace mozilla
+
+#endif // GMPVideoDecoder_h_
diff --git a/dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.cpp b/dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.cpp
new file mode 100644
index 000000000..5a196f8e6
--- /dev/null
+++ b/dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.cpp
@@ -0,0 +1,90 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MediaDataDecoderProxy.h"
+#include "MediaData.h"
+
+namespace mozilla {
+
+void
+MediaDataDecoderCallbackProxy::Error(const MediaResult& aError)
+{
+ mProxyCallback->Error(aError);
+}
+
+void
+MediaDataDecoderCallbackProxy::FlushComplete()
+{
+ mProxyDecoder->FlushComplete();
+}
+
+RefPtr<MediaDataDecoder::InitPromise>
+MediaDataDecoderProxy::InternalInit()
+{
+ return mProxyDecoder->Init();
+}
+
+RefPtr<MediaDataDecoder::InitPromise>
+MediaDataDecoderProxy::Init()
+{
+ MOZ_ASSERT(!mIsShutdown);
+
+ return InvokeAsync(mProxyThread, this, __func__,
+ &MediaDataDecoderProxy::InternalInit);
+}
+
+void
+MediaDataDecoderProxy::Input(MediaRawData* aSample)
+{
+ MOZ_ASSERT(!IsOnProxyThread());
+ MOZ_ASSERT(!mIsShutdown);
+
+ nsCOMPtr<nsIRunnable> task(new InputTask(mProxyDecoder, aSample));
+ mProxyThread->Dispatch(task.forget());
+}
+
+void
+MediaDataDecoderProxy::Flush()
+{
+ MOZ_ASSERT(!IsOnProxyThread());
+ MOZ_ASSERT(!mIsShutdown);
+
+ mFlushComplete.Set(false);
+
+ mProxyThread->Dispatch(NewRunnableMethod(mProxyDecoder, &MediaDataDecoder::Flush));
+
+ mFlushComplete.WaitUntil(true);
+}
+
+void
+MediaDataDecoderProxy::Drain()
+{
+ MOZ_ASSERT(!IsOnProxyThread());
+ MOZ_ASSERT(!mIsShutdown);
+
+ mProxyThread->Dispatch(NewRunnableMethod(mProxyDecoder, &MediaDataDecoder::Drain));
+}
+
+void
+MediaDataDecoderProxy::Shutdown()
+{
+ // Note that this *may* be called from the proxy thread also.
+ MOZ_ASSERT(!mIsShutdown);
+#if defined(DEBUG)
+ mIsShutdown = true;
+#endif
+ mProxyThread->AsXPCOMThread()->Dispatch(NewRunnableMethod(mProxyDecoder,
+ &MediaDataDecoder::Shutdown),
+ NS_DISPATCH_SYNC);
+}
+
+void
+MediaDataDecoderProxy::FlushComplete()
+{
+ mFlushComplete.Set(true);
+}
+
+} // namespace mozilla
diff --git a/dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.h b/dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.h
new file mode 100644
index 000000000..735b6126e
--- /dev/null
+++ b/dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.h
@@ -0,0 +1,181 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#if !defined(MediaDataDecoderProxy_h_)
+#define MediaDataDecoderProxy_h_
+
+#include "PlatformDecoderModule.h"
+#include "mozilla/RefPtr.h"
+#include "nsThreadUtils.h"
+#include "nscore.h"
+#include "GMPService.h"
+
+namespace mozilla {
+
+class InputTask : public Runnable {
+public:
+ InputTask(MediaDataDecoder* aDecoder,
+ MediaRawData* aSample)
+ : mDecoder(aDecoder)
+ , mSample(aSample)
+ {}
+
+ NS_IMETHOD Run() override {
+ mDecoder->Input(mSample);
+ return NS_OK;
+ }
+
+private:
+ RefPtr<MediaDataDecoder> mDecoder;
+ RefPtr<MediaRawData> mSample;
+};
+
+template<typename T>
+class Condition {
+public:
+ explicit Condition(T aValue)
+ : mMonitor("Condition")
+ , mCondition(aValue)
+ {}
+
+ void Set(T aValue) {
+ MonitorAutoLock mon(mMonitor);
+ mCondition = aValue;
+ mon.NotifyAll();
+ }
+
+ void WaitUntil(T aValue) {
+ MonitorAutoLock mon(mMonitor);
+ while (mCondition != aValue) {
+ mon.Wait();
+ }
+ }
+
+private:
+ Monitor mMonitor;
+ T mCondition;
+};
+
+class MediaDataDecoderProxy;
+
+class MediaDataDecoderCallbackProxy : public MediaDataDecoderCallback {
+public:
+ MediaDataDecoderCallbackProxy(MediaDataDecoderProxy* aProxyDecoder,
+ MediaDataDecoderCallback* aCallback)
+ : mProxyDecoder(aProxyDecoder)
+ , mProxyCallback(aCallback)
+ {
+ }
+
+ void Output(MediaData* aData) override {
+ mProxyCallback->Output(aData);
+ }
+
+ void Error(const MediaResult& aError) override;
+
+ void InputExhausted() override {
+ mProxyCallback->InputExhausted();
+ }
+
+ void DrainComplete() override {
+ mProxyCallback->DrainComplete();
+ }
+
+ void ReleaseMediaResources() override {
+ mProxyCallback->ReleaseMediaResources();
+ }
+
+ void FlushComplete();
+
+ bool OnReaderTaskQueue() override
+ {
+ return mProxyCallback->OnReaderTaskQueue();
+ }
+
+ void WaitingForKey() override
+ {
+ mProxyCallback->WaitingForKey();
+ }
+
+private:
+ MediaDataDecoderProxy* mProxyDecoder;
+ MediaDataDecoderCallback* mProxyCallback;
+};
+
+class MediaDataDecoderProxy : public MediaDataDecoder {
+public:
+ MediaDataDecoderProxy(already_AddRefed<AbstractThread> aProxyThread,
+ MediaDataDecoderCallback* aCallback)
+ : mProxyThread(aProxyThread)
+ , mProxyCallback(this, aCallback)
+ , mFlushComplete(false)
+#if defined(DEBUG)
+ , mIsShutdown(false)
+#endif
+ {
+ }
+
+ // Ideally, this would return a regular MediaDataDecoderCallback pointer
+ // to retain the clean abstraction, but until MediaDataDecoderCallback
+ // supports the FlushComplete interface, this will have to do. When MDDC
+ // supports FlushComplete, this, the GMP*Decoders, and the
+ // *CallbackAdapters can be reverted to accepting a regular
+ // MediaDataDecoderCallback pointer.
+ MediaDataDecoderCallbackProxy* Callback()
+ {
+ return &mProxyCallback;
+ }
+
+ void SetProxyTarget(MediaDataDecoder* aProxyDecoder)
+ {
+ MOZ_ASSERT(aProxyDecoder);
+ mProxyDecoder = aProxyDecoder;
+ }
+
+ // These are called from the decoder thread pool.
+ // Init and Shutdown run synchronously on the proxy thread, all others are
+ // asynchronously and responded to via the MediaDataDecoderCallback.
+ // Note: the nsresults returned by the proxied decoder are lost.
+ RefPtr<InitPromise> Init() override;
+ void Input(MediaRawData* aSample) override;
+ void Flush() override;
+ void Drain() override;
+ void Shutdown() override;
+
+ const char* GetDescriptionName() const override
+ {
+ return "GMP proxy data decoder";
+ }
+
+ // Called by MediaDataDecoderCallbackProxy.
+ void FlushComplete();
+
+private:
+ RefPtr<InitPromise> InternalInit();
+
+#ifdef DEBUG
+ bool IsOnProxyThread() {
+ return mProxyThread && mProxyThread->IsCurrentThreadIn();
+ }
+#endif
+
+ friend class InputTask;
+ friend class InitTask;
+
+ RefPtr<MediaDataDecoder> mProxyDecoder;
+ RefPtr<AbstractThread> mProxyThread;
+
+ MediaDataDecoderCallbackProxy mProxyCallback;
+
+ Condition<bool> mFlushComplete;
+#if defined(DEBUG)
+ bool mIsShutdown;
+#endif
+};
+
+} // namespace mozilla
+
+#endif // MediaDataDecoderProxy_h_
diff --git a/dom/media/platforms/agnostic/gmp/moz.build b/dom/media/platforms/agnostic/gmp/moz.build
new file mode 100644
index 000000000..eb2738e26
--- /dev/null
+++ b/dom/media/platforms/agnostic/gmp/moz.build
@@ -0,0 +1,24 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+EXPORTS += [
+ 'GMPAudioDecoder.h',
+ 'GMPDecoderModule.h',
+ 'GMPVideoDecoder.h',
+ 'MediaDataDecoderProxy.h',
+]
+
+UNIFIED_SOURCES += [
+ 'GMPAudioDecoder.cpp',
+ 'GMPDecoderModule.cpp',
+ 'GMPVideoDecoder.cpp',
+ 'MediaDataDecoderProxy.cpp',
+]
+
+# GMPVideoEncodedFrameImpl.h needs IPC
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'