]> de.git.xonotic.org Git - xonotic/darkplaces.git/blob - cap_avi.c
fix support of odd heights in YUV conversion for video capture
[xonotic/darkplaces.git] / cap_avi.c
1 #include "quakedef.h"
2 #include "cap_avi.h"
3
4 #define AVI_MASTER_INDEX_SIZE 640 // GB ought to be enough for anyone
5
6 typedef struct capturevideostate_avi_formatspecific_s
7 {
8         // AVI stuff
9         fs_offset_t videofile_firstchunkframes_offset;
10         fs_offset_t videofile_totalframes_offset1;
11         fs_offset_t videofile_totalframes_offset2;
12         fs_offset_t videofile_totalsampleframes_offset;
13         int videofile_ix_master_audio_inuse;
14         fs_offset_t videofile_ix_master_audio_inuse_offset;
15         fs_offset_t videofile_ix_master_audio_start_offset;
16         int videofile_ix_master_video_inuse;
17         fs_offset_t videofile_ix_master_video_inuse_offset;
18         fs_offset_t videofile_ix_master_video_start_offset;
19         fs_offset_t videofile_ix_movistart;
20         fs_offset_t position;
21         qboolean canseek;
22         sizebuf_t riffbuffer;
23         unsigned char riffbufferdata[128];
24         sizebuf_t riffindexbuffer;
25         int riffstacklevel;
26         fs_offset_t riffstackstartoffset[4];
27         fs_offset_t riffstacksizehint[4];
28         const char *riffstackfourcc[4];
29 }
30 capturevideostate_avi_formatspecific_t;
31 #define LOAD_FORMATSPECIFIC_AVI() capturevideostate_avi_formatspecific_t *format = (capturevideostate_avi_formatspecific_t *) cls.capturevideo.formatspecific
32
33 static void SCR_CaptureVideo_RIFF_Start(void)
34 {
35         LOAD_FORMATSPECIFIC_AVI();
36         memset(&format->riffbuffer, 0, sizeof(sizebuf_t));
37         format->riffbuffer.maxsize = sizeof(format->riffbufferdata);
38         format->riffbuffer.data = format->riffbufferdata;
39         format->position = 0;
40 }
41
42 static void SCR_CaptureVideo_RIFF_Flush(void)
43 {
44         LOAD_FORMATSPECIFIC_AVI();
45         if (format->riffbuffer.cursize > 0)
46         {
47                 if (!FS_Write(cls.capturevideo.videofile, format->riffbuffer.data, format->riffbuffer.cursize))
48                         cls.capturevideo.error = true;
49                 format->position += format->riffbuffer.cursize;
50                 format->riffbuffer.cursize = 0;
51                 format->riffbuffer.overflowed = false;
52         }
53 }
54
55 static void SCR_CaptureVideo_RIFF_FlushNoIncrease(void)
56 {
57         LOAD_FORMATSPECIFIC_AVI();
58         if (format->riffbuffer.cursize > 0)
59         {
60                 if (!FS_Write(cls.capturevideo.videofile, format->riffbuffer.data, format->riffbuffer.cursize))
61                         cls.capturevideo.error = true;
62                 format->riffbuffer.cursize = 0;
63                 format->riffbuffer.overflowed = false;
64         }
65 }
66
67 static void SCR_CaptureVideo_RIFF_WriteBytes(const unsigned char *data, size_t size)
68 {
69         LOAD_FORMATSPECIFIC_AVI();
70         SCR_CaptureVideo_RIFF_Flush();
71         if (!FS_Write(cls.capturevideo.videofile, data, size))
72                 cls.capturevideo.error = true;
73         format->position += size;
74 }
75
76 static void SCR_CaptureVideo_RIFF_Write32(int n)
77 {
78         LOAD_FORMATSPECIFIC_AVI();
79         if (format->riffbuffer.cursize + 4 > format->riffbuffer.maxsize)
80                 SCR_CaptureVideo_RIFF_Flush();
81         MSG_WriteLong(&format->riffbuffer, n);
82 }
83
84 static void SCR_CaptureVideo_RIFF_Write16(int n)
85 {
86         LOAD_FORMATSPECIFIC_AVI();
87         if (format->riffbuffer.cursize + 2 > format->riffbuffer.maxsize)
88                 SCR_CaptureVideo_RIFF_Flush();
89         MSG_WriteShort(&format->riffbuffer, n);
90 }
91
92 static void SCR_CaptureVideo_RIFF_WriteFourCC(const char *chunkfourcc)
93 {
94         LOAD_FORMATSPECIFIC_AVI();
95         if (format->riffbuffer.cursize + (int)strlen(chunkfourcc) > format->riffbuffer.maxsize)
96                 SCR_CaptureVideo_RIFF_Flush();
97         MSG_WriteUnterminatedString(&format->riffbuffer, chunkfourcc);
98 }
99
100 static void SCR_CaptureVideo_RIFF_WriteTerminatedString(const char *string)
101 {
102         LOAD_FORMATSPECIFIC_AVI();
103         if (format->riffbuffer.cursize + (int)strlen(string) > format->riffbuffer.maxsize)
104                 SCR_CaptureVideo_RIFF_Flush();
105         MSG_WriteString(&format->riffbuffer, string);
106 }
107
108 static fs_offset_t SCR_CaptureVideo_RIFF_GetPosition(void)
109 {
110         LOAD_FORMATSPECIFIC_AVI();
111         SCR_CaptureVideo_RIFF_Flush();
112         //return FS_Tell(cls.capturevideo.videofile);
113         return format->position;
114 }
115
116 static void SCR_CaptureVideo_RIFF_Push(const char *chunkfourcc, const char *listtypefourcc, fs_offset_t sizeHint)
117 {
118         LOAD_FORMATSPECIFIC_AVI();
119         if (listtypefourcc && sizeHint >= 0)
120                 sizeHint += 4; // size hint is for INNER size
121         SCR_CaptureVideo_RIFF_WriteFourCC(chunkfourcc);
122         SCR_CaptureVideo_RIFF_Write32(sizeHint);
123         SCR_CaptureVideo_RIFF_Flush();
124         format->riffstacksizehint[format->riffstacklevel] = sizeHint;
125         format->riffstackstartoffset[format->riffstacklevel] = SCR_CaptureVideo_RIFF_GetPosition();
126         format->riffstackfourcc[format->riffstacklevel] = chunkfourcc;
127         ++format->riffstacklevel;
128         if (listtypefourcc)
129                 SCR_CaptureVideo_RIFF_WriteFourCC(listtypefourcc);
130 }
131
132 static void SCR_CaptureVideo_RIFF_Pop(void)
133 {
134         LOAD_FORMATSPECIFIC_AVI();
135         fs_offset_t offset, sizehint;
136         int x;
137         unsigned char sizebytes[4];
138         // write out the chunk size and then return to the current file position
139         format->riffstacklevel--;
140         offset = SCR_CaptureVideo_RIFF_GetPosition();
141
142         sizehint = format->riffstacksizehint[format->riffstacklevel];
143         x = (int)(offset - (format->riffstackstartoffset[format->riffstacklevel]));
144
145         if(x != sizehint)
146         {
147                 if(sizehint != -1)
148                 {
149                         int i;
150                         Con_Printf("WARNING: invalid size hint %d when writing video data (actual size: %d)\n", (int) sizehint, x);
151                         for(i = 0; i <= format->riffstacklevel; ++i)
152                         {
153                                 Con_Printf("  RIFF level %d = %s\n", i, format->riffstackfourcc[i]);
154                         }
155                 }
156                 sizebytes[0] = (x) & 0xff;sizebytes[1] = (x >> 8) & 0xff;sizebytes[2] = (x >> 16) & 0xff;sizebytes[3] = (x >> 24) & 0xff;
157                 if(FS_Seek(cls.capturevideo.videofile, -(x + 4), SEEK_END) >= 0)
158                 {
159                         FS_Write(cls.capturevideo.videofile, sizebytes, 4);
160                 }
161                 FS_Seek(cls.capturevideo.videofile, 0, SEEK_END);
162         }
163
164         if (offset & 1)
165         {
166                 SCR_CaptureVideo_RIFF_WriteBytes((unsigned char *) "\0", 1);
167         }
168 }
169
170 static void GrowBuf(sizebuf_t *buf, int extralen)
171 {
172         if(buf->cursize + extralen > buf->maxsize)
173         {
174                 int oldsize = buf->maxsize;
175                 unsigned char *olddata;
176                 olddata = buf->data;
177                 buf->maxsize = max(buf->maxsize * 2, 4096);
178                 buf->data = (unsigned char *) Mem_Alloc(tempmempool, buf->maxsize);
179                 if(olddata)
180                 {
181                         memcpy(buf->data, olddata, oldsize);
182                         Mem_Free(olddata);
183                 }
184         }
185 }
186
187 static void SCR_CaptureVideo_RIFF_IndexEntry(const char *chunkfourcc, int chunksize, int flags)
188 {
189         LOAD_FORMATSPECIFIC_AVI();
190         if(!format->canseek)
191                 Host_Error("SCR_CaptureVideo_RIFF_IndexEntry called on non-seekable AVI");
192
193         if (format->riffstacklevel != 2)
194                 Sys_Error("SCR_Capturevideo_RIFF_IndexEntry: RIFF stack level is %i (should be 2)\n", format->riffstacklevel);
195         GrowBuf(&format->riffindexbuffer, 16);
196         SCR_CaptureVideo_RIFF_Flush();
197         MSG_WriteUnterminatedString(&format->riffindexbuffer, chunkfourcc);
198         MSG_WriteLong(&format->riffindexbuffer, flags);
199         MSG_WriteLong(&format->riffindexbuffer, (int)FS_Tell(cls.capturevideo.videofile) - format->riffstackstartoffset[1]);
200         MSG_WriteLong(&format->riffindexbuffer, chunksize);
201 }
202
203 static void SCR_CaptureVideo_RIFF_MakeIxChunk(const char *fcc, const char *dwChunkId, fs_offset_t masteridx_counter, int *masteridx_count, fs_offset_t masteridx_start)
204 {
205         LOAD_FORMATSPECIFIC_AVI();
206         int nMatching;
207         int i;
208         fs_offset_t ix = SCR_CaptureVideo_RIFF_GetPosition();
209         fs_offset_t pos, sz;
210         
211         if(!format->canseek)
212                 Host_Error("SCR_CaptureVideo_RIFF_MakeIxChunk called on non-seekable AVI");
213
214         if(*masteridx_count >= AVI_MASTER_INDEX_SIZE)
215                 return;
216
217         nMatching = 0; // go through index and enumerate them
218         for(i = 0; i < format->riffindexbuffer.cursize; i += 16)
219                 if(!memcmp(format->riffindexbuffer.data + i, dwChunkId, 4))
220                         ++nMatching;
221
222         sz = 2+2+4+4+4+4+4;
223         for(i = 0; i < format->riffindexbuffer.cursize; i += 16)
224                 if(!memcmp(format->riffindexbuffer.data + i, dwChunkId, 4))
225                         sz += 8;
226
227         SCR_CaptureVideo_RIFF_Push(fcc, NULL, sz);
228         SCR_CaptureVideo_RIFF_Write16(2); // wLongsPerEntry
229         SCR_CaptureVideo_RIFF_Write16(0x0100); // bIndexType=1, bIndexSubType=0
230         SCR_CaptureVideo_RIFF_Write32(nMatching); // nEntriesInUse
231         SCR_CaptureVideo_RIFF_WriteFourCC(dwChunkId); // dwChunkId
232         SCR_CaptureVideo_RIFF_Write32(format->videofile_ix_movistart & (fs_offset_t) 0xFFFFFFFFu);
233         SCR_CaptureVideo_RIFF_Write32(((fs_offset_t) format->videofile_ix_movistart) >> 32);
234         SCR_CaptureVideo_RIFF_Write32(0); // dwReserved
235
236         for(i = 0; i < format->riffindexbuffer.cursize; i += 16)
237                 if(!memcmp(format->riffindexbuffer.data + i, dwChunkId, 4))
238                 {
239                         unsigned int *p = (unsigned int *) (format->riffindexbuffer.data + i);
240                         unsigned int flags = p[1];
241                         unsigned int rpos = p[2];
242                         unsigned int size = p[3];
243                         size &= ~0x80000000;
244                         if(!(flags & 0x10)) // no keyframe?
245                                 size |= 0x80000000;
246                         SCR_CaptureVideo_RIFF_Write32(rpos + 8);
247                         SCR_CaptureVideo_RIFF_Write32(size);
248                 }
249
250         SCR_CaptureVideo_RIFF_Flush();
251         SCR_CaptureVideo_RIFF_Pop();
252         pos = SCR_CaptureVideo_RIFF_GetPosition();
253
254         if(FS_Seek(cls.capturevideo.videofile, masteridx_start + 16 * *masteridx_count, SEEK_SET) >= 0)
255         {
256                 SCR_CaptureVideo_RIFF_Write32(ix & (fs_offset_t) 0xFFFFFFFFu);
257                 SCR_CaptureVideo_RIFF_Write32(((fs_offset_t) ix) >> 32);
258                 SCR_CaptureVideo_RIFF_Write32(pos - ix);
259                 SCR_CaptureVideo_RIFF_Write32(nMatching);
260                 SCR_CaptureVideo_RIFF_FlushNoIncrease();
261         }
262
263         if(FS_Seek(cls.capturevideo.videofile, masteridx_counter, SEEK_SET) >= 0)
264         {
265                 SCR_CaptureVideo_RIFF_Write32(++*masteridx_count);
266                 SCR_CaptureVideo_RIFF_FlushNoIncrease();
267         }
268
269         FS_Seek(cls.capturevideo.videofile, 0, SEEK_END); // return value doesn't matter here
270 }
271
272 static void SCR_CaptureVideo_RIFF_Finish(qboolean final)
273 {
274         LOAD_FORMATSPECIFIC_AVI();
275         // close the "movi" list
276         SCR_CaptureVideo_RIFF_Pop();
277         if(format->videofile_ix_master_video_inuse_offset)
278                 SCR_CaptureVideo_RIFF_MakeIxChunk("ix00", "00dc", format->videofile_ix_master_video_inuse_offset, &format->videofile_ix_master_video_inuse, format->videofile_ix_master_video_start_offset);
279         if(format->videofile_ix_master_audio_inuse_offset)
280                 SCR_CaptureVideo_RIFF_MakeIxChunk("ix01", "01wb", format->videofile_ix_master_audio_inuse_offset, &format->videofile_ix_master_audio_inuse, format->videofile_ix_master_audio_start_offset);
281         // write the idx1 chunk that we've been building while saving the frames (for old style players)
282         if(final && format->videofile_firstchunkframes_offset)
283         // TODO replace index creating by OpenDML ix##/##ix/indx chunk so it works for more than one AVI part too
284         {
285                 SCR_CaptureVideo_RIFF_Push("idx1", NULL, format->riffindexbuffer.cursize);
286                 SCR_CaptureVideo_RIFF_WriteBytes(format->riffindexbuffer.data, format->riffindexbuffer.cursize);
287                 SCR_CaptureVideo_RIFF_Pop();
288         }
289         format->riffindexbuffer.cursize = 0;
290         // pop the RIFF chunk itself
291         while (format->riffstacklevel > 0)
292                 SCR_CaptureVideo_RIFF_Pop();
293         SCR_CaptureVideo_RIFF_Flush();
294         if(format->videofile_firstchunkframes_offset)
295         {
296                 Con_DPrintf("Finishing first chunk (%d frames)\n", cls.capturevideo.frame);
297                 if(FS_Seek(cls.capturevideo.videofile, format->videofile_firstchunkframes_offset, SEEK_SET) >= 0)
298                 {
299                         SCR_CaptureVideo_RIFF_Write32(cls.capturevideo.frame);
300                         SCR_CaptureVideo_RIFF_FlushNoIncrease();
301                 }
302                 FS_Seek(cls.capturevideo.videofile, 0, SEEK_END);
303                 format->videofile_firstchunkframes_offset = 0;
304         }
305         else
306                 Con_DPrintf("Finishing another chunk (%d frames)\n", cls.capturevideo.frame);
307 }
308
309 static void SCR_CaptureVideo_RIFF_OverflowCheck(int framesize)
310 {
311         LOAD_FORMATSPECIFIC_AVI();
312         fs_offset_t cursize;
313         //fs_offset_t curfilesize;
314         if (format->riffstacklevel != 2)
315                 Sys_Error("SCR_CaptureVideo_RIFF_OverflowCheck: chunk stack leakage!\n");
316         
317         if(!format->canseek)
318                 return;
319
320         // check where we are in the file
321         SCR_CaptureVideo_RIFF_Flush();
322         cursize = SCR_CaptureVideo_RIFF_GetPosition() - format->riffstackstartoffset[0];
323         //curfilesize = SCR_CaptureVideo_RIFF_GetPosition();
324
325         // if this would overflow the windows limit of 1GB per RIFF chunk, we need
326         // to close the current RIFF chunk and open another for future frames
327         if (8 + cursize + framesize + format->riffindexbuffer.cursize + 8 + format->riffindexbuffer.cursize + 64 > 1<<30) // note that the Ix buffer takes less space... I just don't dare to / 2 here now... sorry, maybe later
328         {
329                 SCR_CaptureVideo_RIFF_Finish(false);
330                 // begin a new 1GB extended section of the AVI
331                 SCR_CaptureVideo_RIFF_Push("RIFF", "AVIX", -1);
332                 SCR_CaptureVideo_RIFF_Push("LIST", "movi", -1);
333                 format->videofile_ix_movistart = format->riffstackstartoffset[1];
334         }
335 }
336
337 // converts from BGRA32 to I420 colorspace (identical to YV12 except chroma plane order is reversed), this colorspace is handled by the Intel(r) 4:2:0 codec on Windows
338 static void SCR_CaptureVideo_ConvertFrame_BGRA_to_I420_flip(int width, int height, unsigned char *instart, unsigned char *outstart)
339 {
340         int x, y;
341         int blockr, blockg, blockb;
342         int outoffset = (width/2)*(height/2);
343         unsigned char *b, *out;
344         // process one line at a time, and CbCr every other line at 2 pixel intervals
345         for (y = 0;y < height;y++)
346         {
347                 // 1x1 Y
348                 for (b = instart + (height-1-y)*width*4, out = outstart + y*width, x = 0;x < width;x++, b += 4, out++)
349                 {
350                         blockr = b[2];
351                         blockg = b[1];
352                         blockb = b[0];
353                         *out = cls.capturevideo.yuvnormalizetable[0][cls.capturevideo.rgbtoyuvscaletable[0][0][blockr] + cls.capturevideo.rgbtoyuvscaletable[0][1][blockg] + cls.capturevideo.rgbtoyuvscaletable[0][2][blockb]];
354                 }
355                 if ((y & 1) == 0 && y/2 < h/2) // if h is odd, this skips the last row
356                 {
357                         // 2x2 Cr and Cb planes
358                         int inpitch = width*4;
359                         for (b = instart + (height-2-y)*width*4, out = outstart + width*height + (y/2)*(width/2), x = 0;x < width/2;x++, b += 8, out++)
360                         {
361                                 blockr = (b[2] + b[6] + b[inpitch+2] + b[inpitch+6]) >> 2;
362                                 blockg = (b[1] + b[5] + b[inpitch+1] + b[inpitch+5]) >> 2;
363                                 blockb = (b[0] + b[4] + b[inpitch+0] + b[inpitch+4]) >> 2;
364                                 // Cr
365                                 out[0        ] = cls.capturevideo.yuvnormalizetable[1][cls.capturevideo.rgbtoyuvscaletable[1][0][blockr] + cls.capturevideo.rgbtoyuvscaletable[1][1][blockg] + cls.capturevideo.rgbtoyuvscaletable[1][2][blockb] + 128];
366                                 // Cb
367                                 out[outoffset] = cls.capturevideo.yuvnormalizetable[2][cls.capturevideo.rgbtoyuvscaletable[2][0][blockr] + cls.capturevideo.rgbtoyuvscaletable[2][1][blockg] + cls.capturevideo.rgbtoyuvscaletable[2][2][blockb] + 128];
368                         }
369                 }
370         }
371 }
372
373 static void SCR_CaptureVideo_Avi_VideoFrames(int num)
374 {
375         LOAD_FORMATSPECIFIC_AVI();
376         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;
381         SCR_CaptureVideo_ConvertFrame_BGRA_to_I420_flip(width, height, in, out);
382         x = width*height+(width/2)*(height/2)*2;
383         while(num-- > 0)
384         {
385                 if(format->canseek)
386                 {
387                         SCR_CaptureVideo_RIFF_OverflowCheck(8 + x);
388                         SCR_CaptureVideo_RIFF_IndexEntry("00dc", x, 0x10); // AVIIF_KEYFRAME
389                 }
390
391                 if(!format->canseek)
392                 {
393                         SCR_CaptureVideo_RIFF_Push("RIFF", "AVIX", 12+8+x);
394                         SCR_CaptureVideo_RIFF_Push("LIST", "movi", 8+x);
395                 }
396                 SCR_CaptureVideo_RIFF_Push("00dc", NULL, x);
397                 SCR_CaptureVideo_RIFF_WriteBytes(out, x);
398                 SCR_CaptureVideo_RIFF_Pop();
399                 if(!format->canseek)
400                 {
401                         SCR_CaptureVideo_RIFF_Pop();
402                         SCR_CaptureVideo_RIFF_Pop();
403                 }
404         }
405 }
406
407 void SCR_CaptureVideo_Avi_EndVideo(void)
408 {
409         LOAD_FORMATSPECIFIC_AVI();
410
411         if(format->canseek)
412         {
413                 // close any open chunks
414                 SCR_CaptureVideo_RIFF_Finish(true);
415
416                 // go back and fix the video frames and audio samples fields
417                 if(format->videofile_totalframes_offset1)
418                         if(FS_Seek(cls.capturevideo.videofile, format->videofile_totalframes_offset1, SEEK_SET) >= 0)
419                         {
420                                 SCR_CaptureVideo_RIFF_Write32(cls.capturevideo.frame);
421                                 SCR_CaptureVideo_RIFF_FlushNoIncrease();
422                         }
423                 if(format->videofile_totalframes_offset2)
424                         if(FS_Seek(cls.capturevideo.videofile, format->videofile_totalframes_offset2, SEEK_SET) >= 0)
425                         {
426                                 SCR_CaptureVideo_RIFF_Write32(cls.capturevideo.frame);
427                                 SCR_CaptureVideo_RIFF_FlushNoIncrease();
428                         }
429                 if (cls.capturevideo.soundrate)
430                 {
431                         if(format->videofile_totalsampleframes_offset)
432                                 if(FS_Seek(cls.capturevideo.videofile, format->videofile_totalsampleframes_offset, SEEK_SET) >= 0)
433                                 {
434                                         SCR_CaptureVideo_RIFF_Write32(cls.capturevideo.soundsampleframe);
435                                         SCR_CaptureVideo_RIFF_FlushNoIncrease();
436                                 }
437                 }
438         }
439
440         if (format->riffindexbuffer.data)
441         {
442                 Mem_Free(format->riffindexbuffer.data);
443                 format->riffindexbuffer.data = NULL;
444         }
445
446         FS_Close(cls.capturevideo.videofile);
447         cls.capturevideo.videofile = NULL;
448
449         Mem_Free(format);
450 }
451
452 void SCR_CaptureVideo_Avi_SoundFrame(const portable_sampleframe_t *paintbuffer, size_t length)
453 {
454         LOAD_FORMATSPECIFIC_AVI();
455         int x;
456         unsigned char bufstereo16le[PAINTBUFFER_SIZE * 4];
457         unsigned char* out_ptr;
458         size_t i;
459
460         // write the sound buffer as little endian 16bit interleaved stereo
461         for(i = 0, out_ptr = bufstereo16le; i < length; i++, out_ptr += 4)
462         {
463                 int n0, n1;
464
465                 n0 = paintbuffer[i].sample[0];
466                 n0 = bound(-32768, n0, 32767);
467                 out_ptr[0] = (unsigned char)n0;
468                 out_ptr[1] = (unsigned char)(n0 >> 8);
469
470                 n1 = paintbuffer[i].sample[1];
471                 n1 = bound(-32768, n1, 32767);
472                 out_ptr[2] = (unsigned char)n1;
473                 out_ptr[3] = (unsigned char)(n1 >> 8);
474         }
475
476         x = length*4;
477         if(format->canseek)
478         {
479                 SCR_CaptureVideo_RIFF_OverflowCheck(8 + x);
480                 SCR_CaptureVideo_RIFF_IndexEntry("01wb", x, 0x10); // AVIIF_KEYFRAME
481         }
482
483         if(!format->canseek)
484         {
485                 SCR_CaptureVideo_RIFF_Push("RIFF", "AVIX", 12+8+x);
486                 SCR_CaptureVideo_RIFF_Push("LIST", "movi", 8+x);
487         }
488         SCR_CaptureVideo_RIFF_Push("01wb", NULL, x);
489         SCR_CaptureVideo_RIFF_WriteBytes(bufstereo16le, x);
490         SCR_CaptureVideo_RIFF_Pop();
491         if(!format->canseek)
492         {
493                 SCR_CaptureVideo_RIFF_Pop();
494                 SCR_CaptureVideo_RIFF_Pop();
495         }
496 }
497
498 void SCR_CaptureVideo_Avi_BeginVideo(void)
499 {
500         int width = cls.capturevideo.width;
501         int height = cls.capturevideo.height;
502         int n, d;
503         unsigned int i;
504         double aspect;
505
506         aspect = vid.width / (vid.height * vid_pixelheight.value);
507
508         cls.capturevideo.format = CAPTUREVIDEOFORMAT_AVI_I420;
509         cls.capturevideo.formatextension = "avi";
510         cls.capturevideo.videofile = FS_OpenRealFile(va("%s.%s", cls.capturevideo.basename, cls.capturevideo.formatextension), "wb", false);
511         cls.capturevideo.endvideo = SCR_CaptureVideo_Avi_EndVideo;
512         cls.capturevideo.videoframes = SCR_CaptureVideo_Avi_VideoFrames;
513         cls.capturevideo.soundframe = SCR_CaptureVideo_Avi_SoundFrame;
514         cls.capturevideo.formatspecific = Mem_Alloc(tempmempool, sizeof(capturevideostate_avi_formatspecific_t));
515         {
516                 LOAD_FORMATSPECIFIC_AVI();
517                 format->canseek = (FS_Seek(cls.capturevideo.videofile, 0, SEEK_SET) == 0);
518                 SCR_CaptureVideo_RIFF_Start();
519                 // 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)
520                 SCR_CaptureVideo_RIFF_Push("RIFF", "AVI ", format->canseek ? -1 : 12+(8+56+12+(12+52+8+40+8+68)+(cls.capturevideo.soundrate?(12+12+52+8+18):0)+12+(8+4))+12+(8+(((int) strlen(engineversion) | 1) + 1))+12);
521                 // AVI main header
522                 SCR_CaptureVideo_RIFF_Push("LIST", "hdrl", format->canseek ? -1 : 8+56+12+(12+52+8+40+8+68)+(cls.capturevideo.soundrate?(12+12+52+8+18):0)+12+(8+4));
523                 SCR_CaptureVideo_RIFF_Push("avih", NULL, 56);
524                 SCR_CaptureVideo_RIFF_Write32((int)(1000000.0 / (cls.capturevideo.framerate / cls.capturevideo.framestep))); // microseconds per frame
525                 SCR_CaptureVideo_RIFF_Write32(0); // max bytes per second
526                 SCR_CaptureVideo_RIFF_Write32(0); // padding granularity
527                 SCR_CaptureVideo_RIFF_Write32(0x910); // flags (AVIF_HASINDEX | AVIF_ISINTERLEAVED | AVIF_TRUSTCKTYPE)
528                 format->videofile_firstchunkframes_offset = SCR_CaptureVideo_RIFF_GetPosition();
529                 SCR_CaptureVideo_RIFF_Write32(0); // total frames
530                 SCR_CaptureVideo_RIFF_Write32(0); // initial frames
531                 if (cls.capturevideo.soundrate)
532                         SCR_CaptureVideo_RIFF_Write32(2); // number of streams
533                 else
534                         SCR_CaptureVideo_RIFF_Write32(1); // number of streams
535                 SCR_CaptureVideo_RIFF_Write32(0); // suggested buffer size
536                 SCR_CaptureVideo_RIFF_Write32(width); // width
537                 SCR_CaptureVideo_RIFF_Write32(height); // height
538                 SCR_CaptureVideo_RIFF_Write32(0); // reserved[0]
539                 SCR_CaptureVideo_RIFF_Write32(0); // reserved[1]
540                 SCR_CaptureVideo_RIFF_Write32(0); // reserved[2]
541                 SCR_CaptureVideo_RIFF_Write32(0); // reserved[3]
542                 SCR_CaptureVideo_RIFF_Pop();
543                 // video stream info
544                 SCR_CaptureVideo_RIFF_Push("LIST", "strl", format->canseek ? -1 : 12+52+8+40+8+68);
545                 SCR_CaptureVideo_RIFF_Push("strh", "vids", 52);
546                 SCR_CaptureVideo_RIFF_WriteFourCC("I420"); // stream fourcc (I420 colorspace, uncompressed)
547                 SCR_CaptureVideo_RIFF_Write32(0); // flags
548                 SCR_CaptureVideo_RIFF_Write16(0); // priority
549                 SCR_CaptureVideo_RIFF_Write16(0); // language
550                 SCR_CaptureVideo_RIFF_Write32(0); // initial frames
551                 // find an ideal divisor for the framerate
552                 FindFraction(cls.capturevideo.framerate / cls.capturevideo.framestep, &n, &d, 1000);
553                 SCR_CaptureVideo_RIFF_Write32(d); // samples/second divisor
554                 SCR_CaptureVideo_RIFF_Write32(n); // samples/second multiplied by divisor
555                 SCR_CaptureVideo_RIFF_Write32(0); // start
556                 format->videofile_totalframes_offset1 = SCR_CaptureVideo_RIFF_GetPosition();
557                 SCR_CaptureVideo_RIFF_Write32(0xFFFFFFFF); // length
558                 SCR_CaptureVideo_RIFF_Write32(width*height+(width/2)*(height/2)*2); // suggested buffer size
559                 SCR_CaptureVideo_RIFF_Write32(0); // quality
560                 SCR_CaptureVideo_RIFF_Write32(0); // sample size
561                 SCR_CaptureVideo_RIFF_Write16(0); // frame left
562                 SCR_CaptureVideo_RIFF_Write16(0); // frame top
563                 SCR_CaptureVideo_RIFF_Write16(width); // frame right
564                 SCR_CaptureVideo_RIFF_Write16(height); // frame bottom
565                 SCR_CaptureVideo_RIFF_Pop();
566                 // video stream format
567                 SCR_CaptureVideo_RIFF_Push("strf", NULL, 40);
568                 SCR_CaptureVideo_RIFF_Write32(40); // BITMAPINFO struct size
569                 SCR_CaptureVideo_RIFF_Write32(width); // width
570                 SCR_CaptureVideo_RIFF_Write32(height); // height
571                 SCR_CaptureVideo_RIFF_Write16(3); // planes
572                 SCR_CaptureVideo_RIFF_Write16(12); // bitcount
573                 SCR_CaptureVideo_RIFF_WriteFourCC("I420"); // compression
574                 SCR_CaptureVideo_RIFF_Write32(width*height+(width/2)*(height/2)*2); // size of image
575                 SCR_CaptureVideo_RIFF_Write32(0); // x pixels per meter
576                 SCR_CaptureVideo_RIFF_Write32(0); // y pixels per meter
577                 SCR_CaptureVideo_RIFF_Write32(0); // color used
578                 SCR_CaptureVideo_RIFF_Write32(0); // color important
579                 SCR_CaptureVideo_RIFF_Pop();
580                 // master index
581                 if(format->canseek)
582                 {
583                         SCR_CaptureVideo_RIFF_Push("indx", NULL, -1);
584                         SCR_CaptureVideo_RIFF_Write16(4); // wLongsPerEntry
585                         SCR_CaptureVideo_RIFF_Write16(0); // bIndexSubType=0, bIndexType=0
586                         format->videofile_ix_master_video_inuse_offset = SCR_CaptureVideo_RIFF_GetPosition();
587                         SCR_CaptureVideo_RIFF_Write32(0); // nEntriesInUse
588                         SCR_CaptureVideo_RIFF_WriteFourCC("00dc"); // dwChunkId
589                         SCR_CaptureVideo_RIFF_Write32(0); // dwReserved1
590                         SCR_CaptureVideo_RIFF_Write32(0); // dwReserved2
591                         SCR_CaptureVideo_RIFF_Write32(0); // dwReserved3
592                         format->videofile_ix_master_video_start_offset = SCR_CaptureVideo_RIFF_GetPosition();
593                         for(i = 0; i < AVI_MASTER_INDEX_SIZE * 4; ++i)
594                                 SCR_CaptureVideo_RIFF_Write32(0); // fill up later
595                         SCR_CaptureVideo_RIFF_Pop();
596                 }
597                 // extended format (aspect!)
598                 SCR_CaptureVideo_RIFF_Push("vprp", NULL, 68);
599                 SCR_CaptureVideo_RIFF_Write32(0); // VideoFormatToken
600                 SCR_CaptureVideo_RIFF_Write32(0); // VideoStandard
601                 SCR_CaptureVideo_RIFF_Write32((int)(cls.capturevideo.framerate / cls.capturevideo.framestep)); // dwVerticalRefreshRate (bogus)
602                 SCR_CaptureVideo_RIFF_Write32(width); // dwHTotalInT
603                 SCR_CaptureVideo_RIFF_Write32(height); // dwVTotalInLines
604                 FindFraction(aspect, &n, &d, 1000);
605                 SCR_CaptureVideo_RIFF_Write32((n << 16) | d); // dwFrameAspectRatio // TODO a word
606                 SCR_CaptureVideo_RIFF_Write32(width); // dwFrameWidthInPixels
607                 SCR_CaptureVideo_RIFF_Write32(height); // dwFrameHeightInLines
608                 SCR_CaptureVideo_RIFF_Write32(1); // nFieldPerFrame
609                 SCR_CaptureVideo_RIFF_Write32(width); // CompressedBMWidth
610                 SCR_CaptureVideo_RIFF_Write32(height); // CompressedBMHeight
611                 SCR_CaptureVideo_RIFF_Write32(width); // ValidBMHeight
612                 SCR_CaptureVideo_RIFF_Write32(height); // ValidBMWidth
613                 SCR_CaptureVideo_RIFF_Write32(0); // ValidBMXOffset
614                 SCR_CaptureVideo_RIFF_Write32(0); // ValidBMYOffset
615                 SCR_CaptureVideo_RIFF_Write32(0); // ValidBMXOffsetInT
616                 SCR_CaptureVideo_RIFF_Write32(0); // ValidBMYValidStartLine
617                 SCR_CaptureVideo_RIFF_Pop();
618                 SCR_CaptureVideo_RIFF_Pop();
619                 if (cls.capturevideo.soundrate)
620                 {
621                         // audio stream info
622                         SCR_CaptureVideo_RIFF_Push("LIST", "strl", format->canseek ? -1 : 12+52+8+18);
623                         SCR_CaptureVideo_RIFF_Push("strh", "auds", 52);
624                         SCR_CaptureVideo_RIFF_Write32(1); // stream fourcc (PCM audio, uncompressed)
625                         SCR_CaptureVideo_RIFF_Write32(0); // flags
626                         SCR_CaptureVideo_RIFF_Write16(0); // priority
627                         SCR_CaptureVideo_RIFF_Write16(0); // language
628                         SCR_CaptureVideo_RIFF_Write32(0); // initial frames
629                         SCR_CaptureVideo_RIFF_Write32(1); // samples/second divisor
630                         SCR_CaptureVideo_RIFF_Write32((int)(cls.capturevideo.soundrate)); // samples/second multiplied by divisor
631                         SCR_CaptureVideo_RIFF_Write32(0); // start
632                         format->videofile_totalsampleframes_offset = SCR_CaptureVideo_RIFF_GetPosition();
633                         SCR_CaptureVideo_RIFF_Write32(0xFFFFFFFF); // length
634                         SCR_CaptureVideo_RIFF_Write32(cls.capturevideo.soundrate * 2); // suggested buffer size (this is a half second)
635                         SCR_CaptureVideo_RIFF_Write32(0); // quality
636                         SCR_CaptureVideo_RIFF_Write32(4); // sample size
637                         SCR_CaptureVideo_RIFF_Write16(0); // frame left
638                         SCR_CaptureVideo_RIFF_Write16(0); // frame top
639                         SCR_CaptureVideo_RIFF_Write16(0); // frame right
640                         SCR_CaptureVideo_RIFF_Write16(0); // frame bottom
641                         SCR_CaptureVideo_RIFF_Pop();
642                         // audio stream format
643                         SCR_CaptureVideo_RIFF_Push("strf", NULL, 18);
644                         SCR_CaptureVideo_RIFF_Write16(1); // format (uncompressed PCM?)
645                         SCR_CaptureVideo_RIFF_Write16(2); // channels (stereo)
646                         SCR_CaptureVideo_RIFF_Write32(cls.capturevideo.soundrate); // sampleframes per second
647                         SCR_CaptureVideo_RIFF_Write32(cls.capturevideo.soundrate * 4); // average bytes per second
648                         SCR_CaptureVideo_RIFF_Write16(4); // block align
649                         SCR_CaptureVideo_RIFF_Write16(16); // bits per sample
650                         SCR_CaptureVideo_RIFF_Write16(0); // size
651                         SCR_CaptureVideo_RIFF_Pop();
652                         // master index
653                         if(format->canseek)
654                         {
655                                 SCR_CaptureVideo_RIFF_Push("indx", NULL, -1);
656                                 SCR_CaptureVideo_RIFF_Write16(4); // wLongsPerEntry
657                                 SCR_CaptureVideo_RIFF_Write16(0); // bIndexSubType=0, bIndexType=0
658                                 format->videofile_ix_master_audio_inuse_offset = SCR_CaptureVideo_RIFF_GetPosition();
659                                 SCR_CaptureVideo_RIFF_Write32(0); // nEntriesInUse
660                                 SCR_CaptureVideo_RIFF_WriteFourCC("01wb"); // dwChunkId
661                                 SCR_CaptureVideo_RIFF_Write32(0); // dwReserved1
662                                 SCR_CaptureVideo_RIFF_Write32(0); // dwReserved2
663                                 SCR_CaptureVideo_RIFF_Write32(0); // dwReserved3
664                                 format->videofile_ix_master_audio_start_offset = SCR_CaptureVideo_RIFF_GetPosition();
665                                 for(i = 0; i < AVI_MASTER_INDEX_SIZE * 4; ++i)
666                                         SCR_CaptureVideo_RIFF_Write32(0); // fill up later
667                                 SCR_CaptureVideo_RIFF_Pop();
668                         }
669                         SCR_CaptureVideo_RIFF_Pop();
670                 }
671
672                 format->videofile_ix_master_audio_inuse = format->videofile_ix_master_video_inuse = 0;
673
674                 // extended header (for total #frames)
675                 SCR_CaptureVideo_RIFF_Push("LIST", "odml", 8+4);
676                 SCR_CaptureVideo_RIFF_Push("dmlh", NULL, 4);
677                 format->videofile_totalframes_offset2 = SCR_CaptureVideo_RIFF_GetPosition();
678                 SCR_CaptureVideo_RIFF_Write32(0xFFFFFFFF);
679                 SCR_CaptureVideo_RIFF_Pop();
680                 SCR_CaptureVideo_RIFF_Pop();
681
682                 // close the AVI header list
683                 SCR_CaptureVideo_RIFF_Pop();
684                 // software that produced this AVI video file
685                 SCR_CaptureVideo_RIFF_Push("LIST", "INFO", 8+((strlen(engineversion) | 1) + 1));
686                 SCR_CaptureVideo_RIFF_Push("ISFT", NULL, strlen(engineversion) + 1);
687                 SCR_CaptureVideo_RIFF_WriteTerminatedString(engineversion);
688                 SCR_CaptureVideo_RIFF_Pop();
689                 // enable this junk filler if you like the LIST movi to always begin at 4KB in the file (why?)
690 #if 0
691                 SCR_CaptureVideo_RIFF_Push("JUNK", NULL);
692                 x = 4096 - SCR_CaptureVideo_RIFF_GetPosition();
693                 while (x > 0)
694                 {
695                         const char *junkfiller = "[ DarkPlaces junk data ]";
696                         int i = min(x, (int)strlen(junkfiller));
697                         SCR_CaptureVideo_RIFF_WriteBytes((const unsigned char *)junkfiller, i);
698                         x -= i;
699                 }
700                 SCR_CaptureVideo_RIFF_Pop();
701 #endif
702                 SCR_CaptureVideo_RIFF_Pop();
703                 // begin the actual video section now
704                 SCR_CaptureVideo_RIFF_Push("LIST", "movi", format->canseek ? -1 : 0);
705                 format->videofile_ix_movistart = format->riffstackstartoffset[1];
706                 // we're done with the headers now...
707                 SCR_CaptureVideo_RIFF_Flush();
708                 if (format->riffstacklevel != 2)
709                         Sys_Error("SCR_CaptureVideo_BeginVideo: broken AVI writing code (stack level is %i (should be 2) at end of headers)\n", format->riffstacklevel);
710
711                 if(!format->canseek)
712                 {
713                         // close the movi immediately
714                         SCR_CaptureVideo_RIFF_Pop();
715                         // close the AVI immediately (we'll put all frames into AVIX)
716                         SCR_CaptureVideo_RIFF_Pop();
717                 }
718         }
719 }