380 lines
11 KiB
C
380 lines
11 KiB
C
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <fcntl.h>
|
|
#include <sys/ioctl.h>
|
|
#include <unistd.h>
|
|
|
|
#include <gst/gst.h>
|
|
#include <glib.h>
|
|
|
|
#include <linux/media.h>
|
|
#include <linux/videodev2.h>
|
|
|
|
#include "utility.h"
|
|
#include "pinesrcbin.h"
|
|
|
|
#define GST_TYPE_PINESRCBIN (gst_pinesrcbin_get_type())
|
|
#define GST_PINESRCBIN(obj) \
|
|
(G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_PINESRCBIN, GstPineSrcBin))
|
|
#define GST_PINE64SRCBIN_CLASS(klass) \
|
|
(G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_PINESRCBIN, GstPineSrcBinClass))
|
|
#define GST_IS_PINESRCBIN(obj) \
|
|
(G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_PINESRCBIN))
|
|
#define GST_IS_PINESRCBIN_CLASS(klass) \
|
|
(G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_PINESRCBIN))
|
|
#define GST_PINESRCBIN_GET_CLASS(obj) \
|
|
(G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_PINESRCBIN, GstPineSrcBinClass))
|
|
#define gst_pinesrcbin_parent_class parent_class
|
|
#define DEFAULT_SYNC TRUE
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_CAPS,
|
|
PROP_SYNC,
|
|
PROP_NUM_BUFFERS,
|
|
};
|
|
|
|
|
|
typedef struct _GstPineSrcBinClass
|
|
{
|
|
GstBinClass parent_class;
|
|
|
|
/*< private > */
|
|
/* virtual methods for subclasses */
|
|
void (*configure) (GstPineSrcBin * self, GstElement * kid);
|
|
GstElement *(*create_fake_element) (GstPineSrcBin * bin);
|
|
} GstPineSrcBinClass;
|
|
|
|
|
|
G_DEFINE_TYPE (GstPineSrcBin, gst_pinesrcbin, GST_TYPE_BIN);
|
|
|
|
|
|
static void
|
|
gst_pinesrcbin_clear_kid (GstPineSrcBin * self)
|
|
{
|
|
if (self->kid) {
|
|
gst_element_set_state (self->kid, GST_STATE_NULL);
|
|
gst_element_set_state (self->filter, GST_STATE_NULL);
|
|
gst_bin_remove_many (GST_BIN (self), self->kid, self->filter, NULL);
|
|
self->kid = NULL;
|
|
self->filter = NULL;
|
|
}
|
|
}
|
|
|
|
static GstElement *
|
|
gst_pinesrcbin_create_fake_element_default (GstPineSrcBin * self)
|
|
{
|
|
GstElement *fake;
|
|
gchar dummy_factory[10], dummy_name[20];
|
|
|
|
sprintf (dummy_factory, "fake%s", self->type_klass_lc);
|
|
sprintf (dummy_name, "fake-%s-%s", self->media_klass_lc, self->type_klass_lc);
|
|
fake = gst_element_factory_make (dummy_factory, dummy_name);
|
|
g_object_set (fake, "sync", self->sync, NULL);
|
|
|
|
return fake;
|
|
}
|
|
|
|
static GstElement *
|
|
gst_pinesrcbin_create_fake_element (GstPineSrcBin * self)
|
|
{
|
|
GstPineSrcBinClass *klass = GST_PINESRCBIN_GET_CLASS (self);
|
|
GstElement *fake;
|
|
|
|
if (klass->create_fake_element)
|
|
fake = klass->create_fake_element (self);
|
|
else
|
|
fake = gst_pinesrcbin_create_fake_element_default (self);
|
|
|
|
return fake;
|
|
}
|
|
|
|
static gboolean
|
|
gst_pinesrcbin_attach_ghost_pad (GstPineSrcBin * self)
|
|
{
|
|
GstPad *target = gst_element_get_static_pad (self->filter, self->type_klass_lc);
|
|
gboolean res = gst_ghost_pad_set_target (GST_GHOST_PAD (self->pad), target);
|
|
gst_object_unref (target);
|
|
return res;
|
|
}
|
|
|
|
static void
|
|
gst_pinesrcbin_reset (GstPineSrcBin * self)
|
|
{
|
|
gst_pinesrcbin_clear_kid (self);
|
|
|
|
/* placeholder element */
|
|
// self->kid = gst_pinesrcbin_create_fake_element (self);
|
|
self->kid = gst_pinesrcbin_create_fake_element (self);
|
|
self->filter = gst_element_factory_make ("capsfilter", NULL);
|
|
g_object_set (self->filter, "sync", self->sync, NULL);
|
|
g_object_set (self->filter, "caps", self->filter_caps, NULL);
|
|
gst_bin_add_many (GST_BIN (self), self->kid, self->filter, NULL);
|
|
gst_element_link(self->kid, self->filter);
|
|
|
|
gst_pinesrcbin_attach_ghost_pad (self);
|
|
}
|
|
|
|
static GstStaticCaps raw_video_caps = GST_STATIC_CAPS ("video/x-raw");
|
|
static void
|
|
gst_pinesrcbin_constructed (GObject * object)
|
|
{
|
|
int width, height;
|
|
const char *format;
|
|
struct v4l2_fract interval;
|
|
GstPineSrcBin *self = GST_PINESRCBIN (object);
|
|
if (G_OBJECT_CLASS (parent_class)->constructed)
|
|
G_OBJECT_CLASS (parent_class)->constructed (object);
|
|
self->type_klass = (self->flag == GST_ELEMENT_FLAG_SINK) ? "Sink" : "Source";
|
|
self->filter_caps = gst_static_caps_get (&raw_video_caps);
|
|
gst_pinesrcbin_get_gst_caps_data("rear", &width, &height, &format, &interval);
|
|
self->filter_caps = gst_caps_new_simple ("video/x-raw",
|
|
"format", G_TYPE_STRING, format,
|
|
/* we "flip" interval to get frame rate */
|
|
"framerate", GST_TYPE_FRACTION, interval.denominator, interval.numerator,
|
|
"width", G_TYPE_INT, width,
|
|
"height", G_TYPE_INT, height,
|
|
NULL);
|
|
|
|
self->type_klass_lc = (self->flag == GST_ELEMENT_FLAG_SINK) ? "sink" : "src";
|
|
self->media_klass_lc = "video";
|
|
self->pad = gst_ghost_pad_new_no_target (self->type_klass_lc,
|
|
(self->flag == GST_ELEMENT_FLAG_SINK) ? GST_PAD_SINK : GST_PAD_SRC);
|
|
gst_element_add_pad (GST_ELEMENT (self), self->pad);
|
|
gst_pinesrcbin_reset (self);
|
|
GST_OBJECT_FLAG_SET (self, self->flag);
|
|
gst_bin_set_suppressed_flags (GST_BIN (self),
|
|
GST_ELEMENT_FLAG_SOURCE | GST_ELEMENT_FLAG_SINK);
|
|
}
|
|
|
|
static void
|
|
gst_pinesrcbin_dispose (GObject * object)
|
|
{
|
|
GstPineSrcBin *self = GST_PINESRCBIN (object);
|
|
|
|
gst_pinesrcbin_clear_kid (self);
|
|
|
|
if (self->filter_caps)
|
|
gst_caps_unref (self->filter_caps);
|
|
self->filter_caps = NULL;
|
|
|
|
G_OBJECT_CLASS (parent_class)->dispose ((GObject *) self);
|
|
}
|
|
|
|
static void
|
|
gst_pinesrcbin_set_property (GObject * object, guint prop_id,
|
|
const GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstPineSrcBin *self = GST_PINESRCBIN (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_CAPS:
|
|
if (self->filter_caps)
|
|
gst_caps_unref (self->filter_caps);
|
|
self->filter_caps = gst_caps_copy (gst_value_get_caps (value));
|
|
break;
|
|
case PROP_SYNC:
|
|
self->sync = g_value_get_boolean (value);
|
|
if (self->kid && self->has_sync)
|
|
g_object_set_property (G_OBJECT (self->kid), pspec->name, value);
|
|
break;
|
|
case PROP_NUM_BUFFERS:
|
|
self->num_buffers = g_value_get_int (value);
|
|
if (self->kid)
|
|
g_object_set_property (G_OBJECT (self->kid), pspec->name, value);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_pinesrcbin_get_property (GObject * object, guint prop_id,
|
|
GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstPineSrcBin *self = GST_PINESRCBIN (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_CAPS:
|
|
gst_value_set_caps (value, self->filter_caps);
|
|
break;
|
|
case PROP_SYNC:
|
|
g_value_set_boolean (value, self->sync);
|
|
break;
|
|
case PROP_NUM_BUFFERS:
|
|
g_value_set_int (value, self->num_buffers);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gst_pinesrcbin_detect (GstPineSrcBin * self)
|
|
{
|
|
GstElement *kid, *filter;
|
|
GstPad *target;
|
|
GstPineSrcBinClass *klass = GST_PINESRCBIN_GET_CLASS (self);
|
|
|
|
gst_pinesrcbin_clear_kid (self);
|
|
|
|
GST_DEBUG_OBJECT (self, "Creating v4l2src element");
|
|
kid = gst_element_factory_make ("v4l2src", NULL);
|
|
GST_DEBUG_OBJECT (self, "Creating capsfilter element");
|
|
filter = gst_element_factory_make ("capsfilter", "capsfilter1");
|
|
#if 0
|
|
/* find element */
|
|
GST_DEBUG_OBJECT (self, "Creating new kid");
|
|
if (!(kid = gst_pinesrcbin_find_best (self)))
|
|
#endif
|
|
if (!kid || !filter)
|
|
goto no_sink;
|
|
|
|
GST_DEBUG_OBJECT (self, "Setting fixed pad caps");
|
|
target = gst_element_get_static_pad (kid, self->type_klass_lc);
|
|
gst_pad_use_fixed_caps (target);
|
|
gst_pad_set_caps(target, self->filter_caps);
|
|
|
|
GST_DEBUG_OBJECT (self, "Setting default properties for v4l2src element");
|
|
self->has_sync =
|
|
g_object_class_find_property (G_OBJECT_GET_CLASS (kid), "sync") != NULL;
|
|
if (self->has_sync)
|
|
g_object_set (G_OBJECT (kid), "sync", self->sync, NULL);
|
|
g_object_set (G_OBJECT (kid), "device", self->devnode_path, NULL);
|
|
g_object_set (G_OBJECT (kid), "num-buffers", self->num_buffers, NULL);
|
|
|
|
if (klass->configure) {
|
|
klass->configure (self, kid);
|
|
}
|
|
|
|
self->kid = kid;
|
|
self->filter = filter;
|
|
|
|
GST_DEBUG_OBJECT (self, "Creating bin");
|
|
gst_bin_add_many (GST_BIN (self), kid, filter, NULL);
|
|
if (!gst_element_link (kid, filter))
|
|
g_error("failed to link elements to filter\n");
|
|
|
|
GST_DEBUG_OBJECT (self, "Configuring elements states");
|
|
/* Ensure the child is brought up to the right state to match the parent. */
|
|
if (GST_STATE (self->kid) < GST_STATE (self))
|
|
gst_element_set_state (self->kid, GST_STATE (self));
|
|
if (GST_STATE (filter) < GST_STATE (self))
|
|
gst_element_set_state (filter, GST_STATE (self));
|
|
GST_DEBUG_OBJECT (self, "Setting filter caps");
|
|
g_object_set(filter, "caps", self->filter_caps, NULL);
|
|
|
|
GST_DEBUG_OBJECT (self, "Setting up ghost pad");
|
|
/* attach ghost pad */
|
|
GST_DEBUG_OBJECT (self, "Re-assigning ghostpad");
|
|
if (!gst_pinesrcbin_attach_ghost_pad (self))
|
|
goto target_failed;
|
|
|
|
GST_DEBUG_OBJECT (self, "done changing auto %s %s", self->media_klass_lc,
|
|
self->type_klass_lc);
|
|
|
|
return TRUE;
|
|
|
|
/* ERRORS */
|
|
no_sink:
|
|
{
|
|
GST_ELEMENT_ERROR (self, LIBRARY, INIT, (NULL),
|
|
("Failed to find a supported sink"));
|
|
return FALSE;
|
|
}
|
|
target_failed:
|
|
{
|
|
GST_ELEMENT_ERROR (self, LIBRARY, INIT, (NULL),
|
|
("Failed to set target pad"));
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
|
|
static GstStateChangeReturn
|
|
gst_pinesrcbin_change_state (GstElement * element, GstStateChange transition)
|
|
{
|
|
GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
|
|
GstPineSrcBin *sink = GST_PINESRCBIN (element);
|
|
|
|
switch (transition) {
|
|
case GST_STATE_CHANGE_NULL_TO_READY:
|
|
if (!gst_pinesrcbin_detect (sink))
|
|
return GST_STATE_CHANGE_FAILURE;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
|
|
if (ret == GST_STATE_CHANGE_FAILURE)
|
|
return ret;
|
|
|
|
switch (transition) {
|
|
case GST_STATE_CHANGE_READY_TO_NULL:
|
|
gst_pinesrcbin_reset (sink);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
gst_pinesrcbin_class_init (GstPineSrcBinClass * klass)
|
|
{
|
|
GObjectClass *gobject_class;
|
|
GstElementClass *eklass;
|
|
|
|
gobject_class = G_OBJECT_CLASS (klass);
|
|
eklass = GST_ELEMENT_CLASS (klass);
|
|
|
|
gobject_class->constructed = gst_pinesrcbin_constructed;
|
|
gobject_class->dispose = gst_pinesrcbin_dispose;
|
|
gobject_class->set_property = gst_pinesrcbin_set_property;
|
|
gobject_class->get_property = gst_pinesrcbin_get_property;
|
|
|
|
eklass->change_state = GST_DEBUG_FUNCPTR (gst_pinesrcbin_change_state);
|
|
|
|
g_object_class_install_property (gobject_class, PROP_SYNC,
|
|
g_param_spec_boolean ("sync", "Sync",
|
|
"Sync on the clock", DEFAULT_SYNC,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
g_object_class_install_property (gobject_class, PROP_NUM_BUFFERS,
|
|
g_param_spec_int ("num-buffers", "Num-buffers",
|
|
"Number of buffers until EOS, -1 = unlimited", -1, INT_MAX, 1000,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
}
|
|
|
|
GST_DEBUG_CATEGORY (pinesrcbin_debug);
|
|
|
|
static void
|
|
gst_pinesrcbin_init (GstPineSrcBin * self)
|
|
{
|
|
GST_DEBUG_OBJECT (self, "Starting device configuration");
|
|
gst_pinesrcbin_configure_device(self, "rear");
|
|
self->sync = DEFAULT_SYNC;
|
|
self->num_buffers = 1000;
|
|
}
|
|
|
|
static gboolean
|
|
plugin_init (GstPlugin * plugin)
|
|
{
|
|
GST_DEBUG_CATEGORY_INIT (pinesrcbin_debug, "pinesrcbin", 0,
|
|
"pinephone v4lsrc camera wrapper");
|
|
return gst_element_register (plugin, "pinesrcbin",
|
|
GST_RANK_NONE, GST_TYPE_PINESRCBIN);
|
|
}
|
|
|
|
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR,
|
|
pinesrcbin,
|
|
"pinephone v4lsrc camera wrapper",
|
|
plugin_init, "0.0", GST_LICENSE, "pinesrcbin", "omp")
|