• 编写一个GStreamer应用


    上一节简单介绍了一下GStreamer,并浏览一个官方,本篇主要介绍一下怎么写一个GStreamer应用,在编码之前我们先一起详细了解编写应用的一些基本组件和概念。

    初始化GStreamer

            当我们基于GStreamer开发一个应用程序时,使用gst/gst.h即可访问库文件中的函数,除此之外,还需要初始化GStreamer library,我们使用gst_init 初始化,它的主要功能如下

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

    下面是一个简单的demo,

    1. #include <stdio.h>
    2. #include <gst/gst.h>
    3. int
    4. main (int argc,
    5. char *argv[])
    6. {
    7. const gchar *nano_str;
    8. guint major, minor, micro, nano;
    9. gst_init (&argc, &argv);
    10. gst_version (&major, &minor, &micro, &nano);
    11. if (nano == 1)
    12. nano_str = "(CVS)";
    13. else if (nano == 2)
    14. nano_str = "(Prerelease)";
    15. else
    16. nano_str = "";
    17. printf ("This program is linked against GStreamer %d.%d.%d %s\n",
    18. major, minor, micro, nano_str);
    19. return 0;
    20. }

    你可以使用GST_VERSION_MAJOR,GST_VERSION_MINOR以及GST_VERSION_MICRO三个宏得到你的GStreamer版本信息,或者使用函数gst_version得到当前你所调用的链接库的版本信息。目前GStreamer使用了一种保证主要版本和次要版本中 API-/以及ABI兼容的策略。

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

    在初始化的时候也可以使用GOption来指定参数 demo如下:

    1. #include <gst/gst.h>
    2. int
    3. main (int argc,char *argv[])
    4. {
    5. gboolean silent = FALSE;
    6. gchar *savefile = NULL;
    7. GOptionContext *ctx;
    8. GError *err = NULL;
    9. GOptionEntry entries[] = {
    10. { "silent", 's', 0, G_OPTION_ARG_NONE, &silent,
    11. "do not output status information", NULL },
    12. { "output", 'o', 0, G_OPTION_ARG_STRING, &savefile,
    13. "save xml representation of pipeline to FILE and exit", "FILE" },
    14. { NULL }
    15. };
    16. ctx = g_option_context_new ("- Your application");
    17. g_option_context_add_main_entries (ctx, entries, NULL);
    18. g_option_context_add_group (ctx, gst_init_get_option_group ());
    19. if (!g_option_context_parse (ctx, &argc, &argv, &err)) {
    20. g_print ("Failed to initialize: %s\n", err->message);
    21. g_clear_error (&err);
    22. g_option_context_free (ctx);
    23. return 1;
    24. }
    25. g_option_context_free (ctx);
    26. printf ("Run me with --help to see the Application options appended.\n");
    27. return 0;
    28. }

    如例子中的代码所示,你可以通过GOption数组来定义你的命令行选项。将表与由gst_init_get_option_group函数返回的选项组一同传给GLib初始化函数。通过使用GOption表来初始化GSreamer,你的程序还可以解析除标准GStreamer选项以外的命令行选项。

     Elements

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

    elements是什么

    从应用的角度,我们可以将一个elements认为是一个功能块,他实现一个特定的功能,比如:数据读取,音频解码,声音输出等。各个功能块之间可以通过某种特定的数据接口(这种接口称为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如下图:

    创建GstElement 

    创建一个组件的最简单的方法是通过函数gst_element_factory_make ()。这个函数使用一个已存在的工厂对象名和一个新的组件名来创建组件。创建完之后,你可以用新的组件名在箱柜Bin中查询得到这个组件。这个名字同样可以用来调试程序的输出。你可以通过传递NULL来得到一个默认的具有唯一性的名字。

    当你不再需要一个组件时,你需要使用gst_object_unref() 来对它进行解引用。这会将一个组件的引用数减少1。任何一个组件在创建时,其引用记数为1。当其引用记数为0时,该组件会被销毁。

    下面的例子显示了如果通过一个fakesrc工厂对象来创建一个名叫source的组件。程序会检查组件是否创建成功。检查完毕后,程序会销毁组件。

    1. #include <gst/gst.h>
    2. int
    3. main (int argc,
    4. char *argv[])
    5. {
    6. GstElement *element;
    7. /* init GStreamer */
    8. gst_init (&argc, &argv);
    9. /* create element */
    10. element = gst_element_factory_make ("fakesrc", "source");
    11. if (!element) {
    12. g_print ("Failed to create element of type 'fakesrc'\n");
    13. return -1;
    14. }
    15. gst_object_unref (GST_OBJECT (element));
    16. return 0;
    17. }

     gst_element_ factory_make是2个函数的组合。一个GstElement对象由工厂对象创建而来。为了创建一个组件,你需要通过工厂对象名字使用gst_element_factory_find ()找到GstElementFactory对象。
    下面的代码段创建了一个工厂对象,这个工厂对象被用来创建一个fakesrc组件。函数gst_element_factory_create()将会使用组件工厂并根据给定的名字来创建一个组件。

    1. #include <gst/gst.h>
    2. int
    3. main (int argc,
    4. char *argv[])
    5. {
    6. GstElementFactory *factory;
    7. GstElement * element;
    8. /* init GStreamer */
    9. gst_init (&argc, &argv);
    10. /* create element, method #2 */
    11. factory = gst_element_factory_find ("fakesrc");
    12. if (!factory) {
    13. g_print ("Failed to find factory of type 'fakesrc'\n");
    14. return -1;
    15. }
    16. element = gst_element_factory_create (factory, "source");
    17. if (!element) {
    18. g_print ("Failed to create element, even though its factory exists!\n");
    19. return -1;
    20. }
    21. gst_object_unref (GST_OBJECT (element));
    22. gst_object_unref (GST_OBJECT (factory));
    23. return 0;
    24. }

    element 继承GObject 

    GstElement 中的一些属性使用GObject 属性实现的,其实他们属于继承关系,所以GObject 中的query, set and get property values and GParamSpecs在GstElement中也适用。

    每个GstElement 都从其基类GstObject继承了至少一个“名字“属性。这个名字属性将在函数 gst_element_ factory_make()或者函数gst_element_ factory_create ()中使用到。你可通过函数gst_object_set_name设置该属性,通过gst_object_get_name得到一个对象的名字属性。你也可以通过下面的方法来得到一个对象的名字属性。

    1. #include <gst/gst.h>
    2. int
    3. main (int argc,
    4. char *argv[])
    5. {
    6. GstElement *element;
    7. gchar *name;
    8. /* init GStreamer */
    9. gst_init (&argc, &argv);
    10. /* create element */
    11. element = gst_element_factory_make ("fakesrc", "source");
    12. /* get name */
    13. g_object_get (G_OBJECT (element), "name", &name, NULL);
    14. g_print ("The name of the element is '%s'.\n", name);
    15. g_free (name);
    16. gst_object_unref (GST_OBJECT (element));
    17. return 0;
    18. }

    大多数的插件(plugins)都提供了一些额外的方法,这些方法给程序员提供了更多的关于该组件的注册信息或配置信息。gst-inspect是一个用来查询特定组件特性( properties)的实用工具。它也提供了诸如函数简短介绍,参数的类型及其支持的范围等信息。关于gst-inspect更详细的信息请参考gst-inspect

    关于GObject特性更详细的信息,我们推荐你去阅读GObject手册以及Glib 对象系统介绍.

    GstElement对象同样提供了许多的GObject信号方法来实现一个灵活的回调机制。你同样可以使用gst-inspect来检查一个特定组件所支持的信号。总之,信号和特性是组件与应用程序交互的最基本的方式。

    element 工厂

    在前面的部分,我们简要介绍过GstElementFactory可以用来创建一个组件的实例,但是工厂组件不仅仅只能做这件事,工厂组件作为在GStreamer注册系统中的一个基本类型,它可以描述所有的插件(plugins)以及由GStreamer创建的组件。这意味着工厂组件可以应用于一些自动组件实例,像自动插件(autopluggers);或者创建一个可用组件列表,像管道对应用程序的类似操作(像GStreamer Editor)

    通过组件工厂得到组件的信息

    像 gst-inspect这样的工具可以给出一个组件的概要:插件(plugin)的作者、描述性的组件名称(或者简称)、组件的等级( rank)以及组件的类别(category)。类别可以用来得到一个组件的类型,这个类型是在使用工厂组件创建该组件时做创建的。例如类别可以是Codec/Decoder/Video (video decoder), Codec/Encoder/Video (video encoder), Source/Video (a video generator), Sink/Video (a video output),。音频也有类似的类别。同样还存在Codec/Demuxer和Codec/Muxer,甚至更多的类别。Gst-inspect将会列出当前所有的工厂对象,gst-inspect <factory-name>将会列出特定工厂对象的所有概要信息。

    1. #include <gst/gst.h>
    2. int
    3. main (int argc,
    4. char *argv[])
    5. {
    6. GstElementFactory *factory;
    7. /* init GStreamer */
    8. gst_init (&argc, &argv);
    9. /* get factory */
    10. factory = gst_element_factory_find ("fakesrc");
    11. if (!factory) {
    12. g_print ("You don't have the 'fakesrc' element installed!\n");
    13. return -1;
    14. }
    15. /* display information */
    16. g_print ("The '%s' element is a member of the category %s.\n"
    17. "Description: %s\n",
    18. gst_plugin_feature_get_name (GST_PLUGIN_FEATURE (factory)),
    19. gst_element_factory_get_metadata (factory, GST_ELEMENT_METADATA_KLASS),
    20. gst_element_factory_get_metadata (factory, GST_ELEMENT_METADATA_DESCRIPTION));
    21. gst_object_unref (GST_OBJECT (factory));
    22. return 0;
    23. }

    你可以通过gst_registry_pool_ feature_list (GST_TYPE_ELEMENT_FACTORY)得到所有在GStreamer中注册过的工厂组件。

    找出组件所包含的pads

    工厂组件最有用处的功能可能是它包含了对组件所能产生的pads的一个详细描述,以及这些衬垫的功能(以行外话讲:就是指这些衬垫所支持的媒体类型),而得到这些信息是不需要将所有的插件(plugins)都装载到内存中。这可用来给一个编码器提供一个编码列表,或在多媒体播放器自动加载插件时发挥作用。目前所有基于GStreamer的多媒体播放器以及自动加载器(autoplugger)都是以上述方式工作。当我们在下一章:衬垫与功能( Pads and capabilities)中学习到GstPad 与GstCaps 时,会对上面的特性有个更清晰的了解。

    链接elements

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

    通过链接这三个组件,我们创建了一条简单的组件链。组件链中源组件("element1")的输出将会是类过滤组件("element2")的输入。类过滤组件将会对数据进行某些操作,然后将数据输出给最终的接收组件("element3")。

    把上述过程想象成一个简单的Ogg/V orbis音频译码器。源组件从磁盘读取文件。第二个组件就是Ogg/Vorbis音频解码器。最终的接收组件是你的声卡,它用来播放经过解码的音频数据。我们将在该手册的后部分用一个简单的图来构建这个Ogg/Vorbis播放器。

    1. #include <gst/gst.h>
    2. int
    3. main (int argc,
    4. char *argv[])
    5. {
    6. GstElement *pipeline;
    7. GstElement *source, *filter, *sink;
    8. /* init */
    9. gst_init (&argc, &argv);
    10. /* create pipeline */
    11. pipeline = gst_pipeline_new ("my-pipeline");
    12. /* create elements */
    13. source = gst_element_factory_make ("fakesrc", "source");
    14. filter = gst_element_factory_make ("identity", "filter");
    15. sink = gst_element_factory_make ("fakesink", "sink");
    16. /* must add elements to pipeline before linking them */
    17. gst_bin_add_many (GST_BIN (pipeline), source, filter, sink, NULL);
    18. /* link */
    19. if (!gst_element_link_many (source, filter, sink, NULL)) {
    20. g_warning ("Failed to link elements!");
    21. }
    22. [..]
    23. }

     对于一些特定的链接行为,可以通过函数gst_element link ()以及gst_element_link_pads()来实现。你可以使用不同的gst_pad_link_*()函数来得到单个pad的引用并将它们链接起来。更详细的信息请参考API手册。

    注意:在链接不同的组件之前,你需要确保这些组件都被加在同一个Bin或者pipeline 中,因为将一个组件加载到一个Bin中会破坏该组件已存在的一些链接关系。同时,你不能直接链接不在同一箱柜或管道中的组件。如果你想要连接处于不同层次中的组件或衬垫,你可以使用ghost pads。

    Element 状态

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

    • GST_STATE_NULL:默认状态。该状态将会回收所有被该组件占用的资源。
    • GST_STATE_READY:准备状态。组件会得到所有所需的全局资源,这些全局资源将被通过该组件的数据流所使用。例如打开设备、分配缓存等。但在这种状态下,数据流仍未开始被处理,所以数据流的位置信息应该自动置0。如果数据流先前被打开过,它应该被关闭,并且其位置信息、特性信息应该被重新置为初始状态。
    • GST_STATE_PAUSED:在这种状态下,组件已经对流开始了处理,但此刻暂停了处理。因此该状态下组件可以修改流的位置信息,读取或者处理流数据,以及一旦状态变为PLAYING,流可以重放数据流。这种情况下,时钟是禁止运行的。总之,PAUSED 状态除了不能运行时钟外,其它与PLAYING 状态一模一样。一个组件在被创建后,它不会执行任何操作。所以你需要改变组件的状态,使得它能够做某些事情。Gstreamer中,组件有四种状态,每种状态都有其特定的意义。这四种状态为:
       GST_STATE_READY:准备状态。组件会得到所有所需的全局资源,这些全
      局资源将被通过该组件的数据流所使用。例如打开设备、分配缓存等。但在这种状态下,数据流仍未开始被处理,所以数据流的位置信息应该自动置0。如果数据流先前被打开过,它应该被关闭,并且其位置信息、特性信息应该被重新置为初始状态。

      处于 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在管道线程和应用程序现成间交互信息。详情请参考第7章。
     

    Bin和Pipeline

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

    1. GObject
    2. ╰──GInitiallyUnowned
    3. ╰──GstObject
    4. ╰──GstElement
    5. ╰──GstBin
    6. ╰──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系统,用于消息的接收。

     创建bin

    和elements 一样,Bins可以使用elements factory创建,也可以使用gst_bin_new () and gst_pipeline_new ()创建,使用gst_bin_add () and gst_bin_remove (),增加和删除一个element,不过要注意当你往Bin中增加一个组件后,Bin会对该组件产生一个所属关系;当你销毁一个箱柜后,箱柜中的组件同样被销毁(dereferenced);当你将一个组件从箱柜移除后,该组件会被自动销毁(dereferenced)。

    1. #include <gst/gst.h>
    2. int
    3. main (int argc,
    4. char *argv[])
    5. {
    6. GstElement *bin, *pipeline, *source, *sink;
    7. /* init */
    8. gst_init (&argc, &argv);
    9. /* create */
    10. pipeline = gst_pipeline_new ("my_pipeline");
    11. bin = gst_bin_new ("my_bin");
    12. source = gst_element_factory_make ("fakesrc", "source");
    13. sink = gst_element_factory_make ("fakesink", "sink");
    14. /* First add the elements to the bin */
    15. gst_bin_add_many (GST_BIN (bin), source, sink, NULL);
    16. /* add the bin to the pipeline */
    17. gst_bin_add (GST_BIN (pipeline), bin);
    18. /* link the elements */
    19. gst_element_link (source, sink);
    20. [..]
    21. }

    有多种方法查询Bin中的element,比较常见的如gst_bin_get_by_name() and gst_bin_get_by_interface (),也可以使用gst_bin_iterate_elements ()遍历

    自定义Bin

    程序员可以自定义能执行特定任务的Bin。例如,你可以参照下面的代码写一个ogg/Norbis解码器。

    1. int
    2. main (int argc,
    3. char *argv[])
    4. {
    5. GstElement *player;
    6. /* init */
    7. gst_init (&argc, &argv);
    8. /* create player */
    9. player = gst_element_factory_make ("oggvorbisplayer", "player");
    10. /* set the source audio file */
    11. g_object_set (player, "location", "helloworld.ogg", NULL);
    12. /* start playback */
    13. gst_element_set_state (GST_ELEMENT (player), GST_STATE_PLAYING);
    14. [..]
    15. }

     Bins 管理其下的elements的状态

    Bin可以管理其中包含的所有元素的状态。如果您使用 gst_element_set_state () 将 bin(或pipeline)设置为某个目标状态,它将确保其中包含的所有元素也将设置为该状态。这意味着通常只需设置pipeline的状态即可启动或关闭pipeline。

    bin 将对其所有子元素执行状态更改。这确保当上游元素进入 PAUSED 或 PLAYING 时,下游元素已准备好接收数据。同样在关闭时,sink 元素会先设置为 READY 或 NULL,这将导致上游元素收到 FLUSHING 错误并在元素设置为 READY 或 NULL 状态之前停止流线程。

    但是请注意,如果将元素添加到已经运行的 bin 或管道中,例如在“pad-added”信号回调中,它的状态不会自动与它添加到的 bin 或管道的当前状态或目标状态保持一致。相反,在将元素添加到已经运行的管道时,您需要自己使用 gst_element_set_state () 或 gst_element_sync_state_with_parent () 将其设置为所需的目标状态。

    Bus

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

    使用Bus

    有两种方式使用一个bus

    • 运行GLib/Gtk+主循环(你也可以自己运行默认的GLib的主循环),然后使用侦听器对总线进行侦听。使用这种方法,GLib 的主循环将轮询总线上是否存在新的消息,当存在新的消息的时候,总线会马上通知你。在这种情况下,你会用到gst_bus_add_watch () /gst_bus_add_signal_watch()两个函数。当使用总线时,设置消息处理器到管道的总线上可以使用gst_bus_add_watch ()。来创建一个消息处理器来侦听管道。每当管道发出一个消息到总线,这个消息处理器就会被触发,消息处理器则开始检测消息信号类型(见下章〉从而决定哪些事件将被处理。当处理器从总线删除某个消息的时候,其返回值应为TRUE。
    • 自己侦听总线消息,使用gst_bus_peek ()和/或 gst_bus_poll ()就可以实现。
    1. #include <gst/gst.h>
    2. static GMainLoop *loop;
    3. static gboolean
    4. my_bus_callback (GstBus * bus, GstMessage * message, gpointer data)
    5. {
    6. g_print ("Got %s message\n", GST_MESSAGE_TYPE_NAME (message));
    7. switch (GST_MESSAGE_TYPE (message)) {
    8. case GST_MESSAGE_ERROR:{
    9. GError *err;
    10. gchar *debug;
    11. gst_message_parse_error (message, &err, &debug);
    12. g_print ("Error: %s\n", err->message);
    13. g_error_free (err);
    14. g_free (debug);
    15. g_main_loop_quit (loop);
    16. break;
    17. }
    18. case GST_MESSAGE_EOS:
    19. /* end-of-stream */
    20. g_main_loop_quit (loop);
    21. break;
    22. default:
    23. /* unhandled message */
    24. break;
    25. }
    26. /* we want to be notified again the next time there is a message
    27. * on the bus, so returning TRUE (FALSE means we want to stop watching
    28. * for messages on the bus and our callback should not be called again)
    29. */
    30. return TRUE;
    31. }
    32. gint
    33. main (gint argc, gchar * argv[])
    34. {
    35. GstElement *pipeline;
    36. GstBus *bus;
    37. guint bus_watch_id;
    38. /* init */
    39. gst_init (&argc, &argv);
    40. /* create pipeline, add handler */
    41. pipeline = gst_pipeline_new ("my_pipeline");
    42. /* adds a watch for new message on our pipeline's message bus to
    43. * the default GLib main context, which is the main context that our
    44. * GLib main loop is attached to below
    45. */
    46. bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
    47. bus_watch_id = gst_bus_add_watch (bus, my_bus_callback, NULL);
    48. gst_object_unref (bus);
    49. /* [...] */
    50. /* create a mainloop that runs/iterates the default GLib main context
    51. * (context NULL), in other words: makes the context check if anything
    52. * it watches for has happened. When a message has been posted on the
    53. * bus, the default main context will automatically call our
    54. * my_bus_callback() function to notify us of that message.
    55. * The main loop will be run until someone calls g_main_loop_quit()
    56. */
    57. loop = g_main_loop_new (NULL, FALSE);
    58. g_main_loop_run (loop);
    59. /* clean up */
    60. gst_element_set_state (pipeline, GST_STATE_NULL);
    61. gst_object_unref (pipeline);
    62. g_source_remove (bus_watch_id);
    63. g_main_loop_unref (loop);
    64. return 0;
    65. }

    理解消息处理器在主循环的线程context被调用是相当重要的,因为在总线上管道和应用程序之间的交互是异步,所以上述方法无法适用于实时情况,比如音频轨道、无间隔播放(理论上的)、视频效果之间的交叉混合。如果需要满足实时要求,实现上述功能,你就需要编写一个GStreamer插件来实现在管道中直接触发回调。而对于一些初级的应用来说,使用从管道传递消息给应用程序的方法来实现应用程序与管道的交互,还是非常有用的。这种方法的好处是GStreamer 内部所有的线程将被应用程序隐藏,而开发人员也不必去担心线程问题。

    注意:如果你使用了默认的GLib主循环来实现管道与应用程序的交互,建议你可以将“消息”信号链接到总线上,而不必在管道上使用侦听器,这样对于所有可能的消息类型,你就不需用switch(),只要连接到所需要的信号格式为"message:<type>",其中<Type>是一种消息类型(见下一节对消息类型的详细解释)上面的代码段也可以这样写:

    1. GstBus *bus;
    2. [..]
    3. bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
    4. gst_bus_add_signal_watch (bus);
    5. g_signal_connect (bus, "message::error", G_CALLBACK (cb_message_error), NULL);
    6. g_signal_connect (bus, "message::eos", G_CALLBACK (cb_message_eos), NULL);
    7. [..]

     如果你没有使用GLib主循环,默认的消息信号将会无效,然而,你可以导出一个小助手给集成提供你使用的主循环,启动产生总线信号。(详细见documentation )

    Message类型

    GStreamer有几种由总线传递的预定义消息类型,这些消息都是可扩展的。插件可以定义另外的一些消息,应用程序可以有这些消息的绝对代码或者忽略它们。强烈推荐应用程序至少要处理错误消息并直接的反馈给用户。

    所有的消息都有一个消息源、类型和时间戳。这个消息源能被用来判断由哪个组件发出消息。例如,在众多的消息中,应用程序只对上层的管道发出的消息感兴趣(如状态变换的提示)。下面列出所有的消息种类、代表的意义,以及如何解析具体消息的内容。

    pads及其功能

    如我们在Elements一章中看到的那样,Pads是组件对外的接口。数据流从一个组件的source pad到另一个组件的sink pad。pads的功能(capabilities)决定了一个组件所能处理的媒体类型。在这章的后续讲解中,我们将对衬垫的功能做更详细的说明。

    pads

    一个pads的类型由2个特性决定:它的数据导向(direction)以及它的时效性(availability)。正如我们先前提到的,Gstreamer定义了2种衬垫的数据导向:source pads 和 sink pads。pads的数据导向这个术语是从组件内部的角度给予定义的:组件通过它们的sink pads接收数据,通过它们的source pads输出数据。如果通过一张图来形象地表述,sink pads画在组件的左侧,而source pads画在组件的右侧,数据从左向右流动。

    pads的时效性比pads的数据导向复杂得多。一个衬垫可以拥有三种类型的时效性:永久型(always)、随机型(sometimes)、请求型(on request)。三种时效性的意义顾名思义:永久型的pads一直会存在,随机型的pads只在某种特定的条件下才存在(会随机消失的衬垫也属于随机型),请求型的pads只在应用程序明确发出请求时才出现。

    动态(随机)pads

    一些组件在其被创建时不会立刻产生所有它将用到的pads。例如在一个oggdemuxer的组件中可能发生这种情况。这个组件将会读取Ogg流,每当它在Ogg流中检测到一些元数据流时(例如vorbis,theora ),它会为每个元数据流创建动态pads。同样,它也会在流终止时删除该pads。动态pads在demuxer这种组件中可以起到很大的作用。

    运行gst-inspect oggdemux只会显示出一个pads在组件中:一个名字叫作'sink'的接收pads,其它的pads都处于'休眠'中,你可以从pads模板(pad template)中的"Exists:Sometimes"的属性看到这些信息。pads会根据你所播放的Ogg档的类型而产生,认识到这点对于你创建一个动态管道特别重要。当组件通过它的随机型(sometimes)pads范本创建了一个随机型(sometimes)的pads的时侯,你可以通过对该组件绑定一个信号处理器(signal handler),通过它来得知pads被创建。下面一段代码演示了如何这样做:名叫'sink'的接收pads,其它的pads都处于'休眠'中,显而易见这是pads"有时存在”的特性。pads会根据你所播放的Ogg档的类型而产生,这点在你准备创建一个动态管道时显得特别重要,当组件创建了一个”有时存在”的pads时,你可以通过对该组件触发一个信号处理器(signal handler)来得知pads被创建。下面一段代码演示了如何这样做:

    1. #include <gst/gst.h>
    2. static void
    3. cb_new_pad (GstElement *element,
    4. GstPad *pad,
    5. gpointer data)
    6. {
    7. gchar *name;
    8. name = gst_pad_get_name (pad);
    9. g_print ("A new pad %s was created\n", name);
    10. g_free (name);
    11. /* here, you would setup a new pad link for the newly created pad */
    12. [..]
    13. }
    14. int
    15. main (int argc,
    16. char *argv[])
    17. {
    18. GstElement *pipeline, *source, *demux;
    19. GMainLoop *loop;
    20. /* init */
    21. gst_init (&argc, &argv);
    22. /* create elements */
    23. pipeline = gst_pipeline_new ("my_pipeline");
    24. source = gst_element_factory_make ("filesrc", "source");
    25. g_object_set (source, "location", argv[1], NULL);
    26. demux = gst_element_factory_make ("oggdemux", "demuxer");
    27. /* you would normally check that the elements were created properly */
    28. /* put together a pipeline */
    29. gst_bin_add_many (GST_BIN (pipeline), source, demux, NULL);
    30. gst_element_link_pads (source, "src", demux, "sink");
    31. /* listen for newly created pads */
    32. g_signal_connect (demux, "pad-added", G_CALLBACK (cb_new_pad), NULL);
    33. /* start the pipeline */
    34. gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_PLAYING);
    35. loop = g_main_loop_new (NULL, FALSE);
    36. g_main_loop_run (loop);
    37. [..]
    38. }

     请求pads

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

    下面一段代码演示了怎样在一个" tee ”组件请求一个新的输出pads:

    1. static void
    2. some_function (GstElement * tee)
    3. {
    4. GstPad *pad;
    5. gchar *name;
    6. pad = gst_element_request_pad_simple (tee, "src%d");
    7. name = gst_pad_get_name (pad);
    8. g_print ("A new pad %s was created\n", name);
    9. g_free (name);
    10. /* here, you would link the pad */
    11. /* [..] */
    12. /* and, after doing that, free our reference */
    13. gst_object_unref (GST_OBJECT (pad));
    14. }

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

    1. static void
    2. link_to_multiplexer (GstPad * tolink_pad, GstElement * mux)
    3. {
    4. GstPad *pad;
    5. gchar *srcname, *sinkname;
    6. srcname = gst_pad_get_name (tolink_pad);
    7. pad = gst_element_get_compatible_pad (mux, tolink_pad, NULL);
    8. gst_pad_link (tolink_pad, pad);
    9. sinkname = gst_pad_get_name (pad);
    10. gst_object_unref (GST_OBJECT (pad));
    11. g_print ("A new pad %s was created and linked to %s\n", sinkname, srcname);
    12. g_free (sinkname);
    13. g_free (srcname);
    14. }

    pad功能

    由于pads对于一个组件起了非常重要的作用,因此就有了一个术语来描述能够通过pads或当前通过pads的数据流。这个术语就是功能(capabilities)。在这里,我们将简要介绍什么是pads的功能以及怎么使用它们。这些足以使我们对这个概念有个大致的了解。如果想要对pads的功能有更深入的了解,并知道在GStreamer中定义的所有的pads的功能,请参考插件开发手册Plugin Writers Guide。

    pads的功能(capabilities)是与pads模板(pad templates)以及pads实例相关联的。对于pads模板,pads的功能(capabilities)描述的是:当通过该衬垫范本创建了一个衬垫后,该衬垫允许通过的媒体类型。对于衬垫实例,功能可以描述所有可能通过该衬垫的媒体类型(通常是该衬垫实例所属的衬垫模板的功能的一份拷贝),或者是当前已经通过该衬垫的流媒体类型。前一种情况,该衬垫实例还未有任何数据流在其中通过。

    功能分解

    pads的功能通过GstCaps对象来进行描述。一个GstCaps对象会包含一个或多个GstStructure。一个GstStructure描述一种媒体类型。一个被数据流通过的衬垫(negotiated pad)存在功能集(capabilities set),每种功能只包含一个GstStructure结构。结构中只包含固定的值。但上述约束并不对尚未有数据流通过的衬垫(unnegotiated pads)或衬垫范本有效。

    下面给出了一个例子,你可以通过运行gst-inspect vorbisdec看到"vorbisdec”组件的一些功能。你可能会看到2个pads:源pads和接收pads,2个pads的时效性都是永久型,并且每个pads都有相应的功能描述。接收pads将会接收 vorbis编码的音频数据,其mime-type 显示为"audio/x-vorbis"。源pads可以将译码后的音频数据采样(raw audio samples)发送给下一个组件,其mime-type显示为为"audio/x-raw-int"。源pads的功能描述中还包含了一些其它的特性:音频采样率(audio samplerate)、声道数、以及一些你可能并不太关心的信息。

    1. Pad Templates:
    2. SRC template: 'src'
    3. Availability: Always
    4. Capabilities:
    5. audio/x-raw
    6. format: F32LE
    7. rate: [ 1, 2147483647 ]
    8. channels: [ 1, 256 ]
    9. SINK template: 'sink'
    10. Availability: Always
    11. Capabilities:
    12. audio/x-vorbis

    属性和值

    特性(Properties)用来描述功能中的额外信息(注:除数据流类型之外的信息)。一条特性由一个关键词和一个值组成。下面是一些值的类型:

    •  基本类型,几乎涵盖了Glib 中的所有GType类型。这些类型为每条特性(Properties)指明了一个明确,非动态的值。例子如下所示:

                    整型(G_TYPE_INT):明确的数值(相对范围值)。

                    布尔类型:(G_TYPE_BOOLEAN): TRUE或FALSE。

                    浮点类型:(G_TYPE_FLOAT):明确的浮点数。
                    字符串类型:(G_TYPE_STRING): UTF-8编码的字符串。
                    分数类型:(GST_TYPE_FRACTION):由整数做分子分母的分数。

    • 范围类型(Range types):由GStreamer注册的且属于GTypes的一种数据类型。它指明了一个范围值。范围类型通常被用来指示支持的音频采样率范围或者支持的视频档大小范围。GStreamer中又有2种不同类型的范围值。

                    整型范围值(GST_TYPE_INT_RANGE):用最大和最小边界值指明了一个整型数值范围。举例来说: "vorbisdec"组件的采样率范围为8000-50000。
                    浮点范围值(GST_TYPE_FLOAT_RANGE):用最大和最小边界值指明了一个浮点数值范围。
                    分数范围值(GST_TYPE_FRACTION_RANGE):用最大和最小边界值指明了一个分数数值范围。

    •   列表类型(GST_TYPE_LIST):可以在给定的列表中取任何一个值。

    示例:某个衬垫的功能如果想要表示其支持采样率为44100Hz 以及48000Hz的数据,它可以用一个包含44100和48000的列表数据类型。

    •         数组类型(GST_TYPE_ARRAY):一组数据。数组中的每个元素都是特性的全值(full value)。数组中的元素必须是同样的数据类型。这意味着一个数组可以包含任意的整数,整型的列表,整型范围的组合。对于浮点数与字符串类型也是如此,但一个数组不能同时包含整数与浮点数。

    示例:对于一个多于两个声道的音频档,其声道布局(channel layout)需要被特别指明。(对于单声道和双声道的音频档除非明确指明在特性中指明其声道数,否则按默认处理)。因此声道布局应该用一个枚举数组类型来存储。每个枚举值代表了一个喇叭位置。与GST_TYPE_LIST 类型不一样的是,数组类型是作为一个整体来看待的。

    pads性能的用途

    pads的功能(Capabilities)(简称caps)描述了两个衬垫之间的数据流类型,或者它们所支持的数据流类型。功能主要用于以下用途:
    ·自动填充(Autoplugging):根据组件的功能自动找到可连接的组件。所有的自动充填器(autopluggers)都采用的这种方法。
    ·兼容性检测(Compatibility detection):当两个个衬垫连接时,GStreamer会验
    证它们是否采用的同样的数据流格式进行交互。连接并验证两个衬垫是否兼容的过程叫”功能谈判"(caps negotiation)。
    ·元数据(Metadata):通过读取衬垫的功能(capabilities),应用程序能够提供有
    关当前流经衬垫的正在播放的媒体类型信息。而这个信息我们叫做元数据(Metadata)。
    ·过滤(Filtering):应用程序可以通过衬垫的功能(capabilities)来给两个交互的
    衬垫之间的媒体类型加以限制,这些被限制的媒体类型的集合应该是两个交互的衬垫共同支持的格式集的子集。举例来说:应用程序可以使用"filtered caps"指明两个交互的衬垫所支持的视频大小(固定或不固定)在本手册的后面部分Section 18.2,你可以看到一个使用带过滤功能(filteredcaps)衬垫的例子。你可以往你的管道中插入一个capsfilter组件,并设置其衬垫的功能(capabilities)属性,从而实现衬垫的功能(capabilities)的过滤。功能过滤器(caps filters)一般放在一些转换组件后面,将数据在特定的位置强制转换成特定的输出格式。这些转换组件有: audioconvert、 audioresample、ffmpegcolorspace和videoscale。

    使用衬垫的功能(capabilities)来操作元数据
    一个衬垫能够有多个功能。功能(GstCaps)可以用一个包含一个或多个GstStructures的数组来表示.每个GstStructures 由一个名字字符串(比如说“width")和相应的值(类型可能为G_TYPE_INT或GST_TYPE_INT_RANGE)构成。

    值得注意的是,这里有三种不同的衬垫的功能(capabilities)需要区分:衬垫的可能功能(possible capabilities)(通常是通过对衬垫模板使用gst-inspect得到),衬垫的允许功能(allowed caps)(它是衬垫模板的功能的子集,具体取决于每对交互衬垫的可能功能),衬垫的最后协商功能(lastly negotiated caps)(准确的流或缓存格式,只包含一个结构,以及没有像范围值或列表值这种不定变量)。

    你可以通过查询每个功能的结构得到一个衬垫功能集的所有功能。你可以通过gst_caps_get_structure()得到一个功能的GstStructure,通过gst_caps_get_size()得到一个GstCaps对象中的GstStructure数量。

    简易衬垫的功能(capabilities)(simple caps )是指仅有一个GstStructure,固定衬垫的功能(capabilities)(fixed caps)指其仅有一个GstStructure,且没有可变的数据类型(像范围或列表等)。另外还有两种特殊的功能–任意衬垫的功能(capabilities)(ANYcaps)和空衬垫的功能(capabilities)(empty caps)。

    下面的例子演示了如何从一个固定的视频功能提取出宽度和高度信息:

    1. static void
    2. read_video_props (GstCaps *caps)
    3. {
    4. gint width, height;
    5. const GstStructure *str;
    6. g_return_if_fail (gst_caps_is_fixed (caps));
    7. str = gst_caps_get_structure (caps, 0);
    8. if (!gst_structure_get_int (str, "width", &width) ||
    9. !gst_structure_get_int (str, "height", &height)) {
    10. g_print ("No width/height available\n");
    11. return;
    12. }
    13. g_print ("The video size of this set of capabilities is %dx%d\n",
    14. width, height);
    15. }

    功能(capabilities)应用于过滤器

    由于衬垫的功能(capabilities)常被包含于插件(plugin)中,且用来描述衬垫支持的媒体类型,所以程序员在为了在插件(plugin)间进行交互时,尤其是使用过滤功能(filtered caps)时,通常需要对衬垫功能有着基本的理解。当你使用过滤功能(filteredcaps)或固定功能(fixation)时,你就对交互的衬垫间所允许的媒体类型做了限制,限制其为交互的衬垫所支持的媒体类型的一个子集。你可以通过在管道中使用capsfilter组件实现上述功能,而为了做这些,你需要创建你自己的GstCaps。这里我们给出最容易的方法是,你可以通过gst_caps_new_simple()函数来创建你自己的GstCaps。

    1. static gboolean
    2. link_elements_with_filter (GstElement *element1, GstElement *element2)
    3. {
    4. gboolean link_ok;
    5. GstCaps *caps;
    6. caps = gst_caps_new_simple ("video/x-raw",
    7. "format", G_TYPE_STRING, "I420",
    8. "width", G_TYPE_INT, 384,
    9. "height", G_TYPE_INT, 288,
    10. "framerate", GST_TYPE_FRACTION, 25, 1,
    11. NULL);
    12. link_ok = gst_element_link_filtered (element1, element2, caps);
    13. gst_caps_unref (caps);
    14. if (!link_ok) {
    15. g_warning ("Failed to link element1 and element2!");
    16. }
    17. return link_ok;
    18. }

    上述代码会将两个组件间交互的数据限制为特定的视频格式、宽度、高度以及帧率(如果没达到这些限制条件,两个组件就会连接失败)。请记住:当你使用gst_element_link_filtered()时,Gstreamer 会自动创建一个capsfilter组件,将其加入到你的箱柜或管道中,并插入到你想要交互的两个组件间。(当你想要断开两个组件的连接时,你需要注意到这一点)。 

    在某些情况下,当你想要在两个衬垫间创建一个更精确的带过滤连接的功能集时,你可以用到一个更精简的函数- gst_caps_new_full ():

    1. static gboolean
    2. link_elements_with_filter (GstElement *element1, GstElement *element2)
    3. {
    4. gboolean link_ok;
    5. GstCaps *caps;
    6. caps = gst_caps_new_full (
    7. gst_structure_new ("video/x-raw",
    8. "width", G_TYPE_INT, 384,
    9. "height", G_TYPE_INT, 288,
    10. "framerate", GST_TYPE_FRACTION, 25, 1,
    11. NULL),
    12. gst_structure_new ("video/x-bayer",
    13. "width", G_TYPE_INT, 384,
    14. "height", G_TYPE_INT, 288,
    15. "framerate", GST_TYPE_FRACTION, 25, 1,
    16. NULL),
    17. NULL);
    18. link_ok = gst_element_link_filtered (element1, element2, caps);
    19. gst_caps_unref (caps);
    20. if (!link_ok) {
    21. g_warning ("Failed to link element1 and element2!");
    22. }
    23. return link_ok;
    24. }

    Ghost pads

    你可以从下图看到,箱柜没有一个属于它自己的pads,这就是“精灵pads“的由来。 

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

    下图显示了一个精灵pads。最左边组件的接收pads同样也是整个Bin的精灵衬垫。由于精灵衬垫看起来与其它pads没什么区别,而且与其它pads有着类似的功能。所以它们可以加到任何一种组件上,而不仅仅是GstBin。

    通过gst_ghost_pad_new ()可以创建一个ghostpad

    1. #include <gst/gst.h>
    2. int
    3. main (int argc,
    4. char *argv[])
    5. {
    6. GstElement *bin, *sink;
    7. GstPad *pad;
    8. /* init */
    9. gst_init (&argc, &argv);
    10. /* create element, add to bin */
    11. sink = gst_element_factory_make ("fakesink", "sink");
    12. bin = gst_bin_new ("mybin");
    13. gst_bin_add (GST_BIN (bin), sink);
    14. /* add ghostpad */
    15. pad = gst_element_get_static_pad (sink, "sink");
    16. gst_element_add_pad (bin, gst_ghost_pad_new ("sink", pad));
    17. gst_object_unref (GST_OBJECT (pad));
    18. [..]
    19. }

     上面的例子中,箱柜不仅有精灵衬垫,而且还存在一个带名叫" sink "的接收衬垫的组件。因此这个箱柜可以作为那个组件的替代者。你可以将其它的组件与这个箱柜进行连接。

    Buffers和Event

    pipeline 中的数据流时buffer和event的组合,Buffers包含媒体数据,而Event重要时一些控制信息,比如seek信息和视频结束信息,

    缓冲区

    缓冲区包含了你创建的管道里的数据流。通常一个源组件会创建一个新的缓冲区,同时组件还将会把缓冲区的数据传递给下一个组件。当使用GStreamer底层构造来创建一个媒体管道的时候,你不需要自己来处理缓冲区,组件将会为你处理这些缓冲区。

    一个缓冲区主要由以下一个组成:

    • 指向某块内存的指针
    • ·内存的大小
    • 缓冲区的时间戳
    • 一个引用计数,指出缓冲区被组件使用的次数,没有组件引用了,就会销毁

    这里有一个简单的例子,我们先创建了一个缓冲区,然后为这个缓冲区分配内存,然后将数据存放在缓冲区中,并传递至下一个组件。该组件读取数据,处理某些事件(像创建一个新的缓冲区并进行译码),对该缓冲区解引用,这将造成数据空闲,导致缓冲区被销毁。典型的音频和视频译码器就是这样工作的。

    尽管如此,还有一些更为复杂的设定,组件会适当的修改缓冲区,也就是说,不会分配一个新的缓冲区。组件也可以写入硬件内存(如视频捕获源)或是使用XShm 从X-server 分配内存。缓冲区只能读,等等。

    事件

    事件是一系列控制粒子,随着缓冲区被发送到管道的上游和下游。下游事件通知流状态相同的组件,可能的事件包括中断,flush,流的终止信号等等。在应用程序与组件之间的交互以及事件与事件之间的交互中,上游事件被用于改变管道中数据流的状态,如查找。对于应用程序来说,上游事件非常重要,下游事件则是为了说明获取更加完善的数据概念上的图像。

    由于大多数应用程序以时间为单位查找,下面的例子实现了同样的功能:

    1. static void
    2. seek_to_time (GstElement *element,
    3. guint64 time_ns)
    4. {
    5. GstEvent *event;
    6. event = gst_event_new_seek (1.0, GST_FORMAT_TIME,
    7. GST_SEEK_FLAG_NONE,
    8. GST_SEEK_METHOD_SET, time_ns,
    9. GST_SEEK_TYPE_NONE, G_GUINT64_CONSTANT (0));
    10. gst_element_send_event (element, event);
    11. }

     gst_element_seek ()功能和上面功能类似

    编写一个应用程序

    通过上面的学习,对各个功能组件应该有个简单认识,下面我们一起把这些组件串起来,组成一个简单的应用,Ogg/Vorbis音乐播放器

    Hello world

    我们现在开始创建第一个简易的应用程序―基于命令行并支持Ogg/Vorbis格式的音频播放器。我们只需要使用标准的Gstreamer的组件(components)就能够开发出这个程序。它通过命令行来指定播放的档。让我们开始这段旅程:

    如在前面学到的那样,第一件事情是要通过gst_init()函数来初始化GStreamer库。确保程序包含了gst/gst.h头文件,这样GStreamer库中的对象和函数才能够被正确地定义。你可以通过#include <gst/gst.h>指令来包含 gst/gst.h 头文件。然后你可以通过函数gst_element_factory_make ()来创建不同的组件。对于ogg/Vorbis音频播放器,我们需要一个源组件从磁盘读取文件。GStreamer中有一个" filesrc”的组件可以胜任此事。其次我们需要一些东西来解析从磁盘读取的文件。GStreamer中有两个组件可以分别来解析Ogg/Vorbis槽。第一个将Ogg数据流解析成元数据流的组件叫" oggdemux"。第二个是Vorbis音频译码器,通常称为"vorbisdec ”。由于 " oggdemux”为每个元数据流动态创建pads,所以你得为" oggdemux”组件设置"pad- added”的事件处理函数。pads部分讲解的那样,"pad-added”的事件处理函数可以用来将Ogg 译码组件和 Vorbis译码组件连接起来。最后,我们还需要一个音频输出组件– “alsasink "。它会将数据传送给ALSA 音频设备。

    万事俱备,只欠东风。我们需要把所有的组件都包含到一个容器组件中- GstPipeline,然后在这个管道中一直轮循,直到我们播放完整的歌曲。我们在第6章中学习过如何将组件包含进容器组件,在Bin部分了解过组件的状态信息。我们同样需要在管道总线上加消息处理来处理错误信息和检测流结束标志。

    现在给出我们第一个音频播放器的所有代码:

    1. #include <gst/gst.h>
    2. #include <glib.h>
    3. static gboolean
    4. bus_call (GstBus *bus,
    5. GstMessage *msg,
    6. gpointer data)
    7. {
    8. GMainLoop *loop = (GMainLoop *) data;
    9. switch (GST_MESSAGE_TYPE (msg)) {
    10. case GST_MESSAGE_EOS:
    11. g_print ("End of stream\n");
    12. g_main_loop_quit (loop);
    13. break;
    14. case GST_MESSAGE_ERROR: {
    15. gchar *debug;
    16. GError *error;
    17. gst_message_parse_error (msg, &error, &debug);
    18. g_free (debug);
    19. g_printerr ("Error: %s\n", error->message);
    20. g_error_free (error);
    21. g_main_loop_quit (loop);
    22. break;
    23. }
    24. default:
    25. break;
    26. }
    27. return TRUE;
    28. }
    29. static void
    30. on_pad_added (GstElement *element,
    31. GstPad *pad,
    32. gpointer data)
    33. {
    34. GstPad *sinkpad;
    35. GstElement *decoder = (GstElement *) data;
    36. /* We can now link this pad with the vorbis-decoder sink pad */
    37. g_print ("Dynamic pad created, linking demuxer/decoder\n");
    38. sinkpad = gst_element_get_static_pad (decoder, "sink");
    39. gst_pad_link (pad, sinkpad);
    40. gst_object_unref (sinkpad);
    41. }
    42. int
    43. main (int argc,
    44. char *argv[])
    45. {
    46. GMainLoop *loop;
    47. GstElement *pipeline, *source, *demuxer, *decoder, *conv, *sink;
    48. GstBus *bus;
    49. guint bus_watch_id;
    50. /* Initialisation */
    51. gst_init (&argc, &argv);
    52. loop = g_main_loop_new (NULL, FALSE);
    53. /* Check input arguments */
    54. if (argc != 2) {
    55. g_printerr ("Usage: %s <Ogg/Vorbis filename>\n", argv[0]);
    56. return -1;
    57. }
    58. /* Create gstreamer elements */
    59. pipeline = gst_pipeline_new ("audio-player");
    60. source = gst_element_factory_make ("filesrc", "file-source");
    61. demuxer = gst_element_factory_make ("oggdemux", "ogg-demuxer");
    62. decoder = gst_element_factory_make ("vorbisdec", "vorbis-decoder");
    63. conv = gst_element_factory_make ("audioconvert", "converter");
    64. sink = gst_element_factory_make ("autoaudiosink", "audio-output");
    65. if (!pipeline || !source || !demuxer || !decoder || !conv || !sink) {
    66. g_printerr ("One element could not be created. Exiting.\n");
    67. return -1;
    68. }
    69. /* Set up the pipeline */
    70. /* we set the input filename to the source element */
    71. g_object_set (G_OBJECT (source), "location", argv[1], NULL);
    72. /* we add a message handler */
    73. bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
    74. bus_watch_id = gst_bus_add_watch (bus, bus_call, loop);
    75. gst_object_unref (bus);
    76. /* we add all elements into the pipeline */
    77. /* file-source | ogg-demuxer | vorbis-decoder | converter | alsa-output */
    78. gst_bin_add_many (GST_BIN (pipeline),
    79. source, demuxer, decoder, conv, sink, NULL);
    80. /* we link the elements together */
    81. /* file-source -> ogg-demuxer ~> vorbis-decoder -> converter -> alsa-output */
    82. gst_element_link (source, demuxer);
    83. gst_element_link_many (decoder, conv, sink, NULL);
    84. g_signal_connect (demuxer, "pad-added", G_CALLBACK (on_pad_added), decoder);
    85. /* note that the demuxer will be linked to the decoder dynamically.
    86. The reason is that Ogg may contain various streams (for example
    87. audio and video). The source pad(s) will be created at run time,
    88. by the demuxer when it detects the amount and nature of streams.
    89. Therefore we connect a callback function which will be executed
    90. when the "pad-added" is emitted.*/
    91. /* Set the pipeline to "playing" state*/
    92. g_print ("Now playing: %s\n", argv[1]);
    93. gst_element_set_state (pipeline, GST_STATE_PLAYING);
    94. /* Iterate */
    95. g_print ("Running...\n");
    96. g_main_loop_run (loop);
    97. /* Out of the main loop, clean up nicely */
    98. g_print ("Returned, stopping playback\n");
    99. gst_element_set_state (pipeline, GST_STATE_NULL);
    100. g_print ("Deleting pipeline\n");
    101. gst_object_unref (GST_OBJECT (pipeline));
    102. g_source_remove (bus_watch_id);
    103. g_main_loop_unref (loop);
    104. return 0;
    105. }

     如上是一个完整的处理流程,其流程图如下:

     编译运行

    编译

    gcc -Wall helloworld.c -o helloworld $(pkg-config --cflags --libs gstreamer-1.0)

     通过命令gcc -Wall $(pkg-config --cflags --libs gstreamer-0.10) helloworld.c -ohelloworld来编译例子helloworld。编译这个应用程序,GStreamer需要使用pkg-config来得到编译器和连接标志。如果你的GStreamer不是以默认的方式安装,请确保环境变量PKG_CONFIG_PATH 设置正确($libdir/pkgconfig)。如果环境变量设置不对,应用程序不会通过编译。你可以通过./helloworld file.ogg命令来运行例子。用你自己的 Ogg/Norbis档来代替fil

    结论

    对我们的第一个例子做个总结。像你看到的那样,我们是通过非常底层(low-level)的API来建立的管道,这样也非常有效。在这份手册的后面部分,你可以看到通过使用一些高层(higher-level)的API可以花费比这个例子更少的代码来建立一个有着更强大功能的媒体播放器。我们将在GStreamer应用程序开发手册的第四部分讨论这个话题。我们现在需要对GStreamer的内部机制有更深的了解。

    在这个例子中,我们可以很容易的用其它的组件来代替"filesrc"组件。比如从网络读取数据的组件,或者从其它任何能够更好的将数据与你桌面环境整和在一起的组件。同样你可以使用其它的译码器来支持其它的媒体类型。如果你的程序不是运行在Linux上,而是运行在Mac OS x,Windows或FreeBSD 上,你可以使用不同的音频接收组件。甚至你可以使用filesink 将数据写到磁盘上而不是将它们播放出来。所有这些,都显示了GStreamer组件的一个巨大优势–可重用性(reusability)。

    参考文章

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

    Your first application

  • 相关阅读:
    智能位移监测,更新传统井盖的功能
    阿里这份Github星标63.7K的Redis高级笔记简直不要太细,看到就是赚到。
    转行挨批的一天:什么垃圾方案,连问题都没搞清楚
    前端例程20220729:按钮悬停边框卷动效果
    Kotlin协程:flowOn与线程切换
    文献 | 柳叶刀发文:虚拟现实的新用途之治疗场所恐惧症
    JAVA 8: Stream流方法
    centos7搭建DNS服务完整版
    泡咖啡问题
    Spring AOP 实现方式与应用
  • 原文地址:https://blog.csdn.net/hn_zhangkun/article/details/125506014