mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-19 06:46:38 +00:00
decklinkvideosink: Pass video frames directly to the Decklink SDK without copying
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. Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/2674>
This commit is contained in:
parent
5ffaf2aa09
commit
2aae64edb9
2 changed files with 331 additions and 49 deletions
|
@ -44,13 +44,16 @@
|
|||
# ifdef __MINGW32__
|
||||
# define CONVERT_COM_STRING(s) G_STMT_START { BSTR _s = (BSTR)s; s = (char*) malloc(100); wcstombs(s, _s, 100); ::SysFreeString(_s); } G_STMT_END
|
||||
# define FREE_COM_STRING(s) free(s);
|
||||
# define CONVERT_TO_COM_STRING(s) G_STMT_START { char * _s = (char *)s; s = (BSTR) malloc(100); mbstowcs(s, _s, 100); g_free(_s); } G_STMT_END
|
||||
# else
|
||||
# define CONVERT_COM_STRING(s) G_STMT_START { BSTR _s = (BSTR)s; s = _com_util::ConvertBSTRToString(_s); ::SysFreeString(_s); } G_STMT_END
|
||||
# define FREE_COM_STRING(s) G_STMT_START { delete[] s; } G_STMT_END
|
||||
# define CONVERT_TO_COM_STRING(s) G_STMT_START { char * _s = (char *)s; s = _com_util::ConvertStringToBSTR(_s); g_free(_s); } G_STMT_END
|
||||
# endif /* __MINGW32__ */
|
||||
#else
|
||||
#define COMSTR_T const char*
|
||||
#define CONVERT_COM_STRING(s)
|
||||
#define CONVERT_TO_COM_STRING(s)
|
||||
#define FREE_COM_STRING(s)
|
||||
#define WINAPI
|
||||
#endif /* G_OS_WIN32 */
|
||||
|
|
|
@ -232,6 +232,273 @@ private:
|
|||
gint m_refcount;
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE QueryInterface (REFIID, LPVOID *)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class GstDecklinkVideoFrame:public IDeckLinkVideoFrame
|
||||
{
|
||||
public:
|
||||
GstDecklinkVideoFrame (GstVideoFrame * frame):m_frame (0),
|
||||
m_dframe (0), m_ancillary (0), m_timecode (0), m_refcount (1)
|
||||
{
|
||||
m_frame = g_new0 (GstVideoFrame, 1);
|
||||
*m_frame = *frame;
|
||||
}
|
||||
|
||||
GstDecklinkVideoFrame (IDeckLinkMutableVideoFrame * dframe):m_frame (0),
|
||||
m_dframe (dframe), m_ancillary (0), m_timecode (0), m_refcount (1)
|
||||
{
|
||||
}
|
||||
|
||||
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;
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
}
|
||||
virtual BMDFrameFlags STDMETHODCALLTYPE GetFlags (void)
|
||||
{
|
||||
return m_dframe ? m_dframe->GetFlags () : bmdFrameFlagDefault;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE QueryInterface (REFIID, LPVOID *)
|
||||
{
|
||||
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:
|
||||
GstVideoFrame * m_frame;
|
||||
IDeckLinkMutableVideoFrame *m_dframe;
|
||||
IDeckLinkVideoFrameAncillary *m_ancillary;
|
||||
GstDecklinkTimecode *m_timecode;
|
||||
int m_refcount;
|
||||
|
||||
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 ();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* GstDecklinkMappingFormat:
|
||||
* @GST_DECKLINK_MAPPING_FORMAT_DEFAULT: Don't change the mapping format
|
||||
|
@ -289,8 +556,9 @@ gst_decklink_video_sink_start_scheduled_playback (GstElement * element);
|
|||
#define parent_class gst_decklink_video_sink_parent_class
|
||||
G_DEFINE_TYPE (GstDecklinkVideoSink, gst_decklink_video_sink,
|
||||
GST_TYPE_BASE_SINK);
|
||||
GST_ELEMENT_REGISTER_DEFINE_WITH_CODE (decklinkvideosink, "decklinkvideosink", GST_RANK_NONE,
|
||||
GST_TYPE_DECKLINK_VIDEO_SINK, decklink_element_init (plugin));
|
||||
GST_ELEMENT_REGISTER_DEFINE_WITH_CODE (decklinkvideosink, "decklinkvideosink",
|
||||
GST_RANK_NONE, GST_TYPE_DECKLINK_VIDEO_SINK,
|
||||
decklink_element_init (plugin));
|
||||
|
||||
static gboolean
|
||||
reset_framerate (GstCapsFeatures * features, GstStructure * structure,
|
||||
|
@ -446,7 +714,8 @@ gst_decklink_video_sink_class_init (GstDecklinkVideoSinkClass * klass)
|
|||
GST_DEBUG_CATEGORY_INIT (gst_decklink_video_sink_debug, "decklinkvideosink",
|
||||
0, "debug category for decklinkvideosink element");
|
||||
|
||||
gst_type_mark_as_plugin_api(GST_TYPE_DECKLINK_MAPPING_FORMAT, (GstPluginAPIFlags)0);
|
||||
gst_type_mark_as_plugin_api (GST_TYPE_DECKLINK_MAPPING_FORMAT,
|
||||
(GstPluginAPIFlags) 0);
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -517,7 +786,8 @@ gst_decklink_video_sink_set_property (GObject * object, guint property_id,
|
|||
self->afd_bar_line = g_value_get_int (value);
|
||||
break;
|
||||
case PROP_MAPPING_FORMAT:
|
||||
self->mapping_format = (GstDecklinkMappingFormat) g_value_get_enum (value);
|
||||
self->mapping_format =
|
||||
(GstDecklinkMappingFormat) g_value_get_enum (value);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
|
@ -958,7 +1228,7 @@ convert_cea708_cc_data_cea708_cdp_internal (GstDecklinkVideoSink * self,
|
|||
|
||||
static void
|
||||
write_vbi (GstDecklinkVideoSink * self, GstBuffer * buffer,
|
||||
BMDPixelFormat format, IDeckLinkMutableVideoFrame * frame,
|
||||
BMDPixelFormat format, GstDecklinkVideoFrame * frame,
|
||||
GstVideoTimeCodeMeta * tc_meta)
|
||||
{
|
||||
IDeckLinkVideoFrameAncillary *vanc_frame = NULL;
|
||||
|
@ -1237,23 +1507,32 @@ write_vbi (GstDecklinkVideoSink * self, GstBuffer * buffer,
|
|||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
static GstFlowReturn
|
||||
gst_decklink_video_sink_prepare (GstBaseSink * bsink, GstBuffer * buffer)
|
||||
{
|
||||
GstDecklinkVideoSink *self = GST_DECKLINK_VIDEO_SINK_CAST (bsink);
|
||||
GstVideoFrame vframe;
|
||||
IDeckLinkMutableVideoFrame *frame;
|
||||
guint8 *outdata, *indata;
|
||||
GstDecklinkVideoFrame *frame = NULL;
|
||||
GstFlowReturn flow_ret;
|
||||
HRESULT ret;
|
||||
GstClockTime timestamp, duration;
|
||||
GstClockTime running_time, running_time_duration;
|
||||
GstClockTime latency, render_delay;
|
||||
GstClockTimeDiff ts_offset;
|
||||
gint i;
|
||||
GstDecklinkVideoFormat caps_format;
|
||||
BMDPixelFormat format;
|
||||
gint stride;
|
||||
GstVideoTimeCodeMeta *tc_meta;
|
||||
|
||||
GST_DEBUG_OBJECT (self, "Preparing buffer %" GST_PTR_FORMAT, buffer);
|
||||
|
@ -1302,58 +1581,57 @@ gst_decklink_video_sink_prepare (GstBaseSink * bsink, GstBuffer * buffer)
|
|||
else
|
||||
running_time = 0;
|
||||
|
||||
ret = self->output->output->CreateVideoFrame (self->info.width,
|
||||
self->info.height, self->info.stride[0], format, bmdFrameFlagDefault,
|
||||
&frame);
|
||||
if (ret != S_OK) {
|
||||
GST_ELEMENT_ERROR (self, STREAM, FAILED,
|
||||
(NULL), ("Failed to create video frame: 0x%08lx", (unsigned long) ret));
|
||||
return GST_FLOW_ERROR;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
// 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;
|
||||
|
||||
frame->GetBytes ((void **) &outdata);
|
||||
indata = (guint8 *) GST_VIDEO_FRAME_PLANE_DATA (&vframe, 0);
|
||||
stride =
|
||||
MIN (GST_VIDEO_FRAME_PLANE_STRIDE (&vframe, 0), frame->GetRowBytes ());
|
||||
for (i = 0; i < self->info.height; i++) {
|
||||
memcpy (outdata, indata, stride);
|
||||
indata += GST_VIDEO_FRAME_PLANE_STRIDE (&vframe, 0);
|
||||
outdata += frame->GetRowBytes ();
|
||||
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;
|
||||
}
|
||||
gst_video_frame_unmap (&vframe);
|
||||
|
||||
// Takes ownership of the frame
|
||||
frame = new GstDecklinkVideoFrame (dframe);
|
||||
} else {
|
||||
// Takes ownership of the frame
|
||||
frame = new GstDecklinkVideoFrame (&vframe);
|
||||
}
|
||||
gst_video_frame_unmap (&vframe);
|
||||
|
||||
tc_meta = gst_buffer_get_video_time_code_meta (buffer);
|
||||
if (tc_meta) {
|
||||
BMDTimecodeFlags bflags = (BMDTimecodeFlags) 0;
|
||||
gchar *tc_str;
|
||||
|
||||
if (((GstVideoTimeCodeFlags) (tc_meta->tc.
|
||||
config.flags)) & GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME)
|
||||
bflags = (BMDTimecodeFlags) (bflags | bmdTimecodeIsDropFrame);
|
||||
else
|
||||
bflags = (BMDTimecodeFlags) (bflags | bmdTimecodeFlagDefault);
|
||||
if (tc_meta->tc.field_count == 2)
|
||||
bflags = (BMDTimecodeFlags) (bflags | bmdTimecodeFieldMark);
|
||||
|
||||
frame->SetTimecode (&tc_meta->tc);
|
||||
tc_str = gst_video_time_code_to_string (&tc_meta->tc);
|
||||
ret = frame->SetTimecodeFromComponents (self->timecode_format,
|
||||
(uint8_t) tc_meta->tc.hours,
|
||||
(uint8_t) tc_meta->tc.minutes,
|
||||
(uint8_t) tc_meta->tc.seconds, (uint8_t) tc_meta->tc.frames, bflags);
|
||||
if (ret != S_OK) {
|
||||
GST_ERROR_OBJECT (self,
|
||||
"Failed to set timecode %s to video frame: 0x%08lx", tc_str,
|
||||
(unsigned long) ret);
|
||||
flow_ret = GST_FLOW_ERROR;
|
||||
g_free (tc_str);
|
||||
goto out;
|
||||
}
|
||||
GST_DEBUG_OBJECT (self, "Set frame timecode to %s", tc_str);
|
||||
g_free (tc_str);
|
||||
}
|
||||
|
@ -1380,7 +1658,8 @@ gst_decklink_video_sink_prepare (GstBaseSink * bsink, GstBuffer * buffer)
|
|||
|
||||
out:
|
||||
|
||||
frame->Release ();
|
||||
if (frame)
|
||||
frame->Release ();
|
||||
|
||||
return flow_ret;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue