gStreamer -Part I (Design Overview)

1、Gstreamer design Overview

概括性介绍GStreamer的架构及Gstreamer内部工作机制。详细内容可以查看Gstreamer doc/markdown

1.1 Introduction

GStreamer由一些列库和插件组成,可用于实现各种多媒体应用:桌面播放器、音视频解码器、多媒体服务器、音视频转码器等。

上述应用通过pipeline构建,而pipeline又由单元(elements)构成。
单元是在多媒体流中执行一系列动作的对象:

  • 读文件
  • 不同格式之间的编解码
  • 从硬件设备捕获图像
  • 输出给硬件设备
  • 多媒体流的混合或复用(mix or multiplex)

每个单元都有输入pads、输出pads,分别叫sink pads、source pads,单元之间通过pads连接构成pipeline,下面示例是ogg/vorbis 播放的pipeline:

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

filesrc读取来自磁盘上的文件,oggdemux单元对数据解复用(demultiplexes 1转多),将压缩的音频数据流发送给vorbisdec单元,vorbisdec单元对压缩的数据解码并发送给alsasink单元,alsasink将解码后的数据发送给声卡播放。
Downstream、Upstream用于描述pipeline的方向,从source端到sink端传递叫Downstream,反之称为Upstream,数据流总是Downstream(source到sink端)

应用的本质是使用上述存在的单元构成pipeline,这在pipeline topic中有详细介绍。
应用不用去管理复杂的数据流/解码/转换/同步,只需调用pipeline对象的高层函数如:播放/暂停/停止。
应用还接受来自pipeline的消息和通知,如metadata,warning,error,EOS消息。
若应用需要对图像更多的控制,可以直接访问pipeline中的单元和pads。

gst-application to pipeline.png

1.2 Design overview

GStreamer设计目标:

  • 对大量数据的快速处理
  • 允许同时多线程处理
  • 多种多媒体格式的支持能力
  • 不同数据流之间的同步
  • 应对多个硬件设备的处理能力

应用的能力依赖于构成单元的数量以及各单元在系统中的功能。
GStreamer core主要用于media agnostic,但仍可以提供一些单元特性用于描述media format。

1.3 单元(Elements)

pipeline最小构成为单元,每个单元提供一定数目的pads,可以是source 或sink pads。source pads提供数据,sink pads接收数据,下面是oggdemux单元由一个接收数据的sink pads,两个提供数据的source pads组成。

 +-----------+
 | oggdemux  |
 |          src0
sink        src1
 +-----------+

单元包含四种状态:NULL, READY, PAUSED, PLAYINGNULLREADY状态下,单元不会处理数据。PLAYING状态下处理数据,中间态PAUSED用于在pipeline中预装载数据,使用gst_element_set_state()函数改变状态。
单元状态之间的变换需要经过中间态,即处于READY状态想要变换为PLAYING状态,需要先变为中间态PAUSED
当单元状态变为PAUSED,将激活单元的pads。首先激活的是source pads,然后sink pads。pads激活后,会调用激活函数,有些pads将开启GstTask线程或其它的机制,开始发送和接收数据(producing and consuming data)
PAUSED比较特别,因为它需要将数据预装载(preroll)到pipeline中,目的是用数据填充pipeline中的所有的连接单元以使得接下来的PLAYING状态快速完成,有些单元甚至会出现已经接收完所有数据,但还没有完成PAUSED状态的转换。

详细的单元状态的转变见:states

单元的分类:

  • source elements: pipeline中不接收数据,仅提供数据的单元

  • sink elements:不输出数据给其它单元,仅接收数据提供给外部设备。

  • transform elements: 需要完成数据流之间转变的单元,如编码器/解码器/转换器。

  • demuxer elements: 一个数据流变成多个数据流。

  • mixer/muxer elements: 多个数据流组合成单个数据流。

单元的其它分类方式 (see klass).

1.4 Bins

bin是单元的子类,其作用是作为其它单元的容器,这样多个单元可以组成一个单元。组成的一个单元叫bin。
bin相配合的子状态改变稍后给出解释,bin也会给单元分配事件和各种函数。
bin可以有自己的source 和 sink pads,通过镜像一个或多个自身的子pads。
如下图,有两个单元的bin,bin的sink pad由一个单元的sink pad镜像而来。

 +---------------------------+
 | bin                       |
 |    +--------+   +-------+ |
 |    |        |   |       | |
 |  /sink     src-sink     | |
sink  +--------+   +-------+ |
 +---------------------------+

1.5 Pipeline

pipeline 特殊的bin子类,为其小孩提供下面特性:

  • 为其所有小孩选择和管理全局时钟。
  • 管理 running_time ,Running_time是pipeline消耗在PLAYING状态以及用于同步的时间。
  • 管理pipeline的延迟。
  • 为单元提供与应用之间的通讯手段:GstBus
  • 管理单元的全局状态,如Errors、EOS
    一般情况下,应用创建一个pipeline来管理应用中的所有单元。

1.6 Dataflow and buffers

GStreamer支持两种可能类型的数据流,push and pull model,push model,upstream单元通过调用sinkpad的方法发送数据给downstream单元;pull model,downstream单元通过调用source pad的方法向upstream单元请求数据。
大都数数据流的方式是push model,pull model可用于demuxer单元,也用于低延迟的音频应用中。
在pads之间传输的数据存储在Buffer中,buffer包含指向存储区域的指针以及对存储区域的描述metadata。

metadata包含:

  • 数据的时间戳,何时捕捉的数据,何时播放数据...

  • 数据的偏置,音频小样,视频帧..

  • 数据持续时间。

  • 数据其它特性的描述符号,如discontinuities or delta units.

  • 其它仲裁metadata

通过连接单元之间的pad发送buffer,push model下,通过 gst_pad_push()函数将buffer推送给相对应的pad;pull model下,通过gst_pad_pull_range()函数向upstream的pad请求数据。

push model单元在发送buffer前,需要确认相配对的单元能够理解buffer的内容。询问相配对单元能够支持的格式,选择合适的格式,发送buffer前,通过CAPS事件先发送选择的格式。buffer发送之间的握手详见:negotiation

配对单元接收CAPS事件,将会检查其是否能够理解媒体类型(media type),当媒体类型不被接受时,配对单元将拒绝接受buffer。

gst_pad_push()gst_pad_pull_range() 函数都有返回值指示操作是否成功,返回值错误表示数据发送or数据接收没有成功,线程中初始化数据流的source单元将停止处理线程。

buffer的创建:gst_buffer_new() 或从buffer pool中请求可用的buffer:gst_buffer_pool_acquire_buffer(),当使用第二种方法时,配对单元可以执行定制化的buffer分配算法。

The process of selecting a media type is called caps negotiation.
选择媒体类型的过程叫:caps negotiation

1.7 Caps

媒体类型(Caps)的描述使用包含key/value对的列表,key类型是string,value类型是single/list/range of int/float/string。

带有变量的Caps也可用于描述可能的媒体类型--可被pad处理。

1.8 Dataflow and events

数据流并行是一个事件流,不同于buffer,事件可在upstream和downstream之间同时传递,有些事件仅传送给upstream或downstream。

事件可用于指代数据流的特别条件,如EOS、通知插件的一些具体事件如 flushing or seeking。

一些事件必须用buffer流序列化,序列化的事件在buffer之间插入,没有序列化的事件跳到当前处理的任何buffer前面。

序列化事件的示例是TAG事件:buffer之间插入TAG事件来标识这些buffer的metadata。

非序列化事件的例子:FLUSH事件。

1.9 Pipeline construction

使用gst_pipeline_new()创建pipeline,使用gst_bin_add() and gst_bin_remove()在pipeline中添加和删除单元

增加单元后,通过gst_element_get_pad()函数找回单元的pads,使用gst_pad_link()函数完成pads之间的连接。

当真实的数据流出现在pipeline中,一些单元会创建新的pads,当单元创建新的pad时,g_signal_connect()函数会接收到一个通知,而后新创建的pads才能连接到其它未连接的pads上。

一些单元不能连接,因为他们的数据类型不一样, gst_pad_get_caps()函数可以找回pad支持的数据类型。

下面创建了一个MP3播放器的pipeline,后续的example中将会使用该示例。

+-------------------------------------------+
| pipeline                                  |
| +---------+   +----------+   +----------+ |
| | filesrc |   | mp3dec   |   | alsasink | |
| |        src-sink       src-sink        | |
| +---------+   +----------+   +----------+ |
+-------------------------------------------+

1.10 Pipeline clock

pipeline的一个重要作用:为所有的单元选择全局时钟。
该时钟的目的是提供严格的每秒增加值GST_SECOND,各单元使用该时钟同步数据的播放。
在pipeline状态设置为PLAYING前,pipeline会询问每个单元是否能提供时钟,时钟以下面的顺序选择:

  • 应用选择时钟。
  • source单元提供时钟。
  • 其他单元提供时钟,从sink开始。
  • 如果没有单元提供始终,使用pipeline的默认系统时钟。

对典型的播放pipeline,算法将会选择sink单元提供的时钟,如audio sink。
对捕捉pipeline,典型地使用数据产生器的时钟,大都数情况下不能控制其产生数据的数据率。

1.11 Pipeline states

当所有pads、所有信号连接好,pipeline可以放在PAUSED状态以开始数据流。
当bin开始转变状态,将改变其所有孩子的状态。pipeline将从sink单元到source单元逐步改变孩子的状态,这将确保upstream单元发送数据给其它单元时,不会发生该单元还没有准备好接收的情况。
对MP3播放pipeline,单元的状态改变依次为alsasink,mp3dec,filesrc。

下面列出MP3 pipeline中各个单元的所有中间态转变,导致下面状态改变:

  • alsasink 转变为READY:音频设备已准备好(probed)。
  • mp3dec转变为READY:Nothing happens
  • filesrc转变为READY:文件已准备好
  • alsasink 转变为PAUSED:音频设备已打开,alsasink作为sink,将返回 ASYNC(因为还不能接收数据)
  • mp3dec to PAUSED: mp3dec转变为 PAUSED:解码库已完成初始化。
  • filesrc 转变为PAUSED:文件已打开,线程已开始push数据到mp3dec。
    至此,数据流从filesrc--mp3dec--alsasink,因为mp3dec处于PAUSED状态,其接收来自filesrc sinkpad端的数据,并对此压缩的数据进行解码为原始音频格式。

mp3解码器计算原始音频数据的采样率,信道数以及其他音频特性,并发送媒体类型的caps事件。
Alsasink接收该caps事件,解析caps,并完成自身配置,以能够处理媒体类型。

mp3dec 将解码后samples放到Buffer中,继而给buffer发送给下一个单元。

Alsasink接收带有samples的buffer,因为它接收samples的第一个buffer,完成状态转变为PAUSED状态,pipeline已prerolled,所有的单元都有samples,此时Alsasink有能力提供时钟给pipeline。

alsasink处于PAUSED状态,接收第一个buffer数据,alsasink blocks。mp3dec 和filesrc 在 gst_pad_push()中也是blocks。

所有单元gst_element_get_state()函数返回SUCCESS ,pipeline可以变为PLAYING状态。

在进入PLAYING之前,pipeline会选择一时钟,采样时钟的当前时间,成为base_time,将该时间分配给所有单元,各单元利用该时钟的 running_time base_time 实现同步,详见:synchronisation

而后发生下面的状态转变:

  • alsasink to PLAYING: 解码后的数据在音频设备中播放。

  • mp3dec 转变为PLAYING:Nothing Happens。

  • 转变为PLAYING:Nothing Happens

1.12 Pipeline status

pipeline通过bus给应用发具体事件,bus是pipieline提供的一个对象,可通过gst_pipeline_get_bus()配置。

bus能够被polled 或增加到glib mainloop。
bus分布在所有单元中,单元使用bus往上层推送消息,消息类型主要有:ERRORS, WARNINGS, EOS, STATE_CHANGED等。

pipeline以一特别的方式来处理来自单元的EOS消息,当所有sink单元向上推送EOS消息时,仅发送第一条给应用。
其他的获取pipeline状态包括:询问功能(Query functionality),通过执行函数 gst_element_query()获取状态,询问的类型对于获取pipeline的位置及总时间信息是非常有用的,也可以用于询问支持的seeking格式和范围。

1.13 Pipeline EOS

当source滤波器遇到数据流的结束部分,将发出EOS事件给配对的单元,该事件将会通知downstream单元以及与其连接的所有单元,配对端sink pad收到EOS事件后,将不会接收任何数据。

单元在发送完EOS事件后,将停止提供流线程。

EOS事件最终会到达sink单元,sink单元将在bus上发送EOS消息,以通知pipeline一个特别的数据流已经完成。当所有的sink都向上发送EOS消息,pipeline仅向应用发送第一条EOS消息。EOS消息仅在PLAYING状态下发送给应用。

当处于EOS,pipeline保持PLAYING 状态,这是应用的职责对于PAUSE or READY pipeline。应用还可以发布seek。

1.14 Pipeline READY

当运行的pipeline从 PLAYING 变为READY状态,pipeline接下来将发生:
alsasink转为 PAUSED,alsasink将在下一个样本完成状态改变,若单元之前是EOS,将不会等待样本完成状态转变。

  • mp3dec to PAUSED: Nothing.
  • filesrc to PAUSED: Nothing

转换到中间态PAUSED,将阻止所有的单元( _push()函数),这是由于sink单元接收完第一个buffer后阻止后面的数据传输。

Some elements might be performing blocking operations in the PLAYING state that must be unblocked when they go into the PAUSED state. This makes sure that the state change happens very fast.
有些单元在PLAYING状态下也会出现阻止操作,在这些单元进入到PAUSED状态时,必须放开。确保状态能够快速转变。

下一个PAUSEDREADY状态的改变,pipeline关闭,所有流线程需停止数据传输,过程如下:

  • alsasink转为READY:alsasink放开( _chain()函数),函数FLUSHING 返回值发送给配对的单元,sinkpad此时为非激活状态不能用于发送数据。
  • mp3dec转为READY:pads进入非激活状态,当mp3dec离开_chain()函数后完成状态变化。
  • filesrc转为READY:pads进入非激活状态,线程停止。

由于downstream单元返回一个错误代码(_push()返回FLUSHING),upstream单元完成_chain() 函数。这个错误代码最终会反馈开始流线程的单元(filesrc),filesrc将停止线程并完成状态改变。

这个事件的顺序使得所有的单元放开,所有的流线程停止。

1.15 Pipeline seeking

pipeline seeking需要严格的操作步骤,以确保单元保持同步,seek以最小的延迟运行。

应用通过单元gst_element_send_event() 函数触发pipeline上seek事件,该事件可以是单元支持的任何格式的seek事件。

pipeline暂停pipeline以加速seek操作。

pipeline继而将seek事件发送给所有sink单元。sink前传seek事件给upstream直到某个单元对seek事件进行操作。所有的中间单元可以给seek偏置转为另外的格式,由此,解码器单元能够将seek转为时间戳的帧数。

当seek事件到达一个单元,将执行seek操作,单元相应地执行下面步骤:

  1. 发送 FLUSH_START事件给配对的所有downstream和upstream单元。
  2. 确保流线程没有运行,因为步骤1,流线程将一直是停止。
  3. 执行seek操作。
  4. 发送FLUSH完成事件给所有配对的downstream和upstream。
  5. 发送SEGMENT事件通知新位置的所有的单元,完成seek操作。

步骤1中所有的downstream单元必须从所有的阻塞操作返回,必须拒绝接下来的任何buffer或者除FLUSH外的其它事件。

第一个步骤确保流线程最终解除阻塞,继而第二步可以操作,此时pipeline的数据流全面停止。

步骤3,单元执行请求位置的seek操作。

步骤4,所有的配对单元又能够接收数据,新位置的数据流能够连续,FLUSH完成事件发送给所有配对单元,配对单元也能够接收新的数据,开启其数据流线程。

步骤5,通知新位置的所有单元,事件函数返回应用,流线程开始处理新的数据。
因为pipeline仍然处于PAUSED状态,这将在sink端预处理下一个媒体样本,应用可以用_get_state()函数等待预处理完成。

seek操作的最后一步是调节pipeline流的running_time =0,设置pipeline返回PLAYING状态。

MP3播放器示例中的seek事件顺序:

                                   | a) seek on pipeline
                                   | b) PAUSE pipeline
+----------------------------------V--------+
| pipeline                         | c) seek on sink
| +---------+   +----------+   +---V------+ |
| | filesrc |   | mp3dec   |   | alsasink | |
| |        src-sink       src-sink        | |
| +---------+   +----------+   +----|-----+ |
+-----------------------------------|-------+
           <------------------------+
                 d) seek travels upstream

    --------------------------> 1) FLUSH event
    | 2) stop streaming
    | 3) perform seek
    --------------------------> 4) FLUSH done event
    --------------------------> 5) SEGMENT event

    | e) update running_time to 0
    | f) PLAY pipeline
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,179评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,229评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,032评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,533评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,531评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,539评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,916评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,813评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,568评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,654评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,354评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,937评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,918评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,152评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,852评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,378评论 2 342

推荐阅读更多精彩内容

  • 本文对 V4L2 的运行时数据流设备管理做一个详细的介绍,包括什么叫「运行时设备管理」,它是干什么用的,怎么使用等...
    yellowmax阅读 6,684评论 0 4
  • Java NIO(New IO)是从Java 1.4版本开始引入的一个新的IO API,可以替代标准的Java I...
    zhisheng_blog阅读 1,111评论 0 7
  • Java NIO(New IO)是从Java 1.4版本开始引入的一个新的IO API,可以替代标准的Java I...
    编码前线阅读 2,258评论 0 5
  • 先扔个炸弹,如果我是海军司令,我就不让红海行动这样播出,即便是事实,是根据真实事件改编的,也要求编剧从另一个角度来...
    红帽姐姐阅读 562评论 0 0
  • 就在刚刚午饭后,我还在热火朝天地玩着单机游戏1010,打到一半的时候,却突然想起了一个人,然后鬼使神差地,编辑了条...
    沉栖阅读 460评论 0 2