/*
* Blackmagic DeckLink output
* Copyright ( c ) 2013 - 2014 Ramiro Polla , Luca Barbato , Deti Fliegl
*
* This file is part of FFmpeg .
*
* FFmpeg 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 .
*
* FFmpeg 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 FFmpeg ; if not , write to the Free Software
* Foundation , Inc . , 51 Franklin Street , Fifth Floor , Boston , MA 02110 - 1301 USA
*/
/* Include internal.h first to avoid conflict between winsock.h (used by
* DeckLink headers ) and winsock2 . h ( used by libavformat ) in MSVC + + builds */
extern " C " {
# include "libavformat/internal.h"
}
# include <DeckLinkAPI.h>
# ifdef _WIN32
# include <DeckLinkAPI_i.c>
# else
/* The file provided by the SDK is known to be missing prototypes, which doesn't
cause issues with GCC since the warning doesn ' t apply to C + + files . However
Clang does complain ( and warnings are treated as errors ) , so suppress the
warning just for this one file */
# ifdef __clang__
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wmissing-prototypes"
# endif
# include <DeckLinkAPIDispatch.cpp>
# ifdef __clang__
# pragma clang diagnostic pop
# endif
# endif
extern " C " {
# include "libavformat/avformat.h"
# include "libavutil/imgutils.h"
# include "libavutil/intreadwrite.h"
# include "libavutil/bswap.h"
# include "avdevice.h"
}
# include "decklink_common.h"
static IDeckLinkIterator * decklink_create_iterator ( AVFormatContext * avctx )
{
IDeckLinkIterator * iter ;
# ifdef _WIN32
if ( CoInitialize ( NULL ) < 0 ) {
av_log ( avctx , AV_LOG_ERROR , " COM initialization failed. \n " ) ;
return NULL ;
}
if ( CoCreateInstance ( CLSID_CDeckLinkIterator , NULL , CLSCTX_ALL ,
IID_IDeckLinkIterator , ( void * * ) & iter ) ! = S_OK ) {
iter = NULL ;
}
# else
iter = CreateDeckLinkIteratorInstance ( ) ;
# endif
if ( ! iter )
av_log ( avctx , AV_LOG_ERROR , " Could not create DeckLink iterator. "
" Make sure you have DeckLink drivers " BLACKMAGIC_DECKLINK_API_VERSION_STRING " or newer installed. \n " ) ;
return iter ;
}
static int decklink_get_attr_string ( IDeckLink * dl , BMDDeckLinkAttributeID cfg_id , const char * * s )
{
DECKLINK_STR tmp ;
HRESULT hr ;
IDeckLinkAttributes * attr ;
* s = NULL ;
if ( dl - > QueryInterface ( IID_IDeckLinkAttributes , ( void * * ) & attr ) ! = S_OK )
return AVERROR_EXTERNAL ;
hr = attr - > GetString ( cfg_id , & tmp ) ;
attr - > Release ( ) ;
if ( hr = = S_OK ) {
* s = DECKLINK_STRDUP ( tmp ) ;
DECKLINK_FREE ( tmp ) ;
if ( ! * s )
return AVERROR ( ENOMEM ) ;
} else if ( hr = = E_FAIL ) {
return AVERROR_EXTERNAL ;
}
return 0 ;
}
static int decklink_select_input ( AVFormatContext * avctx , BMDDeckLinkConfigurationID cfg_id )
{
struct decklink_cctx * cctx = ( struct decklink_cctx * ) avctx - > priv_data ;
struct decklink_ctx * ctx = ( struct decklink_ctx * ) cctx - > ctx ;
BMDDeckLinkAttributeID attr_id = ( cfg_id = = bmdDeckLinkConfigAudioInputConnection ) ? BMDDeckLinkAudioInputConnections : BMDDeckLinkVideoInputConnections ;
int64_t bmd_input = ( cfg_id = = bmdDeckLinkConfigAudioInputConnection ) ? ( int64_t ) ctx - > audio_input : ( int64_t ) ctx - > video_input ;
const char * type_name = ( cfg_id = = bmdDeckLinkConfigAudioInputConnection ) ? " audio " : " video " ;
int64_t supported_connections = 0 ;
HRESULT res ;
if ( bmd_input ) {
res = ctx - > attr - > GetInt ( attr_id , & supported_connections ) ;
if ( res ! = S_OK ) {
av_log ( avctx , AV_LOG_ERROR , " Failed to query supported %s inputs. \n " , type_name ) ;
return AVERROR_EXTERNAL ;
}
if ( ( supported_connections & bmd_input ) ! = bmd_input ) {
av_log ( avctx , AV_LOG_ERROR , " Device does not support selected %s input. \n " , type_name ) ;
return AVERROR ( ENOSYS ) ;
}
res = ctx - > cfg - > SetInt ( cfg_id , bmd_input ) ;
if ( res ! = S_OK ) {
av_log ( avctx , AV_LOG_ERROR , " Failed to select %s input. \n " , type_name ) ;
return AVERROR_EXTERNAL ;
}
}
return 0 ;
}
static DECKLINK_BOOL field_order_eq ( enum AVFieldOrder field_order , BMDFieldDominance bmd_field_order )
{
if ( field_order = = AV_FIELD_UNKNOWN )
return true ;
if ( ( field_order = = AV_FIELD_TT | | field_order = = AV_FIELD_TB ) & & bmd_field_order = = bmdUpperFieldFirst )
return true ;
if ( ( field_order = = AV_FIELD_BB | | field_order = = AV_FIELD_BT ) & & bmd_field_order = = bmdLowerFieldFirst )
return true ;
if ( field_order = = AV_FIELD_PROGRESSIVE & & ( bmd_field_order = = bmdProgressiveFrame | | bmd_field_order = = bmdProgressiveSegmentedFrame ) )
return true ;
return false ;
}
int ff_decklink_set_configs ( AVFormatContext * avctx ,
decklink_direction_t direction ) {
struct decklink_cctx * cctx = ( struct decklink_cctx * ) avctx - > priv_data ;
struct decklink_ctx * ctx = ( struct decklink_ctx * ) cctx - > ctx ;
HRESULT res ;
if ( ctx - > duplex_mode ) {
DECKLINK_BOOL duplex_supported = false ;
if ( ctx - > attr - > GetFlag ( BMDDeckLinkSupportsDuplexModeConfiguration , & duplex_supported ) ! = S_OK )
duplex_supported = false ;
if ( duplex_supported ) {
res = ctx - > cfg - > SetInt ( bmdDeckLinkConfigDuplexMode , ctx - > duplex_mode = = 2 ? bmdDuplexModeFull : bmdDuplexModeHalf ) ;
if ( res ! = S_OK )
av_log ( avctx , AV_LOG_WARNING , " Setting duplex mode failed. \n " ) ;
else
av_log ( avctx , AV_LOG_VERBOSE , " Successfully set duplex mode to %s duplex. \n " , ctx - > duplex_mode = = 2 ? " full " : " half " ) ;
} else {
av_log ( avctx , AV_LOG_WARNING , " Unable to set duplex mode, because it is not supported. \n " ) ;
}
}
if ( direction = = DIRECTION_IN ) {
int ret ;
ret = decklink_select_input ( avctx , bmdDeckLinkConfigAudioInputConnection ) ;
if ( ret < 0 )
return ret ;
ret = decklink_select_input ( avctx , bmdDeckLinkConfigVideoInputConnection ) ;
if ( ret < 0 )
return ret ;
}
if ( direction = = DIRECTION_OUT & & cctx - > timing_offset ! = INT_MIN ) {
res = ctx - > cfg - > SetInt ( bmdDeckLinkConfigReferenceInputTimingOffset , cctx - > timing_offset ) ;
if ( res ! = S_OK )
av_log ( avctx , AV_LOG_WARNING , " Setting timing offset failed. \n " ) ;
}
return 0 ;
}
int ff_decklink_set_format ( AVFormatContext * avctx ,
int width , int height ,
int tb_num , int tb_den ,
enum AVFieldOrder field_order ,
decklink_direction_t direction , int num )
{
struct decklink_cctx * cctx = ( struct decklink_cctx * ) avctx - > priv_data ;
struct decklink_ctx * ctx = ( struct decklink_ctx * ) cctx - > ctx ;
BMDDisplayModeSupport support ;
IDeckLinkDisplayModeIterator * itermode ;
IDeckLinkDisplayMode * mode ;
int i = 1 ;
HRESULT res ;
av_log ( avctx , AV_LOG_DEBUG , " Trying to find mode for frame size %dx%d, frame timing %d/%d, field order %d, direction %d, mode number %d, format code %s \n " ,
width , height , tb_num , tb_den , field_order , direction , num , ( cctx - > format_code ) ? cctx - > format_code : " (unset) " ) ;
if ( direction = = DIRECTION_IN ) {
res = ctx - > dli - > GetDisplayModeIterator ( & itermode ) ;
} else {
res = ctx - > dlo - > GetDisplayModeIterator ( & itermode ) ;
}
if ( res ! = S_OK ) {
av_log ( avctx , AV_LOG_ERROR , " Could not get Display Mode Iterator \n " ) ;
return AVERROR ( EIO ) ;
}
char format_buf [ ] = " " ;
if ( cctx - > format_code )
memcpy ( format_buf , cctx - > format_code , FFMIN ( strlen ( cctx - > format_code ) , sizeof ( format_buf ) ) ) ;
BMDDisplayMode target_mode = ( BMDDisplayMode ) AV_RB32 ( format_buf ) ;
AVRational target_tb = av_make_q ( tb_num , tb_den ) ;
ctx - > bmd_mode = bmdModeUnknown ;
while ( ( ctx - > bmd_mode = = bmdModeUnknown ) & & itermode - > Next ( & mode ) = = S_OK ) {
BMDTimeValue bmd_tb_num , bmd_tb_den ;
int bmd_width = mode - > GetWidth ( ) ;
int bmd_height = mode - > GetHeight ( ) ;
BMDDisplayMode bmd_mode = mode - > GetDisplayMode ( ) ;
BMDFieldDominance bmd_field_dominance = mode - > GetFieldDominance ( ) ;
mode - > GetFrameRate ( & bmd_tb_num , & bmd_tb_den ) ;
AVRational mode_tb = av_make_q ( bmd_tb_num , bmd_tb_den ) ;
if ( ( bmd_width = = width & &
bmd_height = = height & &
! av_cmp_q ( mode_tb , target_tb ) & &
field_order_eq ( field_order , bmd_field_dominance ) )
| | i = = num
| | target_mode = = bmd_mode ) {
ctx - > bmd_mode = bmd_mode ;
ctx - > bmd_width = bmd_width ;
ctx - > bmd_height = bmd_height ;
ctx - > bmd_tb_den = bmd_tb_den ;
ctx - > bmd_tb_num = bmd_tb_num ;
ctx - > bmd_field_dominance = bmd_field_dominance ;
av_log ( avctx , AV_LOG_INFO , " Found Decklink mode %d x %d with rate %.2f%s \n " ,
bmd_width , bmd_height , 1 / av_q2d ( mode_tb ) ,
( ctx - > bmd_field_dominance = = bmdLowerFieldFirst | | ctx - > bmd_field_dominance = = bmdUpperFieldFirst ) ? " (i) " : " " ) ;
}
mode - > Release ( ) ;
i + + ;
}
itermode - > Release ( ) ;
if ( ctx - > bmd_mode = = bmdModeUnknown )
return - 1 ;
if ( direction = = DIRECTION_IN ) {
if ( ctx - > dli - > DoesSupportVideoMode ( ctx - > bmd_mode , ( BMDPixelFormat ) cctx - > raw_format ,
bmdVideoOutputFlagDefault ,
& support , NULL ) ! = S_OK )
return - 1 ;
} else {
if ( ! ctx - > supports_vanc | | ctx - > dlo - > DoesSupportVideoMode ( ctx - > bmd_mode , ctx - > raw_format ,
bmdVideoOutputVANC ,
& support , NULL ) ! = S_OK ) {
/* Try without VANC enabled */
if ( ctx - > dlo - > DoesSupportVideoMode ( ctx - > bmd_mode , ctx - > raw_format ,
bmdVideoOutputFlagDefault ,
& support , NULL ) ! = S_OK ) {
return - 1 ;
}
ctx - > supports_vanc = 0 ;
}
}
if ( support = = bmdDisplayModeSupported )
return 0 ;
return - 1 ;
}
int ff_decklink_set_format ( AVFormatContext * avctx , decklink_direction_t direction , int num ) {
return ff_decklink_set_format ( avctx , 0 , 0 , 0 , 0 , AV_FIELD_UNKNOWN , direction , num ) ;
}
int ff_decklink_list_devices ( AVFormatContext * avctx ,
struct AVDeviceInfoList * device_list ,
int show_inputs , int show_outputs )
{
IDeckLink * dl = NULL ;
IDeckLinkIterator * iter = decklink_create_iterator ( avctx ) ;
int ret = 0 ;
if ( ! iter )
return AVERROR ( EIO ) ;
while ( ret = = 0 & & iter - > Next ( & dl ) = = S_OK ) {
IDeckLinkOutput * output_config ;
IDeckLinkInput * input_config ;
const char * display_name = NULL ;
const char * unique_name = NULL ;
AVDeviceInfo * new_device = NULL ;
int add = 0 ;
ret = decklink_get_attr_string ( dl , BMDDeckLinkDisplayName , & display_name ) ;
if ( ret < 0 )
goto next ;
ret = decklink_get_attr_string ( dl , BMDDeckLinkDeviceHandle , & unique_name ) ;
if ( ret < 0 )
goto next ;
if ( show_outputs ) {
if ( dl - > QueryInterface ( IID_IDeckLinkOutput , ( void * * ) & output_config ) = = S_OK ) {
output_config - > Release ( ) ;
add = 1 ;
}
}
if ( show_inputs ) {
if ( dl - > QueryInterface ( IID_IDeckLinkInput , ( void * * ) & input_config ) = = S_OK ) {
input_config - > Release ( ) ;
add = 1 ;
}
}
if ( add = = 1 ) {
new_device = ( AVDeviceInfo * ) av_mallocz ( sizeof ( AVDeviceInfo ) ) ;
if ( ! new_device ) {
ret = AVERROR ( ENOMEM ) ;
goto next ;
}
new_device - > device_name = av_strdup ( unique_name ? unique_name : display_name ) ;
new_device - > device_description = av_strdup ( display_name ) ;
if ( ! new_device - > device_name | |
! new_device - > device_description | |
av_dynarray_add_nofree ( & device_list - > devices , & device_list - > nb_devices , new_device ) < 0 ) {
ret = AVERROR ( ENOMEM ) ;
av_freep ( & new_device - > device_name ) ;
av_freep ( & new_device - > device_description ) ;
av_freep ( & new_device ) ;
goto next ;
}
}
next :
av_freep ( & display_name ) ;
av_freep ( & unique_name ) ;
dl - > Release ( ) ;
}
iter - > Release ( ) ;
return ret ;
}
/* This is a wrapper around the ff_decklink_list_devices() which dumps the
output to av_log ( ) and exits ( for backward compatibility with the
" -list_devices " argument ) . */
void ff_decklink_list_devices_legacy ( AVFormatContext * avctx ,
int show_inputs , int show_outputs )
{
struct AVDeviceInfoList * device_list = NULL ;
int ret ;
device_list = ( struct AVDeviceInfoList * ) av_mallocz ( sizeof ( AVDeviceInfoList ) ) ;
if ( ! device_list )
return ;
ret = ff_decklink_list_devices ( avctx , device_list , show_inputs , show_outputs ) ;
if ( ret = = 0 ) {
av_log ( avctx , AV_LOG_INFO , " Blackmagic DeckLink %s devices: \n " ,
show_inputs ? " input " : " output " ) ;
for ( int i = 0 ; i < device_list - > nb_devices ; i + + ) {
av_log ( avctx , AV_LOG_INFO , " \t '%s' \n " , device_list - > devices [ i ] - > device_description ) ;
}
}
avdevice_free_list_devices ( & device_list ) ;
}
int ff_decklink_list_formats ( AVFormatContext * avctx , decklink_direction_t direction )
{
struct decklink_cctx * cctx = ( struct decklink_cctx * ) avctx - > priv_data ;
struct decklink_ctx * ctx = ( struct decklink_ctx * ) cctx - > ctx ;
IDeckLinkDisplayModeIterator * itermode ;
IDeckLinkDisplayMode * mode ;
uint32_t format_code ;
HRESULT res ;
if ( direction = = DIRECTION_IN ) {
int ret ;
ret = decklink_select_input ( avctx , bmdDeckLinkConfigAudioInputConnection ) ;
if ( ret < 0 )
return ret ;
ret = decklink_select_input ( avctx , bmdDeckLinkConfigVideoInputConnection ) ;
if ( ret < 0 )
return ret ;
res = ctx - > dli - > GetDisplayModeIterator ( & itermode ) ;
} else {
res = ctx - > dlo - > GetDisplayModeIterator ( & itermode ) ;
}
if ( res ! = S_OK ) {
av_log ( avctx , AV_LOG_ERROR , " Could not get Display Mode Iterator \n " ) ;
return AVERROR ( EIO ) ;
}
av_log ( avctx , AV_LOG_INFO , " Supported formats for '%s': \n \t format_code \t description " ,
avctx - > url ) ;
while ( itermode - > Next ( & mode ) = = S_OK ) {
BMDTimeValue tb_num , tb_den ;
mode - > GetFrameRate ( & tb_num , & tb_den ) ;
format_code = av_bswap32 ( mode - > GetDisplayMode ( ) ) ;
av_log ( avctx , AV_LOG_INFO , " \n \t %.4s \t \t %ldx%ld at %d/%d fps " ,
( char * ) & format_code , mode - > GetWidth ( ) , mode - > GetHeight ( ) ,
( int ) tb_den , ( int ) tb_num ) ;
switch ( mode - > GetFieldDominance ( ) ) {
case bmdLowerFieldFirst :
av_log ( avctx , AV_LOG_INFO , " (interlaced, lower field first) " ) ; break ;
case bmdUpperFieldFirst :
av_log ( avctx , AV_LOG_INFO , " (interlaced, upper field first) " ) ; break ;
}
mode - > Release ( ) ;
}
av_log ( avctx , AV_LOG_INFO , " \n " ) ;
itermode - > Release ( ) ;
return 0 ;
}
void ff_decklink_cleanup ( AVFormatContext * avctx )
{
struct decklink_cctx * cctx = ( struct decklink_cctx * ) avctx - > priv_data ;
struct decklink_ctx * ctx = ( struct decklink_ctx * ) cctx - > ctx ;
if ( ctx - > dli )
ctx - > dli - > Release ( ) ;
if ( ctx - > dlo )
ctx - > dlo - > Release ( ) ;
if ( ctx - > attr )
ctx - > attr - > Release ( ) ;
if ( ctx - > cfg )
ctx - > cfg - > Release ( ) ;
if ( ctx - > dl )
ctx - > dl - > Release ( ) ;
}
int ff_decklink_init_device ( AVFormatContext * avctx , const char * name )
{
struct decklink_cctx * cctx = ( struct decklink_cctx * ) avctx - > priv_data ;
struct decklink_ctx * ctx = ( struct decklink_ctx * ) cctx - > ctx ;
IDeckLink * dl = NULL ;
IDeckLinkIterator * iter = decklink_create_iterator ( avctx ) ;
if ( ! iter )
return AVERROR_EXTERNAL ;
while ( iter - > Next ( & dl ) = = S_OK ) {
const char * display_name = NULL ;
const char * unique_name = NULL ;
decklink_get_attr_string ( dl , BMDDeckLinkDisplayName , & display_name ) ;
decklink_get_attr_string ( dl , BMDDeckLinkDeviceHandle , & unique_name ) ;
if ( display_name & & ! strcmp ( name , display_name ) | | unique_name & & ! strcmp ( name , unique_name ) ) {
av_free ( ( void * ) unique_name ) ;
av_free ( ( void * ) display_name ) ;
ctx - > dl = dl ;
break ;
}
av_free ( ( void * ) display_name ) ;
av_free ( ( void * ) unique_name ) ;
dl - > Release ( ) ;
}
iter - > Release ( ) ;
if ( ! ctx - > dl )
return AVERROR ( ENXIO ) ;
if ( ctx - > dl - > QueryInterface ( IID_IDeckLinkConfiguration , ( void * * ) & ctx - > cfg ) ! = S_OK ) {
av_log ( avctx , AV_LOG_ERROR , " Could not get configuration interface for '%s' \n " , name ) ;
ff_decklink_cleanup ( avctx ) ;
return AVERROR_EXTERNAL ;
}
if ( ctx - > dl - > QueryInterface ( IID_IDeckLinkAttributes , ( void * * ) & ctx - > attr ) ! = S_OK ) {
av_log ( avctx , AV_LOG_ERROR , " Could not get attributes interface for '%s' \n " , name ) ;
ff_decklink_cleanup ( avctx ) ;
return AVERROR_EXTERNAL ;
}
return 0 ;
}