GstPadProbe

Add probe callback for GstPad.

GStreamer-APP章节讲到了应用程序和GStreamer pipeline进行数据方式的一种方式,并且在示例中,使用appsink完成了从pipeline中取图像数据绘制,并把绘制后的图像经由appsrc重新送回pipeline中,这是目前基于GStreamer框架开发的应用程序最简单的一种架构。但是需要注意的是这里的appsinkappsrc实际上是两条pipeline,使用起来非常麻烦。在这篇教程中我将展示如何使用类似于Basic tutorial 7: Multithreading and Pad Availability中一样的example pipeline来实现相同的目标。

GstPadProbe

GstElement实际是通过GstPad完成连接,这是一种非常轻量的原始链接点。数据在GstPad之间进行传递,

gst_pad_add_probe()

gulong
gst_pad_add_probe (GstPad * pad,
GstPadProbeType mask,
GstPadProbeCallback callback,
gpointer user_data,
GDestroyNotify destroy_data)

在pad状态发生改变的时候发出通知,为匹配掩码的每个状态调用提供的回调函数。

pad:添加probe的GstPad

mask:probe的掩码,详细参考GstPadProbeType

callback:回调函数指针

user_data:用户传递给回调的数据

destroy_data

返回值是一个无符号整型id,用于标识probe,gst_pad_remove_probe()释放probe用.

GstPadProbeCallback

GstPadProbeReturn
(*GstPadProbeCallback) (GstPad * pad,
GstPadProbeInfo * info,
gpointer user_data)

pad的对应状态下调用的probe回调函数,可以修改info指向的数据。

GstPadProbeInfo

struct _GstPadProbeInfo
{
GstPadProbeType type;
gulong id;
gpointer data;
guint64 offset;
guint size;

/*< private >*/
union {
gpointer _gst_reserved[GST_PADDING];
struct {
GstFlowReturn flow_ret;
} abi;
} ABI;
};

data根据不同的probe type具有不同的类型,可以直接操作data指针,也可以通过GstPadProbeInfo提供的借口获取其下的数据。常用的有gst_pad_probe_info_get_buffer(),用于获取经过pad的GstBuffer。

Pipeline

Overview

GStreamer-APP教程的实例中,我们通过appsink将GstBuffer传递到用户空间然后使用OpenCV绘制了一个红色的矩形框和appsink字符串,并且通过appsrc的回调中绘制了一个绿色的矩形框和appsrc字符串,最后将绘制后的cv::Mat转为GstBuffer送回pipeline中并用waylandsink显示在屏幕上。

开头说过,appsinkappsrc各为一条pipeline,为了程序的正常运行,需要用户自行维护两条pipeline的数据同步,这是一个令人头疼的问题,并且为了画图总共发生了两次内存拷贝,这在应用中将占用一部分CPU性能。在本教程中,我们通过在queue0src pad中注册一个GST_PAD_PROBE_TYPE_BUFFER类型的probe回调,取出经过queue0的GstBuffer并将要绘制的内容直接加到该buffer的metadata中,使用qtioverlay完成了相关内容的绘制。

qtioverlay

qtioverlay是高通平台上的一个Overlay插件,内部依赖metadata调用C2D库完成了在NV12图像上bounding box和一个简单的bbox text的绘制,为了支持动态修改overlay color,我为其添加了一个meta-color的property,有关修改和使用的详情请阅读Qualcomm-gst-plugin: qtioverlay

假如只需要在NV12图像上画矩形框,draw-yuv-rectangle库实现了相同的功能。

Issue

tee的request-pad

Basic tutorial 7: Multithreading and Pad Availability中使用了gst_element_request_pad_simple()tee请求生成的src-pad,并且使用gst_element_release_request_pad()释放请求的GstPad。

GstPad *
gst_element_request_pad_simple (GstElement * element,
const gchar * name)

但是需要注意的是gst_element_request_pad_simple()是在GStreamer-1.20之后才引入的新特性,旧的版本应该使用gst_element_get_request_pad()来申请。

GstBuffer isn't writable

  • cb_queue0_probe()

(GstPadProbe:9069): GStreamer-CRITICAL **: 14:05:03.871: gst_buffer_add_meta: assertion 'gst_buffer_is_writable (buffer)' failed

1. 等待GstBuffer同步

Buffers not writable after tee这个关于tee的issue中提到:

If there are multiple references to a single buffer, writing while another thread may be reading results in data corruption.

假如传递的buffer存在多个引用,在一个线程读buffer的同时在另一个线程中执行写buffer操作会引起竞争。

这句话的核心在于pipeline的多个分支线程中维护的其实是同一个buffer的不同引用,这是建立在tee插件只做了浅拷贝而不是深拷贝的基础上的,官方对于tee的说明其实比较含糊,只提到Split data to multiple pads.用的是split而不是copy也不是reference,所以我也并不确定tee的底层机制。

假如基于tee只是增加引用计数的思路来考虑,这就意味着display branch和appsink branch使用的是同一个GstBuffer的不同引用,也就是当cb_queue0_probe()请求访问probe buffer的时候,qtivtransform有可能正在对这个buffer进行读写操作,这时候为了线程安全自然应该上锁,所以probe buffer不可写。

因此我的解决思路是给qtivtransformsrc-pad也加一个probe,当GstBuffer到达src-pad时说明qtivtransform的操作已经完成,这时进行一个unlock通知cb_queue0_probe取buffer并进行相关操作即可。

// sync
if (info->type & GST_PAD_PROBE_TYPE_BUFFER && !vp->isExited) {
g_mutex_lock (&vp->m_syncMuxtex);
while (g_atomic_int_get (&vp->m_syncCount) <= 0)
g_cond_wait (&vp->m_syncCondition, &vp->m_syncMuxtex);
if (!g_atomic_int_dec_and_test (&vp->m_syncCount)) {
//LOG_INFO_MSG ("m_syncCount:%d/%d", vp->m_syncCount,
// vp->pipeline_id_);
}
g_mutex_unlock (&vp->m_syncMuxtex);
}

// osd the result
if (vp->m_getResultFunc) {
const std::shared_ptr<cv::Rect> result =
vp->m_getResultFunc (vp->m_getResultArgs);
if (result && vp->m_procDataFunc) {
vp->m_procDataFunc (buffer, result);
}
}

2. gst_buffer_make_writable()

查看GstBffer文档可以知道,我们还可以通过gst_buffer_make_writable()来拷贝一份buffer,使得buffer可写,而且假如原buffer已经可写,那么这个调用只是简单的返回,拷贝并不会发生,因此不会造成过多的性能损耗。

buffer操作完之后再使用gst_pad_push()将buffer传递给与srd pad连接的下一个插件的sink pad中即可。

buffer = gst_buffer_make_writable (buffer);

// osd the result
if (vp->m_getResultFunc) {
const std::shared_ptr<cv::Rect> result =
vp->m_getResultFunc (vp->m_getResultArgs);
if (result && vp->m_procDataFunc) {
vp->m_procDataFunc (buffer, result);
}
}

gst_pad_push (pad, buffer);

Summary

至此我相信读者已经具备了开发自己的pipeline的能力,作为一个嵌入式平台的开发者,性能永远是第一目标,因此在实际使用中pipeline的架构需要反复斟酌优化。事实上通过GStreamer-APP和GstPadProbe两个例子,应该已经具备了初步的优化意识,关于架构优化,欢迎读者按顺序阅读下面三个repo的README,它记录了我基于GStreamer框架下的一个yolov3物体识别算法视频应用的从诞生到完善的完整流程:

Ericsson-Yolov3-SNPE

Gst-AIDemo-Optimize

yolov3-thread-pool

希望能给各位一些启发。

Last updated