22http://stackoverflow.com/questions/3191978/how-to-use-glut-opengl-to-render-to-a-file/14324292#14324292
33*/
44
5- #define LIBPNG 0
5+ /* Turn output methods on and off. */
6+ #define PPM 1
7+ #define LIBPNG 1
68#define FFMPEG 1
79
810#include <assert.h>
2123#endif
2224
2325#if FFMPEG
24- #include <libavutil/opt.h> /* av_opt_set */
2526#include <libavcodec/avcodec.h>
26- #include <libavutil/imgutils.h> /* av_image_alloc */
27+ #include <libavutil/imgutils.h>
28+ #include <libavutil/opt.h>
29+ #include <libswscale/swscale.h>
2730#endif
2831
2932enum Constants { SCREENSHOT_MAX_FILENAME = 256 };
33+ static GLubyte * pixels = NULL ;
3034static GLuint fbo ;
3135static GLuint rbo_color ;
3236static GLuint rbo_depth ;
33- static const GLenum FORMAT = GL_RGBA ;
34- static const GLuint format_nchannels = 4 ;
3537static const unsigned int HEIGHT = 100 ;
3638static const unsigned int WIDTH = 100 ;
3739static int offscreen = 1 ;
@@ -43,27 +45,24 @@ static unsigned int time0;
4345static double angle ;
4446static double delta_angle ;
4547
48+ #if PPM
4649/*
4750Take screenshot with glReadPixels and save to a file in PPM format.
4851- filename: file path to save to, without extension
4952- width: screen width in pixels
5053- height: screen height in pixels
51- - format: glReadPixelsFormat
52- - format_nchannels: number of channels per pixel (e.g. R, G, B, A)
53- This is implied by format, but we haven't found a built-in way to get this
54- information automatically without hard-coding a large switch.
5554- pixels: intermediate buffer to avoid repeated mallocs across multiple calls.
5655 Contents of this buffer do not matter. May be NULL, in which case it is initialized.
5756 You must `free` it when you won't be calling this function anymore.
5857*/
59- static GLubyte * pixels = NULL ;
60- static void screenshot_ppm (const char * filename , unsigned int width , unsigned int height ,
61- GLenum format , unsigned int format_nchannels , GLubyte * * pixels ) {
58+ static void screenshot_ppm (const char * filename , unsigned int width ,
59+ unsigned int height , GLubyte * * pixels ) {
6260 size_t i , j , k , cur ;
61+ const size_t format_nchannels = 3 ;
6362 FILE * f = fopen (filename , "w" );
6463 fprintf (f , "P3\n%d %d\n%d\n" , width , height , 255 );
6564 * pixels = realloc (* pixels , format_nchannels * sizeof (GLubyte ) * width * height );
66- glReadPixels (0 , 0 , width , height , format , GL_UNSIGNED_BYTE , * pixels );
65+ glReadPixels (0 , 0 , width , height , GL_RGB , GL_UNSIGNED_BYTE , * pixels );
6766 for (i = 0 ; i < height ; i ++ ) {
6867 for (j = 0 ; j < width ; j ++ ) {
6968 cur = format_nchannels * ((height - i - 1 ) * width + j );
@@ -73,20 +72,22 @@ static void screenshot_ppm(const char *filename, unsigned int width, unsigned in
7372 }
7473 fclose (f );
7574}
75+ #endif
7676
7777#if LIBPNG
78+ /* Adapted from https://github.com/cirosantilli/cpp-cheat/blob/19044698f91fefa9cb75328c44f7a487d336b541/png/open_manipulate_write.c */
7879static png_byte * png_bytes = NULL ;
7980static png_byte * * png_rows = NULL ;
80- static void screenshot_png (const char * filename , unsigned int width ,
81- unsigned int height , GLenum format , unsigned int format_nchannels ,
81+ static void screenshot_png (const char * filename , unsigned int width , unsigned int height ,
8282 GLubyte * * pixels , png_byte * * png_bytes , png_byte * * * png_rows ) {
83- size_t i , j , cur , cur_png , nvals ;
83+ size_t i , nvals ;
84+ const size_t format_nchannels = 4 ;
8485 FILE * f = fopen (filename , "wb" );
8586 nvals = format_nchannels * width * height ;
8687 * pixels = realloc (* pixels , nvals * sizeof (GLubyte ));
8788 * png_bytes = realloc (* png_bytes , nvals * sizeof (png_byte ));
8889 * png_rows = realloc (* png_rows , height * sizeof (png_byte * ));
89- glReadPixels (0 , 0 , width , height , format , GL_UNSIGNED_BYTE , * pixels );
90+ glReadPixels (0 , 0 , width , height , GL_RGBA , GL_UNSIGNED_BYTE , * pixels );
9091 for (i = 0 ; i < nvals ; i ++ )
9192 (* png_bytes )[i ] = (* pixels )[i ];
9293 for (i = 0 ; i < height ; i ++ )
@@ -116,16 +117,28 @@ static void screenshot_png(const char *filename, unsigned int width,
116117#endif
117118
118119#if FFMPEG
120+ /* Adapted from: https://github.com/cirosantilli/cpp-cheat/blob/19044698f91fefa9cb75328c44f7a487d336b541/ffmpeg/encode.c */
121+
119122static AVCodecContext * c = NULL ;
120123static AVFrame * frame ;
121- static FILE * f ;
122- int64_t pts = 0 ;
124+ static AVPacket pkt ;
125+ static FILE * file ;
126+ static struct SwsContext * sws_context = NULL ;
127+ static uint8_t * rgb = NULL ;
128+
129+ static void ffmpeg_encoder_set_frame_yuv_from_rgb (uint8_t * rgb ) {
130+ const int in_linesize [1 ] = { 3 * c -> width };
131+ sws_context = sws_getCachedContext (sws_context ,
132+ c -> width , c -> height , AV_PIX_FMT_RGB24 ,
133+ c -> width , c -> height , AV_PIX_FMT_YUV420P ,
134+ 0 , 0 , 0 , 0 );
135+ sws_scale (sws_context , (const uint8_t * const * )& rgb , in_linesize , 0 ,
136+ c -> height , frame -> data , frame -> linesize );
137+ }
123138
124- static void screenshot_mpg_init (const char * filename , int width , int height ) {
139+ void ffmpeg_encoder_start (const char * filename , int codec_id , int fps , int width , int height ) {
125140 AVCodec * codec ;
126- int codec_id = AV_CODEC_ID_MPEG1VIDEO ;
127141 int ret ;
128-
129142 avcodec_register_all ();
130143 codec = avcodec_find_encoder (codec_id );
131144 if (!codec ) {
@@ -140,7 +153,8 @@ static void screenshot_mpg_init(const char *filename, int width, int height) {
140153 c -> bit_rate = 400000 ;
141154 c -> width = width ;
142155 c -> height = height ;
143- c -> time_base = (AVRational ){1 , 25 };
156+ c -> time_base .num = 1 ;
157+ c -> time_base .den = fps ;
144158 c -> gop_size = 10 ;
145159 c -> max_b_frames = 1 ;
146160 c -> pix_fmt = AV_PIX_FMT_YUV420P ;
@@ -150,8 +164,8 @@ static void screenshot_mpg_init(const char *filename, int width, int height) {
150164 fprintf (stderr , "Could not open codec\n" );
151165 exit (1 );
152166 }
153- f = fopen (filename , "wb" );
154- if (!f ) {
167+ file = fopen (filename , "wb" );
168+ if (!file ) {
155169 fprintf (stderr , "Could not open %s\n" , filename );
156170 exit (1 );
157171 }
@@ -170,75 +184,61 @@ static void screenshot_mpg_init(const char *filename, int width, int height) {
170184 }
171185}
172186
173- static void screenshot_mpg_deinit (void ) {
174- AVPacket pkt ;
175- int ret , x , y , got_output ;
187+ void ffmpeg_encoder_finish (void ) {
176188 uint8_t endcode [] = { 0 , 0 , 1 , 0xb7 };
177-
178- av_init_packet (& pkt );
179- pkt .data = NULL ;
180- pkt .size = 0 ;
181- for (got_output = 1 ; got_output ; pts ++ ) {
189+ int got_output , ret ;
190+ do {
182191 fflush (stdout );
183192 ret = avcodec_encode_video2 (c , & pkt , NULL , & got_output );
184193 if (ret < 0 ) {
185194 fprintf (stderr , "Error encoding frame\n" );
186195 exit (1 );
187196 }
188197 if (got_output ) {
189- fwrite (pkt .data , 1 , pkt .size , f );
198+ fwrite (pkt .data , 1 , pkt .size , file );
190199 av_packet_unref (& pkt );
191200 }
192- }
193- fwrite (endcode , 1 , sizeof (endcode ), f );
194- fclose (f );
201+ } while ( got_output );
202+ fwrite (endcode , 1 , sizeof (endcode ), file );
203+ fclose (file );
195204 avcodec_close (c );
196205 av_free (c );
197206 av_freep (& frame -> data [0 ]);
198207 av_frame_free (& frame );
199208}
200209
201- /*
202- Compress one frame.
203- screenshot_mpg_init must have been called previously,
204- and screenshot_mpg_deinit must be called to clean this afterwards.
205- Adapted from https://github.com/FFmpeg/FFmpeg/blob/4b150fbe1f3905f8245f63d74ff72f2ef92d9717/doc/examples/decoding_encoding.c
206- */
207- static void screenshot_mpg (unsigned int width , unsigned int height , GLenum format ,
208- unsigned int format_nchannels , GLubyte * * pixels ) {
209- AVPacket pkt ;
210- int ret , x , y , got_output ;
211- size_t nvals ;
212-
213- nvals = format_nchannels * width * height ;
214- * pixels = realloc (* pixels , nvals * sizeof (GLubyte ));
215- glReadPixels (0 , 0 , width , height , format , GL_UNSIGNED_BYTE , * pixels );
216-
210+ void ffmpeg_encoder_encode_frame (uint8_t * rgb ) {
211+ int ret , got_output ;
212+ ffmpeg_encoder_set_frame_yuv_from_rgb (rgb );
217213 av_init_packet (& pkt );
218214 pkt .data = NULL ;
219215 pkt .size = 0 ;
220- frame -> pts = pts ;
221- for (y = 0 ; y < c -> height ; y ++ ) {
222- for (x = 0 ; x < c -> width ; x ++ ) {
223- frame -> data [0 ][y * frame -> linesize [0 ] + x ] = x + y + pts * 3 ;
224- }
225- }
226- for (y = 0 ; y < c -> height / 2 ; y ++ ) {
227- for (x = 0 ; x < c -> width / 2 ; x ++ ) {
228- frame -> data [1 ][y * frame -> linesize [1 ] + x ] = 128 + y + pts * 2 ;
229- frame -> data [2 ][y * frame -> linesize [2 ] + x ] = 64 + x + pts * 5 ;
230- }
231- }
232216 ret = avcodec_encode_video2 (c , & pkt , frame , & got_output );
233217 if (ret < 0 ) {
234218 fprintf (stderr , "Error encoding frame\n" );
235219 exit (1 );
236220 }
237221 if (got_output ) {
238- fwrite (pkt .data , 1 , pkt .size , f );
222+ fwrite (pkt .data , 1 , pkt .size , file );
239223 av_packet_unref (& pkt );
240224 }
241- pts ++ ;
225+ }
226+
227+ void ffmpeg_encoder_glread_rgb (uint8_t * * rgb , GLubyte * * pixels , unsigned int width , unsigned int height ) {
228+ size_t i , j , k , cur_gl , cur_rgb , nvals ;
229+ const size_t format_nchannels = 3 ;
230+ nvals = format_nchannels * width * height ;
231+ * pixels = realloc (* pixels , nvals * sizeof (GLubyte ));
232+ * rgb = realloc (* rgb , nvals * sizeof (uint8_t ));
233+ glReadPixels (0 , 0 , width , height , GL_RGB , GL_UNSIGNED_BYTE , * pixels );
234+ for (i = 0 ; i < height ; i ++ ) {
235+ for (j = 0 ; j < width ; j ++ ) {
236+ cur_gl = format_nchannels * (width * (height - i - 1 ) + j );
237+ cur_rgb = format_nchannels * (width * i + j );
238+ for (k = 0 ; k < format_nchannels ; k ++ )
239+ (* rgb )[cur_rgb + k ] = (* pixels )[cur_gl + k ];
240+ }
241+ }
242242}
243243#endif
244244
@@ -299,7 +299,7 @@ static void init(void) {
299299 time0 = glutGet (GLUT_ELAPSED_TIME );
300300 model_init ();
301301#if FFMPEG
302- screenshot_mpg_init ("tmp.mpg" , WIDTH , HEIGHT );
302+ ffmpeg_encoder_start ("tmp.mpg" , AV_CODEC_ID_MPEG1VIDEO , 25 , WIDTH , HEIGHT );
303303#endif
304304}
305305
@@ -311,7 +311,8 @@ static void deinit(void) {
311311 free (png_rows );
312312#endif
313313#if FFMPEG
314- screenshot_mpg_deinit ();
314+ ffmpeg_encoder_finish ();
315+ free (rgb );
315316#endif
316317 if (offscreen ) {
317318 glDeleteFramebuffers (1 , & fbo );
@@ -343,20 +344,18 @@ static void display(void) {
343344 } else {
344345 glutSwapBuffers ();
345346 }
346- #if LIBPNG
347- strcpy (extension , ".png" );
348- #else
349- strcpy (extension , ".ppm" );
347+ #if PPM
348+ snprintf (filename , SCREENSHOT_MAX_FILENAME , "tmp%d.ppm" , nframes );
349+ screenshot_ppm (filename , WIDTH , HEIGHT , & pixels );
350350#endif
351- snprintf (filename , SCREENSHOT_MAX_FILENAME , "tmp%d%s" , nframes , extension );
352351#if LIBPNG
353- screenshot_png (filename , WIDTH , HEIGHT , FORMAT , format_nchannels , & pixels , & png_bytes , & png_rows );
354- #else
352+ snprintf (filename , SCREENSHOT_MAX_FILENAME , "tmp%d.png" , nframes );
353+ screenshot_png (filename , WIDTH , HEIGHT , & pixels , & png_bytes , & png_rows );
354+ #endif
355355# if FFMPEG
356- screenshot_mpg (WIDTH , HEIGHT , FORMAT , format_nchannels , & pixels );
357- # else
358- screenshot_ppm (filename , WIDTH , HEIGHT , FORMAT , format_nchannels , & pixels );
359- # endif
356+ frame -> pts = nframes ;
357+ ffmpeg_encoder_glread_rgb (& rgb , & pixels , WIDTH , HEIGHT );
358+ ffmpeg_encoder_encode_frame (rgb );
360359#endif
361360 nframes ++ ;
362361 if (model_finished ())
0 commit comments