940 lines
25 KiB
C++
Executable File
940 lines
25 KiB
C++
Executable File
//-----------------------------------------------------------------------------
|
|
// Torque Game Engine
|
|
// Copyright (C) GarageGames.com, Inc.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#include "theoraPlayer.h"
|
|
#include "math/mMath.h"
|
|
#include "util/safeDelete.h"
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
TheoraTexture::MagicalTrevor::BufInf::BufInf()
|
|
{
|
|
id=0;
|
|
time=0;
|
|
next=NULL;
|
|
}
|
|
|
|
TheoraTexture::MagicalTrevor::MagicalTrevor()
|
|
{
|
|
mBufListHead = NULL;
|
|
mLastBufferID = -1;
|
|
|
|
mMutex = Mutex::createMutex();
|
|
}
|
|
|
|
TheoraTexture::MagicalTrevor::~MagicalTrevor()
|
|
{
|
|
// Cheat on freeing, since BufInf has no destructor.
|
|
mBuffPool.freeBlocks();
|
|
|
|
Mutex::destroyMutex(mMutex);
|
|
}
|
|
|
|
const F64 TheoraTexture::MagicalTrevor::advanceTime(const ALuint buffId)
|
|
{
|
|
MutexHandle handle;
|
|
handle.lock(mMutex);
|
|
|
|
// We basically find the last entry on the list that references
|
|
// this buffId, then count how much time it + all its followers
|
|
// contains, and return that amount. Then the list is truncated.
|
|
|
|
// Skip if we just saw this one... we'd better not go
|
|
// through all the buffers in one advanceTime call, that would
|
|
// confuse the hell out of this code.
|
|
if(mLastBufferID == buffId)
|
|
return 0.f;
|
|
|
|
mLastBufferID = buffId;
|
|
|
|
// Ok, find last occurence of buffId.
|
|
BufInf **walk = &mBufListHead;
|
|
|
|
BufInf **lastOccurence = NULL;
|
|
|
|
while(*walk)
|
|
{
|
|
if((*walk)->id == buffId)
|
|
lastOccurence = walk;
|
|
|
|
walk = &(*walk)->next;
|
|
}
|
|
|
|
if(lastOccurence == NULL)
|
|
{
|
|
Con::warnf("MagicalTrevor::advancetime - no last occurrence for buffer %d found!", buffId);
|
|
return 0.f;
|
|
}
|
|
|
|
// We've got the last occurrence, sum the time and truncate the list.
|
|
F64 timeSum = 0.f;
|
|
|
|
walk = lastOccurence;
|
|
|
|
while(*walk)
|
|
{
|
|
timeSum += (*walk)->time;
|
|
|
|
// Blast it and advance.
|
|
BufInf *del = *walk;
|
|
*walk = (*walk)->next;
|
|
mBuffPool.free(del);
|
|
}
|
|
|
|
return timeSum;
|
|
}
|
|
|
|
void TheoraTexture::MagicalTrevor::postBuffer(const ALuint buffId, const F64 duration)
|
|
{
|
|
MutexHandle handle;
|
|
handle.lock(mMutex);
|
|
|
|
// Stick the buffer at the front of the queue...
|
|
BufInf *walk = mBuffPool.alloc();
|
|
walk->id = buffId;
|
|
walk->time = duration;
|
|
|
|
// Link it in.
|
|
walk->next = mBufListHead;
|
|
mBufListHead = walk;
|
|
|
|
}
|
|
|
|
const U32 TheoraTexture::MagicalTrevor::getListSize() const
|
|
{
|
|
MutexHandle handle;
|
|
handle.lock(mMutex);
|
|
|
|
U32 size=0;
|
|
|
|
// Ok, find last occurence of buffId.
|
|
const BufInf *walk = mBufListHead;
|
|
while(walk)
|
|
{
|
|
size++;
|
|
walk = walk->next;
|
|
}
|
|
|
|
return size;
|
|
}
|
|
|
|
void TheoraTexture::MagicalTrevor::reset()
|
|
{
|
|
MutexHandle handle;
|
|
handle.lock(mMutex);
|
|
|
|
// Since we're mostly touched by the thread, let's make this a mutex
|
|
// operation.
|
|
|
|
mBuffPool.freeBlocks();
|
|
mBufListHead = NULL;
|
|
mLastBufferID = -1;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
TheoraTexture::TheoraTexture()
|
|
{
|
|
init();
|
|
}
|
|
|
|
TheoraTexture::TheoraTexture(const char* szFilename, bool fPlay, Audio::Description* desc)
|
|
{
|
|
init();
|
|
setFile(szFilename, fPlay, false, desc);
|
|
}
|
|
|
|
void TheoraTexture::init()
|
|
{
|
|
mReady = false;
|
|
mPlaying = false;
|
|
mHasVorbis = false;
|
|
mVorbisHandle = NULL;
|
|
mVorbisBuffer = NULL;
|
|
mPlayThread = NULL;
|
|
mTheoraFile = NULL;
|
|
mTextureHandle = NULL;
|
|
mMagicalTrevor.reset();
|
|
}
|
|
|
|
TheoraTexture::~TheoraTexture()
|
|
{
|
|
destroyTexture();
|
|
}
|
|
|
|
// tears down anything the texture has
|
|
void TheoraTexture::destroyTexture(bool restartOgg)
|
|
{
|
|
mPlaying = false;
|
|
|
|
// kill the thread if its playing
|
|
SAFE_DELETE(mPlayThread);
|
|
|
|
// kill the sound if its playing
|
|
if(mVorbisHandle)
|
|
{
|
|
alxStop(mVorbisHandle);
|
|
mVorbisHandle = NULL;
|
|
mVorbisBuffer = NULL; // this is already deleted in alxStop
|
|
mMagicalTrevor.reset();
|
|
}
|
|
|
|
if(mHasVorbis)
|
|
{
|
|
ogg_stream_clear(&mOggVorbisStream);
|
|
dMemset(&mOggVorbisStream, 0, sizeof(ogg_stream_state));
|
|
|
|
vorbis_dsp_clear(&mVorbisDspState);
|
|
dMemset(&mVorbisDspState, 0, sizeof(vorbis_dsp_state));
|
|
|
|
vorbis_block_clear(&mVorbisBlock);
|
|
dMemset(&mVorbisBlock, 0, sizeof(vorbis_block));
|
|
|
|
vorbis_comment_clear(&mVorbisComment);
|
|
vorbis_info_clear(&mVorbisInfo);
|
|
|
|
mHasVorbis = false;
|
|
mMagicalTrevor.reset();
|
|
}
|
|
|
|
if(mReady)
|
|
{
|
|
ogg_stream_clear(&mOggTheoraStream);
|
|
theora_clear(&mTheoraState);
|
|
theora_comment_clear(&mTheoraComment);
|
|
theora_info_clear(&mTheoraInfo);
|
|
ogg_sync_clear(&mOggSyncState);
|
|
}
|
|
|
|
// close the file if it's open
|
|
if(mTheoraFile)
|
|
{
|
|
ResourceManager->closeStream(mTheoraFile);
|
|
mTheoraFile = NULL;
|
|
}
|
|
|
|
if(restartOgg)
|
|
return;
|
|
|
|
// Set us to a null state.
|
|
mReady = false;
|
|
|
|
//SAFE_DELETE(mTextureHandle);
|
|
}
|
|
|
|
|
|
// Takes file name to open, and whether it should autoplay when loaded
|
|
bool TheoraTexture::setFile(const char* filename, bool doPlay, bool doRestart, Audio::Description* desc)
|
|
{
|
|
if(mPlaying)
|
|
stop();
|
|
|
|
if(mReady)
|
|
destroyTexture(doRestart);
|
|
|
|
// open the theora file
|
|
mTheoraFile = ResourceManager->openStream(filename);
|
|
|
|
mMagicalTrevor.reset();
|
|
|
|
if(!mTheoraFile)
|
|
{
|
|
Con::errorf("TheoraTexture::setFile - Theora file '%s' not found.", filename);
|
|
return false;
|
|
}
|
|
|
|
Con::printf("TheoraTexture - Loading file '%s'", filename);
|
|
|
|
// start up Ogg stream synchronization layer
|
|
ogg_sync_init(&mOggSyncState);
|
|
|
|
// init supporting Theora structures needed in header parsing
|
|
theora_comment_init(&mTheoraComment);
|
|
theora_info_init(&mTheoraInfo);
|
|
|
|
// init supporting Vorbis structures needed in header parsing
|
|
vorbis_comment_init(&mVorbisComment);
|
|
vorbis_info_init(&mVorbisInfo);
|
|
|
|
if(!parseHeaders())
|
|
{
|
|
// No theora stream found (must be a vorbis only file?)
|
|
// Clean up all the structs
|
|
theora_comment_clear(&mTheoraComment);
|
|
theora_info_clear(&mTheoraInfo);
|
|
|
|
// trash vorbis too, this class isn't for playing lone vorbis streams
|
|
vorbis_info_clear(&mVorbisInfo);
|
|
vorbis_comment_clear(&mVorbisComment);
|
|
|
|
Con::errorf("TheoraTexture::setFile - Failed to parse Ogg headers");
|
|
return false;
|
|
}
|
|
|
|
// If theora stream found, initialize decoders...
|
|
theora_decode_init(&mTheoraState, &mTheoraInfo);
|
|
|
|
// This is a work around for a bug in theora when you're using only the
|
|
// decoder (think its fixed in newest theora lib).
|
|
mTheoraState.internal_encode = NULL;
|
|
|
|
// Note our state.
|
|
Con::printf(" Ogg logical stream %x is Theora %dx%d %.02f fps video",
|
|
mOggTheoraStream.serialno,
|
|
mTheoraInfo.width,
|
|
mTheoraInfo.height,
|
|
(F64)mTheoraInfo.fps_numerator / (F64)mTheoraInfo.fps_denominator);
|
|
|
|
Con::printf(" - Frame content is %dx%d with offset (%d,%d).",
|
|
mTheoraInfo.frame_width,
|
|
mTheoraInfo.frame_height,
|
|
mTheoraInfo.offset_x,
|
|
mTheoraInfo.offset_y);
|
|
|
|
if(mHasVorbis)
|
|
{
|
|
vorbis_synthesis_init(&mVorbisDspState, &mVorbisInfo);
|
|
vorbis_block_init(&mVorbisDspState, &mVorbisBlock);
|
|
Con::printf(" Ogg logical stream %x is Vorbis %d channel %d Hz audio.",
|
|
mOggVorbisStream.serialno,
|
|
mVorbisInfo.channels,
|
|
mVorbisInfo.rate);
|
|
|
|
if(!(mHasVorbis = createAudioBuffers(desc)))
|
|
{
|
|
ogg_stream_clear(&mOggVorbisStream);
|
|
vorbis_block_clear(&mVorbisBlock);
|
|
vorbis_dsp_clear(&mVorbisDspState);
|
|
}
|
|
}
|
|
|
|
// Check again because the buffers might fail.
|
|
if(!mHasVorbis)
|
|
{
|
|
// no vorbis stream was found, throw out the vorbis structs
|
|
vorbis_info_clear(&mVorbisInfo);
|
|
vorbis_comment_clear(&mVorbisComment);
|
|
}
|
|
|
|
if(!mReady)
|
|
{
|
|
if(!createVideoBuffers())
|
|
{
|
|
// failed to create buffers, blow everything else up..
|
|
ogg_stream_clear(&mOggTheoraStream);
|
|
theora_clear(&mTheoraState);
|
|
theora_comment_clear(&mTheoraComment);
|
|
theora_info_clear(&mTheoraInfo);
|
|
ogg_sync_clear(&mOggSyncState);
|
|
|
|
// And destroy our texture.
|
|
destroyTexture();
|
|
|
|
return false;
|
|
}
|
|
|
|
mReady = true;
|
|
}
|
|
|
|
if(doPlay)
|
|
play();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool TheoraTexture::parseHeaders()
|
|
{
|
|
ogg_packet sOggPacket;
|
|
S32 nTheora = 0;
|
|
S32 nVorbis = 0;
|
|
S32 ret;
|
|
|
|
mHasVorbis = false;
|
|
|
|
// Parse the headers
|
|
// find theora and vorbis streams
|
|
// search pages till you find the headers
|
|
mTheoraFile->setPosition(0);
|
|
|
|
while(1)
|
|
{
|
|
ret = bufferOggPage();
|
|
if(ret == 0)
|
|
break;
|
|
|
|
if(!ogg_sync_pageout(&mOggSyncState, &mOggPage))
|
|
break;
|
|
|
|
ogg_stream_state testStream;
|
|
|
|
if(!ogg_page_bos(&mOggPage))
|
|
{
|
|
// this is not an initial header, queue it up
|
|
// exit stream header finding loop (headers always come before non header stuff)
|
|
queueOggPage(&mOggPage);
|
|
break;
|
|
}
|
|
|
|
// create a stream
|
|
ogg_stream_init(&testStream, ogg_page_serialno(&mOggPage));
|
|
ogg_stream_pagein(&testStream, &mOggPage);
|
|
ogg_stream_packetout(&testStream, &sOggPacket);
|
|
|
|
// test if its a theora header
|
|
if(theora_decode_header(&mTheoraInfo, &mTheoraComment, &sOggPacket) >= 0)
|
|
{
|
|
// it is theora, copy testStream over to the theora stream
|
|
dMemcpy(&mOggTheoraStream, &testStream, sizeof(testStream));
|
|
nTheora = 1;
|
|
}
|
|
// test if its vorbis
|
|
else if(vorbis_synthesis_headerin(&mVorbisInfo, &mVorbisComment, &sOggPacket) >= 0)
|
|
{
|
|
// it is vorbis, copy testStream over to the vorbis stream
|
|
dMemcpy(&mOggVorbisStream, &testStream, sizeof(testStream));
|
|
mHasVorbis = true;
|
|
nVorbis = 1;
|
|
}
|
|
else
|
|
{
|
|
// some other stream header? unsupported, toss it
|
|
ogg_stream_clear(&testStream);
|
|
}
|
|
|
|
// if both vorbis and theora have been found, exit loop
|
|
if(nVorbis && nTheora)
|
|
break;
|
|
}
|
|
|
|
if(!nTheora)
|
|
{
|
|
// no theora stream header found
|
|
Con::errorf("TheoraTexture::parseHeaders - No theora stream headers found.");
|
|
|
|
// HAVE to have theora, thats what this class is for. return failure
|
|
return false;
|
|
}
|
|
|
|
// we've now identified all the streams. parse (toss) the secondary header packets.
|
|
// it looks like we just have to throw out a few packets from the header page
|
|
// so that they arent mistaken as theora movie data? nothing is done with these things..
|
|
while((nTheora < 3) ||
|
|
(nVorbis && nVorbis < 3))
|
|
{
|
|
// look for further theora headers
|
|
|
|
while((nTheora < 3) &&
|
|
(ret = ogg_stream_packetout(&mOggTheoraStream, &sOggPacket)))
|
|
{
|
|
if(ret < 0)
|
|
{
|
|
Con::errorf("TheoraPlayer::parseHeaders - Error parsing Theora stream headers; corrupt stream? (nothing read?)");
|
|
return false;
|
|
}
|
|
|
|
if(theora_decode_header(&mTheoraInfo, &mTheoraComment, &sOggPacket))
|
|
{
|
|
Con::errorf("TheoraPlayer::parseHeaders - Error parsing Theora stream headers; corrupt stream? (failed to decode)");
|
|
return false;
|
|
}
|
|
|
|
// Sanity around corrupt headers.
|
|
nTheora++;
|
|
if(nTheora == 3)
|
|
break;
|
|
}
|
|
|
|
// look for more vorbis headers
|
|
while((nVorbis) &&
|
|
(nVorbis < 3) &&
|
|
(ret = ogg_stream_packetout(&mOggVorbisStream, &sOggPacket)))
|
|
{
|
|
if(ret < 0)
|
|
{
|
|
Con::errorf("Error parsing vorbis stream headers; corrupt stream? (nothing read?)");
|
|
return false;
|
|
}
|
|
if(vorbis_synthesis_headerin(&mVorbisInfo, &mVorbisComment, &sOggPacket))
|
|
{
|
|
Con::errorf("Error parsing Vorbis stream headers; corrupt stream? (bad synthesis_headerin)");
|
|
return false;
|
|
}
|
|
nVorbis++;
|
|
if(nVorbis == 3)
|
|
break;
|
|
}
|
|
|
|
// The header pages/packets will arrive before anything else we
|
|
// care about, or the stream is not obeying spec
|
|
// continue searching the next page for the headers
|
|
|
|
// put the next page into the theora stream
|
|
if(ogg_sync_pageout(&mOggSyncState, &mOggPage) > 0)
|
|
{
|
|
queueOggPage(&mOggPage);
|
|
}
|
|
else
|
|
{
|
|
// if there are no more pages, buffer another one
|
|
const S32 ret = bufferOggPage();
|
|
|
|
// if there is nothing left to buffer..
|
|
if(ret == 0)
|
|
{
|
|
Con::errorf("TheoraTexture::parseHeaders - End of file while searching for codec headers.");
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool TheoraTexture::createVideoBuffers()
|
|
{
|
|
// Set up our texture.
|
|
const GBitmap *bmp = new GBitmap(
|
|
getMax((U32)mTheoraInfo.frame_width, (U32)mTheoraInfo.width),
|
|
getMax((U32)mTheoraInfo.frame_height, (U32)mTheoraInfo.height),
|
|
false, GBitmap::RGB);
|
|
|
|
mTextureHandle = new TextureHandle(NULL, bmp, true);
|
|
|
|
// generate yuv conversion lookup tables
|
|
generateLookupTables();
|
|
return true;
|
|
}
|
|
|
|
bool TheoraTexture::createAudioBuffers(Audio::Description* desc)
|
|
{
|
|
// Just to be sure, clear out Trevor.
|
|
mMagicalTrevor.reset();
|
|
|
|
// if they didnt pass a description...
|
|
if(!desc)
|
|
{
|
|
// ...fill a default
|
|
static Audio::Description sDesc;
|
|
desc = &sDesc;
|
|
|
|
sDesc.mReferenceDistance = 1.0f;
|
|
sDesc.mMaxDistance = 100.0f;
|
|
sDesc.mConeInsideAngle = 360;
|
|
sDesc.mConeOutsideAngle = 360;
|
|
sDesc.mConeOutsideVolume = 1.0f;
|
|
sDesc.mConeVector.set(0, 0, 1);
|
|
sDesc.mEnvironmentLevel = 0.f;
|
|
sDesc.mLoopCount = -1;
|
|
sDesc.mMinLoopGap = 0;
|
|
sDesc.mMaxLoopGap = 0;
|
|
|
|
sDesc.mIs3D = false;
|
|
sDesc.mVolume = 1.0f;
|
|
sDesc.mIsLooping = false;
|
|
sDesc.mType = 1;
|
|
sDesc.mIsStreaming = true;
|
|
}
|
|
|
|
// create an audio handle to use
|
|
mVorbisHandle = alxCreateSource(desc, "oggMixedStream");
|
|
if(!mVorbisHandle)
|
|
{
|
|
Con::errorf("Could not alxCreateSource for oggMixedStream.\n");
|
|
return false;
|
|
}
|
|
|
|
// get a pointer for it
|
|
mVorbisBuffer = dynamic_cast<OggMixedStreamSource*>(alxFindAudioStreamSource(mVorbisHandle));
|
|
if(!mVorbisBuffer)
|
|
{
|
|
alxStop(mVorbisHandle); // not sure how alxStop would find it if i couldnt..
|
|
Con::errorf("Could not find oggMixedStreamSource ptr.");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// 6K!! memory needed to speed up theora player. Small price to pay!
|
|
static S32 sAdjCrr[256];
|
|
static S32 sAdjCrg[256];
|
|
static S32 sAdjCbg[256];
|
|
static S32 sAdjCbb[256];
|
|
static S32 sAdjY[256];
|
|
static U8 sClampBuff[1024];
|
|
static U8* sClamp = sClampBuff + 384;
|
|
|
|
// precalculate adjusted YUV values for faster RGB conversion
|
|
void TheoraTexture::generateLookupTables()
|
|
{
|
|
static bool fGenerated = false;
|
|
S32 i;
|
|
|
|
for(i = 0; i < 256; i++)
|
|
{
|
|
sAdjCrr[i] = (409 * (i - 128) + 128) >> 8;
|
|
sAdjCrg[i] = (208 * (i - 128) + 128) >> 8;
|
|
sAdjCbg[i] = (100 * (i - 128) + 128) >> 8;
|
|
sAdjCbb[i] = (516 * (i - 128) + 128) >> 8;
|
|
sAdjY[i] = (298 * (i - 16)) >> 8;
|
|
}
|
|
|
|
// and setup LUT clamp range
|
|
for(i = -384; i < 640; i++)
|
|
{
|
|
sClamp[i] = mClamp(i, 0, 0xFF);
|
|
}
|
|
}
|
|
|
|
void TheoraTexture::drawFrame()
|
|
{
|
|
yuv_buffer yuv;
|
|
|
|
// decode a frame! (into yuv)
|
|
theora_decode_YUVout(&mTheoraState, &yuv);
|
|
|
|
// get destination buffer (and 1 row offset)
|
|
GBitmap *bmp = mTextureHandle->getBitmap();
|
|
U8* dst0 = bmp->getAddress(0, 0);
|
|
U8 *dst1 = dst0 + bmp->getWidth() * bmp->bytesPerPixel;
|
|
|
|
// find picture offset
|
|
const S32 pictOffset = yuv.y_stride * mTheoraInfo.offset_y + mTheoraInfo.offset_x;
|
|
|
|
const U8 *pY0, *pY1, *pU, *pV;
|
|
|
|
for(S32 y = 0; y < yuv.y_height; y += 2)
|
|
{
|
|
// set pointers into yuv buffers (2 lines for y)
|
|
pY0 = yuv.y + pictOffset + y * (yuv.y_stride);
|
|
pY1 = yuv.y + pictOffset + (y | 1) * (yuv.y_stride);
|
|
pU = yuv.u + ((pictOffset + y * (yuv.uv_stride)) >> 1);
|
|
pV = yuv.v + ((pictOffset + y * (yuv.uv_stride)) >> 1);
|
|
|
|
for(S32 x = 0; x < yuv.y_width; x += 2)
|
|
{
|
|
// convert a 2x2 block over
|
|
|
|
// speed up G conversion a very very small amount ;)
|
|
const S32 G = sAdjCrg[*pV] + sAdjCbg[*pU];
|
|
|
|
// pixel 0x0
|
|
*dst0++ = sClamp[sAdjY[*pY0] + sAdjCrr[*pV]];
|
|
*dst0++ = sClamp[sAdjY[*pY0] - G];
|
|
*dst0++ = sClamp[sAdjY[*pY0++] + sAdjCbb[*pU]];
|
|
|
|
// pixel 1x0
|
|
*dst0++ = sClamp[sAdjY[*pY0] + sAdjCrr[*pV]];
|
|
*dst0++ = sClamp[sAdjY[*pY0] - G];
|
|
*dst0++ = sClamp[sAdjY[*pY0++] + sAdjCbb[*pU]];
|
|
|
|
// pixel 0x1
|
|
*dst1++ = sClamp[sAdjY[*pY1] + sAdjCrr[*pV]];
|
|
*dst1++ = sClamp[sAdjY[*pY1] - G];
|
|
*dst1++ = sClamp[sAdjY[*pY1++] + sAdjCbb[*pU]];
|
|
|
|
// pixel 1x1
|
|
*dst1++ = sClamp[sAdjY[*pY1] + sAdjCrr[*pV++]];
|
|
*dst1++ = sClamp[sAdjY[*pY1] - G];
|
|
*dst1++ = sClamp[sAdjY[*pY1++] + sAdjCbb[*pU++]];
|
|
}
|
|
|
|
// shift the destination pointers a row (loop incs 2 at a time)
|
|
dst0 = dst1;
|
|
dst1 += bmp->getWidth() * bmp->bytesPerPixel;
|
|
}
|
|
}
|
|
|
|
bool TheoraTexture::play()
|
|
{
|
|
if(mReady && !mPlaying)
|
|
mPlayThread = new Thread((ThreadRunFunction)playThread, (S32) this, 1);
|
|
|
|
return mPlayThread;
|
|
}
|
|
|
|
void TheoraTexture::stop()
|
|
{
|
|
mPlaying = false;
|
|
|
|
if(mPlayThread)
|
|
{
|
|
SAFE_DELETE(mPlayThread);
|
|
}
|
|
}
|
|
|
|
void TheoraTexture::playThread( void *udata )
|
|
{
|
|
TheoraTexture* pThis = (TheoraTexture *)udata;
|
|
pThis->playLoop();
|
|
}
|
|
|
|
bool TheoraTexture::playLoop()
|
|
{
|
|
bool fMoreVideo = true;
|
|
bool fMoreAudio = mHasVorbis;
|
|
|
|
// timing variables
|
|
F64 dVBuffTime = 0;
|
|
F64 dLastFrame = 0;
|
|
|
|
mPlaying = true;
|
|
mCurrentTime = 0.f;
|
|
mStartTick = Platform::getRealMilliseconds();
|
|
|
|
bool isAudioActive = false;
|
|
|
|
while(mPlaying)
|
|
{
|
|
if(fMoreAudio)
|
|
fMoreAudio = readyAudio();
|
|
|
|
if(fMoreVideo)
|
|
fMoreVideo = readyVideo(dLastFrame, dVBuffTime);
|
|
|
|
if(!fMoreVideo && !fMoreAudio)
|
|
break; // if we have no more audio to buffer, and no more video frames to display, we are done
|
|
|
|
// if we haven't started yet, start it! :)
|
|
if(!isAudioActive && mHasVorbis)
|
|
{
|
|
alxPlay(mVorbisHandle);
|
|
isAudioActive = true;
|
|
}
|
|
|
|
// if we're set for the next frame, sleep
|
|
/*S32 t = (int)((double) 1000 * (dVBuffTime - getTheoraTime()));
|
|
if(t>0)
|
|
Platform::sleep(t); */
|
|
U32 safety = 0;
|
|
while((dVBuffTime - getTheoraTime()) >= 0.f && safety < 500)
|
|
{
|
|
safety++;
|
|
Platform::sleep(2);
|
|
}
|
|
|
|
// time to draw the frame!
|
|
drawFrame();
|
|
|
|
// keep track of the last frame time
|
|
dLastFrame = getTheoraTime();
|
|
}
|
|
|
|
if(mHasVorbis)
|
|
{
|
|
alxStop(mVorbisHandle);
|
|
mMagicalTrevor.reset();
|
|
}
|
|
|
|
mPlaying = false;
|
|
mVorbisHandle = NULL;
|
|
mVorbisBuffer = NULL;
|
|
|
|
return false;
|
|
}
|
|
|
|
// ready a single frame (not a late one either)
|
|
bool TheoraTexture::readyVideo(F64 dLastFrame, F64& dVBuffTime)
|
|
{
|
|
ogg_packet sOggPacket;
|
|
|
|
while(1)
|
|
{
|
|
// get a video packet
|
|
if(ogg_stream_packetout(&mOggTheoraStream, &sOggPacket) > 0)
|
|
{
|
|
theora_decode_packetin(&mTheoraState, &sOggPacket);
|
|
|
|
dVBuffTime = theora_granule_time(&mTheoraState, mTheoraState.granulepos);
|
|
|
|
// check if this frame time has not passed yet.
|
|
// If the frame is late we need to decode additional
|
|
// ones and keep looping, since theora at this stage
|
|
// needs to decode all frames
|
|
const F64 dNow = getTheoraTime();
|
|
|
|
if((dVBuffTime - dNow) >= 0.0)
|
|
return true; // got a good frame, not late, ready to break out
|
|
else if((dNow - dLastFrame) >= 1.0)
|
|
return true; // display at least one frame per second, regardless
|
|
|
|
// else frame is dropped (its behind), look again
|
|
}
|
|
else // get another page
|
|
if(!demandOggPage()) // end of file
|
|
return false;
|
|
|
|
//else we got a page, try and get a frame out of it
|
|
}
|
|
}
|
|
|
|
|
|
// buffers up as much audio as it can fit into OggMixedStream audiostream thing
|
|
bool TheoraTexture::readyAudio()
|
|
{
|
|
#define BUFFERSIZE 8192
|
|
|
|
ogg_packet sOggPacket;
|
|
ALuint bufferId = 0;
|
|
S32 ret, i, count;
|
|
float **pcm;
|
|
|
|
// i was making buffers to fit the exact size
|
|
// but the memory manager doesn't seem to be working
|
|
// with multiple threads..
|
|
|
|
S16 samples[BUFFERSIZE]; // this should be large enough
|
|
|
|
// If i don't have a buffer to put samples into..
|
|
while(1)
|
|
{
|
|
bufferId = mVorbisBuffer->GetAvailableBuffer();
|
|
if(!bufferId) // buffered all that it cant fit
|
|
return true;
|
|
|
|
// Skip if we're ahead of the video...
|
|
|
|
// if the buffer is ready now
|
|
// fill it!
|
|
|
|
while(!(ret = vorbis_synthesis_pcmout(&mVorbisDspState, &pcm)))
|
|
{
|
|
// no pending audio; is there a pending packet to decode?
|
|
if(ogg_stream_packetout(&mOggVorbisStream, &sOggPacket) > 0)
|
|
{
|
|
if(vorbis_synthesis(&mVorbisBlock, &sOggPacket) == 0) // test for success!
|
|
vorbis_synthesis_blockin(&mVorbisDspState, &mVorbisBlock);
|
|
}
|
|
else // we need more data; break out to suck in another page
|
|
{
|
|
if(!demandOggPage())
|
|
return false; // end of file
|
|
}
|
|
}
|
|
|
|
// found samples to buffer!
|
|
for(count = i = 0; i < ret && i < (BUFFERSIZE * mVorbisInfo.channels); i++)
|
|
{
|
|
for(int j = 0; j < mVorbisInfo.channels; j++)
|
|
{
|
|
int val = (int)(pcm[j][i] * 32767.f);
|
|
if(val > 32767)
|
|
val = 32767;
|
|
if(val < -32768)
|
|
val = -32768;
|
|
|
|
samples[count++] = val;
|
|
}
|
|
}
|
|
|
|
// bump up my buffering position
|
|
vorbis_synthesis_read(&mVorbisDspState, i);
|
|
|
|
// by this point the buffer should be filled (or as close as its gonna get)
|
|
// ... Queue buffer
|
|
alBufferData( bufferId,
|
|
(mVorbisInfo.channels == 1) ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16,
|
|
samples,
|
|
count * 2,
|
|
mVorbisInfo.rate);
|
|
|
|
// Note the time for synchronization by magical trevor.
|
|
const F64 newTimeSlice = F64(ret) / F64(mVorbisInfo.rate);
|
|
|
|
mMagicalTrevor.postBuffer(bufferId, newTimeSlice);
|
|
|
|
mVorbisBuffer->QueueBuffer(bufferId);
|
|
}
|
|
}
|
|
|
|
F64 TheoraTexture::getTheoraTime()
|
|
{
|
|
if(mHasVorbis)
|
|
{
|
|
// We have audio, so synch to audio track.
|
|
ALint buf=-1;
|
|
alGetSourcei(mVorbisBuffer->mSource, AL_BUFFER, &buf);
|
|
mCurrentTime += mMagicalTrevor.advanceTime(buf);
|
|
return mCurrentTime;
|
|
}
|
|
else
|
|
{
|
|
// We have no audio, just synch to start time.
|
|
return (double)0.001 * (double)(Platform::getRealMilliseconds() - mStartTick);
|
|
}
|
|
}
|
|
|
|
|
|
// helpers
|
|
|
|
// function does whatever it can get pages into streams
|
|
bool TheoraTexture::demandOggPage()
|
|
{
|
|
while(1)
|
|
{
|
|
if(ogg_sync_pageout(&mOggSyncState,&mOggPage) > 0)
|
|
{
|
|
// found a page, queue it to its stream
|
|
queueOggPage(&mOggPage);
|
|
return true;
|
|
}
|
|
|
|
if(bufferOggPage() == 0)
|
|
{
|
|
// Ogg buffering stopped, end of file reached
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// grabs some more compressed bitstream and syncs it for page extraction
|
|
S32 TheoraTexture::bufferOggPage()
|
|
{
|
|
char *buffer = ogg_sync_buffer(&mOggSyncState, 4096);
|
|
|
|
// this is a bit of a hack, i guess, i dont know how else to do it
|
|
// you dont want to send extra data to ogg_sync or it will try and
|
|
// pull it out like its real data. thats no good
|
|
// grab current position
|
|
U32 bytes = mTheoraFile->getPosition();
|
|
|
|
mTheoraFile->read(4096, buffer);
|
|
|
|
// find out how much was read
|
|
bytes = mTheoraFile->getPosition() - bytes;
|
|
|
|
// give it to ogg and tell it how many bytes
|
|
ogg_sync_wrote(&mOggSyncState, bytes);
|
|
|
|
return bytes;
|
|
}
|
|
|
|
// try and put the page into the theora and vorbis streams,
|
|
// they wont accept pages that arent for them
|
|
S32 TheoraTexture::queueOggPage(ogg_page *page)
|
|
{
|
|
ogg_stream_pagein(&mOggTheoraStream, page);
|
|
if(mHasVorbis)
|
|
ogg_stream_pagein(&mOggVorbisStream, page);
|
|
return 0;
|
|
}
|
|
|
|
// returns a handle for OpenGL calls
|
|
U32 TheoraTexture::getGLName()
|
|
{
|
|
if(mTextureHandle)
|
|
{
|
|
mTextureHandle->refresh();
|
|
return mTextureHandle->getGLName();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// copies the newest texture data to openGL video memory
|
|
void TheoraTexture::refresh()
|
|
{
|
|
if(mTextureHandle)
|
|
mTextureHandle->refresh();
|
|
}
|