Update
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
---
|
||||
title: Adding Properties
|
||||
...
|
||||
|
||||
# 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.
|
||||
|
||||
``` c
|
||||
|
||||
/* 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\n",
|
||||
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.
|
||||
|
||||
``` c
|
||||
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));
|
||||
[..]
|
||||
}
|
||||
|
||||
```
|
||||
@@ -0,0 +1,373 @@
|
||||
---
|
||||
title: Constructing the Boilerplate
|
||||
...
|
||||
|
||||
# Constructing the Boilerplate
|
||||
|
||||
In this chapter you will learn how to construct the bare minimum code
|
||||
for a new plugin. Starting from ground zero, you will see how to get the
|
||||
GStreamer template source. Then you will learn how to use a few basic
|
||||
tools to copy and modify a template plugin to create a new plugin. If
|
||||
you follow the examples here, then by the end of this chapter you will
|
||||
have a functional audio filter plugin that you can compile and use in
|
||||
GStreamer applications.
|
||||
|
||||
## 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. (Errm, that is, “it is left as an exercise to the reader.”)
|
||||
|
||||
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.
|
||||
|
||||
```
|
||||
|
||||
This command will check out a series of files and directories into
|
||||
`gst-template`. The template you will be using is in the
|
||||
`gst-template/gst-plugin/` directory. You should look over the files in
|
||||
that directory to get a general idea of the structure of a source tree
|
||||
for a plugin.
|
||||
|
||||
If for some reason you can't access the git repository, you can also
|
||||
[download a snapshot of the latest
|
||||
revision](https://gitlab.freedesktop.org/gstreamer/gst-template)
|
||||
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/src` directory, 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 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.)
|
||||
|
||||
``` c
|
||||
#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);
|
||||
|
||||
GST_ELEMENT_REGISTER_DECLARE(my_filter)
|
||||
|
||||
```
|
||||
|
||||
Using this header file, you can use the following macros to setup the
|
||||
`Element` basics in your source file so that all functions will be
|
||||
called appropriately:
|
||||
|
||||
``` c
|
||||
#include "filter.h"
|
||||
|
||||
G_DEFINE_TYPE (GstMyFilter, gst_my_filter, GST_TYPE_ELEMENT);
|
||||
GST_ELEMENT_REGISTER_DEFINE(my_filter, "my-filter", GST_RANK_NONE, GST_TYPE_MY_FILTER);
|
||||
|
||||
```
|
||||
|
||||
The macro `GST_ELEMENT_REGISTER_DEFINE` in combination with `GST_ELEMENT_REGISTER_DECLARE`
|
||||
allows to register the element from within the plugin or from any other plugin/application by calling
|
||||
`GST_ELEMENT_REGISTER (my_filter)`.
|
||||
|
||||
## Element metadata
|
||||
|
||||
The Element metadata provides extra element information. It is
|
||||
configured with `gst_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:
|
||||
|
||||
``` c
|
||||
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.
|
||||
|
||||
``` c
|
||||
static void
|
||||
gst_my_filter_class_init (GstMyFilterClass * klass)
|
||||
{
|
||||
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
|
||||
|
||||
[..]
|
||||
gst_element_class_set_static_metadata (element_class,
|
||||
"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:
|
||||
|
||||
``` c
|
||||
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 the `gst_element_class_add_pad_template ()`. For this function you
|
||||
need a handle to the `GstPadTemplate` which 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 using `gst_pad_new_from_static_template ()`. In order to create
|
||||
a new pad from this template using `gst_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][pads].
|
||||
|
||||
```c
|
||||
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:
|
||||
|
||||
``` c
|
||||
|
||||
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][pads].
|
||||
|
||||
[pads]: plugin-development/basics/pads.md
|
||||
|
||||
## 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.
|
||||
|
||||
``` c
|
||||
|
||||
|
||||
static gboolean
|
||||
plugin_init (GstPlugin *plugin)
|
||||
{
|
||||
return GST_ELEMENT_REGISTER (my_filter, plugin);
|
||||
}
|
||||
|
||||
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.
|
||||
@@ -0,0 +1,114 @@
|
||||
---
|
||||
title: The chain function
|
||||
...
|
||||
|
||||
# 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:
|
||||
|
||||
``` c
|
||||
|
||||
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!\n",
|
||||
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.).
|
||||
|
||||
```c
|
||||
static 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);
|
||||
}
|
||||
```
|
||||
|
||||
In some cases, it might be useful for an element to have control over
|
||||
the input data rate, too. In that case, you probably want to write a
|
||||
so-called *loop-based* element. Source elements (with only source pads)
|
||||
can also be *get-based* elements. These concepts will be explained in
|
||||
the advanced section of this guide, and in the section that specifically
|
||||
discusses source pads.
|
||||
@@ -0,0 +1,70 @@
|
||||
---
|
||||
title: The event function
|
||||
...
|
||||
|
||||
# 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.
|
||||
|
||||
``` c
|
||||
|
||||
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.
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
title: The Basics of Writing a Plugin
|
||||
...
|
||||
|
||||
# Writing a Plugin
|
||||
|
||||
You are now ready to learn how to build a plugin. In this part of the
|
||||
guide, you will learn how to apply basic GStreamer programming concepts
|
||||
to write a simple plugin. The previous parts of the guide have contained
|
||||
no explicit example code, perhaps making things a bit abstract and
|
||||
difficult to understand. In contrast, this section will present both
|
||||
applications and code by following the development of an example audio
|
||||
filter plugin called “MyFilter”.
|
||||
|
||||
The example filter element will begin with a single input pad and a
|
||||
single output pad. The filter will, at first, simply pass media and
|
||||
event data from its sink pad to its source pad without modification. But
|
||||
by the end of this part of the guide, you will learn to add some more
|
||||
interesting functionality, including properties and signal handlers. And
|
||||
after reading the next part of the guide, [Advanced Filter Concepts][advanced],
|
||||
you will be able to add even more functionality to your plugins.
|
||||
|
||||
[advanced]: plugin-development/advanced/index.md
|
||||
@@ -0,0 +1,55 @@
|
||||
---
|
||||
title: Specifying the pads
|
||||
...
|
||||
|
||||
# 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:
|
||||
|
||||
``` c
|
||||
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
```
|
||||
@@ -0,0 +1,69 @@
|
||||
---
|
||||
title: The query function
|
||||
...
|
||||
|
||||
# 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.
|
||||
|
||||
``` c
|
||||
|
||||
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.
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
title: Signals
|
||||
...
|
||||
|
||||
# Signals
|
||||
|
||||
GObject signals can be used to notify applications of events specific to
|
||||
this object. Note, however, that the application needs to be aware of
|
||||
signals and their meaning, so if you're looking for a generic way for
|
||||
application-element interaction, signals are probably not what you're
|
||||
looking for. In many cases, however, signals can be very useful. See the
|
||||
[GObject documentation](http://library.gnome.org/devel/gobject/stable/)
|
||||
for all internals about signals.
|
||||
@@ -0,0 +1,139 @@
|
||||
---
|
||||
title: What are states?
|
||||
...
|
||||
|
||||
# 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](plugin-development/element-types/base-classes.md)). 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.
|
||||
|
||||
```c
|
||||
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.
|
||||
@@ -0,0 +1,202 @@
|
||||
---
|
||||
title: Building a Test Application
|
||||
...
|
||||
|
||||
# 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.
|
||||
This section explains - in a few words - how to do that. For a complete
|
||||
application development guide, see the [Application Development
|
||||
Manual](application-development/index.md).
|
||||
|
||||
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.
|
||||
|
||||
``` c
|
||||
#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\n");
|
||||
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\n", err->message);
|
||||
g_error_free (err);
|
||||
|
||||
if (debug) {
|
||||
g_print ("Debug details: %s\n", 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>\n", 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\n");
|
||||
return -1;
|
||||
} else if (!convert1 || !convert2 || !resample) {
|
||||
g_print ("Could not create audioconvert or audioresample element, "
|
||||
"check your installation\n");
|
||||
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!\n");
|
||||
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!\n");
|
||||
|
||||
/* 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\n", 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;
|
||||
}
|
||||
|
||||
```
|
||||
Reference in New Issue
Block a user