GStreamer 是一个用于创建流媒体应用程序的框架。基本设计来自俄勒冈研究生院的视频管道,以及来自 DirectShow 的一些想法。
GStreamer 的开发框架使得编写任何类型的流媒体应用程序成为可能。GStreamer 框架旨在使编写处理音频或视频或两者的应用程序变得容易。它不限于音频和视频,可以处理任何类型的数据流。管道设计的开销比所应用的过滤器所产生的开销小。这使得 GStreamer 成为一个很好的框架,用于设计对延迟有很高要求的高端音频应用程序。
GStreamer 最明显的用途之一是使用它来构建媒体播放器。GStreamer 已经包含用于构建可以支持多种格式的媒体播放器的组件,包括 MP3、Ogg/Vorbis、MPEG-1/2、AVI、Quicktime、mod 等。然而,GStreamer 不仅仅是另一个媒体播放器。它的主要优点是可以将可插拔组件混合并匹配到任意管道中,以便编写成熟的视频或音频编辑应用程序。
GStreamer 的核心功能是为插件、数据流和媒体类型处理/协商提供框架。它还提供了一个 API 来使用各种插件编写应用程序。
更具体的说明与文字表述参照官方文档中第一章(What is GStreamer?),这里直接引出架构设计图:
具体来说,GStreamer 提供:
用于多媒体应用程序的 API
插件架构
管道架构
一种媒体类型处理/协商的机制
同步机制
超过 250 个插件,提供超过 1000 个元素
一套工具
GStreamer 插件可以分为:
协议处理
来源:用于音频和视频(涉及协议插件)
格式:解析器、格式化程序、复用器、解复用器、元数据、字幕
编解码器:编码器和解码器
过滤器:转换器,混音器,效果器,…
sinks:用于音频和视频(涉及协议插件)
结构图中蓝色部分皆为gstreamer带有,或者可以使用的部分。我们先看左上角的蓝色部分,即gstreamer 工具部分,一些主要的工具见如下表格:
姓名 | 概要 |
---|---|
gst-launch-1.0 | 构建和运行基本GStreamer管道的工具 |
gst-inspect-1.0 | 可以打印出可用GStreamer 插件的信息、特定插件的信息或特定元素的信息 |
gst-play-1.0 | gst-play-1.0 - 简单的命令行播放测试工具 |
gst-typefind-1.0 | 文件的打印介质类型 |
gst-discoverer-1.0 | 显示文件元数据和流信息 |
gst-device-monitor-1.0 | 用于 GStreamer 设备监视器的简单命令行测试工具 |
gstreamer与ffmpeg类似,同样也提供了不同的命令行工具用于快速的查看信息以及验证Pipeline的是否能够正确运行,在开发过程中,可以跟SQL一样,首先在命令行进行验证,再将Pipeline集成到应用中,即Gst.parse_launch(PIPE)
。我这里会提到gst-inspect-1.0,gst-discoverer-1.0,gst-launch-1.0等命令行工具的使用。
用一个mp3文件举例,正好现在在听歌,gstreamer的gst-play
命令能在有桌面的系统下直接打开音频测试,并且还有较丰富的选项。
$ gst-play-1.0 ceshi.mp3
Press 'k' to see a list of keyboard shortcuts.
Now playing /data/Music/ceshi.mp3
Redistribute latency...
0:00:00.2 / 0:00:18.0
Interactive mode - keyboard controls:
space : pause/unpause
q or ESC : quit
> or n : play next
< or b : play previous
→ : seek forward
← : seek backward
↑ : volume up
↓ : volume down
+ : increase playback rate
- : decrease playback rate
d : change playback direction
t : enable/disable trick modes
a : change audio track
v : change video track
s : change subtitle track
0 : seek to beginning
k : show keyboard shortcuts
0:00:05.6 / 0:00:18.0
gst-discoverer-1.0
可以很方便的查看媒体文件的编码,帧率等信息:
$ gst-discoverer-1.0 ceshi.mp3
Analyzing file:///home/Music/ceshi.mp3
Done discovering file:///home/Music/ceshi.mp3
Topology:
unknown: ID3 tag
audio: MPEG-1 Layer 3 (MP3)
Properties:
Duration: 0:04:07.118367346
Seekable: yes
Live: no
Tags:
title: 科幻
artist: 许嵩
album: 呼吸之野
track number: 3
genre: Blues
container format: ID3 tag
ID3v2 frame: buffer of 41 bytes
image: buffer of 49606 bytes, type: image/jpeg, width=(int)500, height=(int)500, sof-marker=(int)0
has crc: false
channel mode: joint-stereo
audio codec: MPEG-1 Layer 3 (MP3)
没错,ceshi.mp3就是许嵩的科幻,gst-discoverer-1.0
输出了详细的歌曲包含信息,这同样适用于视频或者视频流,如果还想看封装信息,可以使用gst-typefind
,这个命令行下用处不大,主要是接口方面会使用:
$ gst-typefind-1.0 ceshi.mp3
ceshi.mp3 - application/x-id3
其余的两个主要的命令为gst-inspect-1.0
与 gst-launch-1.0
,前者是打印有关 GStreamer 插件或元素的信息,当前系统所拥有的GStreamer插件以及每个插件的详细信息都能通过它全部显示出来,具体命令为:
$ gst-inspect-1.0
"""
不带任何参数。这样会列出当前系统中支持的所有Element,这些Element可用于构造Pipeline.具体信息就不复制黏贴了,很多,通过这里显示的参数,可以再使用gst-inspect-1.0进行输出
"""
$ gst-inspect-1.0 rtsp
"""
Plugin Details:
Name rtsp
Description transfer data via RTSP
Filename /usr/lib/x86_64-linux-gnu/gstreamer-1.0/libgstrtsp.so
Version 1.14.5
License LGPL
Source module gst-plugins-good
Source release date 2019-05-29
Binary package GStreamer Good Plugins (Ubuntu)
Origin URL https://launchpad.net/distros/ubuntu/+source/gst-plugins-good1.0
rtpdec: RTP Decoder
rtspsrc: RTSP packet receiver
2 features:
+-- 2 elements
"""
另一个gst-launch-1.0
为使用最多的一个命令,它接收一个用字符串方式描述的Pipline,将其实例化并运行,我们可以用此命令快速的检查Pipeline中各个元素是否能够正确的连接起来。当我们需要构建的Pipeline很复杂时,也可以将Pipeline进行拆分,逐步通过gst-launch验证Pipeline的合法性。而同样,我上面提到的Gst.parse_launch(PIPE)
,为在python中的方法,C里面应该是gst_parse_launch()
,通过这个api可以将其转化为GstPipeline对象。具体的使用实例,在官方文档中也提到了很多,这里进行一定部分的引用:
# 查看说明书
$ man gst-launch-1.0
# 使用 playbin 播放文件:
$ gst-launch-1.0 playbin \
uri=http://docs.gstreamer.com/media/sintel_trailer-480p.webm
# 也可以用“!”将元素链接在一起:
$ gst-launch-1.0 audiotestsrc ! alsasink
# 在管道中创建不同的流:
$ gst-launch-1.0 audiotestsrc !alsasink videotestsrc !xvimagesink
# 制作单帧 JPEG
$ gst-launch-1.0 videotestsrc num-buffers=1 ! jpegenc ! filesink location=img8.jpg
! filesink location=img8.jpg
Setting pipeline to PAUSED ...
Pipeline is PREROLLING ...
Pipeline is PREROLLED ...
Setting pipeline to PLAYING ...
New clock: GstSystemClock
Got EOS from element "pipeline0".
Execution ended after 0:00:00.000077116
Setting pipeline to PAUSED ...
Setting pipeline to READY ...
Setting pipeline to NULL ...
Freeing pipeline ...
# 使用 fakesink dump = true 转储输入,并连接到流检查数据是否在传输,GST_DEBUG是日志等级,分为1到9
$ GST_DEBUG=3 gst-launch-1.0 -v rtspsrc location=rtsp://192.168.0.10:554/MediaInput/h264 user-id="admin" user-pw="password" ! fakesink dump=true
# decodebin! 可以使用 autovideosink 很好地解码它并在屏幕上显示视频。
$ GST_DEBUG=3 gst-launch-1.0 -v rtspsrc location=rtsp://192.168.0.10:554/MediaInput/h264 user-id="admin" user-pw="password" ! decodebin ! autovideosink
# 使用 gst-play-1.0 播放流并在屏幕上查看。
$ gst-play-1.0 rtsp://admin:password@192.168.0.10:554/MediaInput/h264
还有很多很好玩的实例可以自己尝试,这里只是引出一部分,另外,作为对比,之前我写过一篇关于ffmpeg针对图像和视频的一篇博文,可以对照参考:
ffmpeg图片与视频命令笔记
架构图下方蓝色部分,蓝框主要是gstreamer的Element
,在官方文档中,gstreamer内部最重要的四个组成部分为: Elements
,Pads
,Bins and pipelines
,Communication
。其中需主要理解的是前三个,第四个字面意思是通信,但其实比较浅显易懂,deepstream的通信方式主推kafka,如果用过的话,基本没有什么问题。
对于应用程序员来说,GStreamer 中最重要的对象就是 GstElement
对象。元素是媒体管道的基本构建块。使用的所有不同的高级组件都源自 GstElement
. 每个解码器、编码器、解复用器、视频或音频输出实际上都是一个GstElement
,可以理解为是一个黑盒,一端进去,一端出来,具体见下面四个element实例:
每个element被定义出来,都具有不同的作用,而两个element必须通过pad才能连接起来,我们也可以从图中看到,左边一般为sink,而右边为src,这其实可以理解为生产者,和消费者。
上面提到了两个element必须通过pad才能连接起来,这里引出pad的概念,pad主要有两个属性——数据导向(direction)以及它的时效性(availability),在gstreamer中, Pad是一个element的输入/输出接口,即sink pad
(生产者)和 src pad
(消费者)。
通俗来讲,一个类比在这里可能会有所帮助。pad似于物理设备上的插头或插孔。例如,考虑一个由放大器、DVD 播放器和(静音)视频投影仪组成的家庭影院系统。允许将 DVD 播放器连接到放大器,因为这两个设备都有音频插孔,并且允许将投影仪连接到 DVD 播放器,因为两个设备都有兼容的视频插孔。由于投影机和放大器的插孔类型不同,可能无法在投影机和放大器之间建立链接。GStreamer 中的Pads 与家庭影院系统中的插孔具有相同的用途。
因此,pad可以被视为元素上的“位置”或“端口”,与其他Element建立链接,并且数据可以通过这些element流入或流出这些element。pad具有特定的数据处理能力:限制流经它的数据类型。只有当两个pad允许的数据类型兼容时,才允许之间进行链接(link)。
从上节图可以看到,sources element(上述数据处理 element)仅包含src pad
,sink element(数据输出)仅包含sink pad
,而filter 两者皆有,数据的流动形式一般从左到右,即一个pipeline正常来讲,最左边是源pad,最右边为sink pad,但这个顺序是可以反着来的,如果去查询一些gstreamer的pad 与 buffer资料,不过很少反着做就是了。
那一个element有pad了,就可以与其它element两两相link,这就是下面要引出的pipeline与bin。
bin
简单来讲,就是一个element的组合,一个container,就类似于k8s中的pod,编程语言里的类之余element属性。而pipeline
就是比bin更完整的一个管道,即有输入、输出以及中间处理的可以启动的pipeline。
bin 将一组链接元素组合成一个逻辑元素。这让整体的逻辑不再处理单个element,而只处理一个element,即 bin。当要构建复杂的管道时,我们将看到这非常强大,因为它允许将管道分解成更小的块。下图为两个示例:
图一是bin的一个原理图,element组合与pad相连,图二是基于图一的基础上,带有两个 Element 和一个 Ghost Pad(即没有element 的一种pad) 的 Bin,这个根据播放教程7,就是playbin
的原理图,playbin
允许选择所需的音频和视频接收器的两个属性:audio-sink
和video-sink
。应用程序只需要实例化适当的GstElement并 playbin
,通过这些属性传递给它。具体的可以看Playback tutorial 7: Custom playbin sinks,会有一个playbin的自定义教程。
而如果将这种bin结构完善成一个完整的图,并给它输入输出,这就具有pipeline的能力,见下图:
这是一个简化的pipeline,其中包含一个解复用器和两个分支,一个用于音频,一个用于视频。我们还可以将其还原成更加真实的管道环境:
如图所示,这是一个比较标准的流程,首先输入从file-sources ,如果是文件就为filesrc中出来,经过demuxer解复用器用于从ogg文件容器中解复用视频和音频,使这些基本流可用于进一步处理(解码),然后再通过vorbis 编解码器音频解码为原始音频格式,再经过一层转换器,如图中将F32LE压成S16LE格式音频,最后获取到输出。
GStreamer 为应用程序和管道之间的通信和数据交换提供了多种机制:
缓冲区是用于在管道中的元素之间传递流数据的对象。缓冲区总是从源到汇(下游)。
事件是在元素之间或从应用程序发送到元素的对象。事件可以上游和下游传播。下游事件可以同步到数据流。
消息是由管道消息总线上的元素发布的对象,它们将被保存在那里以供应用程序收集。消息可以从发布消息的元素的流线程上下文中同步拦截,但通常由应用程序从应用程序的主线程异步处理。消息用于以线程安全的方式将错误、标签、状态更改、缓冲状态、重定向等信息从元素传输到应用程序。
查询允许应用程序从管道请求信息,例如持续时间或当前播放位置。查询总是同步回答。元素还可以使用查询从其对等元素请求信息(例如文件大小或持续时间)。它们可以在管道中以两种方式使用,但上游查询更为常见。
总结一下上面的话就是,gstreamer除了位于下层的buffer,还提供了bus系统以及多种数据类型(Buffers、Events、Messages,Queries)来达到此目的。
这里主要说明一下bus,其余几种字如其名,而bus相当于web框架中的中间件,而且是各种类似HTTP 400以上异常捕获的中间件。这里直接看deepstream-6.1关于common/bus_call.py
文件中函数bus的描述:
def bus_call(bus, message, loop):
t = message.type
if t == Gst.MessageType.EOS:
print("Bus call: End-of-stream\n")
# loop.quit()
elif t == Gst.MessageType.WARNING:
err, debug = message.parse_warning()
sys.stderr.write("Bus call: Warning: %s: %s\n" % (err, debug))
elif t == Gst.MessageType.ERROR:
err, debug = message.parse_error()
sys.stderr.write("Bus call: Error: %s: %s\n" % (err, debug))
# loop.quit()
elif t == Gst.MessageType.BUFFERING:
print("Bus call: Buffering\n")
elif t == Gst.MessageType.STATE_CHANGED:
old_state, new_state, pending_state = message.parse_state_changed()
print((
f"Bus call: Pipeline state changed from {old_state.value_nick} to {new_state.value_nick} "
f"(pending {pending_state.value_nick})"
))
else:
print(f"Bus call: {message}\n")
return True
此处的message,就是各种出现异常的情况,这里还可以根据没有提到的异常做出不同的判断,比如说接入视频流的时候,突然流断了,这个问题好像在deepstream-6.1的时候解决了,我之前测试5.1的时候还需要自己写,不过当时是测试的c版本。而bus的启动也就是如下几句代码:
# create an event loop and feed gstreamer bus mesages to it
loop = GLib.MainLoop()
bus = pipeline.get_bus()
bus.add_signal_watch()
bus.connect("message", bus_call, loop)
至此,关于gstreamer一个简单的介绍,就结束了。关于里面更深入层次的东西,比如sink的种类,pad的种类,还有各种element的一些使用情况,再加上nvidia的一些插件进入的时候,这就需要转去看nvidia写得关于gstreamer的一些api的介绍了。
这里贴出一些深入的链接为:
最后,如果对于上述管道图还理解的不深,可以看看关于管道图的导出,参考Nvidia Deepstream小细节系列:Deepstream python保存pipeline结构图 一文,gstreamer给出了debug_bin_to_dot_file
接口,还需加一个文件保存路径即可生成。
图:deepstream-imagedata-multistream
两幅图为 deepstream-test1
与 deepstream-imagedata-multistream
中得到,我们可以通过管道图粗略地看到,test1是只有一个输入源,而它主要做的复杂场景在h264到yuv412之间,其余的和下面的multistream多输入源管道图走向差不多,deepstream-test1的c/python版本逻辑基本一致,根据nvidia的描述,大致顺序为:
下一篇将解析deepstream-test1文件具体逻辑与一些插件说明,同时,重新整理出基于python版本的deepstream-yolov5版本测试。