diff --git a/CMakeLists.txt b/CMakeLists.txt index eb9d2c9..8e86a36 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -117,6 +117,11 @@ macro_log_feature(SAPERA_FOUND "Teledyne DALSA Sapera" "Required to build Teledy find_package(XCLIB) macro_log_feature(XCLIB_FOUND "EPIX PIXCI" "Required to build EPIX PIXCI source element" "http://www.epixinc.com/" FALSE) +if (WIN32) + # Windows distributions of GStreamer include ZLIB + set(ZLIB_ROOT ${GSTREAMER_ROOT}) +endif () +find_package(ZLIB) # Setup common environment include_directories( diff --git a/cmake/modules/FindPleora.cmake b/cmake/modules/FindPleora.cmake index e60c37c..1d44027 100644 --- a/cmake/modules/FindPleora.cmake +++ b/cmake/modules/FindPleora.cmake @@ -16,29 +16,34 @@ if (NOT Pleora_DIR) set (Pleora_DIR $ENV{PUREGEV_ROOT} CACHE PATH "Directory containing Pleora SDK includes and libraries") endif () -if (CMAKE_SIZEOF_VOID_P MATCHES "8") +if (WIN32 AND CMAKE_SIZEOF_VOID_P MATCHES "8") set(_LIB_SUFFIX "64") else () set(_LIB_SUFFIX "") endif () -set(_Pleora_PATHS PATHS +set (_Pleora_PATHS PATHS "${Pleora_DIR}" "C:/Program Files/Pleora Technologies Inc/eBUS SDK/Includes" "C:/Program Files (x86)/Pleora Technologies Inc/eBUS SDK/Includes") find_path (Pleora_INCLUDE_DIR PvBase.h PATHS ${_Pleora_PATHS} - PATH_SUFFIXES Includes) + PATH_SUFFIXES Includes include) +message (STATUS "Found Pleora include dir in ${Pleora_INCLUDE_DIR}") -find_path (Pleora_LIBRARY_DIR PvBase${_LIB_SUFFIX}.lib +find_path (Pleora_LIBRARY_DIR NAMES libPvBase.so "PvBase${_LIB_NAME}" PATHS ${_Pleora_PATHS} - PATH_SUFFIXES Libraries) + PATH_SUFFIXES Libraries lib) -find_library (Pleora_LIBRARY_BASE PvBase${_LIB_SUFFIX} ${Pleora_LIBRARY_DIR}) -find_library (Pleora_LIBRARY_DEVICE PvDevice${_LIB_SUFFIX} ${Pleora_LIBRARY_DIR}) +message (STATUS "Found Pleora library in ${Pleora_LIBRARY_DIR}") -set (Pleora_LIBRARIES ${Pleora_LIBRARY_BASE} ${Pleora_LIBRARY_DEVICE}) +find_library (Pleora_LIBRARY_BASE "PvBase${_LIB_SUFFIX}" ${Pleora_LIBRARY_DIR}) +find_library (Pleora_LIBRARY_DEVICE "PvDevice${_LIB_SUFFIX}" ${Pleora_LIBRARY_DIR}) +find_library (Pleora_LIBRARY_PERSISTENCE "PvPersistence${_LIB_SUFFIX}" ${Pleora_LIBRARY_DIR}) +find_library (Pleora_LIBRARY_VIRTUAL_DEVICE "PvVirtualDevice${_LIB_SUFFIX}" ${Pleora_LIBRARY_DIR}) + +set (Pleora_LIBRARIES ${Pleora_LIBRARY_BASE} ${Pleora_LIBRARY_DEVICE} ${Pleora_LIBRARY_PERSISTENCE} ${Pleora_LIBRARY_VIRTUAL_DEVICE}) if (Pleora_INCLUDE_DIR) file(STRINGS "${Pleora_INCLUDE_DIR}/PvVersion.h" _pleora_VERSION_CONTENTS REGEX "#define NVERSION_STRING") diff --git a/cmake/modules/FindPylon.cmake b/cmake/modules/FindPylon.cmake index e215e96..cd94154 100644 --- a/cmake/modules/FindPylon.cmake +++ b/cmake/modules/FindPylon.cmake @@ -29,7 +29,9 @@ find_path (PYLON_INCLUDE_DIR pylonc/PylonC.h find_library (_PylonCLib NAMES PylonC_MD_VC120 pylonc PATHS "${PYLON_DIR}/Development/lib/x64" - "${PYLON_DIR}/lib64") + "${PYLON_DIR}/lib64" + "${PYLON_DIR}/lib" + ) set (PYLON_LIBRARIES ${_PylonCLib}) diff --git a/cmake/modules/FindZlib.cmake b/cmake/modules/FindZlib.cmake deleted file mode 100644 index e604b44..0000000 --- a/cmake/modules/FindZlib.cmake +++ /dev/null @@ -1,17 +0,0 @@ -FILE(TO_CMAKE_PATH "$ENV{ZLIB_DIR}" TRY1_DIR) -FILE(TO_CMAKE_PATH "${ZLIB_DIR}" TRY2_DIR) -FILE(GLOB ZLIB_DIR ${TRY1_DIR} ${TRY2_DIR}) - -FIND_PATH(ZLIB_INCLUDE_DIR zlib.h - PATHS ${ZLIB_DIR}/include /usr/local/include /usr/include - ENV INCLUDE DOC "Directory containing zlib.h include file") - -FIND_LIBRARY(ZLIB_LIBRARY NAMES z - PATHS ${ZLIB_DIR}/bin ${ZLIB_DIR}/win32/bin ${ZLIB_DIR}/lib ${ZLIB_DIR}/win32/lib /usr/local/lib /usr/lib - ENV LIB - DOC "zlib library to link with" - NO_SYSTEM_ENVIRONMENT_PATH) - -IF (ZLIB_INCLUDE_DIR AND ZLIB_LIBRARY) - SET(ZLIB_FOUND TRUE) -ENDIF (ZLIB_INCLUDE_DIR AND ZLIB_LIBRARY) diff --git a/common/get_unix_ns.h b/common/get_unix_ns.h new file mode 100644 index 0000000..6fb6cec --- /dev/null +++ b/common/get_unix_ns.h @@ -0,0 +1,64 @@ +#ifndef _GET_UNIX_NS_H_ +#define _GET_UNIX_NS_H_ + +#include + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#endif + + +#ifdef _WIN32 +typedef struct _MYFILETIME +{ + guint32 dwLowDateTime; + guint32 dwHighDateTime; +} MYFILETIME; +typedef void (*GetSystemTimeFunc) (MYFILETIME * lpSystemTimeAsFileTime); + +static guint64 +get_unix_ns () +{ + MYFILETIME ftime; + LARGE_INTEGER ltime; + static GetSystemTimeFunc time_func = NULL; + if (!time_func) { + GModule *module; + module = g_module_open ("Kernel32.dll", G_MODULE_BIND_LAZY); + if (module) { + if (!g_module_symbol (module, "GetSystemTimePreciseAsFileTime", + (gpointer *) & time_func) || time_func == NULL) { + GST_WARNING + ("Couldn't find GetSystemTimePreciseAsFileTime, falling back to GetSystemTimeAsFileTime"); + if (!g_module_symbol (module, "GetSystemTimeAsFileTime", + (gpointer *) & time_func) || time_func == NULL) { + GST_WARNING + ("Couldn't find GetSystemTimeAsFileTime, something is very wrong"); + } + } + } + } + //GetSystemTimePreciseAsFileTime(&ftime); + time_func (&ftime); + ltime.HighPart = ftime.dwHighDateTime; + ltime.LowPart = ftime.dwLowDateTime; + ltime.QuadPart -= 11644473600000 * 10000; + return ltime.QuadPart * 100; +} +#endif /* _WIN32 */ + + +#ifdef __unix__ +static guint64 +get_unix_ns () +{ + struct timespec spec; + + clock_gettime (CLOCK_REALTIME, &spec); + return (guint64) spec.tv_sec * 1000000000L + (guint64) spec.tv_nsec; +} +#endif /* __unix__ */ + + +#endif /* _GET_UNIX_NS_H_ */ diff --git a/gst/videoadjust/gstvideolevels.c b/gst/videoadjust/gstvideolevels.c index b139a1a..c538e34 100644 --- a/gst/videoadjust/gstvideolevels.c +++ b/gst/videoadjust/gstvideolevels.c @@ -61,6 +61,12 @@ enum PROP_HIGHOUT, PROP_AUTO, PROP_INTERVAL, + PROP_LOWER_SATURATION, + PROP_UPPER_SATURATION, + PROP_ROI_X, + PROP_ROI_Y, + PROP_ROI_WIDTH, + PROP_ROI_HEIGHT, PROP_LAST }; @@ -72,6 +78,12 @@ static GParamSpec *properties[PROP_LAST]; #define DEFAULT_PROP_HIGHOUT 255 #define DEFAULT_PROP_AUTO 0 #define DEFAULT_PROP_INTERVAL (GST_SECOND / 2) +#define DEFAULT_PROP_LOW_SAT 0.01 +#define DEFAULT_PROP_HIGH_SAT 0.01 +#define DEFAULT_PROP_ROI_X -1 +#define DEFAULT_PROP_ROI_Y -1 +#define DEFAULT_PROP_ROI_WIDTH 0 +#define DEFAULT_PROP_ROI_HEIGHT 0 /* the capabilities of the inputs and outputs */ static GstStaticPadTemplate gst_videolevels_src_template = @@ -224,6 +236,30 @@ gst_videolevels_class_init (GstVideoLevelsClass * klass) g_param_spec_uint64 ("interval", "Interval", "Interval of time between adjustments (in nanoseconds)", 1, G_MAXUINT64, DEFAULT_PROP_INTERVAL, G_PARAM_READWRITE)); + g_object_class_install_property (gobject_class, PROP_LOWER_SATURATION, + g_param_spec_double ("lower-saturation", "Lower saturation", + "The fraction of the histogram to saturate on the low end when auto is enabled", + 0, 0.99, DEFAULT_PROP_LOW_SAT, G_PARAM_READWRITE)); + g_object_class_install_property (gobject_class, PROP_UPPER_SATURATION, + g_param_spec_double ("upper-saturation", "Upper saturation", + "The fraction of the histogram to saturate on the upper end when auto is enabled", + 0, 0.99, DEFAULT_PROP_HIGH_SAT, G_PARAM_READWRITE)); + g_object_class_install_property (gobject_class, PROP_ROI_X, + g_param_spec_int ("roi-x", "ROI x", + "Starting column of the ROI when auto is enabled (-1 centers ROI)", + -1, G_MAXINT, DEFAULT_PROP_ROI_X, G_PARAM_READWRITE)); + g_object_class_install_property (gobject_class, PROP_ROI_Y, + g_param_spec_int ("roi-y", "ROI y", + "Starting row of the ROI when auto is enabled (-1 centers ROI)", + -1, G_MAXINT, DEFAULT_PROP_ROI_Y, G_PARAM_READWRITE)); + g_object_class_install_property (gobject_class, PROP_ROI_WIDTH, + g_param_spec_int ("roi-width", "ROI width", + "Width of the ROI when auto is enabled (0 uses 1/2 of the image width)", + 0, G_MAXINT, DEFAULT_PROP_ROI_WIDTH, G_PARAM_READWRITE)); + g_object_class_install_property (gobject_class, PROP_ROI_HEIGHT, + g_param_spec_int ("roi-height", "ROI height", + "Height of the ROI when auto is enabled (0 uses 1/2 of the image height)", + 0, G_MAXINT, DEFAULT_PROP_ROI_HEIGHT, G_PARAM_READWRITE)); gst_element_class_add_pad_template (gstelement_class, gst_static_pad_template_get (&gst_videolevels_sink_template)); @@ -305,6 +341,30 @@ gst_videolevels_set_property (GObject * object, guint prop_id, videolevels->interval = g_value_get_uint64 (value); videolevels->last_auto_timestamp = GST_CLOCK_TIME_NONE; break; + case PROP_LOWER_SATURATION: + videolevels->lower_pix_sat = g_value_get_double (value); + gst_videolevels_calculate_lut (videolevels); + break; + case PROP_UPPER_SATURATION: + videolevels->upper_pix_sat = g_value_get_double (value); + gst_videolevels_calculate_lut (videolevels); + break; + case PROP_ROI_X: + videolevels->roi_x = g_value_get_int (value); + videolevels->check_roi = TRUE; + break; + case PROP_ROI_Y: + videolevels->roi_y = g_value_get_int (value); + videolevels->check_roi = TRUE; + break; + case PROP_ROI_WIDTH: + videolevels->roi_width = g_value_get_int (value); + videolevels->check_roi = TRUE; + break; + case PROP_ROI_HEIGHT: + videolevels->roi_height = g_value_get_int (value); + videolevels->check_roi = TRUE; + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -325,7 +385,7 @@ gst_videolevels_get_property (GObject * object, guint prop_id, GValue * value, { GstVideoLevels *videolevels = GST_VIDEOLEVELS (object); - GST_DEBUG_OBJECT (videolevels, "getting property %s", pspec->name); + GST_LOG_OBJECT (videolevels, "getting property %s", pspec->name); switch (prop_id) { case PROP_LOWIN: @@ -346,6 +406,24 @@ gst_videolevels_get_property (GObject * object, guint prop_id, GValue * value, case PROP_INTERVAL: g_value_set_uint64 (value, videolevels->interval); break; + case PROP_LOWER_SATURATION: + g_value_set_double (value, videolevels->lower_pix_sat); + break; + case PROP_UPPER_SATURATION: + g_value_set_double (value, videolevels->upper_pix_sat); + break; + case PROP_ROI_X: + g_value_set_int (value, videolevels->roi_x); + break; + case PROP_ROI_Y: + g_value_set_int (value, videolevels->roi_y); + break; + case PROP_ROI_WIDTH: + g_value_set_int (value, videolevels->roi_width); + break; + case PROP_ROI_HEIGHT: + g_value_set_int (value, videolevels->roi_height); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -571,6 +649,8 @@ gst_videolevels_set_caps (GstBaseTransform * trans, GstCaps * incaps, levels->nbins = MIN (4096, 1 << levels->bpp_in); + levels->check_roi = TRUE; + res = gst_videolevels_calculate_lut (levels); return res; @@ -689,13 +769,18 @@ gst_videolevels_reset (GstVideoLevels * videolevels) videolevels->upper_input = DEFAULT_PROP_HIGHIN; videolevels->lower_output = DEFAULT_PROP_LOWOUT; videolevels->upper_output = DEFAULT_PROP_HIGHOUT; + videolevels->lower_pix_sat = DEFAULT_PROP_LOW_SAT; + videolevels->upper_pix_sat = DEFAULT_PROP_HIGH_SAT; + videolevels->roi_x = DEFAULT_PROP_ROI_X; + videolevels->roi_y = DEFAULT_PROP_ROI_Y; + videolevels->roi_width = DEFAULT_PROP_ROI_WIDTH; + videolevels->roi_height = DEFAULT_PROP_ROI_HEIGHT; videolevels->auto_adjust = DEFAULT_PROP_AUTO; videolevels->interval = DEFAULT_PROP_INTERVAL; - videolevels->last_auto_timestamp = GST_CLOCK_TIME_NONE; - videolevels->lower_pix_sat = 0.01f; - videolevels->upper_pix_sat = 0.01f; + videolevels->check_roi = TRUE; + videolevels->last_auto_timestamp = GST_CLOCK_TIME_NONE; /* if GRAY8, this will be set in set_info */ videolevels->nbins = 4096; @@ -805,23 +890,29 @@ gst_videolevels_calculate_histogram (GstVideoLevels * videolevels, GST_LOG_OBJECT (videolevels, "Calculating histogram"); if (videolevels->bpp_in > 8) { if (endianness == G_BYTE_ORDER) { - for (r = 0; r < videolevels->height; r++) { - for (c = 0; c < videolevels->width; c++) { + for (r = videolevels->roi_y; + r < videolevels->roi_y + videolevels->roi_height; r++) { + for (c = videolevels->roi_x; + c < videolevels->roi_x + videolevels->roi_width; c++) { hist[GINT_CLAMP (data[c + r * stride / 2] * factor, 0, nbins - 1)]++; } } } else { - for (r = 0; r < videolevels->height; r++) { - for (c = 0; c < videolevels->width; c++) { - hist[GINT_CLAMP (GUINT16_FROM_BE (data[c + - r * stride / 2]) * factor, 0, nbins - 1)]++; + for (r = videolevels->roi_y; + r < videolevels->roi_y + videolevels->roi_height; r++) { + for (c = videolevels->roi_x; + c < videolevels->roi_x + videolevels->roi_width; c++) { + hist[GINT_CLAMP (GUINT16_FROM_BE (data[c + r * stride / 2]) * factor, + 0, nbins - 1)]++; } } } } else { guint8 *data8 = (guint8 *) data; - for (r = 0; r < videolevels->height; r++) { - for (c = 0; c < videolevels->width; c++) { + for (r = videolevels->roi_y; + r < videolevels->roi_y + videolevels->roi_height; r++) { + for (c = videolevels->roi_x; + c < videolevels->roi_x + videolevels->roi_width; c++) { hist[GINT_CLAMP (data8[c + r * stride / 2] * factor, 0, nbins - 1)]++; } } @@ -830,6 +921,38 @@ gst_videolevels_calculate_histogram (GstVideoLevels * videolevels, return TRUE; } + +void +gst_videolevels_check_roi (GstVideoLevels * filt) +{ + GST_DEBUG_OBJECT (filt, "ROI before check is (%d, %d, %d, %d)", filt->roi_x, + filt->roi_y, filt->roi_width, filt->roi_height); + + /* adjust ROI if defaults are set */ + if (filt->roi_width <= 0) { + filt->roi_width = filt->width / 2; + } + if (filt->roi_height <= 0) { + filt->roi_height = filt->height / 2; + } + if (filt->roi_x <= -1) { + filt->roi_x = (filt->width - filt->roi_width) / 2; + } + if (filt->roi_y <= -1) { + filt->roi_y = (filt->height - filt->roi_height) / 2; + } + + /* ensure ROI is within image */ + filt->roi_x = GINT_CLAMP (filt->roi_x, 0, filt->width - 1); + filt->roi_y = GINT_CLAMP (filt->roi_y, 0, filt->height - 1); + filt->roi_width = GINT_CLAMP (filt->roi_width, 1, filt->width - filt->roi_x); + filt->roi_height = + GINT_CLAMP (filt->roi_height, 1, filt->height - filt->roi_y); + + GST_DEBUG_OBJECT (filt, "ROI after check is (%d, %d, %d, %d)", filt->roi_x, + filt->roi_y, filt->roi_width, filt->roi_height); +} + /** * gst_videolevels_auto_adjust * @videolevels: #GstVideoLevels @@ -840,48 +963,54 @@ gst_videolevels_calculate_histogram (GstVideoLevels * videolevels, * Returns: TRUE on success */ gboolean -gst_videolevels_auto_adjust (GstVideoLevels * videolevels, guint16 * data) +gst_videolevels_auto_adjust (GstVideoLevels * filt, guint16 * data) { guint npixsat; guint sum; gint i; - gint size; + gint pixel_count; gint minVal = 0; - gint maxVal = (1 << videolevels->bpp_in) - 1; - float factor = maxVal / (videolevels->nbins - 1.0f); - gst_videolevels_calculate_histogram (videolevels, data); + gint maxVal = (1 << filt->bpp_in) - 1; + float factor = maxVal / (filt->nbins - 1.0f); - size = videolevels->width * videolevels->height; + if (filt->check_roi) { + gst_videolevels_check_roi (filt); + filt->check_roi = FALSE; + } + + gst_videolevels_calculate_histogram (filt, data); + + pixel_count = filt->roi_width * filt->roi_height; /* pixels to saturate on low end */ - npixsat = (guint) (videolevels->lower_pix_sat * size); + npixsat = (guint) (filt->lower_pix_sat * pixel_count); sum = 0; - for (i = 0; i < videolevels->nbins; i++) { - sum += videolevels->histogram[i]; + for (i = 0; i < filt->nbins; i++) { + sum += filt->histogram[i]; if (sum > npixsat) { - videolevels->lower_input = (gint) CLAMP (i * factor, minVal, maxVal); + filt->lower_input = (gint) CLAMP (i * factor, minVal, maxVal); break; } } /* pixels to saturate on high end */ - npixsat = (guint) (videolevels->upper_pix_sat * size); + npixsat = (guint) (filt->upper_pix_sat * pixel_count); sum = 0; - for (i = videolevels->nbins - 1; i >= 0; i--) { - sum += videolevels->histogram[i]; + for (i = filt->nbins - 1; i >= 0; i--) { + sum += filt->histogram[i]; if (sum > npixsat) { - videolevels->upper_input = (gint) CLAMP (i * factor, minVal, maxVal); + filt->upper_input = (gint) CLAMP (i * factor, minVal, maxVal); break; } } - gst_videolevels_calculate_lut (videolevels); + gst_videolevels_calculate_lut (filt); - GST_LOG_OBJECT (videolevels, "Contrast stretch with npixsat=%d, (%d, %d)", - npixsat, videolevels->lower_input, videolevels->upper_input); + GST_LOG_OBJECT (filt, "Contrast stretch with npixsat=%d, (%d, %d)", + npixsat, filt->lower_input, filt->upper_input); - g_object_notify_by_pspec (G_OBJECT (videolevels), properties[PROP_LOWIN]); - g_object_notify_by_pspec (G_OBJECT (videolevels), properties[PROP_HIGHIN]); + g_object_notify_by_pspec (G_OBJECT (filt), properties[PROP_LOWIN]); + g_object_notify_by_pspec (G_OBJECT (filt), properties[PROP_HIGHIN]); return TRUE; } @@ -891,6 +1020,7 @@ gst_videolevels_check_passthrough (GstVideoLevels * levels) { gboolean passthrough; if (levels->bpp_in == 8 && + levels->auto_adjust == GST_VIDEOLEVELS_AUTO_OFF && levels->lower_input == levels->lower_output && levels->upper_input == levels->upper_output) { passthrough = TRUE; diff --git a/gst/videoadjust/gstvideolevels.h b/gst/videoadjust/gstvideolevels.h index 0d7719e..e0400f1 100644 --- a/gst/videoadjust/gstvideolevels.h +++ b/gst/videoadjust/gstvideolevels.h @@ -83,14 +83,19 @@ struct _GstVideoLevels gint upper_input; gint lower_output; gint upper_output; + gfloat lower_pix_sat; + gfloat upper_pix_sat; + gint roi_x; + gint roi_y; + gint roi_width; + gint roi_height; /* tables */ gpointer lookup_table; GstVideoLevelsAuto auto_adjust; guint64 interval; - gfloat lower_pix_sat; - gfloat upper_pix_sat; + gboolean check_roi; gint nbins; gint * histogram; diff --git a/sys/gentl/CMakeLists.txt b/sys/gentl/CMakeLists.txt index 4660464..4406430 100644 --- a/sys/gentl/CMakeLists.txt +++ b/sys/gentl/CMakeLists.txt @@ -23,7 +23,7 @@ target_link_libraries (${libname} ${GSTREAMER_LIBRARY} ${GSTREAMER_BASE_LIBRARY} ${GSTREAMER_VIDEO_LIBRARY} - ${GSTREAMER_INCLUDE_DIR}/../../lib/z.lib + ${ZLIB_LIBRARIES} ) if (WIN32) diff --git a/sys/gentl/gstgentlsrc.c b/sys/gentl/gstgentlsrc.c index d7c72d8..259d2dc 100644 --- a/sys/gentl/gstgentlsrc.c +++ b/sys/gentl/gstgentlsrc.c @@ -42,6 +42,7 @@ #include #include "genicampixelformat.h" +#include "get_unix_ns.h" #include "unzip.h" @@ -56,24 +57,61 @@ // "C:\\BitFlow SDK 6.20\\Bin64\\BFGTL.cti"; // "C:\\Program Files\\Basler\\pylon 5\\Runtime\\x64\\ProducerGEV.cti"; -// EVT -#define GENAPI_WIDTH 0xA000 -#define GENAPI_HEIGHT 0xA004 -#define GENAPI_PIXFMT 0xA008 -#define GENAPI_PAYLOAD_SIZE 0xD008 -#define GENAPI_ACQMODE 0xB000 -#define GENAPI_ACQSTART 0xB004 -#define GENAPI_ACQSTOP 0xB008 -#define CTI_PATH "C:\\Program Files\\EVT\\eSDK\\bin\\EmergentGenTL.cti" +static void +initialize_evt_addresses (GstGenTlProducer * producer) +{ + memset (producer, 0, sizeof (producer)); + producer->cti_path = + g_strdup ("C:\\Program Files\\EVT\\eSDK\\bin\\EmergentGenTL.cti"); + producer->acquisition_mode_value = 0; + producer->width = 0xA000; + producer->height = 0xA004; + producer->pixel_format = 0xA008; + producer->payload_size = 0xD008; + producer->acquisition_mode = 0xB000; + producer->acquisition_start = 0xB004; + producer->acquisition_stop = 0xB008; + producer->tick_frequency_low = 0x0940; + producer->tick_frequency_high = 0x093C; + producer->timestamp_control_latch = 0x944; + producer->timestamp_low = 0x094C; + producer->timestamp_high = 0x0948; +} -// Basler -//#define GENAPI_WIDTH 0x30204 -//#define GENAPI_HEIGHT 0x30224 -//#define GENAPI_PAYLOAD_SIZE 0x10088 -//#define GENAPI_ACQMODE 0x40004 -//#define GENAPI_ACQSTART 0x40024 -//#define GENAPI_ACQSTOP 0x40044 -//#define CTI_PATH "C:\\Program Files\\Basler\\pylon 6\\Runtime\\x64\\ProducerGEV.cti" +static void +initialize_basler_addresses (GstGenTlProducer * producer) +{ + memset (producer, 0, sizeof (producer)); + producer->cti_path = + g_strdup + ("C:\\Program Files\\Basler\\pylon 5\\Runtime\\x64\\ProducerGEV.cti"); + producer->acquisition_mode_value = 2; + producer->width = 0x30204; + producer->height = 0x30224; + producer->pixel_format = 0x30024; + producer->payload_size = 0x10088; + producer->acquisition_mode = 0x40004; + producer->acquisition_start = 0x40024; + producer->acquisition_stop = 0x40044; +} + +#define GST_TYPE_GENTLSRC_PRODUCER (gst_gentlsrc_producer_get_type()) +static GType +gst_gentlsrc_producer_get_type (void) +{ + static GType gentlsrc_producer_type = 0; + static const GEnumValue gentlsrc_producer[] = { + {GST_GENTLSRC_PRODUCER_BASLER, "Basler producer", "basler"}, + {GST_GENTLSRC_PRODUCER_EVT, "EVT producer", "evt"}, + {0, NULL, NULL}, + }; + + if (!gentlsrc_producer_type) { + gentlsrc_producer_type = + g_enum_register_static ("GstGenTlSrcProducer", gentlsrc_producer); + } + return gentlsrc_producer_type; +} GST_DEBUG_CATEGORY_STATIC (gst_gentlsrc_debug); #define GST_CAT_DEFAULT gst_gentlsrc_debug @@ -96,10 +134,12 @@ static gboolean gst_gentlsrc_unlock_stop (GstBaseSrc * src); static GstFlowReturn gst_gentlsrc_create (GstPushSrc * src, GstBuffer ** buf); static gchar *gst_gentlsrc_get_error_string (GstGenTlSrc * src); +static void gst_gentlsrc_cleanup_tl (GstGenTlSrc * src); enum { PROP_0, + PROP_PRODUCER, PROP_INTERFACE_INDEX, PROP_INTERFACE_ID, PROP_DEVICE_INDEX, @@ -107,9 +147,11 @@ enum PROP_STREAM_INDEX, PROP_STREAM_ID, PROP_NUM_CAPTURE_BUFFERS, - PROP_TIMEOUT + PROP_TIMEOUT, + PROP_ATTRIBUTES }; +#define DEFAULT_PROP_PRODUCER GST_GENTLSRC_PRODUCER_BASLER #define DEFAULT_PROP_INTERFACE_INDEX 0 #define DEFAULT_PROP_INTERFACE_ID "" #define DEFAULT_PROP_DEVICE_INDEX 0 @@ -118,6 +160,7 @@ enum #define DEFAULT_PROP_STREAM_ID "" #define DEFAULT_PROP_NUM_CAPTURE_BUFFERS 3 #define DEFAULT_PROP_TIMEOUT 1000 +#define DEFAULT_PROP_ATTRIBUTES "" /* pad templates */ @@ -140,6 +183,13 @@ static GstStaticPadTemplate gst_gentlsrc_src_template = goto error; \ } +#define HANDLE_GTL_WARNING(arg) \ + if (ret != GC_ERR_SUCCESS) { \ + GST_ELEMENT_WARNING (src, LIBRARY, FAILED, \ + (arg ": %s", gst_gentlsrc_get_error_string (src)), (NULL)); \ + goto error; \ + } + PGCGetInfo GTL_GCGetInfo; PGCGetLastError GTL_GCGetLastError; PGCInitLib GTL_GCInitLib; @@ -197,7 +247,7 @@ gboolean gst_gentlsrc_bind_functions (GstGenTlSrc * src) { GModule *module; - const char cti_path[] = CTI_PATH; + const char *cti_path = src->producer.cti_path; gboolean ret = TRUE; GST_DEBUG_OBJECT (src, "Trying to bind functions from '%s'", cti_path); @@ -302,6 +352,12 @@ gst_gentlsrc_class_init (GstGenTlSrcClass * klass) gstpushsrc_class->create = GST_DEBUG_FUNCPTR (gst_gentlsrc_create); /* Install GObject properties */ + g_object_class_install_property (gobject_class, PROP_PRODUCER, + g_param_spec_enum ("producer", "Producer", "GenTL producer", + GST_TYPE_GENTLSRC_PRODUCER, DEFAULT_PROP_PRODUCER, + G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | + GST_PARAM_MUTABLE_PLAYING)); + g_object_class_install_property (gobject_class, PROP_INTERFACE_INDEX, g_param_spec_uint ("interface-index", "Interface index", "Interface index number, zero-based, overridden by interface-id", @@ -348,12 +404,22 @@ gst_gentlsrc_class_init (GstGenTlSrcClass * klass) "Timeout (ms)", "Timeout in ms (0 to use default)", 0, G_MAXINT, DEFAULT_PROP_TIMEOUT, G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE)); + g_object_class_install_property (G_OBJECT_CLASS (klass), + PROP_ATTRIBUTES, g_param_spec_string ("attributes", + "Attributes", "Attributes to change, comma separated key=value pairs", + DEFAULT_PROP_ATTRIBUTES, G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE)); + klass->hTL = NULL; + g_mutex_init (&klass->tl_mutex); + klass->tl_refcount = 0; } static void gst_gentlsrc_reset (GstGenTlSrc * src) { + src->gentl_latched_ticks = 0; + src->unix_latched_time = 0; + src->error_string[0] = 0; src->last_frame_count = 0; src->total_dropped_frames = 0; @@ -374,10 +440,12 @@ gst_gentlsrc_init (GstGenTlSrc * src) gst_base_src_set_format (GST_BASE_SRC (src), GST_FORMAT_TIME); /* initialize member variables */ + src->producer_prop = DEFAULT_PROP_PRODUCER; src->interface_index = DEFAULT_PROP_INTERFACE_INDEX; src->interface_id = g_strdup (DEFAULT_PROP_INTERFACE_ID); src->num_capture_buffers = DEFAULT_PROP_NUM_CAPTURE_BUFFERS; src->timeout = DEFAULT_PROP_TIMEOUT; + src->attributes = g_strdup (DEFAULT_PROP_ATTRIBUTES); src->stop_requested = FALSE; src->caps = NULL; @@ -399,6 +467,9 @@ gst_gentlsrc_set_property (GObject * object, guint property_id, src = GST_GENTL_SRC (object); switch (property_id) { + case PROP_PRODUCER: + src->producer_prop = g_value_get_enum (value); + break; case PROP_INTERFACE_INDEX: src->interface_index = g_value_get_uint (value); break; @@ -426,6 +497,11 @@ gst_gentlsrc_set_property (GObject * object, guint property_id, case PROP_TIMEOUT: src->timeout = g_value_get_int (value); break; + case PROP_ATTRIBUTES: + if (src->attributes) + g_free (src->attributes); + src->attributes = g_strdup (g_value_get_string (value)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; @@ -442,6 +518,9 @@ gst_gentlsrc_get_property (GObject * object, guint property_id, src = GST_GENTL_SRC (object); switch (property_id) { + case PROP_PRODUCER: + g_value_set_enum (value, src->producer_prop); + break; case PROP_INTERFACE_INDEX: g_value_set_uint (value, src->interface_index); break; @@ -466,6 +545,9 @@ gst_gentlsrc_get_property (GObject * object, guint property_id, case PROP_TIMEOUT: g_value_set_int (value, src->timeout); break; + case PROP_ATTRIBUTES: + g_value_set_string (value, src->attributes); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; @@ -718,7 +800,9 @@ gst_gentlsrc_get_payload_size (GstGenTlSrc * src) guint32 val = 0; size_t datasize = 4; // TODO: use node map - ret = GTL_GCReadPort (src->hDevPort, GENAPI_PAYLOAD_SIZE, &val, &datasize); + ret = + GTL_GCReadPort (src->hDevPort, src->producer.payload_size, &val, + &datasize); HANDLE_GTL_ERROR ("Failed to get payload size"); payload_size = GUINT32_FROM_BE (val); GST_DEBUG_OBJECT (src, "Payload size defined by node map: %d", @@ -771,52 +855,182 @@ error: return FALSE; } -static gboolean -gst_gentlsrc_start (GstBaseSrc * bsrc) +static guint64 +gst_gentlsrc_get_gev_tick_frequency (GstGenTlSrc * src) { - GstGenTlSrc *src = GST_GENTL_SRC (bsrc); GC_ERROR ret; - uint32_t i, num_ifaces, num_devs; - guint32 width, height, stride; - GstVideoInfo vinfo; - GST_DEBUG_OBJECT (src, "start"); + if (!src->producer.tick_frequency_high || !src->producer.tick_frequency_low) + return 0; - /* bind functions from CTI */ - /* TODO: Enumerate CTI files in env var GENTL_GENTL64_PATH */ - if (!gst_gentlsrc_bind_functions (src)) { - GST_ELEMENT_ERROR (src, LIBRARY, INIT, - ("GenTL CTI could not be opened: %s", g_module_error ()), (NULL)); - return FALSE; - } + guint32 freq_low, freq_high; + size_t datasize = 4; + ret = GTL_GCReadPort (src->hDevPort, src->producer.tick_frequency_low, &freq_low, &datasize); // GevTimestampTickFrequencyLow + HANDLE_GTL_ERROR ("Failed to get GevTimestampTickFrequencyLow"); + ret = GTL_GCReadPort (src->hDevPort, src->producer.tick_frequency_high, &freq_high, &datasize); // GevTimestampTickFrequencyHigh + HANDLE_GTL_ERROR ("Failed to get GevTimestampTickFrequencyHigh"); - /* initialize library and print info */ - ret = GTL_GCInitLib (); - HANDLE_GTL_ERROR ("GenTL Producer library could not be initialized"); + guint64 tick_frequency = + GUINT64_FROM_BE ((guint64) freq_low << 32 | freq_high); + GST_DEBUG_OBJECT (src, "GEV Timestamp tick frequency is %llu", + tick_frequency); - gst_gentl_print_gentl_impl_info (src); + return tick_frequency; - /* open GenTL, print info, and update interface list */ - ret = GTL_TLOpen (&src->hTL); - HANDLE_GTL_ERROR ("System module failed to open"); +error: + return 0; +} - gst_gentl_print_system_info (src); +static guint64 +gst_gentlsrc_get_gev_timestamp_ticks (GstGenTlSrc * src) +{ + GC_ERROR ret; + size_t datasize = 4; + guint32 val, ts_low, ts_high; - ret = GTL_TLUpdateInterfaceList (src->hTL, NULL, src->timeout); - HANDLE_GTL_ERROR ("Failed to update interface list within timeout"); + val = GUINT32_TO_BE (2); + datasize = sizeof (val); + ret = GTL_GCWritePort (src->hDevPort, src->producer.timestamp_control_latch, &val, &datasize); // GevTimestampControlLatch + HANDLE_GTL_WARNING ("Failed to latch timestamp GevTimestampControlLatch"); - /* print info for all interfaces and open specified interface */ - ret = GTL_TLGetNumInterfaces (src->hTL, &num_ifaces); - HANDLE_GTL_ERROR ("Failed to get number of interfaces"); - if (num_ifaces > 0) { - GST_DEBUG_OBJECT (src, "Found %d GenTL interfaces", num_ifaces); - for (i = 0; i < num_ifaces; ++i) { - gst_gentl_print_interface_info (src, i); - } + ret = GTL_GCReadPort (src->hDevPort, src->producer.timestamp_low, &ts_low, &datasize); // GevTimestampValueLow + HANDLE_GTL_WARNING ("Failed to get GevTimestampValueLow"); + ret = GTL_GCReadPort (src->hDevPort, src->producer.timestamp_high, &ts_high, &datasize); // GevTimestampValueHigh + HANDLE_GTL_WARNING ("Failed to get GevTimestampValueHigh"); + guint64 ticks = GUINT64_FROM_BE ((guint64) ts_low << 32 | ts_high); + GST_LOG_OBJECT (src, "Timestamp ticks are %llu", ticks); + + return ticks; + +error: + return 0; +} + +static void +gst_gentlsrc_src_latch_timestamps (GstGenTlSrc * src) +{ + guint64 unix_ts, gev_ts; + + unix_ts = get_unix_ns (); + gev_ts = gst_gentlsrc_get_gev_timestamp_ticks (src); + + if (gev_ts != 0) { + src->unix_latched_time = unix_ts; + src->gentl_latched_ticks = gev_ts; } else { - GST_ELEMENT_ERROR (src, LIBRARY, FAILED, ("No interfaces found"), (NULL)); - goto error; + GST_WARNING_OBJECT (src, "Failed to latch GEV time, using old latch value"); } +} + +static void +gst_gentlsrc_set_attributes (GstGenTlSrc * src) +{ + gchar **pairs; + int i; + guint32 val; + size_t datasize; + GC_ERROR ret; + + if (!src->attributes || src->attributes == 0) { + return; + } + + GST_DEBUG_OBJECT (src, "Trying to set following attributes: '%s'", + src->attributes); + + pairs = g_strsplit (src->attributes, ";", 0); + + for (i = 0;; i++) { + gchar **pair; + + if (!pairs[i]) + break; + + pair = g_strsplit (pairs[i], "=", 2); + + if (!pair[0] || !pair[1]) { + GST_WARNING_OBJECT (src, "Failed to parse attribute/value: '%s'", pair); + continue; + } + + GST_DEBUG_OBJECT (src, "Setting attribute, '%s'='%s'", pair[0], pair[1]); + + val = GUINT32_TO_BE (atoi (pair[1])); + datasize = sizeof (val); + ret = + GTL_GCWritePort (src->hDevPort, strtol (pair[0], NULL, 16), &val, + &datasize); + if (ret != GC_ERR_SUCCESS) { + GST_WARNING_OBJECT (src, "Failed to set attribute: %s", + gst_gentlsrc_get_error_string (src)); + } + g_strfreev (pair); + } + g_strfreev (pairs); + + if (src->attributes) { + g_free (src->attributes); + src->attributes = NULL; + } +} + +static gboolean +gst_gentlsrc_open_tl (GstGenTlSrc * src) +{ + GstGenTlSrcClass *klass = GST_GENTL_SRC_GET_CLASS (src); + GC_ERROR ret; + uint32_t i, num_ifaces; + + /* open framegrabber if it isn't already opened */ + if (klass->tl_refcount > 0) { + GST_DEBUG_OBJECT (src, + "Framegrabber interface already opened in this process, reusing"); + src->hTL = klass->hTL; + klass->tl_refcount++; + } else { + /* initialize library and print info */ + ret = GTL_GCInitLib (); + //HANDLE_GTL_ERROR ("GenTL Producer library could not be initialized"); + + gst_gentl_print_gentl_impl_info (src); + + /* open GenTL, print info, and update interface list */ + ret = GTL_TLOpen (&src->hTL); + HANDLE_GTL_ERROR ("System module failed to open"); + + gst_gentl_print_system_info (src); + + ret = GTL_TLUpdateInterfaceList (src->hTL, NULL, src->timeout); + HANDLE_GTL_ERROR ("Failed to update interface list within timeout"); + + /* print info for all interfaces and open specified interface */ + ret = GTL_TLGetNumInterfaces (src->hTL, &num_ifaces); + HANDLE_GTL_ERROR ("Failed to get number of interfaces"); + if (num_ifaces > 0) { + GST_DEBUG_OBJECT (src, "Found %d GenTL interfaces", num_ifaces); + for (i = 0; i < num_ifaces; ++i) { + gst_gentl_print_interface_info (src, i); + } + } else { + GST_ELEMENT_ERROR (src, LIBRARY, FAILED, ("No interfaces found"), (NULL)); + goto error; + } + + klass->hTL = src->hTL; + klass->tl_refcount++; + } + + return TRUE; + +error: + return FALSE; +} + +static gboolean +gst_gentlsrc_open_interface (GstGenTlSrc * src) +{ + GstGenTlSrcClass *klass = GST_GENTL_SRC_GET_CLASS (src); + GC_ERROR ret; if (!src->interface_id || src->interface_id[0] == 0) { size_t id_size; @@ -839,6 +1053,54 @@ gst_gentlsrc_start (GstBaseSrc * bsrc) ret = GTL_TLOpenInterface (src->hTL, src->interface_id, &src->hIF); HANDLE_GTL_ERROR ("Interface module failed to open"); + return TRUE; + +error: + return FALSE; +} + +static gboolean +gst_gentlsrc_start (GstBaseSrc * bsrc) +{ + GstGenTlSrc *src = GST_GENTL_SRC (bsrc); + GstGenTlSrcClass *klass = GST_GENTL_SRC_GET_CLASS (src); + GC_ERROR ret; + uint32_t i, num_devs; + guint32 width, height, stride; + GstVideoInfo vinfo; + + GST_DEBUG_OBJECT (src, "start"); + + if (src->producer_prop == GST_GENTLSRC_PRODUCER_BASLER) { + initialize_basler_addresses (&src->producer); + } else if (src->producer_prop == GST_GENTLSRC_PRODUCER_EVT) { + initialize_evt_addresses (&src->producer); + } else { + g_assert_not_reached (); + } + + /* bind functions from CTI */ + /* TODO: Enumerate CTI files in env var GENTL_GENTL64_PATH */ + if (!gst_gentlsrc_bind_functions (src)) { + GST_ELEMENT_ERROR (src, LIBRARY, INIT, + ("GenTL CTI could not be opened: %s", g_module_error ()), (NULL)); + return FALSE; + } + + g_mutex_lock (&klass->tl_mutex); + + if (!gst_gentlsrc_open_tl (src)) { + g_mutex_unlock (&klass->tl_mutex); + goto error; + } + + if (!gst_gentlsrc_open_interface (src)) { + g_mutex_unlock (&klass->tl_mutex); + goto error; + } + + g_mutex_unlock (&klass->tl_mutex); + ret = GTL_IFUpdateDeviceList (src->hIF, NULL, src->timeout); HANDLE_GTL_ERROR ("Failed to update device list within timeout"); @@ -927,7 +1189,7 @@ gst_gentlsrc_start (GstBaseSrc * bsrc) GST_ELEMENT_ERROR (src, RESOURCE, TOO_LAZY, ("file url not supported yet"), (NULL)); goto error; - } else if (g_str_has_prefix (url, "local")) { + } else if (g_ascii_strncasecmp (url, "local", 5) == 0) { GError *err = NULL; GMatchInfo *matchInfo; GRegex *regex; @@ -938,7 +1200,7 @@ gst_gentlsrc_start (GstBaseSrc * bsrc) regex = g_regex_new - ("local:(?:///)?(?[^;]+);(?
[^;]+);(?[^?]+)(?:[?]SchemaVersion=([^&]+))?", + ("[lL]ocal:(?:///)?(?[^;]+);(?
[^;]+);(?[^?]+)(?:[?]SchemaVersion=([^&]+))?", (GRegexCompileFlags) 0, (GRegexMatchFlags) 0, &err); if (!regex) { goto error; @@ -967,6 +1229,7 @@ gst_gentlsrc_start (GstBaseSrc * bsrc) gchar *xml; zipfilepath = g_build_filename (g_get_tmp_dir (), filename, NULL); + GST_DEBUG_OBJECT (src, "Writing XML ZIP file to %s", zipfilepath); if (!g_file_set_contents (zipfilepath, buf, len, &err)) { GST_ELEMENT_ERROR (src, RESOURCE, TOO_LAZY, ("Failed to write zipped XML to %s", zipfilepath), (NULL)); @@ -1012,6 +1275,7 @@ gst_gentlsrc_start (GstBaseSrc * bsrc) g_free (zipfilepath); zipfilepath = g_build_filename (g_get_tmp_dir (), xmlfilename, NULL); + GST_DEBUG_OBJECT (src, "Writing XML file to %s", zipfilepath); g_file_set_contents (zipfilepath, xml, fileinfo.uncompressed_size, &err); g_free (zipfilepath); @@ -1044,24 +1308,40 @@ gst_gentlsrc_start (GstBaseSrc * bsrc) } } + src->tick_frequency = gst_gentlsrc_get_gev_tick_frequency (src); + + gst_gentlsrc_set_attributes (src); + { // TODO: use GenTl node map for this guint32 val = 0; size_t datasize = 4; - ret = GTL_GCReadPort (src->hDevPort, GENAPI_WIDTH, &val, &datasize); + ret = GTL_GCReadPort (src->hDevPort, src->producer.width, &val, &datasize); HANDLE_GTL_ERROR ("Failed to get width"); width = GUINT32_FROM_BE (val); - ret = GTL_GCReadPort (src->hDevPort, GENAPI_HEIGHT, &val, &datasize); + ret = GTL_GCReadPort (src->hDevPort, src->producer.height, &val, &datasize); HANDLE_GTL_ERROR ("Failed to get height"); height = GUINT32_FROM_BE (val); GST_DEBUG_OBJECT (src, "Width and height %dx%d", width, height); - ret = GTL_GCReadPort (src->hDevPort, GENAPI_PIXFMT, &val, &datasize); + ret = + GTL_GCReadPort (src->hDevPort, src->producer.pixel_format, &val, + &datasize); HANDLE_GTL_ERROR ("Failed to get height"); const char *genicam_pixfmt; guint32 pixfmt_enum = GUINT32_FROM_BE (val); switch (pixfmt_enum) { - + case 0x1: // Basler Ace + case 0x01080001: + genicam_pixfmt = "Mono8"; + break; + case 0x5: // Basler Ace + case 0x01100005: + genicam_pixfmt = "Mono12"; + break; + case 0x1100010: // Basler Ace + genicam_pixfmt = "BayerGR12"; + break; case 0x01080009: genicam_pixfmt = "BayerRG8"; break; @@ -1132,15 +1412,19 @@ gst_gentlsrc_start (GstBaseSrc * bsrc) /* set AcquisitionMode to Continuous */ // TODO: "Continuous" value can have different integer values, we need // to look it up in the node map (EVT is 0, Basler is 2) - val = GUINT32_TO_BE (0); + val = GUINT32_TO_BE (src->producer.acquisition_mode_value); datasize = sizeof (val); - ret = GTL_GCWritePort (src->hDevPort, GENAPI_ACQMODE, &val, &datasize); + ret = + GTL_GCWritePort (src->hDevPort, src->producer.acquisition_mode, &val, + &datasize); HANDLE_GTL_ERROR ("Failed to start device acquisition"); /* send AcquisitionStart command */ val = GUINT32_TO_BE (1); datasize = sizeof (val); - ret = GTL_GCWritePort (src->hDevPort, GENAPI_ACQSTART, &val, &datasize); + ret = + GTL_GCWritePort (src->hDevPort, src->producer.acquisition_start, &val, + &datasize); HANDLE_GTL_ERROR ("Failed to start device acquisition"); } @@ -1169,16 +1453,33 @@ error: src->hIF = NULL; } - if (src->hTL) { - GTL_TLClose (src->hTL); - src->hTL = NULL; - } - - GTL_GCCloseLib (); + gst_gentlsrc_cleanup_tl (src); return FALSE; } +static void +gst_gentlsrc_cleanup_tl (GstGenTlSrc * src) +{ + GstGenTlSrcClass *klass = GST_GENTL_SRC_GET_CLASS (src); + if (src->hTL) { + g_mutex_lock (&klass->tl_mutex); + GST_DEBUG_OBJECT (src, "Framegrabber open with refcount=%d", + klass->tl_refcount); + klass->tl_refcount--; + if (klass->tl_refcount == 0) { + GST_DEBUG_OBJECT (src, "Framegrabber ref dropped to 0, closing"); + GTL_TLClose (src->hTL); + src->hTL = NULL; + } + g_mutex_unlock (&klass->tl_mutex); + src->hTL = NULL; + + // don't close library, otherwise we can't reopen in the same process + //GTL_GCCloseLib (); + } +} + static gboolean gst_gentlsrc_stop (GstBaseSrc * bsrc) { @@ -1191,7 +1492,8 @@ gst_gentlsrc_stop (GstBaseSrc * bsrc) guint32 val = GUINT32_TO_BE (1); gsize datasize = sizeof (val); GC_ERROR ret = - GTL_GCWritePort (src->hDevPort, GENAPI_ACQSTOP, &val, &datasize); + GTL_GCWritePort (src->hDevPort, src->producer.acquisition_stop, &val, + &datasize); GTL_DSStopAcquisition (src->hDS, ACQ_STOP_FLAGS_DEFAULT); GTL_DSFlushQueue (src->hDS, ACQ_QUEUE_INPUT_TO_OUTPUT); @@ -1210,12 +1512,7 @@ gst_gentlsrc_stop (GstBaseSrc * bsrc) src->hIF = NULL; } - if (src->hTL) { - GTL_TLClose (src->hTL); - src->hTL = NULL; - } - - GTL_GCCloseLib (); + gst_gentlsrc_cleanup_tl (src); GST_DEBUG_OBJECT (src, "Closed data stream, device, interface, and library"); @@ -1298,6 +1595,8 @@ gst_gentlsrc_unlock_stop (GstBaseSrc * bsrc) return TRUE; } +static GstStaticCaps unix_reference = GST_STATIC_CAPS ("timestamp/x-unix"); + static GstBuffer * gst_gentlsrc_get_buffer (GstGenTlSrc * src) { @@ -1311,18 +1610,43 @@ gst_gentlsrc_get_buffer (GstGenTlSrc * src) bool8_t buffer_is_incomplete, is_acquiring; guint8 *data_ptr; GstMapInfo minfo; + GstClockTime unix_ts; + uint64_t buf_timestamp_ticks; - datasize = sizeof (new_buffer_data); - ret = - GTL_EventGetData (src->hNewBufferEvent, &new_buffer_data, &datasize, - src->timeout); - HANDLE_GTL_ERROR ("Failed to get New Buffer event within timeout period"); - datasize = sizeof (payload_type); + /* sometimes we get non-image payloads, try several times for an image */ + for (int i = 0; i < 5; ++i) { + datasize = sizeof (new_buffer_data); + ret = + GTL_EventGetData (src->hNewBufferEvent, &new_buffer_data, &datasize, + src->timeout); + HANDLE_GTL_ERROR ("Failed to get New Buffer event within timeout period"); + + datasize = sizeof (payload_type); + ret = + GTL_DSGetBufferInfo (src->hDS, new_buffer_data.BufferHandle, + BUFFER_INFO_PAYLOADTYPE, &datatype, &payload_type, &datasize); + HANDLE_GTL_ERROR ("Failed to get payload type"); + if (payload_type != PAYLOAD_TYPE_IMAGE) { + GST_WARNING_OBJECT (src, "Non-image payload type, trying again"); + continue; + } else { + break; + } + } + + if (payload_type != PAYLOAD_TYPE_IMAGE) { + GST_ELEMENT_ERROR (src, STREAM, TOO_LAZY, + ("Unsupported payload type: %d", payload_type), (NULL)); + goto error; + } + + datasize = sizeof (buf_timestamp_ticks); ret = GTL_DSGetBufferInfo (src->hDS, new_buffer_data.BufferHandle, - BUFFER_INFO_PAYLOADTYPE, &datatype, &payload_type, &datasize); - HANDLE_GTL_ERROR ("Failed to get payload type"); + BUFFER_INFO_TIMESTAMP, &datatype, &buf_timestamp_ticks, &datasize); + HANDLE_GTL_ERROR ("Failed to get buffer timestamp"); + GST_LOG_OBJECT (src, "Buffer GentTL timestamp: %llu", buf_timestamp_ticks); datasize = sizeof (frame_id); ret = @@ -1335,6 +1659,9 @@ gst_gentlsrc_get_buffer (GstGenTlSrc * src) GTL_DSGetBufferInfo (src->hDS, new_buffer_data.BufferHandle, BUFFER_INFO_IS_INCOMPLETE, &datatype, &buffer_is_incomplete, &datasize); HANDLE_GTL_ERROR ("Failed to get complete flag"); + if (buffer_is_incomplete) { + GST_WARNING_OBJECT (src, "Buffer is incomplete"); + } datasize = sizeof (buffer_size); ret = @@ -1348,11 +1675,6 @@ gst_gentlsrc_get_buffer (GstGenTlSrc * src) BUFFER_INFO_BASE, &datatype, &data_ptr, &datasize); HANDLE_GTL_ERROR ("Failed to get buffer pointer"); - if (payload_type != PAYLOAD_TYPE_IMAGE) { - GST_ELEMENT_ERROR (src, STREAM, TOO_LAZY, - ("Unsupported payload type: %d", payload_type), (NULL)); - goto error; - } // TODO: what if strides aren't same? buf = gst_buffer_new_allocate (NULL, buffer_size, NULL); @@ -1361,7 +1683,7 @@ gst_gentlsrc_get_buffer (GstGenTlSrc * src) ("Failed to allocate buffer"), (NULL)); goto error; } - + // TODO: try to eliminate this memcpy by using gst_buffer_new_wrapped_full gst_buffer_map (buf, &minfo, GST_MAP_WRITE); orc_memcpy (minfo.data, (void *) data_ptr, minfo.size); gst_buffer_unmap (buf, &minfo); @@ -1369,6 +1691,26 @@ gst_gentlsrc_get_buffer (GstGenTlSrc * src) GTL_DSQueueBuffer (src->hDS, new_buffer_data.BufferHandle); HANDLE_GTL_ERROR ("Failed to queue buffer"); + GST_BUFFER_OFFSET (buf) = frame_id; + + if (src->tick_frequency) { + gint64 nanoseconds_after_latch; + gint64 ticks_after_latch; + + /* resync system clock and buffer clock periodically */ + if (GST_CLOCK_DIFF (src->unix_latched_time, get_unix_ns ()) > GST_SECOND) { + gst_gentlsrc_src_latch_timestamps (src); + } + + ticks_after_latch = buf_timestamp_ticks - src->gentl_latched_ticks; + nanoseconds_after_latch = (gint64) + (ticks_after_latch * ((double) GST_SECOND / src->tick_frequency)); + unix_ts = src->unix_latched_time + nanoseconds_after_latch; + GST_LOG_OBJECT (src, "Adding Unix timestamp: %llu", unix_ts); + gst_buffer_add_reference_timestamp_meta (buf, + gst_static_caps_get (&unix_reference), unix_ts, GST_CLOCK_TIME_NONE); + } + return buf; error: @@ -1388,6 +1730,8 @@ gst_gentlsrc_create (GstPushSrc * psrc, GstBuffer ** buf) GST_LOG_OBJECT (src, "create"); + gst_gentlsrc_set_attributes (src); + *buf = gst_gentlsrc_get_buffer (src); if (!*buf) { return GST_FLOW_ERROR; @@ -1426,7 +1770,6 @@ gst_gentlsrc_create (GstPushSrc * psrc, GstBuffer ** buf) GST_BUFFER_TIMESTAMP (*buf) = GST_CLOCK_DIFF (gst_element_get_base_time (GST_ELEMENT (src)), clock_time); - //GST_BUFFER_OFFSET (*buf) = circ_handle.FrameCount - 1; if (src->stop_requested) { if (*buf != NULL) { diff --git a/sys/gentl/gstgentlsrc.h b/sys/gentl/gstgentlsrc.h index a6f6f0e..9090c31 100644 --- a/sys/gentl/gstgentlsrc.h +++ b/sys/gentl/gstgentlsrc.h @@ -34,10 +34,46 @@ G_BEGIN_DECLS #define GST_GENTL_SRC_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_GENTL_SRC,GstGenTlSrcClass)) #define GST_IS_GENTL_SRC(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_GENTL_SRC)) #define GST_IS_GENTL_SRC_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_GENTL_SRC)) +#define GST_GENTL_SRC_GET_CLASS(klass) \ + (G_TYPE_INSTANCE_GET_CLASS ((klass), GST_TYPE_GENTL_SRC, GstGenTlSrcClass)) typedef struct _GstGenTlSrc GstGenTlSrc; typedef struct _GstGenTlSrcClass GstGenTlSrcClass; +typedef struct _GstGenTlProducer GstGenTlProducer; +struct _GstGenTlProducer +{ + gchar* cti_path; + guint32 acquisition_mode_value; + + guint64 width; + guint64 height; + guint64 pixel_format; + guint64 payload_size; + guint64 acquisition_mode; + guint64 acquisition_start; + guint64 acquisition_stop; + guint64 tick_frequency_low; + guint64 tick_frequency_high; + guint64 timestamp_control_latch; + guint64 timestamp_low; + guint64 timestamp_high; +}; + + +/** +* GstGenTlSrcProducer: +* @GST_GENTLSRC_PRODUCER_BASLER: Basler producer +* @GST_GENTLSRC_PRODUCER_EVT: EVT producer +* +* Producer to use. +*/ +typedef enum { + GST_GENTLSRC_PRODUCER_BASLER, + GST_GENTLSRC_PRODUCER_EVT, +} GstGenTlSrcProducer; + + struct _GstGenTlSrc { GstPushSrc base_gentlsrc; @@ -50,8 +86,10 @@ struct _GstGenTlSrc PORT_HANDLE hDevPort; EVENT_HANDLE hNewBufferEvent; char error_string[MAX_ERROR_STRING_LEN]; + GstGenTlProducer producer; /* properties */ + GstGenTlSrcProducer producer_prop; guint interface_index; gchar *interface_id; guint device_index; @@ -60,11 +98,16 @@ struct _GstGenTlSrc gchar *stream_id; guint num_capture_buffers; gint timeout; + gchar* attributes; GstClockTime acq_start_time; guint32 last_frame_count; guint32 total_dropped_frames; + guint64 tick_frequency; + guint64 unix_latched_time; + guint64 gentl_latched_ticks; + GstCaps *caps; gint height; gint gst_stride; @@ -75,6 +118,9 @@ struct _GstGenTlSrc struct _GstGenTlSrcClass { GstPushSrcClass base_gentlsrc_class; + TL_HANDLE hTL; + GMutex tl_mutex; + guint tl_refcount; }; GType gst_gentlsrc_get_type (void); diff --git a/sys/kaya/gstkayasrc.c b/sys/kaya/gstkayasrc.c index 140f438..94fa043 100644 --- a/sys/kaya/gstkayasrc.c +++ b/sys/kaya/gstkayasrc.c @@ -799,11 +799,11 @@ gst_kayasrc_stream_buffer_callback (STREAM_BUFFER_HANDLE buffer_handle, GST_BUFFER_OFFSET (buf) = src->frame_count; src->frame_count++; - if (src->kaya_base == GST_CLOCK_TIME_NONE) { - /* assume delay between these two calls is negligible */ - src->kaya_base = KYFG_GetGrabberValueInt (src->cam_handle, "Timestamp"); - src->unix_base = g_get_real_time () * 1000; - } + //if (src->kaya_base == GST_CLOCK_TIME_NONE) { + // assume delay between these two calls is negligible + src->kaya_base = KYFG_GetGrabberValueInt (src->cam_handle, "Timestamp"); + src->unix_base = g_get_real_time () * 1000; + //} #if GST_CHECK_VERSION(1,14,0) { GstClockTime unix_ts = src->unix_base + (timestamp - src->kaya_base); diff --git a/sys/pleora/CMakeLists.txt b/sys/pleora/CMakeLists.txt index cafa736..da65b83 100644 --- a/sys/pleora/CMakeLists.txt +++ b/sys/pleora/CMakeLists.txt @@ -2,6 +2,10 @@ if (ENABLE_KLV) add_definitions(-DGST_PLUGINS_VISION_ENABLE_KLV) endif () +if (UNIX) + add_definitions(-D_UNIX_) +endif () + add_definitions(-D_XKEYCHECK_H) set (SOURCES @@ -42,6 +46,7 @@ set (LIBRARIES ${GSTREAMER_LIBRARY} ${GSTREAMER_BASE_LIBRARY} ${GSTREAMER_VIDEO_LIBRARY} + ${Pleora_LIBRARIES} ) if (ENABLE_KLV) diff --git a/sys/pleora/gstpleorasink.cpp b/sys/pleora/gstpleorasink.cpp index db01fc2..027daf3 100644 --- a/sys/pleora/gstpleorasink.cpp +++ b/sys/pleora/gstpleorasink.cpp @@ -255,9 +255,6 @@ gst_pleorasink_init (GstPleoraSink * sink) sink->source = new GstStreamingChannelSource (); sink->source->SetSink (sink); sink->device = new PvSoftDeviceGEV (); - - g_mutex_init (&sink->mutex); - g_cond_init (&sink->cond); } void @@ -396,9 +393,6 @@ gst_pleorasink_dispose (GObject * object) sink->source = NULL; } - g_mutex_clear (&sink->mutex); - g_cond_clear (&sink->cond); - g_free (sink->address); G_OBJECT_CLASS (gst_pleorasink_parent_class)->dispose (object); @@ -695,7 +689,6 @@ GstFlowReturn gst_pleorasink_render (GstBaseSink * basesink, GstBuffer * buffer) { GstPleoraSink *sink = GST_PLEORASINK (basesink); - GST_LOG_OBJECT (sink, "Rendering buffer"); if (sink->stop_requested) { GST_DEBUG_OBJECT (sink, "stop requested, flushing"); @@ -713,10 +706,7 @@ gst_pleorasink_unlock (GstBaseSink * basesink) { GstPleoraSink *sink = GST_PLEORASINK (basesink); - g_mutex_lock (&sink->mutex); sink->stop_requested = TRUE; - g_cond_signal (&sink->cond); - g_mutex_unlock (&sink->mutex); return TRUE; } diff --git a/sys/pleora/gstpleorasink.h b/sys/pleora/gstpleorasink.h index 9ac40b0..dd5891d 100644 --- a/sys/pleora/gstpleorasink.h +++ b/sys/pleora/gstpleorasink.h @@ -62,8 +62,6 @@ struct _GstPleoraSink gboolean camera_connected; GstVideoInfo vinfo; - GMutex mutex; - GCond cond; gboolean acquisition_started; gboolean stop_requested; diff --git a/sys/pleora/streamingchannelsource.cpp b/sys/pleora/streamingchannelsource.cpp index 9c4e626..23e7287 100644 --- a/sys/pleora/streamingchannelsource.cpp +++ b/sys/pleora/streamingchannelsource.cpp @@ -29,10 +29,24 @@ GST_DEBUG_CATEGORY_EXTERN (pleorasink_debug); #define KLV_CHUNKID 0xFEDC GstStreamingChannelSource::GstStreamingChannelSource () -: mAcquisitionBuffer (NULL), mBufferCount (0), mBufferValid (FALSE), - mChunkModeActive(TRUE), mChunkKlvEnabled(TRUE), mKlvChunkSize(0) +: mBufferCount (0), + mChunkModeActive(TRUE), mChunkKlvEnabled(TRUE), mKlvChunkSize(0), + mStreamingStarted(false) { + mInputQueue = g_async_queue_new (); + mOutputQueue = g_async_queue_new (); +} +void GstStreamingChannelSource::OnStreamingStart() +{ + GST_DEBUG_OBJECT(mSink, "Controller has requested streaming start"); + mStreamingStarted = true; +} + +void GstStreamingChannelSource::OnStreamingStop() +{ + GST_DEBUG_OBJECT(mSink, "Controller has requested streaming stop"); + mStreamingStarted = false; } void @@ -128,8 +142,12 @@ gboolean GstStreamingChannelSource::GetKlvEnabled() PvBuffer * GstStreamingChannelSource::AllocBuffer () { if (mBufferCount < mSink->num_internal_buffers) { + GST_LOG_OBJECT(mSink, "Allocating buffer #%d", mBufferCount); + PvBuffer *buf = new PvBuffer; + buf->SetID(mBufferCount); mBufferCount++; - return new PvBuffer; + + return buf; } return NULL; } @@ -142,39 +160,21 @@ void GstStreamingChannelSource::FreeBuffer (PvBuffer * aBuffer) PvResult GstStreamingChannelSource::QueueBuffer (PvBuffer * aBuffer) { - g_mutex_lock (&mSink->mutex); - if (mAcquisitionBuffer == NULL) { - // No buffer queued, accept it - mAcquisitionBuffer = aBuffer; - mBufferValid = FALSE; - g_mutex_unlock (&mSink->mutex); - return PvResult::Code::OK; - } - g_mutex_unlock (&mSink->mutex); - return PvResult::Code::BUSY; + GST_LOG_OBJECT(mSink, "Pushing buffer #%d to input queue", aBuffer->GetID()); + g_async_queue_push(mInputQueue, aBuffer); + return PvResult::Code::OK; } -PvResult GstStreamingChannelSource::RetrieveBuffer (PvBuffer ** aBuffer) +PvResult GstStreamingChannelSource::RetrieveBuffer(PvBuffer** aBuffer) { - gint64 end_time; - - g_mutex_lock (&mSink->mutex); - // WAIT for buffer - end_time = g_get_monotonic_time () + 50 * G_TIME_SPAN_MILLISECOND; - while ((mAcquisitionBuffer == NULL || !mBufferValid) - && !mSink->stop_requested) { - if (!g_cond_wait_until (&mSink->cond, &mSink->mutex, end_time)) { - // No buffer queued for acquisition - g_mutex_unlock (&mSink->mutex); - return PvResult::Code::NO_AVAILABLE_DATA; - } + guint64 timeout_ms = 50; + *aBuffer = (PvBuffer*)(g_async_queue_timeout_pop(mOutputQueue, timeout_ms * 1000)); + if (!*aBuffer) { + GST_WARNING_OBJECT(mSink, "No buffers available in output queue after %llu ms, possibly slow video framerate", timeout_ms); + return PvResult::Code::NO_AVAILABLE_DATA; } - // Remove buffer from 1-deep pipeline - *aBuffer = mAcquisitionBuffer; - mAcquisitionBuffer = NULL; - mBufferValid = FALSE; - g_mutex_unlock (&mSink->mutex); + GST_LOG_OBJECT (mSink, "Returning buffer #%llu from output queue to GEV streaming thread", (*aBuffer)->GetID()); return PvResult::Code::OK; } @@ -237,22 +237,21 @@ GstStreamingChannelSource::SetBuffer (GstBuffer * buf) { GByteArray * klv_byte_array = NULL; - GST_LOG_OBJECT (mSink, "SetBuffer"); + PvBuffer* pvBuffer; - g_mutex_lock (&mSink->mutex); - - if (mAcquisitionBuffer == NULL) { - GST_WARNING_OBJECT (mSink, "No PvBuffer available to fill, dropping frame"); - g_mutex_unlock (&mSink->mutex); + guint64 timeout_ms = 50; + pvBuffer = (PvBuffer*)(g_async_queue_timeout_pop (mInputQueue, timeout_ms * 1000)); + if (!pvBuffer) { + if (mStreamingStarted) { + GST_WARNING_OBJECT(mSink, "No free buffers, dropping frame. No consumers connected, or insufficient network bandwidth. Try increasing num-internal-buffers and/or packet-size."); + } + else { + GST_LOG_OBJECT(mSink, "Dropping frame as no controller has requested streaming to start"); + } return; } - if (mBufferValid) { - GST_WARNING_OBJECT (mSink, - "Buffer already filled, dropping incoming frame"); - g_mutex_unlock (&mSink->mutex); - return; - } + GST_LOG_OBJECT(mSink, "Got buffer #%llu from input queue to fill with video data", pvBuffer->GetID()); if (mChunkKlvEnabled) { klv_byte_array = GetKlvByteArray (buf); @@ -263,31 +262,31 @@ GstStreamingChannelSource::SetBuffer (GstBuffer * buf) } } - ResizeBufferIfNeeded (mAcquisitionBuffer); + ResizeBufferIfNeeded (pvBuffer); /* TODO: avoid memcpy (when strides align) by attaching to PvBuffer */ GstMapInfo minfo; gst_buffer_map (buf, &minfo, GST_MAP_READ); - guint8 *dst = mAcquisitionBuffer->GetDataPointer (); + guint8 *dst = pvBuffer->GetDataPointer (); if (!dst) { GST_ERROR_OBJECT (mSink, "Have buffer to fill, but data pointer is invalid"); - g_mutex_unlock (&mSink->mutex); + //g_mutex_unlock (&mSink->mutex); return; } - g_assert (mAcquisitionBuffer->GetSize () >= minfo.size); + g_assert (pvBuffer->GetSize () >= minfo.size); /* TODO: fix stride if needed */ memcpy (dst, minfo.data, minfo.size); gst_buffer_unmap (buf, &minfo); - mAcquisitionBuffer->ResetChunks(); - mAcquisitionBuffer->SetChunkLayoutID(CHUNKLAYOUTID); + pvBuffer->ResetChunks(); + pvBuffer->SetChunkLayoutID(CHUNKLAYOUTID); if (mChunkKlvEnabled && klv_byte_array && klv_byte_array->len > 0) { PvResult pvRes; - pvRes = mAcquisitionBuffer->AddChunk (KLV_CHUNKID, (uint8_t*)klv_byte_array->data, klv_byte_array->len); + pvRes = pvBuffer->AddChunk (KLV_CHUNKID, (uint8_t*)klv_byte_array->data, klv_byte_array->len); if (pvRes.IsOK ()) { GST_LOG_OBJECT (mSink, "Added KLV as chunk data (len=%d)", klv_byte_array->len); } else { @@ -301,10 +300,8 @@ GstStreamingChannelSource::SetBuffer (GstBuffer * buf) g_byte_array_unref (klv_byte_array); } - mBufferValid = TRUE; - g_cond_signal (&mSink->cond); - - g_mutex_unlock (&mSink->mutex); + GST_LOG_OBJECT(mSink, "Pushing buffer #%d to output queue", pvBuffer->GetID()); + g_async_queue_push(mOutputQueue, pvBuffer); } GByteArray * GstStreamingChannelSource::GetKlvByteArray (GstBuffer * buf) diff --git a/sys/pleora/streamingchannelsource.h b/sys/pleora/streamingchannelsource.h index 1da196f..dc02cd3 100644 --- a/sys/pleora/streamingchannelsource.h +++ b/sys/pleora/streamingchannelsource.h @@ -26,6 +26,9 @@ class GstStreamingChannelSource:public PvStreamingChannelSourceDefault public: GstStreamingChannelSource (); + void OnStreamingStart(); + void OnStreamingStop(); + void SetSink (GstPleoraSink * sink); void SetCaps (GstCaps * caps); void ResizeBufferIfNeeded (PvBuffer * aBuffer); @@ -55,8 +58,8 @@ public: private: GstPleoraSink * mSink; - PvBuffer *mAcquisitionBuffer; - gboolean mBufferValid; + GAsyncQueue* mInputQueue; + GAsyncQueue* mOutputQueue; gint mBufferCount; gint mWidth; @@ -67,4 +70,6 @@ private: bool mChunkKlvEnabled; gint mKlvChunkSize; + + bool mStreamingStarted; }; \ No newline at end of file