一、简述
在本章中,我们将如下搭建一条管线:
gst_bin_add_many(GST_BIN(pipeline), source, capsfilter, myele, convert, sink, NULL);
在这条管线中,我们将从v4l2src中获取视频流,经过我们自己的插件myele
后,将数据推送给waylandsink。
二、源码
2.1 main
我们首先来看一下main是怎么写的。这和普通的v4l2捕获一样,只是我们在caps之后链接上我们自己的插件。
int my_main(int argc, char** argv)
{
GstElement *pipeline;
GstElement *source, *capsfilter, *convert, *sink;
GstElement *myele;
GstCaps *caps;
GstBus *bus;
GstMessage *msg;
GstStateChangeReturn ret;
gboolean terminate = FALSE;
int retval = 0;
/* Initialize GStreamer */
gst_init(&argc, &argv);
/* Create the elements */
source = gst_element_factory_make("v4l2src", "source");
convert = gst_element_factory_make("videoconvert", "convert");
sink = gst_element_factory_make("waylandsink", "sink");
capsfilter = gst_element_factory_make("capsfilter", "capsfilter");
//debug myele
myele = gst_element_factory_make("rgb2grey", "rgb2grey");
/* Create the empty pipeline */
pipeline = gst_pipeline_new("test-pipeline");
/* Check creation */
if (!pipeline || !source || !convert || !sink || !capsfilter) {
g_printerr("Not all elements could be created.\n");
retval = -1;
goto out_return;
}
//debug myele
if (!myele) {
g_printerr("myele not created.\n");
retval = -1;
goto out_return;
}
g_object_set(G_OBJECT(myele), "width", 1920, "height", 1080, NULL);
/* Set caps on source */
g_object_set(G_OBJECT(source), "device", "/dev/video0", NULL);
caps = gst_caps_new_simple(
"video/x-raw",
"format", G_TYPE_STRING, "NV12",
"width", G_TYPE_INT, 1920,
"height", G_TYPE_INT, 1080,
"framerate", GST_TYPE_FRACTION, 30, 1,
NULL
);
g_object_set(G_OBJECT(capsfilter), "caps", caps, NULL);
gst_caps_unref(caps);
/* Build the pipeline */
gst_bin_add_many(GST_BIN(pipeline), source, capsfilter, myele, convert, sink, NULL);
if (!gst_element_link_many(source, capsfilter, myele, convert, sink, NULL))
{
g_printerr("Elements could not be linked.\n");
retval = -2;
goto out_unref;
}
/* Set the pipeline to "playing" state */
ret = gst_element_set_state(pipeline, GST_STATE_PLAYING);
if (ret == GST_STATE_CHANGE_FAILURE) {
g_printerr("Unable to set the pipeline to the playing state.\n");
retval = -3;
goto out_unref;
}
/* Wait until error or EOS */
bus = gst_element_get_bus(pipeline);
do {
msg = gst_bus_timed_pop_filtered(bus, GST_CLOCK_TIME_NONE, GST_MESSAGE_ERROR | GST_MESSAGE_EOS);
/* Parse message */
if (msg != NULL) {
GError *err;
gchar *debug_info;
switch (GST_MESSAGE_TYPE(msg)) {
case GST_MESSAGE_ERROR:
gst_message_parse_error(msg, &err, &debug_info);
g_printerr("Error received from element %s: %s\n", GST_OBJECT_NAME(msg->src), err->message);
g_printerr("Debugging information: %s\n", debug_info ? debug_info : "none");
g_clear_error(&err);
g_free(debug_info);
terminate = TRUE;
break;
case GST_MESSAGE_EOS:
g_print("End-Of-Stream reached.\n");
terminate = TRUE;
break;
default:
/* Should not be reached */
g_printerr("Unexpected message received.\n");
break;
}
gst_message_unref(msg);
}
} while (!terminate);
gst_object_unref(bus);
gst_element_set_state(pipeline, GST_STATE_NULL);
out_unref:
gst_object_unref(pipeline);
out_return:
return retval;
}
2.2 plugin.h
这里参照gst类的模板完成,需要注意:成员变量的枚举,有效数字应该从1
开始。
#pragma once
#include <gstreamer-1.0/gst/gst.h>
#include <gstreamer-1.0/gst/gstcontext.h>
#include <gstreamer-1.0/gst/video/video-info.h>
#define VERSION "1.0"
#define PACKAGE "rgb2grey"
G_BEGIN_DECLS
enum{
PROP_0 = 0,
PROP_WIDTH,
PROP_HEIGHT,
};
typedef struct _GstRGB2GreyFilter {
GstElement element;
GstPad *sinkpad;
GstPad *srcpad;
guint width;
guint height;
}GstRGB2GreyFilter;
typedef struct _GstRGB2GreyFilterClass {
GstElementClass parent_class;
}GstRGB2GreyFilterClass;
/* declare get_type Func, defined in G_DEFINE_TYPE */
GType gst_rgb2grey_filter_get_type(void);
/* ===== Standard macros for defining types for this element. ===== */
//return GType
#define GST_TYPE_RGB2GREY_FILTER (gst_rgb2grey_filter_get_type())
//get a GstObtFilter ptr from any ptr #obj ,if obj is GST_TYPE_OBT_FILTER GType , return result , otherwise return null
#define GST_RGB2GREY_FILTER(obj) \
(G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_RGB2GREY_FILTER, GstRGB2GreyFilter))
//get a GstObtFilterClass ptr from any ptr #obj ,if obj is GST_TYPE_OBT_FILTER GType , return result , otherwise return null
#define GST_RGB2GREY_FILTER_CLASS(klass) \
(G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_RGB2GREY_FILTER, GstRGB2GreyFilterClass))
#define GST_IS_RGB2GREY_FILTER(obj) \
(G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_RGB2GREY_FILTER))
#define GST_IS_RGB2GREY_FILTER_CLASS(klass) \
(G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_RGB2GREY_FILTER))
G_END_DECLS
2.3 plugin.c
#include "rgb2grey.h"
static GstBuffer* gst_rgb2grey_filter_process_data(GstRGB2GreyFilter* filter, GstBuffer* buf);
static gboolean gst_rgb2grey_filter_stop_processing(GstRGB2GreyFilter* filter);
static gboolean gst_rgb2grey_filter_allocate_memory(GstRGB2GreyFilter* filter);
static gboolean gst_rgb2grey_filter_free_memory(GstRGB2GreyFilter* filter);
/**
* ==============================
* @par boilerplate Funcs [START]
* ==============================
*/
G_DEFINE_TYPE(GstRGB2GreyFilter, gst_rgb2grey_filter, GST_TYPE_ELEMENT);
static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE(
"sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS("ANY")
);
static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE(
"src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS("ANY")
);
/**
* @brief
*
* @param pad pad reveive buffer
* @param parent
* @param buf to be precessed
* @return GstFlowReturn
*/
static GstFlowReturn gst_rgb2grey_filter_chain(GstPad* pad, GstObject *parent, GstBuffer *buf)
{
// g_print("gst_rgb2grey_filter_chain\n");
GstRGB2GreyFilter *filter = GST_RGB2GREY_FILTER(parent);
/* Get input Buffer */
GstMapInfo in_map;
gst_buffer_map(buf, &in_map, GST_MAP_READ | GST_MAP_WRITE);
guint in_width = filter->width;
guint in_height = filter->height;
guint in_pixel_size = in_width * in_height;
guint in_full_size = in_pixel_size * 1.5;
gsize in_size = in_map.size;
/* TODO: set UV to 0 */
// input format is NV12
for (int i = in_pixel_size; i < in_full_size; ++i)
{
in_map.data[i] = (guint8)(0);
}
// g_print("in_size = %d\n", in_size);
gst_buffer_unmap(buf, &in_map);
return gst_pad_push(filter->srcpad, buf);
}
/**
* @brief
*
* @param pad pad reveive event
* @param parent
* @param event to be preocessed
* @return gboolean
*/
static gboolean gst_rgb2grey_filter_event(GstPad* pad, GstObject *parent, GstEvent *event)
{
gboolean ret;
GstRGB2GreyFilter *filter = GST_RGB2GREY_FILTER(parent);
g_print("event : %s \n",GST_EVENT_TYPE_NAME(event));
switch (GST_EVENT_TYPE(event)) {
case GST_EVENT_CAPS:
ret = gst_pad_push_event(filter->srcpad, event); /* push the event downstream */
break;
case GST_EVENT_EOS:
gst_rgb2grey_filter_stop_processing(filter);
ret = gst_pad_event_default(pad, parent, event); //default deal transaction
break;
default:
ret = gst_pad_event_default(pad, parent, event);
break;
}
return ret;
}
static gboolean gst_rgb2grey_filter_src_query(GstPad* pad, GstObject* parent, GstQuery* query)
{
gboolean ret;
GstRGB2GreyFilter *filter = GST_RGB2GREY_FILTER(parent);
switch (GST_QUERY_TYPE(query)) {
case GST_QUERY_POSITION:
/* we should report the current position */
break;
case GST_QUERY_DURATION:
/* we should report the duration here */
break;
case GST_QUERY_CAPS:
/* we should report the supported caps here */
break;
default:
/* just call the default handler */
ret = gst_pad_query_default(pad, parent, query);
break;
}
return ret;
}
static GstStateChangeReturn gst_rgb2grey_filter_change_state(GstElement *element, GstStateChange transition)
{
GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
GstRGB2GreyFilter *filter = GST_RGB2GREY_FILTER(element);
//process upwards state change
switch (transition) {
case GST_STATE_CHANGE_NULL_TO_READY:
if (!gst_rgb2grey_filter_allocate_memory(filter)) //require for resource, memory / libs / ... are included
return GST_STATE_CHANGE_FAILURE;
break;
case GST_STATE_CHANGE_READY_TO_PAUSED:
//dosomething
break;
case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
//dosomething
break;
default:
break;
}
//call state changed Func of parent class.
ret = GST_ELEMENT_CLASS (gst_rgb2grey_filter_parent_class)->change_state (element, transition);
if (ret == GST_STATE_CHANGE_FAILURE) return ret;
//process downwards state change
switch (transition) {
case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
//dosomething
break;
case GST_STATE_CHANGE_PAUSED_TO_READY:
//dosomething
break;
case GST_STATE_CHANGE_READY_TO_NULL:
gst_rgb2grey_filter_free_memory(filter);
break;
default:
break;
}
return ret;
}
static void gst_rgb2grey_filter_set_property(GObject* object, guint prop_id, const GValue * value, GParamSpec *pspec)
{
GstRGB2GreyFilter* filter = GST_RGB2GREY_FILTER(object);
switch (prop_id) {
case PROP_WIDTH:
filter->width = g_value_get_uint(value);
break;
case PROP_HEIGHT:
filter->height = g_value_get_uint(value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
break;
}
}
static void gst_rgb2grey_filter_get_property(GObject* object, guint prop_id, GValue * value, GParamSpec *pspec)
{
GstRGB2GreyFilter *filter = GST_RGB2GREY_FILTER(object);
switch (prop_id) {
// do sth
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
break;
}
}
static void gst_rgb2grey_filter_class_init(GstRGB2GreyFilterClass *klass)
{
g_print("gst_rgb2grey_filter_class_init\n");
GstElementClass *element_class = GST_ELEMENT_CLASS(klass); // for others
GObjectClass *object_class = G_OBJECT_CLASS(klass); // for property setter and getter
// meta data
gst_element_class_set_static_metadata(element_class,
"[meta data]Filter Demo of rgb2grey Plugin",
"[meta data]rgb2grey/Filter Demo",
"[meta data]Shows the basic structure of a plugin",
"[meta data]xjt");
// register state change funcution
element_class->change_state = gst_rgb2grey_filter_change_state;
// register property setter and getter
object_class->set_property = gst_rgb2grey_filter_set_property;
object_class->get_property = gst_rgb2grey_filter_get_property;
// register pads
gst_element_class_add_pad_template(element_class,
gst_static_pad_template_get(&src_factory));
gst_element_class_add_pad_template(element_class,
gst_static_pad_template_get(&sink_factory));
// width and height
GParamSpec *param_spec;
param_spec = g_param_spec_uint(
"width", "Width", "The width of the input video",
0, G_MAXUINT, 0,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS
);
g_object_class_install_property(object_class, PROP_WIDTH, param_spec);
param_spec = g_param_spec_uint(
"height", "Height",
"The height of the input video",
0, G_MAXUINT, 0,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS
);
g_object_class_install_property(object_class, PROP_HEIGHT, param_spec);
}
static void gst_rgb2grey_filter_init(GstRGB2GreyFilter *filter)
{
g_print("gst_rgb2grey_filter_init\n");
//instantiates and assigns pads
filter->srcpad = gst_pad_new_from_static_template(&src_factory, "src");
filter->sinkpad = gst_pad_new_from_static_template(&sink_factory, "sink");
//add pads to element
gst_element_add_pad(GST_ELEMENT(filter), filter->srcpad);
gst_element_add_pad(GST_ELEMENT(filter), filter->sinkpad);
//set chain function for sink pad
gst_pad_set_chain_function(filter->sinkpad, gst_rgb2grey_filter_chain);
//set event function
gst_pad_set_event_function(filter->sinkpad, gst_rgb2grey_filter_event);
//set query function
gst_pad_set_query_function(filter->srcpad, gst_rgb2grey_filter_src_query);
}
static gboolean plugin_init(GstPlugin* plugin)
{
g_print("rgb2grey_plugin_init\n");
gboolean ret;
//register plugin feature
//[todo]
//register element into plugin
ret = gst_element_register(plugin, "rgb2grey", GST_RANK_MARGINAL, GST_TYPE_RGB2GREY_FILTER);
return ret;
}
GST_PLUGIN_DEFINE(
GST_VERSION_MAJOR,
GST_VERSION_MINOR,
rgb2grey,
"rgb2grey filter plugin",
plugin_init,
VERSION,
"LGPL",
"LocalPlugin",
"n/a"
)
/**
* ========================
* @par Local Funcs [START]
* ========================
*/
static GstBuffer* gst_rgb2grey_filter_process_data(GstRGB2GreyFilter* filter, GstBuffer* buf)
{
return buf;
}
static gboolean gst_rgb2grey_filter_stop_processing(GstRGB2GreyFilter* filter)
{
return TRUE;
}
static gboolean gst_rgb2grey_filter_allocate_memory(GstRGB2GreyFilter* filter)
{
return TRUE;
}
static gboolean gst_rgb2grey_filter_free_memory(GstRGB2GreyFilter* filter)
{
return TRUE;
}
三、使用
这里我的插件名称为rgb2grey,通过cmake创建一个动态库。
ADD_LIBRARY(rgb2grey SHARED rgb2grey.c rgb2grey.h)
编译好之后我们将文件传入/lib/
或者usr/lib/gstreamer-1.0/
中,之后就可以在主程序中调用gst_element_factory_make
来创建元素了。