Supporting Dynamic Parameters
Sometimes object properties are not powerful enough to control the
parameters that affect the behaviour of your element. When this is the case
you can expose these parameters as Dynamic Parameters which can be
manipulated by any Dynamic Parameters aware application.
Throughout this section, the term dparams will be used
as an abbreviation for "Dynamic Parameters".
Comparing Dynamic Parameters with GObject Properties
Your first exposure to dparams may be to convert an existing element from
using object properties to using dparams. The following table gives an
overview of the difference between these approaches. The significance of
these differences should become apparent later on.
Object Properties
Dynamic Parameters
Parameter definition
Class level at compile time
Any level at run time
Getting and setting
Implemented by element subclass as functions
Handled entirely by dparams subsystem
Extra objects required
None - all functionality is derived from base GObject
Element needs to create and store a GstDParamManager at object creation
Frequency and resolution of updates
Object properties will only be updated between calls to _get, _chain or _loop
dparams can be updated at any rate independent of calls to _get, _chain or _loop up to sample-level accuracy
Getting Started
The dparams subsystem is contained within the
gstcontrol library. You need to include the header in
your element's source file:
#include <gst/control/control.h>
Even though the gstcontrol library may be linked into
the host application, you should make sure it is loaded in your
plugin_init function:
static gboolean
plugin_init (GModule *module, GstPlugin *plugin)
{
...
/* load dparam support library */
if (!gst_library_load ("gstcontrol"))
{
gst_info ("example: could not load support library: 'gstcontrol'\n");
return FALSE;
}
...
}
You need to store an instance of GstDParamManager in
your element's struct:
struct _GstExample {
GstElement element;
...
GstDParamManager *dpman;
...
};
The GstDParamManager can be initialised in your
element's init function:
static void
gst_example_init (GstExample *example)
{
...
example->dpman = gst_dpman_new ("example_dpman", GST_ELEMENT(example));
...
}
Defining Parameter Specifications
You can define the dparams you need anywhere within your element but will
usually need to do so in only a couple of places:
In the element init function, just after the call
to gst_dpman_new
Whenever a new pad is created so that parameters can affect data going
into or out of a specific pad. An example of this would be a mixer
element where a separate volume parameter is needed on every pad.
There are three different ways the dparams subsystem can pass parameters
into your element. Which one you use will depend on how that parameter is
used within your element. Each of these methods has its own function to
define a required dparam:
gst_dpman_add_required_dparam_direct
gst_dpman_add_required_dparam_callback
gst_dpman_add_required_dparam_array
These functions will return TRUE if the required dparam was added
successfully.
The following function will be used as an example.
gboolean
gst_dpman_add_required_dparam_direct (GstDParamManager *dpman,
GParamSpec *param_spec,
gboolean is_log,
gboolean is_rate,
gpointer update_data)
The common parameters to these functions are:
GstDParamManager *dpman the element's dparam
manager
GParamSpec *param_spec the param spec which defines
the required dparam
gboolean is_log whether this dparam value should be
interpreted on a log scale (such as a frequency or a decibel value)
gboolean is_rate whether this dparam value is a
proportion of the sample rate. For example with a sample rate of 44100,
0.5 would be 22050 Hz and 0.25 would be 11025 Hz.
Direct Method
This method is the simplest and has the lowest overhead for parameters
which change less frequently than the sample rate. First you need
somewhere to store the parameter - this will usually be in your element's
struct.
struct _GstExample {
GstElement element;
...
GstDParamManager *dpman;
gfloat volume;
...
};
Then to define the required dparam just call
gst_dpman_add_required_dparam_direct and pass in the
location of the parameter to change. In this case the location is
&(example->volume).
gst_dpman_add_required_dparam_direct (
example->dpman,
g_param_spec_float("volume","Volume","Volume of the audio",
0.0, 1.0, 0.8, G_PARAM_READWRITE),
FALSE,
FALSE,
&(example->volume)
);
You can now use example->volume anywhere in your
element knowing that it will always contain the correct value to use.
Callback Method
This should be used if the you have other values to calculate whenever a
parameter changes. If you used the direct method you wouldn't know if a
parameter had changed so you would have to recalculate the other values
every time you needed them. By using the callback method, other values
only have to be recalculated when the dparam value actually changes.
The following code illustrates an instance where you might want to use the
callback method. If you had a volume dparam which was represented by a
gfloat number, your element may only deal with integer arithmetic. The
callback could be used to calculate the integer scaler when the volume
changes. First you will need somewhere to store these values.
struct _GstExample {
GstElement element;
...
GstDParamManager *dpman;
gfloat volume_f;
gint volume_i;
...
};
When the required dparam is defined, the callback function
gst_example_update_volume and some user data (which
in this case is our element instance) is passed in to the call to
gst_dpman_add_required_dparam_callback.
gst_dpman_add_required_dparam_callback (
example->dpman,
g_param_spec_float("volume","Volume","Volume of the audio",
0.0, 1.0, 0.8, G_PARAM_READWRITE),
FALSE,
FALSE,
gst_example_update_volume,
example
);
The callback function needs to conform to this signature
typedef void (*GstDPMUpdateFunction) (GValue *value, gpointer data);
In our example the callback function looks like this
static void
gst_example_update_volume(GValue *value, gpointer data)
{
GstExample *example = (GstExample*)data;
g_return_if_fail(GST_IS_EXAMPLE(example));
example->volume_f = g_value_get_float(value);
example->volume_i = example->volume_f * 8192;
}
Now example->volume_i can be used elsewhere and it
will always contain the correct value.
Array Method
This method is quite different from the other two. It could be thought of
as a specialised method which should only be used if you need the
advantages that it provides. Instead of giving the element a single value
it provides an array of values where each item in the array corresponds to
a sample of audio in your buffer. There are a couple of reasons why this
might be useful.
Certain optimisations may be possible since you can iterate over your
dparams array and your buffer data together.
Some dparams may be able to interpolate changing values at the sample
rate. This would allow the array to contain very smoothly changing
values which may be required for the stability and quality of some DSP
algorithms.
The array method is currently the least mature of the three methods and is
not yet ready to be used in elements, but plugin writers should be aware
of its existence for the future.
The Data Processing Loop
This is the most critical aspect of the dparams subsystem as it relates to
elements. In a traditional audio processing loop, a for
loop will usually iterate over each sample in the buffer, processing one
sample at a time until the buffer is finished. A simplified loop with no
error checking might look something like this.
static void
example_chain (GstPad *pad, GstBuffer *buf)
{
...
gfloat *float_data;
int j;
GstExample *example = GST_EXAMPLE(GST_OBJECT_PARENT (pad));
int num_samples = GST_BUFFER_SIZE(buf)/sizeof(gfloat);
float_data = (gfloat *)GST_BUFFER_DATA(buf);
...
for (j = 0; j < num_samples; j++) {
float_data[j] *= example->volume;
}
...
}
To make this dparams aware, a couple of changes are needed.
static void
example_chain (GstPad *pad, GstBuffer *buf)
{
...
int j = 0;
GstExample *example = GST_EXAMPLE(GST_OBJECT_PARENT (pad));
int num_samples = GST_BUFFER_SIZE(buf)/sizeof(gfloat);
gfloat *float_data = (gfloat *)GST_BUFFER_DATA(buf);
int frame_countdown = GST_DPMAN_PREPROCESS(example->dpman, num_samples, GST_BUFFER_TIMESTAMP(buf));
...
while (GST_DPMAN_PROCESS_COUNTDOWN(example->dpman, frame_countdown, j)) {
float_data[j++] *= example->volume;
}
...
}
The biggest changes here are 2 new macros,
GST_DPMAN_PREPROCESS and
GST_DPMAN_PROCESS_COUNTDOWN. You will also notice that
the for loop has become a while loop.
GST_DPMAN_PROCESS_COUNTDOWN is called as the condition
for the while loop so that any required dparams can be updated in the middle
of a buffer if required. This is because one of the required behaviours of
dparams is that they can be sample accurate. This means
that parameters change at the exact timestamp that they are supposed to -
not after the buffer has finished being processed.
It may be alarming to see a macro as the condition for a while loop, but it
is actually very efficient. The macro expands to the following.
#define GST_DPMAN_PROCESS_COUNTDOWN(dpman, frame_countdown, frame_count) \
(frame_countdown-- || \
(frame_countdown = GST_DPMAN_PROCESS(dpman, frame_count)))
So as long as frame_countdown is greater than 0,
GST_DPMAN_PROCESS will not be called at all. Also in
many cases, GST_DPMAN_PROCESS will do nothing and
simply return 0, meaning that there is no more data in the buffer to
process.
The macro GST_DPMAN_PREPROCESS will do the following:
Update any dparams which are due to be updated.
Calculate how many samples should be processed before the next required
update
Return the number of samples until next update, or the number of samples
in the buffer - whichever is less.
In fact GST_DPMAN_PROCESS may do the same things as
GST_DPMAN_PREPROCESS depending on the mode that the
dparam manager is running in (see below).
DParam Manager Modes
A brief explanation of dparam manager modes might be useful here even
though it doesn't generally affect the way your element is written. There
are different ways media applications will be used which require that an
element's parameters be updated in differently. These include:
Timelined - all parameter changes are known in
advance before the pipeline is run.
Realtime low-latency - Nothing is known ahead of
time about when a parameter might change. Changes need to be
propagated to the element as soon as possible.
When a dparam-aware application gets the dparam manager for an element,
the first thing it will do is set the dparam manager mode. Current modes
are "synchronous" and
"asynchronous".
If you are in a realtime low-latency situation then the
"synchronous" mode is appropriate. During
GST_DPMAN_PREPROCESS this mode will poll all dparams
for required updates and propagate them.
GST_DPMAN_PROCESS will do nothing in this mode. To
then achieve the desired latency, the size of the buffers needs to be
reduced so that the dparams will be polled for updates at the desired
frequency.
In a timelined situation, the "asynchronous" mode
will be required. This mode hasn't actually been implemented yet but will
be described anyway. The GST_DPMAN_PREPROCESS call
will precalculate when and how often each dparam needs to update for the
duration of the current buffer. From then on
GST_DPMAN_PROCESS will propagate the calculated
updates each time it is called until end of the buffer. If the application
is rendering to disk in non-realtime, the render could be sped up by
increasing the buffer size. In the "asynchronous"
mode this could be done without affecting the sample accuracy of the
parameter updates
Dynamic Parameters for Video
All of the explanation so far has presumed that the buffer contains audio
data with many samples. Video should be regarded differently since a video
buffer often contains only 1 frame. In this case some of the complexity of
dparams isn't required but the other benefits still make it useful for
video parameters. If a buffer only contains one frame of video, only a
single call to GST_DPMAN_PREPROCESS should be
required. For more than one frame per buffer, treat it the same as the
audio case.