zoukankan      html  css  js  c++  java
  • 转载:GStreamer: the Basic of of Writing plugin

    https://gstreamer.freedesktop.org/documentation/plugin-development/index.html?gi-language=c

    Constructing the Boilerplate

    In this chapter you will learn how to construct the bare minimum code for a new plugin.  you will learn how to use a few basic tools to copy and modify a template plugin to create a new plugin.

    Getting the GStreamer Plugin Templates

    There are currently two ways to develop a new plugin for GStreamer: You can write the entire plugin by hand, or you can copy an existing plugin template and write the plugin code you need. The second method is by far the simpler of the two, so the first method will not even be described here. 

    The first step is to check out a copy of the gst-template git module to get an important tool and the source code template for a basic GStreamer plugin. To check out the gst-template module, make sure you are connected to the internet, and type the following commands at a command console:

    shell $ git clone https://gitlab.freedesktop.org/gstreamer/gst-template.git
    Initialized empty Git repository in /some/path/gst-template/.git/
    remote: Counting objects: 373, done.
    remote: Compressing objects: 100% (114/114), done.
    remote: Total 373 (delta 240), reused 373 (delta 240)
    Receiving objects: 100% (373/373), 75.16 KiB | 78 KiB/s, done.
    Resolving deltas: 100% (240/240), done.

    You can also download a snapshot of the latest revision via the gitlab web interface.

    Using the Project Stamp

    The first thing to do when making a new element is to specify some basic details about it: what its name is, who wrote it, what version number it is, etc. We also need to define an object to represent the element and to store the data the element needs. These details are collectively known as the boilerplate.

    The standard way of defining the boilerplate is simply to write some code, and fill in some structures. As mentioned in the previous section, the easiest way to do this is to copy a template and add functionality according to your needs. To help you do so, there is a tool in the ./gst-plugin/tools/ directory. This tool, make_element, is a command line utility that creates the boilerplate code for you.

    To use make_element, first open up a terminal window. Change to the gst-template/gst-plugin/srcdirectory, and then run the make_element command. The arguments to the make_element are:

    1. the name of the plugin, and

    2. the source file that the tool will use. By default, gstplugin is used.

    For example, the following commands create the MyFilter plugin based on the plugin template and put the output files in the gst-template/gst-plugin/src directory:

    shell $ cd gst-template/gst-plugin/src
    shell $ ../tools/make_element MyFilter
    
    

    Note

    Capitalization is important for the name of the plugin. Keep in mind that under some operating systems, capitalization is also important when specifying directory and file names in general.

    The last command creates two files: gstmyfilter.c and gstmyfilter.h.

    Note

    It is recommended that you create a copy of the gst-plugin directory before continuing.

    Now one needs to adjust the meson.build to use the new filenames and run meson build from the parent directory to bootstrap the build environment. After that, the project can be built and installed using the well known ninja -C build commands.

    Note

    Be aware that by default meson will choose /usr/local as a default location. One would need to add /usr/local/lib/gstreamer-1.0 to GST_PLUGIN_PATH in order to make the new plugin show up in a gstreamer that's been installed from packages.

    Note

    FIXME: this section is slightly outdated. gst-template is still useful as an example for a minimal plugin build system skeleton. However, for creating elements the tool gst-element-maker from gst-plugins-bad is recommended these days.

    Examining the Basic Code

    First we will examine the code you would be likely to place in a header file (although since the interface to the code is entirely defined by the plugin system, and doesn't depend on reading a header file, this is not crucial.)

    #include <gst/gst.h>
    
    /* Definition of structure storing data for this element. */
    typedef struct _GstMyFilter {
      GstElement element;
    
      GstPad *sinkpad, *srcpad;
    
      gboolean silent;
    
    
    
    } GstMyFilter;
    
    /* Standard definition defining a class for this element. */
    typedef struct _GstMyFilterClass {
      GstElementClass parent_class;
    } GstMyFilterClass;
    
    /* Standard macros for defining types for this element.  */
    #define GST_TYPE_MY_FILTER (gst_my_filter_get_type())
    #define GST_MY_FILTER(obj) 
      (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_MY_FILTER,GstMyFilter))
    #define GST_MY_FILTER_CLASS(klass) 
      (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_MY_FILTER,GstMyFilterClass))
    #define GST_IS_MY_FILTER(obj) 
      (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_MY_FILTER))
    #define GST_IS_MY_FILTER_CLASS(klass) 
      (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_MY_FILTER))
    
    /* Standard function returning type information. */
    GType gst_my_filter_get_type (void);

    Using this header file, you can use the following macro to setup the GObject basics in your source file so that all functions will be called appropriately:

    #include "filter.h"
    
    G_DEFINE_TYPE (GstMyFilter, gst_my_filter, GST_TYPE_ELEMENT);

    Element metadata

    The Element metadata provides extra element information. It is configured withgst_element_class_set_metadata or gst_element_class_set_static_metadata which takes the following parameters:

    • A long, English, name for the element.

    • The type of the element, see the docs/additional/design/draft-klass.txt document in the GStreamer core source tree for details and examples.

    • A brief description of the purpose of the element.

    • The name of the author of the element, optionally followed by a contact email address in angle brackets.

    For example:

    gst_element_class_set_static_metadata (klass,
      "An example plugin",
      "Example/FirstExample",
      "Shows the basic structure of a plugin",
      "your name <your.name@your.isp>");

    The element details are registered with the plugin during the _class_init () function, which is part of the GObject system. The _class_init () function should be set for this GObject in the function where you register the type with GLib.

    static void
    gst_my_filter_class_init (GstMyFilterClass * klass)
    {
      GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
    
    [..]
      gst_element_class_set_static_metadata (element_klass,
        "An example plugin",
        "Example/FirstExample",
        "Shows the basic structure of a plugin",
        "your name <your.name@your.isp>");
    
    }

    GstStaticPadTemplate

    A GstStaticPadTemplate is a description of a pad that the element will (or might) create and use. It contains:

    • A short name for the pad.

    • Pad direction.

    • Existence property. This indicates whether the pad exists always (an “always” pad), only in some cases (a “sometimes” pad) or only if the application requested such a pad (a “request” pad).

    • Supported types by this element (capabilities).

    For example:

    static GstStaticPadTemplate sink_factory =
    GST_STATIC_PAD_TEMPLATE (
      "sink",
      GST_PAD_SINK,
      GST_PAD_ALWAYS,
      GST_STATIC_CAPS ("ANY")
    );

    Those pad templates are registered during the _class_init () function with thegst_element_class_add_pad_template (). For this function you need a handle to the GstPadTemplatewhich you can create from the static pad template with gst_static_pad_template_get (). See below for more details on this.

    Pads are created from these static templates in the element's _init () function usinggst_pad_new_from_static_template (). In order to create a new pad from this template usinggst_pad_new_from_static_template (), you will need to declare the pad template as a global variable. More on this subject in Specifying the pads.

    static GstStaticPadTemplate sink_factory = [..],
        src_factory = [..];
    
    static void
    gst_my_filter_class_init (GstMyFilterClass * klass)
    {
      GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
    [..]
    
      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));
    }

    The last argument in a template is its type or list of supported types. In this example, we use 'ANY', which means that this element will accept all input. In real-life situations, you would set a media type and optionally a set of properties to make sure that only supported input will come in. This representation should be a string that starts with a media type, then a set of comma-separates properties with their supported values. In case of an audio filter that supports raw integer 16-bit audio, mono or stereo at any samplerate, the correct template would look like this:

    static GstStaticPadTemplate sink_factory =
    GST_STATIC_PAD_TEMPLATE (
      "sink",
      GST_PAD_SINK,
      GST_PAD_ALWAYS,
      GST_STATIC_CAPS (
        "audio/x-raw, "
          "format = (string) " GST_AUDIO_NE (S16) ", "
          "channels = (int) { 1, 2 }, "
          "rate = (int) [ 8000, 96000 ]"
      )
    );

    Values surrounded by curly brackets (“{” and “}”) are lists, values surrounded by square brackets (“[” and “]”) are ranges. Multiple sets of types are supported too, and should be separated by a semicolon (“;”). Later, in the chapter on pads, we will see how to use types to know the exact format of a stream:Specifying the pads.

    Constructor Functions

    Each element has two functions which are used for construction of an element. The _class_init() function, which is used to initialise the class only once (specifying what signals, arguments and virtual functions the class has and setting up global state); and the _init() function, which is used to initialise a specific instance of this type.

    The plugin_init function

    Once we have written code defining all the parts of the plugin, we need to write the plugin_init() function. This is a special function, which is called as soon as the plugin is loaded, and should return TRUE or FALSE depending on whether it loaded initialized any dependencies correctly. Also, in this function, any supported element type in the plugin should be registered.

    static gboolean
    plugin_init (GstPlugin *plugin)
    {
      return gst_element_register (plugin, "my_filter",
                       GST_RANK_NONE,
                       GST_TYPE_MY_FILTER);
    }
    
    GST_PLUGIN_DEFINE (
      GST_VERSION_MAJOR,
      GST_VERSION_MINOR,
      my_filter,
      "My filter plugin",
      plugin_init,
      VERSION,
      "LGPL",
      "GStreamer",
      "http://gstreamer.net/"
    )

    Note that the information returned by the plugin_init() function will be cached in a central registry. For this reason, it is important that the same information is always returned by the function: for example, it must not make element factories available based on runtime conditions. If an element can only work in certain conditions (for example, if the soundcard is not being used by some other process) this must be reflected by the element being unable to enter the READY state if unavailable, rather than the plugin attempting to deny existence of the plugin.

    Specifying the pads

    As explained before, pads are the port through which data goes in and out of your element, and that makes them a very important item in the process of element creation. In the boilerplate code, we have seen how static pad templates take care of registering pad templates with the element class. Here, we will see how to create actual elements, use an _event ()-function to configure for a particular format and how to register functions to let data flow through the element.

    In the element _init () function, you create the pad from the pad template that has been registered with the element class in the _class_init () function. After creating the pad, you have to set a _chain ()function pointer that will receive and process the input data on the sinkpad. You can optionally also set an_event () function pointer and a _query () function pointer. Alternatively, pads can also operate in looping mode, which means that they can pull data themselves. More on this topic later. After that, you have to register the pad with the element. This happens like this:

    static void
    gst_my_filter_init (GstMyFilter *filter)
    {
      /* pad through which data comes in to the element */
      filter->sinkpad = gst_pad_new_from_static_template (
        &sink_template, "sink");
      /* pads are configured here with gst_pad_set_*_function () */
    
    
    
      gst_element_add_pad (GST_ELEMENT (filter), filter->sinkpad);
    
      /* pad through which data goes out of the element */
      filter->srcpad = gst_pad_new_from_static_template (
        &src_template, "src");
      /* pads are configured here with gst_pad_set_*_function () */
    
    
    
      gst_element_add_pad (GST_ELEMENT (filter), filter->srcpad);
    
      /* properties initial value */
      filter->silent = FALSE;

    The chain function

    The chain function is the function in which all data processing takes place. In the case of a simple filter,_chain () functions are mostly linear functions - so for each incoming buffer, one buffer will go out, too. Below is a very simple implementation of a chain function:

    static GstFlowReturn gst_my_filter_chain (GstPad    *pad,
                                              GstObject *parent,
                                              GstBuffer *buf);
    
    [..]
    
    static void
    gst_my_filter_init (GstMyFilter * filter)
    {
    [..]
      /* configure chain function on the pad before adding
       * the pad to the element */
      gst_pad_set_chain_function (filter->sinkpad,
          gst_my_filter_chain);
    [..]
    }
    
    static GstFlowReturn
    gst_my_filter_chain (GstPad    *pad,
                         GstObject *parent,
                 GstBuffer *buf)
    {
      GstMyFilter *filter = GST_MY_FILTER (parent);
    
      if (!filter->silent)
        g_print ("Have data of size %" G_GSIZE_FORMAT" bytes!
    ",
            gst_buffer_get_size (buf));
    
      return gst_pad_push (filter->srcpad, buf);
    }

    Obviously, the above doesn't do much useful. Instead of printing that the data is in, you would normally process the data there. Remember, however, that buffers are not always writeable.

    In more advanced elements (the ones that do event processing), you may want to additionally specify an event handling function, which will be called when stream-events are sent (such as caps, end-of-stream, newsegment, tags, etc.).

    tatic void
    gst_my_filter_init (GstMyFilter * filter)
    {
    [..]
      gst_pad_set_event_function (filter->sinkpad,
          gst_my_filter_sink_event);
    [..]
    }
    
    
    
    static gboolean
    gst_my_filter_sink_event (GstPad    *pad,
                      GstObject *parent,
                      GstEvent  *event)
    {
      GstMyFilter *filter = GST_MY_FILTER (parent);
    
      switch (GST_EVENT_TYPE (event)) {
        case GST_EVENT_CAPS:
          /* we should handle the format here */
          break;
        case GST_EVENT_EOS:
          /* end-of-stream, we should close down all stream leftovers here */
          gst_my_filter_stop_processing (filter);
          break;
        default:
          break;
      }
    
      return gst_pad_event_default (pad, parent, event);
    }
    
    static GstFlowReturn
    gst_my_filter_chain (GstPad    *pad,
                 GstObject *parent,
                 GstBuffer *buf)
    {
      GstMyFilter *filter = GST_MY_FILTER (parent);
      GstBuffer *outbuf;
    
      outbuf = gst_my_filter_process_data (filter, buf);
      gst_buffer_unref (buf);
      if (!outbuf) {
        /* something went wrong - signal an error */
        GST_ELEMENT_ERROR (GST_ELEMENT (filter), STREAM, FAILED, (NULL), (NULL));
        return GST_FLOW_ERROR;
      }
    
      return gst_pad_push (filter->srcpad, outbuf);
    }

    The event function

    The event function notifies you of special events that happen in the datastream (such as caps, end-of-stream, newsegment, tags, etc.). Events can travel both upstream and downstream, so you can receive them on sink pads as well as source pads.

    Below follows a very simple event function that we install on the sink pad of our element.

    static gboolean gst_my_filter_sink_event (GstPad    *pad,
                                              GstObject *parent,
                                              GstEvent  *event);
    
    [..]
    
    static void
    gst_my_filter_init (GstMyFilter * filter)
    {
    [..]
      /* configure event function on the pad before adding
       * the pad to the element */
      gst_pad_set_event_function (filter->sinkpad,
          gst_my_filter_sink_event);
    [..]
    }
    
    static gboolean
    gst_my_filter_sink_event (GstPad    *pad,
                      GstObject *parent,
                      GstEvent  *event)
    {
      gboolean ret;
      GstMyFilter *filter = GST_MY_FILTER (parent);
    
      switch (GST_EVENT_TYPE (event)) {
        case GST_EVENT_CAPS:
          /* we should handle the format here */
    
          /* push the event downstream */
          ret = gst_pad_push_event (filter->srcpad, event);
          break;
        case GST_EVENT_EOS:
          /* end-of-stream, we should close down all stream leftovers here */
          gst_my_filter_stop_processing (filter);
    
          ret = gst_pad_event_default (pad, parent, event);
          break;
        default:
          /* just call the default handler */
          ret = gst_pad_event_default (pad, parent, event);
          break;
      }
      return ret;
    }

    It is a good idea to call the default event handler gst_pad_event_default () for unknown events. Depending on the event type, the default handler will forward the event or simply unref it. The CAPS event is by default not forwarded so we need to do this in the event handler ourselves.

    The query function

    Through the query function, your element will receive queries that it has to reply to. These are queries like position, duration but also about the supported formats and scheduling modes your element supports. Queries can travel both upstream and downstream, so you can receive them on sink pads as well as source pads.

    Below follows a very simple query function that we install on the source pad of our element.

    static gboolean gst_my_filter_src_query (GstPad    *pad,
                                             GstObject *parent,
                                             GstQuery  *query);
    
    [..]
    
    static void
    gst_my_filter_init (GstMyFilter * filter)
    {
    [..]
      /* configure event function on the pad before adding
       * the pad to the element */
      gst_pad_set_query_function (filter->srcpad,
          gst_my_filter_src_query);
    [..]
    }
    
    static gboolean
    gst_my_filter_src_query (GstPad    *pad,
                     GstObject *parent,
                     GstQuery  *query)
    {
      gboolean ret;
      GstMyFilter *filter = GST_MY_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;
    }

    It is a good idea to call the default query handler gst_pad_query_default () for unknown queries. Depending on the query type, the default handler will forward the query or simply unref it.

    What are states?

    A state describes whether the element instance is initialized, whether it is ready to transfer data and whether it is currently handling data. There are four states defined in GStreamer:

    • GST_STATE_NULL

    • GST_STATE_READY

    • GST_STATE_PAUSED

    • GST_STATE_PLAYING

    which will from now on be referred to simply as “NULL”, “READY”, “PAUSED” and “PLAYING”.

    GST_STATE_NULL is the default state of an element. In this state, it has not allocated any runtime resources, it has not loaded any runtime libraries and it can obviously not handle data.

    GST_STATE_READY is the next state that an element can be in. In the READY state, an element has all default resources (runtime-libraries, runtime-memory) allocated. However, it has not yet allocated or defined anything that is stream-specific. When going from NULL to READY state (GST_STATE_CHANGE_NULL_TO_READY), an element should allocate any non-stream-specific resources and should load runtime-loadable libraries (if any). When going the other way around (from READY to NULL, GST_STATE_CHANGE_READY_TO_NULL), an element should unload these libraries and free all allocated resources. Examples of such resources are hardware devices. Note that files are generally streams, and these should thus be considered as stream-specific resources; therefore, they should not be allocated in this state.

    GST_STATE_PAUSED is the state in which an element is ready to accept and handle data. For most elements this state is the same as PLAYING. The only exception to this rule are sink elements. Sink elements only accept one single buffer of data and then block. At this point the pipeline is 'prerolled' and ready to render data immediately.

    GST_STATE_PLAYING is the highest state that an element can be in. For most elements this state is exactly the same as PAUSED, they accept and process events and buffers with data. Only sink elements need to differentiate between PAUSED and PLAYING state. In PLAYING state, sink elements actually render incoming data, e.g. output audio to a sound card or render video pictures to an image sink.

    Managing filter state

    If at all possible, your element should derive from one of the new base classes (Pre-made base classes). There are ready-made general purpose base classes for different types of sources, sinks and filter/transformation elements. In addition to those, specialised base classes exist for audio and video elements and others.

    If you use a base class, you will rarely have to handle state changes yourself. All you have to do is override the base class's start() and stop() virtual functions (might be called differently depending on the base class) and the base class will take care of everything for you.

    If, however, you do not derive from a ready-made base class, but from GstElement or some other class not built on top of a base class, you will most likely have to implement your own state change function to be notified of state changes. This is definitively necessary if your plugin is a demuxer or a muxer, as there are no base classes for muxers or demuxers yet.

    An element can be notified of state changes through a virtual function pointer. Inside this function, the element can initialize any sort of specific data needed by the element, and it can optionally fail to go from one state to another.

    Do not g_assert for unhandled state changes; this is taken care of by the GstElement base class.

    static GstStateChangeReturn
    gst_my_filter_change_state (GstElement *element, GstStateChange transition);
    
    static void
    gst_my_filter_class_init (GstMyFilterClass *klass)
    {
      GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
    
      element_class->change_state = gst_my_filter_change_state;
    }
    
    
    
    static GstStateChangeReturn
    gst_my_filter_change_state (GstElement *element, GstStateChange transition)
    {
      GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
      GstMyFilter *filter = GST_MY_FILTER (element);
    
      switch (transition) {
        case GST_STATE_CHANGE_NULL_TO_READY:
          if (!gst_my_filter_allocate_memory (filter))
            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_my_filter_free_memory (filter);
          break;
        default:
          break;
      }
    
      return ret;
    }

    Note that upwards (NULL=>READY, READY=>PAUSED, PAUSED=>PLAYING) and downwards (PLAYING=>PAUSED, PAUSED=>READY, READY=>NULL) state changes are handled in two separate blocks with the downwards state change handled only after we have chained up to the parent class's state change function. This is necessary in order to safely handle concurrent access by multiple threads.

    The reason for this is that in the case of downwards state changes you don't want to destroy allocated resources while your plugin's chain function (for example) is still accessing those resources in another thread. Whether your chain function might be running or not depends on the state of your plugin's pads, and the state of those pads is closely linked to the state of the element. Pad states are handled in the GstElement class's state change function, including proper locking, that's why it is essential to chain up before destroying allocated resources.

    Adding Properties

    The primary and most important way of controlling how an element behaves, is through GObject properties. GObject properties are defined in the _class_init () function. The element optionally implements a_get_property () and a _set_property () function. These functions will be notified if an application changes or requests the value of a property, and can then fill in the value or take action required for that property to change value internally.

    You probably also want to keep an instance variable around with the currently configured value of the property that you use in the get and set functions. Note that GObject will not automatically set your instance variable to the default value, you will have to do that in the _init () function of your element.

    /* properties */
    enum {
      PROP_0,
      PROP_SILENT
      /* FILL ME */
    };
    
    static void gst_my_filter_set_property  (GObject      *object,
                             guint         prop_id,
                             const GValue *value,
                             GParamSpec   *pspec);
    static void gst_my_filter_get_property  (GObject      *object,
                             guint         prop_id,
                             GValue       *value,
                             GParamSpec   *pspec);
    
    static void
    gst_my_filter_class_init (GstMyFilterClass *klass)
    {
      GObjectClass *object_class = G_OBJECT_CLASS (klass);
    
      /* define virtual function pointers */
      object_class->set_property = gst_my_filter_set_property;
      object_class->get_property = gst_my_filter_get_property;
    
      /* define properties */
      g_object_class_install_property (object_class, PROP_SILENT,
        g_param_spec_boolean ("silent", "Silent",
                  "Whether to be very verbose or not",
                  FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
    }
    
    static void
    gst_my_filter_set_property (GObject      *object,
                    guint         prop_id,
                    const GValue *value,
                    GParamSpec   *pspec)
    {
      GstMyFilter *filter = GST_MY_FILTER (object);
    
      switch (prop_id) {
        case PROP_SILENT:
          filter->silent = g_value_get_boolean (value);
          g_print ("Silent argument was changed to %s
    ",
               filter->silent ? "true" : "false");
          break;
        default:
          G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
          break;
      }
    }
    
    static void
    gst_my_filter_get_property (GObject    *object,
                    guint       prop_id,
                    GValue     *value,
                    GParamSpec *pspec)
    {
      GstMyFilter *filter = GST_MY_FILTER (object);
    
      switch (prop_id) {
        case PROP_SILENT:
          g_value_set_boolean (value, filter->silent);
          break;
        default:
          G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
          break;
      }
    }

    The above is a very simple example of how properties are used. Graphical applications will use these properties and will display a user-controllable widget with which these properties can be changed. This means that - for the property to be as user-friendly as possible - you should be as exact as possible in the definition of the property. Not only in defining ranges in between which valid properties can be located (for integers, floats, etc.), but also in using very descriptive (better yet: internationalized) strings in the definition of the property, and if possible using enums and flags instead of integers. The GObject documentation describes these in a very complete way, but below, we'll give a short example of where this is useful. Note that using integers here would probably completely confuse the user, because they make no sense in this context. The example is stolen from videotestsrc.

    typedef enum {
      GST_VIDEOTESTSRC_SMPTE,
      GST_VIDEOTESTSRC_SNOW,
      GST_VIDEOTESTSRC_BLACK
    } GstVideotestsrcPattern;
    
    [..]
    
    #define GST_TYPE_VIDEOTESTSRC_PATTERN (gst_videotestsrc_pattern_get_type ())
    static GType
    gst_videotestsrc_pattern_get_type (void)
    {
      static GType videotestsrc_pattern_type = 0;
    
      if (!videotestsrc_pattern_type) {
        static GEnumValue pattern_types[] = {
          { GST_VIDEOTESTSRC_SMPTE, "SMPTE 100% color bars",    "smpte" },
          { GST_VIDEOTESTSRC_SNOW,  "Random (television snow)", "snow"  },
          { GST_VIDEOTESTSRC_BLACK, "0% Black",                 "black" },
          { 0, NULL, NULL },
        };
    
        videotestsrc_pattern_type =
        g_enum_register_static ("GstVideotestsrcPattern",
                    pattern_types);
      }
    
      return videotestsrc_pattern_type;
    }
    
    [..]
    
    static void
    gst_videotestsrc_class_init (GstvideotestsrcClass *klass)
    {
    [..]
      g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_PATTERN,
        g_param_spec_enum ("pattern", "Pattern",
                   "Type of test pattern to generate",
                           GST_TYPE_VIDEOTESTSRC_PATTERN, GST_VIDEOTESTSRC_SMPTE,
                           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
    [..]
    }
     

    Building a Test Application

    Often, you will want to test your newly written plugin in an as small setting as possible. Usually, gst-launch-1.0 is a good first step at testing a plugin. If you have not installed your plugin in a directory that GStreamer searches, then you will need to set the plugin path. Either set GST_PLUGIN_PATH to the directory containing your plugin, or use the command-line option --gst-plugin-path. If you based your plugin off of the gst-plugin template, then this will look something like gst-launch-1.0 --gst-plugin-path=$HOME/gst-template/gst-plugin/src/.libs TESTPIPELINE However, you will often need more testing features than gst-launch-1.0 can provide, such as seeking, events, interactivity and more. Writing your own small testing program is the easiest way to accomplish this. 

    At the start, you need to initialize the GStreamer core library by calling gst_init (). You can alternatively call gst_init_get_option_group (), which will return a pointer to GOptionGroup. You can then use GOption to handle the initialization, and this will finish the GStreamer initialization.

    You can create elements using gst_element_factory_make (), where the first argument is the element type that you want to create, and the second argument is a free-form name. The example at the end uses a simple filesource - decoder - soundcard output pipeline, but you can use specific debugging elements if that's necessary. For example, an identity element can be used in the middle of the pipeline to act as a data-to-application transmitter. This can be used to check the data for misbehaviours or correctness in your test application. Also, you can use a fakesink element at the end of the pipeline to dump your data to the stdout (in order to do this, set the dump property to TRUE). Lastly, you can use valgrind to check for memory errors.

    During linking, your test application can use filtered caps as a way to drive a specific type of data to or from your element. This is a very simple and effective way of checking multiple types of input and output in your element.

    Note that during running, you should listen for at least the “error” and “eos” messages on the bus and/or your plugin/element to check for correct handling of this. Also, you should add events into the pipeline and make sure your plugin handles these correctly (with respect to clocking, internal caching, etc.).

    Never forget to clean up memory in your plugin or your test application. When going to the NULL state, your element should clean up allocated memory and caches. Also, it should close down any references held to possible support libraries. Your application should unref () the pipeline and make sure it doesn't crash.

    #include <gst/gst.h>
    
    static gboolean
    bus_call (GstBus     *bus,
          GstMessage *msg,
          gpointer    data)
    {
      GMainLoop *loop = data;
    
      switch (GST_MESSAGE_TYPE (msg)) {
        case GST_MESSAGE_EOS:
          g_print ("End-of-stream
    ");
          g_main_loop_quit (loop);
          break;
        case GST_MESSAGE_ERROR: {
          gchar *debug = NULL;
          GError *err = NULL;
    
          gst_message_parse_error (msg, &err, &debug);
    
          g_print ("Error: %s
    ", err->message);
          g_error_free (err);
    
          if (debug) {
            g_print ("Debug details: %s
    ", debug);
            g_free (debug);
          }
    
          g_main_loop_quit (loop);
          break;
        }
        default:
          break;
      }
    
      return TRUE;
    }
    
    gint
    main (gint   argc,
          gchar *argv[])
    {
      GstStateChangeReturn ret;
      GstElement *pipeline, *filesrc, *decoder, *filter, *sink;
      GstElement *convert1, *convert2, *resample;
      GMainLoop *loop;
      GstBus *bus;
      guint watch_id;
    
      /* initialization */
      gst_init (&argc, &argv);
      loop = g_main_loop_new (NULL, FALSE);
      if (argc != 2) {
        g_print ("Usage: %s <mp3 filename>
    ", argv[0]);
        return 01;
      }
    
      /* create elements */
      pipeline = gst_pipeline_new ("my_pipeline");
    
      /* watch for messages on the pipeline's bus (note that this will only
       * work like this when a GLib main loop is running) */
      bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
      watch_id = gst_bus_add_watch (bus, bus_call, loop);
      gst_object_unref (bus);
    
      filesrc  = gst_element_factory_make ("filesrc", "my_filesource");
      decoder  = gst_element_factory_make ("mad", "my_decoder");
    
      /* putting an audioconvert element here to convert the output of the
       * decoder into a format that my_filter can handle (we are assuming it
       * will handle any sample rate here though) */
      convert1 = gst_element_factory_make ("audioconvert", "audioconvert1");
    
      /* use "identity" here for a filter that does nothing */
      filter   = gst_element_factory_make ("my_filter", "my_filter");
    
      /* there should always be audioconvert and audioresample elements before
       * the audio sink, since the capabilities of the audio sink usually vary
       * depending on the environment (output used, sound card, driver etc.) */
      convert2 = gst_element_factory_make ("audioconvert", "audioconvert2");
      resample = gst_element_factory_make ("audioresample", "audioresample");
      sink     = gst_element_factory_make ("pulsesink", "audiosink");
    
      if (!sink || !decoder) {
        g_print ("Decoder or output could not be found - check your install
    ");
        return -1;
      } else if (!convert1 || !convert2 || !resample) {
        g_print ("Could not create audioconvert or audioresample element, "
                 "check your installation
    ");
        return -1;
      } else if (!filter) {
        g_print ("Your self-written filter could not be found. Make sure it "
                 "is installed correctly in $(libdir)/gstreamer-1.0/ or "
                 "~/.gstreamer-1.0/plugins/ and that gst-inspect-1.0 lists it. "
                 "If it doesn't, check with 'GST_DEBUG=*:2 gst-inspect-1.0' for "
                 "the reason why it is not being loaded.");
        return -1;
      }
    
      g_object_set (G_OBJECT (filesrc), "location", argv[1], NULL);
    
      gst_bin_add_many (GST_BIN (pipeline), filesrc, decoder, convert1, filter,
                        convert2, resample, sink, NULL);
    
      /* link everything together */
      if (!gst_element_link_many (filesrc, decoder, convert1, filter, convert2,
                                  resample, sink, NULL)) {
        g_print ("Failed to link one or more elements!
    ");
        return -1;
      }
    
      /* run */
      ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
      if (ret == GST_STATE_CHANGE_FAILURE) {
        GstMessage *msg;
    
        g_print ("Failed to start up pipeline!
    ");
    
        /* check if there is an error message with details on the bus */
        msg = gst_bus_poll (bus, GST_MESSAGE_ERROR, 0);
        if (msg) {
          GError *err = NULL;
    
          gst_message_parse_error (msg, &err, NULL);
          g_print ("ERROR: %s
    ", err->message);
          g_error_free (err);
          gst_message_unref (msg);
        }
        return -1;
      }
    
      g_main_loop_run (loop);
    
      /* clean up */
      gst_element_set_state (pipeline, GST_STATE_NULL);
      gst_object_unref (pipeline);
      g_source_remove (watch_id);
      g_main_loop_unref (loop);
    
      return 0;
    }
  • 相关阅读:
    原生JS之温故而知新(一)
    jQuery版本问题
    AngularJs我的学习整理(不定时修改)
    Js事件处理程序跨浏览器
    AngularJs的关于路由问题
    很棒的十句话,分享给大家。
    一个人为什么要努力?
    春熙路。
    成都
    springboot 使用mybatis-generator自动生成代码
  • 原文地址:https://www.cnblogs.com/fellow1988/p/12900606.html
Copyright © 2011-2022 走看看