Inside3D!
     

Tutorial: Adding AVI capture to stock GLQuake

 
Post new topic   Reply to topic    Inside3d Forums Forum Index -> Programming Tutorials
View previous topic :: View next topic  
Author Message
Baker



Joined: 14 Mar 2006
Posts: 1538

PostPosted: Mon Nov 24, 2008 8:05 am    Post subject: Tutorial: Adding AVI capture to stock GLQuake Reply with quote

Quote:
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!


Quote:
Notes 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 ...


Adding AVI Capture to GLQuake

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:

Quote:
capture_codec divx; capture_fps 15; capturedemo demo1


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.

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

Quote:
(a) Below #include "quakedef.h" add:

Code:
#ifdef _WIN32
#include "movie.h"
#endif


Below this:

Code:
   if (cls.timedemo)
      CL_FinishTimeDemo ();


(b) Add:

Code:
#ifdef _WIN32
   Movie_StopPlayback ();
#endif


We are making it so AVI capture stops when a demo is done playing.



F2. client.h

Quote:
(a) Below sizebuf_t message; in our client_static_t definition add:

Code:
qboolean   capturedemo;


We are defining a client state of AVI capturing.


F3. common.c

Quote:
(a) For simplicity, just paste this at the bottom of common.c:

Code:
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);
}


We are adding this only because the files we will be adding later use them a lot.


F4. common.h

Quote:
(a) Again, for reasons of simplicity just add the below to the bottom of common.h:

Code:
#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);


Adding for same reason as before. Tidy up the location of these if you wish, but pasting them to the bottom will work.


F5. gl_screen.c

Quote:
(a) Below #include "quakedef.h" add:

Code:
#ifdef _WIN32
#include "movie.h"
#endif


Below this:

Code:
scr_turtle = Draw_PicFromWad ("turtle");


(b) Add:

Code:
#ifdef _WIN32
   Movie_Init ();
#endif


Above this near very bottom of our file:

Code:
GL_EndRendering ();


(c) Add:

Code:
#ifdef _WIN32
   Movie_UpdateScreen ();
#endif


This does the initialization of the AVI capture module and part C is part of the capture process.



F6. host.c

Quote:
(a) Below #include "quakedef.h" add (look familiar?):

Code:
#ifdef _WIN32
#include "movie.h"
#endif


Above this:

Code:
host_frametime = realtime - oldrealtime;


(b) Add:

Code:
#ifdef _WIN32
   if (Movie_IsActive())
      host_frametime = Movie_FrameTime ();
   else
#endif


Regulates the host time so video capture can work, otherwise you'd have big problems trying to capture.


F7. mathlib.h
Quote:
(a) Add at bottom for simplicity:

Code:
#define Q_rint(x) ((x) > 0 ? (int)((x) + 0.5) : (int)((x) - 0.5))


The files we add use this function.


F8. snd_dma.c

Quote:
(a) Below #include "quakedef.h" add:

Code:
#ifdef _WIN32
#include "movie.h"
#endif


Below this:

Code:
int      fullsamples;


(b) Add:

Code:
#ifdef _WIN32
   if (Movie_GetSoundtime())
      return;
#endif


Further down, below this:

Code:
void S_ExtraUpdate (void)
{

#ifdef _WIN32


(c) Add as very next line:

Code:
   if (Movie_IsActive())
      return;


Helps capture the sound and keep it in synchronized.


F9. snd_mix.c

Quote:
(a) Below #include "quakedef.h" add:

Code:
#ifdef _WIN32
#include "movie.h"
#endif


Below this:

Code:
lpaintedtime += (snd_linear_count>>1);


(b) Add:

Code:
#ifdef _WIN32
      Movie_TransferStereo16 ();
#endif


More sound capturing.



F10. zone.c

Quote:
(a) You can paste this at the bottom:

Code:
/*
===================
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;
}


Memory allocation goodness that the files we will be adding use so we must have them.


F11. zone.h

Quote:
(a) You can paste this at the bottom:

Code:
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);      //


Same reason as previous step.


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.

F13. movie.c

We have to make some changes to movie.c ...

Quote:
(a) Locate this code:

Code:
cvar_t   capture_dir   = {"capture_dir", "capture", 0, OnChange_capture_dir};

cvar_t   capture_codec   = {"capture_codec", "0"};


Now GLQuake doesn't have cvar callback, so replace with this, deleting the capture_dir and forcing capture_codec to save to config:

Code:
cvar_t   capture_codec   = {"capture_codec", "0", true};


Locate this code:

Code:
void Movie_Start_f (void)
{
   char   name[MAX_FILELENGTH], path[256];


(b) Change MAX_FILELENGTH to MAX_OSPATH because GLQUAKE doesn't have MAX_FILELENGTH.

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:
   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:
   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:
   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:
   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:
#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:
         *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:
         *p++ = host_basepal[vid.buffer[rowp]*3+2];
         *p++ = host_basepal[vid.buffer[rowp]*3+1];
         *p++ = host_basepal[vid.buffer[rowp]*3+0];


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.

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.
Back to top
View user's profile Send private message
qbism



Joined: 04 Nov 2004
Posts: 82

PostPosted: Fri Jan 29, 2010 2:41 am    Post subject: Reply with quote

Thanks, Baker. This tute works for swquake (Makaqu)!

Comments, looking at movie.c:
Change this line-
Code:
Q_snprintfz (path, sizeof(path), "%s/id1/%s", host_parms.basedir, name);


to this-
Code:
Q_snprintfz (path, sizeof(path), "%s/%s", com_gamedir, name);

...so that movie will save in gamedir rather than hard-coded to id1.

Delete this-
Code:
Cvar_RegisterVariable (&capture_dir);


My computer is too slow to encode the avi without dropping a few frames and getting out-of-sync with audio. Anyone, is there a way to make the engine more "patient"?

BTW, I found that the GSpot utility that comes with K-Lite codec pack will list available codecs and fourcc codes.
_________________
http://qbism.com
Back to top
View user's profile Send private message Visit poster's website
Baker



Joined: 14 Mar 2006
Posts: 1538

PostPosted: Fri Jan 29, 2010 3:03 am    Post subject: Reply with quote

qbism wrote:

My computer is too slow to encode the avi without dropping a few frames and getting out-of-sync with audio. Anyone, is there a way to make the engine more "patient"?

BTW, I found that the GSpot utility that comes with K-Lite codec pack will list available codecs and fourcc codes.


What codec are you using? DivX, while certainly not the best, is in my opinion the easiest to setup for initial testing of capture and is rather fast.
_________________
Tomorrow Never Dies. I feel this Tomorrow knocking on the door ...
Back to top
View user's profile Send private message
Spike



Joined: 05 Nov 2004
Posts: 944
Location: UK

PostPosted: Fri Jan 29, 2010 3:44 am    Post subject: Reply with quote

the general recommendation is to use a really poor codec that takes little cpu usage purely to reduce disk usage, and to then compress it properly in an external application, where it can benefit from knowing how it will become, rather than just how it changed. Depends on the app.

the sound system generally needs some sort of tweek to ensure it mixes the correct quantities and blocks of sound. one way is to grab the dma position from the recording code instead of the sound card - it'll sound terrible while recording, but the sound that is actually mixed should then be reasonably correct. The sound driver should have a GetDMAPosition or something, which you can just make return some multiple of the running count of frames written, based on the sample rate.
_________________
What's a signature?
Back to top
View user's profile Send private message Visit poster's website
frag.machine



Joined: 25 Nov 2006
Posts: 728

PostPosted: Fri Jan 29, 2010 4:34 pm    Post subject: Reply with quote

There's an alternating (not as well documented as yours, of course) method to do AVI capture, from the QdQ team custom engine. It's a really straightforward recipe to add a couple ready to use source files to any vanilla-based engine, copy & paste some calls in strategic points and bang, you get essentially the same features from your solution. I'll post it later in a separated thread.
_________________
frag.machine - Q2K4 Project
http://fragmachine.quakedev.com/
Back to top
View user's profile Send private message Visit poster's website
frag.machine



Joined: 25 Nov 2006
Posts: 728

PostPosted: Fri Jan 29, 2010 4:44 pm    Post subject: Reply with quote

And some more useful AVI capture tips:
1- record a demo first, then play it back to capture the AVI;
2 - if your engine allow to set the AVI fps, use something as low as the host ticrate (usually 10Hz when model interpolation is disabled, 15 if enabled);
3 - also, set low screen dimensions (my default is 512 x 384, which gives a more than enough good resolution for YouTube videos).
_________________
frag.machine - Q2K4 Project
http://fragmachine.quakedev.com/
Back to top
View user's profile Send private message Visit poster's website
qbism



Joined: 04 Nov 2004
Posts: 82

PostPosted: Fri Jan 29, 2010 5:56 pm    Post subject: Reply with quote

Working great now in swquake with some settings tweaks!

1. Performance is greatly improved by switching to windowed rather than full-screen mode. Would like to figure out why... May need to look at where Movie_UpdateScreen () is placed, or maybe it's just because the laptop video chip is stretching output to "fake" low-res modes.

2.
Quote:
the general recommendation is to use a really poor codec that takes little cpu usage purely to reduce disk usage
Experimenting with the codecs available with K-lite installed, FFDS seemed to be the fastest. DIVX was slightly slower but much more compressed in size.

3. Setting CAPTURE_HACK to 1 solved the audio sync issue. I plan to try other values of CAPTURE_HACK also. ( Try 30! )
Quote:
it'll sound terrible while recording, but the sound that is actually mixed should then be reasonably correct
That is exactly what happens- audio is way off during record, but correct in the video.

Quote:
record a demo first, then play it back to capture the AVI
This is good advice, partly because mouse input will not be very smooth during avi recording.
_________________
http://qbism.com
Back to top
View user's profile Send private message Visit poster's website
Display posts from previous:   
Post new topic   Reply to topic    Inside3d Forums Forum Index -> Programming Tutorials All times are GMT
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum


Powered by phpBB © 2004 phpBB Group