]> git.sesse.net Git - mlt/blob - src/modules/decklink/consumer_decklink.cpp
Fix building decklink on win32
[mlt] / src / modules / decklink / consumer_decklink.cpp
1 /*
2  * consumer_decklink.c -- output through Blackmagic Design DeckLink
3  * Copyright (C) 2010 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 #define __STDC_FORMAT_MACROS  /* see inttypes.h */
21 #include <framework/mlt.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <pthread.h>
25 #include <unistd.h>
26 #include <sys/time.h>
27 #include <limits.h>
28 #ifdef WIN32
29 #include <objbase.h>
30 #include "DeckLinkAPI_h.h"
31 #else
32 #include "DeckLinkAPI.h"
33 #endif
34
35 static const unsigned PREROLL_MINIMUM = 3;
36
37 class DeckLinkConsumer
38         : public IDeckLinkVideoOutputCallback
39 {
40 private:
41         mlt_consumer_s              m_consumer;
42         IDeckLink*                  m_deckLink;
43         IDeckLinkOutput*            m_deckLinkOutput;
44         IDeckLinkDisplayMode*       m_displayMode;
45         int                         m_width;
46         int                         m_height;
47         BMDTimeValue                m_duration;
48         BMDTimeScale                m_timescale;
49         double                      m_fps;
50         uint64_t                    m_count;
51         int                         m_channels;
52         unsigned                    m_dropped;
53         IDeckLinkMutableVideoFrame* m_decklinkFrame;
54         bool                        m_isAudio;
55         int                         m_isKeyer;
56         IDeckLinkKeyer*             m_deckLinkKeyer;
57         bool                        m_terminate_on_pause;
58         uint32_t                    m_preroll;
59         uint32_t                    m_acnt;
60         bool                        m_reprio;
61
62         IDeckLinkDisplayMode* getDisplayMode()
63         {
64                 mlt_profile profile = mlt_service_profile( MLT_CONSUMER_SERVICE( getConsumer() ) );
65                 IDeckLinkDisplayModeIterator* iter;
66                 IDeckLinkDisplayMode* mode;
67                 IDeckLinkDisplayMode* result = 0;
68                 
69                 if ( m_deckLinkOutput->GetDisplayModeIterator( &iter ) == S_OK )
70                 {
71                         while ( !result && iter->Next( &mode ) == S_OK )
72                         {
73                                 m_width = mode->GetWidth();
74                                 m_height = mode->GetHeight();
75                                 mode->GetFrameRate( &m_duration, &m_timescale );
76                                 m_fps = (double) m_timescale / m_duration;
77                                 int p = mode->GetFieldDominance() == bmdProgressiveFrame;
78                                 mlt_log_verbose( getConsumer(), "BMD mode %dx%d %.3f fps prog %d\n", m_width, m_height, m_fps, p );
79                                 
80                                 if ( m_width == profile->width && m_height == profile->height && p == profile->progressive
81                                          && m_fps == mlt_profile_fps( profile ) )
82                                         result = mode;
83                         }
84                 }
85                 
86                 return result;
87         }
88         
89 public:
90         mlt_consumer getConsumer()
91                 { return &m_consumer; }
92         
93         ~DeckLinkConsumer()
94         {
95                 if ( m_deckLinkKeyer )
96                         m_deckLinkKeyer->Release();
97                 if ( m_deckLinkOutput )
98                         m_deckLinkOutput->Release();
99                 if ( m_deckLink )
100                         m_deckLink->Release();
101         }
102         
103         bool open( unsigned card = 0 )
104         {
105                 unsigned i = 0;
106 #ifdef WIN32
107                 IDeckLinkIterator* deckLinkIterator = NULL;
108                 HRESULT result =  CoInitialize( NULL );
109                 if ( FAILED( result ) )
110                 {
111                         mlt_log_error( getConsumer(), "COM initialization failed\n" );
112                         return false;
113                 }
114                 result = CoCreateInstance( CLSID_CDeckLinkIterator, NULL, CLSCTX_ALL, IID_IDeckLinkIterator, (void**) &deckLinkIterator );
115                 if ( FAILED( result ) )
116                 {
117                         mlt_log_error( getConsumer(), "The DeckLink drivers not installed.\n" );
118                         return false;
119                 }
120 #else
121                 IDeckLinkIterator* deckLinkIterator = CreateDeckLinkIteratorInstance();
122                 
123                 if ( !deckLinkIterator )
124                 {
125                         mlt_log_error( getConsumer(), "The DeckLink drivers not installed.\n" );
126                         return false;
127                 }
128 #endif
129                 
130                 // Connect to the Nth DeckLink instance
131                 do {
132                         if ( deckLinkIterator->Next( &m_deckLink ) != S_OK )
133                         {
134                                 mlt_log_error( getConsumer(), "DeckLink card not found\n" );
135                                 deckLinkIterator->Release();
136                                 return false;
137                         }
138                 } while ( ++i <= card );
139                 deckLinkIterator->Release();
140                 
141                 // Obtain the audio/video output interface (IDeckLinkOutput)
142                 if ( m_deckLink->QueryInterface( IID_IDeckLinkOutput, (void**)&m_deckLinkOutput ) != S_OK )
143                 {
144                         mlt_log_error( getConsumer(), "No DeckLink cards support output\n" );
145                         m_deckLink->Release();
146                         m_deckLink = 0;
147                         return false;
148                 }
149                 
150                 // Get the keyer interface
151                 IDeckLinkAttributes *deckLinkAttributes = 0;
152                 m_deckLinkKeyer = 0;
153                 if ( m_deckLink->QueryInterface( IID_IDeckLinkAttributes, (void**) &deckLinkAttributes ) == S_OK )
154                 {
155 #ifdef WIN32
156                         BOOL flag = FALSE;
157 #else
158                         bool flag = false;
159 #endif
160                         if ( deckLinkAttributes->GetFlag( BMDDeckLinkSupportsInternalKeying, &flag ) == S_OK && flag )
161                         {
162                                 if ( m_deckLink->QueryInterface( IID_IDeckLinkKeyer, (void**) &m_deckLinkKeyer ) != S_OK )
163                                 {
164                                         mlt_log_error( getConsumer(), "Failed to get keyer\n" );
165                                         m_deckLinkOutput->Release();
166                                         m_deckLinkOutput = 0;
167                                         m_deckLink->Release();
168                                         m_deckLink = 0;
169                                         return false;
170                                 }
171                         }
172                         deckLinkAttributes->Release();
173                 }
174
175                 // Provide this class as a delegate to the audio and video output interfaces
176                 m_deckLinkOutput->SetScheduledFrameCompletionCallback( this );
177                 
178                 return true;
179         }
180         
181         bool start( unsigned preroll )
182         {
183                 unsigned i;
184                 mlt_properties properties = MLT_CONSUMER_PROPERTIES( getConsumer() );
185
186                 // Initialize members
187                 m_count = 0;
188                 m_dropped = 0;
189                 m_decklinkFrame = NULL;
190                 preroll = preroll < PREROLL_MINIMUM ? PREROLL_MINIMUM : preroll;
191                 m_channels = mlt_properties_get_int( properties, "channels" );
192                 m_isAudio = !mlt_properties_get_int( properties, "audio_off" );
193                 m_terminate_on_pause = mlt_properties_get_int( properties, "terminate_on_pause" );
194
195
196                 m_displayMode = getDisplayMode();
197                 if ( !m_displayMode )
198                 {
199                         mlt_log_error( getConsumer(), "Profile is not compatible with decklink.\n" );
200                         return false;
201                 }
202                 
203                 // Set the keyer
204                 if ( m_deckLinkKeyer && ( m_isKeyer = mlt_properties_get_int( properties, "keyer" ) ) )
205                 {
206                         bool external = ( m_isKeyer == 2 );
207                         double level = mlt_properties_get_double( properties, "keyer_level" );
208
209                         if ( m_deckLinkKeyer->Enable( external ) != S_OK )
210                                 mlt_log_error( getConsumer(), "Failed to enable %s keyer\n",
211                                         external ? "external" : "internal" );
212                         m_deckLinkKeyer->SetLevel( level <= 1 ? ( level > 0 ? 255 * level : 255 ) : 255 );
213                 }
214                 else if ( m_deckLinkKeyer )
215                 {
216                         m_deckLinkKeyer->Disable();
217                 }
218
219                 // Set the video output mode
220                 if ( S_OK != m_deckLinkOutput->EnableVideoOutput( m_displayMode->GetDisplayMode(), bmdVideoOutputFlagDefault ) )
221                 {
222                         mlt_log_error( getConsumer(), "Failed to enable video output\n" );
223                         return false;
224                 }
225
226                 // Set the audio output mode
227                 if ( !m_isAudio )
228                 {
229                         m_deckLinkOutput->DisableAudioOutput();
230                         return true;
231                 }
232                 if ( S_OK != m_deckLinkOutput->EnableAudioOutput( bmdAudioSampleRate48kHz, bmdAudioSampleType16bitInteger,
233                         m_channels, bmdAudioOutputStreamTimestamped ) )
234                 {
235                         mlt_log_error( getConsumer(), "Failed to enable audio output\n" );
236                         stop();
237                         return false;
238                 }
239
240                 m_preroll = preroll;
241                 m_reprio = false;
242
243                 // preroll frames
244                 for( i = 0; i < preroll; i++ )
245                         ScheduleNextFrame( true );
246
247                 // start scheduled playback
248                 m_deckLinkOutput->StartScheduledPlayback( 0, m_timescale, 1.0 );
249
250                 // Set the running state
251                 mlt_properties_set_int( properties, "running", 1 );
252
253                 return true;
254         }
255         
256         bool stop()
257         {
258                 mlt_properties properties = MLT_CONSUMER_PROPERTIES( getConsumer() );
259
260                 // set running state is 0
261                 mlt_properties_set_int( properties, "running", 0 );
262                 mlt_consumer_stopped( getConsumer() );
263
264                 // release decklink frame
265                 if ( m_decklinkFrame )
266                         m_decklinkFrame->Release();
267                 m_decklinkFrame = NULL;
268
269                 // Stop the audio and video output streams immediately
270                 if ( m_deckLinkOutput )
271                 {
272                         m_deckLinkOutput->StopScheduledPlayback( 0, 0, 0 );
273                         m_deckLinkOutput->DisableAudioOutput();
274                         m_deckLinkOutput->DisableVideoOutput();
275                 }
276
277                 return true;
278         }
279
280         void renderAudio( mlt_frame frame )
281         {
282                 mlt_audio_format format = mlt_audio_s16;
283                 int frequency = bmdAudioSampleRate48kHz;
284                 int samples = mlt_sample_calculator( m_fps, frequency, m_count );
285                 int16_t *pcm = 0;
286
287                 if ( !mlt_frame_get_audio( frame, (void**) &pcm, &format, &frequency, &m_channels, &samples ) )
288                 {
289 #ifdef WIN32
290                         unsigned long written = 0;
291 #else
292                         uint32_t written = 0;
293 #endif
294                         BMDTimeValue streamTime = m_count * frequency * m_duration / m_timescale;
295                         m_deckLinkOutput->GetBufferedAudioSampleFrameCount( &written );
296                         if ( written > (m_preroll + 1) * samples )
297                         {
298                                 mlt_log_verbose( getConsumer(), "renderAudio: will flush %lu audiosamples\n", written );
299                                 m_deckLinkOutput->FlushBufferedAudioSamples();
300                         };
301 #ifdef WIN32
302                         m_deckLinkOutput->ScheduleAudioSamples( pcm, samples, streamTime, frequency, (unsigned long*) &written );
303 #else
304                         m_deckLinkOutput->ScheduleAudioSamples( pcm, samples, streamTime, frequency, &written );
305 #endif
306
307                         if ( written != (uint32_t) samples )
308                                 mlt_log_verbose( getConsumer(), "renderAudio: samples=%d, written=%lu\n", samples, written );
309                 }
310         }
311
312         bool createFrame( IDeckLinkMutableVideoFrame** decklinkFrame )
313         {
314                 BMDPixelFormat format = m_isKeyer? bmdFormat8BitARGB : bmdFormat8BitYUV;
315                 IDeckLinkMutableVideoFrame* frame = 0;
316                 uint8_t *buffer = 0;
317                 int stride = m_width * ( m_isKeyer? 4 : 2 );
318
319                 *decklinkFrame = NULL;
320
321                 // Generate a DeckLink video frame
322                 if ( S_OK != m_deckLinkOutput->CreateVideoFrame( m_width, m_height,
323                         stride, format, bmdFrameFlagDefault, &frame ) )
324                 {
325                         mlt_log_verbose( getConsumer(), "Failed to create video frame\n" );
326                         stop();
327                         return false;
328                 }
329                 
330                 // Make the first line black for field order correction.
331                 if ( S_OK == frame->GetBytes( (void**) &buffer ) && buffer )
332                 {
333                         if ( m_isKeyer )
334                         {
335                                 memset( buffer, 0, stride );
336                         }
337                         else for ( int i = 0; i < m_width; i++ )
338                         {
339                                 *buffer++ = 128;
340                                 *buffer++ = 16;
341                         }
342                 }
343
344                 *decklinkFrame = frame;
345
346                 return true;
347         }
348
349         void renderVideo( mlt_frame frame )
350         {
351                 mlt_image_format format = m_isKeyer? mlt_image_rgb24a : mlt_image_yuv422;
352                 uint8_t* image = 0;
353                 int rendered = mlt_properties_get_int( MLT_FRAME_PROPERTIES(frame), "rendered");
354
355                 if ( rendered && !mlt_frame_get_image( frame, &image, &format, &m_width, &m_height, 0 ) )
356                 {
357                         uint8_t* buffer = 0;
358                         int stride = m_width * ( m_isKeyer? 4 : 2 );
359
360                         if ( m_decklinkFrame )
361                                 m_decklinkFrame->Release();
362                         if ( createFrame( &m_decklinkFrame ) )
363                                 m_decklinkFrame->GetBytes( (void**) &buffer );
364
365                         if ( buffer )
366                         {
367                                 int progressive = mlt_properties_get_int( MLT_FRAME_PROPERTIES( frame ), "progressive" );
368
369                                 if ( !m_isKeyer )
370                                 {
371                                         // Normal non-keyer playout - needs byte swapping
372                                         if ( !progressive && m_displayMode->GetFieldDominance() == bmdUpperFieldFirst )
373                                                 // convert lower field first to top field first
374                                                 swab( (char*) image, (char*) buffer + stride, stride * ( m_height - 1 ) );
375                                         else
376                                                 swab( (char*) image, (char*) buffer, stride * m_height );
377                                 }
378                                 else if ( !mlt_properties_get_int( MLT_FRAME_PROPERTIES( frame ), "test_image" ) )
379                                 {
380                                         // Normal keyer output
381                                         int y = m_height + 1;
382                                         uint32_t* s = (uint32_t*) image;
383                                         uint32_t* d = (uint32_t*) buffer;
384
385                                         if ( !progressive && m_displayMode->GetFieldDominance() == bmdUpperFieldFirst )
386                                         {
387                                                 // Correct field order
388                                                 m_height--;
389                                                 y--;
390                                                 d += m_width;
391                                         }
392
393                                         // Need to relocate alpha channel RGBA => ARGB
394                                         while ( --y )
395                                         {
396                                                 int x = m_width + 1;
397                                                 while ( --x )
398                                                 {
399                                                         *d++ = ( *s << 8 ) | ( *s >> 24 );
400                                                         s++;
401                                                 }
402                                         }
403                                 }
404                                 else
405                                 {
406                                         // Keying blank frames - nullify alpha
407                                         memset( buffer, 0, stride * m_height );
408                                 }
409                         }
410                 }
411                 if ( m_decklinkFrame )
412                         m_deckLinkOutput->ScheduleVideoFrame( m_decklinkFrame, m_count * m_duration, m_duration, m_timescale );
413
414                 if ( !rendered )
415                         mlt_log_verbose( getConsumer(), "dropped video frame %u\n", ++m_dropped );
416         }
417
418         HRESULT render( mlt_frame frame )
419         {
420                 HRESULT result = S_OK;
421
422                 // Get the audio
423                 double speed = mlt_properties_get_double( MLT_FRAME_PROPERTIES(frame), "_speed" );
424                 if ( m_isAudio && speed == 1.0 )
425                         renderAudio( frame );
426
427                 // Get the video
428                 renderVideo( frame );
429                 ++m_count;
430
431                 return result;
432         }
433         
434         // *** DeckLink API implementation of IDeckLinkVideoOutputCallback IDeckLinkAudioOutputCallback *** //
435
436         // IUnknown needs only a dummy implementation
437         virtual HRESULT STDMETHODCALLTYPE QueryInterface( REFIID iid, LPVOID *ppv )
438                 { return E_NOINTERFACE; }
439         virtual ULONG STDMETHODCALLTYPE AddRef()
440                 { return 1; }
441         virtual ULONG STDMETHODCALLTYPE Release()
442                 { return 1; }
443         
444         /************************* DeckLink API Delegate Methods *****************************/
445         
446         virtual HRESULT STDMETHODCALLTYPE ScheduledFrameCompleted( IDeckLinkVideoFrame* completedFrame, BMDOutputFrameCompletionResult completed )
447         {
448                 if( !m_reprio )
449                 {
450                         mlt_properties properties = MLT_CONSUMER_PROPERTIES( getConsumer() );
451
452                         if ( mlt_properties_get( properties, "priority" ) )
453                         {
454                                 int r;
455                                 pthread_t thread;
456                                 pthread_attr_t tattr;
457                                 struct sched_param param;
458
459                                 pthread_attr_init(&tattr);
460                                 pthread_attr_setschedpolicy(&tattr, SCHED_FIFO);
461
462                                 if ( !strcmp( "max", mlt_properties_get( properties, "priority" ) ) )
463                                         param.sched_priority = sched_get_priority_max(SCHED_FIFO) - 1;
464                                 else if ( !strcmp( "min", mlt_properties_get( properties, "priority" ) ) )
465                                         param.sched_priority = sched_get_priority_min(SCHED_FIFO) + 1;
466                                 else
467                                         param.sched_priority = mlt_properties_get_int( properties, "priority" );
468
469                                 pthread_attr_setschedparam(&tattr, &param);
470
471                                 thread = pthread_self();
472
473                                 r = pthread_setschedparam(thread, SCHED_FIFO, &param);
474                                 if( r )
475                                         mlt_log_verbose( getConsumer(),
476                                                 "ScheduledFrameCompleted: pthread_setschedparam retured %d\n", r);
477                                 else
478                                         mlt_log_verbose( getConsumer(),
479                                                 "ScheduledFrameCompleted: param.sched_priority=%d\n", param.sched_priority);
480                         };
481
482                         m_reprio = true;
483                 };
484
485 #ifdef WIN32
486                 unsigned long cnt;
487 #else
488                 uint32_t cnt;
489 #endif
490                 m_deckLinkOutput->GetBufferedAudioSampleFrameCount( &cnt );
491                 if ( cnt != m_acnt )
492                 {
493                         mlt_log_verbose( getConsumer(),
494                                 "ScheduledFrameCompleted: GetBufferedAudioSampleFrameCount %u -> %lu, m_count=%"PRIu64"\n",
495                                 m_acnt, cnt, m_count );
496                         m_acnt = cnt;
497                 }
498
499                 // When a video frame has been released by the API, schedule another video frame to be output
500
501                 // ignore handler if frame was flushed
502                 if ( bmdOutputFrameFlushed == completed )
503                         return S_OK;
504
505                 // schedule next frame
506                 ScheduleNextFrame( false );
507
508                 // step forward frames counter if underrun
509                 if ( bmdOutputFrameDisplayedLate == completed )
510                 {
511                         mlt_log_verbose( getConsumer(), "ScheduledFrameCompleted: bmdOutputFrameDisplayedLate == completed\n" );
512                         m_count++;
513                 }
514                 if ( bmdOutputFrameDropped == completed )
515                 {
516                         mlt_log_verbose( getConsumer(), "ScheduledFrameCompleted: bmdOutputFrameDropped == completed\n" );
517                         m_count++;
518                 }
519
520                 return S_OK;
521         }
522
523         virtual HRESULT STDMETHODCALLTYPE ScheduledPlaybackHasStopped()
524         {
525                 return mlt_consumer_is_stopped( getConsumer() ) ? S_FALSE : S_OK;
526         }
527         
528
529         void ScheduleNextFrame( bool preroll )
530         {
531                 // get the consumer
532                 mlt_consumer consumer = getConsumer();
533
534                 // Get the properties
535                 mlt_properties properties = MLT_CONSUMER_PROPERTIES( consumer );
536
537                 // Frame and size
538                 mlt_frame frame = NULL;
539
540                 if( mlt_properties_get_int( properties, "running" ) || preroll )
541                 {
542                         frame = mlt_consumer_rt_frame( consumer );
543                         if ( frame != NULL )
544                         {
545                                 render( frame );
546
547                                 mlt_events_fire( properties, "consumer-frame-show", frame, NULL );
548
549                                 // terminate on pause
550                                 if ( m_terminate_on_pause &&
551                                         mlt_properties_get_double( MLT_FRAME_PROPERTIES( frame ), "_speed" ) == 0.0 )
552                                         stop();
553
554                                 mlt_frame_close( frame );
555                         }
556                 }
557         }
558 };
559
560 /** Start the consumer.
561  */
562
563 static int start( mlt_consumer consumer )
564 {
565         // Get the properties
566         mlt_properties properties = MLT_CONSUMER_PROPERTIES( consumer );
567         DeckLinkConsumer* decklink = (DeckLinkConsumer*) consumer->child;
568         return decklink->start( mlt_properties_get_int( properties, "preroll" ) ) ? 0 : 1;
569 }
570
571 /** Stop the consumer.
572  */
573
574 static int stop( mlt_consumer consumer )
575 {
576         // Get the properties
577         DeckLinkConsumer* decklink = (DeckLinkConsumer*) consumer->child;
578         return decklink->stop();
579 }
580
581 /** Determine if the consumer is stopped.
582  */
583
584 static int is_stopped( mlt_consumer consumer )
585 {
586         // Get the properties
587         mlt_properties properties = MLT_CONSUMER_PROPERTIES( consumer );
588         return !mlt_properties_get_int( properties, "running" );
589 }
590
591 /** Close the consumer.
592  */
593
594 static void close( mlt_consumer consumer )
595 {
596         // Stop the consumer
597         mlt_consumer_stop( consumer );
598
599         // Close the parent
600         consumer->close = NULL;
601         mlt_consumer_close( consumer );
602
603         // Free the memory
604         delete (DeckLinkConsumer*) consumer->child;
605 }
606
607 extern "C" {
608
609 /** Initialise the consumer.
610  */
611
612 mlt_consumer consumer_decklink_init( mlt_profile profile, mlt_service_type type, const char *id, char *arg )
613 {
614         // Allocate the consumer
615         DeckLinkConsumer* decklink = new DeckLinkConsumer();
616         mlt_consumer consumer = NULL;
617
618         // If allocated
619         if ( decklink && !mlt_consumer_init( decklink->getConsumer(), decklink, profile ) )
620         {
621                 // If initialises without error
622                 if ( decklink->open( arg? atoi(arg) : 0 ) )
623                 {
624                         consumer = decklink->getConsumer();
625                         
626                         // Setup callbacks
627                         consumer->close = close;
628                         consumer->start = start;
629                         consumer->stop = stop;
630                         consumer->is_stopped = is_stopped;
631                         mlt_properties_set( MLT_CONSUMER_PROPERTIES(consumer), "deinterlace_method", "onefield" );
632                 }
633         }
634
635         // Return consumer
636         return consumer;
637 }
638
639 extern mlt_producer producer_decklink_init( mlt_profile profile, mlt_service_type type, const char *id, char *arg );
640
641 static mlt_properties metadata( mlt_service_type type, const char *id, void *data )
642 {
643         char file[ PATH_MAX ];
644         const char *service_type = NULL;
645         switch ( type )
646         {
647                 case consumer_type:
648                         service_type = "consumer";
649                         break;
650                 case producer_type:
651                         service_type = "producer";
652                         break;
653                 default:
654                         return NULL;
655         }
656         snprintf( file, PATH_MAX, "%s/decklink/%s_%s.yml", mlt_environment( "MLT_DATA" ), service_type, id );
657         return mlt_properties_parse_yaml( file );
658 }
659
660 MLT_REPOSITORY
661 {
662         MLT_REGISTER( consumer_type, "decklink", consumer_decklink_init );
663         MLT_REGISTER( producer_type, "decklink", producer_decklink_init );
664         MLT_REGISTER_METADATA( consumer_type, "decklink", metadata, NULL );
665         MLT_REGISTER_METADATA( producer_type, "decklink", metadata, NULL );
666 }
667
668 } // extern C