From 2ee4399f3f2c844e2f60b668ecb9b36e1d48a358 Mon Sep 17 00:00:00 2001 From: "Joshua M. Doe" Date: Thu, 1 Aug 2019 12:25:23 -0400 Subject: [PATCH] imperxsdisrc: new source element for IMPERX HD-SDI Express framegrabbers The IMPERX SDI cards support BGR, YUY2, and a unique 10-bit YUV 4:2:2 format which is not currently supported in GStreamer. For now, we'll unpack it to AYUV64. This has only been tested on a ExpressCard device, but is expected to work with all IMPERX grabbers that use this SDK. Special attention has not been given yet to timestamping or accounting for dropped frames. Currently if the source is interlaced this element will deinterlace. Another mode could be supported to push out the frames as is, with appropriate caps to indicate interlacing mode. --- CMakeLists.txt | 3 + cmake/modules/FindImperxSDI.cmake | 47 ++ sys/CMakeLists.txt | 4 + sys/imperxsdi/CMakeLists.txt | 27 + sys/imperxsdi/gstimperxsdisrc.cpp | 832 ++++++++++++++++++++++++++++++ sys/imperxsdi/gstimperxsdisrc.h | 83 +++ 6 files changed, 996 insertions(+) create mode 100644 cmake/modules/FindImperxSDI.cmake create mode 100644 sys/imperxsdi/CMakeLists.txt create mode 100644 sys/imperxsdi/gstimperxsdisrc.cpp create mode 100644 sys/imperxsdi/gstimperxsdisrc.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 4e9c4be..b848ef4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -79,6 +79,9 @@ macro_log_feature(IDSUEYE_FOUND "IDS uEye" "Required to build IDS uEye source el find_package(Imperx) macro_log_feature(IMPERX_FLEX_FOUND "Imperx FrameLink Express" "Required to build Imperx FrameLink Express source element" "http://www.imperx.com/" FALSE) +find_package(ImperxSDI) +macro_log_feature(IMPERX_SDI_FOUND "Imperx HD-SDI Express" "Required to build Imperx HD-SDI Express source element" "http://www.imperx.com/" FALSE) + find_package(IOtechDaqX) macro_log_feature(IOTECHDAQX_FOUND "IOtech DaqX" "Required to build IOtech DaqX source element" "http://www.mccdaq.com/" FALSE) diff --git a/cmake/modules/FindImperxSDI.cmake b/cmake/modules/FindImperxSDI.cmake new file mode 100644 index 0000000..b285b10 --- /dev/null +++ b/cmake/modules/FindImperxSDI.cmake @@ -0,0 +1,47 @@ +# - Try to find Imperx FrameLink Express +# Once done this will define +# +# IMPERX_SDI_FOUND - system has Imperx SDI +# IMPERX_SDI_INCLUDE_DIR - the Imperx SDI include directory +# IMPERX_SDI_LIBRARIES - the libraries needed to use Imperx SDI + +# Copyright (c) 2006, Tim Beaulen +# Copyright (c) 2019, United States Government, Joshua M. Doe +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +if (NOT IMPERX_SDI_DIR) + # 32-bit dir on win32 + file(TO_CMAKE_PATH "$ENV{ProgramFiles}" _PROG_FILES) + # 32-bit dir on win64 + file(TO_CMAKE_PATH "$ENV{ProgramFiles(x86)}" _PROG_FILES_X86) + # 64-bit dir on win64 + file(TO_CMAKE_PATH "$ENV{ProgramW6432}" _PROG_FILES_W6432) + if (_PROG_FILES_X86) + set(_PROGFILESDIR "${_PROG_FILES_W6432}") + else () + set(_PROGFILESDIR "${_PROG_FILES}") + endif () + + set (IMPERX_SDI_DIR "${_PROGFILESDIR}/Imperx/HD-SDI Express" CACHE PATH "Directory containing Imperx HD-SDI Express includes and libraries") + + if (CMAKE_SIZEOF_VOID_P MATCHES "8") + set(_LIB_PATH "${IMPERX_SDI_DIR}/SDK/lib/x64") + else () + set(_LIB_PATH "${IMPERX_SDI_DIR}/SDK/lib/win32") + endif () +endif () + +find_path (IMPERX_SDI_INCLUDE_DIR VCESDI.h + PATHS + "${IMPERX_SDI_DIR}/SDK/inc" + DOC "Directory containing VCESDI.h include file") + +find_library (IMPERX_SDI_LIBRARIES NAMES VCESDI + PATHS + "${_LIB_PATH}" + DOC "Imperx HD-SDI Express library to link with") + +include (FindPackageHandleStandardArgs) +find_package_handle_standard_args (IMPERX_SDI DEFAULT_MSG IMPERX_SDI_INCLUDE_DIR IMPERX_SDI_LIBRARIES) diff --git a/sys/CMakeLists.txt b/sys/CMakeLists.txt index 361922e..335e4bc 100644 --- a/sys/CMakeLists.txt +++ b/sys/CMakeLists.txt @@ -42,6 +42,10 @@ if (IMPERX_FLEX_FOUND) add_subdirectory (imperxflex) endif () +if (IMPERX_SDI_FOUND) + add_subdirectory (imperxsdi) +endif () + if (IOTECHDAQX_FOUND) add_subdirectory (iotechdaqx) endif (IOTECHDAQX_FOUND) diff --git a/sys/imperxsdi/CMakeLists.txt b/sys/imperxsdi/CMakeLists.txt new file mode 100644 index 0000000..5e24196 --- /dev/null +++ b/sys/imperxsdi/CMakeLists.txt @@ -0,0 +1,27 @@ +set (SOURCES + gstimperxsdisrc.cpp) + +set (HEADERS + gstimperxsdisrc.h) + +include_directories (AFTER + ${IMPERX_SDI_INCLUDE_DIR}) + +set (libname libgstimperxsdi) + +add_library (${libname} MODULE + ${SOURCES} + ${HEADERS}) + +target_link_libraries (${libname} + ${GLIB2_LIBRARIES} + ${GOBJECT_LIBRARIES} + ${GSTREAMER_LIBRARY} + ${GSTREAMER_BASE_LIBRARY} + ${GSTREAMER_VIDEO_LIBRARY} + ${IMPERX_SDI_LIBRARIES}) + +set (pdbfile "${CMAKE_CURRENT_BINARY_DIR}/\${CMAKE_INSTALL_CONFIG_NAME}/${libname}.pdb") +install (FILES ${pdbfile} DESTINATION lib/gstreamer-1.0 COMPONENT pdb) +install(TARGETS ${libname} + LIBRARY DESTINATION lib/gstreamer-1.0) diff --git a/sys/imperxsdi/gstimperxsdisrc.cpp b/sys/imperxsdi/gstimperxsdisrc.cpp new file mode 100644 index 0000000..9c606eb --- /dev/null +++ b/sys/imperxsdi/gstimperxsdisrc.cpp @@ -0,0 +1,832 @@ +/* GStreamer + * Copyright (C) 2011 FIXME + * + * 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. + */ +/** + * SECTION:element-gstimperxsdisrc + * + * The imperxsdisrc element is a source for IMPERX HD-SDI Express framegrabbers. + * + * + * Example launch line + * |[ + * gst-launch -v imperxsdisrc ! videoconvert ! autovideosink + * ]| + * Shows video from the default IMPERX HD-SDI Express framegrabber + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#ifdef HAVE_ORC +#include +#else +#define orc_memcpy memcpy +#endif + +#include "gstimperxsdisrc.h" + +GST_DEBUG_CATEGORY_STATIC (gst_imperxsdisrc_debug); +#define GST_CAT_DEFAULT gst_imperxsdisrc_debug + +#define PORT_VIDEO 0 +#define PORT_AUDIO 1 + +/* prototypes */ +static void gst_imperxsdisrc_set_property (GObject * object, + guint property_id, const GValue * value, GParamSpec * pspec); +static void gst_imperxsdisrc_get_property (GObject * object, + guint property_id, GValue * value, GParamSpec * pspec); +static void gst_imperxsdisrc_dispose (GObject * object); +static void gst_imperxsdisrc_finalize (GObject * object); + +static gboolean gst_imperxsdisrc_start (GstBaseSrc * src); +static gboolean gst_imperxsdisrc_stop (GstBaseSrc * src); +static GstCaps *gst_imperxsdisrc_get_caps (GstBaseSrc * src, GstCaps * filter); +static gboolean gst_imperxsdisrc_set_caps (GstBaseSrc * src, GstCaps * caps); +static gboolean gst_imperxsdisrc_unlock (GstBaseSrc * src); +static gboolean gst_imperxsdisrc_unlock_stop (GstBaseSrc * src); + +static GstFlowReturn gst_imperxsdisrc_create (GstPushSrc * src, + GstBuffer ** buf); + +static GstCaps *gst_imperxsdisrc_create_caps (GstImperxSdiSrc * src); +enum +{ + PROP_0, + PROP_NUM_CAPTURE_BUFFERS, + PROP_BOARD, + PROP_TIMEOUT +}; + +#define DEFAULT_PROP_NUM_CAPTURE_BUFFERS 3 +#define DEFAULT_PROP_BOARD 0 +#define DEFAULT_PROP_TIMEOUT 1000 + +/* pad templates */ + +static GstStaticPadTemplate gst_imperxsdisrc_src_template = +GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("{ AYUV64, YUY2, BGR }")) + ); + +/* class initialization */ + +G_DEFINE_TYPE (GstImperxSdiSrc, gst_imperxsdisrc, GST_TYPE_PUSH_SRC); + +static void +gst_imperxsdisrc_class_init (GstImperxSdiSrcClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass); + GstBaseSrcClass *gstbasesrc_class = GST_BASE_SRC_CLASS (klass); + GstPushSrcClass *gstpushsrc_class = GST_PUSH_SRC_CLASS (klass); + + gobject_class->set_property = gst_imperxsdisrc_set_property; + gobject_class->get_property = gst_imperxsdisrc_get_property; + gobject_class->dispose = gst_imperxsdisrc_dispose; + gobject_class->finalize = gst_imperxsdisrc_finalize; + + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&gst_imperxsdisrc_src_template)); + + gst_element_class_set_static_metadata (gstelement_class, + "IMPERX HD-SDI Express Video Source", "Source/Video", + "IMPERX HD-SDI Express framegrabber video source", + "Joshua M. Doe "); + + gstbasesrc_class->start = GST_DEBUG_FUNCPTR (gst_imperxsdisrc_start); + gstbasesrc_class->stop = GST_DEBUG_FUNCPTR (gst_imperxsdisrc_stop); + gstbasesrc_class->get_caps = GST_DEBUG_FUNCPTR (gst_imperxsdisrc_get_caps); + gstbasesrc_class->set_caps = GST_DEBUG_FUNCPTR (gst_imperxsdisrc_set_caps); + gstbasesrc_class->unlock = GST_DEBUG_FUNCPTR (gst_imperxsdisrc_unlock); + gstbasesrc_class->unlock_stop = + GST_DEBUG_FUNCPTR (gst_imperxsdisrc_unlock_stop); + + gstpushsrc_class->create = GST_DEBUG_FUNCPTR (gst_imperxsdisrc_create); + + /* Install GObject properties */ + g_object_class_install_property (gobject_class, PROP_NUM_CAPTURE_BUFFERS, + g_param_spec_uint ("num-capture-buffers", "Number of capture buffers", + "Number of capture buffers", 3, G_MAXUINT, + DEFAULT_PROP_NUM_CAPTURE_BUFFERS, + (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + g_object_class_install_property (gobject_class, PROP_BOARD, + g_param_spec_uint ("board", "Board", "Board number", 0, 7, + DEFAULT_PROP_BOARD, + (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + g_object_class_install_property (G_OBJECT_CLASS (klass), + PROP_TIMEOUT, g_param_spec_int ("timeout", + "Timeout (ms)", + "Timeout in ms (0 to use default)", 0, G_MAXINT, + DEFAULT_PROP_TIMEOUT, + (GParamFlags) (G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE))); + +} + +static void +gst_imperxsdisrc_reset (GstImperxSdiSrc * src) +{ + g_assert (src->grabber == NULL); + + src->acq_started = FALSE; + + if (src->caps) { + gst_caps_unref (src->caps); + src->caps = NULL; + } + if (src->buffer) { + gst_buffer_unref (src->buffer); + src->buffer = NULL; + } + + src->width = 0; + src->height = 0; + src->framerate = 0; + src->is_interlaced = FALSE; +} + +static void +gst_imperxsdisrc_init (GstImperxSdiSrc * src) +{ + /* set source as live (no preroll) */ + gst_base_src_set_live (GST_BASE_SRC (src), TRUE); + + /* override default of BYTES to operate in time mode */ + gst_base_src_set_format (GST_BASE_SRC (src), GST_FORMAT_TIME); + + /* initialize member variables */ + src->num_capture_buffers = DEFAULT_PROP_NUM_CAPTURE_BUFFERS; + src->board = DEFAULT_PROP_BOARD; + src->timeout = DEFAULT_PROP_TIMEOUT; + + g_mutex_init (&src->mutex); + g_cond_init (&src->cond); + src->stop_requested = FALSE; + src->caps = NULL; + src->buffer = NULL; + + gst_imperxsdisrc_reset (src); +} + +void +gst_imperxsdisrc_set_property (GObject * object, guint property_id, + const GValue * value, GParamSpec * pspec) +{ + GstImperxSdiSrc *src; + + src = GST_IMPERX_SDI_SRC (object); + + switch (property_id) { + case PROP_NUM_CAPTURE_BUFFERS: + if (src->acq_started) { + GST_ELEMENT_WARNING (src, RESOURCE, SETTINGS, + ("Number of capture buffers cannot be changed after acquisition has started."), + (NULL)); + } else { + src->num_capture_buffers = g_value_get_uint (value); + } + break; + case PROP_BOARD: + src->board = g_value_get_uint (value); + break; + case PROP_TIMEOUT: + src->timeout = g_value_get_int (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +void +gst_imperxsdisrc_get_property (GObject * object, guint property_id, + GValue * value, GParamSpec * pspec) +{ + GstImperxSdiSrc *src; + + g_return_if_fail (GST_IS_IMPERX_SDI_SRC (object)); + src = GST_IMPERX_SDI_SRC (object); + + switch (property_id) { + case PROP_NUM_CAPTURE_BUFFERS: + g_value_set_uint (value, src->num_capture_buffers); + break; + case PROP_BOARD: + g_value_set_uint (value, src->board); + break; + case PROP_TIMEOUT: + g_value_set_int (value, src->timeout); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +void +gst_imperxsdisrc_dispose (GObject * object) +{ + GstImperxSdiSrc *src; + + g_return_if_fail (GST_IS_IMPERX_SDI_SRC (object)); + src = GST_IMPERX_SDI_SRC (object); + + /* clean up as possible. may be called multiple times */ + + g_mutex_clear (&src->mutex); + g_cond_clear (&src->cond); + + G_OBJECT_CLASS (gst_imperxsdisrc_parent_class)->dispose (object); +} + +void +gst_imperxsdisrc_finalize (GObject * object) +{ + GstImperxSdiSrc *src; + + g_return_if_fail (GST_IS_IMPERX_SDI_SRC (object)); + src = GST_IMPERX_SDI_SRC (object); + + /* clean up object here */ + + if (src->caps) { + gst_caps_unref (src->caps); + src->caps = NULL; + } + + if (src->buffer) { + gst_buffer_unref (src->buffer); + src->buffer = NULL; + } + + G_OBJECT_CLASS (gst_imperxsdisrc_parent_class)->finalize (object); +} + +static const gchar * +gst_imperxsdisrc_get_error (VCESDI_Error err) +{ + switch (err) { + case VCESDI_Err_Success: + return "Operation successful"; + case VCESDI_Err_badArgument: + return "Argument is invalid"; + case VCESDI_Err_noDriver: + "Driver is not installed"; + case VCESDI_Err_noDevicePresent: + return "Device is not present"; + case VCESDI_Err_DeviceBusy: + return "Device busy or not responding"; + case VCESDI_Err_noMemory: + return "Not enough memory to perform operation"; + case VCESDI_Err_notInitialized: + return "Sub-component has not been initialized"; + case VCESDI_Err_notSupported: + return "Operation is not supported"; + case VCESDI_Err_UnknownError: + return "Unknown (system) error occurs"; + default: + return "Invalid error code"; + } +} + +static gboolean +gst_imperxsdisrc_start (GstBaseSrc * bsrc) +{ + GstImperxSdiSrc *src = GST_IMPERX_SDI_SRC (bsrc); + VCESDI_Error err; + VCESDI_EnumData enumData; + VCESDI_ENUM hDevEnum; + guint8 camera_connected; + VCESDI_CameraStatus camera_status; + gint fps_n, fps_d; + + GST_DEBUG_OBJECT (src, "start"); + + /* enumerate devices */ + enumData.cbSize = sizeof (VCESDI_EnumData); + hDevEnum = VCESDI_EnumInit (); + while (VCESDI_EnumNext (hDevEnum, &enumData) == VCESDI_Err_Success) { + GST_DEBUG_OBJECT (src, "Found device: slot #%d, name '%s'", + enumData.dwSlot, enumData.pSlotName); + + if (enumData.dwSlot == src->board) { + src->grabber = VCESDI_InitByHandle (enumData.deviceData); + break; + } + } + VCESDI_EnumClose (hDevEnum); + + if (src->grabber == NULL) { + GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, + ("Invalid board/device number or failed to initialize grabber"), + (NULL)); + return FALSE; + } + + err = VCESDI_IsCameraConnected (src->grabber, PORT_VIDEO, &camera_connected); + if (err != VCESDI_Err_Success) { + GST_ELEMENT_ERROR (src, RESOURCE, SETTINGS, + ("Failed to get camera connected status (%s)", + gst_imperxsdisrc_get_error (err)), (NULL)); + return FALSE; + } + + if (camera_connected) { + GST_DEBUG_OBJECT (src, "Camera has been detected"); + } else { + GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, + ("No camera detected"), (NULL)); + return FALSE; + } + + err = VCESDI_GetCameraStatus (src->grabber, PORT_VIDEO, &camera_status); + if (err != VCESDI_Err_Success) { + GST_ELEMENT_ERROR (src, RESOURCE, SETTINGS, + ("Failed to get camera status (%s)", gst_imperxsdisrc_get_error (err)), + (NULL)); + return FALSE; + } + + src->width = camera_status.Width; + src->height = camera_status.Height; + src->framerate = camera_status.FrameRate; + src->is_interlaced = (camera_status.Mode == VCESDI_CameraMode_Interlaced); + + if (src->caps) { + gst_caps_unref (src->caps); + src->caps = NULL; + } + fps_n = (gint) (src->framerate * 1000); + fps_d = 1000; + src->caps = gst_pad_get_pad_template_caps (GST_BASE_SRC_PAD (src)); + src->caps = gst_caps_make_writable (src->caps); + gst_caps_set_simple (src->caps, + "width", G_TYPE_INT, src->width, + "height", G_TYPE_INT, src->height, + "framerate", GST_TYPE_FRACTION, fps_n, fps_d, NULL); + GST_DEBUG_OBJECT (src, "Detected video is %dx%d@%d", src->width, src->height, + src->framerate); + + return TRUE; +} + +static void +unpack_YVYU10 (gpointer dest, const gpointer data, + const gint stride, gint x, gint y, gint width) +{ + int i; + const guint8 *restrict s = (guint8 *) (data) + stride * y; + guint16 *restrict d = (guint16 *) dest; + guint32 a0, a1, a2, a3; + guint16 y0, y1, y2, y3, y4, y5; + guint16 u0, u2, u4; + guint16 v0, v2, v4; + + s += x * 2; + for (i = 0; i < width; i += 6) { + a0 = GST_READ_UINT32_LE (s + (i / 6) * 16 + 0); + a1 = GST_READ_UINT32_LE (s + (i / 6) * 16 + 4); + a2 = GST_READ_UINT32_LE (s + (i / 6) * 16 + 8); + a3 = GST_READ_UINT32_LE (s + (i / 6) * 16 + 12); + + y0 = ((a0 >> 0) & 0x3ff) << 6; + u0 = ((a0 >> 10) & 0x3ff) << 6; + y1 = ((a0 >> 20) & 0x3ff) << 6; + v0 = (((a0 >> 30) | (a1 << 2)) & 0x3ff) << 6; + y2 = ((a1 >> 8) & 0x3ff) << 6; + u2 = ((a1 >> 18) & 0x3ff) << 6; + + y3 = ((a2 >> 0) & 0x3ff) << 6; + v2 = ((a2 >> 10) & 0x3ff) << 6; + y4 = ((a2 >> 20) & 0x3ff) << 6; + u4 = (((a2 >> 30) | (a3 << 2)) & 0x3ff) << 6; + y5 = ((a3 >> 8) & 0x3ff) << 6; + v4 = ((a3 >> 18) & 0x3ff) << 6; + + d[4 * (i + 0) + 0] = 0xffff; + d[4 * (i + 0) + 1] = y0; + d[4 * (i + 0) + 2] = u0; + d[4 * (i + 0) + 3] = v0; + + if (i < width - 1) { + d[4 * (i + 1) + 0] = 0xffff; + d[4 * (i + 1) + 1] = y1; + d[4 * (i + 1) + 2] = u0; + d[4 * (i + 1) + 3] = v0; + } + if (i < width - 2) { + d[4 * (i + 2) + 0] = 0xffff; + d[4 * (i + 2) + 1] = y2; + d[4 * (i + 2) + 2] = u2; + d[4 * (i + 2) + 3] = v2; + } + if (i < width - 3) { + d[4 * (i + 3) + 0] = 0xffff; + d[4 * (i + 3) + 1] = y3; + d[4 * (i + 3) + 2] = u2; + d[4 * (i + 3) + 3] = v2; + } + if (i < width - 4) { + d[4 * (i + 4) + 0] = 0xffff; + d[4 * (i + 4) + 1] = y4; + d[4 * (i + 4) + 2] = u4; + d[4 * (i + 4) + 3] = v4; + } + if (i < width - 5) { + d[4 * (i + 5) + 0] = 0xffff; + d[4 * (i + 5) + 1] = y5; + d[4 * (i + 5) + 2] = u4; + d[4 * (i + 5) + 3] = v4; + } + } +} + + +static GstBuffer * +gst_imperxsdisrc_create_buffer_from_frameinfo (GstImperxSdiSrc * src, + VCESDI_FrameInfo * pFrameInfo) +{ + GstMapInfo minfo; + GstBuffer *buf; + int buffer_size; + + if (src->is_interlaced && src->format != GST_VIDEO_FORMAT_AYUV64) { + VCESDI_Decode (&src->camera_data, NULL, NULL, &src->imperx_stride, + src->camera_data.Format, NULL, NULL); + buffer_size = src->camera_data.CameraStatus.Height * src->imperx_stride; + } else { + buffer_size = src->height * src->gst_stride; + } + + buf = gst_buffer_new_and_alloc (buffer_size); + + /* Copy image to buffer from surface */ + gst_buffer_map (buf, &minfo, GST_MAP_WRITE); + GST_LOG_OBJECT (src, + "SrcSize=%d, SrcStride=%d, DstSize=%d, DstStride=%d, number=%d, timestamp=%d", + pFrameInfo->bufferSize, src->imperx_stride, minfo.size, src->gst_stride, + pFrameInfo->number, pFrameInfo->timestamp); + + if (src->is_interlaced && src->format != GST_VIDEO_FORMAT_AYUV64) { + VCESDI_Decode (&src->camera_data, pFrameInfo->lpRawBuffer, minfo.data, + &src->imperx_stride, src->camera_data.Format, NULL, NULL); + } else { + if (src->format == GST_VIDEO_FORMAT_AYUV64) { + gint row; + if (src->is_interlaced) { + gint hh = src->height / 2; + /* assumes even number of rows, which is the case for all formats */ + for (row = 0; row < hh; row++) { + unpack_YVYU10 (minfo.data + src->gst_stride * (row * 2), + pFrameInfo->lpRawBuffer, src->imperx_stride, 0, row, src->width); + unpack_YVYU10 (minfo.data + src->gst_stride * (row * 2 + 1), + pFrameInfo->lpRawBuffer, src->imperx_stride, 0, row + hh, + src->width); + } + } else { + for (row = 0; row < src->height; row++) { + unpack_YVYU10 (minfo.data + src->gst_stride * row, + pFrameInfo->lpRawBuffer, src->imperx_stride, 0, row, src->width); + } + } + } else { + /* all supported formats should already be 4-byte aligned */ + g_assert (pFrameInfo->bufferSize >= minfo.size); + orc_memcpy (minfo.data, pFrameInfo->lpRawBuffer, minfo.size); + } + } + + gst_buffer_unmap (buf, &minfo); + + return buf; +} + +static void __stdcall +gst_imperxsdisrc_callback (void *lpUserData, VCESDI_FrameInfo * pFrameInfo) +{ + GstImperxSdiSrc *src = GST_IMPERX_SDI_SRC (lpUserData); + gint dropped_frames; + static guint32 last_frame_number = 0; + static guint64 buffers_processed = 0; + static guint64 total_dropped_frames = 0; + + g_assert (src != NULL); + + /* check for DMA errors */ + if (pFrameInfo->dma_status == DMA_STATUS_FRAME_DROPED) { + /* TODO: save this in dropped frame total? */ + GST_WARNING_OBJECT (src, "Frame dropped from DMA system."); + return; + } else if (pFrameInfo->dma_status == DMA_STATUS_FIFO_OVERRUN) { + GST_WARNING_OBJECT (src, "DMA system reports FIFO overrun"); + return; + } else if (pFrameInfo->dma_status == DMA_STATUS_ABORTED) { + GST_WARNING_OBJECT (src, "DMA system reports acquisition was aborted"); + return; + } else if (pFrameInfo->dma_status == DMA_STATUS_DICONNECTED) { + GST_WARNING_OBJECT (src, "DMA system reports camera is disconnected"); + return; + } else if (pFrameInfo->dma_status != DMA_STATUS_OK) { + GST_WARNING_OBJECT (src, "DMA system reports unknown error"); + return; + } + + /* check for dropped frames and disrupted signal */ + dropped_frames = (pFrameInfo->number - last_frame_number) - 1; + if (dropped_frames > 0) { + total_dropped_frames += dropped_frames; + GST_WARNING_OBJECT (src, "Dropped %d frames (%d total)", dropped_frames, + total_dropped_frames); + } else if (dropped_frames < 0) { + GST_WARNING_OBJECT (src, + "Signal disrupted, frames likely dropped and timestamps inaccurate"); + + /* frame timestamps reset, so adjust start time, accuracy reduced */ + src->acq_start_time = + gst_clock_get_time (gst_element_get_clock (GST_ELEMENT (src))) - + pFrameInfo->timestamp * GST_USECOND; + } + last_frame_number = pFrameInfo->number; + + g_mutex_lock (&src->mutex); + + if (src->buffer) { + /* TODO: save this in dropped frame total? */ + GST_WARNING_OBJECT (src, + "Got new buffer before old handled, dropping old."); + gst_buffer_unref (src->buffer); + src->buffer = NULL; + } + + src->buffer = gst_imperxsdisrc_create_buffer_from_frameinfo (src, pFrameInfo); + + GST_BUFFER_TIMESTAMP (src->buffer) = + GST_CLOCK_DIFF (gst_element_get_base_time (GST_ELEMENT (src)), + src->acq_start_time + pFrameInfo->timestamp * GST_USECOND); + GST_BUFFER_OFFSET (src->buffer) = buffers_processed; + ++buffers_processed; + + g_cond_signal (&src->cond); + g_mutex_unlock (&src->mutex); +} + +static gboolean +gst_imperxsdisrc_start_grab (GstImperxSdiSrc * src) +{ + GstBaseSrc *basesrc = GST_BASE_SRC (src); + GstCaps *peercaps; + VCESDI_Error err; + GstVideoInfo vinfo; + + /* we've already fixated width, height, framerate */ + peercaps = gst_pad_peer_query_caps (GST_BASE_SRC_PAD (basesrc), src->caps); + peercaps = gst_caps_fixate (peercaps); + gst_caps_replace (&src->caps, peercaps); + gst_video_info_from_caps (&vinfo, src->caps); + src->gst_stride = GST_VIDEO_INFO_COMP_STRIDE (&vinfo, 0); + + src->format = GST_VIDEO_INFO_FORMAT (&vinfo); + switch (src->format) { + case GST_VIDEO_FORMAT_AYUV64: + src->camera_data.Format = VCESDI_OutputFormat_YCrCb10; + /* 12 components (6 pixels), in 16 bytes */ + src->imperx_stride = GST_ROUND_UP_4 ((int) ((src->width * 16) / 6.0)); + break; + case GST_VIDEO_FORMAT_YUY2: + src->camera_data.Format = VCESDI_OutputFormat_YCrCb8; + src->imperx_stride = src->width * 2; + break; + case GST_VIDEO_FORMAT_BGR: + src->camera_data.Format = VCESDI_OutputFormat_RGB24; + src->imperx_stride = src->width * 3; + break; + default: + g_assert_not_reached (); + } + + err = VCESDI_GetDMAAccess (src->grabber, PORT_VIDEO); + if (err != VCESDI_Err_Success) { + GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, + ("Failed to get DMA access to port on grabber (%s)", + gst_imperxsdisrc_get_error (err)), (NULL)); + return FALSE; + } + + err = + VCESDI_SetBufferCount (src->grabber, PORT_VIDEO, + src->num_capture_buffers); + if (err != VCESDI_Err_Success) { + GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, + ("Failed to set buffer count (%s)", gst_imperxsdisrc_get_error (err)), + (NULL)); + return FALSE; + } + + src->camera_data.TransferMode = VCESDI_TransferMode_Frames; + err = + VCESDI_StartGrab (src->grabber, PORT_VIDEO, 0, &src->camera_data, + (VCESDI_GrabFrame_Callback) gst_imperxsdisrc_callback, src); + + return TRUE; +} + +static gboolean +gst_imperxsdisrc_stop (GstBaseSrc * bsrc) +{ + GstImperxSdiSrc *src = GST_IMPERX_SDI_SRC (bsrc); + VCESDI_Error err; + + GST_DEBUG_OBJECT (src, "stop"); + + if (src->acq_started) { + err = VCESDI_StopGrab (src->grabber, PORT_VIDEO); + if (err) { + GST_WARNING_OBJECT (src, "Error stopping grab: '%s'", + gst_imperxsdisrc_get_error (err)); + } + src->acq_started = FALSE; + } + + if (src->grabber) { + err = VCESDI_ReleaseDMAAccess (src->grabber, PORT_VIDEO); + if (err) { + GST_WARNING_OBJECT (src, "Error releasing DMA accessx: %s", + gst_imperxsdisrc_get_error (err)); + } + err = VCESDI_Done (src->grabber); + if (err) { + GST_WARNING_OBJECT (src, "Error closing grabber: %s", + gst_imperxsdisrc_get_error (err)); + } + src->grabber = NULL; + } + + gst_imperxsdisrc_reset (src); + + return TRUE; +} + +static GstCaps * +gst_imperxsdisrc_get_caps (GstBaseSrc * bsrc, GstCaps * filter) +{ + GstImperxSdiSrc *src = GST_IMPERX_SDI_SRC (bsrc); + GstCaps *caps; + + if (src->grabber == NULL) { + caps = gst_pad_get_pad_template_caps (GST_BASE_SRC_PAD (src)); + } else { + caps = gst_caps_copy (src->caps); + } + + GST_DEBUG_OBJECT (src, "The caps before filtering are %" GST_PTR_FORMAT, + caps); + + if (filter && caps) { + GstCaps *tmp = gst_caps_intersect (caps, filter); + gst_caps_unref (caps); + caps = tmp; + } + + GST_DEBUG_OBJECT (src, "The caps after filtering are %" GST_PTR_FORMAT, caps); + + return caps; +} + +static gboolean +gst_imperxsdisrc_set_caps (GstBaseSrc * bsrc, GstCaps * caps) +{ + GstImperxSdiSrc *src = GST_IMPERX_SDI_SRC (bsrc); + GstVideoInfo vinfo; + GstStructure *s = gst_caps_get_structure (caps, 0); + + GST_DEBUG_OBJECT (src, "The caps being set are %" GST_PTR_FORMAT, caps); + + gst_video_info_from_caps (&vinfo, caps); + + if (GST_VIDEO_INFO_FORMAT (&vinfo) != GST_VIDEO_FORMAT_UNKNOWN) { + src->gst_stride = GST_VIDEO_INFO_COMP_STRIDE (&vinfo, 0); + } else { + goto unsupported_caps; + } + + return TRUE; + +unsupported_caps: + GST_ERROR_OBJECT (src, "Unsupported caps: %" GST_PTR_FORMAT, caps); + return FALSE; +} + +static gboolean +gst_imperxsdisrc_unlock (GstBaseSrc * bsrc) +{ + GstImperxSdiSrc *src = GST_IMPERX_SDI_SRC (bsrc); + + GST_LOG_OBJECT (src, "unlock"); + + g_mutex_lock (&src->mutex); + src->stop_requested = TRUE; + g_cond_signal (&src->cond); + g_mutex_unlock (&src->mutex); + + return TRUE; +} + +static gboolean +gst_imperxsdisrc_unlock_stop (GstBaseSrc * bsrc) +{ + GstImperxSdiSrc *src = GST_IMPERX_SDI_SRC (bsrc); + + GST_LOG_OBJECT (src, "unlock_stop"); + + src->stop_requested = FALSE; + + return TRUE; +} + +static GstFlowReturn +gst_imperxsdisrc_create (GstPushSrc * psrc, GstBuffer ** buf) +{ + GstImperxSdiSrc *src = GST_IMPERX_SDI_SRC (psrc); + gint64 end_time; + + GST_LOG_OBJECT (src, "create"); + + /* Start acquisition if not already started */ + if (G_UNLIKELY (!src->acq_started)) { + GST_LOG_OBJECT (src, "starting acquisition"); + src->acq_start_time = + gst_clock_get_time (gst_element_get_clock (GST_ELEMENT (src))); + if (!gst_imperxsdisrc_start_grab (src)) { + GST_ELEMENT_ERROR (src, RESOURCE, FAILED, + ("Failed to start grabbing"), (NULL)); + return GST_FLOW_ERROR; + } + src->acq_started = TRUE; + } + + /* wait for a buffer to be ready */ + g_mutex_lock (&src->mutex); + end_time = g_get_monotonic_time () + src->timeout * G_TIME_SPAN_MILLISECOND; + while (!src->buffer && !src->stop_requested) { + if (!g_cond_wait_until (&src->cond, &src->mutex, end_time)) { + g_mutex_unlock (&src->mutex); + GST_ELEMENT_ERROR (src, RESOURCE, FAILED, + ("Timeout, no data received after %d ms", src->timeout), (NULL)); + return GST_FLOW_ERROR; + } + } + *buf = src->buffer; + src->buffer = NULL; + g_mutex_unlock (&src->mutex); + + if (src->stop_requested) { + if (*buf != NULL) { + gst_buffer_unref (*buf); + *buf = NULL; + } + return GST_FLOW_FLUSHING; + } + + return GST_FLOW_OK; +} + +static gboolean +plugin_init (GstPlugin * plugin) +{ + GST_DEBUG_CATEGORY_INIT (gst_imperxsdisrc_debug, "imperxsdisrc", 0, + "debug category for imperxsdisrc element"); + gst_element_register (plugin, "imperxsdisrc", GST_RANK_NONE, + gst_imperxsdisrc_get_type ()); + + return TRUE; +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + imperxsdi, + "IMPERX HD-SDI Express frame grabber source", + plugin_init, GST_PACKAGE_VERSION, GST_PACKAGE_LICENSE, GST_PACKAGE_NAME, + GST_PACKAGE_ORIGIN) diff --git a/sys/imperxsdi/gstimperxsdisrc.h b/sys/imperxsdi/gstimperxsdisrc.h new file mode 100644 index 0000000..897a8eb --- /dev/null +++ b/sys/imperxsdi/gstimperxsdisrc.h @@ -0,0 +1,83 @@ +/* GStreamer + * Copyright (C) 2011 FIXME + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef _GST_IMPERX_SDI_SRC_H_ +#define _GST_IMPERX_SDI_SRC_H_ + +#include + +#define WIN32_LEAN_AND_MEAN +#include + +#define bool gboolean +#include + +G_BEGIN_DECLS + +#define GST_TYPE_IMPERX_SDI_SRC (gst_imperxsdisrc_get_type()) +#define GST_IMPERX_SDI_SRC(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_IMPERX_SDI_SRC,GstImperxSdiSrc)) +#define GST_IMPERX_SDI_SRC_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_IMPERX_SDI_SRC,GstImperxSdiSrcClass)) +#define GST_IS_IMPERX_SDI_SRC(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_IMPERX_SDI_SRC)) +#define GST_IS_IMPERX_SDI_SRC_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_IMPERX_SDI_SRC)) + +typedef struct _GstImperxSdiSrc GstImperxSdiSrc; +typedef struct _GstImperxSdiSrcClass GstImperxSdiSrcClass; + +struct _GstImperxSdiSrc +{ + GstPushSrc base_imperxsdisrc; + + gboolean acq_started; + + /* camera handle */ + VCESDI_GRABBER grabber; + VCESDI_CameraData camera_data; + + /* properties */ + guint num_capture_buffers; + guint board; + gint timeout; + + GstBuffer *buffer; + GstClockTime acq_start_time; + + GstCaps *caps; + GstVideoFormat format; + gint width; + gint height; + gfloat framerate; + gboolean is_interlaced; + gint gst_stride; + gint imperx_stride; + + GMutex mutex; + GCond cond; + gboolean stop_requested; +}; + +struct _GstImperxSdiSrcClass +{ + GstPushSrcClass base_imperxsdisrc_class; +}; + +GType gst_imperxsdisrc_get_type (void); + +G_END_DECLS + +#endif