theoraTexture.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699
  1. //-----------------------------------------------------------------------------
  2. // Copyright (c) 2012 GarageGames, LLC
  3. //
  4. // Permission is hereby granted, free of charge, to any person obtaining a copy
  5. // of this software and associated documentation files (the "Software"), to
  6. // deal in the Software without restriction, including without limitation the
  7. // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  8. // sell copies of the Software, and to permit persons to whom the Software is
  9. // furnished to do so, subject to the following conditions:
  10. //
  11. // The above copyright notice and this permission notice shall be included in
  12. // all copies or substantial portions of the Software.
  13. //
  14. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  19. // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  20. // IN THE SOFTWARE.
  21. //-----------------------------------------------------------------------------
  22. #ifdef TORQUE_OGGTHEORA
  23. /// If defined, uses a separate file stream to read Vorbis sound data
  24. /// from the Ogg stream. This removes both the contention caused by
  25. /// concurrently streaming from a single master stream as well as the
  26. /// coupling between Theora reads and Vorbis reads that arises from
  27. /// multiplexing.
  28. #define SPLIT_VORBIS
  29. #include "theoraTexture.h"
  30. #include "sfx/sfxDescription.h"
  31. #include "sfx/sfxSystem.h"
  32. #include "sfx/sfxCommon.h"
  33. #include "sfx/sfxMemoryStream.h"
  34. #include "sfx/sfxSound.h"
  35. #ifdef SPLIT_VORBIS
  36. #include "sfx/media/sfxVorbisStream.h"
  37. #endif
  38. #include "core/stream/fileStream.h"
  39. #include "core/ogg/oggInputStream.h"
  40. #include "core/ogg/oggTheoraDecoder.h"
  41. #include "core/ogg/oggVorbisDecoder.h"
  42. #include "core/util/safeDelete.h"
  43. #include "core/util/rawData.h"
  44. #include "console/console.h"
  45. #include "math/mMath.h"
  46. #include "gfx/bitmap/gBitmap.h"
  47. #include "gfx/gfxDevice.h"
  48. #include "gfx/gfxFormatUtils.h"
  49. #include "gfx/gfxTextureManager.h"
  50. /// Profile for the video texture.
  51. GFX_ImplementTextureProfile( GFXTheoraTextureProfile,
  52. GFXTextureProfile::DiffuseMap,
  53. GFXTextureProfile::NoMipmap | GFXTextureProfile::Dynamic,
  54. GFXTextureProfile::NONE );
  55. //-----------------------------------------------------------------------------
  56. static const char* GetPixelFormatName( OggTheoraDecoder::EPixelFormat format )
  57. {
  58. switch( format )
  59. {
  60. case OggTheoraDecoder::PIXEL_FORMAT_420:
  61. return "4:2:0";
  62. case OggTheoraDecoder::PIXEL_FORMAT_422:
  63. return "4:2:2";
  64. case OggTheoraDecoder::PIXEL_FORMAT_444:
  65. return "4:4:4";
  66. case OggTheoraDecoder::PIXEL_FORMAT_Unknown: ;
  67. }
  68. return "Unknown";
  69. }
  70. //=============================================================================
  71. // TheoraTexture::FrameReadItem implementation.
  72. //=============================================================================
  73. //-----------------------------------------------------------------------------
  74. TheoraTexture::FrameReadItem::FrameReadItem( AsyncBufferedInputStream< TheoraTextureFrame*, IInputStream< OggTheoraFrame* >* >* stream, ThreadContext* context )
  75. : Parent( context ),
  76. mFrameStream( dynamic_cast< FrameStream* >( stream ) )
  77. {
  78. AssertFatal( mFrameStream != NULL, "TheoraTexture::FrameReadItem::FrameReadItem() - expecting stream of type 'FrameStream'" );
  79. mAsyncState = mFrameStream->mAsyncState;
  80. // Assign a TheoraTextureFrame record to us. The nature of
  81. // AsyncBufferedInputStream ensures that we are always serial
  82. // here so this is thread-safe.
  83. mFrame = &mFrameStream->mFrames[ mFrameStream->mFrameIndex ];
  84. mFrameStream->mFrameIndex = ( mFrameStream->mFrameIndex + 1 ) % FrameStream::NUM_FRAME_RECORDS;
  85. }
  86. //-----------------------------------------------------------------------------
  87. void TheoraTexture::FrameReadItem::execute()
  88. {
  89. // Read Theora frame data.
  90. OggTheoraFrame* frame;
  91. if( mFrameStream->getSourceStream()->read( &frame, 1 ) != 1 )
  92. return;
  93. // Copy the data into the texture.
  94. OggTheoraDecoder* decoder = mAsyncState->getTheora();
  95. const U32 height = decoder->getFrameHeight();
  96. const U32 framePitch = decoder->getFrameWidth() * 4;
  97. GFXLockedRect* rect = mFrame->mLockedRect;
  98. if( rect )
  99. {
  100. const U32 usePitch = getMin(framePitch, mFrame->mTexture->getWidth() * 4);
  101. const U32 maxHeight = getMin(height, mFrame->mTexture->getHeight());
  102. if( (framePitch == rect->pitch) && (height == maxHeight) )
  103. dMemcpy( rect->bits, frame->data, rect->pitch * height );
  104. else
  105. {
  106. // Scanline length does not match. Copy line by line.
  107. U8* dst = rect->bits;
  108. U8* src = ( U8* ) frame->data;
  109. // Center the video if it is too big for the texture mode
  110. if ( height > maxHeight )
  111. src += framePitch * ((height - maxHeight) / 2);
  112. if ( framePitch > usePitch )
  113. src += (framePitch - usePitch) / 2;
  114. for( U32 i = 0; i < maxHeight; ++ i )
  115. {
  116. dMemcpy( dst, src, usePitch );
  117. dst += rect->pitch;
  118. src += framePitch;
  119. }
  120. }
  121. }
  122. #ifdef TORQUE_DEBUG
  123. else
  124. Platform::outputDebugString( "[TheoraTexture] texture not locked on frame %i", frame->mFrameNumber );
  125. #endif
  126. // Copy frame metrics.
  127. mFrame->mFrameNumber = frame->mFrameNumber;
  128. mFrame->mFrameTime = frame->mFrameTime;
  129. mFrame->mFrameDuration = frame->mFrameDuration;
  130. // Yield the frame packet back to the Theora decoder.
  131. decoder->reusePacket( frame );
  132. // Buffer the frame.
  133. mFrameStream->_onArrival( mFrame );
  134. }
  135. //=============================================================================
  136. // TheoraTexture::FrameStream implementation.
  137. //=============================================================================
  138. //-----------------------------------------------------------------------------
  139. TheoraTexture::FrameStream::FrameStream( AsyncState* asyncState, bool looping )
  140. : Parent( asyncState->getTheora(), 0, FRAME_READ_AHEAD, looping ),
  141. mAsyncState( asyncState ),
  142. mFrameIndex( 0 )
  143. {
  144. // Create the textures.
  145. OggTheoraDecoder* theora = asyncState->getTheora();
  146. const U32 width = theora->getFrameWidth();
  147. const U32 height = theora->getFrameHeight();
  148. for( U32 i = 0; i < NUM_FRAME_RECORDS; ++ i )
  149. {
  150. mFrames[ i ].mTexture.set(
  151. width,
  152. height,
  153. GFXFormatR8G8B8A8,
  154. &GFXTheoraTextureProfile,
  155. String::ToString( "Theora texture frame buffer %i (%s:%i)", i, __FILE__, __LINE__ )
  156. );
  157. }
  158. acquireTextureLocks();
  159. }
  160. //-----------------------------------------------------------------------------
  161. void TheoraTexture::FrameStream::acquireTextureLocks()
  162. {
  163. for( U32 i = 0; i < NUM_FRAME_RECORDS; ++ i )
  164. if( !mFrames[ i ].mLockedRect )
  165. mFrames[ i ].mLockedRect = mFrames[ i ].mTexture.lock();
  166. }
  167. //-----------------------------------------------------------------------------
  168. void TheoraTexture::FrameStream::releaseTextureLocks()
  169. {
  170. for( U32 i = 0; i < NUM_FRAME_RECORDS; ++ i )
  171. if( mFrames[ i ].mLockedRect )
  172. {
  173. mFrames[ i ].mTexture.unlock();
  174. mFrames[ i ].mLockedRect = NULL;
  175. }
  176. }
  177. //=============================================================================
  178. // TheoraTexture::AsyncState implementation.
  179. //=============================================================================
  180. //-----------------------------------------------------------------------------
  181. TheoraTexture::AsyncState::AsyncState( const ThreadSafeRef< OggInputStream >& oggStream, bool looping )
  182. : mOggStream( oggStream ),
  183. mTheoraDecoder( dynamic_cast< OggTheoraDecoder* >( oggStream->getDecoder( "Theora" ) ) ),
  184. mCurrentTime( 0 ),
  185. mVorbisDecoder( dynamic_cast< OggVorbisDecoder* >( oggStream->getDecoder( "Vorbis" ) ) )
  186. {
  187. if( mTheoraDecoder )
  188. {
  189. mTheoraDecoder->setTimeSource( this );
  190. mFrameStream = new FrameStream( this, looping );
  191. }
  192. }
  193. //-----------------------------------------------------------------------------
  194. TheoraTextureFrame* TheoraTexture::AsyncState::readNextFrame()
  195. {
  196. TheoraTextureFrame* frame;
  197. if( mFrameStream->read( &frame, 1 ) )
  198. return frame;
  199. else
  200. return NULL;
  201. }
  202. //-----------------------------------------------------------------------------
  203. void TheoraTexture::AsyncState::start()
  204. {
  205. mFrameStream->start();
  206. }
  207. //-----------------------------------------------------------------------------
  208. void TheoraTexture::AsyncState::stop()
  209. {
  210. mFrameStream->stop();
  211. }
  212. //-----------------------------------------------------------------------------
  213. bool TheoraTexture::AsyncState::isAtEnd()
  214. {
  215. return mOggStream->isAtEnd();
  216. }
  217. //=============================================================================
  218. // TheoraTexture implementation.
  219. //=============================================================================
  220. //-----------------------------------------------------------------------------
  221. TheoraTexture::TheoraTexture()
  222. : mCurrentFrame( NULL ),
  223. mPlaybackQueue( NULL ),
  224. mIsPaused( true ),
  225. mLastFrameNumber(0),
  226. mNumDroppedFrames(0)
  227. {
  228. GFXTextureManager::addEventDelegate( this, &TheoraTexture::_onTextureEvent );
  229. }
  230. //-----------------------------------------------------------------------------
  231. TheoraTexture::~TheoraTexture()
  232. {
  233. GFXTextureManager::removeEventDelegate( this, &TheoraTexture::_onTextureEvent );
  234. _reset();
  235. }
  236. //-----------------------------------------------------------------------------
  237. void TheoraTexture::_reset()
  238. {
  239. // Stop the async streams.
  240. if( mAsyncState != NULL )
  241. mAsyncState->stop();
  242. // Delete the playback queue.
  243. if( mPlaybackQueue )
  244. SAFE_DELETE( mPlaybackQueue );
  245. // Kill the sound source.
  246. if( mSFXSource != NULL )
  247. {
  248. mSFXSource->stop();
  249. SFX_DELETE( mSFXSource );
  250. mSFXSource = NULL;
  251. }
  252. mLastFrameNumber = 0;
  253. mNumDroppedFrames = 0;
  254. mCurrentFrame = NULL;
  255. mAsyncState = NULL;
  256. mIsPaused = false;
  257. mPlaybackTimer.reset();
  258. }
  259. //-----------------------------------------------------------------------------
  260. void TheoraTexture::_onTextureEvent( GFXTexCallbackCode code )
  261. {
  262. switch( code )
  263. {
  264. case GFXZombify:
  265. mCurrentFrame = NULL;
  266. if( mAsyncState )
  267. {
  268. // Blast out work items and then release all texture locks.
  269. ThreadPool::GLOBAL().flushWorkItems();
  270. mAsyncState->getFrameStream()->releaseTextureLocks();
  271. // The Theora decoder does not implement seeking at the moment,
  272. // so we absolutely want to make sure we don't fall behind too far or
  273. // we may end up having the decoder go crazy trying to skip through
  274. // Ogg packets (even just reading these undecoded packets takes a
  275. // lot of time). So, for the time being, just pause playback when
  276. // we go zombie.
  277. if( mSFXSource )
  278. mSFXSource->pause();
  279. else
  280. mPlaybackTimer.pause();
  281. }
  282. break;
  283. case GFXResurrect:
  284. if( mAsyncState )
  285. {
  286. // Reacquire texture locks.
  287. mAsyncState->getFrameStream()->acquireTextureLocks();
  288. // Resume playback if we have paused it.
  289. if( !mIsPaused )
  290. {
  291. if( mSFXSource )
  292. mSFXSource->play();
  293. else
  294. mPlaybackTimer.start();
  295. }
  296. }
  297. break;
  298. }
  299. }
  300. //-----------------------------------------------------------------------------
  301. void TheoraTexture::_initVideo()
  302. {
  303. OggTheoraDecoder* theora = _getTheora();
  304. // Set the decoder's pixel output format to match
  305. // the texture format.
  306. OggTheoraDecoder::PacketFormat format;
  307. format.mFormat = GFXFormatR8G8B8A8;
  308. format.mPitch = GFXFormatInfo( format.mFormat ).getBytesPerPixel() * theora->getFrameWidth();
  309. theora->setPacketFormat( format );
  310. }
  311. //-----------------------------------------------------------------------------
  312. void TheoraTexture::_initAudio( const ThreadSafeRef< SFXStream >& stream )
  313. {
  314. // Create an SFXDescription if we don't have one.
  315. if( !mSFXDescription )
  316. {
  317. SFXDescription* description = new SFXDescription;
  318. description->mIsStreaming = true;
  319. description->registerObject();
  320. description->setAutoDelete( true );
  321. mSFXDescription = description;
  322. }
  323. // Create an SFX memory stream that consumes the output
  324. // of the Vorbis decoder.
  325. ThreadSafeRef< SFXStream > sfxStream = stream;
  326. if( !sfxStream )
  327. {
  328. OggVorbisDecoder* vorbis = _getVorbis();
  329. SFXFormat sfxFormat( vorbis->getNumChannels(),
  330. vorbis->getNumChannels() * 16,
  331. vorbis->getSamplesPerSecond() );
  332. sfxStream = new SFXMemoryStream( sfxFormat, vorbis );
  333. }
  334. // Create the SFXSource.
  335. mSFXSource = SFX->createSourceFromStream( sfxStream, mSFXDescription );
  336. }
  337. //-----------------------------------------------------------------------------
  338. TheoraTexture::TimeSourceType* TheoraTexture::_getTimeSource() const
  339. {
  340. if( mSFXSource != NULL )
  341. return mSFXSource;
  342. else
  343. return ( TimeSourceType* ) &mPlaybackTimer;
  344. }
  345. //-----------------------------------------------------------------------------
  346. U32 TheoraTexture::getWidth() const
  347. {
  348. return _getTheora()->getFrameWidth();
  349. }
  350. //-----------------------------------------------------------------------------
  351. U32 TheoraTexture::getHeight() const
  352. {
  353. return _getTheora()->getFrameHeight();
  354. }
  355. //-----------------------------------------------------------------------------
  356. bool TheoraTexture::setFile( const String& filename, SFXDescription* desc )
  357. {
  358. _reset();
  359. if( filename.isEmpty() )
  360. return true;
  361. // Check SFX profile.
  362. if( desc && !desc->mIsStreaming )
  363. {
  364. Con::errorf( "TheoraTexture::setFile - Not a streaming SFXDescription" );
  365. return false;
  366. }
  367. mSFXDescription = desc;
  368. // Open the Theora file.
  369. Stream* stream = FileStream::createAndOpen( filename, Torque::FS::File::Read );
  370. if( !stream )
  371. {
  372. Con::errorf( "TheoraTexture::setFile - Theora file '%s' not found.", filename.c_str() );
  373. return false;
  374. }
  375. // Create the OGG stream.
  376. Con::printf( "TheoraTexture - Loading file '%s'", filename.c_str() );
  377. ThreadSafeRef< OggInputStream > oggStream = new OggInputStream( stream );
  378. oggStream->addDecoder< OggTheoraDecoder >();
  379. #ifndef SPLIT_VORBIS
  380. oggStream->addDecoder< OggVorbisDecoder >();
  381. #endif
  382. if( !oggStream->init() )
  383. {
  384. Con::errorf( "TheoraTexture - Failed to initialize OGG stream" );
  385. return false;
  386. }
  387. mFilename = filename;
  388. mAsyncState = new AsyncState( oggStream, desc ? desc->mIsLooping : false );
  389. // Set up video.
  390. OggTheoraDecoder* theoraDecoder = _getTheora();
  391. if( !theoraDecoder )
  392. {
  393. Con::errorf( "TheoraTexture - '%s' is not a Theora file", filename.c_str() );
  394. mAsyncState = NULL;
  395. return false;
  396. }
  397. Con::printf( " - Theora: %ix%i pixels, %.02f fps, %s format",
  398. theoraDecoder->getFrameWidth(),
  399. theoraDecoder->getFrameHeight(),
  400. theoraDecoder->getFramesPerSecond(),
  401. GetPixelFormatName( theoraDecoder->getDecoderPixelFormat() ) );
  402. _initVideo();
  403. // Set up sound if we have it. For performance reasons, create
  404. // a separate physical stream for the Vorbis sound data rather than
  405. // using the bitstream from the multiplexed OGG master stream. The
  406. // contention caused by the OGG page/packet feeding will otherwise
  407. // slow us down significantly.
  408. #ifdef SPLIT_VORBIS
  409. stream = FileStream::createAndOpen( filename, Torque::FS::File::Read );
  410. if( stream )
  411. {
  412. ThreadSafeRef< SFXStream > vorbisStream = SFXVorbisStream::create( stream );
  413. if( !vorbisStream )
  414. {
  415. Con::errorf( "TheoraTexture - could not create Vorbis stream for '%s'", filename.c_str() );
  416. // Stream is deleted by SFXVorbisStream.
  417. }
  418. else
  419. {
  420. Con::printf( " - Vorbis: %i channels, %i kHz",
  421. vorbisStream->getFormat().getChannels(),
  422. vorbisStream->getFormat().getSamplesPerSecond() / 1000 );
  423. _initAudio( vorbisStream );
  424. }
  425. }
  426. #else
  427. OggVorbisDecoder* vorbisDecoder = _getVorbis();
  428. if( vorbisDecoder )
  429. {
  430. Con::printf( " - Vorbis: %i bits, %i channels, %i kHz",
  431. vorbisDecoder->getNumChannels(),
  432. vorbisDecoder->getSamplesPerSecond() / 1000 );
  433. _initAudio();
  434. }
  435. #endif
  436. // Initiate the background request chain.
  437. mAsyncState->start();
  438. return true;
  439. }
  440. //-----------------------------------------------------------------------------
  441. bool TheoraTexture::isPlaying() const
  442. {
  443. if( !mAsyncState || !mCurrentFrame )
  444. return false;
  445. if( mSFXSource )
  446. return mSFXSource->isPlaying();
  447. else
  448. return mPlaybackTimer.isStarted();
  449. }
  450. //-----------------------------------------------------------------------------
  451. void TheoraTexture::play()
  452. {
  453. if( isPlaying() )
  454. return;
  455. if( !mAsyncState )
  456. setFile( mFilename, mSFXDescription );
  457. // Construct playback queue that sync's to our time source,
  458. // writes to us, and drops outdated packets.
  459. if( !mPlaybackQueue )
  460. mPlaybackQueue = new PlaybackQueueType( 1, _getTimeSource(), this, 0, true );
  461. // Start playback.
  462. if( mSFXSource )
  463. mSFXSource->play();
  464. else
  465. mPlaybackTimer.start();
  466. mIsPaused = false;
  467. }
  468. //-----------------------------------------------------------------------------
  469. void TheoraTexture::pause()
  470. {
  471. if( mSFXSource )
  472. mSFXSource->pause();
  473. else
  474. mPlaybackTimer.pause();
  475. mIsPaused = true;
  476. }
  477. //-----------------------------------------------------------------------------
  478. void TheoraTexture::stop()
  479. {
  480. _reset();
  481. }
  482. //-----------------------------------------------------------------------------
  483. void TheoraTexture::refresh()
  484. {
  485. PROFILE_SCOPE( TheoraTexture_refresh );
  486. if( !mAsyncState || !mPlaybackQueue )
  487. return;
  488. // Synchronize the async state to our current time.
  489. // Unfortunately, we cannot set the Theora decoder to
  490. // synchronize directly with us as our lifetime and the
  491. // lifetime of our time sources isn't bound to the
  492. // threaded state.
  493. mAsyncState->syncTime( _getTimeSource()->getPosition() );
  494. // Update the texture, if necessary.
  495. bool haveFrame = false;
  496. while( mPlaybackQueue->needPacket() )
  497. {
  498. // Lock the current frame.
  499. if( mCurrentFrame && !mCurrentFrame->mLockedRect )
  500. mCurrentFrame->mLockedRect = mCurrentFrame->mTexture.lock();
  501. // Try to read a new frame.
  502. TheoraTextureFrame* frame = mAsyncState->readNextFrame();
  503. if( !frame )
  504. break;
  505. // Submit frame to queue.
  506. mPlaybackQueue->submitPacket(
  507. frame,
  508. frame->mFrameDuration * 1000.f,
  509. false,
  510. frame->mFrameTime * 1000.f
  511. );
  512. // See if we have dropped frames.
  513. if( frame->mFrameNumber != mLastFrameNumber + 1 )
  514. mNumDroppedFrames += frame->mFrameNumber - mLastFrameNumber - 1;
  515. mLastFrameNumber = frame->mFrameNumber;
  516. haveFrame = true;
  517. }
  518. // Unlock current frame.
  519. if( mCurrentFrame && mCurrentFrame->mLockedRect )
  520. {
  521. mCurrentFrame->mTexture.unlock();
  522. mCurrentFrame->mLockedRect = NULL;
  523. }
  524. // Release async state if we have reached the
  525. // end of the Ogg stream.
  526. if( mAsyncState->isAtEnd() && !haveFrame )
  527. _reset();
  528. }
  529. //-----------------------------------------------------------------------------
  530. void TheoraTexture::write( TheoraTextureFrame* const* frames, U32 num )
  531. {
  532. if( !num )
  533. return;
  534. mCurrentFrame = frames[ num - 1 ]; // Only used last.
  535. }
  536. #endif // TORQUE_OGGTHEORA