What are states?
状态描述元素实例是否已初始化,是否准备传输数据,以及当前是否正在处理数据。GStreamer中定义了四种状态。
- GST_STATE_NULL
- GST_STATE_READY (已经初始化)
- GST_STATE_PAUSED (准备传输数据)
- GST_STATE_PLAYING (正在处理数据)
从现在起,它们将被简称为“NULL”、“READY”、“PAUSED”和“PLAYING”。
-
GST_STATE_NULL是元素的默认状态。在这种状态下,它没有分配任何运行时资源,没有加载任何运行时库,显然不能处理数据。
-
GST_STATE_READY是元素可能处于的下一个状态。在就绪状态下,元素拥有分配的所有默认资源(运行时库、运行时内存)。然而,它还没有分配或定义任何特定于流的东西。当从NULL变为就绪状态(GST_STATE_CHANGE_NULL_TO_READY)时,元素应该分配所有非流特定的资源,并加载运行时可加载的库(如果有的话)。如果反过来(从READY到NULL, GST_STATE_CHANGE_READY_TO_NULL),则元素应该卸载这些库并释放所有已分配的资源。硬件设备就是这样的资源。
请注意,文件通常是流,因此应该将其视为流特有的资源。因此,它们不应该在这种状态下分配
。 -
GST_STATE_PAUSED是元素准备接受和处理数据时的状态。对于大多数元素来说,这个状态等同于PLAYING。唯一的例外是sink元素。Sink元素只接受一个数据缓冲区,然后进行块处理。此时,管道已经
prerolled
并准备好立即渲染数据。 -
GST_STATE_PLAYING是元素可以处于的最高状态。对于大多数元素来说,这个状态与PAUSED状态完全相同,它们接受并处理事件和缓冲区中的数据。只有sink元素需要区分暂停和播放状态。在播放状态下,sink元素实际上渲染传入的数据,例如将音频输出到声卡或将视频图像渲染到图像sink。
Managing filter state
如果可能的话,元素应该从一个新的基类(预先创建的基类)派生。对于不同类型的sources、sinks和filter/transformation元素,有现成的通用基类。除此之外,还存在用于音频和视频元素等的专用基类。
如果你使用基类,你将很少需要自己处理状态更改。你所要做的就是覆盖基类的start()和stop()虚函数(可能会根据基类的不同调用),基类将为你处理一切。
但是,如果您不是从一个现成的基类派生而来,而是从GstElement或其他一些没有构建在基类之上的类派生而来,那么您很可能必须实现自己的状态更改函数以获得状态更改的通知。如果您的插件是一个demuxer或muxer,这是绝对必要的,因为还没有用于demuxer或muxer的基类。
可以通过虚函数指针通知元素状态的变化。在这个函数内部,元素可以初始化元素所需的任何特定类型的数据,并且可以选择从一种状态切换到另一种状态。
对于未处理的状态变化,不要使用g_assert。这是由GstElement基类处理的。
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;
}
请注意,向上(NULL=>READY, READY=>PAUSED, PAUSED=>PLAYING)和向下(PLAYING =>PAUSED, PAUSED=>READY, READY=>NULL)状态更改在两个单独的块中处理,向下状态更改只有在我们链接到父类的状态更改函数
后才处理。为了安全地处理多个线程的并发访问,这是必要的。
原因是,在向下状态变化的情况下,你不希望在插件的chain函数(例如)仍然在另一个线程中访问这些资源时销毁已分配的资源。链式函数是否在运行取决于插件的plugin’s pads状态,而这些plugin’s pads的状态与元素的状态密切相关。Pad状态是在GstElement类的状态改变函数中处理的,包括正确的锁定,这就是为什么在销毁分配的资源之前必须将其链接起来。
Adding Properties
控制元素行为的最主要也是最重要的方式,就是通过GObject属性。GObject属性在_class_init()函数中定义。元素可选地实现_get_property()和_set_property()函数。如果应用程序更改或请求属性的值,这些函数将收到通知,然后可以填充该值或采取该属性内部更改值所需的操作。
你可能还想在get和set函数中使用的属性的当前配置值周围保存一个实例变量。注意,GObject不会自动将实例变量设置为默认值,您必须在元素的_init()函数中这样做。
/* 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;
}
}
以上是如何使用属性的一个非常简单的例子。图形化应用程序将使用这些属性,并显示一个用户可控制的小部件,通过它可以更改这些属性。这意味着——为了让属性尽可能对用户友好——你应该尽可能精确地定义属性。不仅可以定义可以放置有效属性的范围(对于整数、浮点数等),还可以在属性定义中使用非常具有描述性(最好是国际化)的字符串,如果可能的话,可以使用枚举和flags而不是整数。GObject文档以非常完整的方式描述了这些,但下面,我们将给出一个简短的示例,说明这在哪里有用。请注意,在这里使用整数可能会让用户完全迷惑,因为它们在这个上下文中没有任何意义。这个例子是从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
通常,你会希望在尽可能小的设置中测试新编写的插件。通常,gst-launch-1.0是测试插件的第一步。如果没有将插件安装在GStreamer搜索的目录中,则需要设置插件路径。将GST_PLUGIN_PATH设置为包含插件的目录,或者使用命令行选项--gst-plugin-path
。如果你的插件基于gst-plugin模板,那么它看起来类似于gst-launch-1.0 --gst-plugin-path=$HOME/gst-template/gst-plugin/src/.libs TESTPIPELINE
,然而库测试管道,您通常需要比gst-launch-1.0提供的更多的测试功能,例如查找、事件、交互等。编写自己的小测试程序是实现这一点的最简单方法。本节简单地解释了如何做到这一点。要了解完整的应用程序开发指南,请参阅应用程序开发手册。
开始时,你需要通过调用gst_init()初始化GStreamer核心库。您也可以调用gst_init_get_option_group(),它将返回一个指向GOptionGroup的指针。然后您可以使用GOption来处理初始化,这将完成GStreamer的初始化。
可以使用gst_element_factory_make()创建元素,其中第一个参数是要创建的元素类型,第二个参数是自由格式的名称。最后的示例使用了一个简单的filesource - decoder -声卡输出管道,但如果有必要,您可以使用特定的调试元素。例如,可以在管道中间使用标识元素作为数据到应用的发送器。这可以用于检查测试应用程序中的数据是否有错误行为或是否正确。此外,还可以在管道的末尾使用fakesink元素将数据转储到stdout(为此,请将dump属性设置为TRUE)。最后,你可以使用valgrind
检查内存错误。
在链接期间,您的测试应用程序可以使用过滤的caps作为一种方式来驱动特定类型的数据与元素之间的传输。这是检查元素中多种输入和输出类型的一种非常简单有效的方法。
请注意,在运行期间,您应该至少侦听总线和/或插件/元素上的“error”和“eos”消息,以检查是否正确处理此问题。此外,还应该向管道中添加事件,并确保插件正确地处理这些事件(时钟、内部缓存等)。
永远不要忘记清理插件或测试应用程序中的内存。当变为NULL状态时,你的元素应该清理分配的内存和缓存。此外,它应该关闭所有可能的支持库的引用。您的应用程序应该unref()管道并确保它不会崩溃。
#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;
}