站点图标

GStreamer Plugin 01 Basic

一、简述

  在本章中,我们将如下搭建一条管线:

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来创建元素了。

退出移动版