]> git.sesse.net Git - mlt/blob - src/modules/rtaudio/consumer_rtaudio.cpp
Fix consumer reconnection with rtaudio
[mlt] / src / modules / rtaudio / consumer_rtaudio.cpp
1 /*
2  * consumer_rtaudio.c -- output through RtAudio audio wrapper
3  * Copyright (C) 2011 Dan Dennedy <dan@dennedy.org>
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 consumer library; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  */
19
20 #include <framework/mlt.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <pthread.h>
24 #include <sys/time.h>
25 #include "RtAudio.h"
26
27 static void consumer_refresh_cb( mlt_consumer sdl, mlt_consumer consumer, char *name );
28 static int  rtaudio_callback( void *outputBuffer, void *inputBuffer,
29         unsigned int nFrames, double streamTime, RtAudioStreamStatus status, void *userData );
30 static void *consumer_thread_proxy( void *arg );
31 static void *video_thread_proxy( void *arg );
32
33 class RtAudioConsumer
34 {
35 public:
36         struct mlt_consumer_s consumer;
37         RtAudio               rt;
38         int                   device_id;
39         mlt_deque             queue;
40         pthread_t             thread;
41         int                   joined;
42         int                   running;
43         uint8_t               audio_buffer[4096 * 10];
44         int                   audio_avail;
45         pthread_mutex_t       audio_mutex;
46         pthread_cond_t        audio_cond;
47         pthread_mutex_t       video_mutex;
48         pthread_cond_t        video_cond;
49         int                   playing;
50         pthread_cond_t        refresh_cond;
51         pthread_mutex_t       refresh_mutex;
52         int                   refresh_count;
53         bool                  is_purge;
54
55         mlt_consumer getConsumer()
56                 { return &consumer; }
57
58         RtAudioConsumer()
59                 : device_id(-1)
60                 , queue(NULL)
61                 , joined(0)
62                 , running(0)
63                 , audio_avail(0)
64                 , playing(0)
65                 , refresh_count(0)
66                 , is_purge(false)
67         {
68                 memset( &consumer, 0, sizeof( consumer ) );
69         }
70
71         ~RtAudioConsumer()
72         {
73                 // Close the queue
74                 mlt_deque_close( queue );
75
76                 // Destroy mutexes
77                 pthread_mutex_destroy( &audio_mutex );
78                 pthread_cond_destroy( &audio_cond );
79                 pthread_mutex_destroy( &video_mutex );
80                 pthread_cond_destroy( &video_cond );
81                 pthread_mutex_destroy( &refresh_mutex );
82                 pthread_cond_destroy( &refresh_cond );
83
84                 if ( rt.isStreamOpen() )
85                         rt.closeStream();
86         }
87
88         bool open( const char* arg )
89         {
90                 if ( rt.getDeviceCount() < 1 )
91                 {
92                         mlt_log_warning( getConsumer(), "no audio devices found\n" );
93                         return false;
94                 }
95
96 #ifndef __LINUX_ALSA__
97                 device_id = rt.getDefaultOutputDevice();
98 #endif
99                 if ( arg && strcmp( arg, "" ) && strcmp( arg, "default" ) )
100                 {
101                         // Get device ID by name
102                         unsigned int n = rt.getDeviceCount();
103                         RtAudio::DeviceInfo info;
104                         unsigned int i;
105
106                         for ( i = 0; i < n; i++ )
107                         {
108                                 info = rt.getDeviceInfo( i );
109                                 mlt_log_verbose( NULL, "RtAudio device %d = %s\n",
110                                         i, info.name.c_str() );
111                                 if ( info.probed && info.name == arg )
112                                 {
113                                         device_id = i;
114                                         break;
115                                 }
116                         }
117                         // Name selection failed, try arg as numeric
118                         if ( i == n )
119                                 device_id = (int) strtol( arg, NULL, 0 );
120                 }
121
122                 // Create the queue
123                 queue = mlt_deque_init( );
124
125                 // get a handle on properties
126                 mlt_properties properties = MLT_CONSUMER_PROPERTIES( &consumer );
127
128                 // Set the default volume
129                 mlt_properties_set_double( properties, "volume", 1.0 );
130
131                 // This is the initialisation of the consumer
132                 pthread_mutex_init( &audio_mutex, NULL );
133                 pthread_cond_init( &audio_cond, NULL);
134                 pthread_mutex_init( &video_mutex, NULL );
135                 pthread_cond_init( &video_cond, NULL);
136
137                 // Default scaler (for now we'll use nearest)
138                 mlt_properties_set( properties, "rescale", "nearest" );
139                 mlt_properties_set( properties, "deinterlace_method", "onefield" );
140
141                 // Default buffer for low latency
142                 mlt_properties_set_int( properties, "buffer", 1 );
143
144                 // Default audio buffer
145                 mlt_properties_set_int( properties, "audio_buffer", 1024 );
146
147                 // Set the resource to the device name arg
148                 mlt_properties_set( properties, "resource", arg );
149
150                 // Ensure we don't join on a non-running object
151                 joined = 1;
152
153                 // Initialize the refresh handler
154                 pthread_cond_init( &refresh_cond, NULL );
155                 pthread_mutex_init( &refresh_mutex, NULL );
156                 mlt_events_listen( properties, this, "property-changed", ( mlt_listener )consumer_refresh_cb );
157
158                 return true;
159         }
160
161         int start()
162         {
163                 if ( !running )
164                 {
165                         stop();
166                         running = 1;
167                         joined = 0;
168                         pthread_create( &thread, NULL, consumer_thread_proxy, this );
169                 }
170
171                 return 0;
172         }
173
174         int stop()
175         {
176                 if ( running && !joined )
177                 {
178                         // Kill the thread and clean up
179                         joined = 1;
180                         running = 0;
181
182                         // Unlatch the consumer thread
183                         pthread_mutex_lock( &refresh_mutex );
184                         pthread_cond_broadcast( &refresh_cond );
185                         pthread_mutex_unlock( &refresh_mutex );
186
187                         // Cleanup the main thread
188                         pthread_join( thread, NULL );
189
190                         // Unlatch the video thread
191                         pthread_mutex_lock( &video_mutex );
192                         pthread_cond_broadcast( &video_cond );
193                         pthread_mutex_unlock( &video_mutex );
194
195                         // Unlatch the audio callback
196                         pthread_mutex_lock( &audio_mutex );
197                         pthread_cond_broadcast( &audio_cond );
198                         pthread_mutex_unlock( &audio_mutex );
199
200                         if ( rt.isStreamOpen() )
201                         try {
202                                 // Stop the stream
203                                 rt.stopStream();
204                         }
205                         catch ( RtError& e ) {
206                                 mlt_log_error( getConsumer(), "%s\n", e.getMessage().c_str() );
207                         }
208                 }
209
210                 return 0;
211         }
212
213         void purge()
214         {
215                 if ( running )
216                 {
217                         pthread_mutex_lock( &video_mutex );
218                         mlt_frame frame = MLT_FRAME( mlt_deque_peek_back( queue ) );
219                         // When playing rewind or fast forward then we need to keep one
220                         // frame in the queue to prevent playback stalling.
221                         double speed = frame? mlt_properties_get_double( MLT_FRAME_PROPERTIES(frame), "_speed" ) : 0;
222                         int n = ( speed == 0.0 || speed == 1.0 ) ? 0 : 1;
223                         while ( mlt_deque_count( queue ) > n )
224                                 mlt_frame_close( MLT_FRAME( mlt_deque_pop_back( queue ) ) );
225                         is_purge = true;
226                         pthread_cond_broadcast( &video_cond );
227                         pthread_mutex_unlock( &video_mutex );
228                 }
229         }
230
231         void consumer_thread()
232         {
233                 // Get the properties
234                 mlt_properties consumer_props = MLT_CONSUMER_PROPERTIES( getConsumer() );
235
236                 // Video thread
237                 pthread_t thread;
238
239                 // internal intialization
240                 int init_audio = 1;
241                 int init_video = 1;
242                 mlt_frame frame = NULL;
243                 mlt_properties properties = NULL;
244                 int duration = 0;
245                 int64_t playtime = 0;
246                 struct timespec tm = { 0, 100000 };
247         //      int last_position = -1;
248
249                 pthread_mutex_lock( &refresh_mutex );
250                 refresh_count = 0;
251                 pthread_mutex_unlock( &refresh_mutex );
252
253                 // Loop until told not to
254                 while ( running )
255                 {
256                         // Get a frame from the attached producer
257                         frame = mlt_consumer_rt_frame( getConsumer() );
258
259                         // Ensure that we have a frame
260                         if ( frame )
261                         {
262                                 // Get the frame properties
263                                 properties =  MLT_FRAME_PROPERTIES( frame );
264
265                                 // Get the speed of the frame
266                                 double speed = mlt_properties_get_double( properties, "_speed" );
267
268                                 // Get refresh request for the current frame
269                                 int refresh = mlt_properties_get_int( consumer_props, "refresh" );
270
271                                 // Clear refresh
272                                 mlt_events_block( consumer_props, consumer_props );
273                                 mlt_properties_set_int( consumer_props, "refresh", 0 );
274                                 mlt_events_unblock( consumer_props, consumer_props );
275
276                                 // Play audio
277                                 init_audio = play_audio( frame, init_audio, &duration );
278
279                                 // Determine the start time now
280                                 if ( playing && init_video )
281                                 {
282                                         // Create the video thread
283                                         pthread_create( &thread, NULL, video_thread_proxy, this );
284
285                                         // Video doesn't need to be initialised any more
286                                         init_video = 0;
287                                 }
288
289                                 // Set playtime for this frame
290                                 mlt_properties_set_int( properties, "playtime", playtime );
291
292                                 while ( running && speed != 0 && mlt_deque_count( queue ) > 15 )
293                                         nanosleep( &tm, NULL );
294
295                                 // Push this frame to the back of the queue
296                                 if ( running && speed )
297                                 {
298                                         pthread_mutex_lock( &video_mutex );
299                                         if ( is_purge && speed == 1.0 )
300                                         {
301                                                 mlt_frame_close( frame );
302                                                 is_purge = false;
303                                         }
304                                         else
305                                         {
306                                                 mlt_deque_push_back( queue, frame );
307                                                 pthread_cond_broadcast( &video_cond );
308                                         }
309                                         pthread_mutex_unlock( &video_mutex );
310
311                                         // Calculate the next playtime
312                                         playtime += ( duration * 1000 );
313                                 }
314                                 else if ( running )
315                                 {
316                                         pthread_mutex_lock( &refresh_mutex );
317                                         if ( refresh == 0 && refresh_count <= 0 )
318                                         {
319                                                 play_video( frame );
320                                                 pthread_cond_wait( &refresh_cond, &refresh_mutex );
321                                         }
322                                         mlt_frame_close( frame );
323                                         refresh_count --;
324                                         pthread_mutex_unlock( &refresh_mutex );
325                                 }
326                                 else
327                                 {
328                                         mlt_frame_close( frame );
329                                         frame = NULL;
330                                 }
331
332                                 // Optimisation to reduce latency
333                                 if ( frame && speed == 1.0 )
334                                 {
335                                         // TODO: disabled due to misbehavior on parallel-consumer
336         //                              if ( last_position != -1 && last_position + 1 != mlt_frame_get_position( frame ) )
337         //                                      mlt_consumer_purge( consumer );
338         //                              last_position = mlt_frame_get_position( frame );
339                                 }
340                                 else
341                                 {
342                                         mlt_consumer_purge( getConsumer() );
343         //                              last_position = -1;
344                                 }
345                         }
346                 }
347
348                 // Kill the video thread
349                 if ( init_video == 0 )
350                 {
351                         pthread_mutex_lock( &video_mutex );
352                         pthread_cond_broadcast( &video_cond );
353                         pthread_mutex_unlock( &video_mutex );
354                         pthread_join( thread, NULL );
355                 }
356
357                 while( mlt_deque_count( queue ) )
358                         mlt_frame_close( (mlt_frame) mlt_deque_pop_back( queue ) );
359
360                 audio_avail = 0;
361         }
362
363         int callback( int16_t *outbuf, int16_t *inbuf,
364                 unsigned int samples, double streamTime, RtAudioStreamStatus status )
365         {
366                 mlt_properties properties = MLT_CONSUMER_PROPERTIES( getConsumer() );
367                 double volume = mlt_properties_get_double( properties, "volume" );
368                 int channels = mlt_properties_get_int( properties, "channels" );
369                 int len = mlt_audio_format_size( mlt_audio_s16, samples, channels );
370
371                 pthread_mutex_lock( &audio_mutex );
372
373                 // Block until audio received
374                 while ( running && len > audio_avail )
375                         pthread_cond_wait( &audio_cond, &audio_mutex );
376
377                 if ( audio_avail >= len )
378                 {
379                         // Place in the audio buffer
380                         memcpy( outbuf, audio_buffer, len );
381
382                         // Remove len from the audio available
383                         audio_avail -= len;
384
385                         // Remove the samples
386                         memmove( audio_buffer, audio_buffer + len, audio_avail );
387                 }
388                 else
389                 {
390                         // Just to be safe, wipe the stream first
391                         memset( outbuf, 0, len );
392
393                         // Copy what we have
394                         memcpy( outbuf, audio_buffer, audio_avail );
395
396                         // No audio left
397                         audio_avail = 0;
398                 }
399
400                 if ( volume != 1.0 )
401                 {
402                         int16_t *p = outbuf;
403                         int i = samples * channels + 1;
404                         while ( --i )
405                                 *p++ *= volume;
406                 }
407
408                 // We're definitely playing now
409                 playing = 1;
410
411                 pthread_cond_broadcast( &audio_cond );
412                 pthread_mutex_unlock( &audio_mutex );
413
414                 return 0;
415         }
416
417         int play_audio( mlt_frame frame, int init_audio, int *duration )
418         {
419                 // Get the properties of this consumer
420                 mlt_properties properties = MLT_CONSUMER_PROPERTIES( getConsumer() );
421                 mlt_audio_format afmt = mlt_audio_s16;
422
423                 // Set the preferred params of the test card signal
424                 int channels = mlt_properties_get_int( properties, "channels" );
425                 int frequency = mlt_properties_get_int( properties, "frequency" );
426                 int scrub = mlt_properties_get_int( properties, "scrub_audio" );
427                 static int counter = 0;
428                 int samples = mlt_sample_calculator( mlt_properties_get_double( properties, "fps" ), frequency, counter++ );
429                 int16_t *pcm;
430
431                 mlt_frame_get_audio( frame, (void**) &pcm, &afmt, &frequency, &channels, &samples );
432                 *duration = ( ( samples * 1000 ) / frequency );
433
434                 if ( mlt_properties_get_int( properties, "audio_off" ) )
435                 {
436                         playing = 1;
437                         return init_audio;
438                 }
439
440                 if ( init_audio == 1 )
441                 {
442                         RtAudio::StreamParameters parameters;
443                         parameters.deviceId = device_id;
444                         parameters.nChannels = channels;
445                         parameters.firstChannel = 0;
446                         RtAudio::StreamOptions options;
447                         unsigned int bufferFrames = mlt_properties_get_int( properties, "audio_buffer" );
448
449                         if ( device_id == -1 )
450                         {
451                                 options.flags = RTAUDIO_ALSA_USE_DEFAULT;
452                                 parameters.deviceId = 0;
453                         }
454                         if ( mlt_properties_get( properties, "resource" ) )
455                                 parameters.deviceName = mlt_properties_get( properties, "resource" );
456
457                         try {
458                                 if ( rt.isStreamOpen() ) {
459                                     rt.closeStream();
460                                 }
461                                 rt.openStream( &parameters, NULL, RTAUDIO_SINT16,
462                                         frequency, &bufferFrames, &rtaudio_callback, this, &options );
463                                 rt.startStream();
464                                 init_audio = 0;
465                                 playing = 1;
466                         }
467                         catch ( RtError& e ) {
468                                 mlt_log_error( getConsumer(), "%s\n", e.getMessage().c_str() );
469                                 init_audio = 2;
470                         }
471                 }
472
473                 if ( init_audio == 0 )
474                 {
475                         mlt_properties properties = MLT_FRAME_PROPERTIES( frame );
476                         size_t bytes = ( samples * channels * 2 );
477                         pthread_mutex_lock( &audio_mutex );
478                         while ( running && bytes > ( sizeof( audio_buffer) - audio_avail ) )
479                                 pthread_cond_wait( &audio_cond, &audio_mutex );
480                         if ( running )
481                         {
482                                 if ( scrub || mlt_properties_get_double( properties, "_speed" ) == 1 )
483                                         memcpy( &audio_buffer[ audio_avail ], pcm, bytes );
484                                 else
485                                         memset( &audio_buffer[ audio_avail ], 0, bytes );
486                                 audio_avail += bytes;
487                         }
488                         pthread_cond_broadcast( &audio_cond );
489                         pthread_mutex_unlock( &audio_mutex );
490                 }
491
492                 return init_audio;
493         }
494
495         int play_video( mlt_frame frame )
496         {
497                 // Get the properties of this consumer
498                 mlt_properties properties = MLT_CONSUMER_PROPERTIES( getConsumer() );
499                 if ( running && !mlt_consumer_is_stopped( getConsumer() ) )
500                         mlt_events_fire( properties, "consumer-frame-show", frame, NULL );
501
502                 return 0;
503         }
504
505         void video_thread()
506         {
507                 // Obtain time of thread start
508                 struct timeval now;
509                 int64_t start = 0;
510                 int64_t elapsed = 0;
511                 struct timespec tm;
512                 mlt_frame next = NULL;
513                 mlt_properties properties = MLT_CONSUMER_PROPERTIES( getConsumer() );
514                 double speed = 0;
515
516                 // Get real time flag
517                 int real_time = mlt_properties_get_int( properties, "real_time" );
518
519                 // Get the current time
520                 gettimeofday( &now, NULL );
521
522                 // Determine start time
523                 start = ( int64_t )now.tv_sec * 1000000 + now.tv_usec;
524
525                 while ( running )
526                 {
527                         // Pop the next frame
528                         pthread_mutex_lock( &video_mutex );
529                         next = (mlt_frame) mlt_deque_pop_front( queue );
530                         while ( next == NULL && running )
531                         {
532                                 pthread_cond_wait( &video_cond, &video_mutex );
533                                 next = (mlt_frame) mlt_deque_pop_front( queue );
534                         }
535                         pthread_mutex_unlock( &video_mutex );
536
537                         if ( !running || next == NULL ) break;
538
539                         // Get the properties
540                         properties =  MLT_FRAME_PROPERTIES( next );
541
542                         // Get the speed of the frame
543                         speed = mlt_properties_get_double( properties, "_speed" );
544
545                         // Get the current time
546                         gettimeofday( &now, NULL );
547
548                         // Get the elapsed time
549                         elapsed = ( ( int64_t )now.tv_sec * 1000000 + now.tv_usec ) - start;
550
551                         // See if we have to delay the display of the current frame
552                         if ( mlt_properties_get_int( properties, "rendered" ) == 1 && running )
553                         {
554                                 // Obtain the scheduled playout time
555                                 int64_t scheduled = mlt_properties_get_int( properties, "playtime" );
556
557                                 // Determine the difference between the elapsed time and the scheduled playout time
558                                 int64_t difference = scheduled - elapsed;
559
560                                 // Smooth playback a bit
561                                 if ( real_time && ( difference > 20000 && speed == 1.0 ) )
562                                 {
563                                         tm.tv_sec = difference / 1000000;
564                                         tm.tv_nsec = ( difference % 1000000 ) * 500;
565                                         nanosleep( &tm, NULL );
566                                 }
567
568                                 // Show current frame if not too old
569                                 if ( !real_time || ( difference > -10000 || speed != 1.0 || mlt_deque_count( queue ) < 2 ) )
570                                         play_video( next );
571
572                                 // If the queue is empty, recalculate start to allow build up again
573                                 if ( real_time && ( mlt_deque_count( queue ) == 0 && speed == 1.0 ) )
574                                 {
575                                         gettimeofday( &now, NULL );
576                                         start = ( ( int64_t )now.tv_sec * 1000000 + now.tv_usec ) - scheduled + 20000;
577                                 }
578                         }
579
580                         // This frame can now be closed
581                         mlt_frame_close( next );
582                         next = NULL;
583                 }
584
585                 if ( next != NULL )
586                         mlt_frame_close( next );
587
588                 mlt_consumer_stopped( getConsumer() );
589         }
590
591 };
592
593 static void consumer_refresh_cb( mlt_consumer sdl, mlt_consumer consumer, char *name )
594 {
595         if ( !strcmp( name, "refresh" ) )
596         {
597                 RtAudioConsumer* rtaudio = (RtAudioConsumer*) consumer->child;
598                 pthread_mutex_lock( &rtaudio->refresh_mutex );
599                 rtaudio->refresh_count = rtaudio->refresh_count <= 0 ? 1 : rtaudio->refresh_count + 1;
600                 pthread_cond_broadcast( &rtaudio->refresh_cond );
601                 pthread_mutex_unlock( &rtaudio->refresh_mutex );
602         }
603 }
604
605 static int rtaudio_callback( void *outputBuffer, void *inputBuffer,
606         unsigned int nFrames, double streamTime, RtAudioStreamStatus status, void *userData )
607 {
608         RtAudioConsumer* rtaudio = (RtAudioConsumer*) userData;
609         return rtaudio->callback( (int16_t*) outputBuffer, (int16_t*) inputBuffer, nFrames, streamTime, status );
610 }
611
612 static void *consumer_thread_proxy( void *arg )
613 {
614         RtAudioConsumer* rtaudio = (RtAudioConsumer*) arg;
615         rtaudio->consumer_thread();
616         return NULL;
617 }
618
619 static void *video_thread_proxy( void *arg )
620 {
621         RtAudioConsumer* rtaudio = (RtAudioConsumer*) arg;
622         rtaudio->video_thread();
623         return NULL;
624 }
625
626 /** Start the consumer.
627  */
628
629 static int start( mlt_consumer consumer )
630 {
631         RtAudioConsumer* rtaudio = (RtAudioConsumer*) consumer->child;
632         return rtaudio->start();
633 }
634
635 /** Stop the consumer.
636  */
637
638 static int stop( mlt_consumer consumer )
639 {
640         RtAudioConsumer* rtaudio = (RtAudioConsumer*) consumer->child;
641         return rtaudio->stop();
642 }
643
644 /** Determine if the consumer is stopped.
645  */
646
647 static int is_stopped( mlt_consumer consumer )
648 {
649         RtAudioConsumer* rtaudio = (RtAudioConsumer*) consumer->child;
650         return !rtaudio->running;
651 }
652
653 static void purge( mlt_consumer consumer )
654 {
655         RtAudioConsumer* rtaudio = (RtAudioConsumer*) consumer->child;
656         rtaudio->purge();
657 }
658
659 /** Close the consumer.
660  */
661
662 static void close( mlt_consumer consumer )
663 {
664         // Stop the consumer
665         mlt_consumer_stop( consumer );
666
667         // Close the parent
668         consumer->close = NULL;
669         mlt_consumer_close( consumer );
670
671         // Free the memory
672         delete (RtAudioConsumer*) consumer->child;
673 }
674
675 extern "C" {
676
677 mlt_consumer consumer_rtaudio_init( mlt_profile profile, mlt_service_type type, const char *id, char *arg )
678 {
679         // Allocate the consumer
680         RtAudioConsumer* rtaudio = new RtAudioConsumer();
681         mlt_consumer consumer = NULL;
682
683         // If allocated
684         if ( rtaudio && !mlt_consumer_init( rtaudio->getConsumer(), rtaudio, profile ) )
685         {
686                 // If initialises without error
687                 if ( rtaudio->open( arg? arg : getenv( "AUDIODEV" ) ) )
688                 {
689                         // Setup callbacks
690                         consumer = rtaudio->getConsumer();
691                         consumer->close = close;
692                         consumer->start = start;
693                         consumer->stop = stop;
694                         consumer->is_stopped = is_stopped;
695                         consumer->purge = purge;
696                 }
697                 else
698                 {
699                         mlt_consumer_close( rtaudio->getConsumer() );
700                         delete rtaudio;
701                 }
702         }
703
704         // Return consumer
705         return consumer;
706 }
707
708 static mlt_properties metadata( mlt_service_type type, const char *id, void *data )
709 {
710         char file[ PATH_MAX ];
711         const char *service_type = "consumer";
712         snprintf( file, PATH_MAX, "%s/rtaudio/%s_%s.yml", mlt_environment( "MLT_DATA" ), service_type, id );
713         return mlt_properties_parse_yaml( file );
714 }
715
716 MLT_REPOSITORY
717 {
718         MLT_REGISTER( consumer_type, "rtaudio", consumer_rtaudio_init );
719         MLT_REGISTER_METADATA( consumer_type, "rtaudio", metadata, NULL );
720 }
721
722 } // extern C