Skip to content

Commit

Permalink
* AVI video output
Browse files Browse the repository at this point in the history
  - Uses motion jpeg codec by default
  - Use cl_avidemo to set a framerate
  - \video [filename] to start capture
  - \stopvideo to stop capture
  - Audio capture is a bit ropey
  • Loading branch information
timangus committed Jan 4, 2006
1 parent 92ad3e9 commit a21eb2b
Show file tree
Hide file tree
Showing 15 changed files with 910 additions and 11 deletions.
619 changes: 619 additions & 0 deletions code/client/cl_avi.c

Large diffs are not rendered by default.

101 changes: 93 additions & 8 deletions code/client/cl_main.c
Expand Up @@ -44,6 +44,7 @@ cvar_t *cl_shownet;
cvar_t *cl_showSend;
cvar_t *cl_timedemo;
cvar_t *cl_avidemo;
cvar_t *cl_aviMotionJpeg;
cvar_t *cl_forceavidemo;

cvar_t *cl_freelook;
Expand Down Expand Up @@ -773,6 +774,11 @@ void CL_Disconnect( qboolean showMainMenu ) {

// not connected to a pure server anymore
cl_connectedToPureServer = qfalse;

// Stop recording any video
if( CL_VideoRecording( ) ) {
CL_CloseAVI( );
}
}


Expand Down Expand Up @@ -1189,6 +1195,11 @@ doesn't know what graphics to reload
*/
void CL_Vid_Restart_f( void ) {

// Settings may have changed so stop recording now
if( CL_VideoRecording( ) ) {
CL_CloseAVI( );
}

// don't let them loop during the restart
S_StopAllSounds();
// shutdown the UI
Expand Down Expand Up @@ -2014,15 +2025,16 @@ void CL_Frame ( int msec ) {
}

// if recording an avi, lock to a fixed fps
if ( cl_avidemo->integer && msec) {
if ( CL_VideoRecording( ) && cl_avidemo->integer && msec) {
// save the current screen
if ( cls.state == CA_ACTIVE || cl_forceavidemo->integer) {
Cbuf_ExecuteText( EXEC_NOW, "screenshot silent\n" );
}
// fixed time for next frame'
msec = (1000 / cl_avidemo->integer) * com_timescale->value;
if (msec == 0) {
msec = 1;
CL_TakeVideoFrame( );

// fixed time for next frame'
msec = (int)ceil( (1000.0f / cl_avidemo->value) * com_timescale->value );
if (msec == 0) {
msec = 1;
}
}
}

Expand Down Expand Up @@ -2222,6 +2234,8 @@ void CL_InitRef( void ) {
ri.CIN_UploadCinematic = CIN_UploadCinematic;
ri.CIN_PlayCinematic = CIN_PlayCinematic;
ri.CIN_RunCinematic = CIN_RunCinematic;

ri.CL_WriteAVIVideoFrame = CL_WriteAVIVideoFrame;

ret = GetRefAPI( REF_API_VERSION, &ri );

Expand Down Expand Up @@ -2259,6 +2273,72 @@ void CL_SetModel_f( void ) {
}
}


//===========================================================================================


/*
===============
CL_Video_f
video
video [filename]
===============
*/
void CL_Video_f( void )
{
char filename[ MAX_OSPATH ];
int i, last;

if( Cmd_Argc( ) == 2 )
{
// explicit filename
Com_sprintf( filename, MAX_OSPATH, "videos/%s.avi", Cmd_Argv( 1 ) );
}
else
{
// scan for a free filename
for( i = 0; i <= 9999; i++ )
{
int a, b, c, d;

last = i;

a = last / 1000;
last -= a * 1000;
b = last / 100;
last -= b * 100;
c = last / 10;
last -= c * 10;
d = last;

Com_sprintf( filename, MAX_OSPATH, "videos/video%d%d%d%d.avi",
a, b, c, d );

if( !FS_FileExists( filename ) )
break; // file doesn't exist
}

if( i > 9999 )
{
Com_Printf( S_COLOR_RED "ERROR: no free file names to create video\n" );
return;
}
}

CL_OpenAVIForWriting( filename );
}

/*
===============
CL_StopVideo_f
===============
*/
void CL_StopVideo_f( void )
{
CL_CloseAVI( );
}

/*
====================
CL_Init
Expand Down Expand Up @@ -2294,7 +2374,8 @@ void CL_Init( void ) {
cl_activeAction = Cvar_Get( "activeAction", "", CVAR_TEMP );

cl_timedemo = Cvar_Get ("timedemo", "0", 0);
cl_avidemo = Cvar_Get ("cl_avidemo", "0", 0);
cl_avidemo = Cvar_Get ("cl_avidemo", "25", CVAR_ARCHIVE);
cl_aviMotionJpeg = Cvar_Get ("cl_aviMotionJpeg", "1", CVAR_ARCHIVE);
cl_forceavidemo = Cvar_Get ("cl_forceavidemo", "0", 0);

rconAddress = Cvar_Get ("rconAddress", "", 0);
Expand Down Expand Up @@ -2395,6 +2476,8 @@ void CL_Init( void ) {
Cmd_AddCommand ("fs_openedList", CL_OpenedPK3List_f );
Cmd_AddCommand ("fs_referencedList", CL_ReferencedPK3List_f );
Cmd_AddCommand ("model", CL_SetModel_f );
Cmd_AddCommand ("video", CL_Video_f );
Cmd_AddCommand ("stopvideo", CL_StopVideo_f );
CL_InitRef();

SCR_Init ();
Expand Down Expand Up @@ -2450,6 +2533,8 @@ void CL_Shutdown( void ) {
Cmd_RemoveCommand ("serverstatus");
Cmd_RemoveCommand ("showip");
Cmd_RemoveCommand ("model");
Cmd_RemoveCommand ("video");
Cmd_RemoveCommand ("stopvideo");

Cvar_Set( "cl_running", "0" );

Expand Down
12 changes: 12 additions & 0 deletions code/client/client.h
Expand Up @@ -343,6 +343,8 @@ extern cvar_t *m_side;
extern cvar_t *m_filter;

extern cvar_t *cl_timedemo;
extern cvar_t *cl_avidemo;
extern cvar_t *cl_aviMotionJpeg;

extern cvar_t *cl_activeAction;

Expand Down Expand Up @@ -518,3 +520,13 @@ void LAN_SaveServersToCache( void );
void CL_Netchan_Transmit( netchan_t *chan, msg_t* msg); //int length, const byte *data );
void CL_Netchan_TransmitNextFragment( netchan_t *chan );
qboolean CL_Netchan_Process( netchan_t *chan, msg_t *msg );

//
// cl_avi.c
//
qboolean CL_OpenAVIForWriting( const char *filename );
void CL_TakeVideoFrame( void );
void CL_WriteAVIVideoFrame( const byte *imageBuffer, int size );
void CL_WriteAVIAudioFrame( const byte *pcmBuffer, int size );
qboolean CL_CloseAVI( void );
qboolean CL_VideoRecording( void );
6 changes: 6 additions & 0 deletions code/client/snd_dma.c
Expand Up @@ -1139,6 +1139,12 @@ void S_GetSoundtime(void)

fullsamples = dma.samples / dma.channels;

if( CL_VideoRecording( ) )
{
s_soundtime += (int)ceil( dma.speed / cl_avidemo->value );
return;
}

// it is possible to miscount buffers if it has wrapped twice between
// calls to S_Update. Oh well.
samplepos = SNDDMA_GetDMAPos();
Expand Down
4 changes: 4 additions & 0 deletions code/client/snd_main.c
Expand Up @@ -29,6 +29,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
cvar_t *s_volume;
cvar_t *s_musicVolume;
cvar_t *s_doppler;
cvar_t *s_backend;

static soundInterface_t si;

Expand Down Expand Up @@ -370,6 +371,7 @@ void S_Init( void )
s_volume = Cvar_Get( "s_volume", "0.8", CVAR_ARCHIVE );
s_musicVolume = Cvar_Get( "s_musicvolume", "0.25", CVAR_ARCHIVE );
s_doppler = Cvar_Get( "s_doppler", "1", CVAR_ARCHIVE );
s_backend = Cvar_Get( "s_backend", "", CVAR_ROM );

cv = Cvar_Get( "s_initsound", "1", 0 );
if( !cv->integer ) {
Expand All @@ -388,10 +390,12 @@ void S_Init( void )
if( cv->integer ) {
//OpenAL
started = S_AL_Init( &si );
Cvar_Set( "s_backend", "OpenAL" );
}

if( !started ) {
started = S_Base_Init( &si );
Cvar_Set( "s_backend", "base" );
}

if( started ) {
Expand Down
4 changes: 4 additions & 0 deletions code/client/snd_mix.c
Expand Up @@ -21,6 +21,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
// snd_mix.c -- portable code to mix sounds for snd_dma.c

#include "client.h"
#include "snd_local.h"
#if idppc_altivec && !defined(MACOS_X)
#include <altivec.h>
Expand Down Expand Up @@ -137,6 +138,9 @@ void S_TransferStereo16 (unsigned long *pbuf, int endtime)

snd_p += snd_linear_count;
ls_paintedtime += (snd_linear_count>>1);

if( CL_VideoRecording( ) )
CL_WriteAVIAudioFrame( (byte *)snd_out, snd_linear_count << 1 );
}
}

Expand Down
13 changes: 12 additions & 1 deletion code/qcommon/files.c
Expand Up @@ -565,10 +565,21 @@ FS_Remove
===========
*/
static void FS_Remove( const char *osPath ) {
void FS_Remove( const char *osPath ) {
remove( osPath );
}

/*
===========
FS_HomeRemove
===========
*/
void FS_HomeRemove( const char *homePath ) {
remove( FS_BuildOSPath( fs_homepath->string,
fs_gamedir, homePath ) );
}

/*
================
FS_FileExists
Expand Down
4 changes: 3 additions & 1 deletion code/qcommon/qcommon.h
Expand Up @@ -654,6 +654,9 @@ qboolean FS_ComparePaks( char *neededpaks, int len, qboolean dlstring );

void FS_Rename( const char *from, const char *to );

void FS_Remove( const char *osPath );
void FS_HomeRemove( const char *homePath );

/*
==============================================================
Expand Down Expand Up @@ -899,7 +902,6 @@ void S_ClearSoundBuffer( void );

void SCR_DebugGraph (float value, int color); // FIXME: move logging to common?


//
// server interface
//
Expand Down
3 changes: 3 additions & 0 deletions code/renderer/tr_backend.c
Expand Up @@ -1081,6 +1081,9 @@ void RB_ExecuteRenderCommands( const void *data ) {
case RC_SCREENSHOT:
data = RB_TakeScreenshotCmd( data );
break;
case RC_VIDEOFRAME:
data = RB_TakeVideoFrameCmd( data );
break;

case RC_END_OF_LIST:
default:
Expand Down
27 changes: 27 additions & 0 deletions code/renderer/tr_cmds.c
Expand Up @@ -445,3 +445,30 @@ void RE_EndFrame( int *frontEndMsec, int *backEndMsec ) {
backEnd.pc.msec = 0;
}

/*
=============
RE_TakeVideoFrame
=============
*/
void RE_TakeVideoFrame( int width, int height,
byte *captureBuffer, byte *encodeBuffer, qboolean motionJpeg )
{
videoFrameCommand_t *cmd;

if( !tr.registered ) {
return;
}

cmd = R_GetCommandBuffer( sizeof( *cmd ) );
if( !cmd ) {
return;
}

cmd->commandId = RC_VIDEOFRAME;

cmd->width = width;
cmd->height = height;
cmd->captureBuffer = captureBuffer;
cmd->encodeBuffer = encodeBuffer;
cmd->motionJpeg = motionJpeg;
}
58 changes: 58 additions & 0 deletions code/renderer/tr_image.c
Expand Up @@ -1852,6 +1852,64 @@ void SaveJPG(char * filename, int quality, int image_width, int image_height, un
/* And we're done! */
}

/*
=================
SaveJPGToBuffer
=================
*/
int SaveJPGToBuffer( byte *buffer, int quality,
int image_width, int image_height,
byte *image_buffer )
{
struct jpeg_compress_struct cinfo;
struct jpeg_error_mgr jerr;
JSAMPROW row_pointer[1]; /* pointer to JSAMPLE row[s] */
int row_stride; /* physical row width in image buffer */

/* Step 1: allocate and initialize JPEG compression object */
cinfo.err = jpeg_std_error(&jerr);
/* Now we can initialize the JPEG compression object. */
jpeg_create_compress(&cinfo);

/* Step 2: specify data destination (eg, a file) */
/* Note: steps 2 and 3 can be done in either order. */
jpegDest(&cinfo, buffer, image_width*image_height*4);

/* Step 3: set parameters for compression */
cinfo.image_width = image_width; /* image width and height, in pixels */
cinfo.image_height = image_height;
cinfo.input_components = 4; /* # of color components per pixel */
cinfo.in_color_space = JCS_RGB; /* colorspace of input image */

jpeg_set_defaults(&cinfo);
jpeg_set_quality(&cinfo, quality, TRUE /* limit to baseline-JPEG values */);

/* Step 4: Start compressor */
jpeg_start_compress(&cinfo, TRUE);

/* Step 5: while (scan lines remain to be written) */
/* jpeg_write_scanlines(...); */
row_stride = image_width * 4; /* JSAMPLEs per row in image_buffer */

while (cinfo.next_scanline < cinfo.image_height) {
/* jpeg_write_scanlines expects an array of pointers to scanlines.
* Here the array is only one element long, but you could pass
* more than one scanline at a time if that's more convenient.
*/
row_pointer[0] = & image_buffer[((cinfo.image_height-1)*row_stride)-cinfo.next_scanline * row_stride];
(void) jpeg_write_scanlines(&cinfo, row_pointer, 1);
}

/* Step 6: Finish compression */
jpeg_finish_compress(&cinfo);

/* Step 7: release JPEG compression object */
jpeg_destroy_compress(&cinfo);

/* And we're done! */
return hackSize;
}

//===================================================================

/*
Expand Down

0 comments on commit a21eb2b

Please sign in to comment.