X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=src%2Fmodules%2Fdecklink%2Fconsumer_decklink.cpp;h=e6d20e09998373ac179cb0251c4a345aacf30f4c;hb=307a2ef3af84a5dba60a5026a1a24f430a0e0bf2;hp=9268c05d281535e3488f6d51a649612ddc5cdf56;hpb=50af2f0fb0a3b4a1c02b28405326ff7476eb4aec;p=mlt diff --git a/src/modules/decklink/consumer_decklink.cpp b/src/modules/decklink/consumer_decklink.cpp index 9268c05d..e6d20e09 100644 --- a/src/modules/decklink/consumer_decklink.cpp +++ b/src/modules/decklink/consumer_decklink.cpp @@ -17,97 +17,52 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#define __STDC_FORMAT_MACROS /* see inttypes.h */ #include #include #include #include #include #include -#include "DeckLinkAPI.h" - -const unsigned PREROLL_MINIMUM = 3; - -typedef struct -{ - int16_t *buffer; - int size; - int used; - pthread_mutex_t mutex; -} *sample_fifo; - -static sample_fifo sample_fifo_init() -{ - sample_fifo fifo = (sample_fifo) calloc( 1, sizeof( *fifo ) ); - pthread_mutex_init( &fifo->mutex, NULL ); - return fifo; -} - -static void sample_fifo_append( sample_fifo fifo, int16_t *samples, int count ) -{ - pthread_mutex_lock( &fifo->mutex ); - if ( ( fifo->size - fifo->used ) < count ) - { - fifo->size += count * 5; - fifo->buffer = (int16_t*) realloc( fifo->buffer, fifo->size * sizeof( int16_t ) ); - } - memcpy( fifo->buffer + fifo->used, samples, count * sizeof( int16_t ) ); - fifo->used += count; - pthread_mutex_unlock( &fifo->mutex ); -} - -static void sample_fifo_remove( sample_fifo fifo, int count ) -{ - pthread_mutex_lock( &fifo->mutex ); - if ( count > fifo->used ) - count = fifo->used; - fifo->used -= count; - memmove( fifo->buffer, fifo->buffer + count, fifo->used * sizeof( int16_t ) ); - pthread_mutex_unlock( &fifo->mutex ); -} - -static void sample_fifo_close( sample_fifo fifo ) -{ - free( fifo->buffer ); - pthread_mutex_destroy( &fifo->mutex ); - free( fifo ); -} +#include +#include +#include "common.h" +static const unsigned PREROLL_MINIMUM = 3; class DeckLinkConsumer : public IDeckLinkVideoOutputCallback - , public IDeckLinkAudioOutputCallback { private: mlt_consumer_s m_consumer; IDeckLink* m_deckLink; IDeckLinkOutput* m_deckLinkOutput; - IDeckLinkMutableVideoFrame* m_videoFrame; IDeckLinkDisplayMode* m_displayMode; - pthread_mutex_t m_mutex; - pthread_cond_t m_condition; int m_width; int m_height; BMDTimeValue m_duration; BMDTimeScale m_timescale; double m_fps; uint64_t m_count; - sample_fifo m_fifo; - unsigned m_preroll; - bool m_isPrerolling; - unsigned m_prerollCounter; int m_channels; - uint32_t m_maxAudioBuffer; - mlt_deque m_videoFrameQ; - mlt_frame m_frame; unsigned m_dropped; - + IDeckLinkMutableVideoFrame* m_decklinkFrame; + bool m_isAudio; + int m_isKeyer; + IDeckLinkKeyer* m_deckLinkKeyer; + bool m_terminate_on_pause; + uint32_t m_preroll; + uint32_t m_acnt; + bool m_reprio; + pthread_t m_prerollThread; + IDeckLinkDisplayMode* getDisplayMode() { - mlt_profile profile = mlt_service_profile( MLT_CONSUMER_SERVICE( &m_consumer ) ); - IDeckLinkDisplayModeIterator* iter; - IDeckLinkDisplayMode* mode; + mlt_profile profile = mlt_service_profile( MLT_CONSUMER_SERVICE( getConsumer() ) ); + IDeckLinkDisplayModeIterator* iter = NULL; + IDeckLinkDisplayMode* mode = NULL; IDeckLinkDisplayMode* result = 0; - + if ( m_deckLinkOutput->GetDisplayModeIterator( &iter ) == S_OK ) { while ( !result && iter->Next( &mode ) == S_OK ) @@ -117,137 +72,223 @@ private: mode->GetFrameRate( &m_duration, &m_timescale ); m_fps = (double) m_timescale / m_duration; int p = mode->GetFieldDominance() == bmdProgressiveFrame; - mlt_log_verbose( &m_consumer, "BMD mode %dx%d %.3f fps prog %d\n", m_width, m_height, m_fps, p ); - - if ( m_width == profile->width && m_height == profile->height && p == profile->progressive - && m_fps == mlt_profile_fps( profile ) ) + mlt_log_verbose( getConsumer(), "BMD mode %dx%d %.3f fps prog %d\n", m_width, m_height, m_fps, p ); + + if ( m_width == profile->width && p == profile->progressive + && (int) m_fps == (int) mlt_profile_fps( profile ) + && ( m_height == profile->height || ( m_height == 486 && profile->height == 480 ) ) ) result = mode; + else + SAFE_RELEASE( mode ); } + SAFE_RELEASE( iter ); } - + return result; } - + public: mlt_consumer getConsumer() { return &m_consumer; } - uint64_t isBuffering() const - { return m_prerollCounter < m_preroll; } - - ~DeckLinkConsumer() + + DeckLinkConsumer() { - if ( m_deckLinkOutput ) - m_deckLinkOutput->Release(); - if ( m_deckLink ) - m_deckLink->Release(); - if ( m_videoFrameQ ) - mlt_deque_close( m_videoFrameQ ); + m_displayMode = NULL; + m_deckLinkKeyer = NULL; + m_deckLinkOutput = NULL; + m_deckLink = NULL; + m_decklinkFrame = NULL; } - + + virtual ~DeckLinkConsumer() + { + SAFE_RELEASE( m_displayMode ); + SAFE_RELEASE( m_deckLinkKeyer ); + SAFE_RELEASE( m_deckLinkOutput ); + SAFE_RELEASE( m_deckLink ); + } + bool open( unsigned card = 0 ) { - IDeckLinkIterator* deckLinkIterator = CreateDeckLinkIteratorInstance(); unsigned i = 0; - +#ifdef WIN32 + IDeckLinkIterator* deckLinkIterator = NULL; + HRESULT result = CoInitialize( NULL ); + if ( FAILED( result ) ) + { + mlt_log_error( getConsumer(), "COM initialization failed\n" ); + return false; + } + result = CoCreateInstance( CLSID_CDeckLinkIterator, NULL, CLSCTX_ALL, IID_IDeckLinkIterator, (void**) &deckLinkIterator ); + if ( FAILED( result ) ) + { + mlt_log_error( getConsumer(), "The DeckLink drivers not installed.\n" ); + return false; + } +#else + IDeckLinkIterator* deckLinkIterator = CreateDeckLinkIteratorInstance(); + if ( !deckLinkIterator ) { - mlt_log_verbose( NULL, "The DeckLink drivers not installed.\n" ); + mlt_log_error( getConsumer(), "The DeckLink drivers not installed.\n" ); return false; } - +#endif + // Connect to the Nth DeckLink instance - do { - if ( deckLinkIterator->Next( &m_deckLink ) != S_OK ) - { - mlt_log_verbose( NULL, "DeckLink card not found\n" ); - deckLinkIterator->Release(); - return false; - } - } while ( ++i <= card ); - + for ( i = 0; deckLinkIterator->Next( &m_deckLink ) == S_OK ; i++) + { + if( i == card ) + break; + else + SAFE_RELEASE( m_deckLink ); + } + SAFE_RELEASE( deckLinkIterator ); + if ( !m_deckLink ) + { + mlt_log_error( getConsumer(), "DeckLink card not found\n" ); + return false; + } + // Obtain the audio/video output interface (IDeckLinkOutput) if ( m_deckLink->QueryInterface( IID_IDeckLinkOutput, (void**)&m_deckLinkOutput ) != S_OK ) { - mlt_log_verbose( NULL, "No DeckLink cards support output\n" ); - m_deckLink->Release(); - m_deckLink = 0; - deckLinkIterator->Release(); + mlt_log_error( getConsumer(), "No DeckLink cards support output\n" ); + SAFE_RELEASE( m_deckLink ); return false; } - + + // Get the keyer interface + IDeckLinkAttributes *deckLinkAttributes = 0; + if ( m_deckLink->QueryInterface( IID_IDeckLinkAttributes, (void**) &deckLinkAttributes ) == S_OK ) + { +#ifdef WIN32 + BOOL flag = FALSE; +#else + bool flag = false; +#endif + if ( deckLinkAttributes->GetFlag( BMDDeckLinkSupportsInternalKeying, &flag ) == S_OK && flag ) + { + if ( m_deckLink->QueryInterface( IID_IDeckLinkKeyer, (void**) &m_deckLinkKeyer ) != S_OK ) + { + mlt_log_error( getConsumer(), "Failed to get keyer\n" ); + SAFE_RELEASE( m_deckLinkOutput ); + SAFE_RELEASE( m_deckLink ); + return false; + } + } + SAFE_RELEASE( deckLinkAttributes ); + } + // Provide this class as a delegate to the audio and video output interfaces m_deckLinkOutput->SetScheduledFrameCompletionCallback( this ); - m_deckLinkOutput->SetAudioCallback( this ); - - pthread_mutex_init( &m_mutex, NULL ); - pthread_cond_init( &m_condition, NULL ); - m_maxAudioBuffer = bmdAudioSampleRate48kHz; - m_videoFrameQ = mlt_deque_init(); - + return true; } - + + + void* preroll_thread() + { + mlt_properties properties = MLT_CONSUMER_PROPERTIES( getConsumer() ); + + // preroll frames + for ( unsigned i = 0; i < m_preroll && mlt_properties_get_int( properties, "running" ); i++ ) + ScheduleNextFrame( true ); + + // start scheduled playback + if ( mlt_properties_get_int( properties, "running" ) ) + m_deckLinkOutput->StartScheduledPlayback( 0, m_timescale, 1.0 ); + + return 0; + } + + static void* preroll_thread_proxy( void* arg ) + { + DeckLinkConsumer* self = static_cast< DeckLinkConsumer* >( arg ); + return self->preroll_thread(); + } + bool start( unsigned preroll ) { + mlt_properties properties = MLT_CONSUMER_PROPERTIES( getConsumer() ); + + // Initialize members + m_count = 0; + m_dropped = 0; + m_decklinkFrame = NULL; + preroll = preroll < PREROLL_MINIMUM ? PREROLL_MINIMUM : preroll; + m_channels = mlt_properties_get_int( properties, "channels" ); + m_isAudio = !mlt_properties_get_int( properties, "audio_off" ); + m_terminate_on_pause = mlt_properties_get_int( properties, "terminate_on_pause" ); + + m_displayMode = getDisplayMode(); if ( !m_displayMode ) { - mlt_log_error( &m_consumer, "Profile is not compatible with decklink.\n" ); + mlt_log_error( getConsumer(), "Profile is not compatible with decklink.\n" ); return false; } - + + // Set the keyer + if ( m_deckLinkKeyer && ( m_isKeyer = mlt_properties_get_int( properties, "keyer" ) ) ) + { + bool external = ( m_isKeyer == 2 ); + double level = mlt_properties_get_double( properties, "keyer_level" ); + + if ( m_deckLinkKeyer->Enable( external ) != S_OK ) + mlt_log_error( getConsumer(), "Failed to enable %s keyer\n", + external ? "external" : "internal" ); + m_deckLinkKeyer->SetLevel( level <= 1 ? ( level > 0 ? 255 * level : 255 ) : 255 ); + } + else if ( m_deckLinkKeyer ) + { + m_deckLinkKeyer->Disable(); + } + // Set the video output mode - if ( S_OK != m_deckLinkOutput->EnableVideoOutput( m_displayMode->GetDisplayMode(), bmdVideoOutputFlagDefault) ) + if ( S_OK != m_deckLinkOutput->EnableVideoOutput( m_displayMode->GetDisplayMode(), bmdVideoOutputFlagDefault ) ) { - mlt_log_error( &m_consumer, "Failed to enable video output\n" ); + mlt_log_error( getConsumer(), "Failed to enable video output\n" ); return false; } - + // Set the audio output mode - m_channels = 2; + if ( !m_isAudio ) + { + m_deckLinkOutput->DisableAudioOutput(); + return true; + } if ( S_OK != m_deckLinkOutput->EnableAudioOutput( bmdAudioSampleRate48kHz, bmdAudioSampleType16bitInteger, - m_channels, bmdAudioOutputStreamContinuous ) ) + m_channels, bmdAudioOutputStreamTimestamped ) ) { - mlt_log_error( &m_consumer, "Failed to enable audio output\n" ); + mlt_log_error( getConsumer(), "Failed to enable audio output\n" ); stop(); return false; } - m_fifo = sample_fifo_init(); - - // Preroll - m_isPrerolling = true; - m_prerollCounter = 0; - m_preroll = preroll < PREROLL_MINIMUM ? PREROLL_MINIMUM : preroll; - m_count = 0; - m_deckLinkOutput->BeginAudioPreroll(); - m_frame = 0; - m_dropped = 0; - + + m_preroll = preroll; + m_reprio = false; + + // Set the running state + mlt_properties_set_int( properties, "running", 1 ); + + // Do preroll in thread to ensure asynchronicity of mlt_consumer_start(). + pthread_create( &m_prerollThread, NULL, preroll_thread_proxy, this ); + return true; } - - void wakeup() - { - pthread_mutex_lock( &m_mutex ); - pthread_cond_broadcast( &m_condition ); - pthread_mutex_unlock( &m_mutex ); - } - - void wait() - { - struct timeval tv; - struct timespec ts; - - gettimeofday( &tv, NULL ); - ts.tv_sec = tv.tv_sec + 1; - ts.tv_nsec = tv.tv_usec * 1000; - pthread_mutex_lock( &m_mutex ); - pthread_cond_timedwait( &m_condition, &m_mutex, &ts ); - pthread_mutex_unlock( &m_mutex ); - } - - void stop() + + bool stop() { + mlt_properties properties = MLT_CONSUMER_PROPERTIES( getConsumer() ); + bool wasRunning = !!mlt_properties_get_int( properties, "running" ); + + // set running state is 0 + mlt_properties_set_int( properties, "running", 0 ); + + if ( wasRunning ) + pthread_join( m_prerollThread, NULL ); + // Stop the audio and video output streams immediately if ( m_deckLinkOutput ) { @@ -255,145 +296,186 @@ public: m_deckLinkOutput->DisableAudioOutput(); m_deckLinkOutput->DisableVideoOutput(); } - while ( mlt_deque_count( m_videoFrameQ ) ) + + // release decklink frame + SAFE_RELEASE( m_decklinkFrame ); + + mlt_consumer_stopped( getConsumer() ); + + return true; + } + + void renderAudio( mlt_frame frame ) + { + mlt_audio_format format = mlt_audio_s16; + int frequency = bmdAudioSampleRate48kHz; + int samples = mlt_sample_calculator( m_fps, frequency, m_count ); + int16_t *pcm = 0; + + if ( !mlt_frame_get_audio( frame, (void**) &pcm, &format, &frequency, &m_channels, &samples ) ) { - m_videoFrame = (IDeckLinkMutableVideoFrame*) mlt_deque_pop_back( m_videoFrameQ ); - m_videoFrame->Release(); +#ifdef WIN32 +#define DECKLINK_UNSIGNED_FORMAT "%lu" + unsigned long written = 0; +#else +#define DECKLINK_UNSIGNED_FORMAT "%u" + uint32_t written = 0; +#endif + BMDTimeValue streamTime = m_count * frequency * m_duration / m_timescale; + m_deckLinkOutput->GetBufferedAudioSampleFrameCount( &written ); + if ( written > (m_preroll + 1) * samples ) + { + mlt_log_verbose( getConsumer(), "renderAudio: will flush " DECKLINK_UNSIGNED_FORMAT " audiosamples\n", written ); + m_deckLinkOutput->FlushBufferedAudioSamples(); + }; +#ifdef WIN32 + m_deckLinkOutput->ScheduleAudioSamples( pcm, samples, streamTime, frequency, (unsigned long*) &written ); +#else + m_deckLinkOutput->ScheduleAudioSamples( pcm, samples, streamTime, frequency, &written ); +#endif + + if ( written != (uint32_t) samples ) + mlt_log_verbose( getConsumer(), "renderAudio: samples=%d, written=" DECKLINK_UNSIGNED_FORMAT "\n", samples, written ); } - m_videoFrame = 0; - if ( m_fifo ) sample_fifo_close( m_fifo ); - mlt_frame_close( m_frame ); } - - void createFrame() + + bool createFrame( IDeckLinkMutableVideoFrame** decklinkFrame ) { - m_videoFrame = 0; + BMDPixelFormat format = m_isKeyer? bmdFormat8BitARGB : bmdFormat8BitYUV; + IDeckLinkMutableVideoFrame* frame = 0; + uint8_t *buffer = 0; + int stride = m_width * ( m_isKeyer? 4 : 2 ); + + *decklinkFrame = NULL; + // Generate a DeckLink video frame if ( S_OK != m_deckLinkOutput->CreateVideoFrame( m_width, m_height, - m_width * 2, bmdFormat8BitYUV, bmdFrameFlagDefault, &m_videoFrame ) ) + stride, format, bmdFrameFlagDefault, &frame ) ) { - mlt_log_verbose( &m_consumer, "Failed to create video frame\n" ); + mlt_log_verbose( getConsumer(), "Failed to create video frame\n" ); stop(); - return; + return false; } - + // Make the first line black for field order correction. - uint8_t *buffer = 0; - if ( S_OK == m_videoFrame->GetBytes( (void**) &buffer ) && buffer ) + if ( S_OK == frame->GetBytes( (void**) &buffer ) && buffer ) { - for ( int i = 0; i < m_width; i++ ) + if ( m_isKeyer ) + { + memset( buffer, 0, stride ); + } + else for ( int i = 0; i < m_width; i++ ) { *buffer++ = 128; *buffer++ = 16; } } - mlt_log_debug( &m_consumer, "created video frame\n" ); - mlt_deque_push_back( m_videoFrameQ, m_videoFrame ); + + *decklinkFrame = frame; + + return true; } - HRESULT render( mlt_frame frame ) + void renderVideo( mlt_frame frame ) { - HRESULT result = S_OK; - // Get the audio - double speed = mlt_properties_get_double( MLT_FRAME_PROPERTIES(frame), "_speed" ); - if ( speed == 1.0 ) + mlt_image_format format = m_isKeyer? mlt_image_rgb24a : mlt_image_yuv422; + uint8_t* image = 0; + int rendered = mlt_properties_get_int( MLT_FRAME_PROPERTIES(frame), "rendered"); + int height = m_height; + + if ( rendered && !mlt_frame_get_image( frame, &image, &format, &m_width, &height, 0 ) ) { - mlt_audio_format format = mlt_audio_s16; - int frequency = bmdAudioSampleRate48kHz; - int samples = mlt_sample_calculator( m_fps, frequency, m_count ); - int16_t *pcm = 0; - - if ( !mlt_frame_get_audio( frame, (void**) &pcm, &format, &frequency, &m_channels, &samples ) ) + uint8_t* buffer = 0; + int stride = m_width * ( m_isKeyer? 4 : 2 ); + + SAFE_RELEASE( m_decklinkFrame ); + if ( createFrame( &m_decklinkFrame ) ) + m_decklinkFrame->GetBytes( (void**) &buffer ); + + if ( buffer ) { - int count = samples; - - if ( !m_isPrerolling ) + int progressive = mlt_properties_get_int( MLT_FRAME_PROPERTIES( frame ), "progressive" ); + + // NTSC SDI is always 486 lines + if ( m_height == 486 && height == 480 ) { - uint32_t audioCount = 0; - uint32_t videoCount = 0; - - // Check for resync - m_deckLinkOutput->GetBufferedAudioSampleFrameCount( &audioCount ); - m_deckLinkOutput->GetBufferedVideoFrameCount( &videoCount ); - - // Underflow typically occurs during non-normal speed playback. - if ( audioCount < 1 || videoCount < 1 ) + // blank first 6 lines + if ( m_isKeyer ) { - // Upon switching to normal playback, buffer some frames faster than realtime. - mlt_log_info( &m_consumer, "buffer underrun: audio buf %u video buf %u frames\n", audioCount, videoCount ); - m_prerollCounter = 0; + memset( buffer, 0, stride * 6 ); + buffer += stride * 6; } - - // While rebuffering - if ( isBuffering() ) + else for ( int i = 0; i < m_width * 6; i++ ) { - // Only append audio to reach the ideal level and not overbuffer. - int ideal = ( m_preroll - 1 ) * bmdAudioSampleRate48kHz / m_fps; - int actual = m_fifo->used / m_channels + audioCount; - int diff = ideal / 2 - actual; - count = diff < 0 ? 0 : diff < count ? diff : count; + *buffer++ = 128; + *buffer++ = 16; } } - if ( count > 0 ) - sample_fifo_append( m_fifo, pcm, count * m_channels ); - } - } - - // Create video frames while pre-rolling - if ( m_isPrerolling ) - { - createFrame(); - if ( !m_videoFrame ) - { - mlt_log_error( &m_consumer, "failed to create video frame\n" ); - return S_FALSE; + if ( !m_isKeyer ) + { + // Normal non-keyer playout - needs byte swapping + if ( !progressive && m_displayMode->GetFieldDominance() == bmdUpperFieldFirst ) + // convert lower field first to top field first + swab2( (char*) image, (char*) buffer + stride, stride * ( height - 1 ) ); + else + swab2( (char*) image, (char*) buffer, stride * height ); + } + else if ( !mlt_properties_get_int( MLT_FRAME_PROPERTIES( frame ), "test_image" ) ) + { + // Normal keyer output + int y = height + 1; + uint32_t* s = (uint32_t*) image; + uint32_t* d = (uint32_t*) buffer; + + if ( !progressive && m_displayMode->GetFieldDominance() == bmdUpperFieldFirst ) + { + // Correct field order + height--; + y--; + d += m_width; + } + + // Need to relocate alpha channel RGBA => ARGB + while ( --y ) + { + int x = m_width + 1; + while ( --x ) + { + *d++ = ( *s << 8 ) | ( *s >> 24 ); + s++; + } + } + } + else + { + // Keying blank frames - nullify alpha + memset( buffer, 0, stride * height ); + } } } - - if ( mlt_properties_get_int( MLT_FRAME_PROPERTIES( frame ), "rendered") ) - { - // Close the previous frame and use the new one - mlt_frame_close( m_frame ); - m_frame = frame; - } - else - { - if ( !m_frame ) - m_frame = frame; - // Reuse the last frame - mlt_log_verbose( &m_consumer, "dropped video frame %u\n", ++m_dropped ); - } + if ( m_decklinkFrame ) + m_deckLinkOutput->ScheduleVideoFrame( m_decklinkFrame, m_count * m_duration, m_duration, m_timescale ); + + if ( !rendered ) + mlt_log_verbose( getConsumer(), "dropped video frame %u\n", ++m_dropped ); + } + + HRESULT render( mlt_frame frame ) + { + HRESULT result = S_OK; + + // Get the audio + double speed = mlt_properties_get_double( MLT_FRAME_PROPERTIES(frame), "_speed" ); + if ( m_isAudio && speed == 1.0 ) + renderAudio( frame ); // Get the video - mlt_image_format format = mlt_image_yuv422; - uint8_t* image = 0; - uint8_t* buffer = 0; - if ( !mlt_frame_get_image( m_frame, &image, &format, &m_width, &m_height, 0 ) ) - { - m_videoFrame = (IDeckLinkMutableVideoFrame*) mlt_deque_pop_back( m_videoFrameQ ); - m_videoFrame->GetBytes( (void**) &buffer ); - if ( m_displayMode->GetFieldDominance() == bmdUpperFieldFirst ) - // convert lower field first to top field first - swab( image, buffer + m_width * 2, m_width * ( m_height - 1 ) * 2 ); - else - swab( image, buffer, m_width * m_height * 2 ); - m_deckLinkOutput->ScheduleVideoFrame( m_videoFrame, m_count * m_duration, m_duration, m_timescale ); - mlt_deque_push_front( m_videoFrameQ, m_videoFrame ); - } + renderVideo( frame ); ++m_count; - // Check for end of pre-roll - if ( ++m_prerollCounter > m_preroll && m_isPrerolling ) - { - // Start audio and video output - m_deckLinkOutput->EndAudioPreroll(); - m_deckLinkOutput->StartScheduledPlayback( 0, m_timescale, 1.0 ); - m_isPrerolling = false; - } - return result; } - + // *** DeckLink API implementation of IDeckLinkVideoOutputCallback IDeckLinkAudioOutputCallback *** // // IUnknown needs only a dummy implementation @@ -403,94 +485,122 @@ public: { return 1; } virtual ULONG STDMETHODCALLTYPE Release() { return 1; } - + /************************* DeckLink API Delegate Methods *****************************/ - + virtual HRESULT STDMETHODCALLTYPE ScheduledFrameCompleted( IDeckLinkVideoFrame* completedFrame, BMDOutputFrameCompletionResult completed ) { + if( !m_reprio ) + { + mlt_properties properties = MLT_CONSUMER_PROPERTIES( getConsumer() ); + + if ( mlt_properties_get( properties, "priority" ) ) + { + int r; + pthread_t thread; + pthread_attr_t tattr; + struct sched_param param; + + pthread_attr_init(&tattr); + pthread_attr_setschedpolicy(&tattr, SCHED_FIFO); + + if ( !strcmp( "max", mlt_properties_get( properties, "priority" ) ) ) + param.sched_priority = sched_get_priority_max(SCHED_FIFO) - 1; + else if ( !strcmp( "min", mlt_properties_get( properties, "priority" ) ) ) + param.sched_priority = sched_get_priority_min(SCHED_FIFO) + 1; + else + param.sched_priority = mlt_properties_get_int( properties, "priority" ); + + pthread_attr_setschedparam(&tattr, ¶m); + + thread = pthread_self(); + + r = pthread_setschedparam(thread, SCHED_FIFO, ¶m); + if( r ) + mlt_log_verbose( getConsumer(), + "ScheduledFrameCompleted: pthread_setschedparam returned %d\n", r); + else + mlt_log_verbose( getConsumer(), + "ScheduledFrameCompleted: param.sched_priority=%d\n", param.sched_priority); + }; + + m_reprio = true; + }; + +#ifdef WIN32 + unsigned long cnt; +#else + uint32_t cnt; +#endif + m_deckLinkOutput->GetBufferedAudioSampleFrameCount( &cnt ); + if ( cnt != m_acnt ) + { + mlt_log_debug( getConsumer(), + "ScheduledFrameCompleted: GetBufferedAudioSampleFrameCount %u -> " DECKLINK_UNSIGNED_FORMAT + ", m_count=%"PRIu64"\n", m_acnt, cnt, m_count ); + m_acnt = cnt; + } + // When a video frame has been released by the API, schedule another video frame to be output - wakeup(); - - return S_OK; - } - virtual HRESULT STDMETHODCALLTYPE ScheduledPlaybackHasStopped() - { - return mlt_consumer_is_stopped( &m_consumer ) ? S_FALSE : S_OK; - } - - virtual HRESULT STDMETHODCALLTYPE RenderAudioSamples( bool preroll ) - { - // Provide more audio samples to the DeckLink API - HRESULT result = S_OK; + // ignore handler if frame was flushed + if ( bmdOutputFrameFlushed == completed ) + return S_OK; - uint32_t count = m_fifo->used / m_channels; - uint32_t buffered = count; + // schedule next frame + ScheduleNextFrame( false ); - if ( count - // Stay under preferred buffer level - && ( S_OK == m_deckLinkOutput->GetBufferedAudioSampleFrameCount( &buffered ) ) - && buffered < m_maxAudioBuffer ) + // step forward frames counter if underrun + if ( bmdOutputFrameDisplayedLate == completed ) { - uint32_t written = 0; - - buffered = m_maxAudioBuffer - buffered; - count = buffered > count ? count : buffered; - result = m_deckLinkOutput->ScheduleAudioSamples( m_fifo->buffer, count, 0, 0, &written ); - if ( written ) - sample_fifo_remove( m_fifo, written * m_channels ); + mlt_log_verbose( getConsumer(), "ScheduledFrameCompleted: bmdOutputFrameDisplayedLate == completed\n" ); + m_count++; + } + if ( bmdOutputFrameDropped == completed ) + { + mlt_log_verbose( getConsumer(), "ScheduledFrameCompleted: bmdOutputFrameDropped == completed\n" ); + m_count++; } - return result; + return S_OK; } -}; - -/** The main thread. - */ -static void *run( void *arg ) -{ - // Map the argument to the object - DeckLinkConsumer* decklink = (DeckLinkConsumer*) arg; - mlt_consumer consumer = decklink->getConsumer(); - - // Get the properties - mlt_properties properties = MLT_CONSUMER_PROPERTIES( consumer ); + virtual HRESULT STDMETHODCALLTYPE ScheduledPlaybackHasStopped() + { + return mlt_consumer_is_stopped( getConsumer() ) ? S_FALSE : S_OK; + } - // Convenience functionality - int terminate_on_pause = mlt_properties_get_int( properties, "terminate_on_pause" ); - int terminated = 0; - // Frame and size - mlt_frame frame = NULL; - - // Loop while running - while ( !terminated && mlt_properties_get_int( properties, "running" ) ) + void ScheduleNextFrame( bool preroll ) { - // Get the frame - frame = mlt_consumer_rt_frame( consumer ); + // get the consumer + mlt_consumer consumer = getConsumer(); - // Check for termination - if ( terminate_on_pause && frame ) - terminated = mlt_properties_get_double( MLT_FRAME_PROPERTIES( frame ), "_speed" ) == 0.0; + // Get the properties + mlt_properties properties = MLT_CONSUMER_PROPERTIES( consumer ); - // Check that we have a frame to work with - if ( frame ) + // Frame and size + mlt_frame frame = NULL; + + if( mlt_properties_get_int( properties, "running" ) || preroll ) { - decklink->render( frame ); - if ( !decklink->isBuffering() ) - decklink->wait(); - mlt_events_fire( properties, "consumer-frame-show", frame, NULL ); - } - } + frame = mlt_consumer_rt_frame( consumer ); + if ( frame != NULL ) + { + render( frame ); - // Indicate that the consumer is stopped - decklink->stop(); - mlt_properties_set_int( properties, "running", 0 ); - mlt_consumer_stopped( consumer ); + mlt_events_fire( properties, "consumer-frame-show", frame, NULL ); - return NULL; -} + // terminate on pause + if ( m_terminate_on_pause && + mlt_properties_get_double( MLT_FRAME_PROPERTIES( frame ), "_speed" ) == 0.0 ) + stop(); + + mlt_frame_close( frame ); + } + } + } +}; /** Start the consumer. */ @@ -500,25 +610,7 @@ static int start( mlt_consumer consumer ) // Get the properties mlt_properties properties = MLT_CONSUMER_PROPERTIES( consumer ); DeckLinkConsumer* decklink = (DeckLinkConsumer*) consumer->child; - int result = decklink->start( mlt_properties_get_int( properties, "preroll" ) ) ? 0 : 1; - - // Check that we're not already running - if ( !result && !mlt_properties_get_int( properties, "running" ) ) - { - // Allocate a thread - pthread_t *pthread = (pthread_t*) calloc( 1, sizeof( pthread_t ) ); - - // Assign the thread to properties - mlt_properties_set_data( properties, "pthread", pthread, sizeof( pthread_t ), free, NULL ); - - // Set the running state - mlt_properties_set_int( properties, "running", 1 ); - mlt_properties_set_int( properties, "joined", 0 ); - - // Create the thread - pthread_create( pthread, NULL, run, consumer->child ); - } - return result; + return decklink->start( mlt_properties_get_int( properties, "preroll" ) ) ? 0 : 1; } /** Stop the consumer. @@ -527,26 +619,8 @@ static int start( mlt_consumer consumer ) static int stop( mlt_consumer consumer ) { // Get the properties - mlt_properties properties = MLT_CONSUMER_PROPERTIES( consumer ); - - // Check that we're running - if ( !mlt_properties_get_int( properties, "joined" ) ) - { - // Get the thread - pthread_t *pthread = (pthread_t*) mlt_properties_get_data( properties, "pthread", NULL ); - - if ( pthread ) - { - // Stop the thread - mlt_properties_set_int( properties, "running", 0 ); - mlt_properties_set_int( properties, "joined", 1 ); - - // Wait for termination - pthread_join( *pthread, NULL ); - } - } - - return 0; + DeckLinkConsumer* decklink = (DeckLinkConsumer*) consumer->child; + return decklink->stop(); } /** Determine if the consumer is stopped. @@ -577,6 +651,53 @@ static void close( mlt_consumer consumer ) extern "C" { +// Listen for the list_devices property to be set +static void on_property_changed( void*, mlt_properties properties, const char *name ) +{ + IDeckLinkIterator* decklinkIterator = NULL; + IDeckLink* decklink = NULL; + IDeckLinkInput* decklinkOutput = NULL; + int i = 0; + + if ( name && !strcmp( name, "list_devices" ) ) + mlt_event_block( (mlt_event) mlt_properties_get_data( properties, "list-devices-event", NULL ) ); + else + return; + +#ifdef WIN32 + if ( FAILED( CoInitialize( NULL ) ) ) + return; + if ( FAILED( CoCreateInstance( CLSID_CDeckLinkIterator, NULL, CLSCTX_ALL, IID_IDeckLinkIterator, (void**) &decklinkIterator ) ) ) + return; +#else + if ( !( decklinkIterator = CreateDeckLinkIteratorInstance() ) ) + return; +#endif + for ( ; decklinkIterator->Next( &decklink ) == S_OK; i++ ) + { + if ( decklink->QueryInterface( IID_IDeckLinkOutput, (void**) &decklinkOutput ) == S_OK ) + { + DLString name = NULL; + if ( decklink->GetModelName( &name ) == S_OK ) + { + char *name_cstr = getCString( name ); + const char *format = "device.%d"; + char *key = (char*) calloc( 1, strlen( format ) + 1 ); + + sprintf( key, format, i ); + mlt_properties_set( properties, key, name_cstr ); + free( key ); + freeDLString( name ); + freeCString( name_cstr ); + } + SAFE_RELEASE( decklinkOutput ); + } + SAFE_RELEASE( decklink ); + } + SAFE_RELEASE( decklinkIterator ); + mlt_properties_set_int( properties, "devices", i ); +} + /** Initialise the consumer. */ @@ -593,12 +714,17 @@ mlt_consumer consumer_decklink_init( mlt_profile profile, mlt_service_type type, if ( decklink->open( arg? atoi(arg) : 0 ) ) { consumer = decklink->getConsumer(); - + mlt_properties properties = MLT_CONSUMER_PROPERTIES( consumer ); + // Setup callbacks consumer->close = close; consumer->start = start; consumer->stop = stop; consumer->is_stopped = is_stopped; + mlt_properties_set( properties, "deinterlace_method", "onefield" ); + + mlt_event event = mlt_events_listen( properties, properties, "property-changed", (mlt_listener) on_property_changed ); + mlt_properties_set_data( properties, "list-devices-event", event, 0, NULL, NULL ); } } @@ -606,10 +732,33 @@ mlt_consumer consumer_decklink_init( mlt_profile profile, mlt_service_type type, return consumer; } +extern mlt_producer producer_decklink_init( mlt_profile profile, mlt_service_type type, const char *id, char *arg ); + +static mlt_properties metadata( mlt_service_type type, const char *id, void *data ) +{ + char file[ PATH_MAX ]; + const char *service_type = NULL; + switch ( type ) + { + case consumer_type: + service_type = "consumer"; + break; + case producer_type: + service_type = "producer"; + break; + default: + return NULL; + } + snprintf( file, PATH_MAX, "%s/decklink/%s_%s.yml", mlt_environment( "MLT_DATA" ), service_type, id ); + return mlt_properties_parse_yaml( file ); +} MLT_REPOSITORY { MLT_REGISTER( consumer_type, "decklink", consumer_decklink_init ); + MLT_REGISTER( producer_type, "decklink", producer_decklink_init ); + MLT_REGISTER_METADATA( consumer_type, "decklink", metadata, NULL ); + MLT_REGISTER_METADATA( producer_type, "decklink", metadata, NULL ); } } // extern C