mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-01-20 04:34:48 +00:00
698 lines
20 KiB
C++
698 lines
20 KiB
C++
//-----------------------------------------------------------------------------
|
|
// Copyright (c) 2012 GarageGames, LLC
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to
|
|
// deal in the Software without restriction, including without limitation the
|
|
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
// sell copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
// IN THE SOFTWARE.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#ifdef TORQUE_OGGTHEORA
|
|
|
|
/// If defined, uses a separate file stream to read Vorbis sound data
|
|
/// from the Ogg stream. This removes both the contention caused by
|
|
/// concurrently streaming from a single master stream as well as the
|
|
/// coupling between Theora reads and Vorbis reads that arises from
|
|
/// multiplexing.
|
|
#define SPLIT_VORBIS
|
|
|
|
|
|
#include "theoraTexture.h"
|
|
|
|
#include "sfx/sfxDescription.h"
|
|
#include "sfx/sfxSystem.h"
|
|
#include "sfx/sfxCommon.h"
|
|
#include "sfx/sfxMemoryStream.h"
|
|
#include "sfx/sfxSound.h"
|
|
#ifdef SPLIT_VORBIS
|
|
#include "sfx/media/sfxVorbisStream.h"
|
|
#endif
|
|
|
|
#include "core/stream/fileStream.h"
|
|
#include "core/ogg/oggInputStream.h"
|
|
#include "core/ogg/oggTheoraDecoder.h"
|
|
#include "core/ogg/oggVorbisDecoder.h"
|
|
#include "core/util/safeDelete.h"
|
|
#include "core/util/rawData.h"
|
|
#include "console/console.h"
|
|
#include "math/mMath.h"
|
|
#include "gfx/bitmap/gBitmap.h"
|
|
#include "gfx/gfxDevice.h"
|
|
#include "gfx/gfxFormatUtils.h"
|
|
#include "gfx/gfxTextureManager.h"
|
|
|
|
|
|
|
|
/// Profile for the video texture.
|
|
GFX_ImplementTextureProfile( GFXTheoraTextureProfile,
|
|
GFXTextureProfile::DiffuseMap,
|
|
GFXTextureProfile::NoMipmap | GFXTextureProfile::Dynamic,
|
|
GFXTextureProfile::None );
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
static const char* GetPixelFormatName( OggTheoraDecoder::EPixelFormat format )
|
|
{
|
|
switch( format )
|
|
{
|
|
case OggTheoraDecoder::PIXEL_FORMAT_420:
|
|
return "4:2:0";
|
|
case OggTheoraDecoder::PIXEL_FORMAT_422:
|
|
return "4:2:2";
|
|
case OggTheoraDecoder::PIXEL_FORMAT_444:
|
|
return "4:4:4";
|
|
|
|
case OggTheoraDecoder::PIXEL_FORMAT_Unknown: ;
|
|
}
|
|
return "Unknown";
|
|
}
|
|
|
|
//=============================================================================
|
|
// TheoraTexture::FrameReadItem implementation.
|
|
//=============================================================================
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
TheoraTexture::FrameReadItem::FrameReadItem( AsyncBufferedInputStream< TheoraTextureFrame*, IInputStream< OggTheoraFrame* >* >* stream, ThreadContext* context )
|
|
: Parent( context ),
|
|
mFrameStream( dynamic_cast< FrameStream* >( stream ) )
|
|
{
|
|
AssertFatal( mFrameStream != NULL, "TheoraTexture::FrameReadItem::FrameReadItem() - expecting stream of type 'FrameStream'" );
|
|
|
|
mAsyncState = mFrameStream->mAsyncState;
|
|
|
|
// Assign a TheoraTextureFrame record to us. The nature of
|
|
// AsyncBufferedInputStream ensures that we are always serial
|
|
// here so this is thread-safe.
|
|
|
|
mFrame = &mFrameStream->mFrames[ mFrameStream->mFrameIndex ];
|
|
mFrameStream->mFrameIndex = ( mFrameStream->mFrameIndex + 1 ) % FrameStream::NUM_FRAME_RECORDS;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void TheoraTexture::FrameReadItem::execute()
|
|
{
|
|
// Read Theora frame data.
|
|
|
|
OggTheoraFrame* frame;
|
|
if( mFrameStream->getSourceStream()->read( &frame, 1 ) != 1 )
|
|
return;
|
|
|
|
// Copy the data into the texture.
|
|
|
|
OggTheoraDecoder* decoder = mAsyncState->getTheora();
|
|
const U32 height = decoder->getFrameHeight();
|
|
const U32 framePitch = decoder->getFrameWidth() * 4;
|
|
|
|
GFXLockedRect* rect = mFrame->mLockedRect;
|
|
if( rect )
|
|
{
|
|
const U32 usePitch = getMin(framePitch, mFrame->mTexture->getWidth() * 4);
|
|
const U32 maxHeight = getMin(height, mFrame->mTexture->getHeight());
|
|
if( (framePitch == rect->pitch) && (height == maxHeight) )
|
|
dMemcpy( rect->bits, frame->data, rect->pitch * height );
|
|
else
|
|
{
|
|
// Scanline length does not match. Copy line by line.
|
|
|
|
U8* dst = rect->bits;
|
|
U8* src = ( U8* ) frame->data;
|
|
|
|
// Center the video if it is too big for the texture mode
|
|
if ( height > maxHeight )
|
|
src += framePitch * ((height - maxHeight) / 2);
|
|
if ( framePitch > usePitch )
|
|
src += (framePitch - usePitch) / 2;
|
|
for( U32 i = 0; i < maxHeight; ++ i )
|
|
{
|
|
dMemcpy( dst, src, usePitch );
|
|
dst += rect->pitch;
|
|
src += framePitch;
|
|
}
|
|
}
|
|
}
|
|
#ifdef TORQUE_DEBUG
|
|
else
|
|
Platform::outputDebugString( "[TheoraTexture] texture not locked on frame %i", frame->mFrameNumber );
|
|
#endif
|
|
|
|
// Copy frame metrics.
|
|
|
|
mFrame->mFrameNumber = frame->mFrameNumber;
|
|
mFrame->mFrameTime = frame->mFrameTime;
|
|
mFrame->mFrameDuration = frame->mFrameDuration;
|
|
|
|
// Yield the frame packet back to the Theora decoder.
|
|
|
|
decoder->reusePacket( frame );
|
|
|
|
// Buffer the frame.
|
|
|
|
mFrameStream->_onArrival( mFrame );
|
|
}
|
|
|
|
//=============================================================================
|
|
// TheoraTexture::FrameStream implementation.
|
|
//=============================================================================
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
TheoraTexture::FrameStream::FrameStream( AsyncState* asyncState, bool looping )
|
|
: Parent( asyncState->getTheora(), 0, FRAME_READ_AHEAD, looping ),
|
|
mAsyncState( asyncState ),
|
|
mFrameIndex( 0 )
|
|
{
|
|
// Create the textures.
|
|
|
|
OggTheoraDecoder* theora = asyncState->getTheora();
|
|
const U32 width = theora->getFrameWidth();
|
|
const U32 height = theora->getFrameHeight();
|
|
|
|
for( U32 i = 0; i < NUM_FRAME_RECORDS; ++ i )
|
|
{
|
|
mFrames[ i ].mTexture.set(
|
|
width,
|
|
height,
|
|
GFXFormatR8G8B8A8,
|
|
&GFXTheoraTextureProfile,
|
|
String::ToString( "Theora texture frame buffer %i (%s:%i)", i, __FILE__, __LINE__ )
|
|
);
|
|
}
|
|
|
|
acquireTextureLocks();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void TheoraTexture::FrameStream::acquireTextureLocks()
|
|
{
|
|
for( U32 i = 0; i < NUM_FRAME_RECORDS; ++ i )
|
|
if( !mFrames[ i ].mLockedRect )
|
|
mFrames[ i ].mLockedRect = mFrames[ i ].mTexture.lock();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void TheoraTexture::FrameStream::releaseTextureLocks()
|
|
{
|
|
for( U32 i = 0; i < NUM_FRAME_RECORDS; ++ i )
|
|
if( mFrames[ i ].mLockedRect )
|
|
{
|
|
mFrames[ i ].mTexture.unlock();
|
|
mFrames[ i ].mLockedRect = NULL;
|
|
}
|
|
}
|
|
|
|
//=============================================================================
|
|
// TheoraTexture::AsyncState implementation.
|
|
//=============================================================================
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
TheoraTexture::AsyncState::AsyncState( const ThreadSafeRef< OggInputStream >& oggStream, bool looping )
|
|
: mOggStream( oggStream ),
|
|
mTheoraDecoder( dynamic_cast< OggTheoraDecoder* >( oggStream->getDecoder( "Theora" ) ) ),
|
|
mVorbisDecoder( dynamic_cast< OggVorbisDecoder* >( oggStream->getDecoder( "Vorbis" ) ) ),
|
|
mCurrentTime( 0 )
|
|
{
|
|
if( mTheoraDecoder )
|
|
{
|
|
mTheoraDecoder->setTimeSource( this );
|
|
mFrameStream = new FrameStream( this, looping );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
TheoraTextureFrame* TheoraTexture::AsyncState::readNextFrame()
|
|
{
|
|
TheoraTextureFrame* frame;
|
|
if( mFrameStream->read( &frame, 1 ) )
|
|
return frame;
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void TheoraTexture::AsyncState::start()
|
|
{
|
|
mFrameStream->start();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void TheoraTexture::AsyncState::stop()
|
|
{
|
|
mFrameStream->stop();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool TheoraTexture::AsyncState::isAtEnd()
|
|
{
|
|
return mOggStream->isAtEnd();
|
|
}
|
|
|
|
//=============================================================================
|
|
// TheoraTexture implementation.
|
|
//=============================================================================
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
TheoraTexture::TheoraTexture()
|
|
: mPlaybackQueue( NULL ),
|
|
mCurrentFrame( NULL ),
|
|
mIsPaused( true )
|
|
{
|
|
GFXTextureManager::addEventDelegate( this, &TheoraTexture::_onTextureEvent );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
TheoraTexture::~TheoraTexture()
|
|
{
|
|
GFXTextureManager::removeEventDelegate( this, &TheoraTexture::_onTextureEvent );
|
|
_reset();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void TheoraTexture::_reset()
|
|
{
|
|
// Stop the async streams.
|
|
|
|
if( mAsyncState != NULL )
|
|
mAsyncState->stop();
|
|
|
|
// Delete the playback queue.
|
|
|
|
if( mPlaybackQueue )
|
|
SAFE_DELETE( mPlaybackQueue );
|
|
|
|
// Kill the sound source.
|
|
|
|
if( mSFXSource != NULL )
|
|
{
|
|
mSFXSource->stop();
|
|
SFX_DELETE( mSFXSource );
|
|
mSFXSource = NULL;
|
|
}
|
|
|
|
mLastFrameNumber = 0;
|
|
mNumDroppedFrames = 0;
|
|
mCurrentFrame = NULL;
|
|
mAsyncState = NULL;
|
|
mIsPaused = false;
|
|
mPlaybackTimer.reset();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void TheoraTexture::_onTextureEvent( GFXTexCallbackCode code )
|
|
{
|
|
switch( code )
|
|
{
|
|
case GFXZombify:
|
|
mCurrentFrame = NULL;
|
|
if( mAsyncState )
|
|
{
|
|
// Blast out work items and then release all texture locks.
|
|
|
|
ThreadPool::GLOBAL().flushWorkItems();
|
|
mAsyncState->getFrameStream()->releaseTextureLocks();
|
|
|
|
// The Theora decoder does not implement seeking at the moment,
|
|
// so we absolutely want to make sure we don't fall behind too far or
|
|
// we may end up having the decoder go crazy trying to skip through
|
|
// Ogg packets (even just reading these undecoded packets takes a
|
|
// lot of time). So, for the time being, just pause playback when
|
|
// we go zombie.
|
|
|
|
if( mSFXSource )
|
|
mSFXSource->pause();
|
|
else
|
|
mPlaybackTimer.pause();
|
|
}
|
|
break;
|
|
|
|
case GFXResurrect:
|
|
if( mAsyncState )
|
|
{
|
|
// Reacquire texture locks.
|
|
|
|
mAsyncState->getFrameStream()->acquireTextureLocks();
|
|
|
|
// Resume playback if we have paused it.
|
|
|
|
if( !mIsPaused )
|
|
{
|
|
if( mSFXSource )
|
|
mSFXSource->play();
|
|
else
|
|
mPlaybackTimer.start();
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void TheoraTexture::_initVideo()
|
|
{
|
|
OggTheoraDecoder* theora = _getTheora();
|
|
|
|
// Set the decoder's pixel output format to match
|
|
// the texture format.
|
|
|
|
OggTheoraDecoder::PacketFormat format;
|
|
format.mFormat = GFXFormatR8G8B8A8;
|
|
format.mPitch = GFXFormatInfo( format.mFormat ).getBytesPerPixel() * theora->getFrameWidth();
|
|
|
|
theora->setPacketFormat( format );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void TheoraTexture::_initAudio( const ThreadSafeRef< SFXStream >& stream )
|
|
{
|
|
// Create an SFXDescription if we don't have one.
|
|
|
|
if( !mSFXDescription )
|
|
{
|
|
SFXDescription* description = new SFXDescription;
|
|
description->mIsStreaming = true;
|
|
description->registerObject();
|
|
description->setAutoDelete( true );
|
|
|
|
mSFXDescription = description;
|
|
}
|
|
|
|
// Create an SFX memory stream that consumes the output
|
|
// of the Vorbis decoder.
|
|
|
|
ThreadSafeRef< SFXStream > sfxStream = stream;
|
|
if( !sfxStream )
|
|
{
|
|
OggVorbisDecoder* vorbis = _getVorbis();
|
|
SFXFormat sfxFormat( vorbis->getNumChannels(),
|
|
vorbis->getNumChannels() * 16,
|
|
vorbis->getSamplesPerSecond() );
|
|
|
|
sfxStream = new SFXMemoryStream( sfxFormat, vorbis );
|
|
}
|
|
|
|
// Create the SFXSource.
|
|
|
|
mSFXSource = SFX->createSourceFromStream( sfxStream, mSFXDescription );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
TheoraTexture::TimeSourceType* TheoraTexture::_getTimeSource() const
|
|
{
|
|
if( mSFXSource != NULL )
|
|
return mSFXSource;
|
|
else
|
|
return ( TimeSourceType* ) &mPlaybackTimer;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
U32 TheoraTexture::getWidth() const
|
|
{
|
|
return _getTheora()->getFrameWidth();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
U32 TheoraTexture::getHeight() const
|
|
{
|
|
return _getTheora()->getFrameHeight();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool TheoraTexture::setFile( const String& filename, SFXDescription* desc )
|
|
{
|
|
_reset();
|
|
|
|
if( filename.isEmpty() )
|
|
return true;
|
|
|
|
// Check SFX profile.
|
|
|
|
if( desc && !desc->mIsStreaming )
|
|
{
|
|
Con::errorf( "TheoraTexture::setFile - Not a streaming SFXDescription" );
|
|
return false;
|
|
}
|
|
mSFXDescription = desc;
|
|
|
|
// Open the Theora file.
|
|
|
|
Stream* stream = FileStream::createAndOpen( filename, Torque::FS::File::Read );
|
|
if( !stream )
|
|
{
|
|
Con::errorf( "TheoraTexture::setFile - Theora file '%s' not found.", filename.c_str() );
|
|
return false;
|
|
}
|
|
|
|
// Create the OGG stream.
|
|
|
|
Con::printf( "TheoraTexture - Loading file '%s'", filename.c_str() );
|
|
|
|
ThreadSafeRef< OggInputStream > oggStream = new OggInputStream( stream );
|
|
oggStream->addDecoder< OggTheoraDecoder >();
|
|
#ifndef SPLIT_VORBIS
|
|
oggStream->addDecoder< OggVorbisDecoder >();
|
|
#endif
|
|
|
|
if( !oggStream->init() )
|
|
{
|
|
Con::errorf( "TheoraTexture - Failed to initialize OGG stream" );
|
|
return false;
|
|
}
|
|
|
|
mFilename = filename;
|
|
mAsyncState = new AsyncState( oggStream, desc ? desc->mIsLooping : false );
|
|
|
|
// Set up video.
|
|
|
|
OggTheoraDecoder* theoraDecoder = _getTheora();
|
|
if( !theoraDecoder )
|
|
{
|
|
Con::errorf( "TheoraTexture - '%s' is not a Theora file", filename.c_str() );
|
|
mAsyncState = NULL;
|
|
return false;
|
|
}
|
|
|
|
Con::printf( " - Theora: %ix%i pixels, %.02f fps, %s format",
|
|
theoraDecoder->getFrameWidth(),
|
|
theoraDecoder->getFrameHeight(),
|
|
theoraDecoder->getFramesPerSecond(),
|
|
GetPixelFormatName( theoraDecoder->getDecoderPixelFormat() ) );
|
|
|
|
_initVideo();
|
|
|
|
// Set up sound if we have it. For performance reasons, create
|
|
// a separate physical stream for the Vorbis sound data rather than
|
|
// using the bitstream from the multiplexed OGG master stream. The
|
|
// contention caused by the OGG page/packet feeding will otherwise
|
|
// slow us down significantly.
|
|
|
|
#ifdef SPLIT_VORBIS
|
|
|
|
stream = FileStream::createAndOpen( filename, Torque::FS::File::Read );
|
|
if( stream )
|
|
{
|
|
ThreadSafeRef< SFXStream > vorbisStream = SFXVorbisStream::create( stream );
|
|
if( !vorbisStream )
|
|
{
|
|
Con::errorf( "TheoraTexture - could not create Vorbis stream for '%s'", filename.c_str() );
|
|
|
|
// Stream is deleted by SFXVorbisStream.
|
|
}
|
|
else
|
|
{
|
|
Con::printf( " - Vorbis: %i channels, %i kHz",
|
|
vorbisStream->getFormat().getChannels(),
|
|
vorbisStream->getFormat().getSamplesPerSecond() / 1000 );
|
|
|
|
_initAudio( vorbisStream );
|
|
}
|
|
}
|
|
|
|
#else
|
|
|
|
OggVorbisDecoder* vorbisDecoder = _getVorbis();
|
|
if( vorbisDecoder )
|
|
{
|
|
Con::printf( " - Vorbis: %i bits, %i channels, %i kHz",
|
|
vorbisDecoder->getNumChannels(),
|
|
vorbisDecoder->getSamplesPerSecond() / 1000 );
|
|
|
|
_initAudio();
|
|
}
|
|
|
|
#endif
|
|
|
|
// Initiate the background request chain.
|
|
|
|
mAsyncState->start();
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool TheoraTexture::isPlaying() const
|
|
{
|
|
if( !mAsyncState || !mCurrentFrame )
|
|
return false;
|
|
|
|
if( mSFXSource )
|
|
return mSFXSource->isPlaying();
|
|
else
|
|
return mPlaybackTimer.isStarted();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void TheoraTexture::play()
|
|
{
|
|
if( isPlaying() )
|
|
return;
|
|
|
|
if( !mAsyncState )
|
|
setFile( mFilename, mSFXDescription );
|
|
|
|
// Construct playback queue that sync's to our time source,
|
|
// writes to us, and drops outdated packets.
|
|
|
|
if( !mPlaybackQueue )
|
|
mPlaybackQueue = new PlaybackQueueType( 1, _getTimeSource(), this, 0, true );
|
|
|
|
// Start playback.
|
|
|
|
if( mSFXSource )
|
|
mSFXSource->play();
|
|
else
|
|
mPlaybackTimer.start();
|
|
|
|
mIsPaused = false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void TheoraTexture::pause()
|
|
{
|
|
if( mSFXSource )
|
|
mSFXSource->pause();
|
|
else
|
|
mPlaybackTimer.pause();
|
|
|
|
mIsPaused = true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void TheoraTexture::stop()
|
|
{
|
|
_reset();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void TheoraTexture::refresh()
|
|
{
|
|
PROFILE_SCOPE( TheoraTexture_refresh );
|
|
|
|
if( !mAsyncState || !mPlaybackQueue )
|
|
return;
|
|
|
|
// Synchronize the async state to our current time.
|
|
// Unfortunately, we cannot set the Theora decoder to
|
|
// synchronize directly with us as our lifetime and the
|
|
// lifetime of our time sources isn't bound to the
|
|
// threaded state.
|
|
|
|
mAsyncState->syncTime( _getTimeSource()->getPosition() );
|
|
|
|
// Update the texture, if necessary.
|
|
|
|
bool haveFrame = false;
|
|
while( mPlaybackQueue->needPacket() )
|
|
{
|
|
// Lock the current frame.
|
|
|
|
if( mCurrentFrame && !mCurrentFrame->mLockedRect )
|
|
mCurrentFrame->mLockedRect = mCurrentFrame->mTexture.lock();
|
|
|
|
// Try to read a new frame.
|
|
|
|
TheoraTextureFrame* frame = mAsyncState->readNextFrame();
|
|
if( !frame )
|
|
break;
|
|
|
|
// Submit frame to queue.
|
|
|
|
mPlaybackQueue->submitPacket(
|
|
frame,
|
|
frame->mFrameDuration * 1000.f,
|
|
false,
|
|
frame->mFrameTime * 1000.f
|
|
);
|
|
|
|
// See if we have dropped frames.
|
|
|
|
if( frame->mFrameNumber != mLastFrameNumber + 1 )
|
|
mNumDroppedFrames += frame->mFrameNumber - mLastFrameNumber - 1;
|
|
mLastFrameNumber = frame->mFrameNumber;
|
|
|
|
haveFrame = true;
|
|
}
|
|
|
|
// Unlock current frame.
|
|
|
|
if( mCurrentFrame && mCurrentFrame->mLockedRect )
|
|
{
|
|
mCurrentFrame->mTexture.unlock();
|
|
mCurrentFrame->mLockedRect = NULL;
|
|
}
|
|
|
|
// Release async state if we have reached the
|
|
// end of the Ogg stream.
|
|
|
|
if( mAsyncState->isAtEnd() && !haveFrame )
|
|
_reset();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void TheoraTexture::write( TheoraTextureFrame* const* frames, U32 num )
|
|
{
|
|
if( !num )
|
|
return;
|
|
|
|
mCurrentFrame = frames[ num - 1 ]; // Only used last.
|
|
}
|
|
|
|
#endif // TORQUE_OGGTHEORA
|