mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 570904 - Implement buffered support for WebM. r=roc
This commit is contained in:
parent
81a5a215c9
commit
48fd3a8330
@ -296,6 +296,8 @@ public:
|
||||
|
||||
virtual nsresult GetBuffered(nsTimeRanges* aBuffered) = 0;
|
||||
|
||||
virtual void NotifyDataArrived(const char* aBuffer, PRUint32 aLength, PRUint32 aOffset) = 0;
|
||||
|
||||
// Causes the state machine to switch to buffering state, and to
|
||||
// immediately stop playback and buffer downloaded data. Must be called
|
||||
// with the decode monitor held. Called on the state machine thread and
|
||||
@ -436,6 +438,10 @@ class nsBuiltinDecoder : public nsMediaDecoder
|
||||
return mDecoderStateMachine->GetBuffered(aBuffered);
|
||||
}
|
||||
|
||||
virtual void NotifyDataArrived(const char* aBuffer, PRUint32 aLength, PRUint32 aOffset) {
|
||||
return mDecoderStateMachine->NotifyDataArrived(aBuffer, aLength, aOffset);
|
||||
}
|
||||
|
||||
public:
|
||||
// Return the current state. Can be called on any thread. If called from
|
||||
// a non-main thread, the decoder monitor must be held.
|
||||
|
@ -463,6 +463,10 @@ public:
|
||||
virtual nsresult GetBuffered(nsTimeRanges* aBuffered,
|
||||
PRInt64 aStartTime) = 0;
|
||||
|
||||
// Only used by nsWebMReader for now, so stub here rather than in every
|
||||
// reader than inherits from nsBuiltinDecoderReader.
|
||||
virtual void NotifyDataArrived(const char* aBuffer, PRUint32 aLength, PRUint32 aOffset) {}
|
||||
|
||||
protected:
|
||||
|
||||
// Pumps the decode until we reach frames/samples required to play at
|
||||
|
@ -240,6 +240,11 @@ public:
|
||||
return mReader->GetBuffered(aBuffered, mStartTime);
|
||||
}
|
||||
|
||||
void NotifyDataArrived(const char* aBuffer, PRUint32 aLength, PRUint32 aOffset) {
|
||||
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
|
||||
mReader->NotifyDataArrived(aBuffer, aLength, aOffset);
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
// Returns PR_TRUE when there's decoded audio waiting to play.
|
||||
|
@ -220,6 +220,10 @@ public:
|
||||
// the result from OnStopRequest.
|
||||
virtual void NotifyDownloadEnded(nsresult aStatus) = 0;
|
||||
|
||||
// Called as data arrives on the stream and is read into the cache. Called
|
||||
// on the main thread only.
|
||||
virtual void NotifyDataArrived(const char* aBuffer, PRUint32 aLength, PRUint32 aOffset) = 0;
|
||||
|
||||
// Cleanup internal data structures. Must be called on the main
|
||||
// thread by the owning object before that object disposes of this object.
|
||||
virtual void Shutdown();
|
||||
|
@ -347,6 +347,9 @@ nsMediaChannelStream::CopySegmentToCache(nsIInputStream *aInStream,
|
||||
PRUint32 *aWriteCount)
|
||||
{
|
||||
CopySegmentClosure* closure = static_cast<CopySegmentClosure*>(aClosure);
|
||||
|
||||
closure->mStream->mDecoder->NotifyDataArrived(aFromSegment, aCount, closure->mStream->mOffset);
|
||||
|
||||
// Keep track of where we're up to
|
||||
closure->mStream->mOffset += aCount;
|
||||
closure->mStream->mCacheStream.NotifyDataReceived(aCount, aFromSegment,
|
||||
|
@ -363,7 +363,7 @@ nsresult nsOggReader::DecodeVorbis(nsTArray<SoundData*>& aChunks,
|
||||
while ((samples = vorbis_synthesis_pcmout(&mVorbisState->mDsp, &pcm)) > 0) {
|
||||
float* buffer = new float[samples * channels];
|
||||
float* p = buffer;
|
||||
for (PRUint32 i = 0; i < samples; ++i) {
|
||||
for (PRUint32 i = 0; i < PRUint32(samples); ++i) {
|
||||
for (PRUint32 j = 0; j < channels; ++j) {
|
||||
*p++ = pcm[j][i];
|
||||
}
|
||||
|
@ -73,17 +73,8 @@ function ended(e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only support for buffered is available for Ogg and WAV.
|
||||
// Eventually we'll support WebM as well.
|
||||
function supportsBuffered(type) {
|
||||
var s = type.toLowerCase();
|
||||
return /ogg$/.test(s) || /wav$/.test(s)
|
||||
}
|
||||
|
||||
function startTest(test, token) {
|
||||
var v = document.createElement('video');
|
||||
if (!supportsBuffered(test.type))
|
||||
return;
|
||||
v.token = token;
|
||||
manager.started(token);
|
||||
|
||||
|
@ -241,6 +241,8 @@ class nsWaveDecoder : public nsMediaDecoder
|
||||
// are buffered and playable.
|
||||
virtual nsresult GetBuffered(nsTimeRanges* aBuffered);
|
||||
|
||||
virtual void NotifyDataArrived(const char* aBuffer, PRUint32 aLength, PRUint32 aOffset) {}
|
||||
|
||||
private:
|
||||
// Notifies the element that seeking has started.
|
||||
void SeekingStarted();
|
||||
|
@ -51,6 +51,7 @@ EXPORTS += \
|
||||
$(NULL)
|
||||
|
||||
CPPSRCS = \
|
||||
nsWebMBufferedParser.cpp \
|
||||
nsWebMDecoder.cpp \
|
||||
nsWebMReader.cpp \
|
||||
$(NULL)
|
||||
|
200
content/media/webm/nsWebMBufferedParser.cpp
Normal file
200
content/media/webm/nsWebMBufferedParser.cpp
Normal file
@ -0,0 +1,200 @@
|
||||
/* -*- 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) 2010
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Matthew Gregan <kinetik@flim.org>
|
||||
*
|
||||
* 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 "nsAlgorithm.h"
|
||||
#include "nsWebMBufferedParser.h"
|
||||
|
||||
static PRUint32
|
||||
VIntLength(unsigned char aFirstByte, PRUint32* aMask)
|
||||
{
|
||||
PRUint32 count = 1;
|
||||
PRUint32 mask = 1 << 7;
|
||||
while (count < 8) {
|
||||
if ((aFirstByte & mask) != 0) {
|
||||
break;
|
||||
}
|
||||
mask >>= 1;
|
||||
count += 1;
|
||||
}
|
||||
if (aMask) {
|
||||
*aMask = mask;
|
||||
}
|
||||
NS_ASSERTION(count >= 1 && count <= 8, "Insane VInt length.");
|
||||
return count;
|
||||
}
|
||||
|
||||
void nsWebMBufferedParser::Append(const unsigned char* aBuffer, PRUint32 aLength,
|
||||
nsTArray<nsWebMTimeDataOffset>& aMapping)
|
||||
{
|
||||
static const unsigned char CLUSTER_ID[] = { 0x1f, 0x43, 0xb6, 0x75 };
|
||||
static const unsigned char TIMECODE_ID = 0xe7;
|
||||
static const unsigned char BLOCKGROUP_ID = 0xa0;
|
||||
static const unsigned char BLOCK_ID = 0xa1;
|
||||
static const unsigned char SIMPLEBLOCK_ID = 0xa3;
|
||||
|
||||
const unsigned char* p = aBuffer;
|
||||
|
||||
// Parse each byte in aBuffer one-by-one, producing timecodes and updating
|
||||
// aMapping as we go. Parser pauses at end of stream (which may be at any
|
||||
// point within the parse) and resumes parsing the next time Append is
|
||||
// called with new data.
|
||||
while (p < aBuffer + aLength) {
|
||||
switch (mState) {
|
||||
case CLUSTER_SYNC:
|
||||
if (*p++ == CLUSTER_ID[mClusterIDPos]) {
|
||||
mClusterIDPos += 1;
|
||||
} else {
|
||||
mClusterIDPos = 0;
|
||||
}
|
||||
// Cluster ID found, it's likely this is a valid sync point. If this
|
||||
// is a spurious match, the later parse steps will encounter an error
|
||||
// and return to CLUSTER_SYNC.
|
||||
if (mClusterIDPos == sizeof(CLUSTER_ID)) {
|
||||
mClusterIDPos = 0;
|
||||
mState = READ_VINT;
|
||||
mNextState = TIMECODE_SYNC;
|
||||
}
|
||||
break;
|
||||
case READ_VINT: {
|
||||
unsigned char c = *p++;
|
||||
PRUint32 mask;
|
||||
mVIntLength = VIntLength(c, &mask);
|
||||
mVIntLeft = mVIntLength - 1;
|
||||
mVInt = c & ~mask;
|
||||
mState = READ_VINT_REST;
|
||||
break;
|
||||
}
|
||||
case READ_VINT_REST:
|
||||
if (mVIntLeft) {
|
||||
mVInt <<= 8;
|
||||
mVInt |= *p++;
|
||||
mVIntLeft -= 1;
|
||||
} else {
|
||||
mState = mNextState;
|
||||
}
|
||||
break;
|
||||
case TIMECODE_SYNC:
|
||||
if (*p++ != TIMECODE_ID) {
|
||||
p -= 1;
|
||||
mState = CLUSTER_SYNC;
|
||||
break;
|
||||
}
|
||||
mClusterTimecode = 0;
|
||||
mState = READ_VINT;
|
||||
mNextState = READ_CLUSTER_TIMECODE;
|
||||
break;
|
||||
case READ_CLUSTER_TIMECODE:
|
||||
if (mVInt) {
|
||||
mClusterTimecode <<= 8;
|
||||
mClusterTimecode |= *p++;
|
||||
mVInt -= 1;
|
||||
} else {
|
||||
mState = ANY_BLOCK_SYNC;
|
||||
}
|
||||
break;
|
||||
case ANY_BLOCK_SYNC: {
|
||||
unsigned char c = *p++;
|
||||
if (c == BLOCKGROUP_ID) {
|
||||
mState = READ_VINT;
|
||||
mNextState = ANY_BLOCK_SYNC;
|
||||
} else if (c == SIMPLEBLOCK_ID || c == BLOCK_ID) {
|
||||
mBlockOffset = mCurrentOffset + (p - aBuffer) - 1;
|
||||
mState = READ_VINT;
|
||||
mNextState = READ_BLOCK;
|
||||
} else {
|
||||
PRUint32 length = VIntLength(c, nsnull);
|
||||
if (length == 4) {
|
||||
p -= 1;
|
||||
mState = CLUSTER_SYNC;
|
||||
} else {
|
||||
mState = READ_VINT;
|
||||
mNextState = SKIP_ELEMENT;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case READ_BLOCK:
|
||||
mBlockSize = mVInt;
|
||||
mBlockTimecode = 0;
|
||||
mBlockTimecodeLength = 2;
|
||||
mState = READ_VINT;
|
||||
mNextState = READ_BLOCK_TIMECODE;
|
||||
break;
|
||||
case READ_BLOCK_TIMECODE:
|
||||
if (mBlockTimecodeLength) {
|
||||
mBlockTimecode <<= 8;
|
||||
mBlockTimecode |= *p++;
|
||||
mBlockTimecodeLength -= 1;
|
||||
} else {
|
||||
// It's possible we've parsed this data before, so avoid inserting
|
||||
// duplicate nsWebMTimeDataOffset entries.
|
||||
PRUint32 idx;
|
||||
if (!aMapping.GreatestIndexLtEq(mBlockOffset, idx)) {
|
||||
nsWebMTimeDataOffset entry(mBlockOffset, mClusterTimecode + mBlockTimecode);
|
||||
aMapping.InsertElementAt(idx, entry);
|
||||
}
|
||||
|
||||
// Skip rest of block header and the block's payload.
|
||||
mBlockSize -= mVIntLength;
|
||||
mBlockSize -= 2;
|
||||
mSkipBytes = PRUint32(mBlockSize);
|
||||
mState = SKIP_DATA;
|
||||
mNextState = ANY_BLOCK_SYNC;
|
||||
}
|
||||
break;
|
||||
case SKIP_DATA:
|
||||
if (mSkipBytes) {
|
||||
PRUint32 left = aLength - (p - aBuffer);
|
||||
left = NS_MIN(left, mSkipBytes);
|
||||
p += left;
|
||||
mSkipBytes -= left;
|
||||
} else {
|
||||
mState = mNextState;
|
||||
}
|
||||
break;
|
||||
case SKIP_ELEMENT:
|
||||
mSkipBytes = PRUint32(mVInt);
|
||||
mState = SKIP_DATA;
|
||||
mNextState = ANY_BLOCK_SYNC;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
NS_ASSERTION(p == aBuffer + aLength, "Must have parsed to end of data.");
|
||||
mCurrentOffset += aLength;
|
||||
}
|
207
content/media/webm/nsWebMBufferedParser.h
Normal file
207
content/media/webm/nsWebMBufferedParser.h
Normal file
@ -0,0 +1,207 @@
|
||||
/* -*- 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) 2010
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Matthew Gregan <kinetik@flim.org>
|
||||
*
|
||||
* 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(nsWebMBufferedParser_h_)
|
||||
#define nsWebMBufferedParser_h_
|
||||
|
||||
#include "nsTArray.h"
|
||||
|
||||
// Stores a stream byte offset and the scaled timecode of the block at
|
||||
// that offset. The timecode must be scaled by the stream's timecode
|
||||
// scale before use.
|
||||
struct nsWebMTimeDataOffset
|
||||
{
|
||||
nsWebMTimeDataOffset(PRInt64 aOffset, PRUint64 aTimecode)
|
||||
: mOffset(aOffset), mTimecode(aTimecode)
|
||||
{}
|
||||
|
||||
bool operator==(PRInt64 aOffset) const {
|
||||
return mOffset == aOffset;
|
||||
}
|
||||
|
||||
bool operator<(PRInt64 aOffset) const {
|
||||
return mOffset < aOffset;
|
||||
}
|
||||
|
||||
PRInt64 mOffset;
|
||||
PRUint64 mTimecode;
|
||||
};
|
||||
|
||||
// A simple WebM parser that produces data offset to timecode pairs as it
|
||||
// consumes blocks. A new parser is created for each distinct range of data
|
||||
// received and begins parsing from the first WebM cluster within that
|
||||
// range. Old parsers are destroyed when their range merges with a later
|
||||
// parser or an already parsed range. The parser may start at any position
|
||||
// within the stream.
|
||||
struct nsWebMBufferedParser
|
||||
{
|
||||
nsWebMBufferedParser(PRInt64 aOffset)
|
||||
: mStartOffset(aOffset), mCurrentOffset(aOffset), mState(CLUSTER_SYNC), mClusterIDPos(0)
|
||||
{}
|
||||
|
||||
// Steps the parser through aLength bytes of data. Always consumes
|
||||
// aLength bytes. Updates mCurrentOffset before returning.
|
||||
void Append(const unsigned char* aBuffer, PRUint32 aLength,
|
||||
nsTArray<nsWebMTimeDataOffset>& aMapping);
|
||||
|
||||
bool operator==(PRInt64 aOffset) const {
|
||||
return mCurrentOffset == aOffset;
|
||||
}
|
||||
|
||||
bool operator<(PRInt64 aOffset) const {
|
||||
return mCurrentOffset < aOffset;
|
||||
}
|
||||
|
||||
// The offset at which this parser started parsing. Used to merge
|
||||
// adjacent parsers, in which case the later parser adopts the earlier
|
||||
// parser's mStartOffset.
|
||||
PRInt64 mStartOffset;
|
||||
|
||||
// Current offset with the stream. Updated in chunks as Append() consumes
|
||||
// data.
|
||||
PRInt64 mCurrentOffset;
|
||||
|
||||
private:
|
||||
enum State {
|
||||
// Parser start state. Scans forward searching for stream sync by
|
||||
// matching CLUSTER_ID with the curernt byte. The match state is stored
|
||||
// in mClusterIDPos. Once this reaches sizeof(CLUSTER_ID), stream may
|
||||
// have sync. The parser then advances to read the cluster size and
|
||||
// timecode.
|
||||
CLUSTER_SYNC,
|
||||
|
||||
/*
|
||||
The the parser states below assume that CLUSTER_SYNC has found a valid
|
||||
sync point within the data. If parsing fails in these states, the
|
||||
parser returns to CLUSTER_SYNC to find a new sync point.
|
||||
*/
|
||||
|
||||
// Read the first byte of a variable length integer. The first byte
|
||||
// encodes both the variable integer's length and part of the value.
|
||||
// The value read so far is stored in mVInt and the length is stored in
|
||||
// mVIntLength. The number of bytes left to read is stored in
|
||||
// mVIntLeft.
|
||||
READ_VINT,
|
||||
|
||||
// Reads the remaining mVIntLeft bytes into mVInt.
|
||||
READ_VINT_REST,
|
||||
|
||||
// Check that the next element is TIMECODE_ID. The cluster timecode is
|
||||
// required to be the first element in a cluster. Advances to READ_VINT
|
||||
// to read the timecode's length into mVInt.
|
||||
TIMECODE_SYNC,
|
||||
|
||||
// mVInt holds the length of the variable length unsigned integer
|
||||
// containing the cluster timecode. Read mVInt bytes into
|
||||
// mClusterTimecode.
|
||||
READ_CLUSTER_TIMECODE,
|
||||
|
||||
// Skips elements with a cluster until BLOCKGROUP_ID or SIMPLEBLOCK_ID
|
||||
// is found. If BLOCKGROUP_ID is found, the parser returns to
|
||||
// ANY_BLOCK_ID searching for a BLOCK_ID. Once a block or simpleblock
|
||||
// is found, the current data offset is stored in mBlockOffset. If the
|
||||
// current byte is the beginning of a four byte variant integer, it
|
||||
// indicates the parser has reached a top-level element ID and the
|
||||
// parser returns to CLUSTER_SYNC.
|
||||
ANY_BLOCK_SYNC,
|
||||
|
||||
// Start reading a block. Blocks and simpleblocks are parsed the same
|
||||
// way as the initial layouts are identical. mBlockSize is initialized
|
||||
// from mVInt (holding the element size), and mBlockTimecode(Length) is
|
||||
// initialized for parsing.
|
||||
READ_BLOCK,
|
||||
|
||||
// Reads mBlockTimecodeLength bytes of data into mBlockTimecode. When
|
||||
// mBlockTimecodeLength reaches 0, the timecode has been read. The sum
|
||||
// of mClusterTimecode and mBlockTimecode is stored as a pair with
|
||||
// mBlockOffset into the offset-to-time map.
|
||||
READ_BLOCK_TIMECODE,
|
||||
|
||||
// Skip mSkipBytes of data before resuming parse at mNextState.
|
||||
SKIP_DATA,
|
||||
|
||||
// Skip the content of an element. mVInt holds the element length.
|
||||
SKIP_ELEMENT
|
||||
};
|
||||
|
||||
// Current state machine action.
|
||||
State mState;
|
||||
|
||||
// Next state machine action. SKIP_DATA and READ_VINT_REST advance to
|
||||
// mNextState when the current action completes.
|
||||
State mNextState;
|
||||
|
||||
// Match position within CLUSTER_ID. Used to find sync within arbitrary
|
||||
// data.
|
||||
PRUint32 mClusterIDPos;
|
||||
|
||||
// Variable length integer read from data.
|
||||
PRUint64 mVInt;
|
||||
|
||||
// Encoding length of mVInt. This is the total number of bytes used to
|
||||
// encoding mVInt's value.
|
||||
PRUint32 mVIntLength;
|
||||
|
||||
// Number of bytes of mVInt left to read. mVInt is complete once this
|
||||
// reaches 0.
|
||||
PRUint32 mVIntLeft;
|
||||
|
||||
// Size of the block currently being parsed. Any unused data within the
|
||||
// block is skipped once the block timecode has been parsed.
|
||||
PRUint64 mBlockSize;
|
||||
|
||||
// Cluster-level timecode.
|
||||
PRUint64 mClusterTimecode;
|
||||
|
||||
// Start offset of the block currently being parsed. Used as the byte
|
||||
// offset for the offset-to-time mapping once the block timecode has been
|
||||
// parsed.
|
||||
PRInt64 mBlockOffset;
|
||||
|
||||
// Block-level timecode. This is summed with mClusterTimecode to produce
|
||||
// an absolute timecode for the offset-to-time mapping.
|
||||
PRInt16 mBlockTimecode;
|
||||
|
||||
// Number of bytes of mBlockTimecode left to read.
|
||||
PRUint32 mBlockTimecodeLength;
|
||||
|
||||
// Count of bytes left to skip before resuming parse at mNextState.
|
||||
// Mostly used to skip block payload data after reading a block timecode.
|
||||
PRUint32 mSkipBytes;
|
||||
};
|
||||
|
||||
#endif
|
@ -42,6 +42,7 @@
|
||||
#include "nsMediaStream.h"
|
||||
#include "nsWebMReader.h"
|
||||
#include "VideoUtils.h"
|
||||
#include "nsTimeRanges.h"
|
||||
|
||||
using namespace mozilla;
|
||||
|
||||
@ -62,6 +63,8 @@ extern PRLogModuleInfo* gBuiltinDecoderLog;
|
||||
#endif
|
||||
|
||||
static const unsigned NS_PER_MS = 1000000;
|
||||
static const float NS_PER_S = 1e9;
|
||||
static const float MS_PER_S = 1e3;
|
||||
|
||||
// Functions for reading and seeking using nsMediaStream required for
|
||||
// nestegg_io. The 'user data' passed to these functions is the
|
||||
@ -118,8 +121,9 @@ nsWebMReader::nsWebMReader(nsBuiltinDecoder* aDecoder)
|
||||
mChannels(0),
|
||||
mVideoTrack(0),
|
||||
mAudioTrack(0),
|
||||
mAudioSamples(0),
|
||||
mAudioStartMs(-1),
|
||||
mAudioSamples(0),
|
||||
mTimecodeScale(1000000),
|
||||
mHasVideo(PR_FALSE),
|
||||
mHasAudio(PR_FALSE)
|
||||
{
|
||||
@ -208,6 +212,12 @@ nsresult nsWebMReader::ReadMetadata()
|
||||
mDecoder->GetStateMachine()->SetDuration(duration / NS_PER_MS);
|
||||
}
|
||||
|
||||
r = nestegg_tstamp_scale(mContext, &mTimecodeScale);
|
||||
if (r == -1) {
|
||||
Cleanup();
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
unsigned int ntracks = 0;
|
||||
r = nestegg_track_count(mContext, &ntracks);
|
||||
if (r == -1) {
|
||||
@ -418,7 +428,7 @@ PRBool nsWebMReader::DecodeAudioPacket(nestegg_packet* aPacket)
|
||||
while ((samples = vorbis_synthesis_pcmout(&mVorbisDsp, &pcm)) > 0) {
|
||||
float* buffer = new float[samples * mChannels];
|
||||
float* p = buffer;
|
||||
for (PRUint32 i = 0; i < samples; ++i) {
|
||||
for (PRUint32 i = 0; i < PRUint32(samples); ++i) {
|
||||
for (PRUint32 j = 0; j < mChannels; ++j) {
|
||||
*p++ = pcm[j][i];
|
||||
}
|
||||
@ -677,7 +687,8 @@ PRBool nsWebMReader::DecodeVideoFrame(PRBool &aKeyframeSkip,
|
||||
return PR_TRUE;
|
||||
}
|
||||
|
||||
nsresult nsWebMReader::Seek(PRInt64 aTarget, PRInt64 aStartTime, PRInt64 aEndTime, PRInt64 aCurrentTime)
|
||||
nsresult nsWebMReader::Seek(PRInt64 aTarget, PRInt64 aStartTime, PRInt64 aEndTime,
|
||||
PRInt64 aCurrentTime)
|
||||
{
|
||||
MonitorAutoEnter mon(mMonitor);
|
||||
NS_ASSERTION(mDecoder->OnStateMachineThread(),
|
||||
@ -693,7 +704,110 @@ nsresult nsWebMReader::Seek(PRInt64 aTarget, PRInt64 aStartTime, PRInt64 aEndTim
|
||||
return DecodeToTarget(aTarget);
|
||||
}
|
||||
|
||||
void nsWebMReader::CalculateBufferedForRange(nsTimeRanges* aBuffered,
|
||||
PRInt64 aStartOffset, PRInt64 aEndOffset)
|
||||
{
|
||||
// Find the first nsWebMTimeDataOffset at or after aStartOffset.
|
||||
PRUint32 start;
|
||||
mTimeMapping.GreatestIndexLtEq(aStartOffset, start);
|
||||
if (start == mTimeMapping.Length()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the first nsWebMTimeDataOffset at or before aEndOffset.
|
||||
PRUint32 end;
|
||||
if (!mTimeMapping.GreatestIndexLtEq(aEndOffset, end) && end > 0) {
|
||||
// No exact match, so adjust end to be the first entry before
|
||||
// aEndOffset.
|
||||
end -= 1;
|
||||
}
|
||||
|
||||
// Range is empty.
|
||||
if (end <= start) {
|
||||
return;
|
||||
}
|
||||
|
||||
NS_ASSERTION(mTimeMapping[start].mOffset >= aStartOffset &&
|
||||
mTimeMapping[end].mOffset <= aEndOffset,
|
||||
"Computed time range must lie within data range.");
|
||||
if (start > 0) {
|
||||
NS_ASSERTION(mTimeMapping[start - 1].mOffset <= aStartOffset,
|
||||
"Must have found least nsWebMTimeDataOffset for start");
|
||||
}
|
||||
if (end < mTimeMapping.Length() - 1) {
|
||||
NS_ASSERTION(mTimeMapping[end + 1].mOffset >= aEndOffset,
|
||||
"Must have found greatest nsWebMTimeDataOffset for end");
|
||||
}
|
||||
|
||||
float startTime = mTimeMapping[start].mTimecode * mTimecodeScale / NS_PER_S;
|
||||
float endTime = mTimeMapping[end].mTimecode * mTimecodeScale / NS_PER_S;
|
||||
aBuffered->Add(startTime, endTime);
|
||||
}
|
||||
|
||||
nsresult nsWebMReader::GetBuffered(nsTimeRanges* aBuffered, PRInt64 aStartTime)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
|
||||
nsMediaStream* stream = mDecoder->GetCurrentStream();
|
||||
|
||||
// Special case completely cached files. This also handles local files.
|
||||
if (stream->IsDataCachedToEndOfStream(0)) {
|
||||
uint64_t duration = 0;
|
||||
if (nestegg_duration(mContext, &duration) == 0) {
|
||||
aBuffered->Add(aStartTime / MS_PER_S, duration / NS_PER_S);
|
||||
}
|
||||
} else {
|
||||
PRInt64 startOffset = stream->GetNextCachedData(0);
|
||||
while (startOffset >= 0) {
|
||||
PRInt64 endOffset = stream->GetCachedDataEnd(startOffset);
|
||||
NS_ASSERTION(startOffset < endOffset, "Cached range invalid");
|
||||
|
||||
CalculateBufferedForRange(aBuffered, startOffset, endOffset);
|
||||
|
||||
// Advance to the next cached data range.
|
||||
startOffset = stream->GetNextCachedData(endOffset);
|
||||
NS_ASSERTION(startOffset == -1 || startOffset > endOffset,
|
||||
"Next cached range invalid");
|
||||
}
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void nsWebMReader::NotifyDataArrived(const char* aBuffer, PRUint32 aLength, PRUint32 aOffset)
|
||||
{
|
||||
PRUint32 idx;
|
||||
if (!mRangeParsers.GreatestIndexLtEq(aOffset, idx)) {
|
||||
// If the incoming data overlaps an already parsed range, adjust the
|
||||
// buffer so that we only reparse the new data. It's also possible to
|
||||
// have an overlap where the end of the incoming data is within an
|
||||
// already parsed range, but we don't bother handling that other than by
|
||||
// avoiding storing duplicate timecodes when the parser runs.
|
||||
if (idx != mRangeParsers.Length() && mRangeParsers[idx].mStartOffset <= aOffset) {
|
||||
// Complete overlap, skip parsing.
|
||||
if (aOffset + aLength <= mRangeParsers[idx].mCurrentOffset) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Partial overlap, adjust the buffer to parse only the new data.
|
||||
PRInt64 adjust = mRangeParsers[idx].mCurrentOffset - aOffset;
|
||||
NS_ASSERTION(adjust >= 0, "Overlap detection bug.");
|
||||
aBuffer += adjust;
|
||||
aLength -= PRUint32(adjust);
|
||||
} else {
|
||||
mRangeParsers.InsertElementAt(idx, nsWebMBufferedParser(aOffset));
|
||||
}
|
||||
}
|
||||
|
||||
mRangeParsers[idx].Append(reinterpret_cast<const unsigned char*>(aBuffer), aLength, mTimeMapping);
|
||||
|
||||
// Merge parsers with overlapping regions and clean up the remnants.
|
||||
PRUint32 i = 0;
|
||||
while (i + 1 < mRangeParsers.Length()) {
|
||||
if (mRangeParsers[i].mCurrentOffset >= mRangeParsers[i + 1].mStartOffset) {
|
||||
mRangeParsers[i + 1].mStartOffset = mRangeParsers[i].mStartOffset;
|
||||
mRangeParsers.RemoveElementAt(i);
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -41,6 +41,7 @@
|
||||
|
||||
#include "nsDeque.h"
|
||||
#include "nsBuiltinDecoderReader.h"
|
||||
#include "nsWebMBufferedParser.h"
|
||||
#include "nestegg/nestegg.h"
|
||||
#include "vpx/vpx_decoder.h"
|
||||
#include "vpx/vp8dx.h"
|
||||
@ -126,6 +127,7 @@ public:
|
||||
virtual nsresult ReadMetadata();
|
||||
virtual nsresult Seek(PRInt64 aTime, PRInt64 aStartTime, PRInt64 aEndTime, PRInt64 aCurrentTime);
|
||||
virtual nsresult GetBuffered(nsTimeRanges* aBuffered, PRInt64 aStartTime);
|
||||
virtual void NotifyDataArrived(const char* aBuffer, PRUint32 aLength, PRUint32 aOffset);
|
||||
|
||||
private:
|
||||
// Value passed to NextPacket to determine if we are reading a video or an
|
||||
@ -190,6 +192,21 @@ private:
|
||||
// Number of samples we've decoded since decoding began at mAudioStartMs.
|
||||
PRUint64 mAudioSamples;
|
||||
|
||||
// Time in ns by which raw timecodes from the media must be scaled to
|
||||
// produce absolute timecodes. Used by CalculateBufferedForRange.
|
||||
PRUint64 mTimecodeScale;
|
||||
|
||||
// Update aBuffered with the time range for the given data range.
|
||||
void CalculateBufferedForRange(nsTimeRanges* aBuffered,
|
||||
PRInt64 aStartOffset, PRInt64 aEndOffset);
|
||||
|
||||
// Sorted (by offset) map of data offsets to timecodes. Populated
|
||||
// on the main thread as data is received and parsed by nsWebMBufferedParsers.
|
||||
nsTArray<nsWebMTimeDataOffset> mTimeMapping;
|
||||
|
||||
// Sorted (by offset) live parser instances. Main thread only.
|
||||
nsTArray<nsWebMBufferedParser> mRangeParsers;
|
||||
|
||||
// Booleans to indicate if we have audio and/or video data
|
||||
PRPackedBool mHasVideo;
|
||||
PRPackedBool mHasAudio;
|
||||
|
Loading…
Reference in New Issue
Block a user