1 /*
2 * consumer_sdl2_audio.c -- A Simple DirectMedia Layer audio-only consumer
3 * Copyright (C) 2009-2020 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 "common.h"
21
22 #include <framework/mlt_consumer.h>
23 #include <framework/mlt_frame.h>
24 #include <framework/mlt_deque.h>
25 #include <framework/mlt_factory.h>
26 #include <framework/mlt_filter.h>
27 #include <framework/mlt_log.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <pthread.h>
32 #include <SDL.h>
33 #include <sys/time.h>
34 #include <stdatomic.h>
35
36 extern pthread_mutex_t mlt_sdl_mutex;
37
38 /** This classes definition.
39 */
40
41 typedef struct consumer_sdl_s *consumer_sdl;
42
43 struct consumer_sdl_s
44 {
45 struct mlt_consumer_s parent;
46 mlt_properties properties;
47 mlt_deque queue;
48 pthread_t thread;
49 int joined;
50 atomic_int running;
51 uint8_t audio_buffer[ 4096 * 10 ];
52 int audio_avail;
53 pthread_mutex_t audio_mutex;
54 pthread_cond_t audio_cond;
55 pthread_mutex_t video_mutex;
56 pthread_cond_t video_cond;
57 int out_channels;
58 atomic_int playing;
59
60 pthread_cond_t refresh_cond;
61 pthread_mutex_t refresh_mutex;
62 int refresh_count;
63 int is_purge;
64 #ifdef _WIN32
65 int no_quit_subsystem;
66 #endif
67 };
68
69 /** Forward references to static functions.
70 */
71
72 static int consumer_start( mlt_consumer parent );
73 static int consumer_stop( mlt_consumer parent );
74 static int consumer_is_stopped( mlt_consumer parent );
75 static void consumer_purge( mlt_consumer parent );
76 static void consumer_close( mlt_consumer parent );
77 static void *consumer_thread( void * );
78 static void consumer_refresh_cb( mlt_consumer sdl, mlt_consumer self, char *name );
79
80 /** This is what will be called by the factory - anything can be passed in
81 via the argument, but keep it simple.
82 */
83
consumer_sdl2_audio_init(mlt_profile profile,mlt_service_type type,const char * id,char * arg)84 mlt_consumer consumer_sdl2_audio_init( mlt_profile profile, mlt_service_type type, const char *id, char *arg )
85 {
86 // Create the consumer object
87 consumer_sdl self = calloc( 1, sizeof( struct consumer_sdl_s ) );
88
89 // If no malloc'd and consumer init ok
90 if ( self != NULL && mlt_consumer_init( &self->parent, self, profile ) == 0 )
91 {
92 // Create the queue
93 self->queue = mlt_deque_init( );
94
95 // Get the parent consumer object
96 mlt_consumer parent = &self->parent;
97
98 // We have stuff to clean up, so override the close method
99 parent->close = consumer_close;
100
101 // get a handle on properties
102 mlt_service service = MLT_CONSUMER_SERVICE( parent );
103 self->properties = MLT_SERVICE_PROPERTIES( service );
104
105 // Set the default volume
106 mlt_properties_set_double( self->properties, "volume", 1.0 );
107
108 // This is the initialisation of the consumer
109 pthread_mutex_init( &self->audio_mutex, NULL );
110 pthread_cond_init( &self->audio_cond, NULL);
111 pthread_mutex_init( &self->video_mutex, NULL );
112 pthread_cond_init( &self->video_cond, NULL);
113
114 // Default scaler (for now we'll use nearest)
115 mlt_properties_set( self->properties, "rescale", "nearest" );
116 mlt_properties_set( self->properties, "deinterlace_method", "onefield" );
117 mlt_properties_set_int( self->properties, "top_field_first", -1 );
118
119 // Default buffer for low latency
120 mlt_properties_set_int( self->properties, "buffer", 1 );
121
122 // Default audio buffer
123 mlt_properties_set_int( self->properties, "audio_buffer", 2048 );
124
125 // Ensure we don't join on a non-running object
126 self->joined = 1;
127
128 // Allow thread to be started/stopped
129 parent->start = consumer_start;
130 parent->stop = consumer_stop;
131 parent->is_stopped = consumer_is_stopped;
132 parent->purge = consumer_purge;
133
134 // Initialize the refresh handler
135 pthread_cond_init( &self->refresh_cond, NULL );
136 pthread_mutex_init( &self->refresh_mutex, NULL );
137 mlt_events_listen( MLT_CONSUMER_PROPERTIES( parent ), self, "property-changed", ( mlt_listener )consumer_refresh_cb );
138
139 // Return the consumer produced
140 return parent;
141 }
142
143 // malloc or consumer init failed
144 free( self );
145
146 // Indicate failure
147 return NULL;
148 }
149
consumer_refresh_cb(mlt_consumer sdl,mlt_consumer parent,char * name)150 static void consumer_refresh_cb( mlt_consumer sdl, mlt_consumer parent, char *name )
151 {
152 if ( !strcmp( name, "refresh" ) )
153 {
154 consumer_sdl self = parent->child;
155 pthread_mutex_lock( &self->refresh_mutex );
156 if ( self->refresh_count < 2 )
157 self->refresh_count = self->refresh_count <= 0 ? 1 : self->refresh_count + 1;
158 pthread_cond_broadcast( &self->refresh_cond );
159 pthread_mutex_unlock( &self->refresh_mutex );
160 }
161 }
162
consumer_start(mlt_consumer parent)163 int consumer_start( mlt_consumer parent )
164 {
165 consumer_sdl self = parent->child;
166
167 if ( !self->running )
168 {
169 consumer_stop( parent );
170
171 mlt_properties properties = MLT_CONSUMER_PROPERTIES( parent );
172 char *audio_driver = mlt_properties_get( properties, "audio_driver" );
173 char *audio_device = mlt_properties_get( properties, "audio_device" );
174
175 if ( audio_driver && strcmp( audio_driver, "" ) )
176 setenv( "SDL_AUDIODRIVER", audio_driver, 1 );
177
178 if ( audio_device && strcmp( audio_device, "" ) )
179 setenv( "AUDIODEV", audio_device, 1 );
180
181 pthread_mutex_lock( &mlt_sdl_mutex );
182 int ret = SDL_Init( SDL_INIT_AUDIO | SDL_INIT_NOPARACHUTE );
183 pthread_mutex_unlock( &mlt_sdl_mutex );
184 if ( ret < 0 )
185 {
186 mlt_log_error( MLT_CONSUMER_SERVICE(parent), "Failed to initialize SDL: %s\n", SDL_GetError() );
187 return -1;
188 }
189 self->running = 1;
190 self->joined = 0;
191 pthread_create( &self->thread, NULL, consumer_thread, self );
192 }
193
194 return 0;
195 }
196
consumer_stop(mlt_consumer parent)197 int consumer_stop( mlt_consumer parent )
198 {
199 // Get the actual object
200 consumer_sdl self = parent->child;
201
202 if ( self->running && !self->joined )
203 {
204 // Kill the thread and clean up
205 self->joined = 1;
206 self->running = 0;
207
208 // Unlatch the consumer thread
209 pthread_mutex_lock( &self->refresh_mutex );
210 pthread_cond_broadcast( &self->refresh_cond );
211 pthread_mutex_unlock( &self->refresh_mutex );
212
213 // Cleanup the main thread
214 #ifndef _WIN32
215 if ( self->thread )
216 #endif
217 pthread_join( self->thread, NULL );
218
219 // Unlatch the video thread
220 pthread_mutex_lock( &self->video_mutex );
221 pthread_cond_broadcast( &self->video_cond );
222 pthread_mutex_unlock( &self->video_mutex );
223
224 // Unlatch the audio callback
225 pthread_mutex_lock( &self->audio_mutex );
226 pthread_cond_broadcast( &self->audio_cond );
227 pthread_mutex_unlock( &self->audio_mutex );
228 #ifdef _WIN32
229 if ( !self->no_quit_subsystem )
230 #endif
231 SDL_QuitSubSystem( SDL_INIT_AUDIO );
232 }
233
234 return 0;
235 }
236
consumer_is_stopped(mlt_consumer parent)237 int consumer_is_stopped( mlt_consumer parent )
238 {
239 consumer_sdl self = parent->child;
240 return !self->running;
241 }
242
consumer_purge(mlt_consumer parent)243 void consumer_purge( mlt_consumer parent )
244 {
245 consumer_sdl self = parent->child;
246 if ( self->running )
247 {
248 pthread_mutex_lock( &self->video_mutex );
249 mlt_frame frame = MLT_FRAME( mlt_deque_peek_back( self->queue ) );
250 // When playing rewind or fast forward then we need to keep one
251 // frame in the queue to prevent playback stalling.
252 double speed = frame? mlt_properties_get_double( MLT_FRAME_PROPERTIES(frame), "_speed" ) : 0;
253 int n = ( speed == 0.0 || speed == 1.0 ) ? 0 : 1;
254 while ( mlt_deque_count( self->queue ) > n )
255 mlt_frame_close( mlt_deque_pop_back( self->queue ) );
256 self->is_purge = 1;
257 pthread_cond_broadcast( &self->video_cond );
258 pthread_mutex_unlock( &self->video_mutex );
259 }
260 }
261
sdl_fill_audio(void * udata,uint8_t * stream,int len)262 static void sdl_fill_audio( void *udata, uint8_t *stream, int len )
263 {
264 consumer_sdl self = udata;
265
266 // Get the volume
267 double volume = mlt_properties_get_double( self->properties, "volume" );
268
269 // Wipe the stream first
270 memset( stream, 0, len );
271
272 pthread_mutex_lock( &self->audio_mutex );
273 int bytes = MIN(len, self->audio_avail);
274
275 // Place in the audio buffer
276 if ( volume != 1.0 ) {
277 // Adjust the volume while copying.
278 int16_t *src = (int16_t*) self->audio_buffer;
279 int16_t *dst = (int16_t*) stream;
280 int i = bytes / sizeof(*dst) + 1;
281 while (--i) {
282 *dst++ = CLAMP(volume * src[0], -32768, 32767);
283 src++;
284 }
285 } else {
286 memcpy( stream, self->audio_buffer, bytes );
287 }
288
289 // Remove len from the audio available
290 self->audio_avail -= bytes;
291
292 // Remove the samples
293 memmove( self->audio_buffer, self->audio_buffer + bytes, self->audio_avail );
294
295 // We're definitely playing now
296 self->playing = 1;
297
298 pthread_cond_broadcast( &self->audio_cond );
299 pthread_mutex_unlock( &self->audio_mutex );
300 }
301
consumer_play_audio(consumer_sdl self,mlt_frame frame,int init_audio,int64_t * duration)302 static int consumer_play_audio( consumer_sdl self, mlt_frame frame, int init_audio, int64_t *duration )
303 {
304 // Get the properties of self consumer
305 mlt_properties properties = self->properties;
306 mlt_audio_format afmt = mlt_audio_s16;
307
308 // Set the preferred params of the test card signal
309 int channels = mlt_properties_get_int( properties, "channels" );
310 int frequency = mlt_properties_get_int( properties, "frequency" );
311 int scrub = mlt_properties_get_int( properties, "scrub_audio" );
312 static int counter = 0;
313
314 int samples = mlt_audio_calculate_frame_samples( mlt_properties_get_double( self->properties, "fps" ), frequency, counter++ );
315 int16_t *pcm;
316 mlt_frame_get_audio( frame, (void**) &pcm, &afmt, &frequency, &channels, &samples );
317 *duration = 1000000LL * samples / frequency;
318 pcm += mlt_properties_get_int( properties, "audio_offset" );
319
320 if ( mlt_properties_get_int( properties, "audio_off" ) )
321 {
322 self->playing = 1;
323 init_audio = 1;
324 return init_audio;
325 }
326
327 if ( init_audio == 1 )
328 {
329 SDL_AudioSpec request;
330 SDL_AudioSpec got;
331 SDL_AudioDeviceID dev;
332 int audio_buffer = mlt_properties_get_int( properties, "audio_buffer" );
333
334 // specify audio format
335 memset( &request, 0, sizeof( SDL_AudioSpec ) );
336 self->playing = 0;
337 request.freq = frequency;
338 request.format = AUDIO_S16SYS;
339 request.channels = mlt_properties_get_int( properties, "channels" );
340 request.samples = audio_buffer;
341 request.callback = sdl_fill_audio;
342 request.userdata = (void *)self;
343
344 dev = sdl2_open_audio( &request, &got );
345 if( dev == 0 )
346 {
347 mlt_log_error( MLT_CONSUMER_SERVICE( self ), "SDL failed to open audio\n" );
348 init_audio = 2;
349 }
350 else
351 {
352 if( got.channels != request.channels )
353 {
354 mlt_log_info( MLT_CONSUMER_SERVICE( self ), "Unable to output %d channels. Change to %d\n", request.channels, got.channels );
355 }
356 mlt_log_info( MLT_CONSUMER_SERVICE( self ), "Audio Opened: driver=%s channels=%d frequency=%d\n", SDL_GetCurrentAudioDriver(), got.channels, got.freq );
357 SDL_PauseAudioDevice( dev, 0 );
358 init_audio = 0;
359 self->out_channels = got.channels;
360 }
361 }
362
363 if ( init_audio == 0 )
364 {
365 mlt_properties properties = MLT_FRAME_PROPERTIES( frame );
366 int samples_copied = 0;
367 int dst_stride = self->out_channels * sizeof( *pcm );
368
369 pthread_mutex_lock( &self->audio_mutex );
370
371 while ( self->running && samples_copied < samples )
372 {
373 int sample_space = ( sizeof( self->audio_buffer ) - self->audio_avail ) / dst_stride;
374 while ( self->running && sample_space == 0 )
375 {
376 struct timeval now;
377 struct timespec tm;
378
379 gettimeofday( &now, NULL );
380 tm.tv_sec = now.tv_sec + 1;
381 tm.tv_nsec = now.tv_usec * 1000;
382 pthread_cond_timedwait( &self->audio_cond, &self->audio_mutex, &tm );
383 sample_space = ( sizeof( self->audio_buffer ) - self->audio_avail ) / dst_stride;
384
385 if ( sample_space == 0 )
386 {
387 mlt_log_warning( MLT_CONSUMER_SERVICE(&self->parent), "audio timed out\n" );
388 pthread_mutex_unlock( &self->audio_mutex );
389 #ifdef _WIN32
390 self->no_quit_subsystem = 1;
391 #endif
392 return 1;
393 }
394 }
395 if ( self->running )
396 {
397 int samples_to_copy = samples - samples_copied;
398 if ( samples_to_copy > sample_space )
399 {
400 samples_to_copy = sample_space;
401 }
402 int dst_bytes = samples_to_copy * dst_stride;
403
404 if ( scrub || mlt_properties_get_double( properties, "_speed" ) == 1 )
405 {
406 if ( channels == self->out_channels )
407 {
408 memcpy( &self->audio_buffer[ self->audio_avail ], pcm, dst_bytes );
409 pcm += samples_to_copy * channels;
410 }
411 else
412 {
413 int16_t *dest = (int16_t*) &self->audio_buffer[ self->audio_avail ];
414 int i = samples_to_copy + 1;
415 while ( --i )
416 {
417 memcpy( dest, pcm, dst_stride );
418 pcm += channels;
419 dest += self->out_channels;
420 }
421 }
422 }
423 else
424 {
425 memset( &self->audio_buffer[ self->audio_avail ], 0, dst_bytes );
426 pcm += samples_to_copy * channels;
427 }
428 self->audio_avail += dst_bytes;
429 samples_copied += samples_to_copy;
430 }
431 pthread_cond_broadcast( &self->audio_cond );
432 }
433 pthread_mutex_unlock( &self->audio_mutex );
434 }
435 else
436 {
437 self->playing = 1;
438 }
439
440 return init_audio;
441 }
442
consumer_play_video(consumer_sdl self,mlt_frame frame)443 static int consumer_play_video( consumer_sdl self, mlt_frame frame )
444 {
445 // Get the properties of this consumer
446 mlt_properties properties = self->properties;
447 mlt_events_fire( properties, "consumer-frame-show", frame, NULL );
448 return 0;
449 }
450
video_thread(void * arg)451 static void *video_thread( void *arg )
452 {
453 // Identify the arg
454 consumer_sdl self = arg;
455
456 // Obtain time of thread start
457 struct timeval now;
458 int64_t start = 0;
459 int64_t elapsed = 0;
460 struct timespec tm;
461 mlt_frame next = NULL;
462 mlt_properties properties = NULL;
463 double speed = 0;
464
465 // Get real time flag
466 int real_time = mlt_properties_get_int( self->properties, "real_time" );
467
468 // Get the current time
469 gettimeofday( &now, NULL );
470
471 // Determine start time
472 start = ( int64_t )now.tv_sec * 1000000 + now.tv_usec;
473
474 while ( self->running )
475 {
476 // Pop the next frame
477 pthread_mutex_lock( &self->video_mutex );
478 next = mlt_deque_pop_front( self->queue );
479 while ( next == NULL && self->running )
480 {
481 pthread_cond_wait( &self->video_cond, &self->video_mutex );
482 next = mlt_deque_pop_front( self->queue );
483 }
484 pthread_mutex_unlock( &self->video_mutex );
485
486 if ( !self->running || next == NULL ) break;
487
488 // Get the properties
489 properties = MLT_FRAME_PROPERTIES( next );
490
491 // Get the speed of the frame
492 speed = mlt_properties_get_double( properties, "_speed" );
493
494 // Get the current time
495 gettimeofday( &now, NULL );
496
497 // Get the elapsed time
498 elapsed = ( ( int64_t )now.tv_sec * 1000000 + now.tv_usec ) - start;
499
500 // See if we have to delay the display of the current frame
501 if ( mlt_properties_get_int( properties, "rendered" ) == 1 )
502 {
503 // Obtain the scheduled playout time in microseconds
504 int64_t scheduled = mlt_properties_get_int64( properties, "playtime" );
505
506 // Determine the difference between the elapsed time and the scheduled playout time
507 int64_t difference = scheduled - elapsed;
508
509 // Smooth playback a bit
510 if ( real_time && ( difference > 20000 && speed == 1.0 ) )
511 {
512 tm.tv_sec = difference / 1000000;
513 tm.tv_nsec = ( difference % 1000000 ) * 1000;
514 nanosleep( &tm, NULL );
515 }
516
517 // Show current frame if not too old
518 if ( !real_time || ( difference > -10000 || speed != 1.0 || mlt_deque_count( self->queue ) < 2 ) )
519 consumer_play_video( self, next );
520
521 // If the queue is empty, recalculate start to allow build up again
522 if ( real_time && ( mlt_deque_count( self->queue ) == 0 && speed == 1.0 ) )
523 {
524 gettimeofday( &now, NULL );
525 start = ( ( int64_t )now.tv_sec * 1000000 + now.tv_usec ) - scheduled + 20000;
526 start += mlt_properties_get_int(self->properties, "video_delay") * 1000;
527 }
528 }
529
530 // This frame can now be closed
531 mlt_frame_close( next );
532 next = NULL;
533 }
534
535 // This consumer is stopping. But audio has already been played for all
536 // the frames in the queue. Spit out all the frames so that the display has
537 // the option to catch up with the audio.
538 if ( next != NULL ) {
539 consumer_play_video( self, next );
540 mlt_frame_close( next );
541 next = NULL;
542 }
543 while ( mlt_deque_count( self->queue ) > 0 ) {
544 next = mlt_deque_pop_front( self->queue );
545 consumer_play_video( self, next );
546 mlt_frame_close( next );
547 next = NULL;
548 }
549
550 mlt_consumer_stopped( &self->parent );
551
552 return NULL;
553 }
554
555 /** Threaded wrapper for pipe.
556 */
557
consumer_thread(void * arg)558 static void *consumer_thread( void *arg )
559 {
560 // Identify the arg
561 consumer_sdl self = arg;
562
563 // Get the consumer
564 mlt_consumer consumer = &self->parent;
565
566 // Get the properties
567 mlt_properties consumer_props = MLT_CONSUMER_PROPERTIES( consumer );
568
569 // Video thread
570 pthread_t thread;
571
572 // internal initialization
573 int init_audio = 1;
574 int init_video = 1;
575 mlt_frame frame = NULL;
576 mlt_properties properties = NULL;
577 int64_t duration = 0;
578 int64_t playtime = mlt_properties_get_int(consumer_props, "video_delay") * 1000;
579 struct timespec tm = { 0, 100000 };
580 // int last_position = -1;
581
582 pthread_mutex_lock( &self->refresh_mutex );
583 self->refresh_count = 0;
584 pthread_mutex_unlock( &self->refresh_mutex );
585
586 // Loop until told not to
587 while( self->running )
588 {
589 // Get a frame from the attached producer
590 frame = mlt_consumer_rt_frame( consumer );
591
592 // Ensure that we have a frame
593 if ( frame )
594 {
595 // Get the frame properties
596 properties = MLT_FRAME_PROPERTIES( frame );
597
598 // Get the speed of the frame
599 double speed = mlt_properties_get_double( properties, "_speed" );
600
601 // Clear refresh
602 mlt_events_block( consumer_props, consumer_props );
603 mlt_properties_set_int( consumer_props, "refresh", 0 );
604 mlt_events_unblock( consumer_props, consumer_props );
605
606 // Play audio
607 init_audio = consumer_play_audio( self, frame, init_audio, &duration );
608
609 // Determine the start time now
610 if ( self->playing && init_video )
611 {
612 // Create the video thread
613 pthread_create( &thread, NULL, video_thread, self );
614
615 // Video doesn't need to be initialised any more
616 init_video = 0;
617 }
618
619 // Set playtime for this frame in microseconds
620 mlt_properties_set_int64( properties, "playtime", playtime );
621
622 while ( self->running && speed != 0 && mlt_deque_count( self->queue ) > 15 )
623 nanosleep( &tm, NULL );
624
625 // Push this frame to the back of the queue
626 if ( self->running && speed )
627 {
628 pthread_mutex_lock( &self->video_mutex );
629 if ( self->is_purge && speed == 1.0 )
630 {
631 mlt_frame_close( frame );
632 frame = NULL;
633 self->is_purge = 0;
634 }
635 else
636 {
637 mlt_deque_push_back( self->queue, frame );
638 pthread_cond_broadcast( &self->video_cond );
639 }
640 pthread_mutex_unlock( &self->video_mutex );
641
642 // Calculate the next playtime
643 playtime += duration;
644 }
645 else if ( self->running )
646 {
647 pthread_mutex_lock( &self->refresh_mutex );
648 consumer_play_video( self, frame );
649 mlt_frame_close( frame );
650 frame = NULL;
651 self->refresh_count --;
652 if ( self->refresh_count <= 0 )
653 {
654 pthread_cond_wait( &self->refresh_cond, &self->refresh_mutex );
655 }
656 pthread_mutex_unlock( &self->refresh_mutex );
657 }
658
659 // Optimisation to reduce latency
660 if ( speed == 1.0 )
661 {
662 // TODO: disabled due to misbehavior on parallel-consumer
663 // if ( last_position != -1 && last_position + 1 != mlt_frame_get_position( frame ) )
664 // mlt_consumer_purge( consumer );
665 // last_position = mlt_frame_get_position( frame );
666 }
667 else if (speed == 0.0)
668 {
669 mlt_consumer_purge( consumer );
670 }
671 }
672 }
673
674 // Kill the video thread
675 if ( init_video == 0 )
676 {
677 pthread_mutex_lock( &self->video_mutex );
678 pthread_cond_broadcast( &self->video_cond );
679 pthread_mutex_unlock( &self->video_mutex );
680 pthread_join( thread, NULL );
681 }
682
683 if ( frame )
684 {
685 // The video thread has cleared out the queue. But the audio was played
686 // for this frame. So play the video before stopping so the display has
687 // the option to catch up with the audio.
688 consumer_play_video( self, frame );
689 mlt_frame_close( frame );
690 frame = NULL;
691 }
692
693 pthread_mutex_lock( &self->audio_mutex );
694 self->audio_avail = 0;
695 pthread_mutex_unlock( &self->audio_mutex );
696
697 return NULL;
698 }
699
700 /** Callback to allow override of the close method.
701 */
702
consumer_close(mlt_consumer parent)703 static void consumer_close( mlt_consumer parent )
704 {
705 // Get the actual object
706 consumer_sdl self = parent->child;
707
708 // Stop the consumer
709 mlt_consumer_stop( parent );
710
711 // Now clean up the rest
712 mlt_consumer_close( parent );
713
714 // Close the queue
715 mlt_deque_close( self->queue );
716
717 // Destroy mutexes
718 pthread_mutex_destroy( &self->audio_mutex );
719 pthread_cond_destroy( &self->audio_cond );
720 pthread_mutex_destroy( &self->video_mutex );
721 pthread_cond_destroy( &self->video_cond );
722 pthread_mutex_destroy( &self->refresh_mutex );
723 pthread_cond_destroy( &self->refresh_cond );
724
725 // Finally clean up this
726 free( self );
727 }
728