Bug 886196 - MP4 demuxing using Chromium's MP4 demuxer. No decoding yet. r=kinetik
authorChris Pearce <cpearce@mozilla.com>
Thu, 21 Nov 2013 10:04:33 +1300
changeset 172219 426dee2867bc71141d08c0fa5b9d237a39f1c320
parent 172218 9d06b8bfb5caece9fc85fde5879876cc84903347
child 172220 ca0986418ca02d93343096402e24e6fbf279a525
push id445
push userffxbld
push dateMon, 10 Mar 2014 22:05:19 +0000
treeherdermozilla-release@dc38b741b04e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskinetik
bugs886196
milestone28.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 886196 - MP4 demuxing using Chromium's MP4 demuxer. No decoding yet. r=kinetik
content/media/fmp4/MP4Decoder.cpp
content/media/fmp4/MP4Decoder.h
content/media/fmp4/MP4Reader.cpp
content/media/fmp4/MP4Reader.h
content/media/fmp4/PlatformDecoderModule.cpp
content/media/fmp4/PlatformDecoderModule.h
content/media/fmp4/demuxer/basictypes.h
content/media/fmp4/moz.build
new file mode 100644
--- /dev/null
+++ b/content/media/fmp4/MP4Decoder.cpp
@@ -0,0 +1,78 @@
+/* -*- 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 "MP4Decoder.h"
+#include "MP4Reader.h"
+#include "MediaDecoderStateMachine.h"
+#include "mozilla/Preferences.h"
+
+namespace mozilla {
+
+MediaDecoderStateMachine* MP4Decoder::CreateStateMachine()
+{
+  return new MediaDecoderStateMachine(this, new MP4Reader(this));
+}
+
+bool
+MP4Decoder::GetSupportedCodecs(const nsACString& aType,
+                               char const *const ** aCodecList)
+{
+  if (!IsEnabled()) {
+    return false;
+  }
+
+  // AAC in M4A.
+  static char const *const aacAudioCodecs[] = {
+    "mp4a.40.2",    // AAC-LC
+    // TODO: AAC-HE ?
+    nullptr
+  };
+  if (aType.EqualsASCII("audio/mp4") ||
+      aType.EqualsASCII("audio/x-m4a")) {
+    if (aCodecList) {
+      *aCodecList = aacAudioCodecs;
+    }
+    return true;
+  }
+
+  // H.264 + AAC in MP4.
+  static char const *const h264Codecs[] = {
+    "avc1.42E01E",  // H.264 Constrained Baseline Profile Level 3.0
+    "avc1.42001E",  // H.264 Baseline Profile Level 3.0
+    "avc1.58A01E",  // H.264 Extended Profile Level 3.0
+    "avc1.4D401E",  // H.264 Main Profile Level 3.0
+    "avc1.64001E",  // H.264 High Profile Level 3.0
+    "avc1.64001F",  // H.264 High Profile Level 3.1
+    "mp4a.40.2",    // AAC-LC
+    // TODO: There must be more profiles here?
+    nullptr
+  };
+  if (aType.EqualsASCII("video/mp4")) {
+    if (aCodecList) {
+      *aCodecList = h264Codecs;
+    }
+    return true;
+  }
+
+  return false;
+}
+
+static bool
+HavePlatformMPEGDecoders()
+{
+  return false;
+}
+
+/* static */
+bool
+MP4Decoder::IsEnabled()
+{
+  return HavePlatformMPEGDecoders() &&
+         Preferences::GetBool("media.fragmented-mp4.enabled");
+}
+
+} // namespace mozilla
+
new file mode 100644
--- /dev/null
+++ b/content/media/fmp4/MP4Decoder.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(MP4Decoder_h_)
+#define MP4Decoder_h_
+
+#include "MediaDecoder.h"
+
+namespace mozilla {
+
+// Decoder that uses a bundled MP4 demuxer and platform decoders to play MP4.
+class MP4Decoder : public MediaDecoder
+{
+public:
+
+  virtual MediaDecoder* Clone() {
+    if (!IsEnabled()) {
+      return nullptr;
+    }
+    return new MP4Decoder();
+  }
+
+  virtual MediaDecoderStateMachine* CreateStateMachine();
+
+  // Returns true if aType is a MIME type that we can render with the
+  // a MP4 platform decoder backend. If aCodecList is non null,
+  // it is filled with a (static const) null-terminated list of strings
+  // denoting the codecs we'll playback.
+  static bool GetSupportedCodecs(const nsACString& aType,
+                                 char const *const ** aCodecList);
+
+  // Returns true if the MP4 backend is preffed on, and we're running on a
+  // platform that is likely to have decoders for the contained formats.
+  static bool IsEnabled();
+};
+
+} // namespace mozilla
+
+#endif
new file mode 100644
--- /dev/null
+++ b/content/media/fmp4/MP4Reader.cpp
@@ -0,0 +1,393 @@
+/* -*- 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 "MP4Reader.h"
+#include "MediaResource.h"
+#include "mp4_demuxer/mp4_demuxer.h"
+#include "mp4_demuxer/Streams.h"
+#include "nsSize.h"
+#include "VideoUtils.h"
+#include "mozilla/dom/HTMLMediaElement.h"
+#include "ImageContainer.h"
+#include "Layers.h"
+
+using mozilla::layers::Image;
+using mozilla::layers::LayerManager;
+using mozilla::layers::LayersBackend;
+
+#ifdef PR_LOGGING
+PRLogModuleInfo* GetDemuxerLog() {
+  static PRLogModuleInfo* log = nullptr;
+  if (!log) {
+    log = PR_NewLogModule("MP4Demuxer");
+  }
+  return log;
+}
+#define LOG(...) PR_LOG(GetDemuxerLog(), PR_LOG_DEBUG, (__VA_ARGS__))
+#else
+#define LOG(...)
+#endif
+
+using namespace mp4_demuxer;
+
+namespace mozilla {
+
+// Uncomment to enable verbose per-sample logging.
+//#define LOG_SAMPLE_DECODE 1
+
+class MP4Stream : public mp4_demuxer::Stream {
+public:
+
+  MP4Stream(MediaResource* aResource)
+    : mResource(aResource)
+  {
+    MOZ_COUNT_CTOR(MP4Stream);
+    MOZ_ASSERT(aResource);
+  }
+  ~MP4Stream() {
+    MOZ_COUNT_DTOR(MP4Stream);
+  }
+
+  virtual bool ReadAt(int64_t aOffset,
+                      uint8_t* aBuffer,
+                      uint32_t aCount,
+                      uint32_t* aBytesRead) MOZ_OVERRIDE {
+    uint32_t sum = 0;
+    do {
+      uint32_t offset = aOffset + sum;
+      char* buffer = reinterpret_cast<char*>(aBuffer + sum);
+      uint32_t toRead = aCount - sum;
+      uint32_t bytesRead = 0;
+      nsresult rv = mResource->ReadAt(offset, buffer, toRead, &bytesRead);
+      if (NS_FAILED(rv)) {
+        return false;
+      }
+      sum += bytesRead;
+    } while (sum < aCount);
+    *aBytesRead = sum;
+    return true;
+  }
+
+  virtual int64_t Length() const MOZ_OVERRIDE {
+    return mResource->GetLength();
+  }
+
+private:
+  RefPtr<MediaResource> mResource;
+};
+
+MP4Reader::MP4Reader(AbstractMediaDecoder* aDecoder)
+  : MediaDecoderReader(aDecoder),
+    mLayersBackendType(layers::LAYERS_NONE),
+    mHasAudio(false),
+    mHasVideo(false)
+{
+  MOZ_COUNT_CTOR(MP4Reader);
+}
+
+MP4Reader::~MP4Reader()
+{
+  MOZ_COUNT_DTOR(MP4Reader);
+}
+
+nsresult
+MP4Reader::Init(MediaDecoderReader* aCloneDonor)
+{
+  MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread.");
+  mMP4Stream = new MP4Stream(mDecoder->GetResource());
+  mDemuxer = new MP4Demuxer(mMP4Stream);
+
+  mPlatform = PlatformDecoderModule::Create();
+  NS_ENSURE_TRUE(mPlatform, NS_ERROR_FAILURE);
+
+  if (IsVideoContentType(mDecoder->GetResource()->GetContentType())) {
+    // Extract the layer manager backend type so that platform decoders
+    // can determine whether it's worthwhile using hardware accelerated
+    // video decoding.
+    MediaDecoderOwner* owner = mDecoder->GetOwner();
+    NS_ENSURE_TRUE(owner, NS_ERROR_FAILURE);
+
+    dom::HTMLMediaElement* element = owner->GetMediaElement();
+    NS_ENSURE_TRUE(element, NS_ERROR_FAILURE);
+
+    nsRefPtr<LayerManager> layerManager =
+      nsContentUtils::LayerManagerForDocument(element->OwnerDoc());
+    NS_ENSURE_TRUE(layerManager, NS_ERROR_FAILURE);
+
+    mLayersBackendType = layerManager->GetBackendType();
+  }
+
+  return NS_OK;
+}
+
+nsresult
+MP4Reader::ReadMetadata(MediaInfo* aInfo,
+                        MetadataTags** aTags)
+{
+  bool ok = mDemuxer->Init();
+  NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE);
+
+  mInfo.mAudio.mHasAudio = mHasAudio = mDemuxer->HasAudio();
+  if (mHasAudio) {
+    const AudioDecoderConfig& config = mDemuxer->AudioConfig();
+    mInfo.mAudio.mRate = config.samples_per_second();
+    mInfo.mAudio.mChannels = ChannelLayoutToChannelCount(config.channel_layout());
+    mAudioDecoder = mPlatform->CreateAudioDecoder(mInfo.mAudio.mChannels,
+                                                  mInfo.mAudio.mRate,
+                                                  config.bits_per_channel(),
+                                                  config.extra_data(),
+                                                  config.extra_data_size());
+    NS_ENSURE_TRUE(mAudioDecoder != nullptr, NS_ERROR_FAILURE);
+  }
+
+  mInfo.mVideo.mHasVideo = mHasVideo = mDemuxer->HasVideo();
+  if (mHasVideo) {
+    const VideoDecoderConfig& config = mDemuxer->VideoConfig();
+    IntSize sz = config.natural_size();
+    mInfo.mVideo.mDisplay = nsIntSize(sz.width(), sz.height());
+
+    mVideoDecoder = mPlatform->CreateVideoDecoder(mLayersBackendType,
+                                                  mDecoder->GetImageContainer());
+    NS_ENSURE_TRUE(mVideoDecoder != nullptr, NS_ERROR_FAILURE);
+  }
+
+  // Get the duration, and report it to the decoder if we have it.
+  Microseconds duration = mDemuxer->Duration();
+  if (duration != -1) {
+    ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+    mDecoder->SetMediaDuration(duration);
+  }
+  // We can seek if we get a duration *and* the reader reports that it's
+  // seekable.
+  if (!mDemuxer->CanSeek()) {
+    mDecoder->SetMediaSeekable(false);
+  }
+
+  *aInfo = mInfo;
+  *aTags = nullptr;
+
+  return NS_OK;
+}
+
+bool
+MP4Reader::HasAudio()
+{
+  return mHasAudio;
+}
+
+bool
+MP4Reader::HasVideo()
+{
+  return mHasVideo;
+}
+
+MP4SampleQueue&
+MP4Reader::SampleQueue(TrackType aTrack)
+{
+  MOZ_ASSERT(aTrack == kAudio || aTrack == kVideo);
+  return (aTrack == kAudio) ? mCompressedAudioQueue
+                            : mCompressedVideoQueue;
+}
+
+MediaDataDecoder*
+MP4Reader::Decoder(mp4_demuxer::TrackType aTrack)
+{
+  MOZ_ASSERT(aTrack == kAudio || aTrack == kVideo);
+  return (aTrack == kAudio) ? mAudioDecoder
+                            : mVideoDecoder;
+}
+
+MP4Sample*
+MP4Reader::PopSample(TrackType aTrack)
+{
+  // Unfortunately the demuxer outputs in the order samples appear in the
+  // media, not on a per stream basis. We cache the samples we get from
+  // streams other than the one we want.
+  MP4SampleQueue& sampleQueue = SampleQueue(aTrack);
+  while (sampleQueue.empty()) {
+    nsAutoPtr<MP4Sample> sample;
+    bool eos = false;
+    bool ok = mDemuxer->Demux(&sample, &eos);
+    if (!ok || eos) {
+      MOZ_ASSERT(!sample);
+      return nullptr;
+    }
+    MOZ_ASSERT(sample);
+    MP4Sample* s = sample.forget();
+    SampleQueue(s->type).push_back(s);
+  }
+  MOZ_ASSERT(!sampleQueue.empty());
+  MP4Sample* sample = sampleQueue.front();
+  sampleQueue.pop_front();
+  return sample;
+}
+
+bool
+MP4Reader::Decode(TrackType aTrack, nsAutoPtr<MediaData>& aOutData)
+{
+  MP4SampleQueue& sampleQueue = SampleQueue(aTrack);
+  MediaDataDecoder* decoder = Decoder(aTrack);
+
+  MOZ_ASSERT(decoder);
+
+  // Loop until we hit a return condition; we produce samples, or hit an error.
+  while (true) {
+    DecoderStatus status = decoder->Output(aOutData);
+    if (status == DECODE_STATUS_OK) {
+      MOZ_ASSERT(aOutData);
+      return true;
+    }
+    // |aOutData| should only be non-null in success case.
+    MOZ_ASSERT(!aOutData);
+
+    if (status == DECODE_STATUS_ERROR) {
+      return false;
+    }
+
+    if (status == DECODE_STATUS_NEED_MORE_INPUT) {
+      // We need to push more data from the demuxer into the decoder.
+      // Now loop back and try to extract output again.
+      nsAutoPtr<MP4Sample> compressed;
+      do {
+        compressed = PopSample(aTrack);
+        if (!compressed) {
+          // EOS, or error. Let the state machine know there are no more
+          // frames coming.
+          return false;
+        }
+        const std::vector<uint8_t>* data = compressed->data;
+        status = decoder->Input(&data->front(),
+                                data->size(),
+                                compressed->decode_timestamp,
+                                compressed->composition_timestamp,
+                                compressed->byte_offset);
+      } while (status == DECODE_STATUS_OK);
+      if (status == DECODE_STATUS_NOT_ACCEPTING) {
+        // Decoder should now be able to produce an output.
+        SampleQueue(aTrack).push_front(compressed.forget());
+        continue;
+      }
+      LOG("MP4Reader decode failure. track=%d status=%d\n", aTrack, status);
+      return false;
+    } else {
+      LOG("MP4Reader unexpected error. track=%d status=%d\n", aTrack, status);
+      return false;
+    }
+  }
+}
+
+bool
+MP4Reader::DecodeAudioData()
+{
+  MOZ_ASSERT(mHasAudio && mPlatform && mAudioDecoder);
+  nsAutoPtr<MediaData> audio;
+  bool ok = Decode(kAudio, audio);
+  if (ok && audio && audio->mType == MediaData::AUDIO_SAMPLES) {
+#ifdef LOG_SAMPLE_DECODE
+    LOG("DecodeAudioData time=%lld dur=%lld", audio->mTime, audio->mDuration);
+#endif
+    mAudioQueue.Push(static_cast<AudioData*>(audio.forget()));
+  }
+  return ok;
+}
+
+bool
+MP4Reader::SkipVideoDemuxToNextKeyFrame(int64_t aTimeThreshold, uint32_t& parsed)
+{
+  MOZ_ASSERT(mVideoDecoder);
+
+  // Purge the current decoder's state.
+  mVideoDecoder->Flush();
+
+  // Loop until we reach the next keyframe after the threshold.
+  while (true) {
+    nsAutoPtr<MP4Sample> compressed(PopSample(kVideo));
+    if (!compressed) {
+      // EOS, or error. Let the state machine know.
+      return false;
+    }
+    parsed++;
+    if (!compressed->is_sync_point ||
+        compressed->composition_timestamp < aTimeThreshold) {
+      continue;
+    }
+    mCompressedVideoQueue.push_front(compressed.forget());
+    break;
+  }
+
+  return true;
+}
+
+bool
+MP4Reader::DecodeVideoFrame(bool &aKeyframeSkip,
+                            int64_t aTimeThreshold)
+{
+  // Record number of frames decoded and parsed. Automatically update the
+  // stats counters using the AutoNotifyDecoded stack-based class.
+  uint32_t parsed = 0, decoded = 0;
+  AbstractMediaDecoder::AutoNotifyDecoded autoNotify(mDecoder, parsed, decoded);
+
+  MOZ_ASSERT(mHasVideo && mPlatform && mVideoDecoder);
+
+  if (aKeyframeSkip) {
+    bool ok = SkipVideoDemuxToNextKeyFrame(aTimeThreshold, parsed);
+    if (!ok) {
+      NS_WARNING("Failed to skip demux up to next keyframe");
+      return false;
+    }
+    aKeyframeSkip = false;
+  }
+
+  nsAutoPtr<MediaData> data;
+  bool ok = Decode(kVideo, data);
+  MOZ_ASSERT(!data || data->mType == MediaData::VIDEO_FRAME);
+  if (ok && data) {
+    parsed++;
+    if (data->mTime < aTimeThreshold) {
+      // Skip frame, it's too late to be displayed.
+      return true;
+    }
+    decoded++;
+    VideoData* video = static_cast<VideoData*>(data.forget());
+#ifdef LOG_SAMPLE_DECODE
+    LOG("DecodeVideoData time=%lld dur=%lld", video->mTime, video->mDuration);
+#endif
+    mVideoQueue.Push(video);
+  }
+  return ok;
+}
+
+nsresult
+MP4Reader::Seek(int64_t aTime,
+                int64_t aStartTime,
+                int64_t aEndTime,
+                int64_t aCurrentTime)
+{
+  if (!mDemuxer->CanSeek()) {
+    return NS_ERROR_FAILURE;
+  }
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void
+MP4Reader::OnDecodeThreadStart()
+{
+  MOZ_ASSERT(!NS_IsMainThread(), "Must not be on main thread.");
+  MOZ_ASSERT(mDecoder->OnDecodeThread(), "Should be on decode thread.");
+  MOZ_ASSERT(mPlatform);
+  mPlatform->OnDecodeThreadStart();
+}
+
+void
+MP4Reader::OnDecodeThreadFinish()
+{
+  MOZ_ASSERT(!NS_IsMainThread(), "Must not be on main thread.");
+  MOZ_ASSERT(mDecoder->OnDecodeThread(), "Should be on decode thread.");
+  MOZ_ASSERT(mPlatform);
+  mPlatform->OnDecodeThreadFinish();
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/content/media/fmp4/MP4Reader.h
@@ -0,0 +1,88 @@
+/* -*- 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(MP4Reader_h_)
+#define MP4Reader_h_
+
+#include "MediaDecoderReader.h"
+#include "nsAutoPtr.h"
+#include "PlatformDecoderModule.h"
+#include "mp4_demuxer/mp4_demuxer.h"
+#include "mp4_demuxer/box_definitions.h"
+
+#include <deque>
+
+namespace mozilla {
+
+namespace dom {
+class TimeRanges;
+}
+
+typedef std::deque<mp4_demuxer::MP4Sample*> MP4SampleQueue;
+
+class MP4Stream;
+
+class MP4Reader : public MediaDecoderReader
+{
+public:
+  MP4Reader(AbstractMediaDecoder* aDecoder);
+
+  virtual ~MP4Reader();
+
+  virtual nsresult Init(MediaDecoderReader* aCloneDonor) MOZ_OVERRIDE;
+
+  virtual bool DecodeAudioData() MOZ_OVERRIDE;
+  virtual bool DecodeVideoFrame(bool &aKeyframeSkip,
+                                int64_t aTimeThreshold) MOZ_OVERRIDE;
+
+  virtual bool HasAudio() MOZ_OVERRIDE;
+  virtual bool HasVideo() MOZ_OVERRIDE;
+
+  virtual nsresult ReadMetadata(MediaInfo* aInfo,
+                                MetadataTags** aTags) MOZ_OVERRIDE;
+
+  virtual nsresult Seek(int64_t aTime,
+                        int64_t aStartTime,
+                        int64_t aEndTime,
+                        int64_t aCurrentTime) MOZ_OVERRIDE;
+
+  virtual void OnDecodeThreadStart() MOZ_OVERRIDE;
+  virtual void OnDecodeThreadFinish() MOZ_OVERRIDE;
+
+private:
+
+  MP4SampleQueue& SampleQueue(mp4_demuxer::TrackType aTrack);
+
+  // Blocks until the demuxer produces an sample of specified type.
+  // Returns nullptr on error on EOS. Caller must delete sample.
+  mp4_demuxer::MP4Sample* PopSample(mp4_demuxer::TrackType aTrack);
+
+  bool Decode(mp4_demuxer::TrackType aTrack,
+              nsAutoPtr<MediaData>& aOutData);
+
+  MediaDataDecoder* Decoder(mp4_demuxer::TrackType aTrack);
+
+  bool SkipVideoDemuxToNextKeyFrame(int64_t aTimeThreshold, uint32_t& parsed);
+
+  nsAutoPtr<mp4_demuxer::MP4Demuxer> mDemuxer;
+  nsAutoPtr<MP4Stream> mMP4Stream;
+  nsAutoPtr<PlatformDecoderModule> mPlatform;
+  nsAutoPtr<MediaDataDecoder> mVideoDecoder;
+  nsAutoPtr<MediaDataDecoder> mAudioDecoder;
+
+  MP4SampleQueue mCompressedAudioQueue;
+  MP4SampleQueue mCompressedVideoQueue;
+
+  layers::LayersBackend mLayersBackendType;
+
+  bool mHasAudio;
+  bool mHasVideo;
+
+};
+
+} // namespace mozilla
+
+#endif
new file mode 100644
--- /dev/null
+++ b/content/media/fmp4/PlatformDecoderModule.cpp
@@ -0,0 +1,29 @@
+/* -*- 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 "PlatformDecoderModule.h"
+
+namespace mozilla {
+
+/* static */
+PlatformDecoderModule*
+PlatformDecoderModule::Create()
+{
+  // TODO: Create appropriate PlatformDecoderModule...
+  return nullptr;
+}
+
+void
+PlatformDecoderModule::OnDecodeThreadStart()
+{
+}
+
+void
+PlatformDecoderModule::OnDecodeThreadFinish()
+{
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/content/media/fmp4/PlatformDecoderModule.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/. */
+
+#if !defined(PlatformDecoderModule_h_)
+#define PlatformDecoderModule_h_
+
+#include "MediaDecoderReader.h"
+#include "mozilla/layers/LayersTypes.h"
+
+namespace mozilla {
+
+namespace layers {
+class ImageContainer;
+}
+
+typedef int64_t Microseconds;
+
+enum DecoderStatus {
+  DECODE_STATUS_NOT_ACCEPTING, // Can't accept input at this time.
+  DECODE_STATUS_NEED_MORE_INPUT, // Nothing in pipeline to produce output with at this time.
+  DECODE_STATUS_OK,
+  DECODE_STATUS_ERROR
+};
+
+class MediaDataDecoder {
+public:
+  virtual ~MediaDataDecoder() {};
+
+  virtual nsresult Shutdown() = 0;
+
+  // Returns true if future decodes may produce output, or false
+  // on end of segment.
+  // Inserts data into the decoder's pipeline.
+  virtual DecoderStatus Input(const uint8_t* aData,
+                              uint32_t aLength,
+                              Microseconds aDTS,
+                              Microseconds aPTS,
+                              int64_t aOffsetInStream) = 0;
+
+  // Blocks until decoded sample is produced by the deoder.
+  virtual DecoderStatus Output(nsAutoPtr<MediaData>& aOutData) = 0;
+
+  virtual DecoderStatus Flush() = 0;
+};
+
+class PlatformDecoderModule {
+public:
+
+  // Creates the appropriate PlatformDecoderModule for this platform.
+  // Caller is responsible for deleting this instance. It's safe to have
+  // multiple PlatformDecoderModules alive at the same time.
+  // There is one PlatformDecoderModule's created per media decoder.
+  static PlatformDecoderModule* Create();
+
+  // Called when the decoders have shutdown. Main thread only.
+  virtual nsresult Shutdown() = 0;
+
+  // TODO: Parameters for codec type.
+  // Decode thread.
+  virtual MediaDataDecoder* CreateVideoDecoder(layers::LayersBackend aLayersBackend,
+                                               layers::ImageContainer* aImageContainer) = 0;
+
+  // Decode thread.
+  virtual MediaDataDecoder* CreateAudioDecoder(uint32_t aChannelCount,
+                                               uint32_t aSampleRate,
+                                               uint16_t aBitsPerSample,
+                                               const uint8_t* aUserData,
+                                               uint32_t aUserDataLength) = 0;
+
+  // Platform decoders can override these. Base implementation does nothing.
+  virtual void OnDecodeThreadStart();
+  virtual void OnDecodeThreadFinish();
+
+  virtual ~PlatformDecoderModule() {}
+protected:
+  PlatformDecoderModule() {}
+};
+
+} // namespace mozilla
+
+#endif
--- a/content/media/fmp4/demuxer/basictypes.h
+++ b/content/media/fmp4/demuxer/basictypes.h
@@ -2,16 +2,21 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
 #ifndef MEDIA_MP4_BASIC_TYPES_H_
 #define MEDIA_MP4_BASIC_TYPES_H_
 
 #include <iostream>
 #include <stdint.h>
+#include "prlog.h"
+
+#ifdef PR_LOGGING
+PRLogModuleInfo* GetDemuxerLog();
+#endif
 
 namespace mp4_demuxer {
 
 // Define to enable logging.
 //#define LOG_DEMUXER
 
 #define kint32max std::numeric_limits<int32_t>::max()
 #define kuint64max std::numeric_limits<uint64_t>::max()
@@ -47,17 +52,21 @@ namespace mp4_demuxer {
         DMX_LOG("Failure while parsing MP4: %s %s:%d\n", #x, __FILE__, __LINE__); \
         return false; \
       } \
     } while (0)
 
 #define arraysize(f) (sizeof(f) / sizeof(*f))
 
 #ifdef LOG_DEMUXER
-#define DMX_LOG(...) printf(__VA_ARGS__)
+#ifdef PR_LOGGING
+#define DMX_LOG(...) PR_LOG(GetDemuxerLog(), PR_LOG_DEBUG, (__VA_ARGS__))
+#else
+#define DMX_LOG(...) 0
+#endif
 #else
 // define DMX_LOG as 0, so that if(condition){DMX_LOG(...)} branches don't elicit
 // a warning-as-error.
 #define DMX_LOG(...) 0
 #endif
 
 // A macro to disallow the evil copy constructor and operator= functions
 // This should be used in the private: declarations for a class
--- a/content/media/fmp4/moz.build
+++ b/content/media/fmp4/moz.build
@@ -1,15 +1,18 @@
 # -*- 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 += [
+    'MP4Decoder.h',
+    'MP4Reader.h',
+    'PlatformDecoderModule.h',
 ]
 
 EXPORTS.mp4_demuxer += [
     'demuxer/aac.h',
     'demuxer/audio_decoder_config.h',
     'demuxer/avc.h',
     'demuxer/basictypes.h',
     'demuxer/bit_reader.h',
@@ -19,31 +22,34 @@ EXPORTS.mp4_demuxer += [
     'demuxer/channel_layout.h',
     'demuxer/decrypt_config.h',
     'demuxer/es_descriptor.h',
     'demuxer/fourccs.h',
     'demuxer/mp4_demuxer.h',
     'demuxer/Streams.h',
     'demuxer/track_run_iterator.h',
     'demuxer/video_decoder_config.h',
-    'demuxer/video_util.h'
+    'demuxer/video_util.h',
 ]
 
 SOURCES += [
     'demuxer/aac.cc',
     'demuxer/audio_decoder_config.cc',
     'demuxer/avc.cc',
     'demuxer/bit_reader.cc',
     'demuxer/box_definitions.cc',
     'demuxer/box_reader.cc',
     'demuxer/cenc.cc',
     'demuxer/channel_layout.cc',
     'demuxer/decrypt_config.cc',
     'demuxer/es_descriptor.cc',
     'demuxer/mp4_demuxer.cc',
     'demuxer/track_run_iterator.cc',
     'demuxer/video_decoder_config.cc',
-    'demuxer/video_util.cc'
+    'demuxer/video_util.cc',
+    'MP4Decoder.cpp',
+    'MP4Reader.cpp',
+    'PlatformDecoderModule.cpp',
 ]
 
 FINAL_LIBRARY = 'gklayout'
 
 FAIL_ON_WARNINGS = True