全面解析C语言多媒体开源框架GStreamer
Posted dvlinker
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了全面解析C语言多媒体开源框架GStreamer相关的知识,希望对你有一定的参考价值。
目录
之前参与的万能视频播放器项目采用了多媒体GStreamer开源框架,在最上层业务层通过连接各个插件形成一个pipeline来完成相应的业务需求。但之前没接触过GStreamer框架,所以从项目的前期预研开始,就从GStreamer最基本的概念开始熟悉,逐步解决项目中遇到的多个问题。本文根据此次项目实践,对GStreamer多媒体框架做一个相对全面的总结。
1、GStreamer简介
GStreamer是GNOME桌面环境下用来创建流媒体应用的多媒体框架,其基本设计思想来自于俄勒冈(Oregon)研究生学院有关视频管道的创意,同时也借鉴了DirectShow的设计思想。
GStreamer是用c语言实现的,使用了面向对象的思维。GStreamer 框架是基于插件和管道的,所有的插件都能够被链接到任意的已经定义的数据流管道中,数据通过管道机制进行统一交换。GStreamer的很多优点来源于其框架的模块化,使得新的插件能够无缝合并。
GStreamer能够处理任意类型的数据流,其目标是要简化音/视频应用程序的开发,其最显著的用途是在构建音视频播放器、编辑音视频文件、音视频格式转换和流媒体服务上,GStreamer已经能够被用来处理像 MP3、Ogg、MPEG1、MPEG2、AVI、Quicktime 等多种格式的多媒体数据。
GStreamer核心库函数是一个处理插件、数据流和媒体操作的框架。另外,其还提供了一套API,用于程序员使用其它插件来编写他所需要的应用程序时使用。但是,由于追求模块化和高效率,使得GStreamer在整个框架上变的复杂,也同时因为复杂度的提高,使得开发一个新的应用程序显得不是那么的简单。
2、GStreamer基本概念
2.1、元件(Element)
元件是GStreamer的核心,是具有一定功能的基本单元,可将其描述为一个具有特定属性的黑盒子。其在代码里面的类型是GstElement,可以理解为Gstreamer里面的基类。Gstreamer默认安装了很多有用的元件,按照功能上的差异,element分为以下几类:
(1)source element 数据源元件,只有输出端,用来产生供管道消费的数据,例如,音频捕捉单元,它从声卡读取原始音频数据,供其它模块用;
(2)filter(/filter-like) element 中间元件,包括过滤器、转换器、复用器、解复用器、编解码器等,其既有输入端又有输出端,从输入端获得相应数据,经过处理之后传递给输出端,有的element可能有一个source pad多个sink pads(demux),有的可能有多个source pads一个sink pad(mux),有的有一个source pad一个sink pad,例如,音频编码单元,它从外界获得音频数据之后,根据压缩算法编码后,给其它模块使用;
(3)sink elements 接收器元件,只有输入端,仅有消费数据的能力,是整条媒体管道的终端,例如,音频回放单元,负责将接收到的数据写到声卡上;
2.2、箱柜(Bin)
由多个基本单元组成的一个高级的功能单元,是装载元件的容器,可以通过改变一个Bin的状态来改变其内部所有元件的状态,Bin可以发送总线消息(bus message)给其子集元件。
Bin和pipeline的区别就是pipeline肯定是bin,但bin不一定是pipeline,bin就像一个盒子,里面放了什么东西,功能具体是怎么实现的,用户可以不关心,bin是元件的集合,而pipeline更强调应用的可执行性。
2.3、管道(Pipeline)
最高等级的Bin,是一种允许对所包含的元件进行安排(scheduling)的普通容器。顶层(toplevel)箱柜必须为一个管道,因此每个GStreamer应用程序都至少需要一个管道。当应用程序启动后,管道会自动运行在后台线程中,下面是一个典型的pipeline示例:
2.4、衬垫(Pad)
不同Elements之间的链接点,数据流在元件之间流动就是依靠Pads。Pads有处理特殊数据的能力,也就是其支持特定媒体类型的能力,一个Pads能够限制数据流类型的通过,链接成功的条件是,两个Pads允许通过的数据类型一致时才能建立(数据类型协商)。
Pads按照数据导向,可分为source pads(element的输出),sink pads(element 的输入),按照时效性可分为,永久型(always)、随机型(sometimes)、请求型(on request),三种时效性的意义顾名思义: 永久型的衬垫一直会存在,随机型的衬垫只在某种特定的条件下才存在(会随机消失的衬垫也属于随机型),请求型的衬垫只在应用程序明确发出请求时才出现。
Pads通过GstCaps对象进行描述,一个GstCaps对象包括一个或者多个GstStructure对象,一个GstStructure描述一种媒体类型,其结构中只包含功能集中规定的固定值。
2.5、能力集(Caps)
Pad的属性描述,例如:
SRC template: 'src'
Availability: Always
Capabilities:
audio/x-raw-float
rate: [ 8000, 50000 ]
channels: [ 1, 2 ]
endianness: 1234
width: 32
buffer-frames: 0
SINK template: 'sink'
Availability: Always
Capabilities:
audio/x-vorbis
2.6、幽灵pad(ghost pad)
bin本身没有pad,所以就没有办法把两个bin链接起来。但可以用bin中的一个元件的pad构造一个代理pad,这样bin就有一个代理pad了。这个pad实际指向被代理的那个单元的pad,示例如下:
2.7、Bus
Bus采用自己的线程机制,负责pipeline线程和应用程序程序之间的通信。每个pipeline缺省创建一个Bus,应用程序在总线上设置一个类似于对象的信号处理的消息处理器,当主循环运行的时候,总线将会轮询这个消息处理器是否有新的消息,当消息被采集到后,总线将呼叫相应的回调函数来完成相关操作。
应用程序有两种方法使用Bus,第一种是使用 GLib/Gtk+ main loop及gst_bus_add_watch () or gst_bus_add_signal_watch()事件回调函数机制,第二种是程序通过gst_bus_peek () /gst_bus_poll ()主动检查Bus中的消息;
2.8、缓冲区(Buffer)
管道的数据流由一组缓冲区和事件组成,缓冲区包括实际的管道数据,事件包括控制信息,如寻找信息和流的终止信号。所有这些数据流在运行的时候自动的流过管道。
缓冲区包含了你创建的管道里的数据流,通常一个源元件会创建一个新的缓冲区,同时元件还将会把缓冲区的数据传递给下一个元件。一个缓冲区主要由以下一个组成:
(1)指向某块内存的指针;
(2)内存的大小;
(3)缓冲区的时间戳;
(4)一个引用计数,指出了缓冲区所使用的元件数。没有元件可引用的时候,这个引用将用于销毁缓冲区。
buffer的创建有2种方式,一种是由当前的element自己创建,然后把这个buffer传递给下一个element;另外一种方式就是dwonstream-allocated buffers,就是由下一个element来创建要求大小的buffer,并提供buffer操作函数,当前element通过调用buffer操作函数将数据写入这个buffer中完成buffer数据传递。其区别在于buffer的创建是在数据传输的源端element创建还是在数据接收端element来创建。
2.9、插件(Plugin)
元件必须封装在插件中才能被使用,一个插件是一块可以加载的代码,通常被称为共享对象文件(shared object file)或动态链接库(dynamically linked library),一个插件中可以包含一个或若干element。
3、GStreamer基本架构
GStreamer core、Plugins以及依赖的第三方开源库的架构关系,如下图所示,
Gstreamer的组成结构如下图所示:
4、GStreamer通信机制
Gstreamer的通信机制示意图及解释如下:
4.1、Message
pipeline用来主动向外报告自己的运行状态。这些Message被发送到一个消息队列,也就是pipeline的Bus,应用程序可以从Bus中获取Message,并作出自定义的反应。Message是GST提供的,属于异步操作;
4.2、Event
pipeline中插件之间进行通信的机制,分为下行事件,上行事件和双向事件。也可以由应用程序直接向某一个插件发送事件,但起作用的前提是:该插件定义了该事件的响应操作。 通过事件可以控制整个pipeline的运行状态。
下行事件是由source插件向sink插件方向传输,例如,
GST_EVENT_EOS (流的终止信号)
GST_EVENT_NEWSEGMENT
上行事件是由sink插件向source插件方向传输,用于改变管道中数据流的状态,例如:
GST_EVENT_QOS
GST_EVENT_SEEK(查找)
双向事件,例如:
GST_EVENT_FLUSH_START
GST_EVENT_FLUSH_STOP
4.3、Signal
应用程序控制某一插件的运行状态,signal可以看做Glib对象的一个属性,是由Gobject系统提供的,属于同步操作,与linux中的系统信号有差别。通过信号可以让某个插件做一些对插件本身变量的操作,比如增加或者删除一些维护信息等等。
4.4、Probe
应用程序可以通过探针Probe来探测某个插件的pad中流过的数据,比如:在audioconert 插件的src pad 加一个探针,每当有buf到达时,就调用callback_have_data(),这里这个函数只是打印一下buf的大小,统计一下buf流过的个数;
//main
GstPad *m_pad_concert_src = gst_element_get_static_pad(m_gst_convert, "src");
gst_pad_add_buffer_probe(m_pad_concert_src, G_CALLBACK(callback_have_data), NULL);
gst_object_unref(m_pad_concert_src);
/*******Callback handler when probe date received***********/
static gboolean callback_have_data(GstPad *padsrc, GstBuffer *buffer, gpointer data)
gint iBufSize = 0;
gchar* pBuffer = NULL;
iBufSize = GST_BUFFER_SIZE(buffer);
pBuffer = (gchar*)GST_BUFFER_DATA(buffer);
static gint numBuf = 0;
g_print("\\rBUF %d Size=%d ", numBuf++, iBufSize);
return TRUE;
4.5、Quary
应用程序可以查询pipline当前的运行状态,比如:以下代码用来查询当前播放的位置,和总的播放时间。
GstFormat m_format = GST_FORMAT_TIME;
gint64 m_position , m_length;
if( gst_element_query_position(pipeline, &m_format,&m_position) &&
gst_element_query_duration(pipeline, &m_format, &m_length))
g_print("Current: %"GST_TIME_FORMAT" Total: %" GST_TIME_FORMAT "\\r",
GST_TIME_ARGS(m_position),GST_TIME_ARGS(m_length));
5、GStreamer元件状态
一个元件在被创建后,它不会执行任何操作,通过改变元件的状态,才能使它做某些事情。元件有四种状态,每种状态都有其特定的意义,具体如下:
GST_STATE_NULL 默认状态:没有分配任何资源,没有载入插件,不能处理数据;
GST_STATE_READY 预备状态:分配或载入所有与流无关的资源(非硬件资源),所有数据流的位置信息应该自动置0,如果数据流先前被打开过,它应该被关闭,并且其位置信息、特性信息应该被重新置为初始状态;
GST_STATE_PAUSED 暂停状态:准备好全部资源,接受数据流,只是sink element暂停,收到数据不处理,只是block;
GST_STATE_PLAYING 播放状态:准备好全部资源,接受并处理数据流;其实这个状态除了当前运行时钟外,其它与PAUSED状态一样,可以通过gst_element_set_state()来改变一个元件的状态,当元件处于GST_STATE_PLAYING状态,管道会开始自动处理数据。
6、GStreamer中的几个关键概念
6.1、识别流的MIME类型
元件通过caps来描述其能处理的媒体格式,元件之间交互数据流通过caps协商,caps是一个mime类型或者一些特性集的组合。
一个加载进系统的元件必须提供其源衬垫和接收衬垫支持的mime类型。通过Gstreamer注册中心可以知道目前注册的不同的元件,以及他们所期望得到的与他们能够产生的媒体类型,下图显示了管道中每个Pads所处理的MIME类型。
6.2、媒体流类型检测(typefind)
通常当加载一个新的媒体流时,媒体的类型并不明了。这意味着选择一条管道来对媒体流进行解码之前,首先需要检测媒体流的类型。 GStreamer 使用了类型检测 (typefinding) 来达到此目的。类型检测是构建管道所必经的步骤。
首先它会一直读取数据流,在此期间,它会把数据提供给所有的实现了类型检测器 (typefinder) 的插件,当其中任何一个类型检测器识别出数据流,这个类型检测器元件将会发送一个信号,并开始像一个关卡 (passthrough)模块一样工作。如果数据流的类型没有被任何类型检测器识别出来,管道会发送一个错误信息,并终止所有正在处理该数据流的动作。一旦类型检测元件找到一个类型,应用程序将会使用该元件作为管道的一部分来解码媒体流。
6.3、数据探测
探测是衬垫监听器的形象比喻,从技术上,探针仅仅是一个可以依附于衬垫的回调信号。这些信号默认是没有被发射(fired)的(不然的话会降低性能),但是可以通过附加探针调用gst_pad_add_data_probe() 或类似的函数被激活,这些函数附加了信号处理器,并激活实际信号的发射。
同样地,你可以用 gst_pad_remove_data_probe () 或相关函数来删除信号处理器,也可以只是监听时间或缓冲区。 探针在管道的线程context运行,所以回调不应该阻塞,而且通常不能有异常的阻塞,否则会降低管道的性能,如果出现这样的缺陷,会导致死锁甚至崩溃。
6.4、插件加载流程
如下图所示,基于插件的程序,其工作原理本质上都是通过读取动态库实现的,只需要每个动态库中实现某一个特定的接口就可以了,比如XX_init等,这里就是plugin_init。里面会有个像注册表一样的数据结构会存储所有的插件的信息。
7、GStreamer开发示例-MP3文件播放器
利用GStreamer框架提供的组件,来实现一个简单的MP3播放器。数据源元件负责从磁盘上读取数据,过滤器元件负责对数据进行解码,而接受器元件则负责将解码后的数据写入声卡,示例代码和注释如下:
#include <gst/gst.h>
#include <glib.h>
//定义消息处理函数,
static gboolean bus_call(GstBus *bus,GstMessage *msg,gpointer data)
GMainLoop *loop = (GMainLoop *) data;//主循环的指针,接受EOS消息时退出
switch (GST_MESSAGE_TYPE(msg))
case GST_MESSAGE_EOS:
g_print("End of stream\\n");
g_main_loop_quit(loop);
break;
case GST_MESSAGE_ERROR:
gchar *debug;
GError *error;
gst_message_parse_error(msg,&error,&debug);
g_free(debug);
g_printerr("ERROR:%s\\n",error->message);
g_error_free(error);
g_main_loop_quit(loop);
break;
default:
break;
return TRUE;
int main(int argc,char *argv[])
GMainLoop *loop;
GstElement *pipeline,*source,*decoder,*sink;//定义组件
GstBus *bus;
gst_init(&argc,&argv); //初始化gstreamer
loop = g_main_loop_new(NULL,FALSE);//创建主循环,在执行 g_main_loop_run后正式开始循环
if(argc != 2)
g_printerr("Usage:%s <mp3 filename>\\n",argv[0]);
return -1;
//创建管道和元件
pipeline = gst_pipeline_new("audio-player"); //管道用来容纳元件
source = gst_element_factory_make("filesrc","file-source");//数据源元件
decoder = gst_element_factory_make("mad","mad-decoder");//过滤器元件
sink = gst_element_factory_make("autoaudiosink","audio-output");//接收器元件
if(!pipeline||!source||!decoder||!sink)
g_printerr("One element could not be created.Exiting.\\n");
return -1;
//设置 source的location 参数,即文件地址.
g_object_set(G_OBJECT(source),"location",argv[1],NULL);
//得到管道的消息总线
bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline));
//添加消息监视器
gst_bus_add_watch(bus,bus_call,loop);
gst_object_unref(bus);
//把元件添加到管道中。管道是一个特殊的组件,可以更好的让数据流动
gst_bin_add_many(GST_BIN(pipeline),source,decoder,sink,NULL);
//通过衬垫依次连接元件
gst_element_link_many(source,decoder,sink,NULL);
//启动管道,开始播放
gst_element_set_state(pipeline,GST_STATE_PLAYING);
g_print("Running\\n");
//开始循环
g_main_loop_run(loop);
g_print("Returned,stopping playback\\n");
//终止管道,释放资源
gst_element_set_state(pipeline,GST_STATE_NULL);
gst_object_unref(GST_OBJECT(pipeline));
return 0;
编译运行
gcc -Wall $(pkg-config --cflags --libs gstreamer-0.10) -g test2.c -o test2
./test2 /home/phinecos/test.mp3
8、最后
本文总结了多媒体框架GStreamer一些基本概念及流程,希望能给使用GStreamer开源库的朋友提供一个借鉴或参考。
以上是关于全面解析C语言多媒体开源框架GStreamer的主要内容,如果未能解决你的问题,请参考以下文章