Skip to content

Commit f399f8b

Browse files
cl_capturevideo: accelerate scaling and transfer
Improves cl_capturevideo_printfps. Renames some func ptrs for clarity. Signed-off-by: bones_was_here <bones_was_here@xonotic.au>
1 parent ef76498 commit f399f8b

File tree

7 files changed

+162
-114
lines changed

7 files changed

+162
-114
lines changed

cap.h

+11-4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "qdefs.h"
88
#include "fs.h"
99
#include "snd_main.h"
10+
#include "glquake.h"
1011

1112
typedef enum capturevideoformat_e
1213
{
@@ -31,7 +32,6 @@ typedef struct capturevideostate_s
3132
double lastfpstime;
3233
int lastfpsframe;
3334
int soundsampleframe;
34-
unsigned char *screenbuffer;
3535
unsigned char *outbuffer;
3636
char basename[MAX_QPATH];
3737
int width, height;
@@ -51,12 +51,19 @@ typedef struct capturevideostate_s
5151
qfile_t *videofile;
5252
// always use this:
5353
// cls.capturevideo.videofile = FS_OpenRealFile(va(vabuf, sizeof(vabuf), "%s.%s", cls.capturevideo.basename, cls.capturevideo.formatextension), "wb", false);
54-
void (*endvideo) (void);
55-
void (*videoframes) (int num);
56-
void (*soundframe) (const portable_sampleframe_t *paintbuffer, size_t length);
54+
void (*writeEndVideo) (void);
55+
void (*writeVideoFrame) (int num, u8 *in);
56+
void (*writeSoundFrame) (const portable_sampleframe_t *paintbuffer, size_t length);
5757

5858
// format specific data
5959
void *formatspecific;
60+
61+
// GL backend
62+
#define PBO_COUNT 3 // bones_was_here: slightly faster than double buffering
63+
GLuint PBOs[PBO_COUNT];
64+
GLuint PBOindex;
65+
GLuint FBO;
66+
GLuint FBOtex;
6067
}
6168
capturevideostate_t;
6269
#endif

cap_avi.c

+6-8
Original file line numberDiff line numberDiff line change
@@ -370,14 +370,12 @@ static void SCR_CaptureVideo_ConvertFrame_BGRA_to_I420_flip(int width, int heigh
370370
}
371371
}
372372

373-
static void SCR_CaptureVideo_Avi_VideoFrames(int num)
373+
static void SCR_CaptureVideo_Avi_VideoFrames(int num, u8 *in)
374374
{
375375
LOAD_FORMATSPECIFIC_AVI();
376376
int x = 0, width = cls.capturevideo.width, height = cls.capturevideo.height;
377-
unsigned char *in, *out;
378-
// FIXME: width/height must be multiple of 2, enforce this?
379-
in = cls.capturevideo.outbuffer;
380-
out = cls.capturevideo.outbuffer + width*height*4;
377+
unsigned char *out = cls.capturevideo.outbuffer;
378+
381379
SCR_CaptureVideo_ConvertFrame_BGRA_to_I420_flip(width, height, in, out);
382380
x = width*height+(width/2)*(height/2)*2;
383381
while(num-- > 0)
@@ -509,9 +507,9 @@ void SCR_CaptureVideo_Avi_BeginVideo(void)
509507
cls.capturevideo.format = CAPTUREVIDEOFORMAT_AVI_I420;
510508
cls.capturevideo.formatextension = "avi";
511509
cls.capturevideo.videofile = FS_OpenRealFile(va(vabuf, sizeof(vabuf), "%s.%s", cls.capturevideo.basename, cls.capturevideo.formatextension), "wb", false);
512-
cls.capturevideo.endvideo = SCR_CaptureVideo_Avi_EndVideo;
513-
cls.capturevideo.videoframes = SCR_CaptureVideo_Avi_VideoFrames;
514-
cls.capturevideo.soundframe = SCR_CaptureVideo_Avi_SoundFrame;
510+
cls.capturevideo.writeEndVideo = SCR_CaptureVideo_Avi_EndVideo;
511+
cls.capturevideo.writeVideoFrame = SCR_CaptureVideo_Avi_VideoFrames;
512+
cls.capturevideo.writeSoundFrame = SCR_CaptureVideo_Avi_SoundFrame;
515513
cls.capturevideo.formatspecific = Mem_Alloc(tempmempool, sizeof(capturevideostate_avi_formatspecific_t));
516514
{
517515
LOAD_FORMATSPECIFIC_AVI();

cap_ogg.c

+8-8
Original file line numberDiff line numberDiff line change
@@ -805,7 +805,7 @@ static void SCR_CaptureVideo_Ogg_EndVideo(void)
805805
cls.capturevideo.videofile = NULL;
806806
}
807807

808-
static void SCR_CaptureVideo_Ogg_ConvertFrame_BGRA_to_YUV(void)
808+
static void SCR_CaptureVideo_Ogg_ConvertFrame_BGRA_to_YUV(u8 *in)
809809
{
810810
LOAD_FORMATSPECIFIC_OGG();
811811
yuv_buffer *yuv;
@@ -820,7 +820,7 @@ static void SCR_CaptureVideo_Ogg_ConvertFrame_BGRA_to_YUV(void)
820820

821821
for(y = 0; y < h; ++y)
822822
{
823-
for(b = cls.capturevideo.outbuffer + (h-1-y)*w*4, x = 0; x < w; ++x)
823+
for(b = in + (h-1-y)*w*4, x = 0; x < w; ++x)
824824
{
825825
blockr = b[2];
826826
blockg = b[1];
@@ -832,7 +832,7 @@ static void SCR_CaptureVideo_Ogg_ConvertFrame_BGRA_to_YUV(void)
832832

833833
if ((y & 1) == 0 && y/2 < h/2) // if h is odd, this skips the last row
834834
{
835-
for(b = cls.capturevideo.outbuffer + (h-2-y)*w*4, x = 0; x < w/2; ++x)
835+
for(b = in + (h-2-y)*w*4, x = 0; x < w/2; ++x)
836836
{
837837
blockr = (b[2] + b[6] + b[inpitch+2] + b[inpitch+6]) >> 2;
838838
blockg = (b[1] + b[5] + b[inpitch+1] + b[inpitch+5]) >> 2;
@@ -847,7 +847,7 @@ static void SCR_CaptureVideo_Ogg_ConvertFrame_BGRA_to_YUV(void)
847847
}
848848
}
849849

850-
static void SCR_CaptureVideo_Ogg_VideoFrames(int num)
850+
static void SCR_CaptureVideo_Ogg_VideoFrames(int num, u8 *in)
851851
{
852852
LOAD_FORMATSPECIFIC_OGG();
853853
ogg_packet pt;
@@ -869,7 +869,7 @@ static void SCR_CaptureVideo_Ogg_VideoFrames(int num)
869869
}
870870

871871
format->yuvi = (format->yuvi + 1) % 2;
872-
SCR_CaptureVideo_Ogg_ConvertFrame_BGRA_to_YUV();
872+
SCR_CaptureVideo_Ogg_ConvertFrame_BGRA_to_YUV(in);
873873
format->lastnum = num;
874874

875875
// TODO maybe send num-1 frames from here already
@@ -924,9 +924,9 @@ void SCR_CaptureVideo_Ogg_BeginVideo(void)
924924
cls.capturevideo.format = CAPTUREVIDEOFORMAT_OGG_VORBIS_THEORA;
925925
cls.capturevideo.formatextension = "ogv";
926926
cls.capturevideo.videofile = FS_OpenRealFile(va(vabuf, sizeof(vabuf), "%s.%s", cls.capturevideo.basename, cls.capturevideo.formatextension), "wb", false);
927-
cls.capturevideo.endvideo = SCR_CaptureVideo_Ogg_EndVideo;
928-
cls.capturevideo.videoframes = SCR_CaptureVideo_Ogg_VideoFrames;
929-
cls.capturevideo.soundframe = SCR_CaptureVideo_Ogg_SoundFrame;
927+
cls.capturevideo.writeEndVideo = SCR_CaptureVideo_Ogg_EndVideo;
928+
cls.capturevideo.writeVideoFrame = SCR_CaptureVideo_Ogg_VideoFrames;
929+
cls.capturevideo.writeSoundFrame = SCR_CaptureVideo_Ogg_SoundFrame;
930930
cls.capturevideo.formatspecific = Mem_Alloc(tempmempool, sizeof(capturevideostate_ogg_formatspecific_t));
931931
{
932932
LOAD_FORMATSPECIFIC_OGG();

cl_screen.c

+29-93
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ cvar_t scr_screenshot_timestamp = {CF_CLIENT | CF_ARCHIVE, "scr_screenshot_times
6666
#ifdef CONFIG_VIDEO_CAPTURE
6767
cvar_t cl_capturevideo = {CF_CLIENT, "cl_capturevideo", "0", "enables saving of video to a .avi file using uncompressed I420 colorspace and PCM audio, note that scr_screenshot_gammaboost affects the brightness of the output)"};
6868
cvar_t cl_capturevideo_demo_stop = {CF_CLIENT | CF_ARCHIVE, "cl_capturevideo_demo_stop", "1", "automatically stops video recording when demo ends"};
69-
cvar_t cl_capturevideo_printfps = {CF_CLIENT | CF_ARCHIVE, "cl_capturevideo_printfps", "1", "prints the frames per second captured in capturevideo (is only written to the log file, not to the console, as that would be visible on the video)"};
69+
cvar_t cl_capturevideo_printfps = {CF_CLIENT | CF_ARCHIVE, "cl_capturevideo_printfps", "1", "prints the frames per second captured in capturevideo (is only written to stdout and any log file, not to the console as that would be visible on the video), value is seconds of wall time between prints"};
7070
cvar_t cl_capturevideo_width = {CF_CLIENT | CF_ARCHIVE, "cl_capturevideo_width", "0", "scales all frames to this resolution before saving the video"};
7171
cvar_t cl_capturevideo_height = {CF_CLIENT | CF_ARCHIVE, "cl_capturevideo_height", "0", "scales all frames to this resolution before saving the video"};
7272
cvar_t cl_capturevideo_realtime = {CF_CLIENT, "cl_capturevideo_realtime", "0", "causes video saving to operate in realtime (mostly useful while playing, not while capturing demos), this can produce a much lower quality video due to poor sound/video sync and will abort saving if your machine stalls for over a minute"};
@@ -1077,8 +1077,7 @@ static void SCR_CaptureVideo_BeginVideo(void)
10771077
cls.capturevideo.starttime = cls.capturevideo.lastfpstime = host.realtime;
10781078
cls.capturevideo.soundsampleframe = 0;
10791079
cls.capturevideo.realtime = cl_capturevideo_realtime.integer != 0;
1080-
cls.capturevideo.screenbuffer = (unsigned char *)Mem_Alloc(tempmempool, vid.mode.width * vid.mode.height * 4);
1081-
cls.capturevideo.outbuffer = (unsigned char *)Mem_Alloc(tempmempool, width * height * (4+4) + 18);
1080+
cls.capturevideo.outbuffer = (unsigned char *)Mem_Alloc(tempmempool, width * height * 4 + 18); // +18 ?
10821081
Sys_TimeString(timestring, sizeof(timestring), cl_capturevideo_nameformat.string);
10831082
dpsnprintf(cls.capturevideo.basename, sizeof(cls.capturevideo.basename), "video/%s%03i", timestring, cl_capturevideo_number.integer);
10841083
Cvar_SetValueQuick(&cl_capturevideo_number, cl_capturevideo_number.integer + 1);
@@ -1139,6 +1138,8 @@ Cr = R * .500 + G * -.419 + B * -.0813 + 128.;
11391138
cls.capturevideo.yuvnormalizetable[2][i] = 16 + i * (240-16) / 256;
11401139
}
11411140

1141+
GL_CaptureVideo_BeginVideo();
1142+
11421143
if (cl_capturevideo_ogg.integer)
11431144
{
11441145
if(SCR_CaptureVideo_Ogg_Available())
@@ -1162,16 +1163,10 @@ void SCR_CaptureVideo_EndVideo(void)
11621163

11631164
Con_Printf("Finishing capture of %s.%s (%d frames, %d audio frames)\n", cls.capturevideo.basename, cls.capturevideo.formatextension, cls.capturevideo.frame, cls.capturevideo.soundsampleframe);
11641165

1165-
if (cls.capturevideo.videofile)
1166-
{
1167-
cls.capturevideo.endvideo();
1168-
}
1166+
GL_CaptureVideo_EndVideo(); // must be called before writeEndVideo !
11691167

1170-
if (cls.capturevideo.screenbuffer)
1171-
{
1172-
Mem_Free (cls.capturevideo.screenbuffer);
1173-
cls.capturevideo.screenbuffer = NULL;
1174-
}
1168+
if (cls.capturevideo.videofile)
1169+
cls.capturevideo.writeEndVideo();
11751170

11761171
if (cls.capturevideo.outbuffer)
11771172
{
@@ -1182,98 +1177,24 @@ void SCR_CaptureVideo_EndVideo(void)
11821177
memset(&cls.capturevideo, 0, sizeof(cls.capturevideo));
11831178
}
11841179

1185-
static void SCR_ScaleDownBGRA(unsigned char *in, int inw, int inh, unsigned char *out, int outw, int outh)
1186-
{
1187-
// TODO optimize this function
1188-
1189-
int x, y;
1190-
float area;
1191-
1192-
// memcpy is faster than me
1193-
if(inw == outw && inh == outh)
1194-
{
1195-
memcpy(out, in, 4 * inw * inh);
1196-
return;
1197-
}
1198-
1199-
// otherwise: a box filter
1200-
area = (float)outw * (float)outh / (float)inw / (float)inh;
1201-
for(y = 0; y < outh; ++y)
1202-
{
1203-
float iny0 = y / (float)outh * inh; int iny0_i = (int) floor(iny0);
1204-
float iny1 = (y+1) / (float)outh * inh; int iny1_i = (int) ceil(iny1);
1205-
for(x = 0; x < outw; ++x)
1206-
{
1207-
float inx0 = x / (float)outw * inw; int inx0_i = (int) floor(inx0);
1208-
float inx1 = (x+1) / (float)outw * inw; int inx1_i = (int) ceil(inx1);
1209-
float r = 0, g = 0, b = 0, alpha = 0;
1210-
int xx, yy;
1211-
1212-
for(yy = iny0_i; yy < iny1_i; ++yy)
1213-
{
1214-
float ya = min(yy+1, iny1) - max(iny0, yy);
1215-
for(xx = inx0_i; xx < inx1_i; ++xx)
1216-
{
1217-
float a = ya * (min(xx+1, inx1) - max(inx0, xx));
1218-
r += a * in[4*(xx + inw * yy)+0];
1219-
g += a * in[4*(xx + inw * yy)+1];
1220-
b += a * in[4*(xx + inw * yy)+2];
1221-
alpha += a * in[4*(xx + inw * yy)+3];
1222-
}
1223-
}
1224-
1225-
out[4*(x + outw * y)+0] = (unsigned char) (r * area);
1226-
out[4*(x + outw * y)+1] = (unsigned char) (g * area);
1227-
out[4*(x + outw * y)+2] = (unsigned char) (b * area);
1228-
out[4*(x + outw * y)+3] = (unsigned char) (alpha * area);
1229-
}
1230-
}
1231-
}
1232-
1233-
static void SCR_CaptureVideo_VideoFrame(int newframestepframenum)
1234-
{
1235-
int x = 0, y = 0;
1236-
int width = cls.capturevideo.width, height = cls.capturevideo.height;
1237-
1238-
if(newframestepframenum == cls.capturevideo.framestepframe)
1239-
return;
1240-
1241-
CHECKGLERROR
1242-
// speed is critical here, so do saving as directly as possible
1243-
1244-
GL_ReadPixelsBGRA(x, y, vid.mode.width, vid.mode.height, cls.capturevideo.screenbuffer);
1245-
1246-
SCR_ScaleDownBGRA (cls.capturevideo.screenbuffer, vid.mode.width, vid.mode.height, cls.capturevideo.outbuffer, width, height);
1247-
1248-
cls.capturevideo.videoframes(newframestepframenum - cls.capturevideo.framestepframe);
1249-
cls.capturevideo.framestepframe = newframestepframenum;
1250-
1251-
if(cl_capturevideo_printfps.integer && host.realtime > cls.capturevideo.lastfpstime + 1)
1252-
{
1253-
double fps1 = (cls.capturevideo.frame - cls.capturevideo.lastfpsframe) / (host.realtime - cls.capturevideo.lastfpstime + 0.0000001);
1254-
double fps = (cls.capturevideo.frame ) / (host.realtime - cls.capturevideo.starttime + 0.0000001);
1255-
Sys_Printf("capturevideo: (%.1fs) last second %.3ffps, total %.3ffps\n", cls.capturevideo.frame / cls.capturevideo.framerate, fps1, fps);
1256-
cls.capturevideo.lastfpstime = host.realtime;
1257-
cls.capturevideo.lastfpsframe = cls.capturevideo.frame;
1258-
}
1259-
}
1260-
12611180
void SCR_CaptureVideo_SoundFrame(const portable_sampleframe_t *paintbuffer, size_t length)
12621181
{
12631182
cls.capturevideo.soundsampleframe += (int)length;
1264-
cls.capturevideo.soundframe(paintbuffer, length);
1183+
cls.capturevideo.writeSoundFrame(paintbuffer, length);
12651184
}
12661185

12671186
static void SCR_CaptureVideo(void)
12681187
{
12691188
int newframenum;
1189+
int newframestepframenum;
1190+
12701191
if (cl_capturevideo.integer)
12711192
{
12721193
if (!cls.capturevideo.active)
12731194
SCR_CaptureVideo_BeginVideo();
12741195
if (cls.capturevideo.framerate != cl_capturevideo_fps.value * cl_capturevideo_framestep.integer)
12751196
{
1276-
Con_Printf("You can not change the video framerate while recording a video.\n");
1197+
Con_Printf(CON_WARN "You can not change the video framerate while recording a video.\n");
12771198
Cvar_SetValueQuick(&cl_capturevideo_fps, cls.capturevideo.framerate / (double) cl_capturevideo_framestep.integer);
12781199
}
12791200
// for AVI saving we have to make sure that sound is saved before video
@@ -1290,17 +1211,32 @@ static void SCR_CaptureVideo(void)
12901211
if (newframenum - cls.capturevideo.frame > 60 * (int)ceil(cls.capturevideo.framerate))
12911212
{
12921213
Cvar_SetValueQuick(&cl_capturevideo, 0);
1293-
Con_Printf("video saving failed on frame %i, your machine is too slow for this capture speed.\n", cls.capturevideo.frame);
1214+
Con_Printf(CON_ERROR "video saving failed on frame %i, your machine is too slow for this capture speed.\n", cls.capturevideo.frame);
12941215
SCR_CaptureVideo_EndVideo();
12951216
return;
12961217
}
12971218
// write frames
1298-
SCR_CaptureVideo_VideoFrame(newframenum / cls.capturevideo.framestep);
1219+
newframestepframenum = newframenum / cls.capturevideo.framestep;
1220+
if (newframestepframenum != cls.capturevideo.framestepframe)
1221+
GL_CaptureVideo_VideoFrame(newframestepframenum);
1222+
cls.capturevideo.framestepframe = newframestepframenum;
1223+
// report progress
1224+
if(cl_capturevideo_printfps.value && host.realtime > cls.capturevideo.lastfpstime + cl_capturevideo_printfps.value)
1225+
{
1226+
double fps1 = (cls.capturevideo.frame - cls.capturevideo.lastfpsframe) / (host.realtime - cls.capturevideo.lastfpstime + 0.0000001);
1227+
double fps = (cls.capturevideo.frame ) / (host.realtime - cls.capturevideo.starttime + 0.0000001);
1228+
Sys_Printf("captured %.1fs of video, last second %.3ffps (%.1fx), total %.3ffps (%.1fx)\n",
1229+
cls.capturevideo.frame / cls.capturevideo.framerate,
1230+
fps1, fps1 / cls.capturevideo.framerate,
1231+
fps, fps / cls.capturevideo.framerate);
1232+
cls.capturevideo.lastfpstime = host.realtime;
1233+
cls.capturevideo.lastfpsframe = cls.capturevideo.frame;
1234+
}
12991235
cls.capturevideo.frame = newframenum;
13001236
if (cls.capturevideo.error)
13011237
{
13021238
Cvar_SetValueQuick(&cl_capturevideo, 0);
1303-
Con_Printf("video saving failed on frame %i, out of disk space? stopping video capture.\n", cls.capturevideo.frame);
1239+
Con_Printf(CON_ERROR "video saving failed on frame %i, out of disk space? stopping video capture.\n", cls.capturevideo.frame);
13041240
SCR_CaptureVideo_EndVideo();
13051241
}
13061242
}

0 commit comments

Comments
 (0)