Bug 1121258 - Add a GMP PDM to allow MP4 playback via OpenH264. r=cpearce a=lmandel
authorMatthew Gregan <kinetik@flim.org>
Mon, 02 Feb 2015 21:49:00 +1300
changeset 250198 f2e35a9f30a7
parent 250197 949ce3e9c42e
child 250199 87bba928e233
push id4521
push usercpearce@mozilla.com
push date2015-03-04 01:22 +0000
treeherdermozilla-beta@8abdbdecd2d6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscpearce, lmandel
bugs1121258
milestone37.0
Bug 1121258 - Add a GMP PDM to allow MP4 playback via OpenH264. r=cpearce a=lmandel
dom/media/MediaInfo.h
dom/media/fmp4/MP4Decoder.cpp
dom/media/fmp4/PlatformDecoderModule.cpp
dom/media/fmp4/PlatformDecoderModule.h
dom/media/fmp4/gmp/GMPAudioDecoder.cpp
dom/media/fmp4/gmp/GMPAudioDecoder.h
dom/media/fmp4/gmp/GMPDecoderModule.cpp
dom/media/fmp4/gmp/GMPDecoderModule.h
dom/media/fmp4/gmp/GMPVideoDecoder.cpp
dom/media/fmp4/gmp/GMPVideoDecoder.h
dom/media/fmp4/gmp/MediaDataDecoderProxy.cpp
dom/media/fmp4/gmp/MediaDataDecoderProxy.h
dom/media/fmp4/gmp/moz.build
dom/media/fmp4/moz.build
modules/libpref/init/all.js
--- a/dom/media/MediaInfo.h
+++ b/dom/media/MediaInfo.h
@@ -31,29 +31,40 @@ struct TrackInfo {
   nsString mKind;
   nsString mLabel;
   nsString mLanguage;
   bool mEnabled;
 };
 
 // Stores info relevant to presenting media frames.
 class VideoInfo {
+private:
+  VideoInfo(int32_t aWidth, int32_t aHeight, bool aHasVideo)
+    : mDisplay(aWidth, aHeight)
+    , mStereoMode(StereoMode::MONO)
+    , mHasVideo(aHasVideo)
+    , mIsHardwareAccelerated(false)
+  {
+  }
+
 public:
   VideoInfo()
-    : mDisplay(0,0)
-    , mStereoMode(StereoMode::MONO)
-    , mHasVideo(false)
-    , mIsHardwareAccelerated(false)
+    : VideoInfo(0, 0, false)
   {
     // TODO: TrackInfo should be initialized by its specific codec decoder.
     // This following call should be removed once we have that implemented.
     mTrackInfo.Init(NS_LITERAL_STRING("2"), NS_LITERAL_STRING("main"),
     EmptyString(), EmptyString(), true);
   }
 
+  VideoInfo(int32_t aWidth, int32_t aHeight)
+    : VideoInfo(aWidth, aHeight, true)
+  {
+  }
+
   // Size in pixels at which the video is rendered. This is after it has
   // been scaled by its aspect ratio.
   nsIntSize mDisplay;
 
   // Indicates the frame layout for single track stereo videos.
   StereoMode mStereoMode;
 
   // True if we have an active video bitstream.
--- a/dom/media/fmp4/MP4Decoder.cpp
+++ b/dom/media/fmp4/MP4Decoder.cpp
@@ -208,27 +208,34 @@ IsAndroidAvailable()
 
 static bool
 IsGonkMP4DecoderAvailable()
 {
   return Preferences::GetBool("media.fragmented-mp4.gonk.enabled", false);
 }
 
 static bool
+IsGMPDecoderAvailable()
+{
+  return Preferences::GetBool("media.fragmented-mp4.gmp.enabled", false);
+}
+
+static bool
 HavePlatformMPEGDecoders()
 {
   return Preferences::GetBool("media.fragmented-mp4.use-blank-decoder") ||
 #ifdef XP_WIN
          // We have H.264/AAC platform decoders on Windows Vista and up.
          IsVistaOrLater() ||
 #endif
          IsAndroidAvailable() ||
          IsFFmpegAvailable() ||
          IsAppleAvailable() ||
          IsGonkMP4DecoderAvailable() ||
+         IsGMPDecoderAvailable() ||
          // TODO: Other platforms...
          false;
 }
 
 /* static */
 bool
 MP4Decoder::IsEnabled()
 {
--- a/dom/media/fmp4/PlatformDecoderModule.cpp
+++ b/dom/media/fmp4/PlatformDecoderModule.cpp
@@ -17,16 +17,17 @@
 #include "AppleDecoderModule.h"
 #endif
 #ifdef MOZ_GONK_MEDIACODEC
 #include "GonkDecoderModule.h"
 #endif
 #ifdef MOZ_WIDGET_ANDROID
 #include "AndroidDecoderModule.h"
 #endif
+#include "GMPDecoderModule.h"
 
 #include "mozilla/Preferences.h"
 #ifdef MOZ_EME
 #include "EMEDecoderModule.h"
 #include "mozilla/CDMProxy.h"
 #endif
 #include "SharedThreadPool.h"
 #include "MediaTaskQueue.h"
@@ -35,16 +36,17 @@ namespace mozilla {
 
 extern already_AddRefed<PlatformDecoderModule> CreateBlankDecoderModule();
 
 bool PlatformDecoderModule::sUseBlankDecoder = false;
 bool PlatformDecoderModule::sFFmpegDecoderEnabled = false;
 bool PlatformDecoderModule::sGonkDecoderEnabled = false;
 bool PlatformDecoderModule::sAndroidMCDecoderEnabled = false;
 bool PlatformDecoderModule::sAndroidMCDecoderPreferred = false;
+bool PlatformDecoderModule::sGMPDecoderEnabled = false;
 
 /* static */
 void
 PlatformDecoderModule::Init()
 {
   MOZ_ASSERT(NS_IsMainThread());
   static bool alreadyInitialized = false;
   if (alreadyInitialized) {
@@ -63,16 +65,19 @@ PlatformDecoderModule::Init()
 #endif
 #ifdef MOZ_WIDGET_ANDROID
   Preferences::AddBoolVarCache(&sAndroidMCDecoderEnabled,
                                "media.fragmented-mp4.android-media-codec.enabled", false);
   Preferences::AddBoolVarCache(&sAndroidMCDecoderPreferred,
                                "media.fragmented-mp4.android-media-codec.preferred", false);
 #endif
 
+  Preferences::AddBoolVarCache(&sGMPDecoderEnabled,
+                               "media.fragmented-mp4.gmp.enabled", false);
+
 #ifdef XP_WIN
   WMFDecoderModule::Init();
 #endif
 #ifdef MOZ_APPLEMEDIA
   AppleDecoderModule::Init();
 #endif
 }
 
@@ -161,16 +166,20 @@ PlatformDecoderModule::CreatePDM()
   }
 #endif
 #ifdef MOZ_WIDGET_ANDROID
   if(sAndroidMCDecoderEnabled){
     nsRefPtr<PlatformDecoderModule> m(new AndroidDecoderModule());
     return m.forget();
   }
 #endif
+  if (sGMPDecoderEnabled) {
+    nsRefPtr<PlatformDecoderModule> m(new AVCCDecoderModule(new GMPDecoderModule()));
+    return m.forget();
+  }
   return nullptr;
 }
 
 bool
 PlatformDecoderModule::SupportsAudioMimeType(const char* aMimeType)
 {
   return !strcmp(aMimeType, "audio/mp4a-latm");
 }
--- a/dom/media/fmp4/PlatformDecoderModule.h
+++ b/dom/media/fmp4/PlatformDecoderModule.h
@@ -139,16 +139,17 @@ protected:
   PlatformDecoderModule() {}
   virtual ~PlatformDecoderModule() {}
   // Caches pref media.fragmented-mp4.use-blank-decoder
   static bool sUseBlankDecoder;
   static bool sFFmpegDecoderEnabled;
   static bool sGonkDecoderEnabled;
   static bool sAndroidMCDecoderPreferred;
   static bool sAndroidMCDecoderEnabled;
+  static bool sGMPDecoderEnabled;
 };
 
 // A callback used by MediaDataDecoder to return output/errors to the
 // MP4Reader. Implementation is threadsafe, and can be called on any thread.
 class MediaDataDecoderCallback {
 public:
   virtual ~MediaDataDecoderCallback() {}
 
new file mode 100644
--- /dev/null
+++ b/dom/media/fmp4/gmp/GMPAudioDecoder.cpp
@@ -0,0 +1,224 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GMPAudioDecoder.h"
+
+namespace mozilla {
+
+#if defined(DEBUG)
+static bool IsOnGMPThread()
+{
+  nsCOMPtr<mozIGeckoMediaPluginService> mps = do_GetService("@mozilla.org/gecko-media-plugin-service;1");
+  MOZ_ASSERT(mps);
+
+  nsCOMPtr<nsIThread> gmpThread;
+  nsresult rv = mps->GetThread(getter_AddRefs(gmpThread));
+  MOZ_ASSERT(NS_SUCCEEDED(rv) && gmpThread);
+  return NS_GetCurrentThread() == gmpThread;
+}
+#endif
+
+void
+AudioCallbackAdapter::Decoded(const nsTArray<int16_t>& aPCM, uint64_t aTimeStamp, uint32_t aChannels, uint32_t aRate)
+{
+  MOZ_ASSERT(IsOnGMPThread());
+
+  if (aRate == 0 || aChannels == 0) {
+    NS_WARNING("Invalid rate or num channels returned on GMP audio samples");
+    mCallback->Error();
+    return;
+  }
+
+  size_t numFrames = aPCM.Length() / aChannels;
+  MOZ_ASSERT((aPCM.Length() % aChannels) == 0);
+  nsAutoArrayPtr<AudioDataValue> audioData(new AudioDataValue[aPCM.Length()]);
+
+  for (size_t i = 0; i < aPCM.Length(); ++i) {
+    audioData[i] = AudioSampleToFloat(aPCM[i]);
+  }
+
+  if (mMustRecaptureAudioPosition) {
+    mAudioFrameSum = 0;
+    auto timestamp = UsecsToFrames(aTimeStamp, aRate);
+    if (!timestamp.isValid()) {
+      NS_WARNING("Invalid timestamp");
+      mCallback->Error();
+      return;
+    }
+    mAudioFrameOffset = timestamp.value();
+    MOZ_ASSERT(mAudioFrameOffset >= 0);
+    mMustRecaptureAudioPosition = false;
+  }
+
+  auto timestamp = FramesToUsecs(mAudioFrameOffset + mAudioFrameSum, aRate);
+  if (!timestamp.isValid()) {
+    NS_WARNING("Invalid timestamp on audio samples");
+    mCallback->Error();
+    return;
+  }
+  mAudioFrameSum += numFrames;
+
+  auto duration = FramesToUsecs(numFrames, aRate);
+  if (!duration.isValid()) {
+    NS_WARNING("Invalid duration on audio samples");
+    mCallback->Error();
+    return;
+  }
+
+  nsRefPtr<AudioData> audio(new AudioData(mLastStreamOffset,
+                                          timestamp.value(),
+                                          duration.value(),
+                                          numFrames,
+                                          audioData.forget(),
+                                          aChannels,
+                                          aRate));
+
+#ifdef LOG_SAMPLE_DECODE
+  LOG("Decoded audio sample! timestamp=%lld duration=%lld currentLength=%u",
+      timestamp, duration, currentLength);
+#endif
+
+  mCallback->Output(audio);
+}
+
+void
+AudioCallbackAdapter::InputDataExhausted()
+{
+  MOZ_ASSERT(IsOnGMPThread());
+  mCallback->InputExhausted();
+}
+
+void
+AudioCallbackAdapter::DrainComplete()
+{
+  MOZ_ASSERT(IsOnGMPThread());
+  mCallback->DrainComplete();
+}
+
+void
+AudioCallbackAdapter::ResetComplete()
+{
+  MOZ_ASSERT(IsOnGMPThread());
+  mMustRecaptureAudioPosition = true;
+  mCallback->FlushComplete();
+}
+
+void
+AudioCallbackAdapter::Error(GMPErr aErr)
+{
+  MOZ_ASSERT(IsOnGMPThread());
+  mCallback->Error();
+}
+
+void
+AudioCallbackAdapter::Terminated()
+{
+  NS_WARNING("AAC GMP decoder terminated.");
+  mCallback->Error();
+}
+
+void
+GMPAudioDecoder::InitTags(nsTArray<nsCString>& aTags)
+{
+  aTags.AppendElement(NS_LITERAL_CSTRING("aac"));
+}
+
+nsCString
+GMPAudioDecoder::GetNodeId()
+{
+  return NS_LITERAL_CSTRING("");
+}
+
+nsresult
+GMPAudioDecoder::Init()
+{
+  MOZ_ASSERT(IsOnGMPThread());
+
+  mMPS = do_GetService("@mozilla.org/gecko-media-plugin-service;1");
+  MOZ_ASSERT(mMPS);
+
+  nsTArray<nsCString> tags;
+  InitTags(tags);
+  nsresult rv = mMPS->GetGMPAudioDecoder(&tags, GetNodeId(), &mGMP);
+  NS_ENSURE_SUCCESS(rv, rv);
+  MOZ_ASSERT(mGMP);
+
+  nsTArray<uint8_t> codecSpecific;
+  codecSpecific.AppendElements(mConfig.audio_specific_config->Elements(),
+                               mConfig.audio_specific_config->Length());
+
+  rv = mGMP->InitDecode(kGMPAudioCodecAAC,
+                        mConfig.channel_count,
+                        mConfig.bits_per_sample,
+                        mConfig.samples_per_second,
+                        codecSpecific,
+                        mAdapter);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+nsresult
+GMPAudioDecoder::Input(mp4_demuxer::MP4Sample* aSample)
+{
+  MOZ_ASSERT(IsOnGMPThread());
+
+  nsAutoPtr<mp4_demuxer::MP4Sample> sample(aSample);
+  if (!mGMP) {
+    mCallback->Error();
+    return NS_ERROR_FAILURE;
+  }
+
+  mAdapter->SetLastStreamOffset(sample->byte_offset);
+
+  gmp::GMPAudioSamplesImpl samples(sample, mConfig.channel_count, mConfig.samples_per_second);
+  nsresult rv = mGMP->Decode(samples);
+  if (NS_FAILED(rv)) {
+    mCallback->Error();
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+nsresult
+GMPAudioDecoder::Flush()
+{
+  MOZ_ASSERT(IsOnGMPThread());
+
+  if (!mGMP || NS_FAILED(mGMP->Reset())) {
+    // Abort the flush.
+    mCallback->FlushComplete();
+  }
+
+  return NS_OK;
+}
+
+nsresult
+GMPAudioDecoder::Drain()
+{
+  MOZ_ASSERT(IsOnGMPThread());
+
+  if (!mGMP || NS_FAILED(mGMP->Drain())) {
+    mCallback->DrainComplete();
+  }
+
+  return NS_OK;
+}
+
+nsresult
+GMPAudioDecoder::Shutdown()
+{
+  if (!mGMP) {
+    return NS_ERROR_FAILURE;
+  }
+  mGMP->Close();
+  mGMP = nullptr;
+
+  return NS_OK;
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/media/fmp4/gmp/GMPAudioDecoder.h
@@ -0,0 +1,89 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#if !defined(GMPAudioDecoder_h_)
+#define GMPAudioDecoder_h_
+
+#include "GMPAudioDecoderProxy.h"
+#include "MediaDataDecoderProxy.h"
+#include "PlatformDecoderModule.h"
+#include "mozIGeckoMediaPluginService.h"
+
+namespace mozilla {
+
+class AudioCallbackAdapter : public GMPAudioDecoderCallbackProxy {
+public:
+  explicit AudioCallbackAdapter(MediaDataDecoderCallbackProxy* aCallback)
+   : mCallback(aCallback)
+   , mLastStreamOffset(0)
+   , mAudioFrameSum(0)
+   , mAudioFrameOffset(0)
+   , mMustRecaptureAudioPosition(true)
+  {}
+
+  // GMPAudioDecoderCallbackProxy
+  virtual void Decoded(const nsTArray<int16_t>& aPCM, uint64_t aTimeStamp, uint32_t aChannels, uint32_t aRate) MOZ_OVERRIDE;
+  virtual void InputDataExhausted() MOZ_OVERRIDE;
+  virtual void DrainComplete() MOZ_OVERRIDE;
+  virtual void ResetComplete() MOZ_OVERRIDE;
+  virtual void Error(GMPErr aErr) MOZ_OVERRIDE;
+  virtual void Terminated() MOZ_OVERRIDE;
+
+  void SetLastStreamOffset(int64_t aStreamOffset) {
+    mLastStreamOffset = aStreamOffset;
+  }
+
+private:
+  MediaDataDecoderCallbackProxy* mCallback;
+  int64_t mLastStreamOffset;
+
+  int64_t mAudioFrameSum;
+  int64_t mAudioFrameOffset;
+  bool mMustRecaptureAudioPosition;
+};
+
+class GMPAudioDecoder : public MediaDataDecoder {
+protected:
+  GMPAudioDecoder(const mp4_demuxer::AudioDecoderConfig& aConfig,
+                  MediaTaskQueue* aTaskQueue,
+                  MediaDataDecoderCallbackProxy* aCallback,
+                  AudioCallbackAdapter* aAdapter)
+   : mConfig(aConfig)
+   , mCallback(aCallback)
+   , mGMP(nullptr)
+   , mAdapter(aAdapter)
+  {
+  }
+
+public:
+  GMPAudioDecoder(const mp4_demuxer::AudioDecoderConfig& aConfig,
+                  MediaTaskQueue* aTaskQueue,
+                  MediaDataDecoderCallbackProxy* aCallback)
+   : GMPAudioDecoder(aConfig, aTaskQueue, aCallback, new AudioCallbackAdapter(aCallback))
+  {
+  }
+
+  virtual nsresult Init() MOZ_OVERRIDE;
+  virtual nsresult Input(mp4_demuxer::MP4Sample* aSample) MOZ_OVERRIDE;
+  virtual nsresult Flush() MOZ_OVERRIDE;
+  virtual nsresult Drain() MOZ_OVERRIDE;
+  virtual nsresult Shutdown() MOZ_OVERRIDE;
+
+protected:
+  virtual void InitTags(nsTArray<nsCString>& aTags);
+  virtual nsCString GetNodeId();
+
+private:
+  const mp4_demuxer::AudioDecoderConfig& mConfig;
+  MediaDataDecoderCallbackProxy* mCallback;
+  nsCOMPtr<mozIGeckoMediaPluginService> mMPS;
+  GMPAudioDecoderProxy* mGMP;
+  nsAutoPtr<AudioCallbackAdapter> mAdapter;
+};
+
+} // namespace mozilla
+
+#endif // GMPAudioDecoder_h_
new file mode 100644
--- /dev/null
+++ b/dom/media/fmp4/gmp/GMPDecoderModule.cpp
@@ -0,0 +1,91 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GMPDecoderModule.h"
+#include "GMPAudioDecoder.h"
+#include "GMPVideoDecoder.h"
+#include "MediaDataDecoderProxy.h"
+#include "mozIGeckoMediaPluginService.h"
+#include "nsServiceManagerUtils.h"
+
+namespace mozilla {
+
+GMPDecoderModule::GMPDecoderModule()
+{
+}
+
+GMPDecoderModule::~GMPDecoderModule()
+{
+}
+
+nsresult
+GMPDecoderModule::Shutdown()
+{
+  return NS_OK;
+}
+
+static already_AddRefed<MediaDataDecoderProxy>
+CreateDecoderWrapper(MediaDataDecoderCallback* aCallback)
+{
+  nsCOMPtr<mozIGeckoMediaPluginService> gmpService = do_GetService("@mozilla.org/gecko-media-plugin-service;1");
+  if (!gmpService) {
+    return nullptr;
+  }
+
+  nsCOMPtr<nsIThread> thread;
+  nsresult rv = gmpService->GetThread(getter_AddRefs(thread));
+  if (NS_FAILED(rv)) {
+    return nullptr;
+  }
+
+  nsRefPtr<MediaDataDecoderProxy> decoder(new MediaDataDecoderProxy(thread, aCallback));
+  return decoder.forget();
+}
+
+already_AddRefed<MediaDataDecoder>
+GMPDecoderModule::CreateVideoDecoder(const mp4_demuxer::VideoDecoderConfig& aConfig,
+                                     layers::LayersBackend aLayersBackend,
+                                     layers::ImageContainer* aImageContainer,
+                                     FlushableMediaTaskQueue* aVideoTaskQueue,
+                                     MediaDataDecoderCallback* aCallback)
+{
+  if (strcmp(aConfig.mime_type, "video/avc") != 0) {
+    return nullptr;
+  }
+
+  nsRefPtr<MediaDataDecoderProxy> wrapper = CreateDecoderWrapper(aCallback);
+  wrapper->SetProxyTarget(new GMPVideoDecoder(aConfig,
+                                              aLayersBackend,
+                                              aImageContainer,
+                                              aVideoTaskQueue,
+                                              wrapper->Callback()));
+  return wrapper.forget();
+}
+
+already_AddRefed<MediaDataDecoder>
+GMPDecoderModule::CreateAudioDecoder(const mp4_demuxer::AudioDecoderConfig& aConfig,
+                                     FlushableMediaTaskQueue* aAudioTaskQueue,
+                                     MediaDataDecoderCallback* aCallback)
+{
+  if (strcmp(aConfig.mime_type, "audio/mp4a-latm") != 0) {
+    return nullptr;
+  }
+
+  nsRefPtr<MediaDataDecoderProxy> wrapper = CreateDecoderWrapper(aCallback);
+  wrapper->SetProxyTarget(new GMPAudioDecoder(aConfig,
+                                              aAudioTaskQueue,
+                                              wrapper->Callback()));
+  return wrapper.forget();
+}
+
+bool
+GMPDecoderModule::DecoderNeedsAVCC(const mp4_demuxer::VideoDecoderConfig& aConfig)
+{
+  // GMPVideoCodecType::kGMPVideoCodecH264 specifies that encoded frames must be in AVCC format.
+  return true;
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/media/fmp4/gmp/GMPDecoderModule.h
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#if !defined(GMPDecoderModule_h_)
+#define GMPDecoderModule_h_
+
+#include "PlatformDecoderModule.h"
+
+namespace mozilla {
+
+class GMPDecoderModule : public PlatformDecoderModule {
+public:
+  GMPDecoderModule();
+
+  virtual ~GMPDecoderModule();
+
+  // Called when the decoders have shutdown. Main thread only.
+  virtual nsresult Shutdown() MOZ_OVERRIDE;
+
+  // Decode thread.
+  virtual already_AddRefed<MediaDataDecoder>
+  CreateVideoDecoder(const mp4_demuxer::VideoDecoderConfig& aConfig,
+                     layers::LayersBackend aLayersBackend,
+                     layers::ImageContainer* aImageContainer,
+                     FlushableMediaTaskQueue* aVideoTaskQueue,
+                     MediaDataDecoderCallback* aCallback) MOZ_OVERRIDE;
+
+  // Decode thread.
+  virtual already_AddRefed<MediaDataDecoder>
+  CreateAudioDecoder(const mp4_demuxer::AudioDecoderConfig& aConfig,
+                     FlushableMediaTaskQueue* aAudioTaskQueue,
+                     MediaDataDecoderCallback* aCallback) MOZ_OVERRIDE;
+
+  virtual bool DecoderNeedsAVCC(const mp4_demuxer::VideoDecoderConfig& aConfig) MOZ_OVERRIDE;
+};
+
+} // namespace mozilla
+
+#endif // GMPDecoderModule_h_
new file mode 100644
--- /dev/null
+++ b/dom/media/fmp4/gmp/GMPVideoDecoder.cpp
@@ -0,0 +1,245 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GMPVideoDecoder.h"
+#include "GMPVideoHost.h"
+#include "prsystem.h"
+
+namespace mozilla {
+
+#if defined(DEBUG)
+static bool IsOnGMPThread();
+#endif
+
+void
+VideoCallbackAdapter::Decoded(GMPVideoi420Frame* aDecodedFrame)
+{
+  GMPUniquePtr<GMPVideoi420Frame> decodedFrame(aDecodedFrame);
+
+  MOZ_ASSERT(IsOnGMPThread());
+
+  VideoData::YCbCrBuffer b;
+  for (int i = 0; i < kGMPNumOfPlanes; ++i) {
+    b.mPlanes[i].mData = decodedFrame->Buffer(GMPPlaneType(i));
+    b.mPlanes[i].mStride = decodedFrame->Stride(GMPPlaneType(i));
+    if (i == kGMPYPlane) {
+      b.mPlanes[i].mWidth = decodedFrame->Width();
+      b.mPlanes[i].mHeight = decodedFrame->Height();
+    } else {
+      b.mPlanes[i].mWidth = (decodedFrame->Width() + 1) / 2;
+      b.mPlanes[i].mHeight = (decodedFrame->Height() + 1) / 2;
+    }
+    b.mPlanes[i].mOffset = 0;
+    b.mPlanes[i].mSkip = 0;
+  }
+
+  gfx::IntRect pictureRegion(0, 0, decodedFrame->Width(), decodedFrame->Height());
+  nsRefPtr<VideoData> v = VideoData::Create(mVideoInfo,
+                                            mImageContainer,
+                                            mLastStreamOffset,
+                                            decodedFrame->Timestamp(),
+                                            decodedFrame->Duration(),
+                                            b,
+                                            false,
+                                            -1,
+                                            pictureRegion);
+  if (v) {
+    mCallback->Output(v);
+  } else {
+    mCallback->Error();
+  }
+}
+
+void
+VideoCallbackAdapter::ReceivedDecodedReferenceFrame(const uint64_t aPictureId)
+{
+  MOZ_ASSERT(IsOnGMPThread());
+}
+
+void
+VideoCallbackAdapter::ReceivedDecodedFrame(const uint64_t aPictureId)
+{
+  MOZ_ASSERT(IsOnGMPThread());
+}
+
+void
+VideoCallbackAdapter::InputDataExhausted()
+{
+  MOZ_ASSERT(IsOnGMPThread());
+  mCallback->InputExhausted();
+}
+
+void
+VideoCallbackAdapter::DrainComplete()
+{
+  MOZ_ASSERT(IsOnGMPThread());
+  mCallback->DrainComplete();
+}
+
+void
+VideoCallbackAdapter::ResetComplete()
+{
+  MOZ_ASSERT(IsOnGMPThread());
+  mCallback->FlushComplete();
+}
+
+void
+VideoCallbackAdapter::Error(GMPErr aErr)
+{
+  MOZ_ASSERT(IsOnGMPThread());
+  mCallback->Error();
+}
+
+void
+VideoCallbackAdapter::Terminated()
+{
+  // Note that this *may* be called from the proxy thread also.
+  NS_WARNING("H.264 GMP decoder terminated.");
+  mCallback->Error();
+}
+
+void
+GMPVideoDecoder::InitTags(nsTArray<nsCString>& aTags)
+{
+  aTags.AppendElement(NS_LITERAL_CSTRING("h264"));
+}
+
+nsCString
+GMPVideoDecoder::GetNodeId()
+{
+  return NS_LITERAL_CSTRING("");
+}
+
+GMPUniquePtr<GMPVideoEncodedFrame>
+GMPVideoDecoder::CreateFrame(mp4_demuxer::MP4Sample* aSample)
+{
+  GMPVideoFrame* ftmp = nullptr;
+  GMPErr err = mHost->CreateFrame(kGMPEncodedVideoFrame, &ftmp);
+  if (GMP_FAILED(err)) {
+    mCallback->Error();
+    return nullptr;
+  }
+
+  GMPUniquePtr<GMPVideoEncodedFrame> frame(static_cast<GMPVideoEncodedFrame*>(ftmp));
+  err = frame->CreateEmptyFrame(aSample->size);
+  if (GMP_FAILED(err)) {
+    mCallback->Error();
+    return nullptr;
+  }
+
+  memcpy(frame->Buffer(), aSample->data, frame->Size());
+
+  frame->SetEncodedWidth(mConfig.display_width);
+  frame->SetEncodedHeight(mConfig.display_height);
+  frame->SetTimeStamp(aSample->composition_timestamp);
+  frame->SetCompleteFrame(true);
+  frame->SetDuration(aSample->duration);
+  frame->SetFrameType(aSample->is_sync_point ? kGMPKeyFrame : kGMPDeltaFrame);
+  frame->SetBufferType(GMP_BufferLength32);
+
+  return frame;
+}
+
+nsresult
+GMPVideoDecoder::Init()
+{
+  MOZ_ASSERT(IsOnGMPThread());
+
+  mMPS = do_GetService("@mozilla.org/gecko-media-plugin-service;1");
+  MOZ_ASSERT(mMPS);
+
+  nsTArray<nsCString> tags;
+  InitTags(tags);
+  nsresult rv = mMPS->GetGMPVideoDecoder(&tags, GetNodeId(), &mHost, &mGMP);
+  NS_ENSURE_SUCCESS(rv, rv);
+  MOZ_ASSERT(mHost && mGMP);
+
+  GMPVideoCodec codec;
+  memset(&codec, 0, sizeof(codec));
+
+  codec.mGMPApiVersion = kGMPVersion33;
+
+  codec.mCodecType = kGMPVideoCodecH264;
+  codec.mWidth = mConfig.display_width;
+  codec.mHeight = mConfig.display_height;
+
+  nsTArray<uint8_t> codecSpecific;
+  codecSpecific.AppendElement(0); // mPacketizationMode.
+  codecSpecific.AppendElements(mConfig.extra_data->Elements(),
+                               mConfig.extra_data->Length());
+
+  rv = mGMP->InitDecode(codec,
+                        codecSpecific,
+                        mAdapter,
+                        PR_GetNumberOfProcessors());
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+nsresult
+GMPVideoDecoder::Input(mp4_demuxer::MP4Sample* aSample)
+{
+  MOZ_ASSERT(IsOnGMPThread());
+
+  nsAutoPtr<mp4_demuxer::MP4Sample> sample(aSample);
+  if (!mGMP) {
+    mCallback->Error();
+    return NS_ERROR_FAILURE;
+  }
+
+  mAdapter->SetLastStreamOffset(sample->byte_offset);
+
+  GMPUniquePtr<GMPVideoEncodedFrame> frame = CreateFrame(sample);
+  nsTArray<uint8_t> info; // No codec specific per-frame info to pass.
+  nsresult rv = mGMP->Decode(Move(frame), false, info, 0);
+  if (NS_FAILED(rv)) {
+    mCallback->Error();
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+nsresult
+GMPVideoDecoder::Flush()
+{
+  MOZ_ASSERT(IsOnGMPThread());
+
+  if (!mGMP || NS_FAILED(mGMP->Reset())) {
+    // Abort the flush.
+    mCallback->FlushComplete();
+  }
+
+  return NS_OK;
+}
+
+nsresult
+GMPVideoDecoder::Drain()
+{
+  MOZ_ASSERT(IsOnGMPThread());
+
+  if (!mGMP || NS_FAILED(mGMP->Drain())) {
+    mCallback->DrainComplete();
+  }
+
+  return NS_OK;
+}
+
+nsresult
+GMPVideoDecoder::Shutdown()
+{
+  // Note that this *may* be called from the proxy thread also.
+  if (!mGMP) {
+    return NS_ERROR_FAILURE;
+  }
+  mGMP->Close();
+  mGMP = nullptr;
+
+  return NS_OK;
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/media/fmp4/gmp/GMPVideoDecoder.h
@@ -0,0 +1,105 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#if !defined(GMPVideoDecoder_h_)
+#define GMPVideoDecoder_h_
+
+#include "GMPVideoDecoderProxy.h"
+#include "ImageContainer.h"
+#include "MediaDataDecoderProxy.h"
+#include "PlatformDecoderModule.h"
+#include "mozIGeckoMediaPluginService.h"
+#include "mp4_demuxer/DecoderData.h"
+
+namespace mozilla {
+
+class VideoCallbackAdapter : public GMPVideoDecoderCallbackProxy {
+public:
+  VideoCallbackAdapter(MediaDataDecoderCallbackProxy* aCallback,
+                       VideoInfo aVideoInfo,
+                       layers::ImageContainer* aImageContainer)
+   : mCallback(aCallback)
+   , mLastStreamOffset(0)
+   , mVideoInfo(aVideoInfo)
+   , mImageContainer(aImageContainer)
+  {}
+
+  // GMPVideoDecoderCallbackProxy
+  virtual void Decoded(GMPVideoi420Frame* aDecodedFrame) MOZ_OVERRIDE;
+  virtual void ReceivedDecodedReferenceFrame(const uint64_t aPictureId) MOZ_OVERRIDE;
+  virtual void ReceivedDecodedFrame(const uint64_t aPictureId) MOZ_OVERRIDE;
+  virtual void InputDataExhausted() MOZ_OVERRIDE;
+  virtual void DrainComplete() MOZ_OVERRIDE;
+  virtual void ResetComplete() MOZ_OVERRIDE;
+  virtual void Error(GMPErr aErr) MOZ_OVERRIDE;
+  virtual void Terminated() MOZ_OVERRIDE;
+
+  void SetLastStreamOffset(int64_t aStreamOffset) {
+    mLastStreamOffset = aStreamOffset;
+  }
+
+private:
+  MediaDataDecoderCallbackProxy* mCallback;
+  int64_t mLastStreamOffset;
+
+  VideoInfo mVideoInfo;
+  nsRefPtr<layers::ImageContainer> mImageContainer;
+};
+
+class GMPVideoDecoder : public MediaDataDecoder {
+protected:
+  GMPVideoDecoder(const mp4_demuxer::VideoDecoderConfig& aConfig,
+                  layers::LayersBackend aLayersBackend,
+                  layers::ImageContainer* aImageContainer,
+                  MediaTaskQueue* aTaskQueue,
+                  MediaDataDecoderCallbackProxy* aCallback,
+                  VideoCallbackAdapter* aAdapter)
+   : mConfig(aConfig)
+   , mCallback(aCallback)
+   , mGMP(nullptr)
+   , mHost(nullptr)
+   , mAdapter(aAdapter)
+  {
+  }
+
+public:
+  GMPVideoDecoder(const mp4_demuxer::VideoDecoderConfig& aConfig,
+                  layers::LayersBackend aLayersBackend,
+                  layers::ImageContainer* aImageContainer,
+                  MediaTaskQueue* aTaskQueue,
+                  MediaDataDecoderCallbackProxy* aCallback)
+   : GMPVideoDecoder(aConfig, aLayersBackend, aImageContainer, aTaskQueue, aCallback,
+                     new VideoCallbackAdapter(aCallback,
+                                              VideoInfo(aConfig.display_width,
+                                                        aConfig.display_height),
+                                              aImageContainer))
+  {
+  }
+
+  virtual nsresult Init() MOZ_OVERRIDE;
+  virtual nsresult Input(mp4_demuxer::MP4Sample* aSample) MOZ_OVERRIDE;
+  virtual nsresult Flush() MOZ_OVERRIDE;
+  virtual nsresult Drain() MOZ_OVERRIDE;
+  virtual nsresult Shutdown() MOZ_OVERRIDE;
+
+protected:
+  virtual void InitTags(nsTArray<nsCString>& aTags);
+  virtual nsCString GetNodeId();
+  virtual GMPUniquePtr<GMPVideoEncodedFrame> CreateFrame(mp4_demuxer::MP4Sample* aSample);
+
+private:
+  const mp4_demuxer::VideoDecoderConfig& mConfig;
+  MediaDataDecoderCallbackProxy* mCallback;
+  nsCOMPtr<mozIGeckoMediaPluginService> mMPS;
+  GMPVideoDecoderProxy* mGMP;
+  GMPVideoHost* mHost;
+  nsAutoPtr<VideoCallbackAdapter> mAdapter;
+};
+
+
+} // namespace mozilla
+
+#endif // GMPVideoDecoder_h_
new file mode 100644
--- /dev/null
+++ b/dom/media/fmp4/gmp/MediaDataDecoderProxy.cpp
@@ -0,0 +1,101 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MediaDataDecoderProxy.h"
+
+namespace mozilla {
+
+void
+MediaDataDecoderCallbackProxy::Error()
+{
+  mProxyCallback->Error();
+  mProxyDecoder->Shutdown();
+}
+
+void
+MediaDataDecoderCallbackProxy::FlushComplete()
+{
+  mProxyDecoder->FlushComplete();
+}
+
+nsresult
+MediaDataDecoderProxy::Init()
+{
+  MOZ_ASSERT(!mIsShutdown);
+  nsRefPtr<InitTask> task(new InitTask(mProxyDecoder));
+  nsresult rv = mProxyThread->Dispatch(task, NS_DISPATCH_SYNC);
+  NS_ENSURE_SUCCESS(rv, rv);
+  NS_ENSURE_SUCCESS(task->Result(), task->Result());
+
+  return NS_OK;
+}
+
+nsresult
+MediaDataDecoderProxy::Input(mp4_demuxer::MP4Sample* aSample)
+{
+  MOZ_ASSERT(!IsOnProxyThread());
+  MOZ_ASSERT(!mIsShutdown);
+
+  nsRefPtr<nsIRunnable> task(new InputTask(mProxyDecoder, aSample));
+  nsresult rv = mProxyThread->Dispatch(task, NS_DISPATCH_NORMAL);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+nsresult
+MediaDataDecoderProxy::Flush()
+{
+  MOZ_ASSERT(!IsOnProxyThread());
+  MOZ_ASSERT(!mIsShutdown);
+
+  mFlushComplete.Set(false);
+
+  nsRefPtr<nsIRunnable> task;
+  task = NS_NewRunnableMethod(mProxyDecoder, &MediaDataDecoder::Flush);
+  nsresult rv = mProxyThread->Dispatch(task, NS_DISPATCH_NORMAL);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  mFlushComplete.WaitUntil(true);
+
+  return NS_OK;
+}
+
+nsresult
+MediaDataDecoderProxy::Drain()
+{
+  MOZ_ASSERT(!IsOnProxyThread());
+  MOZ_ASSERT(!mIsShutdown);
+
+  nsRefPtr<nsIRunnable> task;
+  task = NS_NewRunnableMethod(mProxyDecoder, &MediaDataDecoder::Drain);
+  nsresult rv = mProxyThread->Dispatch(task, NS_DISPATCH_NORMAL);
+  NS_ENSURE_SUCCESS(rv, rv);
+  return NS_OK;
+}
+
+nsresult
+MediaDataDecoderProxy::Shutdown()
+{
+  // Note that this *may* be called from the proxy thread also.
+  MOZ_ASSERT(!mIsShutdown);
+#if defined(DEBUG)
+  mIsShutdown = true;
+#endif
+  nsRefPtr<nsIRunnable> task;
+  task = NS_NewRunnableMethod(mProxyDecoder, &MediaDataDecoder::Shutdown);
+  nsresult rv = mProxyThread->Dispatch(task, NS_DISPATCH_SYNC);
+  NS_ENSURE_SUCCESS(rv, rv);
+  return NS_OK;
+}
+
+void
+MediaDataDecoderProxy::FlushComplete()
+{
+  mFlushComplete.Set(true);
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/media/fmp4/gmp/MediaDataDecoderProxy.h
@@ -0,0 +1,191 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#if !defined(MediaDataDecoderProxy_h_)
+#define MediaDataDecoderProxy_h_
+
+#include "PlatformDecoderModule.h"
+#include "mp4_demuxer/DecoderData.h"
+#include "nsAutoPtr.h"
+#include "nsRefPtr.h"
+#include "nsThreadUtils.h"
+#include "nscore.h"
+
+namespace mozilla {
+
+class InputTask : public nsRunnable {
+public:
+  InputTask(MediaDataDecoder* aDecoder,
+            mp4_demuxer::MP4Sample* aSample)
+   : mDecoder(aDecoder)
+   , mSample(aSample)
+  {}
+
+  NS_IMETHOD Run() {
+    mDecoder->Input(mSample.forget());
+    return NS_OK;
+  }
+
+private:
+  nsRefPtr<MediaDataDecoder> mDecoder;
+  nsAutoPtr<mp4_demuxer::MP4Sample> mSample;
+};
+
+class InitTask : public nsRunnable {
+public:
+  explicit InitTask(MediaDataDecoder* aDecoder)
+   : mDecoder(aDecoder)
+   , mResultValid(false)
+  {}
+
+  NS_IMETHOD Run() {
+    mResult = mDecoder->Init();
+    mResultValid = true;
+    return NS_OK;
+  }
+
+  nsresult Result() {
+    MOZ_ASSERT(mResultValid);
+    return mResult;
+  }
+
+private:
+  MediaDataDecoder* mDecoder;
+  nsresult mResult;
+  bool mResultValid;
+};
+
+template<typename T>
+class Condition {
+public:
+  explicit Condition(T aValue)
+   : mMonitor("Condition")
+   , mCondition(aValue)
+  {}
+
+  void Set(T aValue) {
+    MonitorAutoLock mon(mMonitor);
+    mCondition = aValue;
+    mon.NotifyAll();
+  }
+
+  void WaitUntil(T aValue) {
+    MonitorAutoLock mon(mMonitor);
+    while (mCondition != aValue) {
+      mon.Wait();
+    }
+  }
+
+private:
+  Monitor mMonitor;
+  T mCondition;
+};
+
+class MediaDataDecoderProxy;
+
+class MediaDataDecoderCallbackProxy : public MediaDataDecoderCallback {
+public:
+  explicit MediaDataDecoderCallbackProxy(MediaDataDecoderProxy* aProxyDecoder, MediaDataDecoderCallback* aCallback)
+   : mProxyDecoder(aProxyDecoder)
+   , mProxyCallback(aCallback)
+  {
+  }
+
+  virtual void Output(MediaData* aData) MOZ_OVERRIDE {
+    mProxyCallback->Output(aData);
+  }
+
+  virtual void Error() MOZ_OVERRIDE;
+
+  virtual void InputExhausted() MOZ_OVERRIDE {
+    mProxyCallback->InputExhausted();
+  }
+
+  virtual void DrainComplete() MOZ_OVERRIDE {
+    mProxyCallback->DrainComplete();
+  }
+
+  virtual void NotifyResourcesStatusChanged() MOZ_OVERRIDE {
+    mProxyCallback->NotifyResourcesStatusChanged();
+  }
+
+  virtual void ReleaseMediaResources() MOZ_OVERRIDE {
+    mProxyCallback->ReleaseMediaResources();
+  }
+
+  virtual void FlushComplete();
+
+private:
+  MediaDataDecoderProxy* mProxyDecoder;
+  MediaDataDecoderCallback* mProxyCallback;
+};
+
+class MediaDataDecoderProxy : public MediaDataDecoder {
+public:
+  MediaDataDecoderProxy(nsIThread* aProxyThread, MediaDataDecoderCallback* aCallback)
+   : mProxyThread(aProxyThread)
+   , mProxyCallback(this, aCallback)
+   , mFlushComplete(false)
+#if defined(DEBUG)
+   , mIsShutdown(false)
+#endif
+  {
+  }
+
+  // Ideally, this would return a regular MediaDataDecoderCallback pointer
+  // to retain the clean abstraction, but until MediaDataDecoderCallback
+  // supports the FlushComplete interface, this will have to do.  When MDDC
+  // supports FlushComplete, this, the GMP*Decoders, and the
+  // *CallbackAdapters can be reverted to accepting a regular
+  // MediaDataDecoderCallback pointer.
+  MediaDataDecoderCallbackProxy* Callback()
+  {
+    return &mProxyCallback;
+  }
+
+  void SetProxyTarget(MediaDataDecoder* aProxyDecoder)
+  {
+    MOZ_ASSERT(aProxyDecoder);
+    mProxyDecoder = aProxyDecoder;
+  }
+
+  // These are called from the decoder thread pool.
+  // Init and Shutdown run synchronously on the proxy thread, all others are
+  // asynchronously and responded to via the MediaDataDecoderCallback.
+  // Note: the nsresults returned by the proxied decoder are lost.
+  virtual nsresult Init() MOZ_OVERRIDE;
+  virtual nsresult Input(mp4_demuxer::MP4Sample* aSample) MOZ_OVERRIDE;
+  virtual nsresult Flush() MOZ_OVERRIDE;
+  virtual nsresult Drain() MOZ_OVERRIDE;
+  virtual nsresult Shutdown() MOZ_OVERRIDE;
+
+  // Called by MediaDataDecoderCallbackProxy.
+  void FlushComplete();
+
+private:
+#ifdef DEBUG
+  bool IsOnProxyThread() {
+    return NS_GetCurrentThread() == mProxyThread;
+  }
+#endif
+
+  friend class InputTask;
+  friend class InitTask;
+
+  nsRefPtr<MediaDataDecoder> mProxyDecoder;
+  nsCOMPtr<nsIThread> mProxyThread;
+
+  MediaDataDecoderCallbackProxy mProxyCallback;
+
+  Condition<bool> mFlushComplete;
+#if defined(DEBUG)
+  bool mIsShutdown;
+#endif
+};
+
+} // namespace mozilla
+
+#endif // MediaDataDecoderProxy_h_
new file mode 100644
--- /dev/null
+++ b/dom/media/fmp4/gmp/moz.build
@@ -0,0 +1,26 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+EXPORTS += [
+    'GMPAudioDecoder.h',
+    'GMPDecoderModule.h',
+    'GMPVideoDecoder.h',
+    'MediaDataDecoderProxy.h',
+]
+
+UNIFIED_SOURCES += [
+    'GMPAudioDecoder.cpp',
+    'GMPDecoderModule.cpp',
+    'GMPVideoDecoder.cpp',
+    'MediaDataDecoderProxy.cpp',
+]
+
+# GMPVideoEncodedFrameImpl.h needs IPC
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
+
+FAIL_ON_WARNINGS = True
--- a/dom/media/fmp4/moz.build
+++ b/dom/media/fmp4/moz.build
@@ -21,16 +21,18 @@ UNIFIED_SOURCES += [
     'PlatformDecoderModule.cpp',
     'SharedDecoderManager.cpp',
 ]
 
 SOURCES += [
     'MP4Reader.cpp',
 ]
 
+DIRS += ['gmp']
+
 if CONFIG['MOZ_WMF']:
     DIRS += [ 'wmf' ];
 
 if CONFIG['MOZ_EME']:
     DIRS += ['eme']
 
 if CONFIG['MOZ_FFMPEG']:
     EXPORTS += [
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -270,16 +270,17 @@ pref("media.windows-media-foundation.ena
 pref("media.windows-media-foundation.use-dxva", true);
 #endif
 #ifdef MOZ_DIRECTSHOW
 pref("media.directshow.enabled", true);
 #endif
 #ifdef MOZ_FMP4
 pref("media.fragmented-mp4.enabled", true);
 pref("media.fragmented-mp4.ffmpeg.enabled", false);
+pref("media.fragmented-mp4.gmp.enabled", false);
 #if defined(XP_WIN) && defined(MOZ_WMF) || defined(XP_MACOSX) || defined(MOZ_WIDGET_GONK)
 // Denotes that the fragmented MP4 parser can be created by <video> elements.
 pref("media.fragmented-mp4.exposed", true);
 #else
 pref("media.fragmented-mp4.exposed", false);
 #endif
 // Specifies whether the fragmented MP4 parser uses a test decoder that
 // just outputs blank frames/audio instead of actually decoding. The blank