2010-04-02 03:03:07 +00:00
|
|
|
/* -*- 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: ML 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 Foundation.
|
|
|
|
* Portions created by the Initial Developer are Copyright (C) 2010
|
|
|
|
* the Initial Developer. All Rights Reserved.
|
|
|
|
*
|
|
|
|
* Contributor(s):
|
|
|
|
* Chris Double <chris.double@double.co.nz>
|
|
|
|
* Chris Pearce <chris@pearce.org.nz>
|
|
|
|
*
|
|
|
|
* Alternatively, the contents of this file may be used under the terms of
|
|
|
|
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
|
|
|
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
|
|
|
* in which case the provisions of the GPL or the LGPL are applicable instead
|
|
|
|
* of those above. If you wish to allow use of your version of this file only
|
|
|
|
* under the terms of either the GPL or the LGPL, and not to allow others to
|
|
|
|
* use your version of this file under the terms of the MPL, indicate your
|
|
|
|
* decision by deleting the provisions above and replace them with the notice
|
|
|
|
* and other provisions required by the GPL or the LGPL. If you do not delete
|
|
|
|
* the provisions above, a recipient may use your version of this file under
|
|
|
|
* the terms of any one of the MPL, the GPL or the LGPL.
|
|
|
|
*
|
|
|
|
* ***** END LICENSE BLOCK ***** */
|
|
|
|
|
|
|
|
#include "nsISeekableStream.h"
|
|
|
|
#include "nsClassHashtable.h"
|
|
|
|
#include "nsTArray.h"
|
2010-05-06 02:31:02 +00:00
|
|
|
#include "nsBuiltinDecoder.h"
|
|
|
|
#include "nsBuiltinDecoderReader.h"
|
|
|
|
#include "nsBuiltinDecoderStateMachine.h"
|
2010-04-02 03:03:07 +00:00
|
|
|
#include "mozilla/mozalloc.h"
|
2010-04-27 08:53:44 +00:00
|
|
|
#include "VideoUtils.h"
|
2010-04-02 03:03:07 +00:00
|
|
|
|
2010-05-06 02:31:02 +00:00
|
|
|
using namespace mozilla;
|
2010-04-02 03:03:07 +00:00
|
|
|
|
|
|
|
// Un-comment to enable logging of seek bisections.
|
|
|
|
//#define SEEK_LOGGING
|
|
|
|
|
|
|
|
#ifdef PR_LOGGING
|
2010-04-27 08:53:44 +00:00
|
|
|
extern PRLogModuleInfo* gBuiltinDecoderLog;
|
|
|
|
#define LOG(type, msg) PR_LOG(gBuiltinDecoderLog, type, msg)
|
2010-04-02 03:03:07 +00:00
|
|
|
#ifdef SEEK_LOGGING
|
2010-04-27 08:53:44 +00:00
|
|
|
#define SEEK_LOG(type, msg) PR_LOG(gBuiltinDecoderLog, type, msg)
|
2010-04-02 03:03:07 +00:00
|
|
|
#else
|
|
|
|
#define SEEK_LOG(type, msg)
|
|
|
|
#endif
|
|
|
|
#else
|
|
|
|
#define LOG(type, msg)
|
|
|
|
#define SEEK_LOG(type, msg)
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// 32 bit integer multiplication with overflow checking. Returns PR_TRUE
|
|
|
|
// if the multiplication was successful, or PR_FALSE if the operation resulted
|
|
|
|
// in an integer overflow.
|
|
|
|
PRBool MulOverflow32(PRUint32 a, PRUint32 b, PRUint32& aResult) {
|
|
|
|
PRUint64 a64 = a;
|
|
|
|
PRUint64 b64 = b;
|
|
|
|
PRUint64 r64 = a64 * b64;
|
|
|
|
if (r64 > PR_UINT32_MAX)
|
|
|
|
return PR_FALSE;
|
|
|
|
aResult = static_cast<PRUint32>(r64);
|
|
|
|
return PR_TRUE;
|
|
|
|
}
|
|
|
|
|
2010-04-27 08:53:45 +00:00
|
|
|
VideoData* VideoData::Create(PRInt64 aOffset,
|
|
|
|
PRInt64 aTime,
|
2010-05-06 02:31:02 +00:00
|
|
|
const YCbCrBuffer &aBuffer,
|
2010-04-02 03:03:07 +00:00
|
|
|
PRBool aKeyframe,
|
2010-05-06 02:31:02 +00:00
|
|
|
PRInt64 aTimecode)
|
2010-04-02 03:03:07 +00:00
|
|
|
{
|
2010-05-06 02:31:02 +00:00
|
|
|
nsAutoPtr<VideoData> v(new VideoData(aOffset, aTime, aKeyframe, aTimecode));
|
2010-04-02 03:03:07 +00:00
|
|
|
for (PRUint32 i=0; i < 3; ++i) {
|
|
|
|
PRUint32 size = 0;
|
2010-05-06 02:31:02 +00:00
|
|
|
if (!MulOverflow32(PR_ABS(aBuffer.mPlanes[i].mHeight),
|
|
|
|
PR_ABS(aBuffer.mPlanes[i].mStride),
|
2010-04-02 03:03:07 +00:00
|
|
|
size))
|
|
|
|
{
|
|
|
|
// Invalid frame size. Skip this plane. The plane will have 0
|
|
|
|
// dimensions, thanks to our constructor.
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
unsigned char* p = static_cast<unsigned char*>(moz_xmalloc(size));
|
|
|
|
if (!p) {
|
|
|
|
NS_WARNING("Failed to allocate memory for video frame");
|
|
|
|
return nsnull;
|
|
|
|
}
|
2010-05-06 02:31:02 +00:00
|
|
|
v->mBuffer.mPlanes[i].mData = p;
|
|
|
|
v->mBuffer.mPlanes[i].mWidth = aBuffer.mPlanes[i].mWidth;
|
|
|
|
v->mBuffer.mPlanes[i].mHeight = aBuffer.mPlanes[i].mHeight;
|
|
|
|
v->mBuffer.mPlanes[i].mStride = aBuffer.mPlanes[i].mStride;
|
|
|
|
memcpy(v->mBuffer.mPlanes[i].mData, aBuffer.mPlanes[i].mData, size);
|
2010-04-02 03:03:07 +00:00
|
|
|
}
|
|
|
|
return v.forget();
|
|
|
|
}
|
|
|
|
|
2010-05-06 02:31:02 +00:00
|
|
|
nsBuiltinDecoderReader::nsBuiltinDecoderReader(nsBuiltinDecoder* aDecoder)
|
|
|
|
: mMonitor("media.decoderreader"),
|
|
|
|
mDecoder(aDecoder),
|
|
|
|
mDataOffset(0)
|
2010-04-02 03:03:07 +00:00
|
|
|
{
|
2010-05-06 02:31:02 +00:00
|
|
|
MOZ_COUNT_CTOR(nsBuiltinDecoderReader);
|
2010-04-02 03:03:07 +00:00
|
|
|
}
|
|
|
|
|
2010-05-06 02:31:02 +00:00
|
|
|
nsBuiltinDecoderReader::~nsBuiltinDecoderReader()
|
2010-04-02 03:03:07 +00:00
|
|
|
{
|
|
|
|
ResetDecode();
|
2010-05-06 02:31:02 +00:00
|
|
|
MOZ_COUNT_DTOR(nsBuiltinDecoderReader);
|
2010-04-02 03:03:07 +00:00
|
|
|
}
|
|
|
|
|
2010-05-06 02:31:02 +00:00
|
|
|
nsresult nsBuiltinDecoderReader::ResetDecode()
|
2010-04-02 03:03:07 +00:00
|
|
|
{
|
|
|
|
nsresult res = NS_OK;
|
|
|
|
|
|
|
|
mVideoQueue.Reset();
|
|
|
|
mAudioQueue.Reset();
|
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2010-05-06 02:31:02 +00:00
|
|
|
nsresult nsBuiltinDecoderReader::GetBufferedBytes(nsTArray<ByteRange>& aRanges)
|
2010-04-02 03:03:07 +00:00
|
|
|
{
|
2010-05-06 02:31:02 +00:00
|
|
|
NS_ASSERTION(mDecoder->OnStateMachineThread(),
|
2010-04-02 03:03:07 +00:00
|
|
|
"Should be on state machine thread.");
|
|
|
|
mMonitor.AssertCurrentThreadIn();
|
|
|
|
PRInt64 startOffset = mDataOffset;
|
2010-05-06 02:31:02 +00:00
|
|
|
nsMediaStream* stream = mDecoder->GetCurrentStream();
|
2010-04-02 03:03:07 +00:00
|
|
|
while (PR_TRUE) {
|
|
|
|
PRInt64 endOffset = stream->GetCachedDataEnd(startOffset);
|
|
|
|
if (endOffset == startOffset) {
|
|
|
|
// Uncached at startOffset.
|
|
|
|
endOffset = stream->GetNextCachedData(startOffset);
|
|
|
|
if (endOffset == -1) {
|
|
|
|
// Uncached at startOffset until endOffset of stream, or we're at
|
|
|
|
// the end of stream.
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Bytes [startOffset..endOffset] are cached.
|
|
|
|
PRInt64 startTime = -1;
|
|
|
|
PRInt64 endTime = -1;
|
|
|
|
if (NS_FAILED(ResetDecode())) {
|
|
|
|
return NS_ERROR_FAILURE;
|
|
|
|
}
|
|
|
|
FindStartTime(startOffset, startTime);
|
|
|
|
if (startTime != -1 &&
|
|
|
|
(endTime = FindEndTime(endOffset) != -1))
|
|
|
|
{
|
|
|
|
aRanges.AppendElement(ByteRange(startOffset,
|
|
|
|
endOffset,
|
|
|
|
startTime,
|
|
|
|
endTime));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
startOffset = endOffset;
|
|
|
|
}
|
|
|
|
if (NS_FAILED(ResetDecode())) {
|
|
|
|
return NS_ERROR_FAILURE;
|
|
|
|
}
|
|
|
|
return NS_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
ByteRange
|
2010-05-06 02:31:02 +00:00
|
|
|
nsBuiltinDecoderReader::GetSeekRange(const nsTArray<ByteRange>& ranges,
|
|
|
|
PRInt64 aTarget,
|
|
|
|
PRInt64 aStartTime,
|
|
|
|
PRInt64 aEndTime,
|
|
|
|
PRBool aExact)
|
2010-04-02 03:03:07 +00:00
|
|
|
{
|
2010-05-06 02:31:02 +00:00
|
|
|
NS_ASSERTION(mDecoder->OnStateMachineThread(),
|
2010-04-02 03:03:07 +00:00
|
|
|
"Should be on state machine thread.");
|
|
|
|
PRInt64 so = mDataOffset;
|
2010-05-06 02:31:02 +00:00
|
|
|
PRInt64 eo = mDecoder->GetCurrentStream()->GetLength();
|
2010-04-02 03:03:07 +00:00
|
|
|
PRInt64 st = aStartTime;
|
|
|
|
PRInt64 et = aEndTime;
|
|
|
|
for (PRUint32 i = 0; i < ranges.Length(); i++) {
|
|
|
|
const ByteRange &r = ranges[i];
|
|
|
|
if (r.mTimeStart < aTarget) {
|
|
|
|
so = r.mOffsetStart;
|
|
|
|
st = r.mTimeStart;
|
|
|
|
}
|
|
|
|
if (r.mTimeEnd >= aTarget && r.mTimeEnd < et) {
|
|
|
|
eo = r.mOffsetEnd;
|
|
|
|
et = r.mTimeEnd;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (r.mTimeStart < aTarget && aTarget <= r.mTimeEnd) {
|
|
|
|
// Target lies exactly in this range.
|
|
|
|
return ranges[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return aExact ? ByteRange() : ByteRange(so, eo, st, et);
|
|
|
|
}
|
|
|
|
|
2010-05-06 02:31:02 +00:00
|
|
|
VideoData* nsBuiltinDecoderReader::FindStartTime(PRInt64 aOffset,
|
2010-04-02 03:03:07 +00:00
|
|
|
PRInt64& aOutStartTime)
|
|
|
|
{
|
2010-05-06 02:31:02 +00:00
|
|
|
NS_ASSERTION(mDecoder->OnStateMachineThread(), "Should be on state machine thread.");
|
2010-04-02 03:03:07 +00:00
|
|
|
|
2010-05-06 02:31:02 +00:00
|
|
|
nsMediaStream* stream = mDecoder->GetCurrentStream();
|
2010-04-02 03:03:07 +00:00
|
|
|
|
|
|
|
stream->Seek(nsISeekableStream::NS_SEEK_SET, aOffset);
|
|
|
|
if (NS_FAILED(ResetDecode())) {
|
|
|
|
return nsnull;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Extract the start times of the bitstreams in order to calculate
|
|
|
|
// the duration.
|
|
|
|
PRInt64 videoStartTime = PR_INT64_MAX;
|
|
|
|
PRInt64 audioStartTime = PR_INT64_MAX;
|
|
|
|
VideoData* videoData = nsnull;
|
|
|
|
|
|
|
|
if (HasVideo()) {
|
2010-05-06 02:31:02 +00:00
|
|
|
videoData = DecodeToFirstData(&nsBuiltinDecoderReader::DecodeVideoFrame,
|
2010-04-02 03:03:07 +00:00
|
|
|
mVideoQueue);
|
|
|
|
if (videoData) {
|
|
|
|
videoStartTime = videoData->mTime;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (HasAudio()) {
|
2010-05-06 02:31:02 +00:00
|
|
|
SoundData* soundData = DecodeToFirstData(&nsBuiltinDecoderReader::DecodeAudioData,
|
2010-04-02 03:03:07 +00:00
|
|
|
mAudioQueue);
|
|
|
|
if (soundData) {
|
|
|
|
audioStartTime = soundData->mTime;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
PRInt64 startTime = PR_MIN(videoStartTime, audioStartTime);
|
|
|
|
if (startTime != PR_INT64_MAX) {
|
|
|
|
aOutStartTime = startTime;
|
|
|
|
}
|
|
|
|
|
|
|
|
return videoData;
|
|
|
|
}
|
|
|
|
|
2010-05-06 02:31:02 +00:00
|
|
|
template<class Data>
|
|
|
|
Data* nsBuiltinDecoderReader::DecodeToFirstData(DecodeFn aDecodeFn,
|
|
|
|
MediaQueue<Data>& aQueue)
|
2010-04-02 03:03:07 +00:00
|
|
|
{
|
2010-05-06 02:31:02 +00:00
|
|
|
PRBool eof = PR_FALSE;
|
|
|
|
while (!eof && aQueue.GetSize() == 0) {
|
2010-04-02 03:03:07 +00:00
|
|
|
{
|
2010-05-06 02:31:02 +00:00
|
|
|
MonitorAutoEnter decoderMon(mDecoder->GetMonitor());
|
|
|
|
if (mDecoder->GetDecodeState() == nsDecoderStateMachine::DECODER_STATE_SHUTDOWN) {
|
|
|
|
return nsnull;
|
2010-04-02 03:03:07 +00:00
|
|
|
}
|
|
|
|
}
|
2010-05-06 02:31:02 +00:00
|
|
|
eof = !(this->*aDecodeFn)();
|
2010-04-02 03:03:07 +00:00
|
|
|
}
|
2010-05-06 02:31:02 +00:00
|
|
|
Data* d = nsnull;
|
|
|
|
return (d = aQueue.PeekFront()) ? d : nsnull;
|
|
|
|
}
|
2010-04-02 03:03:07 +00:00
|
|
|
|
|
|
|
|