Bug 566245 - WebM/VP8 decoder backend. r=kinetik
authorChris Double <chris.double@double.co.nz>
Wed, 09 Jun 2010 11:31:27 +1200
changeset 43340 d2d79b60f068ecde2a833367ed2d9044efb92f92
parent 43339 e196bc1ce3c50425ba7ce9650f3d7e4a2b9ec328
child 43341 4461a59d1b7a182e01b052d7f2649264dc4c249c
push id13661
push usercpearce@mozilla.com
push dateTue, 08 Jun 2010 23:38:27 +0000
treeherdermozilla-central@1a95c30ec47b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskinetik
bugs566245
milestone1.9.3a5pre
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 566245 - WebM/VP8 decoder backend. r=kinetik
content/media/Makefile.in
content/media/nsBuiltinDecoder.h
content/media/nsBuiltinDecoderReader.cpp
content/media/nsBuiltinDecoderReader.h
content/media/nsBuiltinDecoderStateMachine.cpp
content/media/webm/Makefile.in
content/media/webm/nsWebMDecoder.cpp
content/media/webm/nsWebMDecoder.h
content/media/webm/nsWebMReader.cpp
content/media/webm/nsWebMReader.h
--- a/content/media/Makefile.in
+++ b/content/media/Makefile.in
@@ -76,16 +76,20 @@ endif
 ifdef MOZ_OGG
 PARALLEL_DIRS += ogg
 endif
 
 ifdef MOZ_WAVE
 PARALLEL_DIRS += wave
 endif
 
+ifdef MOZ_WEBM
+PARALLEL_DIRS += webm
+endif
+
 ifdef ENABLE_TESTS
 PARALLEL_DIRS += test
 endif
 
 FORCE_STATIC_LIB = 1
 
 include $(topsrcdir)/config/rules.mk
 
--- a/content/media/nsBuiltinDecoder.h
+++ b/content/media/nsBuiltinDecoder.h
@@ -249,18 +249,19 @@ public:
 
   virtual void Shutdown() = 0;
 
   // Called from the main thread to get the duration. The decoder monitor
   // must be obtained before calling this. It is in units of milliseconds.
   virtual PRInt64 GetDuration() = 0;
 
   // Called from the main thread to set the duration of the media resource
-  // if it is able to be obtained via HTTP headers. The decoder monitor
-  // must be obtained before calling this.
+  // if it is able to be obtained via HTTP headers. Called from the 
+  // state machine thread to set the duration if it is obtained from the
+  // media metadata. The decoder monitor must be obtained before calling this.
   virtual void SetDuration(PRInt64 aDuration) = 0;
 
   // Functions used by assertions to ensure we're calling things
   // on the appropriate threads.
   virtual PRBool OnDecodeThread() = 0;
 
   virtual nsHTMLMediaElement::NextFrameStatus GetNextFrameStatus() = 0;
 
--- a/content/media/nsBuiltinDecoderReader.cpp
+++ b/content/media/nsBuiltinDecoderReader.cpp
@@ -294,16 +294,21 @@ VideoData* nsBuiltinDecoderReader::FindS
   PRInt64 startTime = PR_MIN(videoStartTime, audioStartTime);
   if (startTime != PR_INT64_MAX) {
     aOutStartTime = startTime;
   }
 
   return videoData;
 }
 
+PRInt64 nsBuiltinDecoderReader::FindEndTime(PRInt64 aEndOffset)
+{
+  return -1;
+}
+
 template<class Data>
 Data* nsBuiltinDecoderReader::DecodeToFirstData(DecodeFn aDecodeFn,
                                                 MediaQueue<Data>& aQueue)
 {
   PRBool eof = PR_FALSE;
   while (!eof && aQueue.GetSize() == 0) {
     {
       MonitorAutoEnter decoderMon(mDecoder->GetMonitor());
--- a/content/media/nsBuiltinDecoderReader.h
+++ b/content/media/nsBuiltinDecoderReader.h
@@ -428,18 +428,18 @@ public:
 
 
   // Stores the presentation time of the first sample in the stream in
   // aOutStartTime, and returns the first video sample, if we have video.
   virtual VideoData* FindStartTime(PRInt64 aOffset,
                                    PRInt64& aOutStartTime);
 
   // Returns the end time of the last page which occurs before aEndOffset.
-  // This will not read past aEndOffset. Returns -1 on failure.
-  virtual PRInt64 FindEndTime(PRInt64 aEndOffset) = 0;
+  // This will not read past aEndOffset. Returns -1 on failure. 
+  virtual PRInt64 FindEndTime(PRInt64 aEndOffset);
 
   // Moves the decode head to aTime milliseconds. aStartTime and aEndTime
   // denote the start and end times of the media.
   virtual nsresult Seek(PRInt64 aTime, PRInt64 aStartTime, PRInt64 aEndTime) = 0;
 
   // Gets presentation info required for playback.
   const nsVideoInfo& GetInfo() {
     return mInfo;
--- a/content/media/nsBuiltinDecoderStateMachine.cpp
+++ b/content/media/nsBuiltinDecoderStateMachine.cpp
@@ -71,17 +71,17 @@ extern PRLogModuleInfo* gBuiltinDecoderL
 // If audio queue has less than this many ms of decoded audio, we won't risk
 // trying to decode the video, we'll skip decoding video up to the next
 // keyframe.
 //
 // Also if the decode catches up with the end of the downloaded data,
 // we'll only go into BUFFERING state if we've got audio and have queued
 // less than LOW_AUDIO_MS of audio, or if we've got video and have queued
 // less than LOW_VIDEO_FRAMES frames.
-static const PRUint32 LOW_AUDIO_MS = 100;
+static const PRUint32 LOW_AUDIO_MS = 300;
 
 // If more than this many ms of decoded audio is queued, we'll hold off
 // decoding more audio.
 const unsigned AMPLE_AUDIO_MS = 2000;
 
 // If we have fewer than LOW_VIDEO_FRAMES decoded frames, and
 // we're not "pumping video", we'll skip the video up to the next keyframe
 // which is at or after the current playback position.
@@ -167,17 +167,17 @@ void nsBuiltinDecoderStateMachine::Decod
   // If we've got more than videoWaitThreshold decoded video frames waiting in
   // the video queue, we will not decode any more video frames until they've
   // been consumed by the play state machine thread.
   const unsigned videoWaitThreshold = 10;
 
   // After the audio decode fills with more than audioPumpThresholdMs ms
   // of decoded audio, we'll start to check whether the audio or video decode
   // is falling behind.
-  const unsigned audioPumpThresholdMs = 250;
+  const unsigned audioPumpThresholdMs = LOW_AUDIO_MS * 2;
 
   // Main decode loop.
   while (videoPlaying || audioPlaying) {
     PRBool audioWait = !audioPlaying;
     PRBool videoWait = !videoPlaying;
     {
       // Wait for more data to download if we've exhausted all our
       // buffered data.
@@ -575,17 +575,18 @@ PRInt64 nsBuiltinDecoderStateMachine::Ge
 
   if (mEndTime == -1 || mStartTime == -1)
     return -1;
   return mEndTime - mStartTime;
 }
 
 void nsBuiltinDecoderStateMachine::SetDuration(PRInt64 aDuration)
 {
-  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
+  NS_ASSERTION(NS_IsMainThread() || mDecoder->OnStateMachineThread(),
+    "Should be on main or state machine thread.");
   mDecoder->GetMonitor().AssertCurrentThreadIn();
 
   if (mStartTime != -1) {
     mEndTime = mStartTime + aDuration;
   } else {
     mStartTime = 0;
     mEndTime = aDuration;
   }
@@ -1240,18 +1241,16 @@ void nsBuiltinDecoderStateMachine::Updat
 void nsBuiltinDecoderStateMachine::LoadMetadata()
 {
   NS_ASSERTION(IsCurrentThread(mDecoder->mStateMachineThread),
                "Should be on state machine thread.");
   mDecoder->GetMonitor().AssertCurrentThreadIn();
 
   LOG(PR_LOG_DEBUG, ("Loading Media Headers"));
 
-  nsMediaStream* stream = mDecoder->mStream;
-
   {
     MonitorAutoExit exitMon(mDecoder->GetMonitor());
     mReader->ReadMetadata();
   }
   mDecoder->StartProgressUpdates();
   const nsVideoInfo& info = mReader->GetInfo();
 
   if (!info.mHasVideo && !info.mHasAudio) {
new file mode 100644
--- /dev/null
+++ b/content/media/webm/Makefile.in
@@ -0,0 +1,65 @@
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is Mozilla code.
+#
+# The Initial Developer of the Original Code is the Mozilla Corporation.
+# Portions created by the Initial Developer are Copyright (C) 2007
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#  Chris Double <chris.double@double.co.nz>
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+DEPTH		= ../../..
+topsrcdir	= @top_srcdir@
+srcdir		= @srcdir@
+VPATH		= @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+MODULE		= content
+LIBRARY_NAME	= gkconwebm_s
+LIBXUL_LIBRARY 	= 1
+
+
+EXPORTS		+= \
+		nsWebMDecoder.h \
+		$(NULL)
+
+CPPSRCS		= \
+		nsWebMDecoder.cpp \
+		nsWebMReader.cpp \
+		$(NULL)
+
+FORCE_STATIC_LIB = 1
+
+include $(topsrcdir)/config/rules.mk
+
+INCLUDES	+= \
+		-I$(srcdir)/../../base/src \
+		-I$(srcdir)/../../html/content/src \
+		$(NULL)
new file mode 100644
--- /dev/null
+++ b/content/media/webm/nsWebMDecoder.cpp
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla code.
+ *
+ * The Initial Developer of the Original Code is the Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Chris Double <chris.double@double.co.nz>
+ *  Chris Pearce <chris@pearce.org.nz>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsBuiltinDecoderStateMachine.h"
+#include "nsWebMReader.h"
+#include "nsWebMDecoder.h"
+
+nsDecoderStateMachine* nsWebMDecoder::CreateStateMachine()
+{
+  return new nsBuiltinDecoderStateMachine(this, new nsWebMReader(this));
+}
new file mode 100644
--- /dev/null
+++ b/content/media/webm/nsWebMDecoder.h
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla code.
+ *
+ * The Initial Developer of the Original Code is the Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Chris Double <chris.double@double.co.nz>
+ *  Chris Pearce <chris@pearce.org.nz>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+#if !defined(nsWebMDecoder_h_)
+#define nsWebMDecoder_h_
+
+#include "nsBuiltinDecoder.h"
+
+class nsWebMDecoder : public nsBuiltinDecoder
+{
+public:
+  virtual nsMediaDecoder* Clone() { return new nsWebMDecoder(); }
+  virtual nsDecoderStateMachine* CreateStateMachine();
+};
+
+#endif
new file mode 100644
--- /dev/null
+++ b/content/media/webm/nsWebMReader.cpp
@@ -0,0 +1,664 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla code.
+ *
+ * The Initial Developer of the Original Code is the Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Chris Double <chris.double@double.co.nz>
+ *  Chris Pearce <chris@pearce.org.nz>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+#include "nsError.h"
+#include "nsBuiltinDecoderStateMachine.h"
+#include "nsBuiltinDecoder.h"
+#include "nsMediaStream.h"
+#include "nsWebMReader.h"
+#include "VideoUtils.h"
+
+using namespace mozilla;
+
+// Un-comment to enable logging of seek bisections.
+//#define SEEK_LOGGING
+
+#ifdef PR_LOGGING
+extern PRLogModuleInfo* gBuiltinDecoderLog;
+#define LOG(type, msg) PR_LOG(gBuiltinDecoderLog, type, msg)
+#ifdef SEEK_LOGGING
+#define SEEK_LOG(type, msg) PR_LOG(gBuiltinDecoderLog, type, msg)
+#else
+#define SEEK_LOG(type, msg)
+#endif
+#else
+#define LOG(type, msg)
+#define SEEK_LOG(type, msg)
+#endif
+
+// Nestegg doesn't expose the framerate and the framerate is optional
+// anyway. We use a default value - the backend playback code
+// only uses it for a 'maximum wait time' not actual frame display time
+// so an estimate is fine. A value higher than a standard framerate is
+// used to ensure that backend Wait's don't take longer than frame
+// display. Bug 568431 should remove the need for 'faking' a framerate in
+// the future.
+#define DEFAULT_FRAMERATE 32.0
+
+// Functions for reading and seeking using nsMediaStream required for
+// nestegg_io. The 'user data' passed to these functions is the
+// decoder from which the media stream is obtained.
+static int webm_read(void *aBuffer, size_t aLength, void *aUserData)
+{
+  NS_ASSERTION(aUserData, "aUserData must point to a valid nsBuiltinDecoder");
+  nsBuiltinDecoder* decoder = reinterpret_cast<nsBuiltinDecoder*>(aUserData);
+  nsMediaStream* stream = decoder->GetCurrentStream();
+  NS_ASSERTION(stream, "Decoder has no media stream");
+
+  nsresult rv = NS_OK;
+  PRBool eof = PR_FALSE;
+
+  char *p = static_cast<char *>(aBuffer);
+  while (NS_SUCCEEDED(rv) && aLength > 0) {
+    PRUint32 bytes = 0;
+    rv = stream->Read(p, aLength, &bytes);
+    if (bytes == 0) {
+      eof = PR_TRUE;
+      break;
+    }
+    aLength -= bytes;
+    p += bytes;
+  }
+
+  return NS_FAILED(rv) ? -1 : eof ? 0 : 1;
+}
+
+static int webm_seek(int64_t aOffset, int aWhence, void *aUserData)
+{
+  NS_ASSERTION(aUserData, "aUserData must point to a valid nsBuiltinDecoder");
+  nsBuiltinDecoder* decoder = reinterpret_cast<nsBuiltinDecoder*>(aUserData);
+  nsMediaStream* stream = decoder->GetCurrentStream();
+  NS_ASSERTION(stream, "Decoder has no media stream");
+  nsresult rv = stream->Seek(aWhence, aOffset);
+  return NS_SUCCEEDED(rv) ? 0 : -1;
+}
+
+static int64_t webm_tell(void *aUserData)
+{
+  NS_ASSERTION(aUserData, "aUserData must point to a valid nsBuiltinDecoder");
+  nsBuiltinDecoder* decoder = reinterpret_cast<nsBuiltinDecoder*>(aUserData);
+  nsMediaStream* stream = decoder->GetCurrentStream();
+  NS_ASSERTION(stream, "Decoder has no media stream");
+  return stream->Tell();
+}
+
+nsWebMReader::nsWebMReader(nsBuiltinDecoder* aDecoder)
+  : nsBuiltinDecoderReader(aDecoder),
+  mContext(nsnull),
+  mPacketCount(0),
+  mChannels(0),
+  mVideoTrack(0),
+  mAudioTrack(0),
+  mHasVideo(PR_FALSE),
+  mHasAudio(PR_FALSE)
+{
+  MOZ_COUNT_CTOR(nsWebMReader);
+}
+
+nsWebMReader::~nsWebMReader()
+{
+  Cleanup();
+
+  mVideoPackets.Reset();
+  mAudioPackets.Reset();
+
+  vorbis_block_clear(&mVorbisBlock);
+  vorbis_dsp_clear(&mVorbisDsp);
+  vorbis_info_clear(&mVorbisInfo);
+  vorbis_comment_clear(&mVorbisComment);
+
+  MOZ_COUNT_DTOR(nsWebMReader);
+}
+
+nsresult nsWebMReader::Init()
+{
+  if(vpx_codec_dec_init(&mVP8, &vpx_codec_vp8_dx_algo, NULL, 0)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  vorbis_info_init(&mVorbisInfo);
+  vorbis_comment_init(&mVorbisComment);
+  memset(&mVorbisDsp, 0, sizeof(vorbis_dsp_state));
+  memset(&mVorbisBlock, 0, sizeof(vorbis_block));
+
+  return NS_OK;
+}
+
+nsresult nsWebMReader::ResetDecode()
+{
+  nsresult res = NS_OK;
+  if (NS_FAILED(nsBuiltinDecoderReader::ResetDecode())) {
+    res = NS_ERROR_FAILURE;
+  }
+
+  // Ignore failed results from vorbis_synthesis_restart. They
+  // aren't fatal and it fails when ResetDecode is called at a
+  // time when no vorbis data has been read.
+  vorbis_synthesis_restart(&mVorbisDsp);
+
+  mVideoPackets.Reset();
+  mAudioPackets.Reset();
+
+  return res;
+}
+
+void nsWebMReader::Cleanup()
+{
+  if (mContext) {
+    nestegg_destroy(mContext);
+    mContext = nsnull;
+  }
+}
+
+nsresult nsWebMReader::ReadMetadata()
+{
+  NS_ASSERTION(mDecoder->OnStateMachineThread(), "Should be on state machine thread.");
+  MonitorAutoEnter mon(mMonitor);
+
+  nestegg_io io;
+  io.read = webm_read;
+  io.seek = webm_seek;
+  io.tell = webm_tell;
+  io.userdata = static_cast<nsBuiltinDecoder*>(mDecoder);
+  int r = nestegg_init(&mContext, io, NULL);
+  if (r == -1) {
+    return NS_ERROR_FAILURE;
+  }
+
+  uint64_t duration = 0;
+  r = nestegg_duration(mContext, &duration);
+  if (r == 0) {
+    MonitorAutoExit exitReaderMon(mMonitor);
+    MonitorAutoEnter decoderMon(mDecoder->GetMonitor());
+    mDecoder->GetStateMachine()->SetDuration(duration / 1000000);
+  }
+
+  unsigned int ntracks = 0;
+  r = nestegg_track_count(mContext, &ntracks);
+  if (r == -1) {
+    Cleanup();
+    return NS_ERROR_FAILURE;
+  }
+
+  mInfo.mHasAudio = PR_FALSE;
+  mInfo.mHasVideo = PR_FALSE;
+  for (PRUint32 track = 0; track < ntracks; ++track) {
+    int id = nestegg_track_codec_id(mContext, track);
+    if (id == -1) {
+      Cleanup();
+      return NS_ERROR_FAILURE;
+    }
+    int type = nestegg_track_type(mContext, track);
+    if (!mHasVideo && type == NESTEGG_TRACK_VIDEO) {
+      nestegg_video_params params;
+      r = nestegg_track_video_params(mContext, track, &params);
+      if (r == -1) {
+        Cleanup();
+        return NS_ERROR_FAILURE;
+      }
+
+      mVideoTrack = track;
+      mHasVideo = PR_TRUE;
+      mInfo.mHasVideo = PR_TRUE;
+      mInfo.mPicture.x = params.crop_left;
+      mInfo.mPicture.y = params.crop_top;
+      mInfo.mPicture.width = params.width - (params.crop_right - params.crop_left);
+      mInfo.mPicture.height = params.height - (params.crop_bottom - params.crop_top);
+      mInfo.mFrame.width = params.width;
+      mInfo.mFrame.height = params.height;
+      mInfo.mPixelAspectRatio = (float(params.display_width) / params.width) /
+                                (float(params.display_height) / params.height);
+
+      // If the cropping data appears invalid then use the frame data
+      if (mInfo.mPicture.width <= 0 || mInfo.mPicture.height <= 0) {
+        mInfo.mPicture.x = 0;
+        mInfo.mPicture.y = 0;
+        mInfo.mPicture.width = params.width;
+        mInfo.mPicture.height = params.height;
+      }
+
+      // mDataOffset is not used by the WebM backend.
+      // See bug 566779 for a suggestion to refactor
+      // and remove it.
+      mInfo.mDataOffset = -1;
+
+      mInfo.mFramerate = DEFAULT_FRAMERATE;
+      mInfo.mCallbackPeriod = 1000 / mInfo.mFramerate;
+    }
+    else if (!mHasAudio && type == NESTEGG_TRACK_AUDIO) {
+      nestegg_audio_params params;
+      r = nestegg_track_audio_params(mContext, track, &params);
+      if (r == -1) {
+        Cleanup();
+        return NS_ERROR_FAILURE;
+      }
+
+      mAudioTrack = track;
+      mHasAudio = PR_TRUE;
+      mInfo.mHasAudio = PR_TRUE;
+
+      if (!mInfo.mHasVideo) {
+        mInfo.mCallbackPeriod = 1000 / DEFAULT_FRAMERATE;
+      }
+
+      // Get the Vorbis header data
+      unsigned int nheaders = 0;
+      r = nestegg_track_codec_data_count(mContext, track, &nheaders);
+      if (r == -1 || nheaders != 3) {
+        Cleanup();
+        return NS_ERROR_FAILURE;
+      }
+
+      for (PRUint32 header = 0; header < nheaders; ++header) {
+        unsigned char* data = 0;
+        size_t length = 0;
+
+        r = nestegg_track_codec_data(mContext, track, header, &data, &length);
+        if (r == -1) {
+          Cleanup();
+          return NS_ERROR_FAILURE;
+        }
+
+        ogg_packet opacket = InitOggPacket(data, length, header == 0, PR_FALSE, 0);
+
+        r = vorbis_synthesis_headerin(&mVorbisInfo,
+                                      &mVorbisComment,
+                                      &opacket);
+        if (r < 0) {
+          Cleanup();
+          return NS_ERROR_FAILURE;
+        }
+      }
+
+      r = vorbis_synthesis_init(&mVorbisDsp, &mVorbisInfo);
+      if (r < 0) {
+        Cleanup();
+        return NS_ERROR_FAILURE;
+      }
+
+      r = vorbis_block_init(&mVorbisDsp, &mVorbisBlock);
+      if (r < 0) {
+        Cleanup();
+        return NS_ERROR_FAILURE;
+      }
+
+      mInfo.mAudioRate = mVorbisDsp.vi->rate;
+      mInfo.mAudioChannels = mVorbisDsp.vi->channels;
+      mChannels = mInfo.mAudioChannels;
+    }
+  }
+
+  return NS_OK;
+}
+
+ogg_packet nsWebMReader::InitOggPacket(unsigned char* aData,
+                                       size_t aLength,
+                                       PRBool aBOS,
+                                       PRBool aEOS,
+                                       PRInt64 aGranulepos)
+{
+  ogg_packet packet;
+  packet.packet = aData;
+  packet.bytes = aLength;
+  packet.b_o_s = aBOS;
+  packet.e_o_s = aEOS;
+  packet.granulepos = aGranulepos;
+  packet.packetno = mPacketCount++;
+  return packet;
+}
+ 
+PRBool nsWebMReader::DecodeAudioPacket(nestegg_packet* aPacket)
+{
+  mMonitor.AssertCurrentThreadIn();
+
+  int r = 0;
+  unsigned int count = 0;
+  r = nestegg_packet_count(aPacket, &count);
+  if (r == -1) {
+    return PR_FALSE;
+  }
+
+  uint64_t tstamp = 0;
+  r = nestegg_packet_tstamp(aPacket, &tstamp);
+  if (r == -1) {
+    nestegg_free_packet(aPacket);
+    return PR_FALSE;
+  }
+
+  PRUint64 tstamp_ms = tstamp / 1000000;
+  for (PRUint32 i = 0; i < count; ++i) {
+    unsigned char* data;
+    size_t length;
+    r = nestegg_packet_data(aPacket, i, &data, &length);
+    if (r == -1) {
+      nestegg_free_packet(aPacket);
+      return PR_FALSE;
+    }
+
+    ogg_packet opacket = InitOggPacket(data, length, PR_FALSE, PR_FALSE, -1);
+
+    if (vorbis_synthesis(&mVorbisBlock, &opacket) != 0) {
+      nestegg_free_packet(aPacket);
+      return PR_FALSE;
+    }
+
+    if (vorbis_synthesis_blockin(&mVorbisDsp,
+                                 &mVorbisBlock) != 0) {
+      nestegg_free_packet(aPacket);
+      return PR_FALSE;
+    }
+
+    float** pcm = 0;
+    PRUint32 samples = 0;
+    while ((samples = vorbis_synthesis_pcmout(&mVorbisDsp, &pcm)) > 0) {
+      if (samples > 0) {
+        float* buffer = new float[samples * mChannels];
+        float* p = buffer;
+        for (PRUint32 i = 0; i < samples; ++i) {
+          for (PRUint32 j = 0; j < mChannels; ++j) {
+            *p++ = pcm[j][i];
+          }
+        }
+
+        PRInt64 duration = samples * 1000 / mVorbisDsp.vi->rate;
+        SoundData* s = new SoundData(0,
+                                     tstamp_ms,
+                                     duration,
+                                     samples,
+                                     buffer,
+                                     mChannels);
+        mAudioQueue.Push(s);
+        tstamp_ms += duration;
+      }
+      if (vorbis_synthesis_read(&mVorbisDsp, samples) != 0) {
+        nestegg_free_packet(aPacket);
+        return PR_FALSE;
+      }
+    }
+  }
+
+  nestegg_free_packet(aPacket);
+
+  return PR_TRUE;
+}
+
+nestegg_packet* nsWebMReader::NextPacket(TrackType aTrackType)
+{
+  // The packet queue that packets will be pushed on if they
+  // are not the type we are interested in.
+  PacketQueue& otherPackets = 
+    aTrackType == VIDEO ? mAudioPackets : mVideoPackets;
+
+  // The packet queue for the type that we are interested in.
+  PacketQueue &packets =
+    aTrackType == VIDEO ? mVideoPackets : mAudioPackets;
+
+  // Flag to indicate that we do need to playback these types of
+  // packets.
+  PRPackedBool hasType = aTrackType == VIDEO ? mHasVideo : mHasAudio;
+
+  // Flag to indicate that we do need to playback the other type
+  // of track.
+  PRPackedBool hasOtherType = aTrackType == VIDEO ? mHasAudio : mHasVideo;
+
+  // Track we are interested in
+  PRUint32 ourTrack = aTrackType == VIDEO ? mVideoTrack : mAudioTrack;
+
+  // Value of other track
+  PRUint32 otherTrack = aTrackType == VIDEO ? mAudioTrack : mVideoTrack;
+
+  nestegg_packet* packet = NULL;
+
+  if (packets.GetSize() > 0) {
+    packet = packets.PopFront();
+  }
+  else {
+    // Keep reading packets until we find a packet
+    // for the track we want.
+    do {
+      int r = nestegg_read_packet(mContext, &packet);
+      if (r <= 0) {
+        return NULL;
+      }
+
+      unsigned int track = 0;
+      r = nestegg_packet_track(packet, &track);
+      if (r == -1) {
+        nestegg_free_packet(packet);
+        return NULL;
+      }
+
+      if (hasOtherType && otherTrack == track) {
+        // Save the packet for when we want these packets
+        otherPackets.Push(packet);
+        continue;
+      }
+
+      // The packet is for the track we want to play
+      if (hasType && ourTrack == track) {
+        break;
+      }
+
+      // The packet is for a track we're not interested in
+      nestegg_free_packet(packet);
+    } while (PR_TRUE);
+  }
+
+  return packet;
+}
+
+PRBool nsWebMReader::DecodeAudioData()
+{
+  MonitorAutoEnter mon(mMonitor);
+  NS_ASSERTION(mDecoder->OnStateMachineThread() || mDecoder->OnDecodeThread(),
+    "Should be on state machine thread or decode thread.");
+  nestegg_packet* packet = NextPacket(AUDIO);
+  if (!packet) {
+    mAudioQueue.Finish();
+    return PR_FALSE;
+  }
+
+  return DecodeAudioPacket(packet);
+}
+
+PRBool nsWebMReader::DecodeVideoFrame(PRBool &aKeyframeSkip,
+                                     PRInt64 aTimeThreshold)
+{
+  MonitorAutoEnter mon(mMonitor);
+  NS_ASSERTION(mDecoder->OnStateMachineThread() || mDecoder->OnDecodeThread(),
+               "Should be on state machine or decode thread.");
+  int r = 0;
+  nestegg_packet* packet = NextPacket(VIDEO);
+
+  if (!packet) {
+    mVideoQueue.Finish();
+    return PR_FALSE;
+  }
+
+  unsigned int track = 0;
+  r = nestegg_packet_track(packet, &track);
+  if (r == -1) {
+    nestegg_free_packet(packet);
+    return PR_FALSE;
+  }
+
+  unsigned int count = 0;
+  r = nestegg_packet_count(packet, &count);
+  if (r == -1) {
+    nestegg_free_packet(packet);
+    return PR_FALSE;
+  }
+
+  uint64_t tstamp = 0;
+  r = nestegg_packet_tstamp(packet, &tstamp);
+  if (r == -1) {
+    nestegg_free_packet(packet);
+    return PR_FALSE;
+  }
+
+  PRInt64 tstamp_ms = tstamp / 1000000;
+  for (PRUint32 i = 0; i < count; ++i) {
+    unsigned char* data;
+    size_t length;
+    r = nestegg_packet_data(packet, i, &data, &length);
+    if (r == -1) {
+      nestegg_free_packet(packet);
+      return PR_FALSE;
+    }
+
+    vpx_codec_stream_info_t si;
+    memset(&si, 0, sizeof(si));
+    si.sz = sizeof(si);
+    vpx_codec_peek_stream_info(&vpx_codec_vp8_dx_algo, data, length, &si);
+    if ((aKeyframeSkip && !si.is_kf) || (aKeyframeSkip && si.is_kf && tstamp_ms < aTimeThreshold)) {
+      aKeyframeSkip = PR_TRUE;
+      break;
+    }
+
+    if (aKeyframeSkip && si.is_kf) {
+      aKeyframeSkip = PR_FALSE;
+    }
+
+    if(vpx_codec_decode(&mVP8, data, length, NULL, 0)) {
+      nestegg_free_packet(packet);
+      return PR_FALSE;
+    }
+
+    // If the timestamp of the video frame is less than
+    // the time threshold required then it is not added
+    // to the video queue and won't be displayed.
+    if (tstamp_ms < aTimeThreshold) {
+      continue;
+    }
+
+    vpx_codec_iter_t  iter = NULL;
+    vpx_image_t      *img;
+
+    while((img = vpx_codec_get_frame(&mVP8, &iter))) {
+      NS_ASSERTION(mInfo.mPicture.width == static_cast<PRInt32>(img->d_w), 
+        "WebM picture width from header does not match decoded frame");
+      NS_ASSERTION(mInfo.mPicture.height == static_cast<PRInt32>(img->d_h),
+        "WebM picture height from header does not match decoded frame");
+      NS_ASSERTION(img->fmt == IMG_FMT_I420, "WebM image format is not I420");
+
+      // Chroma shifts are rounded down as per the decoding examples in the VP8 SDK
+      VideoData::YCbCrBuffer b;
+      b.mPlanes[0].mData = img->planes[0];
+      b.mPlanes[0].mStride = img->stride[0];
+      b.mPlanes[0].mHeight = img->d_h;
+      b.mPlanes[0].mWidth = img->d_w;
+
+      b.mPlanes[1].mData = img->planes[1];
+      b.mPlanes[1].mStride = img->stride[1];
+      b.mPlanes[1].mHeight = img->d_h >> img->y_chroma_shift;
+      b.mPlanes[1].mWidth = img->d_w >> img->x_chroma_shift;
+ 
+      b.mPlanes[2].mData = img->planes[2];
+      b.mPlanes[2].mStride = img->stride[2];
+      b.mPlanes[2].mHeight = img->d_h >> img->y_chroma_shift;
+      b.mPlanes[2].mWidth = img->d_w >> img->x_chroma_shift;
+  
+      VideoData *v = VideoData::Create(mInfo,
+                                       mDecoder->GetImageContainer(),
+                                       -1,
+                                       tstamp_ms,
+                                       b,
+                                       si.is_kf,
+                                       -1);
+      if (!v) {
+        nestegg_free_packet(packet);
+        return PR_FALSE;
+      }
+      mVideoQueue.Push(v);
+    }
+  }
+ 
+  nestegg_free_packet(packet);
+  return PR_TRUE;
+}
+
+nsresult nsWebMReader::Seek(PRInt64 aTarget, PRInt64 aStartTime, PRInt64 aEndTime)
+{
+  MonitorAutoEnter mon(mMonitor);
+  NS_ASSERTION(mDecoder->OnStateMachineThread(),
+               "Should be on state machine thread.");
+  LOG(PR_LOG_DEBUG, ("%p About to seek to %lldms", mDecoder, aTarget));
+  if (NS_FAILED(ResetDecode())) {
+    return NS_ERROR_FAILURE;
+  }
+  int r = nestegg_track_seek(mContext, 0, aTarget * 1000000);
+  if (r != 0) {
+    return NS_ERROR_FAILURE;
+  }
+  if (HasVideo()) {
+    nsAutoPtr<VideoData> video;
+    PRBool eof = PR_FALSE;
+    PRInt64 startTime = -1;
+    video = nsnull;
+    while (HasVideo() && !eof) {
+      while (mVideoQueue.GetSize() == 0 && !eof) {
+        PRBool skip = PR_FALSE;
+        eof = !DecodeVideoFrame(skip, 0);
+        MonitorAutoExit exitReaderMon(mMonitor);
+        MonitorAutoEnter decoderMon(mDecoder->GetMonitor());
+        if (mDecoder->GetDecodeState() == nsBuiltinDecoderStateMachine::DECODER_STATE_SHUTDOWN) {
+          return NS_ERROR_FAILURE;
+        }
+      }
+      if (mVideoQueue.GetSize() == 0) {
+        break;
+      }
+      video = mVideoQueue.PeekFront();
+      // If the frame end time is less than the seek target, we won't want
+      // to display this frame after the seek, so discard it.
+      if (video && video->mTime + 40 < aTarget) {
+        if (startTime == -1) {
+          startTime = video->mTime;
+        }
+        mVideoQueue.PopFront();
+        video = nsnull;
+      } else {
+        video.forget();
+        break;
+      }
+    }
+    SEEK_LOG(PR_LOG_DEBUG, ("First video frame after decode is %lld", startTime));
+  }
+  return NS_OK;
+}
+
new file mode 100644
--- /dev/null
+++ b/content/media/webm/nsWebMReader.h
@@ -0,0 +1,185 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla code.
+ *
+ * The Initial Developer of the Original Code is the Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Chris Double <chris.double@double.co.nz>
+ *  Chris Pearce <chris@pearce.org.nz>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+#if !defined(nsWebMReader_h_)
+#define nsWebMReader_h_
+
+#include "nsDeque.h"
+#include "nsBuiltinDecoderReader.h"
+#include "nestegg/nestegg.h"
+#include "vpx/vpx_decoder.h"
+#include "vpx/vp8dx.h"
+#include "vorbis/codec.h"
+
+class nsMediaDecoder;
+
+// Thread and type safe wrapper around nsDeque.
+class PacketQueueDeallocator : public nsDequeFunctor {
+  virtual void* operator() (void* anObject) {
+    nestegg_free_packet(static_cast<nestegg_packet*>(anObject));
+    return nsnull;
+  }
+};
+
+// Typesafe queue for holding nestegg packets. It has
+// ownership of the items in the queue and will free them
+// when destroyed.
+class PacketQueue : private nsDeque {
+ public:
+   PacketQueue()
+     : nsDeque(new PacketQueueDeallocator())
+   {}
+  
+  ~PacketQueue() {
+    Reset();
+  }
+
+  inline PRInt32 GetSize() { 
+    return nsDeque::GetSize();
+  }
+  
+  inline void Push(nestegg_packet* aItem) {
+    nsDeque::Push(aItem);
+  }
+  
+  inline nestegg_packet* PopFront() {
+    return static_cast<nestegg_packet*>(nsDeque::PopFront());
+  }
+  
+  void Reset() {
+    while (GetSize() > 0) {
+      nestegg_free_packet(PopFront());
+    }
+  }
+};
+
+
+class nsWebMReader : public nsBuiltinDecoderReader
+{
+public:
+  nsWebMReader(nsBuiltinDecoder* aDecoder);
+  ~nsWebMReader();
+
+  virtual nsresult Init();
+  virtual nsresult ResetDecode();
+  virtual PRBool DecodeAudioData();
+
+  // If the Theora granulepos has not been captured, it may read several packets
+  // until one with a granulepos has been captured, to ensure that all packets
+  // read have valid time info.  
+  virtual PRBool DecodeVideoFrame(PRBool &aKeyframeSkip,
+                                  PRInt64 aTimeThreshold);
+
+  virtual PRBool HasAudio()
+  {
+    mozilla::MonitorAutoEnter mon(mMonitor);
+    return mHasAudio;
+  }
+
+  virtual PRBool HasVideo()
+  {
+    mozilla::MonitorAutoEnter mon(mMonitor);
+    return mHasVideo;
+  }
+
+  virtual nsresult ReadMetadata();
+  virtual nsresult Seek(PRInt64 aTime, PRInt64 aStartTime, PRInt64 aEndTime);
+
+private:
+  // Value passed to NextPacket to determine if we are reading a video or an
+  // audio packet.
+  enum TrackType {
+    VIDEO = 0,
+    AUDIO = 1
+  };
+
+  // Read a packet from the nestegg file. Returns NULL if all packets for
+  // the particular track have been read. Pass VIDEO or AUDIO to indicate the
+  // type of the packet we want to read.
+  nestegg_packet* NextPacket(TrackType aTrackType);
+
+  // Returns an initialized ogg packet with data obtained from the WebM container.
+  ogg_packet InitOggPacket(unsigned char* aData,
+                           size_t aLength,
+                           PRBool aBOS,
+                           PRBool aEOS,
+                           PRInt64 aGranulepos);
+                     
+  // Decode a nestegg packet of audio data. Push the audio data on the
+  // audio queue. Returns PR_TRUE when there's more audio to decode,
+  // PR_FALSE if the audio is finished, end of file has been reached,
+  // or an un-recoverable read error has occured. The reader's monitor
+  // must be held during this call. This function will free the packet
+  // so the caller must not use the packet after calling.
+  PRBool DecodeAudioPacket(nestegg_packet* aPacket);
+
+  // Release context and set to null. Called when an error occurs during
+  // reading metadata or destruction of the reader itself.
+  void Cleanup();
+
+private:
+  // libnestegg context for webm container. Access on state machine thread
+  // or decoder thread only.
+  nestegg* mContext;
+
+  // VP8 decoder state
+  vpx_codec_ctx_t  mVP8;
+
+  // Vorbis decoder state
+  vorbis_info mVorbisInfo;
+  vorbis_comment mVorbisComment;
+  vorbis_dsp_state mVorbisDsp;
+  vorbis_block mVorbisBlock;
+  PRUint32 mPacketCount;
+  PRUint32 mChannels;
+
+  // Queue of video and audio packets that have been read but not decoded. These
+  // must only be accessed from the state machine thread.
+  PacketQueue mVideoPackets;
+  PacketQueue mAudioPackets;
+
+  // Index of video and audio track to play
+  PRUint32 mVideoTrack;
+  PRUint32 mAudioTrack;
+
+  // Booleans to indicate if we have audio and/or video data
+  PRPackedBool mHasVideo;
+  PRPackedBool mHasAudio;
+};
+
+#endif