//====== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======= // // Purpose: // //============================================================================= #include "avi/iavi.h" #include "avi.h" #include "filesystem.h" #include "tier1/strtools.h" #include "tier1/utllinkedlist.h" #include "tier1/keyvalues.h" #include "materialsystem/imaterial.h" #include "materialsystem/imaterialsystem.h" #include "materialsystem/materialsystemutil.h" #include "materialsystem/itexture.h" #include "vtf/vtf.h" #include "pixelwriter.h" #include "tier3/tier3.h" #pragma warning( disable : 4201 ) #define WIN32_LEAN_AND_MEAN #include #include #pragma warning( default : 4201 ) DWORD g_dwLastValidCodec = 0; //----------------------------------------------------------------------------- // // Class used to write out AVI files // //----------------------------------------------------------------------------- class CAviFile { public: CAviFile(); void Init( const AVIParams_t& params, void *hWnd ); void Shutdown(); void AppendMovieSound( short *buf, size_t bufsize ); void AppendMovieFrame( const BGR888_t *pRGBData ); private: void Reset(); void CreateVideoStreams( const AVIParams_t& params, void *hWnd ); void CreateAudioStream(); bool m_bValid; int m_nWidth; int m_nHeight; IAVIFile *m_pAVIFile; WAVEFORMATEX m_wFormat; int m_nFrameRate; int m_nFrameScale; IAVIStream *m_pAudioStream; IAVIStream *m_pVideoStream; IAVIStream *m_pCompressedStream; int m_nFrame; int m_nSample; HDC m_memdc; HBITMAP m_DIBSection; BITMAPINFO m_bi; BITMAPINFOHEADER *m_bih; }; //----------------------------------------------------------------------------- // Constructor //----------------------------------------------------------------------------- CAviFile::CAviFile() { Reset(); } //----------------------------------------------------------------------------- // Reset the avi file //----------------------------------------------------------------------------- void CAviFile::Reset() { Q_memset( &m_wFormat, 0, sizeof( m_wFormat ) ); Q_memset( &m_bi, 0, sizeof( m_bi ) ); m_bValid = false; m_nWidth = 0; m_nHeight = 0; m_pAVIFile = NULL; m_nFrameRate = 0; m_nFrameScale = 1; m_pAudioStream = NULL; m_pVideoStream = NULL; m_pCompressedStream = NULL; m_nFrame = 0; m_nSample = 0; m_memdc = ( HDC )0; m_DIBSection = ( HBITMAP )0; m_bih = &m_bi.bmiHeader; m_bih->biSize = sizeof( *m_bih ); //m_bih->biWidth = xxx //m_bih->biHeight = xxx m_bih->biPlanes = 1; m_bih->biBitCount = 24; m_bih->biCompression = BI_RGB; //m_bih->biSizeImage = ( ( m_bih->biWidth * m_bih->biBitCount/8 + 3 )& 0xFFFFFFFC ) * m_bih->biHeight; m_bih->biXPelsPerMeter = 10000; m_bih->biYPelsPerMeter = 10000; m_bih->biClrUsed = 0; m_bih->biClrImportant = 0; } //----------------------------------------------------------------------------- // Start recording an AVI //----------------------------------------------------------------------------- void CAviFile::Init( const AVIParams_t& params, void *hWnd ) { Reset(); char avifilename[ 512 ]; char fullavifilename[ 512 ]; Q_snprintf( avifilename, sizeof( avifilename ), "%s", params.m_pFileName ); Q_SetExtension( avifilename, ".avi", sizeof( avifilename ) ); g_pFullFileSystem->RelativePathToFullPath( avifilename, params.m_pPathID, fullavifilename, sizeof( fullavifilename ) ); if ( g_pFullFileSystem->FileExists( fullavifilename, params.m_pPathID ) ) { g_pFullFileSystem->RemoveFile( fullavifilename, params.m_pPathID ); } HRESULT hr = AVIFileOpen( &m_pAVIFile, fullavifilename, OF_WRITE | OF_CREATE, NULL ); if ( hr != AVIERR_OK ) return; m_wFormat.cbSize = sizeof( m_wFormat ); m_wFormat.wFormatTag = WAVE_FORMAT_PCM; m_wFormat.nChannels = params.m_nNumChannels; m_wFormat.nSamplesPerSec = params.m_nSampleRate; m_wFormat.nBlockAlign = params.m_nNumChannels * ( params.m_nSampleBits == 8 ? 1 : 2 ); m_wFormat.nAvgBytesPerSec = m_wFormat.nBlockAlign * params.m_nSampleRate; m_wFormat.wBitsPerSample = params.m_nSampleBits; m_nFrameRate = params.m_nFrameRate; m_nFrameScale = params.m_nFrameScale; m_bValid = true; m_nHeight = params.m_nHeight; m_nWidth = params.m_nWidth; CreateVideoStreams( params, hWnd ); CreateAudioStream(); } void CAviFile::Shutdown() { if ( m_pAudioStream ) { AVIStreamRelease( m_pAudioStream ); m_pAudioStream = NULL; } if ( m_pVideoStream ) { AVIStreamRelease( m_pVideoStream ); m_pVideoStream = NULL; } if ( m_pCompressedStream ) { AVIStreamRelease( m_pCompressedStream ); m_pCompressedStream = NULL; } if ( m_pAVIFile ) { AVIFileRelease( m_pAVIFile ); m_pAVIFile = NULL; } if ( m_DIBSection != 0 ) { DeleteObject( m_DIBSection ); } if ( m_memdc != 0 ) { // Release the compatible DC DeleteDC( m_memdc ); } Reset(); } static unsigned int FormatAviMessage( HRESULT code, char *buf, unsigned int len) { const char *msg="unknown avi result code"; switch (code) { case S_OK: msg="Success"; break; case AVIERR_BADFORMAT: msg="AVIERR_BADFORMAT: corrupt file or unrecognized format"; break; case AVIERR_MEMORY: msg="AVIERR_MEMORY: insufficient memory"; break; case AVIERR_FILEREAD: msg="AVIERR_FILEREAD: disk error while reading file"; break; case AVIERR_FILEOPEN: msg="AVIERR_FILEOPEN: disk error while opening file"; break; case REGDB_E_CLASSNOTREG: msg="REGDB_E_CLASSNOTREG: file type not recognised"; break; case AVIERR_READONLY: msg="AVIERR_READONLY: file is read-only"; break; case AVIERR_NOCOMPRESSOR: msg="AVIERR_NOCOMPRESSOR: a suitable compressor could not be found"; break; case AVIERR_UNSUPPORTED: msg="AVIERR_UNSUPPORTED: compression is not supported for this type of data"; break; case AVIERR_INTERNAL: msg="AVIERR_INTERNAL: internal error"; break; case AVIERR_BADFLAGS: msg="AVIERR_BADFLAGS"; break; case AVIERR_BADPARAM: msg="AVIERR_BADPARAM"; break; case AVIERR_BADSIZE: msg="AVIERR_BADSIZE"; break; case AVIERR_BADHANDLE: msg="AVIERR_BADHANDLE"; break; case AVIERR_FILEWRITE: msg="AVIERR_FILEWRITE: disk error while writing file"; break; case AVIERR_COMPRESSOR: msg="AVIERR_COMPRESSOR"; break; case AVIERR_NODATA: msg="AVIERR_READONLY"; break; case AVIERR_BUFFERTOOSMALL: msg="AVIERR_BUFFERTOOSMALL"; break; case AVIERR_CANTCOMPRESS: msg="AVIERR_CANTCOMPRESS"; break; case AVIERR_USERABORT: msg="AVIERR_USERABORT"; break; case AVIERR_ERROR: msg="AVIERR_ERROR"; break; } unsigned int mlen = (unsigned int)Q_strlen( msg ); if ( buf==0 || len==0 ) return mlen; unsigned int n=mlen; if (n+1>len) { n=len-1; } strncpy(buf,msg,n); buf[n]=0; return mlen; } static void ReportError( HRESULT hr ) { char buf[ 512 ]; FormatAviMessage( hr, buf, sizeof( buf ) ); Warning( "%s\n", buf ); } void CAviFile::CreateVideoStreams( const AVIParams_t& params, void *hWnd ) { AVISTREAMINFO streaminfo; Q_memset( &streaminfo, 0, sizeof( streaminfo ) ) ; streaminfo.fccType = streamtypeVIDEO; streaminfo.fccHandler = 0; streaminfo.dwScale = params.m_nFrameScale; streaminfo.dwRate = params.m_nFrameRate; streaminfo.dwSuggestedBufferSize = params.m_nWidth * params.m_nHeight * 3; SetRect( &streaminfo.rcFrame, 0, 0, params.m_nWidth, params.m_nHeight ); HRESULT hr = AVIFileCreateStream( m_pAVIFile, &m_pVideoStream, &streaminfo ); if ( hr != AVIERR_OK ) { m_bValid = false; ReportError( hr ); return; } AVICOMPRESSOPTIONS compression; Q_memset( &compression, 0, sizeof( compression ) ); AVICOMPRESSOPTIONS *aopts[1]; aopts[ 0 ] = &compression; // Choose DIVX compressor for now Warning( "FIXME: DIVX only for now\n" ); if ( params.m_bGetCodecFromUser ) { // FIXME: This won't work so well in full screen!!! if ( !AVISaveOptions( (HWND)hWnd, 0, 1, &m_pVideoStream, aopts ) ) { m_bValid = false; return; } // Cache for next time g_dwLastValidCodec = compression.fccHandler; } else { compression.fccHandler = g_dwLastValidCodec ? g_dwLastValidCodec : mmioFOURCC( 'd', 'i', 'b', ' ' ); } hr = AVIMakeCompressedStream( &m_pCompressedStream, m_pVideoStream, &compression, NULL ); if ( hr != AVIERR_OK ) { m_bValid = false; ReportError( hr ); return; } // Create a compatible DC HDC hdcscreen = GetDC( GetDesktopWindow() ); m_memdc = CreateCompatibleDC(hdcscreen); ReleaseDC( GetDesktopWindow(), hdcscreen ); // Set up a DIBSection for the screen m_bih->biWidth = params.m_nWidth; m_bih->biHeight = params.m_nHeight; m_bih->biSizeImage = ( ( m_bih->biWidth * m_bih->biBitCount / 8 + 3 )& 0xFFFFFFFC ) * m_bih->biHeight; // Create the DIBSection void *bits; m_DIBSection = CreateDIBSection ( m_memdc, ( BITMAPINFO *)m_bih, DIB_RGB_COLORS, &bits, NULL, NULL ); // Get at the DIBSection object DIBSECTION dibs; GetObject( m_DIBSection, sizeof( dibs ), &dibs ); // Set the stream format hr = AVIStreamSetFormat( m_pCompressedStream, 0, &dibs.dsBmih, dibs.dsBmih.biSize + dibs.dsBmih.biClrUsed *sizeof( RGBQUAD ) ); if ( hr != AVIERR_OK ) { m_bValid = false; ReportError( hr ); return; } } void CAviFile::CreateAudioStream() { AVISTREAMINFO audiostream; Q_memset( &audiostream, 0, sizeof( audiostream ) ); audiostream.fccType = streamtypeAUDIO; audiostream.dwScale = m_wFormat.nBlockAlign; audiostream.dwRate = m_wFormat.nSamplesPerSec * m_wFormat.nBlockAlign; audiostream.dwSampleSize = m_wFormat.nBlockAlign; audiostream.dwQuality = (DWORD)-1; HRESULT hr = AVIFileCreateStream( m_pAVIFile, &m_pAudioStream, &audiostream ); if ( hr != AVIERR_OK ) { m_bValid = false; ReportError( hr ); return; } hr = AVIStreamSetFormat( m_pAudioStream, 0, &m_wFormat, sizeof( m_wFormat ) ); if ( hr != AVIERR_OK ) { m_bValid = false; ReportError( hr ); return; } } void CAviFile::AppendMovieSound( short *buf, size_t bufsize ) { if ( !m_bValid ) return; unsigned long numsamps = bufsize / sizeof( short ); // numbytes*8 / au->wfx.wBitsPerSample; // // now we can write the data HRESULT hr = AVIStreamWrite ( m_pAudioStream, m_nSample, numsamps, buf, bufsize, 0, NULL, NULL ); if ( hr != AVIERR_OK ) { m_bValid = false; ReportError( hr ); return; } m_nSample += numsamps; } //----------------------------------------------------------------------------- // Adds a frame of the movie to the AVI //----------------------------------------------------------------------------- void CAviFile::AppendMovieFrame( const BGR888_t *pRGBData ) { if ( !m_bValid ) return; DIBSECTION dibs; HGDIOBJ hOldObject = SelectObject( m_memdc, m_DIBSection ); // Update the DIBSection bits // FIXME: Have to invert this vertically since passing in negative // biHeights in the m_bih field doesn't make the system know it's a top-down AVI int scanlines = 0; for ( int i = 0; i < m_nHeight; ++i ) { scanlines += SetDIBits( m_memdc, m_DIBSection, m_nHeight - i - 1, 1, pRGBData, ( CONST BITMAPINFO * )m_bih, DIB_RGB_COLORS ); pRGBData += m_nWidth; } int objectSize = GetObject( m_DIBSection, sizeof( dibs ), &dibs ); if ( scanlines != m_nHeight || objectSize != sizeof( DIBSECTION )) { SelectObject( m_memdc, hOldObject ); m_bValid = false; return; } // Now we can add the frame HRESULT hr = AVIStreamWrite( m_pCompressedStream, m_nFrame, 1, dibs.dsBm.bmBits, dibs.dsBmih.biSizeImage, AVIIF_KEYFRAME, NULL, NULL ); SelectObject( m_memdc, hOldObject ); if ( hr != AVIERR_OK ) { m_bValid = false; ReportError( hr ); return; } ++m_nFrame; } //----------------------------------------------------------------------------- // // Class used to associated AVI files with IMaterials // //----------------------------------------------------------------------------- class CAVIMaterial : public ITextureRegenerator { public: CAVIMaterial(); // Initializes, shuts down the material bool Init( const char *pMaterialName, const char *pFileName, const char *pPathID ); void Shutdown(); // Inherited from ITextureRegenerator virtual void RegenerateTextureBits( ITexture *pTexture, IVTFTexture *pVTFTexture, Rect_t *pRect ); virtual void Release(); // Returns the material IMaterial *GetMaterial(); // Returns the texcoord range void GetTexCoordRange( float *pMaxU, float *pMaxV ); // Returns the frame size of the AVI (stored in a subrect of the material itself) void GetFrameSize( int *pWidth, int *pHeight ); // Sets the current time void SetTime( float flTime ); // Returns the frame rate/count of the AVI int GetFrameRate( ); int GetFrameCount( ); // Sets the frame for an AVI material (use instead of SetTime) void SetFrame( float flFrame ); private: // Initializes, shuts down the procedural texture void CreateProceduralTexture( const char *pTextureName ); void DestroyProceduralTexture(); // Initializes, shuts down the procedural material void CreateProceduralMaterial( const char *pMaterialName ); void DestroyProceduralMaterial(); // Initializes, shuts down the video stream void CreateVideoStream( ); void DestroyVideoStream( ); CMaterialReference m_Material; CTextureReference m_Texture; IAVIFile *m_pAVIFile; IAVIStream *m_pAVIStream; IGetFrame *m_pGetFrame; int m_nAVIWidth; int m_nAVIHeight; int m_nFrameRate; int m_nFrameCount; int m_nCurrentSample; HDC m_memdc; HBITMAP m_DIBSection; BITMAPINFO m_bi; BITMAPINFOHEADER *m_bih; }; //----------------------------------------------------------------------------- // Constructor //----------------------------------------------------------------------------- CAVIMaterial::CAVIMaterial() { Q_memset( &m_bi, 0, sizeof( m_bi ) ); m_memdc = ( HDC )0; m_DIBSection = ( HBITMAP )0; m_pAVIStream = NULL; m_pAVIFile = NULL; m_pGetFrame = NULL; } //----------------------------------------------------------------------------- // Initializes the material //----------------------------------------------------------------------------- bool CAVIMaterial::Init( const char *pMaterialName, const char *pFileName, const char *pPathID ) { // Determine the full path name of the AVI char pAVIFileName[ 512 ]; char pFullAVIFileName[ 512 ]; Q_snprintf( pAVIFileName, sizeof( pAVIFileName ), "%s", pFileName ); Q_DefaultExtension( pAVIFileName, ".avi", sizeof( pAVIFileName ) ); g_pFullFileSystem->RelativePathToFullPath( pAVIFileName, pPathID, pFullAVIFileName, sizeof( pFullAVIFileName ) ); HRESULT hr = AVIFileOpen( &m_pAVIFile, pFullAVIFileName, OF_READ, NULL ); if ( hr != AVIERR_OK ) { Warning( "AVI '%s' not found\n", pFullAVIFileName ); m_nAVIWidth = 64; m_nAVIHeight = 64; m_nFrameRate = 1; m_nFrameCount = 1; m_Material.Init( "debug/debugempty", TEXTURE_GROUP_OTHER ); return false; } // Get AVI size AVIFILEINFO info; AVIFileInfo( m_pAVIFile, &info, sizeof(info) ); m_nAVIWidth = info.dwWidth; m_nAVIHeight = info.dwHeight; m_nFrameRate = (int)( (float)info.dwRate / (float)info.dwScale + 0.5f ); CreateProceduralTexture( pMaterialName ); CreateProceduralMaterial( pMaterialName ); CreateVideoStream(); // Get frame count m_nFrameCount = MAX( 0, AVIStreamLength( m_pAVIStream ) ); m_Texture->Download(); return true; } void CAVIMaterial::Shutdown() { DestroyVideoStream(); DestroyProceduralMaterial( ); DestroyProceduralTexture( ); if ( m_pAVIFile ) { AVIFileRelease( m_pAVIFile ); m_pAVIFile = NULL; } } //----------------------------------------------------------------------------- // Returns the material //----------------------------------------------------------------------------- IMaterial *CAVIMaterial::GetMaterial() { return m_Material; } //----------------------------------------------------------------------------- // Returns the texcoord range //----------------------------------------------------------------------------- void CAVIMaterial::GetTexCoordRange( float *pMaxU, float *pMaxV ) { if ( !m_Texture ) { *pMaxU = *pMaxV = 1.0f; return; } int nTextureWidth = m_Texture->GetActualWidth(); int nTextureHeight = m_Texture->GetActualHeight(); if ( nTextureWidth ) *pMaxU = (float)m_nAVIWidth / (float)nTextureWidth; else *pMaxU = 0.0f; if ( nTextureHeight ) *pMaxV = (float)m_nAVIHeight / (float)nTextureHeight; else *pMaxV = 0.0f; } //----------------------------------------------------------------------------- // Returns the frame size of the AVI (stored in a subrect of the material itself) //----------------------------------------------------------------------------- void CAVIMaterial::GetFrameSize( int *pWidth, int *pHeight ) { *pWidth = m_nAVIWidth; *pHeight = m_nAVIHeight; } //----------------------------------------------------------------------------- // Computes a power of two at least as big as the passed-in number //----------------------------------------------------------------------------- static inline int ComputeGreaterPowerOfTwo( int n ) { int i = 1; while ( i < n ) { i <<= 1; } return i; } //----------------------------------------------------------------------------- // Initializes, shuts down the procedural texture //----------------------------------------------------------------------------- void CAVIMaterial::CreateProceduralTexture( const char *pTextureName ) { // Choose power-of-two textures which are at least as big as the AVI int nWidth = ComputeGreaterPowerOfTwo( m_nAVIWidth ); int nHeight = ComputeGreaterPowerOfTwo( m_nAVIHeight ); m_Texture.InitProceduralTexture( pTextureName, "avi", nWidth, nHeight, IMAGE_FORMAT_RGBA8888, TEXTUREFLAGS_CLAMPS | TEXTUREFLAGS_CLAMPT | TEXTUREFLAGS_NOMIP | TEXTUREFLAGS_PROCEDURAL | TEXTUREFLAGS_SINGLECOPY ); m_Texture->SetTextureRegenerator( this ); } void CAVIMaterial::DestroyProceduralTexture() { if (m_Texture) { m_Texture->SetTextureRegenerator( NULL ); m_Texture.Shutdown(); } } //----------------------------------------------------------------------------- // Initializes, shuts down the procedural material //----------------------------------------------------------------------------- void CAVIMaterial::CreateProceduralMaterial( const char *pMaterialName ) { // FIXME: gak, this is backwards. Why doesn't the material just see that it has a funky basetexture? char vmtfilename[ 512 ]; Q_strcpy( vmtfilename, pMaterialName ); Q_SetExtension( vmtfilename, ".vmt", sizeof( vmtfilename ) ); KeyValues *pVMTKeyValues = new KeyValues( "UnlitGeneric" ); if (!pVMTKeyValues->LoadFromFile( g_pFullFileSystem , vmtfilename, "GAME" )) { pVMTKeyValues->SetString( "$basetexture", m_Texture->GetName() ); pVMTKeyValues->SetInt( "$nofog", 1 ); pVMTKeyValues->SetInt( "$spriteorientation", 3 ); pVMTKeyValues->SetInt( "$translucent", 1 ); } m_Material.Init( pMaterialName, pVMTKeyValues ); m_Material->Refresh(); } void CAVIMaterial::DestroyProceduralMaterial() { m_Material.Shutdown(); } //----------------------------------------------------------------------------- // Sets the current time //----------------------------------------------------------------------------- void CAVIMaterial::SetTime( float flTime ) { if ( m_pAVIStream ) { // Round to the nearest frame // FIXME: Strangely, AVIStreamTimeToSample gets off by several frames if you're a ways down the stream // int nCurrentSample = AVIStreamTimeToSample( m_pAVIStream, ( flTime + 0.5f / m_nFrameRate )* 1000.0f ); int nCurrentSample = (int)( flTime * m_nFrameRate + 0.5f ); if ( m_nCurrentSample != nCurrentSample ) { m_nCurrentSample = nCurrentSample; m_Texture->Download(); } } } //----------------------------------------------------------------------------- // Returns the frame rate of the AVI //----------------------------------------------------------------------------- int CAVIMaterial::GetFrameRate( ) { return m_nFrameRate; } int CAVIMaterial::GetFrameCount( ) { return m_nFrameCount; } //----------------------------------------------------------------------------- // Sets the frame for an AVI material (use instead of SetTime) //----------------------------------------------------------------------------- void CAVIMaterial::SetFrame( float flFrame ) { if ( m_pAVIStream ) { int nCurrentSample = (int)( flFrame + 0.5f ); if ( m_nCurrentSample != nCurrentSample ) { m_nCurrentSample = nCurrentSample; m_Texture->Download(); } } } //----------------------------------------------------------------------------- // Initializes, shuts down the video stream //----------------------------------------------------------------------------- void CAVIMaterial::CreateVideoStream( ) { HRESULT hr = AVIFileGetStream( m_pAVIFile, &m_pAVIStream, streamtypeVIDEO, 0 ); if ( hr != AVIERR_OK ) { ReportError( hr ); return; } m_nCurrentSample = AVIStreamStart( m_pAVIStream ); // Create a compatible DC HDC hdcscreen = GetDC( GetDesktopWindow() ); m_memdc = CreateCompatibleDC( hdcscreen ); ReleaseDC( GetDesktopWindow(), hdcscreen ); // Set up a DIBSection for the screen m_bih = &m_bi.bmiHeader; m_bih->biSize = sizeof( *m_bih ); m_bih->biWidth = m_nAVIWidth; m_bih->biHeight = m_nAVIHeight; m_bih->biPlanes = 1; m_bih->biBitCount = 32; m_bih->biCompression = BI_RGB; m_bih->biSizeImage = ( ( m_bih->biWidth * m_bih->biBitCount / 8 + 3 )& 0xFFFFFFFC ) * m_bih->biHeight; m_bih->biXPelsPerMeter = 10000; m_bih->biYPelsPerMeter = 10000; m_bih->biClrUsed = 0; m_bih->biClrImportant = 0; // Create the DIBSection void *bits; m_DIBSection = CreateDIBSection( m_memdc, ( BITMAPINFO *)m_bih, DIB_RGB_COLORS, &bits, NULL, NULL ); // Get at the DIBSection object DIBSECTION dibs; GetObject( m_DIBSection, sizeof( dibs ), &dibs ); m_pGetFrame = AVIStreamGetFrameOpen( m_pAVIStream, &dibs.dsBmih ); } void CAVIMaterial::DestroyVideoStream( ) { if ( m_pGetFrame ) { AVIStreamGetFrameClose( m_pGetFrame ); m_pGetFrame = NULL; } if ( m_DIBSection != 0 ) { DeleteObject( m_DIBSection ); m_DIBSection = (HBITMAP)0; } if ( m_memdc != 0 ) { // Release the compatible DC DeleteDC( m_memdc ); m_memdc = (HDC)0; } if ( m_pAVIStream ) { AVIStreamRelease( m_pAVIStream ); m_pAVIStream = NULL; } } //----------------------------------------------------------------------------- // Inherited from ITextureRegenerator //----------------------------------------------------------------------------- void CAVIMaterial::RegenerateTextureBits( ITexture *pTexture, IVTFTexture *pVTFTexture, Rect_t *pRect ) { CPixelWriter pixelWriter; LPBITMAPINFOHEADER lpbih; unsigned char *pData; int i, y, nIncY; // Error condition if ( !m_pAVIStream || !m_pGetFrame || (pVTFTexture->FrameCount() > 1) || (pVTFTexture->FaceCount() > 1) || (pVTFTexture->MipCount() > 1) || (pVTFTexture->Depth() > 1) ) { goto AVIMaterialError; } lpbih = (LPBITMAPINFOHEADER)AVIStreamGetFrame( m_pGetFrame, m_nCurrentSample ); if ( !lpbih ) goto AVIMaterialError; // Set up the pixel writer to write into the VTF texture pixelWriter.SetPixelMemory( pVTFTexture->Format(), pVTFTexture->ImageData( ), pVTFTexture->RowSizeInBytes( 0 ) ); int nWidth = pVTFTexture->Width(); int nHeight = pVTFTexture->Height(); int nBihHeight = abs( lpbih->biHeight ); if ( lpbih->biWidth > nWidth || nBihHeight > nHeight ) goto AVIMaterialError; pData = (unsigned char *)lpbih + lpbih->biSize; if ( lpbih->biBitCount == 8 ) { // This is the palette pData += 256 * sizeof(RGBQUAD); } if ( (( lpbih->biBitCount == 16 ) || ( lpbih->biBitCount == 32 )) && ( lpbih->biCompression == BI_BITFIELDS ) ) { pData += 3 * sizeof(DWORD); // MASKS NOT IMPLEMENTED YET Assert( 0 ); } int nStride = ( lpbih->biWidth * lpbih->biBitCount / 8 + 3 ) & 0xFFFFFFFC; if ( lpbih->biHeight > 0 ) { y = nBihHeight - 1; nIncY = -1; } else { y = 0; nIncY = 1; } if ( lpbih->biBitCount == 24) { for ( i = 0; i < nBihHeight; ++i, pData += nStride, y += nIncY ) { pixelWriter.Seek( 0, y ); BGR888_t *pAVIPixel = (BGR888_t*)pData; for (int x = 0; x < lpbih->biWidth; ++x, ++pAVIPixel) { pixelWriter.WritePixel( pAVIPixel->r, pAVIPixel->g, pAVIPixel->b, 255 ); } } } else if (lpbih->biBitCount == 32) { for ( i = 0; i < nBihHeight; ++i, pData += nStride, y += nIncY ) { pixelWriter.Seek( 0, y ); BGRA8888_t *pAVIPixel = (BGRA8888_t*)pData; for (int x = 0; x < lpbih->biWidth; ++x, ++pAVIPixel) { pixelWriter.WritePixel( pAVIPixel->r, pAVIPixel->g, pAVIPixel->b, pAVIPixel->a ); } } } return; AVIMaterialError: int nBytes = pVTFTexture->ComputeTotalSize(); memset( pVTFTexture->ImageData(), 0xFF, nBytes ); return; } void CAVIMaterial::Release() { } //----------------------------------------------------------------------------- // // Implementation of IAvi // //----------------------------------------------------------------------------- class CAvi : public CTier3AppSystem< IAvi > { typedef CTier3AppSystem< IAvi > BaseClass; public: CAvi(); // Inherited from IAppSystem virtual bool Connect( CreateInterfaceFn factory ); virtual void *QueryInterface( const char *pInterfaceName ); virtual InitReturnVal_t Init(); virtual void Shutdown(); // Inherited from IAvi virtual void SetMainWindow( void* hWnd ); virtual AVIHandle_t StartAVI( const AVIParams_t& params ); virtual void FinishAVI( AVIHandle_t h ); virtual void AppendMovieSound( AVIHandle_t h, short *buf, size_t bufsize ); virtual void AppendMovieFrame( AVIHandle_t h, const BGR888_t *pRGBData ); virtual AVIMaterial_t CreateAVIMaterial( const char *pMaterialName, const char *pFileName, const char *pPathID ); virtual void DestroyAVIMaterial( AVIMaterial_t hMaterial ); virtual void SetTime( AVIMaterial_t hMaterial, float flTime ); virtual IMaterial* GetMaterial( AVIMaterial_t hMaterial ); virtual void GetTexCoordRange( AVIMaterial_t hMaterial, float *pMaxU, float *pMaxV ); virtual void GetFrameSize( AVIMaterial_t hMaterial, int *pWidth, int *pHeight ); virtual int GetFrameRate( AVIMaterial_t hMaterial ); virtual void SetFrame( AVIMaterial_t hMaterial, float flFrame ); virtual int GetFrameCount( AVIMaterial_t hMaterial ); private: HWND m_hWnd; CUtlLinkedList< CAviFile, AVIHandle_t > m_AVIFiles; // NOTE: Have to use pointers here since AVIMaterials inherit from ITextureRegenerator // The realloc screws up the pointers held to ITextureRegenerators in the material system. CUtlLinkedList< CAVIMaterial*, AVIMaterial_t > m_AVIMaterials; }; //----------------------------------------------------------------------------- // Singleton //----------------------------------------------------------------------------- static CAvi g_AVI; EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CAvi, IAvi, AVI_INTERFACE_VERSION, g_AVI ); //----------------------------------------------------------------------------- // Constructor/destructor //----------------------------------------------------------------------------- CAvi::CAvi() { m_hWnd = NULL; } //----------------------------------------------------------------------------- // Connect/disconnect //----------------------------------------------------------------------------- bool CAvi::Connect( CreateInterfaceFn factory ) { if ( !BaseClass::Connect( factory ) ) return false; if ( !( g_pFullFileSystem && materials ) ) { Msg( "Avi failed to connect to a required system\n" ); } return ( g_pFullFileSystem && materials ); } //----------------------------------------------------------------------------- // Query Interface //----------------------------------------------------------------------------- void *CAvi::QueryInterface( const char *pInterfaceName ) { if (!Q_strncmp( pInterfaceName, AVI_INTERFACE_VERSION, Q_strlen(AVI_INTERFACE_VERSION) + 1)) return (IAvi*)this; return NULL; } //----------------------------------------------------------------------------- // Init/shutdown //----------------------------------------------------------------------------- InitReturnVal_t CAvi::Init() { InitReturnVal_t nRetVal = BaseClass::Init(); if ( nRetVal != INIT_OK ) return nRetVal; AVIFileInit(); return INIT_OK; } void CAvi::Shutdown() { AVIFileExit(); BaseClass::Shutdown(); } //----------------------------------------------------------------------------- // Sets the main window //----------------------------------------------------------------------------- void CAvi::SetMainWindow( void* hWnd ) { m_hWnd = (HWND)hWnd; } //----------------------------------------------------------------------------- // Start, finish recording an AVI //----------------------------------------------------------------------------- AVIHandle_t CAvi::StartAVI( const AVIParams_t& params ) { AVIHandle_t h = m_AVIFiles.AddToTail(); m_AVIFiles[h].Init( params, m_hWnd ); return h; } void CAvi::FinishAVI( AVIHandle_t h ) { if ( h != AVIHANDLE_INVALID ) { m_AVIFiles[h].Shutdown(); m_AVIFiles.Remove( h ); } } //----------------------------------------------------------------------------- // Add sound buffer //----------------------------------------------------------------------------- void CAvi::AppendMovieSound( AVIHandle_t h, short *buf, size_t bufsize ) { if ( h != AVIHANDLE_INVALID ) { m_AVIFiles[h].AppendMovieSound( buf, bufsize ); } } //----------------------------------------------------------------------------- // Add movie frame //----------------------------------------------------------------------------- void CAvi::AppendMovieFrame( AVIHandle_t h, const BGR888_t *pRGBData ) { if ( h != AVIHANDLE_INVALID ) { m_AVIFiles[h].AppendMovieFrame( pRGBData ); } } //----------------------------------------------------------------------------- // Create/destroy an AVI material //----------------------------------------------------------------------------- AVIMaterial_t CAvi::CreateAVIMaterial( const char *pMaterialName, const char *pFileName, const char *pPathID ) { AVIMaterial_t h = m_AVIMaterials.AddToTail(); m_AVIMaterials[h] = new CAVIMaterial; m_AVIMaterials[h]->Init( pMaterialName, pFileName, pPathID ); return h; } void CAvi::DestroyAVIMaterial( AVIMaterial_t h ) { if ( h != AVIMATERIAL_INVALID ) { m_AVIMaterials[h]->Shutdown(); delete m_AVIMaterials[h]; m_AVIMaterials.Remove( h ); } } //----------------------------------------------------------------------------- // Sets the time for an AVI material //----------------------------------------------------------------------------- void CAvi::SetTime( AVIMaterial_t h, float flTime ) { if ( h != AVIMATERIAL_INVALID ) { m_AVIMaterials[h]->SetTime( flTime ); } } //----------------------------------------------------------------------------- // Gets the IMaterial associated with an AVI material //----------------------------------------------------------------------------- IMaterial* CAvi::GetMaterial( AVIMaterial_t h ) { if ( h != AVIMATERIAL_INVALID ) return m_AVIMaterials[h]->GetMaterial(); return NULL; } //----------------------------------------------------------------------------- // Returns the max texture coordinate of the AVI //----------------------------------------------------------------------------- void CAvi::GetTexCoordRange( AVIMaterial_t h, float *pMaxU, float *pMaxV ) { if ( h != AVIMATERIAL_INVALID ) { m_AVIMaterials[h]->GetTexCoordRange( pMaxU, pMaxV ); } else { *pMaxU = *pMaxV = 1.0f; } } //----------------------------------------------------------------------------- // Returns the frame size of the AVI (is a subrect of the material itself) //----------------------------------------------------------------------------- void CAvi::GetFrameSize( AVIMaterial_t h, int *pWidth, int *pHeight ) { if ( h != AVIMATERIAL_INVALID ) { m_AVIMaterials[h]->GetFrameSize( pWidth, pHeight ); } else { *pWidth = *pHeight = 1; } } //----------------------------------------------------------------------------- // Returns the frame rate of the AVI //----------------------------------------------------------------------------- int CAvi::GetFrameRate( AVIMaterial_t h ) { if ( h != AVIMATERIAL_INVALID ) return m_AVIMaterials[h]->GetFrameRate(); return 1; } int CAvi::GetFrameCount( AVIMaterial_t h ) { if ( h != AVIMATERIAL_INVALID ) return m_AVIMaterials[h]->GetFrameCount(); return 1; } //----------------------------------------------------------------------------- // Sets the frame for an AVI material (use instead of SetTime) //----------------------------------------------------------------------------- void CAvi::SetFrame( AVIMaterial_t h, float flFrame ) { if ( h != AVIMATERIAL_INVALID ) { m_AVIMaterials[h]->SetFrame( flFrame ); } }