#include "global.h" /* ----------------------------------------------------------------------------- Class: MovieTexture_DShow Desc: See header. Copyright (c) 2001-2003 by the person(s) listed below. All rights reserved. Chris Danford Glenn Maynard ----------------------------------------------------------------------------- */ #pragma comment(lib, "winmm.lib") // Link with the DirectShow base class libraries #if defined(DEBUG) #pragma comment(lib, "baseclasses/debug/strmbasd.lib") #else #pragma comment(lib, "baseclasses/release/strmbase.lib") #endif #include "MovieTexture_DShowHelper.h" #include "MovieTexture_DShow.h" /* for TEXTUREMAN->GetTextureColorDepth() */ #include "RageTextureManager.h" #include "RageUtil.h" #include "RageLog.h" #include "RageException.h" #include "archutils/win32/tls.h" #include #include "SDL_utils.h" //----------------------------------------------------------------------------- // MovieTexture_DShow constructor //----------------------------------------------------------------------------- MovieTexture_DShow::MovieTexture_DShow( RageTextureID ID ) : RageMovieTexture( ID ) { LOG->Trace( "RageBitmapTexture::RageBitmapTexture()" ); m_uTexHandle = 0; buffer = NULL; buffer_lock = SDL_CreateSemaphore(1); buffer_finished = SDL_CreateSemaphore(0); m_img = NULL; Create(); CreateFrameRects(); // flip all frame rects because movies are upside down for( unsigned i=0; iTrace("MovieTexture_DShow::~MovieTexture_DShow"); LOG->Flush(); SkipUpdates(); /* Shut down the graph. We can't call Stop() here, since that will * call SkipUpdates again, which will deadlock if we call it twice * in a row. */ if (m_pGB) { LOG->Trace("MovieTexture_DShow: shutdown"); LOG->Flush(); CComPtr pMC; m_pGB.QueryInterface(&pMC); HRESULT hr; if( FAILED( hr = pMC->Stop() ) ) RageException::Throw( hr_ssprintf(hr, "Could not stop the DirectShow graph.") ); // Stop(); m_pGB.Release(); } LOG->Trace("MovieTexture_DShow: shutdown ok"); LOG->Flush(); if(m_uTexHandle) DISPLAY->DeleteTexture( m_uTexHandle ); SDL_FreeSurface( m_img ); SDL_DestroySemaphore(buffer_lock); SDL_DestroySemaphore(buffer_finished); } void MovieTexture_DShow::Reload() { // do nothing } /* If there's a frame waiting in the buffer, then the decoding thread put it there * and is waiting for us to do something with it. */ void MovieTexture_DShow::CheckFrame() { if(buffer == NULL) return; ASSERT( m_img ); VDCHECKPOINT; /* Just in case we were invalidated: */ CreateTexture(); // DirectShow feeds us in BGR8 SDL_Surface *fromDShow = SDL_CreateRGBSurfaceFrom( (void*)buffer, m_iSourceWidth, m_iSourceHeight, 24, m_iSourceWidth*3, 0xFF0000, 0x00FF00, 0x0000FF, 0x000000 ); /* We don't want to actually blend the alpha channel over the destination converted * surface; we want to simply blit it, so make sure SDL_SRCALPHA is not on. */ SDL_SetAlpha( fromDShow, 0, SDL_ALPHA_OPAQUE ); SDL_Rect area; area.x = area.y = 0; area.w = short(fromDShow->w); area.h = short(fromDShow->h); /* XXX: For high-resolution movies, it's much faster to convert a BGR8 surface * to RGB8 in place than to blit it to another surface, since it's much easier * on the cache. It'd be even faster to try as hard as we can to not do a * conversion at all; both OpenGL and D3D have BGR texture modes. */ SDL_BlitSurface(fromDShow, &area, m_img, &area); VDCHECKPOINT; DISPLAY->UpdateTexture( m_uTexHandle, m_PixelFormat, m_img, 0, 0, m_iImageWidth, m_iImageHeight ); // min(m_iSourceWidth,m_iTextureWidth), min(m_iSourceHeight,m_iTextureHeight) ); VDCHECKPOINT; SDL_FreeSurface( fromDShow ); buffer = NULL; VDCHECKPOINT; /* Start the decoding thread again. */ SDL_SemPost(buffer_finished); VDCHECKPOINT; } void MovieTexture_DShow::Update(float fDeltaTime) { VDCHECKPOINT; // restart the movie if we reach the end if(m_bLoop) { // Check for completion events CComPtr pME; m_pGB.QueryInterface(&pME); long lEventCode, lParam1, lParam2; pME->GetEvent( &lEventCode, &lParam1, &lParam2, 0 ); if( lEventCode == EC_COMPLETE ) SetPosition(0); } CheckFrame(); } void PrintCodecError(HRESULT hr, CString s) { /* Actually, we might need XviD; we might want to look * at the file and try to figure out if it's something * common: DIV3, DIV4, DIV5, XVID, or maybe even MPEG2. */ CString err = hr_ssprintf(hr, "%s", s.c_str()); LOG->Warn( ssprintf( "There was an error initializing a movie: %s.\n" "Could not locate the DivX video codec.\n" "DivX is required to movie textures and must\n" "be installed before running the application.\n\n" "Please visit http://www.divx.com to download the latest version.", err.c_str()) ); } void MovieTexture_DShow::Create() { RageTextureID actualID = GetID(); HRESULT hr; actualID.iAlphaBits = 0; if( FAILED( hr=CoInitialize(NULL) ) ) RageException::Throw( hr_ssprintf(hr, "Could not CoInitialize") ); // Create the filter graph if( FAILED( hr=m_pGB.CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC) ) ) RageException::Throw( hr_ssprintf(hr, "Could not create CLSID_FilterGraph!") ); // Create the Texture Renderer object CTextureRenderer *pCTR = new CTextureRenderer; /* Get a pointer to the IBaseFilter on the TextureRenderer, and add it to the * graph. When m_pGB is released, it will free pFTR. */ CComPtr pFTR = pCTR; if( FAILED( hr = m_pGB->AddFilter(pFTR, L"TEXTURERENDERER" ) ) ) RageException::Throw( hr_ssprintf(hr, "Could not add renderer filter to graph!") ); // Add the source filter CComPtr pFSrc; // Source Filter WCHAR wFileName[MAX_PATH]; MultiByteToWideChar(CP_ACP, 0, actualID.filename.c_str(), -1, wFileName, MAX_PATH); // if this fails, it's probably because the user doesn't have DivX installed if( FAILED( hr = m_pGB->AddSourceFilter( wFileName, L"SOURCE", &pFSrc ) ) ) { PrintCodecError(hr, "Could not create source filter to graph!"); return; // survive and don't Run the graph. } // Find the source's output and the renderer's input CComPtr pFTRPinIn; // Texture Renderer Input Pin if( FAILED( hr = pFTR->FindPin( L"In", &pFTRPinIn ) ) ) RageException::Throw( hr_ssprintf(hr, "Could not find input pin!") ); CComPtr pFSrcPinOut; // Source Filter Output Pin if( FAILED( hr = pFSrc->FindPin( L"Output", &pFSrcPinOut ) ) ) RageException::Throw( hr_ssprintf(hr, "Could not find output pin!") ); // Connect these two filters if( FAILED( hr = m_pGB->Connect( pFSrcPinOut, pFTRPinIn ) ) ) { PrintCodecError(hr, "Could not connect pins!"); return; // survive and don't Run the graph. } // Pass us to our TextureRenderer. pCTR->SetRenderTarget(this); /* Cap the max texture size to the hardware max. */ actualID.iMaxSize = min( actualID.iMaxSize, DISPLAY->GetMaxTextureSize() ); // The graph is built, now get the set the output video width and height. // The source and image width will always be the same since we can't scale the video m_iSourceWidth = pCTR->GetVidWidth(); m_iSourceHeight = pCTR->GetVidHeight(); /* image size cannot exceed max size */ m_iImageWidth = min( m_iSourceWidth, actualID.iMaxSize ); m_iImageHeight = min( m_iSourceHeight, actualID.iMaxSize ); /* Texture dimensions need to be a power of two; jump to the next. */ m_iTextureWidth = power_of_two(m_iImageWidth); m_iTextureHeight = power_of_two(m_iImageHeight); /* We've set up the movie, so we know the dimensions we need. Set * up the texture. */ CreateTexture(); // Start the graph running if( !PlayMovie() ) RageException::Throw( "Could not run the DirectShow graph." ); } void MovieTexture_DShow::NewData(const char *data) { ASSERT(data); /* Try to lock. */ if(SDL_SemTryWait(buffer_lock)) { /* The main thread is doing something uncommon, such as pausing. * Drop this frame. */ return; } buffer = data; SDL_SemWait(buffer_finished); ASSERT(buffer == NULL); SDL_SemPost(buffer_lock); } void MovieTexture_DShow::CreateTexture() { if(m_uTexHandle) return; /* Have to free our frame holder becuase we might need a different * PixelFormat than before. */ if( m_img ) SDL_FreeSurface( m_img ); /* My test clip (a high-res, MPEG1 video) goes from 12 fps to 14 fps * if I use a 16-bit internalformat instead of a 32-bit one; that's a * 16% jump, which is significant. (Simply decoding this video is probably * taking 30-40% CPU.) It means much less bus traffic (sending textures to * the card is slow). * * So, it *might* make sense to make this separately configurable. However, * that's getting pretty detailed; well beyond what most users will tweak. * Some way to figure this out dynamically would be nice, but it's probably * impossible. (For example, 24-bit textures may even be cheaper on pure * AGP cards; 16-bit requires a conversion.) */ switch( TEXTUREMAN->GetTextureColorDepth() ) { default: ASSERT(0); case 16: if( DISPLAY->SupportsTextureFormat(FMT_RGB5) ) m_PixelFormat = FMT_RGB5; else m_PixelFormat = FMT_RGBA4; // everything supports RGBA4 break; case 32: if( DISPLAY->SupportsTextureFormat(FMT_RGB8) ) m_PixelFormat = FMT_RGB8; else if( DISPLAY->SupportsTextureFormat(FMT_RGBA8) ) m_PixelFormat = FMT_RGBA8; else if( DISPLAY->SupportsTextureFormat(FMT_RGB5) ) m_PixelFormat = FMT_RGB5; else m_PixelFormat = FMT_RGBA4; // everything supports RGBA4 break; } const PixelFormatDesc *pfd = DISPLAY->GetPixelFormatDesc(m_PixelFormat); m_img = SDL_CreateRGBSurfaceSane(SDL_SWSURFACE, m_iTextureWidth, m_iTextureHeight, pfd->bpp, pfd->masks[0], pfd->masks[1], pfd->masks[2], pfd->masks[3]); m_uTexHandle = DISPLAY->CreateTexture( m_PixelFormat, m_img ); } bool MovieTexture_DShow::PlayMovie() { SkipUpdates(); LOG->Trace("MovieTexture_DShow::PlayMovie()"); CComPtr pMC; m_pGB.QueryInterface(&pMC); // Start the graph running; HRESULT hr; if( FAILED( hr = pMC->Run() ) ) RageException::Throw( hr_ssprintf(hr, "Could not run the DirectShow graph.") ); m_bPlaying = true; StopSkippingUpdates(); return true; } void MovieTexture_DShow::Play() { PlayMovie(); } void MovieTexture_DShow::Pause() { SkipUpdates(); CComPtr pMC; m_pGB.QueryInterface(&pMC); HRESULT hr; if( FAILED( hr = pMC->Pause() ) ) RageException::Throw( hr_ssprintf(hr, "Could not pause the DirectShow graph.") ); StopSkippingUpdates(); } void MovieTexture_DShow::Stop() { SkipUpdates(); CComPtr pMC; m_pGB.QueryInterface(&pMC); HRESULT hr; if( FAILED( hr = pMC->Stop() ) ) RageException::Throw( hr_ssprintf(hr, "Could not stop the DirectShow graph.") ); m_bPlaying = false; StopSkippingUpdates(); } void MovieTexture_DShow::SetPosition( float fSeconds ) { SkipUpdates(); CComPtr pMP; m_pGB.QueryInterface(&pMP); pMP->put_CurrentPosition(0); StopSkippingUpdates(); } void MovieTexture_DShow::SetPlaybackRate( float fRate ) { SkipUpdates(); CComPtr pMP; m_pGB.QueryInterface(&pMP); pMP->put_Rate(fRate); StopSkippingUpdates(); } bool MovieTexture_DShow::IsPlaying() const { return m_bPlaying; }