前言
本文是GStreamer学习笔记,也可以看成是对原文的意译。
这些教程描述了理解其余教程所需的GStreamer主题。
GStreamer教程:
环境
系统环境
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. 目标
有时您可能想快速找出文件(或 URI)包含的媒体类型,或者您是否能够播放该媒体。您可以构建管道,将其设置为运行,并查看总线消息,但 GStreamer 有一个实用程序可以为您完成这些工作。本教程显示:
- 如何恢复有关 URI 的信息
- 如何确定 URI 是否可播放
2. 介绍
GstDiscoverer
是在pbutils
库(插件基础实用程序)中找到的实用程序对象,它接受 URI 或 URI 列表,并返回有关它们的信息。它可以在同步或异步模式下工作。
在同步模式下,只有一个函数可以调用, gst_discoverer_discover_uri()
该函数会一直阻塞,直到信息准备好。由于这种阻塞,基于 GUI 的应用程序通常不太有趣,并且使用异步模式,如本教程中所述。
恢复的信息包括编解码器描述、流拓扑(流和子流的数量)和可用的元数据(如音频语言)。
例如,这是发现 https://www.freedesktop.org/software/gstreamer-sdk/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,则使用默认值)。
这是该gst-discoverer-1.0
工具的简化版本(基础教程 10: GStreamer工具),它是一个仅显示数据但不执行任何回放的应用程序。
3. 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://www.freedesktop.org/software/gstreamer-sdk/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;
}
编译运行
gcc basic-tutorial-9.c -o basic-tutorial-9 `pkg-config --cflags --libs gstreamer-1.0 gstreamer-pbutils-1.0`
./basic-tutorial-9
4. 演练
这些是使用的主要步骤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 的这一部分确实有点晦涩难懂)。
5. 结论
本教程显示:
- 如何使用
GstDiscoverer
- 如何通过查看使用
gst_discoverer_info_get_result()
.