Gstreamer 应用开发:1-基础介绍

Posted 机器人虎哥

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Gstreamer 应用开发:1-基础介绍相关的知识,希望对你有一定的参考价值。

        我们之前的系列,正式的介绍了Gstreamer,并且围绕如何使用USB相机推流实现RTSP服务器来做了介绍,并在Jeston TX1 平台上做了优化急速的一些探索。

        今天我们开始围绕如何用命令实现一个音视频混合,或者单独的音频,和单独的视频文件如何实现播放,并逐步开始使用C语言程序来实现之前我们用命令行来实现的功能。

        在国内,在音视频领域接触最多实现的方案通常是通过ffmpeg(PC和sever端居多)或者硬件厂家的的SDK实现特定硬件的编解码功能(机顶盒,电视等嵌入式设备)。

        gstreamer跟ffmpeg一样,也是一个媒体框架,可以实现采集,编码,解码,渲染,滤镜等一条龙的媒体解决方案。 gstreamer基于glib实现,用C语言来实现面向对象思维,完全不是标准C++那一套逻辑,由于要跨平台,原生的系统API都是适配封装了一套,甚至自己实现队列,MAP,容器,协程,线程,异步操作,不熟悉glib 的API话,代码理解比较困难,用惯了C++,STL,boost,感觉得这是gstream最让人反感的一点,不合主流,搞的又要学一套API。 Gstreamer采用插件管理各个模块,软件框架比较复杂,采用了异步,协程编程模型,进一步增加了理解难度。

 

1、一个简单的视频播放示例

关于播放,我们先举例说明:我们首先有一个AVI的视频文件。

 

接下来我们播放这个视频。

gst-launch-1.0 filesrc location=/home/nvidia/Pictures/x-raw-640-480-25.avi ! avidemux ! videoconvert ! videoscale ! xvimagesink

 

接下来我们播放的过程中,叠加一个计时器看看:

gst-launch-1.0 filesrc location=/home/nvidia/Pictures/x-raw-640-480-25.avi ! avidemux ! videoconvert ! timeoverlay ! videoscale ! xvimagesink

 

计时显示,播放了20秒内容,这和录制时保持了一致。

#播放MPEG 视频保存成的 AVI文件
gst-launch-1.0 filesrc location=/home/nvidia/Pictures/jpeg_1280_720_30.avi ! avidemux ! jpegdec ! videoconvert ! timeoverlay ! videoscale ! xvimagesink


#播放YUV2视频压缩成H.264的视频 保存成的AVI文件
gst-launch-1.0 filesrc location=/home/nvidia/Pictures/x264-video-640-480-30.avi ! avidemux \\
! h264parse ! avdec_h264 \\
! videoconvert ! timeoverlay ! videoscale ! xvimagesink

2、基础概念介绍

#播放音视频混录ogv文件
gst-launch-1.0 filesrc location=sintel_trailer-480p.ogv \\
! oggdemux name=demux \\
! queue ! vorbisdec ! autoaudiosink demux. \\
! queue ! theoradec ! videoconvert ! xvimagesink
​通过上面的命令播放文件时,会创建如下pipeline:

元件(Elements)

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

箱柜(Bins)和管道(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)信息)。

也有一种简单的介绍和理解。

        Pad是一个element的输入/输出接口,分为src pad(生产数据)和sink pad(消费数据)两种。两个element必须通过pad才能连接起来,pad拥有当前element能处理数据类型的能力(capabilities),会在连接时通过比较src pad和sink pad中所支持的能力,来选择最恰当的数据类型用于传输,如果element不支持,程序会直接退出。在element通过pad连接成功后,数据会从上一个element的src pad传到下一个element的sink pad然后进行处理。当element支持多种数据处理能力时,我们可以通过Cap来指定数据类型.

比如下面的命令:videotestsrc 会根据 "video/x-raw,width=1280,height=720"说明的格式和长宽高产生视频

gst-launch-1.0 videotestsrc ! "video/x-raw,width=1280,height=720" ! xvimagesink

 

#使用USB的YUY2数据格式通道,格式为! 'video/x-raw,format=YUY2,width=1280, height=720, framerate=5/1'
gst-launch-1.0 v4l2src device=/dev/video0 num-buffers=1 \\
! 'video/x-raw,format=YUY2,width=1280, height=720, framerate=5/1' \\
! jpegenc ! filesink location=/home/nvidia/Pictures/file_1280_720_YUV_1401.jpg
#使用USB的MPEG数据格式通道,格式为! ' image/jpeg,width=640,height=480,framerate=30/1'
gst-launch-1.0 v4l2src device=/dev/video0 num-buffers=1 \\
! image/jpeg,width=640,height=480,framerate=30/1 ! \\
filesink location=/home/nvidia/Pictures/file_jpeg_640_480_1417.jpg

在之前的介绍中,我们就有很多类似Cap来指定数据类型的使用。

gst-launch-1.0

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

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

3、信息交互

这一部分内容,我们主要是为后续的编程实现,准备一些基本的概念思路。

在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查询总时间,当前时间,文件大小等信息。

gst-inspect-1.0

        查看gstreamer的plugin、element的信息。直接将plugin/element的类型作为参数,会列出其详细信息。如果不跟任何参数,会列出当前系统gstreamer所能查找到的所有插件。

GStreamer基础教程02 - 基本概念

摘要

Gstreamer基础教程01 - Hello World中,我们介绍了如何快速的通过一个字符串创建一个简单的pipeline。为了能够更好的控制pipline中的element,我们需要单独创建element,然后再构造pipeline,下面将介绍GStreamer的一些基本概念并展示pipeline的另一种构造方式。

 

基本概念

Element

我们知道element是构建GStreamer pipeline的基础,element在框架中的类型为GstElement,所有GStreamer提供的解码器(decoder),编码器(encoder), 分离器(demuxer), 音视频输出设备都是派生自GstElement。

那么element到底是什么呢?
从应用的角度,我们可以将一个element认为是一个功能块,他实现一个特定的功能,比如:数据读取,音频解码,声音输出等。各个功能块之间可以通过某种特定的数据接口(这种接口称为pad,将在后续章节讲述)进行数据传输。每个element有唯一的类型,还有相应的属性,用于控制element的行为。

 

为了更直观的展现element,我们通常用一个框来表示一个element,在element内部使用小框表示pad。


这些功能块有些可以生成数据,有些只接收数据,有些先接收数据,再生成数据。为了便于区分这些element,我们把他们分为三类:
1. source element
只能生成数据,不能接收数据的element称为source element。例如用于文件读取的filesrc等。
对于source element,我们通常用src pad表示element能产生数据,并将其放在element的右边。source element只有src pad,通过设备、文件、网络等方式读取数据后,通过src pad向pipeline发送数据,开始pipeline的处理流程。如下图:

技术图片

2. sink element
只能接收数据,不能产生数据的element称为sink element。例如播放声音的alsasink等。
对于sink element,我们通常用sink pad表示element能接收处理数据,并将其放在element的左边。sink element只有sink pad,从sink pad读取数据后,将数据发送到指定设备或位置,结束pipeline的处理流程。如下图:

技术图片

3. filter-like element
既能接收数据,又能生成数据的element称为filter-like element。例如分离器,解码器,音量控制器等。
对于filter-like element,既包含位于element左边的sink pad,又包含位于element右边的src pad。Element首先从sink pad读取数据,然后对数据进行处理,最后在src pad产生新的数据。如下图:

技术图片

对于这些的element,可能包含多个src pad,也可能包含多个sink pad,例如mp4的demuxer(qtdemux)会将mp4文件中的音频和视频的分离到audio src pad和video src pad。而mp4的muxer(mp4mux)则相反,会将audio sink pad和video sink pad的数据合并到一个src pad,再经其他element将数据写入文件或发送到网络。demuxer如下图:

技术图片

 

连接element
当我们有很多element时,我们需要将他们按数据的传输路径将其串联起来,src pad只能联接到sink pad,这样才能够实现相应的功能。

技术图片

 

 

 

Bin和Pipeline

我们将element串联起来后就能实现相应的功能,为什么我们还需要bin和pipline呢?我们首先来看看在GStreamer框架中element,bin,pipeline对象之间的继承关系:

GObject
    ╰──GInitiallyUnowned
        ╰──GstObject
            ╰──GstElement
                ╰──GstBin
                    ╰──GstPipeline

 

这里bin和pipeline都是一个element,那么bin和pipeline都在element的基础上实现了什么功能,解决了什么问题呢?
我们在创建了element多个element后,我们需要对element进行状态/资源管理,如果每次状态改变时,都需要依次去操作每个element,这样每次编写一个应用都会有大量的重复工作,这时就有了bin。
Bin继承自element后,实现了容器的功能,可以将多个element添加到bin,当操作bin时,bin会将相应的操作转发到内部所有的element中,我们可以将bin认为认为是一个新的逻辑element,由bin来管理其内部element的状态及资源,同事转发其产生的消息。常见的bin有decodebin,autovideoconvert等。

技术图片

 

Bin实现了容器的功能,那pipeline又有什么功能呢?
在多媒体应用中,音视频同步是一个基本的功能,需要支持这样的功能,所有的element必须要有一个相同的时钟,这样才能保证各个音频和视频在同一时间输出。pipeline就会为其内部所有的element选择一个相同的时钟,同时还为应用提供了bus系统,用于消息的接收。

 

Bus

刚才我们提到pipeline会提供一个bus,这个pipeline上所有的element都可以使用这个bus向应用程序发送消息。Bus主要是为了解决多线程之间消息处理的问题。由于GStreamer内部可能会创建多个线程,如果没有bus,应用程序可能同时收到从多个线程的消息,如果应用程序在发送线程中通过回调去处理消息,应用程序有可能阻塞播放线程,造成播放卡顿,死锁等其他问题。为了解决这类问题,GStreamer通常是将多个线程的消息发送到bus系统,由应用程序从bus中取出消息,然后进行处理。Bus在这里扮演了消息队列的角色,通过bus解耦了GStreamer框架和应用程序对消息的处理,降低了应用程序的复杂度。

 

Element Hello World

在有上面的知识后,我们通过一个示例来看看element是如何创建及使用的。

#include <gst/gst.h>

int main (int argc, char *argv[])

  GstElement *pipeline, *source, *filter, *sink;
  GstBus *bus;
  GstMessage *msg;
  GstStateChangeReturn ret;

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

  /* Create the elements */
  source = gst_element_factory_make ("videotestsrc", "source");
  filter = gst_element_factory_make ("timeoverlay", "filter");
  sink = gst_element_factory_make ("autovideosink", "sink");

  /* Create the empty pipeline */
  pipeline = gst_pipeline_new ("test-pipeline");

  if (!pipeline || !source || !filter || !sink) 
    g_printerr ("Not all elements could be created.\\n");
    return -1;
  

  /* Build the pipeline */
  gst_bin_add_many (GST_BIN (pipeline), source, filter, sink, NULL);
  if (gst_element_link_many (source, filter, sink, NULL) != TRUE) 
    g_printerr ("Elements could not be linked.\\n");
    gst_object_unref (pipeline);
    return -1;
  

  /* Modify the source‘s properties */
  g_object_set (source, "pattern", 0, NULL);

  /* Start playing */
  ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
  if (ret == GST_STATE_CHANGE_FAILURE) 
    g_printerr ("Unable to set the pipeline to the playing state.\\n");
    gst_object_unref (pipeline);
    return -1;
  

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

  /* Parse message */
  if (msg != NULL) 
    GError *err;
    gchar *debug_info;

    switch (GST_MESSAGE_TYPE (msg)) 
      case GST_MESSAGE_ERROR:
        gst_message_parse_error (msg, &err, &debug_info);
        g_printerr ("Error received from element %s: %s\\n",
            GST_OBJECT_NAME (msg->src), err->message);
        g_printerr ("Debugging information: %s\\n",
            debug_info ? debug_info : "none");
        g_clear_error (&err);
        g_free (debug_info);
        break;
      case GST_MESSAGE_EOS:
        g_print ("End-Of-Stream reached.\\n");
        break;
      default:
        /* We should not reach here because we only asked for ERRORs and EOS */
        g_printerr ("Unexpected message received.\\n");
        break;
    
    gst_message_unref (msg);
  

  /* Free resources */
  gst_object_unref (bus);
  gst_element_set_state (pipeline, GST_STATE_NULL);
  gst_object_unref (pipeline);
  return 0;

编译并运行示例,可以看到弹出的窗口中播放着测试视频,并且还显示着播放时间。

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

 

源码分析

创建Element

/* Create the elements */
  source = gst_element_factory_make ("videotestsrc", "source");
  filter = gst_element_factory_make ("timeoverlay", "filter");
  sink = gst_element_factory_make ("autovideosink", "sink");

在对GStreamer进行初始化后,我们可以通过gst_element_factory_make创建element。第一个参数是element的类型,可以通过这个字符串,找到对应的类型,从而创建element对象。第二个参数指定了创建element的名字,当我们没有保存创建element的对象指针时,我们可以通过gst_bin_get_by_name从pipeline中取得该element的对象指针。如果第二个参数为NULL,则GStreamer内部会为该element自动生成一个唯一的名字。
我们在当前示例中创建了3个element:videotestsrc,timeoverlay,autovideosink,其作用分别为:

  • videotestsrc是一个source element,用于产生视频数据,通常用于调试。
  • timeoverlay是一个filter-like element,可以在视频数据中叠加一个时间字符串。
  • autovideosink上一个sink element,用于自动选择视频输出设备,创建视频显示窗口,并显示其收到的数据。

 

创建Pipeline

 /* Create the empty pipeline */
  pipeline = gst_pipeline_new ("test-pipeline");

Pipeline通过gst_pipeline_new创建,参数为pipeline的名字。


我们知道pipeline会提供播放所必须的时钟以及对消息的处理,所以我们需要把我们创建的element添加到pipeline中。

/* Build the pipeline */
gst_bin_add_many (GST_BIN (pipeline), source, filter, sink, NULL);
if (gst_element_link_many (source, filter, sink, NULL) != TRUE) 
  g_printerr ("Elements could not be linked.\\n");
  gst_object_unref (pipeline);
  return -1;

从上面的讲解我们知道pipeline是继承自bin,所以所有bin的方法都可以应用于pipeline,需要注意的是,我们需要通过相应的宏(这里是GST_BIN)来将子类转换为父类,宏内部会对其做类型检查。在这里我们使用gst_bin_add_many将多个element加入到pipeline中,这个函数接受任意多个参数,最后以NULL表示参数列表的结束。如果一次只需要加入一个,可以使用gst_bin_add函数。


在将element加入bin后,我们需要将其连接起来才能完成相应的功能,由于有多个element,所以我们这里使用gst_element_link_many,element会根据参数的顺序依次将element连接起来。

技术图片

需要注意的是,只有被加入到同一个bin的element才能够被连接在一起,所以我们需要在连接前,将所需要的element加入到pipeline/bin中。

 

至此,我们已经完成了pipeline的创建,test-pipeline可以表示为:

技术图片

 

 设置element属性

/* Modify the source‘s properties */
g_object_set (source, "pattern", 0, NULL);

大部分的element都有自己的属性。有的属性只能被读取,这种属性常用于查询element的状态。有的属性同时支持修改,这种属性常用于控制element的行为。


由于GstElement继承于GObject,同时GObject对象系统提供了 g_object_get()用于读取属性,g_object_set()用于修改属性,g_object_set()支持以NULL结束的属性-值的键值对,所以可以一次修改element的多个属性。

我们这里通过g_object_set()来修改videotestsrc的pattern属性。pattern属性可以控制测试图像的类型,可以尝试将0修改为1,查看输出结果有何不同。

我们可以通过gst-inspect-1.0 videotestsrc命令来查看pattern所支持的所有值。

 

设置播放状态

  /* Start playing */
  ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
  if (ret == GST_STATE_CHANGE_FAILURE) 
    g_printerr ("Unable to set the pipeline to the playing state.\\n");
    gst_object_unref (pipeline);
    return -1;
  

在完成pipeline的创建以及属性的修改后,我们将pipeline的状态设置为PLAYING,这里与上一文章中的示例相同,只增加来错误处理,其他的返回值处理讲在后续章节讲述。

 

等待播放结束与释放资源

这部分内容与上一文章中的示例相同,这里只增加了消息类型的判断。更多关于消息的内容将在后续章节讲述。
由于videotestsrc会持续输出视频数据,所以我们在这个例子中不会受到EOS消息,只有当我们关闭视频窗口时会收到error消息,发送消息的element名和具体的消息内容会通过终端输出。

 

总结

在本教程中,我们掌握了:

  • GStreamer element,bin,pipeline,bus的基本概念。
  • 如何使用gst_element_factory_make ()创建element。
  • 如何使用gst_pipeline_new ()创建pipeline。
  • 如何使用gst_bin_add_many ()将多个element加入pipeline。
  • 如何使用gst_element_link_many ()将多个element连接起来。

在这两篇文章中,我们介绍了pipeline的两种创建方式,下一篇文章我们将介绍GStreamer是如何保证element可以正确的连接在一起。


引用

https://gstreamer.freedesktop.org/documentation/tutorials/basic/concepts.html?gi-language=c
https://gstreamer.freedesktop.org/documentation/application-development/basics/elements.html?gi-language=c
https://gstreamer.freedesktop.org/documentation/application-development/basics/bins.html?gi-language=c
https://gstreamer.freedesktop.org/documentation/gstreamer/gstpipeline.html?gi-language=c

 

作者:John.Leng
本文版权归作者所有,欢迎转载。商业转载请联系作者获得授权,非商业转载请在文章页面明显位置给出原文连接.

以上是关于Gstreamer 应用开发:1-基础介绍的主要内容,如果未能解决你的问题,请参考以下文章

GStreamer应用开发手册学习笔记之二

GStreamer应用开发手册学习笔记之二

GStreamer基础教程02 - 基本概念

GStreamer基础教程03 - 媒体类型与Pad

音视频处理基础框架介绍,FFmpegGStreamerOpenCVOpenGL

GStreamer基础教程07 - 播放速率控制