mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-27 18:50:48 +00:00
e575e4eda8
This way we can pass the pad name instead of the element for tracing which helps identifying which v4l2object is used withing M2M element like decoder, encoder and transform. For the reference, pads are name <parent-name>:<pad-name>.
646 lines
21 KiB
C
646 lines
21 KiB
C
/* GStreamer
|
|
*
|
|
* Copyright (C) 2009 Texas Instruments, Inc - http://www.ti.com/
|
|
*
|
|
* Description: V4L2 sink element
|
|
* Created on: Jul 2, 2009
|
|
* Author: Rob Clark <rob@ti.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., 51 Franklin St, Fifth Floor,
|
|
* Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
/**
|
|
* SECTION:element-v4l2sink
|
|
*
|
|
* v4l2sink can be used to display video to v4l2 devices (screen overlays
|
|
* provided by the graphics hardware, tv-out, etc)
|
|
*
|
|
* <refsect2>
|
|
* <title>Example launch lines</title>
|
|
* |[
|
|
* gst-launch-1.0 videotestsrc ! v4l2sink device=/dev/video1
|
|
* ]| This pipeline displays a test pattern on /dev/video1
|
|
* |[
|
|
* gst-launch-1.0 -v videotestsrc ! navigationtest ! v4l2sink
|
|
* ]| A pipeline to test navigation events.
|
|
* While moving the mouse pointer over the test signal you will see a black box
|
|
* following the mouse pointer. If you press the mouse button somewhere on the
|
|
* video and release it somewhere else a green box will appear where you pressed
|
|
* the button and a red one where you released it. (The navigationtest element
|
|
* is part of gst-plugins-good.) You can observe here that even if the images
|
|
* are scaled through hardware the pointer coordinates are converted back to the
|
|
* original video frame geometry so that the box can be drawn to the correct
|
|
* position. This also handles borders correctly, limiting coordinates to the
|
|
* image area
|
|
* </refsect2>
|
|
*/
|
|
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include "gst/video/gstvideometa.h"
|
|
|
|
#include "gstv4l2colorbalance.h"
|
|
#include "gstv4l2tuner.h"
|
|
#include "gstv4l2vidorient.h"
|
|
|
|
#include "gstv4l2sink.h"
|
|
#include "gst/gst-i18n-plugin.h"
|
|
|
|
#include <string.h>
|
|
|
|
GST_DEBUG_CATEGORY (v4l2sink_debug);
|
|
#define GST_CAT_DEFAULT v4l2sink_debug
|
|
|
|
#define DEFAULT_PROP_DEVICE "/dev/video1"
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
V4L2_STD_OBJECT_PROPS,
|
|
PROP_OVERLAY_TOP,
|
|
PROP_OVERLAY_LEFT,
|
|
PROP_OVERLAY_WIDTH,
|
|
PROP_OVERLAY_HEIGHT,
|
|
PROP_CROP_TOP,
|
|
PROP_CROP_LEFT,
|
|
PROP_CROP_WIDTH,
|
|
PROP_CROP_HEIGHT,
|
|
};
|
|
|
|
|
|
GST_IMPLEMENT_V4L2_COLOR_BALANCE_METHODS (GstV4l2Sink, gst_v4l2sink);
|
|
GST_IMPLEMENT_V4L2_TUNER_METHODS (GstV4l2Sink, gst_v4l2sink);
|
|
GST_IMPLEMENT_V4L2_VIDORIENT_METHODS (GstV4l2Sink, gst_v4l2sink);
|
|
|
|
#define gst_v4l2sink_parent_class parent_class
|
|
G_DEFINE_TYPE_WITH_CODE (GstV4l2Sink, gst_v4l2sink, GST_TYPE_VIDEO_SINK,
|
|
G_IMPLEMENT_INTERFACE (GST_TYPE_TUNER, gst_v4l2sink_tuner_interface_init);
|
|
G_IMPLEMENT_INTERFACE (GST_TYPE_COLOR_BALANCE,
|
|
gst_v4l2sink_color_balance_interface_init);
|
|
G_IMPLEMENT_INTERFACE (GST_TYPE_VIDEO_ORIENTATION,
|
|
gst_v4l2sink_video_orientation_interface_init));
|
|
|
|
|
|
static void gst_v4l2sink_finalize (GstV4l2Sink * v4l2sink);
|
|
|
|
/* GObject methods: */
|
|
static void gst_v4l2sink_set_property (GObject * object, guint prop_id,
|
|
const GValue * value, GParamSpec * pspec);
|
|
static void gst_v4l2sink_get_property (GObject * object, guint prop_id,
|
|
GValue * value, GParamSpec * pspec);
|
|
|
|
/* GstElement methods: */
|
|
static GstStateChangeReturn gst_v4l2sink_change_state (GstElement * element,
|
|
GstStateChange transition);
|
|
|
|
/* GstBaseSink methods: */
|
|
static gboolean gst_v4l2sink_propose_allocation (GstBaseSink * bsink,
|
|
GstQuery * query);
|
|
static GstCaps *gst_v4l2sink_get_caps (GstBaseSink * bsink, GstCaps * filter);
|
|
static gboolean gst_v4l2sink_set_caps (GstBaseSink * bsink, GstCaps * caps);
|
|
static GstFlowReturn gst_v4l2sink_show_frame (GstVideoSink * bsink,
|
|
GstBuffer * buf);
|
|
static gboolean gst_v4l2sink_unlock (GstBaseSink * sink);
|
|
static gboolean gst_v4l2sink_unlock_stop (GstBaseSink * sink);
|
|
|
|
static void
|
|
gst_v4l2sink_class_init (GstV4l2SinkClass * klass)
|
|
{
|
|
GObjectClass *gobject_class;
|
|
GstElementClass *element_class;
|
|
GstBaseSinkClass *basesink_class;
|
|
GstVideoSinkClass *videosink_class;
|
|
|
|
gobject_class = G_OBJECT_CLASS (klass);
|
|
element_class = GST_ELEMENT_CLASS (klass);
|
|
basesink_class = GST_BASE_SINK_CLASS (klass);
|
|
videosink_class = GST_VIDEO_SINK_CLASS (klass);
|
|
|
|
gobject_class->finalize = (GObjectFinalizeFunc) gst_v4l2sink_finalize;
|
|
gobject_class->set_property = gst_v4l2sink_set_property;
|
|
gobject_class->get_property = gst_v4l2sink_get_property;
|
|
|
|
element_class->change_state = gst_v4l2sink_change_state;
|
|
|
|
gst_v4l2_object_install_properties_helper (gobject_class,
|
|
DEFAULT_PROP_DEVICE);
|
|
|
|
g_object_class_install_property (gobject_class, PROP_OVERLAY_TOP,
|
|
g_param_spec_int ("overlay-top", "Overlay top",
|
|
"The topmost (y) coordinate of the video overlay; top left corner of screen is 0,0",
|
|
G_MININT, G_MAXINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
g_object_class_install_property (gobject_class, PROP_OVERLAY_LEFT,
|
|
g_param_spec_int ("overlay-left", "Overlay left",
|
|
"The leftmost (x) coordinate of the video overlay; top left corner of screen is 0,0",
|
|
G_MININT, G_MAXINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
g_object_class_install_property (gobject_class, PROP_OVERLAY_WIDTH,
|
|
g_param_spec_uint ("overlay-width", "Overlay width",
|
|
"The width of the video overlay; default is equal to negotiated image width",
|
|
0, G_MAXUINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
g_object_class_install_property (gobject_class, PROP_OVERLAY_HEIGHT,
|
|
g_param_spec_uint ("overlay-height", "Overlay height",
|
|
"The height of the video overlay; default is equal to negotiated image height",
|
|
0, G_MAXUINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_CROP_TOP,
|
|
g_param_spec_int ("crop-top", "Crop top",
|
|
"The topmost (y) coordinate of the video crop; top left corner of image is 0,0",
|
|
0x80000000, 0x7fffffff, 0, G_PARAM_READWRITE));
|
|
g_object_class_install_property (gobject_class, PROP_CROP_LEFT,
|
|
g_param_spec_int ("crop-left", "Crop left",
|
|
"The leftmost (x) coordinate of the video crop; top left corner of image is 0,0",
|
|
0x80000000, 0x7fffffff, 0, G_PARAM_READWRITE));
|
|
g_object_class_install_property (gobject_class, PROP_CROP_WIDTH,
|
|
g_param_spec_uint ("crop-width", "Crop width",
|
|
"The width of the video crop; default is equal to negotiated image width",
|
|
0, 0xffffffff, 0, G_PARAM_READWRITE));
|
|
g_object_class_install_property (gobject_class, PROP_CROP_HEIGHT,
|
|
g_param_spec_uint ("crop-height", "Crop height",
|
|
"The height of the video crop; default is equal to negotiated image height",
|
|
0, 0xffffffff, 0, G_PARAM_READWRITE));
|
|
|
|
gst_element_class_set_static_metadata (element_class,
|
|
"Video (video4linux2) Sink", "Sink/Video",
|
|
"Displays frames on a video4linux2 device", "Rob Clark <rob@ti.com>,");
|
|
|
|
gst_element_class_add_pad_template (element_class,
|
|
gst_pad_template_new ("sink", GST_PAD_SINK, GST_PAD_ALWAYS,
|
|
gst_v4l2_object_get_all_caps ()));
|
|
|
|
basesink_class->get_caps = GST_DEBUG_FUNCPTR (gst_v4l2sink_get_caps);
|
|
basesink_class->set_caps = GST_DEBUG_FUNCPTR (gst_v4l2sink_set_caps);
|
|
basesink_class->propose_allocation =
|
|
GST_DEBUG_FUNCPTR (gst_v4l2sink_propose_allocation);
|
|
basesink_class->unlock = GST_DEBUG_FUNCPTR (gst_v4l2sink_unlock);
|
|
basesink_class->unlock_stop = GST_DEBUG_FUNCPTR (gst_v4l2sink_unlock_stop);
|
|
|
|
videosink_class->show_frame = GST_DEBUG_FUNCPTR (gst_v4l2sink_show_frame);
|
|
|
|
klass->v4l2_class_devices = NULL;
|
|
|
|
GST_DEBUG_CATEGORY_INIT (v4l2sink_debug, "v4l2sink", 0, "V4L2 sink element");
|
|
|
|
}
|
|
|
|
static void
|
|
gst_v4l2sink_init (GstV4l2Sink * v4l2sink)
|
|
{
|
|
v4l2sink->v4l2object = gst_v4l2_object_new (GST_ELEMENT (v4l2sink),
|
|
GST_OBJECT (GST_BASE_SINK_PAD (v4l2sink)), V4L2_BUF_TYPE_VIDEO_OUTPUT,
|
|
DEFAULT_PROP_DEVICE, gst_v4l2_get_output, gst_v4l2_set_output, NULL);
|
|
|
|
/* same default value for video output device as is used for
|
|
* v4l2src/capture is no good.. so lets set a saner default
|
|
* (which can be overridden by the one creating the v4l2sink
|
|
* after the constructor returns)
|
|
*/
|
|
g_object_set (v4l2sink, "device", "/dev/video1", NULL);
|
|
|
|
v4l2sink->overlay_fields_set = 0;
|
|
v4l2sink->crop_fields_set = 0;
|
|
}
|
|
|
|
|
|
static void
|
|
gst_v4l2sink_finalize (GstV4l2Sink * v4l2sink)
|
|
{
|
|
gst_v4l2_object_destroy (v4l2sink->v4l2object);
|
|
|
|
G_OBJECT_CLASS (parent_class)->finalize ((GObject *) (v4l2sink));
|
|
}
|
|
|
|
|
|
/*
|
|
* flags to indicate which overlay/crop properties the user has set (and
|
|
* therefore which ones should override the defaults from the driver)
|
|
*/
|
|
enum
|
|
{
|
|
RECT_TOP_SET = 0x01,
|
|
RECT_LEFT_SET = 0x02,
|
|
RECT_WIDTH_SET = 0x04,
|
|
RECT_HEIGHT_SET = 0x08
|
|
};
|
|
|
|
static void
|
|
gst_v4l2sink_sync_overlay_fields (GstV4l2Sink * v4l2sink)
|
|
{
|
|
if (!v4l2sink->overlay_fields_set)
|
|
return;
|
|
|
|
if (GST_V4L2_IS_OPEN (v4l2sink->v4l2object)) {
|
|
|
|
GstV4l2Object *obj = v4l2sink->v4l2object;
|
|
struct v4l2_format format;
|
|
|
|
memset (&format, 0x00, sizeof (struct v4l2_format));
|
|
if (obj->device_caps & V4L2_CAP_VIDEO_OUTPUT_OVERLAY)
|
|
format.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY;
|
|
else
|
|
format.type = V4L2_BUF_TYPE_VIDEO_OVERLAY;
|
|
|
|
if (obj->ioctl (obj->video_fd, VIDIOC_G_FMT, &format) < 0) {
|
|
GST_WARNING_OBJECT (v4l2sink, "VIDIOC_G_FMT failed");
|
|
return;
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (v4l2sink,
|
|
"setting overlay: overlay_fields_set=0x%02x, top=%d, left=%d, width=%d, height=%d",
|
|
v4l2sink->overlay_fields_set,
|
|
v4l2sink->overlay.top, v4l2sink->overlay.left,
|
|
v4l2sink->overlay.width, v4l2sink->overlay.height);
|
|
|
|
if (v4l2sink->overlay_fields_set & RECT_TOP_SET)
|
|
format.fmt.win.w.top = v4l2sink->overlay.top;
|
|
if (v4l2sink->overlay_fields_set & RECT_LEFT_SET)
|
|
format.fmt.win.w.left = v4l2sink->overlay.left;
|
|
if (v4l2sink->overlay_fields_set & RECT_WIDTH_SET)
|
|
format.fmt.win.w.width = v4l2sink->overlay.width;
|
|
if (v4l2sink->overlay_fields_set & RECT_HEIGHT_SET)
|
|
format.fmt.win.w.height = v4l2sink->overlay.height;
|
|
|
|
if (obj->ioctl (obj->video_fd, VIDIOC_S_FMT, &format) < 0) {
|
|
GST_WARNING_OBJECT (v4l2sink, "VIDIOC_S_FMT failed");
|
|
return;
|
|
}
|
|
|
|
v4l2sink->overlay_fields_set = 0;
|
|
v4l2sink->overlay = format.fmt.win.w;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_v4l2sink_sync_crop_fields (GstV4l2Sink * v4l2sink)
|
|
{
|
|
if (!v4l2sink->crop_fields_set)
|
|
return;
|
|
|
|
if (GST_V4L2_IS_OPEN (v4l2sink->v4l2object)) {
|
|
|
|
GstV4l2Object *obj = v4l2sink->v4l2object;
|
|
struct v4l2_crop crop;
|
|
|
|
memset (&crop, 0x00, sizeof (struct v4l2_crop));
|
|
crop.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
|
|
|
|
if (obj->ioctl (obj->video_fd, VIDIOC_G_CROP, &crop) < 0) {
|
|
GST_WARNING_OBJECT (v4l2sink, "VIDIOC_G_CROP failed");
|
|
return;
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (v4l2sink,
|
|
"setting crop: crop_fields_set=0x%02x, top=%d, left=%d, width=%d, height=%d",
|
|
v4l2sink->crop_fields_set,
|
|
v4l2sink->crop.top, v4l2sink->crop.left,
|
|
v4l2sink->crop.width, v4l2sink->crop.height);
|
|
|
|
if (v4l2sink->crop_fields_set & RECT_TOP_SET)
|
|
crop.c.top = v4l2sink->crop.top;
|
|
if (v4l2sink->crop_fields_set & RECT_LEFT_SET)
|
|
crop.c.left = v4l2sink->crop.left;
|
|
if (v4l2sink->crop_fields_set & RECT_WIDTH_SET)
|
|
crop.c.width = v4l2sink->crop.width;
|
|
if (v4l2sink->crop_fields_set & RECT_HEIGHT_SET)
|
|
crop.c.height = v4l2sink->crop.height;
|
|
|
|
if (obj->ioctl (obj->video_fd, VIDIOC_S_CROP, &crop) < 0) {
|
|
GST_WARNING_OBJECT (v4l2sink, "VIDIOC_S_CROP failed");
|
|
return;
|
|
}
|
|
|
|
if (obj->ioctl (obj->video_fd, VIDIOC_G_CROP, &crop) < 0) {
|
|
GST_WARNING_OBJECT (v4l2sink, "VIDIOC_G_CROP failed");
|
|
return;
|
|
}
|
|
|
|
v4l2sink->crop_fields_set = 0;
|
|
v4l2sink->crop = crop.c;
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
gst_v4l2sink_set_property (GObject * object,
|
|
guint prop_id, const GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstV4l2Sink *v4l2sink = GST_V4L2SINK (object);
|
|
|
|
if (!gst_v4l2_object_set_property_helper (v4l2sink->v4l2object,
|
|
prop_id, value, pspec)) {
|
|
switch (prop_id) {
|
|
case PROP_OVERLAY_TOP:
|
|
v4l2sink->overlay.top = g_value_get_int (value);
|
|
v4l2sink->overlay_fields_set |= RECT_TOP_SET;
|
|
gst_v4l2sink_sync_overlay_fields (v4l2sink);
|
|
break;
|
|
case PROP_OVERLAY_LEFT:
|
|
v4l2sink->overlay.left = g_value_get_int (value);
|
|
v4l2sink->overlay_fields_set |= RECT_LEFT_SET;
|
|
gst_v4l2sink_sync_overlay_fields (v4l2sink);
|
|
break;
|
|
case PROP_OVERLAY_WIDTH:
|
|
v4l2sink->overlay.width = g_value_get_uint (value);
|
|
v4l2sink->overlay_fields_set |= RECT_WIDTH_SET;
|
|
gst_v4l2sink_sync_overlay_fields (v4l2sink);
|
|
break;
|
|
case PROP_OVERLAY_HEIGHT:
|
|
v4l2sink->overlay.height = g_value_get_uint (value);
|
|
v4l2sink->overlay_fields_set |= RECT_HEIGHT_SET;
|
|
gst_v4l2sink_sync_overlay_fields (v4l2sink);
|
|
break;
|
|
case PROP_CROP_TOP:
|
|
v4l2sink->crop.top = g_value_get_int (value);
|
|
v4l2sink->crop_fields_set |= RECT_TOP_SET;
|
|
gst_v4l2sink_sync_crop_fields (v4l2sink);
|
|
break;
|
|
case PROP_CROP_LEFT:
|
|
v4l2sink->crop.left = g_value_get_int (value);
|
|
v4l2sink->crop_fields_set |= RECT_LEFT_SET;
|
|
gst_v4l2sink_sync_crop_fields (v4l2sink);
|
|
break;
|
|
case PROP_CROP_WIDTH:
|
|
v4l2sink->crop.width = g_value_get_uint (value);
|
|
v4l2sink->crop_fields_set |= RECT_WIDTH_SET;
|
|
gst_v4l2sink_sync_crop_fields (v4l2sink);
|
|
break;
|
|
case PROP_CROP_HEIGHT:
|
|
v4l2sink->crop.height = g_value_get_uint (value);
|
|
v4l2sink->crop_fields_set |= RECT_HEIGHT_SET;
|
|
gst_v4l2sink_sync_crop_fields (v4l2sink);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
gst_v4l2sink_get_property (GObject * object,
|
|
guint prop_id, GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstV4l2Sink *v4l2sink = GST_V4L2SINK (object);
|
|
|
|
if (!gst_v4l2_object_get_property_helper (v4l2sink->v4l2object,
|
|
prop_id, value, pspec)) {
|
|
switch (prop_id) {
|
|
case PROP_OVERLAY_TOP:
|
|
g_value_set_int (value, v4l2sink->overlay.top);
|
|
break;
|
|
case PROP_OVERLAY_LEFT:
|
|
g_value_set_int (value, v4l2sink->overlay.left);
|
|
break;
|
|
case PROP_OVERLAY_WIDTH:
|
|
g_value_set_uint (value, v4l2sink->overlay.width);
|
|
break;
|
|
case PROP_OVERLAY_HEIGHT:
|
|
g_value_set_uint (value, v4l2sink->overlay.height);
|
|
break;
|
|
case PROP_CROP_TOP:
|
|
g_value_set_int (value, v4l2sink->crop.top);
|
|
break;
|
|
case PROP_CROP_LEFT:
|
|
g_value_set_int (value, v4l2sink->crop.left);
|
|
break;
|
|
case PROP_CROP_WIDTH:
|
|
g_value_set_uint (value, v4l2sink->crop.width);
|
|
break;
|
|
case PROP_CROP_HEIGHT:
|
|
g_value_set_uint (value, v4l2sink->crop.height);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static GstStateChangeReturn
|
|
gst_v4l2sink_change_state (GstElement * element, GstStateChange transition)
|
|
{
|
|
GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
|
|
GstV4l2Sink *v4l2sink = GST_V4L2SINK (element);
|
|
|
|
GST_DEBUG_OBJECT (v4l2sink, "%d -> %d",
|
|
GST_STATE_TRANSITION_CURRENT (transition),
|
|
GST_STATE_TRANSITION_NEXT (transition));
|
|
|
|
switch (transition) {
|
|
case GST_STATE_CHANGE_NULL_TO_READY:
|
|
/* open the device */
|
|
if (!gst_v4l2_object_open (v4l2sink->v4l2object))
|
|
return GST_STATE_CHANGE_FAILURE;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
|
|
|
|
switch (transition) {
|
|
case GST_STATE_CHANGE_PAUSED_TO_READY:
|
|
if (!gst_v4l2_object_stop (v4l2sink->v4l2object))
|
|
return GST_STATE_CHANGE_FAILURE;
|
|
break;
|
|
case GST_STATE_CHANGE_READY_TO_NULL:
|
|
/* we need to call stop here too */
|
|
if (!gst_v4l2_object_stop (v4l2sink->v4l2object))
|
|
return GST_STATE_CHANGE_FAILURE;
|
|
/* close the device */
|
|
if (!gst_v4l2_object_close (v4l2sink->v4l2object))
|
|
return GST_STATE_CHANGE_FAILURE;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static GstCaps *
|
|
gst_v4l2sink_get_caps (GstBaseSink * bsink, GstCaps * filter)
|
|
{
|
|
GstV4l2Sink *v4l2sink = GST_V4L2SINK (bsink);
|
|
|
|
if (!GST_V4L2_IS_OPEN (v4l2sink->v4l2object)) {
|
|
/* FIXME: copy? */
|
|
GST_DEBUG_OBJECT (v4l2sink, "device is not open");
|
|
return gst_pad_get_pad_template_caps (GST_BASE_SINK_PAD (v4l2sink));
|
|
}
|
|
|
|
|
|
return gst_v4l2_object_get_caps (v4l2sink->v4l2object, filter);
|
|
}
|
|
|
|
static gboolean
|
|
gst_v4l2sink_set_caps (GstBaseSink * bsink, GstCaps * caps)
|
|
{
|
|
GstV4l2Error error = GST_V4L2_ERROR_INIT;
|
|
GstV4l2Sink *v4l2sink = GST_V4L2SINK (bsink);
|
|
GstV4l2Object *obj = v4l2sink->v4l2object;
|
|
|
|
GST_DEBUG_OBJECT (v4l2sink, "caps: %" GST_PTR_FORMAT, caps);
|
|
|
|
if (!GST_V4L2_IS_OPEN (obj)) {
|
|
GST_DEBUG_OBJECT (v4l2sink, "device is not open");
|
|
return FALSE;
|
|
}
|
|
|
|
/* make sure the caps changed before doing anything */
|
|
if (gst_v4l2_object_caps_equal (obj, caps))
|
|
return TRUE;
|
|
|
|
if (!gst_v4l2_object_stop (obj))
|
|
goto stop_failed;
|
|
|
|
if (!gst_v4l2_object_set_format (obj, caps, &error))
|
|
goto invalid_format;
|
|
|
|
gst_v4l2sink_sync_overlay_fields (v4l2sink);
|
|
gst_v4l2sink_sync_crop_fields (v4l2sink);
|
|
|
|
GST_INFO_OBJECT (v4l2sink, "outputting buffers via mode %u", obj->mode);
|
|
|
|
v4l2sink->video_width = GST_V4L2_WIDTH (obj);
|
|
v4l2sink->video_height = GST_V4L2_HEIGHT (obj);
|
|
|
|
/* TODO: videosink width/height should be scaled according to
|
|
* pixel-aspect-ratio
|
|
*/
|
|
GST_VIDEO_SINK_WIDTH (v4l2sink) = v4l2sink->video_width;
|
|
GST_VIDEO_SINK_HEIGHT (v4l2sink) = v4l2sink->video_height;
|
|
|
|
return TRUE;
|
|
|
|
/* ERRORS */
|
|
stop_failed:
|
|
{
|
|
GST_DEBUG_OBJECT (v4l2sink, "failed to stop streaming");
|
|
return FALSE;
|
|
}
|
|
invalid_format:
|
|
{
|
|
/* error already posted */
|
|
gst_v4l2_error (v4l2sink, &error);
|
|
GST_DEBUG_OBJECT (v4l2sink, "can't set format");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gst_v4l2sink_propose_allocation (GstBaseSink * bsink, GstQuery * query)
|
|
{
|
|
GstV4l2Sink *v4l2sink = GST_V4L2SINK (bsink);
|
|
gboolean last_sample_enabled;
|
|
|
|
if (!gst_v4l2_object_propose_allocation (v4l2sink->v4l2object, query))
|
|
return FALSE;
|
|
|
|
g_object_get (bsink, "enable-last-sample", &last_sample_enabled, NULL);
|
|
|
|
if (last_sample_enabled) {
|
|
GstBufferPool *pool;
|
|
guint size, min, max;
|
|
|
|
gst_query_parse_nth_allocation_pool (query, 0, &pool, &size, &min, &max);
|
|
|
|
/* we need 1 more, otherwise we'll run out of buffers at preroll */
|
|
min++;
|
|
if (max < min)
|
|
max = min;
|
|
|
|
gst_query_set_nth_allocation_pool (query, 0, pool, size, min, max);
|
|
gst_object_unref (pool);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* called after A/V sync to render frame */
|
|
static GstFlowReturn
|
|
gst_v4l2sink_show_frame (GstVideoSink * vsink, GstBuffer * buf)
|
|
{
|
|
GstFlowReturn ret;
|
|
GstV4l2Sink *v4l2sink = GST_V4L2SINK (vsink);
|
|
GstV4l2Object *obj = v4l2sink->v4l2object;
|
|
GstBufferPool *bpool = GST_BUFFER_POOL (obj->pool);
|
|
|
|
GST_DEBUG_OBJECT (v4l2sink, "render buffer: %p", buf);
|
|
|
|
if (G_UNLIKELY (obj->pool == NULL))
|
|
goto not_negotiated;
|
|
|
|
if (G_UNLIKELY (!gst_buffer_pool_is_active (bpool))) {
|
|
GstStructure *config;
|
|
|
|
/* this pool was not activated, configure and activate */
|
|
GST_DEBUG_OBJECT (v4l2sink, "activating pool");
|
|
|
|
config = gst_buffer_pool_get_config (bpool);
|
|
gst_buffer_pool_config_add_option (config,
|
|
GST_BUFFER_POOL_OPTION_VIDEO_META);
|
|
gst_buffer_pool_set_config (bpool, config);
|
|
|
|
if (!gst_buffer_pool_set_active (bpool, TRUE))
|
|
goto activate_failed;
|
|
}
|
|
|
|
gst_buffer_ref (buf);
|
|
again:
|
|
ret = gst_v4l2_buffer_pool_process (GST_V4L2_BUFFER_POOL_CAST (obj->pool),
|
|
&buf);
|
|
if (ret == GST_FLOW_FLUSHING) {
|
|
ret = gst_base_sink_wait_preroll (GST_BASE_SINK (vsink));
|
|
if (ret == GST_FLOW_OK)
|
|
goto again;
|
|
}
|
|
gst_buffer_unref (buf);
|
|
|
|
return ret;
|
|
|
|
/* ERRORS */
|
|
not_negotiated:
|
|
{
|
|
GST_ERROR_OBJECT (v4l2sink, "not negotiated");
|
|
return GST_FLOW_NOT_NEGOTIATED;
|
|
}
|
|
activate_failed:
|
|
{
|
|
GST_ELEMENT_ERROR (v4l2sink, RESOURCE, SETTINGS,
|
|
(_("Failed to allocated required memory.")),
|
|
("Buffer pool activation failed"));
|
|
return GST_FLOW_ERROR;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gst_v4l2sink_unlock (GstBaseSink * sink)
|
|
{
|
|
GstV4l2Sink *v4l2sink = GST_V4L2SINK (sink);
|
|
return gst_v4l2_object_unlock (v4l2sink->v4l2object);
|
|
}
|
|
|
|
static gboolean
|
|
gst_v4l2sink_unlock_stop (GstBaseSink * sink)
|
|
{
|
|
GstV4l2Sink *v4l2sink = GST_V4L2SINK (sink);
|
|
return gst_v4l2_object_unlock_stop (v4l2sink->v4l2object);
|
|
}
|