theoraTexture.cpp
Engine/source/gfx/video/theoraTexture.cpp
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