Engine directory for ticket #1

This commit is contained in:
DavidWyand-GG 2012-09-19 11:15:01 -04:00
parent 352279af7a
commit 7dbfe6994d
3795 changed files with 1363358 additions and 0 deletions

View file

@ -0,0 +1,261 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#include "core/ogg/oggInputStream.h"
#include "core/stream/stream.h"
#include "core/util/safeDelete.h"
//#define DEBUG_SPEW
//-----------------------------------------------------------------------------
// OggDecoder implementation.
//-----------------------------------------------------------------------------
OggDecoder::OggDecoder( const ThreadSafeRef< OggInputStream >& stream )
: mOggStream( stream )
{
}
OggDecoder::~OggDecoder()
{
ogg_stream_clear( &mOggStreamState );
}
void OggDecoder::_setStartPage( ogg_page* startPage )
{
ogg_stream_init( &mOggStreamState, ogg_page_serialno( startPage ) );
ogg_stream_pagein( &mOggStreamState, startPage );
}
bool OggDecoder::_readNextPacket( ogg_packet* packet )
{
MutexHandle mutex;
mutex.lock( &mMutex, true );
while( 1 )
{
int result = ogg_stream_packetout( &mOggStreamState, packet );
if( result == 0 )
{
if( !mOggStream->_requestData() )
return false;
}
else if( result < 0 )
return false;
else
{
#ifdef DEBUG_SPEW
Platform::outputDebugString( "[OggDecoder] read packet %i in %s (bytes: %i, bos: %s, eos: %s)",
( U32 ) packet->packetno,
getName(),
( U32 ) packet->bytes,
packet->b_o_s ? "1" : "0",
packet->e_o_s ? "1" : "0" );
#endif
return true;
}
}
}
bool OggDecoder::_nextPacket()
{
MutexHandle mutex;
mutex.lock( &mMutex, true );
ogg_packet packet;
do
{
if( !_readNextPacket( &packet ) )
return false;
}
while( !_packetin( &packet ) );
return true;
}
//-----------------------------------------------------------------------------
// OggInputStream implementation.
//-----------------------------------------------------------------------------
OggInputStream::OggInputStream( Stream* stream )
: mStream( stream ),
mIsAtEnd( false )
{
ogg_sync_init( &mOggSyncState );
VECTOR_SET_ASSOCIATION( mConstructors );
VECTOR_SET_ASSOCIATION( mDecoders );
}
OggInputStream::~OggInputStream()
{
_freeDecoders();
ogg_sync_clear( &mOggSyncState );
if( mStream )
SAFE_DELETE( mStream );
}
OggDecoder* OggInputStream::getDecoder( const String& name ) const
{
for( U32 i = 0; i < mDecoders.size(); ++ i )
if( name.equal( mDecoders[ i ]->getName(), String::NoCase ) )
return mDecoders[ i ];
return NULL;
}
bool OggInputStream::isAtEnd()
{
MutexHandle mutex;
mutex.lock( &mMutex, true );
return mIsAtEnd;
}
bool OggInputStream::init()
{
if( !mStream->hasCapability( Stream::StreamPosition ) )
return false;
mStream->setPosition( 0 );
// Read all beginning-of-stream pages and construct decoders
// for all streams we recognize.
while( 1 )
{
// Read next page.
ogg_page startPage;
_pullNextPage( &startPage );
// If not a beginning-of-stream page, push it to the decoders
// and stop reading headers.
if( !ogg_page_bos( &startPage ) )
{
_pushNextPage( &startPage );
break;
}
// Try the list of constructors for one that consumes
// the page.
for( U32 i = 0; i < mConstructors.size(); ++ i )
{
OggDecoder* decoder = mConstructors[ i ]( this );
if( decoder->_detect( &startPage ) )
mDecoders.push_back( decoder );
else
delete decoder;
}
}
// Initialize decoders and let all them finish up header processing.
for( U32 i = 0; i < mDecoders.size(); ++ i )
if( !mDecoders[ i ]->_init() )
{
delete mDecoders[ i ];
mDecoders.erase( i );
-- i;
}
if( !mDecoders.size() )
return false;
return true;
}
void OggInputStream::_freeDecoders()
{
for( U32 i = 0; i < mDecoders.size(); ++ i )
delete mDecoders[ i ];
mDecoders.clear();
}
bool OggInputStream::_pullNextPage( ogg_page* page)
{
// Read another page.
while( ogg_sync_pageout( &mOggSyncState, page ) != 1 )
{
enum { BUFFER_SIZE = 4096 };
// Read more data.
char* buffer = ogg_sync_buffer( &mOggSyncState, BUFFER_SIZE );
const U32 oldPos = mStream->getPosition();
mStream->read( BUFFER_SIZE, buffer );
const U32 numBytes = mStream->getPosition() - oldPos;
if( numBytes )
ogg_sync_wrote( &mOggSyncState, numBytes );
else
return false;
}
#ifdef DEBUG_SPEW
Platform::outputDebugString( "[OggInputStream] pulled next page (header: %i, body: %i)",
page->header_len, page->body_len );
#endif
return true;
}
void OggInputStream::_pushNextPage( ogg_page* page )
{
for( U32 i = 0; i < mDecoders.size(); ++ i )
{
MutexHandle mutex;
mutex.lock( &mDecoders[ i ]->mMutex, true );
ogg_stream_pagein( &mDecoders[ i ]->mOggStreamState, page );
}
}
bool OggInputStream::_requestData()
{
// Lock at this level to ensure correct ordering of page writes.
// Technically, the proper place to lock would be _pullNextPage
// but then it could happen that one thread pushes a page before
// another thread gets to push a page that has been read earlier.
MutexHandle mutex;
mutex.lock( &mMutex, true );
ogg_page nextPage;
if( !_pullNextPage( &nextPage ) )
{
mIsAtEnd = true;
return false;
}
_pushNextPage( &nextPage );
return true;
}

View file

@ -0,0 +1,173 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _OGGINPUTSTREAM_H_
#define _OGGINPUTSTREAM_H_
#ifndef _TVECTOR_H_
#include "core/util/tVector.h"
#endif
#ifndef _TYPETRAITS_H_
#include "platform/typetraits.h"
#endif
#ifndef _PLATFORM_THREADS_MUTEX_H_
#include "platform/threads/mutex.h"
#endif
#ifndef _THREADSAFEREFCOUNT_H_
#include "platform/threads/threadSafeRefCount.h"
#endif
#include "ogg/ogg.h"
class Stream;
class OggInputStream;
/// Single substream in a multiplexed OGG stream.
class OggDecoder
{
public:
typedef void Parent;
friend class OggInputStream;
protected:
/// The Ogg container stream.
OggInputStream* mOggStream;
/// The Ogg bitstream.
ogg_stream_state mOggStreamState;
/// Lock for synchronizing access to Ogg stream state.
Mutex mMutex;
/// Read the next packet in the stream.
/// @return false if there is no next packet.
bool _readNextPacket( ogg_packet* packet );
///
bool _nextPacket();
///
virtual bool _detect( ogg_page* startPage ) = 0;
///
virtual bool _init() = 0;
///
virtual bool _packetin( ogg_packet* packet ) = 0;
///
void _setStartPage( ogg_page* startPage );
public:
///
OggDecoder( const ThreadSafeRef< OggInputStream >& stream );
virtual ~OggDecoder();
/// Return the serial number of the Ogg bitstream.
U32 getStreamSerialNo() const { return mOggStreamState.serialno; }
///
virtual const char* getName() const = 0;
};
/// A multiplexed OGG input stream feeding into stream decoders.
class OggInputStream : public ThreadSafeRefCount< OggInputStream >
{
public:
typedef void Parent;
friend class OggDecoder; // _requestData
protected:
typedef OggDecoder* ( *Constructor )( const ThreadSafeRef< OggInputStream >& stream );
template< typename T >
struct _SpellItOutForGCC
{
static OggDecoder* _fn( const ThreadSafeRef< OggInputStream >& stream )
{
return constructSingle< T* >( stream );
}
};
///
bool mIsAtEnd;
///
Stream* mStream;
///
Vector< Constructor > mConstructors;
///
Vector< OggDecoder* > mDecoders;
///
ogg_sync_state mOggSyncState;
///
Mutex mMutex;
/// Pull the next page from the OGG stream.
bool _pullNextPage( ogg_page* page );
/// Push the given page to the attached decoder streams.
void _pushNextPage( ogg_page* page );
///
bool _requestData();
///
void _freeDecoders();
public:
///
/// @note Ownership of "stream" is transferred to OggInputStream.
OggInputStream( Stream* stream );
~OggInputStream();
/// Register a decoder class with the stream.
template< class T >
void addDecoder()
{
mConstructors.push_back( &_SpellItOutForGCC< T >::_fn );
}
///
OggDecoder* getDecoder( const String& name ) const;
///
bool init();
///
bool isAtEnd();
};
#endif // !_OGGINPUTSTREAM_H_

View file

@ -0,0 +1,694 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "core/ogg/oggTheoraDecoder.h"
#include "gfx/gfxFormatUtils.h"
#include "math/mMathFn.h"
#include "console/console.h"
//#define DEBUG_SPEW
//-----------------------------------------------------------------------------
// Lookup tables for the transcoders.
//
// The Y, Cb, and Cr tables are used by both the SSE2 and the generic transcoder.
// For the SSE2 code, the data must be 16 byte aligned.
//
// The clamping table is only used by the generic transcoder. The SSE2 transcoder
// uses instructions to implicitly clamp out-of-range values.
dALIGN( static S32 sRGBY[ 256 ][ 4 ] );
dALIGN( static S32 sRGBCb[ 256 ][ 4 ] );
dALIGN( static S32 sRGBCr[ 256 ][ 4 ] );
static U8 sClampBuff[ 1024 ];
static U8* sClamp = sClampBuff + 384;
static void initLookupTables()
{
static bool sGenerated = false;
if( !sGenerated )
{
for( S32 i = 0; i < 256; ++ i )
{
// Y.
sRGBY[ i ][ 0 ] = ( 298 * ( i - 16 ) ) >> 8; // B
sRGBY[ i ][ 1 ] = ( 298 * ( i - 16 ) ) >> 8; // G
sRGBY[ i ][ 2 ] = ( 298 * ( i - 16 ) ) >> 8; // R
sRGBY[ i ][ 3 ] = 0xff; // A
// Cb.
sRGBCb[ i ][ 0 ] = ( 516 * ( i - 128 ) + 128 ) >> 8; // B
sRGBCb[ i ][ 1 ] = - ( ( 100 * ( i - 128 ) + 128 ) >> 8 ); // G
// Cr.
sRGBCr[ i ][ 1 ] = - ( ( 208 * ( i - 128 ) + 128 ) >> 8 ); // B
sRGBCr[ i ][ 2 ] = ( 409 * ( i - 128 ) + 128 ) >> 8; // R
}
// Setup clamping table for generic transcoder.
for( S32 i = -384; i < 640; ++ i )
sClamp[ i ] = mClamp( i, 0, 0xFF );
sGenerated = true;
}
}
static inline S32 sampleG( U8* pCb, U8* pCr )
{
return sRGBCr[ *pCr ][ 1 ] + sRGBCr[ *pCb ][ 1 ];
}
//=============================================================================
// OggTheoraDecoder.
//=============================================================================
//-----------------------------------------------------------------------------
OggTheoraDecoder::OggTheoraDecoder( const ThreadSafeRef< OggInputStream >& stream )
: Parent( stream ),
#ifdef TORQUE_DEBUG
mLock( 0 ),
#endif
mTheoraSetup( NULL ),
mTheoraDecoder( NULL ),
mTranscoder( TRANSCODER_Auto )
{
// Initialize.
th_info_init( &mTheoraInfo );
th_comment_init( &mTheoraComment );
initLookupTables();
}
//-----------------------------------------------------------------------------
OggTheoraDecoder::~OggTheoraDecoder()
{
// Free packets on the freelist.
OggTheoraFrame* packet;
while( mFreePackets.tryPopFront( packet ) )
destructSingle( packet );
// Clean up libtheora structures.
if( mTheoraDecoder )
th_decode_free( mTheoraDecoder );
if( mTheoraSetup )
th_setup_free( mTheoraSetup );
th_comment_clear( &mTheoraComment );
th_info_clear( &mTheoraInfo );
}
//-----------------------------------------------------------------------------
bool OggTheoraDecoder::_detect( ogg_page* startPage )
{
_setStartPage( startPage );
// Read first header packet.
ogg_packet nextPacket;
if( !_readNextPacket( &nextPacket )
|| th_decode_headerin( &mTheoraInfo, &mTheoraComment, &mTheoraSetup, &nextPacket ) < 0 )
{
th_comment_clear( &mTheoraComment );
th_info_clear( &mTheoraInfo );
return false;
}
return true;
}
//-----------------------------------------------------------------------------
bool OggTheoraDecoder::_init()
{
ogg_packet nextPacket;
// Read header packets.
bool haveTheoraHeader = true;
while( 1 )
{
if( !_readNextPacket( &nextPacket ) )
{
haveTheoraHeader = false;
break;
}
int result = th_decode_headerin( &mTheoraInfo, &mTheoraComment, &mTheoraSetup, &nextPacket );
if( result < 0 )
{
haveTheoraHeader = false;
break;
}
else if( result == 0 )
break;
}
// Fail if we have no valid and complete Theora header.
if( !haveTheoraHeader )
{
th_comment_clear( &mTheoraComment );
th_info_clear( &mTheoraInfo );
Con::errorf( "OggTheoraDecoder::_init() - incorrect or corrupt Theora headers" );
return false;
}
// Init the decoder.
mTheoraDecoder = th_decode_alloc( &mTheoraInfo, mTheoraSetup );
// Feed the first video packet to the decoder.
ogg_int64_t granulePos;
th_decode_packetin( mTheoraDecoder, &nextPacket, &granulePos );
mCurrentFrameTime = th_granule_time( mTheoraDecoder, granulePos );
mCurrentFrameNumber = 0;
mFrameDuration = 1.f / getFramesPerSecond();
// Make sure we have a valid pitch.
if( !mPacketFormat.mPitch )
mPacketFormat.mPitch = getFrameWidth() * GFXFormatInfo( mPacketFormat.mFormat ).getBytesPerPixel();
return true;
}
//-----------------------------------------------------------------------------
bool OggTheoraDecoder::_packetin( ogg_packet* packet )
{
ogg_int64_t granulePos;
if( th_decode_packetin( mTheoraDecoder, packet, &granulePos ) != 0 )
return false;
// See if we should drop this frame.
//RDTODO: if we have fallen too far behind, start skipping pages
F32 granuleTime = th_granule_time( mTheoraDecoder, granulePos );
mCurrentFrameTime = granuleTime;
mCurrentFrameNumber ++;
bool dropThisFrame = false;
TimeSourceRef timeSource = mTimeSource;
if( timeSource )
{
F32 currentTick = F32( timeSource->getPosition() ) / 1000.f;
if( currentTick >= ( mCurrentFrameTime + mFrameDuration ) )
dropThisFrame = true;
}
#ifdef DEBUG_SPEW
Platform::outputDebugString( "[OggTheoraDecoder] new frame %i at %f sec%s",
U32( th_granule_frame( mTheoraDecoder, granulePos ) ),
granuleTime,
dropThisFrame ? " !! DROPPED !!" : "" );
#endif
return !dropThisFrame;
}
//-----------------------------------------------------------------------------
U32 OggTheoraDecoder::read( OggTheoraFrame** buffer, U32 num )
{
#ifdef TORQUE_DEBUG
AssertFatal( dCompareAndSwap( mLock, 0, 1 ), "OggTheoraDecoder::read() - simultaneous reads not thread-safe" );
#endif
U32 numRead = 0;
for( U32 i = 0; i < num; ++ i )
{
// Read and decode a packet.
if( !_nextPacket() )
return numRead; // End of stream.
// Decode the frame to Y'CbCr.
th_ycbcr_buffer ycbcr;
th_decode_ycbcr_out( mTheoraDecoder, ycbcr );
// Allocate a packet.
const U32 width = getFrameWidth();
const U32 height = getFrameHeight();
OggTheoraFrame* packet;
if( !mFreePackets.tryPopFront( packet ) )
packet = constructSingle< OggTheoraFrame* >( mPacketFormat.mPitch * height );
packet->mFrameNumber = mCurrentFrameNumber;
packet->mFrameTime = mCurrentFrameTime;
packet->mFrameDuration = mFrameDuration;
// Transcode the packet.
#if ( defined( TORQUE_COMPILER_GCC ) || defined( TORQUE_COMPILER_VISUALC ) ) && defined( TORQUE_CPU_X86 )
if( ( mTranscoder == TRANSCODER_Auto || mTranscoder == TRANSCODER_SSE2420RGBA ) &&
getDecoderPixelFormat() == PIXEL_FORMAT_420 &&
Platform::SystemInfo.processor.properties & CPU_PROP_SSE2 &&
mPacketFormat.mFormat == GFXFormatR8G8B8A8 &&
mTheoraInfo.pic_x == 0 &&
mTheoraInfo.pic_y == 0 )
{
_transcode420toRGBA_SSE2( ycbcr, ( U8* ) packet->data, width, height, mPacketFormat.mPitch );
}
else
#endif
{
// Use generic transcoder.
_transcode( ycbcr, ( U8* ) packet->data, width, height );
}
buffer[ i ] = packet;
++ numRead;
}
#ifdef TORQUE_DEBUG
AssertFatal( dCompareAndSwap( mLock, 1, 0 ), "" );
#endif
return numRead;
}
//-----------------------------------------------------------------------------
void OggTheoraDecoder::_transcode( th_ycbcr_buffer ycbcr, U8* buffer, const U32 width, const U32 height )
{
#define ycbcrToRGB( rgb, pY, pCb, pCr, G ) \
{ \
GFXPackPixel( \
mPacketFormat.mFormat, \
rgb, \
sClamp[ sRGBY[ *pY ][ 2 ] + sRGBCr[ *pCr ][ 2 ] ], \
sClamp[ sRGBY[ *pY ][ 1 ] + G ], \
sClamp[ sRGBY[ *pY ][ 0 ] + sRGBCb[ *pCb ][ 0 ] ], \
255 \
); \
}
// Determine number of chroma samples per 4-pixel luma block.
U32 numChromaSamples = 4;
EPixelFormat pixelFormat = getDecoderPixelFormat();
if( pixelFormat == PIXEL_FORMAT_422 )
numChromaSamples = 2;
else if( pixelFormat == OggTheoraDecoder::PIXEL_FORMAT_420 )
numChromaSamples = 1;
// Convert and copy the pixels. Deal with all three
// possible plane configurations.
const U32 pictOffsetY = _getPictureOffset( ycbcr, 0 );
const U32 pictOffsetU = _getPictureOffset( ycbcr, 1 );
const U32 pictOffsetV = _getPictureOffset( ycbcr, 2 );
for( U32 y = 0; y < height; y += 2 )
{
U8* dst0 = buffer + y * mPacketFormat.mPitch;
U8* dst1 = dst0 + mPacketFormat.mPitch;
U8* pY0 = _getPixelPtr( ycbcr, 0, pictOffsetY, 0, y );
U8* pY1 = _getPixelPtr( ycbcr, 0, pictOffsetY, 0, y + 1 );
U8* pU0 = _getPixelPtr( ycbcr, 1, pictOffsetU, 0, y );
U8* pU1 = _getPixelPtr( ycbcr, 1, pictOffsetU, 0, y + 1 );
U8* pV0 = _getPixelPtr( ycbcr, 2, pictOffsetV, 0, y );
U8* pV1 = _getPixelPtr( ycbcr, 2, pictOffsetV, 0, y + 1 );
for( U32 x = 0; x < width; x += 2 )
{
// Pixel 0x0.
S32 G = sampleG( pU0, pV0 );
ycbcrToRGB( dst0, pY0, pU0, pV0, G );
++ pY0;
if( numChromaSamples == 4 )
{
++ pU0;
++ pV0;
}
// Pixel 0x1.
if( numChromaSamples == 4 )
G = sampleG( pU0, pV0 );
ycbcrToRGB( dst0, pY0, pU0, pV0, G );
++ pY0;
++ pU0;
++ pV0;
// Pixel 1x0.
if( numChromaSamples != 1 )
G = sampleG( pU1, pV1 );
ycbcrToRGB( dst1, pY1, pU1, pV1, G );
++ pY1;
if( numChromaSamples == 4 )
{
++ pU1;
++ pV1;
}
// Pixel 1x1.
if( numChromaSamples == 4 )
G = sampleG( pU1, pV1 );
ycbcrToRGB( dst1, pY1, pU1, pV1, G );
++ pY1;
++ pU1;
++ pV1;
}
}
#undef ycbcrToRGB
}
//-----------------------------------------------------------------------------
void OggTheoraDecoder::_transcode420toRGBA_SSE2( th_ycbcr_buffer ycbcr, U8* buffer, U32 width, U32 height, U32 pitch )
{
AssertFatal( width % 2 == 0, "OggTheoraDecoder::_transcode420toRGBA_SSE2() - width must be multiple of 2" );
AssertFatal( height % 2 == 0, "OggTheoraDecoder::_transcode420toRGBA_SSE2() - height must be multiple of 2" );
unsigned char* ydata = ycbcr[ 0 ].data;
unsigned char* udata = ycbcr[ 1 ].data;
unsigned char* vdata = ycbcr[ 2 ].data;
S32* ycoeff = ( S32* ) sRGBY;
S32* ucoeff = ( S32* ) sRGBCb;
S32* vcoeff = ( S32* ) sRGBCr;
// At the end of a line loop, we need to jump over the padding resulting from the difference
// between pitch and width plus jump a whole scanline as we always operate two scanlines
// at a time.
const U32 stride = pitch - width * 4 + pitch;
// Same thing for the Y channel.
const U32 ystrideDelta = ycbcr[ 0 ].stride - width + ycbcr[ 0 ].stride;
const U32 ypitch = ycbcr[ 0 ].stride;
// U and V only jump a single scanline so we only need to advance by the padding on the
// right. Both planes are half-size.
const U32 ustrideDelta = ycbcr[ 1 ].stride - width / 2;
const U32 vstrideDelta = ycbcr[ 2 ].stride - width / 2;
#if defined( TORQUE_COMPILER_VISUALC ) && defined( TORQUE_CPU_X86 )
__asm
{
mov ecx,height
hloop:
push ecx
mov ecx,width
wloop:
push ecx
xor eax,eax
// Load and accumulate coefficients for U and V in XMM0.
mov esi,udata
mov ebx,ucoeff
mov edx,ydata
mov al,[esi]
xor ecx,ecx
mov edi,vdata
shl eax,4
movdqa xmm0,[ebx+eax]
mov ebx,vcoeff
mov cl,[edi]
mov esi,ycoeff
shl ecx,4
paddd xmm0,[ebx+ecx]
xor eax,eax
xor ebx,ebx
// Load coefficients for Y of the four pixels into XMM1-XMM4.
mov ecx,ypitch
mov al,[edx]
mov bl,[edx+1]
shl eax,4
shl ebx,4
movdqa xmm1,[esi+eax]
movdqa xmm2,[esi+ebx]
xor eax,eax
xor ebx,ebx
mov al,[edx+ecx]
mov bl,[edx+ecx+1]
shl eax,4
shl ebx,4
movdqa xmm3,[esi+eax]
movdqa xmm4,[esi+ebx]
mov edi,buffer
mov ecx,pitch
// Add Cb and Cr on top of Y.
paddd xmm1,xmm0
paddd xmm2,xmm0
paddd xmm3,xmm0
paddd xmm4,xmm0
// Pack pixels together. We need to pack twice per pixel
// to go from 32bits via 16bits to 8bits.
//
// Right now we're simply packing two garbage pixels for the
// second packing operation. An alternative would be to pack the
// four pixels into one XMM register and then do a packed shuffle
// to split out the lower two pixels before the move.
packssdw xmm1,xmm2
packssdw xmm3,xmm4
packuswb xmm1,xmm6
packuswb xmm3,xmm7
// Store pixels.
movq qword ptr [edi],xmm1
movq qword ptr [edi+ecx],xmm3
// Loop width.
pop ecx
add ydata,2
inc udata
inc vdata
add buffer,8
sub ecx,2
jnz wloop
// Loop height.
pop ecx
mov ebx,stride
mov eax,ystrideDelta
mov edi,ustrideDelta
mov esi,vstrideDelta
add buffer,ebx
add ydata,eax
add udata,edi
add vdata,esi
sub ecx,2
jnz hloop
};
#elif defined( TORQUE_COMPILER_GCC ) && defined( TORQUE_CPU_X86 )
asm( "pushal\n" // Save all general-purpose registers.
"movl %0,%%ecx\n" // Load height into ECX.
".hloop_sse:\n"
"pushl %%ecx\n" // Save counter.
"movl %1,%%ecx\n" // Load width into ECX.
".wloop_sse:\n"
"pushl %%ecx\n" // Save counter.
"xorl %%eax,%%eax\n" // Zero out eax for later use.
// Load and accumulate coefficients for U and V in XMM0.
"movl %3,%%esi\n" // Load U pointer into ESI.
"movl %8,%%ebx\n" // Load U coefficient table into EBX.
"movl %2,%%edx\n" // Load Y pointer into EDX.
"movb (%%esi),%%al\n" // Load U into AL.
"xorl %%ecx,%%ecx\n" // Clear ECX.
"movl %4,%%edi\n" // Load V pointer into EDI.
"shll $4,%%eax\n" // Multiply EAX by 16 to index into table.
"movdqa (%%ebx,%%eax),%%xmm0\n" // Load Cb coefficient into XMM0.
"movl %9,%%ebx\n" // Load V coefficients table into EBX.
"movb (%%edi),%%cl\n" // Load V into CL.
"movl %7,%%esi\n" // Load Y coefficients table into ESI.
"shll $4,%%ecx\n" // Multiply ECX by 16 to index into table.
"paddd (%%ebx,%%ecx),%%xmm0\n" // Add Cr coefficient to Cb coefficient.
"xorl %%eax,%%eax\n" // Clear EAX.
"xorl %%ebx,%%ebx\n" // Clear EBX.
// Load coefficients for Y of the four pixels into XMM1-XMM4.
"movl %14,%%ecx\n" // Load Y pitch into ECX (needed later for lower two pixels).
"movb (%%edx),%%al\n" // Load upper-left pixel Y into AL.
"movb 1(%%edx),%%bl\n" // Load upper-right pixel Y into BL.
"shll $4,%%eax\n" // Multiply EAX by 16 to index into table.
"shll $4,%%ebx\n" // Multiply EBX by 16 to index into table.
"movdqa (%%esi,%%eax),%%xmm1\n" // Load coefficient for upper-left pixel Y into XMM1.
"movdqa (%%esi,%%ebx),%%xmm2\n" // Load coefficient for upper-right pixel Y into XMM2.
"xorl %%eax,%%eax\n" // Clear EAX.
"xorl %%ebx,%%ebx\n" // Clear EBX.
"movb (%%edx,%%ecx),%%al\n" // Load lower-left pixel Y into AL.
"movb 1(%%edx,%%ecx),%%bl\n" // Load lower-right pixel Y into AL.
"shll $4,%%eax\n" // Multiply EAX by 16 to index into table.
"shll $4,%%ebx\n" // Multiply EBX by 16 to index into table.
"movdqa (%%esi,%%eax),%%xmm3\n" // Load coefficient for lower-left pixel Y into XMM3.
"movdqa (%%esi,%%ebx),%%xmm4\n" // Load coefficient for lower-right pixel Y into XMM4.
"movl %5,%%edi\n" // Load buffer pointer into EDI (for later use).
"movl %6,%%ecx\n" // Load pitch into ECX (for later use).
// Add Cb and Cr on top of Y.
"paddd %%xmm0,%%xmm1\n" // Add chroma channels to upper-left pixel.
"paddd %%xmm0,%%xmm2\n" // Add chroma channels to upper-right pixel.
"paddd %%xmm0,%%xmm3\n" // Add chroma channels to lower-left pixel.
"paddd %%xmm0,%%xmm4\n" // Add chroma channels to lower-right pixel.
// Pack pixels together. We need to pack twice per pixel
// to go from 32bits via 16bits to 8bits.
//
// Right now we're simply packing two garbage pixels for the
// second packing operation. An alternative would be to pack the
// four pixels into one XMM register and then do a packed shuffle
// to split out the lower two pixels before the move.
"packssdw %%xmm2,%%xmm1\n" // Pack 32bit channels together into 16bit channels on upper two pixels.
"packssdw %%xmm4,%%xmm3\n" // Pack 32bit channels together into 16bit channels on lower two pixels.
"packuswb %%xmm6,%%xmm1\n" // Pack 16bit channels together into 8bit channels on upper two pixels (plus two garbage pixels).
"packuswb %%xmm7,%%xmm3\n" // Pack 16bit channels together into 8bit channels on lower two pixels (plus two garbage pixels).
// Store pixels.
"movq %%xmm1,(%%edi)\n" // Store upper two pixels.
"movq %%xmm3,(%%edi,%%ecx)\n" // Store lower two pixels.
// Loop width.
"popl %%ecx\n" // Restore width counter.
"addl $2,%2\n" // Bump Y pointer by two pixels (1 bpp).
"incl %3\n" // Bump U pointer by one pixel (1 bpp).
"incl %4\n" // Bump V pointer by one pixel (1 bpp).
"addl $8,%5\n" // Bump buffer pointer by two pixels (4 bpp).
"subl $2,%%ecx\n"
"jnz .wloop_sse\n"
// Loop height.
"popl %%ecx\n" // Restore height counter.
"movl %10,%%ebx\n" // Load buffer stride into EBX.
"movl %11,%%eax\n" // Load Y stride delta into EAX.
"movl %12,%%edi\n" // Load U stride delta into EDI.
"movl %13,%%esi\n" // Load V stride delta into ESI.
"addl %%ebx,%5\n" // Bump buffer pointer by stride delta.
"addl %%eax,%2\n" // Bump Y pointer by stride delta.
"addl %%edi,%3\n" // Bump U pointer by stride delta.
"addl %%esi,%4\n" // Bump V pointer by stride delta.
"subl $2,%%ecx\n"
"jnz .hloop_sse\n"
"popal\n"
:
: "m" ( height ), // 0
"m" ( width ), // 1
"m" ( ydata ), // 2
"m" ( udata ), // 3
"m" ( vdata ), // 4
"m" ( buffer ), // 5
"m" ( pitch ), // 6
"m" ( ycoeff ), // 7
"m" ( ucoeff ), // 8
"m" ( vcoeff ), // 9
"m" ( stride ), // 10
"m" ( ystrideDelta ), // 11
"m" ( ustrideDelta ), // 12
"m" ( vstrideDelta ), // 13
"m" ( ypitch ) // 14
);
#endif
}

View file

@ -0,0 +1,267 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _OGGTHEORADECODER_H_
#define _OGGTHEORADECODER_H_
#ifndef _OGGINPUTSTREAM_H_
#include "core/ogg/oggInputStream.h"
#endif
#ifndef _TSTREAM_H_
#include "core/stream/tStream.h"
#endif
#ifndef _RAWDATA_H_
#include "core/util/rawData.h"
#endif
#ifndef _GFXENUMS_H_
#include "gfx/gfxEnums.h"
#endif
#ifndef _THREADSAFEDEQUE_H_
#include "platform/threads/threadSafeDeque.h"
#endif
#include "theora/theoradec.h"
/// A single decoded Theora video frame.
class OggTheoraFrame : public RawData
{
public:
typedef RawData Parent;
OggTheoraFrame() {}
OggTheoraFrame( S8* data, U32 size, bool ownMemory = false )
: Parent( data, size, ownMemory ) {}
/// Serial number of this frame in the stream.
U32 mFrameNumber;
/// Playtime in seconds at which to display this frame.
F32 mFrameTime;
/// Seconds to display this frame.
F32 mFrameDuration;
};
/// Decodes a Theora substream into frame packets.
///
/// Frame packets contain raw pixel data in the set pixel format (default is R8G8B8).
/// Reading on a thread is safe, but remember to keep a reference to the OggInputStream
/// master from the worker thread.
class OggTheoraDecoder : public OggDecoder,
public IInputStream< OggTheoraFrame* >
{
public:
typedef OggDecoder Parent;
/// Y'CbCr pixel format of the source video stream.
/// For informational purposes only. Packet out is determined
/// by PacketFormat.
enum EPixelFormat
{
PIXEL_FORMAT_444, // Full Y, full Cb, full Cr.
PIXEL_FORMAT_422, // Full Y, half-width Cb, half-width Cr.
PIXEL_FORMAT_420, // Full Y, half-widht+height Cb, half-width+height Cr.
PIXEL_FORMAT_Unknown
};
/// Descriptor for surface format that this stream should
/// decode into. This saves an otherwise potentitally necessary
/// swizzling step.
///
/// @note The output channel ordering will be in device format, i.e.
/// least-significant first.
struct PacketFormat
{
/// Pixel format.
GFXFormat mFormat;
/// Bytes per scanline.
U32 mPitch;
/// Default descriptor sets up for RGB.
PacketFormat()
: mFormat( GFXFormatR8G8B8 ),
mPitch( 0 ) {}
///
PacketFormat( GFXFormat format, U32 pitch )
: mFormat( format ),
mPitch( pitch ) {}
};
///
enum ETranscoder
{
TRANSCODER_Auto, ///< Auto-detect from current formats and processor capabilities.
TRANSCODER_Generic, ///< Generic transcoder that handles all source and target formats; 32bit integer + lookup tables.
TRANSCODER_SSE2420RGBA, ///< SSE2 transcoder with fixed 4:2:0 to RGBA conversion; 32bit integer + lookup tables.
};
protected:
typedef IPositionable< U32 >* TimeSourceRef;
/// @name libtheora Data
/// @{
///
th_comment mTheoraComment;
///
th_info mTheoraInfo;
///
th_setup_info* mTheoraSetup;
///
th_dec_ctx* mTheoraDecoder;
/// @}
///
PacketFormat mPacketFormat;
///
F32 mFrameDuration;
///
F32 mCurrentFrameTime;
///
U32 mCurrentFrameNumber;
/// If this is set, the decoder will drop frames that are
/// already outdated with respect to the time source.
///
/// @note Times are in milliseconds and in video time.
TimeSourceRef mTimeSource;
/// Transcoder to use for color space conversion. If the current
/// setting is invalid, will fall back to generic.
ETranscoder mTranscoder;
///
ThreadSafeDeque< OggTheoraFrame* > mFreePackets;
#ifdef TORQUE_DEBUG
U32 mLock;
#endif
/// Generic transcoder going from any of the Y'CbCr pixel formats to
/// any RGB format (that is supported by GFXFormatUtils).
void _transcode( th_ycbcr_buffer ycbcr, U8* buffer, U32 width, U32 height );
/// Transcoder with fixed 4:2:0 to RGBA conversion using SSE2 assembly.
void _transcode420toRGBA_SSE2( th_ycbcr_buffer ycbcr, U8* buffer, U32 width, U32 height, U32 pitch );
// OggDecoder.
virtual bool _detect( ogg_page* startPage );
virtual bool _init();
virtual bool _packetin( ogg_packet* packet );
///
U32 _getPixelOffset( th_ycbcr_buffer buffer, U32 plane, U32 x, U32 y ) const
{
switch( getDecoderPixelFormat() )
{
case PIXEL_FORMAT_444: break;
case PIXEL_FORMAT_422: if( plane != 0 ) x >>= 1; break;
case PIXEL_FORMAT_420: if( plane != 0 ) { x >>= 1; y >>= 1; } break;
default:
AssertFatal( false, "OggTheoraDecoder::_getPixelOffset() - invalid pixel format" );
}
return ( y * buffer[ plane ].stride + x );
}
///
U8* _getPixelPtr( th_ycbcr_buffer buffer, U32 plane, U32 offset, U32 x, U32 y ) const
{
return ( buffer[ plane ].data + offset + _getPixelOffset( buffer, plane, x, y ) );
}
///
U32 _getPictureOffset( th_ycbcr_buffer buffer, U32 plane )
{
return _getPixelOffset( buffer, plane, mTheoraInfo.pic_x, mTheoraInfo.pic_y );
}
public:
///
OggTheoraDecoder( const ThreadSafeRef< OggInputStream >& stream );
~OggTheoraDecoder();
/// Return the width of video image frames in pixels.
/// @note This returns the actual picture width rather than Theora's internal encoded frame width.
U32 getFrameWidth() const { return mTheoraInfo.pic_width; }
/// Return the height of video image frames in pixels.
/// @note This returns the actual picture height rather than Theora's internal encoded frame height.
U32 getFrameHeight() const { return mTheoraInfo.pic_height; }
///
F32 getFramesPerSecond() const { return ( F32( mTheoraInfo.fps_numerator ) / F32( mTheoraInfo.fps_denominator ) ); }
///
EPixelFormat getDecoderPixelFormat() const
{
switch( mTheoraInfo.pixel_fmt )
{
case TH_PF_444: return PIXEL_FORMAT_444;
case TH_PF_422: return PIXEL_FORMAT_422;
case TH_PF_420: return PIXEL_FORMAT_420;
default: return PIXEL_FORMAT_Unknown;
}
}
///
const PacketFormat& getPacketFormat() const { return mPacketFormat; }
///
void setPacketFormat( const PacketFormat& format ) { mPacketFormat = format; }
/// Set the reference time source. Frames will be dropped if the decoder
/// falls behind the time of this source.
///
/// @note The time source must have at least the same lifetime as the decoder.
void setTimeSource( const TimeSourceRef& timeSource ) { mTimeSource = timeSource; }
/// Set the Y'CbCr->RGB transcoder to use.
void setTranscoder( ETranscoder transcoder ) { mTranscoder = transcoder; }
///
void reusePacket( OggTheoraFrame* packet ) { mFreePackets.pushBack( packet ); }
// OggDecoder.
virtual const char* getName() const { return "Theora"; }
// IInputStream.
virtual U32 read( OggTheoraFrame** buffer, U32 num );
};
#endif // !_OGGTHEORADECODER_H_

View file

@ -0,0 +1,195 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#include "core/ogg/oggVorbisDecoder.h"
#include "console/console.h"
//#define DEBUG_SPEW
//-----------------------------------------------------------------------------
OggVorbisDecoder::OggVorbisDecoder( const ThreadSafeRef< OggInputStream >& stream )
: Parent( stream )
#ifdef TORQUE_DEBUG
, mLock( 0 )
#endif
{
// Initialize.
vorbis_info_init( &mVorbisInfo );
vorbis_comment_init( &mVorbisComment );
dMemset( &mVorbisBlock, 0, sizeof( mVorbisBlock ) );
dMemset( &mVorbisDspState, 0, sizeof( mVorbisDspState ) );
}
//-----------------------------------------------------------------------------
OggVorbisDecoder::~OggVorbisDecoder()
{
vorbis_block_clear( &mVorbisBlock );
vorbis_dsp_clear( &mVorbisDspState );
vorbis_info_clear( &mVorbisInfo );
vorbis_comment_clear( &mVorbisComment );
}
//-----------------------------------------------------------------------------
bool OggVorbisDecoder::_detect( ogg_page* startPage )
{
_setStartPage( startPage );
// Read initial header packet.
ogg_packet nextPacket;
if( !_readNextPacket( &nextPacket )
|| vorbis_synthesis_headerin( &mVorbisInfo, &mVorbisComment, &nextPacket ) < 0 )
{
vorbis_info_clear( &mVorbisInfo );
vorbis_comment_clear( &mVorbisComment );
return false;
}
return true;
}
//-----------------------------------------------------------------------------
bool OggVorbisDecoder::_init()
{
// Read header packets.
bool haveVorbisHeader = true;
for( U32 i = 0; i < 2; ++ i )
{
ogg_packet nextPacket;
if( !_readNextPacket( &nextPacket ) )
{
haveVorbisHeader = false;
break;
}
int result = vorbis_synthesis_headerin( &mVorbisInfo, &mVorbisComment, &nextPacket );
if( result != 0 )
{
haveVorbisHeader = false;
break;
}
}
// Fail if we don't have a complete and valid Vorbis header.
if( !haveVorbisHeader )
{
vorbis_info_clear( &mVorbisInfo );
vorbis_comment_clear( &mVorbisComment );
Con::errorf( "OggVorbisDecoder::_init() - Incorrect or corrupt Vorbis headers" );
return false;
}
// Init synthesis.
vorbis_synthesis_init( &mVorbisDspState, &mVorbisInfo );
vorbis_block_init( &mVorbisDspState, &mVorbisBlock );
return true;
}
//-----------------------------------------------------------------------------
bool OggVorbisDecoder::_packetin( ogg_packet* packet )
{
return ( vorbis_synthesis( &mVorbisBlock, packet ) == 0 );
}
//-----------------------------------------------------------------------------
U32 OggVorbisDecoder::read( RawData** buffer, U32 num )
{
#ifdef TORQUE_DEBUG
AssertFatal( dCompareAndSwap( mLock, 0, 1 ), "OggVorbisDecoder::read() - simultaneous reads not thread-safe" );
#endif
U32 numRead = 0;
for( U32 i = 0; i < num; ++ i )
{
float** pcmData;
U32 numSamples;
// Read sample data.
while( 1 )
{
numSamples = vorbis_synthesis_pcmout( &mVorbisDspState, &pcmData );
if( numSamples )
break;
else
{
if( !_nextPacket() )
return numRead; // End of stream.
vorbis_synthesis_blockin( &mVorbisDspState, &mVorbisBlock );
}
}
vorbis_synthesis_read( &mVorbisDspState, numSamples );
#ifdef DEBUG_SPEW
Platform::outputDebugString( "[OggVorbisDecoder] read %i samples", numSamples );
#endif
// Allocate a packet.
const U32 numChannels = getNumChannels();
RawData* packet = constructSingle< RawData* >( numSamples * 2 * numChannels ); // Two bytes per channel.
// Convert and copy the samples.
S16* samplePtr = ( S16* ) packet->data;
for( U32 n = 0; n < numSamples; ++ n )
for( U32 c = 0; c < numChannels; ++ c )
{
S32 val = S32( pcmData[ c ][ n ] * 32767.f );
if( val > 32767 )
val = 32767;
else if( val < -34768 )
val = -32768;
*samplePtr = val;
++ samplePtr;
}
// Success.
buffer[ i ] = packet;
numRead ++;
}
#ifdef TORQUE_DEBUG
AssertFatal( dCompareAndSwap( mLock, 1, 0 ), "" );
#endif
return numRead;
}

View file

@ -0,0 +1,91 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _OGGVORBISDECODER_H_
#define _OGGVORBISDECODER_H_
#ifndef _OGGINPUTSTREAM_H_
#include "core/ogg/oggInputStream.h"
#endif
#ifndef _TSTREAM_H_
#include "core/stream/tStream.h"
#endif
#ifndef _RAWDATA_H_
#include "core/util/rawData.h"
#endif
#include "vorbis/codec.h"
/// Decodes a Vorbis substream into sample packets.
///
/// Vorbis samples are always 16bits.
class OggVorbisDecoder : public OggDecoder,
public IInputStream< RawData* >
{
public:
typedef OggDecoder Parent;
protected:
///
vorbis_info mVorbisInfo;
///
vorbis_comment mVorbisComment;
///
vorbis_dsp_state mVorbisDspState;
///
vorbis_block mVorbisBlock;
#ifdef TORQUE_DEBUG
U32 mLock;
#endif
// OggDecoder.
virtual bool _detect( ogg_page* startPage );
virtual bool _init();
virtual bool _packetin( ogg_packet* packet );
public:
///
OggVorbisDecoder( const ThreadSafeRef< OggInputStream >& stream );
~OggVorbisDecoder();
///
U32 getNumChannels() const { return mVorbisInfo.channels; }
///
U32 getSamplesPerSecond() const { return mVorbisInfo.rate; }
// OggDecoder.
virtual const char* getName() const { return "Vorbis"; }
// IInputStream.
virtual U32 read( RawData** buffer, U32 num );
};
#endif // !_OGGVORBISDECODER_H_