* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
+#define __STDC_FORMAT_MACROS /* see inttypes.h */
#include <framework/mlt.h>
-#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
-#include "DeckLinkAPI.h"
-
-const unsigned PREROLL_CUTOFF = 2;
-
-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 <sys/time.h>
+#include <limits.h>
+#include <pthread.h>
+#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;
- bool m_preroll;
int m_channels;
- uint32_t m_maxAudioBuffer;
- unsigned m_prerollCount;
-
+ 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 )
mode->GetFrameRate( &m_duration, &m_timescale );
m_fps = (double) m_timescale / m_duration;
int p = mode->GetFieldDominance() == bmdProgressiveFrame;
- fprintf(stderr, "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
+ && m_fps == 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 isPreroll() const
- { return m_prerollCount <= PREROLL_CUTOFF; }
-
+
+ DeckLinkConsumer()
+ {
+ m_displayMode = NULL;
+ m_deckLinkKeyer = NULL;
+ m_deckLinkOutput = NULL;
+ m_deckLink = NULL;
+ m_decklinkFrame = NULL;
+ }
+
~DeckLinkConsumer()
{
- if ( m_deckLinkOutput )
- m_deckLinkOutput->Release();
- if ( m_deckLink )
- m_deckLink->Release();
+ 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;
-
+
return true;
}
-
- void start()
+
+
+ void* preroll_thread()
+ {
+ // preroll frames
+ for ( unsigned i = 0; i < m_preroll; i++ )
+ ScheduleNextFrame( true );
+
+ // start scheduled playback
+ 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();
-
- // Set the video output mode
- if ( S_OK != m_deckLinkOutput->EnableVideoOutput( m_displayMode->GetDisplayMode(), bmdVideoOutputFlagDefault) )
+ if ( !m_displayMode )
{
- mlt_log_verbose( &m_consumer, "Failed to enable video output\n" );
- return;
+ mlt_log_error( getConsumer(), "Profile is not compatible with decklink.\n" );
+ return false;
}
-
- // Generate a DeckLink video frame
- if ( S_OK != m_deckLinkOutput->CreateVideoFrame( m_width, m_height,
- m_width * 2, bmdFormat8BitYUV, bmdFrameFlagDefault, &m_videoFrame ) )
+
+ // Set the keyer
+ if ( m_deckLinkKeyer && ( m_isKeyer = mlt_properties_get_int( properties, "keyer" ) ) )
{
- mlt_log_verbose( &m_consumer, "Failed to create video frame\n" );
- stop();
- return;
+ 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 );
}
-
- // Make the first line black for field order correction.
- uint8_t *buffer = 0;
- if ( S_OK == m_videoFrame->GetBytes( (void**) &buffer ) && buffer )
+ else if ( m_deckLinkKeyer )
{
- for ( int i = 0; i < m_width; i++ )
- {
- *buffer++ = 128;
- *buffer++ = 16;
- }
+ m_deckLinkKeyer->Disable();
+ }
+
+ // Set the video output mode
+ if ( S_OK != m_deckLinkOutput->EnableVideoOutput( m_displayMode->GetDisplayMode(), bmdVideoOutputFlagDefault ) )
+ {
+ 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_verbose( &m_consumer, "Failed to enable audio output\n" );
+ mlt_log_error( getConsumer(), "Failed to enable audio output\n" );
stop();
- return;
+ return false;
}
- m_fifo = sample_fifo_init();
-
- // Preroll
- m_preroll = true;
- m_prerollCount = 0;
- m_count = 0;
- m_deckLinkOutput->BeginAudioPreroll();
- }
-
- void wakeup()
- {
- pthread_mutex_lock( &m_mutex );
- pthread_cond_broadcast( &m_condition );
- pthread_mutex_unlock( &m_mutex );
- }
-
- void wait()
- {
- pthread_mutex_lock( &m_mutex );
- pthread_cond_wait( &m_condition, &m_mutex );
- pthread_mutex_unlock( &m_mutex );
+
+ m_preroll = preroll;
+ m_reprio = false;
+
+ // Do preroll in thread to ensure asynchronicity of mlt_consumer_start().
+ pthread_create( &m_prerollThread, NULL, preroll_thread_proxy, this );
+
+ // Set the running state
+ mlt_properties_set_int( properties, "running", 1 );
+
+ return true;
}
-
- 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 );
+ mlt_consumer_stopped( getConsumer() );
+
// Stop the audio and video output streams immediately
if ( m_deckLinkOutput )
{
m_deckLinkOutput->DisableAudioOutput();
m_deckLinkOutput->DisableVideoOutput();
}
- if ( m_videoFrame ) m_videoFrame->Release();
- m_videoFrame = 0;
- if ( m_fifo ) sample_fifo_close( m_fifo );
+
+ // release decklink frame
+ SAFE_RELEASE( m_decklinkFrame );
+
+ if ( wasRunning )
+ pthread_join( m_prerollThread, NULL );
+
+ return true;
}
-
- HRESULT render( mlt_frame frame )
+
+ void renderAudio( 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_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 ) )
{
- 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 ) )
+#ifdef WIN32
+ unsigned long written = 0;
+#else
+ uint32_t written = 0;
+#endif
+ BMDTimeValue streamTime = m_count * frequency * m_duration / m_timescale;
+ m_deckLinkOutput->GetBufferedAudioSampleFrameCount( &written );
+ if ( written > (m_preroll + 1) * samples )
{
- int count = samples;
-
- if ( !m_preroll )
- {
- uint32_t audioCount = 0;
- uint32_t videoCount = 0;
-
- // Check for resync
- m_deckLinkOutput->GetBufferedAudioSampleFrameCount( &audioCount );
- m_deckLinkOutput->GetBufferedVideoFrameCount( &videoCount );
- mlt_log_debug( &m_consumer, "audio buf %u video buf %u frames\n", audioCount, videoCount );
-
- // Underflow typically occurs during non-normal speed playback.
- if ( audioCount < 1 || videoCount < 1 )
- // Upon switching to normal playback, buffer some frames faster than realtime.
- m_prerollCount = 0;
-
- // While rebuffering
- if ( m_prerollCount <= PREROLL_CUTOFF )
- {
- // Only append audio to reach the ideal level and not overbuffer.
- int ideal = ( PREROLL_CUTOFF - 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;
- }
- }
- if ( count > 0 )
- sample_fifo_append( m_fifo, pcm, count * m_channels );
+ mlt_log_verbose( getConsumer(), "renderAudio: will flush %lu 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=%lu\n", samples, written );
+ }
+ }
+
+ bool createFrame( IDeckLinkMutableVideoFrame** decklinkFrame )
+ {
+ 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,
+ stride, format, bmdFrameFlagDefault, &frame ) )
+ {
+ mlt_log_verbose( getConsumer(), "Failed to create video frame\n" );
+ stop();
+ return false;
+ }
+
+ // Make the first line black for field order correction.
+ if ( S_OK == frame->GetBytes( (void**) &buffer ) && buffer )
+ {
+ if ( m_isKeyer )
+ {
+ memset( buffer, 0, stride );
+ }
+ else for ( int i = 0; i < m_width; i++ )
+ {
+ *buffer++ = 128;
+ *buffer++ = 16;
}
}
- // Get the video
- if ( mlt_properties_get_int( MLT_FRAME_PROPERTIES( frame ), "rendered") )
+
+ *decklinkFrame = frame;
+
+ return true;
+ }
+
+ void renderVideo( mlt_frame frame )
+ {
+ 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_image_format format = mlt_image_yuv422;
- uint8_t* image = 0;
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 ( !mlt_frame_get_image( frame, &image, &format, &m_width, &m_height, 0 ) )
+ if ( buffer )
{
- 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 );
+ int progressive = mlt_properties_get_int( MLT_FRAME_PROPERTIES( frame ), "progressive" );
+
+ // NTSC SDI is always 486 lines
+ if ( m_height == 486 && height == 480 )
+ {
+ // blank first 6 lines
+ if ( m_isKeyer )
+ {
+ memset( buffer, 0, stride * 6 );
+ buffer += stride * 6;
+ }
+ else for ( int i = 0; i < m_width * 6; i++ )
+ {
+ *buffer++ = 128;
+ *buffer++ = 16;
+ }
+ }
+ if ( !m_isKeyer )
+ {
+ // Normal non-keyer playout - needs byte swapping
+ if ( !progressive && m_displayMode->GetFieldDominance() == bmdUpperFieldFirst )
+ // convert lower field first to top field first
+ swab( (char*) image, (char*) buffer + stride, stride * ( height - 1 ) );
+ else
+ swab( (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
- swab( image, buffer, m_width * m_height * 2 );
- result = m_deckLinkOutput->ScheduleVideoFrame( m_videoFrame, m_count * m_duration, m_duration, m_timescale );
+ {
+ // Keying blank frames - nullify alpha
+ memset( buffer, 0, stride * height );
+ }
}
}
- ++m_count;
+ if ( m_decklinkFrame )
+ m_deckLinkOutput->ScheduleVideoFrame( m_decklinkFrame, m_count * m_duration, m_duration, m_timescale );
- // Preroll handling
- if ( ++m_prerollCount > PREROLL_CUTOFF && m_preroll )
- {
- // Start audio and video output
- m_deckLinkOutput->EndAudioPreroll();
- m_deckLinkOutput->StartScheduledPlayback( 0, m_timescale, 1.0 );
- m_preroll = false;
- }
+ 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
+ renderVideo( frame );
+ ++m_count;
return result;
}
-
+
// *** DeckLink API implementation of IDeckLinkVideoOutputCallback IDeckLinkAudioOutputCallback *** //
// IUnknown needs only a dummy implementation
{ return 1; }
virtual ULONG STDMETHODCALLTYPE Release()
{ return 1; }
-
+
/************************* DeckLink API Delegate Methods *****************************/
-
+
virtual HRESULT STDMETHODCALLTYPE ScheduledFrameCompleted( IDeckLinkVideoFrame* completedFrame, BMDOutputFrameCompletionResult completed )
{
- // When a video frame has been released by the API, schedule another video frame to be output
- wakeup();
-
- return S_OK;
- }
+ if( !m_reprio )
+ {
+ mlt_properties properties = MLT_CONSUMER_PROPERTIES( getConsumer() );
- 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;
+ 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);
- uint32_t count = m_fifo->used / m_channels;
- uint32_t buffered = count;
+ thread = pthread_self();
- if ( count
- // Stay under preferred buffer level
- && ( S_OK == m_deckLinkOutput->GetBufferedAudioSampleFrameCount( &buffered ) )
- && buffered < m_maxAudioBuffer )
+ 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 )
{
- 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_debug( getConsumer(),
+ "ScheduledFrameCompleted: GetBufferedAudioSampleFrameCount %u -> %lu, m_count=%"PRIu64"\n",
+ m_acnt, cnt, m_count );
+ m_acnt = cnt;
}
- return result;
- }
-};
+ // When a video frame has been released by the API, schedule another video frame to be output
-/** The main thread.
- */
+ // ignore handler if frame was flushed
+ if ( bmdOutputFrameFlushed == completed )
+ return S_OK;
-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 );
+ // schedule next frame
+ ScheduleNextFrame( false );
- // Convenience functionality
- int terminate_on_pause = mlt_properties_get_int( properties, "terminate_on_pause" );
- int terminated = 0;
+ // step forward frames counter if underrun
+ if ( bmdOutputFrameDisplayedLate == completed )
+ {
+ mlt_log_verbose( getConsumer(), "ScheduledFrameCompleted: bmdOutputFrameDisplayedLate == completed\n" );
+ m_count++;
+ }
+ if ( bmdOutputFrameDropped == completed )
+ {
+ mlt_log_verbose( getConsumer(), "ScheduledFrameCompleted: bmdOutputFrameDropped == completed\n" );
+ m_count++;
+ }
- // Frame and size
- mlt_frame frame = NULL;
-
- decklink->start();
+ return S_OK;
+ }
+
+ virtual HRESULT STDMETHODCALLTYPE ScheduledPlaybackHasStopped()
+ {
+ return mlt_consumer_is_stopped( getConsumer() ) ? S_FALSE : S_OK;
+ }
- // 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();
+
+ // Get the properties
+ mlt_properties properties = MLT_CONSUMER_PROPERTIES( consumer );
- // Check for termination
- if ( terminate_on_pause && frame )
- terminated = mlt_properties_get_double( MLT_FRAME_PROPERTIES( frame ), "_speed" ) == 0.0;
+ // Frame and size
+ mlt_frame frame = NULL;
- // Check that we have a frame to work with
- if ( frame )
+ if( mlt_properties_get_int( properties, "running" ) || preroll )
{
- decklink->render( frame );
- if ( !decklink->isPreroll() )
- decklink->wait();
-
- // Close the frame
- mlt_events_fire( properties, "consumer-frame-show", frame, NULL );
- mlt_frame_close( frame );
- }
- }
+ frame = mlt_consumer_rt_frame( consumer );
+ if ( frame != NULL )
+ {
+ render( frame );
- // Indicate that the consumer is stopped
- 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.
*/
{
// Get the properties
mlt_properties properties = MLT_CONSUMER_PROPERTIES( consumer );
-
- // Check that we're not already running
- if ( !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 0;
+ DeckLinkConsumer* decklink = (DeckLinkConsumer*) consumer->child;
+ return decklink->start( mlt_properties_get_int( properties, "preroll" ) ) ? 0 : 1;
}
/** Stop the 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.
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.
*/
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 );
}
}
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