diff --git a/CMakeLists.txt b/CMakeLists.txt index 21f033e..eb9d2c9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -108,6 +108,9 @@ macro_log_feature(Pleora_FOUND "Pleora eBUS" "Required to build Pleora eBUS sour find_package(Pylon) macro_log_feature(PYLON_FOUND "Basler Pylon" "Required to build Basler Pylon source element" "http://www.baslerweb.com/" FALSE) +find_package(QCam) +macro_log_feature(QCAM_FOUND "QImaging QCam" "Required to build QImaging QCam source element" "https://www.photometrics.com/qimaging" FALSE) + find_package(Sapera) macro_log_feature(SAPERA_FOUND "Teledyne DALSA Sapera" "Required to build Teledyne DALSA Sapera source element" "http://www.teledynedalsa.com/" FALSE) diff --git a/cmake/modules/FindQCam.cmake b/cmake/modules/FindQCam.cmake new file mode 100644 index 0000000..e0af63d --- /dev/null +++ b/cmake/modules/FindQCam.cmake @@ -0,0 +1,34 @@ +# - Try to find QCam SDK +# Once done this will define +# +# QCAM_FOUND - system has QCam SDK +# QCAM_INCLUDE_DIR - the QCam SDK include directory +# QCAM_LIBRARIES - the libraries needed to use QCam SDK + +# Copyright (c) 2006, Tim Beaulen +# Copyright (c) 2021 outside US, 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 QCAM_DIR) + set (QCAM_DIR "C:/Program Files/QImaging/SDK" CACHE PATH "Directory containing QCam SDK includes and libraries") +endif () + +if (CMAKE_SIZEOF_VOID_P MATCHES "8") + set(_LIB_NAME "QCamDriverx64") +else () + set(_LIB_NAME "QCamDriver") +endif () + +find_path (QCAM_INCLUDE_DIR QCamApi.h + PATHS + "${QCAM_DIR}/Headers" + DOC "Directory containing QCam API include files") + +find_library (QCAM_LIBRARIES NAMES ${_LIB_NAME} + PATHS + "${QCAM_DIR}/libs/AMD64" "${QCAM_DIR}/libs/i386") + +include (FindPackageHandleStandardArgs) +find_package_handle_standard_args (QCAM DEFAULT_MSG QCAM_INCLUDE_DIR QCAM_LIBRARIES) \ No newline at end of file diff --git a/sys/CMakeLists.txt b/sys/CMakeLists.txt index 9207aa0..53e290e 100644 --- a/sys/CMakeLists.txt +++ b/sys/CMakeLists.txt @@ -62,6 +62,10 @@ if (PYLON_FOUND) add_subdirectory(pylon) endif (PYLON_FOUND) +if (QCAM_FOUND) + add_subdirectory(qcam) +endif (QCAM_FOUND) + if (SAPERA_FOUND) add_subdirectory(sapera) endif (SAPERA_FOUND) diff --git a/sys/qcam/CMakeLists.txt b/sys/qcam/CMakeLists.txt new file mode 100644 index 0000000..0694ae7 --- /dev/null +++ b/sys/qcam/CMakeLists.txt @@ -0,0 +1,33 @@ +set (SOURCES + gstqcamsrc.c) + +set (HEADERS + gstqcamsrc.h) + +include_directories (AFTER + ${QCAM_INCLUDE_DIR} + ) + +set (libname gstqcam) + +add_library (${libname} MODULE + ${SOURCES} + ${HEADERS}) + +set (LIBRARIES + ${GLIB2_LIBRARIES} + ${GOBJECT_LIBRARIES} + ${GSTREAMER_LIBRARY} + ${GSTREAMER_BASE_LIBRARY} + ${GSTREAMER_VIDEO_LIBRARY} + ${QCAM_LIBRARIES} + ) + +target_link_libraries (${libname} + ${LIBRARIES} + ) + +if (WIN32) + install (FILES $ DESTINATION ${PDB_INSTALL_DIR} COMPONENT pdb OPTIONAL) +endif () +install(TARGETS ${libname} LIBRARY DESTINATION ${PLUGIN_INSTALL_DIR}) diff --git a/sys/qcam/gstqcamsrc.c b/sys/qcam/gstqcamsrc.c new file mode 100644 index 0000000..0fded4f --- /dev/null +++ b/sys/qcam/gstqcamsrc.c @@ -0,0 +1,713 @@ +/* GStreamer + * Copyright (C) 2021 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-gstqcamsrc + * + * The qcamsrc element is a source for QImaging QCam cameras like the Retiga 2000R + * + * + * Example launch line + * |[ + * gst-launch -v qcamsrc ! videoconvert ! autovideosink + * ]| + * Shows video from the default QCam device + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include "gstqcamsrc.h" + +#include + +GST_DEBUG_CATEGORY_STATIC (gst_qcamsrc_debug); +#define GST_CAT_DEFAULT gst_qcamsrc_debug + +/* prototypes */ +static void gst_qcamsrc_set_property (GObject * object, + guint property_id, const GValue * value, GParamSpec * pspec); +static void gst_qcamsrc_get_property (GObject * object, + guint property_id, GValue * value, GParamSpec * pspec); +static void gst_qcamsrc_dispose (GObject * object); +static void gst_qcamsrc_finalize (GObject * object); + +static gboolean gst_qcamsrc_start (GstBaseSrc * src); +static gboolean gst_qcamsrc_stop (GstBaseSrc * src); +static GstCaps *gst_qcamsrc_get_caps (GstBaseSrc * src, GstCaps * filter); +static gboolean gst_qcamsrc_set_caps (GstBaseSrc * src, GstCaps * caps); +static gboolean gst_qcamsrc_unlock (GstBaseSrc * src); +static gboolean gst_qcamsrc_unlock_stop (GstBaseSrc * src); + +static GstFlowReturn gst_qcamsrc_create (GstPushSrc * src, GstBuffer ** buf); + +static void gst_qcamsrc_frame_callback (void *userPtr, unsigned long userData, + QCam_Err errcode, unsigned long flags); + +enum +{ + PROP_0, + PROP_DEVICE_INDEX, + PROP_NUM_CAPTURE_BUFFERS, + PROP_TIMEOUT, + PROP_EXPOSURE, + PROP_GAIN, + PROP_OFFSET, + PROP_FORMAT +}; + +#define DEFAULT_PROP_DEVICE_INDEX 0 +#define DEFAULT_PROP_NUM_CAPTURE_BUFFERS 3 +#define DEFAULT_PROP_TIMEOUT 1000 +#define DEFAULT_PROP_EXPOSURE 16384 +#define DEFAULT_PROP_GAIN 1.0 +#define DEFAULT_PROP_OFFSET 0 +#define DEFAULT_PROP_FORMAT qfmtMono16 + + +/* pad templates */ +static GstStaticPadTemplate gst_qcamsrc_src_template = +GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("{ GRAY16_LE, GRAY8 }") + ) + ); + +/* class initialization */ + +G_DEFINE_TYPE (GstQcamSrc, gst_qcamsrc, GST_TYPE_PUSH_SRC); + +static int g_qcam_use_count = 0; + +static void +gst_qcamsrc_driver_ref () +{ + if (g_qcam_use_count == 0) { + QCam_LoadDriver (); + } + g_qcam_use_count++; +} + +static void +gst_qcamsrc_driver_unref () +{ + g_qcam_use_count--; + if (g_qcam_use_count == 0) { + QCam_ReleaseDriver (); + } +} + +static void +gst_qcamsrc_class_init (GstQcamSrcClass * 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); + + GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "qcamsrc", 0, + "QImaging QCam source"); + + gobject_class->set_property = gst_qcamsrc_set_property; + gobject_class->get_property = gst_qcamsrc_get_property; + gobject_class->dispose = gst_qcamsrc_dispose; + gobject_class->finalize = gst_qcamsrc_finalize; + + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&gst_qcamsrc_src_template)); + + gst_element_class_set_static_metadata (gstelement_class, + "QCam Video Source", "Source/Video", + "QImaging QCam video source", "Joshua M. Doe "); + + gstbasesrc_class->start = GST_DEBUG_FUNCPTR (gst_qcamsrc_start); + gstbasesrc_class->stop = GST_DEBUG_FUNCPTR (gst_qcamsrc_stop); + gstbasesrc_class->get_caps = GST_DEBUG_FUNCPTR (gst_qcamsrc_get_caps); + gstbasesrc_class->set_caps = GST_DEBUG_FUNCPTR (gst_qcamsrc_set_caps); + gstbasesrc_class->unlock = GST_DEBUG_FUNCPTR (gst_qcamsrc_unlock); + gstbasesrc_class->unlock_stop = GST_DEBUG_FUNCPTR (gst_qcamsrc_unlock_stop); + + gstpushsrc_class->create = GST_DEBUG_FUNCPTR (gst_qcamsrc_create); + + /* Install GObject properties */ + g_object_class_install_property (gobject_class, PROP_DEVICE_INDEX, + g_param_spec_int ("device-index", "Device index", + "Index of device, use -1 to enumerate all and select last", -1, + G_MAXINT, DEFAULT_PROP_DEVICE_INDEX, + (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + 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", 1, G_MAXUINT, + DEFAULT_PROP_NUM_CAPTURE_BUFFERS, + (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))); + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_EXPOSURE, + g_param_spec_uint ("exposure", "Exposure (us)", + "Exposure time in microseconds", 0, G_MAXINT, DEFAULT_PROP_EXPOSURE, + (GParamFlags) (G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE))); + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_GAIN, + g_param_spec_float ("gain", "Normalized gain", + "Normalized gain", 0, 1000, DEFAULT_PROP_GAIN, + (GParamFlags) (G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE))); + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_OFFSET, + g_param_spec_int ("offset", "Offset", + "Absolute offset", -G_MAXINT, G_MAXINT, DEFAULT_PROP_OFFSET, + (GParamFlags) (G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE))); + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_FORMAT, + g_param_spec_int ("format", "Image format", + "Image format (2=GRAY8, 3=GRAY16_LE)", 2, 3, DEFAULT_PROP_FORMAT, + (GParamFlags) (G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE))); +} + +static void +gst_qcamsrc_reset (GstQcamSrc * src) +{ + src->handle = NULL; + + src->exposure = DEFAULT_PROP_EXPOSURE; + src->gain = DEFAULT_PROP_GAIN; + src->offset = DEFAULT_PROP_OFFSET; + src->format = DEFAULT_PROP_FORMAT; + + src->last_frame_count = 0; + src->total_dropped_frames = 0; + + if (src->caps) { + gst_caps_unref (src->caps); + src->caps = NULL; + } + + src->width = 0; + src->height = 0; + + if (src->queue) { + // TODO: remove dangling buffers + g_async_queue_unref (src->queue); + } + src->queue = g_async_queue_new (); +} + +static void +gst_qcamsrc_init (GstQcamSrc * src) +{ + GST_DEBUG_OBJECT (src, "Initialize instance"); + + gst_qcamsrc_driver_ref (); + + /* 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->device_index = DEFAULT_PROP_DEVICE_INDEX; + src->num_capture_buffers = DEFAULT_PROP_NUM_CAPTURE_BUFFERS; + src->timeout = DEFAULT_PROP_TIMEOUT; + + src->stop_requested = FALSE; + src->caps = NULL; + src->queue = NULL; + + gst_qcamsrc_reset (src); +} + + +static void +gst_qcamsrc_set_exposure (GstQcamSrc * src, unsigned long exposure) +{ + QCam_SetParam (&src->qsettings, qprmExposure, exposure); + QCam_SendSettingsToCam (src->handle, &src->qsettings); +} + +static void +gst_qcamsrc_set_gain (GstQcamSrc * src, float gain) +{ + QCam_SetParam (&src->qsettings, qprmNormalizedGain, + (unsigned long) (gain * 1000000)); + QCam_SendSettingsToCam (src->handle, &src->qsettings); +} + +static void +gst_qcamsrc_set_offset (GstQcamSrc * src, long offset) +{ + QCam_SetParamS32 (&src->qsettings, qprmS32AbsoluteOffset, offset); + QCam_SendSettingsToCam (src->handle, &src->qsettings); +} + + +void +gst_qcamsrc_set_property (GObject * object, guint property_id, + const GValue * value, GParamSpec * pspec) +{ + GstQcamSrc *src; + + src = GST_QCAM_SRC (object); + + switch (property_id) { + case PROP_DEVICE_INDEX: + src->device_index = g_value_get_int (value); + break; + case PROP_NUM_CAPTURE_BUFFERS: + src->num_capture_buffers = g_value_get_uint (value); + break; + case PROP_TIMEOUT: + src->timeout = g_value_get_int (value); + break; + case PROP_EXPOSURE: + src->exposure = g_value_get_uint (value); + if (src->handle) + gst_qcamsrc_set_exposure (src, src->exposure); + break; + case PROP_GAIN: + src->gain = g_value_get_float (value); + if (src->handle) + gst_qcamsrc_set_gain (src, src->gain); + break; + case PROP_OFFSET: + src->offset = g_value_get_int (value); + if (src->handle) + gst_qcamsrc_set_offset (src, src->offset); + break; + case PROP_FORMAT: + src->format = g_value_get_int (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +void +gst_qcamsrc_get_property (GObject * object, guint property_id, + GValue * value, GParamSpec * pspec) +{ + GstQcamSrc *src; + + g_return_if_fail (GST_IS_QCAM_SRC (object)); + src = GST_QCAM_SRC (object); + + switch (property_id) { + case PROP_DEVICE_INDEX: + g_value_set_int (value, src->device_index); + break; + case PROP_NUM_CAPTURE_BUFFERS: + g_value_set_uint (value, src->num_capture_buffers); + break; + case PROP_TIMEOUT: + g_value_set_int (value, src->timeout); + break; + case PROP_EXPOSURE: + g_value_set_uint (value, src->exposure); + break; + case PROP_GAIN: + g_value_set_float (value, src->gain); + break; + case PROP_OFFSET: + g_value_set_int (value, src->offset); + break; + case PROP_FORMAT: + g_value_set_int (value, src->format); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +void +gst_qcamsrc_dispose (GObject * object) +{ + GstQcamSrc *src; + + g_return_if_fail (GST_IS_QCAM_SRC (object)); + src = GST_QCAM_SRC (object); + + /* clean up as possible. may be called multiple times */ + + G_OBJECT_CLASS (gst_qcamsrc_parent_class)->dispose (object); +} + +void +gst_qcamsrc_finalize (GObject * object) +{ + GstQcamSrc *src; + + g_return_if_fail (GST_IS_QCAM_SRC (object)); + src = GST_QCAM_SRC (object); + + /* clean up object here */ + + if (src->caps) { + gst_caps_unref (src->caps); + src->caps = NULL; + } + + gst_qcamsrc_driver_unref (); + + G_OBJECT_CLASS (gst_qcamsrc_parent_class)->finalize (object); +} + +typedef struct +{ + GstQcamSrc *src; + QCam_Frame frame; + GstClockTime clock_time; +} VideoFrame; + +static void +video_frame_queue (VideoFrame * frame) +{ + QCam_Err err; + g_assert (frame->src->handle); + + GST_TRACE_OBJECT (frame->src, "Queuing frame 0x%x", frame); + err = QCam_QueueFrame (frame->src->handle, + &frame->frame, + gst_qcamsrc_frame_callback, + qcCallbackDone | qcCallbackExposeDone, frame, 0); +} + +static void +video_frame_release (void *data) +{ + VideoFrame *frame = (VideoFrame *) data; + video_frame_queue (frame); +} + +static VideoFrame * +video_frame_create (GstQcamSrc * src, gsize buf_size) +{ + VideoFrame *frame = g_new (VideoFrame, 1); + frame->frame.pBuffer = g_malloc (buf_size); + + if (!frame->frame.pBuffer) { + GST_ERROR_OBJECT (src, "Failed to allocate buffer of size %d", buf_size); + g_free (frame); + return NULL; + } + frame->src = src; + frame->frame.bufferSize = buf_size; + + return frame; +} + + +static gboolean +gst_qcamsrc_setup_stream (GstQcamSrc * src) +{ + QCam_Err err; + QCam_CamListItem cam_list[255]; + unsigned long num_cams = 255; + unsigned long width, height, imageFormat, exposure, gain; + long offset; + + err = QCam_ListCameras (cam_list, &num_cams); + if (err != qerrSuccess) { + GST_ELEMENT_ERROR (src, RESOURCE, SETTINGS, + ("Failed to get list of cameras (errcode=%d)", err), (NULL)); + return FALSE; + } + + GST_DEBUG_OBJECT (src, "Found %d cameras", num_cams); + + if (src->device_index + 1 > num_cams) { + GST_ELEMENT_ERROR (src, RESOURCE, SETTINGS, + ("device-index (%d) exceeds number of cameras found (%d)", + src->device_index, num_cams), (NULL)); + return FALSE; + } + + err = QCam_OpenCamera (cam_list[src->device_index].cameraId, &src->handle); + if (err != qerrSuccess) { + GST_ELEMENT_ERROR (src, RESOURCE, SETTINGS, + ("Failed to open camera (errcode=%d)", err), (NULL)); + return FALSE; + } + + err = QCam_SetStreaming (src->handle, TRUE); + if (err != qerrSuccess) { + GST_ELEMENT_ERROR (src, RESOURCE, SETTINGS, + ("Failed to start streaming (errcode=%d)", err), (NULL)); + QCam_CloseCamera (src->handle); + return FALSE; + } + + err = QCam_ReadSettingsFromCam (src->handle, &src->qsettings); + if (err != qerrSuccess) { + GST_ELEMENT_ERROR (src, RESOURCE, SETTINGS, + ("Failed to read settings (errcode=%d)", err), (NULL)); + QCam_CloseCamera (src->handle); + return FALSE; + } + + err = QCam_SetParam (&src->qsettings, qprmImageFormat, src->format); + gst_qcamsrc_set_exposure (src, src->exposure); + gst_qcamsrc_set_gain (src, src->gain); + gst_qcamsrc_set_offset (src, src->offset); + + err = QCam_GetInfo (src->handle, qinfCcdWidth, &width); + err = QCam_GetInfo (src->handle, qinfCcdHeight, &height); + + GST_DEBUG_OBJECT (src, "Opened camera with CCD width,height=%d,%d", width, + height); + + err = QCam_GetParam (&src->qsettings, qprmRoiWidth, &width); + err = QCam_GetParam (&src->qsettings, qprmRoiHeight, &height); + err = QCam_GetParam (&src->qsettings, qprmImageFormat, &imageFormat); + err = QCam_GetParam (&src->qsettings, qprmExposure, &exposure); + err = QCam_GetParam (&src->qsettings, qprmNormalizedGain, &gain); + err = QCam_GetParamS32 (&src->qsettings, qprmS32AbsoluteOffset, &offset); + GST_DEBUG_OBJECT (src, + "ROI configured with width,height,format,exposure,gain,offset=%d,%d,%d,%d,%d,%d,%d", + width, height, imageFormat, exposure, gain, offset); + + for (int i = 0; i < src->num_capture_buffers; ++i) { + unsigned long buf_size; + QCam_GetInfo (src->handle, qinfImageSize, &buf_size); + VideoFrame *frame = video_frame_create (src, buf_size); + video_frame_queue (frame); + } + + { + GstStructure *structure; + GstCaps *caps; + caps = gst_caps_new_empty (); + structure = gst_structure_from_string ("video/x-raw", NULL); + const char *gst_format; + if (imageFormat == qfmtMono8) + gst_format = "GRAY8"; + else if (imageFormat == qfmtMono16) + gst_format = "GRAY16_LE"; + gst_structure_set (structure, + "format", G_TYPE_STRING, gst_format, + "width", G_TYPE_INT, width, + "height", G_TYPE_INT, height, + "framerate", GST_TYPE_FRACTION, 30, 1, NULL); + gst_caps_append_structure (caps, structure); + + if (src->caps) { + gst_caps_unref (src->caps); + } + src->caps = caps; + gst_base_src_set_caps (GST_BASE_SRC (src), src->caps); + } + + return TRUE; +} + +static gboolean +gst_qcamsrc_start (GstBaseSrc * bsrc) +{ + GstQcamSrc *src = GST_QCAM_SRC (bsrc); + + GST_DEBUG_OBJECT (src, "start"); + + if (!gst_qcamsrc_setup_stream (src)) { + /* error already sent */ + goto error; + } + + return TRUE; + +error: + return FALSE; +} + +static gboolean +gst_qcamsrc_stop (GstBaseSrc * bsrc) +{ + GstQcamSrc *src = GST_QCAM_SRC (bsrc); + GST_DEBUG_OBJECT (src, "stop"); + + if (src->handle) { + QCam_CloseCamera (src->handle); + src->handle = NULL; + } + + gst_qcamsrc_reset (src); + + return TRUE; +} + +static GstCaps * +gst_qcamsrc_get_caps (GstBaseSrc * bsrc, GstCaps * filter) +{ + GstQcamSrc *src = GST_QCAM_SRC (bsrc); + GstCaps *caps; + + if (src->caps == 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_qcamsrc_set_caps (GstBaseSrc * bsrc, GstCaps * caps) +{ + GstQcamSrc *src = GST_QCAM_SRC (bsrc); + + GST_DEBUG_OBJECT (src, "The caps being set are %" GST_PTR_FORMAT, caps); + + return TRUE; +} + +static gboolean +gst_qcamsrc_unlock (GstBaseSrc * bsrc) +{ + GstQcamSrc *src = GST_QCAM_SRC (bsrc); + + GST_LOG_OBJECT (src, "unlock"); + + src->stop_requested = TRUE; + + return TRUE; +} + +static gboolean +gst_qcamsrc_unlock_stop (GstBaseSrc * bsrc) +{ + GstQcamSrc *src = GST_QCAM_SRC (bsrc); + + GST_LOG_OBJECT (src, "unlock_stop"); + + src->stop_requested = FALSE; + + return TRUE; +} + +static GstFlowReturn +gst_qcamsrc_create (GstPushSrc * psrc, GstBuffer ** buf) +{ + GstQcamSrc *src = GST_QCAM_SRC (psrc); + GstClock *clock; + GstClockTime clock_time; + QCam_Err err; + VideoFrame *video_frame; + GST_LOG_OBJECT (src, "create"); + + video_frame = + (VideoFrame *) g_async_queue_timeout_pop (src->queue, + (guint64) src->timeout * 1000); + if (!video_frame) { + GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, + ("Failed to get buffer in %d ms", src->timeout), (NULL)); + return GST_FLOW_ERROR; + } + + *buf = gst_buffer_new_wrapped_full ((GstMemoryFlags) + GST_MEMORY_FLAG_PHYSICALLY_CONTIGUOUS, + (gpointer) video_frame->frame.pBuffer, video_frame->frame.bufferSize, 0, + video_frame->frame.bufferSize, video_frame, + (GDestroyNotify) video_frame_release); + + + /* check for dropped frames and disrupted signal */ + //dropped_frames = (circ_handle.FrameCount - src->last_frame_count) - 1; + //if (dropped_frames > 0) { + // src->total_dropped_frames += dropped_frames; + // GST_WARNING_OBJECT (src, "Dropped %d frames (%d total)", dropped_frames, + // src->total_dropped_frames); + //} else if (dropped_frames < 0) { + // GST_WARNING_OBJECT (src, "Frame count non-monotonic, signal disrupted?"); + //} + //src->last_frame_count = circ_handle.FrameCount; + + GST_BUFFER_TIMESTAMP (*buf) = + GST_CLOCK_DIFF (gst_element_get_base_time (GST_ELEMENT (src)), + video_frame->clock_time); + + if (src->stop_requested) { + if (*buf != NULL) { + gst_buffer_unref (*buf); + *buf = NULL; + } + return GST_FLOW_FLUSHING; + } + + return GST_FLOW_OK; +} + +void +gst_qcamsrc_frame_callback (void *userPtr, unsigned long userData, + QCam_Err errcode, unsigned long flags) +{ + VideoFrame *frame = (VideoFrame *) (userPtr); + + if (flags & qcCallbackExposeDone) { + GstClock *clock = gst_element_get_clock (GST_ELEMENT (frame->src)); + frame->clock_time = gst_clock_get_time (clock); + gst_object_unref (clock); + GST_TRACE_OBJECT (frame->src, "ExposeDone callback for frame 0x%x", frame); + } else if (flags & qcCallbackDone) { + GST_TRACE_OBJECT (frame->src, "FrameDone callback for frame 0x%x", frame); + + if (errcode != qerrSuccess) { + GST_WARNING_OBJECT (frame->src, "Error code in callback: %d", errcode); + } + + g_async_queue_push (frame->src->queue, frame); + } else { + g_assert_not_reached (); + } +} + +static gboolean +plugin_init (GstPlugin * plugin) +{ + GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "qcam", 0, + "debug category for qcam plugin"); + + if (!gst_element_register (plugin, "qcamsrc", GST_RANK_NONE, + gst_qcamsrc_get_type ())) { + return FALSE; + } + + return TRUE; +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + qcam, + "QImaging QCam video element", + plugin_init, GST_PACKAGE_VERSION, GST_PACKAGE_LICENSE, GST_PACKAGE_NAME, + GST_PACKAGE_ORIGIN) diff --git a/sys/qcam/gstqcamsrc.h b/sys/qcam/gstqcamsrc.h new file mode 100644 index 0000000..4e81a81 --- /dev/null +++ b/sys/qcam/gstqcamsrc.h @@ -0,0 +1,77 @@ +/* GStreamer + * Copyright (C) 2021 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_QCAM_SRC_H_ +#define _GST_QCAM_SRC_H_ + +#include + +#include + +G_BEGIN_DECLS + +#define GST_TYPE_QCAM_SRC (gst_qcamsrc_get_type()) +#define GST_QCAM_SRC(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_QCAM_SRC,GstQcamSrc)) +#define GST_QCAM_SRC_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_QCAM_SRC,GstQcamSrcClass)) +#define GST_IS_QCAM_SRC(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_QCAM_SRC)) +#define GST_IS_QCAM_SRC_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_QCAM_SRC)) + +typedef struct _GstQcamSrc GstQcamSrc; +typedef struct _GstQcamSrcClass GstQcamSrcClass; + +struct _GstQcamSrc +{ + GstPushSrc base_qcamsrc; + + /* camera handle */ + QCam_Handle handle; + QCam_Settings qsettings; + + /* properties */ + gint device_index; + guint num_capture_buffers; + gint timeout; + guint exposure; + gfloat gain; + gint offset; + gint format; + + GAsyncQueue *queue; + GstClockTime base_time; + + guint32 last_frame_count; + guint32 total_dropped_frames; + + GstCaps *caps; + gint width; + gint height; + + gboolean stop_requested; +}; + +struct _GstQcamSrcClass +{ + GstPushSrcClass base_qcamsrc_class; +}; + +GType gst_qcamsrc_get_type (void); + +G_END_DECLS + +#endif