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