2014-12-13 16:23:31 +00:00
/* GStreamer
* Copyright ( C ) 2011 David Schleef < ds @ entropywave . com >
* Copyright ( C ) 2014 Sebastian Dröge < sebastian @ centricular . com >
*
* This library is free software ; you can redistribute it and / or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation ; either
* version 2 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
* Library General Public License for more details .
*
* You should have received a copy of the GNU Library General Public
* License along with this library ; if not , write to the
* Free Software Foundation , Inc . , 51 Franklin Street , Suite 500 ,
* Boston , MA 02110 - 1335 , USA .
*/
2019-03-03 18:34:11 +00:00
/**
* SECTION : element - decklinkvideosink
* @ short_description : Outputs Video to a BlackMagic DeckLink Device
*
* Playout Video to a BlackMagic DeckLink Device .
*
* # # Sample pipeline
* | [
* gst - launch - 1.0 \
* videotestsrc ! \
* decklinkvideosink device - number = 0 mode = 1080 p25
* ] |
* Playout a 1080 p25 test - video to the SDI - Out of Card 0. Devices are numbered
* starting with 0.
*
2023-11-08 15:59:48 +00:00
* # # Duplex - Mode
2023-11-08 16:23:17 +00:00
* Certain DeckLink Cards like the Duo2 or the Quad2 contain two or four
2019-09-02 19:08:44 +00:00
* independent SDI units with two connectors each . These units can operate either
2019-03-03 18:34:11 +00:00
* in half - or in full - duplex mode .
*
* The Duplex - Mode of a Card can be configured using the ` duplex - mode ` - Property .
* Cards that to not support Duplex - Modes are not influenced by the property .
*
2023-11-08 15:59:48 +00:00
* # # # Half - Duplex - Mode ( default )
2019-03-03 18:34:11 +00:00
* By default decklinkvideosink will configure them into half - duplex mode , so that
2019-09-02 19:08:44 +00:00
* each connector acts as if it were an independent DeckLink Card which can either
2019-03-03 18:34:11 +00:00
* be used as an Input or as an Output . In this mode the Duo2 can be used as as 4 SDI
* In - / Outputs and the Quad2 as 8 SDI In - / Outputs .
*
* | [
* gst - launch - 1.0 \
* videotestsrc foreground - color = 0x00ff0000 ! decklinkvideosink device - number = 0 mode = 1080 p25 \
* videotestsrc foreground - color = 0x0000ff00 ! decklinkvideosink device - number = 1 mode = 1080 p25 \
* videotestsrc foreground - color = 0x000000ff ! decklinkvideosink device - number = 2 mode = 1080 p25 \
* videotestsrc foreground - color = 0x00ffffff ! decklinkvideosink device - number = 3 mode = 1080 p25
* ] |
* Playout four Test - Screen with colored Snow on the first four units in the System
* ( ie . the Connectors 1 - 4 of a Duo2 unit ) .
*
* | [
* gst - launch - 1.0 \
* videotestsrc is - live = true foreground - color = 0x0000ff00 ! decklinkvideosink device - number = 0 mode = 1080 p25 \
* decklinkvideosrc device - number = 1 mode = 1080 p25 ! autovideosink \
* decklinkvideosrc device - number = 2 mode = 1080 p25 ! autovideosink \
* videotestsrc is - live = true foreground - color = 0x00ff0000 ! decklinkvideosink device - number = 3 mode = 1080 p25
* ] |
* Capture 1080 p25 from the second and third unit in the System ,
* Playout a Test - Screen with colored Snow on the first and fourth unit
* ( ie . the Connectors 1 - 4 of a Duo2 unit ) .
*
2023-11-08 15:59:48 +00:00
* # # # Device - Number - Mapping in Half - Duplex - Mode
2019-03-03 18:34:11 +00:00
* The device - number to connector - mapping is as follows for the Duo2
* - ` device - number = 0 ` SDI1
* - ` device - number = 1 ` SDI3
* - ` device - number = 2 ` SDI2
* - ` device - number = 3 ` SDI4
*
* And for the Quad2
* - ` device - number = 0 ` SDI1
* - ` device - number = 1 ` SDI3
* - ` device - number = 2 ` SDI5
* - ` device - number = 3 ` SDI7
* - ` device - number = 4 ` SDI2
* - ` device - number = 5 ` SDI4
* - ` device - number = 6 ` SDI6
* - ` device - number = 7 ` SDI8
*
2023-11-08 15:59:48 +00:00
* # # # Full - Duplex - Mode
2019-03-03 18:34:11 +00:00
* When operating in full - duplex mode , two connectors of a unit are combined to
* a single device , performing keying with the second connection .
*
2023-11-08 15:59:48 +00:00
* # # # Device - Number - Mapping in Full - Duplex - Mode
2019-03-03 18:34:11 +00:00
* The device - number to connector - mapping in full - duplex - mode is as follows for the Duo2
* - ` device - number = 0 ` SDI1 primary , SDI2 secondary
* - ` device - number = 1 ` SDI3 primaty , SDI4 secondary
*
* And for the Quad2
* - ` device - number = 0 ` SDI1 primary , SDI2 secondary
* - ` device - number = 1 ` SDI3 primaty , SDI4 secondary
* - ` device - number = 2 ` SDI5 primary , SDI6 secondary
* - ` device - number = 3 ` SDI7 primary , SDI8 secondary
*
2023-11-08 15:59:48 +00:00
* # # Keying
2019-03-03 18:34:11 +00:00
* Keying is the process of overlaing Video with an Alpha - Channel on top of an
* existing Video - Stream . The Duo2 and Quad2 - Cards can perform two different
* Keying - Modes when operated in full - duplex mode . Both modes expect Video with
* an Alpha - Channel .
*
2023-11-08 15:59:48 +00:00
* # # # Internal Keyer
2019-03-03 18:34:11 +00:00
* In internal Keying - Mode the primary port becomes an Input and the secondary port
* an Output . The unit overlays Video played back from the Computer onto the Input
* and outputs the combined Video - Stream to the Output .
*
* | [
* gst - launch - 1.0 \
* videotestsrc foreground - color = 0x00000000 background - color = 0x00000000 ! \
* video / x - raw , format = BGRA , width = 1920 , height = 1080 ! \
* decklinkvideosink device - number = 0 duplex - mode = full keyer - mode = internal video - format = 8 bit - bgra mode = 1080 p25
* ] |
*
2023-11-08 15:59:48 +00:00
* # # # External Keyer
2019-03-03 18:34:11 +00:00
* In external Keying - Mode the primary port outputs the alpha - chanel as the
* luma - value ( key - channel ) . Transparent pixels are black , opaque pixels are white .
* The RGB - Component of the Video are output on the secondary channel .
*
* | [
* gst - launch - 1.0 \
* videotestsrc foreground - color = 0x00000000 background - color = 0x00000000 ! \
* video / x - raw , format = BGRA , width = 1920 , height = 1080 ! \
* decklinkvideosink device - number = 0 duplex - mode = full keyer - mode = external video - format = 8 bit - bgra mode = 1080 p25
* ] |
*/
2014-12-13 16:23:31 +00:00
# ifdef HAVE_CONFIG_H
# include "config.h"
# endif
# include "gstdecklinkvideosink.h"
# include <string.h>
GST_DEBUG_CATEGORY_STATIC ( gst_decklink_video_sink_debug ) ;
# define GST_CAT_DEFAULT gst_decklink_video_sink_debug
2022-09-26 11:57:15 +00:00
# define DEFAULT_PERSISTENT_ID (-1)
2022-06-28 13:40:55 +00:00
class GstDecklinkTimecode : public IDeckLinkTimecode
{
public :
GstDecklinkTimecode ( GstVideoTimeCode *
timecode ) : m_timecode ( gst_video_time_code_copy ( timecode ) ) , m_refcount ( 1 )
{
}
virtual BMDTimecodeBCD STDMETHODCALLTYPE GetBCD ( void )
{
BMDTimecodeBCD bcd = 0 ;
bcd | = ( m_timecode - > frames % 10 ) < < 0 ;
bcd | = ( ( m_timecode - > frames / 10 ) & 0x0f ) < < 4 ;
bcd | = ( m_timecode - > seconds % 10 ) < < 8 ;
bcd | = ( ( m_timecode - > seconds / 10 ) & 0x0f ) < < 12 ;
bcd | = ( m_timecode - > minutes % 10 ) < < 16 ;
bcd | = ( ( m_timecode - > minutes / 10 ) & 0x0f ) < < 20 ;
bcd | = ( m_timecode - > hours % 10 ) < < 24 ;
bcd | = ( ( m_timecode - > hours / 10 ) & 0x0f ) < < 28 ;
if ( m_timecode - > config . fps_n = = 24 & & m_timecode - > config . fps_d = = 1 )
bcd | = 0x0 < < 30 ;
else if ( m_timecode - > config . fps_n = = 25 & & m_timecode - > config . fps_d = = 1 )
bcd | = 0x1 < < 30 ;
else if ( m_timecode - > config . fps_n = = 30 & & m_timecode - > config . fps_d = = 1001 )
bcd | = 0x2 < < 30 ;
else if ( m_timecode - > config . fps_n = = 30 & & m_timecode - > config . fps_d = = 1 )
bcd | = 0x3 < < 30 ;
return bcd ;
}
virtual HRESULT STDMETHODCALLTYPE GetComponents ( uint8_t * hours ,
uint8_t * minutes , uint8_t * seconds , uint8_t * frames )
{
* hours = m_timecode - > hours ;
* minutes = m_timecode - > minutes ;
* seconds = m_timecode - > seconds ;
* frames = m_timecode - > frames ;
return S_OK ;
}
virtual HRESULT STDMETHODCALLTYPE GetString ( COMSTR_T * timecode )
{
COMSTR_T s = ( COMSTR_T ) gst_video_time_code_to_string ( m_timecode ) ;
CONVERT_TO_COM_STRING ( s ) ;
* timecode = s ;
return S_OK ;
}
virtual BMDTimecodeFlags STDMETHODCALLTYPE GetFlags ( void )
{
BMDTimecodeFlags flags = ( BMDTimecodeFlags ) 0 ;
if ( ( ( GstVideoTimeCodeFlags ) ( m_timecode - >
config . flags ) ) & GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME )
flags = ( BMDTimecodeFlags ) ( flags | bmdTimecodeIsDropFrame ) ;
else
flags = ( BMDTimecodeFlags ) ( flags | bmdTimecodeFlagDefault ) ;
if ( m_timecode - > field_count = = 2 )
flags = ( BMDTimecodeFlags ) ( flags | bmdTimecodeFieldMark ) ;
return flags ;
}
virtual HRESULT STDMETHODCALLTYPE GetTimecodeUserBits ( BMDTimecodeUserBits *
userBits )
{
* userBits = 0 ;
return S_OK ;
}
2024-07-22 13:55:48 +00:00
virtual HRESULT STDMETHODCALLTYPE QueryInterface ( REFIID iid , LPVOID * )
2022-06-28 13:40:55 +00:00
{
return E_NOINTERFACE ;
}
virtual ULONG STDMETHODCALLTYPE AddRef ( void )
{
ULONG ret ;
ret = g_atomic_int_add ( & m_refcount , 1 ) + 1 ;
return ret ;
}
virtual ULONG STDMETHODCALLTYPE Release ( void )
{
ULONG ret ;
ret = g_atomic_int_add ( & m_refcount , - 1 ) ;
if ( ret = = 1 ) {
delete this ;
}
return ret - 1 ;
}
private :
GstVideoTimeCode * m_timecode ;
int m_refcount ;
virtual ~ GstDecklinkTimecode ( ) {
if ( m_timecode ) {
gst_video_time_code_free ( m_timecode ) ;
}
}
} ;
2024-07-22 13:55:48 +00:00
class GstDecklinkVideoFrame : public IDeckLinkVideoFrame , public IDeckLinkVideoFrameMetadataExtensions
2022-06-28 13:40:55 +00:00
{
public :
2024-06-27 12:25:42 +00:00
GstDecklinkVideoFrame ( GstVideoFrame * frame ) :
running_time ( 0 ) , running_time_duration ( 0 ) , sync_buffer ( 0 ) , m_frame ( 0 ) ,
2024-07-22 13:55:48 +00:00
have_light_level ( FALSE ) , have_mastering_info ( FALSE ) , m_dframe ( 0 ) ,
m_ancillary ( 0 ) , m_timecode ( 0 ) , m_refcount ( 1 )
2022-06-28 13:40:55 +00:00
{
m_frame = g_new0 ( GstVideoFrame , 1 ) ;
* m_frame = * frame ;
2024-07-22 13:55:48 +00:00
memset ( & light_level , 0 , sizeof ( light_level ) ) ;
memset ( & mastering_info , 0 , sizeof ( mastering_info ) ) ;
memset ( & colorimetry , 0 , sizeof ( colorimetry ) ) ;
2022-06-28 13:40:55 +00:00
}
2024-06-27 12:25:42 +00:00
GstDecklinkVideoFrame ( IDeckLinkMutableVideoFrame * dframe ) :
running_time ( 0 ) , running_time_duration ( 0 ) , sync_buffer ( 0 ) , m_frame ( 0 ) ,
2024-07-22 13:55:48 +00:00
have_light_level ( FALSE ) , have_mastering_info ( FALSE ) , m_dframe ( dframe ) ,
m_ancillary ( 0 ) , m_timecode ( 0 ) , m_refcount ( 1 )
2022-06-28 13:40:55 +00:00
{
2024-07-22 13:55:48 +00:00
memset ( & light_level , 0 , sizeof ( light_level ) ) ;
memset ( & mastering_info , 0 , sizeof ( mastering_info ) ) ;
memset ( & colorimetry , 0 , sizeof ( colorimetry ) ) ;
}
void SetColorimetry ( GstVideoColorimetry * colorimetry )
{
this - > colorimetry = * colorimetry ;
}
void SetLightLevel ( GstVideoContentLightLevel * ll )
{
light_level = * ll ;
have_light_level = TRUE ;
}
void SetMastringInfo ( GstVideoMasteringDisplayInfo * mdi )
{
memcpy ( & mastering_info , mdi , sizeof ( * mdi ) ) ;
have_mastering_info = TRUE ;
2022-06-28 13:40:55 +00:00
}
virtual long STDMETHODCALLTYPE GetWidth ( void )
{
return m_frame ? GST_VIDEO_FRAME_WIDTH ( m_frame ) : m_dframe - > GetWidth ( ) ;
}
virtual long STDMETHODCALLTYPE GetHeight ( void )
{
return m_frame ? GST_VIDEO_FRAME_HEIGHT ( m_frame ) : m_dframe - > GetHeight ( ) ;
}
virtual long STDMETHODCALLTYPE GetRowBytes ( void )
{
return m_frame ? GST_VIDEO_FRAME_PLANE_STRIDE ( m_frame ,
0 ) : m_dframe - > GetRowBytes ( ) ;
}
virtual BMDPixelFormat STDMETHODCALLTYPE GetPixelFormat ( void )
{
if ( m_dframe )
return m_dframe - > GetPixelFormat ( ) ;
switch ( GST_VIDEO_FRAME_FORMAT ( m_frame ) ) {
case GST_VIDEO_FORMAT_UYVY :
return bmdFormat8BitYUV ;
case GST_VIDEO_FORMAT_v210 :
return bmdFormat10BitYUV ;
case GST_VIDEO_FORMAT_ARGB :
return bmdFormat8BitARGB ;
case GST_VIDEO_FORMAT_BGRA :
return bmdFormat8BitBGRA ;
2023-04-04 10:22:31 +00:00
case GST_VIDEO_FORMAT_r210 :
return bmdFormat10BitRGB ;
2022-06-28 13:40:55 +00:00
default :
g_assert_not_reached ( ) ;
}
}
virtual BMDFrameFlags STDMETHODCALLTYPE GetFlags ( void )
{
2024-07-22 13:55:48 +00:00
BMDFrameFlags flags = m_dframe ? m_dframe - > GetFlags ( ) : bmdFrameFlagDefault ;
if ( have_mastering_info | | have_light_level | |
colorimetry . transfer = = GST_VIDEO_TRANSFER_ARIB_STD_B67 ) {
flags | = bmdFrameContainsHDRMetadata ;
}
return flags ;
2022-06-28 13:40:55 +00:00
}
virtual HRESULT STDMETHODCALLTYPE GetBytes ( void * * buffer )
{
if ( m_dframe )
return m_dframe - > GetBytes ( buffer ) ;
* buffer = GST_VIDEO_FRAME_PLANE_DATA ( m_frame , 0 ) ;
return S_OK ;
}
virtual HRESULT STDMETHODCALLTYPE GetTimecode ( BMDTimecodeFormat format ,
IDeckLinkTimecode * * timecode )
{
* timecode = m_timecode ;
if ( m_timecode ) {
m_timecode - > AddRef ( ) ;
return S_OK ;
} else {
return S_FALSE ;
}
}
virtual HRESULT STDMETHODCALLTYPE SetTimecode ( GstVideoTimeCode * timecode )
{
if ( m_timecode ) {
m_timecode - > Release ( ) ;
}
m_timecode = new GstDecklinkTimecode ( timecode ) ;
return S_OK ;
}
virtual HRESULT STDMETHODCALLTYPE
GetAncillaryData ( IDeckLinkVideoFrameAncillary * * ancillary )
{
* ancillary = m_ancillary ;
if ( m_ancillary ) {
m_ancillary - > AddRef ( ) ;
return S_OK ;
} else {
return S_FALSE ;
}
}
virtual HRESULT STDMETHODCALLTYPE
SetAncillaryData ( IDeckLinkVideoFrameAncillary * ancillary )
{
if ( m_ancillary )
m_ancillary - > Release ( ) ;
if ( ancillary )
ancillary - > AddRef ( ) ;
m_ancillary = ancillary ;
return S_OK ;
}
2024-07-22 13:55:48 +00:00
virtual HRESULT STDMETHODCALLTYPE QueryInterface ( REFIID iid , LPVOID * ret )
2022-06-28 13:40:55 +00:00
{
2024-07-22 13:55:48 +00:00
GST_LOG ( " frame queryinterface: % " REFIID_FORMAT , REFIID_ARGS ( iid ) ) ;
if ( memcmp ( & iid , & IID_IDeckLinkVideoFrameMetadataExtensions , sizeof ( iid ) )
= = 0 ) {
AddRef ( ) ;
* ret = ( LPVOID * ) static_cast < IDeckLinkVideoFrameMetadataExtensions * > ( this ) ;
return S_OK ;
}
2022-06-28 13:40:55 +00:00
return E_NOINTERFACE ;
}
2024-07-22 13:55:48 +00:00
virtual HRESULT STDMETHODCALLTYPE GetInt ( /* in */ BMDDeckLinkFrameMetadataID metadataID , /* out */ int64_t * value )
{
GST_LOG ( " frame meta get int for 0x%x " , metadataID ) ;
switch ( metadataID ) {
case bmdDeckLinkFrameMetadataColorspace : {
switch ( colorimetry . matrix ) {
case GST_VIDEO_COLOR_MATRIX_BT601 :
* value = bmdColorspaceRec601 ;
return S_OK ;
case GST_VIDEO_COLOR_MATRIX_BT709 :
* value = bmdColorspaceRec709 ;
return S_OK ;
case GST_VIDEO_COLOR_MATRIX_BT2020 :
* value = bmdColorspaceRec2020 ;
return S_OK ;
default :
GST_DEBUG ( " no mapping from video color matrix 0x%x to BMD " , colorimetry . matrix ) ;
return E_INVALIDARG ;
}
break ;
}
case bmdDeckLinkFrameMetadataHDRElectroOpticalTransferFunc :
switch ( colorimetry . transfer ) {
case GST_VIDEO_TRANSFER_BT601 :
case GST_VIDEO_TRANSFER_BT709 :
case GST_VIDEO_TRANSFER_BT2020_10 :
if ( have_mastering_info & & have_mastering_info )
* value = 1 ;
else
* value = 0 ;
return S_OK ;
case GST_VIDEO_TRANSFER_SMPTE2084 :
* value = 2 ;
return S_OK ;
case GST_VIDEO_TRANSFER_ARIB_STD_B67 :
* value = 3 ;
return S_OK ;
default :
return E_INVALIDARG ;
}
break ;
default :
return E_INVALIDARG ;
}
}
virtual HRESULT STDMETHODCALLTYPE GetFloat ( /* in */ BMDDeckLinkFrameMetadataID metadataID , /* out */ double * value )
{
GST_LOG ( " frame meta get float for 0x%x " , metadataID ) ; \
switch ( metadataID ) {
case bmdDeckLinkFrameMetadataHDRDisplayPrimariesRedX :
if ( have_mastering_info ) {
* value = ( double ) mastering_info . display_primaries [ 0 ] . x / 50000.0 ;
return S_OK ;
}
return E_INVALIDARG ;
case bmdDeckLinkFrameMetadataHDRDisplayPrimariesRedY :
if ( have_mastering_info ) {
* value = ( double ) mastering_info . display_primaries [ 0 ] . y / 50000.0 ;
return S_OK ;
}
return E_INVALIDARG ;
case bmdDeckLinkFrameMetadataHDRDisplayPrimariesGreenX :
if ( have_mastering_info ) {
* value = ( double ) mastering_info . display_primaries [ 1 ] . x / 50000.0 ;
return S_OK ;
}
return E_INVALIDARG ;
case bmdDeckLinkFrameMetadataHDRDisplayPrimariesGreenY :
if ( have_mastering_info ) {
* value = ( double ) mastering_info . display_primaries [ 1 ] . y / 50000.0 ;
return S_OK ;
}
return E_INVALIDARG ;
case bmdDeckLinkFrameMetadataHDRDisplayPrimariesBlueX :
if ( have_mastering_info ) {
* value = ( double ) mastering_info . display_primaries [ 2 ] . x / 50000.0 ;
return S_OK ;
}
return E_INVALIDARG ;
case bmdDeckLinkFrameMetadataHDRDisplayPrimariesBlueY :
if ( have_mastering_info ) {
* value = ( double ) mastering_info . display_primaries [ 2 ] . y / 50000.0 ;
return S_OK ;
}
return E_INVALIDARG ;
case bmdDeckLinkFrameMetadataHDRWhitePointX :
if ( have_mastering_info ) {
* value = ( double ) mastering_info . white_point . x / 50000.0 ;
return S_OK ;
}
return E_INVALIDARG ;
case bmdDeckLinkFrameMetadataHDRWhitePointY :
if ( have_mastering_info ) {
* value = ( double ) mastering_info . white_point . y / 50000.0 ;
return S_OK ;
}
return E_INVALIDARG ;
case bmdDeckLinkFrameMetadataHDRMaxDisplayMasteringLuminance :
if ( have_mastering_info ) {
* value = ( double ) mastering_info . max_display_mastering_luminance * 65535.0 / 10000.0 ;
return S_OK ;
}
return E_INVALIDARG ;
case bmdDeckLinkFrameMetadataHDRMinDisplayMasteringLuminance :
if ( have_mastering_info ) {
* value = ( double ) mastering_info . min_display_mastering_luminance * 6.55350 / 10000.0 ;
return S_OK ;
}
return E_INVALIDARG ;
case bmdDeckLinkFrameMetadataHDRMaximumContentLightLevel :
if ( have_light_level ) {
* value = ( double ) light_level . max_content_light_level ;
return S_OK ;
}
return E_INVALIDARG ;
case bmdDeckLinkFrameMetadataHDRMaximumFrameAverageLightLevel :
if ( have_light_level ) {
* value = ( double ) light_level . max_frame_average_light_level ;
return S_OK ;
}
return E_INVALIDARG ;
default :
return E_INVALIDARG ;
}
}
virtual HRESULT STDMETHODCALLTYPE GetFlag ( /* in */ BMDDeckLinkFrameMetadataID metadataID , /* out */ bool * value )
{
GST_LOG ( " frame meta get flag for 0x%x " , metadataID ) ;
return E_INVALIDARG ;
}
virtual HRESULT STDMETHODCALLTYPE GetString ( /* in */ BMDDeckLinkFrameMetadataID metadataID , /* out */ COMSTR_T * value )
{
GST_LOG ( " frame meta get string for 0x%x " , metadataID ) ;
return E_INVALIDARG ;
}
virtual HRESULT STDMETHODCALLTYPE GetBytes ( /* in */ BMDDeckLinkFrameMetadataID metadataID , /* out */ void * buffer /* optional */ , /* in, out */ uint32_t * bufferSize )
{
GST_LOG ( " frame meta get bytes for 0x%x " , metadataID ) ;
return E_INVALIDARG ;
}
2022-06-28 13:40:55 +00:00
virtual ULONG STDMETHODCALLTYPE AddRef ( void )
{
ULONG ret ;
ret = g_atomic_int_add ( & m_refcount , 1 ) + 1 ;
return ret ;
}
virtual ULONG STDMETHODCALLTYPE Release ( void )
{
ULONG ret ;
ret = g_atomic_int_add ( & m_refcount , - 1 ) ;
if ( ret = = 1 ) {
delete this ;
}
return ret - 1 ;
}
2024-06-27 12:25:42 +00:00
GstClockTime running_time ;
GstClockTime running_time_duration ;
GstBuffer * sync_buffer ;
2022-06-28 13:40:55 +00:00
private :
GstVideoFrame * m_frame ;
2024-07-22 13:55:48 +00:00
gboolean have_light_level ;
gboolean have_mastering_info ;
2022-06-28 13:40:55 +00:00
IDeckLinkMutableVideoFrame * m_dframe ;
IDeckLinkVideoFrameAncillary * m_ancillary ;
GstDecklinkTimecode * m_timecode ;
int m_refcount ;
2024-07-22 13:55:48 +00:00
GstVideoContentLightLevel light_level ;
GstVideoMasteringDisplayInfo mastering_info ;
GstVideoColorimetry colorimetry ;
2022-06-28 13:40:55 +00:00
virtual ~ GstDecklinkVideoFrame ( ) {
if ( m_frame ) {
gst_video_frame_unmap ( m_frame ) ;
g_free ( m_frame ) ;
}
if ( m_dframe ) {
m_dframe - > Release ( ) ;
}
if ( m_ancillary ) {
m_ancillary - > Release ( ) ;
}
if ( m_timecode ) {
m_timecode - > Release ( ) ;
}
2024-06-27 12:25:42 +00:00
gst_clear_buffer ( & sync_buffer ) ;
}
} ;
class GStreamerVideoOutputCallback : public IDeckLinkVideoOutputCallback
{
public :
GStreamerVideoOutputCallback ( GstDecklinkVideoSink * sink )
: IDeckLinkVideoOutputCallback ( ) , m_refcount ( 1 )
{
m_sink = GST_DECKLINK_VIDEO_SINK_CAST ( gst_object_ref ( sink ) ) ;
g_mutex_init ( & m_mutex ) ;
}
virtual HRESULT WINAPI QueryInterface ( REFIID , LPVOID * )
{
return E_NOINTERFACE ;
}
virtual ULONG WINAPI AddRef ( void )
{
ULONG ret ;
g_mutex_lock ( & m_mutex ) ;
m_refcount + + ;
ret = m_refcount ;
g_mutex_unlock ( & m_mutex ) ;
return ret ;
}
virtual ULONG WINAPI Release ( void )
{
ULONG ret ;
g_mutex_lock ( & m_mutex ) ;
m_refcount - - ;
ret = m_refcount ;
g_mutex_unlock ( & m_mutex ) ;
if ( ret = = 0 ) {
delete this ;
}
return ret ;
}
virtual HRESULT WINAPI ScheduledFrameCompleted ( IDeckLinkVideoFrame *
completedFrame , BMDOutputFrameCompletionResult result )
{
GstDecklinkVideoFrame * frame = ( GstDecklinkVideoFrame * ) completedFrame ;
switch ( result ) {
case bmdOutputFrameCompleted :
GST_LOG_OBJECT ( m_sink , " Completed frame %p running time % "
GST_TIME_FORMAT , completedFrame , GST_TIME_ARGS ( frame - > running_time ) ) ;
break ;
case bmdOutputFrameDisplayedLate :
GST_INFO_OBJECT ( m_sink , " Late Frame %p running time % " GST_TIME_FORMAT ,
completedFrame , GST_TIME_ARGS ( frame - > running_time ) ) ;
break ;
case bmdOutputFrameDropped :
GST_INFO_OBJECT ( m_sink , " Dropped Frame %p running time % "
GST_TIME_FORMAT , completedFrame ,
GST_TIME_ARGS ( frame - > running_time ) ) ;
break ;
case bmdOutputFrameFlushed :
GST_INFO_OBJECT ( m_sink , " Flushed Frame %p running time % "
GST_TIME_FORMAT , completedFrame ,
GST_TIME_ARGS ( frame - > running_time ) ) ;
break ;
default :
GST_INFO_OBJECT ( m_sink , " Unknown Frame %p: %d, running time % "
GST_TIME_FORMAT , completedFrame , ( gint ) result ,
GST_TIME_ARGS ( frame - > running_time ) ) ;
break ;
}
return S_OK ;
}
virtual HRESULT WINAPI ScheduledPlaybackHasStopped ( void )
{
GST_LOG_OBJECT ( m_sink , " Scheduled playback stopped " ) ;
if ( m_sink - > output ) {
g_mutex_lock ( & m_sink - > output - > lock ) ;
g_cond_signal ( & m_sink - > output - > cond ) ;
g_mutex_unlock ( & m_sink - > output - > lock ) ;
}
return S_OK ;
2022-06-28 13:40:55 +00:00
}
2024-06-27 12:25:42 +00:00
virtual ~ GStreamerVideoOutputCallback ( ) {
gst_object_unref ( m_sink ) ;
g_mutex_clear ( & m_mutex ) ;
}
private :
GstDecklinkVideoSink * m_sink ;
GMutex m_mutex ;
gint m_refcount ;
2022-06-28 13:40:55 +00:00
} ;
2022-06-02 14:32:28 +00:00
/**
* GstDecklinkMappingFormat :
* @ GST_DECKLINK_MAPPING_FORMAT_DEFAULT : Don ' t change the mapping format
* @ GST_DECKLINK_MAPPING_FORMAT_LEVEL_A : Level A
* @ GST_DECKLINK_MAPPING_FORMAT_LEVEL_B : Level B
*
* Since : 1.22
*/
2014-12-13 16:23:31 +00:00
enum
{
PROP_0 ,
PROP_MODE ,
2016-04-14 15:26:33 +00:00
PROP_DEVICE_NUMBER ,
2016-05-15 14:25:44 +00:00
PROP_VIDEO_FORMAT ,
2019-06-25 09:51:32 +00:00
PROP_PROFILE_ID ,
2017-08-15 00:57:03 +00:00
PROP_TIMECODE_FORMAT ,
PROP_KEYER_MODE ,
2017-10-04 11:53:35 +00:00
PROP_KEYER_LEVEL ,
2018-11-07 15:15:25 +00:00
PROP_HW_SERIAL_NUMBER ,
PROP_CC_LINE ,
2019-05-03 21:15:32 +00:00
PROP_AFD_BAR_LINE ,
2022-06-02 14:32:28 +00:00
PROP_MAPPING_FORMAT ,
2022-09-26 11:57:15 +00:00
PROP_PERSISTENT_ID
2014-12-13 16:23:31 +00:00
} ;
static void gst_decklink_video_sink_set_property ( GObject * object ,
guint property_id , const GValue * value , GParamSpec * pspec ) ;
static void gst_decklink_video_sink_get_property ( GObject * object ,
guint property_id , GValue * value , GParamSpec * pspec ) ;
static void gst_decklink_video_sink_finalize ( GObject * object ) ;
static GstStateChangeReturn
gst_decklink_video_sink_change_state ( GstElement * element ,
GstStateChange transition ) ;
static GstClock * gst_decklink_video_sink_provide_clock ( GstElement * element ) ;
static GstCaps * gst_decklink_video_sink_get_caps ( GstBaseSink * bsink ,
GstCaps * filter ) ;
2015-01-28 10:41:17 +00:00
static gboolean gst_decklink_video_sink_set_caps ( GstBaseSink * bsink ,
GstCaps * caps ) ;
2014-12-13 16:23:31 +00:00
static GstFlowReturn gst_decklink_video_sink_render ( GstBaseSink * bsink ,
GstBuffer * buffer ) ;
static gboolean gst_decklink_video_sink_open ( GstBaseSink * bsink ) ;
static gboolean gst_decklink_video_sink_close ( GstBaseSink * bsink ) ;
2015-09-22 17:35:00 +00:00
static gboolean gst_decklink_video_sink_stop ( GstDecklinkVideoSink * self ) ;
2014-12-13 16:23:31 +00:00
static gboolean gst_decklink_video_sink_propose_allocation ( GstBaseSink * bsink ,
GstQuery * query ) ;
2018-11-28 04:28:15 +00:00
static gboolean gst_decklink_video_sink_event ( GstBaseSink * bsink ,
GstEvent * event ) ;
2024-06-27 12:25:42 +00:00
static void gst_decklink_video_sink_get_times ( GstBaseSink * bsink ,
GstBuffer * buffer , GstClockTime * start , GstClockTime * end ) ;
2014-12-13 16:23:31 +00:00
2015-01-28 14:48:26 +00:00
static void
gst_decklink_video_sink_start_scheduled_playback ( GstElement * element ) ;
2014-12-13 16:23:31 +00:00
# define parent_class gst_decklink_video_sink_parent_class
G_DEFINE_TYPE ( GstDecklinkVideoSink , gst_decklink_video_sink ,
GST_TYPE_BASE_SINK ) ;
2022-06-28 13:40:55 +00:00
GST_ELEMENT_REGISTER_DEFINE_WITH_CODE ( decklinkvideosink , " decklinkvideosink " ,
GST_RANK_NONE , GST_TYPE_DECKLINK_VIDEO_SINK ,
decklink_element_init ( plugin ) ) ;
2014-12-13 16:23:31 +00:00
2015-06-12 20:33:58 +00:00
static gboolean
reset_framerate ( GstCapsFeatures * features , GstStructure * structure ,
gpointer user_data )
{
gst_structure_set ( structure , " framerate " , GST_TYPE_FRACTION_RANGE , 0 , 1 ,
G_MAXINT , 1 , NULL ) ;
return TRUE ;
}
2014-12-13 16:23:31 +00:00
static void
gst_decklink_video_sink_class_init ( GstDecklinkVideoSinkClass * klass )
{
GObjectClass * gobject_class = G_OBJECT_CLASS ( klass ) ;
GstElementClass * element_class = GST_ELEMENT_CLASS ( klass ) ;
GstBaseSinkClass * basesink_class = GST_BASE_SINK_CLASS ( klass ) ;
GstCaps * templ_caps ;
gobject_class - > set_property = gst_decklink_video_sink_set_property ;
gobject_class - > get_property = gst_decklink_video_sink_get_property ;
gobject_class - > finalize = gst_decklink_video_sink_finalize ;
element_class - > change_state =
GST_DEBUG_FUNCPTR ( gst_decklink_video_sink_change_state ) ;
element_class - > provide_clock =
GST_DEBUG_FUNCPTR ( gst_decklink_video_sink_provide_clock ) ;
basesink_class - > get_caps =
GST_DEBUG_FUNCPTR ( gst_decklink_video_sink_get_caps ) ;
2015-01-28 10:41:17 +00:00
basesink_class - > set_caps =
GST_DEBUG_FUNCPTR ( gst_decklink_video_sink_set_caps ) ;
2014-12-13 16:23:31 +00:00
basesink_class - > render = GST_DEBUG_FUNCPTR ( gst_decklink_video_sink_render ) ;
// FIXME: These are misnamed in basesink!
basesink_class - > start = GST_DEBUG_FUNCPTR ( gst_decklink_video_sink_open ) ;
basesink_class - > stop = GST_DEBUG_FUNCPTR ( gst_decklink_video_sink_close ) ;
basesink_class - > propose_allocation =
GST_DEBUG_FUNCPTR ( gst_decklink_video_sink_propose_allocation ) ;
2018-11-28 04:28:15 +00:00
basesink_class - > event = GST_DEBUG_FUNCPTR ( gst_decklink_video_sink_event ) ;
2024-06-27 12:25:42 +00:00
basesink_class - > get_times =
GST_DEBUG_FUNCPTR ( gst_decklink_video_sink_get_times ) ;
2014-12-13 16:23:31 +00:00
g_object_class_install_property ( gobject_class , PROP_MODE ,
g_param_spec_enum ( " mode " , " Playback Mode " ,
" Video Mode to use for playback " ,
GST_TYPE_DECKLINK_MODE , GST_DECKLINK_MODE_NTSC ,
( GParamFlags ) ( G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
G_PARAM_CONSTRUCT ) ) ) ;
g_object_class_install_property ( gobject_class , PROP_DEVICE_NUMBER ,
g_param_spec_int ( " device-number " , " Device number " ,
" Output device instance to use " , 0 , G_MAXINT , 0 ,
( GParamFlags ) ( G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
G_PARAM_CONSTRUCT ) ) ) ;
2022-09-26 11:57:15 +00:00
/**
* GstDecklinkVideoSink : persistent - id
*
* Decklink device to use . Higher priority than " device-number " .
* BMDDeckLinkPersistentID is a device specific , 32 - bit unique identifier .
* It is stable even when the device is plugged in a different connector ,
* across reboots , and when plugged into different computers .
*
* Since : 1.22
*/
g_object_class_install_property ( gobject_class , PROP_PERSISTENT_ID ,
g_param_spec_int64 ( " persistent-id " , " Persistent id " ,
" Output device instance to use. Higher priority than \" device-number \" . " ,
DEFAULT_PERSISTENT_ID , G_MAXINT64 , DEFAULT_PERSISTENT_ID ,
( GParamFlags ) ( G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
G_PARAM_CONSTRUCT ) ) ) ;
2014-12-13 16:23:31 +00:00
2016-04-14 15:26:33 +00:00
g_object_class_install_property ( gobject_class , PROP_VIDEO_FORMAT ,
g_param_spec_enum ( " video-format " , " Video format " ,
" Video format type to use for playback " ,
GST_TYPE_DECKLINK_VIDEO_FORMAT , GST_DECKLINK_VIDEO_FORMAT_8BIT_YUV ,
( GParamFlags ) ( G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
G_PARAM_CONSTRUCT ) ) ) ;
2020-10-07 18:15:21 +00:00
/**
* GstDecklinkVideoSink : profile
*
* Specifies decklink profile to use .
*
* Since : 1.20
*/
2019-06-25 09:51:32 +00:00
g_object_class_install_property ( gobject_class , PROP_PROFILE_ID ,
g_param_spec_enum ( " profile " , " Profile " ,
" Certain DeckLink devices such as the DeckLink 8K Pro, the DeckLink "
" Quad 2 and the DeckLink Duo 2 support multiple profiles to "
" configure the capture and playback behavior of its sub-devices. "
" For the DeckLink Duo 2 and DeckLink Quad 2, a profile is shared "
" between any 2 sub-devices that utilize the same connectors. For the "
" DeckLink 8K Pro, a profile is shared between all 4 sub-devices. Any "
" sub-devices that share a profile are considered to be part of the "
" same profile group. "
2019-03-03 18:34:06 +00:00
" DeckLink Duo 2 support configuration of the duplex mode of "
2019-06-25 09:51:32 +00:00
" individual sub-devices. " ,
2020-10-25 11:30:55 +00:00
GST_TYPE_DECKLINK_PROFILE_ID , GST_DECKLINK_PROFILE_ID_DEFAULT ,
2019-03-03 18:34:06 +00:00
( GParamFlags ) ( G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
G_PARAM_CONSTRUCT ) ) ) ;
2016-05-15 14:25:44 +00:00
g_object_class_install_property ( gobject_class , PROP_TIMECODE_FORMAT ,
g_param_spec_enum ( " timecode-format " , " Timecode format " ,
" Timecode format type to use for playback " ,
GST_TYPE_DECKLINK_TIMECODE_FORMAT ,
GST_DECKLINK_TIMECODE_FORMAT_RP188ANY ,
( GParamFlags ) ( G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
G_PARAM_CONSTRUCT ) ) ) ;
2017-08-15 00:57:03 +00:00
g_object_class_install_property ( gobject_class , PROP_KEYER_MODE ,
g_param_spec_enum ( " keyer-mode " , " Keyer mode " ,
" Keyer mode to be enabled " ,
GST_TYPE_DECKLINK_KEYER_MODE ,
GST_DECKLINK_KEYER_MODE_OFF ,
( GParamFlags ) ( G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
G_PARAM_CONSTRUCT ) ) ) ;
g_object_class_install_property ( gobject_class , PROP_KEYER_LEVEL ,
g_param_spec_int ( " keyer-level " , " Keyer level " ,
" Keyer level " , 0 , 255 , 255 ,
( GParamFlags ) ( G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
G_PARAM_CONSTRUCT ) ) ) ;
2017-10-04 11:53:35 +00:00
g_object_class_install_property ( gobject_class , PROP_HW_SERIAL_NUMBER ,
g_param_spec_string ( " hw-serial-number " , " Hardware serial number " ,
" The serial number (hardware ID) of the Decklink card " ,
NULL , ( GParamFlags ) ( G_PARAM_READABLE | G_PARAM_STATIC_STRINGS ) ) ) ;
2018-11-07 15:15:25 +00:00
g_object_class_install_property ( gobject_class , PROP_CC_LINE ,
g_param_spec_int ( " cc-line " , " CC Line " ,
" Line number to use for inserting closed captions (0 = disabled) " , 0 ,
22 , 0 ,
( GParamFlags ) ( G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
G_PARAM_CONSTRUCT ) ) ) ;
2019-05-03 21:15:32 +00:00
g_object_class_install_property ( gobject_class , PROP_AFD_BAR_LINE ,
g_param_spec_int ( " afd-bar-line " , " AFD/Bar Line " ,
" Line number to use for inserting AFD/Bar data (0 = disabled) " , 0 ,
10000 , 0 ,
( GParamFlags ) ( G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
G_PARAM_CONSTRUCT ) ) ) ;
2022-06-02 14:32:28 +00:00
/**
* GstDecklinkVideoSink : mapping - format
*
* Specifies the 3 G - SDI mapping format to use ( SMPTE ST 425 - 1 : 2017 ) .
*
* Since : 1.22
*/
g_object_class_install_property ( gobject_class , PROP_MAPPING_FORMAT ,
g_param_spec_enum ( " mapping-format " , " 3G-SDI Mapping Format " ,
" 3G-SDI Mapping Format (Level A/B) " ,
GST_TYPE_DECKLINK_MAPPING_FORMAT , GST_DECKLINK_MAPPING_FORMAT_DEFAULT ,
( GParamFlags ) ( G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
G_PARAM_CONSTRUCT ) ) ) ;
2016-11-28 15:17:43 +00:00
templ_caps = gst_decklink_mode_get_template_caps ( FALSE ) ;
2015-06-12 20:33:58 +00:00
templ_caps = gst_caps_make_writable ( templ_caps ) ;
/* For output we support any framerate and only really care about timestamps */
gst_caps_map_in_place ( templ_caps , reset_framerate , NULL ) ;
2014-12-13 16:23:31 +00:00
gst_element_class_add_pad_template ( element_class ,
gst_pad_template_new ( " sink " , GST_PAD_SINK , GST_PAD_ALWAYS , templ_caps ) ) ;
gst_caps_unref ( templ_caps ) ;
gst_element_class_set_static_metadata ( element_class , " Decklink Video Sink " ,
2019-02-14 10:58:00 +00:00
" Video/Sink/Hardware " , " Decklink Sink " ,
" David Schleef <ds@entropywave.com>, "
2014-12-13 16:23:31 +00:00
" Sebastian Dröge <sebastian@centricular.com> " ) ;
GST_DEBUG_CATEGORY_INIT ( gst_decklink_video_sink_debug , " decklinkvideosink " ,
0 , " debug category for decklinkvideosink element " ) ;
2022-06-02 14:32:28 +00:00
2022-06-28 13:40:55 +00:00
gst_type_mark_as_plugin_api ( GST_TYPE_DECKLINK_MAPPING_FORMAT ,
( GstPluginAPIFlags ) 0 ) ;
2014-12-13 16:23:31 +00:00
}
static void
gst_decklink_video_sink_init ( GstDecklinkVideoSink * self )
{
self - > mode = GST_DECKLINK_MODE_NTSC ;
self - > device_number = 0 ;
2022-09-26 11:57:15 +00:00
self - > persistent_id = DEFAULT_PERSISTENT_ID ;
2016-04-14 15:26:33 +00:00
self - > video_format = GST_DECKLINK_VIDEO_FORMAT_8BIT_YUV ;
2020-10-25 11:30:55 +00:00
self - > profile_id = GST_DECKLINK_PROFILE_ID_DEFAULT ;
2016-05-15 14:25:44 +00:00
/* VITC is legacy, we should expect RP188 in modern use cases */
self - > timecode_format = bmdTimecodeRP188Any ;
2018-11-07 15:15:25 +00:00
self - > caption_line = 0 ;
2019-05-03 21:15:32 +00:00
self - > afd_bar_line = 0 ;
2022-06-02 14:32:28 +00:00
self - > mapping_format = GST_DECKLINK_MAPPING_FORMAT_DEFAULT ;
2024-06-27 12:25:42 +00:00
self - > pending_frames = g_queue_new ( ) ;
2015-01-14 12:32:51 +00:00
gst_base_sink_set_max_lateness ( GST_BASE_SINK_CAST ( self ) , 20 * GST_MSECOND ) ;
gst_base_sink_set_qos_enabled ( GST_BASE_SINK_CAST ( self ) , TRUE ) ;
2014-12-13 16:23:31 +00:00
}
void
gst_decklink_video_sink_set_property ( GObject * object , guint property_id ,
const GValue * value , GParamSpec * pspec )
{
GstDecklinkVideoSink * self = GST_DECKLINK_VIDEO_SINK_CAST ( object ) ;
switch ( property_id ) {
case PROP_MODE :
self - > mode = ( GstDecklinkModeEnum ) g_value_get_enum ( value ) ;
break ;
case PROP_DEVICE_NUMBER :
self - > device_number = g_value_get_int ( value ) ;
break ;
2016-04-14 15:26:33 +00:00
case PROP_VIDEO_FORMAT :
self - > video_format = ( GstDecklinkVideoFormat ) g_value_get_enum ( value ) ;
switch ( self - > video_format ) {
case GST_DECKLINK_VIDEO_FORMAT_AUTO :
case GST_DECKLINK_VIDEO_FORMAT_8BIT_YUV :
case GST_DECKLINK_VIDEO_FORMAT_10BIT_YUV :
case GST_DECKLINK_VIDEO_FORMAT_8BIT_ARGB :
case GST_DECKLINK_VIDEO_FORMAT_8BIT_BGRA :
2023-04-04 10:22:31 +00:00
case GST_DECKLINK_VIDEO_FORMAT_10BIT_RGB :
2016-04-14 15:26:33 +00:00
break ;
default :
GST_ELEMENT_WARNING ( GST_ELEMENT ( self ) , CORE , NOT_IMPLEMENTED ,
( " Format %d not supported " , self - > video_format ) , ( NULL ) ) ;
break ;
}
break ;
2019-06-25 09:51:32 +00:00
case PROP_PROFILE_ID :
2020-10-25 11:30:55 +00:00
self - > profile_id = ( GstDecklinkProfileId ) g_value_get_enum ( value ) ;
2019-03-03 18:34:06 +00:00
break ;
2016-05-15 14:25:44 +00:00
case PROP_TIMECODE_FORMAT :
self - > timecode_format =
gst_decklink_timecode_format_from_enum ( ( GstDecklinkTimecodeFormat )
g_value_get_enum ( value ) ) ;
break ;
2017-08-15 00:57:03 +00:00
case PROP_KEYER_MODE :
self - > keyer_mode =
gst_decklink_keyer_mode_from_enum ( ( GstDecklinkKeyerMode )
g_value_get_enum ( value ) ) ;
break ;
case PROP_KEYER_LEVEL :
self - > keyer_level = g_value_get_int ( value ) ;
break ;
2018-11-07 15:15:25 +00:00
case PROP_CC_LINE :
self - > caption_line = g_value_get_int ( value ) ;
break ;
2019-05-03 21:15:32 +00:00
case PROP_AFD_BAR_LINE :
self - > afd_bar_line = g_value_get_int ( value ) ;
break ;
2022-06-02 14:32:28 +00:00
case PROP_MAPPING_FORMAT :
2022-06-28 13:40:55 +00:00
self - > mapping_format =
( GstDecklinkMappingFormat ) g_value_get_enum ( value ) ;
2022-06-02 14:32:28 +00:00
break ;
2022-09-26 11:57:15 +00:00
case PROP_PERSISTENT_ID :
self - > persistent_id = g_value_get_int64 ( value ) ;
break ;
2014-12-13 16:23:31 +00:00
default :
G_OBJECT_WARN_INVALID_PROPERTY_ID ( object , property_id , pspec ) ;
break ;
}
}
void
gst_decklink_video_sink_get_property ( GObject * object , guint property_id ,
GValue * value , GParamSpec * pspec )
{
GstDecklinkVideoSink * self = GST_DECKLINK_VIDEO_SINK_CAST ( object ) ;
switch ( property_id ) {
case PROP_MODE :
g_value_set_enum ( value , self - > mode ) ;
break ;
case PROP_DEVICE_NUMBER :
g_value_set_int ( value , self - > device_number ) ;
break ;
2016-04-14 15:26:33 +00:00
case PROP_VIDEO_FORMAT :
g_value_set_enum ( value , self - > video_format ) ;
break ;
2019-06-25 09:51:32 +00:00
case PROP_PROFILE_ID :
2020-10-25 11:30:55 +00:00
g_value_set_enum ( value , self - > profile_id ) ;
2019-03-03 18:34:06 +00:00
break ;
2016-05-15 14:25:44 +00:00
case PROP_TIMECODE_FORMAT :
g_value_set_enum ( value ,
gst_decklink_timecode_format_to_enum ( self - > timecode_format ) ) ;
break ;
2017-08-15 00:57:03 +00:00
case PROP_KEYER_MODE :
g_value_set_enum ( value ,
gst_decklink_keyer_mode_to_enum ( self - > keyer_mode ) ) ;
break ;
case PROP_KEYER_LEVEL :
g_value_set_int ( value , self - > keyer_level ) ;
break ;
2017-10-04 11:53:35 +00:00
case PROP_HW_SERIAL_NUMBER :
if ( self - > output )
g_value_set_string ( value , self - > output - > hw_serial_number ) ;
else
g_value_set_string ( value , NULL ) ;
break ;
2018-11-07 15:15:25 +00:00
case PROP_CC_LINE :
g_value_set_int ( value , self - > caption_line ) ;
break ;
2019-05-03 21:15:32 +00:00
case PROP_AFD_BAR_LINE :
g_value_set_int ( value , self - > afd_bar_line ) ;
break ;
2022-06-02 14:32:28 +00:00
case PROP_MAPPING_FORMAT :
g_value_set_enum ( value , self - > mapping_format ) ;
break ;
2022-09-26 11:57:15 +00:00
case PROP_PERSISTENT_ID :
g_value_set_int64 ( value , self - > persistent_id ) ;
break ;
2014-12-13 16:23:31 +00:00
default :
G_OBJECT_WARN_INVALID_PROPERTY_ID ( object , property_id , pspec ) ;
break ;
}
}
2024-06-27 12:25:42 +00:00
static void
unref_frame ( GstDecklinkVideoFrame * frame )
{
if ( frame )
frame - > Release ( ) ;
}
2014-12-13 16:23:31 +00:00
void
gst_decklink_video_sink_finalize ( GObject * object )
{
2024-06-27 12:25:42 +00:00
GstDecklinkVideoSink * self = GST_DECKLINK_VIDEO_SINK_CAST ( object ) ;
g_queue_clear_full ( self - > pending_frames , ( GDestroyNotify ) unref_frame ) ;
self - > pending_frames = NULL ;
2014-12-13 16:23:31 +00:00
G_OBJECT_CLASS ( parent_class ) - > finalize ( object ) ;
}
2015-01-28 10:41:17 +00:00
static gboolean
gst_decklink_video_sink_set_caps ( GstBaseSink * bsink , GstCaps * caps )
{
GstDecklinkVideoSink * self = GST_DECKLINK_VIDEO_SINK_CAST ( bsink ) ;
const GstDecklinkMode * mode ;
HRESULT ret ;
2016-05-15 14:25:44 +00:00
BMDVideoOutputFlags flags ;
2017-02-01 14:45:53 +00:00
GstVideoInfo info ;
2015-01-28 10:41:17 +00:00
GST_DEBUG_OBJECT ( self , " Setting caps % " GST_PTR_FORMAT , caps ) ;
2017-02-01 14:45:53 +00:00
if ( ! gst_video_info_from_caps ( & info , caps ) )
2015-01-28 10:41:17 +00:00
return FALSE ;
2017-02-01 14:45:53 +00:00
g_mutex_lock ( & self - > output - > lock ) ;
if ( self - > output - > video_enabled ) {
if ( self - > info . finfo - > format = = info . finfo - > format & &
self - > info . width = = info . width & & self - > info . height = = info . height ) {
// FIXME: We should also consider the framerate as it is used
// for mode selection below in auto mode
GST_DEBUG_OBJECT ( self , " Nothing relevant has changed " ) ;
self - > info = info ;
2024-07-22 13:55:48 +00:00
self - > have_light_level =
gst_video_content_light_level_from_caps ( & self - > light_level , caps ) ;
self - > have_mastering_info =
gst_video_mastering_display_info_from_caps ( & self - > mastering_info ,
caps ) ;
2017-02-01 14:45:53 +00:00
g_mutex_unlock ( & self - > output - > lock ) ;
return TRUE ;
} else {
GST_DEBUG_OBJECT ( self , " Reconfiguration not supported at this point " ) ;
g_mutex_unlock ( & self - > output - > lock ) ;
return FALSE ;
}
}
g_mutex_unlock ( & self - > output - > lock ) ;
2015-01-28 10:41:17 +00:00
self - > output - > output - > SetScheduledFrameCompletionCallback ( new
GStreamerVideoOutputCallback ( self ) ) ;
2015-12-17 15:26:29 +00:00
if ( self - > mode = = GST_DECKLINK_MODE_AUTO ) {
2016-04-14 15:26:33 +00:00
BMDPixelFormat f ;
mode = gst_decklink_find_mode_and_format_for_caps ( caps , & f ) ;
2015-12-17 15:26:29 +00:00
if ( mode = = NULL ) {
GST_WARNING_OBJECT ( self ,
" Failed to find compatible mode for caps % " GST_PTR_FORMAT , caps ) ;
return FALSE ;
}
2016-04-14 15:26:33 +00:00
if ( self - > video_format ! = GST_DECKLINK_VIDEO_FORMAT_AUTO & &
gst_decklink_pixel_format_from_type ( self - > video_format ) ! = f ) {
GST_WARNING_OBJECT ( self , " Failed to set pixel format to %d " ,
self - > video_format ) ;
return FALSE ;
}
2015-12-17 15:26:29 +00:00
} else {
2016-04-14 15:26:33 +00:00
/* We don't have to give the format in EnableVideoOutput. Therefore,
* even if it ' s AUTO , we have it stored in self - > info and set it in
* gst_decklink_video_sink_prepare */
2015-12-17 15:26:29 +00:00
mode = gst_decklink_get_mode ( self - > mode ) ;
g_assert ( mode ! = NULL ) ;
} ;
2015-01-28 10:41:17 +00:00
2017-08-15 00:57:03 +00:00
/* enable or disable keyer */
if ( self - > output - > keyer ! = NULL ) {
if ( self - > keyer_mode = = bmdKeyerModeOff ) {
self - > output - > keyer - > Disable ( ) ;
} else if ( self - > keyer_mode = = bmdKeyerModeInternal ) {
self - > output - > keyer - > Enable ( false ) ;
self - > output - > keyer - > SetLevel ( self - > keyer_level ) ;
} else if ( self - > keyer_mode = = bmdKeyerModeExternal ) {
self - > output - > keyer - > Enable ( true ) ;
self - > output - > keyer - > SetLevel ( self - > keyer_level ) ;
} else {
g_assert_not_reached ( ) ;
}
} else if ( self - > keyer_mode ! = bmdKeyerModeOff ) {
GST_WARNING_OBJECT ( self , " Failed to set keyer to mode %d " ,
self - > keyer_mode ) ;
}
2016-05-15 14:25:44 +00:00
/* The timecode_format itself is used when we embed the actual timecode data
* into the frame . Now we only need to know which of the two standards the
* timecode format will adhere to : VITC or RP188 , and send the appropriate
* flag to EnableVideoOutput . The exact format is specified later .
*
* Note that this flag will have no effect in practice if the video stream
* does not contain timecode metadata .
*/
2019-05-03 21:15:32 +00:00
if ( ( gint64 ) self - > timecode_format = =
( gint64 ) GST_DECKLINK_TIMECODE_FORMAT_VITC
| | ( gint64 ) self - > timecode_format = =
( gint64 ) GST_DECKLINK_TIMECODE_FORMAT_VITCFIELD2 )
2016-05-15 14:25:44 +00:00
flags = bmdVideoOutputVITC ;
else
flags = bmdVideoOutputRP188 ;
2019-05-03 21:15:32 +00:00
if ( self - > caption_line > 0 | | self - > afd_bar_line > 0 )
2018-11-13 08:02:57 +00:00
flags = ( BMDVideoOutputFlags ) ( flags | bmdVideoOutputVANC ) ;
2018-11-07 15:15:25 +00:00
2016-05-15 14:25:44 +00:00
ret = self - > output - > output - > EnableVideoOutput ( mode - > mode , flags ) ;
2015-01-28 10:41:17 +00:00
if ( ret ! = S_OK ) {
2017-07-18 22:49:34 +00:00
GST_WARNING_OBJECT ( self , " Failed to enable video output: 0x%08lx " ,
( unsigned long ) ret ) ;
2015-01-28 10:41:17 +00:00
return FALSE ;
}
2017-02-01 14:45:53 +00:00
self - > info = info ;
2024-07-22 13:55:48 +00:00
self - > have_light_level =
gst_video_content_light_level_from_caps ( & self - > light_level , caps ) ;
self - > have_mastering_info =
gst_video_mastering_display_info_from_caps ( & self - > mastering_info , caps ) ;
2015-01-28 10:41:17 +00:00
g_mutex_lock ( & self - > output - > lock ) ;
self - > output - > mode = mode ;
2015-01-28 13:21:40 +00:00
self - > output - > video_enabled = TRUE ;
2015-01-28 14:48:26 +00:00
if ( self - > output - > start_scheduled_playback )
self - > output - > start_scheduled_playback ( self - > output - > videosink ) ;
2015-01-28 10:41:17 +00:00
g_mutex_unlock ( & self - > output - > lock ) ;
2018-11-07 15:15:25 +00:00
if ( self - > vbiencoder ) {
gst_video_vbi_encoder_free ( self - > vbiencoder ) ;
self - > vbiencoder = NULL ;
self - > anc_vformat = GST_VIDEO_FORMAT_UNKNOWN ;
}
2015-01-28 10:41:17 +00:00
return TRUE ;
}
2024-07-22 13:55:48 +00:00
static BMDDisplayModeFlags
display_mode_flags ( GstDecklinkVideoSink * self , GstDecklinkModeEnum e )
{
BMDDisplayModeFlags display_flags =
bmdDisplayModeColorspaceRec601 | bmdDisplayModeColorspaceRec709 |
bmdDisplayModeColorspaceRec2020 ;
if ( self - > output & & self - > output - > output ) {
const GstDecklinkMode * gst_mode = gst_decklink_get_mode ( e ) ;
IDeckLinkDisplayMode * display_mode = nullptr ;
bool supports_colorspace = false ;
self - > output - > attributes - > GetFlag ( BMDDeckLinkSupportsColorspaceMetadata ,
& supports_colorspace ) ;
if ( ! supports_colorspace ) {
self - > output - > output - > GetDisplayMode ( gst_mode - > mode , & display_mode ) ;
if ( display_mode ) {
display_flags = display_mode - > GetFlags ( ) ;
display_mode - > Release ( ) ;
}
}
}
return display_flags ;
}
static BMDDynamicRange
device_dynamic_range ( GstDecklinkVideoSink * self )
{
BMDDynamicRange range =
( BMDDynamicRange ) ( bmdDynamicRangeSDR | bmdDynamicRangeHDRStaticPQ |
bmdDynamicRangeHDRStaticHLG ) ;
if ( self - > output & & self - > output - > attributes ) {
gint64 tmp_int = 0 ;
HRESULT ret =
self - > output - > attributes - > GetInt ( BMDDeckLinkSupportedDynamicRange ,
& tmp_int ) ;
if ( ret = = S_OK )
range = ( BMDDynamicRange ) tmp_int ;
}
return range ;
}
2014-12-13 16:23:31 +00:00
static GstCaps *
gst_decklink_video_sink_get_caps ( GstBaseSink * bsink , GstCaps * filter )
{
GstDecklinkVideoSink * self = GST_DECKLINK_VIDEO_SINK_CAST ( bsink ) ;
GstCaps * mode_caps , * caps ;
2016-04-14 15:26:33 +00:00
if ( self - > mode = = GST_DECKLINK_MODE_AUTO
& & self - > video_format = = GST_DECKLINK_VIDEO_FORMAT_AUTO )
2016-11-28 15:17:43 +00:00
mode_caps = gst_decklink_mode_get_template_caps ( FALSE ) ;
2016-04-14 15:26:33 +00:00
else if ( self - > video_format = = GST_DECKLINK_VIDEO_FORMAT_AUTO )
2024-07-22 13:55:48 +00:00
mode_caps =
gst_decklink_mode_get_caps_all_formats ( self - > mode ,
display_mode_flags ( self , self - > mode ) , device_dynamic_range ( self ) ,
FALSE ) ;
2016-04-14 15:26:33 +00:00
else if ( self - > mode = = GST_DECKLINK_MODE_AUTO )
mode_caps =
gst_decklink_pixel_format_get_caps ( gst_decklink_pixel_format_from_type
2016-11-28 15:17:43 +00:00
( self - > video_format ) , FALSE ) ;
2024-07-22 13:55:48 +00:00
else {
2016-04-14 15:26:33 +00:00
mode_caps =
gst_decklink_mode_get_caps ( self - > mode ,
2024-07-22 13:55:48 +00:00
display_mode_flags ( self , self - > mode ) ,
gst_decklink_pixel_format_from_type ( self - > video_format ) ,
device_dynamic_range ( self ) , FALSE ) ;
}
2015-06-12 20:33:58 +00:00
mode_caps = gst_caps_make_writable ( mode_caps ) ;
/* For output we support any framerate and only really care about timestamps */
gst_caps_map_in_place ( mode_caps , reset_framerate , NULL ) ;
2014-12-13 16:23:31 +00:00
if ( filter ) {
caps =
gst_caps_intersect_full ( filter , mode_caps , GST_CAPS_INTERSECT_FIRST ) ;
gst_caps_unref ( mode_caps ) ;
} else {
caps = mode_caps ;
}
return caps ;
}
2017-11-08 17:31:37 +00:00
void
gst_decklink_video_sink_convert_to_internal_clock ( GstDecklinkVideoSink * self ,
2014-12-19 13:35:03 +00:00
GstClockTime * timestamp , GstClockTime * duration )
{
2017-11-08 17:31:37 +00:00
GstClock * clock ;
2018-11-28 04:28:15 +00:00
GstClockTime internal_base , external_base , internal_offset ;
2014-12-19 13:35:03 +00:00
g_assert ( timestamp ! = NULL ) ;
clock = gst_element_get_clock ( GST_ELEMENT_CAST ( self ) ) ;
2018-11-28 04:28:15 +00:00
GST_OBJECT_LOCK ( self ) ;
internal_base = self - > internal_base_time ;
external_base = self - > external_base_time ;
internal_offset = self - > internal_time_offset ;
GST_OBJECT_UNLOCK ( self ) ;
2018-07-08 14:54:04 +00:00
if ( ! clock | | clock ! = self - > output - > clock ) {
2014-12-19 13:35:03 +00:00
GstClockTime internal , external , rate_n , rate_d ;
2018-07-08 14:54:04 +00:00
GstClockTime external_timestamp = * timestamp ;
GstClockTime base_time ;
2014-12-19 13:35:03 +00:00
gst_clock_get_calibration ( self - > output - > clock , & internal , & external ,
& rate_n , & rate_d ) ;
2018-07-08 14:54:04 +00:00
// Convert to the running time corresponding to both clock times
2018-11-28 04:28:15 +00:00
if ( ! GST_CLOCK_TIME_IS_VALID ( internal_base ) | | internal < internal_base )
2018-07-08 14:54:04 +00:00
internal = 0 ;
else
internal - = internal_base ;
2015-09-03 13:36:57 +00:00
2018-11-28 04:28:15 +00:00
if ( ! GST_CLOCK_TIME_IS_VALID ( external_base ) | | external < external_base )
2018-07-08 14:54:04 +00:00
external = 0 ;
else
external - = external_base ;
// Convert timestamp to the "running time" since we started scheduled
// playback, that is the difference between the pipeline's base time
// and our own base time.
base_time = gst_element_get_base_time ( GST_ELEMENT_CAST ( self ) ) ;
if ( base_time > external_base )
base_time = 0 ;
else
base_time = external_base - base_time ;
2015-09-03 13:36:57 +00:00
2018-07-08 14:54:04 +00:00
if ( external_timestamp < base_time )
external_timestamp = 0 ;
else
external_timestamp = external_timestamp - base_time ;
// Get the difference in the external time, note
// that the running time is external time.
// Then scale this difference and offset it to
// our internal time. Now we have the running time
// according to our internal clock.
//
// For the duration we just scale
* timestamp =
gst_clock_unadjust_with_calibration ( NULL , external_timestamp ,
internal , external , rate_n , rate_d ) ;
GST_LOG_OBJECT ( self ,
" Converted % " GST_TIME_FORMAT " to % " GST_TIME_FORMAT " (internal: % "
GST_TIME_FORMAT " external % " GST_TIME_FORMAT " rate: %lf) " ,
GST_TIME_ARGS ( external_timestamp ) , GST_TIME_ARGS ( * timestamp ) ,
GST_TIME_ARGS ( internal ) , GST_TIME_ARGS ( external ) ,
( ( gdouble ) rate_n ) / ( ( gdouble ) rate_d ) ) ;
if ( duration ) {
GstClockTime external_duration = * duration ;
* duration = gst_util_uint64_scale ( external_duration , rate_d , rate_n ) ;
2014-12-19 13:35:03 +00:00
GST_LOG_OBJECT ( self ,
2018-07-08 14:54:04 +00:00
" Converted duration % " GST_TIME_FORMAT " to % " GST_TIME_FORMAT
" (internal: % " GST_TIME_FORMAT " external % " GST_TIME_FORMAT
" rate: %lf) " , GST_TIME_ARGS ( external_duration ) ,
GST_TIME_ARGS ( * duration ) , GST_TIME_ARGS ( internal ) ,
GST_TIME_ARGS ( external ) , ( ( gdouble ) rate_n ) / ( ( gdouble ) rate_d ) ) ;
2014-12-19 13:35:03 +00:00
}
} else {
2018-07-08 14:54:04 +00:00
GST_LOG_OBJECT ( self , " No clock conversion needed, same clocks: % "
GST_TIME_FORMAT , GST_TIME_ARGS ( * timestamp ) ) ;
2014-12-19 13:35:03 +00:00
}
2018-07-08 14:54:04 +00:00
2018-11-28 04:28:15 +00:00
if ( external_base ! = GST_CLOCK_TIME_NONE & &
2019-05-03 21:15:32 +00:00
internal_base ! = GST_CLOCK_TIME_NONE )
2018-11-28 04:28:15 +00:00
* timestamp + = internal_offset ;
else
* timestamp = gst_clock_get_internal_time ( self - > output - > clock ) ;
2018-07-08 14:54:04 +00:00
2019-10-14 07:22:18 +00:00
GST_DEBUG_OBJECT ( self , " Output timestamp % " GST_TIME_FORMAT
2024-06-27 12:25:42 +00:00
" using clock epoch % " GST_TIME_FORMAT " and offset % " GST_TIME_FORMAT ,
GST_TIME_ARGS ( * timestamp ) , GST_TIME_ARGS ( self - > output - > clock_epoch ) ,
GST_TIME_ARGS ( internal_offset ) ) ;
2019-01-02 15:18:58 +00:00
if ( clock )
gst_object_unref ( clock ) ;
2014-12-19 13:35:03 +00:00
}
2018-12-16 09:02:50 +00:00
/* Copied from ext/closedcaption/gstccconverter.c */
/* Converts raw CEA708 cc_data and an optional timecode into CDP */
static guint
convert_cea708_cc_data_cea708_cdp_internal ( GstDecklinkVideoSink * self ,
const guint8 * cc_data , guint cc_data_len , guint8 * cdp , guint cdp_len ,
const GstVideoTimeCodeMeta * tc_meta )
{
GstByteWriter bw ;
guint8 flags , checksum ;
guint i , len ;
const GstDecklinkMode * mode = gst_decklink_get_mode ( self - > mode ) ;
gst_byte_writer_init_with_data ( & bw , cdp , cdp_len , FALSE ) ;
gst_byte_writer_put_uint16_be_unchecked ( & bw , 0x9669 ) ;
/* Write a length of 0 for now */
gst_byte_writer_put_uint8_unchecked ( & bw , 0 ) ;
if ( mode - > fps_n = = 24000 & & mode - > fps_d = = 1001 ) {
gst_byte_writer_put_uint8_unchecked ( & bw , 0x1f ) ;
} else if ( mode - > fps_n = = 24 & & mode - > fps_d = = 1 ) {
gst_byte_writer_put_uint8_unchecked ( & bw , 0x2f ) ;
} else if ( mode - > fps_n = = 25 & & mode - > fps_d = = 1 ) {
gst_byte_writer_put_uint8_unchecked ( & bw , 0x3f ) ;
2021-02-26 14:36:58 +00:00
} else if ( mode - > fps_n = = 30000 & & mode - > fps_d = = 1001 ) {
2018-12-16 09:02:50 +00:00
gst_byte_writer_put_uint8_unchecked ( & bw , 0x4f ) ;
} else if ( mode - > fps_n = = 30 & & mode - > fps_d = = 1 ) {
gst_byte_writer_put_uint8_unchecked ( & bw , 0x5f ) ;
} else if ( mode - > fps_n = = 50 & & mode - > fps_d = = 1 ) {
gst_byte_writer_put_uint8_unchecked ( & bw , 0x6f ) ;
} else if ( mode - > fps_n = = 60000 & & mode - > fps_d = = 1001 ) {
gst_byte_writer_put_uint8_unchecked ( & bw , 0x7f ) ;
} else if ( mode - > fps_n = = 60 & & mode - > fps_d = = 1 ) {
gst_byte_writer_put_uint8_unchecked ( & bw , 0x8f ) ;
} else {
g_assert_not_reached ( ) ;
}
/* ccdata_present | caption_service_active */
flags = 0x42 ;
/* time_code_present */
if ( tc_meta )
flags | = 0x80 ;
/* reserved */
flags | = 0x01 ;
gst_byte_writer_put_uint8_unchecked ( & bw , flags ) ;
gst_byte_writer_put_uint16_be_unchecked ( & bw , self - > cdp_hdr_sequence_cntr ) ;
if ( tc_meta ) {
const GstVideoTimeCode * tc = & tc_meta - > tc ;
2020-07-03 03:02:33 +00:00
guint8 u8 ;
2018-12-16 09:02:50 +00:00
gst_byte_writer_put_uint8_unchecked ( & bw , 0x71 ) ;
2020-07-03 03:02:33 +00:00
/* reserved 11 - 2 bits */
u8 = 0xc0 ;
/* tens of hours - 2 bits */
u8 | = ( ( tc - > hours / 10 ) & 0x3 ) < < 4 ;
/* units of hours - 4 bits */
u8 | = ( tc - > hours % 10 ) & 0xf ;
gst_byte_writer_put_uint8_unchecked ( & bw , u8 ) ;
/* reserved 1 - 1 bit */
u8 = 0x80 ;
/* tens of minutes - 3 bits */
u8 | = ( ( tc - > minutes / 10 ) & 0x7 ) < < 4 ;
/* units of minutes - 4 bits */
u8 | = ( tc - > minutes % 10 ) & 0xf ;
gst_byte_writer_put_uint8_unchecked ( & bw , u8 ) ;
/* field flag - 1 bit */
u8 = tc - > field_count < 2 ? 0x00 : 0x80 ;
/* tens of seconds - 3 bits */
u8 | = ( ( tc - > seconds / 10 ) & 0x7 ) < < 4 ;
/* units of seconds - 4 bits */
u8 | = ( tc - > seconds % 10 ) & 0xf ;
gst_byte_writer_put_uint8_unchecked ( & bw , u8 ) ;
/* drop frame flag - 1 bit */
u8 = ( tc - > config . flags & GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME ) ? 0x80 :
0x00 ;
/* reserved0 - 1 bit */
/* tens of frames - 2 bits */
u8 | = ( ( tc - > frames / 10 ) & 0x3 ) < < 4 ;
/* units of frames 4 bits */
u8 | = ( tc - > frames % 10 ) & 0xf ;
gst_byte_writer_put_uint8_unchecked ( & bw , u8 ) ;
2018-12-16 09:02:50 +00:00
}
gst_byte_writer_put_uint8_unchecked ( & bw , 0x72 ) ;
gst_byte_writer_put_uint8_unchecked ( & bw , 0xe0 | cc_data_len / 3 ) ;
gst_byte_writer_put_data_unchecked ( & bw , cc_data , cc_data_len ) ;
gst_byte_writer_put_uint8_unchecked ( & bw , 0x74 ) ;
gst_byte_writer_put_uint16_be_unchecked ( & bw , self - > cdp_hdr_sequence_cntr ) ;
self - > cdp_hdr_sequence_cntr + + ;
/* We calculate the checksum afterwards */
gst_byte_writer_put_uint8_unchecked ( & bw , 0 ) ;
len = gst_byte_writer_get_pos ( & bw ) ;
gst_byte_writer_set_pos ( & bw , 2 ) ;
gst_byte_writer_put_uint8_unchecked ( & bw , len ) ;
checksum = 0 ;
for ( i = 0 ; i < len ; i + + ) {
checksum + = cdp [ i ] ;
}
checksum & = 0xff ;
checksum = 256 - checksum ;
cdp [ len - 1 ] = checksum ;
return len ;
}
2019-05-03 21:15:32 +00:00
static void
write_vbi ( GstDecklinkVideoSink * self , GstBuffer * buffer ,
2022-06-28 13:40:55 +00:00
BMDPixelFormat format , GstDecklinkVideoFrame * frame ,
2019-05-03 21:15:32 +00:00
GstVideoTimeCodeMeta * tc_meta )
{
IDeckLinkVideoFrameAncillary * vanc_frame = NULL ;
gpointer iter = NULL ;
GstVideoCaptionMeta * cc_meta ;
guint8 * vancdata ;
gboolean got_captions = FALSE ;
if ( self - > caption_line = = 0 & & self - > afd_bar_line = = 0 )
return ;
if ( self - > vbiencoder = = NULL ) {
self - > vbiencoder =
2020-01-08 05:42:21 +00:00
gst_video_vbi_encoder_new ( GST_VIDEO_FORMAT_v210 , self - > info . width ) ;
self - > anc_vformat = GST_VIDEO_FORMAT_v210 ;
2019-05-03 21:15:32 +00:00
}
/* Put any closed captions into the configured line */
while ( ( cc_meta =
( GstVideoCaptionMeta * ) gst_buffer_iterate_meta_filtered ( buffer ,
& iter , GST_VIDEO_CAPTION_META_API_TYPE ) ) ) {
switch ( cc_meta - > caption_type ) {
case GST_VIDEO_CAPTION_TYPE_CEA608_RAW : {
guint8 data [ 138 ] ;
guint i , n ;
n = cc_meta - > size / 2 ;
if ( cc_meta - > size > 46 ) {
GST_WARNING_OBJECT ( self , " Too big raw CEA608 buffer " ) ;
break ;
}
/* This is the offset from line 9 for 525-line fields and from line
* 5 for 625 - line fields .
*
* The highest bit is set for field 1 but not for field 0 , but we
* have no way of knowning the field here
*/
for ( i = 0 ; i < n ; i + + ) {
data [ 3 * i ] = 0x80 | ( self - > info . height = =
525 ? self - > caption_line - 9 : self - > caption_line - 5 ) ;
data [ 3 * i + 1 ] = cc_meta - > data [ 2 * i ] ;
data [ 3 * i + 2 ] = cc_meta - > data [ 2 * i + 1 ] ;
}
if ( ! gst_video_vbi_encoder_add_ancillary ( self - > vbiencoder ,
FALSE ,
GST_VIDEO_ANCILLARY_DID16_S334_EIA_608 > > 8 ,
GST_VIDEO_ANCILLARY_DID16_S334_EIA_608 & 0xff , data , 3 ) )
GST_WARNING_OBJECT ( self , " Couldn't add meta to ancillary data " ) ;
got_captions = TRUE ;
break ;
}
case GST_VIDEO_CAPTION_TYPE_CEA608_S334_1A : {
if ( ! gst_video_vbi_encoder_add_ancillary ( self - > vbiencoder ,
FALSE ,
GST_VIDEO_ANCILLARY_DID16_S334_EIA_608 > > 8 ,
GST_VIDEO_ANCILLARY_DID16_S334_EIA_608 & 0xff , cc_meta - > data ,
cc_meta - > size ) )
GST_WARNING_OBJECT ( self , " Couldn't add meta to ancillary data " ) ;
got_captions = TRUE ;
break ;
}
case GST_VIDEO_CAPTION_TYPE_CEA708_RAW : {
guint8 data [ 256 ] ;
guint n ;
n = cc_meta - > size / 3 ;
if ( cc_meta - > size > 46 ) {
GST_WARNING_OBJECT ( self , " Too big raw CEA708 buffer " ) ;
break ;
}
n = convert_cea708_cc_data_cea708_cdp_internal ( self , cc_meta - > data ,
cc_meta - > size , data , sizeof ( data ) , tc_meta ) ;
if ( ! gst_video_vbi_encoder_add_ancillary ( self - > vbiencoder , FALSE ,
GST_VIDEO_ANCILLARY_DID16_S334_EIA_708 > > 8 ,
GST_VIDEO_ANCILLARY_DID16_S334_EIA_708 & 0xff , data , n ) )
GST_WARNING_OBJECT ( self , " Couldn't add meta to ancillary data " ) ;
got_captions = TRUE ;
break ;
}
case GST_VIDEO_CAPTION_TYPE_CEA708_CDP : {
if ( ! gst_video_vbi_encoder_add_ancillary ( self - > vbiencoder ,
FALSE ,
GST_VIDEO_ANCILLARY_DID16_S334_EIA_708 > > 8 ,
GST_VIDEO_ANCILLARY_DID16_S334_EIA_708 & 0xff , cc_meta - > data ,
cc_meta - > size ) )
GST_WARNING_OBJECT ( self , " Couldn't add meta to ancillary data " ) ;
got_captions = TRUE ;
break ;
}
default : {
GST_FIXME_OBJECT ( self , " Caption type %d not supported " ,
cc_meta - > caption_type ) ;
break ;
}
}
}
if ( ( got_captions | | self - > afd_bar_line ! = 0 )
2020-01-08 05:42:21 +00:00
& & self - > output - > output - > CreateAncillaryData ( bmdFormat10BitYUV ,
2019-05-03 21:15:32 +00:00
& vanc_frame ) = = S_OK ) {
GstVideoAFDMeta * afd_meta = NULL , * afd_meta2 = NULL ;
GstVideoBarMeta * bar_meta = NULL , * bar_meta2 = NULL ;
GstMeta * meta ;
gpointer meta_iter ;
guint8 afd_bar_data [ 8 ] = { 0 , } ;
guint8 afd_bar_data2 [ 8 ] = { 0 , } ;
guint8 afd = 0 ;
gboolean is_letterbox = 0 ;
guint16 bar1 = 0 , bar2 = 0 ;
guint i ;
// Get any reasonable AFD/Bar metas for both fields
meta_iter = NULL ;
while ( ( meta =
gst_buffer_iterate_meta_filtered ( buffer , & meta_iter ,
GST_VIDEO_AFD_META_API_TYPE ) ) ) {
GstVideoAFDMeta * tmp_meta = ( GstVideoAFDMeta * ) meta ;
if ( tmp_meta - > field = = 0 | | ! afd_meta | | ( afd_meta & & afd_meta - > field ! = 0
& & tmp_meta - > field = = 0 ) )
afd_meta = tmp_meta ;
if ( tmp_meta - > field = = 1 | | ! afd_meta2 | | ( afd_meta2
& & afd_meta - > field ! = 1 & & tmp_meta - > field = = 1 ) )
afd_meta2 = tmp_meta ;
}
meta_iter = NULL ;
while ( ( meta =
gst_buffer_iterate_meta_filtered ( buffer , & meta_iter ,
GST_VIDEO_BAR_META_API_TYPE ) ) ) {
GstVideoBarMeta * tmp_meta = ( GstVideoBarMeta * ) meta ;
if ( tmp_meta - > field = = 0 | | ! bar_meta | | ( bar_meta & & bar_meta - > field ! = 0
& & tmp_meta - > field = = 0 ) )
bar_meta = tmp_meta ;
if ( tmp_meta - > field = = 1 | | ! bar_meta2 | | ( bar_meta2
& & bar_meta - > field ! = 1 & & tmp_meta - > field = = 1 ) )
bar_meta2 = tmp_meta ;
}
for ( i = 0 ; i < 2 ; i + + ) {
guint8 * afd_bar_data_ptr ;
if ( i = = 0 ) {
afd_bar_data_ptr = afd_bar_data ;
afd = afd_meta ? afd_meta - > afd : 0 ;
is_letterbox = bar_meta ? bar_meta - > is_letterbox : FALSE ;
bar1 = bar_meta ? bar_meta - > bar_data1 : 0 ;
bar2 = bar_meta ? bar_meta - > bar_data2 : 0 ;
} else {
afd_bar_data_ptr = afd_bar_data2 ;
afd = afd_meta2 ? afd_meta2 - > afd : 0 ;
is_letterbox = bar_meta2 ? bar_meta2 - > is_letterbox : FALSE ;
bar1 = bar_meta2 ? bar_meta2 - > bar_data1 : 0 ;
bar2 = bar_meta2 ? bar_meta2 - > bar_data2 : 0 ;
}
/* See SMPTE 2016-3 Section 4 */
/* AFD and AR */
2020-08-05 07:52:08 +00:00
if ( self - > mode < = ( gint ) GST_DECKLINK_MODE_PAL_P ) {
2019-05-03 21:15:32 +00:00
afd_bar_data_ptr [ 0 ] = ( afd < < 3 ) | 0x0 ;
} else {
afd_bar_data_ptr [ 0 ] = ( afd < < 3 ) | 0x4 ;
}
/* Bar flags */
afd_bar_data_ptr [ 3 ] = is_letterbox ? 0xc0 : 0x30 ;
/* Bar value 1 and 2 */
GST_WRITE_UINT16_BE ( & afd_bar_data_ptr [ 4 ] , bar1 ) ;
GST_WRITE_UINT16_BE ( & afd_bar_data_ptr [ 6 ] , bar2 ) ;
}
/* AFD on the same line as the captions */
if ( self - > caption_line = = self - > afd_bar_line ) {
if ( ! gst_video_vbi_encoder_add_ancillary ( self - > vbiencoder ,
FALSE , GST_VIDEO_ANCILLARY_DID16_S2016_3_AFD_BAR > > 8 ,
GST_VIDEO_ANCILLARY_DID16_S2016_3_AFD_BAR & 0xff , afd_bar_data ,
sizeof ( afd_bar_data ) ) )
GST_WARNING_OBJECT ( self ,
" Couldn't add AFD/Bar data to ancillary data " ) ;
}
/* FIXME: Add captions to the correct field? Captions for the second
* field should probably be inserted into the second field */
if ( got_captions | | self - > caption_line = = self - > afd_bar_line ) {
if ( vanc_frame - > GetBufferForVerticalBlankingLine ( self - > caption_line ,
( void * * ) & vancdata ) = = S_OK ) {
gst_video_vbi_encoder_write_line ( self - > vbiencoder , vancdata ) ;
} else {
GST_WARNING_OBJECT ( self ,
" Failed to get buffer for line %d ancillary data " ,
self - > caption_line ) ;
}
}
/* AFD on a different line than the captions */
if ( self - > afd_bar_line ! = 0 & & self - > caption_line ! = self - > afd_bar_line ) {
if ( ! gst_video_vbi_encoder_add_ancillary ( self - > vbiencoder ,
FALSE , GST_VIDEO_ANCILLARY_DID16_S2016_3_AFD_BAR > > 8 ,
GST_VIDEO_ANCILLARY_DID16_S2016_3_AFD_BAR & 0xff , afd_bar_data ,
sizeof ( afd_bar_data ) ) )
GST_WARNING_OBJECT ( self ,
" Couldn't add AFD/Bar data to ancillary data " ) ;
if ( vanc_frame - > GetBufferForVerticalBlankingLine ( self - > afd_bar_line ,
( void * * ) & vancdata ) = = S_OK ) {
gst_video_vbi_encoder_write_line ( self - > vbiencoder , vancdata ) ;
} else {
GST_WARNING_OBJECT ( self ,
" Failed to get buffer for line %d ancillary data " ,
self - > afd_bar_line ) ;
}
}
/* For interlaced video we need to also add AFD to the second field */
if ( GST_VIDEO_INFO_IS_INTERLACED ( & self - > info ) & & self - > afd_bar_line ! = 0 ) {
guint field2_offset ;
/* The VANC lines for the second field are at an offset, depending on
* the format in use .
*/
switch ( self - > info . height ) {
case 486 :
/* NTSC: 525 / 2 + 1 */
field2_offset = 263 ;
break ;
case 576 :
/* PAL: 625 / 2 + 1 */
field2_offset = 313 ;
break ;
case 1080 :
/* 1080i: 1125 / 2 + 1 */
field2_offset = 563 ;
break ;
default :
g_assert_not_reached ( ) ;
}
if ( ! gst_video_vbi_encoder_add_ancillary ( self - > vbiencoder ,
FALSE , GST_VIDEO_ANCILLARY_DID16_S2016_3_AFD_BAR > > 8 ,
GST_VIDEO_ANCILLARY_DID16_S2016_3_AFD_BAR & 0xff , afd_bar_data2 ,
sizeof ( afd_bar_data ) ) )
GST_WARNING_OBJECT ( self ,
" Couldn't add AFD/Bar data to ancillary data " ) ;
if ( vanc_frame - > GetBufferForVerticalBlankingLine ( self - > afd_bar_line +
field2_offset , ( void * * ) & vancdata ) = = S_OK ) {
gst_video_vbi_encoder_write_line ( self - > vbiencoder , vancdata ) ;
} else {
GST_WARNING_OBJECT ( self ,
" Failed to get buffer for line %d ancillary data " ,
self - > afd_bar_line ) ;
}
}
if ( frame - > SetAncillaryData ( vanc_frame ) ! = S_OK ) {
GST_WARNING_OBJECT ( self , " Failed to set ancillary data " ) ;
}
vanc_frame - > Release ( ) ;
} else if ( got_captions | | self - > afd_bar_line ! = 0 ) {
GST_WARNING_OBJECT ( self , " Failed to allocate ancillary data frame " ) ;
}
}
2022-06-28 13:40:55 +00:00
static gboolean
buffer_is_pbo_memory ( GstBuffer * buffer )
{
GstMemory * mem ;
mem = gst_buffer_peek_memory ( buffer , 0 ) ;
if ( mem - > allocator
& & g_strcmp0 ( mem - > allocator - > mem_type , " GLMemoryPBO " ) = = 0 )
return TRUE ;
return FALSE ;
}
2024-06-27 12:25:42 +00:00
static void
gst_decklink_video_sink_get_times ( GstBaseSink * bsink ,
GstBuffer * buffer , GstClockTime * start , GstClockTime * end )
{
/* in order to push multiple buffers into decklink, we need to reimplement
* basesink ' s clock synchronisation */
* start = GST_CLOCK_TIME_NONE ;
* end = GST_CLOCK_TIME_NONE ;
}
2014-12-13 16:23:31 +00:00
static GstFlowReturn
gst_decklink_video_sink_prepare ( GstBaseSink * bsink , GstBuffer * buffer )
{
GstDecklinkVideoSink * self = GST_DECKLINK_VIDEO_SINK_CAST ( bsink ) ;
GstVideoFrame vframe ;
2022-06-28 13:40:55 +00:00
GstDecklinkVideoFrame * frame = NULL ;
2014-12-13 16:23:31 +00:00
GstFlowReturn flow_ret ;
HRESULT ret ;
2014-12-19 13:35:03 +00:00
GstClockTime timestamp , duration ;
GstClockTime running_time , running_time_duration ;
2015-09-10 11:11:58 +00:00
GstClockTime latency , render_delay ;
GstClockTimeDiff ts_offset ;
2016-04-14 15:26:33 +00:00
GstDecklinkVideoFormat caps_format ;
BMDPixelFormat format ;
2016-05-15 14:25:44 +00:00
GstVideoTimeCodeMeta * tc_meta ;
2014-12-13 16:23:31 +00:00
2019-10-14 07:22:18 +00:00
GST_DEBUG_OBJECT ( self , " Preparing buffer % " GST_PTR_FORMAT , buffer ) ;
2014-12-13 16:23:31 +00:00
// FIXME: Handle no timestamps
if ( ! GST_BUFFER_TIMESTAMP_IS_VALID ( buffer ) ) {
return GST_FLOW_ERROR ;
}
2016-04-14 15:26:33 +00:00
caps_format = gst_decklink_type_from_video_format ( self - > info . finfo - > format ) ;
format = gst_decklink_pixel_format_from_type ( caps_format ) ;
2015-01-14 15:01:07 +00:00
timestamp = GST_BUFFER_TIMESTAMP ( buffer ) ;
duration = GST_BUFFER_DURATION ( buffer ) ;
if ( duration = = GST_CLOCK_TIME_NONE ) {
duration =
gst_util_uint64_scale_int ( GST_SECOND , self - > info . fps_d ,
self - > info . fps_n ) ;
}
running_time =
gst_segment_to_running_time ( & GST_BASE_SINK_CAST ( self ) - > segment ,
GST_FORMAT_TIME , timestamp ) ;
running_time_duration =
gst_segment_to_running_time ( & GST_BASE_SINK_CAST ( self ) - > segment ,
GST_FORMAT_TIME , timestamp + duration ) - running_time ;
2015-09-07 10:36:19 +00:00
/* See gst_base_sink_adjust_time() */
latency = gst_base_sink_get_latency ( bsink ) ;
render_delay = gst_base_sink_get_render_delay ( bsink ) ;
ts_offset = gst_base_sink_get_ts_offset ( bsink ) ;
running_time + = latency ;
if ( ts_offset < 0 ) {
ts_offset = - ts_offset ;
2015-09-10 11:11:58 +00:00
if ( ( GstClockTime ) ts_offset < running_time )
2015-09-07 10:36:19 +00:00
running_time - = ts_offset ;
else
running_time = 0 ;
} else {
running_time + = ts_offset ;
}
if ( running_time > render_delay )
running_time - = render_delay ;
else
running_time = 0 ;
2014-12-13 16:23:31 +00:00
if ( ! gst_video_frame_map ( & vframe , & self - > info , buffer , GST_MAP_READ ) ) {
GST_ERROR_OBJECT ( self , " Failed to map video frame " ) ;
flow_ret = GST_FLOW_ERROR ;
goto out ;
}
2022-06-28 13:40:55 +00:00
// If the video frame is stored in PBO memory then we need to copy anyway as
// it might be stored in CPU-accessible GPU memory that can't be accessed
// from the Decklink driver.
if ( buffer_is_pbo_memory ( buffer ) ) {
guint8 * outdata ;
const guint8 * indata ;
gint i , src_stride , dest_stride , stride ;
IDeckLinkMutableVideoFrame * dframe ;
2024-07-22 13:55:48 +00:00
GstVideoColorimetry colorimetry ;
2022-06-28 13:40:55 +00:00
ret = self - > output - > output - > CreateVideoFrame ( self - > info . width ,
self - > info . height , self - > info . stride [ 0 ] , format , bmdFrameFlagDefault ,
& dframe ) ;
if ( ret ! = S_OK ) {
gst_video_frame_unmap ( & vframe ) ;
GST_ELEMENT_ERROR ( self , STREAM , FAILED ,
( NULL ) , ( " Failed to create video frame: 0x%08lx " ,
( unsigned long ) ret ) ) ;
return GST_FLOW_ERROR ;
}
dframe - > GetBytes ( ( void * * ) & outdata ) ;
indata = ( guint8 * ) GST_VIDEO_FRAME_PLANE_DATA ( & vframe , 0 ) ;
src_stride = GST_VIDEO_FRAME_PLANE_STRIDE ( & vframe , 0 ) ;
dest_stride = dframe - > GetRowBytes ( ) ;
stride = MIN ( src_stride , dest_stride ) ;
for ( i = 0 ; i < self - > info . height ; i + + ) {
memcpy ( outdata , indata , stride ) ;
indata + = src_stride ;
outdata + = dest_stride ;
}
2024-07-22 13:55:48 +00:00
colorimetry = vframe . info . colorimetry ;
2022-06-28 13:40:55 +00:00
gst_video_frame_unmap ( & vframe ) ;
2014-12-13 16:23:31 +00:00
2022-06-28 13:40:55 +00:00
// Takes ownership of the frame
frame = new GstDecklinkVideoFrame ( dframe ) ;
2024-07-22 13:55:48 +00:00
frame - > SetColorimetry ( & colorimetry ) ;
2022-06-28 13:40:55 +00:00
} else {
// Takes ownership of the frame
frame = new GstDecklinkVideoFrame ( & vframe ) ;
2024-07-22 13:55:48 +00:00
frame - > SetColorimetry ( & vframe . info . colorimetry ) ;
2014-12-13 16:23:31 +00:00
}
2016-05-15 14:25:44 +00:00
tc_meta = gst_buffer_get_video_time_code_meta ( buffer ) ;
if ( tc_meta ) {
gchar * tc_str ;
2022-06-28 13:40:55 +00:00
frame - > SetTimecode ( & tc_meta - > tc ) ;
2016-05-15 14:25:44 +00:00
tc_str = gst_video_time_code_to_string ( & tc_meta - > tc ) ;
GST_DEBUG_OBJECT ( self , " Set frame timecode to %s " , tc_str ) ;
g_free ( tc_str ) ;
}
2024-07-22 13:55:48 +00:00
if ( self - > have_light_level )
frame - > SetLightLevel ( & self - > light_level ) ;
if ( self - > have_mastering_info )
frame - > SetMastringInfo ( & self - > mastering_info ) ;
2019-05-03 21:15:32 +00:00
write_vbi ( self , buffer , format , frame , tc_meta ) ;
2018-11-07 15:15:25 +00:00
2024-06-27 12:25:42 +00:00
frame - > running_time = running_time ;
frame - > running_time_duration = running_time_duration ;
frame - > sync_buffer = gst_buffer_ref ( buffer ) ;
2014-12-13 16:23:31 +00:00
2024-06-27 12:25:42 +00:00
g_queue_push_tail ( self - > pending_frames , frame ) ;
2014-12-13 16:23:31 +00:00
2024-06-27 12:25:42 +00:00
frame = nullptr ;
2014-12-13 16:23:31 +00:00
flow_ret = GST_FLOW_OK ;
out :
2022-06-28 13:40:55 +00:00
if ( frame )
frame - > Release ( ) ;
2014-12-13 16:23:31 +00:00
return flow_ret ;
}
2024-06-27 12:25:42 +00:00
static GstFlowReturn
gst_decklink_video_sink_render ( GstBaseSink * bsink , GstBuffer * buffer )
{
GstDecklinkVideoSink * self = GST_DECKLINK_VIDEO_SINK_CAST ( bsink ) ;
GstFlowReturn flow_ret = GST_FLOW_OK ;
HRESULT ret ;
if ( ( flow_ret = gst_decklink_video_sink_prepare ( bsink , buffer ) ) ! = GST_FLOW_OK )
return flow_ret ;
GST_TRACE_OBJECT ( bsink , " render with %u pending frames " , self - > pending_frames - > length ) ;
GST_OBJECT_LOCK ( self ) ;
if ( self - > initial_sync ) {
/* this is effectively the preroll logic. We wait for at least 2 buffers */
GstDecklinkVideoFrame * frame ;
if ( self - > pending_frames - > length < 1 ) {
GST_OBJECT_UNLOCK ( self ) ;
return GST_FLOW_OK ;
}
GST_OBJECT_UNLOCK ( self ) ;
frame = ( GstDecklinkVideoFrame * ) g_queue_peek_head ( self - > pending_frames ) ;
2024-07-22 13:55:48 +00:00
GST_DEBUG_OBJECT ( self , " attempting preroll " ) ;
2024-06-27 12:25:42 +00:00
flow_ret =
gst_base_sink_do_preroll ( bsink ,
GST_MINI_OBJECT_CAST ( frame - > sync_buffer ) ) ;
if ( flow_ret ! = GST_FLOW_OK )
goto out ;
g_mutex_lock ( & self - > output - > lock ) ;
if ( self - > output - > start_scheduled_playback )
self - > output - > start_scheduled_playback ( self - > output - > videosink ) ;
g_mutex_unlock ( & self - > output - > lock ) ;
GstClock * clock = gst_element_get_clock ( GST_ELEMENT_CAST ( self ) ) ;
GST_OBJECT_LOCK ( self ) ;
self - > initial_sync = FALSE ;
if ( clock ) {
if ( clock ! = self - > output - > clock ) {
gst_clock_set_master ( self - > output - > clock , clock ) ;
}
if ( self - > external_base_time = = GST_CLOCK_TIME_NONE
| | self - > internal_base_time = = GST_CLOCK_TIME_NONE ) {
self - > external_base_time = gst_clock_get_internal_time ( clock ) ;
self - > internal_base_time =
gst_clock_get_internal_time ( self - > output - > clock ) ;
self - > internal_time_offset = self - > internal_base_time ;
} else if ( GST_CLOCK_TIME_IS_VALID ( self - > internal_pause_time ) ) {
self - > internal_time_offset + =
gst_clock_get_internal_time ( self - > output - > clock ) -
self - > internal_pause_time ;
self - > internal_pause_time = GST_CLOCK_TIME_NONE ;
}
GST_INFO_OBJECT ( self , " clock has been set to % " GST_PTR_FORMAT
" , updated base times - internal: % " GST_TIME_FORMAT
" external: % " GST_TIME_FORMAT " internal offset % "
GST_TIME_FORMAT , clock ,
GST_TIME_ARGS ( self - > internal_base_time ) ,
GST_TIME_ARGS ( self - > external_base_time ) ,
GST_TIME_ARGS ( self - > internal_time_offset ) ) ;
gst_object_unref ( clock ) ;
} else {
GST_ELEMENT_ERROR ( self , STREAM , FAILED ,
( NULL ) , ( " Need a clock to go to PLAYING " ) ) ;
GST_OBJECT_UNLOCK ( self ) ;
return GST_FLOW_ERROR ;
}
}
GST_OBJECT_UNLOCK ( self ) ;
while ( self - > pending_frames - > length > 0 ) {
GstDecklinkVideoFrame * frame =
( GstDecklinkVideoFrame * ) g_queue_pop_head ( self - > pending_frames ) ;
GstClockTime sync_time = frame - > running_time ;
GstClockTime running_time = frame - > running_time ;
GstClockTime running_time_duration = frame - > running_time_duration ;
GstClockTime clock_time , frame_duration ;
GstClockTimeDiff jitter ;
GstClockReturn status ;
flow_ret =
gst_base_sink_do_preroll ( bsink ,
GST_MINI_OBJECT_CAST ( frame - > sync_buffer ) ) ;
if ( flow_ret ! = GST_FLOW_OK ) {
frame - > Release ( ) ;
goto out ;
}
if ( GST_CLOCK_TIME_IS_VALID ( sync_time ) ) {
if ( sync_time > 30 * GST_MSECOND ) {
sync_time - = 30 * GST_MSECOND ;
} else {
sync_time = 0 ;
}
}
do {
status = gst_base_sink_wait_clock ( bsink , sync_time , & jitter ) ;
if ( status = = GST_CLOCK_BADTIME )
break ;
if ( G_UNLIKELY ( bsink - > flushing ) ) {
frame - > Release ( ) ;
return GST_FLOW_FLUSHING ;
}
} while ( status = = GST_CLOCK_UNSCHEDULED ) ;
/* if we don't have a clock or are not syncing, then display the frame 'now' */
clock_time = gst_clock_get_internal_time ( self - > output - > clock ) ;
if ( status = = GST_CLOCK_BADTIME ) {
running_time = clock_time ;
}
gst_decklink_video_sink_convert_to_internal_clock ( self , & running_time ,
& running_time_duration ) ;
frame_duration =
gst_util_uint64_scale_int ( GST_SECOND , self - > output - > mode - > fps_d ,
self - > output - > mode - > fps_n ) ;
running_time = gst_util_uint64_scale ( running_time , 1 , frame_duration ) ;
running_time = gst_util_uint64_scale_ceil ( running_time , frame_duration , 1 ) ;
GST_DEBUG_OBJECT ( self , " Scheduling video frame %p at % " GST_TIME_FORMAT
" with duration % " GST_TIME_FORMAT " sync time % " GST_TIME_FORMAT
" clock time % " GST_TIME_FORMAT , frame , GST_TIME_ARGS ( running_time ) ,
GST_TIME_ARGS ( running_time_duration ) , GST_TIME_ARGS ( sync_time ) , GST_TIME_ARGS ( clock_time ) ) ;
ret = self - > output - > output - > ScheduleVideoFrame ( frame ,
running_time , running_time_duration , GST_SECOND ) ;
if ( ret ! = S_OK ) {
GST_ELEMENT_ERROR ( self , STREAM , FAILED ,
( NULL ) , ( " Failed to schedule frame: 0x%08lx " , ( unsigned long ) ret ) ) ;
frame - > Release ( ) ;
flow_ret = GST_FLOW_ERROR ;
goto out ;
}
frame - > Release ( ) ;
}
out :
return flow_ret ;
}
2014-12-13 16:23:31 +00:00
static gboolean
gst_decklink_video_sink_open ( GstBaseSink * bsink )
{
GstDecklinkVideoSink * self = GST_DECKLINK_VIDEO_SINK_CAST ( bsink ) ;
const GstDecklinkMode * mode ;
2017-11-22 08:30:22 +00:00
GST_DEBUG_OBJECT ( self , " Starting " ) ;
2014-12-13 16:23:31 +00:00
self - > output =
2022-09-26 11:57:15 +00:00
gst_decklink_acquire_nth_output ( self - > device_number , self - > persistent_id ,
2014-12-13 16:23:31 +00:00
GST_ELEMENT_CAST ( self ) , FALSE ) ;
if ( ! self - > output ) {
GST_ERROR_OBJECT ( self , " Failed to acquire output " ) ;
return FALSE ;
}
2017-10-04 11:53:35 +00:00
g_object_notify ( G_OBJECT ( self ) , " hw-serial-number " ) ;
2014-12-13 16:23:31 +00:00
mode = gst_decklink_get_mode ( self - > mode ) ;
g_assert ( mode ! = NULL ) ;
2014-12-19 11:03:09 +00:00
g_mutex_lock ( & self - > output - > lock ) ;
self - > output - > mode = mode ;
2015-01-28 14:48:26 +00:00
self - > output - > start_scheduled_playback =
gst_decklink_video_sink_start_scheduled_playback ;
2015-01-28 13:21:40 +00:00
self - > output - > clock_start_time = GST_CLOCK_TIME_NONE ;
2015-09-23 13:56:26 +00:00
self - > output - > clock_epoch + = self - > output - > clock_last_time ;
2015-01-28 13:21:40 +00:00
self - > output - > clock_last_time = 0 ;
self - > output - > clock_offset = 0 ;
2018-11-28 04:28:15 +00:00
GST_OBJECT_LOCK ( self ) ;
self - > internal_base_time = GST_CLOCK_TIME_NONE ;
self - > external_base_time = GST_CLOCK_TIME_NONE ;
GST_OBJECT_UNLOCK ( self ) ;
2014-12-19 11:03:09 +00:00
g_mutex_unlock ( & self - > output - > lock ) ;
2014-12-13 16:23:31 +00:00
return TRUE ;
}
static gboolean
gst_decklink_video_sink_close ( GstBaseSink * bsink )
{
GstDecklinkVideoSink * self = GST_DECKLINK_VIDEO_SINK_CAST ( bsink ) ;
2015-09-22 17:35:00 +00:00
GST_DEBUG_OBJECT ( self , " Closing " ) ;
2014-12-13 16:23:31 +00:00
if ( self - > output ) {
2014-12-19 11:03:09 +00:00
g_mutex_lock ( & self - > output - > lock ) ;
self - > output - > mode = NULL ;
2015-01-28 13:21:40 +00:00
self - > output - > video_enabled = FALSE ;
2018-11-06 17:06:01 +00:00
if ( self - > output - > start_scheduled_playback & & self - > output - > videosink )
2015-01-28 14:48:26 +00:00
self - > output - > start_scheduled_playback ( self - > output - > videosink ) ;
2014-12-19 11:03:09 +00:00
g_mutex_unlock ( & self - > output - > lock ) ;
2014-12-13 16:23:31 +00:00
self - > output - > output - > DisableVideoOutput ( ) ;
2022-09-26 11:57:15 +00:00
gst_decklink_release_nth_output ( self - > device_number , self - > persistent_id ,
2014-12-13 16:23:31 +00:00
GST_ELEMENT_CAST ( self ) , FALSE ) ;
self - > output = NULL ;
}
return TRUE ;
}
2015-09-22 17:35:00 +00:00
static gboolean
gst_decklink_video_sink_stop ( GstDecklinkVideoSink * self )
{
GST_DEBUG_OBJECT ( self , " Stopping " ) ;
if ( self - > output & & self - > output - > video_enabled ) {
g_mutex_lock ( & self - > output - > lock ) ;
self - > output - > video_enabled = FALSE ;
g_mutex_unlock ( & self - > output - > lock ) ;
self - > output - > output - > DisableVideoOutput ( ) ;
self - > output - > output - > SetScheduledFrameCompletionCallback ( NULL ) ;
}
2018-11-07 15:15:25 +00:00
if ( self - > vbiencoder ) {
gst_video_vbi_encoder_free ( self - > vbiencoder ) ;
self - > vbiencoder = NULL ;
self - > anc_vformat = GST_VIDEO_FORMAT_UNKNOWN ;
}
2015-09-22 17:35:00 +00:00
return TRUE ;
}
2018-09-12 10:29:09 +00:00
static void
2018-11-07 15:15:25 +00:00
_wait_for_stop_notify ( GstDecklinkVideoSink * self )
2018-09-12 10:29:09 +00:00
{
bool active = false ;
self - > output - > output - > IsScheduledPlaybackRunning ( & active ) ;
while ( active ) {
/* cause sometimes decklink stops without notifying us... */
guint64 wait_time = g_get_monotonic_time ( ) + G_TIME_SPAN_SECOND ;
2018-11-07 15:15:25 +00:00
if ( ! g_cond_wait_until ( & self - > output - > cond , & self - > output - > lock ,
wait_time ) )
2018-09-12 10:29:09 +00:00
GST_WARNING_OBJECT ( self , " Failed to wait for stop notification " ) ;
self - > output - > output - > IsScheduledPlaybackRunning ( & active ) ;
}
}
2015-01-28 14:48:26 +00:00
static void
gst_decklink_video_sink_start_scheduled_playback ( GstElement * element )
{
GstDecklinkVideoSink * self = GST_DECKLINK_VIDEO_SINK_CAST ( element ) ;
GstClockTime start_time ;
HRESULT res ;
bool active ;
2017-12-06 18:36:37 +00:00
// Check if we're already started
if ( self - > output - > started ) {
GST_DEBUG_OBJECT ( self , " Already started " ) ;
return ;
}
// Check if we're ready to start:
// we need video and audio enabled, if there is audio
// and both of the two elements need to be set to PLAYING already
if ( ! self - > output - > video_enabled ) {
GST_DEBUG_OBJECT ( self ,
" Not starting scheduled playback yet: video not enabled yet! " ) ;
return ;
}
2015-01-28 15:58:27 +00:00
2017-12-06 18:36:37 +00:00
if ( self - > output - > audiosink & & ! self - > output - > audio_enabled ) {
GST_DEBUG_OBJECT ( self ,
" Not starting scheduled playback yet: "
" have audio but not enabled yet! " ) ;
return ;
}
2015-01-28 14:48:26 +00:00
2018-07-08 14:54:04 +00:00
if ( ( GST_STATE ( self ) < GST_STATE_PAUSED
& & GST_STATE_PENDING ( self ) < GST_STATE_PAUSED )
2017-12-06 18:36:37 +00:00
| | ( self - > output - > audiosink & &
2018-07-08 14:54:04 +00:00
GST_STATE ( self - > output - > audiosink ) < GST_STATE_PAUSED
2018-11-07 15:15:25 +00:00
& & GST_STATE_PENDING ( self - > output - > audiosink ) < GST_STATE_PAUSED ) ) {
2017-12-06 18:36:37 +00:00
GST_DEBUG_OBJECT ( self ,
" Not starting scheduled playback yet: "
2018-07-08 14:54:04 +00:00
" Elements are not set to PAUSED yet " ) ;
2017-12-06 18:36:37 +00:00
return ;
}
// Need to unlock to get the clock time
g_mutex_unlock ( & self - > output - > lock ) ;
2015-01-28 14:48:26 +00:00
2018-07-08 14:54:04 +00:00
start_time = gst_clock_get_internal_time ( self - > output - > clock ) ;
2015-01-28 14:48:26 +00:00
2017-12-06 18:36:37 +00:00
g_mutex_lock ( & self - > output - > lock ) ;
// Check if someone else started in the meantime
if ( self - > output - > started ) {
return ;
}
2017-11-22 08:42:37 +00:00
2017-12-06 18:36:37 +00:00
active = false ;
self - > output - > output - > IsScheduledPlaybackRunning ( & active ) ;
if ( active ) {
GST_DEBUG_OBJECT ( self , " Stopping scheduled playback " ) ;
2015-01-28 14:48:26 +00:00
2017-12-06 18:36:37 +00:00
self - > output - > started = FALSE ;
2015-01-28 14:48:26 +00:00
2017-12-06 18:36:37 +00:00
res = self - > output - > output - > StopScheduledPlayback ( 0 , 0 , 0 ) ;
2015-01-28 14:48:26 +00:00
if ( res ! = S_OK ) {
GST_ELEMENT_ERROR ( self , STREAM , FAILED ,
2017-12-06 18:36:37 +00:00
( NULL ) , ( " Failed to stop scheduled playback: 0x%08lx " ,
2017-08-02 15:43:54 +00:00
( unsigned long ) res ) ) ;
2016-09-01 11:25:58 +00:00
return ;
2015-01-28 14:48:26 +00:00
}
2017-12-06 18:36:37 +00:00
// Wait until scheduled playback actually stopped
2018-09-12 10:29:09 +00:00
_wait_for_stop_notify ( self ) ;
2017-12-06 18:36:37 +00:00
}
2015-02-10 13:53:55 +00:00
2019-10-14 07:22:18 +00:00
GST_INFO_OBJECT ( self ,
2017-12-06 18:36:37 +00:00
" Starting scheduled playback at % " GST_TIME_FORMAT ,
GST_TIME_ARGS ( start_time ) ) ;
2015-02-10 13:53:55 +00:00
2017-12-06 18:36:37 +00:00
res =
self - > output - > output - > StartScheduledPlayback ( start_time ,
GST_SECOND , 1.0 ) ;
if ( res ! = S_OK ) {
GST_ELEMENT_ERROR ( self , STREAM , FAILED ,
( NULL ) , ( " Failed to start scheduled playback: 0x%08lx " ,
( unsigned long ) res ) ) ;
return ;
2015-01-28 14:48:26 +00:00
}
2017-12-06 18:36:37 +00:00
self - > output - > started = TRUE ;
2015-01-28 14:48:26 +00:00
}
2016-11-02 14:12:42 +00:00
static GstStateChangeReturn
gst_decklink_video_sink_stop_scheduled_playback ( GstDecklinkVideoSink * self )
{
GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS ;
GstClockTime start_time ;
HRESULT res ;
if ( ! self - > output - > started )
return ret ;
2018-07-08 14:54:04 +00:00
start_time = gst_clock_get_internal_time ( self - > output - > clock ) ;
2016-11-02 14:12:42 +00:00
2019-10-14 07:22:18 +00:00
GST_INFO_OBJECT ( self ,
2016-11-02 14:12:42 +00:00
" Stopping scheduled playback at % " GST_TIME_FORMAT ,
GST_TIME_ARGS ( start_time ) ) ;
g_mutex_lock ( & self - > output - > lock ) ;
self - > output - > started = FALSE ;
res = self - > output - > output - > StopScheduledPlayback ( start_time , 0 , GST_SECOND ) ;
if ( res ! = S_OK ) {
GST_ELEMENT_ERROR ( self , STREAM , FAILED ,
2017-07-18 22:49:34 +00:00
( NULL ) , ( " Failed to stop scheduled playback: 0x%08lx " , ( unsigned long )
2017-08-02 15:43:54 +00:00
res ) ) ;
2016-11-02 14:12:42 +00:00
ret = GST_STATE_CHANGE_FAILURE ;
2017-11-22 08:42:37 +00:00
} else {
// Wait until scheduled playback actually stopped
2018-09-12 10:29:09 +00:00
_wait_for_stop_notify ( self ) ;
2016-11-02 14:12:42 +00:00
}
2018-11-28 04:28:15 +00:00
g_mutex_unlock ( & self - > output - > lock ) ;
GST_OBJECT_LOCK ( self ) ;
2016-11-02 14:12:42 +00:00
self - > internal_base_time = GST_CLOCK_TIME_NONE ;
self - > external_base_time = GST_CLOCK_TIME_NONE ;
2018-11-28 04:28:15 +00:00
GST_OBJECT_UNLOCK ( self ) ;
2016-11-02 14:12:42 +00:00
return ret ;
}
2014-12-13 16:23:31 +00:00
static GstStateChangeReturn
gst_decklink_video_sink_change_state ( GstElement * element ,
GstStateChange transition )
{
GstDecklinkVideoSink * self = GST_DECKLINK_VIDEO_SINK_CAST ( element ) ;
2016-09-01 11:25:58 +00:00
GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS ;
2014-12-13 16:23:31 +00:00
2018-07-08 14:54:04 +00:00
GST_DEBUG_OBJECT ( self , " changing state: %s => %s " ,
gst_element_state_get_name ( GST_STATE_TRANSITION_CURRENT ( transition ) ) ,
gst_element_state_get_name ( GST_STATE_TRANSITION_NEXT ( transition ) ) ) ;
2014-12-13 16:23:31 +00:00
switch ( transition ) {
case GST_STATE_CHANGE_READY_TO_PAUSED :
2018-11-07 15:15:25 +00:00
self - > vbiencoder = NULL ;
self - > anc_vformat = GST_VIDEO_FORMAT_UNKNOWN ;
2018-12-16 09:02:50 +00:00
self - > cdp_hdr_sequence_cntr = 0 ;
2018-11-07 15:15:25 +00:00
2015-01-28 13:21:40 +00:00
g_mutex_lock ( & self - > output - > lock ) ;
2015-09-23 13:56:26 +00:00
self - > output - > clock_epoch + = self - > output - > clock_last_time ;
2015-01-28 13:21:40 +00:00
self - > output - > clock_last_time = 0 ;
self - > output - > clock_offset = 0 ;
g_mutex_unlock ( & self - > output - > lock ) ;
2014-12-13 16:23:31 +00:00
gst_element_post_message ( element ,
gst_message_new_clock_provide ( GST_OBJECT_CAST ( element ) ,
self - > output - > clock , TRUE ) ) ;
break ;
case GST_STATE_CHANGE_PAUSED_TO_PLAYING : {
2024-06-27 12:25:42 +00:00
GST_OBJECT_LOCK ( self ) ;
self - > initial_sync = TRUE ;
GST_INFO_OBJECT ( self , " initial sync set to TRUE " ) ;
GST_OBJECT_UNLOCK ( self ) ;
2014-12-13 16:23:31 +00:00
break ;
}
2018-07-08 14:54:04 +00:00
case GST_STATE_CHANGE_PAUSED_TO_READY :
if ( gst_decklink_video_sink_stop_scheduled_playback ( self ) = =
GST_STATE_CHANGE_FAILURE )
ret = GST_STATE_CHANGE_FAILURE ;
break ;
2018-11-21 14:43:56 +00:00
case GST_STATE_CHANGE_PLAYING_TO_PAUSED :
2024-06-27 12:25:42 +00:00
GST_OBJECT_LOCK ( self ) ;
self - > initial_sync = FALSE ;
GST_OBJECT_UNLOCK ( self ) ;
2018-11-21 14:43:56 +00:00
break ;
2014-12-13 16:23:31 +00:00
default :
break ;
}
2016-09-01 11:17:48 +00:00
if ( ret = = GST_STATE_CHANGE_FAILURE )
return ret ;
2014-12-13 16:23:31 +00:00
ret = GST_ELEMENT_CLASS ( parent_class ) - > change_state ( element , transition ) ;
if ( ret = = GST_STATE_CHANGE_FAILURE )
return ret ;
switch ( transition ) {
2018-07-08 14:54:04 +00:00
case GST_STATE_CHANGE_PAUSED_TO_READY : {
2014-12-13 16:23:31 +00:00
gst_element_post_message ( element ,
gst_message_new_clock_lost ( GST_OBJECT_CAST ( element ) ,
self - > output - > clock ) ) ;
gst_clock_set_master ( self - > output - > clock , NULL ) ;
2015-03-02 15:45:45 +00:00
// Reset calibration to make the clock reusable next time we use it
gst_clock_set_calibration ( self - > output - > clock , 0 , 0 , 1 , 1 ) ;
2015-01-28 13:21:40 +00:00
g_mutex_lock ( & self - > output - > lock ) ;
2015-09-23 13:56:26 +00:00
self - > output - > clock_epoch + = self - > output - > clock_last_time ;
2015-01-28 13:21:40 +00:00
self - > output - > clock_last_time = 0 ;
self - > output - > clock_offset = 0 ;
g_mutex_unlock ( & self - > output - > lock ) ;
2015-09-22 17:35:00 +00:00
gst_decklink_video_sink_stop ( self ) ;
2018-11-28 04:28:15 +00:00
GST_OBJECT_LOCK ( self ) ;
self - > internal_base_time = GST_CLOCK_TIME_NONE ;
self - > external_base_time = GST_CLOCK_TIME_NONE ;
2019-10-14 07:22:18 +00:00
self - > internal_pause_time = GST_CLOCK_TIME_NONE ;
2018-11-28 04:28:15 +00:00
GST_OBJECT_UNLOCK ( self ) ;
2014-12-13 16:23:31 +00:00
break ;
}
2018-07-08 14:54:04 +00:00
case GST_STATE_CHANGE_READY_TO_PAUSED : {
2014-12-13 16:23:31 +00:00
break ;
}
2018-07-08 14:54:04 +00:00
case GST_STATE_CHANGE_PAUSED_TO_PLAYING :
break ;
case GST_STATE_CHANGE_PLAYING_TO_PAUSED :
2020-10-25 11:32:26 +00:00
self - > internal_pause_time =
gst_clock_get_internal_time ( self - > output - > clock ) ;
2018-07-08 14:54:04 +00:00
break ;
2014-12-13 16:23:31 +00:00
default :
break ;
}
return ret ;
}
2018-11-28 04:28:15 +00:00
static gboolean
gst_decklink_video_sink_event ( GstBaseSink * bsink , GstEvent * event )
2016-11-02 14:12:42 +00:00
{
2018-11-28 04:28:15 +00:00
GstDecklinkVideoSink * self = GST_DECKLINK_VIDEO_SINK_CAST ( bsink ) ;
2018-07-08 14:54:04 +00:00
2018-11-28 04:28:15 +00:00
switch ( GST_EVENT_TYPE ( event ) ) {
case GST_EVENT_FLUSH_START :
{
break ;
}
case GST_EVENT_FLUSH_STOP :
{
gboolean reset_time ;
gst_event_parse_flush_stop ( event , & reset_time ) ;
if ( reset_time ) {
GST_OBJECT_LOCK ( self ) ;
/* force a recalculation of clock base times */
self - > external_base_time = GST_CLOCK_TIME_NONE ;
self - > internal_base_time = GST_CLOCK_TIME_NONE ;
GST_OBJECT_UNLOCK ( self ) ;
}
break ;
2018-07-08 14:54:04 +00:00
}
2018-11-28 04:28:15 +00:00
default :
break ;
2016-11-02 14:12:42 +00:00
}
2018-11-28 04:28:15 +00:00
return GST_BASE_SINK_CLASS ( parent_class ) - > event ( bsink , event ) ;
2016-11-02 14:12:42 +00:00
}
2014-12-13 16:23:31 +00:00
static GstClock *
gst_decklink_video_sink_provide_clock ( GstElement * element )
{
GstDecklinkVideoSink * self = GST_DECKLINK_VIDEO_SINK_CAST ( element ) ;
if ( ! self - > output )
return NULL ;
return GST_CLOCK_CAST ( gst_object_ref ( self - > output - > clock ) ) ;
}
static gboolean
gst_decklink_video_sink_propose_allocation ( GstBaseSink * bsink ,
GstQuery * query )
{
GstCaps * caps ;
GstVideoInfo info ;
GstBufferPool * pool ;
guint size ;
gst_query_parse_allocation ( query , & caps , NULL ) ;
if ( caps = = NULL )
return FALSE ;
if ( ! gst_video_info_from_caps ( & info , caps ) )
return FALSE ;
size = GST_VIDEO_INFO_SIZE ( & info ) ;
if ( gst_query_get_n_allocation_pools ( query ) = = 0 ) {
GstStructure * structure ;
GstAllocator * allocator = NULL ;
GstAllocationParams params = { ( GstMemoryFlags ) 0 , 15 , 0 , 0 } ;
if ( gst_query_get_n_allocation_params ( query ) > 0 )
gst_query_parse_nth_allocation_param ( query , 0 , & allocator , & params ) ;
else
gst_query_add_allocation_param ( query , allocator , & params ) ;
pool = gst_video_buffer_pool_new ( ) ;
structure = gst_buffer_pool_get_config ( pool ) ;
gst_buffer_pool_config_set_params ( structure , caps , size , 0 , 0 ) ;
gst_buffer_pool_config_set_allocator ( structure , allocator , & params ) ;
if ( allocator )
gst_object_unref ( allocator ) ;
if ( ! gst_buffer_pool_set_config ( pool , structure ) )
goto config_failed ;
gst_query_add_allocation_pool ( query , pool , size , 0 , 0 ) ;
gst_object_unref ( pool ) ;
gst_query_add_allocation_meta ( query , GST_VIDEO_META_API_TYPE , NULL ) ;
}
return TRUE ;
// ERRORS
config_failed :
{
GST_ERROR_OBJECT ( bsink , " failed to set config " ) ;
gst_object_unref ( pool ) ;
return FALSE ;
}
}