GStreamer官方教程系列
Pipeline manipulation
本节展示了诸多可以用来操纵你的应用中的管道(pipeline)的方法。主要有以下主题:
- 怎么将一个应用中的数据插入到管道中。
- 怎么从一个管道中读取数据。
- 怎么控制管道的速度、长度以及起始点。
- 怎么监听一个管道的数据处理过程。
本节部分内容十分底层,你需要一些编程经验并且对GStreamer有较好的理解能力来阅读这些内容。
使用探针(probe)
*最好将探测想象为有一个pad监听器。从技术上来讲,一个探针就是一个回调,可以用gst_pad_add_probe()来将回调插入一个pad中。相反,你可以使用gst_pad_remove()来移除回调。当添加了回调,探针会提醒你pad发生的各种活动。你可以在添加探针的时候定义你感兴趣活动的消息。
探针种类:
- 推出或拉取缓冲区。可以在注册探针时指定为GST_PAD_PROBE_TYPE_BUFFER。因为pad可以被多种方式安排。也可以用GST_PAD_PROBE_TYPE_PUSH和GST_PAD_PROBE_TYPE_PULL选项来制定你需要的安排方式。你可以使用这个探针来检查、修改或删除缓冲区。
- 推缓冲区列表。在注册探针时使用GST_PAD_PROBE_TYPE_BUFFER_LIST。
- 一个事件(event)经过pad。使用GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM或GST_PAD_PROBE_TYPE_EVENT_UPSTREAM标志(flag)来选择顺流或逆流事件。亦或使用GST_PAD_PROBE_TYPE_EVENT_BOTH来得知双向的事件消息。默认的,刷新时间不会通知。但也可以通过显式地加入GST_PAD_PROBE_TYPE_EVENT_FLUSH来获取刷新事件的回调。事件仅通过推送模式来通知。你可以使用这种探针来检查、修改或删除事件。
- 经过pad的请求。使用GST_PAD_PROBE_TYPE_QUERY_DOWNSTREAM或GST_PAD_PROBE_TYPE_QUERY_UPSTREAM标志(flag)来选择顺流或逆流请求。可以使用GST_PAD_PROBE_TYPE_QUERY_BOTH来方便地获取双向的请求。请求探针会提醒两次:当请求顺流/逆流经过和请求结果返回。你可以分别使用GST_PAD_PROBE_TYPE_PUSH或GST_PAD_PROBE_TYPE_PULL来选择在什么情况下调用回调。你可以使用请求探针检查或修改请求,甚至在探针的回调中回复请求。要回复请求,你可以将结果填入请求中并从回调中返回GST_PAD_PROBE_DROP。
- 除了提醒你数据流,你也可以在回调返回的时候令探针阻塞数据流。这叫做阻塞探针,可以指定GST_PAD_PROBE_TYPE_BLOCK标志来激活。你可以同时使用其他标志来选定某种活动发生时阻塞数据流。移除探针或回调返回GST_PAD_PROBE_REMOVE时,阻塞的pad会重新疏通。你可以通过令回调返回GST_PAD_PROBE_PASS仅通过现在阻塞的项目,这会在下一个项目上再次阻塞。阻塞探针用于临时阻塞你将要解除链接或没有链接的探针。如果数据流没有阻塞,管道会由于数据推入一个没有链接的帕的而进入错误状态。我们会探究如何使用阻塞探针来部分地预滚管道。
- 在pad中没有活动发生时提醒。使用GST_PAD_PROBE_TYPE_IDLE标志注册探针。你可以指定GST_PAD_PROBE_TYPE_PUSH和/或GST_PAD_PROBE_TYPE_PULL来选择pad处于哪种安排模式下提醒。这种探针(IDLE probe)也是一种阻塞探针,只要安装了这种探针,它就不会允许数据经过pad。你可以使用闲置探针来动态重链接一个pad。我们会探究如何使用闲置探针来替换管道中的某一个组件。
数据探针
数据探针会在数据经过pad时提示你。将GST_PAD_PROBE_TYPE_BUFFER和/或
GST_PAD_PROBE_TYPE_BUFFER_LIST传递给gst_pad_add_probe()来创建这种探针。大多数寻常的缓冲操作组件在_chain()函数中能够做到的,同样可以在探针回调中做到。
数据探针运行在管道流线程上下文中,因此回调应该尽量避免阻塞和做一些奇怪的操作。这么做的话可能会对管道的性能有负面影响,万一有bug,会导致死锁或崩溃。更确切地说,在探针回调中应该尽量避免调用GUI相关函数,不要尝试改变管道状态。一个应用可以在管道总线中发送自定义消息来与应用主线程通信并使主线程执行诸如停止管道之类的操作。
下面是一个使用数据探针的例子。比较这个程序与gst-launch-1.0 videotestsrc ! xvimagesink的输出,如果你不清楚程序的目的:
#include <gst/gst.h>
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;
}
严格来说一个pad探针回调仅允许修改缓冲内容,如果缓冲允许写入的话。情况是否如此很大程度上取决于管道及其中的组件。相当多的时候,是这种情况,然而有些时候又不是,并且,如果不是的话那么意外的数据或元数据修改会导致出现很难调试和追踪的bug。你可以通过gst_buffer_is_writable()函数来确认缓冲区是否允许写入。由于你可以传递回一个不同于传入的缓冲区,最好在回调中使用gst_buffer_make_writable()函数来使缓冲区可写。
Pad探针非常适合查看通过管道的数据。如果你需要修改数据,你应该编写你自己的GStreamer组件。诸如GstAudioFilter,GstVideoFilter或GstBaseTransform的基类使得这相当容易。
如果你只是想当缓冲经过管道时检查它,你甚至不需要创建一个pad探针。你可以向管道插入一个验证组件并连接到“handoff”信号。验证组件提供了一些有用的调试工具,比如dump和last-message属性,后者可以通过向gst-launch添加-v来激活,并将验证的silent属性设置为FALSE。
播放媒体文件的某一段
在本案例中我们会展示如何播放媒体文件中的某一段。目标是仅播放2-5s的部分然后退出。
第一步中,我们将uridecodebin组件设置为PAUSED的状态并确保阻塞了所有已经创建了的源pad。当所有源pad被阻塞,他们都有了数据,我们称之为预滚。
完成预滚的管道中,我们可以获取媒体时长并且执行跳转。通过在管道中执行跳转来选择2-5s部分。
在设置好我们想要的部分后,我们可以链接槽组件,打开阻塞的源pad并设置管道为PLAYING状态。你会在结束前看到设置的部分在槽中播放。
#include <gst/gst.h>
static GMainLoop *loop;
static 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