GStreamer Basic tutorial 6 Media formats and Pad Capabilities

"GStreamer 基本教程6: 媒体格式和Pad功能"

Posted by Stephen on March 18, 2022

前言

本文是GStreamer学习笔记,也可以看成是对原文的意译。

这些教程描述了理解其余教程所需的GStreamer主题。

GStreamer教程:

基础教程 : GStreamer 介绍

基础教程 1: Hello world!

基础教程 2: GStreamer 概念

基础教程 3: 动态管道

基础教程 4: 时间管理

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

基础教程 6: 媒体格式和pad功能

基础教程 7: 多线程和Pad可用性

基础教程 8: 管道短路操作

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

基础教程 10: GStreamer工具

基础教程 11: 调试工具

基础教程 12: 流媒体

基础教程 13: 播放速度

基础教程 14: 有用的元素

基础教程 16: 特定平台元素

环境

系统环境

Distributor ID:	Ubuntu
Description:	Ubuntu 18.04.4 LTS
Release:	18.04
Codename:	bionic
Linux version :       5.3.0-46-generic ( buildd@lcy01-amd64-013 ) 
Gcc version:         7.5.0  ( Ubuntu 7.5.0-3ubuntu1~18.04 )

软件信息

version : 	
     GStreamer-1.0

正文

1. 目标

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

  • 什么是pad功能。
  • 如何找回它们。
  • 何时检索它们。
  • 为什么你需要了解它们。

2. 介绍

2.1 pad

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

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

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

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

2.2 pad模板

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

垫模板可以被视为谈判过程中的第一步。随着过程的发展,实际的 Pad 会被实例化,并且它们的 Capabilities 会被细化,直到它们被修复(或协商失败)。

2.3 功能示例

  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是一个sink,它在元素上总是可用的(我们现在不讨论可用性)。它支持两种媒体,均为整数格式 ( 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 格式(花括号表示一个列表)。所有这些格式都表示图像平面的不同打包和二次采样。

2.4 最后描述

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

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

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

3. 一个简单的pad功能示例

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;
   }

编译运行

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

4. 演练

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关于 Pads。

然后我们调用gst_pad_get_current_caps()来检索 Pad 当前的 Capabilities,它可以是固定的,也可以不固定,这取决于协商过程的状态。它们甚至可能不存在,在这种情况下,我们调用gst_pad_query_caps()以检索当前可接受的 Pad Capabilities。当前可接受的 Caps 将是处于 NULL 状态的 Pad Template 的 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()并跳过了工厂的讨论,但现在我们将这样做。GstElementFactory负责实例化特定类型的元素,由其工厂名称标识。

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

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

我们跳过管道创建和启动,然后进入 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 Template 的大写字母)是如何逐渐细化的,直到它们完全固定(它们包含一个没有范围的单一类型)。

5. 结论

本教程学习了:

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

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

后记