Bug 941298 - FFmpeg PlatformDecoderModule for Linux r=doublec,cpearce
authorEdwin Flores <eflores@mozilla.com>
Fri, 21 Mar 2014 19:35:15 +1300
changeset 193200 4cedbdf21dddbd125c53e5757e6258f60d88b7d8
parent 193199 2df0b05cb91129ad5677d9e465c42a5edfe3f3c9
child 193201 c902425901af5c33f4e1788118b455b88d541d06
push id3624
push userasasaki@mozilla.com
push dateMon, 09 Jun 2014 21:49:01 +0000
treeherdermozilla-beta@b1a5da15899a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdoublec, cpearce
bugs941298
milestone31.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 941298 - FFmpeg PlatformDecoderModule for Linux r=doublec,cpearce
content/media/fmp4/MP4Decoder.cpp
content/media/fmp4/PlatformDecoderModule.cpp
content/media/fmp4/PlatformDecoderModule.h
content/media/fmp4/ffmpeg/FFmpegAACDecoder.cpp
content/media/fmp4/ffmpeg/FFmpegAACDecoder.h
content/media/fmp4/ffmpeg/FFmpegCompat.h
content/media/fmp4/ffmpeg/FFmpegDataDecoder.cpp
content/media/fmp4/ffmpeg/FFmpegDataDecoder.h
content/media/fmp4/ffmpeg/FFmpegDecoderModule.cpp
content/media/fmp4/ffmpeg/FFmpegDecoderModule.h
content/media/fmp4/ffmpeg/FFmpegFunctionList.h
content/media/fmp4/ffmpeg/FFmpegH264Decoder.cpp
content/media/fmp4/ffmpeg/FFmpegH264Decoder.h
content/media/fmp4/ffmpeg/FFmpegRuntimeLinker.cpp
content/media/fmp4/ffmpeg/FFmpegRuntimeLinker.h
layout/build/moz.build
layout/build/nsLayoutStatics.cpp
modules/libpref/src/init/all.js
--- a/content/media/fmp4/MP4Decoder.cpp
+++ b/content/media/fmp4/MP4Decoder.cpp
@@ -7,16 +7,19 @@
 #include "MP4Decoder.h"
 #include "MP4Reader.h"
 #include "MediaDecoderStateMachine.h"
 #include "mozilla/Preferences.h"
 
 #ifdef XP_WIN
 #include "mozilla/WindowsVersion.h"
 #endif
+#ifdef MOZ_FFMPEG
+#include "FFmpegDecoderModule.h"
+#endif
 
 namespace mozilla {
 
 MediaDecoderStateMachine* MP4Decoder::CreateStateMachine()
 {
   return new MediaDecoderStateMachine(this, new MP4Reader(this));
 }
 
@@ -60,26 +63,42 @@ MP4Decoder::GetSupportedCodecs(const nsA
     }
     return true;
   }
 
   return false;
 }
 
 static bool
+IsFFmpegAvailable()
+{
+#ifndef MOZ_FFMPEG
+  return false;
+#else
+  if (!Preferences::GetBool("media.fragmented-mp4.ffmpeg.enabled", false)) {
+    return false;
+  }
+
+  // If we can link to FFmpeg, then we can almost certainly play H264 and AAC
+  // with it.
+  return FFmpegDecoderModule::Link();
+#endif
+}
+
+static bool
 HavePlatformMPEGDecoders()
 {
-  return
-    Preferences::GetBool("media.fragmented-mp4.use-blank-decoder") ||
+  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() ||
+         // We have H.264/AAC platform decoders on Windows Vista and up.
+         IsVistaOrLater() ||
 #endif
-    // TODO: Other platforms...
-    false;
+         IsFFmpegAvailable() ||
+         // TODO: Other platforms...
+         false;
 }
 
 /* static */
 bool
 MP4Decoder::IsEnabled()
 {
   return HavePlatformMPEGDecoders() &&
          Preferences::GetBool("media.fragmented-mp4.enabled");
--- a/content/media/fmp4/PlatformDecoderModule.cpp
+++ b/content/media/fmp4/PlatformDecoderModule.cpp
@@ -3,35 +3,44 @@
 /* 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 "PlatformDecoderModule.h"
 #ifdef XP_WIN
 #include "WMFDecoderModule.h"
 #endif
+#ifdef MOZ_FFMPEG
+#include "FFmpegDecoderModule.h"
+#endif
 #include "mozilla/Preferences.h"
 
 namespace mozilla {
 
 extern PlatformDecoderModule* CreateBlankDecoderModule();
 
 bool PlatformDecoderModule::sUseBlankDecoder = false;
+bool PlatformDecoderModule::sFFmpegDecoderEnabled = false;
 
 /* static */
 void
 PlatformDecoderModule::Init()
 {
   MOZ_ASSERT(NS_IsMainThread());
   static bool alreadyInitialized = false;
   if (alreadyInitialized) {
     return;
   }
   alreadyInitialized = true;
-  sUseBlankDecoder = Preferences::GetBool("media.fragmented-mp4.use-blank-decoder");
+
+  Preferences::AddBoolVarCache(&sUseBlankDecoder,
+                               "media.fragmented-mp4.use-blank-decoder");
+  Preferences::AddBoolVarCache(&sFFmpegDecoderEnabled,
+                               "media.fragmented-mp4.ffmpeg.enabled", false);
+
 #ifdef XP_WIN
   WMFDecoderModule::Init();
 #endif
 }
 
 /* static */
 PlatformDecoderModule*
 PlatformDecoderModule::Create()
@@ -40,12 +49,17 @@ PlatformDecoderModule::Create()
     return CreateBlankDecoderModule();
   }
 #ifdef XP_WIN
   nsAutoPtr<WMFDecoderModule> m(new WMFDecoderModule());
   if (NS_SUCCEEDED(m->Startup())) {
     return m.forget();
   }
 #endif
+#ifdef MOZ_FFMPEG
+  if (sFFmpegDecoderEnabled) {
+    return new FFmpegDecoderModule();
+  }
+#endif
   return nullptr;
 }
 
 } // namespace mozilla
--- a/content/media/fmp4/PlatformDecoderModule.h
+++ b/content/media/fmp4/PlatformDecoderModule.h
@@ -104,16 +104,17 @@ public:
                                              MediaDataDecoderCallback* aCallback) = 0;
 
   virtual ~PlatformDecoderModule() {}
 
 protected:
   PlatformDecoderModule() {}
   // Caches pref media.fragmented-mp4.use-blank-decoder
   static bool sUseBlankDecoder;
+  static bool sFFmpegDecoderEnabled;
 };
 
 // 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/content/media/fmp4/ffmpeg/FFmpegAACDecoder.cpp
@@ -0,0 +1,130 @@
+/* -*- 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 "MediaTaskQueue.h"
+#include "FFmpegRuntimeLinker.h"
+
+#include "FFmpegAACDecoder.h"
+
+#define MAX_CHANNELS 16
+
+typedef mp4_demuxer::MP4Sample MP4Sample;
+
+namespace mozilla
+{
+
+FFmpegAACDecoder::FFmpegAACDecoder(
+  MediaTaskQueue* aTaskQueue, MediaDataDecoderCallback* aCallback,
+  const mp4_demuxer::AudioDecoderConfig &aConfig)
+  : FFmpegDataDecoder(aTaskQueue, AV_CODEC_ID_AAC)
+  , mCallback(aCallback)
+  , mConfig(aConfig)
+{
+  MOZ_COUNT_CTOR(FFmpegAACDecoder);
+}
+
+nsresult
+FFmpegAACDecoder::Init()
+{
+  nsresult rv = FFmpegDataDecoder::Init();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+static AudioDataValue*
+CopyAndPackAudio(AVFrame* aFrame, uint32_t aNumChannels, uint32_t aNumSamples)
+{
+  // These are the only two valid AAC packet sizes.
+  NS_ASSERTION(aNumSamples == 960 || aNumSamples == 1024,
+               "Should have exactly one AAC audio packet.");
+  MOZ_ASSERT(aNumChannels <= MAX_CHANNELS);
+
+  nsAutoArrayPtr<AudioDataValue> audio(
+    new AudioDataValue[aNumChannels * aNumSamples]);
+
+  AudioDataValue** data = reinterpret_cast<AudioDataValue**>(aFrame->data);
+
+  if (aFrame->format == AV_SAMPLE_FMT_FLT) {
+    // Audio data already packed. No need to do anything other than copy it
+    // into a buffer we own.
+    memcpy(audio, data[0], aNumChannels * aNumSamples * sizeof(AudioDataValue));
+  } else if (aFrame->format == AV_SAMPLE_FMT_FLTP) {
+    // Planar audio data. Pack it into something we can understand.
+    for (uint32_t channel = 0; channel < aNumChannels; channel++) {
+      for (uint32_t sample = 0; sample < aNumSamples; sample++) {
+        audio[sample * aNumChannels + channel] = data[channel][sample];
+      }
+    }
+  }
+
+  return audio.forget();
+}
+
+void
+FFmpegAACDecoder::DecodePacket(MP4Sample* aSample)
+{
+  nsAutoPtr<AVFrame> frame(avcodec_alloc_frame());
+  avcodec_get_frame_defaults(frame);
+
+  AVPacket packet;
+  av_init_packet(&packet);
+
+  packet.data = &(*aSample->data)[0];
+  packet.size = aSample->data->size();
+  packet.pos = aSample->byte_offset;
+  packet.dts = aSample->decode_timestamp;
+
+  int decoded;
+  int bytesConsumed =
+    avcodec_decode_audio4(&mCodecContext, frame.get(), &decoded, &packet);
+
+  if (bytesConsumed < 0 || !decoded) {
+    NS_WARNING("FFmpeg audio decoder error.");
+    mCallback->Error();
+    return;
+  }
+
+  NS_ASSERTION(bytesConsumed == (int)aSample->data->size(),
+               "Only one audio packet should be received at a time.");
+
+  uint32_t numChannels = mCodecContext.channels;
+
+  nsAutoArrayPtr<AudioDataValue> audio(
+    CopyAndPackAudio(frame.get(), numChannels, frame->nb_samples));
+
+  nsAutoPtr<AudioData> data(new AudioData(packet.pos, aSample->decode_timestamp,
+                                          aSample->duration, frame->nb_samples,
+                                          audio.forget(), numChannels));
+
+  mCallback->Output(data.forget());
+
+  if (mTaskQueue->IsEmpty()) {
+    mCallback->InputExhausted();
+  }
+}
+
+nsresult
+FFmpegAACDecoder::Input(MP4Sample* aSample)
+{
+  mTaskQueue->Dispatch(NS_NewRunnableMethodWithArg<nsAutoPtr<MP4Sample> >(
+    this, &FFmpegAACDecoder::DecodePacket, nsAutoPtr<MP4Sample>(aSample)));
+
+  return NS_OK;
+}
+
+nsresult
+FFmpegAACDecoder::Drain()
+{
+  // AAC is never delayed; nothing to do here.
+  return NS_OK;
+}
+
+FFmpegAACDecoder::~FFmpegAACDecoder() {
+  MOZ_COUNT_DTOR(FFmpegAACDecoder);
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/content/media/fmp4/ffmpeg/FFmpegAACDecoder.h
@@ -0,0 +1,36 @@
+/* -*- 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 __FFmpegAACDecoder_h__
+#define __FFmpegAACDecoder_h__
+
+#include "FFmpegDataDecoder.h"
+
+namespace mozilla
+{
+
+class FFmpegAACDecoder : public FFmpegDataDecoder
+{
+public:
+  FFmpegAACDecoder(MediaTaskQueue* aTaskQueue,
+                   MediaDataDecoderCallback* aCallback,
+                   const mp4_demuxer::AudioDecoderConfig &aConfig);
+  virtual ~FFmpegAACDecoder();
+
+  virtual nsresult Init() MOZ_OVERRIDE;
+  virtual nsresult Input(mp4_demuxer::MP4Sample* aSample) MOZ_OVERRIDE;
+  virtual nsresult Drain() MOZ_OVERRIDE;
+
+private:
+  void DecodePacket(mp4_demuxer::MP4Sample* aSample);
+
+  MediaDataDecoderCallback* mCallback;
+  mp4_demuxer::AudioDecoderConfig mConfig;
+};
+
+} // namespace mozilla
+
+#endif // __FFmpegAACDecoder_h__
new file mode 100644
--- /dev/null
+++ b/content/media/fmp4/ffmpeg/FFmpegCompat.h
@@ -0,0 +1,18 @@
+/* -*- 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 __FFmpegCompat_h__
+#define __FFmpegCompat_h__
+
+#include <libavcodec/version.h>
+
+#if LIBAVCODEC_VERSION_MAJOR < 55
+#define AV_CODEC_ID_H264 CODEC_ID_H264
+#define AV_CODEC_ID_AAC CODEC_ID_AAC
+typedef CodecID AVCodecID;
+#endif
+
+#endif // __FFmpegCompat_h__
new file mode 100644
--- /dev/null
+++ b/content/media/fmp4/ffmpeg/FFmpegDataDecoder.cpp
@@ -0,0 +1,124 @@
+/* -*- 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 <string.h>
+#include <unistd.h>
+
+#include "MediaTaskQueue.h"
+#include "mp4_demuxer/mp4_demuxer.h"
+#include "FFmpegRuntimeLinker.h"
+
+#include "FFmpegDataDecoder.h"
+
+namespace mozilla
+{
+
+bool FFmpegDataDecoder::sFFmpegInitDone = false;
+
+FFmpegDataDecoder::FFmpegDataDecoder(MediaTaskQueue* aTaskQueue,
+                                     AVCodecID aCodecID)
+  : mTaskQueue(aTaskQueue), mCodecID(aCodecID)
+{
+  MOZ_COUNT_CTOR(FFmpegDataDecoder);
+}
+
+FFmpegDataDecoder::~FFmpegDataDecoder() {
+  MOZ_COUNT_DTOR(FFmpegDataDecoder);
+}
+
+/**
+ * FFmpeg calls back to this function with a list of pixel formats it supports.
+ * We choose a pixel format that we support and return it.
+ * For now, we just look for YUV420P as it is the only non-HW accelerated format
+ * supported by FFmpeg's H264 decoder.
+ */
+static PixelFormat
+ChoosePixelFormat(AVCodecContext* aCodecContext, const PixelFormat* aFormats)
+{
+  FFMPEG_LOG("Choosing FFmpeg pixel format for video decoding.");
+  for (; *aFormats > -1; aFormats++) {
+    if (*aFormats == PIX_FMT_YUV420P) {
+      FFMPEG_LOG("Requesting pixel format YUV420P.");
+      return PIX_FMT_YUV420P;
+    }
+  }
+
+  NS_WARNING("FFmpeg does not share any supported pixel formats.");
+  return PIX_FMT_NONE;
+}
+
+nsresult
+FFmpegDataDecoder::Init()
+{
+  FFMPEG_LOG("Initialising FFmpeg decoder.");
+
+  if (!FFmpegRuntimeLinker::Link()) {
+    NS_WARNING("Failed to link FFmpeg shared libraries.");
+    return NS_ERROR_FAILURE;
+  }
+
+  if (!sFFmpegInitDone) {
+    av_register_all();
+#ifdef DEBUG
+    av_log_set_level(AV_LOG_DEBUG);
+#endif
+    sFFmpegInitDone = true;
+  }
+
+  AVCodec* codec = avcodec_find_decoder(mCodecID);
+  if (!codec) {
+    NS_WARNING("Couldn't find ffmpeg decoder");
+    return NS_ERROR_FAILURE;
+  }
+
+  if (avcodec_get_context_defaults3(&mCodecContext, codec) < 0) {
+    NS_WARNING("Couldn't init ffmpeg context");
+    return NS_ERROR_FAILURE;
+  }
+
+  mCodecContext.opaque = this;
+
+  // FFmpeg takes this as a suggestion for what format to use for audio samples.
+  mCodecContext.request_sample_fmt = AV_SAMPLE_FMT_FLT;
+
+  // FFmpeg will call back to this to negotiate a video pixel format.
+  mCodecContext.get_format = ChoosePixelFormat;
+
+  AVDictionary* opts = nullptr;
+  if (avcodec_open2(&mCodecContext, codec, &opts) < 0) {
+    NS_WARNING("Couldn't initialise ffmpeg decoder");
+    return NS_ERROR_FAILURE;
+  }
+
+  if (mCodecContext.codec_type == AVMEDIA_TYPE_AUDIO &&
+      mCodecContext.sample_fmt != AV_SAMPLE_FMT_FLT &&
+      mCodecContext.sample_fmt != AV_SAMPLE_FMT_FLTP) {
+    NS_WARNING("FFmpeg AAC decoder outputs unsupported audio format.");
+    return NS_ERROR_FAILURE;
+  }
+
+  FFMPEG_LOG("FFmpeg init successful.");
+  return NS_OK;
+}
+
+nsresult
+FFmpegDataDecoder::Flush()
+{
+  mTaskQueue->Flush();
+  avcodec_flush_buffers(&mCodecContext);
+  return NS_OK;
+}
+
+nsresult
+FFmpegDataDecoder::Shutdown()
+{
+  if (sFFmpegInitDone) {
+    avcodec_close(&mCodecContext);
+  }
+  return NS_OK;
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/content/media/fmp4/ffmpeg/FFmpegDataDecoder.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 __FFmpegDataDecoder_h__
+#define __FFmpegDataDecoder_h__
+
+#include "mp4_demuxer/mp4_demuxer.h"
+
+#include "FFmpegDecoderModule.h"
+#include "FFmpegRuntimeLinker.h"
+#include "FFmpegCompat.h"
+
+namespace mozilla
+{
+
+class FFmpegDataDecoder : public MediaDataDecoder
+{
+public:
+  FFmpegDataDecoder(MediaTaskQueue* aTaskQueue, AVCodecID aCodecID);
+  virtual ~FFmpegDataDecoder();
+
+  static bool Link();
+
+  virtual nsresult Init() MOZ_OVERRIDE;
+  virtual nsresult Input(mp4_demuxer::MP4Sample* aSample) = 0;
+  virtual nsresult Flush() MOZ_OVERRIDE;
+  virtual nsresult Drain() = 0;
+  virtual nsresult Shutdown() MOZ_OVERRIDE;
+
+protected:
+  MediaTaskQueue* mTaskQueue;
+  AVCodecContext mCodecContext;
+
+private:
+  static bool sFFmpegInitDone;
+
+  AVCodecID mCodecID;
+};
+
+} // namespace mozilla
+
+#endif // __FFmpegDataDecoder_h__
new file mode 100644
--- /dev/null
+++ b/content/media/fmp4/ffmpeg/FFmpegDecoderModule.cpp
@@ -0,0 +1,81 @@
+/* -*- 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 "FFmpegRuntimeLinker.h"
+#include "FFmpegAACDecoder.h"
+#include "FFmpegH264Decoder.h"
+
+#include "FFmpegDecoderModule.h"
+
+namespace mozilla
+{
+
+PRLogModuleInfo* GetFFmpegDecoderLog()
+{
+  static PRLogModuleInfo* sFFmpegDecoderLog = nullptr;
+  if (!sFFmpegDecoderLog) {
+    sFFmpegDecoderLog = PR_NewLogModule("FFmpegDecoderModule");
+  }
+  return sFFmpegDecoderLog;
+}
+
+bool FFmpegDecoderModule::sFFmpegLinkDone = false;
+
+FFmpegDecoderModule::FFmpegDecoderModule()
+{
+  MOZ_COUNT_CTOR(FFmpegDecoderModule);
+}
+
+FFmpegDecoderModule::~FFmpegDecoderModule() {
+  MOZ_COUNT_DTOR(FFmpegDecoderModule);
+}
+
+bool
+FFmpegDecoderModule::Link()
+{
+  if (sFFmpegLinkDone) {
+    return true;
+  }
+
+  if (!FFmpegRuntimeLinker::Link()) {
+    NS_WARNING("Failed to link FFmpeg shared libraries.");
+    return false;
+  }
+
+  sFFmpegLinkDone = true;
+
+  return true;
+}
+
+nsresult
+FFmpegDecoderModule::Shutdown()
+{
+  // Nothing to do here.
+  return NS_OK;
+}
+
+MediaDataDecoder*
+FFmpegDecoderModule::CreateH264Decoder(
+  const mp4_demuxer::VideoDecoderConfig& aConfig,
+  mozilla::layers::LayersBackend aLayersBackend,
+  mozilla::layers::ImageContainer* aImageContainer,
+  MediaTaskQueue* aVideoTaskQueue, MediaDataDecoderCallback* aCallback)
+{
+  FFMPEG_LOG("Creating FFmpeg H264 decoder.");
+  return new FFmpegH264Decoder(aVideoTaskQueue, aCallback, aConfig,
+                               aImageContainer);
+}
+
+MediaDataDecoder*
+FFmpegDecoderModule::CreateAACDecoder(
+  const mp4_demuxer::AudioDecoderConfig& aConfig,
+  MediaTaskQueue* aAudioTaskQueue, MediaDataDecoderCallback* aCallback)
+{
+  FFMPEG_LOG("Creating FFmpeg AAC decoder.");
+  return new FFmpegAACDecoder(aAudioTaskQueue, aCallback, aConfig);
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/content/media/fmp4/ffmpeg/FFmpegDecoderModule.h
@@ -0,0 +1,50 @@
+/* -*- 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 __FFmpegDecoderModule_h__
+#define __FFmpegDecoderModule_h__
+
+#include "PlatformDecoderModule.h"
+
+namespace mozilla
+{
+
+#ifdef PR_LOGGING
+extern PRLogModuleInfo* GetFFmpegDecoderLog();
+#define FFMPEG_LOG(...) PR_LOG(GetFFmpegDecoderLog(), PR_LOG_DEBUG, (__VA_ARGS__))
+#else
+#define FFMPEG_LOG(...)
+#endif
+
+class FFmpegDecoderModule : public PlatformDecoderModule
+{
+public:
+  FFmpegDecoderModule();
+  virtual ~FFmpegDecoderModule();
+
+  static bool Link();
+
+  virtual nsresult Shutdown() MOZ_OVERRIDE;
+
+  virtual MediaDataDecoder* CreateH264Decoder(
+    const mp4_demuxer::VideoDecoderConfig& aConfig,
+    mozilla::layers::LayersBackend aLayersBackend,
+    mozilla::layers::ImageContainer* aImageContainer,
+    MediaTaskQueue* aVideoTaskQueue,
+    MediaDataDecoderCallback* aCallback) MOZ_OVERRIDE;
+
+  virtual MediaDataDecoder* CreateAACDecoder(
+    const mp4_demuxer::AudioDecoderConfig& aConfig,
+    MediaTaskQueue* aAudioTaskQueue,
+    MediaDataDecoderCallback* aCallback) MOZ_OVERRIDE;
+
+private:
+  static bool sFFmpegLinkDone;
+};
+
+} // namespace mozilla
+
+#endif // __FFmpegDecoderModule_h__
new file mode 100644
--- /dev/null
+++ b/content/media/fmp4/ffmpeg/FFmpegFunctionList.h
@@ -0,0 +1,25 @@
+/* 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/. */
+
+AV_FUNC(LIBAVCODEC, avcodec_align_dimensions2)
+AV_FUNC(LIBAVCODEC, avcodec_alloc_frame)
+AV_FUNC(LIBAVCODEC, avcodec_close)
+AV_FUNC(LIBAVCODEC, avcodec_decode_audio4)
+AV_FUNC(LIBAVCODEC, avcodec_decode_video2)
+AV_FUNC(LIBAVCODEC, avcodec_default_get_buffer)
+AV_FUNC(LIBAVCODEC, avcodec_default_release_buffer)
+AV_FUNC(LIBAVCODEC, avcodec_find_decoder)
+AV_FUNC(LIBAVCODEC, avcodec_flush_buffers)
+AV_FUNC(LIBAVCODEC, avcodec_get_context_defaults3)
+AV_FUNC(LIBAVCODEC, avcodec_get_edge_width)
+AV_FUNC(LIBAVCODEC, avcodec_get_frame_defaults)
+AV_FUNC(LIBAVCODEC, avcodec_open2)
+AV_FUNC(LIBAVCODEC, av_init_packet)
+AV_FUNC(LIBAVCODEC, av_dict_get)
+
+AV_FUNC(LIBAVFORMAT, av_register_all)
+
+AV_FUNC(LIBAVUTIL, av_image_fill_linesizes)
+AV_FUNC(LIBAVUTIL, av_image_fill_pointers)
+AV_FUNC(LIBAVUTIL, av_log_set_level)
new file mode 100644
--- /dev/null
+++ b/content/media/fmp4/ffmpeg/FFmpegH264Decoder.cpp
@@ -0,0 +1,271 @@
+/* -*- 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 "MediaTaskQueue.h"
+#include "nsThreadUtils.h"
+#include "nsAutoPtr.h"
+#include "ImageContainer.h"
+
+#include "mp4_demuxer/mp4_demuxer.h"
+#include "FFmpegRuntimeLinker.h"
+
+#include "FFmpegH264Decoder.h"
+
+#define GECKO_FRAME_TYPE 0x00093CC0
+
+typedef mozilla::layers::Image Image;
+typedef mozilla::layers::PlanarYCbCrImage PlanarYCbCrImage;
+
+typedef mp4_demuxer::MP4Sample MP4Sample;
+
+namespace mozilla
+{
+
+FFmpegH264Decoder::FFmpegH264Decoder(
+  MediaTaskQueue* aTaskQueue, MediaDataDecoderCallback* aCallback,
+  const mp4_demuxer::VideoDecoderConfig &aConfig,
+  ImageContainer* aImageContainer)
+  : FFmpegDataDecoder(aTaskQueue, AV_CODEC_ID_H264)
+  , mConfig(aConfig)
+  , mCallback(aCallback)
+  , mImageContainer(aImageContainer)
+{
+  MOZ_COUNT_CTOR(FFmpegH264Decoder);
+}
+
+nsresult
+FFmpegH264Decoder::Init()
+{
+  nsresult rv = FFmpegDataDecoder::Init();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  mCodecContext.get_buffer = AllocateBufferCb;
+
+  return NS_OK;
+}
+
+void
+FFmpegH264Decoder::DecodeFrame(mp4_demuxer::MP4Sample* aSample)
+{
+  AVPacket packet;
+  av_init_packet(&packet);
+
+  packet.data = &(*aSample->data)[0];
+  packet.size = aSample->data->size();
+  packet.dts = aSample->decode_timestamp;
+  packet.pts = aSample->composition_timestamp;
+  packet.flags = aSample->is_sync_point ? AV_PKT_FLAG_KEY : 0;
+  packet.pos = aSample->byte_offset;
+
+  nsAutoPtr<AVFrame> frame(avcodec_alloc_frame());
+  avcodec_get_frame_defaults(frame);
+
+  int decoded;
+  int bytesConsumed =
+    avcodec_decode_video2(&mCodecContext, frame, &decoded, &packet);
+
+  if (bytesConsumed < 0) {
+    NS_WARNING("FFmpeg video decoder error.");
+    mCallback->Error();
+    return;
+  }
+
+  if (!decoded) {
+    // The decoder doesn't have enough data to decode a frame yet.
+    return;
+  }
+
+  nsAutoPtr<VideoData> data;
+
+  VideoInfo info;
+  info.mDisplay = nsIntSize(mCodecContext.width, mCodecContext.height);
+  info.mStereoMode = StereoMode::MONO;
+  info.mHasVideo = true;
+
+  data = VideoData::CreateFromImage(
+    info, mImageContainer, aSample->byte_offset, aSample->composition_timestamp,
+    aSample->duration, mCurrentImage, aSample->is_sync_point, -1,
+    gfx::IntRect(0, 0, mCodecContext.width, mCodecContext.height));
+
+  // Insert the frame into the heap for reordering.
+  mDelayedFrames.Push(data.forget());
+
+  // Reorder video frames from decode order to presentation order. The minimum
+  // size of the heap comes from one P frame + |max_b_frames| B frames, which
+  // is the maximum number of frames in a row which will be out-of-order.
+  if (mDelayedFrames.Length() > (uint32_t)mCodecContext.max_b_frames + 1) {
+    VideoData* d = mDelayedFrames.Pop();
+    mCallback->Output(d);
+  }
+
+  if (mTaskQueue->IsEmpty()) {
+    mCallback->InputExhausted();
+  }
+}
+
+static void
+PlanarYCbCrDataFromAVFrame(mozilla::layers::PlanarYCbCrData &aData,
+                           AVFrame* aFrame)
+{
+  aData.mPicX = aData.mPicY = 0;
+  aData.mPicSize = mozilla::gfx::IntSize(aFrame->width, aFrame->height);
+  aData.mStereoMode = StereoMode::MONO;
+
+  aData.mYChannel = aFrame->data[0];
+  aData.mYStride = aFrame->linesize[0];
+  aData.mYSize = aData.mPicSize;
+  aData.mYSkip = 0;
+
+  aData.mCbChannel = aFrame->data[1];
+  aData.mCrChannel = aFrame->data[2];
+  aData.mCbCrStride = aFrame->linesize[1];
+  aData.mCbSkip = aData.mCrSkip = 0;
+  aData.mCbCrSize =
+    mozilla::gfx::IntSize((aFrame->width + 1) / 2, (aFrame->height + 1) / 2);
+}
+
+/* static */ int
+FFmpegH264Decoder::AllocateBufferCb(AVCodecContext* aCodecContext,
+                                    AVFrame* aFrame)
+{
+  MOZ_ASSERT(aCodecContext->codec_type == AVMEDIA_TYPE_VIDEO);
+
+  FFmpegH264Decoder* self =
+    reinterpret_cast<FFmpegH264Decoder*>(aCodecContext->opaque);
+
+  switch (aCodecContext->pix_fmt) {
+  case PIX_FMT_YUV420P:
+    return self->AllocateYUV420PVideoBuffer(aCodecContext, aFrame);
+  default:
+    return avcodec_default_get_buffer(aCodecContext, aFrame);
+  }
+}
+
+int
+FFmpegH264Decoder::AllocateYUV420PVideoBuffer(AVCodecContext* aCodecContext,
+                                              AVFrame* aFrame)
+{
+  // Older versions of ffmpeg require that edges be allocated* around* the
+  // actual image.
+  int edgeWidth = avcodec_get_edge_width();
+  int decodeWidth = aCodecContext->width + edgeWidth * 2;
+  int decodeHeight = aCodecContext->height + edgeWidth * 2;
+
+  // Align width and height to possibly speed up decode.
+  int stride_align[AV_NUM_DATA_POINTERS];
+  avcodec_align_dimensions2(aCodecContext, &decodeWidth, &decodeHeight,
+                            stride_align);
+
+  // Get strides for each plane.
+  av_image_fill_linesizes(aFrame->linesize, aCodecContext->pix_fmt,
+                          decodeWidth);
+
+  // Let FFmpeg set up its YUV plane pointers and tell us how much memory we
+  // need.
+  // Note that we're passing |nullptr| here as the base address as we haven't
+  // allocated our image yet. We will adjust |aFrame->data| below.
+  size_t allocSize =
+    av_image_fill_pointers(aFrame->data, aCodecContext->pix_fmt, decodeHeight,
+                           nullptr /* base address */, aFrame->linesize);
+
+  nsRefPtr<Image> image =
+    mImageContainer->CreateImage(ImageFormat::PLANAR_YCBCR);
+  PlanarYCbCrImage* ycbcr = reinterpret_cast<PlanarYCbCrImage*>(image.get());
+  uint8_t* buffer = ycbcr->AllocateAndGetNewBuffer(allocSize);
+
+  if (!buffer) {
+    NS_WARNING("Failed to allocate buffer for FFmpeg video decoding");
+    return -1;
+  }
+
+  // Now that we've allocated our image, we can add its address to the offsets
+  // set by |av_image_fill_pointers| above. We also have to add |edgeWidth|
+  // pixels of padding here.
+  for (uint32_t i = 0; i < AV_NUM_DATA_POINTERS; i++) {
+    // The C planes are half the resolution of the Y plane, so we need to halve
+    // the edge width here.
+    uint32_t planeEdgeWidth = edgeWidth / (i ? 2 : 1);
+
+    // Add buffer offset, plus a horizontal bar |edgeWidth| pixels high at the
+    // top of the frame, plus |edgeWidth| pixels from the left of the frame.
+    aFrame->data[i] += reinterpret_cast<ptrdiff_t>(
+      buffer + planeEdgeWidth * aFrame->linesize[i] + planeEdgeWidth);
+  }
+
+  // Unused, but needs to be non-zero to keep ffmpeg happy.
+  aFrame->type = GECKO_FRAME_TYPE;
+
+  aFrame->extended_data = aFrame->data;
+  aFrame->width = aCodecContext->width;
+  aFrame->height = aCodecContext->height;
+
+  mozilla::layers::PlanarYCbCrData data;
+  PlanarYCbCrDataFromAVFrame(data, aFrame);
+  ycbcr->SetDataNoCopy(data);
+
+  mCurrentImage.swap(image);
+
+  return 0;
+}
+
+nsresult
+FFmpegH264Decoder::Input(mp4_demuxer::MP4Sample* aSample)
+{
+  mTaskQueue->Dispatch(
+    NS_NewRunnableMethodWithArg<nsAutoPtr<mp4_demuxer::MP4Sample> >(
+      this, &FFmpegH264Decoder::DecodeFrame,
+      nsAutoPtr<mp4_demuxer::MP4Sample>(aSample)));
+
+  return NS_OK;
+}
+
+void
+FFmpegH264Decoder::OutputDelayedFrames()
+{
+  while (!mDelayedFrames.IsEmpty()) {
+    mCallback->Output(mDelayedFrames.Pop());
+  }
+}
+
+nsresult
+FFmpegH264Decoder::Drain()
+{
+  // The maximum number of frames that can be waiting to be decoded is
+  // max_b_frames + 1: One P frame and max_b_frames B frames.
+  for (int32_t i = 0; i <= mCodecContext.max_b_frames; i++) {
+    // An empty frame tells FFmpeg to decode the next delayed frame it has in
+    // its queue, if it has any.
+    nsAutoPtr<MP4Sample> empty(new MP4Sample(0 /* dts */, 0 /* cts */,
+                                              0 /* duration */, 0 /* offset */,
+                                              new std::vector<uint8_t>(),
+                                              mp4_demuxer::kVideo, nullptr,
+                                              false));
+
+    nsresult rv = Input(empty.forget());
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  mTaskQueue->Dispatch(
+    NS_NewRunnableMethod(this, &FFmpegH264Decoder::OutputDelayedFrames));
+
+  return NS_OK;
+}
+
+nsresult
+FFmpegH264Decoder::Flush()
+{
+  nsresult rv = FFmpegDataDecoder::Flush();
+  // Even if the above fails we may as well clear our frame queue.
+  mDelayedFrames.Clear();
+  return rv;
+}
+
+FFmpegH264Decoder::~FFmpegH264Decoder() {
+  MOZ_COUNT_DTOR(FFmpegH264Decoder);
+  MOZ_ASSERT(mDelayedFrames.IsEmpty());
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/content/media/fmp4/ffmpeg/FFmpegH264Decoder.h
@@ -0,0 +1,84 @@
+/* -*- 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 __FFmpegH264Decoder_h__
+#define __FFmpegH264Decoder_h__
+
+#include "nsTPriorityQueue.h"
+
+#include "FFmpegDataDecoder.h"
+
+namespace mozilla
+{
+
+class FFmpegH264Decoder : public FFmpegDataDecoder
+{
+  typedef mozilla::layers::Image Image;
+  typedef mozilla::layers::ImageContainer ImageContainer;
+
+public:
+  FFmpegH264Decoder(MediaTaskQueue* aTaskQueue,
+                    MediaDataDecoderCallback* aCallback,
+                    const mp4_demuxer::VideoDecoderConfig &aConfig,
+                    ImageContainer* aImageContainer);
+  virtual ~FFmpegH264Decoder();
+
+  virtual nsresult Init() MOZ_OVERRIDE;
+  virtual nsresult Input(mp4_demuxer::MP4Sample* aSample) MOZ_OVERRIDE;
+  virtual nsresult Drain() MOZ_OVERRIDE;
+  virtual nsresult Flush() MOZ_OVERRIDE;
+
+private:
+  void DecodeFrame(mp4_demuxer::MP4Sample* aSample);
+  void OutputDelayedFrames();
+
+  /**
+   * This method allocates a buffer for FFmpeg's decoder, wrapped in an Image.
+   * Currently it only supports Planar YUV420, which appears to be the only
+   * non-hardware accelerated image format that FFmpeg's H264 decoder is
+   * capable of outputting.
+   */
+  int AllocateYUV420PVideoBuffer(AVCodecContext* aCodecContext,
+                                 AVFrame* aFrame);
+
+  static int AllocateBufferCb(AVCodecContext* aCodecContext, AVFrame* aFrame);
+
+  mp4_demuxer::VideoDecoderConfig mConfig;
+  MediaDataDecoderCallback* mCallback;
+  nsRefPtr<ImageContainer> mImageContainer;
+
+  /**
+   * Pass Image back from the allocator to our DoDecode method.
+   * We *should* return the image back through ffmpeg wrapped in an AVFrame like
+   * we're meant to. However, if avcodec_decode_video2 fails, it returns a
+   * completely different frame from the one holding our image and it will be
+   * leaked.
+   * This could be handled in the future by wrapping our Image in a reference
+   * counted AVBuffer and letting ffmpeg hold the nsAutoPtr<Image>, but
+   * currently we have to support older versions of ffmpeg which lack
+   * refcounting.
+   */
+  nsRefPtr<Image> mCurrentImage;
+
+  struct VideoDataComparator
+  {
+    bool LessThan(VideoData* const &a, VideoData* const &b) const
+    {
+      return a->mTime < b->mTime;
+    }
+  };
+
+  /**
+   * FFmpeg returns frames in DTS order, so we need to keep a heap of the
+   * previous MAX_B_FRAMES + 1 frames (all B frames in a GOP + one P frame)
+   * ordered by PTS to make sure we present video frames in the intended order.
+   */
+  nsTPriorityQueue<VideoData*, VideoDataComparator> mDelayedFrames;
+};
+
+} // namespace mozilla
+
+#endif // __FFmpegH264Decoder_h__
new file mode 100644
--- /dev/null
+++ b/content/media/fmp4/ffmpeg/FFmpegRuntimeLinker.cpp
@@ -0,0 +1,85 @@
+/* -*- 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 <dlfcn.h>
+
+#include "nsDebug.h"
+
+#include "FFmpegRuntimeLinker.h"
+
+// For FFMPEG_LOG
+#include "FFmpegDecoderModule.h"
+
+#define NUM_ELEMENTS(X) (sizeof(X) / sizeof((X)[0]))
+
+#define LIBAVCODEC 0
+#define LIBAVFORMAT 1
+#define LIBAVUTIL 2
+
+namespace mozilla
+{
+
+FFmpegRuntimeLinker::LinkStatus FFmpegRuntimeLinker::sLinkStatus =
+  LinkStatus_INIT;
+
+static const char * const sLibNames[] = {
+  "libavcodec.so.53", "libavformat.so.53", "libavutil.so.51",
+};
+
+void* FFmpegRuntimeLinker::sLinkedLibs[NUM_ELEMENTS(sLibNames)] = {
+  nullptr, nullptr, nullptr
+};
+
+#define AV_FUNC(lib, func) typeof(func) func;
+#include "FFmpegFunctionList.h"
+#undef AV_FUNC
+
+/* static */ bool
+FFmpegRuntimeLinker::Link()
+{
+  if (sLinkStatus) {
+    return sLinkStatus == LinkStatus_SUCCEEDED;
+  }
+
+  for (uint32_t i = 0; i < NUM_ELEMENTS(sLinkedLibs); i++) {
+    if (!(sLinkedLibs[i] = dlopen(sLibNames[i], RTLD_NOW | RTLD_LOCAL))) {
+      NS_WARNING("Couldn't link ffmpeg libraries.");
+      goto fail;
+    }
+  }
+
+#define AV_FUNC(lib, func)                                                     \
+  func = (typeof(func))dlsym(sLinkedLibs[lib], #func);                         \
+  if (!func) {                                                                 \
+    NS_WARNING("Couldn't load FFmpeg function " #func ".");                    \
+    goto fail;                                                                 \
+  }
+#include "FFmpegFunctionList.h"
+#undef AV_FUNC
+
+  sLinkStatus = LinkStatus_SUCCEEDED;
+  return true;
+
+fail:
+  Unlink();
+
+  sLinkStatus = LinkStatus_FAILED;
+  return false;
+}
+
+/* static */ void
+FFmpegRuntimeLinker::Unlink()
+{
+  FFMPEG_LOG("Unlinking ffmpeg libraries.");
+  for (uint32_t i = 0; i < NUM_ELEMENTS(sLinkedLibs); i++) {
+    if (sLinkedLibs[i]) {
+      dlclose(sLinkedLibs[i]);
+      sLinkedLibs[i] = nullptr;
+    }
+  }
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/content/media/fmp4/ffmpeg/FFmpegRuntimeLinker.h
@@ -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/. */
+
+#ifndef __FFmpegRuntimeLinker_h__
+#define __FFmpegRuntimeLinker_h__
+
+extern "C" {
+#pragma GCC visibility push(default)
+#include <libavcodec/avcodec.h>
+#include <libavformat/avformat.h>
+#include <libavutil/imgutils.h>
+#pragma GCC visibility pop
+}
+
+#include "nsAutoPtr.h"
+
+namespace mozilla
+{
+
+class FFmpegRuntimeLinker
+{
+public:
+  static bool Link();
+  static void Unlink();
+
+private:
+  static void* sLinkedLibs[];
+
+  static enum LinkStatus {
+    LinkStatus_INIT = 0,
+    LinkStatus_FAILED,
+    LinkStatus_SUCCEEDED
+  } sLinkStatus;
+};
+
+#define AV_FUNC(lib, func) extern typeof(func)* func;
+#include "FFmpegFunctionList.h"
+#undef AV_FUNC
+}
+
+#endif // __FFmpegRuntimeLinker_h__
--- a/layout/build/moz.build
+++ b/layout/build/moz.build
@@ -124,9 +124,14 @@ if CONFIG['MOZ_B2G_BT']:
         '/dom/bluetooth',
     ]
 
 if CONFIG['MOZ_WEBSPEECH']:
     LOCAL_INCLUDES += [
         '/content/media/webspeech/synth',
     ]
 
+if CONFIG['MOZ_FFMPEG']:
+    LOCAL_INCLUDES += [
+        '/content/media/fmp4/ffmpeg/include',
+    ]
+
 FINAL_LIBRARY = 'xul'
--- a/layout/build/nsLayoutStatics.cpp
+++ b/layout/build/nsLayoutStatics.cpp
@@ -91,16 +91,20 @@
 #ifdef MOZ_WMF
 #include "WMFDecoder.h"
 #endif
 
 #ifdef MOZ_GSTREAMER
 #include "GStreamerFormatHelper.h"
 #endif
 
+#ifdef MOZ_FFMPEG
+#include "FFmpegRuntimeLinker.h"
+#endif
+
 #include "AudioStream.h"
 #include "Latency.h"
 #include "WebAudioUtils.h"
 
 #ifdef MOZ_WIDGET_GONK
 #include "nsVolumeService.h"
 #include "SpeakerManagerService.h"
 using namespace mozilla::system;
@@ -358,16 +362,20 @@ nsLayoutStatics::Shutdown()
 #ifdef MOZ_MEDIA_PLUGINS
   MediaPluginHost::Shutdown();
 #endif
 
 #ifdef MOZ_GSTREAMER
   GStreamerFormatHelper::Shutdown();
 #endif
 
+#ifdef MOZ_FFMPEG
+  FFmpegRuntimeLinker::Unlink();
+#endif
+
   AudioStream::ShutdownLibrary();
   AsyncLatencyLogger::ShutdownLogger();
   WebAudioUtils::Shutdown();
 
 #ifdef MOZ_WMF
   WMFDecoder::UnloadDLLs();
 #endif
 
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -191,16 +191,17 @@ pref("media.windows-media-foundation.ena
 pref("media.windows-media-foundation.use-dxva", true);
 pref("media.windows-media-foundation.play-stand-alone", 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);
 // Denotes that the fragmented MP4 parser can be created by <video> elements.
 // This is for testing, since the parser can't yet handle non-fragmented MP4,
 // so it will fail to play most MP4 files.
 pref("media.fragmented-mp4.exposed", false);
 // Specifies whether the fragmented MP4 parser uses a test decoder that
 // just outputs blank frames/audio instead of actually decoding. The blank
 // decoder works on all platforms.
 pref("media.fragmented-mp4.use-blank-decoder", false);