Files
gst-plugin-linescan/gst/linescan/gstlinescan.c
yair a7a776fb58 feat: adapt linescan scripts to use uv and proper rollover-based file saving
- Updated launch_with_signal.py with PEP 723 metadata for uv compatibility
- Added day/night mode presets with command-line arguments
- Implemented proper rollover-only file saving via Python/PIL callbacks
- Removed multifilesink from pipeline (was saving every frame incorrectly)
- Added funny auto-generated output directory names (e.g., fuzzy-photon)
- Updated rollover_example.py to follow same pattern with uv support
- Updated rollover_example.c to demonstrate signal detection without file saving
- Updated launch_with_capture.ps1 to remove incorrect multifilesink usage
- All scripts now save files ONLY on rollover events, not every frame
- Pipeline simplified: camera -> linescan -> display (files saved in callback)
2025-11-18 19:39:51 +02:00

671 lines
22 KiB
C

/* GStreamer
* Copyright (C) 2024 FIXME <fixme@example.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
/**
* SECTION:element-linescan
*
* Line scan camera simulator that extracts one row or column of pixels
* from each input frame and stacks them to create a line scan image.
*
* Useful for analyzing fast-moving objects with high frame rate cameras
* (typically 200-750 fps). The user sets the output width, and the height
* is determined by the input resolution.
*
* <refsect2>
* <title>Example launch line</title>
* |[
* gst-launch-1.0 videotestsrc ! linescan direction=horizontal line-index=100 output-size=800 ! videoconvert ! autovideosink
* ]|
* Extracts row 100 from each frame and creates an 800-pixel wide output
* </refsect2>
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gstlinescan.h"
#include <string.h>
GST_DEBUG_CATEGORY_STATIC (gst_linescan_debug);
#define GST_CAT_DEFAULT gst_linescan_debug
/* Signals */
enum
{
SIGNAL_ROLLOVER,
LAST_SIGNAL
};
static guint gst_linescan_signals[LAST_SIGNAL] = { 0 };
/* Properties */
enum
{
PROP_0,
PROP_DIRECTION,
PROP_LINE_INDEX,
PROP_OUTPUT_SIZE
};
#define DEFAULT_PROP_DIRECTION GST_LINESCAN_DIRECTION_HORIZONTAL
#define DEFAULT_PROP_LINE_INDEX -1 /* -1 means middle of image */
#define DEFAULT_PROP_OUTPUT_SIZE 800
/* GStreamer boilerplate */
#define gst_linescan_parent_class parent_class
G_DEFINE_TYPE (GstLinescan, gst_linescan, GST_TYPE_BASE_TRANSFORM);
/* Define the enum type for direction */
#define GST_TYPE_LINESCAN_DIRECTION (gst_linescan_direction_get_type ())
static GType
gst_linescan_direction_get_type (void)
{
static GType direction_type = 0;
static const GEnumValue direction_values[] = {
{GST_LINESCAN_DIRECTION_HORIZONTAL, "Extract horizontal row, stack vertically", "horizontal"},
{GST_LINESCAN_DIRECTION_VERTICAL, "Extract vertical column, stack horizontally", "vertical"},
{0, NULL, NULL}
};
if (!direction_type) {
direction_type = g_enum_register_static ("GstLinescanDirection", direction_values);
}
return direction_type;
}
static GstStaticPadTemplate gst_linescan_sink_template =
GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("{ GRAY8, GRAY16_LE, GRAY16_BE, RGB, BGR, BGRA, RGBx }"))
);
static GstStaticPadTemplate gst_linescan_src_template =
GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("{ GRAY8, GRAY16_LE, GRAY16_BE, RGB, BGR, BGRA, RGBx }"))
);
/* GObject vmethod declarations */
static void gst_linescan_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec);
static void gst_linescan_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec);
static void gst_linescan_finalize (GObject * object);
/* GstBaseTransform vmethod declarations */
static gboolean gst_linescan_start (GstBaseTransform * trans);
static gboolean gst_linescan_stop (GstBaseTransform * trans);
static gboolean gst_linescan_set_caps (GstBaseTransform * trans,
GstCaps * incaps, GstCaps * outcaps);
static GstCaps * gst_linescan_transform_caps (GstBaseTransform * trans,
GstPadDirection direction, GstCaps * caps, GstCaps * filter);
static GstCaps * gst_linescan_fixate_caps (GstBaseTransform * trans,
GstPadDirection direction, GstCaps * caps, GstCaps * othercaps);
static gboolean gst_linescan_transform_size (GstBaseTransform * trans,
GstPadDirection direction, GstCaps * caps, gsize size,
GstCaps * othercaps, gsize * othersize);
static GstFlowReturn gst_linescan_transform (GstBaseTransform * trans,
GstBuffer * inbuf, GstBuffer * outbuf);
/* Helper functions */
static void gst_linescan_reset (GstLinescan * filter);
static gboolean gst_linescan_allocate_buffer (GstLinescan * filter);
static void
gst_linescan_class_init (GstLinescanClass * klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);
GstBaseTransformClass *gstbasetransform_class =
GST_BASE_TRANSFORM_CLASS (klass);
GST_DEBUG_CATEGORY_INIT (gst_linescan_debug, "linescan", 0,
"Line Scan Camera Simulator");
/* Register GObject vmethods */
gobject_class->set_property = gst_linescan_set_property;
gobject_class->get_property = gst_linescan_get_property;
gobject_class->finalize = gst_linescan_finalize;
/* Install GObject properties */
g_object_class_install_property (gobject_class, PROP_DIRECTION,
g_param_spec_enum ("direction", "Direction",
"Direction to extract line (horizontal=row, vertical=column)",
GST_TYPE_LINESCAN_DIRECTION, DEFAULT_PROP_DIRECTION,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_LINE_INDEX,
g_param_spec_int ("line-index", "Line Index",
"Index of row/column to extract (-1 for middle)", -1, G_MAXINT,
DEFAULT_PROP_LINE_INDEX,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_OUTPUT_SIZE,
g_param_spec_int ("output-size", "Output Size",
"Number of lines to accumulate (width for horizontal, height for vertical)",
1, G_MAXINT, DEFAULT_PROP_OUTPUT_SIZE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
/* Install signals */
/**
* GstLinescan::rollover:
* @linescan: the linescan instance
* @buffer: the completed buffer at rollover
*
* Emitted when the buffer position wraps around to 0 after accumulating
* output_size lines. The buffer contains a complete line scan image.
* Applications can connect to this signal to save the image to disk.
*/
gst_linescan_signals[SIGNAL_ROLLOVER] =
g_signal_new ("rollover", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstLinescanClass, rollover),
NULL, NULL, g_cclosure_marshal_VOID__BOXED,
G_TYPE_NONE, 1, GST_TYPE_BUFFER);
/* Set element metadata */
gst_element_class_add_pad_template (gstelement_class,
gst_static_pad_template_get (&gst_linescan_sink_template));
gst_element_class_add_pad_template (gstelement_class,
gst_static_pad_template_get (&gst_linescan_src_template));
gst_element_class_set_static_metadata (gstelement_class,
"Line Scan Camera", "Filter/Effect/Video",
"Extracts single row/column from frames and builds line scan image",
"FIXME <fixme@example.com>");
/* Register GstBaseTransform vmethods */
gstbasetransform_class->start = GST_DEBUG_FUNCPTR (gst_linescan_start);
gstbasetransform_class->stop = GST_DEBUG_FUNCPTR (gst_linescan_stop);
gstbasetransform_class->set_caps = GST_DEBUG_FUNCPTR (gst_linescan_set_caps);
gstbasetransform_class->transform_caps = GST_DEBUG_FUNCPTR (gst_linescan_transform_caps);
gstbasetransform_class->fixate_caps = GST_DEBUG_FUNCPTR (gst_linescan_fixate_caps);
gstbasetransform_class->transform_size = GST_DEBUG_FUNCPTR (gst_linescan_transform_size);
gstbasetransform_class->transform = GST_DEBUG_FUNCPTR (gst_linescan_transform);
}
static void
gst_linescan_init (GstLinescan * filter)
{
/* Initialize properties */
filter->direction = DEFAULT_PROP_DIRECTION;
filter->line_index = DEFAULT_PROP_LINE_INDEX;
filter->output_size = DEFAULT_PROP_OUTPUT_SIZE;
/* Initialize internal state */
filter->line_buffer = NULL;
filter->buffer_position = 0;
filter->line_size = 0;
filter->actual_line_index = 0;
filter->frame_count = 0;
filter->video_info_valid = FALSE;
filter->output_caps_set = FALSE;
/* This is not an in-place transform */
gst_base_transform_set_in_place (GST_BASE_TRANSFORM (filter), FALSE);
}
static void
gst_linescan_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
GstLinescan *filter = GST_LINESCAN (object);
switch (prop_id) {
case PROP_DIRECTION:
filter->direction = g_value_get_enum (value);
gst_linescan_reset (filter);
break;
case PROP_LINE_INDEX:
filter->line_index = g_value_get_int (value);
break;
case PROP_OUTPUT_SIZE:
filter->output_size = g_value_get_int (value);
gst_linescan_reset (filter);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_linescan_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec)
{
GstLinescan *filter = GST_LINESCAN (object);
switch (prop_id) {
case PROP_DIRECTION:
g_value_set_enum (value, filter->direction);
break;
case PROP_LINE_INDEX:
g_value_set_int (value, filter->line_index);
break;
case PROP_OUTPUT_SIZE:
g_value_set_int (value, filter->output_size);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_linescan_finalize (GObject * object)
{
GstLinescan *filter = GST_LINESCAN (object);
if (filter->line_buffer) {
g_free (filter->line_buffer);
filter->line_buffer = NULL;
}
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static gboolean
gst_linescan_start (GstBaseTransform * trans)
{
GstLinescan *filter = GST_LINESCAN (trans);
GST_DEBUG_OBJECT (filter, "start");
filter->frame_count = 0;
filter->buffer_position = 0;
return TRUE;
}
static gboolean
gst_linescan_stop (GstBaseTransform * trans)
{
GstLinescan *filter = GST_LINESCAN (trans);
GST_DEBUG_OBJECT (filter, "stop");
if (filter->line_buffer) {
g_free (filter->line_buffer);
filter->line_buffer = NULL;
}
filter->video_info_valid = FALSE;
filter->output_caps_set = FALSE;
return TRUE;
}
static GstCaps *
gst_linescan_transform_caps (GstBaseTransform * trans,
GstPadDirection direction, GstCaps * caps, GstCaps * filter_caps)
{
GstLinescan *filter = GST_LINESCAN (trans);
GstCaps *ret, *tmp;
GstStructure *structure;
gint i;
GST_DEBUG_OBJECT (filter, "transform_caps (direction: %d) caps: %" GST_PTR_FORMAT, direction, caps);
ret = gst_caps_new_empty ();
for (i = 0; i < gst_caps_get_size (caps); i++) {
structure = gst_caps_get_structure (caps, i);
structure = gst_structure_copy (structure);
if (direction == GST_PAD_SINK) {
/* Transform sink caps to src caps (input -> output) */
if (filter->direction == GST_LINESCAN_DIRECTION_HORIZONTAL) {
/* Horizontal: extract row (width=input_width), stack vertically (height=output_size) */
/* Width stays same as input, height becomes output_size */
gst_structure_set (structure, "height", G_TYPE_INT, filter->output_size, NULL);
} else {
/* Vertical: extract column (height=input_height), stack horizontally (width=output_size) */
/* Height stays same as input, width becomes output_size */
gst_structure_set (structure, "width", G_TYPE_INT, filter->output_size, NULL);
}
} else {
/* Transform src caps to sink caps (output -> input) */
/* We need to be permissive here - input can be any size */
if (filter->direction == GST_LINESCAN_DIRECTION_HORIZONTAL) {
/* For horizontal: output height is fixed, so input height can vary */
gst_structure_remove_field (structure, "height");
gst_structure_set (structure, "height", GST_TYPE_INT_RANGE, 1, G_MAXINT, NULL);
} else {
/* For vertical: output width is fixed, so input width can vary */
gst_structure_remove_field (structure, "width");
gst_structure_set (structure, "width", GST_TYPE_INT_RANGE, 1, G_MAXINT, NULL);
}
}
gst_caps_append_structure (ret, structure);
}
/* Apply filter if provided */
if (filter_caps) {
tmp = gst_caps_intersect_full (ret, filter_caps, GST_CAPS_INTERSECT_FIRST);
gst_caps_unref (ret);
ret = tmp;
}
GST_DEBUG_OBJECT (filter, "transformed caps: %" GST_PTR_FORMAT, ret);
return ret;
}
static GstCaps *
gst_linescan_fixate_caps (GstBaseTransform * trans,
GstPadDirection direction, GstCaps * caps, GstCaps * othercaps)
{
GstLinescan *filter = GST_LINESCAN (trans);
GstStructure *structure;
GstCaps *result;
GST_DEBUG_OBJECT (filter, "fixate_caps (direction: %d) caps: %" GST_PTR_FORMAT
" othercaps: %" GST_PTR_FORMAT, direction, caps, othercaps);
result = gst_caps_make_writable (othercaps);
structure = gst_caps_get_structure (result, 0);
if (direction == GST_PAD_SINK) {
/* Fixating output caps based on input */
GstVideoInfo info;
if (gst_video_info_from_caps (&info, caps)) {
if (filter->direction == GST_LINESCAN_DIRECTION_HORIZONTAL) {
/* Horizontal: extract row, stack vertically */
/* Width stays same as input, height becomes output_size */
gst_structure_fixate_field_nearest_int (structure, "width",
GST_VIDEO_INFO_WIDTH (&info));
gst_structure_fixate_field_nearest_int (structure, "height", filter->output_size);
} else {
/* Vertical: extract column, stack horizontally */
/* Width becomes output_size, height stays same as input */
gst_structure_fixate_field_nearest_int (structure, "width", filter->output_size);
gst_structure_fixate_field_nearest_int (structure, "height",
GST_VIDEO_INFO_HEIGHT (&info));
}
}
} else {
/* Fixating input caps based on output - less constrained */
/* Just fixate to something reasonable */
gst_structure_fixate_field_nearest_int (structure, "width", 640);
gst_structure_fixate_field_nearest_int (structure, "height", 480);
}
/* Fixate other fields using parent implementation */
result = gst_caps_fixate (result);
GST_DEBUG_OBJECT (filter, "fixated caps: %" GST_PTR_FORMAT, result);
return result;
}
static gboolean
gst_linescan_transform_size (GstBaseTransform * trans,
GstPadDirection direction, GstCaps * caps, gsize size,
GstCaps * othercaps, gsize * othersize)
{
GstLinescan *filter = GST_LINESCAN (trans);
GstVideoInfo info;
if (!gst_video_info_from_caps (&info, othercaps)) {
GST_ERROR_OBJECT (filter, "Failed to parse othercaps");
return FALSE;
}
*othersize = GST_VIDEO_INFO_SIZE (&info);
GST_DEBUG_OBJECT (filter, "transform_size: %zu -> %zu", size, *othersize);
return TRUE;
}
static gboolean
gst_linescan_set_caps (GstBaseTransform * trans, GstCaps * incaps,
GstCaps * outcaps)
{
GstLinescan *filter = GST_LINESCAN (trans);
if (!gst_video_info_from_caps (&filter->video_info_in, incaps)) {
GST_ERROR_OBJECT (filter, "Failed to parse input caps");
return FALSE;
}
if (!gst_video_info_from_caps (&filter->video_info_out, outcaps)) {
GST_ERROR_OBJECT (filter, "Failed to parse output caps");
return FALSE;
}
filter->video_info_valid = TRUE;
filter->output_caps_set = TRUE;
/* Calculate actual line index if set to -1 (middle) */
if (filter->line_index < 0) {
if (filter->direction == GST_LINESCAN_DIRECTION_HORIZONTAL) {
filter->actual_line_index = GST_VIDEO_INFO_HEIGHT (&filter->video_info_in) / 2;
} else {
filter->actual_line_index = GST_VIDEO_INFO_WIDTH (&filter->video_info_in) / 2;
}
} else {
filter->actual_line_index = filter->line_index;
}
/* Allocate line buffer */
if (!gst_linescan_allocate_buffer (filter)) {
GST_ERROR_OBJECT (filter, "Failed to allocate line buffer");
return FALSE;
}
GST_INFO_OBJECT (filter, "Set caps - Input: %dx%d, Output: %dx%d, Direction: %s, Line: %d",
GST_VIDEO_INFO_WIDTH (&filter->video_info_in),
GST_VIDEO_INFO_HEIGHT (&filter->video_info_in),
GST_VIDEO_INFO_WIDTH (&filter->video_info_out),
GST_VIDEO_INFO_HEIGHT (&filter->video_info_out),
(filter->direction == GST_LINESCAN_DIRECTION_HORIZONTAL) ? "HORIZONTAL" : "VERTICAL",
filter->actual_line_index);
return TRUE;
}
static gboolean
gst_linescan_allocate_buffer (GstLinescan * filter)
{
gint output_width, output_height;
gsize pixel_stride;
if (!filter->video_info_valid) {
return FALSE;
}
/* Free old buffer if exists */
if (filter->line_buffer) {
g_free (filter->line_buffer);
filter->line_buffer = NULL;
}
output_width = GST_VIDEO_INFO_WIDTH (&filter->video_info_out);
output_height = GST_VIDEO_INFO_HEIGHT (&filter->video_info_out);
pixel_stride = GST_VIDEO_INFO_COMP_PSTRIDE (&filter->video_info_out, 0);
/* Calculate line size based on direction */
if (filter->direction == GST_LINESCAN_DIRECTION_HORIZONTAL) {
/* Extracting horizontal row: line is output_width pixels wide */
filter->line_size = output_width * pixel_stride;
} else {
/* Extracting vertical column: line is 1 pixel wide */
filter->line_size = pixel_stride;
}
/* Allocate buffer for entire output image */
filter->line_buffer = g_malloc0 (GST_VIDEO_INFO_SIZE (&filter->video_info_out));
if (!filter->line_buffer) {
GST_ERROR_OBJECT (filter, "Failed to allocate line buffer");
return FALSE;
}
filter->buffer_position = 0;
GST_DEBUG_OBJECT (filter, "Allocated line buffer: %zu bytes, line size: %zu",
GST_VIDEO_INFO_SIZE (&filter->video_info_out), filter->line_size);
return TRUE;
}
static GstFlowReturn
gst_linescan_transform (GstBaseTransform * trans, GstBuffer * inbuf,
GstBuffer * outbuf)
{
GstLinescan *filter = GST_LINESCAN (trans);
GstMapInfo map_in, map_out;
guint8 *src_line, *dest_line;
gint in_width, in_height, in_stride;
gint out_width, out_height, out_stride;
gint pixel_stride;
gint x, y;
if (!filter->video_info_valid || !filter->line_buffer) {
GST_ERROR_OBJECT (filter, "Not properly initialized");
return GST_FLOW_ERROR;
}
if (!gst_buffer_map (inbuf, &map_in, GST_MAP_READ)) {
GST_ERROR_OBJECT (filter, "Failed to map input buffer");
return GST_FLOW_ERROR;
}
if (!gst_buffer_map (outbuf, &map_out, GST_MAP_WRITE)) {
GST_ERROR_OBJECT (filter, "Failed to map output buffer");
gst_buffer_unmap (inbuf, &map_in);
return GST_FLOW_ERROR;
}
in_width = GST_VIDEO_INFO_WIDTH (&filter->video_info_in);
in_height = GST_VIDEO_INFO_HEIGHT (&filter->video_info_in);
in_stride = GST_VIDEO_INFO_PLANE_STRIDE (&filter->video_info_in, 0);
out_width = GST_VIDEO_INFO_WIDTH (&filter->video_info_out);
out_height = GST_VIDEO_INFO_HEIGHT (&filter->video_info_out);
out_stride = GST_VIDEO_INFO_PLANE_STRIDE (&filter->video_info_out, 0);
pixel_stride = GST_VIDEO_INFO_COMP_PSTRIDE (&filter->video_info_in, 0);
if (filter->direction == GST_LINESCAN_DIRECTION_HORIZONTAL) {
/* Extract horizontal row */
if (filter->actual_line_index >= in_height) {
GST_WARNING_OBJECT (filter, "Line index %d exceeds input height %d",
filter->actual_line_index, in_height);
gst_buffer_unmap (inbuf, &map_in);
gst_buffer_unmap (outbuf, &map_out);
return GST_FLOW_ERROR;
}
/* Get pointer to the row we want to extract */
src_line = map_in.data + (filter->actual_line_index * in_stride);
/* Copy the row to the current position in our buffer */
dest_line = filter->line_buffer + (filter->buffer_position * out_stride);
memcpy (dest_line, src_line, filter->line_size);
} else {
/* Extract vertical column */
if (filter->actual_line_index >= in_width) {
GST_WARNING_OBJECT (filter, "Line index %d exceeds input width %d",
filter->actual_line_index, in_width);
gst_buffer_unmap (inbuf, &map_in);
gst_buffer_unmap (outbuf, &map_out);
return GST_FLOW_ERROR;
}
/* Extract column pixel by pixel and place horizontally in output */
for (y = 0; y < in_height && y < out_height; y++) {
src_line = map_in.data + (y * in_stride) + (filter->actual_line_index * pixel_stride);
dest_line = filter->line_buffer + (y * out_stride) + (filter->buffer_position * pixel_stride);
memcpy (dest_line, src_line, pixel_stride);
}
}
/* Increment buffer position */
filter->buffer_position++;
/* Check for rollover and emit signal */
gboolean rollover_occurred = FALSE;
if (filter->direction == GST_LINESCAN_DIRECTION_HORIZONTAL) {
if (filter->buffer_position >= out_height) {
filter->buffer_position = 0;
rollover_occurred = TRUE;
}
} else {
if (filter->buffer_position >= out_width) {
filter->buffer_position = 0;
rollover_occurred = TRUE;
}
}
/* Copy accumulated buffer to output */
memcpy (map_out.data, filter->line_buffer, map_out.size);
gst_buffer_unmap (inbuf, &map_in);
gst_buffer_unmap (outbuf, &map_out);
/* Emit rollover signal if buffer wrapped around */
if (rollover_occurred) {
GST_DEBUG_OBJECT (filter, "Rollover occurred at frame %lu",
(unsigned long) filter->frame_count);
g_signal_emit (filter, gst_linescan_signals[SIGNAL_ROLLOVER], 0, outbuf);
}
filter->frame_count++;
GST_LOG_OBJECT (filter, "Processed frame %lu, buffer position: %d",
(unsigned long) filter->frame_count, filter->buffer_position);
return GST_FLOW_OK;
}
static void
gst_linescan_reset (GstLinescan * filter)
{
if (filter->line_buffer) {
g_free (filter->line_buffer);
filter->line_buffer = NULL;
}
filter->buffer_position = 0;
filter->frame_count = 0;
if (filter->video_info_valid) {
gst_linescan_allocate_buffer (filter);
}
}
static gboolean
plugin_init (GstPlugin * plugin)
{
return gst_element_register (plugin, "linescan", GST_RANK_NONE,
GST_TYPE_LINESCAN);
}
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
GST_VERSION_MINOR,
linescan,
"Line scan camera simulator that extracts rows/columns from frames",
plugin_init, GST_PACKAGE_VERSION, GST_PACKAGE_LICENSE, GST_PACKAGE_NAME,
GST_PACKAGE_ORIGIN)