mirror of https://bitbucket.org/CPMADevs/cnq3
added FFmpeg support for A/V capture compression
This commit is contained in:
parent
9a6e253dc3
commit
3cea7e0c3d
|
@ -4,6 +4,16 @@ See the end of this file for known issues.
|
|||
|
||||
DD Mmm YY - 1.54
|
||||
|
||||
add: FFmpeg piping support for compression of audio/video captures
|
||||
cl_ffmpeg <0|1> (default: 0) uses FFmpeg instead of writing raw .avi files
|
||||
cl_ffmpegCommand <string> are the FFmpeg command-line options for the output file
|
||||
cl_ffmpegExePath <string> (default: "ffmpeg") is the path of the FFmpeg executable to use
|
||||
note that the path cannot contain spaces on Windows
|
||||
cl_ffmpegOutPath <string> (default: "") is the video output directory
|
||||
leave empty to write to cpma/videos as with .avi files
|
||||
cl_ffmpegOutExt <string> (default: "mp4") is the output file extension
|
||||
cl_ffmpegLog <0|1> (default: 0) enables the creation of 1 log file per capture
|
||||
|
||||
add: togglegui and toggleguiinput to toggle (the mouse input of) the built-in GUI system
|
||||
|
||||
add: key binds starting with "keycatchgui" always take precedence over everything else
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/*
|
||||
===========================================================================
|
||||
Copyright (C) 2005-2006 Tim Angus
|
||||
Copyright (C) 2018-2023 Gian 'myT' Schellenbaum
|
||||
|
||||
This file is part of Quake III Arena source code.
|
||||
|
||||
|
@ -19,16 +20,26 @@ along with Quake III Arena source code; if not, write to the Free Software
|
|||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
===========================================================================
|
||||
*/
|
||||
// captures audio/video to an AVI (raw BGR or MJPEG, PCM) file or pipes raw data to FFmpeg
|
||||
|
||||
#include "client.h"
|
||||
#include "snd_local.h"
|
||||
|
||||
|
||||
#define INDEX_FILE_EXTENSION ".index.dat"
|
||||
|
||||
#define MAX_RIFF_CHUNKS 16
|
||||
|
||||
typedef struct audioFormat_s
|
||||
{
|
||||
#define PCM_BUFFER_SIZE 44100
|
||||
|
||||
#define MAX_AVI_BUFFER 4096
|
||||
|
||||
// flags for the "AVIH" RIFF chunk
|
||||
#define AVIH_HASINDEX_BIT 0x010
|
||||
#define AVIH_ISINTERLEAVED_BIT 0x100
|
||||
|
||||
|
||||
struct audioFormat_t {
|
||||
int rate;
|
||||
int format;
|
||||
int channels;
|
||||
|
@ -36,159 +47,149 @@ typedef struct audioFormat_s
|
|||
|
||||
int sampleSize;
|
||||
int totalBytes;
|
||||
} audioFormat_t;
|
||||
};
|
||||
|
||||
typedef struct aviFileData_s
|
||||
{
|
||||
qbool fileOpen;
|
||||
fileHandle_t f;
|
||||
struct aviFileData_t {
|
||||
qbool isPiped;
|
||||
qbool isFileOpen;
|
||||
fileHandle_t mainFile;
|
||||
char fileName[256 + 64]; // extra room for the "videos/" prefix and name suffix
|
||||
int fileSize;
|
||||
int moviOffset;
|
||||
int moviSize;
|
||||
|
||||
fileHandle_t idxF;
|
||||
fileHandle_t indexFile;
|
||||
int numIndices;
|
||||
|
||||
int frameRate;
|
||||
int framePeriod;
|
||||
int width, height;
|
||||
int width;
|
||||
int height;
|
||||
int numVideoFrames;
|
||||
int maxRecordSize;
|
||||
qbool motionJpeg;
|
||||
qbool useMotionJpeg;
|
||||
|
||||
qbool audio;
|
||||
audioFormat_t a;
|
||||
qbool hasAudio;
|
||||
audioFormat_t audioFormat;
|
||||
int numAudioFrames;
|
||||
|
||||
int chunkStack[MAX_RIFF_CHUNKS];
|
||||
int chunkStackTop;
|
||||
|
||||
byte *cBuffer, *eBuffer; // capture and encoding buffers
|
||||
} aviFileData_t;
|
||||
byte* captureBuffer;
|
||||
byte* encodeBuffer;
|
||||
};
|
||||
|
||||
typedef enum closeMode_s
|
||||
{
|
||||
enum closeMode_t {
|
||||
CM_SEQUENCE_COMPLETE, // last in the sequence -> safe to clear sounds etc.
|
||||
CM_SEQUENCE_INCOMPLETE // NOT last in the sequence -> we'll open a new one
|
||||
} closeMode_t;
|
||||
};
|
||||
|
||||
|
||||
static aviFileData_t afd;
|
||||
|
||||
#define MAX_AVI_BUFFER 2048
|
||||
|
||||
static byte buffer[MAX_AVI_BUFFER];
|
||||
static int bufIndex;
|
||||
static int bufferWriteIndex;
|
||||
|
||||
|
||||
static qbool CloseAVI( closeMode_t closeMode );
|
||||
|
||||
/*
|
||||
===============
|
||||
SafeFS_Write
|
||||
===============
|
||||
*/
|
||||
static ID_INLINE void SafeFS_Write( const void *buff, int len, fileHandle_t f )
|
||||
|
||||
static void AbortCapture( const char* message )
|
||||
{
|
||||
if( FS_Write( buff, len, f ) < len )
|
||||
Com_Error( ERR_DROP, "Failed to write avi file\n" );
|
||||
Com_Printf("^1ERROR: %s\n", message);
|
||||
Cbuf_AddText(clc.newDemoPlayer ? "demo_stopvideo\n" : "stopvideo\n");
|
||||
}
|
||||
|
||||
/*
|
||||
===============
|
||||
WRITE_STRING
|
||||
===============
|
||||
*/
|
||||
static ID_INLINE void WRITE_STRING( const char *s )
|
||||
|
||||
static void Safe_FS_Write( const void* buff, int len, fileHandle_t f )
|
||||
{
|
||||
Com_Memcpy( &buffer[ bufIndex ], s, strlen( s ) );
|
||||
bufIndex += strlen( s );
|
||||
if (f <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
===============
|
||||
WRITE_4BYTES
|
||||
===============
|
||||
*/
|
||||
static ID_INLINE void WRITE_4BYTES( int x )
|
||||
{
|
||||
buffer[ bufIndex + 0 ] = (byte)( ( x >> 0 ) & 0xFF );
|
||||
buffer[ bufIndex + 1 ] = (byte)( ( x >> 8 ) & 0xFF );
|
||||
buffer[ bufIndex + 2 ] = (byte)( ( x >> 16 ) & 0xFF );
|
||||
buffer[ bufIndex + 3 ] = (byte)( ( x >> 24 ) & 0xFF );
|
||||
bufIndex += 4;
|
||||
if (FS_Write(buff, len, f) < len) {
|
||||
AbortCapture("Failed to write to video file/pipe");
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
===============
|
||||
WRITE_2BYTES
|
||||
===============
|
||||
*/
|
||||
static ID_INLINE void WRITE_2BYTES( int x )
|
||||
|
||||
static void Safe_FS_Close( fileHandle_t* file )
|
||||
{
|
||||
buffer[ bufIndex + 0 ] = (byte)( ( x >> 0 ) & 0xFF );
|
||||
buffer[ bufIndex + 1 ] = (byte)( ( x >> 8 ) & 0xFF );
|
||||
bufIndex += 2;
|
||||
if (*file > 0) {
|
||||
FS_FCloseFile(*file);
|
||||
*file = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
===============
|
||||
WRITE_1BYTES
|
||||
===============
|
||||
*/
|
||||
static ID_INLINE void WRITE_1BYTES( int x )
|
||||
|
||||
static void Safe_Z_Free( byte** memory )
|
||||
{
|
||||
buffer[ bufIndex ] = x;
|
||||
bufIndex += 1;
|
||||
if (*memory != NULL) {
|
||||
Z_Free(*memory);
|
||||
*memory = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
===============
|
||||
START_CHUNK
|
||||
===============
|
||||
*/
|
||||
static ID_INLINE void START_CHUNK( const char *s )
|
||||
|
||||
static void WRITE_STRING( const char* s )
|
||||
{
|
||||
if( afd.chunkStackTop == MAX_RIFF_CHUNKS )
|
||||
{
|
||||
Com_Error( ERR_DROP, "ERROR: Top of chunkstack breached\n" );
|
||||
Com_Memcpy(&buffer[bufferWriteIndex], s, strlen(s));
|
||||
bufferWriteIndex += strlen(s);
|
||||
}
|
||||
|
||||
afd.chunkStack[ afd.chunkStackTop ] = bufIndex;
|
||||
|
||||
static void WRITE_4BYTES( int x )
|
||||
{
|
||||
buffer[bufferWriteIndex + 0] = (byte)((x >> 0) & 0xFF);
|
||||
buffer[bufferWriteIndex + 1] = (byte)((x >> 8) & 0xFF);
|
||||
buffer[bufferWriteIndex + 2] = (byte)((x >> 16) & 0xFF);
|
||||
buffer[bufferWriteIndex + 3] = (byte)((x >> 24) & 0xFF);
|
||||
bufferWriteIndex += 4;
|
||||
}
|
||||
|
||||
|
||||
static void WRITE_2BYTES( int x )
|
||||
{
|
||||
buffer[bufferWriteIndex + 0] = (byte)((x >> 0) & 0xFF);
|
||||
buffer[bufferWriteIndex + 1] = (byte)((x >> 8) & 0xFF);
|
||||
bufferWriteIndex += 2;
|
||||
}
|
||||
|
||||
|
||||
static void START_CHUNK( const char* s )
|
||||
{
|
||||
if (afd.chunkStackTop == MAX_RIFF_CHUNKS) {
|
||||
AbortCapture("Top of chunkstack breached");
|
||||
return;
|
||||
}
|
||||
|
||||
afd.chunkStack[afd.chunkStackTop] = bufferWriteIndex;
|
||||
afd.chunkStackTop++;
|
||||
WRITE_STRING(s);
|
||||
WRITE_4BYTES(0);
|
||||
}
|
||||
|
||||
/*
|
||||
===============
|
||||
END_CHUNK
|
||||
===============
|
||||
*/
|
||||
static ID_INLINE void END_CHUNK( void )
|
||||
{
|
||||
int endIndex = bufIndex;
|
||||
|
||||
if( afd.chunkStackTop <= 0 )
|
||||
static void END_CHUNK()
|
||||
{
|
||||
Com_Error( ERR_DROP, "ERROR: Bottom of chunkstack breached\n" );
|
||||
if (afd.chunkStackTop <= 0) {
|
||||
AbortCapture("Bottom of chunkstack breached");
|
||||
return;
|
||||
}
|
||||
|
||||
const int endIndex = bufferWriteIndex;
|
||||
afd.chunkStackTop--;
|
||||
bufIndex = afd.chunkStack[ afd.chunkStackTop ];
|
||||
bufIndex += 4;
|
||||
WRITE_4BYTES( endIndex - bufIndex - 4 );
|
||||
bufIndex = endIndex;
|
||||
bufIndex = PAD( bufIndex, 2 );
|
||||
bufferWriteIndex = afd.chunkStack[afd.chunkStackTop];
|
||||
bufferWriteIndex += 4;
|
||||
WRITE_4BYTES(endIndex - bufferWriteIndex - 4);
|
||||
bufferWriteIndex = endIndex;
|
||||
bufferWriteIndex = PAD(bufferWriteIndex, 2);
|
||||
}
|
||||
|
||||
/*
|
||||
===============
|
||||
CL_WriteAVIHeader
|
||||
===============
|
||||
*/
|
||||
void CL_WriteAVIHeader( void )
|
||||
|
||||
void CL_WriteAVIHeader()
|
||||
{
|
||||
bufIndex = 0;
|
||||
bufferWriteIndex = 0;
|
||||
afd.chunkStackTop = 0;
|
||||
|
||||
START_CHUNK("RIFF");
|
||||
|
@ -201,14 +202,16 @@ void CL_WriteAVIHeader( void )
|
|||
WRITE_STRING("avih");
|
||||
WRITE_4BYTES(56); //"avih" "chunk" size
|
||||
WRITE_4BYTES(afd.framePeriod); //dwMicroSecPerFrame
|
||||
WRITE_4BYTES( afd.maxRecordSize *
|
||||
afd.frameRate ); //dwMaxBytesPerSec
|
||||
WRITE_4BYTES(afd.maxRecordSize * afd.frameRate); //dwMaxBytesPerSec
|
||||
WRITE_4BYTES(0); //dwReserved1
|
||||
WRITE_4BYTES( 0x110 ); //dwFlags bits HAS_INDEX and IS_INTERLEAVED
|
||||
if (afd.isPiped)
|
||||
WRITE_4BYTES(AVIH_ISINTERLEAVED_BIT);
|
||||
else
|
||||
WRITE_4BYTES(AVIH_ISINTERLEAVED_BIT | AVIH_HASINDEX_BIT);
|
||||
WRITE_4BYTES(afd.numVideoFrames); //dwTotalFrames
|
||||
WRITE_4BYTES(0); //dwInitialFrame
|
||||
|
||||
if( afd.audio ) //dwStreams
|
||||
if (afd.hasAudio) //dwStreams
|
||||
WRITE_4BYTES(2);
|
||||
else
|
||||
WRITE_4BYTES(1);
|
||||
|
@ -228,7 +231,8 @@ void CL_WriteAVIHeader( void )
|
|||
WRITE_4BYTES(56); //"strh" "chunk" size
|
||||
WRITE_STRING("vids"); //fccType
|
||||
|
||||
if( afd.motionJpeg ) //fccHandler
|
||||
//fccHandler
|
||||
if (!afd.isPiped && afd.useMotionJpeg)
|
||||
WRITE_STRING("MJPG");
|
||||
else
|
||||
WRITE_4BYTES(0); //raw BGR
|
||||
|
@ -258,16 +262,13 @@ void CL_WriteAVIHeader( void )
|
|||
WRITE_2BYTES(1); //biPlanes
|
||||
WRITE_2BYTES(24); //biBitCount
|
||||
|
||||
if( afd.motionJpeg ) //biCompression and biSizeImage
|
||||
{
|
||||
// we specify the pixel count
|
||||
//biCompression and biSizeImage
|
||||
if (!afd.isPiped && afd.useMotionJpeg) {
|
||||
// We specify the pixel count
|
||||
WRITE_STRING("MJPG");
|
||||
WRITE_4BYTES(afd.width * afd.height);
|
||||
}
|
||||
else
|
||||
{
|
||||
// must either specify 0 or the total byte count per image
|
||||
// but there is no reason to trust most software...
|
||||
} else {
|
||||
// We must either specify 0 or the total byte count per image
|
||||
WRITE_4BYTES(0); // raw BGR
|
||||
WRITE_4BYTES(afd.width * afd.height * 3);
|
||||
}
|
||||
|
@ -279,8 +280,7 @@ void CL_WriteAVIHeader( void )
|
|||
}
|
||||
END_CHUNK();
|
||||
|
||||
if( afd.audio )
|
||||
{
|
||||
if (afd.hasAudio) {
|
||||
START_CHUNK("LIST");
|
||||
{
|
||||
WRITE_STRING("strl");
|
||||
|
@ -292,16 +292,14 @@ void CL_WriteAVIHeader( void )
|
|||
WRITE_4BYTES(0); //dwPriority
|
||||
WRITE_4BYTES(0); //dwInitialFrame
|
||||
|
||||
WRITE_4BYTES( afd.a.sampleSize ); //dwTimescale
|
||||
WRITE_4BYTES( afd.a.sampleSize *
|
||||
afd.a.rate ); //dwDataRate
|
||||
WRITE_4BYTES(afd.audioFormat.sampleSize); //dwTimescale
|
||||
WRITE_4BYTES(afd.audioFormat.sampleSize * afd.audioFormat.rate); //dwDataRate
|
||||
WRITE_4BYTES(0); //dwStartTime
|
||||
WRITE_4BYTES( afd.a.totalBytes /
|
||||
afd.a.sampleSize ); //dwDataLength
|
||||
WRITE_4BYTES(afd.audioFormat.totalBytes / afd.audioFormat.sampleSize); //dwDataLength
|
||||
|
||||
WRITE_4BYTES(0); //dwSuggestedBufferSize
|
||||
WRITE_4BYTES(-1); //dwQuality
|
||||
WRITE_4BYTES( afd.a.sampleSize ); //dwSampleSize
|
||||
WRITE_4BYTES(afd.audioFormat.sampleSize); //dwSampleSize
|
||||
WRITE_2BYTES(0); //rcFrame
|
||||
WRITE_2BYTES(0); //rcFrame
|
||||
WRITE_2BYTES(0); //rcFrame
|
||||
|
@ -309,13 +307,12 @@ void CL_WriteAVIHeader( void )
|
|||
|
||||
WRITE_STRING("strf");
|
||||
WRITE_4BYTES(18); //"strf" "chunk" size
|
||||
WRITE_2BYTES( afd.a.format ); //wFormatTag
|
||||
WRITE_2BYTES( afd.a.channels ); //nChannels
|
||||
WRITE_4BYTES( afd.a.rate ); //nSamplesPerSec
|
||||
WRITE_4BYTES( afd.a.sampleSize *
|
||||
afd.a.rate ); //nAvgBytesPerSec
|
||||
WRITE_2BYTES( afd.a.sampleSize ); //nBlockAlign
|
||||
WRITE_2BYTES( afd.a.bits ); //wBitsPerSample
|
||||
WRITE_2BYTES(afd.audioFormat.format); //wFormatTag
|
||||
WRITE_2BYTES(afd.audioFormat.channels); //nChannels
|
||||
WRITE_4BYTES(afd.audioFormat.rate); //nSamplesPerSec
|
||||
WRITE_4BYTES(afd.audioFormat.sampleSize * afd.audioFormat.rate); //nAvgBytesPerSec
|
||||
WRITE_2BYTES(afd.audioFormat.sampleSize); //nBlockAlign
|
||||
WRITE_2BYTES(afd.audioFormat.bits); //wBitsPerSample
|
||||
WRITE_2BYTES(0); //cbSize
|
||||
}
|
||||
END_CHUNK();
|
||||
|
@ -323,7 +320,7 @@ void CL_WriteAVIHeader( void )
|
|||
}
|
||||
END_CHUNK();
|
||||
|
||||
afd.moviOffset = bufIndex;
|
||||
afd.moviOffset = bufferWriteIndex;
|
||||
|
||||
START_CHUNK("LIST");
|
||||
{
|
||||
|
@ -335,89 +332,121 @@ 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* fileNameNoExt, qbool reOpen )
|
||||
static qbool OpenAVI( const char* fileNameNoExt, qbool reOpen )
|
||||
{
|
||||
static char avi_fileNameNoExt[MAX_QPATH];
|
||||
static int avi_fileNameIndex;
|
||||
|
||||
if ( reOpen )
|
||||
if (!afd.isPiped && reOpen)
|
||||
CloseAVI(CM_SEQUENCE_INCOMPLETE);
|
||||
|
||||
if ( afd.fileOpen )
|
||||
if (afd.isFileOpen)
|
||||
return qfalse;
|
||||
|
||||
Com_Memset(&afd, 0, sizeof(aviFileData_t));
|
||||
afd.isPiped = cl_ffmpeg->integer != 0;
|
||||
|
||||
// don't start if a framerate has not been chosen
|
||||
if (cl_aviFrameRate->integer <= 0) {
|
||||
Com_Printf( S_COLOR_RED "cl_aviFrameRate must be >= 1\n" );
|
||||
Com_Printf("^1ERROR: cl_aviFrameRate must be >= 1\n");
|
||||
return qfalse;
|
||||
}
|
||||
|
||||
if (afd.isPiped) {
|
||||
const char* fileExtension = cl_ffmpegOutExt->string;
|
||||
while (*fileExtension == '.') {
|
||||
fileExtension++;
|
||||
}
|
||||
|
||||
if (cl_ffmpegOutPath->string[0] == '\0') {
|
||||
const char* homePath = Cvar_VariableString("fs_homepath");
|
||||
const char* final = FS_BuildOSPath(homePath, NULL, va("videos/%s.%s", fileNameNoExt, fileExtension));
|
||||
Com_sprintf(afd.fileName, sizeof(afd.fileName), final);
|
||||
} else {
|
||||
Com_sprintf(afd.fileName, sizeof(afd.fileName), "%s/%s.%s", cl_ffmpegOutPath->string, fileNameNoExt, fileExtension);
|
||||
FS_ReplaceSeparators(afd.fileName);
|
||||
}
|
||||
|
||||
// @NOTE: can't double quote the executable's path with _popen
|
||||
const char* pipeCommand;
|
||||
if (cl_ffmpegLog->integer != 0) {
|
||||
pipeCommand = va("%s -f avi -i - %s -y \"%s\" > \"%s.log.txt\" 2>&1",
|
||||
cl_ffmpegExePath->string, cl_ffmpegCommand->string, afd.fileName, afd.fileName);
|
||||
} else {
|
||||
pipeCommand = va("%s -f avi -i - %s -y \"%s\"",
|
||||
cl_ffmpegExePath->string, cl_ffmpegCommand->string, afd.fileName);
|
||||
}
|
||||
|
||||
afd.mainFile = FS_OpenPipeWrite(pipeCommand);
|
||||
if (afd.mainFile <= 0) {
|
||||
Com_Printf("^1ERROR: failed to open video process for writing\n");
|
||||
return qfalse;
|
||||
}
|
||||
} else {
|
||||
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 );
|
||||
Com_sprintf(afd.fileName, sizeof(afd.fileName), "videos/%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, afd.fileName ) ) ) <= 0 ) {
|
||||
FS_FCloseFile( afd.f );
|
||||
afd.mainFile = FS_FOpenFileWrite(afd.fileName);
|
||||
afd.indexFile = FS_FOpenFileWrite(va("%s" INDEX_FILE_EXTENSION, afd.fileName));
|
||||
if (afd.mainFile <= 0 || afd.indexFile <= 0) {
|
||||
Safe_FS_Close(&afd.mainFile);
|
||||
Safe_FS_Close(&afd.indexFile);
|
||||
Com_Printf("^1ERROR: failed to open video file for writing\n");
|
||||
return qfalse;
|
||||
}
|
||||
}
|
||||
|
||||
afd.frameRate = cl_aviFrameRate->integer;
|
||||
afd.framePeriod = (int)(1000000.0f / afd.frameRate);
|
||||
afd.width = cls.glconfig.vidWidth;
|
||||
afd.height = cls.glconfig.vidHeight;
|
||||
|
||||
afd.motionJpeg = (cl_aviMotionJpeg->integer != 0);
|
||||
afd.useMotionJpeg = !afd.isPiped && cl_aviMotionJpeg->integer != 0;
|
||||
|
||||
const int maxByteCount = PAD(afd.width, 4) * afd.height * 4;
|
||||
afd.cBuffer = (byte*)Z_Malloc( maxByteCount );
|
||||
afd.eBuffer = (byte*)Z_Malloc( maxByteCount );
|
||||
afd.captureBuffer = (byte*)Z_Malloc(maxByteCount);
|
||||
afd.encodeBuffer = (byte*)Z_Malloc(maxByteCount);
|
||||
|
||||
afd.a.rate = dma.speed;
|
||||
afd.a.format = WAV_FORMAT_PCM;
|
||||
afd.a.channels = dma.channels;
|
||||
afd.a.bits = dma.samplebits;
|
||||
afd.a.sampleSize = ( afd.a.bits / 8 ) * afd.a.channels;
|
||||
afd.audioFormat.rate = dma.speed;
|
||||
afd.audioFormat.format = WAV_FORMAT_PCM;
|
||||
afd.audioFormat.channels = dma.channels;
|
||||
afd.audioFormat.bits = dma.samplebits;
|
||||
afd.audioFormat.sampleSize = (afd.audioFormat.bits / 8) * afd.audioFormat.channels;
|
||||
|
||||
if ( afd.a.rate % afd.frameRate )
|
||||
{
|
||||
if (afd.audioFormat.rate % afd.frameRate) {
|
||||
int suggestRate = afd.frameRate;
|
||||
while ((afd.a.rate % suggestRate) && suggestRate)
|
||||
while ((afd.audioFormat.rate % suggestRate) && suggestRate)
|
||||
--suggestRate;
|
||||
Com_Printf( S_COLOR_YELLOW "WARNING: cl_aviFrameRate is not a divisor "
|
||||
"of the audio rate, suggest %d\n", suggestRate );
|
||||
Com_Printf("^3WARNING: cl_aviFrameRate is not a divisor of the audio rate, suggesting %d\n", suggestRate);
|
||||
}
|
||||
|
||||
afd.audio = qfalse;
|
||||
afd.hasAudio = qfalse;
|
||||
if (Cvar_VariableIntegerValue("s_initsound")) {
|
||||
afd.audio = ( afd.a.bits == 16 && afd.a.channels == 2 );
|
||||
if (!afd.audio) {
|
||||
Com_Printf( S_COLOR_YELLOW "WARNING: Audio capture needs 16-bit stereo" );
|
||||
afd.hasAudio = (afd.audioFormat.bits == 16 && afd.audioFormat.channels == 2);
|
||||
if (!afd.hasAudio) {
|
||||
Com_Printf("^3WARNING: Audio capture needs 16-bit stereo");
|
||||
}
|
||||
}
|
||||
|
||||
// this doesn't write a real header, but allocates the
|
||||
// This doesn't write a real header, but allocates the
|
||||
// correct amount of space at the beginning of the file
|
||||
CL_WriteAVIHeader();
|
||||
|
||||
SafeFS_Write( buffer, bufIndex, afd.f );
|
||||
afd.fileSize = bufIndex;
|
||||
Safe_FS_Write(buffer, bufferWriteIndex, afd.mainFile);
|
||||
afd.fileSize = bufferWriteIndex;
|
||||
|
||||
bufIndex = 0;
|
||||
if (!afd.isPiped) {
|
||||
bufferWriteIndex = 0;
|
||||
START_CHUNK("idx1");
|
||||
SafeFS_Write( buffer, bufIndex, afd.idxF );
|
||||
Safe_FS_Write(buffer, bufferWriteIndex, afd.indexFile);
|
||||
}
|
||||
|
||||
afd.moviSize = 4; // for the "movi" header signature
|
||||
afd.fileOpen = qtrue;
|
||||
afd.moviSize = 4; // For the "movi" header signature
|
||||
afd.isFileOpen = qtrue;
|
||||
|
||||
Com_Printf("Recording to %s\n", afd.fileName);
|
||||
|
||||
|
@ -425,58 +454,51 @@ qbool CL_OpenAVIForWriting( const char* fileNameNoExt, qbool reOpen )
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
===============
|
||||
CL_CheckFileSize
|
||||
===============
|
||||
*/
|
||||
static qbool CL_CheckFileSize( int bytesToAdd )
|
||||
qbool CL_OpenAVIForWriting( const char* fileNameNoExt )
|
||||
{
|
||||
unsigned int newFileSize;
|
||||
return OpenAVI(fileNameNoExt, qfalse);
|
||||
}
|
||||
|
||||
newFileSize =
|
||||
|
||||
static qbool CheckFileSize( int bytesToAdd )
|
||||
{
|
||||
const unsigned int newFileSize =
|
||||
afd.fileSize + // Current file size
|
||||
bytesToAdd + // What we want to add
|
||||
(afd.numIndices * 16) + // The index
|
||||
4; // The index size
|
||||
|
||||
// I assume all the operating systems
|
||||
// we target can handle a 2Gb file
|
||||
if( newFileSize > INT_MAX )
|
||||
{
|
||||
CL_OpenAVIForWriting( NULL, qtrue );
|
||||
// "AVI " limit is 1 GB (AVI 1.0)
|
||||
// "AVIX" limit is 2 GB (AVI 2.0 / OpenDML)
|
||||
if (!afd.isPiped && newFileSize > (1 << 30)) {
|
||||
OpenAVI(NULL, qtrue);
|
||||
return qtrue;
|
||||
}
|
||||
|
||||
return qfalse;
|
||||
}
|
||||
|
||||
/*
|
||||
===============
|
||||
CL_WriteAVIVideoFrame
|
||||
===============
|
||||
*/
|
||||
|
||||
void CL_WriteAVIVideoFrame( const byte* imageBuffer, int size )
|
||||
{
|
||||
int chunkOffset = afd.fileSize - afd.moviOffset - 8;
|
||||
int chunkSize = 8 + size;
|
||||
int paddingSize = PAD( size, 2 ) - size;
|
||||
byte padding[ 4 ] = { 0 };
|
||||
|
||||
if( !afd.fileOpen )
|
||||
if (!afd.isFileOpen)
|
||||
return;
|
||||
|
||||
// Chunk header + contents + padding
|
||||
if( CL_CheckFileSize( 8 + size + 2 ) )
|
||||
if (CheckFileSize(8 + size + 2))
|
||||
return;
|
||||
|
||||
bufIndex = 0;
|
||||
bufferWriteIndex = 0;
|
||||
WRITE_STRING("00dc");
|
||||
WRITE_4BYTES(size);
|
||||
|
||||
SafeFS_Write( buffer, 8, afd.f );
|
||||
SafeFS_Write( imageBuffer, size, afd.f );
|
||||
SafeFS_Write( padding, paddingSize, afd.f );
|
||||
const int chunkOffset = afd.fileSize - afd.moviOffset - 8;
|
||||
const int chunkSize = 8 + size;
|
||||
const int paddingSize = PAD(size, 2) - size;
|
||||
const byte padding[4] = { 0 };
|
||||
Safe_FS_Write(buffer, 8, afd.mainFile);
|
||||
Safe_FS_Write(imageBuffer, size, afd.mainFile);
|
||||
Safe_FS_Write(padding, paddingSize, afd.mainFile);
|
||||
afd.fileSize += (chunkSize + paddingSize);
|
||||
|
||||
afd.numVideoFrames++;
|
||||
|
@ -485,43 +507,37 @@ void CL_WriteAVIVideoFrame( const byte *imageBuffer, int size )
|
|||
if (size > afd.maxRecordSize)
|
||||
afd.maxRecordSize = size;
|
||||
|
||||
// Index
|
||||
bufIndex = 0;
|
||||
// Write index data
|
||||
if (!afd.isPiped) {
|
||||
bufferWriteIndex = 0;
|
||||
WRITE_STRING("00dc"); //dwIdentifier
|
||||
WRITE_4BYTES(0x00000010); //dwFlags (all frames are KeyFrames)
|
||||
WRITE_4BYTES(chunkOffset); //dwOffset
|
||||
WRITE_4BYTES(size); //dwLength
|
||||
SafeFS_Write( buffer, 16, afd.idxF );
|
||||
Safe_FS_Write(buffer, 16, afd.indexFile);
|
||||
}
|
||||
|
||||
afd.numIndices++;
|
||||
}
|
||||
|
||||
#define PCM_BUFFER_SIZE 44100
|
||||
|
||||
/*
|
||||
===============
|
||||
CL_WriteAVIAudioFrame
|
||||
===============
|
||||
*/
|
||||
void CL_WriteAVIAudioFrame( const byte* pcmBuffer, int size )
|
||||
{
|
||||
static byte pcmCaptureBuffer[PCM_BUFFER_SIZE] = { 0 };
|
||||
static int bytesInBuffer = 0;
|
||||
|
||||
if( !afd.audio )
|
||||
if (!afd.hasAudio)
|
||||
return;
|
||||
|
||||
if( !afd.fileOpen )
|
||||
if (!afd.isFileOpen)
|
||||
return;
|
||||
|
||||
// Chunk header + contents + padding
|
||||
if( CL_CheckFileSize( 8 + bytesInBuffer + size + 2 ) )
|
||||
if (CheckFileSize(8 + bytesInBuffer + size + 2))
|
||||
return;
|
||||
|
||||
if( bytesInBuffer + size > PCM_BUFFER_SIZE )
|
||||
{
|
||||
Com_Printf( S_COLOR_YELLOW
|
||||
"WARNING: Audio capture buffer overflow -- truncating\n" );
|
||||
if (bytesInBuffer + size > PCM_BUFFER_SIZE) {
|
||||
Com_Printf("^3WARNING: Audio capture buffer overflow -- truncating\n");
|
||||
size = PCM_BUFFER_SIZE - bytesInBuffer;
|
||||
}
|
||||
|
||||
|
@ -529,34 +545,34 @@ void CL_WriteAVIAudioFrame( const byte *pcmBuffer, int size )
|
|||
bytesInBuffer += size;
|
||||
|
||||
// Only write if we have a frame's worth of audio
|
||||
if( bytesInBuffer >= (int)ceil( (float)afd.a.rate / (float)afd.frameRate ) *
|
||||
afd.a.sampleSize )
|
||||
{
|
||||
int chunkOffset = afd.fileSize - afd.moviOffset - 8;
|
||||
int chunkSize = 8 + bytesInBuffer;
|
||||
int paddingSize = PAD( bytesInBuffer, 2 ) - bytesInBuffer;
|
||||
byte padding[ 4 ] = { 0 };
|
||||
|
||||
bufIndex = 0;
|
||||
const int bytesToWrite = (int)ceil((float)afd.audioFormat.rate / (float)afd.frameRate) * afd.audioFormat.sampleSize;
|
||||
if (bytesInBuffer >= bytesToWrite) {
|
||||
bufferWriteIndex = 0;
|
||||
WRITE_STRING("01wb");
|
||||
WRITE_4BYTES(bytesInBuffer);
|
||||
|
||||
SafeFS_Write( buffer, 8, afd.f );
|
||||
SafeFS_Write( pcmCaptureBuffer, bytesInBuffer, afd.f );
|
||||
SafeFS_Write( padding, paddingSize, afd.f );
|
||||
const int chunkOffset = afd.fileSize - afd.moviOffset - 8;
|
||||
const int chunkSize = 8 + bytesInBuffer;
|
||||
const int paddingSize = PAD(bytesInBuffer, 2) - bytesInBuffer;
|
||||
const byte padding[4] = { 0 };
|
||||
Safe_FS_Write(buffer, 8, afd.mainFile);
|
||||
Safe_FS_Write(pcmCaptureBuffer, bytesInBuffer, afd.mainFile);
|
||||
Safe_FS_Write(padding, paddingSize, afd.mainFile);
|
||||
afd.fileSize += (chunkSize + paddingSize);
|
||||
|
||||
afd.numAudioFrames++;
|
||||
afd.moviSize += (chunkSize + paddingSize);
|
||||
afd.a.totalBytes += bytesInBuffer;
|
||||
afd.audioFormat.totalBytes += bytesInBuffer;
|
||||
|
||||
// Index
|
||||
bufIndex = 0;
|
||||
// Write index data
|
||||
if (!afd.isPiped) {
|
||||
bufferWriteIndex = 0;
|
||||
WRITE_STRING("01wb"); //dwIdentifier
|
||||
WRITE_4BYTES(0); //dwFlags
|
||||
WRITE_4BYTES(chunkOffset); //dwOffset
|
||||
WRITE_4BYTES(bytesInBuffer); //dwLength
|
||||
SafeFS_Write( buffer, 16, afd.idxF );
|
||||
Safe_FS_Write(buffer, 16, afd.indexFile);
|
||||
}
|
||||
|
||||
afd.numIndices++;
|
||||
|
||||
|
@ -564,109 +580,98 @@ void CL_WriteAVIAudioFrame( const byte *pcmBuffer, int size )
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
===============
|
||||
CL_TakeVideoFrame
|
||||
===============
|
||||
*/
|
||||
void CL_TakeVideoFrame( void )
|
||||
|
||||
void CL_TakeVideoFrame()
|
||||
{
|
||||
// AVI file isn't open
|
||||
if( !afd.fileOpen )
|
||||
if (!afd.isFileOpen)
|
||||
return;
|
||||
|
||||
re.TakeVideoFrame( afd.width, afd.height,
|
||||
afd.cBuffer, afd.eBuffer, afd.motionJpeg );
|
||||
re.TakeVideoFrame(afd.width, afd.height, afd.captureBuffer, afd.encodeBuffer, afd.useMotionJpeg);
|
||||
}
|
||||
|
||||
/*
|
||||
===============
|
||||
CL_CloseAVI
|
||||
|
||||
Closes the AVI file and writes an index chunk
|
||||
===============
|
||||
*/
|
||||
static qbool CloseAVI( closeMode_t closeMode )
|
||||
static qbool FixIndices()
|
||||
{
|
||||
int indexRemainder;
|
||||
int indexSize = afd.numIndices * 16;
|
||||
const char *idxFileName = va( "%s" INDEX_FILE_EXTENSION, afd.fileName );
|
||||
|
||||
// AVI file isn't open
|
||||
if( !afd.fileOpen )
|
||||
return qfalse;
|
||||
|
||||
afd.fileOpen = qfalse;
|
||||
|
||||
FS_Seek( afd.idxF, 4, FS_SEEK_SET );
|
||||
bufIndex = 0;
|
||||
FS_Seek(afd.indexFile, 4, FS_SEEK_SET);
|
||||
bufferWriteIndex = 0;
|
||||
WRITE_4BYTES(indexSize);
|
||||
SafeFS_Write( buffer, bufIndex, afd.idxF );
|
||||
FS_FCloseFile( afd.idxF );
|
||||
|
||||
// Write index
|
||||
Safe_FS_Write(buffer, bufferWriteIndex, afd.indexFile);
|
||||
Safe_FS_Close(&afd.indexFile); // needed in case the follow-up open fails
|
||||
|
||||
// Open the temp index file
|
||||
if( ( indexSize = FS_FOpenFileRead( idxFileName,
|
||||
&afd.idxF, qtrue ) ) <= 0 )
|
||||
{
|
||||
FS_FCloseFile( afd.f );
|
||||
const char* const idxFileName = va("%s" INDEX_FILE_EXTENSION, afd.fileName);
|
||||
if ((indexSize = FS_FOpenFileRead(idxFileName, &afd.indexFile, qtrue)) <= 0) {
|
||||
return qfalse;
|
||||
}
|
||||
|
||||
indexRemainder = indexSize;
|
||||
|
||||
// Append index to end of avi file
|
||||
while( indexRemainder > MAX_AVI_BUFFER )
|
||||
{
|
||||
FS_Read( buffer, MAX_AVI_BUFFER, afd.idxF );
|
||||
SafeFS_Write( buffer, MAX_AVI_BUFFER, afd.f );
|
||||
int indexRemainder = indexSize;
|
||||
while (indexRemainder > MAX_AVI_BUFFER) {
|
||||
FS_Read(buffer, MAX_AVI_BUFFER, afd.indexFile);
|
||||
Safe_FS_Write(buffer, MAX_AVI_BUFFER, afd.mainFile);
|
||||
afd.fileSize += MAX_AVI_BUFFER;
|
||||
indexRemainder -= MAX_AVI_BUFFER;
|
||||
}
|
||||
FS_Read( buffer, indexRemainder, afd.idxF );
|
||||
SafeFS_Write( buffer, indexRemainder, afd.f );
|
||||
FS_Read(buffer, indexRemainder, afd.indexFile);
|
||||
Safe_FS_Write(buffer, indexRemainder, afd.mainFile);
|
||||
afd.fileSize += indexRemainder;
|
||||
FS_FCloseFile( afd.idxF );
|
||||
|
||||
// Remove temp index file
|
||||
// Close and remove temp index file
|
||||
Safe_FS_Close(&afd.indexFile);
|
||||
FS_HomeRemove(idxFileName);
|
||||
|
||||
// Write the real header
|
||||
FS_Seek( afd.f, 0, FS_SEEK_SET );
|
||||
FS_Seek(afd.mainFile, 0, FS_SEEK_SET);
|
||||
CL_WriteAVIHeader();
|
||||
|
||||
bufIndex = 4;
|
||||
bufferWriteIndex = 4;
|
||||
WRITE_4BYTES(afd.fileSize - 8); // "RIFF" size
|
||||
|
||||
bufIndex = afd.moviOffset + 4; // Skip "LIST"
|
||||
bufferWriteIndex = afd.moviOffset + 4; // Skip "LIST"
|
||||
WRITE_4BYTES(afd.moviSize);
|
||||
|
||||
SafeFS_Write( buffer, bufIndex, afd.f );
|
||||
|
||||
Z_Free( afd.cBuffer );
|
||||
Z_Free( afd.eBuffer );
|
||||
FS_FCloseFile( afd.f );
|
||||
|
||||
Com_Printf( "Wrote %d:%d V:A frames to %s\n", afd.numVideoFrames, afd.numAudioFrames, afd.fileName );
|
||||
|
||||
if ( closeMode == CM_SEQUENCE_COMPLETE )
|
||||
S_StopAllSounds();
|
||||
Safe_FS_Write(buffer, bufferWriteIndex, afd.mainFile);
|
||||
|
||||
return qtrue;
|
||||
}
|
||||
|
||||
qbool CL_CloseAVI( void )
|
||||
|
||||
// Closes the AVI file and optionally writes an index chunk
|
||||
static qbool CloseAVI( closeMode_t closeMode )
|
||||
{
|
||||
if (!afd.isFileOpen)
|
||||
return qfalse;
|
||||
|
||||
afd.isFileOpen = qfalse;
|
||||
|
||||
qbool result = qtrue;
|
||||
if (!afd.isPiped) {
|
||||
result = FixIndices();
|
||||
}
|
||||
|
||||
Safe_Z_Free(&afd.captureBuffer);
|
||||
Safe_Z_Free(&afd.encodeBuffer);
|
||||
Safe_FS_Close(&afd.mainFile);
|
||||
Safe_FS_Close(&afd.indexFile);
|
||||
|
||||
Com_Printf("Processed %d:%d V:A frames for %s\n", afd.numVideoFrames, afd.numAudioFrames, afd.fileName);
|
||||
|
||||
if (closeMode == CM_SEQUENCE_COMPLETE) {
|
||||
S_StopAllSounds();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
qbool CL_CloseAVI()
|
||||
{
|
||||
return CloseAVI(CM_SEQUENCE_COMPLETE);
|
||||
}
|
||||
|
||||
/*
|
||||
===============
|
||||
CL_VideoRecording
|
||||
===============
|
||||
*/
|
||||
qbool CL_VideoRecording( void )
|
||||
|
||||
qbool CL_VideoRecording()
|
||||
{
|
||||
return afd.fileOpen;
|
||||
return afd.isFileOpen;
|
||||
}
|
||||
|
|
|
@ -698,7 +698,7 @@ static intptr_t CL_CgameSystemCalls( intptr_t *args )
|
|||
|
||||
case CG_EXT_NDP_STARTVIDEO:
|
||||
Cvar_Set( cl_aviFrameRate->name, va( "%d", (int)args[2] ) );
|
||||
return CL_OpenAVIForWriting( va( "videos/%s", (const char*)VMA(1) ), qfalse );
|
||||
return CL_OpenAVIForWriting( VMA(1) );
|
||||
|
||||
case CG_EXT_NDP_STOPVIDEO:
|
||||
CL_CloseAVI();
|
||||
|
|
|
@ -41,6 +41,12 @@ cvar_t* cl_showSend;
|
|||
cvar_t *cl_timedemo;
|
||||
cvar_t *cl_aviFrameRate;
|
||||
cvar_t *cl_aviMotionJpeg;
|
||||
cvar_t *cl_ffmpeg;
|
||||
cvar_t *cl_ffmpegCommand;
|
||||
cvar_t *cl_ffmpegExePath;
|
||||
cvar_t *cl_ffmpegOutPath;
|
||||
cvar_t *cl_ffmpegOutExt;
|
||||
cvar_t *cl_ffmpegLog;
|
||||
|
||||
cvar_t *cl_allowDownload;
|
||||
cvar_t *cl_inGameVideo;
|
||||
|
@ -1951,7 +1957,7 @@ static void CL_Video_f()
|
|||
1900+t.tm_year, 1+t.tm_mon, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec );
|
||||
}
|
||||
|
||||
CL_OpenAVIForWriting( va( "videos/%s", s ), qfalse );
|
||||
CL_OpenAVIForWriting( s );
|
||||
}
|
||||
|
||||
|
||||
|
@ -2148,6 +2154,41 @@ static const cvarTableItem_t cl_cvars[] =
|
|||
&cl_aviMotionJpeg, "cl_aviMotionJpeg", "1", CVAR_ARCHIVE, CVART_BOOL, NULL, NULL, help_cl_aviMotionJpeg,
|
||||
"AVI motion JPEG", CVARCAT_DEMO, "", ""
|
||||
},
|
||||
{
|
||||
&cl_ffmpeg, "cl_ffmpeg", "0", CVAR_ARCHIVE, CVART_BOOL, NULL, NULL,
|
||||
"use FFmpeg for video export\n"
|
||||
"Pipes video through FFmpeg instead of writing raw .avi files.",
|
||||
"Use FFmpeg", CVARCAT_DEMO, "Uses FFmpeg instead of writing raw .avi files", ""
|
||||
},
|
||||
{
|
||||
&cl_ffmpegCommand, "cl_ffmpegCommand", "-movflags faststart -bf 2 -c:v libx264 -preset medium -crf 23 -vf format=yuv420p -c:a aac -b:a 320k",
|
||||
CVAR_ARCHIVE, CVART_STRING, NULL, NULL,
|
||||
"FFmpeg encode settings\n"
|
||||
"The full command-line options for the output file.",
|
||||
"FFmpeg encode settings", CVARCAT_DEMO, "Command-line options for the output file", ""
|
||||
},
|
||||
{
|
||||
&cl_ffmpegExePath, "cl_ffmpegExePath", "ffmpeg", CVAR_ARCHIVE, CVART_STRING, NULL, NULL,
|
||||
"FFmpeg executable path\n"
|
||||
"The path cannot contain spaces.",
|
||||
"FFmpeg executable path", CVARCAT_DEMO, "The path cannot contain spaces", ""
|
||||
},
|
||||
{
|
||||
&cl_ffmpegOutPath, "cl_ffmpegOutPath", "", CVAR_ARCHIVE, CVART_STRING, NULL, NULL,
|
||||
"FFmpeg output directory\n"
|
||||
"Leave empty to write to cpma/videos as with .avi files.",
|
||||
"FFmpeg output directory", CVARCAT_DEMO, "Leave empty to write to cpma/videos as with .avi files", ""
|
||||
},
|
||||
{
|
||||
&cl_ffmpegOutExt, "cl_ffmpegOutExt", "mp4", CVAR_ARCHIVE, CVART_STRING, NULL, NULL, "FFmpeg output file extension",
|
||||
"FFmpeg output file extension", CVARCAT_DEMO, "", ""
|
||||
},
|
||||
{
|
||||
&cl_ffmpegLog, "cl_ffmpegLog", "0", CVAR_ARCHIVE, CVART_BOOL, NULL, NULL,
|
||||
"FFmpeg log file creation\n"
|
||||
"Creates 1 log file per capture.",
|
||||
"FFmpeg log file creation", CVARCAT_DEMO, "Creates 1 log file per capture", ""
|
||||
},
|
||||
{ &rconAddress, "rconAddress", "", 0, CVART_STRING, NULL, NULL, help_rconAddress },
|
||||
{
|
||||
&cl_maxpackets, "cl_maxpackets", "125", CVAR_ARCHIVE, CVART_INTEGER, "15", "125", "max. packet upload rate",
|
||||
|
|
|
@ -371,6 +371,12 @@ extern cvar_t *cl_serverStatusResendTime;
|
|||
extern cvar_t *cl_timedemo;
|
||||
extern cvar_t *cl_aviFrameRate;
|
||||
extern cvar_t *cl_aviMotionJpeg;
|
||||
extern cvar_t *cl_ffmpeg;
|
||||
extern cvar_t *cl_ffmpegCommand;
|
||||
extern cvar_t *cl_ffmpegExePath;
|
||||
extern cvar_t *cl_ffmpegOutPath;
|
||||
extern cvar_t *cl_ffmpegOutExt;
|
||||
extern cvar_t *cl_ffmpegLog;
|
||||
|
||||
extern cvar_t *cl_allowDownload; // 0=off, 1=CNQ3, -1=id
|
||||
extern cvar_t *cl_inGameVideo;
|
||||
|
@ -547,12 +553,12 @@ qbool CL_Netchan_Process( netchan_t *chan, msg_t *msg );
|
|||
//
|
||||
// cl_avi.c
|
||||
//
|
||||
qbool CL_OpenAVIForWriting( const char *fileNameNoExt, qbool reOpen );
|
||||
void CL_TakeVideoFrame( void );
|
||||
qbool CL_OpenAVIForWriting( const char *fileNameNoExt );
|
||||
void CL_TakeVideoFrame();
|
||||
void CL_WriteAVIVideoFrame( const byte *imageBuffer, int size );
|
||||
void CL_WriteAVIAudioFrame( const byte *pcmBuffer, int size );
|
||||
qbool CL_CloseAVI( void );
|
||||
qbool CL_VideoRecording( void );
|
||||
qbool CL_CloseAVI();
|
||||
qbool CL_VideoRecording();
|
||||
|
||||
//
|
||||
// cl_download.cpp
|
||||
|
|
|
@ -174,6 +174,14 @@ or configs will never get loaded from disk!
|
|||
#define MAX_SEARCH_PATHS 4096
|
||||
#define MAX_FILEHASH_SIZE 1024
|
||||
|
||||
#if defined( _WIN32 )
|
||||
#define Sys_OpenPipeWrite(Command) _popen(Command, "wb")
|
||||
#define Sys_ClosePipe(Pipe) _pclose(Pipe)
|
||||
#else
|
||||
#define Sys_OpenPipeWrite(Command) popen(Command, "w")
|
||||
#define Sys_ClosePipe(Pipe) pclose(Pipe)
|
||||
#endif
|
||||
|
||||
typedef struct fileInPack_s {
|
||||
char *name; // name of the file
|
||||
unsigned long pos; // file info position in zip
|
||||
|
@ -228,6 +236,7 @@ typedef union {
|
|||
typedef struct {
|
||||
qfile_gut file;
|
||||
qbool unique;
|
||||
qbool isPipe;
|
||||
} qfile_ut;
|
||||
|
||||
typedef struct {
|
||||
|
@ -316,6 +325,7 @@ static fileHandle_t FS_HandleForFile()
|
|||
{
|
||||
for ( int i = 1; i < MAX_FILE_HANDLES; ++i ) {
|
||||
if ( fsh[i].handleFiles.file.o == NULL ) {
|
||||
Com_Memset( &fsh[i], 0, sizeof( fsh[i] ) );
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
@ -378,7 +388,7 @@ FS_ReplaceSeparators
|
|||
Fix things up differently for win/unix/mac
|
||||
====================
|
||||
*/
|
||||
static void FS_ReplaceSeparators( char *path ) {
|
||||
void FS_ReplaceSeparators( char *path ) {
|
||||
char *s;
|
||||
|
||||
for ( s = path ; *s ; s++ ) {
|
||||
|
@ -711,8 +721,12 @@ void FS_FCloseFile( fileHandle_t f ) {
|
|||
|
||||
// we didn't find it as a pak, so close it as a unique file
|
||||
if (fsh[f].handleFiles.file.o) {
|
||||
if (fsh[f].handleFiles.isPipe) {
|
||||
Sys_ClosePipe( fsh[f].handleFiles.file.o );
|
||||
} else {
|
||||
fclose( fsh[f].handleFiles.file.o );
|
||||
}
|
||||
}
|
||||
Com_Memset( &fsh[f], 0, sizeof( fsh[f] ) );
|
||||
}
|
||||
|
||||
|
@ -757,6 +771,21 @@ fileHandle_t FS_FOpenFileWrite( const char *filename ) {
|
|||
return f;
|
||||
}
|
||||
|
||||
fileHandle_t FS_OpenPipeWrite( const char* command ) {
|
||||
fileHandle_t f = FS_HandleForFile();
|
||||
fsh[f].zipFile = qfalse;
|
||||
|
||||
fsh[f].handleFiles.file.o = Sys_OpenPipeWrite( command );
|
||||
fsh[f].handleFiles.isPipe = qtrue;
|
||||
Q_strncpyz( fsh[f].name, "$pipe", sizeof(fsh[f].name));
|
||||
fsh[f].handleSync = qfalse;
|
||||
if ( !fsh[f].handleFiles.file.o ) {
|
||||
f = 0;
|
||||
}
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
/*
|
||||
===========
|
||||
FS_FOpenFileAppend
|
||||
|
|
|
@ -677,6 +677,8 @@ void FS_FreeFileList( char **list );
|
|||
qbool FS_FileExists( const char *file ); // checks in current game dir
|
||||
qbool FS_FileExistsEx( const char *file, qbool curGameDir ); // if curGameDir is qfalse, checks in "baseq3"
|
||||
|
||||
void FS_ReplaceSeparators( char *path );
|
||||
|
||||
char* FS_BuildOSPath( const char *base, const char *game, const char *qpath );
|
||||
|
||||
int FS_LoadStack();
|
||||
|
@ -687,6 +689,8 @@ int FS_GetModList( char *listbuf, int bufsize );
|
|||
fileHandle_t FS_FOpenFileWrite( const char *qpath );
|
||||
// will properly create any needed paths and deal with seperater character issues
|
||||
|
||||
fileHandle_t FS_OpenPipeWrite( const char* command );
|
||||
|
||||
fileHandle_t FS_SV_FOpenFileWrite( const char *filename );
|
||||
int FS_SV_FOpenFileRead( const char *filename, fileHandle_t *fp );
|
||||
void FS_SV_Rename( const char *from, const char *to );
|
||||
|
|
|
@ -1059,6 +1059,12 @@ int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLin
|
|||
return 0;
|
||||
}
|
||||
|
||||
// prevent child processes from spawning a command prompt window
|
||||
#ifndef DEDICATED
|
||||
AllocConsole();
|
||||
ShowWindow( GetConsoleWindow(), SW_HIDE );
|
||||
#endif
|
||||
|
||||
// done here so the early console can be shown on the primary monitor
|
||||
WIN_InitMonitorList();
|
||||
|
||||
|
|
Loading…
Reference in New Issue