//========= Copyright Valve Corporation, All rights reserved. ============// //----------------------------------------------------------------------------- // File: WMVPlayer.cpp // // Desc: This helper class provides simple WMV decoding and playback // functionality. It will be expanded as new playback methods are // exposed // // Hist: 2.7.03 - Created, based on work by Jeff Sullivan // // Copyright (c) Microsoft Corporation. All rights reserved. //----------------------------------------------------------------------------- #include "xbox_loader.h" #include #include "XMVHelper.h" #include "XBUtil.h" #include // Funtion Prototypes for packet loading functions for loading from a file. HRESULT CALLBACK GetNextPacket( DWORD dwContext, void **ppPacket, DWORD* pOffsetToNextPacket ); HRESULT CALLBACK ReleasePreviousPacket( DWORD dwContext, LONGLONG llNextReadByteOffset, DWORD dwNextPacketSize ); // Funtion Prototypes for packet loading functions for loading from a block of memory. HRESULT CALLBACK GetNextMemoryPacket( DWORD dwContext, void **ppPacket, DWORD* pOffsetToNextPacket ); HRESULT CALLBACK ReleasePreviousMemoryPacket( DWORD dwContext, LONGLONG llNextReadByteOffset, DWORD dwNextPacketSize ); //----------------------------------------------------------------------------- // Name: CXMVPlayer() // Desc: Constructor for CXMVPlayer //----------------------------------------------------------------------------- CXMVPlayer::CXMVPlayer() { m_pXMVDecoder = NULL; ZeroMemory( &m_VideoDesc, sizeof( m_VideoDesc ) ); ZeroMemory( &m_AudioDesc, sizeof( m_AudioDesc ) ); for ( UINT i=0; iEnableOverlay( FALSE ); m_bOverlaysEnabled = FALSE; } // Free the XMV decoder. if ( NULL != m_pXMVDecoder ) { m_pXMVDecoder->CloseDecoder(); m_pXMVDecoder = NULL; } ZeroMemory( &m_VideoDesc, sizeof( m_VideoDesc ) ); ZeroMemory( &m_AudioDesc, sizeof( m_AudioDesc ) ); // Release our textures. for ( UINT i=0; iRelease(); m_pTextures[i] = 0; } m_dwCurrentFrame = -1; m_dwStartTime = 0; m_bPlaying = FALSE; // Release any file handles we were using. if( INVALID_HANDLE_VALUE != m_loadContext.hFile ) { CloseHandle( m_loadContext.hFile ); m_loadContext.hFile = INVALID_HANDLE_VALUE; } // Free up memory used for playing a movie from memory. if ( m_loadContext.pInputBuffer ) { free( m_loadContext.pInputBuffer ); m_loadContext.pInputBuffer = 0; } // Be sure to release the physical memory last! if( m_physicalBuffer ) { XPhysicalFree( m_physicalBuffer ); m_physicalBuffer = 0; } return S_OK; } //----------------------------------------------------------------------------- // Name: FinishOpeningFile() // Desc: Helper function for the three Open functions. Enables the audio streams, // initializes the video descriptor, and allocates textures if needed. //----------------------------------------------------------------------------- HRESULT CXMVPlayer::FinishOpeningFile( D3DFORMAT format, LPDIRECT3DDEVICE8 pDevice, BOOL bAllocateTextures ) { assert( format == D3DFMT_YUY2 || format == D3DFMT_LIN_A8R8G8B8 ); assert( XMVPLAYER_NUMTEXTURES >= 2); HRESULT hr = S_OK; m_pXMVDecoder->GetVideoDescriptor( &m_VideoDesc ); // Enable the audio streams for ( unsigned i=0; i < m_VideoDesc.AudioStreamCount; i++ ) { m_pXMVDecoder->GetAudioDescriptor( i, &m_AudioDesc ); hr = m_pXMVDecoder->EnableAudioStream( i, 0, NULL, NULL); if ( FAILED( hr ) ) { XBUtil_DebugPrint( "Unable to enable audio stream 0 (error %x)\n", hr ); Destroy(); return hr; } } for ( int i = 0; i < XMVPLAYER_NUMTEXTURES; i++ ) { m_pTextures[i] = 0; if ( bAllocateTextures ) { hr = pDevice->CreateTexture( m_VideoDesc.Width, m_VideoDesc.Height, 1, 0, format, 0, &m_pTextures[i] ); if ( FAILED( hr ) ) { XBUtil_DebugPrint( "Unable to create texture %d (error %x)\n", i, hr ); Destroy(); return hr; } } } // Initialize what texture we are decoding to, if decoding for texture mapping. m_nDecodeTextureIndex = 0; // Initialize the various texture pointers for use when decoding for overlays. pShowingTexture = m_pTextures[0]; pDecodingTexture = m_pTextures[1]; pSubmittedTexture = 0; m_bPlaying = TRUE; m_dwStartTime = GetTickCount(); return hr; } //----------------------------------------------------------------------------- // Name: OpenFile() // Desc: Create an XMV decoder object that reads from a file. //----------------------------------------------------------------------------- HRESULT CXMVPlayer::OpenFile( const CHAR* lpFilename, D3DFORMAT format, LPDIRECT3DDEVICE8 pDevice, BOOL bAllocateTextures ) { HRESULT hr = S_OK; m_bError = FALSE; if ( NULL == lpFilename || NULL == pDevice ) { XBUtil_DebugPrint( "Bad parameter to OpenFile()\n" ); m_bError = TRUE; return E_FAIL; } hr = XMVDecoder_CreateDecoderForFile( XMVFLAG_SYNC_ON_NEXT_VBLANK, ( CHAR* )lpFilename, &m_pXMVDecoder ); if ( FAILED( hr ) ) { XBUtil_DebugPrint( "Unable to create XMV Decoder for %s (error: %x)\n", lpFilename, hr ); m_bError = TRUE; return hr; } hr = FinishOpeningFile( format, pDevice, bAllocateTextures ); if ( FAILED( hr ) ) { m_bError = TRUE; } return hr; } //----------------------------------------------------------------------------- // Name: OpenFileForPackets() // Desc: Create an XMV decoder object that uses the packet reading interface. // Currently this just reads from a file, but it can be altered to read from // custom formats, start partway through a file, etc. //----------------------------------------------------------------------------- HRESULT CXMVPlayer::OpenFileForPackets( const CHAR* lpFilename, D3DFORMAT format, LPDIRECT3DDEVICE8 pDevice, BOOL bAllocateTextures ) { HRESULT hr = S_OK; // We need to read in the first 4K of data for the XMV player to initialize // itself from. This is most conveniently read as an array of DWORDS. DWORD first4Kbytes[4096 / sizeof( DWORD )]; // Clear entire context struct to zero ZeroMemory( &m_loadContext, sizeof( m_loadContext ) ); // Open the input file. m_loadContext.hFile = CreateFile( lpFilename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, NULL ); if( m_loadContext.hFile == INVALID_HANDLE_VALUE ) { Destroy(); return E_INVALIDARG; } // Read the first page from the file. We opened it for // overlapped IO so we do a pair of reads. m_loadContext.Overlapped.Offset = 0; m_loadContext.Overlapped.OffsetHigh = 0; // Start the read. if( 0 == ReadFile( m_loadContext.hFile, first4Kbytes, sizeof( first4Kbytes ), NULL, &m_loadContext.Overlapped ) ) { if( GetLastError() != ERROR_IO_PENDING ) { Destroy(); return E_FAIL; } } // Wait for the read to finish. DWORD dwBytesRead; if( !GetOverlappedResult( m_loadContext.hFile, &m_loadContext.Overlapped, &dwBytesRead, TRUE ) ) { Destroy(); return E_FAIL; } // Check size to make sure input is a valid XMV file. if( dwBytesRead != 4096 ) { Destroy(); return E_FAIL; } // Create an XMV decoder hr = XMVDecoder_CreateDecoderForPackets( XMVFLAG_SYNC_ON_NEXT_VBLANK, first4Kbytes, ( DWORD )&m_loadContext, GetNextPacket, ReleasePreviousPacket, &m_pXMVDecoder ); if( FAILED( hr ) ) { Destroy(); return E_FAIL; } // The size of the first packet and the minimum size of the two packet buffers are stored in the // second and third DWORDS of the file. From xmv.h: // * DWORD NextPacketSize // The size of the next packet // * DWORD ThisPacketSize // The size of this packet // * DWORD MaxPacketSize // The size of the largest packet in the file DWORD dwThisPacketSize = first4Kbytes[1]; DWORD dwRequiredPacketSize = first4Kbytes[2]; // Check for illegal parameters. if( dwThisPacketSize > dwRequiredPacketSize ) { Destroy(); return E_FAIL; } // XPhysicalAlloc is used so that 5.1 or compressed audio streams can be played. m_physicalBuffer = ( BYTE* )XPhysicalAlloc( dwRequiredPacketSize * 2, MAXULONG_PTR, 0, PAGE_READWRITE ); // Save our information. m_loadContext.dwPacketSize = dwRequiredPacketSize; m_loadContext.pLoadingPacket = m_physicalBuffer; m_loadContext.pDecodingPacket = m_physicalBuffer + dwRequiredPacketSize; // Read the first packet. We wind up re-reading the first 4096 // bytes but it makes the logic for figuring out how much we read // a little bit easier... m_loadContext.Overlapped.Offset = 0; m_loadContext.Overlapped.OffsetHigh = 0; if( 0 == ReadFile( m_loadContext.hFile, m_physicalBuffer, dwThisPacketSize, NULL, &m_loadContext.Overlapped ) ) { if( GetLastError() != ERROR_IO_PENDING ) { Destroy(); return E_FAIL; } } // Note - at this point the preceding read has *not* necessarily completed. // Don't try reading anything from that buffer until GetNextPacket has been // successfully called. hr = FinishOpeningFile( format, pDevice, bAllocateTextures ); return hr; } //----------------------------------------------------------------------------- // Name: OpenMovieFromMemory() // Desc: Create an XMV decoder object that uses the packet reading interface to // read from a block of memory. To simplify the memory management this function // also allocates this block of memory and initializes it from a file. //----------------------------------------------------------------------------- HRESULT CXMVPlayer::OpenMovieFromMemory( const CHAR* lpFilename, D3DFORMAT format, LPDIRECT3DDEVICE8 pDevice, BOOL bAllocateTextures ) { HRESULT hr = S_OK; m_bError = FALSE; // Read the entire file into memory. void* data; hr = XBUtil_LoadFile( lpFilename, &data, &m_loadContext.inputSize ); if ( FAILED( hr ) ) { m_bError = TRUE; return hr; } m_loadContext.pInputBuffer = ( BYTE* )data; // Check size to make sure input is a valid XMV file. if( m_loadContext.inputSize < 4096 ) { Destroy(); m_bError = TRUE; return E_FAIL; } // Get a DWORD pointer to the first 4K - needed by CreateDecoderForPackets DWORD* first4Kbytes = ( DWORD* )data; // Create an XMV decoder hr = XMVDecoder_CreateDecoderForPackets( XMVFLAG_SYNC_ON_NEXT_VBLANK, first4Kbytes, ( DWORD )&m_loadContext, GetNextMemoryPacket, ReleasePreviousMemoryPacket, &m_pXMVDecoder ); if ( FAILED( hr ) ) { Destroy(); m_bError = TRUE; return E_FAIL; } // The size of the first packet and the minimum size of the two packet buffers are stored in the // second and third DWORDS of the file. From xmv.h: // * DWORD NextPacketSize // The size of the next packet // * DWORD ThisPacketSize // The size of this packet // * DWORD MaxPacketSize // The size of the largest packet in the file DWORD dwThisPacketSize = first4Kbytes[1]; DWORD dwRequiredPacketSize = first4Kbytes[2]; // Check for illegal parameters. if( dwThisPacketSize > dwRequiredPacketSize ) { Destroy(); m_bError = TRUE; return E_FAIL; } // XPhysicalAlloc is used so that 5.1 or compressed audio streams can be played. m_physicalBuffer = ( BYTE* )XPhysicalAlloc( dwRequiredPacketSize * 2, MAXULONG_PTR, 0, PAGE_READWRITE ); // Save our information for the callback functions. // The size of our two memory blocks. m_loadContext.dwPacketSize = dwRequiredPacketSize; // The addresses of our two memory blocks. m_loadContext.pLoadingPacket = m_physicalBuffer; m_loadContext.pDecodingPacket = m_physicalBuffer + dwRequiredPacketSize; // Information about the block of memory the movie is stored in. m_loadContext.pInputBuffer = ( BYTE* )data; m_loadContext.inputSize = m_loadContext.inputSize; m_loadContext.readOffset = 0; m_loadContext.currentPacketSize = dwThisPacketSize; hr = FinishOpeningFile( format, pDevice, bAllocateTextures ); if ( FAILED( hr ) ) { m_bError = TRUE; } return hr; } //----------------------------------------------------------------------------- // Name: AdvanceFrameForTexturing() // Desc: Unpack the appropriate frames of data for use as textures. //----------------------------------------------------------------------------- LPDIRECT3DTEXTURE8 CXMVPlayer::AdvanceFrameForTexturing( LPDIRECT3DDEVICE8 pDevice ) { // You must pass bAllocateTextures==TRUE to Open if you're going to use GetTexture/AdvanceFrame. assert( m_pTextures[0] ); LPDIRECT3DSURFACE8 pSurface; pDecodingTexture->GetSurfaceLevel( 0, &pSurface ); // Decode some information to the current draw texture. XMVRESULT xr = XMV_NOFRAME; m_pXMVDecoder->GetNextFrame( pSurface, &xr, NULL ); switch ( xr ) { case XMV_NOFRAME: // Do nothing - we didn't get a frame. break; case XMV_NEWFRAME: ++m_dwCurrentFrame; // GetNextFrame produced a new frame. So, the texture we were decoding // to becomes available for drawing as a texture. pShowingTexture = pDecodingTexture; // Setup for decoding to the next texture. m_nDecodeTextureIndex = ( m_nDecodeTextureIndex + 1 ) % XMVPLAYER_NUMTEXTURES; pDecodingTexture = m_pTextures[ m_nDecodeTextureIndex ]; break; case XMV_ENDOFFILE: m_bPlaying = FALSE; break; case XMV_FAIL: // Data corruption or file read error. We'll treat that the same as // end of file. m_bPlaying = FALSE; m_bError = TRUE; break; } SAFE_RELEASE( pSurface ); // If we haven't decoded the first frame then return zero. if ( m_dwCurrentFrame < 0 ) return 0; return pShowingTexture; } //----------------------------------------------------------------------------- // Name: AdvanceFrameForOverlays() // Desc: Unpack the appropriate frames of data for use as an overlay. //----------------------------------------------------------------------------- LPDIRECT3DTEXTURE8 CXMVPlayer::AdvanceFrameForOverlays( LPDIRECT3DDEVICE8 pDevice ) { // You must pass bAllocateTextures==TRUE to Open if you're going to use GetTexture/AdvanceFrame. assert( m_pTextures[0] ); // You have to call CXMVPlayer::EnableOverlays() if you are going to use overlays. assert( m_bOverlaysEnabled ); // If a texture has been submitted to be used as an overlay then we have to // wait for GetUpdateOverlayState() to return TRUE before we can assume that // the previous texture has *stopped* being displayed. Once GetUpdateOverlayState() // returns TRUE then we know that pSubmittedTexture is being displayed, which // means that, pShowingTexture is available as a decoding target. if ( pSubmittedTexture ) { // If GetOverlayUpdateStatus() returns FALSE then we can still proceed and // call GetNextFrame(), but we will pass NULL for the surface parameter. // Some work will still be done, but none of the surfaces will be altered. if ( pDevice->GetOverlayUpdateStatus() ) { // The call to UpdateOverlay() with pSubmittedTexture must have taken // effect now, so pShowingTexture is available as a decoding target. assert( !pDecodingTexture ); pDecodingTexture = pShowingTexture; pShowingTexture = pSubmittedTexture; pSubmittedTexture = NULL; } } LPDIRECT3DSURFACE8 pSurface = NULL; if ( pDecodingTexture ) pDecodingTexture->GetSurfaceLevel( 0, &pSurface ); // Decode some information to the current draw texture, which may be NULL. // pDecodingTexture will be NULL if one texture has been submitted as a new // overlay but the other one is still being displayed as an overlay. // If pSurface is NULL GetNextFrame() will still do some work. XMVRESULT xr = XMV_NOFRAME; m_pXMVDecoder->GetNextFrame( pSurface, &xr, NULL ); switch ( xr ) { case XMV_NOFRAME: // Do nothing - we didn't get a frame. break; case XMV_NEWFRAME: ++m_dwCurrentFrame; // GetNextFrame produced a new frame. So, the texture we were decoding // to becomes available for displaying as an overlay. // The other texture is not ready to be a decoding target. It is still // being displayed as an overlay. So, we assign the newly decoded // texture to pSubmittedTexture for the program to submit as an overlay, // but we don't yet move the previously submitted texture from pShowing // to pDecoding. That happens on a subsequent call to this function, after // GetOverlayUpdateStatus() returns TRUE to tell us that there are no // overlay swaps pending. assert( pDecodingTexture ); assert( !pSubmittedTexture ); pSubmittedTexture = pDecodingTexture; pDecodingTexture = NULL; break; case XMV_ENDOFFILE: m_bPlaying = FALSE; break; case XMV_FAIL: // Data corruption or file read error. We'll treat that the same as // end of file. m_bPlaying = FALSE; m_bError = TRUE; break; } SAFE_RELEASE( pSurface ); // If we just unpacked a new frame then we return that texture // and the program must call UpdateOverlay() with the surface // from that texture. // If we didn't unpack a frame then the program should do nothing - // the previous overlay will continue to be displayed. if ( XMV_NEWFRAME == xr ) return pSubmittedTexture; // No new frame to display. return 0; } //----------------------------------------------------------------------------- // Name: TerminatePlayback() // Desc: Calls XMVDecoder::TerminatePlayback() //----------------------------------------------------------------------------- void CXMVPlayer::TerminatePlayback() { m_pXMVDecoder->TerminatePlayback(); } //----------------------------------------------------------------------------- // Name: Play() // Desc: Calls XMVDecoder::Play() to play the entire movie. //----------------------------------------------------------------------------- HRESULT CXMVPlayer::Play( DWORD Flags, RECT* pRect ) { // You have to call Open before calling Play. assert( m_pXMVDecoder ); // Don't pass bAllocateTextures==TRUE to Open if you're going to use Play. assert( !m_pTextures[0] ); return m_pXMVDecoder->Play( Flags, pRect ); } //----------------------------------------------------------------------------- // Name: EnableOverlays() // Desc: Enable the overlay planes for playing the movie in them, and record // that the overlays should be disabled when Destroy() is called. //----------------------------------------------------------------------------- void CXMVPlayer::EnableOverlays( LPDIRECT3DDEVICE8 pDevice ) { m_pDevice = pDevice; pDevice->EnableOverlay( TRUE ); m_bOverlaysEnabled = TRUE; } //----------------------------------------------------------------------------- // Name: GetNextPacket() // Desc: Callback function to get next packet from a file //----------------------------------------------------------------------------- static HRESULT CALLBACK GetNextPacket( DWORD dwContext, VOID** ppPacket, DWORD* pOffsetToNextPacket ) { LOAD_CONTEXT* pContext = ( LOAD_CONTEXT* )dwContext; if( NULL == pContext ) return E_FAIL; // If the next packet is fully loaded then return it, // otherwise return NULL. DWORD dwBytesRead; if( GetOverlappedResult( pContext->hFile, &pContext->Overlapped, &dwBytesRead, FALSE ) ) { // Make the old decoding packet pending. pContext->pPendingReleasePacket = pContext->pDecodingPacket; pContext->pDecodingPacket = pContext->pLoadingPacket; pContext->pLoadingPacket = NULL; // Offset to the next packet. *pOffsetToNextPacket = dwBytesRead; // Set *ppPacket to the data we just loaded. *ppPacket = pContext->pDecodingPacket; } else { DWORD dwError = GetLastError(); // If we're waiting on the IO to finish, just do nothing. if( dwError != ERROR_IO_INCOMPLETE ) return HRESULT_FROM_WIN32( dwError ); *ppPacket = NULL; *pOffsetToNextPacket = 0; } return S_OK; } //----------------------------------------------------------------------------- // Name: ReleasePreviousPacket() // Desc: Callback function to release previous packet from a file //----------------------------------------------------------------------------- static HRESULT CALLBACK ReleasePreviousPacket( DWORD dwContext, LONGLONG llNextReadByteOffset, DWORD dwNextPacketSize ) { LOAD_CONTEXT* pContext = ( LOAD_CONTEXT* )dwContext; if( NULL == pContext ) return E_FAIL; if( dwNextPacketSize != 0 ) { // Start the next load. pContext->Overlapped.Offset = ( DWORD )( llNextReadByteOffset & 0xFFFFFFFF ); pContext->Overlapped.OffsetHigh = ( DWORD )( llNextReadByteOffset >> 32 ); // Check for bad input file - buffer overrun if( dwNextPacketSize > pContext->dwPacketSize ) return E_FAIL; pContext->pLoadingPacket = pContext->pPendingReleasePacket; pContext->pPendingReleasePacket = NULL; if( 0 == ReadFile( pContext->hFile, pContext->pLoadingPacket, dwNextPacketSize, NULL, &pContext->Overlapped ) ) { if( GetLastError() != ERROR_IO_PENDING ) return HRESULT_FROM_WIN32( GetLastError() ); } } return S_OK; } //----------------------------------------------------------------------------- // Name: GetNextMemoryPacket() // Desc: Callback function to get next packet from a file, // and setup for the next packet. //----------------------------------------------------------------------------- static HRESULT CALLBACK GetNextMemoryPacket( DWORD dwContext, VOID** ppPacket, DWORD* pOffsetToNextPacket ) { LOAD_CONTEXT* pContext = ( LOAD_CONTEXT* )dwContext; if( NULL == pContext ) return E_FAIL; DWORD dwBytesRead = pContext->inputSize - pContext->readOffset; if ( pContext->currentPacketSize < dwBytesRead ) dwBytesRead = pContext->currentPacketSize; memcpy( pContext->pLoadingPacket, pContext->pInputBuffer + pContext->readOffset , dwBytesRead ); pContext->readOffset +=dwBytesRead; // Swap pointers so that next time we load it goes into the other packet block. BYTE* temp = pContext->pLoadingPacket; pContext->pLoadingPacket = pContext->pDecodingPacket; pContext->pDecodingPacket = temp; // Offset to the next packet. *pOffsetToNextPacket = dwBytesRead; // Set *ppPacket to the data we just loaded. *ppPacket = pContext->pDecodingPacket; return S_OK; } //----------------------------------------------------------------------------- // Name: ReleasePreviousMemoryPacket() // Desc: Callback function to release previous packet from a block of memory, // and setup for the next packet. //----------------------------------------------------------------------------- static HRESULT CALLBACK ReleasePreviousMemoryPacket( DWORD dwContext, LONGLONG llNextReadByteOffset, DWORD dwNextPacketSize ) { LOAD_CONTEXT* pContext = ( LOAD_CONTEXT* )dwContext; if( NULL == pContext ) return E_FAIL; // Check for bad input file - buffer overrun if( dwNextPacketSize > pContext->dwPacketSize ) return E_FAIL; // Record the size of the next packet we are supposed to read, for GetNextMemoryPacket. pContext->currentPacketSize = dwNextPacketSize; return S_OK; }