+ cls.capturevideo.format = CAPTUREVIDEOFORMAT_AVI_I420;
+ cls.capturevideo.videofile = FS_Open (va("%s.avi", cls.capturevideo.basename), "wb", false, true);
+ SCR_CaptureVideo_RIFF_Start();
+ // enclosing RIFF chunk (there can be multiple of these in >1GB files, the later ones are "AVIX" instead of "AVI " and have no header/stream info)
+ SCR_CaptureVideo_RIFF_Push("RIFF", "AVI ");
+ // AVI main header
+ SCR_CaptureVideo_RIFF_Push("LIST", "hdrl");
+ SCR_CaptureVideo_RIFF_Push("avih", NULL);
+ SCR_CaptureVideo_RIFF_Write32((int)(1000000.0 / cls.capturevideo.framerate)); // microseconds per frame
+ SCR_CaptureVideo_RIFF_Write32(0); // max bytes per second
+ SCR_CaptureVideo_RIFF_Write32(0); // padding granularity
+ SCR_CaptureVideo_RIFF_Write32(0x910); // flags (AVIF_HASINDEX | AVIF_ISINTERLEAVED | AVIF_TRUSTCKTYPE)
+ cls.capturevideo.videofile_totalframes_offset1 = SCR_CaptureVideo_RIFF_GetPosition();
+ SCR_CaptureVideo_RIFF_Write32(0); // total frames
+ SCR_CaptureVideo_RIFF_Write32(0); // initial frames
+ if (cls.capturevideo.soundrate)
+ SCR_CaptureVideo_RIFF_Write32(2); // number of streams
+ else
+ SCR_CaptureVideo_RIFF_Write32(1); // number of streams
+ SCR_CaptureVideo_RIFF_Write32(0); // suggested buffer size
+ SCR_CaptureVideo_RIFF_Write32(width); // width
+ SCR_CaptureVideo_RIFF_Write32(height); // height
+ SCR_CaptureVideo_RIFF_Write32(0); // reserved[0]
+ SCR_CaptureVideo_RIFF_Write32(0); // reserved[1]
+ SCR_CaptureVideo_RIFF_Write32(0); // reserved[2]
+ SCR_CaptureVideo_RIFF_Write32(0); // reserved[3]
+ SCR_CaptureVideo_RIFF_Pop();
+ // video stream info
+ SCR_CaptureVideo_RIFF_Push("LIST", "strl");
+ SCR_CaptureVideo_RIFF_Push("strh", "vids");
+ SCR_CaptureVideo_RIFF_WriteFourCC("I420"); // stream fourcc (I420 colorspace, uncompressed)
+ SCR_CaptureVideo_RIFF_Write32(0); // flags
+ SCR_CaptureVideo_RIFF_Write16(0); // priority
+ SCR_CaptureVideo_RIFF_Write16(0); // language
+ SCR_CaptureVideo_RIFF_Write32(0); // initial frames
+ // find an ideal divisor for the framerate
+ for (x = 1;x < 1000;x++)
+ if (cls.capturevideo.framerate * x == floor(cls.capturevideo.framerate * x))
+ break;
+ SCR_CaptureVideo_RIFF_Write32(x); // samples/second divisor
+ SCR_CaptureVideo_RIFF_Write32((int)(cls.capturevideo.framerate * x)); // samples/second multiplied by divisor
+ SCR_CaptureVideo_RIFF_Write32(0); // start
+ cls.capturevideo.videofile_totalframes_offset2 = SCR_CaptureVideo_RIFF_GetPosition();
+ SCR_CaptureVideo_RIFF_Write32(0); // length
+ SCR_CaptureVideo_RIFF_Write32(width*height+(width/2)*(height/2)*2); // suggested buffer size
+ SCR_CaptureVideo_RIFF_Write32(0); // quality
+ SCR_CaptureVideo_RIFF_Write32(0); // sample size
+ SCR_CaptureVideo_RIFF_Write16(0); // frame left
+ SCR_CaptureVideo_RIFF_Write16(0); // frame top
+ SCR_CaptureVideo_RIFF_Write16(width); // frame right
+ SCR_CaptureVideo_RIFF_Write16(height); // frame bottom
+ SCR_CaptureVideo_RIFF_Pop();
+ // video stream format
+ SCR_CaptureVideo_RIFF_Push("strf", NULL);
+ SCR_CaptureVideo_RIFF_Write32(40); // BITMAPINFO struct size
+ SCR_CaptureVideo_RIFF_Write32(width); // width
+ SCR_CaptureVideo_RIFF_Write32(height); // height
+ SCR_CaptureVideo_RIFF_Write16(3); // planes
+ SCR_CaptureVideo_RIFF_Write16(12); // bitcount
+ SCR_CaptureVideo_RIFF_WriteFourCC("I420"); // compression
+ SCR_CaptureVideo_RIFF_Write32(width*height+(width/2)*(height/2)*2); // size of image
+ SCR_CaptureVideo_RIFF_Write32(0); // x pixels per meter
+ SCR_CaptureVideo_RIFF_Write32(0); // y pixels per meter
+ SCR_CaptureVideo_RIFF_Write32(0); // color used
+ SCR_CaptureVideo_RIFF_Write32(0); // color important
+ SCR_CaptureVideo_RIFF_Pop();
+ SCR_CaptureVideo_RIFF_Pop();
+ if (cls.capturevideo.soundrate)
+ {
+ // audio stream info
+ SCR_CaptureVideo_RIFF_Push("LIST", "strl");
+ SCR_CaptureVideo_RIFF_Push("strh", "auds");
+ SCR_CaptureVideo_RIFF_Write32(1); // stream fourcc (PCM audio, uncompressed)
+ SCR_CaptureVideo_RIFF_Write32(0); // flags
+ SCR_CaptureVideo_RIFF_Write16(0); // priority
+ SCR_CaptureVideo_RIFF_Write16(0); // language
+ SCR_CaptureVideo_RIFF_Write32(0); // initial frames
+ SCR_CaptureVideo_RIFF_Write32(1); // samples/second divisor
+ SCR_CaptureVideo_RIFF_Write32((int)(cls.capturevideo.soundrate)); // samples/second multiplied by divisor
+ SCR_CaptureVideo_RIFF_Write32(0); // start
+ cls.capturevideo.videofile_totalsampleframes_offset = SCR_CaptureVideo_RIFF_GetPosition();
+ SCR_CaptureVideo_RIFF_Write32(0); // length
+ SCR_CaptureVideo_RIFF_Write32(cls.capturevideo.soundrate * 2); // suggested buffer size (this is a half second)
+ SCR_CaptureVideo_RIFF_Write32(0); // quality
+ SCR_CaptureVideo_RIFF_Write32(4); // sample size
+ SCR_CaptureVideo_RIFF_Write16(0); // frame left
+ SCR_CaptureVideo_RIFF_Write16(0); // frame top
+ SCR_CaptureVideo_RIFF_Write16(0); // frame right
+ SCR_CaptureVideo_RIFF_Write16(0); // frame bottom
+ SCR_CaptureVideo_RIFF_Pop();
+ // audio stream format
+ SCR_CaptureVideo_RIFF_Push("strf", NULL);
+ SCR_CaptureVideo_RIFF_Write16(1); // format (uncompressed PCM?)
+ SCR_CaptureVideo_RIFF_Write16(2); // channels (stereo)
+ SCR_CaptureVideo_RIFF_Write32(cls.capturevideo.soundrate); // sampleframes per second
+ SCR_CaptureVideo_RIFF_Write32(cls.capturevideo.soundrate * 4); // average bytes per second
+ SCR_CaptureVideo_RIFF_Write16(4); // block align
+ SCR_CaptureVideo_RIFF_Write16(16); // bits per sample
+ SCR_CaptureVideo_RIFF_Write16(0); // size
+ SCR_CaptureVideo_RIFF_Pop();
+ SCR_CaptureVideo_RIFF_Pop();
+ }
+ // close the AVI header list
+ SCR_CaptureVideo_RIFF_Pop();
+ // software that produced this AVI video file
+ SCR_CaptureVideo_RIFF_Push("LIST", "INFO");
+ SCR_CaptureVideo_RIFF_Push("ISFT", NULL);
+ SCR_CaptureVideo_RIFF_WriteTerminatedString(engineversion);
+ SCR_CaptureVideo_RIFF_Pop();
+ // enable this junk filler if you like the LIST movi to always begin at 4KB in the file (why?)
+#if 0
+ SCR_CaptureVideo_RIFF_Push("JUNK", NULL);
+ x = 4096 - SCR_CaptureVideo_RIFF_GetPosition();
+ while (x > 0)
+ {
+ const char *junkfiller = "[ DarkPlaces junk data ]";
+ int i = min(x, (int)strlen(junkfiller));
+ SCR_CaptureVideo_RIFF_WriteBytes((const unsigned char *)junkfiller, i);
+ x -= i;
+ }
+ SCR_CaptureVideo_RIFF_Pop();
+#endif
+ SCR_CaptureVideo_RIFF_Pop();
+ // begin the actual video section now
+ SCR_CaptureVideo_RIFF_Push("LIST", "movi");
+ // we're done with the headers now...
+ SCR_CaptureVideo_RIFF_Flush();
+ if (cls.capturevideo.riffstacklevel != 2)
+ Sys_Error("SCR_CaptureVideo_BeginVideo: broken AVI writing code (stack level is %i (should be 2) at end of headers)\n", cls.capturevideo.riffstacklevel);