theoraTexture.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697
  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. mVorbisDecoder( dynamic_cast< OggVorbisDecoder* >( oggStream->getDecoder( "Vorbis" ) ) ),
  185. mCurrentTime( 0 )
  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. : mPlaybackQueue( NULL ),
  223. mCurrentFrame( NULL ),
  224. mIsPaused( true )
  225. {
  226. GFXTextureManager::addEventDelegate( this, &TheoraTexture::_onTextureEvent );
  227. }
  228. //-----------------------------------------------------------------------------
  229. TheoraTexture::~TheoraTexture()
  230. {
  231. GFXTextureManager::removeEventDelegate( this, &TheoraTexture::_onTextureEvent );
  232. _reset();
  233. }
  234. //-----------------------------------------------------------------------------
  235. void TheoraTexture::_reset()
  236. {
  237. // Stop the async streams.
  238. if( mAsyncState != NULL )
  239. mAsyncState->stop();
  240. // Delete the playback queue.
  241. if( mPlaybackQueue )
  242. SAFE_DELETE( mPlaybackQueue );
  243. // Kill the sound source.
  244. if( mSFXSource != NULL )
  245. {
  246. mSFXSource->stop();
  247. SFX_DELETE( mSFXSource );
  248. mSFXSource = NULL;
  249. }
  250. mLastFrameNumber = 0;
  251. mNumDroppedFrames = 0;
  252. mCurrentFrame = NULL;
  253. mAsyncState = NULL;
  254. mIsPaused = false;
  255. mPlaybackTimer.reset();
  256. }
  257. //-----------------------------------------------------------------------------
  258. void TheoraTexture::_onTextureEvent( GFXTexCallbackCode code )
  259. {
  260. switch( code )
  261. {
  262. case GFXZombify:
  263. mCurrentFrame = NULL;
  264. if( mAsyncState )
  265. {
  266. // Blast out work items and then release all texture locks.
  267. ThreadPool::GLOBAL().flushWorkItems();
  268. mAsyncState->getFrameStream()->releaseTextureLocks();
  269. // The Theora decoder does not implement seeking at the moment,
  270. // so we absolutely want to make sure we don't fall behind too far or
  271. // we may end up having the decoder go crazy trying to skip through
  272. // Ogg packets (even just reading these undecoded packets takes a
  273. // lot of time). So, for the time being, just pause playback when
  274. // we go zombie.
  275. if( mSFXSource )
  276. mSFXSource->pause();
  277. else
  278. mPlaybackTimer.pause();
  279. }
  280. break;
  281. case GFXResurrect:
  282. if( mAsyncState )
  283. {
  284. // Reacquire texture locks.
  285. mAsyncState->getFrameStream()->acquireTextureLocks();
  286. // Resume playback if we have paused it.
  287. if( !mIsPaused )
  288. {
  289. if( mSFXSource )
  290. mSFXSource->play();
  291. else
  292. mPlaybackTimer.start();
  293. }
  294. }
  295. break;
  296. }
  297. }
  298. //-----------------------------------------------------------------------------
  299. void TheoraTexture::_initVideo()
  300. {
  301. OggTheoraDecoder* theora = _getTheora();
  302. // Set the decoder's pixel output format to match
  303. // the texture format.
  304. OggTheoraDecoder::PacketFormat format;
  305. format.mFormat = GFXFormatR8G8B8A8;
  306. format.mPitch = GFXFormatInfo( format.mFormat ).getBytesPerPixel() * theora->getFrameWidth();
  307. theora->setPacketFormat( format );
  308. }
  309. //-----------------------------------------------------------------------------
  310. void TheoraTexture::_initAudio( const ThreadSafeRef< SFXStream >& stream )
  311. {
  312. // Create an SFXDescription if we don't have one.
  313. if( !mSFXDescription )
  314. {
  315. SFXDescription* description = new SFXDescription;
  316. description->mIsStreaming = true;
  317. description->registerObject();
  318. description->setAutoDelete( true );
  319. mSFXDescription = description;
  320. }
  321. // Create an SFX memory stream that consumes the output
  322. // of the Vorbis decoder.
  323. ThreadSafeRef< SFXStream > sfxStream = stream;
  324. if( !sfxStream )
  325. {
  326. OggVorbisDecoder* vorbis = _getVorbis();
  327. SFXFormat sfxFormat( vorbis->getNumChannels(),
  328. vorbis->getNumChannels() * 16,
  329. vorbis->getSamplesPerSecond() );
  330. sfxStream = new SFXMemoryStream( sfxFormat, vorbis );
  331. }
  332. // Create the SFXSource.
  333. mSFXSource = SFX->createSourceFromStream( sfxStream, mSFXDescription );
  334. }
  335. //-----------------------------------------------------------------------------
  336. TheoraTexture::TimeSourceType* TheoraTexture::_getTimeSource() const
  337. {
  338. if( mSFXSource != NULL )
  339. return mSFXSource;
  340. else
  341. return ( TimeSourceType* ) &mPlaybackTimer;
  342. }
  343. //-----------------------------------------------------------------------------
  344. U32 TheoraTexture::getWidth() const
  345. {
  346. return _getTheora()->getFrameWidth();
  347. }
  348. //-----------------------------------------------------------------------------
  349. U32 TheoraTexture::getHeight() const
  350. {
  351. return _getTheora()->getFrameHeight();
  352. }
  353. //-----------------------------------------------------------------------------
  354. bool TheoraTexture::setFile( const String& filename, SFXDescription* desc )
  355. {
  356. _reset();
  357. if( filename.isEmpty() )
  358. return true;
  359. // Check SFX profile.
  360. if( desc && !desc->mIsStreaming )
  361. {
  362. Con::errorf( "TheoraTexture::setFile - Not a streaming SFXDescription" );
  363. return false;
  364. }
  365. mSFXDescription = desc;
  366. // Open the Theora file.
  367. Stream* stream = FileStream::createAndOpen( filename, Torque::FS::File::Read );
  368. if( !stream )
  369. {
  370. Con::errorf( "TheoraTexture::setFile - Theora file '%s' not found.", filename.c_str() );
  371. return false;
  372. }
  373. // Create the OGG stream.
  374. Con::printf( "TheoraTexture - Loading file '%s'", filename.c_str() );
  375. ThreadSafeRef< OggInputStream > oggStream = new OggInputStream( stream );
  376. oggStream->addDecoder< OggTheoraDecoder >();
  377. #ifndef SPLIT_VORBIS
  378. oggStream->addDecoder< OggVorbisDecoder >();
  379. #endif
  380. if( !oggStream->init() )
  381. {
  382. Con::errorf( "TheoraTexture - Failed to initialize OGG stream" );
  383. return false;
  384. }
  385. mFilename = filename;
  386. mAsyncState = new AsyncState( oggStream, desc ? desc->mIsLooping : false );
  387. // Set up video.
  388. OggTheoraDecoder* theoraDecoder = _getTheora();
  389. if( !theoraDecoder )
  390. {
  391. Con::errorf( "TheoraTexture - '%s' is not a Theora file", filename.c_str() );
  392. mAsyncState = NULL;
  393. return false;
  394. }
  395. Con::printf( " - Theora: %ix%i pixels, %.02f fps, %s format",
  396. theoraDecoder->getFrameWidth(),
  397. theoraDecoder->getFrameHeight(),
  398. theoraDecoder->getFramesPerSecond(),
  399. GetPixelFormatName( theoraDecoder->getDecoderPixelFormat() ) );
  400. _initVideo();
  401. // Set up sound if we have it. For performance reasons, create
  402. // a separate physical stream for the Vorbis sound data rather than
  403. // using the bitstream from the multiplexed OGG master stream. The
  404. // contention caused by the OGG page/packet feeding will otherwise
  405. // slow us down significantly.
  406. #ifdef SPLIT_VORBIS
  407. stream = FileStream::createAndOpen( filename, Torque::FS::File::Read );
  408. if( stream )
  409. {
  410. ThreadSafeRef< SFXStream > vorbisStream = SFXVorbisStream::create( stream );
  411. if( !vorbisStream )
  412. {
  413. Con::errorf( "TheoraTexture - could not create Vorbis stream for '%s'", filename.c_str() );
  414. // Stream is deleted by SFXVorbisStream.
  415. }
  416. else
  417. {
  418. Con::printf( " - Vorbis: %i channels, %i kHz",
  419. vorbisStream->getFormat().getChannels(),
  420. vorbisStream->getFormat().getSamplesPerSecond() / 1000 );
  421. _initAudio( vorbisStream );
  422. }
  423. }
  424. #else
  425. OggVorbisDecoder* vorbisDecoder = _getVorbis();
  426. if( vorbisDecoder )
  427. {
  428. Con::printf( " - Vorbis: %i bits, %i channels, %i kHz",
  429. vorbisDecoder->getNumChannels(),
  430. vorbisDecoder->getSamplesPerSecond() / 1000 );
  431. _initAudio();
  432. }
  433. #endif
  434. // Initiate the background request chain.
  435. mAsyncState->start();
  436. return true;
  437. }
  438. //-----------------------------------------------------------------------------
  439. bool TheoraTexture::isPlaying() const
  440. {
  441. if( !mAsyncState || !mCurrentFrame )
  442. return false;
  443. if( mSFXSource )
  444. return mSFXSource->isPlaying();
  445. else
  446. return mPlaybackTimer.isStarted();
  447. }
  448. //-----------------------------------------------------------------------------
  449. void TheoraTexture::play()
  450. {
  451. if( isPlaying() )
  452. return;
  453. if( !mAsyncState )
  454. setFile( mFilename, mSFXDescription );
  455. // Construct playback queue that sync's to our time source,
  456. // writes to us, and drops outdated packets.
  457. if( !mPlaybackQueue )
  458. mPlaybackQueue = new PlaybackQueueType( 1, _getTimeSource(), this, 0, true );
  459. // Start playback.
  460. if( mSFXSource )
  461. mSFXSource->play();
  462. else
  463. mPlaybackTimer.start();
  464. mIsPaused = false;
  465. }
  466. //-----------------------------------------------------------------------------
  467. void TheoraTexture::pause()
  468. {
  469. if( mSFXSource )
  470. mSFXSource->pause();
  471. else
  472. mPlaybackTimer.pause();
  473. mIsPaused = true;
  474. }
  475. //-----------------------------------------------------------------------------
  476. void TheoraTexture::stop()
  477. {
  478. _reset();
  479. }
  480. //-----------------------------------------------------------------------------
  481. void TheoraTexture::refresh()
  482. {
  483. PROFILE_SCOPE( TheoraTexture_refresh );
  484. if( !mAsyncState || !mPlaybackQueue )
  485. return;
  486. // Synchronize the async state to our current time.
  487. // Unfortunately, we cannot set the Theora decoder to
  488. // synchronize directly with us as our lifetime and the
  489. // lifetime of our time sources isn't bound to the
  490. // threaded state.
  491. mAsyncState->syncTime( _getTimeSource()->getPosition() );
  492. // Update the texture, if necessary.
  493. bool haveFrame = false;
  494. while( mPlaybackQueue->needPacket() )
  495. {
  496. // Lock the current frame.
  497. if( mCurrentFrame && !mCurrentFrame->mLockedRect )
  498. mCurrentFrame->mLockedRect = mCurrentFrame->mTexture.lock();
  499. // Try to read a new frame.
  500. TheoraTextureFrame* frame = mAsyncState->readNextFrame();
  501. if( !frame )
  502. break;
  503. // Submit frame to queue.
  504. mPlaybackQueue->submitPacket(
  505. frame,
  506. frame->mFrameDuration * 1000.f,
  507. false,
  508. frame->mFrameTime * 1000.f
  509. );
  510. // See if we have dropped frames.
  511. if( frame->mFrameNumber != mLastFrameNumber + 1 )
  512. mNumDroppedFrames += frame->mFrameNumber - mLastFrameNumber - 1;
  513. mLastFrameNumber = frame->mFrameNumber;
  514. haveFrame = true;
  515. }
  516. // Unlock current frame.
  517. if( mCurrentFrame && mCurrentFrame->mLockedRect )
  518. {
  519. mCurrentFrame->mTexture.unlock();
  520. mCurrentFrame->mLockedRect = NULL;
  521. }
  522. // Release async state if we have reached the
  523. // end of the Ogg stream.
  524. if( mAsyncState->isAtEnd() && !haveFrame )
  525. _reset();
  526. }
  527. //-----------------------------------------------------------------------------
  528. void TheoraTexture::write( TheoraTextureFrame* const* frames, U32 num )
  529. {
  530. if( !num )
  531. return;
  532. mCurrentFrame = frames[ num - 1 ]; // Only used last.
  533. }
  534. #endif // TORQUE_OGGTHEORA