From 33d1e85788ca608b181aaebe38e9e20d8cf39d9f Mon Sep 17 00:00:00 2001 From: Chris Double Date: Wed, 9 Jun 2010 11:31:27 +1200 Subject: [PATCH] Bug 566245 - WebM/VP8 decoder backend. r=kinetik --- content/media/Makefile.in | 4 + content/media/nsBuiltinDecoder.h | 5 +- content/media/nsBuiltinDecoderReader.cpp | 5 + content/media/nsBuiltinDecoderReader.h | 4 +- .../media/nsBuiltinDecoderStateMachine.cpp | 9 +- content/media/webm/Makefile.in | 65 ++ content/media/webm/nsWebMDecoder.cpp | 47 ++ content/media/webm/nsWebMDecoder.h | 51 ++ content/media/webm/nsWebMReader.cpp | 664 ++++++++++++++++++ content/media/webm/nsWebMReader.h | 185 +++++ 10 files changed, 1030 insertions(+), 9 deletions(-) create mode 100644 content/media/webm/Makefile.in create mode 100644 content/media/webm/nsWebMDecoder.cpp create mode 100644 content/media/webm/nsWebMDecoder.h create mode 100644 content/media/webm/nsWebMReader.cpp create mode 100644 content/media/webm/nsWebMReader.h diff --git a/content/media/Makefile.in b/content/media/Makefile.in index 38892ed731ed..46ad2d42b9b9 100644 --- a/content/media/Makefile.in +++ b/content/media/Makefile.in @@ -81,6 +81,10 @@ ifdef MOZ_WAVE PARALLEL_DIRS += wave endif +ifdef MOZ_WEBM +PARALLEL_DIRS += webm +endif + ifdef ENABLE_TESTS PARALLEL_DIRS += test endif diff --git a/content/media/nsBuiltinDecoder.h b/content/media/nsBuiltinDecoder.h index 63d9a8c4173a..957bde32fc18 100644 --- a/content/media/nsBuiltinDecoder.h +++ b/content/media/nsBuiltinDecoder.h @@ -254,8 +254,9 @@ public: 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 diff --git a/content/media/nsBuiltinDecoderReader.cpp b/content/media/nsBuiltinDecoderReader.cpp index 7066164259a4..d1e09d92d759 100644 --- a/content/media/nsBuiltinDecoderReader.cpp +++ b/content/media/nsBuiltinDecoderReader.cpp @@ -299,6 +299,11 @@ VideoData* nsBuiltinDecoderReader::FindStartTime(PRInt64 aOffset, return videoData; } +PRInt64 nsBuiltinDecoderReader::FindEndTime(PRInt64 aEndOffset) +{ + return -1; +} + template Data* nsBuiltinDecoderReader::DecodeToFirstData(DecodeFn aDecodeFn, MediaQueue& aQueue) diff --git a/content/media/nsBuiltinDecoderReader.h b/content/media/nsBuiltinDecoderReader.h index 0f0b7086cb56..5e70fef0da23 100644 --- a/content/media/nsBuiltinDecoderReader.h +++ b/content/media/nsBuiltinDecoderReader.h @@ -433,8 +433,8 @@ public: 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. diff --git a/content/media/nsBuiltinDecoderStateMachine.cpp b/content/media/nsBuiltinDecoderStateMachine.cpp index 679adbb94fba..4d53ade696a3 100644 --- a/content/media/nsBuiltinDecoderStateMachine.cpp +++ b/content/media/nsBuiltinDecoderStateMachine.cpp @@ -76,7 +76,7 @@ extern PRLogModuleInfo* gBuiltinDecoderLog; // 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. @@ -172,7 +172,7 @@ void nsBuiltinDecoderStateMachine::DecodeLoop() // 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) { @@ -580,7 +580,8 @@ PRInt64 nsBuiltinDecoderStateMachine::GetDuration() 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) { @@ -1245,8 +1246,6 @@ void nsBuiltinDecoderStateMachine::LoadMetadata() LOG(PR_LOG_DEBUG, ("Loading Media Headers")); - nsMediaStream* stream = mDecoder->mStream; - { MonitorAutoExit exitMon(mDecoder->GetMonitor()); mReader->ReadMetadata(); diff --git a/content/media/webm/Makefile.in b/content/media/webm/Makefile.in new file mode 100644 index 000000000000..a472497e59ee --- /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 +# +# 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) diff --git a/content/media/webm/nsWebMDecoder.cpp b/content/media/webm/nsWebMDecoder.cpp new file mode 100644 index 000000000000..c8f380f51d74 --- /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 Pearce + * + * 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)); +} diff --git a/content/media/webm/nsWebMDecoder.h b/content/media/webm/nsWebMDecoder.h new file mode 100644 index 000000000000..ab003dcb73ff --- /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 Pearce + * + * 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 diff --git a/content/media/webm/nsWebMReader.cpp b/content/media/webm/nsWebMReader.cpp new file mode 100644 index 000000000000..37a53e335ff2 --- /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 Pearce + * + * 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(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(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(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(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(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, ¶ms); + 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, ¶ms); + 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(img->d_w), + "WebM picture width from header does not match decoded frame"); + NS_ASSERTION(mInfo.mPicture.height == static_cast(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 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; +} + diff --git a/content/media/webm/nsWebMReader.h b/content/media/webm/nsWebMReader.h new file mode 100644 index 000000000000..c12c4c425776 --- /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 Pearce + * + * 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(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(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