系列文章目录
- GStreamer 简明教程(一):环境搭建,运行 Basic Tutorial 1 Hello world!
- GStreamer 简明教程(二):基本概念介绍,Element 和 Pipeline
- GStreamer 简明教程(三):动态调整 Pipeline
- GStreamer 简明教程(四):Seek 以及获取文件时长
- GStreamer 简明教程(五):Pad 相关概念介绍,Pad Capabilities/Templates
文章目录
- 系列文章目录
- 前言
- Pipeline
- Request Pads
- Show me the code
- 参考
前言
本章基于官方教程 Basic tutorial 7: Multithreading and Pad Availability 进行说明和补充。本章内容并不复杂,引入了两个新的 Element:Tee 和 Queue。Tee 用于复制数据, Queue 创建线程让 Pipeline 的一部分在另外线程中运行。
由于官方教程在本人机器上总是无法运行,我对它做了一些修改,具体代码在 basic-tutorial-7.c
Pipeline
本次教程中使用的 Pipeline 如上图所示,简单做个解释说明:
- audiotestsrc: 生成测试音频数据。
- tee:接收一个输入流,并可以创建多个完全相同的输出流。这些输出流可以被发送到不同的处理管道中。
- queue:创建一个 FIFO(先进先出)缓冲区。它接收输入数据,暂时存储,然后在单独线程中按照接收顺序输出数据。
- wavescope: 可视化音频数据的波形
- spectrascope:可视化音频数据的频谱
- videoconvert: 视频格式转换
- autovideosink: 显示视频数据
Request Pads
在 GStreamer 简明教程(三):动态调整 Pipeline 中我们学习了 uridecodebin
,uridecodebin
是一个典型的例子,展示了如何使用动态创建的 Pad。这种 Pad 类型被称为 Sometime Pad。当 uridecodebin
切换状态后,它会从输入源读取媒体信息,根据识别出的流动态添加相应数量的 Pad。随后,它通过发送 “pad-added” 信号来通知应用程序这些新创建的 Pad。
相比之下,Always Pad 是在元素创建时即存在,并在元素的整个生命周期内保持可用。这类 Pad 通常用于固定格式的数据流处理。
第三种类型是 Request Pad,它们根据需要通过显式请求创建。这类 Pad 被用于动态连接或多路输出的场景。例如,在解码器中,可以根据请求生成相应的 Pad,以处理特定类型的子流。
tee 元素就支持 Request Pad,首先通过 gst-inspect-1.0 来看下 tee 的详细信息,其中有 Pad Templates 的描述
Pad Templates:
SINK template: 'sink'
Availability: Always
Capabilities:
ANY
SRC template: 'src_%u'
Availability: On request
Capabilities:
ANY
src_%u 表示 Request Pad 的名称格式。其中 %u 是一个占位符,代表一个唯一的无符号整数。这意味着每次请求一个新的源 Pad 时,tee 元素将创建一个新的 Pad,并用依次递增的整数替换 %u。例如,第一次请求可能会创建 src_0,第二次创建 src_1,依此类推。这种命名方式帮助管理和区分多个动态创建的 Pad。
tee 元素创建 pad 的代码示例:
// 请求创建新的 src pad
GstPad *pad1 = gst_element_request_pad_simple(tee, "src_%u");
// 这可能创建名为 "src_0" 的 pad
GstPad *pad2 = gst_element_request_pad_simple(tee, "src_%u");
// 这可能创建名为 "src_1" 的 pad
// 使用这些 pad
// ...
// 在不需要时释放
gst_element_release_request_pad(tee, pad1);
gst_element_release_request_pad(tee, pad2);
Show me the code
接下来对具体代码进行说明,完整代码在 basic-tutorial-7.c
代码会生成两个窗口,一个窗口展示音频的频谱图,另一个显示音频的波形图
#include <gst/gst.h>
#ifdef __APPLE__
#include <TargetConditionals.h>
#endif
int tutorial_main(int argc, char *argv[]) {
GstElement *pipeline;
GstElement *audio_source;
GstElement *queue;
GstElement *visual;
GstElement *video_convert;
GstElement *video_sink;
GstElement *tee;
GstElement *queue2;
GstElement *visual2;
GstElement *video_convert2;
GstElement *video_sink2;
/* Initialize GStreamer */
gst_init(&argc, &argv);
// create elements
audio_source = gst_element_factory_make("audiotestsrc", "audio_source");
tee = gst_element_factory_make("tee", "tee");
queue = gst_element_factory_make("queue", "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");
queue2 = gst_element_factory_make("queue", "queue2");
visual2 = gst_element_factory_make("spectrascope", "visual2");
video_convert2 = gst_element_factory_make("videoconvert", "video_convert2");
video_sink2 = gst_element_factory_make("autovideosink", "video_sink2");
// create pipeline
pipeline = gst_pipeline_new("test-pipeline");
if (!pipeline || !audio_source || !tee || !queue || !visual ||
!video_convert || !video_sink || !queue2 || !visual2 || !video_convert2 ||
!video_sink2) {
g_printerr("Not all elements could be created.\n");
return -1;
}
// configure elements
g_object_set(audio_source, "freq", 215.0f, "wave", 2, NULL);
g_object_set(visual, "shader", 0, "style", 1, NULL);
// g_object_set(visual2, "shader", 0, "style", 3, NULL);
// link elements
gst_bin_add_many(GST_BIN(pipeline), audio_source, tee, queue, visual,
video_convert, video_sink, queue2, visual2, video_convert2,
video_sink2, NULL);
if (gst_element_link_many(audio_source, tee, NULL) != TRUE) {
g_printerr("Elements could not be linked.\n");
gst_object_unref(pipeline);
return -1;
}
// link first branch
if (gst_element_link_many(queue, visual, video_convert, video_sink, NULL) !=
TRUE) {
g_printerr("Elements could not be linked.\n");
gst_object_unref(pipeline);
return -1;
}
// link second branch
if (gst_element_link_many(queue2, visual2, video_convert2, video_sink2,
NULL) != TRUE) {
g_printerr("Elements could not be linked.\n");
gst_object_unref(pipeline);
return -1;
}
/* Manually link the Tee, which has "Request" pads */
GstPad *tee_audio_pad = gst_element_request_pad_simple(tee, "src_%u");
g_print("Obtained request pad %s for first audio branch.\n",
gst_pad_get_name(tee_audio_pad));
GstPad *queue_audio_pad = gst_element_get_static_pad(queue, "sink");
GstPad *tee_audio_pad2 = gst_element_request_pad_simple(tee, "src_%u");
g_print("Obtained request pad %s for second audio branch.\n",
gst_pad_get_name(tee_audio_pad));
GstPad *queue_audio_pad2 = gst_element_get_static_pad(queue2, "sink");
if (gst_pad_link(tee_audio_pad, queue_audio_pad) != GST_PAD_LINK_OK ||
gst_pad_link(tee_audio_pad2, queue_audio_pad2) != 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_audio_pad2);
// start playing
gst_element_set_state(pipeline, GST_STATE_PLAYING);
// wait until error or EOS
GstBus *bus = gst_element_get_bus(pipeline);
GstMessage *msg = gst_bus_timed_pop_filtered(
bus, GST_CLOCK_TIME_NONE,
(GstMessageType)(GST_MESSAGE_ERROR | GST_MESSAGE_EOS));
gst_element_release_request_pad(tee, tee_audio_pad);
gst_element_release_request_pad(tee, tee_audio_pad2);
gst_object_unref(tee_audio_pad);
gst_object_unref(tee_audio_pad2);
// release 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;
}
int main(int argc, char *argv[]) {
#if defined(__APPLE__) && TARGET_OS_MAC && !TARGET_OS_IPHONE
return gst_macos_main((GstMainFunc)tutorial_main, argc, argv, NULL);
#else
return tutorial_main(argc, argv);
#endif
}
- 首先,创建各个元素,并将可以连接的部分先进行连接:
- audio_source -> tee
- queue -> visual -> video_convert -> video_sink
- queue2 -> visual2 -> video_convert2 -> video_sink2
- 向 tee 元素请求两个 src pad,连接 tee 与 queue、queue2
- 其他就是启动 pipeline,监听 bus 数据等常规操作,不再赘述
- 最后就是清理工作
总之非常简单,非常清晰。
参考
- Basic tutorial 7: Multithreading and Pad Availability
- basic-tutorial-7.c