当前位置: 首页 > news >正文

GStreamer开发笔记(六):gstreamer基本概念:组件、箱柜、管道、衬垫、链接组件

若该文为原创文章,转载请注明原文出处
本文章博客地址:https://blog.csdn.net/qq21497936/article/details/148334356

长沙红胖子Qt(长沙创微智科)博文大全:开发技术集合(包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软硬结合等等)持续更新中…

FFmpeg、SDL和流媒体开发专栏


上一篇:《GStreamer开发笔记(五):gstreamer创建组件、管道和总线实现简单的播放器》
下一篇:敬请期待…


前言

  前面已经跑了几个Demo有个基本的了解了,进一步深入,本篇对GStreamer进行一些重要的基础概念讲解,解说GStreamer基础概念组成方式。
  对组件、箱柜、管道、衬垫、链接组件、总线、消息类型进行讲解,本篇篇幅较长,根据学习进度调整了阅读顺序,建议反复阅读几次。


注意

  整个讲解顺序进行了调整,以便于能让读者了解熟悉并且可以通过代码实现一个简单的Demo来理解GStreamer的开发过程,随着系列文章进程,会越来越深入。


GStreamer基础核心概念

组件(Elements)

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

箱柜(Bins)

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

管道(pipelines)

  管道(pipelines)是箱柜(Bins)的一个特殊的子类型,管道(pipelines)可以操作包含在它自身内部的所有组件(element)。管道(pipeline)是高级的箱柜(Bins)。当设定管道的暂停或者播放状态的时候,数据流将开始流动,并且媒体数据处理也开始处理。一旦开始,管道将在一个单独的线程中运行,直到被停止或者数据流播放完毕。

衬垫(Pads)

  衬垫(Pads)在GStreamer中被用于多个组件的链接,从而让数据流能在这样的链接中流动。一个衬垫(Pads)可以被看作是一个组件(element)插座或者端口,组件(element)之间的链接就是依靠着衬垫(Pads)。
  衬垫(Pads)有处理特殊数据的能力:一个衬垫(Pads)能够限制数据流类型的通过。链接成功的条件是:只有在两个衬垫(Pads)允许通过的数据类型一致的时候才被建立。数据类型的设定使用了一个叫做caps negotiation的方法。数据类型被为一个GstCaps变数所描述。
对于大部分情况,所有的数据流都是在链接好的元素之间流动。数据向组件(element)以外流出可以通过一个或者多个source衬垫(Pads),组件(element)接受数据是通过一个或者多个sink衬垫(Pads)来完成的。Source组件(element)和sink组件(element)分别有且仅有一个 sink 衬垫(Pads)或者source 衬垫(Pads)。数据在这里代表的是缓冲区(buffers) (GstBuffer对象描述了数据的缓冲区(buffers)的信息)和事件(events) (GstEvent对象描述了数据的事件(events)信息)。


组件(Element)

  GStreamer 中最重要的一个概念就是 GstElement 对象。组件是构建一个媒体管道的基本块。所有上层(high-level)部件都源自GstElement物件。任何一个译码器编码器、分离器、视频/音频输出部件实际上都是一个 GstElement对象。
组件就像一个黑盒子。你从组件的一端输入数据,组件对数据进行一些处理,然后数据从组件的另一段输出。

源组件

  源组件(Source elements)为管道产生数据,比如从磁盘或者声卡读取数据。
  在这里插入图片描述

  源组件不接收数据,仅产生数据。上图仅有一个源衬垫(右端),同样的, 源衬垫也仅产生数据(对外部而言)

过滤器(filters)、转换器(convertors)、分流器(demuxers)、整流器(muxers)以及编译码器(codecs)

  过滤器(Filters)以及类过滤组件(Filter-like elements)都同时拥有输入和输出衬垫。他们对从输入衬垫得到的数据进行操作,然后将数据提供给输出衬垫。音量组件(filter)、视频转换器 (convertor)、 Ogg 分流器或者 Vorbis 译码器都是这种类型的组件。
  类过滤组件可以拥有任意个的源衬垫或者接收衬垫。像一个视频分流器可能有一个接收衬垫以及多个(1-N)源衬垫,每个接收衬垫对应一种元数据流 (elementarystream)。相反地,译码器只有一个源衬垫及一个接收衬垫。
  在这里插入图片描述

  这个特殊的组件同时拥有源端和接收端。接收输入数据的接收衬垫在组件的左端,源衬垫在右端。
  在这里插入图片描述

  另一种了类过滤组件。它有多个输出衬垫(source pad)。Ogg分流器是个很好的实例。因为Ogg流包含了视频和音频。一个源衬垫可能包含视频元数据流,另一个则包含音频元数据流。当一个新的衬垫被创建时,分流器通常会产生一个信号。程序员可以在信号处理事件中处理新的元数据流

接收组件

  收组件是媒体管道的末端,它接收数据但不产生任何数据。写磁盘、利用声卡播放声音以及视频输出等都是由接收组件实现的。
  在这里插入图片描述

创建一个GstElement组件

  创建一个组件的最简单的方法是通过函数gst_element_factory_make()。这个函数使用一个已存在的工厂对象名和一个新的组件名来创建组件。创建完之后,你可以用新的组件名在箱柜(bin)中查询得到这个组件。这个名字同样可以用来调试程序的输出。你可以通过传递 NULL 来得到一个默认的具有唯一性的名字。
  当不再需要一个组件时,使用gst_object_unref()来对它进行解引用。这会将一个组件的引用数减少 1。任何一个组件在创建时,其引用记数为 1。当其引用记数为 0 时,该组件会被销毁。

#include <gst/gst.h>
Int main (int argc, char *argv[]) 
{GstElement *element;/* init GStreamer */gst_init (&argc, &argv);/* create element */element = gst_element_factory_make ("fakesrc", "source");if (!element) {g_print ("Failed to create element of type 'fakesrc'\n");return -1;}gst_object_unref (GST_OBJECT (element));return 0;
}

组件状态

  一个组件在被创建后,它不会执行任何操作。所以你需要改变组件的状态,使得它能够做某些事情。 Gstreamer 中,组件有四种状态,每种状态都有其特定的意义。这四种状态为:

  • GST_STATE_NULL:默认状态。该状态将会回收所有被该组件占用的资源。
  • GST_STATE_READY:准备状态。组件会得到所有所需的全局资源,这些全局资源将被通过该组件的数据流所使用。例如打开设备、分配缓存等。但在这种状态下,数据流仍未开始被处理,所以数据流的位置信息应该自动置0。如果数据流先前被打开过,它应该被关闭,并且其位置信息、特性
    信息应该被重新置为初始状态。
  • GST_STATE_PAUSED:暂停状态。在这种状态下,组件已经对流开始了处理,但此刻暂停了处理。因此该状态下组件可以修改流的位置信息,读取或者处理流数据,以及一旦状态变为 PLAYING,流可以重放数据流。这种情况下,时钟是禁止运行的。总之, PAUSED 状态除了不能运行时钟外,其它与PLAYING 状态一模一样。处于 PAUSED 状态的组件会很快变换到 PLAYING 状态。举例来说,视频或音频输出组件会等待数据的到来,并将它们压入队列。一旦状态改变,组件就会处理接收到的数据。同样,视频接收组件能够播放数据的第一帧。(因为这并不会影响时钟)。自动加载器(Autopluggers)可以对已经加载进管道的插件进行这种状态转换。其它更多的像 codecs 或者 filters 这种组件不需要在这个状态上做任何事情。•
  • GST_STATE_PLAYING:播放状态。PLAYING 状态除了当前运行时钟外,其它与PAUSED 状态一模一样。
      可以通过函数 gst_element_set_state()来改变一个组件的状态。你如果显式地改变一个组件的状态,GStreamer可能会使它在内部经过一些中间状态。例如你将一个组件从NULL状态设置为PLAYING 状态,GStreamer在其内部会使得组件经历过READY以及PAUSED状态。
      当处于GST_STATE_PLAYING状态,管道会自动处理数据。它们不需要任何形式的迭代。GStreamer会开启一个新的线程来处理数据。GStreamer同样可以使用GstBus在管道线程和应用程序线程间交互信息

衬垫(Pads)

  一个衬垫的类型由 2 个特性决定:它的数据导向(direction)以及它的时效性(availability)。正如我们先前提到的,Gstreamer定义了2种衬垫的数据导向:源衬垫以及接收衬垫。衬垫的数据导向这个术语是从组件内部的角度给予定义的:组件通过它们的接收衬垫接收数据,通过它们的源衬垫输出数据。如果通过一张图来形象地表述,接收衬垫画在组件的左侧,而源衬垫画在组件的右侧,数据从左向右流动。
  衬垫的时效性比衬垫的数据导向复杂得多。一个衬垫可以拥有三种类型的时效性:永久型(always)、随机型(sometimes)、请求型(on request)。三种时效性的意义顾名思义:永久型的衬垫一直会存在,随机型的衬垫只在某种特定的条件下才存在(会随机消失的衬垫也属于随机型),请求型的衬垫只在应用程序明确发出请求时才出现

动态(随机)衬垫

  一些组件在其被创建时不会立刻产生所有它将用到的衬垫。例如在一个 Oggdemuxer 的组件中可能发生这种情况。这个组件将会读取 Ogg 流,每当它在Ogg流中检测到一些元数据流时(例如 vorbis,theora),它会为每个元数据流创建动态衬垫。同样,它也会在流终止时删除该衬垫。动态衬垫在 demuxer 这种组件中可以起到很大的作用。
  运行gst-inspect oggdemux只会显示出一个衬垫在组件中:一个名字叫作’sink’的接收衬垫,其它的衬垫都处于’休眠’中,你可以从衬垫模板(pad template)中的"Exists:Sometimes"的属性看到这些信息。衬垫会根据你所播放的 Ogg 档的类型而产生,认识到这点对于你创建一个动态管道特别重要。当组件通过它的随机型(sometimes)衬垫范本创建了一个随机型(sometimes)的衬垫的时侯,你可以通过对该组件绑定一个信号处理器(signal handler),通过它来得知衬垫被创建。下面一段代码演示了如何这样做:名叫’sink’的接收衬垫, 其它的衬垫都处于’休眠’中, 显而易见这是衬垫”有时存在”的特性。衬垫会根据你所播放的 Ogg 档的类型而产生,这点在你准备创建一个动态管道时显得特别重要,当组件创建了一个”有时存在”的衬垫时,你可以通过对该组件触发一个信号处理器(signal handler)来得知衬垫被创建。

/* listen for newly created pads */
g_signal_connect (demux, "pad-added", G_CALLBACK (cb_new_pad), NULL)

请求衬垫

  组件同样可以拥有请求衬垫(request pads)。这种衬垫不是自动被创建,而是根据请求被创建的。这在多任务(multiplexers)类型的组件中有很大的用处。例如aggregators 以及 tee 组件。 Aggregators 组件可以把多个输入流合并成一个输出流;tee 组件正好相反,它只有一个输入流,然后根据请求把数据流发送到不同的输出衬垫。只要应用程序需要另一份数据流,它可以简单的从 tee 组件请求到一个输出衬垫。
  下面一段代码演示了怎样在一个“tee”组件请求一个新的输出衬垫:

static void some_function (GstElement *tee)
{GstPad * pad;
gchar *name;pad = gst_element_get_request_pad (tee, "src%d");name = gst_pad_get_name (pad);g_print ("A new pad %s was created\n", name);g_free (name);/* here, you would link the pad */[..]/* and, after doing that, free our reference */gst_object_unref (GST_OBJECT (pad));
}

  gst_element_get_request_pad()方法可以从一个组件中得到一个衬垫,这个衬垫基于衬垫范本的名字(pad template)。同样可以请求一个同其它衬垫模板兼容的衬垫,这点在某些情况下非常重要。比如当你想要将一个组件连接到一个多任务型的组件时,你就需要请求一个带兼容性的衬垫。
  gst_element_get_compatible_pad()方法可以得到一个带兼容性的衬垫。下面一段代码将从一个基于 Ogg 的带多输入衬垫的组件中请求一个带兼容性的衬垫。

static void link_to_multiplexer (GstPad *tolink_pad, GstElement *mux)
{GstPad *pad;gchar *srcname, *sinkname;srcname = gst_pad_get_name (tolink_pad);pad = gst_element_get_compatible_pad (mux, tolink_pad);gst_pad_link (tolinkpad, pad);sinkname = gst_pad_get_name (pad);gst_object_unref (GST_OBJECT (pad));g_print ("A new pad %s was created and linked to %s\n", srcname,sinkname);g_free (sinkname);g_free (srcname);
}

  由于衬垫对于一个组件起了非常重要的作用,因此就有了一个术语来描述能够通过衬垫或当前通过衬垫的数据流。这个术语就是功能 (capabilities)。


精灵衬垫

  精灵衬垫来自于箱柜中某些组件,它同样可以在该箱柜中被直接访问。精灵衬垫与 UNIX 文件系统中的符号链接很类似。使用箱柜,你可以在你的代码中将箱柜当作一个普通组件来使用。
  在这里插入图片描述

  使用精灵衬垫后:
  在这里插入图片描述

  最左边组件的接收衬垫同样也是整个箱柜的精灵衬垫。由于精灵衬垫看起来与其它衬垫没什么区别,而且与其它衬垫有着类似的功能。所以它们可以加到任何一种组件上,而不仅仅是GstBin。
  通过函数gst_ghost_pad_new()可以创建一个 ghostpad。


链接组件

  通过将一个源组件,零个或多个类过滤组件,和一个接收组件链接在一起,你可以建立起一条媒体管道。数据将在这些组件间流过。这是 GStreamer 中处理媒体的基本概念。下图组件形象化了媒体管道:
  在这里插入图片描述

  通过链接这三个组件,我们创建了一条简单的组件链。组件链中源组件(“element1”)的输出将会是类过滤组件 (“element2”)的输入。类过滤组件将会对数据进行某些操作,然后将数据输出给最终的接收组件(“element3”)。
  把上述过程想象成一个简单的Ogg/Vorbis 音频译码器。源组件从磁盘读取文件。第二个组件就是 Ogg/Vorbis 音频译码器。最终的接收组件是你的声卡,它用来播放经过译码的音频数据。我们将在该手册的后部分用一个简单的图来构建这个Ogg/Vorbis 播放器。代码如下:

#include <gst/gst.h>
Int main (int argc, char *argv[])
{GstElement *pipeline;GstElement *source, *filter, *sink;/* init */gst_init (&argc, &argv);/* create pipeline */pipeline = gst_pipeline_new ("my-pipeline");/* create elements */source = gst_element_factory_make ("fakesrc", "source");filter = gst_element_factory_make ("identity", "filter");sink = gst_element_factory_make ("fakesink", "sink");/* must add elements to pipeline before linking them */gst_bin_add_many (GST_BIN (pipeline), source, filter, sink, NULL);/* link */if (!gst_element_link_many (source, filter, sink, NULL)) {g_warning ("Failed to link elements!");}[..]
}

  对于一些特定的链接行为,可以通过函数 gst_element_link() 以及 gst_element_link_pads() 来实现。可以使用不同的gst_pad_link_*() 函数来得到单个衬垫的引用并将它们链接起来。
  在链接不同的组件之前,你需要确保这些组件都被加在同一个箱柜中,因为将一个组件加载到一个箱柜中会破坏该组件已存在的一些链接关系。同时,你不能直接链接不在同一箱柜或管道中的组件。如果你想要连接处于不同层次中的组件或衬垫,你将使用到精灵衬垫。

总线(Bus)

  总线是一个简单的系统,它采用自己的线程机制将一个管道线程的消息分发到一个应用程序当中。总线的优势是:当使用GStreamer的时候,应用程序不需要线程识别,即便GStreamer已经被加载了多个线程。
  每一个管道默认包含一个总线,所以应用程序不需要再创建总线。应用程序只需要在总线上设置一个类似于对象的信号处理器的消息处理器。当主循环运行的时候,总线将会轮询这个消息处理器是否有新的消息,当消息被采集到后,总线将呼叫相应的回调函数来完成任务。

总线处理三种方式

  • 回调监听方式:用到gst_bus_add_watch()/gst_bus_add_signal_watch()两个函数。当使用总线时,设置消息处理器到管道的总线上可以使用gst_bus_add_watch()来创建一个消息处理器来侦听管道。每当管道发出一个消息到总线,这个消息处理器(写的一个回调处理消息的函数)就会出发调用。(类似Qt信号槽)
  • 监听方式:侦听总线消息,使用gst_bus_peek()/gst_bus_poll()就可以实现。
  • 阻塞方式:使用gst_bus_timed_pop_filtered()方式直接阻塞,得到触发的消息后返回。
    之前用的阻塞方式:
      在这里插入图片描述

消息类型

  GStreamer有几种由总线传递的预定义消息类型,这些消息都是可扩展的。插件可以定义另外的一些消息,应用程序可以有这些消息的绝对代码或者忽略它们。强烈推荐应用程序至少要处理错误消息并直接的反馈给用户。
  所有的消息都有一个消息源、类型和时间戳。这个消息源能被用来判断由哪个组件发出消息。例如,在众多的消息中,应用程序只对上层的管道发出的消息感兴趣(如状态变换的提示)。下面列出所有的消息种类、代表的意义,以及如何解析具体消息的内容。

  • 错误、警告和消息提示:它们被各个组件用来在必要的时候告知用户现在管道的状态。错误信息表明有致命的错误并且终止数据传送。错误应该被修复,这样才能继续管道的工作。警告并不是致命的,但是暗示有问题存在。消息提示用来告知非错误的信息。这些消息含有一个带有主要的错误类型和消息的GError,和一个任选的调试字符串。这两项都可以用gst_message_parse_error(),gst_message_parse_warning()以及gst_message_parse_info()三个函数来提取其信息。当使用完毕后,错误和修正字符串都将被释放。
  • 数据流结束(End-of-stream)提示:当数据流结束的时候,该消息被发送。管道的状态不会改变,但是之后的媒体操作将会停止。应用程序可以通过收到这一消息来跳到播放列表的下一首歌。在数据流结束提示出现之后,仍然可以通过向后搜索来回到以前数据流前面的位置。之后的播放工作将会自动的继续执行。这个消息没有特殊的参数。
  • 标签(Tags):当元数据在数据流中被找到的时候,此消息被发送。一个管道可以发出多个Tag(如元数据的描述里有艺术家、歌曲名,另外的例子如流的信息采样率和比特率)。应用程序应该将元数据存储在缓存里。函数gst_message_parse_tag()被用来解析tag的列表,当该列表不再使用的时候,函数gst_tag_list_free()释放其相应的tag。
  • 状态转换(State-changes):当状态成功的转换时发送该消息。函数gst_message_parse_state_changed()可以用来解析转换中的新旧状态。
  • 缓冲(Buffering):当缓冲网络数据流时此消息被发送。你可以通过函数gst_message_get_structure()的返回值,来解析"buffer-percent"属性,从而手动的得到缓冲进度(该缓冲进度以百分比的形式表示)。
  • 组件消息(Element messages):它是一组特殊的消息,用以标识一个特定组件。这样一组特殊的消息通常表述了一些额外的信息。组件的信息应该被详细的描述,因为这样一些组件信息将被作为消息而发送给其它组件。例如:'qtdemux’QuickTime整流器(demuxer)应该把’redirect’信息保存于该组件信息当中,以便在某种特殊情况下将’redirect’组件信息发送出去。
  • Application-specific消息:我们可以将取得的消息结构解析出来,从而得到有关Application-specific消息的任何信息。通常这些信息是能够被安全地忽略。
      应用程序消息主要用于内部,以备从一些线程排列信息到主线程应用的需求。这些在使用组件信号的应用中非常实用(这些信号在数据流线程的上下文被发射)。

后话

  以上是根据之前几篇以及最近后几篇需要涉及的开发知识进行相关概念的熟悉,多阅读几遍了解个大概有个印象就可以继续往下走,待每次完成再回顾一下加深一下即可。


上一篇:《GStreamer开发笔记(五):gstreamer创建组件、管道和总线实现简单的播放器》
下一篇:敬请期待…


本文章博客地址:https://blog.csdn.net/qq21497936/article/details/148334356

相关文章:

  • 【存储基础】存储设备和服务器的关系和区别
  • 4、ubuntu系统 | 文本和目录操作函数
  • 设备驱动与文件系统:03 生磁盘的使用
  • Python 训练营打卡 Day 33-神经网络
  • SpringMVC的注解
  • Java设计模式之备忘录模式详解
  • 【小沐杂货铺】基于Three.JS构建IFC模型浏览器(WebGL、CAD、Revit、IFC)
  • 使用source ~/.bashrc修改环境变量之后,关闭服务器,在重启,环境变量还有吗?
  • Spark-TTS: AI语音合成的“变声大师“
  • 一步一步配置 Ubuntu Server 的 NodeJS 服务器详细实录——4. 配置服务器终端环境 zsh , oh my zsh, vim
  • isp中的 ISO代表什么意思
  • 实验一:PyTorch基本操作实验
  • 前端八股之CSS
  • 电脑的ip地址会自动变怎么办?原因解析和解决方法
  • Java生态中的NLP框架
  • 探索大语言模型(LLM):RSE流程详解——从文档中精准识别高相关片段
  • 【03】完整开发腾讯云播放器SDK的UniApp官方UTS插件——优雅草上架插件市场-卓伊凡
  • cnn训练并用grad-cam可视化
  • DAY 40 超大力王爱学Python
  • Linux运维笔记:服务器安全加固
  • 注册网站查询/网络营销课程培训机构
  • 网站建设方案模板下载/公司网站设计的内容有哪些
  • 做外汇关注的网站/小辉seo
  • 微信小程序网站建设小图标素材/青岛百度推广seo价格
  • 武昌做网站哪家专业/厦门seo新站策划
  • 网站建立的研究方案/福州seo博客