1 //=============================================================================
2 // savempg.c
3 //
4 // Module for compressing and saving ElmerPost-pictures in MPEG formats.
5 //
6 // Compile e.g. as follows:
7 //
8 // Linux:
9 //
10 // gcc -shared -O -I/usr/include/ffmpeg -I/usr/include/tcl8.4
11 //    savempg.c -o savempg.o -lavcodec -lavutil -lswscale
12 //
13 // MinGW:
14 // gcc -shared -O -I$FFMPEG/include -L$FFMPEG/lib -o savempg.dll
15 //     savempg.c -lopengl32 -ltcl84 -lavcodec -lavutil -lswscale -lz
16 //
17 // ( Note that the libraries required depend on the libavcodec build. )
18 //
19 // Copy the shared library into $ELMER_POST_HOME/modules and run ElmerPost
20 //
21 // Usage in Elmer-Post:
22 //
23 //    savempg codec name          [ default: mpg1 ( mpg2, mpg2, yuv4 )   ]
24 //    savempg bitrate value       [ default: 1000000 bps                 ]
25 //    savempg gopsize value       [ default: 20 frames                   ]
26 //    savempg bframes value       [ default: 2 frames                    ]
27 //    savempg start file.es       [ initializes video compressor         ]
28 //    savempg append              [ adds current frame to video sequence ]
29 //    savempg stop                [ finalizes video compressor           ]
30 //
31 // Parsed together from screensave.c && the ffmpeg samples
32 //
33 // Written by: ML, 30. Sept. 2007
34 //=============================================================================
35 #ifdef HAVE_AV_CONFIG_H
36 #undef HAVE_AV_CONFIG_H
37 #endif
38 
39 #if defined(WIN32) || defined(win32)
40 #include <windows.h>
41 #endif
42 
43 #include <stdlib.h>
44 #include <stdio.h>
45 #include <string.h>
46 #include <inttypes.h>
47 #include <math.h>
48 #include <GL/gl.h>
49 #include <tcl.h>
50 #include "avcodec.h"
51 #include "swscale.h"
52 
53 #define INBUF_SIZE 4096
54 #define STATE_READY 0
55 #define STATE_STARTED 1
56 #define DEFAULT_B_FRAMES 2
57 #define DEFAULT_GOP_SIZE 20
58 #define DEFAULT_BITRATE 1000000
59 #define DEFAULT_MPG_BUFSIZE 500000
60 
61 #undef MPEG4
62 
63 #if !defined(INFINITY)
64 #define INFINITY 999999999
65 #endif
66 
67 typedef struct buffer_s {
68   uint8_t *MPG;
69   uint8_t *YUV;
70   uint8_t *RGB;
71   uint8_t *ROW;
72 } buffer_t;
73 
free_buffers(buffer_t * buff)74 void free_buffers( buffer_t *buff ) {
75   free( buff->MPG );
76   free( buff->YUV );
77   free( buff->RGB );
78   free( buff->ROW );
79 }
80 
SetMessage(Tcl_Interp * interp,char * message)81 void SetMessage( Tcl_Interp *interp, char *message ) {
82   sprintf( interp->result, "savempg: %s\n", message );
83 }
84 
psnr(double d)85 static double psnr( double d ) {
86   if( d == 0 )
87     return INFINITY;
88   return -10.0 * log( d ) / log( 10.0 );
89 }
90 
print_info(int count_frames,AVCodecContext * context,int bytes)91 void print_info( int count_frames, AVCodecContext *context, int bytes ) {
92   double tmp = context->width * context->height * 255.0 * 255.0;
93   double Ypsnr = psnr( context->coded_frame->error[0] / tmp );
94   double quality = context->coded_frame->quality/(double)FF_QP2LAMBDA;
95   char pict_type = av_get_pict_type_char(context->coded_frame->pict_type);
96   fprintf( stdout, "savempg: frame %4d: type=%c, ", count_frames, pict_type );
97   fprintf( stdout, "size=%6d bytes, PSNR(Y)=%5.2f dB, ", bytes, Ypsnr );
98   fprintf( stdout, "q=%2.1f\n", (float)quality );
99   fflush( stdout );
100 }
101 
SaveMPG(ClientData cl,Tcl_Interp * interp,int argc,char ** argv)102 static int SaveMPG( ClientData cl,Tcl_Interp *interp,int argc,char **argv ) {
103   static AVCodec *codec = NULL;
104   static AVCodecContext *context = NULL;
105   static AVFrame *YUVpicture = NULL;
106   static AVFrame *RGBpicture = NULL;
107   static int bytes, PIXsize, stride;
108   static int y, nx, ny, ox, oy, viewp[4];
109   static int i_state = STATE_READY;
110   static int initialized = 0;
111   static int count_frames = 0;
112   static int bitrate = DEFAULT_BITRATE;
113   static int gopsize = DEFAULT_GOP_SIZE;
114   static int bframes = DEFAULT_B_FRAMES;
115   static int MPGbufsize = DEFAULT_MPG_BUFSIZE;
116   static int codec_id = CODEC_ID_MPEG1VIDEO;
117   static FILE *MPGfile;
118   static char *state, fname[256];
119   static buffer_t buff;
120   static struct SwsContext *img_convert_ctx;
121 
122   if( argc < 2 ) {
123     SetMessage( interp, "too few arguments" );
124     return TCL_ERROR;
125   }
126 
127   state = argv[1];
128   //===========================================================================
129   //                             SELECT CODEC
130   //===========================================================================
131   if( !strncmp( state, "codec", 5 ) ) {
132 
133     // Can't change while running:
134     //----------------------------
135     if( i_state != STATE_READY ) {
136       SetMessage( interp, "can't change codec when running" );
137       return TCL_ERROR;
138     }
139 
140     // Default is MPEG1:
141     //------------------
142     codec_id = CODEC_ID_MPEG1VIDEO;
143 
144     if( argc >= 3 ) {
145       char *c = argv[2];
146 
147       if( !strncmp( c, "mpg1", 4 ) ) {
148 	codec_id = CODEC_ID_MPEG1VIDEO;
149 	fprintf( stdout, "savempg: codec: mpg1\n" );
150       }
151 
152       if( !strncmp( c, "mpg2", 4 ) ) {
153 	codec_id = CODEC_ID_MPEG2VIDEO;
154 	fprintf( stdout, "savempg: codec: mpg2\n" );
155       }
156 
157       if( !strncmp( c, "mpg4", 4 ) ) {
158 	codec_id = CODEC_ID_MPEG4;
159 	fprintf( stdout, "savempg: codec: mpg4\n" );
160       }
161 
162       if( !strncmp( c, "yuv4", 4 ) ) {
163 	codec_id = CODEC_ID_RAWVIDEO;
164 	fprintf( stdout, "savempg: codec: yuv4\n" );
165       }
166     }
167 
168     fflush( stdout );
169 
170     return TCL_OK;
171   }
172 
173   //===========================================================================
174   //                              SET BITRATE
175   //===========================================================================
176   if( !strncmp( state, "bitrate", 7 ) ) {
177 
178     // Can't change while running:
179     //----------------------------
180     if( i_state != STATE_READY ) {
181       SetMessage( interp, "can't change bitrate when running" );
182       return TCL_ERROR;
183     }
184 
185     bitrate = DEFAULT_BITRATE;
186 
187     if( argc >= 3 )
188       bitrate = atoi( argv[2] );
189 
190     if( bitrate < 100000 )
191       bitrate = 100000;
192 
193     fprintf( stdout, "savempg: bitrate: %d bps\n", bitrate );
194     fflush( stdout );
195 
196     return TCL_OK;
197   }
198 
199   //===========================================================================
200   //                              SET GOPSIZE
201   //===========================================================================
202   if( !strncmp( state, "gopsize", 7 ) ) {
203 
204     // Can't change while running:
205     //----------------------------
206     if( i_state != STATE_READY ) {
207       SetMessage( interp, "can't change gopsize when running" );
208       return TCL_ERROR;
209     }
210 
211     gopsize = DEFAULT_GOP_SIZE;
212 
213     if( argc >= 3 )
214       gopsize = atoi( argv[2] );
215 
216     if( gopsize < 1 )
217       gopsize = 1;
218 
219     fprintf( stdout, "savempg: gop: %d frames\n", gopsize );
220     fflush( stdout );
221 
222     return TCL_OK;
223   }
224 
225   //===========================================================================
226   //                              SET B-FRAMES
227   //===========================================================================
228   if( !strncmp( state, "bframes", 7 ) ) {
229 
230     // Can't change while running:
231     //----------------------------
232     if( i_state != STATE_READY ) {
233       SetMessage( interp, "can't change bframes when running" );
234       return TCL_ERROR;
235     }
236 
237     bframes = DEFAULT_B_FRAMES;
238 
239     if( argc >= 3 )
240       bframes = atoi( argv[2] );
241 
242     if( bframes < 0 )
243       bframes = 0;
244 
245     fprintf( stdout, "savempg: bframes: %d\n", bframes );
246     fflush( stdout );
247 
248     return TCL_OK;
249   }
250 
251   //===========================================================================
252   //                           START COMPRESSION
253   //===========================================================================
254   else if( !strncmp( state, "start", 5 ) ) {
255 
256     // Can't start if already running:
257     //----------------------------------
258     if( i_state != STATE_READY ) {
259       SetMessage( interp, "already started" );
260       return TCL_ERROR;
261     }
262 
263     // Detemine output file name:
264     //---------------------------
265     if( argc < 3 ) {
266       strcpy( fname, "elmerpost.es" );
267     } else {
268       strncpy( fname, argv[2], 256 );
269     }
270 
271     // Open output file:
272     //------------------
273     if( (MPGfile = fopen(fname, "wb")) == NULL ) {
274       SetMessage( interp, "can't open file" );
275       return TCL_ERROR;
276     }
277 
278     fprintf( stdout, "savempg: file: %s\n", fname );
279     fprintf( stdout, "savempg: libavcodec: %s\n",
280 	     AV_STRINGIFY(LIBAVCODEC_VERSION) );
281     fprintf( stdout, "savempg: libavutil: %s\n",
282 	     AV_STRINGIFY(LIBAVUTIL_VERSION) );
283     fprintf( stdout, "savempg: libswscale: %s\n",
284 	     AV_STRINGIFY(LIBSWSCALE_VERSION) );
285     fflush( stdout );
286 
287     count_frames = 0;
288 
289     // Determine view port size etc.:
290     //-------------------------------
291     glGetIntegerv( GL_VIEWPORT, viewp );
292     ox = viewp[0];
293     oy = viewp[1];
294     nx = viewp[2] + 1;
295     ny = viewp[3] + 1;
296     PIXsize = nx * ny;
297     stride = 3 * nx;
298 
299     // Must be even:
300     //--------------
301     if( nx % 2 || ny % 2 ) {
302       fprintf( stdout, "savempg: state: stopped\n" );
303       fflush( stdout );
304       fclose( MPGfile );
305       SetMessage( interp, "view port must be even" );
306       return TCL_ERROR;
307     }
308 
309     // Allocate memory:
310     //-----------------
311     if( codec_id == CODEC_ID_RAWVIDEO )
312       MPGbufsize = 3 * (PIXsize / 2);
313 
314     if ( !(buff.RGB = (uint8_t*)malloc(stride*ny)) ||
315 	 !(buff.ROW = (uint8_t*)malloc(stride)) ||
316 	 !(buff.YUV = (uint8_t*)malloc(3 * (PIXsize / 2))) ||
317 	 !(buff.MPG = (uint8_t*)malloc(MPGbufsize)) ) {
318       fclose( MPGfile );
319       SetMessage( interp, "can't allocate memory" );
320       return TCL_ERROR;
321     }
322 
323     // Initialize libavcodec:
324     //-----------------------
325     if( !initialized ) {
326       avcodec_init();
327       avcodec_register_all();
328       initialized = 1;
329     }
330 
331     // Choose codec:
332     //--------------
333     codec = avcodec_find_encoder( codec_id );
334 
335     if( !codec ) {
336       free_buffers( &buff );
337       fclose( MPGfile );
338       SetMessage( interp, "can't find codec" );
339       return TCL_ERROR;
340     }
341 
342     // Init codec context etc.:
343     //--------------------------
344     context = avcodec_alloc_context();
345 
346     context->bit_rate = bitrate;
347     context->width = nx;
348     context->height = ny;
349     context->time_base = (AVRational){ 1, 25 };
350     context->gop_size = gopsize;
351     context->max_b_frames = bframes;
352     context->pix_fmt = PIX_FMT_YUV420P;
353     context->flags |= CODEC_FLAG_PSNR;
354 
355     if( avcodec_open( context, codec ) < 0 ) {
356       avcodec_close( context );
357       av_free( context );
358       free_buffers( &buff );
359       fclose( MPGfile );
360       SetMessage( interp, "can't open codec" );
361       return TCL_ERROR;
362     }
363 
364     YUVpicture = avcodec_alloc_frame();
365 
366     YUVpicture->data[0] = buff.YUV;
367     YUVpicture->data[1] = buff.YUV + PIXsize;
368     YUVpicture->data[2] = buff.YUV + PIXsize + PIXsize / 4;
369     YUVpicture->linesize[0] = nx;
370     YUVpicture->linesize[1] = nx / 2;
371     YUVpicture->linesize[2] = nx / 2;
372 
373     RGBpicture = avcodec_alloc_frame();
374 
375     RGBpicture->data[0] = buff.RGB;
376     RGBpicture->data[1] = buff.RGB;
377     RGBpicture->data[2] = buff.RGB;
378     RGBpicture->linesize[0] = stride;
379     RGBpicture->linesize[1] = stride;
380     RGBpicture->linesize[2] = stride;
381 
382     // Write .ym4 header for raw video:
383     //----------------------------------
384     if( codec_id == CODEC_ID_RAWVIDEO ) {
385       fprintf( MPGfile, "YUV4MPEG2 W%d H%d F25:1 Ip A1:1", nx, ny );
386       fprintf( MPGfile, "%c", (char)0x0a );
387     }
388 
389     // Set state "started":
390     //----------------------
391     i_state = STATE_STARTED;
392     fprintf( stdout, "savempg: state: started\n" );
393     fflush( stdout );
394 
395     return TCL_OK;
396   }
397 
398   //===========================================================================
399   //                             COMPRESS FRAME
400   //===========================================================================
401   else if( !strncmp( state, "append", 6) ) {
402 
403     // Can't compress if status != started:
404     //-------------------------------------
405     if( i_state != STATE_STARTED ) {
406       SetMessage( interp, "not started" );
407       return TCL_ERROR;
408     }
409 
410     // Read RGB data:
411     //---------------
412     glReadBuffer( GL_FRONT );
413     glReadPixels( ox, oy, nx, ny, GL_RGB, GL_UNSIGNED_BYTE, buff.RGB );
414 
415     // The picture is upside down - flip it:
416     //---------------------------------------
417     for( y = 0; y < ny/2; y++ ) {
418       uint8_t *r1 = buff.RGB + stride * y;
419       uint8_t *r2 = buff.RGB + stride * (ny - 1 - y);
420       memcpy( buff.ROW, r1, stride );
421       memcpy( r1, r2, stride );
422       memcpy( r2, buff.ROW, stride );
423     }
424 
425     // Convert to YUV:
426     //----------------
427     if( img_convert_ctx == NULL )
428       img_convert_ctx = sws_getContext( nx, ny, PIX_FMT_RGB24,
429 					nx, ny, PIX_FMT_YUV420P,
430 					SWS_BICUBIC, NULL, NULL, NULL );
431 
432     if( img_convert_ctx == NULL ) {
433       SetMessage( interp, "can't initialize scaler context" );
434       return TCL_ERROR;
435     }
436 
437     sws_scale( img_convert_ctx, RGBpicture->data, RGBpicture->linesize,
438 	       0, ny, YUVpicture->data, YUVpicture->linesize );
439 
440     // Encode frame:
441     //--------------
442     bytes = avcodec_encode_video( context, buff.MPG,
443 				  MPGbufsize, YUVpicture );
444 
445     count_frames++;
446     print_info( count_frames, context, bytes );
447     fflush( stdout );
448 
449     if( codec_id == CODEC_ID_RAWVIDEO ) {
450       fprintf( MPGfile, "FRAME" );
451       fprintf( MPGfile, "%c", (char)0x0a );
452     }
453 
454     fwrite( buff.MPG, 1, bytes, MPGfile );
455 
456     return TCL_OK;
457   }
458 
459   //===========================================================================
460   //                           STOP COMPRESSION
461   //===========================================================================
462   else if( !strncmp( state, "stop", 4) ) {
463 
464     // Can't stop if status != started:
465     //---------------------------------
466     if( i_state != STATE_STARTED ) {
467       SetMessage( interp, "not started" );
468       return TCL_ERROR;
469     }
470 
471     // Get the delayed frames, if any:
472     //--------------------------------
473     for( ; bytes; ) {
474       bytes = avcodec_encode_video( context, buff.MPG, MPGbufsize, NULL );
475 
476       count_frames++;
477       print_info( count_frames, context, bytes );
478       fflush( stdout );
479 
480       fwrite( buff.MPG, 1, bytes, MPGfile );
481     }
482 
483     // Add sequence end code:
484     //-----------------------
485     if( codec_id == CODEC_ID_MPEG1VIDEO ) {
486       buff.MPG[0] = 0x00;
487       buff.MPG[1] = 0x00;
488       buff.MPG[2] = 0x01;
489       buff.MPG[3] = 0xb7;
490       fwrite( buff.MPG, 1, 4, MPGfile );
491     }
492 
493     // Finalize:
494     //-----------
495     avcodec_close( context );
496     av_free( context );
497     av_free( YUVpicture );
498     av_free( RGBpicture );
499     free_buffers( &buff );
500     fclose( MPGfile );
501 
502     i_state = STATE_READY;
503     fprintf( stdout, "savempg: state: stopped\n" );
504     fflush( stdout );
505 
506     return TCL_OK;
507   }
508 
509   //===========================================================================
510   //                              UNKNOWN STATE
511   //===========================================================================
512   else {
513     SetMessage( interp, "unknown request" );
514     return TCL_ERROR;
515   }
516 
517   // We should never ever arrive at this point:
518   //-------------------------------------------
519   SetMessage( interp, "unknown error" );
520   return TCL_ERROR;
521 }
522 
523 #if defined(WIN32) || defined(win32)
524 __declspec(dllexport)
525 #endif
526 
Savempg_Init(Tcl_Interp * interp)527 int Savempg_Init( Tcl_Interp *interp ) {
528   Tcl_CreateCommand( interp, "savempg", (Tcl_CmdProc *)SaveMPG,
529 		     (ClientData)NULL, (Tcl_CmdDeleteProc *)NULL );
530   return TCL_OK;
531 }
532