tge/engine/core/theoraPlayer.cc
2017-04-17 06:17:10 -06:00

969 lines
26 KiB
C++
Executable File

//-----------------------------------------------------------------------------
// 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();
}