fixed a bunch of /video and /stopvideo issues

chg: /video can only be used during demo playback
fix: broken audio in the output files due to writing the wrong buffer
fix: files sequences have a "_%3d" name suffix of adding an underscore to the extension
fix: using a better supported video codec (FourCC 0x00000000) for raw BGR output
fix: broken raw video output when r_width wasn't a multiple of 4
fix: /stopvideo no longer leaves sound output broken for a while after stopping
This commit is contained in:
myT 2018-09-29 08:22:47 +02:00
parent a9e7bc7226
commit 5972f247b1
5 changed files with 71 additions and 47 deletions

View file

@ -10,6 +10,14 @@ add: /toggle can now accept a value sequence (2 or more entries) to loop through
if the cvar's current value is in the sequence and not in the last spot, the next one is used
otherwise, the first value in the sequence is used
fix: /video and /stopvideo fixes
chg: /video can only be used during demo playback
fix: broken audio in the output files
fix: properly named file sequences
fix: using a better supported video codec (FourCC 0x00000000) for raw BGR output
fix: broken raw video output when r_width wasn't a multiple of 4
fix: /stopvideo no longer leaves sound output broken for a while after stopping
fix: /cv and /callvote auto-completion were disabled after cgame was shut down at least once

View file

@ -40,7 +40,7 @@ typedef struct audioFormat_s
typedef struct aviFileData_s
{
qbool fileOpen;
qbool fileOpen;
fileHandle_t f;
char fileName[ MAX_QPATH ];
int fileSize;
@ -55,16 +55,16 @@ typedef struct aviFileData_s
int width, height;
int numVideoFrames;
int maxRecordSize;
qbool motionJpeg;
qbool motionJpeg;
qbool audio;
qbool audio;
audioFormat_t a;
int numAudioFrames;
int chunkStack[ MAX_RIFF_CHUNKS ];
int chunkStackTop;
byte *cBuffer, *eBuffer;
byte *cBuffer, *eBuffer; // capture and encoding buffers
} aviFileData_t;
static aviFileData_t afd;
@ -218,12 +218,12 @@ void CL_WriteAVIHeader( void )
WRITE_STRING( "strl" );
WRITE_STRING( "strh" );
WRITE_4BYTES( 56 ); //"strh" "chunk" size
WRITE_STRING( "vids" );
WRITE_STRING( "vids" ); //fccType
if( afd.motionJpeg )
if( afd.motionJpeg ) //fccHandler
WRITE_STRING( "MJPG" );
else
WRITE_STRING( " BGR" );
WRITE_4BYTES( 0 ); //raw BGR
WRITE_4BYTES( 0 ); //dwFlags
WRITE_4BYTES( 0 ); //dwPriority
@ -250,13 +250,20 @@ void CL_WriteAVIHeader( void )
WRITE_2BYTES( 1 ); //biPlanes
WRITE_2BYTES( 24 ); //biBitCount
if( afd.motionJpeg ) //biCompression
if( afd.motionJpeg ) //biCompression and biSizeImage
{
// we specify the pixel count
WRITE_STRING( "MJPG" );
WRITE_4BYTES( afd.width * afd.height );
}
else
WRITE_STRING( " BGR" );
{
// must either specify 0 or the total byte count per image
// but there is no reason to trust most software...
WRITE_4BYTES( 0 ); // raw BGR
WRITE_4BYTES( afd.width * afd.height * 3 );
}
WRITE_4BYTES( afd.width *
afd.height ); //biSizeImage
WRITE_4BYTES( 0 ); //biXPelsPetMeter
WRITE_4BYTES( 0 ); //biYPelsPetMeter
WRITE_4BYTES( 0 ); //biClrUsed
@ -321,8 +328,14 @@ void CL_WriteAVIHeader( void )
// creates an AVI file and gets it into a state where writing the actual data can begin
qbool CL_OpenAVIForWriting( const char* fileName )
qbool CL_OpenAVIForWriting( const char* fileNameNoExt, qbool reOpen )
{
static char avi_fileNameNoExt[MAX_QPATH];
static int avi_fileNameIndex;
if ( reOpen )
CL_CloseAVI();
if ( afd.fileOpen )
return qfalse;
@ -334,16 +347,22 @@ qbool CL_OpenAVIForWriting( const char* fileName )
return qfalse;
}
if ( ( afd.f = FS_FOpenFileWrite( fileName ) ) <= 0 )
if ( reOpen ) {
avi_fileNameIndex++;
} else {
Q_strncpyz( avi_fileNameNoExt, fileNameNoExt, sizeof( avi_fileNameNoExt ) );
avi_fileNameIndex = 0;
}
Com_sprintf( afd.fileName, sizeof( afd.fileName ), "%s_%03d.avi", avi_fileNameNoExt, avi_fileNameIndex );
if ( ( afd.f = FS_FOpenFileWrite( afd.fileName ) ) <= 0 )
return qfalse;
if ( ( afd.idxF = FS_FOpenFileWrite( va( "%s" INDEX_FILE_EXTENSION, fileName ) ) ) <= 0 ) {
if ( ( afd.idxF = FS_FOpenFileWrite( va( "%s" INDEX_FILE_EXTENSION, afd.fileName ) ) ) <= 0 ) {
FS_FCloseFile( afd.f );
return qfalse;
}
Q_strncpyz( afd.fileName, fileName, MAX_QPATH );
afd.frameRate = cl_aviFrameRate->integer;
afd.framePeriod = (int)( 1000000.0f / afd.frameRate );
afd.width = cls.glconfig.vidWidth;
@ -351,8 +370,9 @@ qbool CL_OpenAVIForWriting( const char* fileName )
afd.motionJpeg = (cl_aviMotionJpeg->integer != 0);
afd.cBuffer = (byte*)Z_Malloc( afd.width * afd.height * 4 );
afd.eBuffer = (byte*)Z_Malloc( afd.width * afd.height * 4 );
const int maxByteCount = PAD( afd.width, 4 ) * afd.height * 4;
afd.cBuffer = (byte*)Z_Malloc( maxByteCount );
afd.eBuffer = (byte*)Z_Malloc( maxByteCount );
afd.a.rate = dma.speed;
afd.a.format = WAV_FORMAT_PCM;
@ -391,6 +411,8 @@ qbool CL_OpenAVIForWriting( const char* fileName )
afd.moviSize = 4; // for the "movi" header signature
afd.fileOpen = qtrue;
Com_Printf( "Recording to %s\n", afd.fileName );
return qtrue;
}
@ -414,12 +436,7 @@ static qbool CL_CheckFileSize( int bytesToAdd )
// we target can handle a 2Gb file
if( newFileSize > INT_MAX )
{
// Close the current file...
CL_CloseAVI( );
// ...And open a new one
CL_OpenAVIForWriting( va( "%s_", afd.fileName ) );
CL_OpenAVIForWriting( NULL, qtrue );
return qtrue;
}
@ -517,7 +534,7 @@ void CL_WriteAVIAudioFrame( const byte *pcmBuffer, int size )
WRITE_4BYTES( bytesInBuffer );
SafeFS_Write( buffer, 8, afd.f );
SafeFS_Write( pcmBuffer, bytesInBuffer, afd.f );
SafeFS_Write( pcmCaptureBuffer, bytesInBuffer, afd.f );
SafeFS_Write( padding, paddingSize, afd.f );
afd.fileSize += ( chunkSize + paddingSize );
@ -625,6 +642,8 @@ qbool CL_CloseAVI( void )
Com_Printf( "Wrote %d:%d frames to %s\n", afd.numVideoFrames, afd.numAudioFrames, afd.fileName );
S_StopAllSounds();
return qtrue;
}

View file

@ -1815,20 +1815,25 @@ static void CL_Video_f()
{
char s[ MAX_OSPATH ];
if( !clc.demoplaying )
{
Com_Printf( "ERROR: ^7/video is only enabled during demo playback\n" );
return;
}
if( Cmd_Argc( ) == 2 )
{
Com_sprintf( s, MAX_OSPATH, "videos/%s.avi", Cmd_Argv( 1 ) );
Com_sprintf( s, MAX_OSPATH, "videos/%s", Cmd_Argv( 1 ) );
}
else
{
qtime_t t;
Com_RealTime( &t );
Com_sprintf( s, sizeof(s), "videos/%d_%02d_%02d-%02d_%02d_%02d.avi",
Com_sprintf( s, sizeof(s), "videos/%d_%02d_%02d-%02d_%02d_%02d",
1900+t.tm_year, 1+t.tm_mon, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec );
}
Com_Printf( "recording to %s\n", s );
CL_OpenAVIForWriting( s );
CL_OpenAVIForWriting( s, qfalse );
}
@ -1999,7 +2004,7 @@ static const cvarTableItem_t cl_cvars[] =
{ &cl_showTimeDelta, "cl_showTimeDelta", "0", CVAR_TEMP, CVART_BOOL, NULL, NULL, "prints delta adjustment values and events" },
{ &rconPassword, "rconPassword", "", CVAR_TEMP, CVART_STRING, NULL, NULL, help_rconPassword },
{ &cl_timedemo, "timedemo", "0", 0, CVART_BOOL, NULL, NULL, "demo benchmarking mode" },
{ &cl_aviFrameRate, "cl_aviFrameRate", "25", CVAR_ARCHIVE, CVART_INTEGER, "1", "250", help_cl_aviFrameRate },
{ &cl_aviFrameRate, "cl_aviFrameRate", "50", CVAR_ARCHIVE, CVART_INTEGER, "24", "250", help_cl_aviFrameRate },
{ &cl_aviMotionJpeg, "cl_aviMotionJpeg", "1", CVAR_ARCHIVE, CVART_BOOL, NULL, NULL, help_cl_aviMotionJpeg },
{ &rconAddress, "rconAddress", "", 0, CVART_STRING, NULL, NULL, help_rconAddress },
{ &cl_maxpackets, "cl_maxpackets", "125", CVAR_ARCHIVE, CVART_INTEGER, "15", "125", "max. packet upload rate" },

View file

@ -507,7 +507,7 @@ qbool CL_Netchan_Process( netchan_t *chan, msg_t *msg );
//
// cl_avi.c
//
qbool CL_OpenAVIForWriting( const char *filename );
qbool CL_OpenAVIForWriting( const char *fileNameNoExt, qbool reOpen );
void CL_TakeVideoFrame( void );
void CL_WriteAVIVideoFrame( const byte *imageBuffer, int size );
void CL_WriteAVIAudioFrame( const byte *pcmBuffer, int size );

View file

@ -460,29 +460,21 @@ const void *RB_TakeVideoFrameCmd( const void *data )
int frameSize;
const videoFrameCommand_t* cmd = (const videoFrameCommand_t*)data;
qglReadPixels( 0, 0, cmd->width, cmd->height, GL_RGBA,
GL_UNSIGNED_BYTE, cmd->captureBuffer );
if( cmd->motionJpeg )
{
frameSize = SaveJPGToBuffer( cmd->encodeBuffer, 95,
cmd->width, cmd->height, cmd->captureBuffer );
qglReadPixels( 0, 0, cmd->width, cmd->height, GL_RGBA, GL_UNSIGNED_BYTE, cmd->captureBuffer );
frameSize = SaveJPGToBuffer( cmd->encodeBuffer, 95, cmd->width, cmd->height, cmd->captureBuffer );
ri.CL_WriteAVIVideoFrame( cmd->encodeBuffer, frameSize );
}
else
{
frameSize = cmd->width * cmd->height * 4;
// Vertically flip the image
for(int 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 );
}
qglPixelStorei( GL_PACK_ALIGNMENT, 4 );
qglReadPixels( 0, 0, cmd->width, cmd->height, GL_BGR, GL_UNSIGNED_BYTE, cmd->captureBuffer );
qglPixelStorei( GL_PACK_ALIGNMENT, 1 );
frameSize = PAD( cmd->width, 4 ) * cmd->height * 3;
ri.CL_WriteAVIVideoFrame( cmd->captureBuffer, frameSize );
}
ri.CL_WriteAVIVideoFrame( cmd->encodeBuffer, frameSize );
return (const void *)(cmd + 1);
}