mirror of
https://github.com/YGGverse/xash3d-fwgs.git
synced 2025-01-08 22:27:53 +00:00
5e0a0765ce
The `.editorconfig` file in this repo is configured to trim all trailing whitespace regardless of whether the line is modified. Trims all trailing whitespace in the repository to make the codebase easier to work with in editors that respect `.editorconfig`. `git blame` becomes less useful on these lines but it already isn't very useful. Commands: ``` find . -type f -name '*.h' -exec sed --in-place 's/[[:space:]]\+$//' {} \+ find . -type f -name '*.c' -exec sed --in-place 's/[[:space:]]\+$//' {} \+ ```
735 lines
22 KiB
C
735 lines
22 KiB
C
/*
|
|
avi_win.c - playing AVI files (based on original AVIKit code, Win32 version)
|
|
Copyright (C) 2010 Uncle Mike
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
*/
|
|
|
|
#include "build.h"
|
|
#if XASH_WIN32
|
|
#include "common.h"
|
|
#include "client.h"
|
|
#include <vfw.h> // video for windows
|
|
|
|
// msvfw32.dll exports
|
|
static HDRAWDIB (_stdcall *pDrawDibOpen)( void );
|
|
static BOOL (_stdcall *pDrawDibClose)( HDRAWDIB hdd );
|
|
static BOOL (_stdcall *pDrawDibDraw)( HDRAWDIB, HDC, int, int, int, int, LPBITMAPINFOHEADER, void*, int, int, int, int, uint );
|
|
|
|
static dllfunc_t msvfw_funcs[] =
|
|
{
|
|
{ "DrawDibOpen", (void **) &pDrawDibOpen },
|
|
{ "DrawDibDraw", (void **) &pDrawDibDraw },
|
|
{ "DrawDibClose", (void **) &pDrawDibClose },
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
dll_info_t msvfw_dll = { "msvfw32.dll", msvfw_funcs, false };
|
|
|
|
// msacm32.dll exports
|
|
static MMRESULT (_stdcall *pacmStreamOpen)( LPHACMSTREAM, HACMDRIVER, LPWAVEFORMATEX, LPWAVEFORMATEX, LPWAVEFILTER, DWORD, DWORD, DWORD );
|
|
static MMRESULT (_stdcall *pacmStreamPrepareHeader)( HACMSTREAM, LPACMSTREAMHEADER, DWORD );
|
|
static MMRESULT (_stdcall *pacmStreamUnprepareHeader)( HACMSTREAM, LPACMSTREAMHEADER, DWORD );
|
|
static MMRESULT (_stdcall *pacmStreamConvert)( HACMSTREAM, LPACMSTREAMHEADER, DWORD );
|
|
static MMRESULT (_stdcall *pacmStreamSize)( HACMSTREAM, DWORD, LPDWORD, DWORD );
|
|
static MMRESULT (_stdcall *pacmStreamClose)( HACMSTREAM, DWORD );
|
|
|
|
static dllfunc_t msacm_funcs[] =
|
|
{
|
|
{ "acmStreamOpen", (void **) &pacmStreamOpen },
|
|
{ "acmStreamPrepareHeader", (void **) &pacmStreamPrepareHeader },
|
|
{ "acmStreamUnprepareHeader", (void **) &pacmStreamUnprepareHeader },
|
|
{ "acmStreamConvert", (void **) &pacmStreamConvert },
|
|
{ "acmStreamSize", (void **) &pacmStreamSize },
|
|
{ "acmStreamClose", (void **) &pacmStreamClose },
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
dll_info_t msacm_dll = { "msacm32.dll", msacm_funcs, false };
|
|
|
|
// avifil32.dll exports
|
|
static int (_stdcall *pAVIStreamInfo)( PAVISTREAM pavi, AVISTREAMINFO *psi, LONG lSize );
|
|
static int (_stdcall *pAVIStreamRead)( PAVISTREAM pavi, LONG lStart, LONG lSamples, void *lpBuffer, LONG cbBuffer, LONG *plBytes, LONG *plSamples );
|
|
static PGETFRAME (_stdcall *pAVIStreamGetFrameOpen)( PAVISTREAM pavi, LPBITMAPINFOHEADER lpbiWanted );
|
|
static int (_stdcall *pAVIStreamTimeToSample)( PAVISTREAM pavi, LONG lTime );
|
|
static void* (_stdcall *pAVIStreamGetFrame)( PGETFRAME pg, LONG lPos );
|
|
static int (_stdcall *pAVIStreamGetFrameClose)( PGETFRAME pg );
|
|
static dword (_stdcall *pAVIStreamRelease)( PAVISTREAM pavi );
|
|
static int (_stdcall *pAVIFileOpen)( PAVIFILE *ppfile, LPCSTR szFile, UINT uMode, LPCLSID lpHandler );
|
|
static int (_stdcall *pAVIFileGetStream)( PAVIFILE pfile, PAVISTREAM *ppavi, DWORD fccType, LONG lParam );
|
|
static int (_stdcall *pAVIStreamReadFormat)( PAVISTREAM pavi, LONG lPos,LPVOID lpFormat, LONG *lpcbFormat );
|
|
static int (_stdcall *pAVIStreamStart)( PAVISTREAM pavi );
|
|
static dword (_stdcall *pAVIFileRelease)( PAVIFILE pfile );
|
|
static void (_stdcall *pAVIFileInit)( void );
|
|
static void (_stdcall *pAVIFileExit)( void );
|
|
|
|
static dllfunc_t avifile_funcs[] =
|
|
{
|
|
{ "AVIFileExit", (void **) &pAVIFileExit },
|
|
{ "AVIFileGetStream", (void **) &pAVIFileGetStream },
|
|
{ "AVIFileInit", (void **) &pAVIFileInit },
|
|
{ "AVIFileOpenA", (void **) &pAVIFileOpen },
|
|
{ "AVIFileRelease", (void **) &pAVIFileRelease },
|
|
{ "AVIStreamGetFrame", (void **) &pAVIStreamGetFrame },
|
|
{ "AVIStreamGetFrameClose", (void **) &pAVIStreamGetFrameClose },
|
|
{ "AVIStreamGetFrameOpen", (void **) &pAVIStreamGetFrameOpen },
|
|
{ "AVIStreamInfoA", (void **) &pAVIStreamInfo },
|
|
{ "AVIStreamRead", (void **) &pAVIStreamRead },
|
|
{ "AVIStreamReadFormat", (void **) &pAVIStreamReadFormat },
|
|
{ "AVIStreamRelease", (void **) &pAVIStreamRelease },
|
|
{ "AVIStreamStart", (void **) &pAVIStreamStart },
|
|
{ "AVIStreamTimeToSample", (void **) &pAVIStreamTimeToSample },
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
dll_info_t avifile_dll = { "avifil32.dll", avifile_funcs, false };
|
|
|
|
typedef struct movie_state_s
|
|
{
|
|
qboolean active;
|
|
qboolean quiet; // ignore error messages
|
|
|
|
PAVIFILE pfile; // avi file pointer
|
|
PAVISTREAM video_stream; // video stream pointer
|
|
PGETFRAME video_getframe; // pointer to getframe object for video stream
|
|
int video_frames; // total frames
|
|
int video_xres; // video stream resolution
|
|
int video_yres;
|
|
float video_fps; // video stream fps
|
|
|
|
PAVISTREAM audio_stream; // audio stream pointer
|
|
WAVEFORMAT *audio_header; // audio stream header
|
|
int audio_header_size; // WAVEFORMAT is returned for PCM data; WAVEFORMATEX for others
|
|
int audio_codec; // WAVE_FORMAT_PCM is oldstyle: anything else needs conversion
|
|
int audio_length; // in converted samples
|
|
int audio_bytes_per_sample; // guess.
|
|
|
|
// compressed audio specific data
|
|
dword cpa_blockalign; // block size to read
|
|
HACMSTREAM cpa_conversion_stream;
|
|
ACMSTREAMHEADER cpa_conversion_header;
|
|
byte *cpa_srcbuffer; // maintained buffer for raw data
|
|
byte *cpa_dstbuffer;
|
|
|
|
dword cpa_blocknum; // current block
|
|
dword cpa_blockpos; // read position in current block
|
|
dword cpa_blockoffset; // corresponding offset in bytes in the output stream
|
|
|
|
// for additional unpack Ms-RLE codecs etc
|
|
HDC hDC; // compatible DC
|
|
HDRAWDIB hDD; // DrawDib handler
|
|
HBITMAP hBitmap; // for DIB conversions
|
|
byte *pframe_data; // converted framedata
|
|
} movie_state_t;
|
|
|
|
static qboolean avi_initialized = false;
|
|
static movie_state_t avi[2];
|
|
|
|
// Converts a compressed audio stream into uncompressed PCM.
|
|
qboolean AVI_ACMConvertAudio( movie_state_t *Avi )
|
|
{
|
|
WAVEFORMATEX dest_header, *sh, *dh;
|
|
AVISTREAMINFO stream_info;
|
|
dword dest_length;
|
|
short bits;
|
|
|
|
// WMA codecs, both versions - they simply don't work.
|
|
if( Avi->audio_header->wFormatTag == 0x160 || Avi->audio_header->wFormatTag == 0x161 )
|
|
{
|
|
if( !Avi->quiet )
|
|
Con_Reportf( S_ERROR "ACM does not support this audio codec.\n" );
|
|
return false;
|
|
}
|
|
|
|
// get audio stream info to work with
|
|
pAVIStreamInfo( Avi->audio_stream, &stream_info, sizeof( stream_info ));
|
|
|
|
if( Avi->audio_header_size < sizeof( WAVEFORMATEX ))
|
|
{
|
|
if( !Avi->quiet )
|
|
Con_Reportf( S_ERROR "ACM failed to open conversion stream.\n" );
|
|
return false;
|
|
}
|
|
|
|
sh = (WAVEFORMATEX *)Avi->audio_header;
|
|
bits = 16; // predict state
|
|
|
|
// how much of this is actually required?
|
|
dest_header.wFormatTag = WAVE_FORMAT_PCM; // yay
|
|
dest_header.wBitsPerSample = bits; // 16bit
|
|
dest_header.nChannels = sh->nChannels;
|
|
dest_header.nSamplesPerSec = sh->nSamplesPerSec; // take straight from the source stream
|
|
dest_header.nAvgBytesPerSec = (bits >> 3) * sh->nChannels * sh->nSamplesPerSec;
|
|
dest_header.nBlockAlign = (bits >> 3) * sh->nChannels;
|
|
dest_header.cbSize = 0; // no more data.
|
|
|
|
dh = &dest_header;
|
|
|
|
// open the stream
|
|
if( pacmStreamOpen( &Avi->cpa_conversion_stream, NULL, sh, dh, NULL, 0, 0, 0 ) != MMSYSERR_NOERROR )
|
|
{
|
|
// try with 8 bit destination instead
|
|
bits = 8;
|
|
|
|
dest_header.wBitsPerSample = bits; // 8bit
|
|
dest_header.nAvgBytesPerSec = ( bits >> 3 ) * sh->nChannels * sh->nSamplesPerSec;
|
|
dest_header.nBlockAlign = ( bits >> 3 ) * sh->nChannels; // 1 sample at a time
|
|
|
|
if( pacmStreamOpen( &Avi->cpa_conversion_stream, NULL, sh, dh, NULL, 0, 0, 0 ) != MMSYSERR_NOERROR )
|
|
{
|
|
if( !Avi->quiet )
|
|
Con_Reportf( S_ERROR "ACM failed to open conversion stream.\n" );
|
|
return false;
|
|
}
|
|
}
|
|
|
|
Avi->cpa_blockalign = sh->nBlockAlign;
|
|
dest_length = 0;
|
|
|
|
// mp3 specific fix
|
|
if( sh->wFormatTag == 0x55 )
|
|
{
|
|
LPMPEGLAYER3WAVEFORMAT k;
|
|
|
|
k = (LPMPEGLAYER3WAVEFORMAT)sh;
|
|
Avi->cpa_blockalign = k->nBlockSize;
|
|
}
|
|
|
|
// get the size of the output buffer for streaming the compressed audio
|
|
if( pacmStreamSize( Avi->cpa_conversion_stream, Avi->cpa_blockalign, &dest_length, ACM_STREAMSIZEF_SOURCE ) != MMSYSERR_NOERROR )
|
|
{
|
|
if( !Avi->quiet )
|
|
Con_Reportf( S_ERROR "Couldn't get ACM conversion stream size.\n" );
|
|
pacmStreamClose( Avi->cpa_conversion_stream, 0 );
|
|
return false;
|
|
}
|
|
|
|
Avi->cpa_srcbuffer = (byte *)Mem_Malloc( cls.mempool, Avi->cpa_blockalign );
|
|
Avi->cpa_dstbuffer = (byte *)Mem_Malloc( cls.mempool, dest_length ); // maintained buffer for raw data
|
|
|
|
// prep the headers!
|
|
Avi->cpa_conversion_header.cbStruct = sizeof( ACMSTREAMHEADER );
|
|
Avi->cpa_conversion_header.fdwStatus = 0;
|
|
Avi->cpa_conversion_header.dwUser = 0; // no user data
|
|
Avi->cpa_conversion_header.pbSrc = Avi->cpa_srcbuffer; // source buffer
|
|
Avi->cpa_conversion_header.cbSrcLength = Avi->cpa_blockalign; // source buffer size
|
|
Avi->cpa_conversion_header.cbSrcLengthUsed = 0;
|
|
Avi->cpa_conversion_header.dwSrcUser = 0; // no user data
|
|
Avi->cpa_conversion_header.pbDst = Avi->cpa_dstbuffer; // dest buffer
|
|
Avi->cpa_conversion_header.cbDstLength = dest_length; // dest buffer size
|
|
Avi->cpa_conversion_header.cbDstLengthUsed = 0;
|
|
Avi->cpa_conversion_header.dwDstUser = 0; // no user data
|
|
|
|
if( pacmStreamPrepareHeader( Avi->cpa_conversion_stream, &Avi->cpa_conversion_header, 0 ) != MMSYSERR_NOERROR )
|
|
{
|
|
if( !Avi->quiet )
|
|
Con_Reportf( S_ERROR "couldn't prepare stream headers.\n" );
|
|
pacmStreamClose( Avi->cpa_conversion_stream, 0 );
|
|
return false;
|
|
}
|
|
|
|
Avi->cpa_blocknum = 0; // start at 0.
|
|
Avi->cpa_blockpos = 0;
|
|
Avi->cpa_blockoffset = 0;
|
|
|
|
pAVIStreamRead( Avi->audio_stream, Avi->cpa_blocknum * Avi->cpa_blockalign, Avi->cpa_blockalign, Avi->cpa_srcbuffer, Avi->cpa_blockalign, NULL, NULL );
|
|
pacmStreamConvert( Avi->cpa_conversion_stream, &Avi->cpa_conversion_header, ACM_STREAMCONVERTF_BLOCKALIGN|ACM_STREAMCONVERTF_START );
|
|
|
|
// convert first chunk twice. it often fails the first time. BLACK MAGIC.
|
|
pAVIStreamRead( Avi->audio_stream, Avi->cpa_blocknum * Avi->cpa_blockalign, Avi->cpa_blockalign, Avi->cpa_srcbuffer, Avi->cpa_blockalign, NULL, NULL );
|
|
pacmStreamConvert( Avi->cpa_conversion_stream, &Avi->cpa_conversion_header, ACM_STREAMCONVERTF_BLOCKALIGN );
|
|
|
|
Avi->audio_bytes_per_sample = (bits >> 3 ) * Avi->audio_header->nChannels;
|
|
|
|
return true;
|
|
}
|
|
|
|
qboolean AVI_GetVideoInfo( movie_state_t *Avi, int *xres, int *yres, float *duration )
|
|
{
|
|
if( !Avi->active )
|
|
return false;
|
|
|
|
if( xres != NULL )
|
|
*xres = Avi->video_xres;
|
|
|
|
if( yres != NULL )
|
|
*yres = Avi->video_yres;
|
|
|
|
if( duration != NULL )
|
|
*duration = (float)Avi->video_frames / Avi->video_fps;
|
|
|
|
return true;
|
|
}
|
|
|
|
// returns a unique frame identifier
|
|
int AVI_GetVideoFrameNumber( movie_state_t *Avi, float time )
|
|
{
|
|
if( !Avi->active )
|
|
return 0;
|
|
|
|
return (time * Avi->video_fps);
|
|
}
|
|
|
|
int AVI_GetVideoFrameCount( movie_state_t *Avi )
|
|
{
|
|
if( !Avi->active )
|
|
return 0;
|
|
|
|
return Avi->video_frames;
|
|
}
|
|
|
|
int AVI_TimeToSoundPosition( movie_state_t *Avi, int time )
|
|
{
|
|
if( !Avi->active || !Avi->audio_stream )
|
|
return 0;
|
|
|
|
// UNDONE: what about compressed audio?
|
|
return pAVIStreamTimeToSample( Avi->audio_stream, time ) * Avi->audio_bytes_per_sample;
|
|
}
|
|
|
|
// gets the raw frame data
|
|
byte *AVI_GetVideoFrame( movie_state_t *Avi, int frame )
|
|
{
|
|
LPBITMAPINFOHEADER frame_info;
|
|
byte *frame_raw;
|
|
|
|
if( !Avi->active ) return NULL;
|
|
|
|
if( frame >= Avi->video_frames )
|
|
frame = Avi->video_frames - 1;
|
|
|
|
frame_info = (LPBITMAPINFOHEADER)pAVIStreamGetFrame( Avi->video_getframe, frame );
|
|
frame_raw = (byte *)frame_info + frame_info->biSize + frame_info->biClrUsed * sizeof( RGBQUAD );
|
|
pDrawDibDraw( Avi->hDD, Avi->hDC, 0, 0, Avi->video_xres, Avi->video_yres, frame_info, frame_raw, 0, 0, Avi->video_xres, Avi->video_yres, 0 );
|
|
|
|
return Avi->pframe_data;
|
|
}
|
|
|
|
qboolean AVI_GetAudioInfo( movie_state_t *Avi, wavdata_t *snd_info )
|
|
{
|
|
if( !Avi->active || Avi->audio_stream == NULL || snd_info == NULL )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
snd_info->rate = Avi->audio_header->nSamplesPerSec;
|
|
snd_info->channels = Avi->audio_header->nChannels;
|
|
|
|
if( Avi->audio_codec == WAVE_FORMAT_PCM ) // uncompressed audio!
|
|
snd_info->width = ( Avi->audio_bytes_per_sample > Avi->audio_header->nChannels ) ? 2 : 1;
|
|
else snd_info->width = 2; // assume compressed audio is always 16 bit
|
|
|
|
snd_info->size = snd_info->rate * snd_info->width * snd_info->channels;
|
|
snd_info->loopStart = 0; // using loopStart as streampos
|
|
|
|
return true;
|
|
}
|
|
|
|
// sync the current audio read to a specific offset
|
|
qboolean AVI_SeekPosition( movie_state_t *Avi, dword offset )
|
|
{
|
|
int breaker;
|
|
|
|
if( offset < Avi->cpa_blockoffset ) // well, shit. we can't seek backwards... restart
|
|
{
|
|
if( Avi->cpa_blockoffset - offset < 500000 )
|
|
return false; // don't bother if it's gonna catch up soon
|
|
|
|
Avi->cpa_blocknum = 0; // start at 0, eh.
|
|
Avi->cpa_blockpos = 0;
|
|
Avi->cpa_blockoffset = 0;
|
|
|
|
pAVIStreamRead( Avi->audio_stream, Avi->cpa_blocknum * Avi->cpa_blockalign, Avi->cpa_blockalign, Avi->cpa_srcbuffer, Avi->cpa_blockalign, NULL, NULL );
|
|
pacmStreamConvert( Avi->cpa_conversion_stream, &Avi->cpa_conversion_header, ACM_STREAMCONVERTF_BLOCKALIGN|ACM_STREAMCONVERTF_START );
|
|
|
|
// convert first chunk twice. it often fails the first time. BLACK MAGIC.
|
|
pAVIStreamRead( Avi->audio_stream, Avi->cpa_blocknum * Avi->cpa_blockalign, Avi->cpa_blockalign, Avi->cpa_srcbuffer, Avi->cpa_blockalign, NULL, NULL );
|
|
pacmStreamConvert( Avi->cpa_conversion_stream, &Avi->cpa_conversion_header, ACM_STREAMCONVERTF_BLOCKALIGN );
|
|
}
|
|
|
|
// now then: seek forwards to the required block
|
|
breaker = 30; // maximum zero blocks: anti-freeze protection
|
|
|
|
while( Avi->cpa_blockoffset + Avi->cpa_conversion_header.cbDstLengthUsed < offset )
|
|
{
|
|
Avi->cpa_blocknum++;
|
|
Avi->cpa_blockoffset += Avi->cpa_conversion_header.cbDstLengthUsed;
|
|
|
|
pAVIStreamRead( Avi->audio_stream, Avi->cpa_blocknum * Avi->cpa_blockalign, Avi->cpa_blockalign, Avi->cpa_srcbuffer, Avi->cpa_blockalign, NULL, NULL );
|
|
pacmStreamConvert( Avi->cpa_conversion_stream, &Avi->cpa_conversion_header, ACM_STREAMCONVERTF_BLOCKALIGN );
|
|
|
|
if( Avi->cpa_conversion_header.cbDstLengthUsed == 0 )
|
|
breaker--;
|
|
else breaker = 30;
|
|
|
|
if( breaker <= 0 )
|
|
return false;
|
|
|
|
Avi->cpa_blockpos = 0;
|
|
}
|
|
|
|
// seek to the right position inside the block
|
|
Avi->cpa_blockpos = offset - Avi->cpa_blockoffset;
|
|
|
|
return true;
|
|
}
|
|
|
|
// get a chunk of audio from the stream (in bytes)
|
|
int AVI_GetAudioChunk( movie_state_t *Avi, char *audiodata, int offset, int length )
|
|
{
|
|
int result = 0;
|
|
int i;
|
|
|
|
// zero data past the end of the file
|
|
if( offset + length > Avi->audio_length )
|
|
{
|
|
if( offset <= Avi->audio_length )
|
|
{
|
|
int remaining_length = Avi->audio_length - offset;
|
|
|
|
AVI_GetAudioChunk( Avi, audiodata, offset, remaining_length );
|
|
|
|
for( i = remaining_length; i < length; i++ )
|
|
audiodata[i] = 0;
|
|
}
|
|
else
|
|
{
|
|
// we out of soundtrack, just zeroing buffer
|
|
for( i = 0; i < length; i++ )
|
|
audiodata[i] = 0;
|
|
|
|
// return length;
|
|
}
|
|
}
|
|
|
|
// uncompressed audio!
|
|
if( Avi->audio_codec == WAVE_FORMAT_PCM )
|
|
{
|
|
// very simple - read straight out
|
|
pAVIStreamRead( Avi->audio_stream, offset / Avi->audio_bytes_per_sample, length / Avi->audio_bytes_per_sample, audiodata, length, &result, NULL );
|
|
return result;
|
|
}
|
|
else
|
|
{
|
|
// compressed audio!
|
|
result = 0;
|
|
|
|
// seek to correct chunk and all that stuff
|
|
if( !AVI_SeekPosition( Avi, offset ))
|
|
return 0; // don't continue if we're waiting for the play pointer to catch up
|
|
|
|
while( length > 0 )
|
|
{
|
|
int blockread = Avi->cpa_conversion_header.cbDstLengthUsed - Avi->cpa_blockpos;
|
|
|
|
if( blockread <= 0 ) // read next
|
|
{
|
|
Avi->cpa_blocknum++;
|
|
Avi->cpa_blockoffset += Avi->cpa_conversion_header.cbDstLengthUsed;
|
|
|
|
pAVIStreamRead( Avi->audio_stream, Avi->cpa_blocknum * Avi->cpa_blockalign, Avi->cpa_blockalign, Avi->cpa_srcbuffer, Avi->cpa_blockalign, NULL, NULL );
|
|
pacmStreamConvert( Avi->cpa_conversion_stream, &Avi->cpa_conversion_header, ACM_STREAMCONVERTF_BLOCKALIGN );
|
|
|
|
Avi->cpa_blockpos = 0;
|
|
continue;
|
|
}
|
|
|
|
if( blockread > length )
|
|
blockread = length;
|
|
|
|
// copy the data
|
|
memcpy( audiodata + result, (void *)( Avi->cpa_dstbuffer + Avi->cpa_blockpos ), blockread );
|
|
|
|
Avi->cpa_blockpos += blockread;
|
|
result += blockread;
|
|
length -= blockread;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
}
|
|
|
|
void AVI_CloseVideo( movie_state_t *Avi )
|
|
{
|
|
if( Avi->active )
|
|
{
|
|
pAVIStreamGetFrameClose( Avi->video_getframe );
|
|
|
|
if( Avi->audio_stream != NULL )
|
|
{
|
|
pAVIStreamRelease( Avi->audio_stream );
|
|
Mem_Free( Avi->audio_header );
|
|
|
|
if( Avi->audio_codec != WAVE_FORMAT_PCM )
|
|
{
|
|
pacmStreamUnprepareHeader( Avi->cpa_conversion_stream, &Avi->cpa_conversion_header, 0 );
|
|
pacmStreamClose( Avi->cpa_conversion_stream, 0 );
|
|
Mem_Free( Avi->cpa_srcbuffer );
|
|
Mem_Free( Avi->cpa_dstbuffer );
|
|
}
|
|
}
|
|
|
|
pAVIStreamRelease( Avi->video_stream );
|
|
|
|
DeleteObject( Avi->hBitmap );
|
|
pDrawDibClose( Avi->hDD );
|
|
DeleteDC( Avi->hDC );
|
|
}
|
|
|
|
memset( Avi, 0, sizeof( movie_state_t ));
|
|
}
|
|
|
|
void AVI_OpenVideo( movie_state_t *Avi, const char *filename, qboolean load_audio, int quiet )
|
|
{
|
|
BITMAPINFOHEADER bmih;
|
|
AVISTREAMINFO stream_info;
|
|
int opened_streams = 0;
|
|
LONG hr;
|
|
|
|
// default state: non-working.
|
|
Avi->active = false;
|
|
Avi->quiet = quiet;
|
|
|
|
// can't load Video For Windows :-(
|
|
if( !avi_initialized ) return;
|
|
|
|
// load the AVI
|
|
hr = pAVIFileOpen( &Avi->pfile, filename, OF_SHARE_DENY_WRITE, 0L );
|
|
|
|
if( hr != 0 ) // error opening AVI:
|
|
{
|
|
switch( hr )
|
|
{
|
|
case AVIERR_BADFORMAT:
|
|
if( !Avi->quiet )
|
|
Con_DPrintf( S_ERROR "corrupt file or unknown format.\n" );
|
|
break;
|
|
case AVIERR_MEMORY:
|
|
if( !Avi->quiet )
|
|
Con_DPrintf( S_ERROR "insufficient memory to open file.\n" );
|
|
break;
|
|
case AVIERR_FILEREAD:
|
|
if( !Avi->quiet )
|
|
Con_DPrintf( S_ERROR "disk error reading file.\n" );
|
|
break;
|
|
case AVIERR_FILEOPEN:
|
|
if( !Avi->quiet )
|
|
Con_DPrintf( S_ERROR "disk error opening file.\n" );
|
|
break;
|
|
case REGDB_E_CLASSNOTREG:
|
|
default:
|
|
if( !Avi->quiet )
|
|
Con_DPrintf( S_ERROR "no handler found (or file not found).\n" );
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
|
|
Avi->video_stream = Avi->audio_stream = NULL;
|
|
|
|
// open the streams until a stream is not available.
|
|
while( 1 )
|
|
{
|
|
PAVISTREAM stream = NULL;
|
|
|
|
if( pAVIFileGetStream( Avi->pfile, &stream, 0L, opened_streams++ ) != AVIERR_OK )
|
|
break;
|
|
|
|
if( stream == NULL )
|
|
break;
|
|
|
|
pAVIStreamInfo( stream, &stream_info, sizeof( stream_info ));
|
|
|
|
if( stream_info.fccType == streamtypeVIDEO && Avi->video_stream == NULL )
|
|
{
|
|
Avi->video_stream = stream;
|
|
Avi->video_frames = stream_info.dwLength;
|
|
Avi->video_xres = stream_info.rcFrame.right - stream_info.rcFrame.left;
|
|
Avi->video_yres = stream_info.rcFrame.bottom - stream_info.rcFrame.top;
|
|
Avi->video_fps = (float)stream_info.dwRate / (float)stream_info.dwScale;
|
|
}
|
|
else if( stream_info.fccType == streamtypeAUDIO && Avi->audio_stream == NULL && load_audio )
|
|
{
|
|
int size;
|
|
|
|
Avi->audio_stream = stream;
|
|
|
|
// read the audio header
|
|
pAVIStreamReadFormat( Avi->audio_stream, pAVIStreamStart( Avi->audio_stream ), 0, &size );
|
|
|
|
Avi->audio_header = (WAVEFORMAT *)Mem_Malloc( cls.mempool, size );
|
|
pAVIStreamReadFormat( Avi->audio_stream, pAVIStreamStart( Avi->audio_stream ), Avi->audio_header, &size );
|
|
Avi->audio_header_size = size;
|
|
Avi->audio_codec = Avi->audio_header->wFormatTag;
|
|
|
|
// length of converted audio in samples
|
|
Avi->audio_length = (int)((float)stream_info.dwLength / Avi->audio_header->nAvgBytesPerSec );
|
|
Avi->audio_length *= Avi->audio_header->nSamplesPerSec;
|
|
|
|
if( Avi->audio_codec != WAVE_FORMAT_PCM )
|
|
{
|
|
if( !AVI_ACMConvertAudio( Avi ))
|
|
{
|
|
Mem_Free( Avi->audio_header );
|
|
Avi->audio_stream = NULL;
|
|
continue;
|
|
}
|
|
}
|
|
else Avi->audio_bytes_per_sample = Avi->audio_header->nBlockAlign;
|
|
Avi->audio_length *= Avi->audio_bytes_per_sample;
|
|
}
|
|
else
|
|
{
|
|
pAVIStreamRelease( stream );
|
|
}
|
|
}
|
|
|
|
// display error message-stream not found.
|
|
if( Avi->video_stream == NULL )
|
|
{
|
|
if( Avi->pfile ) // if file is open, close it
|
|
pAVIFileRelease( Avi->pfile );
|
|
if( !Avi->quiet )
|
|
Con_DPrintf( S_ERROR "couldn't find a valid video stream.\n" );
|
|
return;
|
|
}
|
|
|
|
pAVIFileRelease( Avi->pfile ); // release the file
|
|
Avi->video_getframe = pAVIStreamGetFrameOpen( Avi->video_stream, NULL ); // open the frame getter
|
|
|
|
if( Avi->video_getframe == NULL )
|
|
{
|
|
if( !Avi->quiet )
|
|
Con_DPrintf( S_ERROR "error attempting to read video frames.\n" );
|
|
return; // couldn't open frame getter.
|
|
}
|
|
|
|
bmih.biSize = sizeof( BITMAPINFOHEADER );
|
|
bmih.biPlanes = 1;
|
|
bmih.biBitCount = 32;
|
|
bmih.biCompression = BI_RGB;
|
|
bmih.biWidth = Avi->video_xres;
|
|
bmih.biHeight = -Avi->video_yres; // invert height to flip image upside down
|
|
|
|
Avi->hDC = CreateCompatibleDC( 0 );
|
|
Avi->hDD = pDrawDibOpen();
|
|
Avi->hBitmap = CreateDIBSection( Avi->hDC, (BITMAPINFO*)(&bmih), DIB_RGB_COLORS, (void**)(&Avi->pframe_data), NULL, 0 );
|
|
SelectObject( Avi->hDC, Avi->hBitmap );
|
|
|
|
Avi->active = true; // done
|
|
}
|
|
|
|
qboolean AVI_IsActive( movie_state_t *Avi )
|
|
{
|
|
if( Avi != NULL )
|
|
return Avi->active;
|
|
return false;
|
|
}
|
|
|
|
movie_state_t *AVI_GetState( int num )
|
|
{
|
|
return &avi[num];
|
|
}
|
|
|
|
/*
|
|
=============
|
|
AVIKit user interface
|
|
|
|
=============
|
|
*/
|
|
movie_state_t *AVI_LoadVideo( const char *filename, qboolean load_audio )
|
|
{
|
|
movie_state_t *Avi;
|
|
string path;
|
|
const char *fullpath;
|
|
|
|
// fast reject
|
|
if( !avi_initialized )
|
|
return NULL;
|
|
|
|
// open cinematic
|
|
Q_snprintf( path, sizeof( path ), "media/%s", filename );
|
|
COM_DefaultExtension( path, ".avi" );
|
|
fullpath = FS_GetDiskPath( path, false );
|
|
|
|
if( FS_FileExists( path, false ) && !fullpath )
|
|
{
|
|
Con_Printf( "Couldn't load %s from packfile. Please extract it\n", path );
|
|
return NULL;
|
|
}
|
|
|
|
Avi = Mem_Malloc( cls.mempool, sizeof( movie_state_t ));
|
|
AVI_OpenVideo( Avi, fullpath, load_audio, false );
|
|
|
|
if( !AVI_IsActive( Avi ))
|
|
{
|
|
AVI_FreeVideo( Avi ); // something bad happens
|
|
return NULL;
|
|
}
|
|
|
|
// all done
|
|
return Avi;
|
|
}
|
|
|
|
void AVI_FreeVideo( movie_state_t *state )
|
|
{
|
|
if( !state ) return;
|
|
|
|
if( Mem_IsAllocatedExt( cls.mempool, state ))
|
|
{
|
|
AVI_CloseVideo( state );
|
|
Mem_Free( state );
|
|
}
|
|
}
|
|
|
|
qboolean AVI_Initailize( void )
|
|
{
|
|
if( Sys_CheckParm( "-noavi" ))
|
|
{
|
|
Con_Printf( "AVI: Disabled\n" );
|
|
return false;
|
|
}
|
|
|
|
if( !Sys_LoadLibrary( &avifile_dll ))
|
|
return false;
|
|
|
|
if( !Sys_LoadLibrary( &msvfw_dll ))
|
|
{
|
|
Sys_FreeLibrary( &avifile_dll );
|
|
return false;
|
|
}
|
|
|
|
if( !Sys_LoadLibrary( &msacm_dll ))
|
|
{
|
|
Sys_FreeLibrary( &avifile_dll );
|
|
Sys_FreeLibrary( &msvfw_dll );
|
|
return false;
|
|
}
|
|
|
|
avi_initialized = true;
|
|
pAVIFileInit();
|
|
|
|
return true;
|
|
}
|
|
|
|
void AVI_Shutdown( void )
|
|
{
|
|
if( !avi_initialized ) return;
|
|
|
|
pAVIFileExit();
|
|
|
|
Sys_FreeLibrary( &avifile_dll );
|
|
Sys_FreeLibrary( &msvfw_dll );
|
|
Sys_FreeLibrary( &msacm_dll );
|
|
avi_initialized = false;
|
|
}
|
|
#endif // _WIN32
|