接下来的几篇文章将讨论如何在你的应用程序控制管道的几种方法。这些部分有点难度,所以你在阅读本文之前需要一些编程知识。接下来将要讨论的包括
探测可以看做访问Pad的侦听器。从技术上讲,探针不过是可以使用gst_pad_add_probe ()附加到pad上的回调函数 。同样的,你可以使用gst_pad_remove_probe ()删除回调。连接后,探针会通知你pad的任何活动。你可以定义添加探针感兴趣的通知类型。
探测类型:
你可以使用空闲探针动态重新链接pad。我们将看到如何使用空闲探针替换管道中的元素。另请参阅动态更改管道。
pad上有数据通过时,数据探针会通知你。通过 GST_PAD_PROBE_TYPE_BUFFER和/或GST_PAD_PROBE_TYPE_BUFFER_LIST以 gst_pad_add_probe ()创建这种探头。大多数常见的缓冲区操作元素可以在_chain ()函数中执行,也可以在探针回调中完成。
数据探针在管道的流线程上下文中运行,因此回调应尝试避免阻塞,并且通常避免做一些奇怪的事情。这样做可能会对管道的性能产生负面影响,或者在出现错误的情况下导致死锁或崩溃。更准确地说,通常应该避免在探测回调中调用与GUI相关的功能,也不要尝试更改管道的状态。应用程序可以在管道总线上发布自定义消息,以与主应用程序线程进行通信,并执行诸如停止管道之类的操作。
以下是有关使用数据探针的示例。不确定你要查找的内容的,可以将该程序的输出与gst-launch-1.0 videotestsrc ! xvimagesink输出进行比较:
#include
static GstPadProbeReturn
cb_have_data (GstPad *pad,
GstPadProbeInfo *info,
gpointer user_data)
{
gint x, y;
GstMapInfo map;
guint16 *ptr, t;
GstBuffer *buffer;
buffer = GST_PAD_PROBE_INFO_BUFFER (info);
buffer = gst_buffer_make_writable (buffer);
/* Making a buffer writable can fail (for example if it
* cannot be copied and is used more than once)
*/
if (buffer == NULL)
return GST_PAD_PROBE_OK;
/* Mapping a buffer can fail (non-writable) */
if (gst_buffer_map (buffer, &map, GST_MAP_WRITE)) {
ptr = (guint16 *) map.data;
/* invert data */
for (y = 0; y < 288; y++) {
for (x = 0; x < 384 / 2; x++) {
t = ptr[384 - 1 - x];
ptr[384 - 1 - x] = ptr[x];
ptr[x] = t;
}
ptr += 384;
}
gst_buffer_unmap (buffer, &map);
}
GST_PAD_PROBE_INFO_DATA (info) = buffer;
return GST_PAD_PROBE_OK;
}
gint
main (gint argc,
gchar *argv[])
{
GMainLoop *loop;
GstElement *pipeline, *src, *sink, *filter, *csp;
GstCaps *filtercaps;
GstPad *pad;
/* init GStreamer */
gst_init (&argc, &argv);
loop = g_main_loop_new (NULL, FALSE);
/* build */
pipeline = gst_pipeline_new ("my-pipeline");
src = gst_element_factory_make ("videotestsrc", "src");
if (src == NULL)
g_error ("Could not create 'videotestsrc' element");
filter = gst_element_factory_make ("capsfilter", "filter");
g_assert (filter != NULL); /* should always exist */
csp = gst_element_factory_make ("videoconvert", "csp");
if (csp == NULL)
g_error ("Could not create 'videoconvert' element");
sink = gst_element_factory_make ("xvimagesink", "sink");
if (sink == NULL) {
sink = gst_element_factory_make ("ximagesink", "sink");
if (sink == NULL)
g_error ("Could not create neither 'xvimagesink' nor 'ximagesink' element");
}
gst_bin_add_many (GST_BIN (pipeline), src, filter, csp, sink, NULL);
gst_element_link_many (src, filter, csp, sink, NULL);
filtercaps = gst_caps_new_simple ("video/x-raw",
"format", G_TYPE_STRING, "RGB16",
"width", G_TYPE_INT, 384,
"height", G_TYPE_INT, 288,
"framerate", GST_TYPE_FRACTION, 25, 1,
NULL);
g_object_set (G_OBJECT (filter), "caps", filtercaps, NULL);
gst_caps_unref (filtercaps);
pad = gst_element_get_static_pad (src, "src");
gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_BUFFER,
(GstPadProbeCallback) cb_have_data, NULL, NULL);
gst_object_unref (pad);
/* run */
gst_element_set_state (pipeline, GST_STATE_PLAYING);
/* wait until it's up and running or failed */
if (gst_element_get_state (pipeline, NULL, NULL, -1) == GST_STATE_CHANGE_FAILURE) {
g_error ("Failed to go into PLAYING state");
}
g_print ("Running ...\n");
g_main_loop_run (loop);
/* exit */
gst_element_set_state (pipeline, GST_STATE_NULL);
gst_object_unref (pipeline);
return 0;
}
严格来说,仅当缓冲区可写时,才允许填充探针回调修改缓冲区内容。是否是这种情况在很大程度上取决于管道和涉及的元件。通常是这种情况,但有时并非如此,如果不是这样,则对数据或元数据的意外修改可能会引入难以调试和跟踪的错误。你可以使用gst_buffer_is_writable ()来检查缓冲区是否可写。由于你可以传回与传入的缓冲区不同的缓冲区,因此最好使用gst_buffer_make_writable ()将该缓冲区写入回调函数中。
pad式探头最适合查看数据通过管道时的情况。如果需要修改数据,则应该编写自己的GStreamer元件,例如GstAudioFilter,GstVideoFilter或 GstBaseTransform使之相当容易。
如果你只想在缓冲区通过管道时对其进行检查,则甚至无需设置pad探针。你也可以只将标识元件插入管道中并连接到其“切换”信号。identity元件还提供了一些有用的调试工具,例如 dump和last-message属性。通过将'-v'开关传递到gst-launch并将silent身份的属性设置为FALSE来启用。
在此示例中,我们将向你展示如何播放媒体文件的一部分。目标是只播放2到5秒,然后退出。
第一步,我们将uridecodebin元件设置为PAUSED状态,并确保我们阻止所有创建的pad。当所有的pad都被阻塞时,我们在所有pad上都有数据,我们说它们uridecodebin已经预卷。
在预卷管道中,我们可以询问媒体的持续时间,也可以执行搜索。我们在管道上执行搜索操作以选择2到5秒部分。
配置完所需的部分后,我们可以链接接收器元件,解除对pad的阻塞并将管道设置为PLAYING状态。你会看到接收器在转到EOS之前已准确显示了所请求的区域。
这是代码:
#include
static GMainLoop *loop;
static volatile gint counter;
static GstBus *bus;
static gboolean prerolled = FALSE;
static GstPad *sinkpad;
static void
dec_counter (GstElement * pipeline)
{
if (prerolled)
return;
if (g_atomic_int_dec_and_test (&counter)) {
/* all probes blocked and no-more-pads signaled, post
* message on the bus. */
prerolled = TRUE;
gst_bus_post (bus, gst_message_new_application (
GST_OBJECT_CAST (pipeline),
gst_structure_new_empty ("ExPrerolled")));
}
}
/* called when a source pad of uridecodebin is blocked */
static GstPadProbeReturn
cb_blocked (GstPad *pad,
GstPadProbeInfo *info,
gpointer user_data)
{
GstElement *pipeline = GST_ELEMENT (user_data);
if (prerolled)
return GST_PAD_PROBE_REMOVE;
dec_counter (pipeline);
return GST_PAD_PROBE_OK;
}
/* called when uridecodebin has a new pad */
static void
cb_pad_added (GstElement *element,
GstPad *pad,
gpointer user_data)
{
GstElement *pipeline = GST_ELEMENT (user_data);
if (prerolled)
return;
g_atomic_int_inc (&counter);
gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM,
(GstPadProbeCallback) cb_blocked, pipeline, NULL);
/* try to link to the video pad */
gst_pad_link (pad, sinkpad);
}
/* called when uridecodebin has created all pads */
static void
cb_no_more_pads (GstElement *element,
gpointer user_data)
{
GstElement *pipeline = GST_ELEMENT (user_data);
if (prerolled)
return;
dec_counter (pipeline);
}
/* called when a new message is posted on the bus */
static void
cb_message (GstBus *bus,
GstMessage *message,
gpointer user_data)
{
GstElement *pipeline = GST_ELEMENT (user_data);
switch (GST_MESSAGE_TYPE (message)) {
case GST_MESSAGE_ERROR:
g_print ("we received an error!\n");
g_main_loop_quit (loop);
break;
case GST_MESSAGE_EOS:
g_print ("we reached EOS\n");
g_main_loop_quit (loop);
break;
case GST_MESSAGE_APPLICATION:
{
if (gst_message_has_name (message, "ExPrerolled")) {
/* it's our message */
g_print ("we are all prerolled, do seek\n");
gst_element_seek (pipeline,
1.0, GST_FORMAT_TIME,
GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE,
GST_SEEK_TYPE_SET, 2 * GST_SECOND,
GST_SEEK_TYPE_SET, 5 * GST_SECOND);
gst_element_set_state (pipeline, GST_STATE_PLAYING);
}
break;
}
default:
break;
}
}
gint
main (gint argc,
gchar *argv[])
{
GstElement *pipeline, *src, *csp, *vs, *sink;
/* init GStreamer */
gst_init (&argc, &argv);
loop = g_main_loop_new (NULL, FALSE);
if (argc < 2) {
g_print ("usage: %s ", argv[0]);
return -1;
}
/* build */
pipeline = gst_pipeline_new ("my-pipeline");
bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
gst_bus_add_signal_watch (bus);
g_signal_connect (bus, "message", (GCallback) cb_message,
pipeline);
src = gst_element_factory_make ("uridecodebin", "src");
if (src == NULL)
g_error ("Could not create 'uridecodebin' element");
g_object_set (src, "uri", argv[1], NULL);
csp = gst_element_factory_make ("videoconvert", "csp");
if (csp == NULL)
g_error ("Could not create 'videoconvert' element");
vs = gst_element_factory_make ("videoscale", "vs");
if (csp == NULL)
g_error ("Could not create 'videoscale' element");
sink = gst_element_factory_make ("autovideosink", "sink");
if (sink == NULL)
g_error ("Could not create 'autovideosink' element");
gst_bin_add_many (GST_BIN (pipeline), src, csp, vs, sink, NULL);
/* can't link src yet, it has no pads */
gst_element_link_many (csp, vs, sink, NULL);
sinkpad = gst_element_get_static_pad (csp, "sink");
/* for each pad block that is installed, we will increment
* the counter. for each pad block that is signaled, we
* decrement the counter. When the counter is 0 we post
* an app message to tell the app that all pads are
* blocked. Start with 1 that is decremented when no-more-pads
* is signaled to make sure that we only post the message
* after no-more-pads */
g_atomic_int_set (&counter, 1);
g_signal_connect (src, "pad-added",
(GCallback) cb_pad_added, pipeline);
g_signal_connect (src, "no-more-pads",
(GCallback) cb_no_more_pads, pipeline);
gst_element_set_state (pipeline, GST_STATE_PAUSED);
g_main_loop_run (loop);
gst_element_set_state (pipeline, GST_STATE_NULL);
gst_object_unref (sinkpad);
gst_object_unref (bus);
gst_object_unref (pipeline);
g_main_loop_unref (loop);
return 0;
}
请注意,我们使用自定义应用程序消息来向主线程发出 uridecodebin已预卷的信号。然后,主线程将向请求的区域发出刷新搜索。刷新将暂时解除pad的阻塞,并在新数据再次到达时重新阻塞它们。我们检测到第二个块以删除探针。然后我们将管道设置为PLAYING,它应该播放选定的2到5秒部分;应用程序等待EOS消息并退出。