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