三、Plugin states、Building a Test Application

What are states?

状态描述元素实例是否已初始化,是否准备传输数据,以及当前是否正在处理数据。GStreamer中定义了四种状态。

  • GST_STATE_NULL
  • GST_STATE_READY (已经初始化)
  • GST_STATE_PAUSED (准备传输数据)
  • GST_STATE_PLAYING (正在处理数据)

从现在起,它们将被简称为“NULL”、“READY”、“PAUSED”和“PLAYING”。

  • GST_STATE_NULL是元素的默认状态。在这种状态下,它没有分配任何运行时资源,没有加载任何运行时库,显然不能处理数据。

  • GST_STATE_READY是元素可能处于的下一个状态。在就绪状态下,元素拥有分配的所有默认资源(运行时库、运行时内存)。然而,它还没有分配或定义任何特定于流的东西。当从NULL变为就绪状态(GST_STATE_CHANGE_NULL_TO_READY)时,元素应该分配所有非流特定的资源,并加载运行时可加载的库(如果有的话)。如果反过来(从READY到NULL, GST_STATE_CHANGE_READY_TO_NULL),则元素应该卸载这些库并释放所有已分配的资源。硬件设备就是这样的资源。请注意,文件通常是流,因此应该将其视为流特有的资源。因此,它们不应该在这种状态下分配

  • GST_STATE_PAUSED是元素准备接受和处理数据时的状态。对于大多数元素来说,这个状态等同于PLAYING。唯一的例外是sink元素。Sink元素只接受一个数据缓冲区,然后进行块处理。此时,管道已经prerolled并准备好立即渲染数据。

  • GST_STATE_PLAYING是元素可以处于的最高状态。对于大多数元素来说,这个状态与PAUSED状态完全相同,它们接受并处理事件和缓冲区中的数据。只有sink元素需要区分暂停和播放状态。在播放状态下,sink元素实际上渲染传入的数据,例如将音频输出到声卡或将视频图像渲染到图像sink。

Managing filter state

如果可能的话,元素应该从一个新的基类(预先创建的基类)派生。对于不同类型的sources、sinks和filter/transformation元素,有现成的通用基类。除此之外,还存在用于音频和视频元素等的专用基类。

如果你使用基类,你将很少需要自己处理状态更改。你所要做的就是覆盖基类的start()和stop()虚函数(可能会根据基类的不同调用),基类将为你处理一切。

但是,如果您不是从一个现成的基类派生而来,而是从GstElement或其他一些没有构建在基类之上的类派生而来,那么您很可能必须实现自己的状态更改函数以获得状态更改的通知。如果您的插件是一个demuxer或muxer,这是绝对必要的,因为还没有用于demuxer或muxer的基类。

可以通过虚函数指针通知元素状态的变化。在这个函数内部,元素可以初始化元素所需的任何特定类型的数据,并且可以选择从一种状态切换到另一种状态。

对于未处理的状态变化,不要使用g_assert。这是由GstElement基类处理的。

static GstStateChangeReturn
gst_my_filter_change_state (GstElement *element, GstStateChange transition);

static void
gst_my_filter_class_init (GstMyFilterClass *klass)
{
  GstElementClass *element_class = GST_ELEMENT_CLASS (klass);

  element_class->change_state = gst_my_filter_change_state;
}



static GstStateChangeReturn
gst_my_filter_change_state (GstElement *element, GstStateChange transition)
{
  GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
  GstMyFilter *filter = GST_MY_FILTER (element);

  switch (transition) {
	case GST_STATE_CHANGE_NULL_TO_READY:
	  if (!gst_my_filter_allocate_memory (filter))
		return GST_STATE_CHANGE_FAILURE;
	  break;
	default:
	  break;
  }

  ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
  if (ret == GST_STATE_CHANGE_FAILURE)
	return ret;

  switch (transition) {
	case GST_STATE_CHANGE_READY_TO_NULL:
	  gst_my_filter_free_memory (filter);
	  break;
	default:
	  break;
  }

  return ret;
}

请注意,向上(NULL=>READY, READY=>PAUSED, PAUSED=>PLAYING)和向下(PLAYING =>PAUSED, PAUSED=>READY, READY=>NULL)状态更改在两个单独的块中处理,向下状态更改只有在我们链接到父类的状态更改函数后才处理。为了安全地处理多个线程的并发访问,这是必要的。

原因是,在向下状态变化的情况下,你不希望在插件的chain函数(例如)仍然在另一个线程中访问这些资源时销毁已分配的资源。链式函数是否在运行取决于插件的plugin’s pads状态,而这些plugin’s pads的状态与元素的状态密切相关。Pad状态是在GstElement类的状态改变函数中处理的,包括正确的锁定,这就是为什么在销毁分配的资源之前必须将其链接起来。

Adding Properties

控制元素行为的最主要也是最重要的方式,就是通过GObject属性。GObject属性在_class_init()函数中定义。元素可选地实现_get_property()和_set_property()函数。如果应用程序更改或请求属性的值,这些函数将收到通知,然后可以填充该值或采取该属性内部更改值所需的操作。

你可能还想在get和set函数中使用的属性的当前配置值周围保存一个实例变量。注意,GObject不会自动将实例变量设置为默认值,您必须在元素的_init()函数中这样做。

/* properties */
enum {
  PROP_0,
  PROP_SILENT
  /* FILL ME */
};

static void gst_my_filter_set_property  (GObject      *object,
                         guint         prop_id,
                         const GValue *value,
                         GParamSpec   *pspec);
static void gst_my_filter_get_property  (GObject      *object,
                         guint         prop_id,
                         GValue       *value,
                         GParamSpec   *pspec);

static void
gst_my_filter_class_init (GstMyFilterClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  /* define virtual function pointers */
  object_class->set_property = gst_my_filter_set_property;
  object_class->get_property = gst_my_filter_get_property;

  /* define properties */
  g_object_class_install_property (object_class, PROP_SILENT,
    g_param_spec_boolean ("silent", "Silent",
              "Whether to be very verbose or not",
              FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
}

static void
gst_my_filter_set_property (GObject      *object,
                guint         prop_id,
                const GValue *value,
                GParamSpec   *pspec)
{
  GstMyFilter *filter = GST_MY_FILTER (object);

  switch (prop_id) {
    case PROP_SILENT:
      filter->silent = g_value_get_boolean (value);
      g_print ("Silent argument was changed to %s\n",
           filter->silent ? "true" : "false");
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}

static void
gst_my_filter_get_property (GObject    *object,
                guint       prop_id,
                GValue     *value,
                GParamSpec *pspec)
{
  GstMyFilter *filter = GST_MY_FILTER (object);

  switch (prop_id) {
    case PROP_SILENT:
      g_value_set_boolean (value, filter->silent);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}

以上是如何使用属性的一个非常简单的例子。图形化应用程序将使用这些属性,并显示一个用户可控制的小部件,通过它可以更改这些属性。这意味着——为了让属性尽可能对用户友好——你应该尽可能精确地定义属性。不仅可以定义可以放置有效属性的范围(对于整数、浮点数等),还可以在属性定义中使用非常具有描述性(最好是国际化)的字符串,如果可能的话,可以使用枚举和flags而不是整数。GObject文档以非常完整的方式描述了这些,但下面,我们将给出一个简短的示例,说明这在哪里有用。请注意,在这里使用整数可能会让用户完全迷惑,因为它们在这个上下文中没有任何意义。这个例子是从videotestsrc中拷贝来的。

typedef enum {
  GST_VIDEOTESTSRC_SMPTE,
  GST_VIDEOTESTSRC_SNOW,
  GST_VIDEOTESTSRC_BLACK
} GstVideotestsrcPattern;

[..]

#define GST_TYPE_VIDEOTESTSRC_PATTERN (gst_videotestsrc_pattern_get_type ())
static GType
gst_videotestsrc_pattern_get_type (void)
{
  static GType videotestsrc_pattern_type = 0;

  if (!videotestsrc_pattern_type) {
    static GEnumValue pattern_types[] = {
      { GST_VIDEOTESTSRC_SMPTE, "SMPTE 100% color bars",    "smpte" },
      { GST_VIDEOTESTSRC_SNOW,  "Random (television snow)", "snow"  },
      { GST_VIDEOTESTSRC_BLACK, "0% Black",                 "black" },
      { 0, NULL, NULL },
    };

    videotestsrc_pattern_type =
    g_enum_register_static ("GstVideotestsrcPattern",
                pattern_types);
  }

  return videotestsrc_pattern_type;
}

[..]

static void
gst_videotestsrc_class_init (GstvideotestsrcClass *klass)
{
[..]
  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_PATTERN,
    g_param_spec_enum ("pattern", "Pattern",
               "Type of test pattern to generate",
                       GST_TYPE_VIDEOTESTSRC_PATTERN, GST_VIDEOTESTSRC_SMPTE,
                       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
[..]
}


Building a Test Application

通常,你会希望在尽可能小的设置中测试新编写的插件。通常,gst-launch-1.0是测试插件的第一步。如果没有将插件安装在GStreamer搜索的目录中,则需要设置插件路径。将GST_PLUGIN_PATH设置为包含插件的目录,或者使用命令行选项--gst-plugin-path。如果你的插件基于gst-plugin模板,那么它看起来类似于gst-launch-1.0 --gst-plugin-path=$HOME/gst-template/gst-plugin/src/.libs TESTPIPELINE,然而库测试管道,您通常需要比gst-launch-1.0提供的更多的测试功能,例如查找、事件、交互等。编写自己的小测试程序是实现这一点的最简单方法。本节简单地解释了如何做到这一点。要了解完整的应用程序开发指南,请参阅应用程序开发手册。

开始时,你需要通过调用gst_init()初始化GStreamer核心库。您也可以调用gst_init_get_option_group(),它将返回一个指向GOptionGroup的指针。然后您可以使用GOption来处理初始化,这将完成GStreamer的初始化。

可以使用gst_element_factory_make()创建元素,其中第一个参数是要创建的元素类型,第二个参数是自由格式的名称。最后的示例使用了一个简单的filesource - decoder -声卡输出管道,但如果有必要,您可以使用特定的调试元素。例如,可以在管道中间使用标识元素作为数据到应用的发送器。这可以用于检查测试应用程序中的数据是否有错误行为或是否正确。此外,还可以在管道的末尾使用fakesink元素将数据转储到stdout(为此,请将dump属性设置为TRUE)。最后,你可以使用valgrind检查内存错误。

在链接期间,您的测试应用程序可以使用过滤的caps作为一种方式来驱动特定类型的数据与元素之间的传输。这是检查元素中多种输入和输出类型的一种非常简单有效的方法。

请注意,在运行期间,您应该至少侦听总线和/或插件/元素上的“error”和“eos”消息,以检查是否正确处理此问题。此外,还应该向管道中添加事件,并确保插件正确地处理这些事件(时钟、内部缓存等)。

永远不要忘记清理插件或测试应用程序中的内存。当变为NULL状态时,你的元素应该清理分配的内存和缓存。此外,它应该关闭所有可能的支持库的引用。您的应用程序应该unref()管道并确保它不会崩溃。

#include 

static gboolean
bus_call (GstBus     *bus,
      GstMessage *msg,
      gpointer    data)
{
  GMainLoop *loop = data;

  switch (GST_MESSAGE_TYPE (msg)) {
    case GST_MESSAGE_EOS:
      g_print ("End-of-stream\n");
      g_main_loop_quit (loop);
      break;
    case GST_MESSAGE_ERROR: {
      gchar *debug = NULL;
      GError *err = NULL;

      gst_message_parse_error (msg, &err, &debug);

      g_print ("Error: %s\n", err->message);
      g_error_free (err);

      if (debug) {
        g_print ("Debug details: %s\n", debug);
        g_free (debug);
      }

      g_main_loop_quit (loop);
      break;
    }
    default:
      break;
  }

  return TRUE;
}

gint
main (gint   argc,
      gchar *argv[])
{
  GstStateChangeReturn ret;
  GstElement *pipeline, *filesrc, *decoder, *filter, *sink;
  GstElement *convert1, *convert2, *resample;
  GMainLoop *loop;
  GstBus *bus;
  guint watch_id;

  /* initialization */
  gst_init (&argc, &argv);
  loop = g_main_loop_new (NULL, FALSE);
  if (argc != 2) {
    g_print ("Usage: %s \n", argv[0]);
    return 01;
  }

  /* create elements */
  pipeline = gst_pipeline_new ("my_pipeline");

  /* watch for messages on the pipeline's bus (note that this will only
   * work like this when a GLib main loop is running) */
  bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
  watch_id = gst_bus_add_watch (bus, bus_call, loop);
  gst_object_unref (bus);

  filesrc  = gst_element_factory_make ("filesrc", "my_filesource");
  decoder  = gst_element_factory_make ("mad", "my_decoder");

  /* putting an audioconvert element here to convert the output of the
   * decoder into a format that my_filter can handle (we are assuming it
   * will handle any sample rate here though) */
  convert1 = gst_element_factory_make ("audioconvert", "audioconvert1");

  /* use "identity" here for a filter that does nothing */
  filter   = gst_element_factory_make ("my_filter", "my_filter");

  /* there should always be audioconvert and audioresample elements before
   * the audio sink, since the capabilities of the audio sink usually vary
   * depending on the environment (output used, sound card, driver etc.) */
  convert2 = gst_element_factory_make ("audioconvert", "audioconvert2");
  resample = gst_element_factory_make ("audioresample", "audioresample");
  sink     = gst_element_factory_make ("pulsesink", "audiosink");

  if (!sink || !decoder) {
    g_print ("Decoder or output could not be found - check your install\n");
    return -1;
  } else if (!convert1 || !convert2 || !resample) {
    g_print ("Could not create audioconvert or audioresample element, "
             "check your installation\n");
    return -1;
  } else if (!filter) {
    g_print ("Your self-written filter could not be found. Make sure it "
             "is installed correctly in $(libdir)/gstreamer-1.0/ or "
             "~/.gstreamer-1.0/plugins/ and that gst-inspect-1.0 lists it. "
             "If it doesn't, check with 'GST_DEBUG=*:2 gst-inspect-1.0' for "
             "the reason why it is not being loaded.");
    return -1;
  }

  g_object_set (G_OBJECT (filesrc), "location", argv[1], NULL);

  gst_bin_add_many (GST_BIN (pipeline), filesrc, decoder, convert1, filter,
                    convert2, resample, sink, NULL);

  /* link everything together */
  if (!gst_element_link_many (filesrc, decoder, convert1, filter, convert2,
                              resample, sink, NULL)) {
    g_print ("Failed to link one or more elements!\n");
    return -1;
  }

  /* run */
  ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
  if (ret == GST_STATE_CHANGE_FAILURE) {
    GstMessage *msg;

    g_print ("Failed to start up pipeline!\n");

    /* check if there is an error message with details on the bus */
    msg = gst_bus_poll (bus, GST_MESSAGE_ERROR, 0);
    if (msg) {
      GError *err = NULL;

      gst_message_parse_error (msg, &err, NULL);
      g_print ("ERROR: %s\n", err->message);
      g_error_free (err);
      gst_message_unref (msg);
    }
    return -1;
  }

  g_main_loop_run (loop);

  /* clean up */
  gst_element_set_state (pipeline, GST_STATE_NULL);
  gst_object_unref (pipeline);
  g_source_remove (watch_id);
  g_main_loop_unref (loop);

  return 0;
}

你可能感兴趣的:(GStreamer插件开发,ubuntu)