Torque3D Documentation / _generateds / theoraTexture.cpp

theoraTexture.cpp

Engine/source/gfx/video/theoraTexture.cpp

More...

Detailed Description

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