Foreword: This tutorial is how to port the the fine upgraded AVI capture work of Jozsef Szalontai from JoeQuake 1862 of October, 2007.
His AVI capture code is used in several clients, like Qrack and ezQuake.
He did a silent version update of JoeQuake last year and he re-wrote the AVI capture to be even better!
Adding AVI Capture to GLQuakeNotes in Advance: This code only works on Windows, unlike how DarkPlaces video capture is operating system neutral to the best of my knowledge.
However, with this code, you can capture using Xvid or other codecs for far smaller video sizes.
XVid is an open source codec, but unlike DarkPlaces video capture, you'll need to download the Xvid separately.
In addition, this code can be used to capture in WinQuake, but this tutorial doesn't cover that because it's already going to be a long one ...
First, you really need to see JoeQuake 1862's video capture in action and try it once.
Then when we starting adding code, we will be lightly touching 12 files and adding 4. This tutorial is way easier than that sounds.
Capturing a demo in JoeQuake Build 1862
1. Download JoeQuake Build 1862 for Windows.(download) and install Xvid Installer (download) [Xvid is open source, it won't hurt you].
2. Start JoeQuake and type (or paste this) in console:
Then look in quake\capture ... you have a demo1.avi and if you are using 640x480 resolution, you'll see that it is only 18 MB. Very nice! You could upload it to YouTube or whatever.capture_codec divx; capture_fps 15; capturedemo demo1
The Code
Files to edit = 12 = cl_demo.c; client.h; common.c; common.h; gl_screen.c; host.c; mathlib.h; snd_dma.c; snd_mix.c; zone.c; zone.h
F1. cl_demo.c
(a) Below #include "quakedef.h" add:
Below this:Code: Select all
#ifdef _WIN32 #include "movie.h" #endif
(b) Add:Code: Select all
if (cls.timedemo) CL_FinishTimeDemo ();
We are making it so AVI capture stops when a demo is done playing.Code: Select all
#ifdef _WIN32 Movie_StopPlayback (); #endif
F2. client.h
F3. common.c(a) Below sizebuf_t message; in our client_static_t definition add:
We are defining a client state of AVI capturing.Code: Select all
qboolean capturedemo;
F4. common.h(a) For simplicity, just paste this at the bottom of common.c:
We are adding this only because the files we will be adding later use them a lot.Code: Select all
void Q_strncpyz (char *dest, char *src, size_t size) { strncpy (dest, src, size - 1); dest[size-1] = 0; } void Q_snprintfz (char *dest, size_t size, char *fmt, ...) { va_list argptr; va_start (argptr, fmt); vsnprintf (dest, size, fmt, argptr); va_end (argptr); dest[size - 1] = 0; } /* ================== COM_ForceExtension If path doesn't have an extension or has a different extension, append(!) specified extension Extension should include the . ================== */ void COM_ForceExtension (char *path, char *extension) { char *src; src = path + strlen(path) - 1; while (*src != '/' && src != path) { if (*src-- == '.') { COM_StripExtension (path, path); strcat (path, extension); return; } } strncat (path, extension, MAX_OSPATH); }
F5. gl_screen.c(a) Again, for reasons of simplicity just add the below to the bottom of common.h:
Adding for same reason as before. Tidy up the location of these if you wish, but pasting them to the bottom will work.Code: Select all
#ifdef _WIN32 #define vsnprintf _vsnprintf #endif #define bound(a, b, c) ((a) >= (c) ? (a) : (b) < (a) ? (a) : (b) > (c) ? (c) : (b)) void Q_strncpyz (char *dest, char *src, size_t size); void Q_snprintfz (char *dest, size_t size, char *fmt, ...); void COM_ForceExtension (char *path, char *extension);
(a) Below #include "quakedef.h" add:
Below this:Code: Select all
#ifdef _WIN32 #include "movie.h" #endif
(b) Add:Code: Select all
scr_turtle = Draw_PicFromWad ("turtle");
Above this near very bottom of our file:Code: Select all
#ifdef _WIN32 Movie_Init (); #endif
(c) Add:Code: Select all
GL_EndRendering ();
This does the initialization of the AVI capture module and part C is part of the capture process.Code: Select all
#ifdef _WIN32 Movie_UpdateScreen (); #endif
F6. host.c
F7. mathlib.h(a) Below #include "quakedef.h" add (look familiar?):
Above this:Code: Select all
#ifdef _WIN32 #include "movie.h" #endif
(b) Add:Code: Select all
host_frametime = realtime - oldrealtime;
Regulates the host time so video capture can work, otherwise you'd have big problems trying to capture.Code: Select all
#ifdef _WIN32 if (Movie_IsActive()) host_frametime = Movie_FrameTime (); else #endif
F8. snd_dma.c(a) Add at bottom for simplicity:
The files we add use this function.Code: Select all
#define Q_rint(x) ((x) > 0 ? (int)((x) + 0.5) : (int)((x) - 0.5))
F9. snd_mix.c(a) Below #include "quakedef.h" add:
Below this:Code: Select all
#ifdef _WIN32 #include "movie.h" #endif
(b) Add:Code: Select all
int fullsamples;
Further down, below this:Code: Select all
#ifdef _WIN32 if (Movie_GetSoundtime()) return; #endif
(c) Add as very next line:Code: Select all
void S_ExtraUpdate (void) { #ifdef _WIN32
Helps capture the sound and keep it in synchronized.Code: Select all
if (Movie_IsActive()) return;
(a) Below #include "quakedef.h" add:
Below this:Code: Select all
#ifdef _WIN32 #include "movie.h" #endif
(b) Add:Code: Select all
lpaintedtime += (snd_linear_count>>1);
More sound capturing.Code: Select all
#ifdef _WIN32 Movie_TransferStereo16 (); #endif
F10. zone.c
F11. zone.h(a) You can paste this at the bottom:
Memory allocation goodness that the files we will be adding use so we must have them.Code: Select all
/* =================== Q_malloc Use it instead of malloc so that if memory allocation fails, the program exits with a message saying there's not enough memory instead of crashing after trying to use a NULL pointer =================== */ void *Q_malloc (size_t size) { void *p; if (!(p = malloc(size))) Sys_Error ("Not enough memory free; check disk space"); return p; } /* =================== Q_calloc =================== */ void *Q_calloc (size_t n, size_t size) { void *p; if (!(p = calloc(n, size))) Sys_Error ("Not enough memory free; check disk space"); return p; } /* =================== Q_realloc =================== */ void *Q_realloc (void *ptr, size_t size) { void *p; if (!(p = realloc(ptr, size))) Sys_Error ("Not enough memory free; check disk space"); return p; } /* =================== Q_strdup =================== */ void *Q_strdup (const char *str) { char *p; if (!(p = _strdup(str))) Sys_Error ("Not enough memory free; check disk space"); return p; }
F12. Now obtain the files movie.c, movie.h, movie_avi.c, movie_avi.h from JoeQuake Build 1862 Windows source (download) and add them to your project.(a) You can paste this at the bottom:
Same reason as previous step.Code: Select all
void *Q_malloc (size_t size); // joe void *Q_calloc (size_t n, size_t size); // void *Q_realloc (void *ptr, size_t size); // void *Q_strdup (const char *str); //
F13. movie.c
We have to make some changes to movie.c ...
With any luck and assuming this big long, but straightforward tutorial doesn't have any typos -- I was careful, but there is a chance it could have happened -- you are now the proud owner of an engine with AVI capturing capability.(a) Locate this code:
Now GLQuake doesn't have cvar callback, so replace with this, deleting the capture_dir and forcing capture_codec to save to config:Code: Select all
cvar_t capture_dir = {"capture_dir", "capture", 0, OnChange_capture_dir}; cvar_t capture_codec = {"capture_codec", "0"};
Locate this code:Code: Select all
cvar_t capture_codec = {"capture_codec", "0", true};
(b) Change MAX_FILELENGTH to MAX_OSPATH because GLQUAKE doesn't have MAX_FILELENGTH.Code: Select all
void Movie_Start_f (void) { char name[MAX_FILELENGTH], path[256];
Now, we aren't going to bother with the flexibility of specifying a custom capture directory, for the sake of keeping this simple.
Locate this code:
Code: Select all
hack_ctr = capture_hack.value; Q_snprintfz (path, sizeof(path), "%s/%s", capture_dir.string, name); if (!(moviefile = fopen(path, "wb")))
(c) Change to this, unforunately removing the ability for the user to choose a directory themselves but making this tutorial easier to write:
Code: Select all
hack_ctr = capture_hack.value; Q_snprintfz (path, sizeof(path), "%s/id1/%s", host_parms.basedir, name); if (!(moviefile = fopen(path, "wb")))
Locate this code:
Code: Select all
Cvar_Register (&capture_codec); Cvar_Register (&capture_fps); Cvar_Register (&capture_dir); Cvar_Register (&capture_console); Cvar_Register (&capture_hack); Cvar_Set (&capture_dir, va("%s/%s", com_basedir, capture_dir.string)); ACM_LoadLibrary (); if (!acm_loaded) return; Cvar_Register (&capture_mp3); Cvar_Register (&capture_mp3_kbps);
GLQuake doesn't use Cvar_Register, it uses Cvar_RegisterVariable. Furthermore, for simplicity, your recorded demos are going to be saved to quake\id1 instead of a custom user capturedir.
(d) So replace the above code with this:
Code: Select all
Cvar_RegisterVariable (&capture_codec); Cvar_RegisterVariable (&capture_fps); Cvar_RegisterVariable (&capture_dir); Cvar_RegisterVariable (&capture_console); Cvar_RegisterVariable (&capture_hack); ACM_LoadLibrary (); if (!acm_loaded) return; Cvar_RegisterVariable (&capture_mp3); Cvar_RegisterVariable (&capture_mp3_kbps);
Next, GLQuake doesn't have hardware gamma so find this code:
Code: Select all
#ifdef GLQUAKE buffer = Q_malloc (size); glReadPixels (glx, gly, glwidth, glheight, GL_RGB, GL_UNSIGNED_BYTE, buffer); ApplyGamma (buffer, size);
(e) And comment out or delete the line "ApplyGamma (buffer, size);"
And, again, GLQuake doesn't have hardware gamma so locate this code:
Code: Select all
*p++ = current_pal[vid.buffer[rowp]*3+2]; *p++ = current_pal[vid.buffer[rowp]*3+1]; *p++ = current_pal[vid.buffer[rowp]*3+0];
And replace with this:
Code: Select all
*p++ = host_basepal[vid.buffer[rowp]*3+2]; *p++ = host_basepal[vid.buffer[rowp]*3+1]; *p++ = host_basepal[vid.buffer[rowp]*3+0];
So this means you don't need to use some goofy application like Fraps to capture your demos with your engine, you can do it easy and whenever you want.