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