Gstreamer入坑

基本概念

我们先看看gstreamer结构体中的一些对象的继承关系图:

GObject
 +----GstElement(包含GstBus,GstState, List of pad、GstClock等)
   +----GstBin (元件的容器,包含GstClock,子bus等)
     +----GstPipeline(包含stream_time等)

另外几个类关系:

GObject
±— GstPad

GObject
±— GstBus

GstObject
±— GstPluginFeature
±— GstElementFactory(factory that the element was created from,_GstElementClass保存有该结构的一个指针)

我们看一个pipeline的解释和示例图:

Pipeline

A pipeline is a special bin subclass that provides the following features to its
children:

  • Select and manage a global clock for all its children.

  • Manage running_time based on the selected clock. Running_time is the elapsed
    time the pipeline spent in the PLAYING state and is used for
    synchronisation.

  • Manage latency in the pipeline.

  • Provide means for elements to comunicate with the application by the GstBus.

  • Manage the global state of the elements such as Errors and end-of-stream.

Normally the application creates one pipeline that will manage all the elements
in the application.

±----------------------------------------------------------+
| ----------> downstream -------------------> |
| |
| pipeline |
| ±--------+ ±---------+ ±----------+ ±---------+ |
| | filesrc | | oggdemux | | vorbisdec | | alsasink | |
| | src-sink src-sink src-sink | |
| ±--------+ ±---------+ ±----------+ ±---------+ |
| |
| <---------< upstream <-------------------< |
±----------------------------------------------------------+

The application does not have to manage any of the complexities of the
actual dataflow/decoding/conversions/synchronisation etc. but only calls high
level functions on the pipeline object such as PLAY/PAUSE/STOP.

GStreamer supports two possible types of dataflow, the push and pull model. In the
push model, an upstream element sends data to a downstream element by calling a
method on a sinkpad. In the pull model, a downstream element requests data from
an upstream element by calling a method on a source pad.

The most common dataflow is the push model. The pull model can be used in specific
circumstances by demuxer elements. The pull model can also be used by low latency
audio applications.

The data passed between pads is encapsulated in Buffers. The buffer contains a
pointer to the actual data and also metadata describing the data.

可见,pipeline的主要布局是elements, Bins 和pads.把上述几个对象继承关系牢记脑海中。PADs是连接器。pipeline的各对象之间的通信通过缓存buffers、事件event、查询query、消息message机制实现, 调度通过时钟和队列实现。

元件(Elements)

元件(element)是GStreamer中最重要的概念。它是整个gstreamer框架中最重要的一个对象,它定义了pipeline的结构。你可以通过创建一系列的元件(Elements),并把它们连接起来,从而让数据流在这个被连接的各个元件(Elements)之间传输。每个元件(Elements)都有一个特殊的函数接口,对于有些元件(Elements)的函数接口它们是用于能够读取文件的数据,解码文件数据的。而有些元件(Elements)的函数接口只是输出相应的数据到具体的设备上(例如,声卡设备)。你可以将若干个元件(Elements)连接在一起,从而创建一个管道(pipeline)来完成一个特殊的任务,例如,媒体播放或者录音。GStreamer已经默认安装了很多有用的元件(Elements),通过使用这些元件(Elements)你能够构建一个具有多种功能的应用程序。

每个元件包含有多个gstPad(衬垫)。这些gstpad随后用于元件(element)之间的连接和通信,并最终构成一个可以传递和处理数据的管道pipeline.几乎所有的decoder,encoder,demuxer,video or audio output都实际上是一个GstElement。( 注:面向对象里的is-a 关系)

gstelement实例的结构如下所示, 添加和删除pad的方法在gstelement的类结构体中有定义,这里从略。

struct _GstElement {
GstObject object; // 继承自 GstObject

/*< public >*/ /* with LOCK */
GStaticRecMutex *state_lock; // 用于串行化执行 gst_element_set_state()

/* element state */
GCond *state_cond; // 用于状态变化完成时发射通知信号
guint32 state_cookie; // 用于检测 gst_element_set_state() 和 gst_element_get_state() 的并发执行

GstState current_state; // 元件当前所处状态
GstState next_state; // 元件的下一个状态。如果正处于正确的状态,该值可以为 GST_STATE_VOID_PENDING
GstState pending_state; // 元件的应趋向的最终状态。如果正处于正确的状态,该值可以为 GST_STATE_VOID_PENDING
GstStateChangeReturn last_return; // 元件状态改变时,最后的那个返回值

GstBus *bus; // 包含的 GstBus,该总线由父元素或应用程序提供。GstPipeline 有自己的 bus

/* allocated clock */
GstClock *clock; // 时钟,通常由顶级的 gstpipeline 管道提供

/* element 状态刚变为 playing 时的时钟时间,播放状态下时钟时间减去 base_time 可以获得流时间(即播放时间,运行时间) */
GstClockTimeDiff base_time; /* NULL/READY: 0 - PAUSED: current time - PLAYING: difference to clock */

/* element pads, these lists can only be iterated while holding
* the LOCK or checking the cookie after each LOCK. */
guint16 numpads; // 元件的 pad 数量,包含 source 和 sink pads

/* 衬垫列表(每个元件都包含有 pad),一个衬垫 (Pads) 可以被看作是一个元件 (element) 插座或者端口,元件之间的链接依靠这些衬垫完成 */
GList *pads;

guint16 numsrcpads;
GList *srcpads;
guint16 numsinkpads;
GList *sinkpads;
guint32 pads_cookie;

/*< private >*/
union {
struct {
/* state set by application */
GstState target_state; // 应用程序设置的目标状态
} ABI;

/* 预留字段,用于 ABI 兼容性 */
gpointer _gst_reserved[GST_PADDING + 0];
} abidata;
};

箱柜(Bins)和管道(pipelines)

箱柜(Bins)是一个可装载一系列元件(element)的容器。管道(pipelines)是箱柜(Bins)的一个特殊的子类型,管道(pipelines)可以操作包含在它自身内部的所有元件(element)。gstbin的实例结构体定义如下:

A bin is an element subclass and acts as a container for other elements so that multiple
elements can be combined into one element.*

*A bin can have its own source and sinkpads by ghostpadding one or more of its children’s
pads to itself.

struct _GstBin {
GstElement element;

/*< public >*/ /* with LOCK */
/* the number of children in this bin */
gint numchildren; // 子元件的数量

/* the list of children in this bin */
GList *children; // 该 bin 中子元件的列表

guint32 children_cookie; // 每当 @children 变化时更新

GstBus *child_bus; // 处理子元件消息的内部总线
GList *messages; // 排队和缓存的消息

gboolean polling; // bin 正在计算其状态
gboolean state_dirty; // bin 需要重新计算其状态 (已弃用)

gboolean clock_dirty; // bin 需要选择一个新时钟
GstClock *provided_clock; // 上次选择的时钟
GstElement *clock_provider; // 提供 @provided_clock 的元件

/*< private >*/
GstBinPrivate *priv; // 私有结构体

gpointer _gst_reserved[GST_PADDING - 1]; // 预留字段
};

struct _GstPipeline {
GstBin bin; // 继承自 GstBin

/*< public >*/ /* with LOCK */
/* The fixed clock of the pipeline, used when GST_PIPELINE_FLAG_FIXED_CLOCK is set. */
GstClock *fixed_clock; // 管道的固定时钟

/* The stream time of the pipeline. A better name for this property would be the running_time,
the total time spent in the PLAYING state without being flushed.
(deprecated, use the start_time on GstElement).
注意到 gstelement 实例结构里有一个 base_time. 它们是什么关系呢?
当前时间与 base_time 相减可以得到 running-time (运行时间)。
在播放速率一致的情况下,这个运行时间与 stream_time 是相等的!
element 可以提供一个时钟,管道 pipeline 可以选择一个时钟,所有的接收器 sinks 都要与之同步! */
GstClockTime stream_time; // 管道的流时间

/* Extra delay added to base_time to compensate for computing delays when setting elements to PLAYING */
GstClockTime delay; // 额外的延迟,添加到 base_time 以补偿在设置元件为 PLAYING 时的计算延迟

/*< private >*/
GstPipelinePrivate *priv; // 私有结构体

gpointer _gst_reserved[GST_PADDING - 1]; // 预留字段
};

因为箱柜(Bins)本身又是元件(element)的子集,所以你能够象操作普通元件(element)一样的操作一个箱柜(Bins),通过这种方法可以降低你的应用程序的复杂度。你可以改变一个箱柜(Bins)的状态来改变箱柜(Bins)内部所有元件(element)的状态。箱柜(Bins)可以发送总线消息(bus messages)给它的子集元件(element)(这些消息包括:错误消息(error messages),标签消息(tag messages),EOS消息(EOS messages))。

struct _GstPipelineClass {
GstBinClass parent_class;

/*< private >*/
gpointer _gst_reserved[GST_PADDING];
};

管道(pipeline)是高级的箱柜(Bins)。当你设定管道的暂停或者播放状态的时候,数据流将开始流动,并且媒体数据处理也开始处理。一旦开始,管道将在一个单独的线程中运行,直到被停止或者数据流播放完毕。

衬垫(Pads)

衬垫在GStreamer中被用于多个元件的链接,从而让数据流能在这样的链接中流动。一个衬垫(Pads)可以被看作是一个元件(element)插座或者端口,元件(element)之间的链接就是依靠着衬垫(Pads)。衬垫(Pads)有处理特殊数据的能力:一个衬垫(Pads)能够限制数据流类型的通过。链接成功的条件是:只有在两个衬垫(Pads)允许通过的数据类型一致的时候才被建立。数据类型的设定使用了一个叫做caps negotiation的方法。数据类型被为一个GstCaps变量所描述。

struct _GstCaps {
GType type; // 类型

/*< public >*/ /* with COW */
/* refcounting */
gint refcount; // 引用计数

/*< public >*/ /* read only */
GstCapsFlags flags; // 标志位

/*< private >*/
GPtrArray *structs; // 存储结构体的指针数组

/* 注释,方便理解 -----------
struct _GPtrArray {
gpointer *pdata; // 指向数据的指针
guint len; // 数组长度
}; */

/*< private >*/
gpointer _gst_reserved[GST_PADDING]; // 预留字段
};

下面的这个比喻可能对你理解衬垫(Pads)有所帮助。一个衬垫(Pads)很象一个物理设备上的插头。例如一个家庭影院系统。一个家庭影院系统由一个功放(amplifier),一个DVD机,还有一个无声的视频投影组成。我们需要连接DVD机到功放(amplifier),因为两个设备都有音频插口;我们还需要连接投影机到DVD机上,因为两个设备都有视频处理插口。但我们很难将投影机与功放(amplifier)连接起来,因为他们之间处理的是不同的插口。GStreamer衬垫(Pads)的作用跟家庭影院系统中的插口是一样的。

对于大部分情况,所有的数据流都是在链接好的元素之间流动。数据向元件(element)以外流出可以通过一个或者多个 source衬垫(Pads),元件(element)接受数据是通过一个或者多个sink衬垫(Pads)来完成的。Source元件(element)和sink元件(element)分别有且仅有一个 sink 衬垫(Pads)或者source衬垫(Pads)。数据在这里代表的是缓冲区(buffers) (GstBuffer对象描述了数据的缓冲区(buffers)的信息)和事件(events) (GstEvent对象描述了数据的事件(events)信息)。

GstPad结构体字段:

/**
* 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.
* @stream_rec_lock: recursive stream lock of the pad, used to protect
* the data used in streaming.
* @task: task for this pad if the pad is actively driving dataflow.
* @preroll_lock: lock used when prerolling
* @preroll_cond: conf to signal preroll
* @block_cond: conditional to signal pad block
* @block_callback: callback for the pad block if any
* @block_data: user data for @block_callback
* @caps: the current caps of the pad
* @getcapsfunc: function to get caps of the pad
* @setcapsfunc: function to set caps on the pad
* @acceptcapsfunc: function to check if pad can accept caps
* @fixatecapsfunc: function to fixate caps
* @activatefunc: pad activation function
* @activatepushfunc: function to activate/deactivate pad in push mode
* @activatepullfunc: function to activate/deactivate pad in pull mode
* @linkfunc: function called when pad is linked
* @unlinkfunc: function called when pad is unlinked
* @peer: the pad this pad is linked to
* @sched_private: private storage for the scheduler
* @chainfunc: function to chain data to pad
* @checkgetrangefunc: function to check if pad can operate in pull mode
* @getrangefunc: function to get a range of data from a pad
* @eventfunc: function to send an event to a pad
* @mode: current activation mode of the pad
* @querytypefunc: get list of supported queries
* @queryfunc: perform a query on the pad
* @intlinkfunc: get the internal links of this pad
* @bufferallocfunc: function to allocate a buffer for this pad
* @do_buffer_signals: counter counting installed buffer signals
* @do_event_signals: counter counting installed event signals
* @iterintlinkfunc: get the internal links iterator of this pad
*
* The #GstPad structure. Use the functions to update the variables.
*/

在Linux上安装GStreamer

先决条件

GStreamer 已包含在大多数Linux发行版中。建议使用最新的快速更新的发行版(如Fedora、Ubuntu非LTS版、Debian sid或OpenSuse)来获取最近版本的GStreamer。

确保具有超级用户(root)权限来安装GStreamer。

在Fedora上安装GStreamer

在终端中运行以下命令:

dnf install gstreamer1-devel gstreamer1-plugins-base-tools gstreamer1-doc \
gstreamer1-plugins-base-devel gstreamer1-plugins-good gstreamer1-plugins-good-extras \
gstreamer1-plugins-ugly gstreamer1-plugins-bad-free gstreamer1-plugins-bad-free-devel \
gstreamer1-plugins-bad-free-extras

在Ubuntu或Debian上安装GStreamer

在终端中运行以下命令:

apt-get install libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev \
libgstreamer-plugins-bad1.0-dev gstreamer1.0-plugins-base gstreamer1.0-plugins-good \
gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-libav gstreamer1.0-tools \
gstreamer1.0-x gstreamer1.0-alsa gstreamer1.0-gl gstreamer1.0-gtk3 gstreamer1.0-qt5 \
gstreamer1.0-pulseaudio

构建使用GStreamer的应用程序

除了需要安装的GStreamer库,还需要安装gcc编译器。编译代码时,需要在gcc命令中加入以下内容:

pkg-config --cflags --libs gstreamer-1.0

如果使用其他GStreamer库(如视频库),则需要在命令中添加相应的包(如 gstreamer-video-1.0)。

获取教程源码

可以从教程页面复制源代码,也可以通过以下命令克隆GIT仓库:

git clone https://gitlab.freedesktop.org/gstreamer/gstreamer

教程代码位于 subprojects/gst-docs/examples/tutorials 子目录中。

编译教程

例如,编译 basic-tutorial-1

gcc basic-tutorial-1.c -o basic-tutorial-1 `pkg-config --cflags --libs gstreamer-1.0`

运行教程

运行教程代码:

./basic-tutorial-1

Reference

[1] Installing on Linux: https://gstreamer.freedesktop.org/documentation/installing/on-linux.html?gi-language=c

[2] Gstreamer基本概念深入解析

------------------------------- 本文结束啦❤感谢您阅读-------------------------------
赞赏一杯咖啡