Bug 570904 - Implement buffered support for WebM. r=roc

This commit is contained in:
Matthew Gregan 2010-09-13 20:45:50 +12:00
parent 81a5a215c9
commit 48fd3a8330
13 changed files with 568 additions and 14 deletions

View File

@ -295,7 +295,9 @@ public:
virtual void UpdatePlaybackPosition(PRInt64 aTime) = 0;
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.

View File

@ -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

View File

@ -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.

View File

@ -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();

View File

@ -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,

View File

@ -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];
}

View File

@ -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);

View File

@ -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();

View File

@ -51,6 +51,7 @@ EXPORTS += \
$(NULL)
CPPSRCS = \
nsWebMBufferedParser.cpp \
nsWebMDecoder.cpp \
nsWebMReader.cpp \
$(NULL)

View 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;
}

View 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

View File

@ -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;
}
}
}

View File

@ -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;