• GStreamer概述


    本节主要一起来简要看看GStreamer,有个大概认识

    1、什么是GStreamer

    GStreamer 是用来构建流媒体应用的开源多媒体框架(framework),其基本设计思想来自于俄勒冈(Oregon)研究生学院有关视频管道的创意, 同时也借鉴了DirectShow的设计思想。其目标是要简化音/视频应用程序的开发,已经能够被用来处理像 MP3、Ogg、MPEG1、MPEG2、AVI、Quicktime 等多种格式的多媒体数据。实际上现在GStreamer已经不仅仅拥有处理多媒体了,而是广泛用于各种数据流的处理,比如当前Nvidia的deepstream AI能力的应用程序的流分析工具包 是使用开源 GStreamer 框架构建的优化图形架构

    发展历史

    1999年Erik Walthinsen创建了GStreamer,2001年1月11日发表了第一个主要版本是0.1。没过多久,GStreamer出现了第一个商业版本,由RidgeRun公司发行,这是一家嵌入式Linux 公司。RidgeRun后来遇到了财务困难,工作人员大多离去,包括Walthinsen。 GStreamer的进展并未受影响。2001年7月发表了0.2.0的版本,2002年9月,发表了0.4.0,2004年3月又发表了0.8.0。

    2004年,新公司Fluendo成立,并使用GStreamer编写一个流媒体服务器Flumotion, 并提供多媒体解决方案。2005年12月发表了0.10.0版本。日后GStreamer渐渐普及,2006年,Totem , Rhythmbox 和 Banshee等媒体巨头都使用 GStreamer。

    GStreamer日后在商业上获取巨大成功有许多不同的公司采用(诺基亚、摩托罗拉、德州仪器、 飞思卡尔、英特尔等等),并已成为一个非常强大的跨平台多媒体框架。

    跨平台设计,使其能够在Linux(包括x86PowerPCARM架构),Solaris(Intel和SPARC)以及OpenSolarisFreeBSDOpenBSDNetBSDMac OS XMicrosoft Windows和OS/400上运行。GStreame也有针对其他语言的绑定PythonValaC++Perl,GNU Guile和Ruby。GStreamer依据GNU宽通用公共许可证授权。

    框架

    GStreamer框架是基于插件的, 有些插件中提供了各种各样的多媒体数字信号编解码器,也有些提供了其他的功能。所有的插件都能够被链接到任意的已经定义了的数据流管道中。GStreamer的管道能够被GUI编辑器编辑, 能够以XML文件来保存。这样的设计使得管道程序库的消耗变得非常少 [1]  。

    GStreamer核心库函数是一个处理插件、数据流和媒体操作的框架。 GStreamer核心库还提供了一个API, 这个API是开放给程序员使用的---当程序员需要使用其他的插件来编写他所需要的应用程序的时候可以使用它。

    下图是GStreamer的架构图

    Media Applications

    最上面一层为应用,比如gstreamer自带的一些工具(gst-launch,gst-inspect等),以及基于gstreamer封装的库(gst-player,gst-rtsp-server,gst-editing-services等)根据不同场景实现的应用。

    Core Framework

    中间一层为Core Framework,主要提供:

    • 上层应用所需接口
    • Plugin的框架
    • Pipline的框架
    • 数据在各个Element间的传输及处理机制
    • 多个媒体流(Streaming)间的同步(比如音视频同步)
    • 其他各种所需的工具库

    Plugins

    最下层为各种插件,实现具体的数据处理及音视频输出,应用不需要关注插件的细节,会由Core Framework层负责插件的加载及管理。主要分类为:

    • Protocols:负责各种协议的处理,file,http,rtsp等。
    • Sources:负责数据源的处理,alsa,v4l2,tcp/udp等。
    • Formats:负责媒体容器的处理,avi,mp4,ogg等。
    • Codecs:负责媒体的编解码,mp3,vorbis等。
    • Filters:负责媒体流的处理,converters,mixers,effects等。
    • Sinks:负责媒体流输出到指定设备或目的地,alsa,xvideo,tcp/udp等。

    Gstreamer框架根据各个模块的成熟度以及所使用的开源协议,将core及plugins置于不同的源码包中:

    • gstreamer: 包含core framework及core elements。
    • gst-plugins-base: gstreamer应用所需的必要插件。
    • gst-plugins-good: 高质量的采用LGPL授权的插件。
    • gst-plugins-ugly: 高质量,但使用了GPL等其他授权方式的库的插件,比如使用GPL的x264,x265。
    • gst-plugins-bad: 质量有待提高的插件,成熟后可以移到good插件列表中。
    • gst-libav: 对libav封装,使其能在gstreamer框架中使用。

    2、技术特点

    接口简介高效

    GStreamer 提供简洁高效的接口,可以满足以下场景

    • 方便创建多媒体pipeline,框架中已经集成了大量强大的工具,我们再创建我们的多媒体应用pipeline时,可以直接使用,这样就简化了编码工作
    • 编写多媒体插件,GStreamer框架提供了高效常见创建插件的api,集成了调试和跟踪机制,提供了大量示例

    面向对象设计

            GStreamer是基于GLib2.0中的对象模型GObject开发的,熟悉Glib或者旧版本GTK+的程序员可能对GStreamer有很多好感,另外GStreamer采用了信号与对象属性机制,而且所有对象的属性和功能都能在运行态被查询,

    可扩展

    所有 GStreamer 对象都可以使用 GObject 继承方法进行扩展。 所有插件都是动态加载的,可以独立扩展和升级。

    支持插件以二进制形式发布

    插件是在运行时加载的共享库。由于插件的所有属性都可以使用 GObject 属性设置,因此不需要(实际上也没有办法)为插件安装任何头文件。插件完全独立,插件的所有相关方面都可以在运行时查询。

    高性能

    高性能主要体现在以下几个方面:

    • 使用GLib's GSlice allocator
    • 插件之间的连接非常轻型(light-weight)。数据在管道中的传递使用最小的消耗,管道中插件之间的数据传递只会涉及指针废弃。提供了一套对目标内存直接进行操作的机制。例如,插件可以向X server共享的内存空间直接写数据,缓冲区也可以指向任意的内存,如声卡的内部硬件缓冲区。
    • refcounting和写拷贝将 memcpy减少到最低。子缓冲区有效地将缓冲区分离为易于管理的块。
    • 专用流线程(dedicated streaming threads),由内核处理调度。
    • 使用特殊的插件从而支持硬件加速。
    • 采用带有说明的插件注册,这样的话只在实际需要使用该插件才会去装载

    核心库和插件分离机制

    GStreamer 的core与媒体无关。它只知道字节和块,并且只包含基本元素。 GStreamer 的核心功能甚至可以实现低级系统工具,例如 cp。

    所有媒体处理功能都由核心外部的插件提供。这些告诉核心如何处理特定类型的媒体。

    提供编解码器实验框架

    GStreamer 还是一个简单的框架,编解码器开发人员可以在其中测试不同的算法,加速开发开放和免费的多媒体编解码器,例如 Theora 和 Vorbis。

    3、基本概念

    本章将介绍GStreamer的基本概念。理解这些概念对于你后续的学习非常重要,因为后续深入的讲解我们都假定你已经完全理解了这些概念。

    组件(Elements)

    组件(element)是GStreamer中最重要的概念。你可以通过创建一系列的组件(Elements),并把它们连接起来,从而让数据流在这个被连接的各个组件(Elements)之间传输。每个组件(Elements)都有一个特殊的函数接口,对于有些组件(Elements)的函数接口它们是用于能够读取文件的数据,译码文件数据的。而有些组件(E1ements)的函数接口只是输出相应的数据到具体的设备上(例如,声卡设备)。你可以将若干个组件(Elements)连接在一起,从而创建一个管道(pipeline)来完成一个特殊的任务,例如,媒体播放或者录音。GStreamer已经默认安装了很多有用的组件(Elements),通过使用这些组件(Elements)你能够构建一个具有多种功能的应用程序。当然,如果你需要的话,你可以自己编写一个新的组件(Elements)。对于如何编写组件(Elements)的话题在GStreamer PluginWriter's Guide 中有详细的说明。

    Bin 和管道(pipelines)

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

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

    Pads

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

    下面的这个比喻可能对你理解衬垫(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)信息)。

    Gstreamer数据消息交互

    在pipeline运行的过程中,各个element以及应用之间不可避免的需要进行数据消息的传输,gstreamer提供了bus系统以及多种数据类型(Buffers、Events、Messages,Queries)来达到此目的:

    Bus

    Bus是gstreamer内部用于将消息从内部不同的streaming线程,传递到bus线程,再由bus所在线程将消息发送到应用程序。应用程序只需要向bus注册消息处理函数,即可接收到pipline中各element所发出的消息,使用bus后,应用程序就不用关心消息是从哪一个线程发出的,避免了处理多个线程同时发出消息的复杂性。

    Buffers

    用于从sources到sinks的媒体数据传输。

    Events

    用于element之间或者应用到element之间的信息传递,比如播放时的seek操作是通过event实现的。

    Messages

    是由element发出的消息,通过bus,以异步的方式被应用程序处理。通常用于传递errors, tags, state changes, buffering state, redirects等消息。消息处理是线程安全的。由于大部分消息是通过异步方式处理,所以会在应用程序里存在一点延迟,如果要及时的相应消息,需要在streaming线程捕获处理。

    Queries

    用于应用程序向gstreamer查询总时间,当前时间,文件大小等信息。

    gstreamer tools

    Gstreamer自带了gst-inspect-1.0和gst-launch-1.0等其他命令行工具,我们可以使用这些工具完成常见的处理任务。
    gst-inspect-1.0
    查看gstreamer的plugin、element的信息。直接将plugin/element的类型作为参数,会列出其详细信息。如果不跟任何参数,会列出当前系统gstreamer所能查找到的所有插件。

    $ gst-inspect-1.0 playbin

    gst-launch-1.0
    用于创建及执行一个Pipline,因此通常使用gst-launch先验证相关功能,然后再编写相应应用。
    通过上面ogg视频播放的例子,我们已经看到,一个pipeline的多个element之间通过 “!" 分隔,同时可以设置element及Cap的属性。例如:
    播放音视频

    gst-launch-1.0 playbin file:///home/root/test.mp4

    转码

    1. gst-launch-1.0 filesrc location=/videos/sintel_trailer-480p.ogv ! decodebin name=decode ! \
    2. videoscale ! "video/x-raw,width=320,height=240" ! x264enc ! queue ! \
    3. mp4mux name=mux ! filesink location=320x240.mp4 decode. ! audioconvert ! \
    4. avenc_aac ! queue ! mux.

    Streaming

    1. #Server
    2. gst-launch-1.0 -v videotestsrc ! "video/x-raw,framerate=30/1" ! x264enc key-int-max=30 ! rtph264pay ! udpsink host=127.0.0.1 port=1234
    3. #Client
    4. gst-launch-1.0 udpsrc port=1234 ! "application/x-rtp, payload=96" ! rtph264depay ! decodebin ! autovideosink sync=false

    Hello GStreamer

     在面对一个新的软件库时,第一步通常实现一个“hello world”程序,来了解库的用法。对于GStreamer,我们可以实现一个极简的播放器,来了解GStreamer的使用。

    下面我们选择Ubuntu作为我们的开发环境使用官方的HelloWorld作为我们的第一个应用:basic-tutorial-1.c

    1. #include <gst/gst.h>
    2. int
    3. main (int argc, char *argv[])
    4. {
    5. GstElement *pipeline;
    6. GstBus *bus;
    7. GstMessage *msg;
    8. /* Initialize GStreamer */
    9. gst_init (&argc, &argv);
    10. /* Build the pipeline */
    11. pipeline =
    12. gst_parse_launch
    13. ("playbin uri=https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm",
    14. NULL);
    15. /* Start playing */
    16. gst_element_set_state (pipeline, GST_STATE_PLAYING);
    17. /* Wait until error or EOS */
    18. bus = gst_element_get_bus (pipeline);
    19. msg =
    20. gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE,
    21. GST_MESSAGE_ERROR | GST_MESSAGE_EOS);
    22. /* See next tutorial for proper error message handling/parsing */
    23. if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ERROR) {
    24. g_error ("An error occurred! Re-run with the GST_DEBUG=*:WARN environment "
    25. "variable set for more details.");
    26. }
    27. /* Free resources */
    28. gst_message_unref (msg);
    29. gst_object_unref (bus);
    30. gst_element_set_state (pipeline, GST_STATE_NULL);
    31. gst_object_unref (pipeline);
    32. return 0;
    33. }

    在终端中执行

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

    编译成功后,我们可以得到可执行文件,执行 basic-tutorial-1,会在弹出的窗口中,自动读取服务器上的sintel_trailer-480p.webm视频文件并播放。如果网络环境不理想,在播放的过程中会经常处理缓冲状态,造成播放卡顿。也可以使用本地媒体文件,将uri的http路径替换为本地uri(例如: uri=file:///home/john/test.mp4)避免网络的影响。

    源码分析

    通过上面的代码,我们达到了播放一个视频文件的目的,接下来通过分析这个简短的程序来了解gstreamer应用是如何工作的。


    GStreamer初始化

      /* Initialize GStreamer */
      gst_init (&argc, &argv);

    首先我们调用了gstreamer的初始化函数,该初始化函数必须在其他gstreamer接口之前被调用,gst_init会负责以下资源的初始化:

    • 初始化GStreamer库
    • 注册内部element
    • 加载插件列表,扫描列表中及相应路径下的插件
    • 解析并执行命令行参数

    在不需要gst_init处理命令行参数时,我们可以讲NULL作为其参数,例如:gst_init(NULL, NULL);

    创建Pipeline

    1. /* Build the pipeline */
    2. pipeline =
    3. gst_parse_launch
    4. ("playbin uri=https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm",
    5. NULL);

    这一行是示例中的核心逻辑,展示了如何通过gst_parse_launch 创建一个playbin的pipeline,并设置播放文件的uri。

    gst_parse_launch

    在基本介绍中我们了解了Pipeline的概念,在pipeline中,首先通过“source” element获取媒体数据,然后通过一个或多个element对编码数据进行解码,最后通过“sink” element输出声音和画面。通常在创建较复杂的pipeline时,我们需要通过gst_element_factory_make来创建element,然后将其加入到GStreamer Bin中,并连接起来。当pipeline比较简单并且我们不需要对pipeline中的element进行过多的控制时,我们可以采用gst_parse_launch 来简化pipeline的创建。
    这个函数能够巧妙的将pipeline的文本描述转化为pipeline对象,我们也经常需要通过文本方式构建pipeline来查看GStreamer是否支持相应的功能,因此GStreamer提供了gst-launch-1.0命令行工具,极大的方便了pipeline的测试。

    playbin

    我们知道pipeline中需要添加特定的element以实现相应的功能,在本例中,我们通过gst_parse_launch创建了只包含一个element的Pipeline。
    我们刚提到pipeline需要有“source”、“sink” element,为什么这里只需要一个playbin就够了呢?是因为playbin element内部会根据文件的类型自动去查找所需要的“source”,“decoder”,”sink”并将它们连接起来,同时提供了部分接口用于控制pipeline中相应的element。
    在playbin后,我们跟了一个uri参数,指定了我们想要播放的媒体文件地址,playbin会根据uri所使用的协议(“https://”,“ftp://”,“file://”等)自动选择合适的source element(此例中通过https方式)获取数据。

    设置播放状态

    1. /* Start playing */
    2. gst_element_set_state (pipeline, GST_STATE_PLAYING);

    这一行代码引入了一个新的概念“状态”(state)。每个GStreamer element都有相应都状态,我们目前可以简单的把状态与播放器的播放/暂停按钮联系起来,只有当状态处于PLAYING时,pipeline才会播放/处理数据。
    这里gst_element_set_state通过pipeline,将playbin的状态设置为PLAYING,使playbin开始播放视频文件。

    等待播放结束

    1. /* Wait until error or EOS */
    2. bus = gst_element_get_bus (pipeline);
    3. msg = gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE, GST_MESSAGE_ERROR | GST_MESSAGE_EOS);

    这几行会等待pipeline播放结束或者播放出错。我们知道GStreamer框架会通过bus,将所发生的事件通知到应用程序,因此,这里首先取得pipeline的bus对象,通过gst_bus_timed_pop_filtered 以同步的方式等待bus上的ERROR或EOS(End of Stream)消息,该函数收到消息后才会返回。
    我们会在下一篇文章中继续介绍消息相关的内容。

    到目前为止,GStreamer会处理视频播放的所有工作(数据获取,解码,音视频同步,输出)。当到达文件末端(EOS)或出错(直接关闭播放窗口,断开网络)时,播放会自动停止。我们也可以在终端通过ctrl+c中断程序的执行。

    释放资源

    1. /* Free resources */
    2. if (msg != NULL)
    3. gst_message_unref (msg);
    4. gst_object_unref (bus);
    5. gst_element_set_state (pipeline, GST_STATE_NULL);
    6. gst_object_unref (pipeline);

    这里我们将不再使用的msg,bus对象进行销毁,并将pipeline状态设置为NULL(在NULL状态时GStreamer会释放为pipeline分配的所有资源),最后销毁pipeline对象。由于GStreamer是继承自GObject,所以需要通过gst_object_unref 来减少引用计数,当对象的引用计数为0时,函数内部会自动释放为其分配的内存。
    不同接口会对返回的对象进行不同的处理,我们需要详细的阅读API文档,来决定我们是否需要对返回的对象进行释放。

    参考文档

    本篇主要基于官方文档,并深度参考了以下文档

    Basic tutorial 1: Hello world! (gstreamer.freedesktop.org)

    gstreamer - 标签 - John.Leng - 博客园 (cnblogs.com)


     

    gstreamer手册(简体中文版) - 百度文库 (baidu.com)

  • 相关阅读:
    基于SSM的家政服务网站
    【pen200-lab】10.11.1.72
    Mybatis从零开始创建
    Redis介绍和安装
    向量以及矩阵
    抖音获取douyin分享口令url API 返回值说明
    Tesseract .Net SDK C# OCR 2022.1
    PTE考试解析
    matlab双目标定中基线物理长度获取
    python 调用钉钉机器人接口案例一则 —— 筑梦之路
  • 原文地址:https://blog.csdn.net/hn_zhangkun/article/details/125499589