gstreamer 中文教程

基础教程1:Hello World!

目标

没有什么比在屏幕上打印“Hello World”更能获得对软件库的第一印象了!

但是由于我们正在处理多媒体框架,所以我们将改为播放视频。

不要被下面的代码量吓到:只有 4 行是真正的工作。其余的是清理代码,在 C 中,这总是有点冗长。

事不宜迟,为您的第一个 GStreamer 应用程序做好准备……

Hello world

将此代码复制到名为的文本文件中(或在您的 GStreamer 安装中找到它)。basic-tutorial-1.c

基础教程1.c

#include <gst/gst.h>

#ifdef __APPLE__
#include <TargetConditionals.h>
#endif

int
tutorial_main (int argc, char *argv[])
{
  GstElement *pipeline;
  GstBus *bus;
  GstMessage *msg;

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

  /* Build the pipeline */
  pipeline =
      gst_parse_launch
      ("playbin uri=https://gstreamer.freedesktop.org/data/media/sintel_trailer-480p.webm",
      NULL);

  /* Start playing */
  gst_element_set_state (pipeline, GST_STATE_PLAYING);

  /* 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);

  /* See next tutorial for proper error message handling/parsing */
  if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ERROR) {
    g_error ("An error occurred! Re-run with the GST_DEBUG=*:WARN environment "
        "variable set for more details.");
  }

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

int
main (int argc, char *argv[])
{
#if defined(__APPLE__) && TARGET_OS_MAC && !TARGET_OS_IPHONE
  return gst_macos_main (tutorial_main, argc, argv, NULL);
#else
  return tutorial_main (argc, argv);
#endif
}

按照在 Linux 上安装在 Mac OS X 上安装在 Windows 上安装中的描述编译它。如果遇到编译错误,请仔细检查这些部分中给出的说明。

如果一切正常,启动可执行文件!您应该会看到一个弹出窗口,其中包含直接从 Internet 播放的视频以及音频。恭喜!

信息需要帮忙?

如果您需要帮助来编译此代码,请参阅针对您的平台构建教程部分:LinuxMac OS XWindows,或在 Linux 上使用此特定命令:

gcc basic-tutorial-1.c -o basic-tutorial-1 pkg-config --cflags --libs gstreamer-1.0``

如果您需要帮助来运行此代码,请参阅适用于您的平台的运行教程部分: LinuxMac OS XWindows

所需的库:gstreamer-1.0

本教程将打开一个窗口并显示一个带有音频的电影。媒体是从 Internet 获取的,因此窗口可能需要几秒钟才会出现,具体取决于您的连接速度。此外,没有延迟管理(缓冲),因此在连接速度较慢时,电影可能会在几秒钟后停止。查看基础教程 12:Streaming如何解决此问题。

演练

让我们回顾一下这些代码行,看看它们做了什么:

  GstElement *pipeline;
  GstBus *bus;

这必须始终是您的第一个 GStreamer 命令。除其他事项外, gst_init ():

  • 初始化所有内部结构
  • 检查可用的插件
  • 执行任何适用于 GStreamer 的命令行选项

如果您始终将命令行参数传递 argcargvgst_init ( ),您的应用程序将自动受益于 GStreamer 标准命令行选项(更多信息请参见基础教程 10:GStreamer 工具

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

  /* Build the pipeline */

这一行是本教程的核心,举例说明了两个关键点:gst_parse_launch () 和playbin

gst_parse_launch

GStreamer 是一个旨在处理多媒体流的框架。媒体从“源”元素(生产者)向下传播到“汇”元素(消费者),通过一系列执行各种任务的中间元素。所有互连元素的集合称为“管道”。

在 GStreamer 中,您通常通过手动组装各个元素来构建管道,但是,当管道足够简单并且您不需要任何高级功能时,您可以采用快捷方式:gst_parse_launch ( )

此函数采用管道的文本表示并将其转换为实际管道,这非常方便。事实上,这个功能非常方便,有一个完全围绕它构建的工具,您会非常熟悉(请参阅基本教程 10:GStreamer 工具以了解 gst-launch-1.0gst-launch-1.0语法)。

游戏箱

那么,我们要求gst_parse_launch () 为我们构建什么样的管道呢?这里进入第二个关键点:我们正在构建一个由称为playbin的单个元素组成的管道。

playbin是一个特殊的element,作为source和sink,是一个完整的pipeline。在内部,它创建并连接所有必要的元素来播放您的媒体,因此您不必担心。

它不允许手动管道所具有的控制粒度,但是,它仍然允许足够的自定义以满足广泛的应用程序。包括本教程。

在本例中,我们只向playbin传递一个参数,即我们要播放的媒体的 URI。尝试将其更改为其他内容!不管是anhttp://还是file://URI,playbin都会透明的实例化合适的GStreamer源!

如果您输入错误的 URI,或者文件不存在,或者您缺少插件,GStreamer 提供了几种通知机制,但我们在这个例子中唯一做的就是出错退出,所以不要期待太多反馈。

      gst_parse_launch
      ("playbin uri=https://gstreamer.freedesktop.org/data/media/sintel_trailer-480p.webm",

这行突出了另一个有趣的概念:状态。每个 GStreamer 元素都有一个相关联的状态,您可以或多或少地将其视为常规 DVD 播放器中的播放/暂停按钮。就目前而言,除非您将管道设置为状态,否则播放不会开始就足够了PLAYING

在这一行中,gst_element_set_state () 正在设置pipeline(我们唯一的元素,记住)状态PLAYING,从而启动播放。

  /* Start playing */
  gst_element_set_state (pipeline, GST_STATE_PLAYING);

  /* Wait until error or EOS */

这些行将等待直到发生错误或找到流的末尾。gst_element_get_bus () 检索管道的总线, gst_bus_timed_pop_filtered () 将阻塞,直到您通过该总线接收到 ERROR 或EOS(End-Of-Stream)。不要太担心这条线,GStreamer 总线在基础教程 2:GStreamer 概念中有解释。

就是这样!从这一点开始,GStreamer 会处理一切。当媒体到达终点(EOS)或遇到错误(尝试关闭视频窗口,或拔掉网线)时,执行将结束。始终可以通过在控制台中按 control-C 来停止应用程序。

清理

但是,在终止应用程序之前,我们需要做一些事情来正确地整理自己。

  msg =
      gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE,
      GST_MESSAGE_ERROR | GST_MESSAGE_EOS);

  /* See next tutorial for proper error message handling/parsing */
  if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ERROR) {

始终阅读您使用的函数的文档,以了解您是否应该释放它们在使用后返回的对象。

在这种情况下,gst_bus_timed_pop_filtered () 返回一条消息,需要使用gst_message_unref ()释放该消息(有关消息的更多信息,请参见 基础教程 2:GStreamer 概念)。

gst_element_get_bus () 添加了对必须使用gst_object_unref ()释放的总线的引用。将管道设置为 NULL 状态将确保它释放它已分配的所有资源(更多关于 基础教程 3:动态管道中的状态)。最后,取消引用管道将破坏它及其所有内容。

结论

您的第一个 GStreamer 教程到此结束。我们希望它的简洁性可以作为该框架强大功能的一个例子!

让我们回顾一下。今天我们学习了:

下一篇教程将继续介绍更多基本的 GStreamer 元素,并向您展示如何手动构建管道。

很高兴有你在这里,很快再见!

基础教程2:GStreamer概念

目标

上一教程展示了如何自动构建管道。现在我们将通过实例化每个元素并将它们全部链接在一起来手动构建管道。在这个过程中,我们将学习:

  • 什么是 GStreamer 元素以及如何创建它。
  • 如何将元素相互连接。
  • 如何自定义元素的行为。
  • 如何观察总线的错误情况并从 GStreamer 消息中提取信息。

Hello world

将此代码复制到名为的文本文件中(或在您的 GStreamer 安装中找到它)。basic-tutorial-2.c

基础教程2.c

#include <gst/gst.h>

#ifdef __APPLE__
#include <TargetConditionals.h>
#endif

int
tutorial_main (int argc, char *argv[])
{
  GstElement *pipeline, *source, *sink;
  GstBus *bus;
  GstMessage *msg;
  GstStateChangeReturn ret;

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

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

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

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

  /* Build the pipeline */
  gst_bin_add_many (GST_BIN (pipeline), source, sink, NULL);
  if (gst_element_link (source, sink) != 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;
}

int
main (int argc, char *argv[])
{
#if defined(__APPLE__) && TARGET_OS_MAC && !TARGET_OS_IPHONE
  return gst_macos_main (tutorial_main, argc, argv, NULL);
#else
  return tutorial_main (argc, argv);
#endif
}

信息需要帮忙?

如果您需要帮助来编译此代码,请参阅针对您的平台构建教程 部分:LinuxMac OS XWindows,或在 Linux 上使用此特定命令:

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

如果您需要帮助来运行此代码,请参阅适用于您的平台的运行教程部分: LinuxMac OS X [ 2 ] 或Windows

本教程打开一个窗口并显示一个测试模式,没有音频

所需的库:gstreamer-1.0

演练

这些元素是 GStreamer 的基本构造块。它们处理数据,因为数据从源元素(数据生产者)向下游流向接收器元素(数据消费者),并通过过滤器元素。

img

图 1。示例流水线

元素创建

我们将跳过 GStreamer 初始化,因为它与之前的教程相同:

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

如这段代码所示,可以使用gst_element_factory_make ()创建新元素。第一个参数是要创建的元素类型(基础教程 14:Handy elements展示了几种常见类型,基础教程 10:GStreamer 工具展示了如何获取所有可用类型的列表)。第二个参数是我们要给这个特定实例的名称。如果您没有保留指针(以及更有意义的调试输出),命名您的元素对于以后检索它们很有用。但是,如果您为名称传递NULL ,GStreamer 将为您提供一个唯一的名称。

对于本教程,我们创建了两个元素:一个videotestsrc和一个autovideosink。没有过滤元件。因此,管道将如下所示:

img

图 2。本教程中构建的管道

videotestsrc是一个源元素(它产生数据),它创建一个测试视频模式。此元素对于调试目的(和教程)很有用,并且通常不会在实际应用程序中找到。

autovideosink是一个接收器元素(它消耗数据),它在窗口上显示它接收到的图像。根据操作系统的不同,存在多种具有不同功能的视频接收器。 autovideosink会自动选择并实例化最好的一个,因此您不必担心细节,并且您的代码更加独立于平台。

管道创建

  /* Create the elements */
  source = gst_element_factory_make ("videotestsrc", "source");

GStreamer 中的所有元素通常必须包含在管道中才能使用,因为它负责一些时钟和消息传递功能。我们使用gst_pipeline_new ()创建管道。

    g_printerr ("Not all elements could be created.\n");
    return -1;
  }

  /* Build the pipeline */
  gst_bin_add_many (GST_BIN (pipeline), source, sink, NULL);
  if (gst_element_link (source, sink) != TRUE) {

管道是一种特殊类型的bin,它是用于包含其他元素的元素。因此,所有适用于箱的方法也适用于管道。

在我们的例子中,我们调用gst_bin_add_many () 将元素添加到管道中(注意演员表)。此函数接受要添加的元素列表,以NULL结尾。可以使用gst_bin_add ()添加单个元素。

但是,这些元素尚未相互关联。为此,我们需要使用gst_element_link ()。它的第一个参数是源,第二个是目标。顺序很重要,因为必须在数据流之后建立链接(即从源元素到接收器元素)。请记住,只有位于同一容器中的元素才能链接在一起,因此请记住在尝试链接它们之前将它们添加到管道中!

特性

GStreamer 元素都是一种特殊的GObject ,它是提供属性设施的实体。

大多数 GStreamer 元素都具有可自定义的属性:可以修改以更改元素行为(可写属性)或查询以了解元素的内部状态(可读属性)的命名属性。

使用g_object_get ()读取属性并使用g_object_set ()写入属性。

g_object_set () 接受以 NULL结尾的属性名、属性值对列表,因此可以一次更改多个属性。

这就是属性处理方法具有g_前缀的原因。

回到上面例子中的内容,

    gst_object_unref (pipeline);
    return -1;

上面的代码行更改了videotestsrc的“pattern”属性,该属性控制元素输出的测试视频的类型。尝试不同的值!

可以使用基础教程 10:GStreamer 工具中描述的 gst-inspect-1.0 工具找到元素公开的所有属性的名称和可能的值,或者在该元素的文档中找到(这里是 videotestsrc 的情况)。

错误检查

此时,我们已经构建和设置了整个管道,本教程的其余部分与上一个非常相似,但我们将添加更多错误检查:

  /* 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) {

我们调用gst_element_set_state (),但是这次我们检查它的返回值是否有错误。更改状态是一个微妙的过程,基础教程 3:动态管道中提供了更多详细信息。

    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");

gst_bus_timed_pop_filtered () 等待执行结束并返回我们之前忽略的GstMessage 。当 GStreamer 遇到错误条件或EOS时,我们要求gst_bus_timed_pop_filtered () 返回,因此我们需要检查发生了什么,并在屏幕上打印一条消息(您的应用程序可能需要执行更复杂的操作)。

GstMessage是一个非常通用的结构,几乎可以传递任何类型的信息。幸运的是,GStreamer 为每一种消息提供了一系列的解析函数。

在这种情况下,一旦我们知道消息包含错误(通过使用 GST_MESSAGE_TYPE () 宏),我们就可以使用 gst_message_parse_error () 返回一个 GLib GError错误结构和一个对调试有用的字符串。检查代码以了解这些是如何使用和随后释放的。

GStreamer 总线

在这一点上,值得更正式地介绍一下 GStreamer 总线。它是负责将元素生成的GstMessage按顺序传递给应用程序并传递给应用程序线程的对象。最后一点很重要,因为实际的媒体流是在应用程序之外的另一个线程中完成的。

可以使用gst_bus_timed_pop_filtered () 及其同级从总线同步提取消息 ,或者使用信号异步提取消息(在下一教程中显示)。您的应用程序应始终关注总线,以便收到有关错误和其他播放相关问题的通知。

其余代码是清理序列,与基础教程 1:Hello world!.

锻炼

如果您想练习一下,请尝试以下练习:在此管道的源和接收器之间添加一个视频过滤器元素。使用 vertigotv以获得不错的效果。您需要创建它,将它添加到管道,并将它与其他元素链接起来。

根据您的平台和可用的插件,您可能会遇到“协商”错误,因为接收器不了解过滤器正在生成什么(更多关于协商的信息,请参见基础教程 6:媒体格式和 Pad 功能)。在这种情况下,尝试在过滤器之后添加一个名为videoconvert的元素(即构建一个包含 4 个元素的管道。有关 videoconvert的更多信息,请参见基础教程 14:Handy elements)。

结论

本教程显示:

关于 GStreamer 基本概念的两篇教程中的第一篇到此结束。接下来是第二个。

请记住,在本页的附件中,您应该找到教程的完整源代码以及构建它所需的任何附件文件。

很高兴有你在这里,很快再见!

基础教程3:动态管道

目标

本教程展示了使用 GStreamer 所需的其余基本概念,它允许在信息可用时“即时”构建管道,而不是在应用程序开始时定义单一管道。

完成本教程后,您将具备开始回放教程所需的知识 。这里审查的要点是:

  • 如何在链接元素时获得更好的控制。
  • 如何收到有趣事件的通知,以便您及时做出反应。
  • 元素可以处于的各种状态。

介绍

正如您即将看到的,本教程中的管道在设置为播放状态之前并未完全构建。还行吧。如果我们不采取进一步的行动,数据将到达管道的末端,管道将产生一条错误消息并停止。但我们将采取进一步行动......

在此示例中,我们打开一个多路复用(或多路复用)文件,即音频和视频一起存储在一个容器文件中。负责打开此类容器的元素称为 demuxers,容器格式的一些示例包括 Matroska (MKV)、Quick Time(QT、MOV)、Ogg 或高级系统格式(ASF、WMV、WMA)。

如果一个容器嵌入了多个流(例如,一个视频和两个音轨),分路器会将它们分开并通过不同的输出端口公开它们。这样就可以在管道中创建不同的分支,处理不同类型的数据。

GStreamer 元素相互通信的端口称为 pads ( GstPad)。存在接收器垫,数据通过它进入元素,还有源垫,数据通过它退出元素。很自然地,source 元素仅包含 source pad,sink 元素仅包含 sink pad,而 filter 元素包含两者。

img img img

图 1。GStreamer 元素及其垫。

一个解复用器包含一个接收器垫,多路复用数据通过它到达,以及多个源垫,一个用于在容器中找到的每个流:

img

图 2。具有两个源焊盘的解复用器。

为了完整起见,这里有一个简化的管道,其中包含一个多路分解器和两个分支,一个用于音频,一个用于视频。这不是 在此示例中构建的管道:

img

图 3。具有两个分支的示例管道。

处理多路分解器的主要复杂性在于,它们在收到一些数据并有机会查看容器以查看内部内容之前无法生成任何信息。也就是说,多路分解器开始时没有其他元素可以链接到的源垫,因此管道必须在它们处终止。

解决方案是构建从源向下到分路器的管道,并将其设置为运行(播放)。当分路器收到足够的信息来了解容器中流的数量和种类时,它将开始创建源垫。这是我们完成管道构建并将其附加到新添加的多路分解器垫的正确时间。

为简单起见,在本例中,我们将只链接到音频板而忽略视频。

Hello world

将此代码复制到名为的文本文件中basic-tutorial-3.c(或在您的 GStreamer 安装中找到它)。

基础教程-3.c

#include <gst/gst.h>

/* Structure to contain all our information, so we can pass it to callbacks */
typedef struct _CustomData {
  GstElement *pipeline;
  GstElement *source;
  GstElement *convert;
  GstElement *resample;
  GstElement *sink;
} CustomData;

/* Handler for the pad-added signal */
static void pad_added_handler (GstElement *src, GstPad *pad, CustomData *data);

int main(int argc, char *argv[]) {
  CustomData data;
  GstBus *bus;
  GstMessage *msg;
  GstStateChangeReturn ret;
  gboolean terminate = FALSE;

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

  /* Create the elements */
  data.source = gst_element_factory_make ("uridecodebin", "source");
  data.convert = gst_element_factory_make ("audioconvert", "convert");
  data.resample = gst_element_factory_make ("audioresample", "resample");
  data.sink = gst_element_factory_make ("autoaudiosink", "sink");

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

  if (!data.pipeline || !data.source || !data.convert || !data.resample || !data.sink) {
    g_printerr ("Not all elements could be created.\n");
    return -1;
  }

  /* Build the pipeline. Note that we are NOT linking the source at this
   * point. We will do it later. */
  gst_bin_add_many (GST_BIN (data.pipeline), data.source, data.convert, data.resample, data.sink, NULL);
  if (!gst_element_link_many (data.convert, data.resample, data.sink, NULL)) {
    g_printerr ("Elements could not be linked.\n");
    gst_object_unref (data.pipeline);
    return -1;
  }

  /* Set the URI to play */
  g_object_set (data.source, "uri", "https://gstreamer.freedesktop.org/data/media/sintel_trailer-480p.webm", NULL);

  /* Connect to the pad-added signal */
  g_signal_connect (data.source, "pad-added", G_CALLBACK (pad_added_handler), &data);

  /* Start playing */
  ret = gst_element_set_state (data.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 (data.pipeline);
    return -1;
  }

  /* Listen to the bus */
  bus = gst_element_get_bus (data.pipeline);
  do {
    msg = gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE,
        GST_MESSAGE_STATE_CHANGED | 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);
          terminate = TRUE;
          break;
        case GST_MESSAGE_EOS:
          g_print ("End-Of-Stream reached.\n");
          terminate = TRUE;
          break;
        case GST_MESSAGE_STATE_CHANGED:
          /* We are only interested in state-changed messages from the pipeline */
          if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data.pipeline)) {
            GstState old_state, new_state, pending_state;
            gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state);
            g_print ("Pipeline state changed from %s to %s:\n",
                gst_element_state_get_name (old_state), gst_element_state_get_name (new_state));
          }
          break;
        default:
          /* We should not reach here */
          g_printerr ("Unexpected message received.\n");
          break;
      }
      gst_message_unref (msg);
    }
  } while (!terminate);

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

/* This function will be called by the pad-added signal */
static void pad_added_handler (GstElement *src, GstPad *new_pad, CustomData *data) {
  GstPad *sink_pad = gst_element_get_static_pad (data->convert, "sink");
  GstPadLinkReturn ret;
  GstCaps *new_pad_caps = NULL;
  GstStructure *new_pad_struct = NULL;
  const gchar *new_pad_type = NULL;

  g_print ("Received new pad '%s' from '%s':\n", GST_PAD_NAME (new_pad), GST_ELEMENT_NAME (src));

  /* If our converter is already linked, we have nothing to do here */
  if (gst_pad_is_linked (sink_pad)) {
    g_print ("We are already linked. Ignoring.\n");
    goto exit;
  }

  /* Check the new pad's type */
  new_pad_caps = gst_pad_get_current_caps (new_pad);
  new_pad_struct = gst_caps_get_structure (new_pad_caps, 0);
  new_pad_type = gst_structure_get_name (new_pad_struct);
  if (!g_str_has_prefix (new_pad_type, "audio/x-raw")) {
    g_print ("It has type '%s' which is not raw audio. Ignoring.\n", new_pad_type);
    goto exit;
  }

  /* Attempt the link */
  ret = gst_pad_link (new_pad, sink_pad);
  if (GST_PAD_LINK_FAILED (ret)) {
    g_print ("Type is '%s' but link failed.\n", new_pad_type);
  } else {
    g_print ("Link succeeded (type '%s').\n", new_pad_type);
  }

exit:
  /* Unreference the new pad's caps, if we got them */
  if (new_pad_caps != NULL)
    gst_caps_unref (new_pad_caps);

  /* Unreference the sink pad */
  gst_object_unref (sink_pad);
}

信息需要帮忙?

如果您需要帮助来编译此代码,请参阅针对您的平台构建教程 部分:LinuxMac OS XWindows,或在 Linux 上使用此特定命令: gcc basic-tutorial-3.c -o basic-tutorial-3 pkg-config --cflags --libs gstreamer-1.0``

如果您需要帮助来运行此代码,请参阅适用于您的平台的运行教程部分: LinuxMac OS XWindows

本教程仅播放音频。媒体是从 Internet 获取的,因此可能需要几秒钟才能启动,具体取决于您的连接速度。

所需的库:gstreamer-1.0

演练

/* Structure to contain all our information, so we can pass it to callbacks */
typedef struct _CustomData {
  GstElement *pipeline;
  GstElement *source;
  GstElement *convert;
  GstElement *resample;
  GstElement *sink;
} CustomData;

到目前为止,我们已将所需的所有信息(GstElement基本上是指向 s 的指针)保存为局部变量。由于本教程(以及大多数实际应用程序)涉及回调,我们将把所有数据分组到一个结构中以便于处理。

/* Handler for the pad-added signal */
static void pad_added_handler (GstElement *src, GstPad *pad, CustomData *data);

这是前向引用,稍后使用。

/* Create the elements */
data.source = gst_element_factory_make ("uridecodebin", "source");
data.convert = gst_element_factory_make ("audioconvert", "convert");
data.resample = gst_element_factory_make ("audioresample", "resample");
data.sink = gst_element_factory_make ("autoaudiosink", "sink");

我们像往常一样创建元素。uridecodebin将在内部实例化所有必要的元素(源、多路分解器和解码器)以将 URI 转换为原始音频和/或视频流。它完成了一半的工作playbin。由于它包含多路分解器,它的源板最初不可用,我们需要动态链接到它们。

audioconvert用于在不同的音频格式之间进行转换,确保此示例适用于任何平台,因为音频解码器生成的格式可能与音频接收器期望的格式不同。

audioresample对于在不同的音频采样率之间进行转换很有用,同样确保此示例可以在任何平台上运行,因为音频解码器产生的音频采样率可能不是音频接收器支持的采样率。

对于音频,这autoaudiosink相当于autovideosink在上一个教程中看到的。它会将音频流渲染到声卡。

if (!gst_element_link_many (data.convert, data.resample, data.sink, NULL)) {
  g_printerr ("Elements could not be linked.\n");
  gst_object_unref (data.pipeline);
  return -1;
}

这里我们链接元素转换器、重采样和接收器,但我们不将它们与源链接,因为此时它不包含源焊盘。我们只是让这个分支(converter + sink)保持未链接状态,直到稍后。

/* Set the URI to play */
g_object_set (data.source, "uri", "https://gstreamer.freedesktop.org/data/media/sintel_trailer-480p.webm", NULL);

我们将文件的 URI 设置为通过属性播放,就像我们在上一个教程中所做的那样。

信号

/* Connect to the pad-added signal */
g_signal_connect (data.source, "pad-added", G_CALLBACK (pad_added_handler), &data);

GSignals是 GStreamer 中的关键点。当发生有趣的事情时,它们允许您收到通知(通过回调的方式)。信号由名称标识,每个GObject信号都有自己的信号。

在这一行中,我们附加到源(一个元素)的“pad-added”信号uridecodebin。为此,我们使用g_signal_connect()并提供要使用的回调函数 ( pad_added_handler) 和数据指针。GStreamer 对这个数据指针不做任何事情,它只是将它转发给回调,以便我们可以与其共享信息。在这种情况下,我们将指针传递给CustomData我们专门为此目的构建的结构。

a 生成的信号GstElement可以在其文档中找到,也可以使用基础教程 10:GStreamer 工具gst-inspect-1.0中描述的工具。

我们现在准备好了!只需将管道设置为PLAYINGstate 并开始侦听总线以获取有趣的消息(如ERROREOS),就像在之前的教程中一样。

回调

当我们的源元素最终有足够的信息开始生成数据时,它将创建源焊盘,并触发“焊盘添加”信号。此时我们的回调将被调用:

static void pad_added_handler (GstElement *src, GstPad *new_pad, CustomData *data) {

srcGstElement触发信号的。在这个例子中,它只能是uridecodebin,因为它是我们附加的唯一信号。信号处理程序的第一个参数始终是触发它的对象。

new_padGstPad刚刚添加到src元素的 。这通常是我们要链接到的打击垫。

data是我们附加到信号时提供的指针。在这个例子中,我们用它来传递CustomData指针。

GstPad *sink_pad = gst_element_get_static_pad (data->convert, "sink");

我们从中CustomData提取转换器元素,然后使用 检索它的接收器垫gst_element_get_static_pad ()。这是我们要链接的打击垫new_pad。在之前的教程中,我们将元素与元素进行了链接,并让 GStreamer 选择合适的焊盘。现在我们要直接连接焊盘。

/* If our converter is already linked, we have nothing to do here */
if (gst_pad_is_linked (sink_pad)) {
  g_print ("We are already linked. Ignoring.\n");
  goto exit;
}

uridecodebin可以创建它认为合适的任意数量的 pad,并且对于每个 pad,都会调用此回调。一旦我们已经链接,这些代码行将阻止我们尝试链接到新的 pad。

/* Check the new pad's type */
new_pad_caps = gst_pad_get_current_caps (new_pad, NULL);
new_pad_struct = gst_caps_get_structure (new_pad_caps, 0);
new_pad_type = gst_structure_get_name (new_pad_struct);
if (!g_str_has_prefix (new_pad_type, "audio/x-raw")) {
  g_print ("It has type '%s' which is not raw audio. Ignoring.\n", new_pad_type);
  goto exit;
}

现在我们将检查这个新 pad 将要输出的数据类型,因为我们只对产生音频的 pad 感兴趣。我们之前已经创建了一段处理音频的管道(audioconvertaudioresample和 链接autoaudiosink),例如,我们将无法将其链接到制作视频的 pad。

gst_pad_get_current_caps()检索包装在结构中的垫的当前功能(即它当前输出的数据类型)GstCaps 。可以使用查询垫可以支持的所有可能的上限 gst_pad_query_caps()。一个 pad 可以提供许多功能,因此GstCaps 可以包含许多GstStructure,每个代表不同的功能。pad 上的当前上限将始终具有单个GstStructure并代表单一媒体格式,或者如果没有当前上限,则将NULL返回。

因为,在这种情况下,我们知道我们想要的垫子只有一个功能(音频),所以我们用 检索第GstStructure一个 gst_caps_get_structure()

最后,gst_structure_get_name()我们恢复结构的名称,其中包含格式的主要描述(实际上是媒体类型)。

如果名称不是audio/x-raw,则这不是解码音频垫,我们对此不感兴趣。

否则,尝试链接:

/* Attempt the link */
ret = gst_pad_link (new_pad, sink_pad);
if (GST_PAD_LINK_FAILED (ret)) {
  g_print ("Type is '%s' but link failed.\n", new_pad_type);
} else {
  g_print ("Link succeeded (type '%s').\n", new_pad_type);
}

gst_pad_link()试图链接两个垫。与 的情况一样gst_element_link(),必须指定从源到接收器的链接,并且两个焊盘必须由驻留在同一容器(或管道)中的元素拥有。

我们完成了!当出现正确类型的 pad 时,它将链接到音频处理管道的其余部分,并继续执行直到出现 ERROR 或 EOS。但是,我们将通过介绍 State 的概念从本教程中挤出更多内容。

GStreamer 状态

当我们说播放不会开始直到您将管道带到状态时,我们已经讨论了一些状态PLAYING。我们将在这里介绍其余状态及其含义。GStreamer中有4种状态:

状态 描述
NULL 元素的 NULL 状态或初始状态。
READY 该元素已准备好进入暂停状态。
PAUSED 该元素已暂停,它已准备好接受和处理数据。然而,接收器元素只接受一个缓冲区然后阻塞。
PLAYING 元素正在播放,时钟在运行,数据在流动。

只能在相邻的之间移动,也就是说,不能从NULLPLAYING,必须经过中间READYPAUSED 状态。但是,如果您将管道设置为PLAYING,GStreamer 将为您进行中间转换。

case GST_MESSAGE_STATE_CHANGED:
  /* We are only interested in state-changed messages from the pipeline */
  if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data.pipeline)) {
    GstState old_state, new_state, pending_state;
    gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state);
    g_print ("Pipeline state changed from %s to %s:\n",
        gst_element_state_get_name (old_state), gst_element_state_get_name (new_state));
  }
  break;

我们添加了这段代码,用于侦听有关状态更改的总线消息并将它们打印在屏幕上以帮助您了解转换。每个元素都将有关其当前状态的消息放在总线上,因此我们将它们过滤掉并仅监听来自管道的消息。

大多数应用程序只需要担心开始PLAYING播放,然后执行暂停,然后在程序退出时PAUSED返回以释放所有资源。NULL

锻炼

传统上,动态 pad 链接一直是许多程序员的难题。通过实例化一个(可能前面有autovideosink一个)证明你已经掌握了它,并在正确的垫出现时将它链接到多路分解器。videoconvert提示:您已经在屏幕上打印了视频板的类型。

您现在应该看到(和听到)与基础教程 1 中相同的电影:Hello world!. 在您使用的那个教程中playbin,这是一个方便的元素,它会自动为您处理所有的解复用和 pad 链接。大多数播放教程都致力于playbin.

结论

在本教程中,您学习了:

  • 如何通知事件使用GSignals
  • 如何GstPad直接连接 s 而不是它们的父元素。
  • GStreamer 元素的各种状态。

您还组合了这些项目以构建一个动态管道,该管道未在程序启动时定义,但在有关媒体的信息可用时创建。

您现在可以继续基础教程,并在基础教程 4:时间管理中了解如何执行查找和与时间相关的查询,或者转到回放教程,并获得有关playbin元素的更多见解。

请记住,在本页的附件中,您应该找到教程的完整源代码以及构建它所需的任何附件文件。很高兴有你在这里,很快再见!

基础教程4:时间管理

目标

本教程展示了如何使用 GStreamer 与时间相关的工具。尤其:

  • 如何在管道中查询流位置或持续时间等信息。
  • 如何在流中寻找(跳转)到不同的位置(时间)。

介绍

GstQuery是一种允许向元素或垫询问一条信息的机制。在此示例中,我们询问管道是否允许搜索(某些来源,如直播,不允许搜索)。如果允许,那么一旦电影播放了 10 秒,我们就会使用搜索跳到不同的位置。

在之前的教程中,一旦我们设置并运行了管道,我们的主要功能就只是坐着等待接收一个ERROR或一个EOS 通过总线。在这里,我们修改此函数以定期唤醒并查询管道的流位置,以便我们可以将其打印在屏幕上。这类似于媒体播放器会定期更新用户界面。

最后,流的持续时间会在它发生变化时被查询和更新。

求实例

将此代码复制到名为的文本文件中basic-tutorial-4.c(或在您的 GStreamer 安装中找到它)。

基础教程-4.c

#include <gst/gst.h>

/* Structure to contain all our information, so we can pass it around */
typedef struct _CustomData {
  GstElement *playbin;  /* Our one and only element */
  gboolean playing;      /* Are we in the PLAYING state? */
  gboolean terminate;    /* Should we terminate execution? */
  gboolean seek_enabled; /* Is seeking enabled for this media? */
  gboolean seek_done;    /* Have we performed the seek already? */
  gint64 duration;       /* How long does this media last, in nanoseconds */
} CustomData;

/* Forward definition of the message processing function */
static void handle_message (CustomData *data, GstMessage *msg);

int main(int argc, char *argv[]) {
  CustomData data;
  GstBus *bus;
  GstMessage *msg;
  GstStateChangeReturn ret;

  data.playing = FALSE;
  data.terminate = FALSE;
  data.seek_enabled = FALSE;
  data.seek_done = FALSE;
  data.duration = GST_CLOCK_TIME_NONE;

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

  /* Create the elements */
  data.playbin = gst_element_factory_make ("playbin", "playbin");

  if (!data.playbin) {
    g_printerr ("Not all elements could be created.\n");
    return -1;
  }

  /* Set the URI to play */
  g_object_set (data.playbin, "uri", "https://gstreamer.freedesktop.org/data/media/sintel_trailer-480p.webm", NULL);

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

  /* Listen to the bus */
  bus = gst_element_get_bus (data.playbin);
  do {
    msg = gst_bus_timed_pop_filtered (bus, 100 * GST_MSECOND,
        GST_MESSAGE_STATE_CHANGED | GST_MESSAGE_ERROR | GST_MESSAGE_EOS | GST_MESSAGE_DURATION);

    /* Parse message */
    if (msg != NULL) {
      handle_message (&data, msg);
    } else {
      /* We got no message, this means the timeout expired */
      if (data.playing) {
        gint64 current = -1;

        /* Query the current position of the stream */
        if (!gst_element_query_position (data.playbin, GST_FORMAT_TIME, &current)) {
          g_printerr ("Could not query current position.\n");
        }

        /* If we didn't know it yet, query the stream duration */
        if (!GST_CLOCK_TIME_IS_VALID (data.duration)) {
          if (!gst_element_query_duration (data.playbin, GST_FORMAT_TIME, &data.duration)) {
            g_printerr ("Could not query current duration.\n");
          }
        }

        /* Print current position and total duration */
        g_print ("Position %" GST_TIME_FORMAT " / %" GST_TIME_FORMAT "\r",
            GST_TIME_ARGS (current), GST_TIME_ARGS (data.duration));

        /* If seeking is enabled, we have not done it yet, and the time is right, seek */
        if (data.seek_enabled && !data.seek_done && current > 10 * GST_SECOND) {
          g_print ("\nReached 10s, performing seek...\n");
          gst_element_seek_simple (data.playbin, GST_FORMAT_TIME,
              GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT, 30 * GST_SECOND);
          data.seek_done = TRUE;
        }
      }
    }
  } while (!data.terminate);

  /* Free resources */
  gst_object_unref (bus);
  gst_element_set_state (data.playbin, GST_STATE_NULL);
  gst_object_unref (data.playbin);
  return 0;
}

static void handle_message (CustomData *data, GstMessage *msg) {
  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);
      data->terminate = TRUE;
      break;
    case GST_MESSAGE_EOS:
      g_print ("\nEnd-Of-Stream reached.\n");
      data->terminate = TRUE;
      break;
    case GST_MESSAGE_DURATION:
      /* The duration has changed, mark the current one as invalid */
      data->duration = GST_CLOCK_TIME_NONE;
      break;
    case GST_MESSAGE_STATE_CHANGED: {
      GstState old_state, new_state, pending_state;
      gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state);
      if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data->playbin)) {
        g_print ("Pipeline state changed from %s to %s:\n",
            gst_element_state_get_name (old_state), gst_element_state_get_name (new_state));

        /* Remember whether we are in the PLAYING state or not */
        data->playing = (new_state == GST_STATE_PLAYING);

        if (data->playing) {
          /* We just moved to PLAYING. Check if seeking is possible */
          GstQuery *query;
          gint64 start, end;
          query = gst_query_new_seeking (GST_FORMAT_TIME);
          if (gst_element_query (data->playbin, query)) {
            gst_query_parse_seeking (query, NULL, &data->seek_enabled, &start, &end);
            if (data->seek_enabled) {
              g_print ("Seeking is ENABLED from %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT "\n",
                  GST_TIME_ARGS (start), GST_TIME_ARGS (end));
            } else {
              g_print ("Seeking is DISABLED for this stream.\n");
            }
          }
          else {
            g_printerr ("Seeking query failed.");
          }
          gst_query_unref (query);
        }
      }
    } break;
    default:
      /* We should not reach here */
      g_printerr ("Unexpected message received.\n");
      break;
  }
  gst_message_unref (msg);
}

信息需要帮忙?

如果您需要帮助来编译此代码,请参阅针对您的平台构建教程 部分:LinuxMac OS XWindows,或在 Linux 上使用此特定命令:

gcc basic-tutorial-4.c -o basic-tutorial-4 pkg-config --cflags --libs gstreamer-1.0``

如果您需要帮助来运行此代码,请参阅适用于您的平台的运行教程部分: LinuxMac OS XWindows

本教程将打开一个窗口并显示一个带有音频的电影。媒体是从 Internet 获取的,因此窗口可能需要几秒钟才会出现,具体取决于您的连接速度。进入电影 10 秒后,它会跳到一个新位置

所需的库:gstreamer-1.0

演练

/* Structure to contain all our information, so we can pass it around */
typedef struct _CustomData {
  GstElement *playbin;  /* Our one and only element */
  gboolean playing;      /* Are we in the PLAYING state? */
  gboolean terminate;    /* Should we terminate execution? */
  gboolean seek_enabled; /* Is seeking enabled for this media? */
  gboolean seek_done;    /* Have we performed the seek already? */
  gint64 duration;       /* How long does this media last, in nanoseconds */
} CustomData;

/* Forward definition of the message processing function */
static void handle_message (CustomData *data, GstMessage *msg);

我们首先定义一个结构来包含我们所有的信息,这样我们就可以将它传递给其他函数。特别是,在这个例子中,我们将消息处理代码移到它自己的函数中, handle_message因为它变得有点太大了。

然后我们构建一个由单个元素 a 组成的管道 playbin,我们已经在基础教程 1:Hello world!. 然而, playbin本身就是一个管道,在这种情况下它是管道中唯一的元素,所以我们直接使用元素playbin。我们将跳过细节:通过 URI 属性提供剪辑的 URI playbin,并将管道设置为播放状态。

msg = gst_bus_timed_pop_filtered (bus, 100 * GST_MSECOND,
    GST_MESSAGE_STATE_CHANGED | GST_MESSAGE_ERROR | GST_MESSAGE_EOS | GST_MESSAGE_DURATION);

以前我们没有为 提供超时 gst_bus_timed_pop_filtered(),这意味着它在收到消息之前不会返回。现在我们使用 100 毫秒的超时,因此,如果在十分之一秒内没有收到消息,该函数将返回 NULL。我们将使用此逻辑来更新我们的“UI”。

请注意,所需的超时必须指定为 a GstClockTime,因此以纳秒为单位。表示不同时间单位的数字应该乘以GST_SECONDor之类的宏GST_MSECOND。这也使您的代码更具可读性。

如果我们收到一条消息,我们将在handle_message函数中处理它(下一小节),否则:

用户界面刷新

/* We got no message, this means the timeout expired */
if (data.playing) {

如果管道处于PLAYING状态,则该刷新屏幕了。如果我们不在状态中,我们不想做任何事情PLAYING,因为大多数查询都会失败。

我们每秒大约到达这里 10 次,这对于我们的 UI 来说已经足够好了。我们将在屏幕上打印当前媒体位置,我们可以通过查询管道来了解这一点。这涉及将在下一小节中显示的几个步骤,但是,由于位置和持续时间是足够常见的查询,因此GstElement提供更简单的现成替代方案:

/* Query the current position of the stream */
if (!gst_element_query_position (data.pipeline, GST_FORMAT_TIME, &current)) {
  g_printerr ("Could not query current position.\n");
}

gst_element_query_position()隐藏了对查询对象的管理,直接给我们提供结果。

/* If we didn't know it yet, query the stream duration */
if (!GST_CLOCK_TIME_IS_VALID (data.duration)) {
  if (!gst_element_query_duration (data.pipeline, GST_FORMAT_TIME, &data.duration)) {
     g_printerr ("Could not query current duration.\n");
  }
}

现在是了解流长度的好时机,使用另一个GstElement辅助函数:gst_element_query_duration()

/* Print current position and total duration */
g_print ("Position %" GST_TIME_FORMAT " / %" GST_TIME_FORMAT "\r",
    GST_TIME_ARGS (current), GST_TIME_ARGS (data.duration));

请注意使用GST_TIME_FORMATGST_TIME_ARGS宏来提供 GStreamer 时间的用户友好表示。

/* If seeking is enabled, we have not done it yet, and the time is right, seek */
if (data.seek_enabled && !data.seek_done && current > 10 * GST_SECOND) {
  g_print ("\nReached 10s, performing seek...\n");
  gst_element_seek_simple (data.pipeline, GST_FORMAT_TIME,
      GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT, 30 * GST_SECOND);
  data.seek_done = TRUE;
}

gst_element_seek_simple()现在我们通过调用管道“简单地”执行查找。很多求道的奥妙都藏在这个方法里,真是好东西!

让我们回顾一下参数:

GST_FORMAT_TIME表示我们正在以时间单位指定目的地。其他搜索格式使用不同的单位。

那么来吧GstSeekFlags,让我们回顾一下最常见的:

GST_SEEK_FLAG_FLUSH:这会在进行查找之前丢弃当前管道中的所有数据。在重新填充管道并且新数据开始显示时可能会暂停一下,但会大大提高应用程序的“响应能力”。如果未提供此标志,“陈旧”数据可能会显示一段时间,直到新位置出现在管道的末端。

GST_SEEK_FLAG_KEY_UNIT:对于大多数编码视频流,寻找任意位置是不可能的,但只能寻找称为关键帧的特定帧。使用此标志时,搜索实际上将移动到最近的关键帧并立即开始生成数据。如果不使用此标志,管道将在内部移动到最近的关键帧(它没有其他选择),但在到达请求的位置之前不会显示数据。最后一种选择更准确,但可能需要更长的时间。

GST_SEEK_FLAG_ACCURATE:一些媒体剪辑没有提供足够的索引信息,这意味着寻找任意位置非常耗时。在这些情况下,GStreamer 通常会估计要寻找的位置,并且通常工作得很好。如果这个精度对你的情况来说不够好(你看到搜索没有达到你要求的确切时间),那么提供这个标志。请注意,计算搜索位置可能需要更长的时间(在某些文件上很长)。

最后,我们提供要寻求的位置。由于我们要求GST_FORMAT_TIME,该值必须以纳秒为单位,因此为简单起见,我们以秒为单位表示时间,然后乘以GST_SECOND

消息泵

handle_message函数处理通过管道总线接收到的所有消息。ERROREOS处理与之前的教程相同,所以我们跳到有趣的部分:

case GST_MESSAGE_DURATION:
  /* The duration has changed, mark the current one as invalid */
  data->duration = GST_CLOCK_TIME_NONE;
  break;

每当流的持续时间发生变化时,此消息就会发布在总线上。这里我们简单地将当前持续时间标记为无效,以便稍后重新查询。

case GST_MESSAGE_STATE_CHANGED: {
  GstState old_state, new_state, pending_state;
  gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state);
  if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data->pipeline)) {
    g_print ("Pipeline state changed from %s to %s:\n",
        gst_element_state_get_name (old_state), gst_element_state_get_name (new_state));

    /* Remember whether we are in the PLAYING state or not */
    data->playing = (new_state == GST_STATE_PLAYING);

Seeks 和 time queries 通常只有在 PAUSEDorPLAYING状态下才能得到有效的回复,因为所有元素都有机会接收信息并配置自己。在这里,我们使用playing 变量来跟踪管道是否处于PLAYING状态。此外,如果我们刚刚进入该PLAYING状态,我们将进行第一个查询。我们询问管道是否允许在此流上进行搜索:

if (data->playing) {
  /* We just moved to PLAYING. Check if seeking is possible */
  GstQuery *query;
  gint64 start, end;
  query = gst_query_new_seeking (GST_FORMAT_TIME);
  if (gst_element_query (data->pipeline, query)) {
    gst_query_parse_seeking (query, NULL, &data->seek_enabled, &start, &end);
    if (data->seek_enabled) {
      g_print ("Seeking is ENABLED from %" GST_TIME_FORMAT " to %" GST_TIME_FORMAT "\n",
          GST_TIME_ARGS (start), GST_TIME_ARGS (end));
    } else {
      g_print ("Seeking is DISABLED for this stream.\n");
    }
  }
  else {
    g_printerr ("Seeking query failed.");
  }
  gst_query_unref (query);
}

gst_query_new_seeking()创建一个具有格式的“寻求”类型的新查询对象GST_FORMAT_TIME。这表明我们有兴趣通过指定我们想要移动到的新时间来寻找。我们也可以请求GST_FORMAT_BYTES,然后寻找源文件中的特定字节位置,但这通常不太有用。

然后将此查询对象传递给管道 gst_element_query()。结果存储在同一个查询中,可以使用 轻松检索gst_query_parse_seeking()。它提取一个布尔值,指示是否允许查找,以及查找可能的范围。

完成后不要忘记取消引用查询对象。

就是这样!有了这些知识,就可以构建一个媒体播放器,它会根据当前流位置定期更新滑块,并允许通过移动滑块进行搜索!

结论

本教程展示了:

  • 如何查询管道信息使用GstQuery
  • 如何使用gst_element_query_position()和获取位置和持续时间等公共信息gst_element_query_duration()
  • 如何使用流中的任意位置gst_element_seek_simple()
  • 在哪些状态下可以执行所有这些操作。

下一个教程将展示如何将 GStreamer 与图形用户界面工具包集成。

请记住,在本页的附件中,您应该找到教程的完整源代码以及构建它所需的任何附件文件。

很高兴有你在这里,很快再见!

基础教程5:GUI工具包集成

目标

本教程展示了如何将 GStreamer 集成到图形用户界面 (GUI) 工具包中,例如GTK+。基本上,GStreamer 负责媒体播放,而 GUI 工具包处理用户交互。最有趣的部分是两个库必须交互的部分:指示 GStreamer 将视频输出到 GTK+ 窗口并将用户操作转发到 GStreamer。

特别是,您将学习:

  • 如何告诉 GStreamer 将视频输出到特定窗口(而不是创建自己的窗口)。
  • 如何使用来自 GStreamer 的信息不断刷新 GUI。
  • 如何从 GStreamer 的多线程更新 GUI,这是大多数 GUI 工具包禁止的操作。
  • 一种仅订阅您感兴趣的消息的机制,而不是收到所有消息的通知。

介绍

我们将使用 GTK+工具包构建一个媒体播放器,但这些概念适用于其他工具包,例如Qt 。对GTK+有最低限度的了解将有助于理解本教程。

要点是告诉 GStreamer 将视频输出到我们选择的窗口。具体机制取决于操作系统(或者更确切地说,取决于窗口系统),但 GStreamer 为平台独立性提供了一个抽象层。这种独立性来自GstVideoOverlay接口,它允许应用程序告诉视频接收器应该接收渲染的窗口的处理程序。

信息GObject 接口

接口(GStreamer 使用的GObject 接口)是元素可以实现的一组函数。如果是,则称它支持该特定接口。例如,视频接收器通常创建自己的窗口来显示视频,但是,如果它们也能够渲染到外部窗口,则它们可以选择实现接口GstVideoOverlay并提供指定此外部窗口的函数。从应用程序开发人员的角度来看,如果支持某个接口,您就可以使用它而不必管是哪种元素在实现它。而且,如果你在使用playbin,它会自动暴露一些它内部元素支持的接口:你可以直接在上面使用你的接口函数,playbin而不知道是谁在实现它们!

另一个问题是 GUI 工具包通常只允许通过主(或应用程序)线程操作图形“小部件”,而 GStreamer 通常会产生多个线程来处理不同的任务。从回调中调用GTK+函数通常会失败,因为回调在调用线程中执行,不需要是主线程。这个问题可以通过在回调中在 GStreamer 总线上发布一条消息来解决:消息将由主线程接收,然后主线程将做出相应的反应。

最后,到目前为止,我们已经注册了一个handle_message函数,每次消息出现在总线上时都会调用该函数,这迫使我们解析每条消息以查看它是否对我们感兴趣。在本教程中,使用了一种不同的方法来为每种消息注册回调,因此总体上解析和代码更少。

GTK+ 中的媒体播放器

让我们基于 playbin 编写一个非常简单的媒体播放器,这次,使用 GUI!

将此代码复制到名为的文本文件中basic-tutorial-5.c(或在您的 GStreamer 安装中找到它)。

基础教程-5.c

#include <string.h>

#include <gtk/gtk.h>
#include <gst/gst.h>
#include <gst/video/videooverlay.h>

#include <gdk/gdk.h>
#if defined (GDK_WINDOWING_X11)
#include <gdk/gdkx.h>
#elif defined (GDK_WINDOWING_WIN32)
#include <gdk/gdkwin32.h>
#elif defined (GDK_WINDOWING_QUARTZ)
#include <gdk/gdkquartz.h>
#endif

/* Structure to contain all our information, so we can pass it around */
typedef struct _CustomData {
  GstElement *playbin;           /* Our one and only pipeline */

  GtkWidget *slider;              /* Slider widget to keep track of current position */
  GtkWidget *streams_list;        /* Text widget to display info about the streams */
  gulong slider_update_signal_id; /* Signal ID for the slider update signal */

  GstState state;                 /* Current state of the pipeline */
  gint64 duration;                /* Duration of the clip, in nanoseconds */
} CustomData;

/* This function is called when the GUI toolkit creates the physical window that will hold the video.
 * At this point we can retrieve its handler (which has a different meaning depending on the windowing system)
 * and pass it to GStreamer through the VideoOverlay interface. */
static void realize_cb (GtkWidget *widget, CustomData *data) {
  GdkWindow *window = gtk_widget_get_window (widget);
  guintptr window_handle;

  if (!gdk_window_ensure_native (window))
    g_error ("Couldn't create native window needed for GstVideoOverlay!");

  /* Retrieve window handler from GDK */
#if defined (GDK_WINDOWING_WIN32)
  window_handle = (guintptr)GDK_WINDOW_HWND (window);
#elif defined (GDK_WINDOWING_QUARTZ)
  window_handle = gdk_quartz_window_get_nsview (window);
#elif defined (GDK_WINDOWING_X11)
  window_handle = GDK_WINDOW_XID (window);
#endif
  /* Pass it to playbin, which implements VideoOverlay and will forward it to the video sink */
  gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (data->playbin), window_handle);
}

/* This function is called when the PLAY button is clicked */
static void play_cb (GtkButton *button, CustomData *data) {
  gst_element_set_state (data->playbin, GST_STATE_PLAYING);
}

/* This function is called when the PAUSE button is clicked */
static void pause_cb (GtkButton *button, CustomData *data) {
  gst_element_set_state (data->playbin, GST_STATE_PAUSED);
}

/* This function is called when the STOP button is clicked */
static void stop_cb (GtkButton *button, CustomData *data) {
  gst_element_set_state (data->playbin, GST_STATE_READY);
}

/* This function is called when the main window is closed */
static void delete_event_cb (GtkWidget *widget, GdkEvent *event, CustomData *data) {
  stop_cb (NULL, data);
  gtk_main_quit ();
}

/* This function is called everytime the video window needs to be redrawn (due to damage/exposure,
 * rescaling, etc). GStreamer takes care of this in the PAUSED and PLAYING states, otherwise,
 * we simply draw a black rectangle to avoid garbage showing up. */
static gboolean draw_cb (GtkWidget *widget, cairo_t *cr, CustomData *data) {
  if (data->state < GST_STATE_PAUSED) {
    GtkAllocation allocation;

    /* Cairo is a 2D graphics library which we use here to clean the video window.
     * It is used by GStreamer for other reasons, so it will always be available to us. */
    gtk_widget_get_allocation (widget, &allocation);
    cairo_set_source_rgb (cr, 0, 0, 0);
    cairo_rectangle (cr, 0, 0, allocation.width, allocation.height);
    cairo_fill (cr);
  }

  return FALSE;
}

/* This function is called when the slider changes its position. We perform a seek to the
 * new position here. */
static void slider_cb (GtkRange *range, CustomData *data) {
  gdouble value = gtk_range_get_value (GTK_RANGE (data->slider));
  gst_element_seek_simple (data->playbin, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT,
      (gint64)(value * GST_SECOND));
}

/* This creates all the GTK+ widgets that compose our application, and registers the callbacks */
static void create_ui (CustomData *data) {
  GtkWidget *main_window;  /* The uppermost window, containing all other windows */
  GtkWidget *video_window; /* The drawing area where the video will be shown */
  GtkWidget *main_box;     /* VBox to hold main_hbox and the controls */
  GtkWidget *main_hbox;    /* HBox to hold the video_window and the stream info text widget */
  GtkWidget *controls;     /* HBox to hold the buttons and the slider */
  GtkWidget *play_button, *pause_button, *stop_button; /* Buttons */

  main_window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  g_signal_connect (G_OBJECT (main_window), "delete-event", G_CALLBACK (delete_event_cb), data);

  video_window = gtk_drawing_area_new ();
  gtk_widget_set_double_buffered (video_window, FALSE);
  g_signal_connect (video_window, "realize", G_CALLBACK (realize_cb), data);
  g_signal_connect (video_window, "draw", G_CALLBACK (draw_cb), data);

  play_button = gtk_button_new_from_icon_name ("media-playback-start", GTK_ICON_SIZE_SMALL_TOOLBAR);
  g_signal_connect (G_OBJECT (play_button), "clicked", G_CALLBACK (play_cb), data);

  pause_button = gtk_button_new_from_icon_name ("media-playback-pause", GTK_ICON_SIZE_SMALL_TOOLBAR);
  g_signal_connect (G_OBJECT (pause_button), "clicked", G_CALLBACK (pause_cb), data);

  stop_button = gtk_button_new_from_icon_name ("media-playback-stop", GTK_ICON_SIZE_SMALL_TOOLBAR);
  g_signal_connect (G_OBJECT (stop_button), "clicked", G_CALLBACK (stop_cb), data);

  data->slider = gtk_scale_new_with_range (GTK_ORIENTATION_HORIZONTAL, 0, 100, 1);
  gtk_scale_set_draw_value (GTK_SCALE (data->slider), 0);
  data->slider_update_signal_id = g_signal_connect (G_OBJECT (data->slider), "value-changed", G_CALLBACK (slider_cb), data);

  data->streams_list = gtk_text_view_new ();
  gtk_text_view_set_editable (GTK_TEXT_VIEW (data->streams_list), FALSE);

  controls = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
  gtk_box_pack_start (GTK_BOX (controls), play_button, FALSE, FALSE, 2);
  gtk_box_pack_start (GTK_BOX (controls), pause_button, FALSE, FALSE, 2);
  gtk_box_pack_start (GTK_BOX (controls), stop_button, FALSE, FALSE, 2);
  gtk_box_pack_start (GTK_BOX (controls), data->slider, TRUE, TRUE, 2);

  main_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
  gtk_box_pack_start (GTK_BOX (main_hbox), video_window, TRUE, TRUE, 0);
  gtk_box_pack_start (GTK_BOX (main_hbox), data->streams_list, FALSE, FALSE, 2);

  main_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
  gtk_box_pack_start (GTK_BOX (main_box), main_hbox, TRUE, TRUE, 0);
  gtk_box_pack_start (GTK_BOX (main_box), controls, FALSE, FALSE, 0);
  gtk_container_add (GTK_CONTAINER (main_window), main_box);
  gtk_window_set_default_size (GTK_WINDOW (main_window), 640, 480);

  gtk_widget_show_all (main_window);
}

/* This function is called periodically to refresh the GUI */
static gboolean refresh_ui (CustomData *data) {
  gint64 current = -1;

  /* We do not want to update anything unless we are in the PAUSED or PLAYING states */
  if (data->state < GST_STATE_PAUSED)
    return TRUE;

  /* If we didn't know it yet, query the stream duration */
  if (!GST_CLOCK_TIME_IS_VALID (data->duration)) {
    if (!gst_element_query_duration (data->playbin, GST_FORMAT_TIME, &data->duration)) {
      g_printerr ("Could not query current duration.\n");
    } else {
      /* Set the range of the slider to the clip duration, in SECONDS */
      gtk_range_set_range (GTK_RANGE (data->slider), 0, (gdouble)data->duration / GST_SECOND);
    }
  }

  if (gst_element_query_position (data->playbin, GST_FORMAT_TIME, &current)) {
    /* Block the "value-changed" signal, so the slider_cb function is not called
     * (which would trigger a seek the user has not requested) */
    g_signal_handler_block (data->slider, data->slider_update_signal_id);
    /* Set the position of the slider to the current pipeline position, in SECONDS */
    gtk_range_set_value (GTK_RANGE (data->slider), (gdouble)current / GST_SECOND);
    /* Re-enable the signal */
    g_signal_handler_unblock (data->slider, data->slider_update_signal_id);
  }
  return TRUE;
}

/* This function is called when new metadata is discovered in the stream */
static void tags_cb (GstElement *playbin, gint stream, CustomData *data) {
  /* We are possibly in a GStreamer working thread, so we notify the main
   * thread of this event through a message in the bus */
  gst_element_post_message (playbin,
    gst_message_new_application (GST_OBJECT (playbin),
      gst_structure_new_empty ("tags-changed")));
}

/* This function is called when an error message is posted on the bus */
static void error_cb (GstBus *bus, GstMessage *msg, CustomData *data) {
  GError *err;
  gchar *debug_info;

  /* Print error details on the screen */
  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);

  /* Set the pipeline to READY (which stops playback) */
  gst_element_set_state (data->playbin, GST_STATE_READY);
}

/* This function is called when an End-Of-Stream message is posted on the bus.
 * We just set the pipeline to READY (which stops playback) */
static void eos_cb (GstBus *bus, GstMessage *msg, CustomData *data) {
  g_print ("End-Of-Stream reached.\n");
  gst_element_set_state (data->playbin, GST_STATE_READY);
}

/* This function is called when the pipeline changes states. We use it to
 * keep track of the current state. */
static void state_changed_cb (GstBus *bus, GstMessage *msg, CustomData *data) {
  GstState old_state, new_state, pending_state;
  gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state);
  if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data->playbin)) {
    data->state = new_state;
    g_print ("State set to %s\n", gst_element_state_get_name (new_state));
    if (old_state == GST_STATE_READY && new_state == GST_STATE_PAUSED) {
      /* For extra responsiveness, we refresh the GUI as soon as we reach the PAUSED state */
      refresh_ui (data);
    }
  }
}

/* Extract metadata from all the streams and write it to the text widget in the GUI */
static void analyze_streams (CustomData *data) {
  gint i;
  GstTagList *tags;
  gchar *str, *total_str;
  guint rate;
  gint n_video, n_audio, n_text;
  GtkTextBuffer *text;

  /* Clean current contents of the widget */
  text = gtk_text_view_get_buffer (GTK_TEXT_VIEW (data->streams_list));
  gtk_text_buffer_set_text (text, "", -1);

  /* Read some properties */
  g_object_get (data->playbin, "n-video", &n_video, NULL);
  g_object_get (data->playbin, "n-audio", &n_audio, NULL);
  g_object_get (data->playbin, "n-text", &n_text, NULL);

  for (i = 0; i < n_video; i++) {
    tags = NULL;
    /* Retrieve the stream's video tags */
    g_signal_emit_by_name (data->playbin, "get-video-tags", i, &tags);
    if (tags) {
      total_str = g_strdup_printf ("video stream %d:\n", i);
      gtk_text_buffer_insert_at_cursor (text, total_str, -1);
      g_free (total_str);
      gst_tag_list_get_string (tags, GST_TAG_VIDEO_CODEC, &str);
      total_str = g_strdup_printf ("  codec: %s\n", str ? str : "unknown");
      gtk_text_buffer_insert_at_cursor (text, total_str, -1);
      g_free (total_str);
      g_free (str);
      gst_tag_list_free (tags);
    }
  }

  for (i = 0; i < n_audio; i++) {
    tags = NULL;
    /* Retrieve the stream's audio tags */
    g_signal_emit_by_name (data->playbin, "get-audio-tags", i, &tags);
    if (tags) {
      total_str = g_strdup_printf ("\naudio stream %d:\n", i);
      gtk_text_buffer_insert_at_cursor (text, total_str, -1);
      g_free (total_str);
      if (gst_tag_list_get_string (tags, GST_TAG_AUDIO_CODEC, &str)) {
        total_str = g_strdup_printf ("  codec: %s\n", str);
        gtk_text_buffer_insert_at_cursor (text, total_str, -1);
        g_free (total_str);
        g_free (str);
      }
      if (gst_tag_list_get_string (tags, GST_TAG_LANGUAGE_CODE, &str)) {
        total_str = g_strdup_printf ("  language: %s\n", str);
        gtk_text_buffer_insert_at_cursor (text, total_str, -1);
        g_free (total_str);
        g_free (str);
      }
      if (gst_tag_list_get_uint (tags, GST_TAG_BITRATE, &rate)) {
        total_str = g_strdup_printf ("  bitrate: %d\n", rate);
        gtk_text_buffer_insert_at_cursor (text, total_str, -1);
        g_free (total_str);
      }
      gst_tag_list_free (tags);
    }
  }

  for (i = 0; i < n_text; i++) {
    tags = NULL;
    /* Retrieve the stream's subtitle tags */
    g_signal_emit_by_name (data->playbin, "get-text-tags", i, &tags);
    if (tags) {
      total_str = g_strdup_printf ("\nsubtitle stream %d:\n", i);
      gtk_text_buffer_insert_at_cursor (text, total_str, -1);
      g_free (total_str);
      if (gst_tag_list_get_string (tags, GST_TAG_LANGUAGE_CODE, &str)) {
        total_str = g_strdup_printf ("  language: %s\n", str);
        gtk_text_buffer_insert_at_cursor (text, total_str, -1);
        g_free (total_str);
        g_free (str);
      }
      gst_tag_list_free (tags);
    }
  }
}

/* This function is called when an "application" message is posted on the bus.
 * Here we retrieve the message posted by the tags_cb callback */
static void application_cb (GstBus *bus, GstMessage *msg, CustomData *data) {
  if (g_strcmp0 (gst_structure_get_name (gst_message_get_structure (msg)), "tags-changed") == 0) {
    /* If the message is the "tags-changed" (only one we are currently issuing), update
     * the stream info GUI */
    analyze_streams (data);
  }
}

int main(int argc, char *argv[]) {
  CustomData data;
  GstStateChangeReturn ret;
  GstBus *bus;

  /* Initialize GTK */
  gtk_init (&argc, &argv);

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

  /* Initialize our data structure */
  memset (&data, 0, sizeof (data));
  data.duration = GST_CLOCK_TIME_NONE;

  /* Create the elements */
  data.playbin = gst_element_factory_make ("playbin", "playbin");

  if (!data.playbin) {
    g_printerr ("Not all elements could be created.\n");
    return -1;
  }

  /* Set the URI to play */
  g_object_set (data.playbin, "uri", "https://gstreamer.freedesktop.org/data/media/sintel_trailer-480p.webm", NULL);

  /* Connect to interesting signals in playbin */
  g_signal_connect (G_OBJECT (data.playbin), "video-tags-changed", (GCallback) tags_cb, &data);
  g_signal_connect (G_OBJECT (data.playbin), "audio-tags-changed", (GCallback) tags_cb, &data);
  g_signal_connect (G_OBJECT (data.playbin), "text-tags-changed", (GCallback) tags_cb, &data);

  /* Create the GUI */
  create_ui (&data);

  /* Instruct the bus to emit signals for each received message, and connect to the interesting signals */
  bus = gst_element_get_bus (data.playbin);
  gst_bus_add_signal_watch (bus);
  g_signal_connect (G_OBJECT (bus), "message::error", (GCallback)error_cb, &data);
  g_signal_connect (G_OBJECT (bus), "message::eos", (GCallback)eos_cb, &data);
  g_signal_connect (G_OBJECT (bus), "message::state-changed", (GCallback)state_changed_cb, &data);
  g_signal_connect (G_OBJECT (bus), "message::application", (GCallback)application_cb, &data);
  gst_object_unref (bus);

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

  /* Register a function that GLib will call every second */
  g_timeout_add_seconds (1, (GSourceFunc)refresh_ui, &data);

  /* Start the GTK main loop. We will not regain control until gtk_main_quit is called. */
  gtk_main ();

  /* Free resources */
  gst_element_set_state (data.playbin, GST_STATE_NULL);
  gst_object_unref (data.playbin);
  return 0;
}

信息需要帮忙?

如果您需要帮助来编译此代码,请参阅针对您的平台构建教程 部分:LinuxMac OS XWindows,或在 Linux 上使用此特定命令:

gcc basic-tutorial-5.c -o basic-tutorial-5 pkg-config --cflags --libs gstreamer-video-1.0 gtk+-3.0 gstreamer-1.0``

如果您需要帮助来运行此代码,请参阅适用于您的平台的运行教程部分: LinuxMac OS XWindows

本教程打开一个 GTK+ 窗口并显示一个带有音频的电影。媒体是从 Internet 获取的,因此窗口可能需要几秒钟才会出现,具体取决于您的连接速度。窗口有一些 GTK+ 按钮来暂停、停止和播放电影,还有一个滑块来显示流的当前位置,可以拖动它来改变它。此外,有关流的信息显示在窗口右边缘的一列中。

请记住,没有延迟管理(缓冲),因此在连接速度较慢时,电影可能会在几秒钟后停止。查看基础教程 12:Streaming如何解决此问题。

所需的库:gstreamer-video-1.0 gtk+-3.0 gstreamer-1.0

演练

关于本教程的结构,我们将不再使用前向函数定义:函数将在使用前定义。此外,为了解释清楚起见,代码片段的呈现顺序并不总是与程序顺序相匹配。使用行号在完整代码中定位片段。

#include <gdk/gdk.h>
#if defined (GDK_WINDOWING_X11)
#include <gdk/gdkx.h>
#elif defined (GDK_WINDOWING_WIN32)
#include <gdk/gdkwin32.h>
#elif defined (GDK_WINDOWING_QUARTZ)
#include <gdk/gdkquartzwindow.h>
#endif

值得注意的第一件事是我们不再完全独立于平台。我们需要为我们将要使用的窗口系统包含适当的 GDK 头文件。幸运的是,支持的窗口系统并不多,所以这三行通常就足够了:Linux 的 X11、Windows 的 Win32 和 Mac OSX 的 Quartz。

本教程主要由回调函数组成,这些回调函数将从 GStreamer 或 GTK+ 调用,所以让我们回顾一下main注册所有这些回调的函数。

int main(int argc, char *argv[]) {
  CustomData data;
  GstStateChangeReturn ret;
  GstBus *bus;

  /* Initialize GTK */
  gtk_init (&argc, &argv);

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

  /* Initialize our data structure */
  memset (&data, 0, sizeof (data));
  data.duration = GST_CLOCK_TIME_NONE;

  /* Create the elements */
  data.playbin = gst_element_factory_make ("playbin", "playbin");

  if (!data.playbin) {
    g_printerr ("Not all elements could be created.\n");
    return -1;
  }

  /* Set the URI to play */
  g_object_set (data.playbin, "uri", "https://gstreamer.freedesktop.org/data/media/sintel_trailer-480p.webm", NULL);

标准 GStreamer 初始化和 playbin 管道创建,以及 GTK+ 初始化。没有多少新意。

  /* Connect to interesting signals in playbin */
  g_signal_connect (G_OBJECT (data.playbin), "video-tags-changed", (GCallback) tags_cb, &data);
  g_signal_connect (G_OBJECT (data.playbin), "audio-tags-changed", (GCallback) tags_cb, &data);
  g_signal_connect (G_OBJECT (data.playbin), "text-tags-changed", (GCallback) tags_cb, &data);

我们有兴趣在流中出现新标签(元数据)时收到通知。为简单起见,我们将处理来自同一个回调的各种标签(视频、音频和文本)tags_cb

/* Create the GUI */
create_ui (&data);

所有 GTK+ 小部件的创建和信号注册都发生在这个函数中。它只包含与 GTK 相关的函数调用,因此我们将跳过它的定义。它注册的信号传达用户命令,如下面查看回调时所示。

  /* Instruct the bus to emit signals for each received message, and connect to the interesting signals */
  bus = gst_element_get_bus (data.playbin);
  gst_bus_add_signal_watch (bus);
  g_signal_connect (G_OBJECT (bus), "message::error", (GCallback)error_cb, &data);
  g_signal_connect (G_OBJECT (bus), "message::eos", (GCallback)eos_cb, &data);
  g_signal_connect (G_OBJECT (bus), "message::state-changed", (GCallback)state_changed_cb, &data);
  g_signal_connect (G_OBJECT (bus), "message::application", (GCallback)application_cb, &data);
  gst_object_unref (bus);

Playback tutorial 1: Playbin usage中,gst_bus_add_watch()用于注册一个函数,该函数接收发布到 GStreamer 总线的每条消息。我们可以通过使用信号来实现更精细的粒度,这允许我们只注册我们感兴趣的消息。通过调用,gst_bus_add_signal_watch()我们指示总线在每次收到消息时发出信号。此信号的名称message::detaildetail触发信号发射的消息。例如,当总线收到 EOS 消息时,它会发出一个名为 的信号message::eos

This tutorial is using the Signals's details to register only to the messages we care about. If we had registered to the message signal, we would be notified of every single message, just like gst_bus_add_watch() would do.

Keep in mind that, in order for the bus watches to work (be it a gst_bus_add_watch() or a gst_bus_add_signal_watch()), there must be GLib Main Loop running. In this case, it is hidden inside the GTK+ main loop.

/* Register a function that GLib will call every second */
g_timeout_add_seconds (1, (GSourceFunc)refresh_ui, &data);

Before transferring control to GTK+, we use g_timeout_add_seconds () to register yet another callback, this time with a timeout, so it gets called every second. We are going to use it to refresh the GUI from the refresh_ui function.

After this, we are done with the setup and can start the GTK+ main loop. We will regain control from our callbacks when interesting things happen. Let's review the callbacks. Each callback has a different signature, depending on who will call it. You can look up the signature (the meaning of the parameters and the return value) in the documentation of the signal.

/* This function is called when the GUI toolkit creates the physical window that will hold the video.
 * At this point we can retrieve its handler (which has a different meaning depending on the windowing system)
 * and pass it to GStreamer through the VideoOverlay interface. */
static void realize_cb (GtkWidget *widget, CustomData *data) {
  GdkWindow *window = gtk_widget_get_window (widget);
  guintptr window_handle;

  if (!gdk_window_ensure_native (window))
    g_error ("Couldn't create native window needed for GstVideoOverlay!");

  /* Retrieve window handler from GDK */
#if defined (GDK_WINDOWING_WIN32)
  window_handle = (guintptr)GDK_WINDOW_HWND (window);
#elif defined (GDK_WINDOWING_QUARTZ)
  window_handle = gdk_quartz_window_get_nsview (window);
#elif defined (GDK_WINDOWING_X11)
  window_handle = GDK_WINDOW_XID (window);
#endif
  /* Pass it to playbin, which implements VideoOverlay and will forward it to the video sink */
  gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (data->playbin), window_handle);
}

The code comments talks by itself. At this point in the life cycle of the application, we know the handle (be it an X11's XID, a Window's HWND or a Quartz's NSView) of the window where GStreamer should render the video. We simply retrieve it from the windowing system and pass it to playbin through the GstVideoOverlay interface using gst_video_overlay_set_window_handle(). playbin will locate the video sink and pass the handler to it, so it does not create its own window and uses this one.

Not much more to see here; playbin and the GstVideoOverlay really simplify this process a lot!

/* This function is called when the PLAY button is clicked */
static void play_cb (GtkButton *button, CustomData *data) {
  gst_element_set_state (data->playbin, GST_STATE_PLAYING);
}

/* This function is called when the PAUSE button is clicked */
static void pause_cb (GtkButton *button, CustomData *data) {
  gst_element_set_state (data->playbin, GST_STATE_PAUSED);
}

/* This function is called when the STOP button is clicked */
static void stop_cb (GtkButton *button, CustomData *data) {
  gst_element_set_state (data->playbin, GST_STATE_READY);
}

These three little callbacks are associated with the PLAY, PAUSE and STOP buttons in the GUI. They simply set the pipeline to the corresponding state. Note that in the STOP state we set the pipeline to READY. We could have brought the pipeline all the way down to the NULL state, but, the transition would then be a little slower, since some resources (like the audio device) would need to be released and re-acquired.

/* This function is called when the main window is closed */
static void delete_event_cb (GtkWidget *widget, GdkEvent *event, CustomData *data) {
  stop_cb (NULL, data);
  gtk_main_quit ();
}

gtk_main_quit() will eventually make the call to to gtk_main_run() in main to terminate, which, in this case, finishes the program. Here, we call it when the main window is closed, after stopping the pipeline (just for the sake of tidiness).

/* This function is called everytime the video window needs to be redrawn (due to damage/exposure,
 * rescaling, etc). GStreamer takes care of this in the PAUSED and PLAYING states, otherwise,
 * we simply draw a black rectangle to avoid garbage showing up. */
static gboolean draw_cb (GtkWidget *widget, cairo_t *cr, CustomData *data) {
  if (data->state < GST_STATE_PAUSED) {
    GtkAllocation allocation;

    /* Cairo is a 2D graphics library which we use here to clean the video window.
     * It is used by GStreamer for other reasons, so it will always be available to us. */
    gtk_widget_get_allocation (widget, &allocation);
    cairo_set_source_rgb (cr, 0, 0, 0);
    cairo_rectangle (cr, 0, 0, allocation.width, allocation.height);
    cairo_fill (cr);
  }

  return FALSE;
}

When there is data flow (in the PAUSED and PLAYING states) the video sink takes care of refreshing the content of the video window. In the other cases, however, it will not, so we have to do it. In this example, we just fill the window with a black rectangle.

/* This function is called when the slider changes its position. We perform a seek to the
 * new position here. */
static void slider_cb (GtkRange *range, CustomData *data) {
  gdouble value = gtk_range_get_value (GTK_RANGE (data->slider));
  gst_element_seek_simple (data->playbin, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT,
      (gint64)(value * GST_SECOND));
}

This is an example of how a complex GUI element like a seeker bar (or slider that allows seeking) can be very easily implemented thanks to GStreamer and GTK+ collaborating. If the slider has been dragged to a new position, tell GStreamer to seek to that position with gst_element_seek_simple() (as seen in Basic tutorial 4: Time management). The slider has been setup so its value represents seconds.

It is worth mentioning that some performance (and responsiveness) can be gained by doing some throttling, this is, not responding to every single user request to seek. Since the seek operation is bound to take some time, it is often nicer to wait half a second (for example) after a seek before allowing another one. Otherwise, the application might look unresponsive if the user drags the slider frantically, which would not allow any seek to complete before a new one is queued.

/* This function is called periodically to refresh the GUI */
static gboolean refresh_ui (CustomData *data) {
  gint64 current = -1;

  /* We do not want to update anything unless we are in the PAUSED or PLAYING states */
  if (data->state < GST_STATE_PAUSED)
    return TRUE;

This function will move the slider to reflect the current position of the media. First off, if we are not in the PLAYING state, we have nothing to do here (plus, position and duration queries will normally fail).

/* If we didn't know it yet, query the stream duration */
if (!GST_CLOCK_TIME_IS_VALID (data->duration)) {
  if (!gst_element_query_duration (data->playbin, GST_FORMAT_TIME, &data->duration)) {
    g_printerr ("Could not query current duration.\n");
  } else {
    /* Set the range of the slider to the clip duration, in SECONDS */
    gtk_range_set_range (GTK_RANGE (data->slider), 0, (gdouble)data->duration / GST_SECOND);
  }
}

We recover the duration of the clip if we didn't know it, so we can set the range for the slider.

if (gst_element_query_position (data->playbin, GST_FORMAT_TIME, &current)) {
  /* Block the "value-changed" signal, so the slider_cb function is not called
   * (which would trigger a seek the user has not requested) */
  g_signal_handler_block (data->slider, data->slider_update_signal_id);
  /* Set the position of the slider to the current pipeline position, in SECONDS */
  gtk_range_set_value (GTK_RANGE (data->slider), (gdouble)current / GST_SECOND);
  /* Re-enable the signal */
  g_signal_handler_unblock (data->slider, data->slider_update_signal_id);
}
return TRUE;

We query the current pipeline position, and set the position of the slider accordingly. This would trigger the emission of the value-changed signal, which we use to know when the user is dragging the slider. Since we do not want seeks happening unless the user requested them, we disable the value-changed signal emission during this operation with g_signal_handler_block() and g_signal_handler_unblock().

Returning TRUE from this function will keep it called in the future. If we return FALSE, the timer will be removed.

/* This function is called when new metadata is discovered in the stream */
static void tags_cb (GstElement *playbin, gint stream, CustomData *data) {
  /* We are possibly in a GStreamer working thread, so we notify the main
   * thread of this event through a message in the bus */
  gst_element_post_message (playbin,
    gst_message_new_application (GST_OBJECT (playbin),
      gst_structure_new_empty ("tags-changed")));
}

This is one of the key points of this tutorial. This function will be called when new tags are found in the media, from a streaming thread, this is, from a thread other than the application (or main) thread. What we want to do here is to update a GTK+ widget to reflect this new information, but GTK+ does not allow operating from threads other than the main one.

The solution is to make playbin post a message on the bus and return to the calling thread. When appropriate, the main thread will pick up this message and update GTK.

gst_element_post_message()使 GStreamer 元素将给定消息发布到总线。gst_message_new_application()创建该类型的新消息APPLICATION。GStreamer 消息有不同的类型,这种特定类型是为应用程序保留的:它将通过总线不受 GStreamer 的影响。类型列表可以在GstMessageType文档中找到。

消息可以通过其嵌入的 传递附加信息 GstStructure,这是一个非常灵活的数据容器。在这里,我们创建了一个新结构gst_structure_new(),并将其命名为tags-changed,以避免在我们想要发送其他应用程序消息时发生混淆。

稍后,一旦进入主线程,总线将收到此消息并发出信号message::application,我们已将其关联到函数 application_cb

/* This function is called when an "application" message is posted on the bus.
 * Here we retrieve the message posted by the tags_cb callback */
static void application_cb (GstBus *bus, GstMessage *msg, CustomData *data) {
  if (g_strcmp0 (gst_structure_get_name (gst_message_get_structure (msg)), "tags-changed") == 0) {
    /* If the message is the "tags-changed" (only one we are currently issuing), update
     * the stream info GUI */
    analyze_streams (data);
  }
}

一旦我确定它是tags-changed消息,我们就调用该 analyze_streams函数,该函数也在播放教程 1:Playbin 用法中使用,并且在那里更详细。它基本上从流中恢复标签并将它们写入 GUI 中的文本小部件中。

error_cb,和真的不值得解释,因为它们与之前所有教程中的eos_cb功能state_changed_cb相同,但现在来自它们自己的功能。

就是这样!本教程中的代码量可能看起来令人生畏,但所需的概念却很少而且很简单。如果您遵循了前面的教程并且对 GTK 有一点了解,那么您可能已经了解了这个,现在可以享受您自己的媒体播放器了!

img

锻炼

如果此媒体播放器对您来说不够好,请尝试将显示有关流的信息的文本小部件更改为适当的列表视图(或树视图)。然后,当用户选择不同的流时,让 GStreamer 切换流!要切换流,您需要阅读播放教程 1:Playbin 使用

结论

本教程展示了:

  • 如何使用 将视频输出到特定窗口句柄gst_video_overlay_set_window_handle()
  • 如何通过注册超时回调来定期刷新 GUI g_timeout_add_seconds ()
  • 如何通过总线以应用程序消息的方式向主线程传递信息gst_element_post_message()
  • 如何通过使总线发出信号并gst_bus_add_signal_watch()使用信号详细信息区分所有消息类型来仅通知感兴趣的消息。

这允许您使用适当的图形用户界面构建一个比较完整的媒体播放器。

以下基本教程继续关注其他个别 GStreamer 主题

很高兴有你在这里,很快再见!

基础教程 6:媒体格式和 Pad Capabilities

目标

Pad Capabilities 是 GStreamer 的基本元素,尽管大多数时候它们是不可见的,因为框架会自动处理它们。这个有点理论的教程显示:

  • 什么是打击垫功能。
  • 如何找回它们。
  • 何时取回它们。
  • 为什么你需要了解它们。

介绍

如前所述,Pad 允许信息进入和离开元素。然后,Pad的 Capabilities(或简称Caps )指定了什么样的信息可以通过 Pad。例如,“分辨率为 320x200 像素和每秒 30 帧的 RGB 视频”,或“每个样本音频 16 位,每秒 44100 个样本的 5.1 通道”,甚至压缩格式,如 mp3 或 h264。

Pad 可以支持多种功能(例如,视频接收器可以支持不同类型的 RGB 或 YUV 格式的视频)并且功能可以指定为范围(例如,音频接收器可以支持每秒 1 到 48000 个样本的采样率) . 然而,从 Pad 到 Pad 的实际信息必须只有一种明确指定的类型。通过称为协商的过程,两个链接的 Pad 就共同类型达成一致,因此 Pad 的 Capabilities 变得固定 (它们只有一种类型并且不包含范围)。下面的示例代码的演练应该使所有这些都清楚。

为了将两个元素链接在一起,它们必须共享一个共同的能力子集(否则它们不可能相互理解)。这是Capabilities的主要目标。

作为应用程序开发人员,您通常会通过将元素链接在一起来构建管道(如果您使用 all-in-all 元素,则程度较小playbin)。在这种情况下,您需要知道元素的Pad Caps(正如人们熟悉的那样),或者至少知道当 GStreamer 拒绝链接两个具有协商错误的元素时它们是什么。

垫模板

Pad 是从Pad Templates创建的,它指示 Pad 可能拥有的所有可能功能。模板对于创建多个相似的 Pad 很有用,也允许提前拒绝元素之间的连接:如果它们的 Pad Templates 的 Capabilities 没有公共子集(它们的交集为空),则无需进一步协商。

Pad Templates 可以被视为协商过程的第一步。随着过程的发展,实际的 Pad 被实例化并且它们的 Capabilities 被改进直到它们被修复(或协商失败)。

功能示例

SINK template: 'sink'
  Availability: Always
  Capabilities:
    audio/x-raw
               format: S16LE
                 rate: [ 1, 2147483647 ]
             channels: [ 1, 2 ]
    audio/x-raw
               format: U8
                 rate: [ 1, 2147483647 ]
             channels: [ 1, 2 ]

这个 pad 是一个水槽,在元素上总是可用的(我们现在不会谈论可用性)。它支持两种媒体,都是整数格式 ( ) 的原始音频audio/x-raw:有符号的 16 位小端和无符号的 8 位。方括号表示一个范围:例如,通道数从 1 到 2 不等。

SRC template: 'src'
  Availability: Always
  Capabilities:
    video/x-raw
                width: [ 1, 2147483647 ]
               height: [ 1, 2147483647 ]
            framerate: [ 0/1, 2147483647/1 ]
               format: { I420, NV12, NV21, YV12, YUY2, Y42B, Y444, YUV9, YVU9, Y41B, Y800, Y8, GREY, Y16 , UYVY, YVYU, IYU1, v308, AYUV, A420 }

video/x-raw表示此源板输出原始视频。它支持广泛的尺寸和帧率,以及一组 YUV 格式(花括号表示列表。所有这些格式都表示图像平面的不同打包和子采样。

最后的评论

您可以使用基础教程 10:GStreamer 工具gst-inspect-1.0中描述的工具来了解任何 GStreamer 元素的 Caps。

请记住,某些元素会查询底层硬件以获取支持的格式并相应地提供它们的 Pad Caps(它们通常在进入 READY 状态或更高状态时执行此操作)。因此,显示的上限可能因平台而异,甚至因一次执行而异(尽管这种情况很少见)。

本教程实例化两个元素(这次,通过它们的工厂),显示它们的 Pad 模板,链接它们并设置要播放的管道。在每次状态更改时,都会显示接收器元素的 Pad 的 Capabilities,因此您可以观察协商如何进行,直到 Pad Caps 固定为止。

一个简单的 Pad 功能示例

将此代码复制到名为的文本文件中basic-tutorial-6.c(或在您的 GStreamer 安装中找到它)。

basic-tutorial-6.c

#include <gst/gst.h>

/* Functions below print the Capabilities in a human-friendly format */
static gboolean print_field (GQuark field, const GValue * value, gpointer pfx) {
  gchar *str = gst_value_serialize (value);

  g_print ("%s  %15s: %s\n", (gchar *) pfx, g_quark_to_string (field), str);
  g_free (str);
  return TRUE;
}

static void print_caps (const GstCaps * caps, const gchar * pfx) {
  guint i;

  g_return_if_fail (caps != NULL);

  if (gst_caps_is_any (caps)) {
    g_print ("%sANY\n", pfx);
    return;
  }
  if (gst_caps_is_empty (caps)) {
    g_print ("%sEMPTY\n", pfx);
    return;
  }

  for (i = 0; i < gst_caps_get_size (caps); i++) {
    GstStructure *structure = gst_caps_get_structure (caps, i);

    g_print ("%s%s\n", pfx, gst_structure_get_name (structure));
    gst_structure_foreach (structure, print_field, (gpointer) pfx);
  }
}

/* Prints information about a Pad Template, including its Capabilities */
static void print_pad_templates_information (GstElementFactory * factory) {
  const GList *pads;
  GstStaticPadTemplate *padtemplate;

  g_print ("Pad Templates for %s:\n", gst_element_factory_get_longname (factory));
  if (!gst_element_factory_get_num_pad_templates (factory)) {
    g_print ("  none\n");
    return;
  }

  pads = gst_element_factory_get_static_pad_templates (factory);
  while (pads) {
    padtemplate = pads->data;
    pads = g_list_next (pads);

    if (padtemplate->direction == GST_PAD_SRC)
      g_print ("  SRC template: '%s'\n", padtemplate->name_template);
    else if (padtemplate->direction == GST_PAD_SINK)
      g_print ("  SINK template: '%s'\n", padtemplate->name_template);
    else
      g_print ("  UNKNOWN!!! template: '%s'\n", padtemplate->name_template);

    if (padtemplate->presence == GST_PAD_ALWAYS)
      g_print ("    Availability: Always\n");
    else if (padtemplate->presence == GST_PAD_SOMETIMES)
      g_print ("    Availability: Sometimes\n");
    else if (padtemplate->presence == GST_PAD_REQUEST)
      g_print ("    Availability: On request\n");
    else
      g_print ("    Availability: UNKNOWN!!!\n");

    if (padtemplate->static_caps.string) {
      GstCaps *caps;
      g_print ("    Capabilities:\n");
      caps = gst_static_caps_get (&padtemplate->static_caps);
      print_caps (caps, "      ");
      gst_caps_unref (caps);

    }

    g_print ("\n");
  }
}

/* Shows the CURRENT capabilities of the requested pad in the given element */
static void print_pad_capabilities (GstElement *element, gchar *pad_name) {
  GstPad *pad = NULL;
  GstCaps *caps = NULL;

  /* Retrieve pad */
  pad = gst_element_get_static_pad (element, pad_name);
  if (!pad) {
    g_printerr ("Could not retrieve pad '%s'\n", pad_name);
    return;
  }

  /* Retrieve negotiated caps (or acceptable caps if negotiation is not finished yet) */
  caps = gst_pad_get_current_caps (pad);
  if (!caps)
    caps = gst_pad_query_caps (pad, NULL);

  /* Print and free */
  g_print ("Caps for the %s pad:\n", pad_name);
  print_caps (caps, "      ");
  gst_caps_unref (caps);
  gst_object_unref (pad);
}

int main(int argc, char *argv[]) {
  GstElement *pipeline, *source, *sink;
  GstElementFactory *source_factory, *sink_factory;
  GstBus *bus;
  GstMessage *msg;
  GstStateChangeReturn ret;
  gboolean terminate = FALSE;

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

  /* Create the element factories */
  source_factory = gst_element_factory_find ("audiotestsrc");
  sink_factory = gst_element_factory_find ("autoaudiosink");
  if (!source_factory || !sink_factory) {
    g_printerr ("Not all element factories could be created.\n");
    return -1;
  }

  /* Print information about the pad templates of these factories */
  print_pad_templates_information (source_factory);
  print_pad_templates_information (sink_factory);

  /* Ask the factories to instantiate actual elements */
  source = gst_element_factory_create (source_factory, "source");
  sink = gst_element_factory_create (sink_factory, "sink");

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

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

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

  /* Print initial negotiated caps (in NULL state) */
  g_print ("In NULL state:\n");
  print_pad_capabilities (sink, "sink");

  /* 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 (check the bus for error messages).\n");
  }

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

    /* 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);
          terminate = TRUE;
          break;
        case GST_MESSAGE_EOS:
          g_print ("End-Of-Stream reached.\n");
          terminate = TRUE;
          break;
        case GST_MESSAGE_STATE_CHANGED:
          /* We are only interested in state-changed messages from the pipeline */
          if (GST_MESSAGE_SRC (msg) == GST_OBJECT (pipeline)) {
            GstState old_state, new_state, pending_state;
            gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state);
            g_print ("\nPipeline state changed from %s to %s:\n",
                gst_element_state_get_name (old_state), gst_element_state_get_name (new_state));
            /* Print the current capabilities of the sink element */
            print_pad_capabilities (sink, "sink");
          }
          break;
        default:
          /* We should not reach here because we only asked for ERRORs, EOS and STATE_CHANGED */
          g_printerr ("Unexpected message received.\n");
          break;
      }
      gst_message_unref (msg);
    }
  } while (!terminate);

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

信息需要帮忙?

如果您需要帮助来编译此代码,请参阅针对您的平台构建教程 部分:LinuxMac OS XWindows,或在 Linux 上使用此特定命令:

gcc basic-tutorial-6.c -o basic-tutorial-6 pkg-config --cflags --libs gstreamer-1.0``

如果您需要帮助来运行此代码,请参阅适用于您的平台的运行教程部分: LinuxMac OS XWindows

本教程仅显示有关不同时刻 Pad Capabilities 的信息。

所需的库:gstreamer-1.0

演练

print_field,并以人性化的格式简单地显示功能结构print_capsprint_pad_templates如果您想了解结构的内部组织 GstCaps,请阅读GStreamer Documentation有关 Pad Caps 的内容。

/* Shows the CURRENT capabilities of the requested pad in the given element */
static void print_pad_capabilities (GstElement *element, gchar *pad_name) {
  GstPad *pad = NULL;
  GstCaps *caps = NULL;

  /* Retrieve pad */
  pad = gst_element_get_static_pad (element, pad_name);
  if (!pad) {
    g_printerr ("Could not retrieve pad '%s'\n", pad_name);
    return;
  }

  /* Retrieve negotiated caps (or acceptable caps if negotiation is not finished yet) */
  caps = gst_pad_get_current_caps (pad);
  if (!caps)
    caps = gst_pad_query_caps (pad, NULL);

  /* Print and free */
  g_print ("Caps for the %s pad:\n", pad_name);
  print_caps (caps, "      ");
  gst_caps_unref (caps);
  gst_object_unref (pad);
}

gst_element_get_static_pad()从给定元素中检索命名 Pad。这个 Pad 是静态的,因为它总是存在于元素中。要了解有关 Pad 可用性的更多信息,请阅读GStreamer documentation关于 Pad 的信息。

然后我们调用gst_pad_get_current_caps()来检索 Pad 的当前 Capabilities,它可以固定也可以不固定,这取决于协商过程的状态。它们甚至可能不存在,在这种情况下,我们调用gst_pad_query_caps()以检索当前可接受的 Pad Capabilities。当前可接受的 Caps 将是 Pad Template 处于 NULL 状态的 Caps,但在以后的状态中可能会发生变化,因为可能会查询实际的硬件 Capabilities。

然后我们打印这些能力。

/* Create the element factories */
source_factory = gst_element_factory_find ("audiotestsrc");
sink_factory = gst_element_factory_find ("autoaudiosink");
if (!source_factory || !sink_factory) {
  g_printerr ("Not all element factories could be created.\n");
  return -1;
}

/* Print information about the pad templates of these factories */
print_pad_templates_information (source_factory);
print_pad_templates_information (sink_factory);

/* Ask the factories to instantiate actual elements */
source = gst_element_factory_create (source_factory, "source");
sink = gst_element_factory_create (sink_factory, "sink");

在之前的教程中,我们直接使用创建元素 gst_element_factory_make()并跳过了工厂的讨论,但我们现在就来做。AGstElementFactory负责实例化特定类型的元素,由其工厂名称标识。

您可以使用gst_element_factory_find()创建一个类型为“videotestsrc”的工厂,然后使用它来实例化多个“videotestsrc”元素gst_element_factory_create()。 确实是+gst_element_factory_make()的快捷方式 。gst_element_factory_find()``gst_element_factory_create()

Pad Templates 已经可以通过工厂访问,所以一旦工厂被创建,它们就会被打印出来。

我们跳过管道的创建和启动,进入State-Changed消息处理:

case GST_MESSAGE_STATE_CHANGED:
  /* We are only interested in state-changed messages from the pipeline */
  if (GST_MESSAGE_SRC (msg) == GST_OBJECT (pipeline)) {
    GstState old_state, new_state, pending_state;
    gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state);
    g_print ("\nPipeline state changed from %s to %s:\n",
        gst_element_state_get_name (old_state), gst_element_state_get_name (new_state));
    /* Print the current capabilities of the sink element */
    print_pad_capabilities (sink, "sink");
  }
  break;

这只是在每次管道状态发生变化时打印当前 Pad Caps。您应该在输出中看到初始大写字母(Pad 模板的大写字母)是如何逐渐细化直到它们完全固定(它们包含没有范围的单一类型)。

结论

本教程展示了:

  • 什么是 Pad Capabilities 和 Pad Template Capabilities。
  • 如何使用gst_pad_get_current_caps()或检索它们gst_pad_query_caps()
  • 根据管道的状态,它们具有不同的含义(最初它们表示所有可能的功能,后来它们表示当前为 Pad 协商的上限)。
  • 如果两个元素可以链接在一起,那么 Pad Caps 很重要。
  • 可以使用基础教程 10:GStreamer 工具gst-inspect-1.0中描述的工具找到 Pad Caps 。

下一教程将展示如何手动将数据注入 GStreamer 管道和从中提取数据。

请记住,在本页的附件中,您应该找到教程的完整源代码以及构建它所需的任何附件文件。很高兴有你在这里,很快再见!

基础教程7:多线程和Pad Availability

目标

GStreamer 自动处理多线程,但在某些情况下,您可能需要手动分离线程。本教程展示了如何执行此操作,此外,还完成了有关 Pad 可用性的说明。更准确地说,该文档解释了:

  • 如何为管道的某些部分创建新的执行线程
  • 什么是 Pad 可用性
  • 如何复制流

介绍

多线程

GStreamer 是一个多线程框架。这意味着,在内部,它会根据需要创建和销毁线程,例如,将流与应用程序线程分离。此外,插件还可以自由地为自己的处理创建线程,例如,视频解码器可以创建 4 个线程以充分利用 4 核 CPU。

最重要的是,在构建管道时,应用程序可以明确指定一个分支(管道的一部分)在不同的线程上运行(例如,同时执行音频和视频解码器)。

这是使用queue元素完成的,其工作方式如下。sink pad 只是将数据排入队列并返回控制。在不同的线程上,数据被出列并推送到下游。该元素还用于缓冲,如稍后在流式教程中所见。队列的大小可以通过属性来控制。

示例管道

此示例构建以下管道:

img

源是合成音频信号(连续音调),它使用一个tee元素进行拆分(它通过其源垫发送通过其接收器垫接收的所有内容)。然后一个分支将信号发送到声卡,另一个分支渲染波形视频并将其发送到屏幕。

如图所示,队列创建一个新线程,因此该管道在 3 个线程中运行。具有多个接收器的管道通常需要是多线程的,因为为了同步,接收器通常会阻塞执行,直到所有其他接收器都准备就绪,如果只有一个线程,它们将无法准备就绪,被第一个接收器阻塞。

请求垫

基础教程 3:动态管道中,我们看到了一个uridecodebin没有填充的元素 ( ),它们在数据开始流动并且元素了解媒体时出现。这些称为Sometimes Pads,与始终可用且称为Always Pads 的常规垫形成对比。

第三种 pad 是Request Pad,它是按需创建的。经典的例子是tee元素,它有一个 sink pad 而没有初始 source pad:它们需要被请求然后 tee添加它们。通过这种方式,输入流可以被复制任意次数。缺点是使用 Request Pads 链接元素不像链接 Always Pads 那样自动,如本示例的演练所示。

此外,要在PLAYINGPAUSED状态下请求(或释放)焊盘,您需要采取本教程中未描述的额外注意事项(焊盘阻塞)。NULL不过,在或 状态下请求(或释放)pad 是安全的READY

事不宜迟,让我们看看代码。

简单的多线程示例

将此代码复制到名为的文本文件中basic-tutorial-7.c(或在您的 GStreamer 安装中找到它)。

basic-tutorial-7.c

#include <gst/gst.h>

int main(int argc, char *argv[]) {
  GstElement *pipeline, *audio_source, *tee, *audio_queue, *audio_convert, *audio_resample, *audio_sink;
  GstElement *video_queue, *visual, *video_convert, *video_sink;
  GstBus *bus;
  GstMessage *msg;
  GstPad *tee_audio_pad, *tee_video_pad;
  GstPad *queue_audio_pad, *queue_video_pad;

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

  /* Create the elements */
  audio_source = gst_element_factory_make ("audiotestsrc", "audio_source");
  tee = gst_element_factory_make ("tee", "tee");
  audio_queue = gst_element_factory_make ("queue", "audio_queue");
  audio_convert = gst_element_factory_make ("audioconvert", "audio_convert");
  audio_resample = gst_element_factory_make ("audioresample", "audio_resample");
  audio_sink = gst_element_factory_make ("autoaudiosink", "audio_sink");
  video_queue = gst_element_factory_make ("queue", "video_queue");
  visual = gst_element_factory_make ("wavescope", "visual");
  video_convert = gst_element_factory_make ("videoconvert", "csp");
  video_sink = gst_element_factory_make ("autovideosink", "video_sink");

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

  if (!pipeline || !audio_source || !tee || !audio_queue || !audio_convert || !audio_resample || !audio_sink ||
      !video_queue || !visual || !video_convert || !video_sink) {
    g_printerr ("Not all elements could be created.\n");
    return -1;
  }

  /* Configure elements */
  g_object_set (audio_source, "freq", 215.0f, NULL);
  g_object_set (visual, "shader", 0, "style", 1, NULL);

  /* Link all elements that can be automatically linked because they have "Always" pads */
  gst_bin_add_many (GST_BIN (pipeline), audio_source, tee, audio_queue, audio_convert, audio_resample, audio_sink,
      video_queue, visual, video_convert, video_sink, NULL);
  if (gst_element_link_many (audio_source, tee, NULL) != TRUE ||
      gst_element_link_many (audio_queue, audio_convert, audio_resample, audio_sink, NULL) != TRUE ||
      gst_element_link_many (video_queue, visual, video_convert, video_sink, NULL) != TRUE) {
    g_printerr ("Elements could not be linked.\n");
    gst_object_unref (pipeline);
    return -1;
  }

  /* Manually link the Tee, which has "Request" pads */
  tee_audio_pad = gst_element_request_pad_simple (tee, "src_%u");
  g_print ("Obtained request pad %s for audio branch.\n", gst_pad_get_name (tee_audio_pad));
  queue_audio_pad = gst_element_get_static_pad (audio_queue, "sink");
  tee_video_pad = gst_element_request_pad_simple (tee, "src_%u");
  g_print ("Obtained request pad %s for video branch.\n", gst_pad_get_name (tee_video_pad));
  queue_video_pad = gst_element_get_static_pad (video_queue, "sink");
  if (gst_pad_link (tee_audio_pad, queue_audio_pad) != GST_PAD_LINK_OK ||
      gst_pad_link (tee_video_pad, queue_video_pad) != GST_PAD_LINK_OK) {
    g_printerr ("Tee could not be linked.\n");
    gst_object_unref (pipeline);
    return -1;
  }
  gst_object_unref (queue_audio_pad);
  gst_object_unref (queue_video_pad);

  /* Start playing the pipeline */
  gst_element_set_state (pipeline, GST_STATE_PLAYING);

  /* 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);

  /* Release the request pads from the Tee, and unref them */
  gst_element_release_request_pad (tee, tee_audio_pad);
  gst_element_release_request_pad (tee, tee_video_pad);
  gst_object_unref (tee_audio_pad);
  gst_object_unref (tee_video_pad);

  /* Free resources */
  if (msg != NULL)
    gst_message_unref (msg);
  gst_object_unref (bus);
  gst_element_set_state (pipeline, GST_STATE_NULL);

  gst_object_unref (pipeline);
  return 0;
}

信息需要帮忙?

如果您需要帮助来编译此代码,请参阅针对您的平台构建教程 部分:LinuxMac OS XWindows,或在 Linux 上使用此特定命令:

gcc basic-tutorial-7.c -o basic-tutorial-7 pkg-config --cflags --libs gstreamer-1.0``

如果您需要帮助来运行此代码,请参阅适用于您的平台的运行教程部分: LinuxMac OS XWindows

本教程通过声卡播放可听见的音调,并打开一个窗口,其中包含该音调的波形表示。波形应该是正弦波,但由于窗口的刷新可能不会出现。

所需的库:gstreamer-1.0

演练

/* Create the elements */
audio_source = gst_element_factory_make ("audiotestsrc", "audio_source");
tee = gst_element_factory_make ("tee", "tee");
audio_queue = gst_element_factory_make ("queue", "audio_queue");
audio_convert = gst_element_factory_make ("audioconvert", "audio_convert");
audio_resample = gst_element_factory_make ("audioresample", "audio_resample");
audio_sink = gst_element_factory_make ("autoaudiosink", "audio_sink");
video_queue = gst_element_factory_make ("queue", "video_queue");
visual = gst_element_factory_make ("wavescope", "visual");
video_convert = gst_element_factory_make ("videoconvert", "video_convert");
video_sink = gst_element_factory_make ("autovideosink", "video_sink");

上图中的所有元素都在这里实例化:

audiotestsrc产生合成音。wavescope消耗音频信号并呈现波形,就好像它是一个(公认便宜的)示波器一样。我们已经与autoaudiosinkand 合作过autovideosink

转换元素 ( audioconvert,audioresamplevideoconvert) 是确保管道可以链接所必需的。实际上,音频和视频接收器的功能取决于硬件,并且您在设计时不知道它们是否与 和 生成的 Caps 相audiotestsrc匹配wavescope。但是,如果 Caps 匹配,这些元素将以“直通”模式运行并且不会修改信号,对性能的影响可以忽略不计。

/* Configure elements */
g_object_set (audio_source, "freq", 215.0f, NULL);
g_object_set (visual, "shader", 0, "style", 1, NULL);

为更好地演示而进行的小调整:“freq”属性 audiotestsrc控制波浪的频率(215Hz 使波浪在窗口中看起来几乎是静止的),这种样式和着色器使 wavescope波浪连续。使用基础教程 10:GStreamer 工具gst-inspect-1.0中描述的工具来了解这些元素的所有属性。

/* Link all elements that can be automatically linked because they have "Always" pads */
gst_bin_add_many (GST_BIN (pipeline), audio_source, tee, audio_queue, audio_convert, audio_sink,
    video_queue, visual, video_convert, video_sink, NULL);
if (gst_element_link_many (audio_source, tee, NULL) != TRUE ||
    gst_element_link_many (audio_queue, audio_convert, audio_sink, NULL) != TRUE ||
    gst_element_link_many (video_queue, visual, video_convert, video_sink, NULL) != TRUE) {
  g_printerr ("Elements could not be linked.\n");
  gst_object_unref (pipeline);
  return -1;
}

此代码块将所有元素添加到管道,然后链接可以自动链接的元素(如评论所述,具有 Always Pads 的元素)。

警告gst_element_link_many()实际上可以将元素与 Request Pads 链接起来。它在内部请求 Pad,因此您不必担心被链接的元素具有 Always 或 Request Pads。看起来很奇怪,其实很不方便,因为之后还需要释放请求的Pad,而且,如果Pad是自动请求的gst_element_link_many(),很容易忘记。通过始终手动请求 Request Pads 来避免麻烦,如下一个代码块所示。

/* Manually link the Tee, which has "Request" pads */
tee_audio_pad = gst_element_request_pad_simple (tee, "src_%u");
g_print ("Obtained request pad %s for audio branch.\n", gst_pad_get_name (tee_audio_pad));
queue_audio_pad = gst_element_get_static_pad (audio_queue, "sink");
tee_video_pad = gst_element_request_pad_simple (tee, "src_%u");
g_print ("Obtained request pad %s for video branch.\n", gst_pad_get_name (tee_video_pad));
queue_video_pad = gst_element_get_static_pad (video_queue, "sink");
if (gst_pad_link (tee_audio_pad, queue_audio_pad) != GST_PAD_LINK_OK ||
    gst_pad_link (tee_video_pad, queue_video_pad) != GST_PAD_LINK_OK) {
  g_printerr ("Tee could not be linked.\n");
  gst_object_unref (pipeline);
  return -1;
}
gst_object_unref (queue_audio_pad);
gst_object_unref (queue_video_pad);

要链接 Request Pads,需要通过向元素“请求”它们来获得它们。一个元素可能能够产生不同种类的请求垫,因此,在请求它们时,必须提供所需的垫模板名称。在该元素的文档中,tee我们看到它有两个 pad 模板,名为“sink”(用于其 sink Pads)和“src_%u”(用于 Request Pads)。我们从发球台(用于音频和视频分支)请求两个垫子gst_element_request_pad_simple()

然后,我们从这些请求垫需要链接到的下游元素中获取垫。这些都是普通的 Always 垫,所以我们用gst_element_get_static_pad().

最后,我们将焊盘与 链接起来gst_pad_link()gst_element_link()这是内部使用的功能gst_element_link_many()

我们拿到的sink Pads需要用.release来释放 gst_object_unref()。当我们不再需要它们时,将在程序结束时释放请求垫。

然后我们将管道设置为照常播放,并等待直到产生错误消息或 EOS。剩下要做的唯一一件事就是清理请求的 Pad:

/* Release the request pads from the Tee, and unref them */
gst_element_release_request_pad (tee, tee_audio_pad);
gst_element_release_request_pad (tee, tee_video_pad);
gst_object_unref (tee_audio_pad);
gst_object_unref (tee_video_pad);

gst_element_release_request_pad()从 中释放 pad tee,但它仍然需要使用 取消引用(释放)gst_object_unref()

结论

本教程展示了:

  • 如何使用 queue元素使管道的各个部分在不同的线程上运行。
  • 什么是 Request Pad 以及如何将元素与 request pad、 、gst_element_request_pad_simple()gst_pad_link()链接 起来gst_element_release_request_pad()
  • 如何使用元素在不同的分支中提供相同的流 tee

下一个教程建立在这个教程之上,以展示如何将数据手动注入到正在运行的管道中并从中提取数据。

很高兴有你在这里,很快再见!

基础教程8:捷径管道

目标

使用 GStreamer 构建的管道不需要完全关闭。数据可以随时以多种方式注入管道并从中提取。本教程展示:

  • 如何将外部数据注入通用 GStreamer 管道。
  • 如何从通用 GStreamer 管道中提取数据。
  • 如何访问和操作这些数据。

回放教程 3:捷径管道解释了如何在基于 playbin 的管道中实现相同的目标。

介绍

应用程序可以通过多种方式与流经 GStreamer 管道的数据进行交互。本教程描述了最简单的一个,因为它使用了为此唯一目的而创建的元素。

用于将应用程序数据注入 GStreamer 管道的元素是 appsrc,而用于将 GStreamer 数据提取回应用程序的对应元素是appsink。为避免混淆名称,请从 GStreamer 的角度考虑它:appsrc只是一个常规源,它提供神奇地从天而降的数据(实际上由应用程序提供)。appsink是一个常规接收器,其中流经 GStreamer 管道的数据将消亡(实际上由应用程序恢复)。

appsrc并且appsink用途广泛,以至于他们提供了自己的 API(请参阅他们的文档),可以通过链接到 gstreamer-app库来访问这些 API。然而,在本教程中,我们将使用更简单的方法并通过信号控制它们。

appsrc可以在多种模式下工作:在模式下,它会在每次需要时从应用程序请求数据。在推送模式下,应用程序按自己的节奏推送数据。此外,在推送模式下,应用程序可以选择在已经提供足够数据时阻塞在推送功能中,或者它可以监听 enough-dataneed-data信号来控制流量。本示例实现了后一种方法。有关其他方法的信息可以在文档中找到appsrc

缓冲器

数据以称为缓冲区的块的形式通过 GStreamer 管道传输。由于此示例生成和使用数据,因此我们需要了解 GstBuffers。

Source Pads 产生缓冲区,由 Sink Pads 消耗;GStreamer 获取这些缓冲区并将它们从一个元素传递到另一个元素。

缓冲区仅表示一个数据单元,不要假设所有缓冲区都具有相同的大小或表示相同的时间量。你也不应该假设如果一个缓冲区进入一个元素,一个缓冲区就会出来。元素可以随意处理接收到的缓冲区。GstBuffers 也可能包含多个实际内存缓冲区。实际的内存缓冲区是使用 GstMemory对象抽象出来的,一个GstBuffer可以包含多个GstMemory对象。

每个缓冲区都有附加的时间戳和持续时间,它们描述缓冲区的内容应该在哪个时刻被解码、呈现或显示。时间戳是一个非常复杂和微妙的主题,但这种简化的设想现在应该足够了。

例如,a filesrc(一个读取文件的 GStreamer 元素)生成带有“ANY”大写字母且没有时间戳信息的缓冲区。解复用后(参见基础教程 3:动态管道)缓冲区可以有一些特定的上限,例如“video/x-h264”。解码后,每个缓冲区将包含一个带有原始上限的视频帧(例如,“video/x-raw-yuv”)和非常精确的时间戳,指示何时显示该帧。

本教程

本教程以两种方式扩展基础教程 7:多线程和 Pad 可用性audiotestsrc:首先,将 替换为appsrc将生成音频数据的 。其次,一个新的分支被添加到 tee进入音频接收器的 so 数据中,波形显示也被复制到一个appsink. 将appsink信息上传回应用程序,然后通知用户数据已收到,但它显然可以执行更复杂的任务。

img

粗波形发生器

将此代码复制到名为的文本文件中basic-tutorial-8.c(或在您的 GStreamer 安装中找到它)。

#include <gst/gst.h>
#include <gst/audio/audio.h>
#include <string.h>

#define CHUNK_SIZE 1024   /* Amount of bytes we are sending in each buffer */
#define SAMPLE_RATE 44100 /* Samples per second we are sending */

/* Structure to contain all our information, so we can pass it to callbacks */
typedef struct _CustomData {
  GstElement *pipeline, *app_source, *tee, *audio_queue, *audio_convert1, *audio_resample, *audio_sink;
  GstElement *video_queue, *audio_convert2, *visual, *video_convert, *video_sink;
  GstElement *app_queue, *app_sink;

  guint64 num_samples;   /* Number of samples generated so far (for timestamp generation) */
  gfloat a, b, c, d;     /* For waveform generation */

  guint sourceid;        /* To control the GSource */

  GMainLoop *main_loop;  /* GLib's Main Loop */
} CustomData;

/* This method is called by the idle GSource in the mainloop, to feed CHUNK_SIZE bytes into appsrc.
 * The idle handler is added to the mainloop when appsrc requests us to start sending data (need-data signal)
 * and is removed when appsrc has enough data (enough-data signal).
 */
static gboolean push_data (CustomData *data) {
  GstBuffer *buffer;
  GstFlowReturn ret;
  int i;
  GstMapInfo map;
  gint16 *raw;
  gint num_samples = CHUNK_SIZE / 2; /* Because each sample is 16 bits */
  gfloat freq;

  /* Create a new empty buffer */
  buffer = gst_buffer_new_and_alloc (CHUNK_SIZE);

  /* Set its timestamp and duration */
  GST_BUFFER_TIMESTAMP (buffer) = gst_util_uint64_scale (data->num_samples, GST_SECOND, SAMPLE_RATE);
  GST_BUFFER_DURATION (buffer) = gst_util_uint64_scale (num_samples, GST_SECOND, SAMPLE_RATE);

  /* Generate some psychodelic waveforms */
  gst_buffer_map (buffer, &map, GST_MAP_WRITE);
  raw = (gint16 *)map.data;
  data->c += data->d;
  data->d -= data->c / 1000;
  freq = 1100 + 1000 * data->d;
  for (i = 0; i < num_samples; i++) {
    data->a += data->b;
    data->b -= data->a / freq;
    raw[i] = (gint16)(500 * data->a);
  }
  gst_buffer_unmap (buffer, &map);
  data->num_samples += num_samples;

  /* Push the buffer into the appsrc */
  g_signal_emit_by_name (data->app_source, "push-buffer", buffer, &ret);

  /* Free the buffer now that we are done with it */
  gst_buffer_unref (buffer);

  if (ret != GST_FLOW_OK) {
    /* We got some error, stop sending data */
    return FALSE;
  }

  return TRUE;
}

/* This signal callback triggers when appsrc needs data. Here, we add an idle handler
 * to the mainloop to start pushing data into the appsrc */
static void start_feed (GstElement *source, guint size, CustomData *data) {
  if (data->sourceid == 0) {
    g_print ("Start feeding\n");
    data->sourceid = g_idle_add ((GSourceFunc) push_data, data);
  }
}

/* This callback triggers when appsrc has enough data and we can stop sending.
 * We remove the idle handler from the mainloop */
static void stop_feed (GstElement *source, CustomData *data) {
  if (data->sourceid != 0) {
    g_print ("Stop feeding\n");
    g_source_remove (data->sourceid);
    data->sourceid = 0;
  }
}

/* The appsink has received a buffer */
static GstFlowReturn new_sample (GstElement *sink, CustomData *data) {
  GstSample *sample;

  /* Retrieve the buffer */
  g_signal_emit_by_name (sink, "pull-sample", &sample);
  if (sample) {
    /* The only thing we do in this example is print a * to indicate a received buffer */
    g_print ("*");
    gst_sample_unref (sample);
    return GST_FLOW_OK;
  }

  return GST_FLOW_ERROR;
}

/* This function is called when an error message is posted on the bus */
static void error_cb (GstBus *bus, GstMessage *msg, CustomData *data) {
  GError *err;
  gchar *debug_info;

  /* Print error details on the screen */
  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);

  g_main_loop_quit (data->main_loop);
}

int main(int argc, char *argv[]) {
  CustomData data;
  GstPad *tee_audio_pad, *tee_video_pad, *tee_app_pad;
  GstPad *queue_audio_pad, *queue_video_pad, *queue_app_pad;
  GstAudioInfo info;
  GstCaps *audio_caps;
  GstBus *bus;

  /* Initialize custom data structure */
  memset (&data, 0, sizeof (data));
  data.b = 1; /* For waveform generation */
  data.d = 1;

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

  /* Create the elements */
  data.app_source = gst_element_factory_make ("appsrc", "audio_source");
  data.tee = gst_element_factory_make ("tee", "tee");
  data.audio_queue = gst_element_factory_make ("queue", "audio_queue");
  data.audio_convert1 = gst_element_factory_make ("audioconvert", "audio_convert1");
  data.audio_resample = gst_element_factory_make ("audioresample", "audio_resample");
  data.audio_sink = gst_element_factory_make ("autoaudiosink", "audio_sink");
  data.video_queue = gst_element_factory_make ("queue", "video_queue");
  data.audio_convert2 = gst_element_factory_make ("audioconvert", "audio_convert2");
  data.visual = gst_element_factory_make ("wavescope", "visual");
  data.video_convert = gst_element_factory_make ("videoconvert", "video_convert");
  data.video_sink = gst_element_factory_make ("autovideosink", "video_sink");
  data.app_queue = gst_element_factory_make ("queue", "app_queue");
  data.app_sink = gst_element_factory_make ("appsink", "app_sink");

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

  if (!data.pipeline || !data.app_source || !data.tee || !data.audio_queue || !data.audio_convert1 ||
      !data.audio_resample || !data.audio_sink || !data.video_queue || !data.audio_convert2 || !data.visual ||
      !data.video_convert || !data.video_sink || !data.app_queue || !data.app_sink) {
    g_printerr ("Not all elements could be created.\n");
    return -1;
  }

  /* Configure wavescope */
  g_object_set (data.visual, "shader", 0, "style", 0, NULL);

  /* Configure appsrc */
  gst_audio_info_set_format (&info, GST_AUDIO_FORMAT_S16, SAMPLE_RATE, 1, NULL);
  audio_caps = gst_audio_info_to_caps (&info);
  g_object_set (data.app_source, "caps", audio_caps, "format", GST_FORMAT_TIME, NULL);
  g_signal_connect (data.app_source, "need-data", G_CALLBACK (start_feed), &data);
  g_signal_connect (data.app_source, "enough-data", G_CALLBACK (stop_feed), &data);

  /* Configure appsink */
  g_object_set (data.app_sink, "emit-signals", TRUE, "caps", audio_caps, NULL);
  g_signal_connect (data.app_sink, "new-sample", G_CALLBACK (new_sample), &data);
  gst_caps_unref (audio_caps);

  /* Link all elements that can be automatically linked because they have "Always" pads */
  gst_bin_add_many (GST_BIN (data.pipeline), data.app_source, data.tee, data.audio_queue, data.audio_convert1, data.audio_resample,
      data.audio_sink, data.video_queue, data.audio_convert2, data.visual, data.video_convert, data.video_sink, data.app_queue,
      data.app_sink, NULL);
  if (gst_element_link_many (data.app_source, data.tee, NULL) != TRUE ||
      gst_element_link_many (data.audio_queue, data.audio_convert1, data.audio_resample, data.audio_sink, NULL) != TRUE ||
      gst_element_link_many (data.video_queue, data.audio_convert2, data.visual, data.video_convert, data.video_sink, NULL) != TRUE ||
      gst_element_link_many (data.app_queue, data.app_sink, NULL) != TRUE) {
    g_printerr ("Elements could not be linked.\n");
    gst_object_unref (data.pipeline);
    return -1;
  }

  /* Manually link the Tee, which has "Request" pads */
  tee_audio_pad = gst_element_request_pad_simple (data.tee, "src_%u");
  g_print ("Obtained request pad %s for audio branch.\n", gst_pad_get_name (tee_audio_pad));
  queue_audio_pad = gst_element_get_static_pad (data.audio_queue, "sink");
  tee_video_pad = gst_element_request_pad_simple (data.tee, "src_%u");
  g_print ("Obtained request pad %s for video branch.\n", gst_pad_get_name (tee_video_pad));
  queue_video_pad = gst_element_get_static_pad (data.video_queue, "sink");
  tee_app_pad = gst_element_request_pad_simple (data.tee, "src_%u");
  g_print ("Obtained request pad %s for app branch.\n", gst_pad_get_name (tee_app_pad));
  queue_app_pad = gst_element_get_static_pad (data.app_queue, "sink");
  if (gst_pad_link (tee_audio_pad, queue_audio_pad) != GST_PAD_LINK_OK ||
      gst_pad_link (tee_video_pad, queue_video_pad) != GST_PAD_LINK_OK ||
      gst_pad_link (tee_app_pad, queue_app_pad) != GST_PAD_LINK_OK) {
    g_printerr ("Tee could not be linked\n");
    gst_object_unref (data.pipeline);
    return -1;
  }
  gst_object_unref (queue_audio_pad);
  gst_object_unref (queue_video_pad);
  gst_object_unref (queue_app_pad);

  /* Instruct the bus to emit signals for each received message, and connect to the interesting signals */
  bus = gst_element_get_bus (data.pipeline);
  gst_bus_add_signal_watch (bus);
  g_signal_connect (G_OBJECT (bus), "message::error", (GCallback)error_cb, &data);
  gst_object_unref (bus);

  /* Start playing the pipeline */
  gst_element_set_state (data.pipeline, GST_STATE_PLAYING);

  /* Create a GLib Main Loop and set it to run */
  data.main_loop = g_main_loop_new (NULL, FALSE);
  g_main_loop_run (data.main_loop);

  /* Release the request pads from the Tee, and unref them */
  gst_element_release_request_pad (data.tee, tee_audio_pad);
  gst_element_release_request_pad (data.tee, tee_video_pad);
  gst_element_release_request_pad (data.tee, tee_app_pad);
  gst_object_unref (tee_audio_pad);
  gst_object_unref (tee_video_pad);
  gst_object_unref (tee_app_pad);

  /* Free resources */
  gst_element_set_state (data.pipeline, GST_STATE_NULL);
  gst_object_unref (data.pipeline);
  return 0;
}

信息需要帮忙?

If you need help to compile this code, refer to the Building the tutorials section for your platform: Linux, Mac OS X or Windows, or use this specific command on Linux:

gcc basic-tutorial-8.c -o basic-tutorial-8 pkg-config --cflags --libs gstreamer-1.0 gstreamer-audio-1.0``

If you need help to run this code, refer to the Running the tutorials section for your platform: Linux, Mac OS X or Windows.

This tutorial plays an audible tone for varying frequency through the audio card and opens a window with a waveform representation of the tone. The waveform should be a sinusoid, but due to the refreshing of the window might not appear so.

Required libraries: gstreamer-1.0

Walkthrough

The code to create the pipeline (Lines 131 to 205) is an enlarged version of Basic tutorial 7: Multithreading and Pad Availability. It involves instantiating all the elements, link the elements with Always Pads, and manually link the Request Pads of the tee element.

Regarding the configuration of the appsrc and appsink elements:

/* Configure appsrc */
gst_audio_info_set_format (&info, GST_AUDIO_FORMAT_S16, SAMPLE_RATE, 1, NULL);
audio_caps = gst_audio_info_to_caps (&info);
g_object_set (data.app_source, "caps", audio_caps, NULL);
g_signal_connect (data.app_source, "need-data", G_CALLBACK (start_feed), &data);
g_signal_connect (data.app_source, "enough-data", G_CALLBACK (stop_feed), &data);

The first property that needs to be set on the appsrc is caps. It specifies the kind of data that the element is going to produce, so GStreamer can check if linking with downstream elements is possible (this is, if the downstream elements will understand this kind of data). This property must be a GstCaps object, which is easily built from a string with gst_caps_from_string().

We then connect to the need-data and enough-data signals. These are fired by appsrc when its internal queue of data is running low or almost full, respectively. We will use these signals to start and stop (respectively) our signal generation process.

/* Configure appsink */
g_object_set (data.app_sink, "emit-signals", TRUE, "caps", audio_caps, NULL);
g_signal_connect (data.app_sink, "new-sample", G_CALLBACK (new_sample), &data);
gst_caps_unref (audio_caps);

Regarding the appsink configuration, we connect to the new-sample signal, which is emitted every time the sink receives a buffer. Also, the signal emission needs to be enabled through the emit-signals property, because, by default, it is disabled.

Starting the pipeline, waiting for messages and final cleanup is done as usual. Let's review the callbacks we have just registered:

/* This signal callback triggers when appsrc needs data. Here, we add an idle handler
 * to the mainloop to start pushing data into the appsrc */
static void start_feed (GstElement *source, guint size, CustomData *data) {
  if (data->sourceid == 0) {
    g_print ("Start feeding\n");
    data->sourceid = g_idle_add ((GSourceFunc) push_data, data);
  }
}

This function is called when the internal queue of appsrc is about to starve (run out of data). The only thing we do here is register a GLib idle function with g_idle_add() that feeds data to appsrc until it is full again. A GLib idle function is a method that GLib will call from its main loop whenever it is “idle”, this is, when it has no higher-priority tasks to perform. It requires a GLib GMainLoop to be instantiated and running, obviously.

This is only one of the multiple approaches that appsrc allows. In particular, buffers do not need to be fed into appsrc from the main thread using GLib, and you do not need to use the need-data and enough-data signals to synchronize with appsrc (although this is allegedly the most convenient).

We take note of the sourceid that g_idle_add() returns, so we can disable it later.

/* This callback triggers when appsrc has enough data and we can stop sending.
 * We remove the idle handler from the mainloop */
static void stop_feed (GstElement *source, CustomData *data) {
  if (data->sourceid != 0) {
    g_print ("Stop feeding\n");
    g_source_remove (data->sourceid);
    data->sourceid = 0;
  }
}

This function is called when the internal queue of appsrc is full enough so we stop pushing data. Here we simply remove the idle function by using g_source_remove() (The idle function is implemented as a GSource).

/* This method is called by the idle GSource in the mainloop, to feed CHUNK_SIZE bytes into appsrc.
 * The ide handler is added to the mainloop when appsrc requests us to start sending data (need-data signal)
 * and is removed when appsrc has enough data (enough-data signal).
 */
static gboolean push_data (CustomData *data) {
  GstBuffer *buffer;
  GstFlowReturn ret;
  GstMapInfo map;
  int i;
  gint num_samples = CHUNK_SIZE / 2; /* Because each sample is 16 bits */
  gfloat freq;

  /* Create a new empty buffer */
  buffer = gst_buffer_new_and_alloc (CHUNK_SIZE);

  /* Set its timestamp and duration */
  GST_BUFFER_TIMESTAMP (buffer) = gst_util_uint64_scale (data->num_samples, GST_SECOND, SAMPLE_RATE);
  GST_BUFFER_DURATION (buffer) = gst_util_uint64_scale (num_samples, GST_SECOND, SAMPLE_RATE);

  /* Generate some psychodelic waveforms */
  if (gst_buffer_map (buf, &map, GST_MAP_READ)) {
    gint16 *raw = (gint16 *) map.data;

    /* create samples here */

    /* unmap buffer when done */
    gst_buffer_unmap (buf, &map);
  }

This is the function that feeds appsrc. It will be called by GLib at times and rates which are out of our control, but we know that we will disable it when its job is done (when the queue in appsrc is full).

Its first task is to create a new buffer with a given size (in this example, it is arbitrarily set to 1024 bytes) with gst_buffer_new_and_alloc().

We count the number of samples that we have generated so far with the CustomData.num_samples variable, so we can time-stamp this buffer using the GST_BUFFER_TIMESTAMP macro in GstBuffer.

Since we are producing buffers of the same size, their duration is the same and is set using the GST_BUFFER_DURATION in GstBuffer.

gst_util_uint64_scale() is a utility function that scales (multiply and divide) numbers which can be large, without fear of overflows.

In order access the memory of the buffer you first have to map it with gst_buffer_map(), which will give you a pointer and a size inside the GstMapInfo structure which gst_buffer_map() will populate on success. Be careful not to write past the end of the buffer: you allocated it, so you know its size in bytes and samples.

We will skip over the waveform generation, since it is outside the scope of this tutorial (it is simply a funny way of generating a pretty psychedelic wave).

/* Push the buffer into the appsrc */
g_signal_emit_by_name (data->app_source, "push-buffer", buffer, &ret);

/* Free the buffer now that we are done with it */
gst_buffer_unref (buffer);

Note that there is also gst_app_src_push_buffer() as part of the gstreamer-app-1.0 library, which is perhaps a better function to use to push a buffer into appsrc than the signal emission above, because it has a proper type signature so it's harder to get wrong. However, be aware that if you use gst_app_src_push_buffer() it will take ownership of the buffer passed instead, so in that case you won't have to unref it after pushing.

Once we have the buffer ready, we pass it to appsrc with the push-buffer action signal (see information box at the end of Playback tutorial 1: Playbin usage), and then gst_buffer_unref() it since we no longer need it.

/* The appsink has received a buffer */
static GstFlowReturn new_sample (GstElement *sink, CustomData *data) {
  GstSample *sample;
  /* Retrieve the buffer */
  g_signal_emit_by_name (sink, "pull-sample", &sample);
  if (sample) {
    /* The only thing we do in this example is print a * to indicate a received buffer */
    g_print ("*");
    gst_sample_unref (sample);
    return GST_FLOW_OK;
  }
  return GST_FLOW_ERROR;
}

appsink最后,这是接收缓冲区时调用的函数 。我们使用pull-sample动作信号来检索缓冲区,然后在屏幕上打印一些指示符。

请注意,作为库gst_app_src_pull_sample()的一部分 gstreamer-app-1.0,这可能是一个比上面的信号发射更好的函数,用于从 appsink 中提取样本/缓冲区,因为它具有正确的类型签名,因此更难出错。

为了获得数据指针,我们需要gst_buffer_map()像上面那样使用,它将使用GstMapInfo指向数据的指针和数据大小(以字节为单位)填充一个辅助结构。gst_buffer_unmap() 完成数据后不要忘记再次缓冲区。

请记住,此缓冲区不必与我们在函数中生成的缓冲区相匹配,路径中的任何元素都可以以任何方式更改缓冲区(在此示例中不是:在和之间的路径中push_data只有一个,并且确实不改变缓冲区的内容)。tee``appsrc``appsink``tee

然后我们gst_sample_unref()检索样本,本教程就完成了。

结论

本教程展示了应用程序如何:

  • 使用元素将数据注入管道appsrc
  • 使用元素从管道中检索数据appsink
  • 通过访问GstBuffer.

在基于 playbin 的管道中,实现相同目标的方式略有不同。回放教程 3:捷径管道展示了如何操作。

很高兴有你在这里,很快再见!

基础教程9:媒体信息采集

目标

有时您可能想快速找出文件(或 URI)包含的媒体类型,或者您是否能够播放该媒体。您可以构建一个管道,将其设置为运行,并查看总线消息,但 GStreamer 有一个实用程序可以为您完成这些工作。本教程展示:

  • 如何恢复有关 URI 的信息
  • 如何确定 URI 是否可播放

介绍

GstDiscoverer``pbutils是在库(插件基础实用程序)中找到的实用程序对象,它接受 URI 或 URI 列表,并返回有关它们的信息。它可以在同步或异步模式下工作。

在同步模式下,只有一个函数可以调用, gst_discoverer_discover_uri()它会阻塞直到信息准备好。由于这种阻塞,基于 GUI 的应用程序通常不太感兴趣,因此使用异步模式,如本教程中所述。

恢复的信息包括编解码器描述、流拓扑(流和子流的数量)和可用的元数据(如音频语言)。

例如,这是发现 https://gstreamer.freedesktop.org/data/media/sintel_trailer-480p.webm 的结果

Duration: 0:00:52.250000000
Tags:
  video codec: On2 VP8
  language code: en
  container format: Matroska
  application name: ffmpeg2theora-0.24
  encoder: Xiph.Org libVorbis I 20090709
  encoder version: 0
  audio codec: Vorbis
  nominal bitrate: 80000
  bitrate: 80000
Seekable: yes
Stream information:
  container: WebM
    audio: Vorbis
      Tags:
        language code: en
        container format: Matroska
        audio codec: Vorbis
        application name: ffmpeg2theora-0.24
        encoder: Xiph.Org libVorbis I 20090709
        encoder version: 0
        nominal bitrate: 80000
        bitrate: 80000
    video: VP8
      Tags:
        video codec: VP8 video
        container format: Matroska

以下代码尝试发现通过命令行提供的 URI,并输出检索到的信息(如果未提供 URI,则使用默认 URI)。

This is a simplified version of what the gst-discoverer-1.0 tool does (Basic tutorial 10: GStreamer tools), which is an application that only displays data, but does not perform any playback.

The GStreamer Discoverer

Copy this code into a text file named basic-tutorial-9.c (or find it in your GStreamer installation).

basic-tutorial-9.c

#include <string.h>
#include <gst/gst.h>
#include <gst/pbutils/pbutils.h>

/* Structure to contain all our information, so we can pass it around */
typedef struct _CustomData {
  GstDiscoverer *discoverer;
  GMainLoop *loop;
} CustomData;

/* Print a tag in a human-readable format (name: value) */
static void print_tag_foreach (const GstTagList *tags, const gchar *tag, gpointer user_data) {
  GValue val = { 0, };
  gchar *str;
  gint depth = GPOINTER_TO_INT (user_data);

  gst_tag_list_copy_value (&val, tags, tag);

  if (G_VALUE_HOLDS_STRING (&val))
    str = g_value_dup_string (&val);
  else
    str = gst_value_serialize (&val);

  g_print ("%*s%s: %s\n", 2 * depth, " ", gst_tag_get_nick (tag), str);
  g_free (str);

  g_value_unset (&val);
}

/* Print information regarding a stream */
static void print_stream_info (GstDiscovererStreamInfo *info, gint depth) {
  gchar *desc = NULL;
  GstCaps *caps;
  const GstTagList *tags;

  caps = gst_discoverer_stream_info_get_caps (info);

  if (caps) {
    if (gst_caps_is_fixed (caps))
      desc = gst_pb_utils_get_codec_description (caps);
    else
      desc = gst_caps_to_string (caps);
    gst_caps_unref (caps);
  }

  g_print ("%*s%s: %s\n", 2 * depth, " ", gst_discoverer_stream_info_get_stream_type_nick (info), (desc ? desc : ""));

  if (desc) {
    g_free (desc);
    desc = NULL;
  }

  tags = gst_discoverer_stream_info_get_tags (info);
  if (tags) {
    g_print ("%*sTags:\n", 2 * (depth + 1), " ");
    gst_tag_list_foreach (tags, print_tag_foreach, GINT_TO_POINTER (depth + 2));
  }
}

/* Print information regarding a stream and its substreams, if any */
static void print_topology (GstDiscovererStreamInfo *info, gint depth) {
  GstDiscovererStreamInfo *next;

  if (!info)
    return;

  print_stream_info (info, depth);

  next = gst_discoverer_stream_info_get_next (info);
  if (next) {
    print_topology (next, depth + 1);
    gst_discoverer_stream_info_unref (next);
  } else if (GST_IS_DISCOVERER_CONTAINER_INFO (info)) {
    GList *tmp, *streams;

    streams = gst_discoverer_container_info_get_streams (GST_DISCOVERER_CONTAINER_INFO (info));
    for (tmp = streams; tmp; tmp = tmp->next) {
      GstDiscovererStreamInfo *tmpinf = (GstDiscovererStreamInfo *) tmp->data;
      print_topology (tmpinf, depth + 1);
    }
    gst_discoverer_stream_info_list_free (streams);
  }
}

/* This function is called every time the discoverer has information regarding
 * one of the URIs we provided.*/
static void on_discovered_cb (GstDiscoverer *discoverer, GstDiscovererInfo *info, GError *err, CustomData *data) {
  GstDiscovererResult result;
  const gchar *uri;
  const GstTagList *tags;
  GstDiscovererStreamInfo *sinfo;

  uri = gst_discoverer_info_get_uri (info);
  result = gst_discoverer_info_get_result (info);
  switch (result) {
    case GST_DISCOVERER_URI_INVALID:
      g_print ("Invalid URI '%s'\n", uri);
      break;
    case GST_DISCOVERER_ERROR:
      g_print ("Discoverer error: %s\n", err->message);
      break;
    case GST_DISCOVERER_TIMEOUT:
      g_print ("Timeout\n");
      break;
    case GST_DISCOVERER_BUSY:
      g_print ("Busy\n");
      break;
    case GST_DISCOVERER_MISSING_PLUGINS:{
      const GstStructure *s;
      gchar *str;

      s = gst_discoverer_info_get_misc (info);
      str = gst_structure_to_string (s);

      g_print ("Missing plugins: %s\n", str);
      g_free (str);
      break;
    }
    case GST_DISCOVERER_OK:
      g_print ("Discovered '%s'\n", uri);
      break;
  }

  if (result != GST_DISCOVERER_OK) {
    g_printerr ("This URI cannot be played\n");
    return;
  }

  /* If we got no error, show the retrieved information */

  g_print ("\nDuration: %" GST_TIME_FORMAT "\n", GST_TIME_ARGS (gst_discoverer_info_get_duration (info)));

  tags = gst_discoverer_info_get_tags (info);
  if (tags) {
    g_print ("Tags:\n");
    gst_tag_list_foreach (tags, print_tag_foreach, GINT_TO_POINTER (1));
  }

  g_print ("Seekable: %s\n", (gst_discoverer_info_get_seekable (info) ? "yes" : "no"));

  g_print ("\n");

  sinfo = gst_discoverer_info_get_stream_info (info);
  if (!sinfo)
    return;

  g_print ("Stream information:\n");

  print_topology (sinfo, 1);

  gst_discoverer_stream_info_unref (sinfo);

  g_print ("\n");
}

/* This function is called when the discoverer has finished examining
 * all the URIs we provided.*/
static void on_finished_cb (GstDiscoverer *discoverer, CustomData *data) {
  g_print ("Finished discovering\n");

  g_main_loop_quit (data->loop);
}

int main (int argc, char **argv) {
  CustomData data;
  GError *err = NULL;
  gchar *uri = "https://gstreamer.freedesktop.org/data/media/sintel_trailer-480p.webm";

  /* if a URI was provided, use it instead of the default one */
  if (argc > 1) {
    uri = argv[1];
  }

  /* Initialize custom data structure */
  memset (&data, 0, sizeof (data));

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

  g_print ("Discovering '%s'\n", uri);

  /* Instantiate the Discoverer */
  data.discoverer = gst_discoverer_new (5 * GST_SECOND, &err);
  if (!data.discoverer) {
    g_print ("Error creating discoverer instance: %s\n", err->message);
    g_clear_error (&err);
    return -1;
  }

  /* Connect to the interesting signals */
  g_signal_connect (data.discoverer, "discovered", G_CALLBACK (on_discovered_cb), &data);
  g_signal_connect (data.discoverer, "finished", G_CALLBACK (on_finished_cb), &data);

  /* Start the discoverer process (nothing to do yet) */
  gst_discoverer_start (data.discoverer);

  /* Add a request to process asynchronously the URI passed through the command line */
  if (!gst_discoverer_discover_uri_async (data.discoverer, uri)) {
    g_print ("Failed to start discovering URI '%s'\n", uri);
    g_object_unref (data.discoverer);
    return -1;
  }

  /* Create a GLib Main Loop and set it to run, so we can wait for the signals */
  data.loop = g_main_loop_new (NULL, FALSE);
  g_main_loop_run (data.loop);

  /* Stop the discoverer process */
  gst_discoverer_stop (data.discoverer);

  /* Free resources */
  g_object_unref (data.discoverer);
  g_main_loop_unref (data.loop);

  return 0;
}

信息Need help?

If you need help to compile this code, refer to the Building the tutorials section for your platform: Linux, Mac OS X or Windows, or use this specific command on Linux:

gcc basic-tutorial-9.c -o basic-tutorial-9 pkg-config --cflags --libs gstreamer-1.0 gstreamer-pbutils-1.0``

If you need help to run this code, refer to the Running the tutorials section for your platform: Linux, Mac OS X or Windows.

This tutorial opens the URI passed as the first parameter in the command line (or a default URI if none is provided) and outputs information about it on the screen. If the media is located on the Internet, the application might take a bit to react depending on your connection speed.

Required libraries: gstreamer-pbutils-1.0 gstreamer-1.0

Walkthrough

These are the main steps to use the GstDiscoverer:

/* Instantiate the Discoverer */
data.discoverer = gst_discoverer_new (5 * GST_SECOND, &err);
if (!data.discoverer) {
  g_print ("Error creating discoverer instance: %s\n", err->message);
  g_clear_error (&err);
  return -1;
}

gst_discoverer_new() creates a new Discoverer object. The first parameter is the timeout per file, in nanoseconds (use the GST_SECOND macro for simplicity).

/* Connect to the interesting signals */
g_signal_connect (data.discoverer, "discovered", G_CALLBACK (on_discovered_cb), &data);
g_signal_connect (data.discoverer, "finished", G_CALLBACK (on_finished_cb), &data);

Connect to the interesting signals, as usual. We discuss them in the snippet for their callbacks.

/* Start the discoverer process (nothing to do yet) */
gst_discoverer_start (data.discoverer);

gst_discoverer_start() launches the discovering process, but we have not provided any URI to discover yet. This is done next:

/* Add a request to process asynchronously the URI passed through the command line */
if (!gst_discoverer_discover_uri_async (data.discoverer, uri)) {
  g_print ("Failed to start discovering URI '%s'\n", uri);
  g_object_unref (data.discoverer);
  return -1;
}

gst_discoverer_discover_uri_async() enqueues the provided URI for discovery. Multiple URIs can be enqueued with this function. As the discovery process for each of them finishes, the registered callback functions will be fired up.

/* Create a GLib Main Loop and set it to run, so we can wait for the signals */
data.loop = g_main_loop_new (NULL, FALSE);
g_main_loop_run (data.loop);

The usual GLib main loop is instantiated and executed. We will get out of it when g_main_loop_quit() is called from the on_finished_cb callback.

/* Stop the discoverer process */
gst_discoverer_stop (data.discoverer);

Once we are done with the discoverer, we stop it with gst_discoverer_stop() and unref it with g_object_unref().

Let's review now the callbacks we have registered:

/* This function is called every time the discoverer has information regarding
 * one of the URIs we provided.*/
static void on_discovered_cb (GstDiscoverer *discoverer, GstDiscovererInfo *info, GError *err, CustomData *data) {
  GstDiscovererResult result;
  const gchar *uri;
  const GstTagList *tags;
  GstDiscovererStreamInfo *sinfo;

  uri = gst_discoverer_info_get_uri (info);
  result = gst_discoverer_info_get_result (info);

We got here because the Discoverer has finished working on one URI, and provides us a GstDiscovererInfo structure with all the information.

The first step is to retrieve the particular URI this call refers to (in case we had multiple discover process running, which is not the case in this example) with gst_discoverer_info_get_uri() and the discovery result with gst_discoverer_info_get_result().

switch (result) {
  case GST_DISCOVERER_URI_INVALID:
    g_print ("Invalid URI '%s'\n", uri);
    break;
  case GST_DISCOVERER_ERROR:
    g_print ("Discoverer error: %s\n", err->message);
    break;
  case GST_DISCOVERER_TIMEOUT:
    g_print ("Timeout\n");
    break;
  case GST_DISCOVERER_BUSY:
    g_print ("Busy\n");
    break;
  case GST_DISCOVERER_MISSING_PLUGINS:{
    const GstStructure *s;
    gchar *str;

    s = gst_discoverer_info_get_misc (info);
    str = gst_structure_to_string (s);

    g_print ("Missing plugins: %s\n", str);
    g_free (str);
    break;
  }
  case GST_DISCOVERER_OK:
    g_print ("Discovered '%s'\n", uri);
    break;
}

if (result != GST_DISCOVERER_OK) {
  g_printerr ("This URI cannot be played\n");
  return;
}

As the code shows, any result other than GST_DISCOVERER_OK means that there has been some kind of problem, and this URI cannot be played. The reasons can vary, but the enum values are quite explicit (GST_DISCOVERER_BUSY can only happen when in synchronous mode, which is not used in this example).

If no error happened, information can be retrieved from the GstDiscovererInfo structure with the different gst_discoverer_info_get_* methods (like, gst_discoverer_info_get_duration(), for example).

Bits of information which are made of lists, like tags and stream info, needs some extra parsing:

tags = gst_discoverer_info_get_tags (info);
if (tags) {
  g_print ("Tags:\n");
  gst_tag_list_foreach (tags, print_tag_foreach, GINT_TO_POINTER (1));
}

标签是附加到媒体的元数据(标签)。可以使用 来检查它们gst_tag_list_foreach(),这将调用print_tag_foreach找到的每个标签(例如,也可以手动遍历列表,或者可以使用 搜索特定标签 gst_tag_list_get_string())。的代码print_tag_foreach几乎是不言自明的。

sinfo = gst_discoverer_info_get_stream_info (info);
if (!sinfo)
  return;

g_print ("Stream information:\n");

print_topology (sinfo, 1);

gst_discoverer_stream_info_unref (sinfo);

gst_discoverer_info_get_stream_info()返回GstDiscovererStreamInfo在函数中解析的结构print_topology,然后用 丢弃gst_discoverer_stream_info_unref()

/* Print information regarding a stream and its substreams, if any */
static void print_topology (GstDiscovererStreamInfo *info, gint depth) {
  GstDiscovererStreamInfo *next;

  if (!info)
    return;

  print_stream_info (info, depth);

  next = gst_discoverer_stream_info_get_next (info);
  if (next) {
    print_topology (next, depth + 1);
    gst_discoverer_stream_info_unref (next);
  } else if (GST_IS_DISCOVERER_CONTAINER_INFO (info)) {
    GList *tmp, *streams;

    streams = gst_discoverer_container_info_get_streams (GST_DISCOVERER_CONTAINER_INFO (info));
    for (tmp = streams; tmp; tmp = tmp->next) {
      GstDiscovererStreamInfo *tmpinf = (GstDiscovererStreamInfo *) tmp->data;
      print_topology (tmpinf, depth + 1);
    }
    gst_discoverer_stream_info_list_free (streams);
  }
}

print_stream_info函数的代码也几乎不言自明:它打印流的功能,然后打印相关的上限,print_tag_foreach也使用。

然后,print_topology寻找下一个要显示的元素。如果 gst_discoverer_stream_info_get_next()返回一个非 NULL 流信息,它指的是我们的后代,应该显示。否则,如果我们是一个容器,则递归调用print_topology我们通过 获得的每个孩子gst_discoverer_container_info_get_streams()。否则,我们就是一个最终流,不需要递归(这部分 Discoverer API 诚然有点晦涩难懂)。

结论

本教程展示了:

  • 如何使用GstDiscoverer
  • 如何通过查看使用获得的返回码来确定 URI 是否可播放gst_discoverer_info_get_result()

很高兴有你在这里,很快再见!

基础教程9:媒体信息采集

目标

有时您可能想快速找出文件(或 URI)包含的媒体类型,或者您是否能够播放该媒体。您可以构建一个管道,将其设置为运行,并查看总线消息,但 GStreamer 有一个实用程序可以为您完成这些工作。本教程展示:

  • 如何恢复有关 URI 的信息
  • 如何确定 URI 是否可播放

介绍

GstDiscoverer``pbutils是在库(插件基础实用程序)中找到的实用程序对象,它接受 URI 或 URI 列表,并返回有关它们的信息。它可以在同步或异步模式下工作。

在同步模式下,只有一个函数可以调用, gst_discoverer_discover_uri()它会阻塞直到信息准备好。由于这种阻塞,基于 GUI 的应用程序通常不太感兴趣,因此使用异步模式,如本教程中所述。

恢复的信息包括编解码器描述、流拓扑(流和子流的数量)和可用的元数据(如音频语言)。

例如,这是发现 https://gstreamer.freedesktop.org/data/media/sintel_trailer-480p.webm 的结果

Duration: 0:00:52.250000000
Tags:
  video codec: On2 VP8
  language code: en
  container format: Matroska
  application name: ffmpeg2theora-0.24
  encoder: Xiph.Org libVorbis I 20090709
  encoder version: 0
  audio codec: Vorbis
  nominal bitrate: 80000
  bitrate: 80000
Seekable: yes
Stream information:
  container: WebM
    audio: Vorbis
      Tags:
        language code: en
        container format: Matroska
        audio codec: Vorbis
        application name: ffmpeg2theora-0.24
        encoder: Xiph.Org libVorbis I 20090709
        encoder version: 0
        nominal bitrate: 80000
        bitrate: 80000
    video: VP8
      Tags:
        video codec: VP8 video
        container format: Matroska

以下代码尝试发现通过命令行提供的 URI,并输出检索到的信息(如果未提供 URI,则使用默认 URI)。

这是该工具功能的简化版本gst-discoverer-1.0基础教程 10:GStreamer 工具),它是一个仅显示数据但不执行任何播放的应用程序。

GStreamer 发现者

将此代码复制到名为的文本文件中basic-tutorial-9.c(或在您的 GStreamer 安装中找到它)。

basic-tutorial-9.c

#include <string.h>
#include <gst/gst.h>
#include <gst/pbutils/pbutils.h>

/* Structure to contain all our information, so we can pass it around */
typedef struct _CustomData {
  GstDiscoverer *discoverer;
  GMainLoop *loop;
} CustomData;

/* Print a tag in a human-readable format (name: value) */
static void print_tag_foreach (const GstTagList *tags, const gchar *tag, gpointer user_data) {
  GValue val = { 0, };
  gchar *str;
  gint depth = GPOINTER_TO_INT (user_data);

  gst_tag_list_copy_value (&val, tags, tag);

  if (G_VALUE_HOLDS_STRING (&val))
    str = g_value_dup_string (&val);
  else
    str = gst_value_serialize (&val);

  g_print ("%*s%s: %s\n", 2 * depth, " ", gst_tag_get_nick (tag), str);
  g_free (str);

  g_value_unset (&val);
}

/* Print information regarding a stream */
static void print_stream_info (GstDiscovererStreamInfo *info, gint depth) {
  gchar *desc = NULL;
  GstCaps *caps;
  const GstTagList *tags;

  caps = gst_discoverer_stream_info_get_caps (info);

  if (caps) {
    if (gst_caps_is_fixed (caps))
      desc = gst_pb_utils_get_codec_description (caps);
    else
      desc = gst_caps_to_string (caps);
    gst_caps_unref (caps);
  }

  g_print ("%*s%s: %s\n", 2 * depth, " ", gst_discoverer_stream_info_get_stream_type_nick (info), (desc ? desc : ""));

  if (desc) {
    g_free (desc);
    desc = NULL;
  }

  tags = gst_discoverer_stream_info_get_tags (info);
  if (tags) {
    g_print ("%*sTags:\n", 2 * (depth + 1), " ");
    gst_tag_list_foreach (tags, print_tag_foreach, GINT_TO_POINTER (depth + 2));
  }
}

/* Print information regarding a stream and its substreams, if any */
static void print_topology (GstDiscovererStreamInfo *info, gint depth) {
  GstDiscovererStreamInfo *next;

  if (!info)
    return;

  print_stream_info (info, depth);

  next = gst_discoverer_stream_info_get_next (info);
  if (next) {
    print_topology (next, depth + 1);
    gst_discoverer_stream_info_unref (next);
  } else if (GST_IS_DISCOVERER_CONTAINER_INFO (info)) {
    GList *tmp, *streams;

    streams = gst_discoverer_container_info_get_streams (GST_DISCOVERER_CONTAINER_INFO (info));
    for (tmp = streams; tmp; tmp = tmp->next) {
      GstDiscovererStreamInfo *tmpinf = (GstDiscovererStreamInfo *) tmp->data;
      print_topology (tmpinf, depth + 1);
    }
    gst_discoverer_stream_info_list_free (streams);
  }
}

/* This function is called every time the discoverer has information regarding
 * one of the URIs we provided.*/
static void on_discovered_cb (GstDiscoverer *discoverer, GstDiscovererInfo *info, GError *err, CustomData *data) {
  GstDiscovererResult result;
  const gchar *uri;
  const GstTagList *tags;
  GstDiscovererStreamInfo *sinfo;

  uri = gst_discoverer_info_get_uri (info);
  result = gst_discoverer_info_get_result (info);
  switch (result) {
    case GST_DISCOVERER_URI_INVALID:
      g_print ("Invalid URI '%s'\n", uri);
      break;
    case GST_DISCOVERER_ERROR:
      g_print ("Discoverer error: %s\n", err->message);
      break;
    case GST_DISCOVERER_TIMEOUT:
      g_print ("Timeout\n");
      break;
    case GST_DISCOVERER_BUSY:
      g_print ("Busy\n");
      break;
    case GST_DISCOVERER_MISSING_PLUGINS:{
      const GstStructure *s;
      gchar *str;

      s = gst_discoverer_info_get_misc (info);
      str = gst_structure_to_string (s);

      g_print ("Missing plugins: %s\n", str);
      g_free (str);
      break;
    }
    case GST_DISCOVERER_OK:
      g_print ("Discovered '%s'\n", uri);
      break;
  }

  if (result != GST_DISCOVERER_OK) {
    g_printerr ("This URI cannot be played\n");
    return;
  }

  /* If we got no error, show the retrieved information */

  g_print ("\nDuration: %" GST_TIME_FORMAT "\n", GST_TIME_ARGS (gst_discoverer_info_get_duration (info)));

  tags = gst_discoverer_info_get_tags (info);
  if (tags) {
    g_print ("Tags:\n");
    gst_tag_list_foreach (tags, print_tag_foreach, GINT_TO_POINTER (1));
  }

  g_print ("Seekable: %s\n", (gst_discoverer_info_get_seekable (info) ? "yes" : "no"));

  g_print ("\n");

  sinfo = gst_discoverer_info_get_stream_info (info);
  if (!sinfo)
    return;

  g_print ("Stream information:\n");

  print_topology (sinfo, 1);

  gst_discoverer_stream_info_unref (sinfo);

  g_print ("\n");
}

/* This function is called when the discoverer has finished examining
 * all the URIs we provided.*/
static void on_finished_cb (GstDiscoverer *discoverer, CustomData *data) {
  g_print ("Finished discovering\n");

  g_main_loop_quit (data->loop);
}

int main (int argc, char **argv) {
  CustomData data;
  GError *err = NULL;
  gchar *uri = "https://gstreamer.freedesktop.org/data/media/sintel_trailer-480p.webm";

  /* if a URI was provided, use it instead of the default one */
  if (argc > 1) {
    uri = argv[1];
  }

  /* Initialize custom data structure */
  memset (&data, 0, sizeof (data));

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

  g_print ("Discovering '%s'\n", uri);

  /* Instantiate the Discoverer */
  data.discoverer = gst_discoverer_new (5 * GST_SECOND, &err);
  if (!data.discoverer) {
    g_print ("Error creating discoverer instance: %s\n", err->message);
    g_clear_error (&err);
    return -1;
  }

  /* Connect to the interesting signals */
  g_signal_connect (data.discoverer, "discovered", G_CALLBACK (on_discovered_cb), &data);
  g_signal_connect (data.discoverer, "finished", G_CALLBACK (on_finished_cb), &data);

  /* Start the discoverer process (nothing to do yet) */
  gst_discoverer_start (data.discoverer);

  /* Add a request to process asynchronously the URI passed through the command line */
  if (!gst_discoverer_discover_uri_async (data.discoverer, uri)) {
    g_print ("Failed to start discovering URI '%s'\n", uri);
    g_object_unref (data.discoverer);
    return -1;
  }

  /* Create a GLib Main Loop and set it to run, so we can wait for the signals */
  data.loop = g_main_loop_new (NULL, FALSE);
  g_main_loop_run (data.loop);

  /* Stop the discoverer process */
  gst_discoverer_stop (data.discoverer);

  /* Free resources */
  g_object_unref (data.discoverer);
  g_main_loop_unref (data.loop);

  return 0;
}

信息需要帮忙?

如果您需要帮助来编译此代码,请参阅针对您的平台构建教程 部分:LinuxMac OS XWindows,或在 Linux 上使用此特定命令:

gcc basic-tutorial-9.c -o basic-tutorial-9 pkg-config --cflags --libs gstreamer-1.0 gstreamer-pbutils-1.0``

如果您需要帮助来运行此代码,请参阅适用于您的平台的运行教程部分: LinuxMac OS XWindows

本教程打开作为命令行中第一个参数传递的 URI(如果未提供,则打开默认 URI)并在屏幕上输出有关它的信息。如果媒体位于 Internet 上,应用程序可能需要一些时间才能做出反应,具体取决于您的连接速度。

所需的库:gstreamer-pbutils-1.0 gstreamer-1.0

演练

这些是使用的主要步骤GstDiscoverer

/* Instantiate the Discoverer */
data.discoverer = gst_discoverer_new (5 * GST_SECOND, &err);
if (!data.discoverer) {
  g_print ("Error creating discoverer instance: %s\n", err->message);
  g_clear_error (&err);
  return -1;
}

gst_discoverer_new()创建一个新的 Discoverer 对象。第一个参数是每个文件的超时时间,以纳秒为单位( GST_SECOND为简单起见,使用宏)。

/* Connect to the interesting signals */
g_signal_connect (data.discoverer, "discovered", G_CALLBACK (on_discovered_cb), &data);
g_signal_connect (data.discoverer, "finished", G_CALLBACK (on_finished_cb), &data);

像往常一样连接到有趣的信号。我们在片段中讨论它们的回调。

/* Start the discoverer process (nothing to do yet) */
gst_discoverer_start (data.discoverer);

gst_discoverer_start()启动发现过程,但我们还没有提供任何要发现的 URI。这是接下来完成的:

/* Add a request to process asynchronously the URI passed through the command line */
if (!gst_discoverer_discover_uri_async (data.discoverer, uri)) {
  g_print ("Failed to start discovering URI '%s'\n", uri);
  g_object_unref (data.discoverer);
  return -1;
}

gst_discoverer_discover_uri_async()将提供的 URI 排入队列以供发现。多个 URI 可以使用此函数排队。当它们中的每一个的发现过程完成时,注册的回调函数将被激活。

/* Create a GLib Main Loop and set it to run, so we can wait for the signals */
data.loop = g_main_loop_new (NULL, FALSE);
g_main_loop_run (data.loop);

通常的 GLib 主循环被实例化并执行。g_main_loop_quit()当从回调中调用 时,我们将退出它on_finished_cb

/* Stop the discoverer process */
gst_discoverer_stop (data.discoverer);

一旦我们完成了发现者,我们就用 停止它 gst_discoverer_stop()并用 取消引用它g_object_unref()

现在让我们回顾一下我们注册的回调:

/* This function is called every time the discoverer has information regarding
 * one of the URIs we provided.*/
static void on_discovered_cb (GstDiscoverer *discoverer, GstDiscovererInfo *info, GError *err, CustomData *data) {
  GstDiscovererResult result;
  const gchar *uri;
  const GstTagList *tags;
  GstDiscovererStreamInfo *sinfo;

  uri = gst_discoverer_info_get_uri (info);
  result = gst_discoverer_info_get_result (info);

我们来到这里是因为 Discoverer 已经完成了对一个 URI 的处理,并为我们提供了一个GstDiscovererInfo包含所有信息的结构。

第一步是检索此调用引用的特定 URI(如果我们有多个发现进程正在运行,在本例中不是这种情况)和gst_discoverer_info_get_uri()发现结果gst_discoverer_info_get_result()

switch (result) {
  case GST_DISCOVERER_URI_INVALID:
    g_print ("Invalid URI '%s'\n", uri);
    break;
  case GST_DISCOVERER_ERROR:
    g_print ("Discoverer error: %s\n", err->message);
    break;
  case GST_DISCOVERER_TIMEOUT:
    g_print ("Timeout\n");
    break;
  case GST_DISCOVERER_BUSY:
    g_print ("Busy\n");
    break;
  case GST_DISCOVERER_MISSING_PLUGINS:{
    const GstStructure *s;
    gchar *str;

    s = gst_discoverer_info_get_misc (info);
    str = gst_structure_to_string (s);

    g_print ("Missing plugins: %s\n", str);
    g_free (str);
    break;
  }
  case GST_DISCOVERER_OK:
    g_print ("Discovered '%s'\n", uri);
    break;
}

if (result != GST_DISCOVERER_OK) {
  g_printerr ("This URI cannot be played\n");
  return;
}

如代码所示,除此之外的任何结果都GST_DISCOVERER_OK意味着出现了某种问题,无法播放此 URI。原因可能各不相同,但枚举值非常明确(GST_DISCOVERER_BUSY只能在同步模式下发生,本例中未使用)。

GstDiscovererInfo如果没有发生错误,可以使用不同的 gst_discoverer_info_get_*方法(例如, )从结构中检索信息 gst_discoverer_info_get_duration()

由列表组成的信息位,如标签和流信息,需要一些额外的解析:

tags = gst_discoverer_info_get_tags (info);
if (tags) {
  g_print ("Tags:\n");
  gst_tag_list_foreach (tags, print_tag_foreach, GINT_TO_POINTER (1));
}

标签是附加到媒体的元数据(标签)。可以使用 来检查它们gst_tag_list_foreach(),这将调用print_tag_foreach找到的每个标签(例如,也可以手动遍历列表,或者可以使用 搜索特定标签 gst_tag_list_get_string())。的代码print_tag_foreach几乎是不言自明的。

sinfo = gst_discoverer_info_get_stream_info (info);
if (!sinfo)
  return;

g_print ("Stream information:\n");

print_topology (sinfo, 1);

gst_discoverer_stream_info_unref (sinfo);

gst_discoverer_info_get_stream_info()返回GstDiscovererStreamInfo在函数中解析的结构print_topology,然后用 丢弃gst_discoverer_stream_info_unref()

/* Print information regarding a stream and its substreams, if any */
static void print_topology (GstDiscovererStreamInfo *info, gint depth) {
  GstDiscovererStreamInfo *next;

  if (!info)
    return;

  print_stream_info (info, depth);

  next = gst_discoverer_stream_info_get_next (info);
  if (next) {
    print_topology (next, depth + 1);
    gst_discoverer_stream_info_unref (next);
  } else if (GST_IS_DISCOVERER_CONTAINER_INFO (info)) {
    GList *tmp, *streams;

    streams = gst_discoverer_container_info_get_streams (GST_DISCOVERER_CONTAINER_INFO (info));
    for (tmp = streams; tmp; tmp = tmp->next) {
      GstDiscovererStreamInfo *tmpinf = (GstDiscovererStreamInfo *) tmp->data;
      print_topology (tmpinf, depth + 1);
    }
    gst_discoverer_stream_info_list_free (streams);
  }
}

print_stream_info函数的代码也几乎不言自明:它打印流的功能,然后打印相关的上限,print_tag_foreach也使用。

然后,print_topology寻找下一个要显示的元素。如果 gst_discoverer_stream_info_get_next()返回一个非 NULL 流信息,它指的是我们的后代,应该显示。否则,如果我们是一个容器,则递归调用print_topology我们通过 获得的每个孩子gst_discoverer_container_info_get_streams()。否则,我们就是一个最终流,不需要递归(这部分 Discoverer API 诚然有点晦涩难懂)。

结论

本教程展示了:

  • 如何使用GstDiscoverer
  • 如何通过查看使用获得的返回码来确定 URI 是否可播放gst_discoverer_info_get_result()

很高兴有你在这里,很快再见!

基础教程10:GStreamer工具

目标

GStreamer 附带了一组工具,从方便的到绝对必要的。本教程没有代码,高枕无忧,我们将教您:

  • 如何从命令行构建和运行 GStreamer 管道,完全不使用 C!
  • 如何找出可用的 GStreamer 元素及其功能。
  • 如何发现媒体文件的内部结构。

介绍

这些工具在 GStreamer 二进制文件的 bin 目录中可用。你需要移动到这个目录来执行它们,因为它没有添加到系统的PATH环境变量中(避免污染太多)。

只需打开一个终端(或控制台窗口)并转到bin您的 GStreamer 安装目录(再次阅读安装 GStreamer部分以找出它的位置),您就可以开始输入本教程中给出的命令了。

信息

在 Linux 上,您应该使用与您的发行版一起安装的 GStreamer 版本,这些工具应该与一个名为gstreamer1 Fedora 风格发行版或gstreamer1.0-toolsDebian/Ubuntu 风格发行版的包一起安装。

为了允许多个版本的 GStreamer 在同一个系统中共存,这些工具是版本化的,即在它们的名称后附加一个 GStreamer 版本号。该版本基于GStreamer 1.0,所以工具名称gst-launch-1.0gst-inspect-1.0``gst-discoverer-1.0

gst-launch-1.0

该工具接受管道的文本描述,将其实例化,并将其设置为 PLAYING 状态。它允许您在使用 GStreamer API 调用进行实际实施之前快速检查给定管道是否有效。

请记住,它只能创建简单的管道。特别是,它只能在一定程度上模拟管道与应用程序的交互。在任何情况下,快速测试管道都非常方便,全世界的 GStreamer 开发人员每天都在使用它。

请注意,这gst-launch-1.0主要是开发人员的调试工具。你不应该在它之上构建应用程序。相反,使用gst_parse_launch()GStreamer API 的功能作为从管道描述构建管道的简单方法。

虽然构建管道描述的规则非常简单,但多个元素的串联可以很快使此类描述像黑魔法一样。不要害怕,因为每个人 gst-launch-1.0最终都会学习语法。

gst-launch-1.0 的命令行由一系列选项组成,后跟一个 PIPELINE-DESCRIPTION。 接下来给出了一些简化的说明,请参阅参考页上的完整文档gst-launch-1.0

元素

在简单的形式中,PIPELINE-DESCRIPTION 是一个由感叹号 (!) 分隔的元素类型列表。继续并输入以下命令:

gst-launch-1.0 videotestsrc ! videoconvert ! autovideosink

您应该会看到一个带有动画视频模式的窗口。在终端上使用 CTRL+C 停止程序。

这实例化了一个新的类型元素videotestsrc(一个生成示例视频模式的元素),一个videoconvert(一个进行原始视频格式转换的元素,确保其他元素可以相互理解)和一个autovideosink(呈现视频的窗口) . 然后,GStreamer 尝试将每个元素的输出链接到描述中出现在其右侧的元素的输入。如果有多个输入或输出 Pad 可用,则 Pad Caps 用于查找两个兼容的 Pad。

特性

可以将属性附加到元素,形式为 *property=value *(可以指定多个属性,以空格分隔)。使用gst-inspect-1.0工具(接下来解释)找出元素的可用属性。

gst-launch-1.0 videotestsrc pattern=11 ! videoconvert ! autovideosink

您应该会看到一个由圆圈组成的静态视频图案。

命名元素

元素可以使用name属性命名,这样可以创建涉及分支的复杂管道。名称允许链接到先前在描述中创建的元素,并且对于使用具有多个输出板的元素是必不可少的,例如解复用器或三通。

命名元素使用其名称后跟一个点来引用。

gst-launch-1.0 videotestsrc ! videoconvert ! tee name=t ! queue ! autovideosink t. ! queue ! autovideosink

您应该看到两个视频窗口,显示相同的示例视频模式。如果您只看到一个,请尝试移动它,因为它可能位于第二个窗口的顶部。

此示例实例化 a videotestsrc,链接到 a videoconvert,链接到 a tee(请记住基础教程 7:多线程和 Pad 可用性,atee将通过其输入 pad 的所有内容复制到其每个输出 pad)。Thetee被简单地命名为“t”(使用name属性),然后链接到 aqueue和 an autovideosinktee使用“t”表示相同。(注意点)然后链接到第二个 queue和第二个autovideosink

要了解为什么需要队列,请阅读基础教程 7:多线程和 Pad 可用性

您可能希望直接指定 Pad,而不是让 GStreamer 选择在链接两个元素时使用哪个 Pad。您可以通过在元素名称后添加一个点加上 Pad 名称来完成此操作(它必须是命名元素)。使用该工具了解元素的垫的名称gst-inspect-1.0

这很有用,例如,当您想要从多路分离器中检索一个特定的流时:

gst-launch-1.0 souphttpsrc location=https://gstreamer.freedesktop.org/data/media/sintel_trailer-480p.webm ! matroskademux name=d d.video_0 ! matroskamux ! filesink location=sintel_video.mkv

souphttpsrc这使用webm 格式(一种特殊的 Matroska 容器,参见基础教程 2:GStreamer 概念)从 Internet 获取媒体文件。然后我们使用打开容器matroskademux。该媒体包含音频和视频,因此matroskademux将创建两个输出 Pad,命名为 video_0audio_0。我们链接video_0到一个matroskamux元素以将视频流重新打包到一个新的容器中,最后将它链接到一个filesink,它将流写入一个名为“sintel_video.mkv”的文件(该location属性指定文件的名称)。

总而言之,我们获取了一个 webm 文件,去除了音频,并生成了一个包含视频的新 matroska 文件。如果我们只想保留音频:

gst-launch-1.0 souphttpsrc location=https://gstreamer.freedesktop.org/data/media/sintel_trailer-480p.webm ! matroskademux name=d d.audio_0 ! vorbisparse ! matroskamux ! filesink location=sintel_audio.mka

vorbisparse元素需要从流中提取一些信息并将其放入 Pad Caps,因此下一个元素 matroskamux知道如何处理流。在视频的情况下,这是没有必要的,因为matroskademux已经提取了这些信息并将其添加到 Caps 中。

请注意,在上面的两个示例中,没有媒体被解码或播放。我们刚刚从一个容器转移到另一个容器(再次解复用和重新复用)。

盖帽过滤器

当一个元素有多个输出焊盘时,可能会发生与下一个元素的链接不明确的情况:下一个元素可能有多个兼容的输入焊盘,或者它的输入焊盘可能与所有输出的焊盘电容兼容垫。在这些情况下,GStreamer 将使用可用的第一个 pad 进行链接,这几乎等于说 GStreamer 将随机选择一个输出 pad。

考虑以下管道:

gst-launch-1.0 souphttpsrc location=https://gstreamer.freedesktop.org/data/media/sintel_trailer-480p.webm ! matroskademux ! filesink location=test

这是与前面示例中相同的媒体文件和多路分解器。输入的 Pad CapsfilesinkANY,意味着它可以接受任何类型的媒体。的两个输出板中的哪一个matroskademux将链接到文件接收器?video_0或者audio_0?你不可能知道。

不过,您可以通过使用命名垫(如上一节所述)或使用Caps Filters来消除这种歧义:

gst-launch-1.0 souphttpsrc location=https://gstreamer.freedesktop.org/data/media/sintel_trailer-480p.webm ! matroskademux ! video/x-vp8 ! matroskamux ! filesink location=sintel_video.mkv

Caps Filter 的行为就像一个传递元素,它什么都不做,只接受具有给定 Caps 的媒体,有效地解决了歧义。在此示例中,我们在matroskademux和 之间matroskamux添加了一个Caps Filter 以指定我们对可以产生此类视频的video/x-vp8输出垫感兴趣。matroskademux

要找出元素接受和产生的上限,请使用该 gst-inspect-1.0工具。要找出特定文件中包含的上限,请使用该gst-discoverer-1.0工具。要找出某个元素正在为特定管道生成的上限,请gst-launch-1.0照常运行,并 –v提供打印上限信息的选项。

例子

playbin使用(如基础教程 1:Hello world!)播放媒体文件:

gst-launch-1.0 playbin uri=https://gstreamer.freedesktop.org/data/media/sintel_trailer-480p.webm

一个完整的播放管道,带有音频和视频(或多或少与playbin内部创建的管道相同):

gst-launch-1.0 souphttpsrc location=https://gstreamer.freedesktop.org/data/media/sintel_trailer-480p.webm ! matroskademux name=d ! queue ! vp8dec ! videoconvert ! autovideosink d. ! queue ! vorbisdec ! audioconvert ! audioresample ! autoaudiosink

一个转码管道,它打开 webm 容器并解码两个流(通过 uridecodebin),然后用不同的编解码器重新编码音频和视频分支,并将它们放回到 Ogg 容器中(只是为了它)。

gst-launch-1.0 uridecodebin uri=https://gstreamer.freedesktop.org/data/media/sintel_trailer-480p.webm name=d ! queue ! theoraenc ! oggmux name=m ! filesink location=sintel.ogg d. ! queue ! audioconvert ! audioresample ! flacenc ! m.

重新缩放管道。videoscale只要输入和输出上限的帧大小不同,该元素就会执行重新缩放操作。Caps Filter 将输出上限设置为 320x200。

gst-launch-1.0 uridecodebin uri=https://gstreamer.freedesktop.org/data/media/sintel_trailer-480p.webm ! queue ! videoscale ! video/x-raw-yuv,width=320,height=200 ! videoconvert ! autovideosink

的简短描述gst-launch-1.0应该足以让您入门。请记住,您可以在此处获得完整的文档

gst-inspect-1.0

该工具具有三种操作模式:

  • 如果没有参数,它会列出所有可用的元素类型,即可用于实例化新元素的类型。
  • 以文件名作为参数,它将文件视为 GStreamer 插件,尝试打开它,并列出其中描述的所有元素。
  • 使用 GStreamer 元素名称作为参数,它列出了关于该元素的所有信息。

让我们看一个第三种模式的例子:

gst-inspect-1.0 vp8dec

Factory Details:
  Rank                     primary (256)
  Long-name                On2 VP8 Decoder
  Klass                    Codec/Decoder/Video
  Description              Decode VP8 video streams
  Author                   David Schleef <ds@entropywave.com>, Sebastian Dröge <sebastian.droege@collabora.co.uk>

Plugin Details:
  Name                     vpx
  Description              VP8 plugin
  Filename                 /usr/lib64/gstreamer-1.0/libgstvpx.so
  Version                  1.6.4
  License                  LGPL
  Source module            gst-plugins-good
  Source release date      2016-04-14
  Binary package           Fedora GStreamer-plugins-good package
  Origin URL               http://download.fedoraproject.org

GObject
 +----GInitiallyUnowned
       +----GstObject
             +----GstElement
                   +----GstVideoDecoder
                         +----GstVP8Dec

Pad Templates:
  SINK template: 'sink'
    Availability: Always
    Capabilities:
      video/x-vp8

  SRC template: 'src'
    Availability: Always
    Capabilities:
      video/x-raw
                 format: I420
                  width: [ 1, 2147483647 ]
                 height: [ 1, 2147483647 ]
              framerate: [ 0/1, 2147483647/1 ]


Element Flags:
  no flags set

Element Implementation:
  Has change_state() function: gst_video_decoder_change_state

Element has no clocking capabilities.
Element has no URI handling capabilities.

Pads:
  SINK: 'sink'
    Pad Template: 'sink'
  SRC: 'src'
    Pad Template: 'src'

Element Properties:
  name                : The name of the object
                        flags: readable, writable
                        String. Default: "vp8dec0"
  parent              : The parent of the object
                        flags: readable, writable
                        Object of type "GstObject"
  post-processing     : Enable post processing
                        flags: readable, writable
                        Boolean. Default: false
  post-processing-flags: Flags to control post processing
                        flags: readable, writable
                        Flags "GstVP8DecPostProcessingFlags" Default: 0x00000403, "mfqe+demacroblock+deblock"
                           (0x00000001): deblock          - Deblock
                           (0x00000002): demacroblock     - Demacroblock
                           (0x00000004): addnoise         - Add noise
                           (0x00000400): mfqe             - Multi-frame quality enhancement
  deblocking-level    : Deblocking level
                        flags: readable, writable
                        Unsigned Integer. Range: 0 - 16 Default: 4
  noise-level         : Noise level
                        flags: readable, writable
                        Unsigned Integer. Range: 0 - 16 Default: 0
  threads             : Maximum number of decoding threads
                        flags: readable, writable
                        Unsigned Integer. Range: 1 - 16 Default: 0

最相关的部分是:

  • Pad Templates:这列出了该元素可以拥有的所有类型的 Pad,以及它们的功能。在这里您可以查看一个元素是否可以与另一个元素链接。在这种情况下,它只有一个 sink pad 模板,仅接受 video/x-vp8(VP8 格式的编码视频数据),只有一个 source pad 模板,产生video/x-raw(解码视频数据)。
  • 元素属性:这列出了元素的属性,以及它们的类型和可接受的值。

有关更多信息,您可以查看的文档页面gst-inspect-1.0

gst-discoverer-1.0

此工具是基础教程 9:媒体信息收集GstDiscoverer中所示对象的包装器。它从命令行接受 URI 并打印有关 GStreamer 可以提取的媒体的所有信息。找出用于生成媒体的容器和编解码器以及因此需要将哪些元素放入管道中以播放它很有用。

用于gst-discoverer-1.0 --help获取可用选项列表,这些选项基本上控制输出的详细程度。

让我们看一个例子:

gst-discoverer-1.0 https://gstreamer.freedesktop.org/data/media/sintel_trailer-480p.webm -v

Analyzing https://gstreamer.freedesktop.org/data/media/sintel_trailer-480p.webm
Done discovering https://gstreamer.freedesktop.org/data/media/sintel_trailer-480p.webm
Topology:
  container: video/webm
    audio: audio/x-vorbis, channels=(int)2, rate=(int)48000
      Codec:
        audio/x-vorbis, channels=(int)2, rate=(int)48000
      Additional info:
        None
      Language: en
      Channels: 2
      Sample rate: 48000
      Depth: 0
      Bitrate: 80000
      Max bitrate: 0
      Tags:
        taglist, language-code=(string)en, container-format=(string)Matroska, audio-codec=(string)Vorbis, application-name=(string)ffmpeg2theora-0.24, encoder=(string)"Xiph.Org\ libVorbis\ I\ 20090709", encoder-version=(uint)0, nominal-bitrate=(uint)80000, bitrate=(uint)80000;
    video: video/x-vp8, width=(int)854, height=(int)480, framerate=(fraction)25/1
      Codec:
        video/x-vp8, width=(int)854, height=(int)480, framerate=(fraction)25/1
      Additional info:
        None
      Width: 854
      Height: 480
      Depth: 0
      Frame rate: 25/1
      Pixel aspect ratio: 1/1
      Interlaced: false
      Bitrate: 0
      Max bitrate: 0
      Tags:
        taglist, video-codec=(string)"VP8\ video", container-format=(string)Matroska;

Properties:
  Duration: 0:00:52.250000000
  Seekable: yes
  Tags:
      video codec: VP8 video
      language code: en
      container format: Matroska
      application name: ffmpeg2theora-0.24
      encoder: Xiph.Org libVorbis I 20090709
      encoder version: 0
      audio codec: Vorbis
      nominal bitrate: 80000
      bitrate: 80000

结论

本教程展示了:

  • 如何使用该工具从命令行构建和运行 GStreamer 管道gst-launch-1.0
  • 如何使用该工具找出可用的 GStreamer 元素及其功能gst-inspect-1.0
  • 如何发现媒体文件的内部结构,使用 gst-discoverer-1.0.

很高兴有你在这里,很快再见!

基础教程十一:调试工具

目标

有时事情不会按预期进行,从总线检索到的错误消息(如果有的话)只是没有提供足够的信息。幸运的是,GStreamer 附带了大量的调试信息,这些信息通常可以暗示问题所在。本教程展示:

  • 如何从 GStreamer 获取更多调试信息。
  • 如何将您自己的调试信息打印到 GStreamer 日志中。
  • 如何获取流水线图

打印调试信息

调试日志

GStreamer 及其插件充满了调试跟踪,这是在代码中将一条特别有趣的信息打印到控制台的地方,以及时间戳、进程、类别、源代码文件、函数和元素信息。

调试输出由GST_DEBUG环境变量控制。这是一个示例GST_DEBUG=2

0:00:00.868050000  1592   09F62420 WARN                 filesrc gstfilesrc.c:1044:gst_file_src_start:<filesrc0> error: No such file "non-existing-file.webm"

如您所见,这是相当多的信息。事实上,GStreamer 调试日志非常冗长,当完全启用时,它可以使应用程序无响应(由于控制台滚动)或填满数兆字节的文本文件(当重定向到文件时)。因此,日志是分类的,您很少需要一次启用所有类别。

第一类是调试级别,它是一个指定所需输出量的数字:

| # | Name    | Description                                                    |
|---|---------|----------------------------------------------------------------|
| 0 | none    | No debug information is output.                                |
| 1 | ERROR   | Logs all fatal errors. These are errors that do not allow the  |
|   |         | core or elements to perform the requested action. The          |
|   |         | application can still recover if programmed to handle the      |
|   |         | conditions that triggered the error.                           |
| 2 | WARNING | Logs all warnings. Typically these are non-fatal, but          |
|   |         | user-visible problems are expected to happen.                  |
| 3 | FIXME   | Logs all "fixme" messages. Those typically that a codepath that|
|   |         | is known to be incomplete has been triggered. It may work in   |
|   |         | most cases, but may cause problems in specific instances.      |
| 4 | INFO    | Logs all informational messages. These are typically used for  |
|   |         | events in the system that only happen once, or are important   |
|   |         | and rare enough to be logged at this level.                    |
| 5 | DEBUG   | Logs all debug messages. These are general debug messages for  |
|   |         | events that happen only a limited number of times during an    |
|   |         | object's lifetime; these include setup, teardown, change of    |
|   |         | parameters, etc.                                               |
| 6 | LOG     | Logs all log messages. These are messages for events that      |
|   |         | happen repeatedly during an object's lifetime; these include   |
|   |         | streaming and steady-state conditions. This is used for log    |
|   |         | messages that happen on every buffer in an element for example.|
| 7 | TRACE   | Logs all trace messages. Those are message that happen very    |
|   |         | very often. This is for example is each time the reference     |
|   |         | count of a GstMiniObject, such as a GstBuffer or GstEvent, is  |
|   |         | modified.                                                      |
| 9 | MEMDUMP | Logs all memory dump messages. This is the heaviest logging and|
|   |         | may include dumping the content of blocks of memory.           |
+------------------------------------------------------------------------------+

要启用调试输出,请将GST_DEBUG环境变量设置为所需的调试级别。也将显示低于该级别的所有级别(即,如果您设置GST_DEBUG=2,您将同时收到ERRORWARNING消息)。

此外,每个插件或 GStreamer 的一部分都定义了自己的类别,因此您可以为每个单独的类别指定调试级别。例如,GST_DEBUG=2,audiotestsrc:6, 将对audiotestsrc元素使用调试级别 6,对所有其他元素使用 2。

GST_DEBUG然后,环境变量是一个以逗号分隔的类别列表 :级别开头有一个可选级别,代表所有类别的默认调试级别。

通配符'*'也可用。例如, GST_DEBUG=2,audio*:6将对所有以单词开头的类别使用调试级别 6 audioGST_DEBUG=*:2相当于 GST_DEBUG=2.

用于gst-launch-1.0 --gst-debug-help获取所有已注册类别的列表。请记住,每个插件都会注册自己的类别,因此,在安装或删除插件时,此列表可能会发生变化。

GST_DEBUG当发布在 GStreamer 总线上的错误信息无法帮助您确定问题时使用。通常的做法是将输出日志重定向到一个文件,然后稍后检查它,搜索特定消息。

GStreamer 允许自定义调试信息处理程序,但在使用默认处理程序时,调试输出中每一行的内容如下所示:

0:00:00.868050000  1592   09F62420 WARN                 filesrc gstfilesrc.c:1044:gst_file_src_start:<filesrc0> error: No such file "non-existing-file.webm"

这就是信息的格式:

| Example          | Explained                                                 |
|------------------|-----------------------------------------------------------|
|0:00:00.868050000 | Time stamp in HH:MM:SS.sssssssss format since the start of|
|                  | the program.                                              |
|1592              | Process ID from which the message was issued. Useful when |
|                  | your problem involves multiple processes.                 |
|09F62420          | Thread ID from which the message was issued. Useful when  |
|                  | your problem involves multiple threads.                   |
|WARN              | Debug level of the message.                               |
|filesrc           | Debug Category of the message.                            |
|gstfilesrc.c:1044 | Source file and line in the GStreamer source code where   |
|                  | this message was issued.                                  |
|gst_file_src_start| Function that issued the message.                         |
|<filesrc0>        | Name of the object that issued the message. It can be an  |
|                  | element, a pad, or something else. Useful when you have   |
|                  | multiple elements of the same kind and need to distinguish|
|                  | among them. Naming your elements with the name property   |
|                  | makes this debug output more readable but GStreamer       |
|                  | assigns each new element a unique name by default.        |
| error: No such   |                                                           |
| file ....        | The actual message.                                       |
+------------------------------------------------------------------------------+

添加自己的调试信息

在与 GStreamer 交互的代码部分,使用 GStreamer 的调试工具很有趣。通过这种方式,您可以将所有调试输出放在同一个文件中,并且保留不同消息之间的时间关系。

为此,请使用GST_ERROR()GST_WARNING()GST_INFO()GST_LOG()GST_DEBUG()。它们接受与 相同的参数 printf,并且它们使用default类别(default将在输出日志中显示为调试类别)。

要将类别更改为更有意义的内容,请在代码顶部添加这两行:

GST_DEBUG_CATEGORY_STATIC (my_category);
#define GST_CAT_DEFAULT my_category

然后在你初始化 GStreamer 之后这个 gst_init()

GST_DEBUG_CATEGORY_INIT (my_category, "my category", 0, "This is my very own");

这将注册一个新类别(即,在您的应用程序期间:它不存储在任何文件中),并将其设置为代码的默认类别。请参阅文档GST_DEBUG_CATEGORY_INIT()

获取流水线图

对于那些您的管道开始变得过大并且您无法跟踪什么与什么相连的情况,GStreamer 具有输出图形文件的能力。这些文件可使用GraphViz.dot等免费程序读取,描述管道的拓扑结构,以及每个链接中协商的上限。

playbin 这在使用像or 这样的一体化元素时也非常方便uridecodebin,它们在其中实例化了多个元素。使用这些.dot文件了解他们在内部创建的管道(并在此过程中学习一些 GStreamer)。

要获取.dot文件,只需将环境变量设置GST_DEBUG_DUMP_DOT_DIR为指向要放置文件的文件夹即可。gst-launch-1.0将在每次状态更改时创建一个.dot文件,因此您可以看到上限协商的演变。取消设置变量以禁用此功能。在您的应用程序中,您可以使用 GST_DEBUG_BIN_TO_DOT_FILE()GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS()宏在您方便的时候生成.dot文件。

Here 你有一个 playbin 生成的管道类型的例子。它非常复杂,因为playbin可以处理许多不同的情况:您的手动管道通常不需要这么长。如果您的手动管道开始变得非常大,请考虑使用playbin.

img

要下载全尺寸图片,请使用本页顶部的附件链接(它是回形针图标)。

结论

本教程展示了:

  • 如何使用 GST_DEBUG环境变量从 GStreamer 获取更多调试信息。
  • 如何使用GST_ERROR()宏和相关项将您自己的调试信息打印到 GStreamer 日志中。
  • 如何使用 GST_DEBUG_DUMP_DOT_DIR环境变量获取管道图。

很高兴有你在这里,很快再见!

基础教程12:Streaming

目标

直接从 Internet 播放媒体而不将其存储在本地称为流媒体。每当我们使用以 开头的 URI 时,我们在整个教程中一直这样做http://。本教程展示了流式传输时需要牢记的几个额外要点。尤其:

  • 如何启用缓冲(以缓解网络问题)
  • 如何从中断中恢复(丢失时钟)

介绍

流式传输时,媒体块一旦从网络到达,就会被解码并排队等待呈现。这意味着如果一个块被延迟(这在 Internet 上并不罕见),演示队列可能会干涸并且媒体播放可能会停止。

通用的解决方案是构建一个“缓冲区”,即允许一定数量的媒体块在开始播放之前排队。通过这种方式,播放开始会稍微延迟,但是,如果某些块延迟了,则再现不会受到影响,因为队列中有更多块在等待。

事实证明,此解决方案已在 GStreamer 中实现,但之前的教程并未从中受益。某些元素,如内部的queue2multiqueue找到的playbin,能够构建此缓冲区并发布有关缓冲区级别(队列状态)的总线消息。如果缓冲区级别不够高(通常,只要缓冲区级别低于 100%),那么想要具有更多网络弹性的应用程序应该监听这些消息并暂停播放。

为了实现多个接收器(例如音频和视频接收器)之间的同步,使用了全局时钟。该时钟由 GStreamer 在所有可以提供时钟的元素中选择。在某些情况下,例如,RTP 源切换流或更改输出设备,此时钟可能会丢失,需要选择一个新时钟。这主要发生在处理流式处理时,因此本教程中解释了该过程。

当时钟丢失时,应用程序在总线上收到一条消息;要选择一个新的,应用程序只需将管道设置为 PAUSED然后再设置PLAYING一次。

网络弹性示例

将此代码复制到名为basic-tutorial-12.c.

basic-tutorial-12.c

#include <gst/gst.h>
#include <string.h>

typedef struct _CustomData {
  gboolean is_live;
  GstElement *pipeline;
  GMainLoop *loop;
} CustomData;

static void cb_message (GstBus *bus, GstMessage *msg, CustomData *data) {

  switch (GST_MESSAGE_TYPE (msg)) {
    case GST_MESSAGE_ERROR: {
      GError *err;
      gchar *debug;

      gst_message_parse_error (msg, &err, &debug);
      g_print ("Error: %s\n", err->message);
      g_error_free (err);
      g_free (debug);

      gst_element_set_state (data->pipeline, GST_STATE_READY);
      g_main_loop_quit (data->loop);
      break;
    }
    case GST_MESSAGE_EOS:
      /* end-of-stream */
      gst_element_set_state (data->pipeline, GST_STATE_READY);
      g_main_loop_quit (data->loop);
      break;
    case GST_MESSAGE_BUFFERING: {
      gint percent = 0;

      /* If the stream is live, we do not care about buffering. */
      if (data->is_live) break;

      gst_message_parse_buffering (msg, &percent);
      g_print ("Buffering (%3d%%)\r", percent);
      /* Wait until buffering is complete before start/resume playing */
      if (percent < 100)
        gst_element_set_state (data->pipeline, GST_STATE_PAUSED);
      else
        gst_element_set_state (data->pipeline, GST_STATE_PLAYING);
      break;
    }
    case GST_MESSAGE_CLOCK_LOST:
      /* Get a new clock */
      gst_element_set_state (data->pipeline, GST_STATE_PAUSED);
      gst_element_set_state (data->pipeline, GST_STATE_PLAYING);
      break;
    default:
      /* Unhandled message */
      break;
    }
}

int main(int argc, char *argv[]) {
  GstElement *pipeline;
  GstBus *bus;
  GstStateChangeReturn ret;
  GMainLoop *main_loop;
  CustomData data;

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

  /* Initialize our data structure */
  memset (&data, 0, sizeof (data));

  /* Build the pipeline */
  pipeline = gst_parse_launch ("playbin uri=https://gstreamer.freedesktop.org/data/media/sintel_trailer-480p.webm", NULL);
  bus = gst_element_get_bus (pipeline);

  /* 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;
  } else if (ret == GST_STATE_CHANGE_NO_PREROLL) {
    data.is_live = TRUE;
  }

  main_loop = g_main_loop_new (NULL, FALSE);
  data.loop = main_loop;
  data.pipeline = pipeline;

  gst_bus_add_signal_watch (bus);
  g_signal_connect (bus, "message", G_CALLBACK (cb_message), &data);

  g_main_loop_run (main_loop);

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

信息需要帮忙?

如果您需要帮助来编译此代码,请参阅针对您的平台构建教程 部分:LinuxMac OS XWindows,或在 Linux 上使用此特定命令:

gcc basic-tutorial-12.c -o basic-tutorial-12 pkg-config --cflags --libs gstreamer-1.0``

如果您需要帮助来运行此代码,请参阅适用于您的平台的运行教程部分: LinuxMac OS XWindows

本教程将打开一个窗口并显示一个带有音频的电影。媒体是从 Internet 获取的,因此窗口可能需要几秒钟才会出现,具体取决于您的连接速度。在控制台窗口中,您应该会看到一条缓冲消息,只有当缓冲达到 100% 时才应该开始播放。如果您的连接足够快并且不需要缓冲,则此百分比可能根本不会改变。

所需的库:gstreamer-1.0

演练

本教程做的唯一特别的事情是对某些消息做出反应;因此,初始化代码非常简单,现在应该是不言自明的。唯一的新功能是实时流的检测:

/* 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;
} else if (ret == GST_STATE_CHANGE_NO_PREROLL) {
  data.is_live = TRUE;
}

直播流无法暂停,因此它们在PAUSED状态中的行为就像在状态中一样PLAYING。设置直播流PAUSED成功,但返回GST_STATE_CHANGE_NO_PREROLL, 而不是 GST_STATE_CHANGE_SUCCESS表示这是直播。NO_PREROLL即使我们尝试将管道设置为,我们也会收到返回码PLAYING,因为状态变化是逐步发生的(从 NULL 到 READY,PAUSED再到PLAYING)。

我们关心实时流,因为我们想为它们禁用缓冲,所以我们注意到变量gst_element_set_state()中 的结果is_live

现在让我们回顾一下消息解析回调中有趣的部分:

case GST_MESSAGE_BUFFERING: {
  gint percent = 0;

  /* If the stream is live, we do not care about buffering. */
  if (data->is_live) break;

  gst_message_parse_buffering (msg, &percent);
  g_print ("Buffering (%3d%%)\r", percent);
  /* Wait until buffering is complete before start/resume playing */
  if (percent < 100)
    gst_element_set_state (data->pipeline, GST_STATE_PAUSED);
  else
    gst_element_set_state (data->pipeline, GST_STATE_PLAYING);
  break;
}

首先,如果这是实时源,请忽略缓冲消息。

我们解析缓冲消息以gst_message_parse_buffering()检索缓冲级别。

然后,我们在控制台上打印缓冲级别并将管道设置PAUSED为低于 100%。否则,我们将管道设置为 PLAYING.

在启动时,我们会看到缓冲级别在播放开始前上升到 100%,这正是我们想要实现的。如果稍后网络变慢或无响应并且我们的缓冲区耗尽,我们将收到级别低于 100% 的新缓冲消息,因此我们将再次暂停管道,直到建立足够的缓冲区。

case GST_MESSAGE_CLOCK_LOST:
  /* Get a new clock */
  gst_element_set_state (data->pipeline, GST_STATE_PAUSED);
  gst_element_set_state (data->pipeline, GST_STATE_PLAYING);
  break;

对于第二个网络问题,时钟丢失,我们简单地将管道设置为PAUSED并返回PLAYING,因此选择了一个新的时钟,等待在必要时接收新的媒体块。

结论

本教程描述了如何通过两个非常简单的预防措施为您的应用程序添加网络弹性:

  • 处理管道发送的缓冲消息
  • 处理时钟丢失

处理这些消息可以提高应用程序对网络问题的响应,从而提高整体播放的流畅度。

很高兴有你在这里,很快再见!

基础教程13:播放速度

目标

快进、倒放和慢动作都是统称为特技模式的技术,它们都有一个共同点,即修改正常播放速率。本教程展示了如何实现这些效果并将帧步进添加到交易中。特别是,它显示:

  • 如何改变播放速度,比正常情况更快和更慢,向前和向后。
  • 如何逐帧推进视频

介绍

快进是一种以高于其正常(预期)速度的速度播放媒体的技术;而慢动作使用的速度低于预期速度。反向播放做同样的事情,但向后,从流的结尾到开头。

所有这些技术所做的就是改变播放速率,对于正常播放,这是一个等于 1.0 的变量,对于快速模式大于 1.0(绝对值),对于慢速模式小于 1.0(绝对值),正向播放和正向播放负反向播放。

GStreamer 提供了两种改变播放速率的机制:Step Events 和 Seek Events。除了更改后续播放速率(仅限正值)之外,步骤事件还允许跳过给定数量的媒体。此外,Seek Events 允许跳转到流中的任何位置并设置正负播放速率。

基础教程 4:时间管理中已经显示了寻找事件,使用辅助函数来隐藏它们的复杂性。本教程更多地解释了如何使用这些事件。

Step Events 是一种更方便的改变播放速率的方法,因为创建它们所需的参数数量减少了;但是,它们有一些缺点,因此本教程中使用了 Seek Events。Step 事件只影响接收器(在管道的末端),因此它们只有在管道的其余部分可以支持以不同的速度运行时才会起作用,Seek 事件一直贯穿整个管道,因此每个元素都可以对它们做出反应. Step 事件的好处是它们可以更快地采取行动。步进事件也无法改变播放方向。

为了使用这些事件,它们被创建然后传递到管道,在那里它们向上游传播直到它们到达可以处理它们的元素。如果将事件传递到 bin 元素(如 )playbin,它将简单地将事件提供给其所有接收器,这将导致执行多次查找。常见的方法是通过或 属性检索其中一个playbin接收器,并将事件直接馈送到接收器中。video-sink``audio-sink

帧步进是一种允许逐帧播放视频的技术。它是通过暂停管道,然后发送 Step Events 每次跳过一帧来实现的。

特技模式播放器

将此代码复制到名为basic-tutorial-13.c.

basic-tutorial-13.c

#include <string.h>
#include <stdio.h>
#include <gst/gst.h>

#ifdef __APPLE__
#include <TargetConditionals.h>
#endif

typedef struct _CustomData
{
  GstElement *pipeline;
  GstElement *video_sink;
  GMainLoop *loop;

  gboolean playing;             /* Playing or Paused */
  gdouble rate;                 /* Current playback rate (can be negative) */
} CustomData;

/* Send seek event to change rate */
static void
send_seek_event (CustomData * data)
{
  gint64 position;
  GstEvent *seek_event;

  /* Obtain the current position, needed for the seek event */
  if (!gst_element_query_position (data->pipeline, GST_FORMAT_TIME, &position)) {
    g_printerr ("Unable to retrieve current position.\n");
    return;
  }

  /* Create the seek event */
  if (data->rate > 0) {
    seek_event =
        gst_event_new_seek (data->rate, GST_FORMAT_TIME,
        GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE, GST_SEEK_TYPE_SET,
        position, GST_SEEK_TYPE_END, 0);
  } else {
    seek_event =
        gst_event_new_seek (data->rate, GST_FORMAT_TIME,
        GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE, GST_SEEK_TYPE_SET, 0,
        GST_SEEK_TYPE_SET, position);
  }

  if (data->video_sink == NULL) {
    /* If we have not done so, obtain the sink through which we will send the seek events */
    g_object_get (data->pipeline, "video-sink", &data->video_sink, NULL);
  }

  /* Send the event */
  gst_element_send_event (data->video_sink, seek_event);

  g_print ("Current rate: %g\n", data->rate);
}

/* Process keyboard input */
static gboolean
handle_keyboard (GIOChannel * source, GIOCondition cond, CustomData * data)
{
  gchar *str = NULL;

  if (g_io_channel_read_line (source, &str, NULL, NULL,
          NULL) != G_IO_STATUS_NORMAL) {
    return TRUE;
  }

  switch (g_ascii_tolower (str[0])) {
    case 'p':
      data->playing = !data->playing;
      gst_element_set_state (data->pipeline,
          data->playing ? GST_STATE_PLAYING : GST_STATE_PAUSED);
      g_print ("Setting state to %s\n", data->playing ? "PLAYING" : "PAUSE");
      break;
    case 's':
      if (g_ascii_isupper (str[0])) {
        data->rate *= 2.0;
      } else {
        data->rate /= 2.0;
      }
      send_seek_event (data);
      break;
    case 'd':
      data->rate *= -1.0;
      send_seek_event (data);
      break;
    case 'n':
      if (data->video_sink == NULL) {
        /* If we have not done so, obtain the sink through which we will send the step events */
        g_object_get (data->pipeline, "video-sink", &data->video_sink, NULL);
      }

      gst_element_send_event (data->video_sink,
          gst_event_new_step (GST_FORMAT_BUFFERS, 1, ABS (data->rate), TRUE,
              FALSE));
      g_print ("Stepping one frame\n");
      break;
    case 'q':
      g_main_loop_quit (data->loop);
      break;
    default:
      break;
  }

  g_free (str);

  return TRUE;
}

int
tutorial_main (int argc, char *argv[])
{
  CustomData data;
  GstStateChangeReturn ret;
  GIOChannel *io_stdin;

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

  /* Initialize our data structure */
  memset (&data, 0, sizeof (data));

  /* Print usage map */
  g_print ("USAGE: Choose one of the following options, then press enter:\n"
      " 'P' to toggle between PAUSE and PLAY\n"
      " 'S' to increase playback speed, 's' to decrease playback speed\n"
      " 'D' to toggle playback direction\n"
      " 'N' to move to next frame (in the current direction, better in PAUSE)\n"
      " 'Q' to quit\n");

  /* Build the pipeline */
  data.pipeline =
      gst_parse_launch
      ("playbin uri=https://gstreamer.freedesktop.org/data/media/sintel_trailer-480p.webm",
      NULL);

  /* Add a keyboard watch so we get notified of keystrokes */
#ifdef G_OS_WIN32
  io_stdin = g_io_channel_win32_new_fd (fileno (stdin));
#else
  io_stdin = g_io_channel_unix_new (fileno (stdin));
#endif
  g_io_add_watch (io_stdin, G_IO_IN, (GIOFunc) handle_keyboard, &data);

  /* Start playing */
  ret = gst_element_set_state (data.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 (data.pipeline);
    return -1;
  }
  data.playing = TRUE;
  data.rate = 1.0;

  /* Create a GLib Main Loop and set it to run */
  data.loop = g_main_loop_new (NULL, FALSE);
  g_main_loop_run (data.loop);

  /* Free resources */
  g_main_loop_unref (data.loop);
  g_io_channel_unref (io_stdin);
  gst_element_set_state (data.pipeline, GST_STATE_NULL);
  if (data.video_sink != NULL)
    gst_object_unref (data.video_sink);
  gst_object_unref (data.pipeline);
  return 0;
}

int
main (int argc, char *argv[])
{
#if defined(__APPLE__) && TARGET_OS_MAC && !TARGET_OS_IPHONE
  return gst_macos_main (tutorial_main, argc, argv, NULL);
#else
  return tutorial_main (argc, argv);
#endif
}

信息需要帮忙?

如果您需要帮助来编译此代码,请参阅针对您的平台构建教程 部分:LinuxMac OS XWindows,或在 Linux 上使用此特定命令:

gcc basic-tutorial-13.c -o basic-tutorial-13 pkg-config --cflags --libs gstreamer-1.0``

如果您需要帮助来运行此代码,请参阅适用于您的平台的运行教程部分: LinuxMac OS XWindows

本教程将打开一个窗口并显示一个带有音频的电影。媒体是从 Internet 获取的,因此窗口可能需要几秒钟才会出现,具体取决于您的连接速度。控制台显示可用的命令,由单个大写或小写字母组成,您应该输入这些字母,然后按 Enter 键。

所需的库:gstreamer-1.0

演练

main 函数中的初始化代码没有什么新内容: playbin实例化了一个管道,安装了一个 I/O watch 来跟踪击键,并执行了一个 GLib 主循环。

然后,在键盘处理函数中:

/* Process keyboard input */
static gboolean handle_keyboard (GIOChannel *source, GIOCondition cond, CustomData *data) {
  gchar *str = NULL;

  if (g_io_channel_read_line (source, &str, NULL, NULL, NULL) != G_IO_STATUS_NORMAL) {
    return TRUE;
  }

  switch (g_ascii_tolower (str[0])) {
  case 'p':
    data->playing = !data->playing;
    gst_element_set_state (data->pipeline, data->playing ? GST_STATE_PLAYING : GST_STATE_PAUSED);
    g_print ("Setting state to %s\n", data->playing ? "PLAYING" : "PAUSE");
    break;

gst_element_set_state()与之前的教程一样处理暂停/播放切换。

case 's':
  if (g_ascii_isupper (str[0])) {
    data->rate *= 2.0;
  } else {
    data->rate /= 2.0;
  }
  send_seek_event (data);
  break;
case 'd':
  data->rate *= -1.0;
  send_seek_event (data);
  break;

使用“S”和“s”将当前播放速率加倍或减半,使用“d”反转当前播放方向。在这两种情况下, rate变量都会更新并被send_seek_event调用。让我们回顾一下这个函数。

/* Send seek event to change rate */
static void send_seek_event (CustomData *data) {
  gint64 position;
  GstEvent *seek_event;

  /* Obtain the current position, needed for the seek event */
  if (!gst_element_query_position (data->pipeline, GST_FORMAT_TIME, &position)) {
    g_printerr ("Unable to retrieve current position.\n");
    return;
  }

此函数创建一个新的 Seek Event 并将其发送到管道以更新速率。首先,用 恢复当前位置 gst_element_query_position()。这是必需的,因为 Seek Event 跳转到流中的另一个位置,并且由于我们实际上不想移动,所以我们跳转到当前位置。使用 Step Event 会更简单,但是这个事件目前还没有完全发挥作用,如简介中所述。

/* Create the seek event */
if (data->rate > 0) {
  seek_event = gst_event_new_seek (data->rate, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE,
      GST_SEEK_TYPE_SET, position, GST_SEEK_TYPE_END, 0);
} else {
  seek_event = gst_event_new_seek (data->rate, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE,
      GST_SEEK_TYPE_SET, 0, GST_SEEK_TYPE_SET, position);
}

Seek 事件是用gst_event_new_seek(). 它的参数基本上是新汇率、新开始位置和新停止位置。无论播放方向如何,开始位置都必须小于停止位置,所以两个播放方向区别对待。

if (data->video_sink == NULL) {
  /* If we have not done so, obtain the sink through which we will send the seek events */
  g_object_get (data->pipeline, "video-sink", &data->video_sink, NULL);
}

如简介中所述,为避免执行多次搜索,事件仅发送到一个接收器,在本例中为视频接收器。playbin它是通过属性获得的video-sinkPLAYING它在此时而不是在初始化时被读取,因为实际的接收器可能会根据媒体内容而改变,并且直到管道被读取并且一些媒体被读取时才会知道这一点。

/* Send the event */
gst_element_send_event (data->video_sink, seek_event);

新事件最终发送到选定的接收器 gst_element_send_event()

回到键盘处理程序,我们仍然缺少帧步进代码,它非常简单:

case 'n':
  if (data->video_sink == NULL) {
    /* If we have not done so, obtain the sink through which we will send the step events */
    g_object_get (data->pipeline, "video-sink", &data->video_sink, NULL);
  }

  gst_element_send_event (data->video_sink,
      gst_event_new_step (GST_FORMAT_BUFFERS, 1, ABS (data->rate), TRUE, FALSE));
  g_print ("Stepping one frame\n");
  break;

使用 创建一个新的步进事件gst_event_new_step(),其参数基本上指定要跳过的数量(示例中为 1 帧)和新速率(我们不更改)。

视频接收器被抓取,playbin以防我们还没有它,就像以前一样。

这样我们就完成了。测试本教程时,请记住向后播放在许多元素中都不是最佳选择。

警告

更改播放速率可能仅适用于本地文件。如果无法修改,请尝试将playbin第 114 行中传递给的 URI 更改为本地 URI,以file:///

结论

本教程展示了:

  • 如何使用 Seek Event 更改播放速率,该 gst_event_new_seek()事件由gst_element_send_event().
  • 如何使用 Step Events 逐帧推进视频,由gst_event_new_step().

很高兴有你在这里,很快再见!

基础教程 14:方便的元素

目标

本教程提供了一个值得了解的实用 GStreamer 元素列表。它们的范围从允许您轻松构建复杂管道的强大的一体式元素(如playbin)到在调试时非常有用的小助手元素。

为简单起见,下面给出了使用该工具的示例 (在基础教程 10:GStreamer 工具gst-launch-1.0中了解它 )。如果要查看正在协商的 Pad Caps,请使用命令行参数。-v

垃圾箱

这些是您将其视为单个元素的 Bin 元素,它们负责实例化所有必要的内部管道以完成其任务。

playbin

该元素已在整个教程中广泛使用。它管理媒体播放的所有方面,从源到显示,通过多路分解和解码。它非常灵活并且有很多选项,以至于有一整套教程专门介绍它。有关详细信息,请参阅回放教程。

uridecodebin

该元素将来自 URI 的数据解码为原始媒体。它选择一个可以处理给定 URI 方案的源元素并将其连接到一个decodebin元素。它就像一个多路分解器,因此它提供与在媒体中找到的流一样多的源垫。

gst-launch-1.0 uridecodebin uri=https://gstreamer.freedesktop.org/data/media/sintel_trailer-480p.webm ! videoconvert ! autovideosink
gst-launch-1.0 uridecodebin uri=https://gstreamer.freedesktop.org/data/media/sintel_trailer-480p.webm ! audioconvert ! autoaudiosink

decodebin

该元素通过自动插入使用可用的解码器和多路分解器自动构建解码管道,直到获得原始媒体。它在内部使用,uridecodebin通常使用起来更方便,因为它也创建了合适的源元素。它取代了旧decodebin元素。它就像一个多路分解器,因此它提供与在媒体中找到的流一样多的源垫。

gst-launch-1.0 souphttpsrc location=https://gstreamer.freedesktop.org/data/media/sintel_trailer-480p.webm ! decodebin ! autovideosink

文件输入/输出

filesrc

此元素读取本地文件并使用ANYCaps 生成媒体。如果要为媒体获取正确的 Caps,请使用元素或将的属性typefind设置为 来 探索流。typefind``filesrc``TRUE

gst-launch-1.0 filesrc location=f:\\media\\sintel\\sintel_trailer-480p.webm ! decodebin ! autovideosink

filesink

此元素将其接收到的所有媒体写入文件。使用该 location属性指定文件名。

gst-launch-1.0 audiotestsrc ! vorbisenc ! oggmux ! filesink location=test.ogg

网络

souphttpsrc

此元素使用libsoup库通过 HTTP 通过网络作为客户端接收数据。设置要通过属性检索的 URL location

gst-launch-1.0 souphttpsrc location=https://gstreamer.freedesktop.org/data/media/sintel_trailer-480p.webm ! decodebin ! autovideosink

测试媒体生成

这些元素对于检查管道的其他部分是否正常工作非常有用,方法是将源替换为这些“保证”工作的测试源之一。

videotestsrc

这个元素产生一个视频模式(可以在许多不同的选项中选择属性pattern)。用它来测试视频管道。

gst-launch-1.0 videotestsrc ! videoconvert ! autovideosink

audiotestsrc

这个元素产生一个音频波(可以在属性的许多不同选项中选择wave)。用它来测试音频管道。

gst-launch-1.0 audiotestsrc ! audioconvert ! autoaudiosink

视频适配器

videoconvert

该元素将一种颜色空间(例如 RGB)转换为另一种颜色空间(例如 YUV)。它还可以在不同的 YUV 格式(例如 I420、NV12、YUY2……)或 RGB 格式排列(例如 RGBA、ARGB、BGRA……)之间进行转换。

这通常是您解决谈判问题时的首选。在不需要的时候,因为它的上下游元素已经可以相互理解,所以它以直通模式运行,对性能的影响最小。

根据经验,videoconvert每当您使用在设计时其上限未知的元素(如 )autovideosink或可能因外部因素而异(如解码用户提供的文件)时,请始终使用。

gst-launch-1.0 videotestsrc ! videoconvert ! autovideosink

videorate

此元素接收带时间戳的视频帧的传入流,并生成与源板的帧速率匹配的流。校正是通过丢弃和复制帧来执行的,没有使用花哨的算法来插入帧。

这对于允许需要不同帧速率的元素进行链接很有用。与其他适配器一样,如果不需要它(因为存在两个 Pad 可以同意的帧速率),它会以直通模式运行并且不会影响性能。

因此,以防万一,在设计时实际帧速率未知时始终使用它是个好主意。

gst-launch-1.0 videotestsrc ! video/x-raw,framerate=30/1 ! videorate ! video/x-raw,framerate=1/1 ! videoconvert ! autovideosink

videoscale

此元素调整视频帧的大小。默认情况下,元素会尝试在源和汇 Pad 上协商相同的大小,因此不需要缩放。因此,如果不需要缩放,可以安全地将此元素插入管道中以获得更健壮的行为而无需任何成本。

此元素支持范围广泛的色彩空间,包括各种 YUV 和 RGB 格式,因此通常能够在管道中的任何位置运行。

如果要将视频输出到大小由用户控制的窗口,使用元素是个好主意videoscale,因为并非所有视频接收器都能够执行缩放操作。

gst-launch-1.0 uridecodebin uri=https://gstreamer.freedesktop.org/data/media/sintel_trailer-480p.webm ! videoscale ! video/x-raw,width=178,height=100 ! videoconvert ! autovideosink

音频适配器

audioconvert

该元素在各种可能的格式之间转换原始音频缓冲区。它支持整数到浮点数的转换、宽度/深度转换、符号和字节序转换以及通道转换。

就像videoconvert视频一样,您可以使用它来解决音频的协商问题,自由使用它通常是安全的,因为如果不需要,该元素什么都不做。

gst-launch-1.0 audiotestsrc ! audioconvert ! autoaudiosink

audioresample

此元素使用可配置的窗口功能将原始音频缓冲区重新采样为不同的采样率以提高质量。

同样,用它来解决关于采样率的谈判问题,不要害怕慷慨地使用它。

gst-launch-1.0 uridecodebin uri=https://gstreamer.freedesktop.org/data/media/sintel_trailer-480p.webm ! audioresample ! audio/x-raw,rate=4000 ! audioconvert ! autoaudiosink

audiorate

该元素接收带时间戳的原始音频帧的传入流,并通过根据需要插入或删除样本来生成完美的流。它不允许像videorate以前那样更改采样率,它只是填补空白并删除重叠的样本,因此输出流是连续且“干净”的。

它在时间戳将要丢失的情况下很有用(例如,当存储为某些文件格式时)并且接收方将要求所有样本都存在。这个举例比较麻烦,就不举例了。

警告大多数时候,audiorate并不是你想要的。

多线程

queue

队列已经在基础教程 7: Multithreading and Pad Availability中进行了解释。基本上,队列执行两个任务:

  • 数据排队直到达到选定的限制。任何将更多缓冲区推入队列的尝试都会阻塞推送线程,直到有更多空间可用为止。
  • queue 在 source Pad 上创建一个新线程来解耦 sink 和 source Pad 上的处理。

此外,queue当它即将变空或变满时触发信号(根据一些可配置的阈值),并且可以指示丢弃缓冲区而不是在它变满时阻塞。

根据经验,只要您不关心网络缓冲,queue就更喜欢简单的元素。 有关示例,queue2请参见基础教程 7:多线程和 Pad 可用性。

queue2

此元素不是queue. 它具有相同的设计目标,但遵循不同的实现方法,从而产生不同的特性。不幸的是,通常不容易判断哪个队列是最佳选择。

queue2为 执行上面列出的两项任务queue,此外,还能够将接收到的数据(或其中的一部分)存储在磁盘文件中,以供以后检索。它还用基础教程 12:流媒体中描述的更通用和方便的缓冲消息替换信号 。

作为一个经验法则,prefer queue2over queuewhen network buffering is a concern to you. 示例参见基础教程 12:Streamingqueue2 (隐藏在里面playbin)。

multiqueue

该元素同时为多个流提供队列,并通过允许一些队列在其他流上没有接收到数据时增长,或者通过允许一些队列在它们没有连接到任何东西时丢弃数据(而不是返回一个错误,就像一个更简单的队列会做的那样)。此外,它同步不同的流,确保它们都不会超前于其他流。

这是一个高级元素。它位于 中decodebin,但您很少需要在普通播放应用程序中自己实例化它。

tee

基础教程 7:多线程和 Pad 可用性已经展示了如何使用一个tee元素,它将数据拆分到多个 pad。拆分数据流很有用,例如,在捕获视频时,视频会显示在屏幕上,并且还会编码并写入文件。另一个例子是播放音乐和连接可视化模块。

需要queue在每个分支中使用单独的元素来为每个分支提供单独的线程。否则,一个分支中的阻塞数据流会使其他分支停滞。

gst-launch-1.0 audiotestsrc ! tee name=t ! queue ! audioconvert ! autoaudiosink t. ! queue ! wavescope ! videoconvert ! autovideosink

能力

capsfilter

基础教程 10:GStreamer 工具已经解释了如何使用 Caps 过滤器gst-launch-1.0。以编程方式构建管道时,Caps 过滤器是通过capsfilter元素实现的。此元素不会修改数据本身,但会对数据格式施加限制。

gst-launch-1.0 videotestsrc ! video/x-raw, format=GRAY8 ! videoconvert ! autovideosink

typefind

此元素确定流包含的媒体类型。它按排名顺序应用 typefind 函数。一旦检测到类型,它将其源 Pad Caps 设置为找到的媒体类型并发出信号have-type

它由 内部实例化decodebin,您也可以使用它来查找媒体类型,尽管您通常可以使用它 GstDiscoverer提供更多信息(如 基础教程 9:媒体信息收集中所见)。

调试

fakesink

这个接收器元素简单地吞下任何提供给它的数据。它在调试时很有用,可以替换您的普通接收器并将它们排除在等式之外。-v当与的开关结合使用时,它可能会非常冗长gst-launch-1.0,因此请使用该silent属性删除任何不需要的噪音。

gst-launch-1.0 audiotestsrc num-buffers=1000 ! fakesink sync=false

identity

这是一个虚拟元素,它通过未修改的方式传递传入数据。它有几个有用的诊断功能,例如偏移量和时间戳检查,或缓冲区丢弃。阅读它的文档,了解这个看似无害的元素可以做的所有事情。

gst-launch-1.0 audiotestsrc ! identity drop-probability=0.1 ! audioconvert ! autoaudiosink

结论

本教程列出了一些值得了解的元素,因为它们在 GStreamer 的日常工作中很有用。有些对生产流水线很有价值,而另一些仅用于调试目的。

很高兴有你在这里,很快再见!

基础教程 16:特定于平台的元素

目标

尽管 GStreamer 是一个多平台框架,但并非所有元素都适用于所有平台。例如,视频接收器严重依赖底层窗口系统,需要根据平台选择不同的接收器。playbin在使用或 之 类的元素时,您通常不需要担心这一点autovideosink,但是,对于那些需要使用仅在特定平台上可用的接收器之一的情况,本教程会提示您它们的一些特性。

跨平台

glimagesink

此视频接收器基于 OpenGLOpenGL ES。它支持缩放图像的重新缩放和过滤以减轻锯齿。它实现了 VideoOverlay 接口,因此视频窗口可以重新设置父级(嵌入到其他窗口中)。这是除 Windows 之外的大多数平台上推荐的视频接收器(在 Windows 上,d3d11videosink推荐)。特别是在 Android 和 iOS 上,它是唯一可用的视频接收器。它可以分解为 glupload ! glcolorconvert ! glimagesinkelement将进一步的 OpenGL 硬件加速处理插入到管道中。

Linux

ximagesink

标准 RGB 仅基于 X 的视频接收器。它实现了 VideoOverlay 接口,因此视频窗口可以重新设置父级(嵌入到其他窗口中)。它不支持 RGB 以外的缩放或颜色格式;它必须通过不同的方式执行( videoscale例如使用元素)。

xvimagesink

基于 X 的视频接收器,使用X 视频扩展(Xv)。它实现了 VideoOverlay 接口,因此视频窗口可以重新设置父级(嵌入到其他窗口中)。它可以在 GPU 上高效地执行缩放。它仅在硬件和相应的驱动程序支持 Xv 扩展时可用。

alsasink

此音频接收器通过ALSA (高级 Linux 声音架构)输出到声卡 。几乎每个 Linux 平台都可以使用这个接收器。它通常被视为声卡的“低级”接口,配置起来可能很复杂(请参阅 播放教程 9 的评论:数字音频直通)。

pulsesink

此接收器向PulseAudio服务器播放音频 。它是比 ALSA 更高级别的声卡抽象,因此更易于使用并提供更高级的功能。不过,众所周知,它在某些较旧的 Linux 发行版上不稳定。

苹果操作系统

osxvideosink

这是 Mac OS X 上的 GStreamer 可用的视频接收器。也可以使用glimagesinkOpenGL 进行绘制。

osxaudiosink

这是 Mac OS X 上 GStreamer 唯一可用的音频接收器。

视窗

d3d11videosink

此视频接收器基于Direct3D11 ,是 Windows 上推荐的元素。它支持 VideoOverlay 接口和零拷贝方式的重新缩放/色彩空间转换。此元素是 Windows 上性能最高、功能最强大的视频接收器元素。

d3dvideosink

此视频接收器基于 Direct3D9。它支持缩放图像的重新缩放和过滤以减轻锯齿。它实现了 VideoOverlay 接口,因此视频窗口可以重新设置父级(嵌入到其他窗口中)。不建议将此元素用于针对 Windows 8 或更新版本的应用程序。

dshowvideosink (deprecated)

此视频接收器基于Direct Show。它可以使用不同的渲染后端,如 EVRVMR9VMR7, EVR 仅在 Windows Vista 或更新版本上可用。它支持缩放图像的重新缩放和过滤以减轻锯齿。它实现了 VideoOverlay 接口,因此视频窗口可以重新设置父级(嵌入到其他窗口中)。在大多数情况下不建议使用此元素。

wasapisinkwasapi2sink

这些元素是 Windows 上的默认音频接收器元素,基于 WASAPI,可在 Vista 或更新版本上使用。请注意,它是Windows 8 或更新版本的wasapi2sink替代品wasapisink,并且是默认值。wasapi2sink否则wasapisink将是默认的音频接收器元素。

directsoundsink (deprecated)

此音频接收器元素基于 DirectSound,它在所有 Windows 版本中都可用。

dshowdecwrapper

Direct Show是一个类似于GStreamer的多媒体框架。但是,它们非常不同,因此它们的管道无法互连。然而,通过这个元素,GStreamer 可以从 Direct Show 中的解码元素中获益。dshowdecwrapper包装多个 Direct Show 解码器,以便它们可以嵌入到 GStreamer 管道中。使用该gst-inspect-1.0工具(参见基础教程 10:GStreamer 工具)查看可用的解码器。

安卓

openslessink

这是 Android 上 GStreamer 唯一可用的音频接收器。它基于OpenSL ES

openslessrc

这是 Android 上 GStreamer 可用的唯一音频源。它基于OpenSL ES

androidmedia

android.media.MediaCodec 是一个特定于 Android 的 API,用于访问设备上可用的编解码器,包括硬件编解码器。它从 API 级别 16 (JellyBean) 开始可用,GStreamer 可以通过 androidmedia 插件使用它进行音频和视频解码。在 Android 上,将硬件解码器附加到glimagesink元素可以产生高性能的零拷贝 decodebin 管道。

ahcsrc

此视频源可以从 Android 设备上的摄像头捕获,它是 androidmedia 插件的一部分并使用 android.hardware.Camera API

iOS

osxaudiosink

这是 iOS 上 GStreamer 唯一可用的音频接收器。

iosassetsrc

读取 iOS 资产的源元素,即存储在库中的文档(如照片、音乐和视频)。它可以在playbinURI 使用该 assets-library://方案时自动实例化。

iosavassetsrc

读取和解码 iOS 视听资产的源元素,即存储在库中的文档(如照片、音乐和视频)。它可以在playbinURI 使用该 ipod-library://方案时自动实例化。解码由系统执行,因此如果可用,将使用专用硬件。

结论

本教程展示了一些关于某些 GStreamer 元素的具体细节,这些元素并非在所有平台上都可用。playbin在使用或 之 类的多平台元素时,您不必担心它们autovideosink,但如果手动实例化它们,最好了解它们的个人怪癖。

很高兴有你在这里,很快再见!

参考链接:https://gstreamer.freedesktop.org/documentation/tutorials/basic/hello-world.html?gi-language=c

posted @ 2023-05-28 15:58  O-ll-O  阅读(3100)  评论(0编辑  收藏  举报