You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1184 lines
32 KiB
1184 lines
32 KiB
//====== 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 <windows.h>
|
|
#include <vfw.h>
|
|
|
|
#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 );
|
|
}
|
|
}
|