1 // Feedback on playing videos:
2 //   quicktime - ok
3 //   vlc - ok
4 //   xine - ok
5 //   mplayer - ok
6 //   totem - ok
7 //   avidemux - ok - 3Apr09-RockKeyman:had to swap UV channels as it showed up blue
8 //   kino - ok
9 
10 #include "engine.h"
11 #include "SDL_mixer.h"
12 
13 VAR(dbgmovie, 0, 0, 1);
14 
15 struct aviindexentry
16 {
17     int frame, type, size;
18     uint offset;
19 
aviindexentryaviindexentry20     aviindexentry() {}
aviindexentryaviindexentry21     aviindexentry(int frame, int type, int size, uint offset) : frame(frame), type(type), size(size), offset(offset) {}
22 };
23 
24 struct avisegmentinfo
25 {
26     stream::offset offset, videoindexoffset, soundindexoffset;
27     int firstindex;
28     uint videoindexsize, soundindexsize, indexframes, videoframes, soundframes;
29 
avisegmentinfoavisegmentinfo30     avisegmentinfo() {}
avisegmentinfoavisegmentinfo31     avisegmentinfo(stream::offset offset, int firstindex) : offset(offset), videoindexoffset(0), soundindexoffset(0), firstindex(firstindex), videoindexsize(0), soundindexsize(0), indexframes(0), videoframes(0), soundframes(0) {}
32 };
33 
34 struct aviwriter
35 {
36     stream *f;
37     uchar *yuv;
38     uint videoframes;
39     stream::offset totalsize;
40     const uint videow, videoh, videofps;
41     string filename;
42 
43     int soundfrequency, soundchannels;
44     Uint16 soundformat;
45 
46     vector<aviindexentry> index;
47     vector<avisegmentinfo> segments;
48 
49     stream::offset fileframesoffset, fileextframesoffset, filevideooffset, filesoundoffset, superindexvideooffset, superindexsoundoffset;
50 
51     enum { MAX_CHUNK_DEPTH = 16, MAX_SUPER_INDEX = 1024 };
52     stream::offset chunkoffsets[MAX_CHUNK_DEPTH];
53     int chunkdepth;
54 
addindexaviwriter55     aviindexentry &addindex(int frame, int type, int size)
56     {
57         avisegmentinfo &seg = segments.last();
58         int i = index.length();
59         while(--i >= seg.firstindex)
60         {
61             aviindexentry &e = index[i];
62             if(frame > e.frame || (frame == e.frame && type <= e.type)) break;
63         }
64         return index.insert(i + 1, aviindexentry(frame, type, size, uint(totalsize - chunkoffsets[chunkdepth])));
65     }
66 
filespaceguessaviwriter67     double filespaceguess()
68     {
69         return double(totalsize);
70     }
71 
startchunkaviwriter72     void startchunk(const char *fcc, uint size = 0)
73     {
74         f->write(fcc, 4);
75         f->putlil<uint>(size);
76         totalsize += 4 + 4;
77         chunkoffsets[++chunkdepth] = totalsize;
78         totalsize += size;
79     }
80 
listchunkaviwriter81     void listchunk(const char *fcc, const char *lfcc)
82     {
83         startchunk(fcc);
84         f->write(lfcc, 4);
85         totalsize += 4;
86     }
87 
endchunkaviwriter88     void endchunk()
89     {
90         ASSERT(chunkdepth >= 0);
91         --chunkdepth;
92     }
93 
endlistchunkaviwriter94     void endlistchunk()
95     {
96         ASSERT(chunkdepth >= 0);
97         int size = int(totalsize - chunkoffsets[chunkdepth]);
98         f->seek(-4 - size, SEEK_CUR);
99         f->putlil(size);
100         f->seek(0, SEEK_END);
101         if(size & 1) { f->putchar(0x00); totalsize++; }
102         endchunk();
103     }
104 
writechunkaviwriter105     void writechunk(const char *fcc, const void *data, uint len) // simplify startchunk()/endchunk() to avoid f->seek()
106     {
107         f->write(fcc, 4);
108         f->putlil(len);
109         f->write(data, len);
110         totalsize += 4 + 4 + len;
111         if(len & 1) { f->putchar(0x00); totalsize++; }
112     }
113 
closeaviwriter114     void close()
115     {
116         if(!f) return;
117         flushsegment();
118 
119         uint soundindexes = 0, videoindexes = 0, soundframes = 0, videoframes = 0, indexframes = 0;
120         loopv(segments)
121         {
122             avisegmentinfo &seg = segments[i];
123             if(seg.soundindexsize) soundindexes++;
124             videoindexes++;
125             soundframes += seg.soundframes;
126             videoframes += seg.videoframes;
127             indexframes += seg.indexframes;
128         }
129         if(dbgmovie) conoutf(CON_DEBUG, "fileframes: sound=%d, video=%d+%d(dups)\n", soundframes, videoframes, indexframes-videoframes);
130         f->seek(fileframesoffset, SEEK_SET);
131         f->putlil<uint>(segments[0].indexframes);
132         f->seek(filevideooffset, SEEK_SET);
133         f->putlil<uint>(segments[0].videoframes);
134         if(segments[0].soundframes > 0)
135         {
136             f->seek(filesoundoffset, SEEK_SET);
137             f->putlil<uint>(segments[0].soundframes);
138         }
139         f->seek(fileextframesoffset, SEEK_SET);
140         f->putlil<uint>(indexframes); // total video frames
141 
142         f->seek(superindexvideooffset + 2 + 2, SEEK_SET);
143         f->putlil<uint>(videoindexes);
144         f->seek(superindexvideooffset + 2 + 2 + 4 + 4 + 4 + 4 + 4, SEEK_SET);
145         loopv(segments)
146         {
147             avisegmentinfo &seg = segments[i];
148             f->putlil<uint>(seg.videoindexoffset&stream::offset(0xFFFFFFFFU));
149             f->putlil<uint>(seg.videoindexoffset>>32);
150             f->putlil<uint>(seg.videoindexsize);
151             f->putlil<uint>(seg.indexframes);
152         }
153 
154         if(soundindexes > 0)
155         {
156             f->seek(superindexsoundoffset + 2 + 2, SEEK_SET);
157             f->putlil<uint>(soundindexes);
158             f->seek(superindexsoundoffset + 2 + 2 + 4 + 4 + 4 + 4 + 4, SEEK_SET);
159             loopv(segments)
160             {
161                 avisegmentinfo &seg = segments[i];
162                 if(!seg.soundindexsize) continue;
163                 f->putlil<uint>(seg.soundindexoffset&stream::offset(0xFFFFFFFFU));
164                 f->putlil<uint>(seg.soundindexoffset>>32);
165                 f->putlil<uint>(seg.soundindexsize);
166                 f->putlil<uint>(seg.soundframes);
167             }
168         }
169 
170         f->seek(0, SEEK_END);
171 
172         DELETEP(f);
173     }
174 
aviwriteraviwriter175     aviwriter(const char *name, uint w, uint h, uint fps, bool sound) : f(NULL), yuv(NULL), videoframes(0), totalsize(0), videow(w&~1), videoh(h&~1), videofps(fps), soundfrequency(0),soundchannels(0),soundformat(0)
176     {
177         copystring(filename, name);
178         path(filename);
179         if(!strrchr(filename, '.')) concatstring(filename, ".avi");
180 
181         extern bool nosound; // sound.cpp
182         if(sound && !nosound)
183         {
184             Mix_QuerySpec(&soundfrequency, &soundformat, &soundchannels);
185             const char *desc;
186             switch(soundformat)
187             {
188                 case AUDIO_U8:     desc = "u8"; break;
189                 case AUDIO_S8:     desc = "s8"; break;
190                 case AUDIO_U16LSB: desc = "u16l"; break;
191                 case AUDIO_U16MSB: desc = "u16b"; break;
192                 case AUDIO_S16LSB: desc = "s16l"; break;
193                 case AUDIO_S16MSB: desc = "s16b"; break;
194                 default:           desc = "unkn";
195             }
196             if(dbgmovie) conoutf(CON_DEBUG, "soundspec: %dhz %s x %d", soundfrequency, desc, soundchannels);
197         }
198     }
199 
~aviwriteraviwriter200     ~aviwriter()
201     {
202         close();
203         if(yuv) delete [] yuv;
204     }
205 
openaviwriter206     bool open()
207     {
208         f = openfile(filename, "wb");
209         if(!f) return false;
210 
211         chunkdepth = -1;
212 
213         listchunk("RIFF", "AVI ");
214 
215         listchunk("LIST", "hdrl");
216 
217         startchunk("avih", 56);
218         f->putlil<uint>(1000000 / videofps); // microsecsperframe
219         f->putlil<uint>(0); // maxbytespersec
220         f->putlil<uint>(0); // reserved
221         f->putlil<uint>(0x10 | 0x20); // flags - hasindex|mustuseindex
222         fileframesoffset = f->tell();
223         f->putlil<uint>(0); // totalvideoframes
224         f->putlil<uint>(0); // initialframes
225         f->putlil<uint>(soundfrequency > 0 ? 2 : 1); // streams
226         f->putlil<uint>(0); // buffersize
227         f->putlil<uint>(videow); // video width
228         f->putlil<uint>(videoh); // video height
229         loopi(4) f->putlil<uint>(0); // reserved
230         endchunk(); // avih
231 
232         listchunk("LIST", "strl");
233 
234         startchunk("strh", 56);
235         f->write("vids", 4); // fcctype
236         f->write("I420", 4); // fcchandler
237         f->putlil<uint>(0); // flags
238         f->putlil<uint>(0); // priority
239         f->putlil<uint>(0); // initialframes
240         f->putlil<uint>(1); // scale
241         f->putlil<uint>(videofps); // rate
242         f->putlil<uint>(0); // start
243         filevideooffset = f->tell();
244         f->putlil<uint>(0); // length
245         f->putlil<uint>(videow*videoh*3/2); // suggested buffersize
246         f->putlil<uint>(0); // quality
247         f->putlil<uint>(0); // samplesize
248         f->putlil<ushort>(0); // frame left
249         f->putlil<ushort>(0); // frame top
250         f->putlil<ushort>(videow); // frame right
251         f->putlil<ushort>(videoh); // frame bottom
252         endchunk(); // strh
253 
254         startchunk("strf", 40);
255         f->putlil<uint>(40); //headersize
256         f->putlil<uint>(videow); // width
257         f->putlil<uint>(videoh); // height
258         f->putlil<ushort>(3); // planes
259         f->putlil<ushort>(12); // bitcount
260         f->write("I420", 4); // compression
261         f->putlil<uint>(videow*videoh*3/2); // imagesize
262         f->putlil<uint>(0); // xres
263         f->putlil<uint>(0); // yres;
264         f->putlil<uint>(0); // colorsused
265         f->putlil<uint>(0); // colorsrequired
266         endchunk(); // strf
267 
268         startchunk("indx", 24 + 16*MAX_SUPER_INDEX);
269         superindexvideooffset = f->tell();
270         f->putlil<ushort>(4); // longs per entry
271         f->putlil<ushort>(0); // index of indexes
272         f->putlil<uint>(0); // entries in use
273         f->write("00dc", 4); // chunk id
274         f->putlil<uint>(0); // reserved 1
275         f->putlil<uint>(0); // reserved 2
276         f->putlil<uint>(0); // reserved 3
277         loopi(MAX_SUPER_INDEX)
278         {
279             f->putlil<uint>(0); // offset low
280             f->putlil<uint>(0); // offset high
281             f->putlil<uint>(0); // size
282             f->putlil<uint>(0); // duration
283         }
284         endchunk(); // indx
285 
286         startchunk("vprp", 68);
287         f->putlil<uint>(0); // video format token
288         f->putlil<uint>(0); // video standard
289         f->putlil<uint>(videofps); // vertical refresh rate
290         f->putlil<uint>(videow); // horizontal total
291         f->putlil<uint>(videoh); // vertical total
292         int gcd = screenw, rem = screenh;
293         while(rem > 0) { gcd %= rem; swap(gcd, rem); }
294         f->putlil<ushort>(screenh/gcd); // aspect denominator
295         f->putlil<ushort>(screenw/gcd); // aspect numerator
296         f->putlil<uint>(videow); // frame width
297         f->putlil<uint>(videoh); // frame height
298         f->putlil<uint>(1); // fields per frame
299         f->putlil<uint>(videoh); // compressed bitmap height
300         f->putlil<uint>(videow); // compressed bitmap width
301         f->putlil<uint>(videoh); // valid bitmap height
302         f->putlil<uint>(videow); // valid bitmap width
303         f->putlil<uint>(0); // valid bitmap x offset
304         f->putlil<uint>(0); // valid bitmap y offset
305         f->putlil<uint>(0); // video x offset
306         f->putlil<uint>(0); // video y start
307         endchunk(); // vprp
308 
309         endlistchunk(); // LIST strl
310 
311         if(soundfrequency > 0)
312         {
313             const int bps = (soundformat==AUDIO_U8 || soundformat == AUDIO_S8) ? 1 : 2;
314 
315             listchunk("LIST", "strl");
316 
317             startchunk("strh", 56);
318             f->write("auds", 4); // fcctype
319             f->putlil<uint>(1); // fcchandler - normally 4cc, but audio is a special case
320             f->putlil<uint>(0); // flags
321             f->putlil<uint>(0); // priority
322             f->putlil<uint>(0); // initialframes
323             f->putlil<uint>(1); // scale
324             f->putlil<uint>(soundfrequency); // rate
325             f->putlil<uint>(0); // start
326             filesoundoffset = f->tell();
327             f->putlil<uint>(0); // length
328             f->putlil<uint>(soundfrequency*bps*soundchannels/2); // suggested buffer size (this is a half second)
329             f->putlil<uint>(0); // quality
330             f->putlil<uint>(bps*soundchannels); // samplesize
331             f->putlil<ushort>(0); // frame left
332             f->putlil<ushort>(0); // frame top
333             f->putlil<ushort>(0); // frame right
334             f->putlil<ushort>(0); // frame bottom
335             endchunk(); // strh
336 
337             startchunk("strf", 18);
338             f->putlil<ushort>(1); // format (uncompressed PCM)
339             f->putlil<ushort>(soundchannels); // channels
340             f->putlil<uint>(soundfrequency); // sampleframes per second
341             f->putlil<uint>(soundfrequency*bps*soundchannels); // average bytes per second
342             f->putlil<ushort>(bps*soundchannels); // block align <-- guess
343             f->putlil<ushort>(bps*8); // bits per sample
344             f->putlil<ushort>(0); // size
345             endchunk(); //strf
346 
347             startchunk("indx", 24 + 16*MAX_SUPER_INDEX);
348             superindexsoundoffset = f->tell();
349             f->putlil<ushort>(4); // longs per entry
350             f->putlil<ushort>(0); // index of indexes
351             f->putlil<uint>(0); // entries in use
352             f->write("01wb", 4); // chunk id
353             f->putlil<uint>(0); // reserved 1
354             f->putlil<uint>(0); // reserved 2
355             f->putlil<uint>(0); // reserved 3
356             loopi(MAX_SUPER_INDEX)
357             {
358                 f->putlil<uint>(0); // offset low
359                 f->putlil<uint>(0); // offset high
360                 f->putlil<uint>(0); // size
361                 f->putlil<uint>(0); // duration
362             }
363             endchunk(); // indx
364 
365             endlistchunk(); // LIST strl
366         }
367 
368         listchunk("LIST", "odml");
369         startchunk("dmlh", 4);
370         fileextframesoffset = f->tell();
371         f->putlil<uint>(0);
372         endchunk(); // dmlh
373         endlistchunk(); // LIST odml
374 
375         listchunk("LIST", "INFO");
376         const char *software = "Cube 2: Sauerbraten";
377         writechunk("ISFT", software, strlen(software)+1);
378         endlistchunk(); // LIST INFO
379 
380         endlistchunk(); // LIST hdrl
381 
382         nextsegment();
383 
384         return true;
385     }
386 
boxsampleaviwriter387     static inline void boxsample(const uchar *src, const uint stride,
388                                  const uint area, const uint w, uint h,
389                                  const uint xlow, const uint xhigh, const uint ylow, const uint yhigh,
390                                  uint &bdst, uint &gdst, uint &rdst)
391     {
392         const uchar *end = &src[w<<2];
393         uint bt = 0, gt = 0, rt = 0;
394         for(const uchar *cur = &src[4]; cur < end; cur += 4)
395         {
396             bt += cur[0];
397             gt += cur[1];
398             rt += cur[2];
399         }
400         bt = ylow*(bt + ((src[0]*xlow + end[0]*xhigh)>>12));
401         gt = ylow*(gt + ((src[1]*xlow + end[1]*xhigh)>>12));
402         rt = ylow*(rt + ((src[2]*xlow + end[2]*xhigh)>>12));
403         if(h)
404         {
405             for(src += stride, end += stride; --h; src += stride, end += stride)
406             {
407                 uint b = 0, g = 0, r = 0;
408                 for(const uchar *cur = &src[4]; cur < end; cur += 4)
409                 {
410                     b += cur[0];
411                     g += cur[1];
412                     r += cur[2];
413                 }
414                 bt += (b<<12) + src[0]*xlow + end[0]*xhigh;
415                 gt += (g<<12) + src[1]*xlow + end[1]*xhigh;
416                 rt += (r<<12) + src[2]*xlow + end[2]*xhigh;
417             }
418             uint b = 0, g = 0, r = 0;
419             for(const uchar *cur = &src[4]; cur < end; cur += 4)
420             {
421                 b += cur[0];
422                 g += cur[1];
423                 r += cur[2];
424             }
425             bt += yhigh*(b + ((src[0]*xlow + end[0]*xhigh)>>12));
426             gt += yhigh*(g + ((src[1]*xlow + end[1]*xhigh)>>12));
427             rt += yhigh*(r + ((src[2]*xlow + end[2]*xhigh)>>12));
428         }
429         bdst = (bt*area)>>24;
430         gdst = (gt*area)>>24;
431         rdst = (rt*area)>>24;
432     }
433 
scaleyuvaviwriter434     void scaleyuv(const uchar *pixels, uint srcw, uint srch)
435     {
436         const int flip = -1;
437         const uint planesize = videow * videoh;
438         if(!yuv) yuv = new uchar[(planesize*3)/2];
439         uchar *yplane = yuv, *uplane = yuv + planesize, *vplane = yuv + planesize + planesize/4;
440         const int ystride = flip*int(videow), uvstride = flip*int(videow)/2;
441         if(flip < 0) { yplane -= int(videoh-1)*ystride; uplane -= int(videoh/2-1)*uvstride; vplane -= int(videoh/2-1)*uvstride; }
442 
443         const uint stride = srcw<<2;
444         srcw &= ~1;
445         srch &= ~1;
446         const uint wfrac = (srcw<<12)/videow, hfrac = (srch<<12)/videoh,
447                    area = ((ullong)planesize<<12)/(srcw*srch + 1),
448                    dw = videow*wfrac, dh = videoh*hfrac;
449 
450         for(uint y = 0; y < dh;)
451         {
452             uint yn = y + hfrac - 1, yi = y>>12, h = (yn>>12) - yi, ylow = ((yn|(-int(h)>>24))&0xFFFU) + 1 - (y&0xFFFU), yhigh = (yn&0xFFFU) + 1;
453             y += hfrac;
454             uint y2n = y + hfrac - 1, y2i = y>>12, h2 = (y2n>>12) - y2i, y2low = ((y2n|(-int(h2)>>24))&0xFFFU) + 1 - (y&0xFFFU), y2high = (y2n&0xFFFU) + 1;
455             y += hfrac;
456 
457             const uchar *src = &pixels[yi*stride], *src2 = &pixels[y2i*stride];
458             uchar *ydst = yplane, *ydst2 = yplane + ystride, *udst = uplane, *vdst = vplane;
459             for(uint x = 0; x < dw;)
460             {
461                 uint xn = x + wfrac - 1, xi = x>>12, w = (xn>>12) - xi, xlow = ((w+0xFFFU)&0x1000U) - (x&0xFFFU), xhigh = (xn&0xFFFU) + 1;
462                 x += wfrac;
463                 uint x2n = x + wfrac - 1, x2i = x>>12, w2 = (x2n>>12) - x2i, x2low = ((w2+0xFFFU)&0x1000U) - (x&0xFFFU), x2high = (x2n&0xFFFU) + 1;
464                 x += wfrac;
465 
466                 uint b1, g1, r1, b2, g2, r2, b3, g3, r3, b4, g4, r4;
467                 boxsample(&src[xi<<2], stride, area, w, h, xlow, xhigh, ylow, yhigh, b1, g1, r1);
468                 boxsample(&src[x2i<<2], stride, area, w2, h, x2low, x2high, ylow, yhigh, b2, g2, r2);
469                 boxsample(&src2[xi<<2], stride, area, w, h2, xlow, xhigh, y2low, y2high, b3, g3, r3);
470                 boxsample(&src2[x2i<<2], stride, area, w2, h2, x2low, x2high, y2low, y2high, b4, g4, r4);
471 
472 
473                 // Y  = 16 + 65.481*R + 128.553*G + 24.966*B
474                 // Cb = 128 - 37.797*R - 74.203*G + 112.0*B
475                 // Cr = 128 + 112.0*R - 93.786*G - 18.214*B
476                 *ydst++ = ((16<<12) + 1052*r1 + 2065*g1 + 401*b1)>>12;
477                 *ydst++ = ((16<<12) + 1052*r2 + 2065*g2 + 401*b2)>>12;
478                 *ydst2++ = ((16<<12) + 1052*r3 + 2065*g3 + 401*b3)>>12;;
479                 *ydst2++ = ((16<<12) + 1052*r4 + 2065*g4 + 401*b4)>>12;;
480 
481                 const uint b = b1 + b2 + b3 + b4,
482                            g = g1 + g2 + g3 + g4,
483                            r = r1 + r2 + r3 + r4;
484                 // note: weights here are scaled by 1<<10, as opposed to 1<<12, since r/g/b are already *4
485                 *udst++ = ((128<<12) - 152*r - 298*g + 450*b)>>12;
486                 *vdst++ = ((128<<12) + 450*r - 377*g - 73*b)>>12;
487             }
488 
489             yplane += 2*ystride;
490             uplane += uvstride;
491             vplane += uvstride;
492         }
493     }
494 
encodeyuvaviwriter495     void encodeyuv(const uchar *pixels)
496     {
497         const int flip = -1;
498         const uint planesize = videow * videoh;
499         if(!yuv) yuv = new uchar[(planesize*3)/2];
500         uchar *yplane = yuv, *uplane = yuv + planesize, *vplane = yuv + planesize + planesize/4;
501         const int ystride = flip*int(videow), uvstride = flip*int(videow)/2;
502         if(flip < 0) { yplane -= int(videoh-1)*ystride; uplane -= int(videoh/2-1)*uvstride; vplane -= int(videoh/2-1)*uvstride; }
503 
504         const uint stride = videow<<2;
505         const uchar *src = pixels, *yend = src + videoh*stride;
506         while(src < yend)
507         {
508             const uchar *src2 = src + stride, *xend = src2;
509             uchar *ydst = yplane, *ydst2 = yplane + ystride, *udst = uplane, *vdst = vplane;
510             while(src < xend)
511             {
512                 const uint b1 = src[0], g1 = src[1], r1 = src[2],
513                            b2 = src[4], g2 = src[5], r2 = src[6],
514                            b3 = src2[0], g3 = src2[1], r3 = src2[2],
515                            b4 = src2[4], g4 = src2[5], r4 = src2[6];
516 
517                 // Y  = 16 + 65.481*R + 128.553*G + 24.966*B
518                 // Cb = 128 - 37.797*R - 74.203*G + 112.0*B
519                 // Cr = 128 + 112.0*R - 93.786*G - 18.214*B
520                 *ydst++ = ((16<<12) + 1052*r1 + 2065*g1 + 401*b1)>>12;
521                 *ydst++ = ((16<<12) + 1052*r2 + 2065*g2 + 401*b2)>>12;
522                 *ydst2++ = ((16<<12) + 1052*r3 + 2065*g3 + 401*b3)>>12;;
523                 *ydst2++ = ((16<<12) + 1052*r4 + 2065*g4 + 401*b4)>>12;;
524 
525                 const uint b = b1 + b2 + b3 + b4,
526                            g = g1 + g2 + g3 + g4,
527                            r = r1 + r2 + r3 + r4;
528                 // note: weights here are scaled by 1<<10, as opposed to 1<<12, since r/g/b are already *4
529                 *udst++ = ((128<<12) - 152*r - 298*g + 450*b)>>12;
530                 *vdst++ = ((128<<12) + 450*r - 377*g - 73*b)>>12;
531 
532                 src += 8;
533                 src2 += 8;
534             }
535             src = src2;
536             yplane += 2*ystride;
537             uplane += uvstride;
538             vplane += uvstride;
539         }
540     }
541 
compressyuvaviwriter542     void compressyuv(const uchar *pixels)
543     {
544         const int flip = -1;
545         const uint planesize = videow * videoh;
546         if(!yuv) yuv = new uchar[(planesize*3)/2];
547         uchar *yplane = yuv, *uplane = yuv + planesize, *vplane = yuv + planesize + planesize/4;
548         const int ystride = flip*int(videow), uvstride = flip*int(videow)/2;
549         if(flip < 0) { yplane -= int(videoh-1)*ystride; uplane -= int(videoh/2-1)*uvstride; vplane -= int(videoh/2-1)*uvstride; }
550 
551         const uint stride = videow<<2;
552         const uchar *src = pixels, *yend = src + videoh*stride;
553         while(src < yend)
554         {
555             const uchar *src2 = src + stride, *xend = src2;
556             uchar *ydst = yplane, *ydst2 = yplane + ystride, *udst = uplane, *vdst = vplane;
557             while(src < xend)
558             {
559                 *ydst++ = src[0];
560                 *ydst++ = src[4];
561                 *ydst2++ = src2[0];
562                 *ydst2++ = src2[4];
563 
564                 *udst++ = (uint(src[1]) + uint(src[5]) + uint(src2[1]) + uint(src2[5])) >> 2;
565                 *vdst++ = (uint(src[2]) + uint(src[6]) + uint(src2[2]) + uint(src2[6])) >> 2;
566 
567                 src += 8;
568                 src2 += 8;
569             }
570             src = src2;
571             yplane += 2*ystride;
572             uplane += uvstride;
573             vplane += uvstride;
574         }
575     }
576 
writesoundaviwriter577     bool writesound(uchar *data, uint framesize, uint frame)
578     {
579         // do conversion in-place to little endian format
580         // note that xoring by half the range yields the same bit pattern as subtracting the range regardless of signedness
581         // ... so can toggle signedness just by xoring the high byte with 0x80
582         switch(soundformat)
583         {
584             case AUDIO_U8:
585                 for(uchar *dst = data, *end = &data[framesize]; dst < end; dst++) *dst ^= 0x80;
586                 break;
587             case AUDIO_S8:
588                 break;
589             case AUDIO_U16LSB:
590                 for(uchar *dst = &data[1], *end = &data[framesize]; dst < end; dst += 2) *dst ^= 0x80;
591                 break;
592             case AUDIO_U16MSB:
593                 for(ushort *dst = (ushort *)data, *end = (ushort *)&data[framesize]; dst < end; dst++)
594 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
595                     *dst = endianswap(*dst) ^ 0x0080;
596 #else
597                     *dst = endianswap(*dst) ^ 0x8000;
598 #endif
599                 break;
600             case AUDIO_S16LSB:
601                 break;
602             case AUDIO_S16MSB:
603                 endianswap((short *)data, framesize/2);
604                 break;
605         }
606 
607         if(totalsize - segments.last().offset + framesize > 1000*1000*1000 && !nextsegment()) return false;
608 
609         addindex(frame, 1, framesize);
610 
611         writechunk("01wb", data, framesize);
612 
613         return true;
614     }
615 
616 
617     enum
618     {
619         VID_RGB = 0,
620         VID_YUV,
621         VID_YUV420
622     };
623 
flushsegmentaviwriter624     void flushsegment()
625     {
626         endlistchunk(); // LIST movi
627 
628         avisegmentinfo &seg = segments.last();
629 
630         uint indexframes = 0, videoframes = 0, soundframes = 0;
631         for(int i = seg.firstindex; i < index.length(); i++)
632         {
633             aviindexentry &e = index[i];
634             if(e.type) soundframes++;
635             else
636             {
637                 if(i == seg.firstindex || e.offset != index[i-1].offset)
638                     videoframes++;
639                 indexframes++;
640             }
641         }
642 
643         if(segments.length() == 1)
644         {
645             startchunk("idx1", index.length()*16);
646             loopv(index)
647             {
648                 aviindexentry &entry = index[i];
649                 // printf("%3d %s %08x\n", i, (entry.type==1)?"s":"v", entry.offset);
650                 f->write(entry.type ? "01wb" : "00dc", 4); // chunkid
651                 f->putlil<uint>(0x10); // flags - KEYFRAME
652                 f->putlil<uint>(entry.offset); // offset (relative to movi)
653                 f->putlil<uint>(entry.size); // size
654             }
655             endchunk();
656         }
657 
658         seg.videoframes = videoframes;
659         seg.videoindexoffset = totalsize;
660         startchunk("ix00", 24 + indexframes*8);
661         f->putlil<ushort>(2); // longs per entry
662         f->putlil<ushort>(0x0100); // index of chunks
663         f->putlil<uint>(indexframes); // entries in use
664         f->write("00dc", 4); // chunk id
665         f->putlil<uint>(seg.offset&stream::offset(0xFFFFFFFFU)); // offset low
666         f->putlil<uint>(seg.offset>>32); // offset high
667         f->putlil<uint>(0); // reserved 3
668         for(int i = seg.firstindex; i < index.length(); i++)
669         {
670             aviindexentry &e = index[i];
671             if(e.type) continue;
672             f->putlil<uint>(e.offset + 4 + 4);
673             f->putlil<uint>(e.size);
674         }
675         endchunk(); // ix00
676         seg.videoindexsize = uint(totalsize - seg.videoindexoffset);
677 
678         if(soundframes)
679         {
680             seg.soundframes = soundframes;
681             seg.soundindexoffset = totalsize;
682             startchunk("ix01", 24 + soundframes*8);
683             f->putlil<ushort>(2); // longs per entry
684             f->putlil<ushort>(0x0100); // index of chunks
685             f->putlil<uint>(soundframes); // entries in use
686             f->write("01wb", 4); // chunk id
687             f->putlil<uint>(seg.offset&stream::offset(0xFFFFFFFFU)); // offset low
688             f->putlil<uint>(seg.offset>>32); // offset high
689             f->putlil<uint>(0); // reserved 3
690             for(int i = seg.firstindex; i < index.length(); i++)
691             {
692                 aviindexentry &e = index[i];
693                 if(!e.type) continue;
694                 f->putlil<uint>(e.offset + 4 + 4);
695                 f->putlil<uint>(e.size);
696             }
697             endchunk(); // ix01
698             seg.soundindexsize = uint(totalsize - seg.soundindexoffset);
699         }
700 
701         endlistchunk(); // RIFF AVI/AVIX
702     }
703 
nextsegmentaviwriter704     bool nextsegment()
705     {
706         if(segments.length())
707         {
708             if(segments.length() >= MAX_SUPER_INDEX) return false;
709             flushsegment();
710             listchunk("RIFF", "AVIX");
711         }
712         listchunk("LIST", "movi");
713         segments.add(avisegmentinfo(chunkoffsets[chunkdepth], index.length()));
714         return true;
715     }
716 
writevideoframeaviwriter717     bool writevideoframe(const uchar *pixels, uint srcw, uint srch, int format, uint frame)
718     {
719         if(frame < videoframes) return true;
720 
721         switch(format)
722         {
723             case VID_RGB:
724                 if(srcw != videow || srch != videoh) scaleyuv(pixels, srcw, srch);
725                 else encodeyuv(pixels);
726                 break;
727             case VID_YUV:
728                 compressyuv(pixels);
729                 break;
730         }
731 
732         const uint framesize = (videow * videoh * 3) / 2;
733         if(totalsize - segments.last().offset + framesize > 1000*1000*1000 && !nextsegment()) return false;
734 
735         while(videoframes <= frame) addindex(videoframes++, 0, framesize);
736 
737         writechunk("00dc", format == VID_YUV420 ? pixels : yuv, framesize);
738 
739         return true;
740     }
741 
742 };
743 
744 VAR(movieaccelblit, 0, 0, 1);
745 VAR(movieaccelyuv, 0, 1, 1);
746 VARP(movieaccel, 0, 1, 1);
747 VARP(moviesync, 0, 0, 1);
748 FVARP(movieminquality, 0, 0, 1);
749 
750 namespace recorder
751 {
752     static enum { REC_OK = 0, REC_USERHALT, REC_TOOSLOW, REC_FILERROR } state = REC_OK;
753 
754     static aviwriter *file = NULL;
755     static int starttime = 0;
756 
757     static int stats[1000];
758     static int statsindex = 0;
759     static uint dps = 0; // dropped frames per sample
760 
761     enum { MAXSOUNDBUFFERS = 128 }; // sounds queue up until there is a video frame, so at low fps you'll need a bigger queue
762     struct soundbuffer
763     {
764         uchar *sound;
765         uint size, maxsize;
766         uint frame;
767 
soundbufferrecorder::soundbuffer768         soundbuffer() : sound(NULL), maxsize(0) {}
~soundbufferrecorder::soundbuffer769         ~soundbuffer() { cleanup(); }
770 
loadrecorder::soundbuffer771         void load(uchar *stream, uint len, uint fnum)
772         {
773             if(len > maxsize)
774             {
775                 DELETEA(sound);
776                 sound = new uchar[len];
777                 maxsize = len;
778             }
779             size = len;
780             frame = fnum;
781             memcpy(sound, stream, len);
782         }
783 
cleanuprecorder::soundbuffer784         void cleanup() { DELETEA(sound); maxsize = 0; }
785     };
786     static queue<soundbuffer, MAXSOUNDBUFFERS> soundbuffers;
787     static SDL_mutex *soundlock = NULL;
788 
789     enum { MAXVIDEOBUFFERS = 2 }; // double buffer
790     struct videobuffer
791     {
792         uchar *video;
793         uint w, h, bpp, frame;
794         int format;
795 
videobufferrecorder::videobuffer796         videobuffer() : video(NULL){}
~videobufferrecorder::videobuffer797         ~videobuffer() { cleanup(); }
798 
initrecorder::videobuffer799         void init(int nw, int nh, int nbpp)
800         {
801             DELETEA(video);
802             w = nw;
803             h = nh;
804             bpp = nbpp;
805             video = new uchar[w*h*bpp];
806             format = -1;
807         }
808 
cleanuprecorder::videobuffer809         void cleanup() { DELETEA(video); }
810     };
811     static queue<videobuffer, MAXVIDEOBUFFERS> videobuffers;
812     static uint lastframe = ~0U;
813 
814     static GLuint scalefb = 0, scaletex[2] = { 0, 0 };
815     static uint scalew = 0, scaleh = 0;
816     static GLuint encodefb = 0, encoderb = 0;
817 
818     static SDL_Thread *thread = NULL;
819     static SDL_mutex *videolock = NULL;
820     static SDL_cond *shouldencode = NULL, *shouldread = NULL;
821 
isrecording()822     bool isrecording() { return file != NULL; }
823 
calcquality()824     float calcquality()
825     {
826         return 1.0f - float(dps)/float(dps+file->videofps); // strictly speaking should lock to read dps - 1.0=perfect, 0.5=half of frames are beingdropped
827     }
828 
gettime()829     int gettime()
830     {
831         return inbetweenframes ? getclockmillis() : totalmillis;
832     }
833 
videoencoder(void * data)834     int videoencoder(void *data) // runs on a separate thread
835     {
836         for(int numvid = 0, numsound = 0;;)
837         {
838             SDL_LockMutex(videolock);
839             for(; numvid > 0; numvid--) videobuffers.remove();
840             SDL_CondSignal(shouldread);
841             while(videobuffers.empty() && state == REC_OK) SDL_CondWait(shouldencode, videolock);
842             if(state != REC_OK) { SDL_UnlockMutex(videolock); break; }
843             videobuffer &m = videobuffers.removing();
844             numvid++;
845             SDL_UnlockMutex(videolock);
846 
847             if(file->soundfrequency > 0)
848             {
849                 // chug data from lock protected buffer to avoid holding lock while writing to file
850                 SDL_LockMutex(soundlock);
851                 for(; numsound > 0; numsound--) soundbuffers.remove();
852                 for(; numsound < soundbuffers.length(); numsound++)
853                 {
854                     soundbuffer &s = soundbuffers.removing(numsound);
855                     if(s.frame > m.frame) break; // sync with video
856                 }
857                 SDL_UnlockMutex(soundlock);
858                 loopi(numsound)
859                 {
860                     soundbuffer &s = soundbuffers.removing(i);
861                     if(!file->writesound(s.sound, s.size, s.frame)) state = REC_FILERROR;
862                 }
863             }
864 
865             int duplicates = m.frame - (int)file->videoframes + 1;
866             if(duplicates > 0) // determine how many frames have been dropped over the sample window
867             {
868                 dps -= stats[statsindex];
869                 stats[statsindex] = duplicates-1;
870                 dps += stats[statsindex];
871                 statsindex = (statsindex+1)%file->videofps;
872             }
873             //printf("frame %d->%d (%d dps): sound = %d bytes\n", file->videoframes, nextframenum, dps, m.soundlength);
874             if(calcquality() < movieminquality) state = REC_TOOSLOW;
875             else if(!file->writevideoframe(m.video, m.w, m.h, m.format, m.frame)) state = REC_FILERROR;
876 
877             m.frame = ~0U;
878         }
879 
880         return 0;
881     }
882 
soundencoder(void * udata,Uint8 * stream,int len)883     void soundencoder(void *udata, Uint8 *stream, int len) // callback occurs on a separate thread
884     {
885         SDL_LockMutex(soundlock);
886         if(soundbuffers.full())
887         {
888             if(movieminquality >= 1) state = REC_TOOSLOW;
889         }
890         else if(state == REC_OK)
891         {
892             uint nextframe = (max(gettime() - starttime, 0)*file->videofps)/1000;
893             soundbuffer &s = soundbuffers.add();
894             s.load((uchar *)stream, len, nextframe);
895         }
896         SDL_UnlockMutex(soundlock);
897     }
898 
start(const char * filename,int videofps,int videow,int videoh,bool sound)899     void start(const char *filename, int videofps, int videow, int videoh, bool sound)
900     {
901         if(file) return;
902 
903         useshaderbyname("moviergb");
904         useshaderbyname("movieyuv");
905         useshaderbyname("moviey");
906         useshaderbyname("movieu");
907         useshaderbyname("moviev");
908 
909         int fps, bestdiff, worstdiff;
910         getfps(fps, bestdiff, worstdiff);
911         if(videofps > fps) conoutf(CON_WARN, "frame rate may be too low to capture at %d fps", videofps);
912 
913         if(videow%2) videow += 1;
914         if(videoh%2) videoh += 1;
915 
916         file = new aviwriter(filename, videow, videoh, videofps, sound);
917         if(!file->open())
918         {
919             conoutf(CON_ERROR, "unable to create file %s", filename);
920             DELETEP(file);
921             return;
922         }
923         conoutf("movie recording to: %s %dx%d @ %dfps%s", file->filename, file->videow, file->videoh, file->videofps, (file->soundfrequency>0)?" + sound":"");
924 
925         starttime = gettime();
926         loopi(file->videofps) stats[i] = 0;
927         statsindex = 0;
928         dps = 0;
929 
930         lastframe = ~0U;
931         videobuffers.clear();
932         loopi(MAXVIDEOBUFFERS)
933         {
934             uint w = screenw, h = screenw;
935             videobuffers.data[i].init(w, h, 4);
936             videobuffers.data[i].frame = ~0U;
937         }
938 
939         soundbuffers.clear();
940 
941         soundlock = SDL_CreateMutex();
942         videolock = SDL_CreateMutex();
943         shouldencode = SDL_CreateCond();
944         shouldread = SDL_CreateCond();
945         thread = SDL_CreateThread(videoencoder, "video encoder", NULL);
946         if(file->soundfrequency > 0) Mix_SetPostMix(soundencoder, NULL);
947     }
948 
cleanup()949     void cleanup()
950     {
951         if(scalefb) { glDeleteFramebuffers_(1, &scalefb); scalefb = 0; }
952         if(scaletex[0] || scaletex[1]) { glDeleteTextures(2, scaletex); memset(scaletex, 0, sizeof(scaletex)); }
953         scalew = scaleh = 0;
954         if(encodefb) { glDeleteFramebuffers_(1, &encodefb); encodefb = 0; }
955         if(encoderb) { glDeleteRenderbuffers_(1, &encoderb); encoderb = 0; }
956     }
957 
stop()958     void stop()
959     {
960         if(!file) return;
961         if(state == REC_OK) state = REC_USERHALT;
962         if(file->soundfrequency > 0) Mix_SetPostMix(NULL, NULL);
963 
964         SDL_LockMutex(videolock); // wakeup thread enough to kill it
965         SDL_CondSignal(shouldencode);
966         SDL_UnlockMutex(videolock);
967 
968         SDL_WaitThread(thread, NULL); // block until thread is finished
969 
970         cleanup();
971 
972         loopi(MAXVIDEOBUFFERS) videobuffers.data[i].cleanup();
973         loopi(MAXSOUNDBUFFERS) soundbuffers.data[i].cleanup();
974 
975         SDL_DestroyMutex(soundlock);
976         SDL_DestroyMutex(videolock);
977         SDL_DestroyCond(shouldencode);
978         SDL_DestroyCond(shouldread);
979 
980         soundlock = videolock = NULL;
981         shouldencode = shouldread = NULL;
982         thread = NULL;
983 
984         static const char * const mesgs[] = { "ok", "stopped", "computer too slow", "file error"};
985         conoutf("movie recording halted: %s, %d frames", mesgs[state], file->videoframes);
986 
987         DELETEP(file);
988         state = REC_OK;
989     }
990 
readbuffer(videobuffer & m,uint nextframe)991     void readbuffer(videobuffer &m, uint nextframe)
992     {
993         bool accelyuv = movieaccelyuv && !(m.w%8),
994              usefbo = movieaccel && file->videow <= (uint)screenw && file->videoh <= (uint)screenh && (accelyuv || file->videow < (uint)screenw || file->videoh < (uint)screenh);
995         uint w = screenw, h = screenh;
996         if(usefbo) { w = file->videow; h = file->videoh; }
997         if(w != m.w || h != m.h) m.init(w, h, 4);
998         m.format = aviwriter::VID_RGB;
999         m.frame = nextframe;
1000 
1001         glPixelStorei(GL_PACK_ALIGNMENT, texalign(m.video, m.w, 4));
1002         if(usefbo)
1003         {
1004             uint tw = screenw, th = screenh;
1005             if(hasFBB && movieaccelblit) { tw = max(tw/2, m.w); th = max(th/2, m.h); }
1006             if(tw != scalew || th != scaleh)
1007             {
1008                 if(!scalefb) glGenFramebuffers_(1, &scalefb);
1009                 loopi(2)
1010                 {
1011                     if(!scaletex[i]) glGenTextures(1, &scaletex[i]);
1012                     createtexture(scaletex[i], tw, th, NULL, 3, 1, GL_RGB);
1013                 }
1014                 scalew = tw;
1015                 scaleh = th;
1016             }
1017             if(accelyuv && (!encodefb || !encoderb))
1018             {
1019                 if(!encodefb) glGenFramebuffers_(1, &encodefb);
1020                 glBindFramebuffer_(GL_FRAMEBUFFER, encodefb);
1021                 if(!encoderb) glGenRenderbuffers_(1, &encoderb);
1022                 glBindRenderbuffer_(GL_RENDERBUFFER, encoderb);
1023                 glRenderbufferStorage_(GL_RENDERBUFFER, GL_RGBA, (m.w*3)/8, m.h);
1024                 glFramebufferRenderbuffer_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, encoderb);
1025                 glBindRenderbuffer_(GL_RENDERBUFFER, 0);
1026                 glBindFramebuffer_(GL_FRAMEBUFFER, 0);
1027             }
1028 
1029             if(tw < (uint)screenw || th < (uint)screenh)
1030             {
1031                 glBindFramebuffer_(GL_READ_FRAMEBUFFER, 0);
1032                 glBindFramebuffer_(GL_DRAW_FRAMEBUFFER, scalefb);
1033                 glFramebufferTexture2D_(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, scaletex[0], 0);
1034                 glBlitFramebuffer_(0, 0, screenw, screenh, 0, 0, tw, th, GL_COLOR_BUFFER_BIT, GL_LINEAR);
1035                 glBindFramebuffer_(GL_DRAW_FRAMEBUFFER, 0);
1036             }
1037             else
1038             {
1039                 glBindTexture(GL_TEXTURE_2D, scaletex[0]);
1040                 glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, screenw, screenh);
1041             }
1042 
1043             GLOBALPARAMF(moviescale, 1.0f/scalew, 1.0f/scaleh);
1044             if(tw > m.w || th > m.h || (!accelyuv && tw >= m.w && th >= m.h))
1045             {
1046                 glBindFramebuffer_(GL_FRAMEBUFFER, scalefb);
1047                 do
1048                 {
1049                     uint dw = max(tw/2, m.w), dh = max(th/2, m.h);
1050                     glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, scaletex[1], 0);
1051                     glViewport(0, 0, dw, dh);
1052                     glBindTexture(GL_TEXTURE_2D, scaletex[0]);
1053                     if(dw == m.w && dh == m.h && !accelyuv) { SETSHADER(movieyuv); m.format = aviwriter::VID_YUV; }
1054                     else SETSHADER(moviergb);
1055                     screenquad(tw/float(scalew), th/float(scaleh));
1056                     tw = dw;
1057                     th = dh;
1058                     swap(scaletex[0], scaletex[1]);
1059                 } while(tw > m.w || th > m.h);
1060             }
1061             if(accelyuv)
1062             {
1063                 glBindFramebuffer_(GL_FRAMEBUFFER, encodefb);
1064                 glBindTexture(GL_TEXTURE_2D, scaletex[0]);
1065                 glViewport(0, 0, m.w/4, m.h); SETSHADER(moviey); screenquadflipped(m.w/float(scalew), m.h/float(scaleh));
1066                 glViewport(m.w/4, 0, m.w/8, m.h/2); SETSHADER(movieu); screenquadflipped(m.w/float(scalew), m.h/float(scaleh));
1067                 glViewport(m.w/4, m.h/2, m.w/8, m.h/2); SETSHADER(moviev); screenquadflipped(m.w/float(scalew), m.h/float(scaleh));
1068                 const uint planesize = m.w * m.h;
1069                 glPixelStorei(GL_PACK_ALIGNMENT, texalign(m.video, m.w/4, 4));
1070                 glReadPixels(0, 0, m.w/4, m.h, GL_BGRA, GL_UNSIGNED_BYTE, m.video);
1071                 glPixelStorei(GL_PACK_ALIGNMENT, texalign(&m.video[planesize], m.w/8, 4));
1072                 glReadPixels(m.w/4, 0, m.w/8, m.h/2, GL_BGRA, GL_UNSIGNED_BYTE, &m.video[planesize]);
1073                 glPixelStorei(GL_PACK_ALIGNMENT, texalign(&m.video[planesize + planesize/4], m.w/8, 4));
1074                 glReadPixels(m.w/4, m.h/2, m.w/8, m.h/2, GL_BGRA, GL_UNSIGNED_BYTE, &m.video[planesize + planesize/4]);
1075                 m.format = aviwriter::VID_YUV420;
1076             }
1077             else
1078             {
1079                 glBindFramebuffer_(GL_FRAMEBUFFER, scalefb);
1080                 glReadPixels(0, 0, m.w, m.h, GL_BGRA, GL_UNSIGNED_BYTE, m.video);
1081             }
1082             glBindFramebuffer_(GL_FRAMEBUFFER, 0);
1083             glViewport(0, 0, screenw, screenh);
1084 
1085         }
1086         else glReadPixels(0, 0, m.w, m.h, GL_BGRA, GL_UNSIGNED_BYTE, m.video);
1087     }
1088 
readbuffer()1089     bool readbuffer()
1090     {
1091         if(!file) return false;
1092         if(state != REC_OK)
1093         {
1094             stop();
1095             return false;
1096         }
1097         SDL_LockMutex(videolock);
1098         if(moviesync && videobuffers.full()) SDL_CondWait(shouldread, videolock);
1099         uint nextframe = (max(gettime() - starttime, 0)*file->videofps)/1000;
1100         if(!videobuffers.full() && (lastframe == ~0U || nextframe > lastframe))
1101         {
1102             videobuffer &m = videobuffers.adding();
1103             SDL_UnlockMutex(videolock);
1104             readbuffer(m, nextframe);
1105             SDL_LockMutex(videolock);
1106             lastframe = nextframe;
1107             videobuffers.add();
1108             SDL_CondSignal(shouldencode);
1109         }
1110         SDL_UnlockMutex(videolock);
1111         return true;
1112     }
1113 
drawhud()1114     void drawhud()
1115     {
1116         int w = screenw, h = screenh;
1117         if(forceaspect) w = int(ceil(h*forceaspect));
1118         gettextres(w, h);
1119 
1120         hudmatrix.ortho(0, w, h, 0, -1, 1);
1121         hudmatrix.scale(1/3.0f, 1/3.0f, 1);
1122         resethudmatrix();
1123         hudshader->set();
1124 
1125         glEnable(GL_BLEND);
1126 
1127         double totalsize = file->filespaceguess();
1128         const char *unit = "KB";
1129         if(totalsize >= 1e9) { totalsize /= 1e9; unit = "GB"; }
1130         else if(totalsize >= 1e6) { totalsize /= 1e6; unit = "MB"; }
1131         else totalsize /= 1e3;
1132 
1133         draw_textf("recorded %.1f%s %d%%", w*3-10*FONTH, h*3-FONTH-FONTH*3/2, totalsize, unit, int(calcquality()*100));
1134 
1135         glDisable(GL_BLEND);
1136     }
1137 
capture(bool overlay)1138     void capture(bool overlay)
1139     {
1140         if(readbuffer() && overlay) drawhud();
1141     }
1142 }
1143 
1144 VARP(moview, 0, 320, 10000);
1145 VARP(movieh, 0, 240, 10000);
1146 VARP(moviefps, 1, 24, 1000);
1147 VARP(moviesound, 0, 1, 1);
1148 
movie(char * name)1149 void movie(char *name)
1150 {
1151     if(name[0] == '\0') recorder::stop();
1152     else if(!recorder::isrecording()) recorder::start(name, moviefps, moview ? moview : screenw, movieh ? movieh : screenh, moviesound!=0);
1153 }
1154 
1155 COMMAND(movie, "s");
1156 ICOMMAND(movierecording, "", (), intret(recorder::isrecording() ? 1 : 0));
1157 
1158