1 /*
2 * consumer_sdl.c -- A Simple DirectMedia Layer consumer
3 * Copyright (C) 2003-2021 Meltytech, LLC
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20 #include <framework/mlt_consumer.h>
21 #include <framework/mlt_frame.h>
22 #include <framework/mlt_deque.h>
23 #include <framework/mlt_factory.h>
24 #include <framework/mlt_filter.h>
25 #include <framework/mlt_log.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <pthread.h>
30 #include <SDL.h>
31 #include <SDL_syswm.h>
32 #include <sys/time.h>
33 #include <stdatomic.h>
34
35 #include "consumer_sdl_osx.h"
36
37 extern pthread_mutex_t mlt_sdl_mutex;
38
39 /** This classes definition.
40 */
41
42 typedef struct consumer_sdl_s *consumer_sdl;
43
44 struct consumer_sdl_s
45 {
46 struct mlt_consumer_s parent;
47 mlt_properties properties;
48 mlt_deque queue;
49 pthread_t thread;
50 int joined;
51 atomic_int running;
52 uint8_t audio_buffer[ 4096 * 10 ];
53 int audio_avail;
54 pthread_mutex_t audio_mutex;
55 pthread_cond_t audio_cond;
56 pthread_mutex_t video_mutex;
57 pthread_cond_t video_cond;
58 int window_width;
59 int window_height;
60 int previous_width;
61 int previous_height;
62 int width;
63 int height;
64 atomic_int playing;
65 int sdl_flags;
66 SDL_Overlay *sdl_overlay;
67 SDL_Rect rect;
68 uint8_t *buffer;
69 int bpp;
70 int is_purge;
71 };
72
73 /** Forward references to static functions.
74 */
75
76 static int consumer_start( mlt_consumer parent );
77 static int consumer_stop( mlt_consumer parent );
78 static int consumer_is_stopped( mlt_consumer parent );
79 static void consumer_purge( mlt_consumer parent );
80 static void consumer_close( mlt_consumer parent );
81 static void *consumer_thread( void * );
82 static int consumer_get_dimensions( int *width, int *height );
83 static void consumer_sdl_event( mlt_listener listener, mlt_properties owner, mlt_service self, void **args );
84
85 /** This is what will be called by the factory - anything can be passed in
86 via the argument, but keep it simple.
87 */
88
consumer_sdl_init(mlt_profile profile,mlt_service_type type,const char * id,char * arg)89 mlt_consumer consumer_sdl_init( mlt_profile profile, mlt_service_type type, const char *id, char *arg )
90 {
91 // Create the consumer object
92 consumer_sdl self = calloc( 1, sizeof( struct consumer_sdl_s ) );
93
94 // If no malloc'd and consumer init ok
95 if ( self != NULL && mlt_consumer_init( &self->parent, self, profile ) == 0 )
96 {
97 // Create the queue
98 self->queue = mlt_deque_init( );
99
100 // Get the parent consumer object
101 mlt_consumer parent = &self->parent;
102
103 // We have stuff to clean up, so override the close method
104 parent->close = consumer_close;
105
106 // get a handle on properties
107 mlt_service service = MLT_CONSUMER_SERVICE( parent );
108 self->properties = MLT_SERVICE_PROPERTIES( service );
109
110 // Set the default volume
111 mlt_properties_set_double( self->properties, "volume", 1.0 );
112
113 // This is the initialisation of the consumer
114 pthread_mutex_init( &self->audio_mutex, NULL );
115 pthread_cond_init( &self->audio_cond, NULL);
116 pthread_mutex_init( &self->video_mutex, NULL );
117 pthread_cond_init( &self->video_cond, NULL);
118
119 // Default scaler (for now we'll use nearest)
120 mlt_properties_set( self->properties, "rescale", "nearest" );
121 mlt_properties_set( self->properties, "deinterlace_method", "onefield" );
122 mlt_properties_set_int( self->properties, "top_field_first", -1 );
123
124 // Default buffer for low latency
125 mlt_properties_set_int( self->properties, "buffer", 1 );
126
127 // Default audio buffer
128 mlt_properties_set_int( self->properties, "audio_buffer", 2048 );
129
130 // Default scrub audio
131 mlt_properties_set_int( self->properties, "scrub_audio", 1 );
132
133 // Ensure we don't join on a non-running object
134 self->joined = 1;
135
136 // process actual param
137 if ( arg && sscanf( arg, "%dx%d", &self->width, &self->height ) )
138 {
139 mlt_properties_set_int( self->properties, "_arg_size", 1 );
140 }
141 else
142 {
143 self->width = mlt_properties_get_int( self->properties, "width" );
144 self->height = mlt_properties_get_int( self->properties, "height" );
145 }
146
147 // Set the sdl flags
148 self->sdl_flags = SDL_HWSURFACE | SDL_ASYNCBLIT | SDL_HWACCEL | SDL_DOUBLEBUF;
149 #if !defined(__APPLE__)
150 self->sdl_flags |= SDL_RESIZABLE;
151 #endif
152 // Allow thread to be started/stopped
153 parent->start = consumer_start;
154 parent->stop = consumer_stop;
155 parent->is_stopped = consumer_is_stopped;
156 parent->purge = consumer_purge;
157
158 // Register specific events
159 mlt_events_register( self->properties, "consumer-sdl-event" );
160
161 // Return the consumer produced
162 return parent;
163 }
164
165 // malloc or consumer init failed
166 free( self );
167
168 // Indicate failure
169 return NULL;
170 }
171
consumer_start(mlt_consumer parent)172 int consumer_start( mlt_consumer parent )
173 {
174 consumer_sdl self = parent->child;
175
176 if ( !self->running )
177 {
178 mlt_properties properties = MLT_CONSUMER_PROPERTIES( parent );
179 int video_off = mlt_properties_get_int( properties, "video_off" );
180 int preview_off = mlt_properties_get_int( properties, "preview_off" );
181 int display_off = video_off | preview_off;
182 int audio_off = mlt_properties_get_int( properties, "audio_off" );
183 int sdl_started = mlt_properties_get_int( properties, "sdl_started" );
184 char *output_display = mlt_properties_get( properties, "output_display" );
185 char *window_id = mlt_properties_get( properties, "window_id" );
186 char *audio_driver = mlt_properties_get( properties, "audio_driver" );
187 char *video_driver = mlt_properties_get( properties, "video_driver" );
188 char *audio_device = mlt_properties_get( properties, "audio_device" );
189
190 consumer_stop( parent );
191
192 self->running = 1;
193 self->joined = 0;
194
195 if ( output_display != NULL )
196 setenv( "DISPLAY", output_display, 1 );
197
198 if ( window_id != NULL )
199 setenv( "SDL_WINDOWID", window_id, 1 );
200
201 if ( video_driver != NULL )
202 setenv( "SDL_VIDEODRIVER", video_driver, 1 );
203
204 if ( audio_driver != NULL )
205 setenv( "SDL_AUDIODRIVER", audio_driver, 1 );
206
207 if ( audio_device != NULL )
208 setenv( "AUDIODEV", audio_device, 1 );
209
210 if ( ! mlt_properties_get_int( self->properties, "_arg_size" ) )
211 {
212 if ( mlt_properties_get_int( self->properties, "width" ) > 0 )
213 self->width = mlt_properties_get_int( self->properties, "width" );
214 if ( mlt_properties_get_int( self->properties, "height" ) > 0 )
215 self->height = mlt_properties_get_int( self->properties, "height" );
216 }
217
218 self->bpp = mlt_properties_get_int( self->properties, "bpp" );
219
220 if ( sdl_started == 0 && display_off == 0 )
221 {
222 pthread_mutex_lock( &mlt_sdl_mutex );
223 int ret = SDL_Init( SDL_INIT_VIDEO | SDL_INIT_NOPARACHUTE );
224 pthread_mutex_unlock( &mlt_sdl_mutex );
225 if ( ret < 0 )
226 {
227 mlt_log_error( MLT_CONSUMER_SERVICE(parent), "Failed to initialize SDL: %s\n", SDL_GetError() );
228 return -1;
229 }
230
231 SDL_EnableKeyRepeat( SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL );
232 SDL_EnableUNICODE( 1 );
233 }
234
235 if ( audio_off == 0 )
236 SDL_InitSubSystem( SDL_INIT_AUDIO );
237
238 // Default window size
239 if ( mlt_properties_get_int( self->properties, "_arg_size" ) )
240 {
241 self->window_width = self->width;
242 self->window_height = self->height;
243 }
244 else
245 {
246 double display_ratio = mlt_properties_get_double( self->properties, "display_ratio" );
247 self->window_width = ( double )self->height * display_ratio + 0.5;
248 self->window_height = self->height;
249 }
250
251 pthread_mutex_lock( &mlt_sdl_mutex );
252 if ( !SDL_GetVideoSurface() && display_off == 0 )
253 {
254 if ( mlt_properties_get_int( self->properties, "fullscreen" ) )
255 {
256 const SDL_VideoInfo *vi;
257 vi = SDL_GetVideoInfo();
258 self->window_width = vi->current_w;
259 self->window_height = vi->current_h;
260 self->sdl_flags |= SDL_FULLSCREEN;
261 SDL_ShowCursor( SDL_DISABLE );
262 }
263 SDL_SetVideoMode( self->window_width, self->window_height, 0, self->sdl_flags );
264 }
265 pthread_mutex_unlock( &mlt_sdl_mutex );
266
267 pthread_create( &self->thread, NULL, consumer_thread, self );
268 }
269
270 return 0;
271 }
272
consumer_stop(mlt_consumer parent)273 int consumer_stop( mlt_consumer parent )
274 {
275 // Get the actual object
276 consumer_sdl self = parent->child;
277
278 if ( self->joined == 0 )
279 {
280 // Kill the thread and clean up
281 self->joined = 1;
282 self->running = 0;
283
284 if ( !mlt_properties_get_int( MLT_CONSUMER_PROPERTIES( parent ), "audio_off" ) )
285 {
286 pthread_mutex_lock( &self->audio_mutex );
287 pthread_cond_broadcast( &self->audio_cond );
288 pthread_mutex_unlock( &self->audio_mutex );
289 }
290
291 #ifndef _WIN32
292 if ( self->thread )
293 #endif
294 pthread_join( self->thread, NULL );
295
296 // cleanup SDL
297 pthread_mutex_lock( &mlt_sdl_mutex );
298 if ( self->sdl_overlay != NULL )
299 SDL_FreeYUVOverlay( self->sdl_overlay );
300 self->sdl_overlay = NULL;
301 if ( !mlt_properties_get_int( MLT_CONSUMER_PROPERTIES( parent ), "audio_off" ) )
302 SDL_QuitSubSystem( SDL_INIT_AUDIO );
303 if ( mlt_properties_get_int( MLT_CONSUMER_PROPERTIES( parent ), "sdl_started" ) == 0 )
304 SDL_Quit( );
305 pthread_mutex_unlock( &mlt_sdl_mutex );
306 }
307
308 return 0;
309 }
310
consumer_is_stopped(mlt_consumer parent)311 int consumer_is_stopped( mlt_consumer parent )
312 {
313 consumer_sdl self = parent->child;
314 return !self->running;
315 }
316
consumer_purge(mlt_consumer parent)317 void consumer_purge( mlt_consumer parent )
318 {
319 consumer_sdl self = parent->child;
320 if ( self->running )
321 {
322 pthread_mutex_lock( &self->video_mutex );
323 while ( mlt_deque_count( self->queue ) )
324 mlt_frame_close( mlt_deque_pop_back( self->queue ) );
325 self->is_purge = 1;
326 pthread_cond_broadcast( &self->video_cond );
327 pthread_mutex_unlock( &self->video_mutex );
328 }
329 }
330
sdl_lock_display()331 static int sdl_lock_display( )
332 {
333 pthread_mutex_lock( &mlt_sdl_mutex );
334 SDL_Surface *screen = SDL_GetVideoSurface( );
335 int result = screen != NULL && ( !SDL_MUSTLOCK( screen ) || SDL_LockSurface( screen ) >= 0 );
336 pthread_mutex_unlock( &mlt_sdl_mutex );
337 return result;
338 }
339
sdl_unlock_display()340 static void sdl_unlock_display( )
341 {
342 pthread_mutex_lock( &mlt_sdl_mutex );
343 SDL_Surface *screen = SDL_GetVideoSurface( );
344 if ( screen != NULL && SDL_MUSTLOCK( screen ) )
345 SDL_UnlockSurface( screen );
346 pthread_mutex_unlock( &mlt_sdl_mutex );
347 }
348
sdl_fill_audio(void * udata,uint8_t * stream,int len)349 static void sdl_fill_audio( void *udata, uint8_t *stream, int len )
350 {
351 consumer_sdl self = udata;
352
353 // Get the volume
354 double volume = mlt_properties_get_double( self->properties, "volume" );
355
356 pthread_mutex_lock( &self->audio_mutex );
357
358 // Block until audio received
359 while ( self->running && len > self->audio_avail )
360 pthread_cond_wait( &self->audio_cond, &self->audio_mutex );
361
362 if ( self->audio_avail >= len )
363 {
364 // Place in the audio buffer
365 if ( volume != 1.0 )
366 SDL_MixAudio( stream, self->audio_buffer, len, ( int )( ( float )SDL_MIX_MAXVOLUME * volume ) );
367 else
368 memcpy( stream, self->audio_buffer, len );
369
370 // Remove len from the audio available
371 self->audio_avail -= len;
372
373 // Remove the samples
374 memmove( self->audio_buffer, self->audio_buffer + len, self->audio_avail );
375 }
376 else
377 {
378 // Just to be safe, wipe the stream first
379 memset( stream, 0, len );
380
381 // Mix the audio
382 SDL_MixAudio( stream, self->audio_buffer, len, ( int )( ( float )SDL_MIX_MAXVOLUME * volume ) );
383
384 // No audio left
385 self->audio_avail = 0;
386 }
387
388 // We're definitely playing now
389 self->playing = 1;
390
391 pthread_cond_broadcast( &self->audio_cond );
392 pthread_mutex_unlock( &self->audio_mutex );
393 }
394
consumer_play_audio(consumer_sdl self,mlt_frame frame,int init_audio,int * duration)395 static int consumer_play_audio( consumer_sdl self, mlt_frame frame, int init_audio, int *duration )
396 {
397 // Get the properties of self consumer
398 mlt_properties properties = self->properties;
399 mlt_audio_format afmt = mlt_audio_s16;
400
401 // Set the preferred params of the test card signal
402 int channels = mlt_properties_get_int( properties, "channels" );
403 int dest_channels = channels;
404 int frequency = mlt_properties_get_int( properties, "frequency" );
405 int scrub = mlt_properties_get_int( properties, "scrub_audio" );
406 static int counter = 0;
407
408 int samples = mlt_audio_calculate_frame_samples( mlt_properties_get_double( self->properties, "fps" ), frequency, counter++ );
409 int16_t *pcm;
410 mlt_frame_get_audio( frame, (void**) &pcm, &afmt, &frequency, &channels, &samples );
411 *duration = ( ( samples * 1000 ) / frequency );
412 pcm += mlt_properties_get_int( properties, "audio_offset" );
413
414 if ( mlt_properties_get_int( properties, "audio_off" ) )
415 {
416 self->playing = 1;
417 init_audio = 1;
418 return init_audio;
419 }
420
421 if ( init_audio == 1 )
422 {
423 SDL_AudioSpec request;
424 SDL_AudioSpec got;
425
426 int audio_buffer = mlt_properties_get_int( properties, "audio_buffer" );
427
428 // specify audio format
429 memset( &request, 0, sizeof( SDL_AudioSpec ) );
430 self->playing = 0;
431 request.freq = frequency;
432 request.format = AUDIO_S16SYS;
433 request.channels = dest_channels;
434 request.samples = audio_buffer;
435 request.callback = sdl_fill_audio;
436 request.userdata = (void *)self;
437 if ( SDL_OpenAudio( &request, &got ) != 0 )
438 {
439 mlt_log_error( MLT_CONSUMER_SERVICE( self ), "SDL failed to open audio: %s\n", SDL_GetError() );
440 init_audio = 2;
441 }
442 else if ( got.size != 0 )
443 {
444 SDL_PauseAudio( 0 );
445 init_audio = 0;
446 }
447 }
448
449 if ( init_audio == 0 )
450 {
451 mlt_properties properties = MLT_FRAME_PROPERTIES( frame );
452 int samples_copied = 0;
453 int dst_stride = dest_channels * sizeof( *pcm );
454
455 pthread_mutex_lock( &self->audio_mutex );
456
457 while ( self->running && samples_copied < samples )
458 {
459 int sample_space = ( sizeof( self->audio_buffer ) - self->audio_avail ) / dst_stride;
460 while ( self->running && sample_space == 0 )
461 {
462 pthread_cond_wait( &self->audio_cond, &self->audio_mutex );
463 sample_space = ( sizeof( self->audio_buffer ) - self->audio_avail ) / dst_stride;
464 }
465 if ( self->running )
466 {
467 int samples_to_copy = samples - samples_copied;
468 if ( samples_to_copy > sample_space )
469 {
470 samples_to_copy = sample_space;
471 }
472 int dst_bytes = samples_to_copy * dst_stride;
473
474 if ( scrub || mlt_properties_get_double( properties, "_speed" ) == 1 )
475 {
476 if ( channels == dest_channels )
477 {
478 memcpy( &self->audio_buffer[ self->audio_avail ], pcm, dst_bytes );
479 pcm += samples_to_copy * channels;
480 }
481 else
482 {
483 int16_t *dest = (int16_t*) &self->audio_buffer[ self->audio_avail ];
484 int i = samples_to_copy + 1;
485 while ( --i )
486 {
487 memcpy( dest, pcm, dst_stride );
488 pcm += channels;
489 dest += dest_channels;
490 }
491 }
492 }
493 else
494 {
495 memset( &self->audio_buffer[ self->audio_avail ], 0, dst_bytes );
496 pcm += samples_to_copy * channels;
497 }
498 self->audio_avail += dst_bytes;
499 samples_copied += samples_to_copy;
500 }
501 pthread_cond_broadcast( &self->audio_cond );
502 }
503 pthread_mutex_unlock( &self->audio_mutex );
504 }
505 else
506 {
507 self->playing = 1;
508 }
509
510 return init_audio;
511 }
512
consumer_play_video(consumer_sdl self,mlt_frame frame)513 static int consumer_play_video( consumer_sdl self, mlt_frame frame )
514 {
515 // Get the properties of this consumer
516 mlt_properties properties = self->properties;
517
518 mlt_image_format vfmt = mlt_properties_get_int( properties, "mlt_image_format" );
519 int width = self->width, height = self->height;
520 uint8_t *image;
521 int changed = 0;
522
523 int video_off = mlt_properties_get_int( properties, "video_off" );
524 int preview_off = mlt_properties_get_int( properties, "preview_off" );
525 mlt_image_format preview_format = mlt_properties_get_int( properties, "preview_format" );
526 int display_off = video_off | preview_off;
527
528 if ( self->running && display_off == 0 )
529 {
530 // Get the image, width and height
531 mlt_frame_get_image( frame, &image, &vfmt, &width, &height, 0 );
532
533 void *pool = mlt_cocoa_autorelease_init();
534
535 // Handle events
536 if ( SDL_GetVideoSurface() )
537 {
538 SDL_Event event;
539
540 sdl_lock_display( );
541 pthread_mutex_lock( &mlt_sdl_mutex );
542 changed = consumer_get_dimensions( &self->window_width, &self->window_height );
543 pthread_mutex_unlock( &mlt_sdl_mutex );
544 sdl_unlock_display( );
545
546 while ( SDL_PollEvent( &event ) )
547 {
548 mlt_events_fire( self->properties, "consumer-sdl-event", mlt_event_data_from_object(&event) );
549
550 switch( event.type )
551 {
552 case SDL_VIDEORESIZE:
553 self->window_width = event.resize.w;
554 self->window_height = event.resize.h;
555 changed = 1;
556 break;
557 case SDL_QUIT:
558 self->running = 0;
559 break;
560 case SDL_KEYDOWN:
561 {
562 mlt_producer producer = mlt_properties_get_data( properties, "transport_producer", NULL );
563 char keyboard[ 2 ] = " ";
564 void (*callback)( mlt_producer, char * ) = mlt_properties_get_data( properties, "transport_callback", NULL );
565 if ( callback != NULL && producer != NULL && event.key.keysym.unicode < 0x80 && event.key.keysym.unicode > 0 )
566 {
567 keyboard[ 0 ] = ( char )event.key.keysym.unicode;
568 callback( producer, keyboard );
569 }
570 }
571 break;
572 }
573 }
574 }
575
576 sdl_lock_display();
577
578 if ( width != self->width || height != self->height )
579 {
580 if ( self->sdl_overlay != NULL )
581 SDL_FreeYUVOverlay( self->sdl_overlay );
582 self->sdl_overlay = NULL;
583 }
584
585 if ( self->running && ( !SDL_GetVideoSurface() || changed ) )
586 {
587 // Force an overlay recreation
588 if ( self->sdl_overlay != NULL )
589 SDL_FreeYUVOverlay( self->sdl_overlay );
590 self->sdl_overlay = NULL;
591
592 // open SDL window with video overlay, if possible
593 pthread_mutex_lock( &mlt_sdl_mutex );
594 SDL_Surface *screen = SDL_SetVideoMode( self->window_width, self->window_height, self->bpp, self->sdl_flags );
595 if ( consumer_get_dimensions( &self->window_width, &self->window_height ) )
596 screen = SDL_SetVideoMode( self->window_width, self->window_height, self->bpp, self->sdl_flags );
597 pthread_mutex_unlock( &mlt_sdl_mutex );
598
599 if ( screen )
600 {
601 uint32_t color = mlt_properties_get_int( self->properties, "window_background" );
602 SDL_FillRect( screen, NULL, color >> 8 );
603 SDL_Flip( screen );
604 }
605 }
606
607 if ( self->running )
608 {
609 // Determine window's new display aspect ratio
610 double this_aspect = ( double )self->window_width / self->window_height;
611
612 // Get the display aspect ratio
613 double display_ratio = mlt_properties_get_double( properties, "display_ratio" );
614
615 // Determine frame's display aspect ratio
616 double frame_aspect = mlt_frame_get_aspect_ratio( frame ) * width / height;
617
618 // Store the width and height received
619 self->width = width;
620 self->height = height;
621
622 // If using hardware scaler
623 if ( mlt_properties_get( properties, "rescale" ) != NULL &&
624 !strcmp( mlt_properties_get( properties, "rescale" ), "none" ) )
625 {
626 // Use hardware scaler to normalise display aspect ratio
627 self->rect.w = frame_aspect / this_aspect * self->window_width;
628 self->rect.h = self->window_height;
629 if ( self->rect.w > self->window_width )
630 {
631 self->rect.w = self->window_width;
632 self->rect.h = this_aspect / frame_aspect * self->window_height;
633 }
634 }
635 // Special case optimisation to negate odd effect of sample aspect ratio
636 // not corresponding exactly with image resolution.
637 else if ( (int)( this_aspect * 1000 ) == (int)( display_ratio * 1000 ) )
638 {
639 self->rect.w = self->window_width;
640 self->rect.h = self->window_height;
641 }
642 // Use hardware scaler to normalise sample aspect ratio
643 else if ( self->window_height * display_ratio > self->window_width )
644 {
645 self->rect.w = self->window_width;
646 self->rect.h = self->window_width / display_ratio;
647 }
648 else
649 {
650 self->rect.w = self->window_height * display_ratio;
651 self->rect.h = self->window_height;
652 }
653
654 self->rect.x = ( self->window_width - self->rect.w ) / 2;
655 self->rect.y = ( self->window_height - self->rect.h ) / 2;
656 self->rect.x -= self->rect.x % 2;
657
658 mlt_properties_set_int( self->properties, "rect_x", self->rect.x );
659 mlt_properties_set_int( self->properties, "rect_y", self->rect.y );
660 mlt_properties_set_int( self->properties, "rect_w", self->rect.w );
661 mlt_properties_set_int( self->properties, "rect_h", self->rect.h );
662
663 SDL_SetClipRect( SDL_GetVideoSurface(), &self->rect );
664 }
665
666 if ( self->running && SDL_GetVideoSurface() && self->sdl_overlay == NULL )
667 {
668 SDL_SetClipRect( SDL_GetVideoSurface(), &self->rect );
669 self->sdl_overlay = SDL_CreateYUVOverlay( width, height,
670 ( vfmt == mlt_image_yuv422 ? SDL_YUY2_OVERLAY : SDL_IYUV_OVERLAY ),
671 SDL_GetVideoSurface() );
672 }
673
674 if ( self->running && SDL_GetVideoSurface() && self->sdl_overlay != NULL )
675 {
676 self->buffer = self->sdl_overlay->pixels[ 0 ];
677 if ( SDL_LockYUVOverlay( self->sdl_overlay ) >= 0 )
678 {
679 int size = mlt_image_format_size( vfmt, width, height, NULL );
680 if ( image != NULL )
681 memcpy( self->buffer, image, size );
682 SDL_UnlockYUVOverlay( self->sdl_overlay );
683 SDL_DisplayYUVOverlay( self->sdl_overlay, &SDL_GetVideoSurface()->clip_rect );
684 }
685 }
686
687 sdl_unlock_display();
688 mlt_cocoa_autorelease_close( pool );
689 mlt_events_fire( properties, "consumer-frame-show", mlt_event_data_from_frame(frame) );
690 }
691 else if ( self->running )
692 {
693 vfmt = preview_format == mlt_image_none ? mlt_image_rgba : preview_format;
694 if ( !video_off )
695 mlt_frame_get_image( frame, &image, &vfmt, &width, &height, 0 );
696 mlt_events_fire( properties, "consumer-frame-show", mlt_event_data_from_frame(frame) );
697 }
698
699 return 0;
700 }
701
video_thread(void * arg)702 static void *video_thread( void *arg )
703 {
704 // Identify the arg
705 consumer_sdl self = arg;
706
707 // Obtain time of thread start
708 struct timeval now;
709 int64_t start = 0;
710 int64_t elapsed = 0;
711 struct timespec tm;
712 mlt_frame next = NULL;
713 mlt_properties properties = NULL;
714 double speed = 0;
715
716 // Get real time flag
717 int real_time = mlt_properties_get_int( self->properties, "real_time" );
718
719 // Get the current time
720 gettimeofday( &now, NULL );
721
722 // Determine start time
723 start = ( int64_t )now.tv_sec * 1000000 + now.tv_usec;
724
725 while ( self->running )
726 {
727 // Pop the next frame
728 pthread_mutex_lock( &self->video_mutex );
729 next = mlt_deque_pop_front( self->queue );
730 while ( next == NULL && self->running )
731 {
732 pthread_cond_wait( &self->video_cond, &self->video_mutex );
733 next = mlt_deque_pop_front( self->queue );
734 }
735 pthread_mutex_unlock( &self->video_mutex );
736
737 if ( !self->running || next == NULL ) break;
738
739 // Get the properties
740 properties = MLT_FRAME_PROPERTIES( next );
741
742 // Get the speed of the frame
743 speed = mlt_properties_get_double( properties, "_speed" );
744
745 // Get the current time
746 gettimeofday( &now, NULL );
747
748 // Get the elapsed time
749 elapsed = ( ( int64_t )now.tv_sec * 1000000 + now.tv_usec ) - start;
750
751 // See if we have to delay the display of the current frame
752 if ( mlt_properties_get_int( properties, "rendered" ) == 1 && self->running )
753 {
754 // Obtain the scheduled playout time
755 int64_t scheduled = mlt_properties_get_int( properties, "playtime" );
756
757 // Determine the difference between the elapsed time and the scheduled playout time
758 int64_t difference = scheduled - elapsed;
759
760 // Smooth playback a bit
761 if ( real_time && ( difference > 20000 && speed == 1.0 ) )
762 {
763 tm.tv_sec = difference / 1000000;
764 tm.tv_nsec = ( difference % 1000000 ) * 500;
765 nanosleep( &tm, NULL );
766 }
767
768 // Show current frame if not too old
769 if ( !real_time || ( difference > -10000 || speed != 1.0 || mlt_deque_count( self->queue ) < 2 ) )
770 consumer_play_video( self, next );
771
772 // If the queue is empty, recalculate start to allow build up again
773 if ( real_time && ( mlt_deque_count( self->queue ) == 0 && speed == 1.0 ) )
774 {
775 gettimeofday( &now, NULL );
776 start = ( ( int64_t )now.tv_sec * 1000000 + now.tv_usec ) - scheduled + 20000;
777 }
778 }
779 else
780 {
781 static int dropped = 0;
782 mlt_log_info( MLT_CONSUMER_SERVICE(&self->parent), "dropped video frame %d\n", ++dropped );
783 }
784
785 // This frame can now be closed
786 mlt_frame_close( next );
787 next = NULL;
788 }
789
790 if ( next != NULL )
791 mlt_frame_close( next );
792
793 mlt_consumer_stopped( &self->parent );
794
795 return NULL;
796 }
797
798 /** Threaded wrapper for pipe.
799 */
800
consumer_thread(void * arg)801 static void *consumer_thread( void *arg )
802 {
803 // Identify the arg
804 consumer_sdl self = arg;
805
806 // Get the consumer
807 mlt_consumer consumer = &self->parent;
808
809 // Convenience functionality
810 int terminate_on_pause = mlt_properties_get_int( MLT_CONSUMER_PROPERTIES( consumer ), "terminate_on_pause" );
811 int terminated = 0;
812
813 // Video thread
814 pthread_t thread;
815
816 // internal initialization
817 int init_audio = 1;
818 int init_video = 1;
819 mlt_frame frame = NULL;
820 int duration = 0;
821 int64_t playtime = 0;
822 struct timespec tm = { 0, 100000 };
823
824 // Loop until told not to
825 while( self->running )
826 {
827 // Get a frame from the attached producer
828 frame = !terminated? mlt_consumer_rt_frame( consumer ) : NULL;
829
830 // Check for termination
831 if ( terminate_on_pause && frame )
832 terminated = mlt_properties_get_double( MLT_FRAME_PROPERTIES( frame ), "_speed" ) == 0.0;
833
834 // Ensure that we have a frame
835 if ( frame )
836 {
837 // Play audio
838 init_audio = consumer_play_audio( self, frame, init_audio, &duration );
839
840 // Determine the start time now
841 if ( self->playing && init_video )
842 {
843 // Create the video thread
844 pthread_create( &thread, NULL, video_thread, self );
845
846 // Video doesn't need to be initialised any more
847 init_video = 0;
848 }
849
850 // Set playtime for this frame
851 mlt_properties_set_int( MLT_FRAME_PROPERTIES( frame ), "playtime", playtime );
852
853 while ( self->running && mlt_deque_count( self->queue ) > 15 )
854 nanosleep( &tm, NULL );
855
856 // Push this frame to the back of the queue
857 pthread_mutex_lock( &self->video_mutex );
858 if ( self->is_purge )
859 {
860 mlt_frame_close( frame );
861 frame = NULL;
862 self->is_purge = 0;
863 }
864 else
865 {
866 mlt_deque_push_back( self->queue, frame );
867 pthread_cond_broadcast( &self->video_cond );
868 }
869 pthread_mutex_unlock( &self->video_mutex );
870
871 // Calculate the next playtime
872 playtime += ( duration * 1000 );
873 }
874 else if ( terminated )
875 {
876 if ( init_video || mlt_deque_count( self->queue ) == 0 )
877 break;
878 else
879 nanosleep( &tm, NULL );
880 }
881 }
882
883 self->running = 0;
884
885 // Unblock sdl_preview
886 if ( mlt_properties_get_int( MLT_CONSUMER_PROPERTIES( consumer ), "put_mode" ) &&
887 mlt_properties_get_int( MLT_CONSUMER_PROPERTIES( consumer ), "put_pending" ) )
888 {
889 frame = mlt_consumer_get_frame( consumer );
890 if ( frame )
891 mlt_frame_close( frame );
892 frame = NULL;
893 }
894
895 // Kill the video thread
896 if ( init_video == 0 )
897 {
898 pthread_mutex_lock( &self->video_mutex );
899 pthread_cond_broadcast( &self->video_cond );
900 pthread_mutex_unlock( &self->video_mutex );
901 pthread_join( thread, NULL );
902 }
903
904 while( mlt_deque_count( self->queue ) )
905 mlt_frame_close( mlt_deque_pop_back( self->queue ) );
906
907 pthread_mutex_lock( &self->audio_mutex );
908 self->audio_avail = 0;
909 pthread_mutex_unlock( &self->audio_mutex );
910
911 return NULL;
912 }
913
consumer_get_dimensions(int * width,int * height)914 static int consumer_get_dimensions( int *width, int *height )
915 {
916 int changed = 0;
917
918 // SDL windows manager structure
919 SDL_SysWMinfo wm;
920
921 // Specify the SDL Version
922 SDL_VERSION( &wm.version );
923
924 // Lock the display
925 //sdl_lock_display();
926
927 #ifndef __APPLE__
928 // Get the wm structure
929 if ( SDL_GetWMInfo( &wm ) == 1 )
930 {
931 #ifndef _WIN32
932 // Check that we have the X11 wm
933 if ( wm.subsystem == SDL_SYSWM_X11 )
934 {
935 // Get the SDL window
936 Window window = wm.info.x11.window;
937
938 // Get the display session
939 Display *display = wm.info.x11.display;
940
941 // Get the window attributes
942 XWindowAttributes attr;
943 XGetWindowAttributes( display, window, &attr );
944
945 // Determine whether window has changed
946 changed = *width != attr.width || *height != attr.height;
947
948 // Return width and height
949 *width = attr.width;
950 *height = attr.height;
951 }
952 #endif
953 }
954 #endif
955
956 // Unlock the display
957 //sdl_unlock_display();
958
959 return changed;
960 }
961
962 /** Callback to allow override of the close method.
963 */
964
consumer_close(mlt_consumer parent)965 static void consumer_close( mlt_consumer parent )
966 {
967 // Get the actual object
968 consumer_sdl self = parent->child;
969
970 // Stop the consumer
971 ///mlt_consumer_stop( parent );
972
973 // Now clean up the rest
974 mlt_consumer_close( parent );
975
976 // Close the queue
977 mlt_deque_close( self->queue );
978
979 // Destroy mutexes
980 pthread_mutex_destroy( &self->audio_mutex );
981 pthread_cond_destroy( &self->audio_cond );
982
983 // Finally clean up this
984 free( self );
985 }
986