Hi,
i recently did a tests against new decklink board (decklink quad2) that
works with newer driver (blackmagic-io.ko) and found some issues:
- ScheduleVideoFrame stop working in video-callback after the thread
driver initialized get finished.
- ScheduleAudioSamples sometimes cause in kernel driver warnings then
called from ScheduledFrameCompleted.
- on some motherboards ScheduleAudioSamples cause a stalls for a
40miliseconds then it called from ScheduledFrameCompleted
so i did a rework of current consumer_decklink.cpp
it contains a lot of changes so the question: should it be sent as patch
or it would be better to create something like *decklink2_consumer.cpp*
--
Maksym Veremeyenko
/*
* consumer_decklink.cpp -- output through Blackmagic Design DeckLink
* Copyright (C) 2010-2015 Dan Dennedy <d...@dennedy.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with consumer library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#define __STDC_FORMAT_MACROS /* see inttypes.h */
#include <framework/mlt.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/time.h>
#include <limits.h>
#include <pthread.h>
#include "common.h"
static const unsigned PREROLL_MINIMUM = 3;
enum
{
OP_NONE = 0,
OP_OPEN,
OP_START,
OP_STOP,
OP_EXIT
};
class DeckLinkConsumer
: public IDeckLinkVideoOutputCallback
, public IDeckLinkAudioOutputCallback
{
private:
mlt_consumer_s m_consumer;
IDeckLink* m_deckLink;
IDeckLinkOutput* m_deckLinkOutput;
IDeckLinkDisplayMode* m_displayMode;
int m_width;
int m_height;
BMDTimeValue m_duration;
BMDTimeScale m_timescale;
double m_fps;
uint64_t m_count;
int m_channels;
bool m_isAudio;
int m_isKeyer;
IDeckLinkKeyer* m_deckLinkKeyer;
bool m_terminate_on_pause;
uint32_t m_preroll;
uint32_t m_acnt;
uint32_t m_reprio;
mlt_deque m_aqueue;
mlt_deque m_frames;
pthread_mutex_t m_op_lock;
pthread_mutex_t m_op_arg_mutex;
pthread_cond_t m_op_arg_cond;
int m_op_id;
int m_op_res;
int m_op_arg;
pthread_t m_op_thread;
IDeckLinkDisplayMode* getDisplayMode()
{
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 )
{
m_width = mode->GetWidth();
m_height = mode->GetHeight();
mode->GetFrameRate( &m_duration, &m_timescale );
m_fps = (double) m_timescale / m_duration;
int p = mode->GetFieldDominance() ==
bmdProgressiveFrame;
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; }
DeckLinkConsumer()
{
pthread_mutexattr_t mta;
m_displayMode = NULL;
m_deckLinkKeyer = NULL;
m_deckLinkOutput = NULL;
m_deckLink = NULL;
m_aqueue = mlt_deque_init();
m_frames = mlt_deque_init();
// operation locks
m_op_id = OP_NONE;
m_op_arg = NULL;
pthread_mutexattr_init( &mta );
pthread_mutexattr_settype( &mta, PTHREAD_MUTEX_RECURSIVE );
pthread_mutex_init( &m_op_lock, &mta );
pthread_mutex_init( &m_op_arg_mutex, &mta );
pthread_mutexattr_destroy( &mta );
pthread_cond_init( &m_op_arg_cond, NULL );
pthread_create( &m_op_thread, NULL, op_main, this );
}
virtual ~DeckLinkConsumer()
{
mlt_log_verbose( getConsumer(), "%s:%s: entering\n", __FILE__,
__FUNCTION__ );
SAFE_RELEASE( m_displayMode );
SAFE_RELEASE( m_deckLinkKeyer );
SAFE_RELEASE( m_deckLinkOutput );
SAFE_RELEASE( m_deckLink );
mlt_deque_close( m_aqueue );
mlt_deque_close( m_frames );
op(OP_EXIT, 0);
mlt_log_verbose( getConsumer(), "%s:%s: waiting for op
thread\n", __FILE__, __FUNCTION__ );
pthread_join(m_op_thread, NULL);
mlt_log_verbose( getConsumer(), "%s:%s: finished op thread\n",
__FILE__, __FUNCTION__ );
pthread_mutex_destroy(&m_op_lock);
pthread_mutex_destroy(&m_op_arg_mutex);
pthread_cond_destroy(&m_op_arg_cond);
mlt_log_verbose( getConsumer(), "%s:%s: exiting\n", __FILE__,
__FUNCTION__ );
}
int op(int op_id, int arg)
{
int r;
// lock operation mutex
pthread_mutex_lock(&m_op_lock);
mlt_log_verbose( getConsumer(), "%s:%s: op_id=%d\n", __FILE__,
__FUNCTION__, op_id );
// notify op id
pthread_mutex_lock(&m_op_arg_mutex);
m_op_id = op_id;
m_op_arg = arg;
pthread_cond_signal(&m_op_arg_cond);
pthread_mutex_unlock(&m_op_arg_mutex);
// wait op done
pthread_mutex_lock(&m_op_arg_mutex);
while(OP_NONE != m_op_id)
pthread_cond_wait(&m_op_arg_cond, &m_op_arg_mutex);
pthread_mutex_unlock(&m_op_arg_mutex);
// save result
r = m_op_res;
mlt_log_error( getConsumer(), "%s:%s: r=%d\n", __FILE__,
__FUNCTION__, r );
// unlock operation mutex
pthread_mutex_unlock(&m_op_lock);
return r;
}
protected:
static void* op_main(void* thisptr)
{
DeckLinkConsumer* d = static_cast<DeckLinkConsumer*>(thisptr);
mlt_log_verbose( d->getConsumer(), "%s:%s: entering\n",
__FILE__, __FUNCTION__ );
while(1)
{
int o, r;
// wait op command
pthread_mutex_lock ( &d->m_op_arg_mutex );
while ( OP_NONE == d->m_op_id )
pthread_cond_wait( &d->m_op_arg_cond,
&d->m_op_arg_mutex );
pthread_mutex_unlock( &d->m_op_arg_mutex );
o = d->m_op_id;
mlt_log_verbose( d->getConsumer(), "%s:%s:%d
d->m_op_id=%d\n", __FILE__, __FUNCTION__, __LINE__, d->m_op_id );
switch ( d->m_op_id )
{
case OP_OPEN:
r = d->m_op_res = d->open( d->m_op_arg
);
break;
case OP_START:
r = d->m_op_res = d->start( d->m_op_arg
);
break;
case OP_STOP:
r = d->m_op_res = d->stop();
break;
};
// notify op done
pthread_mutex_lock( &d->m_op_arg_mutex );
d->m_op_id = OP_NONE;
pthread_cond_signal( &d->m_op_arg_cond );
pthread_mutex_unlock( &d->m_op_arg_mutex );
// post for async
if ( OP_START == o && r )
d->preroll();
if ( OP_EXIT == o )
{
mlt_log_verbose( d->getConsumer(), "%s:%s:
exiting\n", __FILE__, __FUNCTION__ );
return NULL;
}
};
return NULL;
}
bool open( unsigned card = 0 )
{
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_error( getConsumer(), "The DeckLink drivers not
installed.\n" );
return false;
}
#endif
// Connect to the Nth DeckLink instance
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_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 );
return true;
}
int preroll()
{
mlt_properties properties = MLT_CONSUMER_PROPERTIES(
getConsumer() );
mlt_log_verbose( getConsumer(), "%s:%s: starting\n", __FILE__,
__FUNCTION__ );
if ( !mlt_properties_get_int( properties, "running" ) )
return 0;
mlt_log_verbose( getConsumer(), "%s:%s: m_preroll=%dth\n",
__FILE__, __FUNCTION__, m_preroll );
// preroll frames
for ( unsigned i = 0; i < m_preroll ; i++ )
ScheduleNextFrame( true );
// start audio preroll
if ( m_isAudio )
m_deckLinkOutput->BeginAudioPreroll( );
else
m_deckLinkOutput->StartScheduledPlayback( 0,
m_timescale, 1.0 );
mlt_log_verbose( getConsumer(), "%s:%s: exiting\n", __FILE__,
__FUNCTION__ );
return 0;
}
bool start( unsigned preroll )
{
mlt_properties properties = MLT_CONSUMER_PROPERTIES(
getConsumer() );
// Initialize members
m_count = 0;
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( 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 )
(BMDVideoOutputFlags) (bmdVideoOutputFlagDefault |
bmdVideoOutputRP188 | bmdVideoOutputVITC) ) )
{
mlt_log_error( getConsumer(), "Failed to enable video
output\n" );
return false;
}
// Set the audio output mode
if ( m_isAudio && S_OK != m_deckLinkOutput->EnableAudioOutput(
bmdAudioSampleRate48kHz, bmdAudioSampleType16bitInteger,
m_channels, bmdAudioOutputStreamTimestamped ) )
{
mlt_log_error( getConsumer(), "Failed to enable audio
output\n" );
stop();
return false;
}
m_preroll = preroll;
m_reprio = 2;
for ( unsigned i = 0; i < ( m_preroll + 2 ) ; i++)
{
IDeckLinkMutableVideoFrame* frame;
// Generate a DeckLink video frame
if ( S_OK != m_deckLinkOutput->CreateVideoFrame(
m_width, m_height,
m_width * ( m_isKeyer? 4 : 2 ), m_isKeyer?
bmdFormat8BitARGB : bmdFormat8BitYUV, bmdFrameFlagDefault, &frame ) )
{
mlt_log_error( getConsumer(), "%s:
CreateVideoFrame (%d) failed\n", __FUNCTION__, i );
return false;
}
mlt_deque_push_back( m_frames,
reinterpret_cast<IDeckLinkMutableVideoFrame*>(frame) );
}
// Set the running state
mlt_properties_set_int( properties, "running", 1 );
return true;
}
bool stop()
{
mlt_properties properties = MLT_CONSUMER_PROPERTIES(
getConsumer() );
mlt_log_verbose( getConsumer(), "%s:%s: starting\n", __FILE__,
__FUNCTION__ );
// Stop the audio and video output streams immediately
if ( m_deckLinkOutput )
{
m_deckLinkOutput->StopScheduledPlayback( 0, 0, 0 );
m_deckLinkOutput->DisableAudioOutput();
m_deckLinkOutput->DisableVideoOutput();
}
while ( mlt_frame frame = (mlt_frame) mlt_deque_pop_back(
m_aqueue ) )
mlt_frame_close( frame );
while ( IDeckLinkMutableVideoFrame* frame =
(IDeckLinkMutableVideoFrame*) mlt_deque_pop_back( m_frames ) )
SAFE_RELEASE( frame );
// set running state is 0
mlt_properties_set_int( properties, "running", 0 );
mlt_consumer_stopped( getConsumer() );
mlt_log_verbose( getConsumer(), "%s:%s: exiting\n", __FILE__,
__FUNCTION__ );
return true;
}
void renderAudio( mlt_frame frame )
{
mlt_properties properties;
properties = MLT_FRAME_PROPERTIES( frame );
mlt_properties_set_int64( properties, "m_count", m_count);
mlt_properties_inc_ref( properties );
mlt_deque_push_back( m_aqueue, frame );
mlt_log_debug( NULL, "%s:%d frame=%p, len=%d\n", __FUNCTION__,
__LINE__, frame, mlt_deque_count( m_aqueue ));
}
bool createFrame( IDeckLinkMutableVideoFrame** decklinkFrame )
{
uint8_t *buffer = 0;
int stride = m_width * ( m_isKeyer? 4 : 2 );
IDeckLinkMutableVideoFrame* frame =
(IDeckLinkMutableVideoFrame*)mlt_deque_pop_front( m_frames );;
*decklinkFrame = frame;
// 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;
}
}
return true;
}
void renderVideo( mlt_frame frame )
{
HRESULT hr;
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;
IDeckLinkMutableVideoFrame* m_decklinkFrame = NULL;
mlt_log_debug( NULL, "%s:%s: entering\n", __FILE__,
__FUNCTION__ );
if ( rendered && !mlt_frame_get_image( frame, &image, &format,
&m_width, &height, 0 ) )
{
uint8_t* buffer = 0;
int stride = m_width * ( m_isKeyer? 4 : 2 );
if ( createFrame( &m_decklinkFrame ) )
m_decklinkFrame->GetBytes( (void**) &buffer );
if ( buffer )
{
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
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 ( m_decklinkFrame )
{
char* vitc;
// set timecode
vitc = mlt_properties_get( MLT_FRAME_PROPERTIES( frame
), "meta.attr.vitc.markup" );
if( vitc )
{
int h, m, s, f;
if ( 4 == sscanf( vitc, "%d:%d:%d:%d", &h, &m,
&s, &f ) )
m_decklinkFrame->SetTimecodeFromComponents(bmdTimecodeVITC,
h, m, s, f,
bmdTimecodeFlagDefault);
}
// set userbits
vitc = mlt_properties_get( MLT_FRAME_PROPERTIES( frame
), "meta.attr.vitc.userbits" );
if( vitc )
m_decklinkFrame->SetTimecodeUserBits(bmdTimecodeVITC,
mlt_properties_get_int(
MLT_FRAME_PROPERTIES( frame ), "meta.attr.vitc.userbits" ));
hr = m_deckLinkOutput->ScheduleVideoFrame(
m_decklinkFrame, m_count * m_duration, m_duration, m_timescale );
if ( S_OK != hr )
mlt_log_error( NULL, "%s:%s:%d:
ScheduleVideoFrame failed, hr=%.8X \n", __FILE__, __FUNCTION__, __LINE__, hr);
else
mlt_log_debug( NULL, "%s:%s: ScheduleVideoFrame
SUCCESS\n", __FILE__, __FUNCTION__ );
}
}
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;
}
void reprio( int target )
{
int r;
pthread_t thread;
pthread_attr_t tattr;
struct sched_param param;
mlt_properties properties;
if( m_reprio & target )
return;
m_reprio |= target;
properties = MLT_CONSUMER_PROPERTIES( getConsumer() );
if ( !mlt_properties_get( properties, "priority" ) )
return;
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_error( getConsumer(),
"%s:%s: [%d] pthread_setschedparam returned
%d\n", __FILE__, __FUNCTION__, target, r);
else
mlt_log_verbose( getConsumer(),
"%s:%s: [%d] param.sched_priority=%d\n",
__FILE__, __FUNCTION__, target, param.sched_priority);
};
// *** DeckLink API implementation of IDeckLinkVideoOutputCallback
IDeckLinkAudioOutputCallback *** //
// IUnknown needs only a dummy implementation
virtual HRESULT STDMETHODCALLTYPE QueryInterface( REFIID iid, LPVOID
*ppv )
{ return E_NOINTERFACE; }
virtual ULONG STDMETHODCALLTYPE AddRef()
{ return 1; }
virtual ULONG STDMETHODCALLTYPE Release()
{ return 1; }
/************************* DeckLink API Delegate Methods
*****************************/
virtual HRESULT STDMETHODCALLTYPE RenderAudioSamples ( bool preroll )
{
mlt_log_debug( NULL, "%s:%s: ENTERING preroll=%d, len=%d\n",
__FILE__, __FUNCTION__, (int)preroll, mlt_deque_count( m_aqueue ));
mlt_frame frame = (mlt_frame) mlt_deque_pop_front( m_aqueue );
reprio( 2 );
if ( frame )
{
mlt_properties properties = MLT_FRAME_PROPERTIES( frame
);
uint64_t m_count = mlt_properties_get_int64(
properties, "m_count" );
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 ) )
{
HRESULT hr;
mlt_log_debug( NULL, "%s:%s:%d, samples=%d,
channels=%d, freq=%d\n",
__FILE__, __FUNCTION__, __LINE__,
samples, m_channels, frequency );
#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 );
mlt_log_debug( NULL, "%s:%s:%d
GetBufferedAudioSampleFrameCount=%d\n",
__FILE__, __FUNCTION__, __LINE__,
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
hr = m_deckLinkOutput->ScheduleAudioSamples(
pcm, samples, streamTime, frequency, (unsigned long*) &written );
#else
hr = m_deckLinkOutput->ScheduleAudioSamples(
pcm, samples, streamTime, frequency, &written );
#endif
if ( S_OK != hr )
mlt_log_error( NULL, "%s:%s:%d
ScheduleAudioSamples failed, hr=%.8X \n", __FILE__, __FUNCTION__, __LINE__, hr
);
else
mlt_log_debug( NULL, "%s:%s:%d
ScheduleAudioSamples success %d samples\n", __FILE__, __FUNCTION__, __LINE__,
written );
if ( written != (uint32_t) samples )
mlt_log_verbose( getConsumer(),
"renderAudio: samples=%d, written=" DECKLINK_UNSIGNED_FORMAT "\n", samples,
written );
}
else
mlt_log_error( NULL, "%s:%d mlt_frame_get_audio
failed\n", __FUNCTION__, __LINE__);
mlt_frame_close( frame );
}
if ( preroll )
m_deckLinkOutput->StartScheduledPlayback( 0,
m_timescale, 1.0 );
return S_OK;
}
virtual HRESULT STDMETHODCALLTYPE ScheduledFrameCompleted(
IDeckLinkVideoFrame* completedFrame, BMDOutputFrameCompletionResult completed )
{
mlt_log_debug( NULL, "%s:%s: ENTERING\n", __FILE__,
__FUNCTION__ );
mlt_deque_push_back( m_frames,
reinterpret_cast/*dynamic_cast*/<IDeckLinkMutableVideoFrame*>(completedFrame) );
// change priority of video callback thread
reprio( 1 );
// When a video frame has been released by the API, schedule
another video frame to be output
// ignore handler if frame was flushed
if ( bmdOutputFrameFlushed == completed )
return S_OK;
// schedule next frame
ScheduleNextFrame( false );
// 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++;
}
return S_OK;
}
virtual HRESULT STDMETHODCALLTYPE ScheduledPlaybackHasStopped()
{
return mlt_consumer_is_stopped( getConsumer() ) ? S_FALSE :
S_OK;
}
void ScheduleNextFrame( bool preroll )
{
// get the consumer
mlt_consumer consumer = getConsumer();
// Get the properties
mlt_properties properties = MLT_CONSUMER_PROPERTIES( consumer );
// Frame and size
mlt_frame frame = NULL;
mlt_log_debug( getConsumer(), "%s:%s:%d: preroll=%d\n",
__FILE__, __FUNCTION__, __LINE__, preroll);
if( mlt_properties_get_int( properties, "running" ) || preroll )
{
frame = mlt_consumer_rt_frame( consumer );
if ( frame != NULL )
{
render( frame );
mlt_events_fire( properties,
"consumer-frame-show", frame, 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 );
}
else
mlt_log_error( NULL, "%s:%s:
mlt_consumer_rt_frame return NULL\n", __FILE__, __FUNCTION__ );
}
}
};
/** Start the consumer.
*/
static int start( mlt_consumer consumer )
{
// Get the properties
mlt_properties properties = MLT_CONSUMER_PROPERTIES( consumer );
DeckLinkConsumer* decklink = (DeckLinkConsumer*) consumer->child;
return decklink->op( OP_START, mlt_properties_get_int( properties,
"preroll" ) ) ? 0 : 1;
}
/** Stop the consumer.
*/
static int stop( mlt_consumer consumer )
{
int r;
mlt_log_verbose( NULL, "%s:%s: entering\n", __FILE__, __FUNCTION__ );
// Get the properties
DeckLinkConsumer* decklink = (DeckLinkConsumer*) consumer->child;
r = decklink->op(OP_STOP, 0);
mlt_log_verbose( NULL, "%s:%s: exiting\n", __FILE__, __FUNCTION__ );
return r;
}
/** Determine if the consumer is stopped.
*/
static int is_stopped( mlt_consumer consumer )
{
// Get the properties
mlt_properties properties = MLT_CONSUMER_PROPERTIES( consumer );
return !mlt_properties_get_int( properties, "running" );
}
/** Close the consumer.
*/
static void close( mlt_consumer consumer )
{
mlt_log_verbose( NULL, "%s:%s: entering\n", __FILE__, __FUNCTION__ );
// Stop the consumer
mlt_consumer_stop( consumer );
// Close the parent
consumer->close = NULL;
mlt_consumer_close( consumer );
// Free the memory
delete (DeckLinkConsumer*) consumer->child;
mlt_log_verbose( NULL, "%s:%s: exiting\n", __FILE__, __FUNCTION__ );
}
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.
*/
mlt_consumer consumer_decklink_init( mlt_profile profile, mlt_service_type
type, const char *id, char *arg )
{
// Allocate the consumer
DeckLinkConsumer* decklink = new DeckLinkConsumer();
mlt_consumer consumer = NULL;
// If allocated
if ( decklink && !mlt_consumer_init( decklink->getConsumer(), decklink,
profile ) )
{
// If initialises without error
if ( decklink->op( OP_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
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
------------------------------------------------------------------------------
Mobile security can be enabling, not merely restricting. Employees who
bring their own devices (BYOD) to work are irked by the imposition of MDM
restrictions. Mobile Device Manager Plus allows you to control only the
apps on BYO-devices by containerizing them, leaving personal data untouched!
https://ad.doubleclick.net/ddm/clk/304595813;131938128;j
_______________________________________________
Mlt-devel mailing list
Mlt-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/mlt-devel