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