1 /*
2  *  Copyright (C) 2006, William K Volkman <wkvsf@users.sourceforge.net>
3  *
4  *  This program is free software; you can redistribute it and/or
5  *  modify it under the terms of the GNU General Public License
6  *  as published by the Free Software Foundation; either version 2
7  *  of the License, or (at your option) any later version.
8  *
9  * http://www.fourcc.org/fccyvrgb.php
10  * Ey = 0.299R+0.587G+0.114B
11  * Ecr = 0.713(R - Ey) = 0.500R-0.419G-0.081B
12  * Ecb = 0.564(B - Ey) = -0.169R-0.331G+0.500B
13  *
14  * the defined range for Y is [16,235] (220 steps) and the valid ranges
15  * for Cr and Cb are [16,239] (235 steps)
16  *
17  * http://www.neuro.sfc.keio.ac.jp/~aly/polygon/info/color-space-faq.html
18  *  RGB -> YUV                    | YUV -> RGB
19  *  Y =  0.299*Red+0.587*Green+0.114*Blue    | Red   = Y+0.000*U+1.140*V
20  *  U = -0.147*Red-0.289*Green+0.436*Blue    | Green = Y-0.396*U-0.581*V
21  *  V =  0.615*Red-0.515*Green-0.100*Blue    | Blue  = Y+2.029*U+0.000*V
22  *
23  *  +----------------+---------------+-----------------+----------------+
24  *  | Recommendation | Coef. for red | Coef. for Green | Coef. for Blue |
25  *  +----------------+---------------+-----------------+----------------+
26  *  | Rec 601-1      | 0.299         | 0.587           | 0.114          |
27  *  | Rec 709        | 0.2125        | 0.7154          | 0.0721         |
28  *  | ITU            | 0.2125        | 0.7154          | 0.0721         |
29  *  +----------------+---------------+-----------------+----------------+
30  *  RGB -> YCbCr
31  *  Y  = Coef. for red*Red+Coef. for green*Green+Coef. for blue*Blue
32  *  Cb = (Blue-Y)/(2-2*Coef. for blue)
33  *  Cr = (Red-Y)/(2-2*Coef. for red)
34  *
35  *  RGB -> YCbCr (with Rec 601-1 specs)        | YCbCr (with Rec 601-1 specs) -> RGB
36  *  Y=  0.2989*Red+0.5866*Green+0.1145*Blue    | Red=  Y+0.0000*Cb+1.4022*Cr
37  *  Cb=-0.1687*Red-0.3312*Green+0.5000*Blue    | Green=Y-0.3456*Cb-0.7145*Cr
38  *  Cr= 0.5000*Red-0.4183*Green-0.0816*Blue    | Blue= Y+1.7710*Cb+0.0000*Cr
39  *
40  * http://en.wikipedia.org/wiki/YUV/RGB_conversion_formulas
41  * Y := min(abs(r * 2104 + g * 4130 + b * 802 + 4096 + 131072) >> 13,235)
42  * U := min(abs(r * -1214 + g * -2384 + b * 3598 + 4096 + 1048576) >> 13,240)
43  * V := min(abs(r * 3598 + g * -3013 + b * -585 + 4096 + 1048576) >> 13,240)
44  */
45 
46 
47 #ifndef _GNU_SOURCE
48 # define _GNU_SOURCE
49 #endif
50 #define _LARGEFILE_SOURCE
51 #define _LARGEFILE64_SOURCE
52 #define _FILE_OFFSET_BITS 64
53 
54 #ifdef HAVE_CONFIG_H
55 # include <config.h>
56 #endif
57 
58 #ifndef _REENTRANT
59 # define _REENTRANT
60 #endif
61 
62 #include <cstdlib>
63 #include <cstdio>
64 #include <cmath>
65 #include <celutil/debug.h>
66 #include <celutil/util.h>
67 #include "../celengine/gl.h"
68 #include <string>
69 #include <cstring>
70 #include "theora/theora.h"
71 
72 using namespace std;
73 
74 #include "oggtheoracapture.h"
75 
76 //  {"video-rate-target",required_argument,NULL,'V'},
77 //  {"video-quality",required_argument,NULL,'v'},
78 //  {"aspect-numerator",optional_argument,NULL,'s'},
79 //  {"aspect-denominator",optional_argument,NULL,'S'},
80 //  {"framerate-numerator",optional_argument,NULL,'f'},
81 //  {"framerate-denominator",optional_argument,NULL,'F'},
82 
83 
OggTheoraCapture()84 OggTheoraCapture::OggTheoraCapture():
85     video_x(0),
86     video_y(0),
87     frame_x(0),
88     frame_y(0),
89     frame_x_offset(0),
90     frame_y_offset(0),
91     video_an(4),
92     video_ad(3),
93     video_hzn(12),
94     video_hzd(1),
95     video_r(-1), // 45000 <= video_r <= 2000000 (45Kbps - 2000Kbps)
96     video_q(63), // 0-63 aka 0-10 * 6.3 the higher the value the faster the encoding and the larger the output file
97     capturing(false),
98     video_frame_count(0),
99     video_bytesout(0),
100     rowStride(0),
101     pixels(NULL),
102     outfile(NULL)
103 {
104     yuvframe[0] = NULL;
105     yuvframe[1] = NULL;
106     // Just being anal
107     memset(&yuv, 0, sizeof(yuv));
108     memset(&to, 0, sizeof(to));
109     memset(&videopage, 0, sizeof(videopage));
110     memset(&op, 0, sizeof(op));
111     memset(&td, 0, sizeof(td));
112     memset(&ti, 0, sizeof(ti));
113     memset(&tc, 0, sizeof(tc));
114 }
115 
setAspectRatio(int aspect_numerator,int aspect_denominator)116 void OggTheoraCapture::setAspectRatio(int aspect_numerator, int aspect_denominator)
117 {
118     int    a = aspect_numerator;
119     int    b = aspect_denominator;
120     while (a != b)
121     {
122         if (a > b)
123             a = a - b;
124         else
125             b = b - a;
126     }
127     if (a > 1) {
128         video_an = aspect_numerator / a;
129         video_ad = aspect_denominator / a;
130     }
131     else
132     {
133         video_an = aspect_numerator;
134         video_ad = aspect_denominator;
135     }
136 }
setQuality(float quality)137 void OggTheoraCapture::setQuality(float quality)
138 {
139     if (quality < 0.0)
140         video_q = 7;
141     else if (quality < 1.0)
142         video_q = 0;
143     else if (quality <= 10.00)
144         video_q = (int)ceil(quality * 6.3);
145     else
146         video_q = (int)ceil(quality);
147 
148 }
start(const std::string & filename,int w,int h,float fps)149 bool OggTheoraCapture::start(const std::string& filename,
150                  int w, int h,
151                  float fps)
152 {
153     if (capturing)
154         return false;
155 
156     outfile = fopen(filename.c_str(), "wb");
157     if (!outfile)
158     {
159         DPRINTF(0, _("Error in creating ogg file %s for capture.\n"), filename.c_str());
160         return false;
161     }
162     /* Set up Ogg output stream */
163 #ifdef _WIN32
164 	std::srand(std::time(NULL));
165 #else
166 	std::srand(time(NULL));
167 #endif
168 
169     ogg_stream_init(&to,std::rand());
170 
171     frame_x = w;
172     frame_y = h;
173     if (fps > 0.05) {
174         if (fabs(fps - (30000.0/1001.0)) < 1e-5)
175         {
176             video_hzn = 30000;
177             video_hzd = 1001;
178         }
179         else if (fabs(fps - (24000.0/1001.0)) < 1e-5)
180         {
181             video_hzn = 24000;
182             video_hzd = 1001;
183         }
184         else
185         {
186             video_hzn = (int)ceil(fps*1000.0);
187             video_hzd = 1000;
188             int    a = video_hzn;
189             int    b = video_hzd;
190             while (a != b)
191             {
192                 if (a > b)
193                     a = a - b;
194                 else
195                     b = b - a;
196             }
197             if (a > 1)
198             {
199                 video_hzn /= a;
200                 video_hzd /= a;
201             }
202         }
203     }
204 
205     /* Theora has a divisible-by-sixteen restriction for the encoded video size */
206     /* scale the frame size up to the nearest /16 and calculate offsets */
207     video_x=((frame_x + 15) >>4)<<4;
208     video_y=((frame_y + 15) >>4)<<4;
209 
210     /* We force the offset to be even.
211        This ensures that the chroma samples align properly with the luma
212        samples. */
213     frame_x_offset=((video_x-frame_x)/2)&~1;
214     frame_y_offset=((video_y-frame_y)/2)&~1;
215 
216     theora_info_init(&ti);
217     ti.width=video_x;
218     ti.height=video_y;
219     ti.frame_width=frame_x;
220     ti.frame_height=frame_y;
221     ti.offset_x=frame_x_offset;
222     ti.offset_y=frame_y_offset;
223     ti.fps_numerator=video_hzn;
224     ti.fps_denominator=video_hzd;
225     ti.aspect_numerator=video_an;
226     ti.aspect_denominator=video_ad;
227     if (frame_x == 720 && frame_y == 576)
228         ti.colorspace=OC_CS_ITU_REC_470BG; //OC_CS_UNSPECIFIED;
229     else
230         ti.colorspace=OC_CS_ITU_REC_470M; //OC_CS_UNSPECIFIED;
231 
232     //ti.pixelformat=OC_PF_420;
233     ti.target_bitrate=video_r;
234     ti.quality=video_q;
235 
236     ti.dropframes_p=0;
237     ti.quick_p=1;
238     ti.keyframe_auto_p=1;
239     ti.keyframe_frequency=64;
240     ti.keyframe_frequency_force=64;
241     ti.keyframe_data_target_bitrate=(int)(video_r*1.5);
242     ti.keyframe_auto_threshold=80;
243     ti.keyframe_mindistance=8;
244     ti.noise_sensitivity=1;
245 
246     theora_encode_init(&td,&ti);
247     theora_info_clear(&ti);
248 
249     /* first packet will get its own page automatically */
250     theora_encode_header(&td,&op);
251     ogg_stream_packetin(&to,&op);
252     if(ogg_stream_pageout(&to,&videopage) != 1){
253         cerr << _("Internal Ogg library error.") << endl;
254         return false;
255     }
256     fwrite(videopage.header, 1, videopage.header_len, outfile);
257     fwrite(videopage.body,   1, videopage.body_len, outfile);
258 
259     /* create the remaining theora headers */
260     theora_comment_init(&tc);
261     theora_encode_comment(&tc,&op);
262     theora_comment_clear(&tc);
263     ogg_stream_packetin(&to,&op);
264     theora_encode_tables(&td,&op);
265     ogg_stream_packetin(&to,&op);
266 
267     while(1)
268     {
269         int result = ogg_stream_flush(&to,&videopage);
270         if( result<0 )
271         {
272             /* can't get here */
273             cerr << _("Internal Ogg library error.")  << endl;
274             return false;
275         }
276         if( result==0 )
277             break;
278         fwrite(videopage.header,1,videopage.header_len,outfile);
279         fwrite(videopage.body,1,  videopage.body_len,outfile);
280     }
281     /* Initialize the double frame buffer.
282      * We allocate enough for a 4:4:4 color sampling
283      */
284     yuvframe[0]= new unsigned char[video_x*video_y*3];
285     yuvframe[1]= new unsigned char[video_x*video_y*3];
286 
287     // Now the buffer for reading the GL RGB pixels
288     rowStride = (frame_x * 3 + 3) & ~0x3;
289     pixels = new unsigned char[rowStride*frame_y];
290 
291         /* clear initial frame as it may be larger than actual video data */
292         /* fill Y plane with 0x10 and UV planes with 0x80, for black data */
293     // The UV plane must be 4:2:0
294     memset(yuvframe[0],0x10,video_x*video_y);
295     memset(yuvframe[0]+video_x*video_y,0x80,video_x*video_y*2);
296     memset(yuvframe[1],0x10,video_x*video_y);
297     memset(yuvframe[1]+video_x*video_y,0x80,video_x*video_y*2);
298 
299     yuv.y_width=video_x;
300     yuv.y_height=video_y;
301     yuv.y_stride=video_x;
302 
303     // Note we lie here by saying it's 4:2:0 and we will convert 4:4:4 to 4:2;0 later
304     yuv.uv_width=video_x/2;
305     yuv.uv_height=video_y/2;
306     yuv.uv_stride=video_x/2;
307 
308     printf(_("OggTheoraCapture::start() - Theora video: %s %.2f(%d/%d) fps quality %d %dx%d offset (%dx%d)\n"),
309            filename.c_str(),
310            (double)video_hzn/(double)video_hzd,
311            video_hzn,video_hzd,
312            video_q,
313            video_x,video_y,
314            frame_x_offset,frame_y_offset);
315 
316     capturing = true;
317     return true;
318 }
319 
captureFrame()320 bool OggTheoraCapture::captureFrame()
321 {
322     if (!capturing)
323         return false;
324 
325     while (ogg_stream_pageout(&to,&videopage)>0)
326     {
327         /* flush a video page */
328         video_bytesout+=fwrite(videopage.header,1,videopage.header_len,outfile);
329         video_bytesout+=fwrite(videopage.body,1,videopage.body_len,outfile);
330 
331     }
332     if(ogg_stream_eos(&to)) return false;
333 
334     // Get the dimensions of the current viewport
335     int viewport[4];
336     glGetIntegerv(GL_VIEWPORT, viewport);
337 
338     int x = viewport[0] + (viewport[2] - frame_x) / 2;
339     int y = viewport[1] + (viewport[3] - frame_y) / 2;
340     glReadPixels(x, y, frame_x, frame_y,
341              GL_RGB, GL_UNSIGNED_BYTE,
342              pixels);
343 
344     unsigned char *ybase = yuvframe[0];
345     unsigned char *ubase = yuvframe[0]+ video_x*video_y;
346     unsigned char *vbase = yuvframe[0]+ video_x*video_y*2;
347     // We go ahead and build 4:4:4 frames
348     for (int y=0; y<frame_y; y++)
349     {
350         unsigned char *yptr = ybase + (video_x*(y+frame_y_offset))+frame_x_offset;
351         unsigned char *uptr = ubase + (video_x*(y+frame_y_offset))+frame_x_offset;
352         unsigned char *vptr = vbase + (video_x*(y+frame_y_offset))+frame_x_offset;
353         unsigned char *rgb = pixels + ((frame_y-1-y)*rowStride); // The video is inverted
354         for (int x=0; x<frame_x; x++)
355         {
356             unsigned char r = *rgb++;
357             unsigned char g = *rgb++;
358             unsigned char b = *rgb++;
359             *yptr++ = min(abs(r * 2104 + g * 4130 + b * 802 + 4096 + 131072) >> 13,235);
360             *uptr++ = min(abs(r * -1214 + g * -2384 + b * 3598 + 4096 + 1048576) >> 13,240);
361             *vptr++ = min(abs(r * 3598 + g * -3013 + b * -585 + 4096 + 1048576) >> 13,240);
362         }
363     }
364 
365     /*
366      * The video strategy is to capture one frame ahead so when we're at end of
367      * stream we can mark last video frame as such.  Have two YUV frames before
368      * encoding. Theora is a one-frame-in,one-frame-out system; submit a frame
369      * for compression and pull out the packet
370      */
371 
372     if (video_frame_count > 0)
373     {
374         yuv.y= yuvframe[1];
375         yuv.u= yuvframe[1]+ video_x*video_y;
376         yuv.v= yuvframe[1]+ video_x*video_y*2;
377         // Convert to 4:2:0
378         unsigned char * uin0 = yuv.u;
379         unsigned char * uin1 = yuv.u + video_x;
380         unsigned char * uout = yuv.u;
381         unsigned char * vin0 = yuv.v;
382         unsigned char * vin1 = yuv.v + video_x;
383         unsigned char * vout = yuv.v;
384         for (int y = 0; y < video_y; y += 2)
385         {
386             for (int x = 0; x < video_x; x += 2)
387             {
388                 *uout = (uin0[0] + uin0[1] + uin1[0] + uin1[1]) >> 2;
389                 uin0 += 2;
390                 uin1 += 2;
391                 uout++;
392                 *vout = (vin0[0] + vin0[1] + vin1[0] + vin1[1]) >> 2;
393                 vin0 += 2;
394                 vin1 += 2;
395                 vout++;
396             }
397             uin0 += video_x;
398             uin1 += video_x;
399             vin0 += video_x;
400             vin1 += video_x;
401         }
402         theora_encode_YUVin(&td,&yuv);
403         theora_encode_packetout(&td,0,&op);
404         ogg_stream_packetin(&to,&op);
405     }
406     video_frame_count += 1;
407     //if ((video_frame_count % 10) == 0)
408     //    printf("Writing frame %d\n", video_frame_count);
409     unsigned char *temp = yuvframe[0];
410     yuvframe[0] = yuvframe[1];
411     yuvframe[1] = temp;
412     frameCaptured();
413 
414     return true;
415 }
cleanup()416 void OggTheoraCapture::cleanup()
417 {
418     capturing = false;
419     /* clear out state */
420 
421     if(outfile)
422     {
423         printf(_("OggTheoraCapture::cleanup() - wrote %d frames\n"), video_frame_count);
424         if (video_frame_count > 0)
425         {
426             yuv.y= yuvframe[1];
427             yuv.u= yuvframe[1]+ video_x*video_y;
428             yuv.v= yuvframe[1]+ video_x*video_y*2 ;
429             // Convert to 4:2:0
430             unsigned char * uin0 = yuv.u;
431             unsigned char * uin1 = yuv.u + video_x;
432             unsigned char * uout = yuv.u;
433             unsigned char * vin0 = yuv.v;
434             unsigned char * vin1 = yuv.v + video_x;
435             unsigned char * vout = yuv.v;
436             for (int y = 0; y < video_y; y += 2)
437             {
438                 for (int x = 0; x < video_x; x += 2)
439                 {
440                     *uout = (uin0[0] + uin0[1] + uin1[0] + uin1[1]) >> 2;
441                     uin0 += 2;
442                     uin1 += 2;
443                     uout++;
444                     *vout = (vin0[0] + vin0[1] + vin1[0] + vin1[1]) >> 2;
445                     vin0 += 2;
446                     vin1 += 2;
447                     vout++;
448                 }
449                 uin0 += video_x;
450                 uin1 += video_x;
451                 vin0 += video_x;
452                 vin1 += video_x;
453             }
454             theora_encode_YUVin(&td,&yuv);
455             theora_encode_packetout(&td,1,&op);
456             ogg_stream_packetin(&to,&op);
457         }
458         while(ogg_stream_pageout(&to,&videopage)>0)
459         {
460             /* flush a video page */
461             video_bytesout+=fwrite(videopage.header,1,videopage.header_len,outfile);
462             video_bytesout+=fwrite(videopage.body,1,videopage.body_len,outfile);
463 
464         }
465         if(ogg_stream_flush(&to,&videopage)>0)
466         {
467             /* flush a video page */
468             video_bytesout+=fwrite(videopage.header,1,videopage.header_len,outfile);
469             video_bytesout+=fwrite(videopage.body,1,videopage.body_len,outfile);
470 
471         }
472         theora_clear(&td);
473         ogg_stream_clear(&to);
474         //ogg_stream_destroy(&to); /* Documentation says to do this however we are seeing a double free libogg 1.1.2 */
475 
476         std::fclose(outfile);
477         outfile = NULL;
478         delete [] yuvframe[0];
479         delete [] yuvframe[1];
480         delete [] pixels;
481     }
482 }
483 
end()484 bool OggTheoraCapture::end()
485 {
486     cleanup();
487     return true;
488 }
getWidth() const489 int OggTheoraCapture::getWidth() const
490 {
491     return frame_x;
492 }
getHeight() const493 int OggTheoraCapture::getHeight() const
494 {
495     return frame_y;
496 }
getFrameCount() const497 int OggTheoraCapture::getFrameCount() const
498 {
499     return video_frame_count;
500 }
getFrameRate() const501 float OggTheoraCapture::getFrameRate() const
502 {
503     return float(video_hzn)/float(video_hzd);
504 }
~OggTheoraCapture()505 OggTheoraCapture::~OggTheoraCapture()
506 {
507     cleanup();
508 }
509 
510