文中部分图片来自网络,侵权必删!
前言
GStreamer(Gst)最初做为一个多媒体框架在业界闻名,基于插件(plugins)的流水线(Pipeline)管理使得其在多媒体处理中极其灵活。插件化的需求在AI领域也有应用场景,GStreamer在AI领域(Nvidia AI 框架DeepStream)的应用也非常广泛。
一、GStreamer框架
https://juejin.cn/post/7025435937500823582
1. gstreamer tools:gst-inspect用于查看插件的相关信息,gst-launch用于命令行启动创建一个应用链路,gst-editor没有用过;
2. gstreamer core framework:GStreamer核心框架,体统整个框架的管理,核心模块包括element,event,buffer,bus,pipeline,clock,synchronization等;
类型 | 发送 | 接收 | 应答 |
---|---|---|---|
message | element | bus | |
event | Application, element | element | |
query | application | element | Application |
buffer | element | element |
3. gstreamer plugins:多种类型的插件,包括协议类,数据源,格式转换,编解码,滤波,数据宿,以及三方插件(AI插件).
二、基础概念
在使用Gst之前,我们先熟悉几个基础概念,以便更好地了解GStreamer工作原理。
1. Element
Gstreamer- 元素(Elements)_gstreamer 释放element-CSDN博客
/**
* GstElement:
* @state_lock: Used to serialize execution of gst_element_set_state()
* @state_cond: Used to signal completion of a state change
* @state_cookie: Used to detect concurrent execution of
* gst_element_set_state() and gst_element_get_state()
* @target_state: the target state of an element as set by the application
* @current_state: the current state of an element
* @next_state: the next state of an element, can be #GST_STATE_VOID_PENDING if
* the element is in the correct state.
* @pending_state: the final state the element should go to, can be
* #GST_STATE_VOID_PENDING if the element is in the correct state
* @last_return: the last return value of an element state change
* @bus: the bus of the element. This bus is provided to the element by the
* parent element or the application. A #GstPipeline has a bus of its own.
* @clock: the clock of the element. This clock is usually provided to the
* element by the toplevel #GstPipeline.
* @base_time: the time of the clock right before the element is set to
* PLAYING. Subtracting @base_time from the current clock time in the PLAYING
* state will yield the running_time against the clock.
* @start_time: the running_time of the last PAUSED state
* @numpads: number of pads of the element, includes both source and sink pads.
* @pads: (element-type Gst.Pad): list of pads
* @numsrcpads: number of source pads of the element.
* @srcpads: (element-type Gst.Pad): list of source pads
* @numsinkpads: number of sink pads of the element.
* @sinkpads: (element-type Gst.Pad): list of sink pads
* @pads_cookie: updated whenever the a pad is added or removed
* @contexts: (element-type Gst.Context): list of contexts
*
* GStreamer element abstract base class.
*/
struct _GstElement
{
GstObject object;
/*< public >*/ /* with LOCK */
GRecMutex state_lock;
/* element state */
GCond state_cond;
guint32 state_cookie;
GstState target_state;
GstState current_state;
GstState next_state;
GstState pending_state;
GstStateChangeReturn last_return;
GstBus *bus;
/* allocated clock */
GstClock *clock;
GstClockTimeDiff base_time; /* NULL/READY: 0 - PAUSED: current time - PLAYING: difference to clock */
GstClockTime start_time;
/* element pads, these lists can only be iterated while holding
* the LOCK or checking the cookie after each LOCK. */
guint16 numpads;
GList *pads;
guint16 numsrcpads;
GList *srcpads;
guint16 numsinkpads;
GList *sinkpads;
guint32 pads_cookie;
/* with object LOCK */
GList *contexts;
/*< private >*/
gpointer _gst_reserved[GST_PADDING-1];
};
typedef enum {
GST_STATE_VOID_PENDING = 0,
GST_STATE_NULL = 1,
GST_STATE_READY = 2,
GST_STATE_PAUSED = 3,
GST_STATE_PLAYING = 4
} GstState;
1.1 Source element
Source elements是pipeline的起点,生成供pipeline使用的数据,例如从文件读取音视频数据,从摄像头读取图像,从声卡读取音频数据。Source element不接受数据,它们只生成数据,通常在Source element的右侧绘制一个source pad。
1.2 Sink element
Sink elements是 pipeline的终点,使用pipeline的数据,例如将音视频数据写到文件,向Display写入图像,向声卡写入音频数据。 同Source element相反,他们只消费数据但不产生任何东西。 通常在sink element的左侧绘制一个sink pad。
1.3 Filter elements
Filter elements是 pipeline的中间处理元素,他们消费前序元素的数据,经过处理后向后续元素生成新的数据。 例如音量控制器、音视频解码器,AI推理引擎等。
Filter-Like 的elements 可以有任意数量的source pad 和 sink pad。 例如,音量控制器(FIlter)有一个sink pad 和一个source pad。
音频码器(Filter-Like )则有一个sink pad和多个source pads。
初次接触pad的时候我们或许会有疑惑:传统理解上一个element的左侧应该是source pad,右侧是sink pad,为什么在GStreamer的中不是这样定义的呢?
GStreamer中是这样定义的:souce pad生成数据,sink pad消费数据。
按照上诉定义,source element应该只有source pad,sink element应该只有sink pad,filter element左侧消费数据(sink pad),右侧生成数据(source pad)
所以在GStreamer中,一个典型的pipeline如下:
source element生成数据,经由filter element实现处理,最终在sink element中完成消费。
2. Pad
GStreamer Pad and Capabilities Negotiation_gstreamer pad 能力协商-CSDN博客
pad是GStreamer Element必不可少的组成部分,是element间交换数据的端口。
**
* GstPad:
* @element_private: private data owned by the parent element
* @padtemplate: padtemplate for this pad
* @direction: the direction of the pad, cannot change after creating
* the pad.
*
* The #GstPad structure. Use the functions to update the variables.
*/
struct _GstPad {
GstObject object;
/*< public >*/
gpointer element_private;
GstPadTemplate *padtemplate;
GstPadDirection direction;
/*< private >*/
/* streaming rec_lock */
GRecMutex stream_rec_lock;
GstTask *task;
/* block cond, mutex is from the object */
GCond block_cond;
GHookList probes;
GstPadMode mode;
GstPadActivateFunction activatefunc;
gpointer activatedata;
GDestroyNotify activatenotify;
GstPadActivateModeFunction activatemodefunc;
gpointer activatemodedata;
GDestroyNotify activatemodenotify;
/* pad link */
GstPad *peer;
GstPadLinkFunction linkfunc;
gpointer linkdata;
GDestroyNotify linknotify;
GstPadUnlinkFunction unlinkfunc;
gpointer unlinkdata;
GDestroyNotify unlinknotify;
/* data transport functions */
GstPadChainFunction chainfunc;
gpointer chaindata;
GDestroyNotify chainnotify;
GstPadChainListFunction chainlistfunc;
gpointer chainlistdata;
GDestroyNotify chainlistnotify;
GstPadGetRangeFunction getrangefunc;
gpointer getrangedata;
GDestroyNotify getrangenotify;
GstPadEventFunction eventfunc;
gpointer eventdata;
GDestroyNotify eventnotify;
/* pad offset */
gint64 offset;
/* generic query method */
GstPadQueryFunction queryfunc;
gpointer querydata;
GDestroyNotify querynotify;
/* internal links */
GstPadIterIntLinkFunction iterintlinkfunc;
gpointer iterintlinkdata;
GDestroyNotify iterintlinknotify;
/* counts number of probes attached. */
gint num_probes;
gint num_blocked;
GstPadPrivate *priv;
union {
gpointer _gst_reserved[GST_PADDING];
struct {
GstFlowReturn last_flowret;
GstPadEventFullFunction eventfullfunc;
} abi;
} ABI;
};
/**
* GstPadDirection:
* @GST_PAD_UNKNOWN: direction is unknown.
* @GST_PAD_SRC: the pad is a source pad.
* @GST_PAD_SINK: the pad is a sink pad.
*
* The direction of a pad.
*/
typedef enum {
GST_PAD_UNKNOWN,
GST_PAD_SRC,
GST_PAD_SINK
} GstPadDirection;
typedef enum {
GST_PAD_MODE_NONE,
GST_PAD_MODE_PUSH,
GST_PAD_MODE_PULL
} GstPadMode;
2.1 pad属性 - Property
pad有两个属性:
- direction(方向性):数据流入端叫sink pad, 数据流出端叫source pad;
- availability(可用性):静态pad(always),动态pad(sometimes)和手动pad(on-request);
可用性是指一个element上有几个pad不是固定的:
- 有些pad在element的整个生命周期都存在,这些pad 称为静态pad,也叫always pad;
- 有些pad则是根据需要由element自行创建,比如demuxer element需要根据多媒体文件包含的数据源信息(音频,视频,字幕等)动态的创建;
- 有些pad是由应用程序根据使用场景手动创建的,比如tee element可以根据需要拷贝多份数据到不同的分支。下图中ksvideosrc是一个camera source 插件,我们需要在播放摄像头画面的同时,将摄像头数据编码保存到本地,这时候我们就需要用到tee element,在它的输出端手动创建两个source pad,分别用来预览和编码;
2.2 pad能力 - Capabilities
pad的能力,这里的能力就是可以流经这个pad的数据流格式。我们知道GStreamer pipeline是由一系列互相连接的element组成的数据流通道, 当数据从一个element流向另外一个element时,数据类型必须是双方都能够识别的,从这个角度来讲,数据类型当然是越简单越好。 但显然element的实现者们希望它们的 element 能够尽可能的处理更多的数据类型。 鉴于多媒体数据类型的庞杂, 因此需要一套管理element之间数据类型协商的机制。由于GStreamer element之间的连接是基于pad的, 因此处理数据的能力就定义在pad上,而不是element上。
能力描述了在两个pads之间传输的数据类型,或者一个pad支持的数据类型。能力可以提供如下功能:
-
自动插件(Autoplugging):根据其能力自动查找要链接到pad的element。所有自动插件都使用这种方法。
-
兼容性检测(Compatibility detection):当两个 pad 链接时,GStreamer 可以验证两个 pad 是否在协商相同的媒体类型。连接两个pads并检查它们是否兼容的过程称为“能力协商”。
-
元数据(Metadata):通过从 pad 读取处理能力,应用程序可以提供有关正在通过 pad 流式传输的媒体类型的信息,这是有关当前正在播放的流的信息。
-
过滤(Filtering):应用程序可以使用处理能力将可以在两个 pad 之间流式传输的可能媒体类型限制为其支持的流类型的特定子集。例如,应用程序可以使用“filtered caps”来设置在两个 pad之间传输特定的(固定或非固定)视频大小。Caps 过滤器通常放置在诸如 audioconvert、audioresample、videoconvert 或 videoscale 之类的转换器element之后,以强制这些转换器在流中的某个点将数据转换为特定的输出格式。
pad的能力在 GstCaps 对象中描述,一个 GstCaps 将包含一个或多个 GstStructure 来描述一种媒体类型。已协商的pad将具有的功能集仅仅包含在一个结构体 (structure) 中。
下面显示的是vorbisdec element的pad能力,可以通过运行下面的命名获取到:
gst-inspect-1.0 vorbisdec
这里我们看到两个Pad: SINK pad和SRC pad,这两个pad的可用性为Always并且都附加了能力:sink pad 将接受 x-vorbis 编码的音频数据,媒体类型为“audio/x-vorbis”。src pad具有原始音频媒体类型(在本例中为“audio/x-raw”)。src pad还包含音频格式,采样率和通道数的属性,以及您现在不需要担心的其他属性。
2.2.1 能力协商 - Caps Negotiation
作为插件化管理的框架,GStreamer希望element支持的能力越多越好,譬如上面例子中我们看到vorbisdec element,该element支持很大范围的rate和channels。如果我们将其与alsasink的两个pad连接起来的时候,如何确定rate和channels属性的最终值呢? 这就涉及到了GStreamer中另外一个重要概念: 能力协商(Caps Negotiation)
Caps Negotiation定义了一套element之间互相询问,回答与事件处理机制。 这里有两个概念:查询(Query)和事件(Event)
2.2.1.1 查询
GStreamer内部的Query可以有很多种, 主要涉及到的有下面两个:
- GST_QUERY_CAPS: 用来查询相邻pad所支持的caps
gst_pad_peer_query_caps(srcpad, filter)是srcpad向sinkpad发出的一个GST_QUERY_CAPS查询,旨在获取element2 sinkpad端所支持的所有符合条件的caps集合(这里符合条件是指能够通过filter 过滤的caps, 但是如果filter = NULL, 则会返回所有支持的caps)
- GST_QUERY_ACCEPT_CAPS:用于相邻pad确认最终支持的caps
当element2返回caps列表之后, element1 需要遍历列表并选出最优选线(a fixed caps), 并再次向element2发出查询。
2.2.1.2 事件
在查询的过程中会涉及到两个事件:
- GST_EVENT_CAPS
这是上游element通知下游element,一个fixed caps 已经被选定,各位应该做好相应的准备,等着接受由上游传递的buffer来处理吧 ,buffer的格式则由这个指定的fixed caps限定了。
if( gst_pad_peer_query_accept_caps(srcpad, fixed_caps) )
{
gst_pad_push_event( srcpad, gst_event_new_caps( fixed_caps ));
}
此时我们的能力协商工作已经完成了, 下图总结了此过程:
- GST_EVENT_RECONFIGURE
在以上的能力协商中,发起查询,发送事件的都是上游element, 下游element则一直回复和处理这些请求或事件,最终fixed caps 也是由上游确定的。那么下游element 有没有办法发起一次能力协商或者有自己的意志去选择合适的数据类型呢? 这就是GST_EVENT_RECONFIGURE事件的作用, 它是由下游发送给上游。
一个GST_EVENT_RECONFIGURE事件的发起首先由下游确定一个fixed caps, 然后通过GST_QUERY_ACCEPT_CAPS查询上游是否支持,如果支持的话再发送GST_EVENT_RECONFIGURE事件,此时上游发起一次新的能力协商,在上游查询GstCaps列表时下游只返回选定的fixed caps。
2.2.2 元数据使用能力
一个pad可以有一组附加到它的处理能力,处理能力表示为一个或多个 GstStructures 的数组,每个 GstStructure 是一组字段,其中每个字段由字段名称字符串("format", "layout")+类型值(INT, INT_RANGE)组成。
format: { (string)F64LE, (string)F64BE, (string)F32LE, (string)F32BE }
layout: interleaved
rate: [ 1, 2147483647 ]
channels: [ 1, 2147483647 ]
pad能力的明显区别:
- pad的可能(possible)处理能力:通常是通过 gst-inspect查找显示出来的pad templates的caps;
- pad的允许(allowed)处理能力:可以与pad的template caps 相同或其中的一个子集,取决于对端pad的可能的caps;
- pad的协商(negotiated)处理能力:这些描述了流或缓冲区的确切格式,只包含一个结构体并且没有像范围或列表这样的可变值,即它们是固定的caps
您可以通过查询结构体中的各个属性来获取一组处理能力中的属性值。您可以使用 gst_caps_get_structure () 从 caps 中获取GstStructure结构,并使用 gst_caps_get_size () 获取 GstCaps 中的GstStructure结构体的数量。下面是从一组固定视频caps中提取宽度和高度的示例:
static void
read_video_props (GstCaps *caps)
{
gint width, height;
const GstStructure *str;
g_return_if_fail (gst_caps_is_fixed (caps));
str = gst_caps_get_structure (caps, 0);
if (!gst_structure_get_int (str, "width", &width) ||
!gst_structure_get_int (str, "height", &height)) {
g_print ("No width/height available\n");
return;
}
g_print ("The video size of this set of capabilities is %dx%d\n",
width, height);
}
2.2.3 创建过滤功能
虽然能力(capabilities)主要在插件内部使用来描述pads的媒体类型,但程序员通常还必须对处理能力有基本的了解才能与插件进行交互,尤其是在使用 filtered caps时。 当您使用filtered caps或fixation时,被限制允许在两个pad之间流式传输的媒体类型为其支持的媒体类型的子集。 我们可以在pipeline中使用 capsfilter element执行此操作,为此我们需要创建自己的 GstCaps,最简单的方法是使用便利函数 gst_caps_new_simple ():
static gboolean
link_elements_with_filter (GstElement *element1, GstElement *element2)
{
gboolean link_ok;
GstCaps *caps;
caps = gst_caps_new_simple ("video/x-raw",
"format", G_TYPE_STRING, "I420",
"width", G_TYPE_INT, 384,
"height", G_TYPE_INT, 288,
"framerate", GST_TYPE_FRACTION, 25, 1,
NULL);
link_ok = gst_element_link_filtered (element1, element2, caps);
gst_caps_unref (caps);
if (!link_ok) {
g_warning ("Failed to link element1 and element2!");
}
return link_ok;
}
这将强制这两个element之间的数据流为特定的视频格式、宽度、高度和帧速率(如果在所涉及的element的上下文中无法实现,则链接将失败)。
当我们使用 gst_element_link_filtered () 时,会自动为您创建一个 capsfilter element,并将其插入您要连接的两个element之间的 bin 或pipeline中。如果想要断开这些element的连接,我们需要从capsfilter断开两个element的连接。
在某些情况下,您需要创建一组更精细的处理能力集合来过滤两个pad之间的链接。 此时可以使用gst_caps_new_full() 方法:
static gboolean
link_elements_with_filter (GstElement *element1, GstElement *element2)
{
gboolean link_ok;
GstCaps *caps;
caps = gst_caps_new_full (
gst_structure_new ("video/x-raw",
"width", G_TYPE_INT, 384,
"height", G_TYPE_INT, 288,
"framerate", GST_TYPE_FRACTION, 25, 1,
NULL),
gst_structure_new ("video/x-bayer",
"width", G_TYPE_INT, 384,
"height", G_TYPE_INT, 288,
"framerate", GST_TYPE_FRACTION, 25, 1,
NULL),
NULL);
link_ok = gst_element_link_filtered (element1, element2, caps);
gst_caps_unref (caps);
if (!link_ok) {
g_warning ("Failed to link element1 and element2!");
}
return link_ok;
}
三、总结
这里我们介绍了GStreamer的两个基本概念:element和pad,这些是GStreamer的基础知识。读者要仔细掌握哦……