From 2d1c9fdd89de7f88aaeb32dc9ae521abea60688c Mon Sep 17 00:00:00 2001 From: havoc Date: Wed, 27 Oct 2004 18:11:36 +0000 Subject: [PATCH] greatly improved video capture speed by generating the files much more directly (less processing) added a raw video .rgb output mode (making only one file instead of thousands, for a slight speed gain, assuming you have tools that can read it) video capture now maintains sound sync by duplicating frames to keep up a consistent video output speed (warning: do not attempt higher speeds than your machine is capable of, it WILL fall behind and get progressively slower very fast) rewrote much of Host_FilterTime to improve video capture and simplify timing in general removed host_minfps (now uses sys_ticrate instead) renamed host_maxfps to cl_maxfps (as it only ever affected the client anyway) now runs multiple server frames if host is falling behind, based on sys_ticrate (mainly useful to avoid slowing down when client is rendering really slow) git-svn-id: svn://svn.icculus.org/twilight/trunk/darkplaces@4717 d7cf8633-e32d-0410-b094-e92efae38249 --- cl_screen.c | 221 ++++++++++++++++++++++++++++++++++++++++++++-------- glquake.h | 6 ++ host.c | 148 ++++++++++++++++++----------------- snd_mix.c | 86 ++++---------------- 4 files changed, 287 insertions(+), 174 deletions(-) diff --git a/cl_screen.c b/cl_screen.c index 5d22a2d5..dc31497b 100644 --- a/cl_screen.c +++ b/cl_screen.c @@ -21,7 +21,9 @@ cvar_t vid_pixelaspect = {CVAR_SAVE, "vid_pixelaspect", "1"}; cvar_t scr_screenshot_jpeg = {CVAR_SAVE, "scr_screenshot_jpeg","0"}; cvar_t scr_screenshot_jpeg_quality = {CVAR_SAVE, "scr_screenshot_jpeg_quality","0.9"}; cvar_t scr_screenshot_name = {0, "scr_screenshot_name","dp"}; -cvar_t cl_avidemo = {0, "cl_avidemo", "0"}; +cvar_t cl_capturevideo = {0, "cl_capturevideo", "0"}; +cvar_t cl_capturevideo_fps = {0, "cl_capturevideo_fps", "30"}; +cvar_t cl_capturevideo_raw = {0, "cl_capturevideo_raw", "0"}; cvar_t r_textshadow = {0, "r_textshadow", "0"}; cvar_t r_letterbox = {0, "r_letterbox", "0"}; @@ -474,7 +476,9 @@ void CL_Screen_Init(void) Cvar_RegisterVariable (&vid_pixelaspect); Cvar_RegisterVariable (&scr_screenshot_jpeg); Cvar_RegisterVariable (&scr_screenshot_jpeg_quality); - Cvar_RegisterVariable (&cl_avidemo); + Cvar_RegisterVariable (&cl_capturevideo); + Cvar_RegisterVariable (&cl_capturevideo_fps); + Cvar_RegisterVariable (&cl_capturevideo_raw); Cvar_RegisterVariable (&r_textshadow); Cvar_RegisterVariable (&r_letterbox); @@ -711,48 +715,199 @@ void SCR_ScreenShot_f (void) shotnumber++; } -static int cl_avidemo_frame = 0; +typedef enum capturevideoformat_e +{ + CAPTUREVIDEOFORMAT_TARGA, + CAPTUREVIDEOFORMAT_JPEG, + CAPTUREVIDEOFORMAT_RAW +} +capturevideoformat_t; + +qboolean cl_capturevideo_active = false; +capturevideoformat_t cl_capturevideo_format; +static double cl_capturevideo_starttime = 0; +double cl_capturevideo_framerate = 0; +static int cl_capturevideo_soundrate = 0; +static int cl_capturevideo_frame = 0; +static qbyte *cl_capturevideo_buffer = NULL; +static qfile_t *cl_capturevideo_videofile = NULL; +static qfile_t *cl_capturevideo_soundfile = NULL; + +void SCR_CaptureVideo_BeginVideo(void) +{ + qbyte out[44]; + if (cl_capturevideo_active) + return; + // soundrate is figured out on the first SoundFrame + cl_capturevideo_active = true; + cl_capturevideo_starttime = Sys_DoubleTime(); + cl_capturevideo_framerate = bound(1, cl_capturevideo_fps.value, 1000); + cl_capturevideo_soundrate = 0; + cl_capturevideo_frame = 0; + cl_capturevideo_buffer = Mem_Alloc(tempmempool, vid.realwidth * vid.realheight * (3+3+3) + 18); + + if (cl_capturevideo_raw.integer) + { + cl_capturevideo_format = CAPTUREVIDEOFORMAT_RAW; + cl_capturevideo_videofile = FS_Open ("video/dpvideo.rgb", "wb", false); + } + else if (scr_screenshot_jpeg.integer) + { + cl_capturevideo_format = CAPTUREVIDEOFORMAT_JPEG; + cl_capturevideo_videofile = NULL; + } + else + { + cl_capturevideo_format = CAPTUREVIDEOFORMAT_TARGA; + cl_capturevideo_videofile = NULL; + } -void SCR_CaptureAVIDemo(void) + cl_capturevideo_soundfile = FS_Open ("video/dpvideo.wav", "wb", false); + + // wave header will be filled out when video ends + memset(out, 0, 44); + FS_Write (cl_capturevideo_soundfile, out, 44); +} + +void SCR_CaptureVideo_EndVideo(void) { - static qbyte *avi_buffer1 = NULL; - static qbyte *avi_buffer2 = NULL; - static qbyte *avi_buffer3 = NULL; - char filename[32]; - qboolean jpeg = (scr_screenshot_jpeg.integer != 0); + int i, n; + qbyte out[44]; + if (!cl_capturevideo_active) + return; + cl_capturevideo_active = false; - if (!cl_avidemo.integer) + if (cl_capturevideo_videofile) { - if (avi_buffer1 != NULL) - { - Mem_Free (avi_buffer1); - Mem_Free (avi_buffer2); - Mem_Free (avi_buffer3); - avi_buffer1 = NULL; - avi_buffer2 = NULL; - avi_buffer3 = NULL; - } - cl_avidemo_frame = 0; - return; + FS_Close(cl_capturevideo_videofile); + cl_capturevideo_videofile = NULL; + } + + // finish the wave file + if (cl_capturevideo_soundfile) + { + i = FS_Tell (cl_capturevideo_soundfile); + //"RIFF", (int) unknown (chunk size), "WAVE", + //"fmt ", (int) 16 (chunk size), (short) format 1 (uncompressed PCM), (short) 2 channels, (int) unknown rate, (int) unknown bytes per second, (short) 4 bytes per sample (channels * bytes per channel), (short) 16 bits per channel + //"data", (int) unknown (chunk size) + memcpy (out, "RIFF****WAVEfmt \x10\x00\x00\x00\x01\x00\x02\x00********\x04\x00\x10\0data****", 44); + // the length of the whole RIFF chunk + n = i - 8; + out[4] = (n) & 0xFF; + out[5] = (n >> 8) & 0xFF; + out[6] = (n >> 16) & 0xFF; + out[7] = (n >> 24) & 0xFF; + // rate + n = cl_capturevideo_soundrate; + out[24] = (n) & 0xFF; + out[25] = (n >> 8) & 0xFF; + out[26] = (n >> 16) & 0xFF; + out[27] = (n >> 24) & 0xFF; + // bytes per second (rate * channels * bytes per channel) + n = cl_capturevideo_soundrate * 2 * 2; + out[28] = (n) & 0xFF; + out[29] = (n >> 8) & 0xFF; + out[30] = (n >> 16) & 0xFF; + out[31] = (n >> 24) & 0xFF; + // the length of the data chunk + n = i - 44; + out[40] = (n) & 0xFF; + out[41] = (n >> 8) & 0xFF; + out[42] = (n >> 16) & 0xFF; + out[43] = (n >> 24) & 0xFF; + FS_Seek (cl_capturevideo_soundfile, 0, SEEK_SET); + FS_Write (cl_capturevideo_soundfile, out, 44); + FS_Close (cl_capturevideo_soundfile); + cl_capturevideo_soundfile = NULL; } - if (avi_buffer1 == NULL) + if (cl_capturevideo_buffer) { - avi_buffer1 = Mem_Alloc(tempmempool, vid.realwidth * vid.realheight * 3); - avi_buffer2 = Mem_Alloc(tempmempool, vid.realwidth * vid.realheight * 3); - avi_buffer3 = Mem_Alloc(tempmempool, vid.realwidth * vid.realheight * 3 + 18); + Mem_Free (cl_capturevideo_buffer); + cl_capturevideo_buffer = NULL; } - sprintf(filename, "video/dp%06d.%s", cl_avidemo_frame, jpeg ? "jpg" : "tga"); + cl_capturevideo_starttime = 0; + cl_capturevideo_framerate = 0; + cl_capturevideo_frame = 0; +} - if (SCR_ScreenShot(filename, avi_buffer1, avi_buffer2, avi_buffer3, vid.realx, vid.realy, vid.realwidth, vid.realheight, false, false, false, jpeg)) - cl_avidemo_frame++; - else +qboolean SCR_CaptureVideo_VideoFrame(void) +{ + int x = vid.realx, y = vid.realy, width = vid.realwidth, height = vid.realheight; + char filename[32]; + //return SCR_ScreenShot(filename, cl_capturevideo_buffer, cl_capturevideo_buffer + vid.realwidth * vid.realheight * 3, cl_capturevideo_buffer + vid.realwidth * vid.realheight * 6, vid.realx, vid.realy, vid.realwidth, vid.realheight, false, false, false, jpeg); + // speed is critical here, so do saving as directly as possible + if (!r_render.integer) + return false; + switch (cl_capturevideo_format) + { + case CAPTUREVIDEOFORMAT_RAW: + qglReadPixels (x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, cl_capturevideo_buffer); + CHECKGLERROR + return FS_Write (cl_capturevideo_videofile, cl_capturevideo_buffer, width*height*3); + case CAPTUREVIDEOFORMAT_JPEG: + sprintf(filename, "video/dp%06d.jpg", cl_capturevideo_frame); + qglReadPixels (x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, cl_capturevideo_buffer); + CHECKGLERROR + return JPEG_SaveImage_preflipped (filename, width, height, cl_capturevideo_buffer); + case CAPTUREVIDEOFORMAT_TARGA: + //return Image_WriteTGARGB_preflipped (filename, width, height, cl_capturevideo_buffer, cl_capturevideo_buffer + vid.realwidth * vid.realheight * 3, ); + memset (cl_capturevideo_buffer, 0, 18); + cl_capturevideo_buffer[2] = 2; // uncompressed type + cl_capturevideo_buffer[12] = (width >> 0) & 0xFF; + cl_capturevideo_buffer[13] = (width >> 8) & 0xFF; + cl_capturevideo_buffer[14] = (height >> 0) & 0xFF; + cl_capturevideo_buffer[15] = (height >> 8) & 0xFF; + cl_capturevideo_buffer[16] = 24; // pixel size + qglReadPixels (x, y, width, height, GL_BGR, GL_UNSIGNED_BYTE, cl_capturevideo_buffer + 18); + CHECKGLERROR + sprintf(filename, "video/dp%06d.tga", cl_capturevideo_frame); + return FS_WriteFile (filename, cl_capturevideo_buffer, width*height*3 + 18); + default: + return false; + } +} + +void SCR_CaptureVideo_SoundFrame(qbyte *bufstereo16le, size_t length, int rate) +{ + cl_capturevideo_soundrate = rate; + if (FS_Write (cl_capturevideo_soundfile, bufstereo16le, 4 * length) < 4 * length) + { + Cvar_SetValueQuick(&cl_capturevideo, 0); + Con_Printf("video sound saving failed on frame %i, out of disk space? stopping video capture.\n", cl_capturevideo_frame); + SCR_CaptureVideo_EndVideo(); + } +} + +void SCR_CaptureVideo(void) +{ + int newframenum; + if (cl_capturevideo.integer) { - Cvar_SetValueQuick(&cl_avidemo, 0); - Con_Printf("avi saving failed on frame %i, out of disk space? stopping avi demo capture.\n", cl_avidemo_frame); - cl_avidemo_frame = 0; + if (!cl_capturevideo_active) + SCR_CaptureVideo_BeginVideo(); + if (cl_capturevideo_framerate != cl_capturevideo_fps.value) + { + Con_Printf("You can not change the video framerate while recording a video.\n"); + Cvar_SetValueQuick(&cl_capturevideo_fps, cl_capturevideo_framerate); + } + newframenum = (Sys_DoubleTime() - cl_capturevideo_starttime) * cl_capturevideo_framerate; + while (cl_capturevideo_frame < newframenum) + { + if (SCR_CaptureVideo_VideoFrame()) + cl_capturevideo_frame++; + else + { + Cvar_SetValueQuick(&cl_capturevideo, 0); + Con_Printf("video saving failed on frame %i, out of disk space? stopping avi demo capture.\n", cl_capturevideo_frame); + SCR_CaptureVideo_EndVideo(); + break; + } + } } + else if (cl_capturevideo_active) + SCR_CaptureVideo_EndVideo(); } /* @@ -962,7 +1117,7 @@ void CL_UpdateScreen(void) if (!scr_initialized || !con_initialized || vid_hidden) return; // not initialized yet - SCR_CaptureAVIDemo(); + SCR_CaptureVideo(); if (cls.signon == SIGNONS) R_TimeReport("other"); diff --git a/glquake.h b/glquake.h index cc01ac78..d2646e38 100644 --- a/glquake.h +++ b/glquake.h @@ -675,6 +675,12 @@ extern int gl_support_shading_language_100; #define GL_SHADING_LANGUAGE_VERSION_ARB 0x8B8C #endif +// GL_EXT_bgr +#define GL_BGR 0x80E0 + +// GL_EXT_bgra +#define GL_BGRA 0x80E1 + #define DEBUGGL #ifdef DEBUGGL diff --git a/host.c b/host.c index 9156d886..334817de 100644 --- a/host.c +++ b/host.c @@ -67,10 +67,8 @@ cvar_t host_framerate = {0, "host_framerate","0"}; cvar_t host_speeds = {0, "host_speeds","0"}; // LordHavoc: framerate independent slowmo cvar_t slowmo = {0, "slowmo", "1.0"}; -// LordHavoc: game logic lower cap on framerate (if framerate is below this is, it pretends it is this, so game logic will run normally) -cvar_t host_minfps = {CVAR_SAVE, "host_minfps", "10"}; // LordHavoc: framerate upper cap -cvar_t host_maxfps = {CVAR_SAVE, "host_maxfps", "1000"}; +cvar_t cl_maxfps = {CVAR_SAVE, "cl_maxfps", "1000"}; // print broadcast messages in dedicated mode cvar_t sv_echobprint = {CVAR_SAVE, "sv_echobprint", "1"}; @@ -237,8 +235,7 @@ void Host_InitLocal (void) Cvar_RegisterVariable (&host_framerate); Cvar_RegisterVariable (&host_speeds); Cvar_RegisterVariable (&slowmo); - Cvar_RegisterVariable (&host_minfps); - Cvar_RegisterVariable (&host_maxfps); + Cvar_RegisterVariable (&cl_maxfps); Cvar_RegisterVariable (&sv_echobprint); @@ -552,68 +549,65 @@ Host_FilterTime Returns false if the time is too short to run a frame =================== */ -extern cvar_t cl_avidemo; +extern qboolean cl_capturevideo_active; +extern double cl_capturevideo_framerate; qboolean Host_FilterTime (double time) { double timecap, timeleft; realtime += time; - if (slowmo.value < 0.0f) - Cvar_SetValue("slowmo", 0.0f); - if (host_minfps.value < 10.0f) - Cvar_SetValue("host_minfps", 10.0f); - if (host_maxfps.value < host_minfps.value) - Cvar_SetValue("host_maxfps", host_minfps.value); - if (cl_avidemo.value < 0.1f && cl_avidemo.value != 0.0f) - Cvar_SetValue("cl_avidemo", 0.0f); + if (sys_ticrate.value < 0.01 || sys_ticrate.value > 0.1) + Cvar_SetValue("sys_ticrate", bound(0.01, sys_ticrate.value, 0.1)); + if (slowmo.value < 0) + Cvar_SetValue("slowmo", 0); + if (host_framerate.value < 0.00001 && host_framerate.value != 0) + Cvar_SetValue("host_framerate", 0); + if (cl_maxfps.value < 1) + Cvar_SetValue("cl_maxfps", 1); - // check if framerate is too high - if (!cls.timedemo) + if (cls.timedemo) { - // default to sys_ticrate (server framerate - presumably low) unless we - // have a good reason to run faster + // disable time effects during timedemo + cl.frametime = host_realframetime = host_frametime = realtime - oldrealtime; + oldrealtime = realtime; + return true; + } + + // check if framerate is too high + // default to sys_ticrate (server framerate - presumably low) unless we + // have a good reason to run faster + timecap = host_framerate.value; + if (!timecap) timecap = sys_ticrate.value; - if (cls.state != ca_dedicated) - { - if (cl_avidemo.value >= 0.1f) - timecap = 1.0 / (double)cl_avidemo.value; - else if (vid_activewindow) - timecap = 1.0 / host_maxfps.value; - } + if (cls.state != ca_dedicated) + { + if (cl_capturevideo_active) + timecap = 1.0 / cl_capturevideo_framerate; + else if (vid_activewindow) + timecap = 1.0 / cl_maxfps.value; + } - timeleft = oldrealtime + timecap - realtime; - if (timeleft > 0) - { - // don't totally hog the CPU - if (timeleft >= 0.02) - Sys_Sleep((int)(timeleft * 1000) - 5); - return false; - } + timeleft = (oldrealtime - realtime) + timecap; + if (timeleft > 0) + { + // don't totally hog the CPU + if (timeleft >= 0.03) + Sys_Sleep((int)(timeleft * 1000) - 10); + return false; } // LordHavoc: copy into host_realframetime as well host_realframetime = host_frametime = realtime - oldrealtime; oldrealtime = realtime; - if (cls.timedemo) - { - // disable time effects - cl.frametime = host_frametime; - return true; - } + // apply slowmo scaling + host_frametime *= slowmo.value; - if (host_framerate.value > 0) + // host_framerate overrides all else + if (host_framerate.value) host_frametime = host_framerate.value; - else if (cl_avidemo.value >= 0.1f) - host_frametime = (1.0 / cl_avidemo.value); - else - { - // don't allow really short frames - if (host_frametime > (1.0 / host_minfps.value)) - host_frametime = (1.0 / host_minfps.value); - } - cl.frametime = host_frametime = bound(0, host_frametime * slowmo.value, 0.1f); // LordHavoc: the QC code relies on no less than 10fps + cl.frametime = host_frametime; return true; } @@ -648,37 +642,49 @@ Host_ServerFrame */ void Host_ServerFrame (void) { + double advancetime; static double frametimetotal = 0, lastservertime = 0; frametimetotal += host_frametime; // LordHavoc: cap server at sys_ticrate in networked games - if (!cl.islocalgame && ((realtime - lastservertime) < sys_ticrate.value)) + if (frametimetotal < 0.001 || (!cl.islocalgame && cls.state == ca_connected && sv.active && ((realtime - lastservertime) < sys_ticrate.value))) return; - - NetConn_ServerFrame(); - -// run the world state - if (!sv.paused && (!cl.islocalgame || (key_dest == key_game && !key_consoleactive))) - sv.frametime = pr_global_struct->frametime = frametimetotal; - else - sv.frametime = 0; - frametimetotal = 0; lastservertime = realtime; -// set the time and clear the general datagram + // set the time and clear the general datagram SV_ClearDatagram(); -// read client messages - SV_RunClients(); + // run the world state + // don't allow simulation to run too fast or too slow or logic glitches can occur + while (frametimetotal > 0) + { + advancetime = min(frametimetotal, sys_ticrate.value); + frametimetotal = frametimetotal - advancetime; + + // only advance time if not paused + // the game also pauses in singleplayer when menu or console is used + if (!sv.paused && (!cl.islocalgame || (key_dest == key_game && !key_consoleactive))) + sv.frametime = advancetime; + else + sv.frametime = 0; + + pr_global_struct->frametime = sv.frametime; -// move things around and think -// always pause in single player if in console or menus - if (sv.frametime) - SV_Physics(); + // check for network packets to the server each world step incase they + // come in midframe (particularly if host is running really slow) + NetConn_ServerFrame(); -// send all messages to the clients + // read client messages + SV_RunClients(); + + // move things around and think unless paused + if (sv.frametime) + SV_Physics(); + } + + // send all messages to the clients SV_SendClientMessages(); -// send an heartbeat if enough time has passed since the last one + // send an heartbeat if enough time has passed since the last one NetConn_Heartbeat(0); } @@ -701,13 +707,13 @@ void _Host_Frame (float time) if (setjmp(host_abortserver)) return; // something bad happened, or the server disconnected - // keep the random time dependent - rand(); - // decide the simulation time if (!Host_FilterTime(time)) return; + // keep the random time dependent + rand(); + cl.islocalgame = NetConn_IsLocalGame(); // get new key events diff --git a/snd_mix.c b/snd_mix.c index 6f92eb13..319eebeb 100644 --- a/snd_mix.c +++ b/snd_mix.c @@ -32,83 +32,29 @@ typedef struct #define PAINTBUFFER_SIZE 2048 portable_samplepair_t paintbuffer[PAINTBUFFER_SIZE]; -// FIXME: it desyncs with the video too easily -extern cvar_t cl_avidemo; -static qfile_t *cl_avidemo_soundfile = NULL; +// FIXME: this desyncs with the video too easily +extern qboolean cl_capturevideo_active; +extern void SCR_CaptureVideo_SoundFrame(qbyte *bufstereo16le, size_t length, int rate); void S_CaptureAVISound(portable_samplepair_t *buf, size_t length) { int n; size_t i; qbyte out[PAINTBUFFER_SIZE * 4]; - - if (cl_avidemo.value >= 0.1f) - { - if (cl_avidemo_soundfile == NULL) - { - cl_avidemo_soundfile = FS_Open ("video/dpavi.wav", "wb", false); - memset(out, 0, 44); - FS_Write (cl_avidemo_soundfile, out, 44); - // header will be filled out when file is closed - } - FS_Seek (cl_avidemo_soundfile, 0, SEEK_END); - // write the sound buffer as little endian 16bit interleaved stereo - for(i = 0;i < length;i++) - { - n = buf[i].left >> 2; // quiet enough to prevent clipping most of the time - n = bound(-32768, n, 32767); - out[i*4+0] = n & 0xFF; - out[i*4+1] = (n >> 8) & 0xFF; - n = buf[i].right >> 2; // quiet enough to prevent clipping most of the time - n = bound(-32768, n, 32767); - out[i*4+2] = n & 0xFF; - out[i*4+3] = (n >> 8) & 0xFF; - } - if (FS_Write (cl_avidemo_soundfile, out, 4 * length) < 4 * length) - { - Cvar_SetValueQuick(&cl_avidemo, 0); - Con_Print("avi saving sound failed, out of disk space? stopping avi demo capture.\n"); - } - } - else if (cl_avidemo_soundfile) + if (!cl_capturevideo_active) + return; + // write the sound buffer as little endian 16bit interleaved stereo + for(i = 0;i < length;i++) { - // file has not been closed yet, close it - FS_Seek (cl_avidemo_soundfile, 0, SEEK_END); - i = FS_Tell (cl_avidemo_soundfile); - - //"RIFF", (int) unknown (chunk size), "WAVE", - //"fmt ", (int) 16 (chunk size), (short) format 1 (uncompressed PCM), (short) 2 channels, (int) unknown rate, (int) unknown bytes per second, (short) 4 bytes per sample (channels * bytes per channel), (short) 16 bits per channel - //"data", (int) unknown (chunk size) - memcpy (out, "RIFF****WAVEfmt \x10\x00\x00\x00\x01\x00\x02\x00********\x04\x00\x10\0data****", 44); - // the length of the whole RIFF chunk - n = i - 8; - out[4] = (n) & 0xFF; - out[5] = (n >> 8) & 0xFF; - out[6] = (n >> 16) & 0xFF; - out[7] = (n >> 24) & 0xFF; - // rate - n = shm->format.speed; - out[24] = (n) & 0xFF; - out[25] = (n >> 8) & 0xFF; - out[26] = (n >> 16) & 0xFF; - out[27] = (n >> 24) & 0xFF; - // bytes per second (rate * channels * bytes per channel) - n = shm->format.speed * 2 * 2; - out[28] = (n) & 0xFF; - out[29] = (n >> 8) & 0xFF; - out[30] = (n >> 16) & 0xFF; - out[31] = (n >> 24) & 0xFF; - // the length of the data chunk - n = i - 44; - out[40] = (n) & 0xFF; - out[41] = (n >> 8) & 0xFF; - out[42] = (n >> 16) & 0xFF; - out[43] = (n >> 24) & 0xFF; - - FS_Seek (cl_avidemo_soundfile, 0, SEEK_SET); - FS_Write (cl_avidemo_soundfile, out, 44); - FS_Close (cl_avidemo_soundfile); - cl_avidemo_soundfile = NULL; + n = buf[i].left >> 2; // quiet enough to prevent clipping most of the time + n = bound(-32768, n, 32767); + out[i*4+0] = n & 0xFF; + out[i*4+1] = (n >> 8) & 0xFF; + n = buf[i].right >> 2; // quiet enough to prevent clipping most of the time + n = bound(-32768, n, 32767); + out[i*4+2] = n & 0xFF; + out[i*4+3] = (n >> 8) & 0xFF; } + SCR_CaptureVideo_SoundFrame(out, length, shm->format.speed); } // TODO: rewrite this function -- 2.39.2