2 * producer_decklink.c -- input from Blackmagic Design DeckLink
3 * Copyright (C) 2011 Dan Dennedy <dan@dennedy.org>
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.
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.
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
20 #include <framework/mlt.h>
29 class DeckLinkProducer
30 : public IDeckLinkInputCallback
33 mlt_producer m_producer;
34 IDeckLink* m_decklink;
35 IDeckLinkInput* m_decklinkInput;
37 pthread_mutex_t m_mutex;
38 pthread_cond_t m_condition;
48 BMDDisplayMode getDisplayMode( mlt_profile profile, int vancLines )
50 IDeckLinkDisplayModeIterator* iter = NULL;
51 IDeckLinkDisplayMode* mode = NULL;
52 BMDDisplayMode result = (BMDDisplayMode) bmdDisplayModeNotSupported;
54 if ( m_decklinkInput->GetDisplayModeIterator( &iter ) == S_OK )
56 while ( !result && iter->Next( &mode ) == S_OK )
58 int width = mode->GetWidth();
59 int height = mode->GetHeight();
60 BMDTimeValue duration;
61 BMDTimeScale timescale;
62 mode->GetFrameRate( &duration, ×cale );
63 double fps = (double) timescale / duration;
64 int p = mode->GetFieldDominance() == bmdProgressiveFrame;
65 m_topFieldFirst = mode->GetFieldDominance() == bmdUpperFieldFirst;
66 m_colorspace = ( mode->GetFlags() & bmdDisplayModeColorspaceRec709 ) ? 709 : 601;
67 mlt_log_verbose( getProducer(), "BMD mode %dx%d %.3f fps prog %d tff %d\n", width, height, fps, p, m_topFieldFirst );
69 if ( width == profile->width && p == profile->progressive
70 && ( height + vancLines == profile->height || ( height == 486 && profile->height == 480 + vancLines ) )
71 && (int) fps == (int) mlt_profile_fps( profile ) )
72 result = mode->GetDisplayMode();
83 void setProducer( mlt_producer producer )
84 { m_producer = producer; }
86 mlt_producer getProducer() const
87 { return m_producer; }
93 m_decklinkInput = NULL;
96 virtual ~DeckLinkProducer()
101 mlt_deque_close( m_queue );
102 pthread_mutex_destroy( &m_mutex );
103 pthread_cond_destroy( &m_condition );
104 mlt_cache_close( m_cache );
106 SAFE_RELEASE( m_decklinkInput );
107 SAFE_RELEASE( m_decklink );
110 bool open( unsigned card = 0 )
112 IDeckLinkIterator* decklinkIterator = NULL;
116 HRESULT result = CoInitialize( NULL );
117 if ( FAILED( result ) )
118 throw "COM initialization failed";
119 result = CoCreateInstance( CLSID_CDeckLinkIterator, NULL, CLSCTX_ALL, IID_IDeckLinkIterator, (void**) &decklinkIterator );
120 if ( FAILED( result ) )
121 throw "The DeckLink drivers are not installed.";
123 decklinkIterator = CreateDeckLinkIteratorInstance();
124 if ( !decklinkIterator )
125 throw "The DeckLink drivers are not installed.";
127 // Connect to the Nth DeckLink instance
128 for ( unsigned i = 0; decklinkIterator->Next( &m_decklink ) == S_OK ; i++)
133 SAFE_RELEASE( m_decklink );
135 SAFE_RELEASE( decklinkIterator );
137 throw "DeckLink card not found.";
139 // Get the input interface
140 if ( m_decklink->QueryInterface( IID_IDeckLinkInput, (void**) &m_decklinkInput ) != S_OK )
141 throw "No DeckLink cards support input.";
143 // Provide this class as a delegate to the input callback
144 m_decklinkInput->SetCallback( this );
146 // Initialize other members
147 pthread_mutex_init( &m_mutex, NULL );
148 pthread_cond_init( &m_condition, NULL );
149 m_queue = mlt_deque_init();
152 m_isBuffering = true;
153 m_cache = mlt_cache_init();
155 // 3 covers YADIF and increasing framerate use cases
156 mlt_cache_set_size( m_cache, 3 );
158 catch ( const char *error )
160 SAFE_RELEASE( m_decklinkInput );
161 SAFE_RELEASE( m_decklink );
162 mlt_log_error( getProducer(), "%s\n", error );
168 bool start( mlt_profile profile = 0 )
174 // Initialize some members
175 m_vancLines = mlt_properties_get_int( MLT_PRODUCER_PROPERTIES( getProducer() ), "vanc" );
176 if ( m_vancLines == -1 )
177 m_vancLines = profile->height <= 512 ? 26 : 32;
180 profile = mlt_service_profile( MLT_PRODUCER_SERVICE( getProducer() ) );
182 // Get the display mode
183 BMDDisplayMode displayMode = getDisplayMode( profile, m_vancLines );
184 if ( displayMode == (BMDDisplayMode) bmdDisplayModeNotSupported )
186 mlt_log_info( getProducer(), "profile = %dx%d %f fps %s\n", profile->width, profile->height,
187 mlt_profile_fps( profile ), profile->progressive? "progressive" : "interlace" );
188 throw "Profile is not compatible with decklink.";
191 // Determine if supports input format detection
193 BOOL doesDetectFormat = FALSE;
195 bool doesDetectFormat = false;
197 IDeckLinkAttributes *decklinkAttributes = 0;
198 if ( m_decklink->QueryInterface( IID_IDeckLinkAttributes, (void**) &decklinkAttributes ) == S_OK )
200 if ( decklinkAttributes->GetFlag( BMDDeckLinkSupportsInputFormatDetection, &doesDetectFormat ) != S_OK )
201 doesDetectFormat = false;
202 SAFE_RELEASE( decklinkAttributes );
204 mlt_log_verbose( getProducer(), "%s format detection\n", doesDetectFormat ? "supports" : "does not support" );
206 // Enable video capture
207 BMDPixelFormat pixelFormat = bmdFormat8BitYUV;
208 BMDVideoInputFlags flags = doesDetectFormat ? bmdVideoInputEnableFormatDetection : bmdVideoInputFlagDefault;
209 if ( S_OK != m_decklinkInput->EnableVideoInput( displayMode, pixelFormat, flags ) )
210 throw "Failed to enable video capture.";
212 // Enable audio capture
213 BMDAudioSampleRate sampleRate = bmdAudioSampleRate48kHz;
214 BMDAudioSampleType sampleType = bmdAudioSampleType16bitInteger;
215 int channels = mlt_properties_get_int( MLT_PRODUCER_PROPERTIES( getProducer() ), "channels" );
216 if ( S_OK != m_decklinkInput->EnableAudioInput( sampleRate, sampleType, channels ) )
217 throw "Failed to enable audio capture.";
221 mlt_properties_set_int( MLT_PRODUCER_PROPERTIES( getProducer() ), "dropped", m_dropped );
222 m_started = m_decklinkInput->StartStreams() == S_OK;
224 throw "Failed to start capture.";
226 catch ( const char *error )
228 m_decklinkInput->DisableVideoInput();
229 mlt_log_error( getProducer(), "%s\n", error );
241 // Release the wait in getFrame
242 pthread_mutex_lock( &m_mutex );
243 pthread_cond_broadcast( &m_condition );
244 pthread_mutex_unlock( &m_mutex );
246 m_decklinkInput->StopStreams();
247 m_decklinkInput->DisableVideoInput();
248 m_decklinkInput->DisableAudioInput();
251 pthread_mutex_lock( &m_mutex );
252 while ( mlt_frame frame = (mlt_frame) mlt_deque_pop_back( m_queue ) )
253 mlt_frame_close( frame );
254 pthread_mutex_unlock( &m_mutex );
261 double fps = mlt_producer_get_fps( getProducer() );
262 mlt_position position = mlt_producer_position( getProducer() );
263 mlt_frame frame = mlt_cache_get_frame( m_cache, position );
265 // Allow the buffer to fill to the requested initial buffer level.
268 int prefill = mlt_properties_get_int( MLT_PRODUCER_PROPERTIES( getProducer() ), "prefill" );
269 int buffer = mlt_properties_get_int( MLT_PRODUCER_PROPERTIES( getProducer() ), "buffer" );
271 m_isBuffering = false;
272 prefill = prefill > buffer ? buffer : prefill;
273 pthread_mutex_lock( &m_mutex );
274 while ( mlt_deque_count( m_queue ) < prefill )
276 // Wait up to buffer/fps seconds
277 gettimeofday( &now, NULL );
278 long usec = now.tv_sec * 1000000 + now.tv_usec;
279 usec += 1000000 * buffer / fps;
280 tm.tv_sec = usec / 1000000;
281 tm.tv_nsec = (usec % 1000000) * 1000;
282 if ( pthread_cond_timedwait( &m_condition, &m_mutex, &tm ) )
285 pthread_mutex_unlock( &m_mutex );
290 // Wait if queue is empty
291 pthread_mutex_lock( &m_mutex );
292 while ( mlt_deque_count( m_queue ) < 1 )
294 // Wait up to twice frame duration
295 gettimeofday( &now, NULL );
296 long usec = now.tv_sec * 1000000 + now.tv_usec;
297 usec += 2000000 / fps;
298 tm.tv_sec = usec / 1000000;
299 tm.tv_nsec = (usec % 1000000) * 1000;
300 if ( pthread_cond_timedwait( &m_condition, &m_mutex, &tm ) )
301 // Stop waiting if error (timed out)
304 frame = ( mlt_frame ) mlt_deque_pop_front( m_queue );
305 pthread_mutex_unlock( &m_mutex );
310 mlt_frame_set_position( frame, position );
311 mlt_cache_put_frame( m_cache, frame );
315 // Set frame timestamp and properties
318 mlt_profile profile = mlt_service_profile( MLT_PRODUCER_SERVICE( getProducer() ) );
319 mlt_properties properties = MLT_FRAME_PROPERTIES( frame );
320 mlt_properties_set_int( properties, "progressive", profile->progressive );
321 mlt_properties_set_int( properties, "meta.media.progressive", profile->progressive );
322 mlt_properties_set_int( properties, "top_field_first", m_topFieldFirst );
323 mlt_properties_set_double( properties, "aspect_ratio", mlt_profile_sar( profile ) );
324 mlt_properties_set_int( properties, "meta.media.sample_aspect_num", profile->sample_aspect_num );
325 mlt_properties_set_int( properties, "meta.media.sample_aspect_den", profile->sample_aspect_den );
326 mlt_properties_set_int( properties, "meta.media.frame_rate_num", profile->frame_rate_num );
327 mlt_properties_set_int( properties, "meta.media.frame_rate_den", profile->frame_rate_den );
328 mlt_properties_set_int( properties, "width", profile->width );
329 mlt_properties_set_int( properties, "meta.media.width", profile->width );
330 mlt_properties_set_int( properties, "height", profile->height );
331 mlt_properties_set_int( properties, "meta.media.height", profile->height );
332 mlt_properties_set_int( properties, "format", mlt_image_yuv422 );
333 mlt_properties_set_int( properties, "colorspace", m_colorspace );
334 mlt_properties_set_int( properties, "meta.media.colorspace", m_colorspace );
335 mlt_properties_set_int( properties, "audio_frequency", 48000 );
336 mlt_properties_set_int( properties, "audio_channels",
337 mlt_properties_get_int( MLT_PRODUCER_PROPERTIES( getProducer() ), "channels" ) );
340 mlt_log_warning( getProducer(), "buffer underrun\n" );
345 // *** DeckLink API implementation of IDeckLinkInputCallback *** //
347 // IUnknown needs only a dummy implementation
348 virtual HRESULT STDMETHODCALLTYPE QueryInterface( REFIID iid, LPVOID *ppv )
349 { return E_NOINTERFACE; }
350 virtual ULONG STDMETHODCALLTYPE AddRef()
352 virtual ULONG STDMETHODCALLTYPE Release()
355 /************************* DeckLink API Delegate Methods *****************************/
357 virtual HRESULT STDMETHODCALLTYPE VideoInputFrameArrived(
358 IDeckLinkVideoInputFrame* video,
359 IDeckLinkAudioInputPacket* audio )
363 mlt_properties properties = MLT_PRODUCER_PROPERTIES( getProducer() );
365 if ( mlt_properties_get( properties, "priority" ) )
369 pthread_attr_t tattr;
370 struct sched_param param;
372 pthread_attr_init(&tattr);
373 pthread_attr_setschedpolicy(&tattr, SCHED_FIFO);
375 if ( !strcmp( "max", mlt_properties_get( properties, "priority" ) ) )
376 param.sched_priority = sched_get_priority_max(SCHED_FIFO) - 1;
377 else if ( !strcmp( "min", mlt_properties_get( properties, "priority" ) ) )
378 param.sched_priority = sched_get_priority_min(SCHED_FIFO) + 1;
380 param.sched_priority = mlt_properties_get_int( properties, "priority" );
382 pthread_attr_setschedparam(&tattr, ¶m);
384 thread = pthread_self();
386 r = pthread_setschedparam(thread, SCHED_FIFO, ¶m);
388 mlt_log_verbose( getProducer(),
389 "VideoInputFrameArrived: pthread_setschedparam returned %d\n", r);
391 mlt_log_verbose( getProducer(),
392 "VideoInputFrameArrived: param.sched_priority=%d\n", param.sched_priority);
398 if ( mlt_properties_get_int( MLT_PRODUCER_PROPERTIES( getProducer() ), "preview" ) &&
399 mlt_producer_get_speed( getProducer() ) == 0.0 && !mlt_deque_count( m_queue ))
401 pthread_cond_broadcast( &m_condition );
406 mlt_frame frame = mlt_frame_init( MLT_PRODUCER_SERVICE( getProducer() ) );
411 if ( !( video->GetFlags() & bmdFrameHasNoInputSource ) )
413 int size = video->GetRowBytes() * ( video->GetHeight() + m_vancLines );
414 void* image = mlt_pool_alloc( size );
416 unsigned char* p = (unsigned char*) image;
419 // Initialize VANC lines to nominal black
427 if ( m_vancLines > 0 )
429 IDeckLinkVideoFrameAncillary* vanc = 0;
430 if ( video->GetAncillaryData( &vanc ) == S_OK && vanc )
432 for ( int i = 1; i < m_vancLines + 1; i++ )
434 if ( vanc->GetBufferForVerticalBlankingLine( i, &buffer ) == S_OK )
435 swab2( (char*) buffer, (char*) image + ( i - 1 ) * video->GetRowBytes(), video->GetRowBytes() );
437 mlt_log_debug( getProducer(), "failed capture vanc line %d\n", i );
444 video->GetBytes( &buffer );
445 if ( image && buffer )
447 size = video->GetRowBytes() * video->GetHeight();
448 swab2( (char*) buffer, (char*) image + m_vancLines * video->GetRowBytes(), size );
449 mlt_frame_set_image( frame, (uint8_t*) image, size, mlt_pool_release );
453 mlt_log_verbose( getProducer(), "no video\n" );
454 mlt_pool_release( image );
459 mlt_log_verbose( getProducer(), "no signal\n" );
460 mlt_frame_close( frame );
465 IDeckLinkTimecode* timecode = 0;
466 if ( video->GetTimecode( bmdTimecodeVITC, &timecode ) == S_OK && timecode )
468 DLString timecodeString = 0;
470 if ( timecode->GetString( &timecodeString ) == S_OK )
472 char* s = getCString( timecodeString );
473 mlt_properties_set( MLT_FRAME_PROPERTIES( frame ), "meta.attr.vitc.markup", s );
474 mlt_log_debug( getProducer(), "timecode %s\n", s );
477 freeDLString( timecodeString );
478 SAFE_RELEASE( timecode );
483 mlt_log_verbose( getProducer(), "no video\n" );
484 mlt_frame_close( frame );
489 if ( frame && audio )
491 int channels = mlt_properties_get_int( MLT_PRODUCER_PROPERTIES( getProducer() ), "channels" );
492 int size = audio->GetSampleFrameCount() * channels * sizeof(int16_t);
493 mlt_audio_format format = mlt_audio_s16;
494 void* pcm = mlt_pool_alloc( size );
497 audio->GetBytes( &buffer );
500 memcpy( pcm, buffer, size );
501 mlt_frame_set_audio( frame, pcm, format, size, mlt_pool_release );
502 mlt_properties_set_int( MLT_FRAME_PROPERTIES(frame), "audio_samples", audio->GetSampleFrameCount() );
506 mlt_log_verbose( getProducer(), "no audio\n" );
507 mlt_pool_release( pcm );
512 mlt_log_verbose( getProducer(), "no audio\n" );
515 // Put frame in queue
518 int queueMax = mlt_properties_get_int( MLT_PRODUCER_PROPERTIES( getProducer() ), "buffer" );
519 pthread_mutex_lock( &m_mutex );
520 if ( mlt_deque_count( m_queue ) < queueMax )
522 mlt_deque_push_back( m_queue, frame );
523 pthread_cond_broadcast( &m_condition );
527 mlt_frame_close( frame );
528 mlt_properties_set_int( MLT_PRODUCER_PROPERTIES( getProducer() ), "dropped", ++m_dropped );
529 mlt_log_warning( getProducer(), "buffer overrun, frame dropped %d\n", m_dropped );
531 pthread_mutex_unlock( &m_mutex );
537 virtual HRESULT STDMETHODCALLTYPE VideoInputFormatChanged(
538 BMDVideoInputFormatChangedEvents events,
539 IDeckLinkDisplayMode* mode,
540 BMDDetectedVideoInputFormatFlags flags )
542 mlt_profile profile = mlt_service_profile( MLT_PRODUCER_SERVICE( getProducer() ) );
543 if ( events & bmdVideoInputDisplayModeChanged )
545 BMDTimeValue duration;
546 BMDTimeScale timescale;
547 mode->GetFrameRate( &duration, ×cale );
548 profile->width = mode->GetWidth();
549 profile->height = mode->GetHeight() + m_vancLines;
550 profile->frame_rate_num = timescale;
551 profile->frame_rate_den = duration;
552 if ( profile->width == 720 )
554 if ( profile->height == 576 )
556 profile->sample_aspect_num = 16;
557 profile->sample_aspect_den = 15;
561 profile->sample_aspect_num = 8;
562 profile->sample_aspect_den = 9;
564 profile->display_aspect_num = 4;
565 profile->display_aspect_den = 3;
569 profile->sample_aspect_num = 1;
570 profile->sample_aspect_den = 1;
571 profile->display_aspect_num = 16;
572 profile->display_aspect_den = 9;
574 free( profile->description );
575 profile->description = strdup( "decklink" );
576 mlt_log_verbose( getProducer(), "format changed %dx%d %.3f fps\n",
577 profile->width, profile->height, (double) profile->frame_rate_num / profile->frame_rate_den );
579 if ( events & bmdVideoInputFieldDominanceChanged )
581 profile->progressive = mode->GetFieldDominance() == bmdProgressiveFrame;
582 m_topFieldFirst = mode->GetFieldDominance() == bmdUpperFieldFirst;
583 mlt_log_verbose( getProducer(), "field dominance changed prog %d tff %d\n",
584 profile->progressive, m_topFieldFirst );
586 if ( events & bmdVideoInputColorspaceChanged )
588 profile->colorspace = m_colorspace =
589 ( mode->GetFlags() & bmdDisplayModeColorspaceRec709 ) ? 709 : 601;
590 mlt_log_verbose( getProducer(), "colorspace changed %d\n", profile->colorspace );
596 static int get_audio( mlt_frame frame, int16_t **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples )
598 return mlt_frame_get_audio( frame, (void**) buffer, format, frequency, channels, samples );
601 static int get_image( mlt_frame frame, uint8_t **buffer, mlt_image_format *format, int *width, int *height, int writable )
603 return mlt_frame_get_image( frame, buffer, format, width, height, writable );
606 static int get_frame( mlt_producer producer, mlt_frame_ptr frame, int index )
608 DeckLinkProducer* decklink = (DeckLinkProducer*) producer->child;
609 mlt_position pos = mlt_producer_position( producer );
610 mlt_position end = mlt_producer_get_playtime( producer );
611 end = ( mlt_producer_get_length( producer ) < end ? mlt_producer_get_length( producer ) : end ) - 1;
614 if ( !decklink && pos < end )
616 producer->child = decklink = new DeckLinkProducer();
617 decklink->setProducer( producer );
618 decklink->open( mlt_properties_get_int( MLT_PRODUCER_PROPERTIES(producer), "resource" ) );
624 decklink->start( mlt_service_profile( MLT_PRODUCER_SERVICE( producer ) ) );
626 // Get the next frame from the decklink object
627 if ( ( *frame = decklink->getFrame() ))
629 // Add audio and video getters
630 mlt_frame_push_audio( *frame, (void*) get_audio );
631 mlt_frame_push_get_image( *frame, get_image );
635 *frame = mlt_frame_init( MLT_PRODUCER_SERVICE(producer) );
637 // Calculate the next timecode
638 mlt_producer_prepare_next( producer );
640 // Close DeckLink if at end
641 if ( pos >= end && decklink )
645 producer->child = NULL;
651 static void producer_close( mlt_producer producer )
653 delete (DeckLinkProducer*) producer->child;
654 producer->close = NULL;
655 mlt_producer_close( producer );
660 // Listen for the list_devices property to be set
661 static void on_property_changed( void*, mlt_properties properties, const char *name )
663 IDeckLinkIterator* decklinkIterator = NULL;
664 IDeckLink* decklink = NULL;
665 IDeckLinkInput* decklinkInput = NULL;
668 if ( name && !strcmp( name, "list_devices" ) )
669 mlt_event_block( (mlt_event) mlt_properties_get_data( properties, "list-devices-event", NULL ) );
674 if ( FAILED( CoInitialize( NULL ) ) )
676 if ( FAILED( CoCreateInstance( CLSID_CDeckLinkIterator, NULL, CLSCTX_ALL, IID_IDeckLinkIterator, (void**) &decklinkIterator ) ) )
679 if ( !( decklinkIterator = CreateDeckLinkIteratorInstance() ) )
682 for ( ; decklinkIterator->Next( &decklink ) == S_OK; i++ )
684 if ( decklink->QueryInterface( IID_IDeckLinkInput, (void**) &decklinkInput ) == S_OK )
686 DLString name = NULL;
687 if ( decklink->GetModelName( &name ) == S_OK )
689 char *name_cstr = getCString( name );
690 const char *format = "device.%d";
691 char *key = (char*) calloc( 1, strlen( format ) + 1 );
693 sprintf( key, format, i );
694 mlt_properties_set( properties, key, name_cstr );
696 freeDLString( name );
697 freeCString( name_cstr );
699 SAFE_RELEASE( decklinkInput );
701 SAFE_RELEASE( decklink );
703 SAFE_RELEASE( decklinkIterator );
704 mlt_properties_set_int( properties, "devices", i );
707 /** Initialise the producer.
710 mlt_producer producer_decklink_init( mlt_profile profile, mlt_service_type type, const char *id, char *arg )
712 // Allocate the producer
713 DeckLinkProducer* decklink = new DeckLinkProducer();
714 mlt_producer producer = (mlt_producer) calloc( 1, sizeof( *producer ) );
716 // If allocated and initializes
717 if ( decklink && !mlt_producer_init( producer, decklink ) )
719 if ( decklink->open( arg? atoi( arg ) : 0 ) )
721 mlt_properties properties = MLT_PRODUCER_PROPERTIES( producer );
723 // Close DeckLink and defer re-open to get_frame
725 producer->child = NULL;
728 producer->close = (mlt_destructor) producer_close;
729 producer->get_frame = get_frame;
732 mlt_properties_set( properties, "resource", (arg && strcmp( arg, ""))? arg : "0" );
733 mlt_properties_set_int( properties, "channels", 2 );
734 mlt_properties_set_int( properties, "buffer", 25 );
735 mlt_properties_set_int( properties, "prefill", 25 );
737 // These properties effectively make it infinite.
738 mlt_properties_set_int( properties, "length", INT_MAX );
739 mlt_properties_set_int( properties, "out", INT_MAX - 1 );
740 mlt_properties_set( properties, "eof", "loop" );
742 mlt_event event = mlt_events_listen( properties, properties, "property-changed", (mlt_listener) on_property_changed );
743 mlt_properties_set_data( properties, "list-devices-event", event, 0, NULL, NULL );