* AVI video output
- 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
This commit is contained in:
parent
92ad3e99dc
commit
a21eb2bbcb
15 changed files with 910 additions and 11 deletions
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
//===================================================================
|
||||
|
||||
/*
|
||||
|
|
|
@ -699,6 +699,51 @@ void R_ScreenShotJPEG_f (void) {
|
|||
|
||||
//============================================================================
|
||||
|
||||
/*
|
||||
==================
|
||||
RB_TakeVideoFrameCmd
|
||||
==================
|
||||
*/
|
||||
const void *RB_TakeVideoFrameCmd( const void *data )
|
||||
{
|
||||
const videoFrameCommand_t *cmd;
|
||||
int frameSize;
|
||||
int i;
|
||||
|
||||
cmd = (const videoFrameCommand_t *)data;
|
||||
|
||||
qglReadPixels( 0, 0, cmd->width, cmd->height, GL_RGBA,
|
||||
GL_UNSIGNED_BYTE, cmd->captureBuffer );
|
||||
|
||||
// gamma correct
|
||||
if( ( tr.overbrightBits > 0 ) && glConfig.deviceSupportsGamma )
|
||||
R_GammaCorrect( cmd->captureBuffer, cmd->width * cmd->height * 4 );
|
||||
|
||||
if( cmd->motionJpeg )
|
||||
{
|
||||
frameSize = SaveJPGToBuffer( cmd->encodeBuffer, 95,
|
||||
cmd->width, cmd->height, cmd->captureBuffer );
|
||||
}
|
||||
else
|
||||
{
|
||||
frameSize = cmd->width * cmd->height * 4;
|
||||
|
||||
// Vertically flip the image
|
||||
for( i = 0; i < cmd->height; i++ )
|
||||
{
|
||||
Com_Memcpy( &cmd->encodeBuffer[ i * ( cmd->width * 4 ) ],
|
||||
&cmd->captureBuffer[ ( cmd->height - i - 1 ) * ( cmd->width * 4 ) ],
|
||||
cmd->width * 4 );
|
||||
}
|
||||
}
|
||||
|
||||
ri.CL_WriteAVIVideoFrame( cmd->encodeBuffer, frameSize );
|
||||
|
||||
return (const void *)(cmd + 1);
|
||||
}
|
||||
|
||||
//============================================================================
|
||||
|
||||
/*
|
||||
** GL_SetDefaultState
|
||||
*/
|
||||
|
@ -1201,5 +1246,7 @@ refexport_t *GetRefAPI ( int apiVersion, refimport_t *rimp ) {
|
|||
re.GetEntityToken = R_GetEntityToken;
|
||||
re.inPVS = R_inPVS;
|
||||
|
||||
re.TakeVideoFrame = RE_TakeVideoFrame;
|
||||
|
||||
return &re;
|
||||
}
|
||||
|
|
|
@ -1215,6 +1215,7 @@ skin_t *R_GetSkinByHandle( qhandle_t hSkin );
|
|||
|
||||
int R_ComputeLOD( trRefEntity_t *ent );
|
||||
|
||||
const void *RB_TakeVideoFrameCmd( const void *data );
|
||||
|
||||
//
|
||||
// tr_shader.c
|
||||
|
@ -1579,6 +1580,15 @@ typedef struct {
|
|||
qboolean jpeg;
|
||||
} screenshotCommand_t;
|
||||
|
||||
typedef struct {
|
||||
int commandId;
|
||||
int width;
|
||||
int height;
|
||||
byte *captureBuffer;
|
||||
byte *encodeBuffer;
|
||||
qboolean motionJpeg;
|
||||
} videoFrameCommand_t;
|
||||
|
||||
typedef enum {
|
||||
RC_END_OF_LIST,
|
||||
RC_SET_COLOR,
|
||||
|
@ -1586,7 +1596,8 @@ typedef enum {
|
|||
RC_DRAW_SURFS,
|
||||
RC_DRAW_BUFFER,
|
||||
RC_SWAP_BUFFERS,
|
||||
RC_SCREENSHOT
|
||||
RC_SCREENSHOT,
|
||||
RC_VIDEOFRAME
|
||||
} renderCommand_t;
|
||||
|
||||
|
||||
|
@ -1635,6 +1646,11 @@ void RE_StretchPic ( float x, float y, float w, float h,
|
|||
void RE_BeginFrame( stereoFrame_t stereoFrame );
|
||||
void RE_EndFrame( int *frontEndMsec, int *backEndMsec );
|
||||
void SaveJPG(char * filename, int quality, int image_width, int image_height, unsigned char *image_buffer);
|
||||
int SaveJPGToBuffer( byte *buffer, int quality,
|
||||
int image_width, int image_height,
|
||||
byte *image_buffer );
|
||||
void RE_TakeVideoFrame( int width, int height,
|
||||
byte *captureBuffer, byte *encodeBuffer, qboolean motionJpeg );
|
||||
|
||||
// font stuff
|
||||
void R_InitFreeType( void );
|
||||
|
|
|
@ -97,6 +97,8 @@ typedef struct {
|
|||
void (*RemapShader)(const char *oldShader, const char *newShader, const char *offsetTime);
|
||||
qboolean (*GetEntityToken)( char *buffer, int size );
|
||||
qboolean (*inPVS)( const vec3_t p1, const vec3_t p2 );
|
||||
|
||||
void (*TakeVideoFrame)( int h, int w, byte* captureBuffer, byte *encodeBuffer, qboolean motionJpeg );
|
||||
} refexport_t;
|
||||
|
||||
//
|
||||
|
@ -156,6 +158,7 @@ typedef struct {
|
|||
int (*CIN_PlayCinematic)( const char *arg0, int xpos, int ypos, int width, int height, int bits);
|
||||
e_status (*CIN_RunCinematic) (int handle);
|
||||
|
||||
void (*CL_WriteAVIVideoFrame)( const byte *buffer, int size );
|
||||
} refimport_t;
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue