]> git.sesse.net Git - mlt/blob - src/modules/sdl/consumer_sdl_still.c
Fix image refresh in sdl_still consmuer.
[mlt] / src / modules / sdl / consumer_sdl_still.c
1 /*
2  * consumer_sdl_still.c -- A Simple DirectMedia Layer consumer
3  * Copyright (C) 2003-2004 Ushodaya Enterprises Limited
4  * Author: Charles Yates
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  */
20
21 #include <framework/mlt_consumer.h>
22 #include <framework/mlt_frame.h>
23 #include <framework/mlt_deque.h>
24 #include <framework/mlt_factory.h>
25 #include <framework/mlt_filter.h>
26 #include <framework/mlt_log.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <pthread.h>
31 #include <SDL/SDL.h>
32 #include <SDL/SDL_syswm.h>
33 #include <sys/time.h>
34
35 /** This classes definition.
36 */
37
38 typedef struct consumer_sdl_s *consumer_sdl;
39
40 struct consumer_sdl_s
41 {
42         struct mlt_consumer_s parent;
43         mlt_properties properties;
44         pthread_t thread;
45         int joined;
46         int running;
47         int window_width;
48         int window_height;
49         int width;
50         int height;
51         int playing;
52         int sdl_flags;
53         SDL_Surface *sdl_screen;
54         SDL_Rect rect;
55         uint8_t *buffer;
56         int last_position;
57         mlt_producer last_producer;
58 };
59
60 /** Forward references to static functions.
61 */
62
63 static int consumer_start( mlt_consumer parent );
64 static int consumer_stop( mlt_consumer parent );
65 static int consumer_is_stopped( mlt_consumer parent );
66 static void consumer_close( mlt_consumer parent );
67 static void *consumer_thread( void * );
68 static int consumer_get_dimensions( int *width, int *height );
69 static void consumer_sdl_event( mlt_listener listener, mlt_properties owner, mlt_service this, void **args );
70
71 /** This is what will be called by the factory - anything can be passed in
72         via the argument, but keep it simple.
73 */
74
75 mlt_consumer consumer_sdl_still_init( mlt_profile profile, mlt_service_type type, const char *id, char *arg )
76 {
77         // Create the consumer object
78         consumer_sdl this = calloc( sizeof( struct consumer_sdl_s ), 1 );
79
80         // If no malloc'd and consumer init ok
81         if ( this != NULL && mlt_consumer_init( &this->parent, this, profile ) == 0 )
82         {
83                 // Get the parent consumer object
84                 mlt_consumer parent = &this->parent;
85
86                 // get a handle on properties
87                 mlt_service service = MLT_CONSUMER_SERVICE( parent );
88                 this->properties = MLT_SERVICE_PROPERTIES( service );
89
90                 // We have stuff to clean up, so override the close method
91                 parent->close = consumer_close;
92
93                 // Default scaler (for now we'll use nearest)
94                 mlt_properties_set( this->properties, "rescale", "nearest" );
95
96                 // We're always going to run this in non-realtime mode
97                 mlt_properties_set( this->properties, "real_time", "0" );
98
99                 // Ensure we don't join on a non-running object
100                 this->joined = 1;
101                 
102                 // process actual param
103                 if ( arg == NULL || sscanf( arg, "%dx%d", &this->width, &this->height ) != 2 )
104                 {
105                         this->width = mlt_properties_get_int( this->properties, "width" );
106                         this->height = mlt_properties_get_int( this->properties, "height" );
107                 }
108                 else
109                 {
110                         mlt_properties_set_int( this->properties, "width", this->width );
111                         mlt_properties_set_int( this->properties, "height", this->height );
112                 }
113
114                 // Set the sdl flags
115                 this->sdl_flags = SDL_HWSURFACE | SDL_ASYNCBLIT | SDL_HWACCEL | SDL_RESIZABLE | SDL_DOUBLEBUF;
116
117                 // Allow thread to be started/stopped
118                 parent->start = consumer_start;
119                 parent->stop = consumer_stop;
120                 parent->is_stopped = consumer_is_stopped;
121
122                 // Register specific events
123                 mlt_events_register( this->properties, "consumer-sdl-event", ( mlt_transmitter )consumer_sdl_event );
124
125                 // Return the consumer produced
126                 return parent;
127         }
128
129         // malloc or consumer init failed
130         free( this );
131
132         // Indicate failure
133         return NULL;
134 }
135
136 static void consumer_sdl_event( mlt_listener listener, mlt_properties owner, mlt_service this, void **args )
137 {
138         if ( listener != NULL )
139                 listener( owner, this, ( SDL_Event * )args[ 0 ] );
140 }
141
142 static int consumer_start( mlt_consumer parent )
143 {
144         consumer_sdl this = parent->child;
145
146         if ( !this->running )
147         {
148                 int preview_off = mlt_properties_get_int( MLT_CONSUMER_PROPERTIES( parent ), "preview_off" );
149                 int sdl_started = mlt_properties_get_int( MLT_CONSUMER_PROPERTIES( parent ), "sdl_started" );
150
151                 consumer_stop( parent );
152
153                 this->last_position = -1;
154                 this->running = 1;
155                 this->joined = 0;
156
157                 // Allow the user to force resizing to window size
158                 this->width = mlt_properties_get_int( this->properties, "width" );
159                 this->height = mlt_properties_get_int( this->properties, "height" );
160
161                 // Default window size
162                 double display_ratio = mlt_properties_get_double( this->properties, "display_ratio" );
163                 this->window_width = ( double )this->height * display_ratio;
164                 this->window_height = this->height;
165
166                 if ( sdl_started == 0 && preview_off == 0 )
167                 {
168                         if ( SDL_Init( SDL_INIT_VIDEO | SDL_INIT_NOPARACHUTE ) < 0 )
169                         {
170                                 fprintf( stderr, "Failed to initialize SDL: %s\n", SDL_GetError() );
171                                 return -1;
172                         }
173
174                         SDL_EnableKeyRepeat( SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL );
175                         SDL_EnableUNICODE( 1 );
176                 }
177                 else if ( preview_off == 0 )
178                 {
179                         if ( SDL_GetVideoSurface( ) != NULL )
180                         {
181                                 this->sdl_screen = SDL_GetVideoSurface( );
182                         }
183                 }
184
185                 if ( this->sdl_screen == NULL && preview_off == 0 )
186                         this->sdl_screen = SDL_SetVideoMode( this->window_width, this->window_height, 0, this->sdl_flags );
187
188                 pthread_create( &this->thread, NULL, consumer_thread, this );
189         }
190
191         return 0;
192 }
193
194 static int consumer_stop( mlt_consumer parent )
195 {
196         // Get the actual object
197         consumer_sdl this = parent->child;
198
199         if ( this->joined == 0 )
200         {
201                 int preview_off = mlt_properties_get_int( MLT_CONSUMER_PROPERTIES( parent ), "preview_off" );
202                 int sdl_started = mlt_properties_get_int( MLT_CONSUMER_PROPERTIES( parent ), "sdl_started" );
203
204                 // Kill the thread and clean up
205                 this->running = 0;
206
207                 pthread_join( this->thread, NULL );
208                 this->joined = 1;
209
210                 if ( sdl_started == 0 && preview_off == 0 )
211                         SDL_Quit( );
212
213                 this->sdl_screen = NULL;
214         }
215
216         return 0;
217 }
218
219 static int consumer_is_stopped( mlt_consumer parent )
220 {
221         consumer_sdl this = parent->child;
222         return !this->running;
223 }
224
225 static int sdl_lock_display( )
226 {
227         SDL_Surface *screen = SDL_GetVideoSurface( );
228         return screen != NULL && ( !SDL_MUSTLOCK( screen ) || SDL_LockSurface( screen ) >= 0 );
229 }
230
231 static void sdl_unlock_display( )
232 {
233         SDL_Surface *screen = SDL_GetVideoSurface( );
234         if ( screen != NULL && SDL_MUSTLOCK( screen ) )
235                 SDL_UnlockSurface( screen );
236 }
237
238 static inline void display_1( SDL_Surface *screen, SDL_Rect rect, uint8_t *image, int width, int height )
239 {
240         // Generate the affine transform scaling values
241         if ( rect.w == 0 || rect.h == 0 ) return;
242         int scale_width = ( width << 16 ) / rect.w;
243         int scale_height = ( height << 16 ) / rect.h;
244         int stride = width * 3;
245         int x, y, row_index;
246         uint8_t *q, *row;
247
248         // Constants defined for clarity and optimisation
249         int scanlength = screen->pitch;
250         uint8_t *start = ( uint8_t * )screen->pixels + rect.y * scanlength + rect.x;
251         uint8_t *p;
252
253         // Iterate through the screen using a very basic scaling algorithm
254         for ( y = 0; y < rect.h; y ++ )
255         {
256                 p = start;
257                 row_index = ( 32768 + scale_height * y ) >> 16;
258                 row = image + stride * row_index;
259                 for ( x = 0; x < rect.w; x ++ )
260                 {
261                         q = row + ( ( ( 32768 + scale_width * x ) >> 16 ) * 3 );
262                         *p ++ = SDL_MapRGB( screen->format, *q, *( q + 1 ), *( q + 2 ) );
263                 }
264                 start += scanlength;
265         }
266 }
267
268 static inline void display_2( SDL_Surface *screen, SDL_Rect rect, uint8_t *image, int width, int height )
269 {
270         // Generate the affine transform scaling values
271         if ( rect.w == 0 || rect.h == 0 ) return;
272         int scale_width = ( width << 16 ) / rect.w;
273         int scale_height = ( height << 16 ) / rect.h;
274         int stride = width * 3;
275         int x, y, row_index;
276         uint8_t *q, *row;
277
278         // Constants defined for clarity and optimisation
279         int scanlength = screen->pitch / 2;
280         uint16_t *start = ( uint16_t * )screen->pixels + rect.y * scanlength + rect.x;
281         uint16_t *p;
282
283         // Iterate through the screen using a very basic scaling algorithm
284         for ( y = 0; y < rect.h; y ++ )
285         {
286                 p = start;
287                 row_index = ( 32768 + scale_height * y ) >> 16;
288                 row = image + stride * row_index;
289                 for ( x = 0; x < rect.w; x ++ )
290                 {
291                         q = row + ( ( ( 32768 + scale_width * x ) >> 16 ) * 3 );
292                         *p ++ = SDL_MapRGB( screen->format, *q, *( q + 1 ), *( q + 2 ) );
293                 }
294                 start += scanlength;
295         }
296 }
297
298 static inline void display_3( SDL_Surface *screen, SDL_Rect rect, uint8_t *image, int width, int height )
299 {
300         // Generate the affine transform scaling values
301         if ( rect.w == 0 || rect.h == 0 ) return;
302         int scale_width = ( width << 16 ) / rect.w;
303         int scale_height = ( height << 16 ) / rect.h;
304         int stride = width * 3;
305         int x, y, row_index;
306         uint8_t *q, *row;
307
308         // Constants defined for clarity and optimisation
309         int scanlength = screen->pitch;
310         uint8_t *start = ( uint8_t * )screen->pixels + rect.y * scanlength + rect.x;
311         uint8_t *p;
312         uint32_t pixel;
313
314         // Iterate through the screen using a very basic scaling algorithm
315         for ( y = 0; y < rect.h; y ++ )
316         {
317                 p = start;
318                 row_index = ( 32768 + scale_height * y ) >> 16;
319                 row = image + stride * row_index;
320                 for ( x = 0; x < rect.w; x ++ )
321                 {
322                         q = row + ( ( ( 32768 + scale_width * x ) >> 16 ) * 3 );
323                         pixel = SDL_MapRGB( screen->format, *q, *( q + 1 ), *( q + 2 ) );
324                         *p ++ = (pixel & 0xFF0000) >> 16;
325                         *p ++ = (pixel & 0x00FF00) >> 8;
326                         *p ++ = (pixel & 0x0000FF);
327                 }
328                 start += scanlength;
329         }
330 }
331
332 static inline void display_4( SDL_Surface *screen, SDL_Rect rect, uint8_t *image, int width, int height )
333 {
334         // Generate the affine transform scaling values
335         if ( rect.w == 0 || rect.h == 0 ) return;
336         int scale_width = ( width << 16 ) / rect.w;
337         int scale_height = ( height << 16 ) / rect.h;
338         int stride = width * 3;
339         int x, y, row_index;
340         uint8_t *q, *row;
341
342         // Constants defined for clarity and optimisation
343         int scanlength = screen->pitch / 4;
344         uint32_t *start = ( uint32_t * )screen->pixels + rect.y * scanlength + rect.x;
345         uint32_t *p;
346
347         // Iterate through the screen using a very basic scaling algorithm
348         for ( y = 0; y < rect.h; y ++ )
349         {
350                 p = start;
351                 row_index = ( 32768 + scale_height * y ) >> 16;
352                 row = image + stride * row_index;
353                 for ( x = 0; x < rect.w; x ++ )
354                 {
355                         q = row + ( ( ( 32768 + scale_width * x ) >> 16 ) * 3 );
356                         *p ++ = SDL_MapRGB( screen->format, *q, *( q + 1 ), *( q + 2 ) );
357                 }
358                 start += scanlength;
359         }
360 }
361
362 static int consumer_play_video( consumer_sdl this, mlt_frame frame )
363 {
364         // Get the properties of this consumer
365         mlt_properties properties = this->properties;
366
367         mlt_image_format vfmt = mlt_image_rgb24;
368         int height = this->height;
369         int width = this->width;
370         uint8_t *image = NULL;
371         int changed = 0;
372         double display_ratio = mlt_properties_get_double( this->properties, "display_ratio" );
373
374         void ( *lock )( void ) = mlt_properties_get_data( properties, "app_lock", NULL );
375         void ( *unlock )( void ) = mlt_properties_get_data( properties, "app_unlock", NULL );
376
377         if ( lock != NULL ) lock( );
378
379         sdl_lock_display();
380         
381         // Handle events
382         if ( this->sdl_screen != NULL )
383         {
384                 SDL_Event event;
385
386                 changed = consumer_get_dimensions( &this->window_width, &this->window_height );
387
388                 while ( SDL_PollEvent( &event ) )
389                 {
390                         mlt_events_fire( this->properties, "consumer-sdl-event", &event, NULL );
391
392                         switch( event.type )
393                         {
394                                 case SDL_VIDEORESIZE:
395                                         this->window_width = event.resize.w;
396                                         this->window_height = event.resize.h;
397                                         changed = 1;
398                                         break;
399                                 case SDL_QUIT:
400                                         this->running = 0;
401                                         break;
402                                 case SDL_KEYDOWN:
403                                         {
404                                                 mlt_producer producer = mlt_properties_get_data( properties, "transport_producer", NULL );
405                                                 char keyboard[ 2 ] = " ";
406                                                 void (*callback)( mlt_producer, char * ) = mlt_properties_get_data( properties, "transport_callback", NULL );
407                                                 if ( callback != NULL && producer != NULL && event.key.keysym.unicode < 0x80 && event.key.keysym.unicode > 0 )
408                                                 {
409                                                         keyboard[ 0 ] = ( char )event.key.keysym.unicode;
410                                                         callback( producer, keyboard );
411                                                 }
412                                         }
413                                         break;
414                         }
415                 }
416         }
417
418         if ( this->sdl_screen == NULL || changed )
419         {
420                 // open SDL window 
421                 this->sdl_screen = SDL_SetVideoMode( this->window_width, this->window_height, 0, this->sdl_flags );
422                 if ( consumer_get_dimensions( &this->window_width, &this->window_height ) )
423                         this->sdl_screen = SDL_SetVideoMode( this->window_width, this->window_height, 0, this->sdl_flags );
424                 uint32_t color = mlt_properties_get_int( this->properties, "window_background" );
425                 SDL_FillRect( this->sdl_screen, NULL, color >> 8 );
426                 changed = 1;
427         }
428
429         if ( changed == 0 &&
430                  this->last_position == mlt_frame_get_position( frame ) &&
431                  this->last_producer == mlt_frame_get_original_producer( frame ) &&
432                  !mlt_properties_get_int( MLT_FRAME_PROPERTIES( frame ), "refresh" ) )
433         {
434                 sdl_unlock_display( );
435                 if ( unlock != NULL ) unlock( );
436                 struct timespec tm = { 0, 100000 };
437                 nanosleep( &tm, NULL );
438                 return 0;
439         }
440
441         // Update last frame shown info
442         this->last_position = mlt_frame_get_position( frame );
443         this->last_producer = mlt_frame_get_original_producer( frame );
444
445         // Get the image, width and height
446         mlt_frame_get_image( frame, &image, &vfmt, &width, &height, 0 );
447         mlt_events_fire( properties, "consumer-frame-show", frame, NULL );
448
449         if ( image != NULL )
450         {
451                 char *rescale = mlt_properties_get( properties, "rescale" );
452                 if ( rescale != NULL && strcmp( rescale, "none" ) )
453                 {
454                         double this_aspect = display_ratio / ( ( double )this->window_width / ( double )this->window_height );
455                         this->rect.w = this_aspect * this->window_width;
456                         this->rect.h = this->window_height;
457                         if ( this->rect.w > this->window_width )
458                         {
459                                 this->rect.w = this->window_width;
460                                 this->rect.h = ( 1.0 / this_aspect ) * this->window_height;
461                         }
462                 }
463                 else
464                 {
465                         double frame_aspect = mlt_frame_get_aspect_ratio( frame ) * width / height;
466                         this->rect.w = frame_aspect * this->window_height;
467                         this->rect.h = this->window_height;
468                         if ( this->rect.w > this->window_width )
469                         {
470                                 this->rect.w = this->window_width;
471                                 this->rect.h = ( 1.0 / frame_aspect ) * this->window_width;
472                         }
473                 }
474
475                 this->rect.x = ( this->window_width - this->rect.w ) / 2;
476                 this->rect.y = ( this->window_height - this->rect.h ) / 2;
477
478                 mlt_properties_set_int( this->properties, "rect_x", this->rect.x );
479                 mlt_properties_set_int( this->properties, "rect_y", this->rect.y );
480                 mlt_properties_set_int( this->properties, "rect_w", this->rect.w );
481                 mlt_properties_set_int( this->properties, "rect_h", this->rect.h );
482         }
483         
484         if ( !mlt_consumer_is_stopped( &this->parent ) && SDL_GetVideoSurface( ) != NULL && this->sdl_screen != NULL && this->sdl_screen->pixels != NULL )
485         {
486                 switch( this->sdl_screen->format->BytesPerPixel )
487                 {
488                         case 1:
489                                 display_1( this->sdl_screen, this->rect, image, width, height );
490                                 break;
491                         case 2:
492                                 display_2( this->sdl_screen, this->rect, image, width, height );
493                                 break;
494                         case 3:
495                                 display_3( this->sdl_screen, this->rect, image, width, height );
496                                 break;
497                         case 4:
498                                 display_4( this->sdl_screen, this->rect, image, width, height );
499                                 break;
500                         default:
501                                 fprintf( stderr, "Unsupported video depth %d\n", this->sdl_screen->format->BytesPerPixel );
502                                 break;
503                 }
504
505                 // Flip it into sight
506                 SDL_Flip( this->sdl_screen );
507         }
508
509         sdl_unlock_display();
510
511         if ( unlock != NULL ) unlock( );
512
513         return 1;
514 }
515
516 /** Threaded wrapper for pipe.
517 */
518
519 static void *consumer_thread( void *arg )
520 {
521         // Identify the arg
522         consumer_sdl this = arg;
523
524         // Get the consumer
525         mlt_consumer consumer = &this->parent;
526         mlt_properties properties = MLT_CONSUMER_PROPERTIES( consumer );
527         mlt_frame frame = NULL;
528
529         // Allow the hosting app to provide the preview
530         int preview_off = mlt_properties_get_int( properties, "preview_off" );
531
532         // Loop until told not to
533         while( this->running )
534         {
535                 // Get a frame from the attached producer
536                 frame = mlt_consumer_rt_frame( consumer );
537
538                 // Ensure that we have a frame
539                 if ( this->running && frame != NULL )
540                 {
541                         if ( preview_off == 0 )
542                         {
543                                 consumer_play_video( this, frame );
544                         }
545                         else
546                         {
547                                 mlt_image_format vfmt = mlt_image_rgb24a;
548                                 int height = this->height;
549                                 int width = this->width;
550                                 uint8_t *image = NULL;
551                                 mlt_image_format preview_format = mlt_properties_get_int( properties, "preview_format" );
552
553                                 // Check if a specific colour space has been requested
554                                 if ( preview_off && preview_format != mlt_image_none )
555                                         vfmt = preview_format;
556                         
557                                 mlt_frame_get_image( frame, &image, &vfmt, &width, &height, 0 );
558                                 mlt_properties_set_int( MLT_FRAME_PROPERTIES( frame ), "format", vfmt );
559                                 mlt_events_fire( properties, "consumer-frame-show", frame, NULL );
560                         }
561                         mlt_frame_close( frame );
562                 }
563                 else
564                 {
565                         if ( frame ) mlt_frame_close( frame );
566                         this->running = 0;
567                 }
568         }
569
570         return NULL;
571 }
572
573 static int consumer_get_dimensions( int *width, int *height )
574 {
575         int changed = 0;
576
577         // SDL windows manager structure
578         SDL_SysWMinfo wm;
579
580         // Specify the SDL Version
581         SDL_VERSION( &wm.version );
582
583         // Get the wm structure
584         if ( SDL_GetWMInfo( &wm ) == 1 )
585         {
586 #ifndef __DARWIN__
587                 // Check that we have the X11 wm
588                 if ( wm.subsystem == SDL_SYSWM_X11 ) 
589                 {
590                         // Get the SDL window
591                         Window window = wm.info.x11.window;
592
593                         // Get the display session
594                         Display *display = wm.info.x11.display;
595
596                         // Get the window attributes
597                         XWindowAttributes attr;
598                         XGetWindowAttributes( display, window, &attr );
599
600                         // Determine whether window has changed
601                         changed = *width != attr.width || *height != attr.height;
602
603                         // Return width and height
604                         *width = attr.width;
605                         *height = attr.height;
606                 }
607 #endif
608         }
609
610         return changed;
611 }
612
613 /** Callback to allow override of the close method.
614 */
615
616 static void consumer_close( mlt_consumer parent )
617 {
618         // Get the actual object
619         consumer_sdl this = parent->child;
620
621         // Stop the consumer
622         mlt_consumer_stop( parent );
623
624         // Now clean up the rest
625         mlt_consumer_close( parent );
626
627         // Finally clean up this
628         free( this );
629 }