HEXA娱乐开发日志番外000——Gstreamer编程入门

HEXA开发日志目录


番外篇从此开始,番外用于讲一些HEXA开发过程中的一些在日志中不便展开的一些点点滴滴。

前言

  • 如题,本篇要讲的是Gstreamer的简单使用,最好是看官方文档,除非看英文文档对你来说太痛苦,那就来看这篇好了。
  • 本篇虽然是讲编程,不讲命令行,但是在编程或编写命令行之前的准备工作都是类似的,可以作为参考。

Gstreamer简介

它的来历我还没有了解,目前我只知道它是一个在Linux环境上很容易制作流媒体应用的程序框架。
单说Gstreamer的话,它只是个框架,如果把框架比喻为骨架的话,插件就是它的血肉了,框架+插件就可以构成一个处理流媒体的流水线,基于Gstreamer的应用可以通过对流水线的控制实现各种流媒体处理的功能。
应用开发者只要在丰富的插件中找到适合自己应用的,再把他们连成流水线,立刻就可以构建好一个流媒体处理模块。关于使这个过程变得容易的原因,一方面是插件非常丰富,大多数情况下,没有必要自己制作插件;另一方面原因是插件的连接非常方便,在五花八门的插件中,有很简单的方法可以确定插件之间是否可以连接。

Gstreamer编程模型

我理解的Gstreamer编程要分为以下几步

  1. 构想流水线
    在编程之前,首先要在脑中构造一个流程,即输入是什么,大概要经过哪些处理,最后输出什么。
    输入要想好输入的形式或设备,比如处理视频的话,可以是摄像头、某种格式的视频文件或是网络流等,音频可以是话筒或某种格式的音频文件等。
    处理过程可以大致考虑一下,应该需要哪种编解码器或是格式转换器,需要哪种muxer或demuxer。
    输出也是要想好形式或设备,比如视频的话,可以是显示器、液晶屏、某种格式的文件或是网络流等,音频可以是扬声器或某种格式的音频文件等。
  2. 插件选型
    要在插件库中找出能够满足构想的那些插件,并确认它们能够连接起来,构成我们想要的流水线。那就需要确认以下问题
    • 当前开发环境下哪些插件是可用的?
    • 怎么知道那些从名字上看似乎可用的插件是不是能满足需求?
    • 怎么知道这些看上去能用的插件能不能接起来?
  3. 写代码
    把构想中的对象和代码对应起来,实现流水线。

经过上述过程后,基本的编程就算完成了。下面以我写的simple.c为例,用上述过程复盘一下。

构想流水线

官方例子

下图来自官方文档,看起来这是一个简单的ogg视频文件播放器。整个大方框是一个pipeline,大方框中的小方框(例如file-source)是构成pipeline的一个个插件中的元件,有src/sink字样的蓝色小方框是用来对接元件的东西,其中src表示输出,sink表示输入。


HEXA娱乐开发日志番外000——Gstreamer编程入门_第1张图片

单从字面上也不难理解这些元件

  • 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是插件名字,rtmpsinkrtmpsrc是这个插件的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_mjpegimxvpuenc_mpeg4imxvpuenc_h264imxvpuenc_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 

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

稍微解读一下

  1. Factory Details和Plugin Details都是一些更详尽的描述,比如Source module gst-plugins-bad是说这个插件属于gst-plugins-bad插件库的,如果你想看代码,可以去github搜这个名字。/usr/lib/arm-linux-gnueabihf/gstreamer-1.0/libgstrtmp.so是这个插件库文件的位置。
  2. Pad Templates
    Pad概念后面再讨论,总之这里是描述这个元件可以“讲什么语言”或“听什么语言”的。这里有个SINK template: 'sink'是说它有“听”的能力,如果能“说”,就会有SRC template了。
    Capabilities是对SINK template: 'sink'的能力的具体描述,即video/x-flv,可以理解为一种视频格式,也就是说这个元件能接收video/x-flv格式的视频输入,如果对方的SRC template能输出这个格式,那么就可以连接。
    这里的flv字眼说明,这个元件前面的合成器可以搜索mux、flv或flash找到哦。
  3. Element Properties
    这里是属性列表,是插件自己定制的,所以别的插件可能有不同的属性列表。location属性的值就是rtmp流推送的地址。如果是官方例子中的file-source,应该也会有location属性,它代表着要播放的视频文件路径,即同样的属性名在不同插件中的意思可能不同,属性名和含义完全是插件设计者决定的。

至此,判断插件能否对接的方法有了,即对比SINK templateSRC templateCapabilities,如果有重叠的部分,就是可以对接的
我完成插件选型后设计的流水线如下,下一步就是用代码实现它了。

HEXA娱乐开发日志番外000——Gstreamer编程入门_第2张图片
我的流水线

写代码

最简单的实现,只要遵循以下套路就能满足大多数需求了。

#include 
#include 

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;
}

套路是不是很简单,也就是以下几个步骤

  1. 创建流水线pipeline,想好名字就行了;
  2. 创建元件元件0~元件n,第一个参数是元件类型,第二个参数是给这个元件的实例起的名字,两者就像类和对象、数据类型和变量的关系一样;
  3. 对一些需要配置的元件设置属性(g_object_set);
  4. gst_bin_add_many相当于把元件装配到流水线上,准备连接;
  5. 按顺序连接元件(gst_element_linkgst_element_link_many);
  6. 设置流水线状态为GST_STATE_PLAYING,这个状态就意味着流水线开始播放了;
  7. g_main_loop_run运行一个死循环,只要流水线正常运行,这个循环就不会退出,如果用户ctrl+c或者播放出错了,这个循环就退出了;
  8. 在循环退出后进行收尾工作。

我的代码比这个套路多了一点点东西,不过要是没有多这一点点,也是可以运作的。
上面套路中涉及的概念和数据类型如下表,其中GstElementFactory并没有出现。这是因为gst_element_factory_make把使用GstElementFactory的过程封装了,官方讲elements的文档里有对此说明。

概念 数据类型
元件类型 GstElementFactory
元件实例 GstElement
流水线 GstElement

工厂(factory)?元件(element)?这些名字是不是很形象?其设计思想就是,先请不同的工厂制造出各种元件,然后把元件组装成一条流水线,这条流水线上流动的就是流媒体的不同形态,包括文件或内存等不同形式、编/解码前/后等不同状态。

总结

Gstreamer的编程模型还有很多其他的机制来满足更复杂的需求。例如,当两个元件有多种方式对接时,如何指定用哪种方式对接,再比如,如何对流水线甚至单个元件进行更细致的控制等,这些东西也许会继续出文章说明。


下一篇 也许会有。。。

你可能感兴趣的:(HEXA娱乐开发日志番外000——Gstreamer编程入门)