added everything
This commit is contained in:
968
engine/core/theoraPlayer.cc
Executable file
968
engine/core/theoraPlayer.cc
Executable file
@@ -0,0 +1,968 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Torque Game Engine
|
||||
// Copyright (C) GarageGames.com, Inc.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "theoraPlayer.h"
|
||||
#include "math/mMath.h"
|
||||
#include "util/safeDelete.h"
|
||||
#include "platform/profiler.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)
|
||||
{
|
||||
PROFILE_START(TheoraTexture_readyVideo);
|
||||
|
||||
// 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)
|
||||
{
|
||||
PROFILE_END();
|
||||
return true; // got a good frame, not late, ready to break out
|
||||
}
|
||||
else if((dNow - dLastFrame) >= 1.0)
|
||||
{
|
||||
PROFILE_END();
|
||||
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
|
||||
{
|
||||
PROFILE_END();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//else we got a page, try and get a frame out of it
|
||||
PROFILE_END();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 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)
|
||||
{
|
||||
PROFILE_START(TheoraTexture_readyAudio);
|
||||
|
||||
bufferId = mVorbisBuffer->GetAvailableBuffer();
|
||||
if(!bufferId) // buffered all that it cant fit
|
||||
{
|
||||
PROFILE_END();
|
||||
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())
|
||||
{
|
||||
PROFILE_END();
|
||||
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;
|
||||
|
||||
#if defined(TORQUE_OS_MAC) && !defined(TORQUE_BIG_ENDIAN)
|
||||
samples[count++] = ((val << 8) & 0xFF00) | ((val >> 8) & 0x00FF);
|
||||
#else
|
||||
samples[count++] = val;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
PROFILE_END();
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
Reference in New Issue
Block a user