番外篇从此开始,番外用于讲一些HEXA开发过程中的一些在日志中不便展开的一些点点滴滴。
前言
- 如题,本篇要讲的是Gstreamer的简单使用,最好是看官方文档,除非看英文文档对你来说太痛苦,那就来看这篇好了。
- 本篇虽然是讲编程,不讲命令行,但是在编程或编写命令行之前的准备工作都是类似的,可以作为参考。
Gstreamer简介
它的来历我还没有了解,目前我只知道它是一个在Linux环境上很容易制作流媒体应用的程序框架。
单说Gstreamer的话,它只是个框架,如果把框架比喻为骨架的话,插件就是它的血肉了,框架+插件就可以构成一个处理流媒体的流水线,基于Gstreamer的应用可以通过对流水线的控制实现各种流媒体处理的功能。
应用开发者只要在丰富的插件中找到适合自己应用的,再把他们连成流水线,立刻就可以构建好一个流媒体处理模块。关于使这个过程变得容易的原因,一方面是插件非常丰富,大多数情况下,没有必要自己制作插件;另一方面原因是插件的连接非常方便,在五花八门的插件中,有很简单的方法可以确定插件之间是否可以连接。
Gstreamer编程模型
我理解的Gstreamer编程要分为以下几步
- 构想流水线
在编程之前,首先要在脑中构造一个流程,即输入是什么,大概要经过哪些处理,最后输出什么。
输入要想好输入的形式或设备,比如处理视频的话,可以是摄像头、某种格式的视频文件或是网络流等,音频可以是话筒或某种格式的音频文件等。
处理过程可以大致考虑一下,应该需要哪种编解码器或是格式转换器,需要哪种muxer或demuxer。
输出也是要想好形式或设备,比如视频的话,可以是显示器、液晶屏、某种格式的文件或是网络流等,音频可以是扬声器或某种格式的音频文件等。 - 插件选型
要在插件库中找出能够满足构想的那些插件,并确认它们能够连接起来,构成我们想要的流水线。那就需要确认以下问题- 当前开发环境下哪些插件是可用的?
- 怎么知道那些从名字上看似乎可用的插件是不是能满足需求?
- 怎么知道这些看上去能用的插件能不能接起来?
- 写代码
把构想中的对象和代码对应起来,实现流水线。
经过上述过程后,基本的编程就算完成了。下面以我写的simple.c为例,用上述过程复盘一下。
构想流水线
官方例子
下图来自官方文档,看起来这是一个简单的ogg视频文件播放器。整个大方框是一个pipeline,大方框中的小方框(例如file-source)是构成pipeline的一个个插件中的元件,有src/sink字样的蓝色小方框是用来对接元件的东西,其中src表示输出,sink表示输入。
单从字面上也不难理解这些元件
- file-source
即以文件作为源,这里具体就是某个ogg视频文件了。 - ogg-demuxer
demuxer即分离器,从最后的结果看,它是把ogg文件中音/视频分开了。 - vorbis-decoder
很明显,这应该是一个音频解码器 - audio-sink
看图标,这应该就是扬声器的驱动之类的了 - theora-decoder
很明显+1,这应该是一个视频解码器 - video-sink
看图标+1,这应该就是显示器的驱动之类的了
上面这个例子的输入是ogg视频文件,中间要demuxer分离音视频,然后分别解码,最后输出给音视频设备播放。其中的每个元件都是可以替换的,比如想把输入源换成rtmp流,只要把file-source换成rtmp-src元件就行了;再比如想做一个编码器只要把整个图左右翻转一下,播放设备sink换成采集设备src,xxx-decoder换成xxx-encoder,xxx-demuxer换成xxx-encoder就可以了。
我的流水线
- 输入
视频方面是机器人摄像头采集的原始图像,音频方面反正还没有合适的采集设备,先不考虑 - 输出
rtmp网络流 - 处理
在上面的输入和输出之间肯定要有个编码器
后面加音频的话,音视频需要合成为一个流,所以必然需要一个合成器(muxer)
摄像头==>编码器==>合成器==>rtmp流,我的流水线就这么简单,然后我们来插件选型。
插件选型
- 当前开发环境下哪些插件是可用的?
这很简单,只要使用Gstreamer的命令行工具gst-inspect,如果是1.x版本在终端中输入以下命令就能列出所有插件的名字
gst-inspect-1.0
很多插件......
rtmp: rtmpsink: RTMP output sink
rtmp: rtmpsrc: RTMP Source
很多插件......
这里我只列了两个比较好认的,rtmp
是插件名字,rtmpsink
和rtmpsrc
是这个插件的2个元件类型,第二个冒号后面是对这个元件的说明。
- 怎么知道那些从名字上看似乎可用的插件是不是能满足需求?
这个问题隐含一个已知,我们要先大概知道一些名字,然后才能从名字上过滤掉大量的无关插件,比如关于摄像头,需要知道v4l、uvc等。
首先,找摄像头信号源,可以用正则表达式搜索video.*source
,然后再用搜索引擎大概了解一下就能初步选出来了。把那些比较像的找出来后,只要再用用搜索引擎,很容易确定其中的video4linux2: v4l2src: Video (video4linux2) Source
是大概率可用的。
其次,找输出插件,正则表达式搜rtmp.*sink
,和找输入类似的,rtmp: rtmpsink: RTMP output sink
看起来比较像,先就是它了。
最后,要把中间那些插件找到,先找编码器,正则表达式搜索video.*encoder
,在我的机器人上有3个插件中的6个元件备选,如果在开发环境里装了Gstreamer的libav类插件,就搜出更多了。
此处筛选可以基于这样一个原则,基于已知的知识找出最像的那个作为备选就好。
因为官方给我推荐过libimxvpu
,所以我就用名字叫imxvpu的插件就行了,它有4个元件,分别是imxvpuenc_mjpeg
,imxvpuenc_mpeg4
,imxvpuenc_h264
,imxvpuenc_h263
,看起来就是选一种编码格式就行了,先随便选一个,就先选h264
吧。如果没有些先验只是,关于合成器(mux)就不是那么好找了,如果知道rtmp和flash有关,这一步可以先确定一个,不知道也没关系,就直接进行下一步好了。 - 怎么知道这些看上去能用的插件能不能接起来?
- 为什么会有不能连接的情况?
所有插件不是一个人或一队人写的,这就好像一个国际公司的两个部门要对接一样,两个部门至少要各出一个人作为接口与对方部门交流,如果这俩部门处在不同的国家,那么这两个接口很可能讲着不同的语言,他们之间就无法有效传达信息了。
因此,两个元件要想连接,必须知道讲话的接口讲得是什么语言,听话的接口听得懂什么语言,如果语言一致,就是能连接的。 - 怎么看元件能“讲或听得懂哪种语言”?
我们可以用gst-inspect-1.0 <元件名>
进一步看一下元件详细信息,以便对元件有进一步了解,下面看一下rtmpsink
的信息。
- 为什么会有不能连接的情况?
# gst-inspect-1.0 rtmpsink
Factory Details:
Rank primary (256)
Long-name RTMP output sink
Klass Sink/Network
Description Sends FLV content to a server via RTMP
Author Jan Schmidt <thaytan@noraisin.net>
Plugin Details:
Name rtmp
Description RTMP source and sink
Filename /usr/lib/arm-linux-gnueabihf/gstreamer-1.0/libgstrtmp.so
Version 1.2.4
License LGPL
Source module gst-plugins-bad
Source release date 2014-04-18
Binary package GStreamer Bad Plugins (Ubuntu)
Origin URL https://launchpad.net/distros/ubuntu/+source/gst-plugins-bad1.0
GObject
+----GInitiallyUnowned
+----GstObject
+----GstElement
+----GstBaseSink
+----GstRTMPSink
...
Pad Templates:
SINK template: 'sink'
Availability: Always
Capabilities:
video/x-flv
...
Element Properties:
...
location : RTMP url
flags: readable, writable
String. Default: null
稍微解读一下
- Factory Details和Plugin Details都是一些更详尽的描述,比如
Source module gst-plugins-bad
是说这个插件属于gst-plugins-bad插件库的,如果你想看代码,可以去github搜这个名字。/usr/lib/arm-linux-gnueabihf/gstreamer-1.0/libgstrtmp.so
是这个插件库文件的位置。 - Pad Templates
Pad概念后面再讨论,总之这里是描述这个元件可以“讲什么语言”或“听什么语言”的。这里有个SINK template: 'sink'
是说它有“听”的能力,如果能“说”,就会有SRC template
了。
Capabilities
是对SINK template: 'sink'
的能力的具体描述,即video/x-flv
,可以理解为一种视频格式,也就是说这个元件能接收video/x-flv
格式的视频输入,如果对方的SRC template
能输出这个格式,那么就可以连接。
这里的flv
字眼说明,这个元件前面的合成器可以搜索mux、flv或flash找到哦。 - Element Properties
这里是属性列表,是插件自己定制的,所以别的插件可能有不同的属性列表。location
属性的值就是rtmp流推送的地址。如果是官方例子中的file-source,应该也会有location
属性,它代表着要播放的视频文件路径,即同样的属性名在不同插件中的意思可能不同,属性名和含义完全是插件设计者决定的。
至此,判断插件能否对接的方法有了,即对比SINK template
和SRC template
的Capabilities
,如果有重叠的部分,就是可以对接的。
我完成插件选型后设计的流水线如下,下一步就是用代码实现它了。
写代码
最简单的实现,只要遵循以下套路就能满足大多数需求了。
#include <gst/gst.h>
#include <glib.h>
int main (int argc,char *argv[])
{
GMainLoop *loop;
GstElement *pipeline, *元件0, *元件1, ..., *元件n;
/* Initialisation */
gst_init (&argc, &argv);
loop = g_main_loop_new (NULL, FALSE);
/* 创建流水线 */
pipeline = gst_pipeline_new ("自己起的流水线名");
/* 创建元件 */
元件0 = gst_element_factory_make ("元件名0", "自己起的元件实例名0");
元件1 = gst_element_factory_make ("元件名1", "自己起的元件实例名1");
//其他元件...
/* 如果需要给一些原件设置属性 */
g_object_set (G_OBJECT (元件0), "属性名", 属性值, NULL);
/* 把元件与流水线绑定 */
gst_bin_add_many (GST_BIN (pipeline), 元件0, 元件1, ..., 元件n, NULL);
/* 连接元件,也可以用gst_element_link_many一句连接 */
if (gst_element_link (元件0, 元件1)){
g_print ("link success %d\n", __LINE__);
}
else{
return -1;
}
if (gst_element_link (元件1, 元件2)){
g_print ("link success %d\n", __LINE__);
}
else{
return -1;
}
//......
if (gst_element_link (元件n-1, 元件n)){
g_print ("link success %d\n", __LINE__);
}
else{
return -1;
}
/* 启动播放*/
gst_element_set_state (pipeline, GST_STATE_PLAYING);
/* 运行主循环,ctrl+c可以退出这个循环 */
g_main_loop_run (loop);
/* 释放内存之类的收尾动作 */
//略...
return 0;
}
套路是不是很简单,也就是以下几个步骤
- 创建流水线
pipeline
,想好名字就行了; - 创建元件
元件0
~元件n
,第一个参数是元件类型,第二个参数是给这个元件的实例起的名字,两者就像类和对象、数据类型和变量的关系一样; - 对一些需要配置的元件设置属性(
g_object_set
); -
gst_bin_add_many
相当于把元件装配到流水线上,准备连接; - 按顺序连接元件(
gst_element_link
或gst_element_link_many
); - 设置流水线状态为
GST_STATE_PLAYING
,这个状态就意味着流水线开始播放了; - 用
g_main_loop_run
运行一个死循环,只要流水线正常运行,这个循环就不会退出,如果用户ctrl+c或者播放出错了,这个循环就退出了; - 在循环退出后进行收尾工作。
我的代码比这个套路多了一点点东西,不过要是没有多这一点点,也是可以运作的。
上面套路中涉及的概念和数据类型如下表,其中GstElementFactory
并没有出现。这是因为gst_element_factory_make
把使用GstElementFactory
的过程封装了,官方讲elements的文档里有对此说明。
概念 | 数据类型 |
---|---|
元件类型 | GstElementFactory |
元件实例 | GstElement |
流水线 | GstElement |
工厂(factory)?元件(element)?这些名字是不是很形象?其设计思想就是,先请不同的工厂制造出各种元件,然后把元件组装成一条流水线,这条流水线上流动的就是流媒体的不同形态,包括文件或内存等不同形式、编/解码前/后等不同状态。
总结
Gstreamer的编程模型还有很多其他的机制来满足更复杂的需求。例如,当两个元件有多种方式对接时,如何指定用哪种方式对接,再比如,如何对流水线甚至单个元件进行更细致的控制等,这些东西也许会继续出文章说明。
下一篇 也许会有。。。