用 GStreamer 简化 Linux 多媒体开发

来源:互联网 发布:cheat engine源码 编辑:程序博客网 时间:2024/05/21 10:10

    目录(?)[+]

    1. 原文httpwwwibmcomdeveloperworkscnlinuxl-gstreamer
    2. 一基本概念
      1. 11 元件处理
        1. 图1
        2. 图2
      2. 12 衬垫处理
      3. 13 箱柜
        1. 图3
        2. 图4
      4. 二元件连接
      5. 三元件状态
      6. 四实现MP3播放器
      7. 五小结
      8. 参考资料

    原文:http://www.ibm.com/developerworks/cn/linux/l-gstreamer/

    一、基本概念

    GStreamer 作为 GNOME 桌面环境推荐的流媒体应用框架,采用了基于插件(plugin)和管道(pipeline)的体系结构,框架中的所有的功能模块都被实现成可以插拔的组件(component), 并且在需要的时候能够很方便地安装到任意一个管道上,由于所有插件都通过管道机制进行统一的数据交换,因此很容易利用已有的各种插件“组装”出一个功能完善的多媒体应用程序。

    1.1 元件处理

    对于需要应用 GStreamer 框架的程序员来讲,GstElement 是一个必须理解的概念,因为它是组成管道的基本构件,也是框架中所有可用组件的基础,这也难怪 GStreamer 框架中的大部分函数都会涉及到对 GstElement 对象的操作。从 GStreamer 自身的观点来看,GstElement 可以描述为一个具有特定属性的黑盒子,它通过连接点(link point)与外界进行交互,向框架中的其余部分表征自己的特性或者功能。

    按照各自功能上的差异,GStreamer 又将 GstElement 细分成如下几类:

    • Source Element 数据源元件 只有输出端,它仅能用来产生供管道消费的数据,而不能对数据做任何处理。一个典型的数据源元件的例子是音频捕获单元,它负责从声卡读取原始的音频数据,然后作为数据源提供给其它模块使用。
    • Filter Element 过滤器元件 既有输入端又有输出端,它从输入端获得相应的数据,并在经过特殊处理之后传递给输出端。一个典型的过滤器元件的例子是音频编码单元,它首先从外界获得音频数据,然后根据特定的压缩算法对其进行编码,最后再将编码后的结果提供给其它模块使用。
    • Sink Element 接收器元件 只有输入端,它仅具有消费数据的能力,是整条媒体管道的终端。一个典型的接收器元件的例子是音频回放单元,它负责将接收到的数据写到声卡上,通常这也是音频处理过程中的最后一个环节。

    图1将有助于你更好地理解数据源元件、过滤器元件和接收器元件三者的区别,同时也不难看出它们是如何相互配合形成管道的:

    图1
    图1

    需要注意的是,过滤器元件的具体形式是非常灵活的,GStreamer并没有严格规定输入端和输出端的数目,事实上它们都可以是一个或者多个。图2是一个AVI分离器的基本结构,它能够将输入数据分离成单独的音频信息和视频信息,用于实现该功能的过滤器元件很明显只具有一个输入端,但却需要有两个输出端。

    图2
    图2

    要想在应用程序中创建GstElement对象,唯一的办法是借助于工厂对象GstElementFactory。由于GStreamer框架提供了多种类型的GstElement对象,因此对应地提供了多种类型的GstElementFactory对象,它们是通过特定的工厂名称来进行区分的。例如,下面的代码通过gst_element_factory_find()函数获得了一个名为mad的工厂对象,它之后可以用来创建与之对应的MP3解码器元件:

    GstElementFactory *factory;factory = gst_element_factory_find ("mad");

    成功获得工厂对象之后,接下来就可以通过gst_element_factory_create()函数来创建特定的GstElement对象了,该函数在调用时有两个参数,分别是需要用到的工厂对象,以及即将创建的元件名称。元件名称可以用查询的办法获得,也可以通过传入空指针(NULL)来生成工厂对象的默认元件。下面的代码示范了如何利用已经获得的工厂对象,来创建名为decoder的MP3解码器元件:

    GstElement *element;element = gst_element_factory_create (factory, "decoder");

    当创建的GstElement不再使用的时候,还必须调用gst_element_unref()函数释放其占用的内存资源:

    gst_element_unref (element);

    GStreamer使用了与GObject相同的机制来对属性(property)进行管理,包括查询(query)、设置(set)和读取(get)等。所有的GstElement对象都需要从其父对象GstObject那里继承名称(name)这一最基本的属性,这是因为像gst_element_factory_make()和gst_element_factory_create()这样的函数在创建工厂对象和元件对象时都会用到名称属性,通过调用gst_object_set_name()和gst_object_get_name()函数可以设置和读取GstElement对象的名称属性。

    1.2 衬垫处理

    衬垫(pad)是GStreamer框架引入的另外一个基本概念,它指的是元件(element)与外界的连接通道,对于框架中的某个特定元件来说,其能够处理的媒体类型正是通过衬垫暴露给其它元件的。成功创建GstElement对象之后,可以通过gst_element_get_pad()获得该元件的指定衬垫。例如,下面的代码将返回element元件中名为src的衬垫:

    GstPad *srcpad;srcpad = gst_element_get_pad (element, "src");

    如果需要的话也可以通过gst_element_get_pad_list()函数,来查询指定元件中的所有衬垫。例如,下面的代码将输出element元件中所有衬垫的名称:

    GList *pads;pads = gst_element_get_pad_list (element);while (pads) {  GstPad *pad = GST_PAD (pads->data);  g_print ("pad name is: %s\n", gst_pad_get_name (pad));  pads = g_list_next (pads);}

    与元件一样,衬垫的名称也能够动态设置或者读取,这是通过调用gst_pad_get_name ()和gst_pad_set_name()函数来完成的。所有元件的衬垫都可以细分成输入衬垫和输出衬垫两种,其中输入衬垫只能接收数据但不能产生数据,而输出衬垫则正好相反,只能产生数据但不能接收数据,利用函数gst_pad_get_direction()可以获得指定衬垫的类型。GStreamer框架中的所有衬垫都必然依附于某个元件之上,调用gst_pad_get_parent()可以获得指定衬垫所属的元件,该函数的返回值是一个指向GstElement的指针。 衬垫从某种程度上可以看成是元件的代言人,因为它要负责向外界描述该元件所具有的能力。GStreamer框架提供了统一的机制来让衬垫描述元件所具有的能力(capability),这是借助数据结构_GstCaps来实现的:

    struct _GstCaps {  gchar *name; /* the name of this caps */  guint16 id; /* type id (major type) */  guint refcount; /* caps are refcounted */  GstProps *properties; /* properties for this capability */  GstCaps *next; /* caps can be chained together */};

    以下是对mad元件的能力描述,不难看出该元件中实际包含sink和src两个衬垫,并且每个衬垫都带有特定的功能信息。名为sink的衬垫是mad元件的输入端,它能够接受MIME类型为audio/mp3的媒体数据,此外还具有layer、bitrate和framed三种属性。名为src的衬垫是mad元件的输出端,它负责产生MIME类型为audio/raw媒体数据,此外还具有format、depth、rate和channels等多种属性。

    Pads:  SINK template: ’sink’    Availability: Always    Capabilities:    ’mad_sink’:      MIME type: ’audio/mp3’:  SRC template: ’src’    Availability: Always    Capabilities:      ’mad_src’:        MIME type: ’audio/raw’:        format: String: int        endianness: Integer: 1234        width: Integer: 16        depth: Integer: 16        channels: Integer range: 1 - 2        law: Integer: 0        signed: Boolean: TRUE        rate: Integer range: 11025 - 48000

    准确地说,GStreamer框架中的每个衬垫都可能对应于多个能力描述,它们能够通过函数gst_pad_get_caps()来获得。例如,下面的代码将输出pad衬垫中所有能力描述的名称及其MIME类型:

    GstCaps *caps;caps = gst_pad_get_caps (pad);g_print ("pad name is: %s\n", gst_pad_get_name (pad));while (caps) {  g_print (" Capability name is %s, MIME type is %s\n",  gst_caps_get_name (cap),  gst_caps_get_mime (cap));  caps = caps->next;}

    1.3 箱柜

    箱柜(bin)是GStreamer框架中的容器元件,它通常被用来容纳其它的元件对象,但由于其自身也是一个GstElement对象,因此实际上也能够被用来容纳其它的箱柜对象。利用箱柜可以将需要处理的多个元件组合成一个逻辑元件,由于不再需要对箱柜中的元件逐个进行操作,因此能够很容易地利用它来构造更加复杂的管道。在GStreamer框架中使用箱柜还有另外一个优点,那就是它会试着对数据流进行优化,这对于多媒体应用来讲是很具吸引力的。

    图3描述了箱柜在GStreamer框架中的典型结构:

    图3
    图3

    在GStreamer应用程序中使用的箱柜主要有两种类型:

    • GstPipeline 管道是最常用到的容器,对于一个GStreamer应用程序来讲,其顶层箱柜必须是一条管道。
    • GstThread 线程的作用在于能够提供同步处理能力,如果GStreamer应用程序需要进行严格的音视频同步,一般都需要用到这种类型的箱柜。

    GStreamer框架提供了两种方法来创建箱柜:一种是借助工厂方法,另一种则是使用特定的函数。下面的代码示范了如何使用工厂方法创建线程对象,以及如何使用特定函数来创建管道对象:

    GstElement *thread, *pipeline;// 创建线程对象,同时为其指定唯一的名称。thread = gst_element_factory_make ("thread", NULL);// 根据给出的名称,创建一个特定的管道对象。pipeline = gst_pipeline_new ("pipeline_name");

    箱柜成功创建之后,就可以调用gst_bin_add()函数将已经存在的元件添加到其中来了:

    GstElement *element;GstElement *bin;bin = gst_bin_new ("bin_name");element = gst_element_factory_make ("mpg123", "decoder");gst_bin_add (GST_BIN (bin), element);

    而要从箱柜中找到特定的元件也很容易,可以借助gst_bin_get_by_name()函数实现:

    GstElement *element;element = gst_bin_get_by_name (GST_BIN (bin), "decoder");

    由于GStreamer框架中的一个箱柜能够添加到另一个箱柜之中,因此有可能会出现箱柜嵌套的情况,gst_bin_get_by_name()函数在查找元件时会对嵌套的箱柜作递归查找。元件有添加到箱柜之中以后,在需要的时候还可以从中移出,这是通过调用gst_bin_remove()函数来完成的:

    GstElement *element;gst_bin_remove (GST_BIN (bin), element);

    如果仔细研究一下图3中描述的箱柜,会发现它没有属于自己的输入衬垫和输出衬垫,因此显然是无法作为一个逻辑整体与其它元件交互的。为了解决这一问题,GStreamer引入了精灵衬垫(ghost pad)的概念,它是从箱柜里面所有元件的衬垫中推举出来的,通常来讲会同时选出输入衬垫和输出衬垫,如图4所示:

    图4
    图4

    具有精灵衬垫的箱柜在行为上与元件是完全相同的,所有元件具有的属性它都具有,所有针对元件能够进行的操作也同样能够针对箱柜进行,因此在GStreamer应用程序中能够像使用元件一样使用这类箱柜。下面的代码示范了如何为箱柜添加一个精灵衬垫:

    GstElement *bin;GstElement *element;element = gst_element_factory_create ("mad", "decoder");bin = gst_bin_new ("bin_name");gst_bin_add (GST_BIN (bin), element);gst_element_add_ghost_pad (bin, gst_element_get_pad (element, "sink"), "sink");

    二、元件连接

    在引入了元件和衬垫的概念之后,GStreamer对多媒体数据的处理过程就变得非常清晰了:通过将不同元件的衬垫依次连接起来构成一条媒体处理管道,使数据在流经管道的过程能够被各个元件正常处理,最终实现特定的多媒体功能。

    图1就描述了一条很简单的管道,它由三个基本元件构成:数据源元件只负责产生数据,它的输出衬垫与过滤器元件的输入衬垫相连;过滤器元件负责从自己的输入衬垫中获取数据,并在经过特定的处理之后,将结果通过输出衬垫传给与之相连的接收器元件;接收器元件只负责接收数据,它的输入衬垫与过滤器元件的输出衬垫相连,负责对最终结果进行相应的处理。

    GStreamer框架中的元件是通过各自的衬垫连接起来的,下面的代码示范了如何将两个元件通过衬垫连接起来,以及如何在需要的时候断开它们之间的连接:

    GstPad *srcpad, *sinkpad;srcpad = gst_element_get_pad (element1, "src");sinpad = gst_element_get_pad (element2, "sink");// 连接gst_pad_link (srcpad, sinkpad);// 断开gst_pad_unlink (srcpad, sinkpad);

    如果需要建立起连接的元件都只有一个输入衬垫和一个输出衬垫,那么更简单的做法是调用gst_element_link()函数直接在它们之间建立起连接,或者调用gst_element_unlink()函数断开它们之间的连接:

    // 连接gst_element_link (element1, element2);// 断开gst_element_unlink (element1, element2);

    三、元件状态

    当GStreamer框架中的元件通过管道连接好之后,它们就开始了各自的处理流程,期间一般会经历多次状态切换,其中每个元件在特定时刻将处于如下四种状态之一:

    • NULL 这是所有元件的默认状态,表明它刚刚创建,还没有开始做任何事情。
    • READY 表明元件已经做好准备,随时可以开始处理流程。
    • PAUSED 表明元件因某种原因暂时停止处理数据。
    • PLAYING 表明元件正在进行数据处理。

    所有的元件都从NULL状态开始,依次经历NULL、READY、PAUSED、PLAYING等状态间的转换。元件当前所处的状态可以通过调用gst_element_set_state()函数进行切换:

    GstElement *bin;/* 创建元件,并将其连接成箱柜bin */gst_element_set_state (bin, GST_STATE_PLAYING);

    默认情况下,管道及其包含的所有元件在创建之后将处于NULL状态,此时它们不会进行任何操作。当管道使用完毕之后,不要忘记重新将管道的状态切换回NULL状态,让其中包含的所有元件能够有机会释放它们正在占用的资源。

    管道真正的处理流程是从第一次将其切换到READY状态时开始的,此时管道及其包含的所有元件将做好相应的初始化工作,来为即将执行的数据处理过程做好准备。对于一个典型的元件来讲,处于READY状态时需要执行的操作包括打开媒体文件和音频设备等,或者试图与位于远端的媒体服务器建立起连接。

    处于READY状态的管道一旦切换到PLAYING状态,需要处理的多媒体数据就开始在整个管道中流动,并依次被管道中包含的各个元件进行处理,从而最终实现管道预先定义好的某种多媒体功能。GStreamer框架也允许将管道直接从NULL状态切换到PLAYING状态,而不必经过中间的READY状态。

    正处于播放状态的管道能够随时切换到PAUSED状态,暂时停止管道中所有数据的流动,并能够在需要的时候再次切换回PLAYING状态。如果需要插入或者更改管道中的某个元件,必须先将其切换到PAUSED或者NULL状态,元件在处于PAUSED状态时并不会释放其占用的资源。

    四、实现MP3播放器

    在理解了一些基本概念和处理流程之后,下面来看看如何利用GStreamer框架提供的组件,来实现一个简单的MP3播放器。在图1中描述的结构能够很容易地映射成MP3播放器,其中数据源元件负责从磁盘上读取数据,过滤器元件负责对数据进行解码,而接受器元件则负责将解码后的数据写入声卡。

    与其它众多GNOME项目一样,GStreamer也是用C语言实现的。如果想要在程序中应用GStreamer提供的各种功能,首先必须在主函数中调用gst_init()来完成相应的初始化工作,以便将用户从命令行输入的参数传递给GStreamer函数库。一个典型的GStreamer应用程序的初始化如下所示:

    #include <gst/gst.h>int main (int argc, char *argv[]){  gst_init (&argc, &argv);  /* ... */}

    接下去需要创建三个元件并连接成管道,由于所有GStreamer元件都具有相同的基类GstElement,因此能够采用如下方式进行定义:

      GstElement *pipeline, *filesrc, *decoder, *audiosink;

    管道在GStreamer框架中是用来容纳和管理元件的,下面的代码将创建一条名为pipeline的新管道:

      /* 创建用来容纳元件的新管道 */  pipeline = gst_pipeline_new ("pipeline");

    数据源元件负责从磁盘文件中读取数据,它具有名为location的属性,用来指明文件在磁盘上的位置。使用标准的GObject属性机制可以为元件设置相应的属性:

    /* 创建数据源元件 */filesrc = gst_element_factory_make ("filesrc", "disk_source");g_object_set (G_OBJECT (filesrc), "location", argv[1], NULL);

    过滤器元件负责完成对MP3格式的数据进行解码,最简单的办法是安装mad这一插件,借助它来完成相应的解码工作:

    /* 创建过滤器元件 */decoder = gst_element_factory_make ("mad", "decoder");

    接收器元件负责将解码后的数据利用声卡播放出来:

    /* 创建接收器元件 */audiosink = gst_element_factory_make ("audiosink", "play_audio");

    已经创建好的三个元件需要全部添加到管道中,并按顺序连接起来:

    /* 添加元件到管道中 */gst_bin_add_many (GST_BIN (pipeline), filesrc, decoder, audiosink, NULL);/* 通过衬垫连接元件 */gst_element_link_many (filesrc, decoder, audiosink, NULL);

    所有准备工作都做好之后,就可以通过将管道的状态切换到PLAYING状态,来启动整个管道的数据处理流程:

    /* 启动管道 */gst_element_set_state (pipeline, GST_STATE_PLAYING);

    由于没有用到线程,因此必须通过不断调用gst_bin_iterate()函数的办法,来判断管道的处理过程会在何时结束:

    while (gst_bin_iterate (GST_BIN (pipeline)));

    只要管道内还会继续有新的事件产生,gst_bin_iterate()函数就会一直返回TRUE,只有当整个处理过程都结束的时候,该函数才会返回FALSE,此时就该终止管道并释放占用的资源了:

    /* 终止管道 */gst_element_set_state (pipeline, GST_STATE_NULL);/* 释放资源 */gst_object_unref (GST_OBJECT (pipeline));

    用GStreamer实现的MP3播放器的源代码如下所示:

    #include <gst/gst.h>int main (int argc, char *argv[]){    GstElement *pipeline, *filesrc, *decoder, *audiosink;    gst_init(&argc, &argv);    if (argc != 2) {        g_print ("usage: %s <mp3 filename>\n", argv[0]);        exit (-1);    }    /* 创建一条新的管道 */    pipeline = gst_pipeline_new ("pipeline");    /* 生成用于读取硬盘数据的元件 */    filesrc = gst_element_factory_make ("filesrc", "disk_source");    g_object_set (G_OBJECT (filesrc), "location", argv[1], NULL);    /* 创建解码器元件 */    decoder = gst_element_factory_make ("mad", "decoder");    /* 创建音频回放元件 */    audiosink = gst_element_factory_make ("osssink", "play_audio");    /* 将生成的元件添加到管道中 */    gst_bin_add_many (GST_BIN (pipeline), filesrc, decoder, audiosink, NULL);    /* 连接各个元件 */    gst_element_link_many (filesrc, decoder, audiosink, NULL);    /* 开始播放 */    gst_element_set_state (pipeline, GST_STATE_PLAYING);    while (gst_bin_iterate (GST_BIN (pipeline)));    /* 停止管道处理流程 */    gst_element_set_state (pipeline, GST_STATE_NULL);    /* 释放占用的资源 */    gst_object_unref (GST_OBJECT (pipeline));    exit (0);    }

    五、小结

    随着 GNOME 桌面环境的不断普及,GStreamer 作为一个强大的多媒体应用开发框架,已经开始受到越来越多人的关注。Gstreamer在设计时采用了非常灵活的体系结构,并且提供了许多预定义的媒体处理模块,因此能够极大简化在Linux下开发多媒体应用的难度。

    参考资料

    • 在 GStreamer 的官方网站 http://gstreamer.freedesktop.org上可以找到有关GStreamer的许多资料,包括详细的API手册和插件开发指南等。
    您有83
    0 0