Bug 566779 - Clean up media start and end time calculation. r=doublec

This commit is contained in:
Chris Pearce 2011-05-09 09:10:28 +12:00
parent b0ed41d802
commit c47b80f93b
10 changed files with 112 additions and 149 deletions

View File

@ -260,6 +260,11 @@ public:
// aDuration is in microseconds.
virtual void SetDuration(PRInt64 aDuration) = 0;
// Called while decoding metadata to set the end time of the media
// resource. The decoder monitor must be obtained before calling this.
// aEndTime is in microseconds.
virtual void SetEndTime(PRInt64 aEndTime) = 0;
// Functions used by assertions to ensure we're calling things
// on the appropriate threads.
virtual PRBool OnDecodeThread() const = 0;
@ -285,9 +290,15 @@ public:
virtual void ClearPositionChangeFlag() = 0;
// Called from the main thread to set whether the media resource can
// be seeked. The decoder monitor must be obtained before calling this.
// seek into unbuffered ranges. The decoder monitor must be obtained
// before calling this.
virtual void SetSeekable(PRBool aSeekable) = 0;
// Returns PR_TRUE if the media resource can seek into unbuffered ranges,
// as set by SetSeekable(). The decoder monitor must be obtained before
// calling this.
virtual PRBool GetSeekable() = 0;
// Update the playback position. This can result in a timeupdate event
// and an invalidate of the frame being dispatched asynchronously if
// there is no such event currently queued.

View File

@ -229,15 +229,10 @@ nsresult nsBuiltinDecoderReader::ResetDecode()
return res;
}
VideoData* nsBuiltinDecoderReader::FindStartTime(PRInt64 aOffset,
PRInt64& aOutStartTime)
VideoData* nsBuiltinDecoderReader::FindStartTime(PRInt64& aOutStartTime)
{
NS_ASSERTION(mDecoder->OnStateMachineThread(), "Should be on state machine thread.");
if (NS_FAILED(ResetDecode())) {
return nsnull;
}
// Extract the start times of the bitstreams in order to calculate
// the duration.
PRInt64 videoStartTime = PR_INT64_MAX;
@ -267,11 +262,6 @@ VideoData* nsBuiltinDecoderReader::FindStartTime(PRInt64 aOffset,
return videoData;
}
PRInt64 nsBuiltinDecoderReader::FindEndTime(PRInt64 aEndOffset)
{
return -1;
}
template<class Data>
Data* nsBuiltinDecoderReader::DecodeToFirstData(DecodeFn aDecodeFn,
MediaQueue<Data>& aQueue)

View File

@ -444,14 +444,9 @@ public:
virtual nsresult ReadMetadata(nsVideoInfo* aInfo) = 0;
// Stores the presentation time of the first frame/sample we'd be
// able to play if we started playback at aOffset, 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);
// able to play if we started playback at the current position. Returns
// the first video sample, if we have video.
VideoData* FindStartTime(PRInt64& aOutStartTime);
// Moves the decode head to aTime microseconds. aStartTime and aEndTime
// denote the start and end times of the media in usecs, and aCurrentTime

View File

@ -817,6 +817,10 @@ void nsBuiltinDecoderStateMachine::SetDuration(PRInt64 aDuration)
"Should be on main or state machine thread.");
mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
if (aDuration == -1) {
return;
}
if (mStartTime != -1) {
mEndTime = mStartTime + aDuration;
} else {
@ -825,6 +829,14 @@ void nsBuiltinDecoderStateMachine::SetDuration(PRInt64 aDuration)
}
}
void nsBuiltinDecoderStateMachine::SetEndTime(PRInt64 aEndTime)
{
NS_ASSERTION(OnStateMachineThread(), "Should be on state machine thread");
mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
mEndTime = aEndTime;
}
void nsBuiltinDecoderStateMachine::SetSeekable(PRBool aSeekable)
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
@ -1559,7 +1571,7 @@ VideoData* nsBuiltinDecoderStateMachine::FindStartTime()
VideoData* v = nsnull;
{
ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
v = mReader->FindStartTime(0, startTime);
v = mReader->FindStartTime(startTime);
}
if (startTime != 0) {
mStartTime = startTime;
@ -1580,30 +1592,6 @@ VideoData* nsBuiltinDecoderStateMachine::FindStartTime()
return v;
}
void nsBuiltinDecoderStateMachine::FindEndTime()
{
NS_ASSERTION(OnStateMachineThread(), "Should be on state machine thread.");
mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
nsMediaStream* stream = mDecoder->GetCurrentStream();
// Seek to the end of file to find the length and duration.
PRInt64 length = stream->GetLength();
NS_ASSERTION(length > 0, "Must have a content length to get end time");
mEndTime = 0;
PRInt64 endTime = 0;
{
ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
endTime = mReader->FindEndTime(length);
}
if (endTime != -1) {
mEndTime = endTime;
}
LOG(PR_LOG_DEBUG, ("%p Media end time is %lld", mDecoder, mEndTime));
}
void nsBuiltinDecoderStateMachine::UpdateReadyState() {
mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();

View File

@ -158,6 +158,7 @@ public:
virtual void Shutdown();
virtual PRInt64 GetDuration();
virtual void SetDuration(PRInt64 aDuration);
void SetEndTime(PRInt64 aEndTime);
virtual PRBool OnDecodeThread() const {
return IsCurrentThread(mDecodeThread);
}
@ -247,6 +248,11 @@ public:
return mEndTime;
}
PRBool GetSeekable() {
mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
return mSeekable;
}
// Sets the current frame buffer length for the MozAudioAvailable event.
// Accessed on the main and state machine threads.
virtual void SetFrameBufferLength(PRUint32 aLength);
@ -303,11 +309,6 @@ protected:
// machine thread.
VideoData* FindStartTime();
// Finds the end time of the last frame of data in the file, storing the value
// in mEndTime if successful. The decoder must be held with exactly one lock
// count. Called on the state machine thread.
void FindEndTime();
// Update only the state machine's current playback position (and duration,
// if unknown). Does not update the playback position on the decoder or
// media element -- use UpdatePlaybackPosition for that. Called on the state

View File

@ -45,24 +45,3 @@ nsOggDecoderStateMachine::nsOggDecoderStateMachine(nsBuiltinDecoder* aDecoder) :
nsBuiltinDecoderStateMachine(aDecoder, new nsOggReader(aDecoder))
{
}
void nsOggDecoderStateMachine::LoadMetadata()
{
nsBuiltinDecoderStateMachine::LoadMetadata();
// Get the duration from the media file. We only do this if the
// content length of the resource is known as we need to seek
// to the end of the file to get the last time field. We also
// only do this if the resource is seekable and if we haven't
// already obtained the duration via an HTTP header.
if (mState != DECODER_STATE_SHUTDOWN &&
mDecoder->GetCurrentStream()->GetLength() >= 0 &&
mSeekable &&
mEndTime == -1)
{
mDecoder->StopProgressUpdates();
FindEndTime();
mDecoder->StartProgressUpdates();
}
}

View File

@ -45,10 +45,6 @@ class nsOggDecoderStateMachine : public nsBuiltinDecoderStateMachine
{
public:
nsOggDecoderStateMachine(nsBuiltinDecoder* aDecoder);
// Overload LoadMetadata to seek to the end of the file and get the
// duration.
virtual void LoadMetadata();
};
#endif

View File

@ -261,13 +261,13 @@ nsresult nsOggReader::ReadMetadata(nsVideoInfo* aInfo)
mInfo.mHasVideo = PR_TRUE;
mInfo.mPixelAspectRatio = mTheoraState->mPixelAspectRatio;
mInfo.mPicture = nsIntRect(mTheoraState->mInfo.pic_x,
mTheoraState->mInfo.pic_y,
mTheoraState->mInfo.pic_width,
mTheoraState->mInfo.pic_height);
mTheoraState->mInfo.pic_y,
mTheoraState->mInfo.pic_width,
mTheoraState->mInfo.pic_height);
mInfo.mFrame = nsIntSize(mTheoraState->mInfo.frame_width,
mTheoraState->mInfo.frame_height);
mInfo.mDisplay = nsIntSize(mInfo.mPicture.width,
mInfo.mPicture.height);
mInfo.mPicture.height);
gfxIntSize sz(mTheoraState->mInfo.pic_width,
mTheoraState->mInfo.pic_height);
mDecoder->SetVideoData(sz,
@ -318,6 +318,32 @@ nsresult nsOggReader::ReadMetadata(nsVideoInfo* aInfo)
}
}
{
ReentrantMonitorAutoExit exitReaderMon(mReentrantMonitor);
ReentrantMonitorAutoEnter decoderMon(mDecoder->GetReentrantMonitor());
nsMediaStream* stream = mDecoder->GetCurrentStream();
if (mDecoder->GetStateMachine()->GetDuration() == -1 &&
mDecoder->GetStateMachine()->GetState() != nsDecoderStateMachine::DECODER_STATE_SHUTDOWN &&
stream->GetLength() >= 0 &&
mDecoder->GetStateMachine()->GetSeekable())
{
// We didn't get a duration from the index or a Content-Duration header.
// Seek to the end of file to find the end time.
PRInt64 length = stream->GetLength();
NS_ASSERTION(length > 0, "Must have a content length to get end time");
PRInt64 endTime = 0;
{
ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
endTime = RangeEndTime(length);
}
if (endTime != -1) {
mDecoder->GetStateMachine()->SetEndTime(endTime);
LOG(PR_LOG_DEBUG, ("Got Ogg duration from seeking to end %lld", endTime));
}
}
}
*aInfo = mInfo;
return NS_OK;
@ -401,27 +427,6 @@ PRBool nsOggReader::DecodeAudioData()
return PR_TRUE;
}
#ifdef DEBUG
// Ensures that all the VideoData in aFrames array are stored in increasing
// order by timestamp. Used in assertions in debug builds.
static PRBool
AllFrameTimesIncrease(nsTArray<nsAutoPtr<VideoData> >& aFrames)
{
PRInt64 prevTime = -1;
PRInt64 prevGranulepos = -1;
for (PRUint32 i = 0; i < aFrames.Length(); i++) {
VideoData* f = aFrames[i];
if (f->mTime < prevTime) {
return PR_FALSE;
}
prevTime = f->mTime;
prevGranulepos = f->mTimecode;
}
return PR_TRUE;
}
#endif
nsresult nsOggReader::DecodeTheora(ogg_packet* aPacket)
{
NS_ASSERTION(aPacket->granulepos >= TheoraVersion(&mTheoraState->mInfo,3,2,1),
@ -620,8 +625,7 @@ GetChecksum(ogg_page* page)
return c;
}
VideoData* nsOggReader::FindStartTime(PRInt64 aOffset,
PRInt64& aOutStartTime)
PRInt64 nsOggReader::RangeStartTime(PRInt64 aOffset)
{
NS_ASSERTION(mDecoder->OnStateMachineThread(),
"Should be on state machine thread.");
@ -629,30 +633,42 @@ VideoData* nsOggReader::FindStartTime(PRInt64 aOffset,
NS_ENSURE_TRUE(stream != nsnull, nsnull);
nsresult res = stream->Seek(nsISeekableStream::NS_SEEK_SET, aOffset);
NS_ENSURE_SUCCESS(res, nsnull);
return nsBuiltinDecoderReader::FindStartTime(aOffset, aOutStartTime);
PRInt64 startTime = 0;
nsBuiltinDecoderReader::FindStartTime(startTime);
return startTime;
}
PRInt64 nsOggReader::FindEndTime(PRInt64 aEndOffset)
struct nsAutoOggSyncState {
nsAutoOggSyncState() {
ogg_sync_init(&mState);
}
~nsAutoOggSyncState() {
ogg_sync_clear(&mState);
}
ogg_sync_state mState;
};
PRInt64 nsOggReader::RangeEndTime(PRInt64 aEndOffset)
{
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
NS_ASSERTION(mDecoder->OnStateMachineThread(),
"Should be on state machine thread.");
PRInt64 endTime = FindEndTime(0, aEndOffset, PR_FALSE, &mOggState);
// Reset read head to start of media data.
nsMediaStream* stream = mDecoder->GetCurrentStream();
NS_ENSURE_TRUE(stream != nsnull, -1);
nsresult res = stream->Seek(nsISeekableStream::NS_SEEK_SET, 0);
PRInt64 position = stream->Tell();
PRInt64 endTime = RangeEndTime(0, aEndOffset, PR_FALSE);
nsresult res = stream->Seek(nsISeekableStream::NS_SEEK_SET, position);
NS_ENSURE_SUCCESS(res, -1);
return endTime;
}
PRInt64 nsOggReader::FindEndTime(PRInt64 aStartOffset,
PRInt64 aEndOffset,
PRBool aCachedDataOnly,
ogg_sync_state* aState)
PRInt64 nsOggReader::RangeEndTime(PRInt64 aStartOffset,
PRInt64 aEndOffset,
PRBool aCachedDataOnly)
{
nsMediaStream* stream = mDecoder->GetCurrentStream();
ogg_sync_reset(aState);
nsAutoOggSyncState sync;
// We need to find the last page which ends before aEndOffset that
// has a granulepos that we can convert to a timestamp. We do this by
@ -669,7 +685,7 @@ PRInt64 nsOggReader::FindEndTime(PRInt64 aStartOffset,
PRBool mustBackOff = PR_FALSE;
while (PR_TRUE) {
ogg_page page;
int ret = ogg_sync_pageseek(aState, &page);
int ret = ogg_sync_pageseek(&sync.mState, &page);
if (ret == 0) {
// We need more data if we've not encountered a page we've seen before,
// or we've read to the end of file.
@ -681,7 +697,7 @@ PRInt64 nsOggReader::FindEndTime(PRInt64 aStartOffset,
mustBackOff = PR_FALSE;
prevChecksumAfterSeek = checksumAfterSeek;
checksumAfterSeek = 0;
ogg_sync_reset(aState);
ogg_sync_reset(&sync.mState);
readStartOffset = NS_MAX(static_cast<PRInt64>(0), readStartOffset - step);
readHead = NS_MAX(aStartOffset, readStartOffset);
}
@ -692,7 +708,7 @@ PRInt64 nsOggReader::FindEndTime(PRInt64 aStartOffset,
limit = NS_MIN(limit, static_cast<PRInt64>(step));
PRUint32 bytesToRead = static_cast<PRUint32>(limit);
PRUint32 bytesRead = 0;
char* buffer = ogg_sync_buffer(aState, bytesToRead);
char* buffer = ogg_sync_buffer(&sync.mState, bytesToRead);
NS_ASSERTION(buffer, "Must have buffer");
nsresult res;
if (aCachedDataOnly) {
@ -711,7 +727,7 @@ PRInt64 nsOggReader::FindEndTime(PRInt64 aStartOffset,
// Update the synchronisation layer with the number
// of bytes written to the buffer
ret = ogg_sync_wrote(aState, bytesRead);
ret = ogg_sync_wrote(&sync.mState, bytesRead);
if (ret != 0) {
endTime = -1;
break;
@ -761,8 +777,6 @@ PRInt64 nsOggReader::FindEndTime(PRInt64 aStartOffset,
}
}
ogg_sync_reset(aState);
return endTime;
}
@ -784,9 +798,9 @@ nsresult nsOggReader::GetSeekRanges(nsTArray<SeekRange>& aRanges)
}
PRInt64 startOffset = range.mStart;
PRInt64 endOffset = range.mEnd;
FindStartTime(startOffset, startTime);
startTime = RangeStartTime(startOffset);
if (startTime != -1 &&
((endTime = FindEndTime(endOffset)) != -1))
((endTime = RangeEndTime(endOffset)) != -1))
{
NS_ASSERTION(startTime < endTime,
"Start time must be before end time");
@ -1423,8 +1437,7 @@ nsresult nsOggReader::GetBuffered(nsTimeRanges* aBuffered, PRInt64 aStartTime)
// offset is after the end of the media stream, or there's no more cached
// data after the offset. This loop will run until we've checked every
// buffered range in the media, in increasing order of offset.
ogg_sync_state state;
ogg_sync_init(&state);
nsAutoOggSyncState sync;
for (PRUint32 index = 0; index < ranges.Length(); index++) {
// Ensure the offsets are after the header pages.
PRInt64 startOffset = ranges[index].mStart;
@ -1439,20 +1452,18 @@ nsresult nsOggReader::GetBuffered(nsTimeRanges* aBuffered, PRInt64 aStartTime)
// Find the start time of the range. Read pages until we find one with a
// granulepos which we can convert into a timestamp to use as the time of
// the start of the buffered range.
ogg_sync_reset(&state);
ogg_sync_reset(&sync.mState);
while (startTime == -1) {
ogg_page page;
PRInt32 discard;
PageSyncResult res = PageSync(stream,
&state,
&sync.mState,
PR_TRUE,
startOffset,
endOffset,
&page,
discard);
if (res == PAGE_SYNC_ERROR) {
// If we don't clear the sync state before exit we'll leak.
ogg_sync_clear(&state);
return NS_ERROR_FAILURE;
} else if (res == PAGE_SYNC_END_OF_RANGE) {
// Hit the end of range without reading a page, give up trying to
@ -1486,7 +1497,6 @@ nsresult nsOggReader::GetBuffered(nsTimeRanges* aBuffered, PRInt64 aStartTime)
else {
// Page is for a stream we don't know about (possibly a chained
// ogg), return an error.
ogg_sync_clear(&state);
return PAGE_SYNC_ERROR;
}
}
@ -1494,7 +1504,7 @@ nsresult nsOggReader::GetBuffered(nsTimeRanges* aBuffered, PRInt64 aStartTime)
if (startTime != -1) {
// We were able to find a start time for that range, see if we can
// find an end time.
PRInt64 endTime = FindEndTime(startOffset, endOffset, PR_TRUE, &state);
PRInt64 endTime = RangeEndTime(startOffset, endOffset, PR_TRUE);
if (endTime != -1) {
aBuffered->Add(startTime / static_cast<double>(USECS_PER_S),
(endTime - aStartTime) / static_cast<double>(USECS_PER_S));
@ -1502,9 +1512,6 @@ nsresult nsOggReader::GetBuffered(nsTimeRanges* aBuffered, PRInt64 aStartTime)
}
}
// If we don't clear the sync state before exit we'll leak.
ogg_sync_clear(&state);
return NS_OK;
}

View File

@ -71,13 +71,6 @@ public:
virtual PRBool DecodeVideoFrame(PRBool &aKeyframeSkip,
PRInt64 aTimeThreshold);
virtual VideoData* FindStartTime(PRInt64 aOffset,
PRInt64& aOutStartTime);
// Get the end time of aEndOffset. This is the playback position we'd reach
// after playback finished at aEndOffset.
virtual PRInt64 FindEndTime(PRInt64 aEndOffset);
virtual PRBool HasAudio()
{
mozilla::ReentrantMonitorAutoEnter mon(mReentrantMonitor);
@ -177,17 +170,25 @@ private:
PRInt64 aEndTime,
const nsTArray<SeekRange>& aRanges);
// Get the end time of aEndOffset. This is the playback position we'd reach
// after playback finished at aEndOffset.
PRInt64 RangeEndTime(PRInt64 aEndOffset);
// Get the end time of aEndOffset, without reading before aStartOffset.
// This is the playback position we'd reach after playback finished at
// aEndOffset. If PRBool aCachedDataOnly is PR_TRUE, then we'll only read
// from data which is cached in the media cached, otherwise we'll do
// regular blocking reads from the media stream. If PRBool aCachedDataOnly
// is PR_TRUE, and aState is not mOggState, this can safely be called on
// the main thread, otherwise it must be called on the state machine thread.
PRInt64 FindEndTime(PRInt64 aStartOffset,
PRInt64 aEndOffset,
PRBool aCachedDataOnly,
ogg_sync_state* aState);
// is PR_TRUE, this can safely be called on the main thread, otherwise it
// must be called on the state machine thread.
PRInt64 RangeEndTime(PRInt64 aStartOffset,
PRInt64 aEndOffset,
PRBool aCachedDataOnly);
// Get the start time of the range beginning at aOffset. This is the start
// time of the first frame and or audio sample we'd be able to play if we
// started playback at aOffset.
PRInt64 RangeStartTime(PRInt64 aOffset);
// Performs a seek bisection to move the media stream's read cursor to the
// last ogg page boundary which has end time before aTarget usecs on both the

View File

@ -305,11 +305,6 @@ nsresult nsRawReader::Seek(PRInt64 aTime, PRInt64 aStartTime, PRInt64 aEndTime,
return NS_OK;
}
PRInt64 nsRawReader::FindEndTime(PRInt64 aEndTime)
{
return -1;
}
nsresult nsRawReader::GetBuffered(nsTimeRanges* aBuffered, PRInt64 aStartTime)
{
return NS_OK;