X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=src%2Fmodules%2Fdecklink%2Fproducer_decklink.cpp;h=877c0eacc400742e3ea686e92eb3b2613b74f94e;hb=307a2ef3af84a5dba60a5026a1a24f430a0e0bf2;hp=a0df7f335758d94e6eee98d0fb22d4de3adb4ad9;hpb=909fedd16817b534abca1c45a739426fd2d24559;p=mlt diff --git a/src/modules/decklink/producer_decklink.cpp b/src/modules/decklink/producer_decklink.cpp index a0df7f33..877c0eac 100644 --- a/src/modules/decklink/producer_decklink.cpp +++ b/src/modules/decklink/producer_decklink.cpp @@ -24,13 +24,13 @@ #include #include #include -#include "DeckLinkAPI.h" +#include "common.h" class DeckLinkProducer : public IDeckLinkInputCallback { private: - mlt_producer_s m_producer; + mlt_producer m_producer; IDeckLink* m_decklink; IDeckLinkInput* m_decklinkInput; mlt_deque m_queue; @@ -39,12 +39,17 @@ private: bool m_started; int m_dropped; bool m_isBuffering; + int m_topFieldFirst; + int m_colorspace; + int m_vancLines; + mlt_cache m_cache; + bool m_reprio; - BMDDisplayMode getDisplayMode( mlt_profile profile ) + BMDDisplayMode getDisplayMode( mlt_profile profile, int vancLines ) { - IDeckLinkDisplayModeIterator* iter; - IDeckLinkDisplayMode* mode; - BMDDisplayMode result = bmdDisplayModeNotSupported; + IDeckLinkDisplayModeIterator* iter = NULL; + IDeckLinkDisplayMode* mode = NULL; + BMDDisplayMode result = (BMDDisplayMode) bmdDisplayModeNotSupported; if ( m_decklinkInput->GetDisplayModeIterator( &iter ) == S_OK ) { @@ -57,12 +62,17 @@ private: mode->GetFrameRate( &duration, ×cale ); double fps = (double) timescale / duration; int p = mode->GetFieldDominance() == bmdProgressiveFrame; - mlt_log_verbose( getProducer(), "BMD mode %dx%d %.3f fps prog %d\n", width, height, fps, p ); + m_topFieldFirst = mode->GetFieldDominance() == bmdUpperFieldFirst; + m_colorspace = ( mode->GetFlags() & bmdDisplayModeColorspaceRec709 ) ? 709 : 601; + mlt_log_verbose( getProducer(), "BMD mode %dx%d %.3f fps prog %d tff %d\n", width, height, fps, p, m_topFieldFirst ); - if ( width == profile->width && height == profile->height && p == profile->progressive - && fps == mlt_profile_fps( profile ) ) + if ( width == profile->width && p == profile->progressive + && ( height + vancLines == profile->height || ( height == 486 && profile->height == 480 + vancLines ) ) + && (int) fps == (int) mlt_profile_fps( profile ) ) result = mode->GetDisplayMode(); + SAFE_RELEASE( mode ); } + SAFE_RELEASE( iter ); } return result; @@ -70,39 +80,61 @@ private: public: - mlt_producer getProducer() - { return &m_producer; } + void setProducer( mlt_producer producer ) + { m_producer = producer; } - ~DeckLinkProducer() + mlt_producer getProducer() const + { return m_producer; } + + DeckLinkProducer() + { + m_producer = NULL; + m_decklink = NULL; + m_decklinkInput = NULL; + } + + virtual ~DeckLinkProducer() { - if ( m_decklinkInput ) - m_decklinkInput->Release(); - if ( m_decklink ) - m_decklink->Release(); if ( m_queue ) { stop(); mlt_deque_close( m_queue ); pthread_mutex_destroy( &m_mutex ); pthread_cond_destroy( &m_condition ); + mlt_cache_close( m_cache ); } + SAFE_RELEASE( m_decklinkInput ); + SAFE_RELEASE( m_decklink ); } - bool open( mlt_profile profile, unsigned card = 0 ) + bool open( unsigned card = 0 ) { - IDeckLinkIterator* decklinkIterator = CreateDeckLinkIteratorInstance(); + IDeckLinkIterator* decklinkIterator = NULL; try { +#ifdef WIN32 + HRESULT result = CoInitialize( NULL ); + if ( FAILED( result ) ) + throw "COM initialization failed"; + result = CoCreateInstance( CLSID_CDeckLinkIterator, NULL, CLSCTX_ALL, IID_IDeckLinkIterator, (void**) &decklinkIterator ); + if ( FAILED( result ) ) + throw "The DeckLink drivers are not installed."; +#else + decklinkIterator = CreateDeckLinkIteratorInstance(); if ( !decklinkIterator ) throw "The DeckLink drivers are not installed."; - +#endif // Connect to the Nth DeckLink instance - unsigned i = 0; - do { - if ( decklinkIterator->Next( &m_decklink ) != S_OK ) - throw "DeckLink card not found."; - } while ( ++i <= card ); - decklinkIterator->Release(); + for ( unsigned i = 0; decklinkIterator->Next( &m_decklink ) == S_OK ; i++) + { + if ( i == card ) + break; + else + SAFE_RELEASE( m_decklink ); + } + SAFE_RELEASE( decklinkIterator ); + if ( !m_decklink ) + throw "DeckLink card not found."; // Get the input interface if ( m_decklink->QueryInterface( IID_IDeckLinkInput, (void**) &m_decklinkInput ) != S_OK ) @@ -118,11 +150,15 @@ public: m_started = false; m_dropped = 0; m_isBuffering = true; + m_cache = mlt_cache_init(); + + // 3 covers YADIF and increasing framerate use cases + mlt_cache_set_size( m_cache, 3 ); } catch ( const char *error ) { - if ( decklinkIterator ) - decklinkIterator->Release(); + SAFE_RELEASE( m_decklinkInput ); + SAFE_RELEASE( m_decklink ); mlt_log_error( getProducer(), "%s\n", error ); return false; } @@ -135,17 +171,41 @@ public: return false; try { + // Initialize some members + m_vancLines = mlt_properties_get_int( MLT_PRODUCER_PROPERTIES( getProducer() ), "vanc" ); + if ( m_vancLines == -1 ) + m_vancLines = profile->height <= 512 ? 26 : 32; + if ( !profile ) profile = mlt_service_profile( MLT_PRODUCER_SERVICE( getProducer() ) ); // Get the display mode - BMDDisplayMode displayMode = getDisplayMode( profile ); - if ( displayMode == bmdDisplayModeNotSupported ) + BMDDisplayMode displayMode = getDisplayMode( profile, m_vancLines ); + if ( displayMode == (BMDDisplayMode) bmdDisplayModeNotSupported ) + { + mlt_log_info( getProducer(), "profile = %dx%d %f fps %s\n", profile->width, profile->height, + mlt_profile_fps( profile ), profile->progressive? "progressive" : "interlace" ); throw "Profile is not compatible with decklink."; + } + + // Determine if supports input format detection +#ifdef WIN32 + BOOL doesDetectFormat = FALSE; +#else + bool doesDetectFormat = false; +#endif + IDeckLinkAttributes *decklinkAttributes = 0; + if ( m_decklink->QueryInterface( IID_IDeckLinkAttributes, (void**) &decklinkAttributes ) == S_OK ) + { + if ( decklinkAttributes->GetFlag( BMDDeckLinkSupportsInputFormatDetection, &doesDetectFormat ) != S_OK ) + doesDetectFormat = false; + SAFE_RELEASE( decklinkAttributes ); + } + mlt_log_verbose( getProducer(), "%s format detection\n", doesDetectFormat ? "supports" : "does not support" ); // Enable video capture BMDPixelFormat pixelFormat = bmdFormat8BitYUV; - BMDVideoInputFlags flags = bmdVideoInputFlagDefault; + BMDVideoInputFlags flags = doesDetectFormat ? bmdVideoInputEnableFormatDetection : bmdVideoInputFlagDefault; if ( S_OK != m_decklinkInput->EnableVideoInput( displayMode, pixelFormat, flags ) ) throw "Failed to enable video capture."; @@ -174,8 +234,9 @@ public: void stop() { - if ( m_started ) + if ( !m_started ) return; + m_started = false; // Release the wait in getFrame pthread_mutex_lock( &m_mutex ); @@ -183,22 +244,23 @@ public: pthread_mutex_unlock( &m_mutex ); m_decklinkInput->StopStreams(); + m_decklinkInput->DisableVideoInput(); + m_decklinkInput->DisableAudioInput(); // Cleanup queue pthread_mutex_lock( &m_mutex ); while ( mlt_frame frame = (mlt_frame) mlt_deque_pop_back( m_queue ) ) mlt_frame_close( frame ); pthread_mutex_unlock( &m_mutex ); - - m_started = false; } mlt_frame getFrame() { - mlt_frame frame = NULL; struct timeval now; struct timespec tm; double fps = mlt_producer_get_fps( getProducer() ); + mlt_position position = mlt_producer_position( getProducer() ); + mlt_frame frame = mlt_cache_get_frame( m_cache, position ); // Allow the buffer to fill to the requested initial buffer level. if ( m_isBuffering ) @@ -223,24 +285,32 @@ public: pthread_mutex_unlock( &m_mutex ); } - // Wait if queue is empty - pthread_mutex_lock( &m_mutex ); - while ( mlt_deque_count( m_queue ) < 1 ) + if ( !frame ) { - // Wait up to twice frame duration - gettimeofday( &now, NULL ); - long usec = now.tv_sec * 1000000 + now.tv_usec; - usec += 2000000 / fps; - tm.tv_sec = usec / 1000000; - tm.tv_nsec = (usec % 1000000) * 1000; - if ( pthread_cond_timedwait( &m_condition, &m_mutex, &tm ) ) - // Stop waiting if error (timed out) - break; - } + // Wait if queue is empty + pthread_mutex_lock( &m_mutex ); + while ( mlt_deque_count( m_queue ) < 1 ) + { + // Wait up to twice frame duration + gettimeofday( &now, NULL ); + long usec = now.tv_sec * 1000000 + now.tv_usec; + usec += 2000000 / fps; + tm.tv_sec = usec / 1000000; + tm.tv_nsec = (usec % 1000000) * 1000; + if ( pthread_cond_timedwait( &m_condition, &m_mutex, &tm ) ) + // Stop waiting if error (timed out) + break; + } + frame = ( mlt_frame ) mlt_deque_pop_front( m_queue ); + pthread_mutex_unlock( &m_mutex ); - // Get the first frame from the queue - frame = ( mlt_frame ) mlt_deque_pop_front( m_queue ); - pthread_mutex_unlock( &m_mutex ); + // add to cache + if ( frame ) + { + mlt_frame_set_position( frame, position ); + mlt_cache_put_frame( m_cache, frame ); + } + } // Set frame timestamp and properties if ( frame ) @@ -248,16 +318,27 @@ public: mlt_profile profile = mlt_service_profile( MLT_PRODUCER_SERVICE( getProducer() ) ); mlt_properties properties = MLT_FRAME_PROPERTIES( frame ); mlt_properties_set_int( properties, "progressive", profile->progressive ); + mlt_properties_set_int( properties, "meta.media.progressive", profile->progressive ); + mlt_properties_set_int( properties, "top_field_first", m_topFieldFirst ); mlt_properties_set_double( properties, "aspect_ratio", mlt_profile_sar( profile ) ); + mlt_properties_set_int( properties, "meta.media.sample_aspect_num", profile->sample_aspect_num ); + mlt_properties_set_int( properties, "meta.media.sample_aspect_den", profile->sample_aspect_den ); + mlt_properties_set_int( properties, "meta.media.frame_rate_num", profile->frame_rate_num ); + mlt_properties_set_int( properties, "meta.media.frame_rate_den", profile->frame_rate_den ); mlt_properties_set_int( properties, "width", profile->width ); - mlt_properties_set_int( properties, "real_width", profile->width ); + mlt_properties_set_int( properties, "meta.media.width", profile->width ); mlt_properties_set_int( properties, "height", profile->height ); - mlt_properties_set_int( properties, "real_height", profile->height ); + mlt_properties_set_int( properties, "meta.media.height", profile->height ); mlt_properties_set_int( properties, "format", mlt_image_yuv422 ); + mlt_properties_set_int( properties, "colorspace", m_colorspace ); + mlt_properties_set_int( properties, "meta.media.colorspace", m_colorspace ); mlt_properties_set_int( properties, "audio_frequency", 48000 ); mlt_properties_set_int( properties, "audio_channels", mlt_properties_get_int( MLT_PRODUCER_PROPERTIES( getProducer() ), "channels" ) ); } + else + mlt_log_warning( getProducer(), "buffer underrun\n" ); + return frame; } @@ -277,6 +358,50 @@ public: IDeckLinkVideoInputFrame* video, IDeckLinkAudioInputPacket* audio ) { + if( !m_reprio ) + { + mlt_properties properties = MLT_PRODUCER_PROPERTIES( getProducer() ); + + 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( getProducer(), + "VideoInputFrameArrived: pthread_setschedparam returned %d\n", r); + else + mlt_log_verbose( getProducer(), + "VideoInputFrameArrived: param.sched_priority=%d\n", param.sched_priority); + }; + + m_reprio = true; + }; + + if ( mlt_properties_get_int( MLT_PRODUCER_PROPERTIES( getProducer() ), "preview" ) && + mlt_producer_get_speed( getProducer() ) == 0.0 && !mlt_deque_count( m_queue )) + { + pthread_cond_broadcast( &m_condition ); + return S_OK; + } + // Create mlt_frame mlt_frame frame = mlt_frame_init( MLT_PRODUCER_SERVICE( getProducer() ) ); @@ -285,14 +410,42 @@ public: { if ( !( video->GetFlags() & bmdFrameHasNoInputSource ) ) { - int size = video->GetRowBytes() * video->GetHeight(); + int size = video->GetRowBytes() * ( video->GetHeight() + m_vancLines ); void* image = mlt_pool_alloc( size ); void* buffer = 0; + unsigned char* p = (unsigned char*) image; + int n = size / 2; +\ + // Initialize VANC lines to nominal black + while ( --n ) + { + *p ++ = 16; + *p ++ = 128; + } + + // Capture VANC + if ( m_vancLines > 0 ) + { + IDeckLinkVideoFrameAncillary* vanc = 0; + if ( video->GetAncillaryData( &vanc ) == S_OK && vanc ) + { + for ( int i = 1; i < m_vancLines + 1; i++ ) + { + if ( vanc->GetBufferForVerticalBlankingLine( i, &buffer ) == S_OK ) + swab2( (char*) buffer, (char*) image + ( i - 1 ) * video->GetRowBytes(), video->GetRowBytes() ); + else + mlt_log_debug( getProducer(), "failed capture vanc line %d\n", i ); + } + SAFE_RELEASE(vanc); + } + } + // Capture image video->GetBytes( &buffer ); if ( image && buffer ) { - swab( buffer, image, size ); + size = video->GetRowBytes() * video->GetHeight(); + swab2( (char*) buffer, (char*) image + m_vancLines * video->GetRowBytes(), size ); mlt_frame_set_image( frame, (uint8_t*) image, size, mlt_pool_release ); } else if ( image ) @@ -307,6 +460,23 @@ public: mlt_frame_close( frame ); frame = 0; } + + // Get timecode + IDeckLinkTimecode* timecode = 0; + if ( video->GetTimecode( bmdTimecodeVITC, &timecode ) == S_OK && timecode ) + { + DLString timecodeString = 0; + + if ( timecode->GetString( &timecodeString ) == S_OK ) + { + char* s = getCString( timecodeString ); + mlt_properties_set( MLT_FRAME_PROPERTIES( frame ), "meta.attr.vitc.markup", s ); + mlt_log_debug( getProducer(), "timecode %s\n", s ); + freeCString( s ); + } + freeDLString( timecodeString ); + SAFE_RELEASE( timecode ); + } } else { @@ -356,7 +526,7 @@ public: { mlt_frame_close( frame ); mlt_properties_set_int( MLT_PRODUCER_PROPERTIES( getProducer() ), "dropped", ++m_dropped ); - mlt_log_warning( getProducer(), "frame dropped %d\n", m_dropped ); + mlt_log_warning( getProducer(), "buffer overrun, frame dropped %d\n", m_dropped ); } pthread_mutex_unlock( &m_mutex ); } @@ -369,6 +539,56 @@ public: IDeckLinkDisplayMode* mode, BMDDetectedVideoInputFormatFlags flags ) { + mlt_profile profile = mlt_service_profile( MLT_PRODUCER_SERVICE( getProducer() ) ); + if ( events & bmdVideoInputDisplayModeChanged ) + { + BMDTimeValue duration; + BMDTimeScale timescale; + mode->GetFrameRate( &duration, ×cale ); + profile->width = mode->GetWidth(); + profile->height = mode->GetHeight() + m_vancLines; + profile->frame_rate_num = timescale; + profile->frame_rate_den = duration; + if ( profile->width == 720 ) + { + if ( profile->height == 576 ) + { + profile->sample_aspect_num = 16; + profile->sample_aspect_den = 15; + } + else + { + profile->sample_aspect_num = 8; + profile->sample_aspect_den = 9; + } + profile->display_aspect_num = 4; + profile->display_aspect_den = 3; + } + else + { + profile->sample_aspect_num = 1; + profile->sample_aspect_den = 1; + profile->display_aspect_num = 16; + profile->display_aspect_den = 9; + } + free( profile->description ); + profile->description = strdup( "decklink" ); + mlt_log_verbose( getProducer(), "format changed %dx%d %.3f fps\n", + profile->width, profile->height, (double) profile->frame_rate_num / profile->frame_rate_den ); + } + if ( events & bmdVideoInputFieldDominanceChanged ) + { + profile->progressive = mode->GetFieldDominance() == bmdProgressiveFrame; + m_topFieldFirst = mode->GetFieldDominance() == bmdUpperFieldFirst; + mlt_log_verbose( getProducer(), "field dominance changed prog %d tff %d\n", + profile->progressive, m_topFieldFirst ); + } + if ( events & bmdVideoInputColorspaceChanged ) + { + profile->colorspace = m_colorspace = + ( mode->GetFlags() & bmdDisplayModeColorspaceRec709 ) ? 709 : 601; + mlt_log_verbose( getProducer(), "colorspace changed %d\n", profile->colorspace ); + } return S_OK; } }; @@ -386,32 +606,104 @@ static int get_image( mlt_frame frame, uint8_t **buffer, mlt_image_format *forma static int get_frame( mlt_producer producer, mlt_frame_ptr frame, int index ) { DeckLinkProducer* decklink = (DeckLinkProducer*) producer->child; + mlt_position pos = mlt_producer_position( producer ); + mlt_position end = mlt_producer_get_playtime( producer ); + end = ( mlt_producer_get_length( producer ) < end ? mlt_producer_get_length( producer ) : end ) - 1; - // Get the next frame from the decklink object - *frame = decklink->getFrame(); + // Re-open if needed + if ( !decklink && pos < end ) + { + producer->child = decklink = new DeckLinkProducer(); + decklink->setProducer( producer ); + decklink->open( mlt_properties_get_int( MLT_PRODUCER_PROPERTIES(producer), "resource" ) ); + } + + // Start if needed + if ( decklink ) + { + decklink->start( mlt_service_profile( MLT_PRODUCER_SERVICE( producer ) ) ); + + // Get the next frame from the decklink object + if ( ( *frame = decklink->getFrame() )) + { + // Add audio and video getters + mlt_frame_push_audio( *frame, (void*) get_audio ); + mlt_frame_push_get_image( *frame, get_image ); + } + } if ( !*frame ) - *frame = mlt_frame_init( MLT_PRODUCER_SERVICE( producer ) ); + *frame = mlt_frame_init( MLT_PRODUCER_SERVICE(producer) ); // Calculate the next timecode - mlt_frame_set_position( *frame, mlt_producer_position( producer ) ); mlt_producer_prepare_next( producer ); - // Add audio and video getters - mlt_frame_push_audio( *frame, (void*) get_audio ); - mlt_frame_push_get_image( *frame, get_image ); + // Close DeckLink if at end + if ( pos >= end && decklink ) + { + decklink->stop(); + delete decklink; + producer->child = NULL; + } return 0; } static void producer_close( mlt_producer producer ) { + delete (DeckLinkProducer*) producer->child; producer->close = NULL; mlt_producer_close( producer ); - delete (DeckLinkProducer*) producer->child; } 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* decklinkInput = 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_IDeckLinkInput, (void**) &decklinkInput ) == 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( decklinkInput ); + } + SAFE_RELEASE( decklink ); + } + SAFE_RELEASE( decklinkIterator ); + mlt_properties_set_int( properties, "devices", i ); +} + /** Initialise the producer. */ @@ -419,22 +711,25 @@ mlt_producer producer_decklink_init( mlt_profile profile, mlt_service_type type, { // Allocate the producer DeckLinkProducer* decklink = new DeckLinkProducer(); - mlt_producer producer = NULL; + mlt_producer producer = (mlt_producer) calloc( 1, sizeof( *producer ) ); // If allocated and initializes - if ( decklink && !mlt_producer_init( decklink->getProducer(), decklink ) ) + if ( decklink && !mlt_producer_init( producer, decklink ) ) { - if ( decklink->open( profile, arg? atoi( arg ) : 0 ) ) + if ( decklink->open( arg? atoi( arg ) : 0 ) ) { - producer = decklink->getProducer(); mlt_properties properties = MLT_PRODUCER_PROPERTIES( producer ); + // Close DeckLink and defer re-open to get_frame + delete decklink; + producer->child = NULL; + // Set callbacks producer->close = (mlt_destructor) producer_close; producer->get_frame = get_frame; // Set properties - mlt_properties_set( properties, "resource", arg? arg : "0" ); + mlt_properties_set( properties, "resource", (arg && strcmp( arg, ""))? arg : "0" ); mlt_properties_set_int( properties, "channels", 2 ); mlt_properties_set_int( properties, "buffer", 25 ); mlt_properties_set_int( properties, "prefill", 25 ); @@ -444,12 +739,8 @@ mlt_producer producer_decklink_init( mlt_profile profile, mlt_service_type type, mlt_properties_set_int( properties, "out", INT_MAX - 1 ); mlt_properties_set( properties, "eof", "loop" ); - // Start immediately - if ( !decklink->start( profile ) ) - { - producer_close( producer ); - producer = NULL; - } + 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 ); } }