GStreamer 示例代码学习记录
GStreamer 示例代码学习记录
前言
本文是笔者阅读 GStreamer 官方文档中的基础教程和播放教程去学习 GStreamer 时的记录,大部分内容可以在 GStreamer 官方文档中找到。
笔者学习 GStreamer 的主要目的是:使用 GStreamer 处理从 TCP Socket 接收到的 H.265 裸流(使用 live555 的 testRTSPclient 程序从 RTSP URL 中获取到),将其解码并保存为 jpeg 图片。
部分教程与目的关系不大,为涉及到。笔者最终放弃使用 GStreamer 的想法,使用 FFmpeg 完成了上述目的。
基础教程1:
媒体流从 source 元素到 sink 元素,中间通过一系列中间元素比如 filter过滤器元素,所有这些的集合称为 pipeline。管道是一种特殊类型的 bin,也是用于包含其他元素的元素。因此,适用于 bin 的所有方法也适用于管道。pipeline 的状态为 GST_STATE_PLAYING 时播放流经的音/视频。
playbin 是一个特殊的元素,它既作为源又作为接收器,是一个完整的流水线。在内部,它创建并连接所有必要的元素来播放媒体,见下例,URI 作为唯一参数传递给playbin。
1 | gst_init (&argc, &argv); // 初始化,必须是第一个GStreamer命令 |
基础教程2:
videotestsrc 是一个源元素(产生数据),它创建一个测试视频模式。此元素对于调试目的(和教程)很有用,通常在真实的应用程序中找不到。
autovideosink 是一个接收器元素(消耗数据),它在窗口上显示它接收的图像。根据操作系统的不同,存在几个具有不同功能范围的视频接收器。autovideosink 会自动选择并实例化最好的一个,因此不必担心细节,并且代码更加独立于平台。
管道:
GStreamer 中的所有元素通常必须包含在管道中才能使用,因为它负责一些时钟和消息传递功能。我们使用gst_pipeline_new()创建管道。
属性:
GStreamer元素都是一种特殊的GObject,它是提供属性设施的实体。大多数 GStreamer 元素都具有可自定义的属性:命名属性,可以修改这些属性以更改元素的行为(可写属性),或者查询这些属性以了解元素的内部状态(可读属性)。
使用g_object_get()读取属性,使用g_object_set()写入属性。g_object_set() 接受以 NULL 结尾的属性名称、属性值对列表,因此可以一次性更改多个属性。
1 | // 将元素添加到管道中(注意强制转换为bin类型)。此函数接受要添加的元素列表,以NULL结尾。可以使用gst_bin_add()添加单个元素。 |
基础教程3动态管道
pad:GStreamer 元素相互通信的端口称为 pad ( GstPad
)。存在 sink pad(数据通过其进入元素)和 source pad(数据通过其退出元素)。自然地,source 元素仅包含 source pad,sink 元素仅包含 sink pad,而 filter 元素包含两者。
多路分配器 demuxer 包含一个 sink pad(多路复用数据通过其到达)和多个 source pads(一个用于在容器中找到的每个流):
demuxer 只有在收到视频流之后,收集到流的信息,才会创建自己的 source pad,因此,不能事先构建 demuxer 之后的连接。
1 | // 保存在结构体中便于处理 |
uridecodebin
将在内部实例化所有必要的元素(源、解复用器和解码器),以将 URI 转换为原始音频和/或视频流。它完成了 playbin
一半的工作。由于它包含解复用器,因此它的源焊盘最初不可用,我们需要动态链接到它们。
audioconvert
对于在不同音频格式之间进行转换非常有用,确保此示例可以在任何平台上运行,因为音频解码器生成的格式可能与音频接收器期望的格式不同。
audioresample
对于在不同音频采样率之间进行转换非常有用,同样确保此示例适用于任何平台,因为音频解码器产生的音频采样率可能不是音频接收器支持的采样率。
autoaudiosink
相当于上一教程中的 autovideosink
。它将音频流渲染到声卡。
1 | // 添加给源(uridecodebin 元素)一个“pad-added”信号,第三个参数提供回调函数,第四个参数是数据指针(CustomData 结构的指针),对数据指针不执行任何操作,只是转发到回调函数便于共享信息。 |
基础教程4
1 | msg = gst_bus_timed_pop_filtered (bus, 100 * GST_MSECOND, |
从总线上获取消息的函数,这次加上超时,如果100毫秒没消息,返回 NULL,依次来更新终端显示。
事件用数字乘以 GST_MSECOND 等宏表示。
1 | if (!gst_element_query_position (data.pipeline, GST_FORMAT_TIME, ¤t)) { |
gst_element_query_position()
隐藏了查询对象的管理,直接为我们提供结果。直接获得当前流的播放的位置。
gst_element_query_duration()
函数获得流的总长度。
GST_TIME_FORMAT
和 GST_TIME_ARGS
宏的使用提供用户友好的 GStreamer 时间表示。
1 | if (data.seek_enabled && !data.seek_done && current > 10 * GST_SECOND) { |
gst_element_seek_simple()
来“简单地”执行查找。第二个参数表示以时间为单位指定目的地。
第三个参数是GstSeekFlags
,本例中的两个选项:
GST_SEEK_FLAG_FLUSH
:这会在执行查找之前丢弃当前管道中的所有数据。当管道重新填充并且新数据开始显示时可能会暂停一点,但会大大提高应用程序的“响应能力”。如果未提供此标志,则“陈旧”数据可能会显示一段时间,直到新位置出现在管道末尾。
GST_SEEK_FLAG_KEY_UNIT
:对于大多数编码视频流,不可能寻找任意位置,而只能寻找称为关键帧的某些帧。使用此标志时,搜索实际上会移动到最近的关键帧并立即开始生成数据。如果不使用此标志,管道将在内部移动到最近的关键帧(它没有其他选择),但数据在到达请求的位置之前不会显示。最后一种选择更准确,但可能需要更长的时间。
第四个参数提供搜索位置,本例是30秒,即跳转到30秒处继续播放。
1 | case GST_MESSAGE_DURATION: |
消息类型为GST_MESSAGE_DURATION
时,每当流的总长度发生变化时,该消息就会发布到总线上。这里我们只是将总长度(时间)标记为无效,以便稍后重新查询。
1 | case GST_MESSAGE_STATE_CHANGED: { |
消息类型为GST_MESSAGE_STATE_CHANGED
时打印流的前后状态,搜索和时间查询通常仅在处于 PAUSED
或 PLAYING
状态时才能获得有效答复,因为所有元素都有机会接收信息并配置自身。在这里,我们使用 playing
变量来跟踪管道是否处于 PLAYING
状态。另外,如果我们刚刚进入 PLAYING
状态,我们将执行第一个查询。我们询问管道是否允许在此流上查找:
1 | if (data->playing) { |
gst_query_new_seeking()
创建一个“寻求”类型的新查询对象,格式为 GST_FORMAT_TIME
。这表明我们有兴趣通过指定我们想要移动到的新时间来进行搜索。我们还可以请求 GST_FORMAT_BYTES
,然后查找源文件内的特定字节位置,但这通常不太有用。
然后,该查询对象通过 gst_element_query()
传递到管道。结果存储在同一查询中,并且可以使用 gst_query_parse_seeking()
轻松检索。它提取一个布尔值,指示是否允许查找以及可以查找的范围。
基础教程6
Pad 允许信息进入和离开元素,Pad 可以指定 Pad 传输的信息类型,比如“分辨率为 320x200 像素、每秒 30 帧的 RGB 视频”,可以支持多种功能。从 Pad 到 Pad 传输的实际信息必须只有一种明确指定的类型。通过”协商“,两个链接的 Pad 就通用类型达成一致,因此 Pad 的功能变得固定(它们只有一种类型且不包含范围)。
为了将两个元素连接在一起,他们必须有一个公共的功能子集。
Pad 是根据 Pad 模板创建的,该模板指示 Pad 可能具有的所有可能功能。模板可用于创建多个相似的 Pad,并且还允许提前拒绝元素之间的连接:如果其 Pad 模板的功能没有公共子集(它们的交集为空),则无需进一步协商。Pad 模板可以被视为谈判过程的第一步。随着流程的发展,实际的 Pad 会被实例化,其功能也会得到完善,直到它们被修复(或协商失败)。
1 |
|
1 | /* 创建元素工厂 */ |
在之前的教程中,我们直接使用 gst_element_factory_make()
创建元素且没有谈论工厂,但现在我们用 GstElementFactory
负责实例化特定类型的元素,由其工厂名称标识。
gst_element_factory_find()
创建类型为“videotestsrc”的工厂,然后使用它通过 gst_element_factory_create()
实例化多个“videotestsrc”元素。 gst_element_factory_make()
实际上是 gst_element_factory_find()
+ gst_element_factory_create()
的快捷方式。
1 | case GST_MESSAGE_STATE_CHANGED: |
每次管道状态发生变化时,这都会简单地打印当前的 Pad Caps。您应该在输出中看到初始 Caps(Pad 模板的 Caps)如何逐渐细化,直到完全固定(它们包含没有范围的单一类型)。
基础教程7:多线程和Pad可用性
GStreamer 是一个多线程框架。这意味着,它在内部根据需要创建和销毁线程,例如,将流与应用程序线程解耦。此外,插件还可以自由创建线程用于自己的处理,例如,视频解码器可以创建 4 个线程以充分利用 4 核 CPU。
除此之外,在构建管道时,应用程序可以明确指定分支(管道的一部分)在不同的线程上运行(例如,让音频和视频解码器同时执行)。
这是使用 queue
元素完成的,其工作原理为:接收器 Pad 只是将数据排队并返回控制。在不同的线程上,数据出队并推送到下游。该元素还用于缓冲,如后面的流教程中所示。队列的大小可以通过属性来控制。
此示例构建以下管道:
source 是一个合成音频信号(连续音调),它使用 tee
元素进行分割(它把 sink pad 接收的所有内容用 source pad 发送出去)。然后,一个分支将信号发送到声卡,另一个分支渲染波形视频并将其发送到屏幕。
如图所示,队列创建一个新线程,因此该管道在 3 个线程中运行。具有多个接收器的管道通常需要多线程,因为为了同步,接收器通常会阻塞执行,直到所有其他接收器准备好为止,并且如果只有一个线程,则它们无法准备好,会被第一个接收器阻塞。
在基础教程 3:动态管道中,我们看到一个元素 ( uridecodebin
) 一开始就没有 pad,它们随着数据开始流动并且媒体流经元素而出现。这些称为 Sometimes Pads,与始终可用且称为 Always Pads的常规 pad 形成对比。
第三种 pad 是 Request Pad,它是按需创建的。典型的示例是 tee
元素,它有一个 sink pad 并且没有初始 source pad:需要请求它们,然后 tee
添加它们。通过这种方式,输入流可以被复制任意多次。缺点是使用 Request Pads 链接元素并不像链接 Always Pads 那样自动,如本示例的演练所示。
此外,要在 PLAYING
或 PAUSED
状态下请求(或释放)Pad,需要采取额外的注意事项(Pad 阻塞),本教程中未对此进行描述。不过,在 NULL
或 READY
状态下请求(或释放)pad 是安全的。
1 | /* 创建元素 */ |
audiotestsrc
产生合成音。 wavescope
消耗音频信号并呈现波形,就像它是一个示波器一样。我们已经使用过 autoaudiosink
和 autovideosink
。
转换元素( audioconvert
、 audioresample
和 videoconvert
)对于保证管道可以链接是必需的。事实上,音频和视频接收器的功能取决于硬件,并且您在设计时不知道它们是否与 audiotestsrc
和 wavescope
生成的 Caps 匹配。不过,如果 Caps 匹配,这些元素将以“直通”模式运行,不会修改信号,对性能的影响可以忽略不计。
1 | /* 配置元素 */ |
audiotestsrc
的“freq”属性控制波的频率(215Hz 使波在窗口中看起来几乎静止), wavescope
使波连续。
1 | /* Link all elements that can be automatically linked because they have "Always" pads */ |
此代码块将所有元素添加到管道中,然后链接可以自动链接的元素(带有 Always Pads 的元素)。
gst_element_link_many()
实际上可以将元素与 Request Pads 链接起来。它在内部请求 Pad,因此不必担心链接的元素具有 Always 或者 Request Pads。这实际上很不方便,因为之后您仍然需要释放所请求的 Pad,而且,如果 Pad 是由gst_element_link_many()
自动请求的,会很容易忘记。始终手动请求 Request Pads ,以避免出现麻烦,如下所示。
1 | /* 手动连接具有 "Request" pads 的 tee 元素*/ |
要链接 request pad,需要通过向元素“请求”它们来获取它们。一个元素可能能够生成不同类型的请求 pad,因此,在请求它们时,必须提供所需的 pad 模板名称。tee 有两个 pad 模板,分别为“sink”(用于其接收器 pad)和“src_%u”(用于Request pad)。我们使用gst_element_request_pad_simple()
从 tee(用于音频和视频分支)请求两个 pad 。
然后,我们从这些请求 Pad 需要链接到的下游元素获取 Pad。这些是普通的 Always Pad,因此我们使用 gst_element_get_static_pad()
获取它们。
最后,我们将 pad 与 gst_pad_link()
链接起来。这是 gst_element_link()
和 gst_element_link_many()
内部使用的函数。
我们获得的sink Pad需要通过 gst_object_unref()
来释放。当我们不再需要它们时,在程序结束时,请求垫将被释放。
然后,我们将管道设置为正常播放,并等待生成错误消息或 EOS。剩下要做的唯一一件事就是清理请求的 Pad:
1 | gst_element_release_request_pad (tee, tee_audio_pad); |
gst_element_release_request_pad()
从 tee
释放 pad,但仍需要使用 gst_object_unref()
取消引用(释放)它。
基础教程8:简化管道
用于将应用程序数据注入 GStreamer 管道的元素是 appsrc
,其对应元素用于将 GStreamer 数据提取回应用程序是 appsink
。 appsrc
只是一个常规源,由应用程序提供数据(在GStreamer中视为appsrc
提供。 appsink
是一个常规接收器,流经 GStreamer 管道的数据将被消费(实际上由应用程序获得并使用)。
appsrc
可以在多种模式下工作:在拉模式下,它每次需要时都会向应用程序请求数据。在推送模式下,应用程序按照自己的节奏推送数据。此外,在推送模式下,当已经提供了足够的数据时,应用程序可以选择在推送功能中阻塞,或者可以监听 enough-data
和 need-data
信号来控制流量。
数据以称为缓冲区(buffers)的块的形式通过 GStreamer 管道。类型是GstBuffer
,Source Pad 产生缓冲区,由 Sink Pad 消耗; GStreamer 获取这些缓冲区并将它们从一个元素传递到另一个元素。
缓冲区仅表示一个数据单元,不要假设所有缓冲区都具有相同的大小,或表示相同的时间量。如果单个缓冲区进入一个元素,那么单个缓冲区也不一定会出来。元素可以随意处理接收到的缓冲区。 GstBuffer
也可能包含多个实际内存缓冲区。实际的内存缓冲区是使用 GstMemory
对象抽象出来的,一个 GstBuffer
可以包含多个 GstMemory
对象。
每个缓冲区都附加了时间戳和持续时间,描述了缓冲区内容应在哪个时刻被解码、渲染或显示。例如, filesrc
(读取文件的 GStreamer 元素)生成具有“ANY”上限且没有时间戳信息的缓冲区。解复用后(请参阅基础教程 3:动态管道),缓冲区可以具有一些特定的上限,例如“video/x-h264”。解码后,每个缓冲区将包含一个带有原始上限的视频帧(例如“video/x-raw-yuv”)和非常精确的时间戳(指示何时应显示该帧)。
本教程以两种方式扩展了基础教程 7:多线程和 Pad 可用性:首先,将 audiotestsrc
替换为将生成音频数据的 appsrc
。其次,一个新分支被添加到 tee
中,因此进入音频接收器和波形显示的数据也被复制到 appsink
中。 appsink
将信息上传回应用程序,然后应用程序仅通知用户已收到数据,但它显然可以执行更复杂的任务。
创建管道的代码是基础教程 7:多线程和 Pad 可用性的放大版本。它涉及实例化所有元素,将元素与Always Pads 链接,并手动链接 tee
元素的Request Pads。
关于 appsrc
和 appsink
元素的配置:
1 | /* Configure appsrc */ |
需要在 appsrc
上设置的第一个属性是 caps
。它指定元素将生成的数据类型,因此 GStreamer 可以检查是否可以与下游元素链接(即,下游元素是否能够理解此类数据)。此属性必须是 GstCaps
对象,可以使用 gst_caps_from_string()
轻松地从字符串构建该对象。
然后我们连接到 need-data
和 enough-data
信号。当 appsrc
的内部数据队列不足或几乎满时,它们会分别被触发。我们将使用这些信号来(分别)启动和停止我们的信号生成过程。
启动管道、等待消息和最终清理都像往常一样完成。让我们回顾一下我们刚刚注册的回调:
1 | //当 appsrc 需要数据时,触发下面的回调函数。主循环添加一个空闲处理程序以开始将数据推送到 appsrc |
当 appsrc
的内部队列即将耗尽(数据耗尽)时调用此函数。我们在这里做的唯一一件事就是向 g_idle_add()
注册一个 GLib 空闲函数,该函数将数据提供给 appsrc
直到它再次填满。 GLib 空闲函数是 GLib 在“空闲”时(即没有更高优先级的任务要执行时)从其主循环调用的方法。显然,它需要一个 GLib GMainLoop
来实例化并运行。
这只是 appsrc
允许的多种方法之一。特别是,缓冲区不需要使用 GLib 从主线程输入 appsrc
,并且不需要使用 need-data
和 enough-data
信号来与 appsrc
同步(尽管据称这是最方便的)。
我们记下 g_idle_add()
返回的 sourceid,以便稍后禁用它。
1 | //当 appsrc 有足够的数据时,此回调会触发,我们可以停止发送。 |
当 appsrc
的内部队列足够满时调用此函数,因此我们停止推送数据。这里我们简单地使用 g_source_remove()
删除空闲函数(空闲函数被实现为 GSource
)。
1 | /* 此方法由主循环中的空闲 GSource 调用,将 CHUNK_SIZE 字节输入到 appsrc 中。 |
这是提供 appsrc
的函数。 GLib 会以我们无法控制的时间和速率调用它,但我们知道,当它的工作完成时(当 appsrc
中的队列已满时),我们将禁用它。
gst_util_uint64_scale()
是一个实用函数,可以缩放(乘法和除法)可能很大的数字,而不必担心溢出。
为了访问缓冲区的内存,您首先必须将其映射到 gst_buffer_map()
,这将为您提供 GstMapInfo
结构内的指针和大小,其中 gst_buffer_map()
将在成功时填充。请小心,不要写入超过缓冲区末尾的内容:您分配了它,因此您知道它的大小(以字节和样本为单位)。
1 | /* Push the buffer into the appsrc */ |
请注意,还有 gst_app_src_push_buffer()
作为 gstreamer-app-1.0
库的一部分,与上面的信回调相比,它可能是一个更好的用于将缓冲区推送到 appsrc 的函数,因为它具有适当的键入签名,这样就很难出错。但是,请注意,如果您使用 gst_app_src_push_buffer()
,它将获得传递的缓冲区的所有权,因此在这种情况下,您不必在推送后取消引用它。
准备好缓冲区后,我们将其与 push-buffer
操作信号一起传递给 appsrc
(请参阅播放教程 1:Playbin 用法末尾的信息框),然后 gst_buffer_unref()
因为我们不再需要它了。
1 | /* Configure appsink */ |
当 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()
检索到的样本,本教程就完成了。
基础教程9:媒体信息采集
感觉用处不大
基础教程10:
无用
基础教程11:调试工具
GStreamer 及其插件充满了调试跟踪,即代码中将特别有趣的信息打印到控制台的位置,以及时间戳、进程、类别、源代码文件、函数和元素信息。调试输出由 GST_DEBUG
环境变量控制。
GStreamer 调试日志非常详细,完全启用会打印很多。设置 GST_DEBUG=2
就可以获得 ERROR
和 WARNING
消息。
使用 GST_ERROR()
、 GST_WARNING()
、 GST_INFO()
、 GST_LOG()
和 GST_DEBUG()
宏。它们接受与 printf
相同的参数,并使用 default
类别( default
将在输出日志中显示为“DEBUG”类别,对应值为5)。
获取管道图:
GStreamer 能够输出图形文件。这些是 .dot
文件,可以使用 GraphViz 等免费程序读取,描述管道的拓扑结构以及每个链接中协商的上限。
要获取 .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
文件。
基础教程 12:流式传输
感觉用处不大
基础教程13:播放速度
所要做的就是改变播放速率,该变量对于正常播放来说等于 1.0,对于快速模式大于 1.0(绝对值),对于慢速模式小于 1.0(绝对值),对于向前播放为正值,反向播放时为负值。
GStreamer 提供了两种更改播放速率的机制:Step Events 和 Seek Events。除了更改后续播放速率(仅限正值)之外,Step Events 还允许跳过给定数量的媒体。此外,“Seek Events”允许跳转到流中的任何位置并设置正和负播放速率。
Step Events 是更改播放速率的更方便的方法,因为创建它们所需的参数数量减少了;但是,它们有一些缺点,因此本教程中使用 Seek Events。Step Events 仅影响接收器(在管道末端),因此只有当管道的其余部分可以支持以不同的速度运行时,它们才会起作用, Seek Events 会一直通过管道,因此每个元素都可以对它们做出反应。 Step Events 事件的优点是行动速度更快。步骤事件也无法改变播放方向。
要使用这些事件,需要创建它们,然后将其传递到管道,它们在管道中向上游传播,直到到达可以处理它们的元素。如果一个事件被传递到像 playbin
这样的 bin 元素上,它只会将事件提供给它的所有接收器,这将导致执行多次查找。常见的方法是通过 video-sink
或 audio-sink
属性检索 playbin
的接收器之一,并将事件直接送入接收器。
帧步进是一种允许逐帧播放视频的技术。它是通过暂停管道,然后发送 Step Events 每次跳过一帧来实现的。
main 函数中的初始化代码没有什么新内容:实例化了 playbin
管道,安装了 I/O 监视器来跟踪击键,并执行了 GLib 主循环。然后,在键盘处理函数中:
1 | /* Process keyboard input */ |
与之前的教程一样,暂停/播放切换是通过 gst_element_set_state()
处理的。
1 | case 's': |
使用“S”和“s”将当前播放速率加倍或减半,使用“d”反转当前播放方向。在这两种情况下,都会更新 rate
变量并调用 send_seek_event
。我们来回顾一下这个函数。
1 | /* Send seek event to change rate */ |
该函数创建一个新的 Seek Event 并将其发送到管道以更新速率。首先,使用 gst_element_query_position()
恢复当前位置。这是必需的,因为 Seek Events 跳转到流中的另一个位置,并且由于我们实际上不想移动,所以我们跳转到当前位置。使用 Step Events 会更简单,但该事件目前尚未完全发挥作用,如简介中所述。
1 | /* Create the seek event */ |
Seek 事件是使用 gst_event_new_seek()
创建的。它的参数基本上是新的速率、新的开始位置和新的停止位置。无论哪种播放方向,起始位置都必须小于停止位置,因此两个播放方向要区别对待。
1 | if (data->video_sink == NULL) { |
新的事件最终通过 gst_element_send_event()
发送到选定的接收器。
回到键盘处理程序及,看一下帧步进代码,这非常简单:
1 | case 'n': |
使用 gst_event_new_step()
创建一个新的步进事件,其参数基本上指定要跳过的量(示例中为 1 帧)和新速率(我们不更改)。
视频接收器是从 playbin
获取的,以防万一我们还没有,就像以前一样。
这样我们就完成了。测试本教程时,请记住,向后播放在许多元素中都不是最佳的。
基础教程 14:方便的元素:
都是用在 gst-launch-1.0
工具中的参数,其中部分:
uridecodebin
该元素将数据从 URI 解码为原始媒体。它选择一个可以处理给定 URI 方案的源元素并将其连接到 decodebin
元素。它的作用就像一个解复用器,因此它提供与媒体中找到的流一样多的源 pad。
decodebin
该元素通过自动插入使用可用的解码器和解复用器自动构建解码管道,直到获得原始媒体。它由 uridecodebin
内部使用,通常使用起来更方便,因为它也创建了合适的源元素。它取代了旧的 decodebin
元素。它的作用就像一个解复用器,因此它提供与媒体中找到的流一样多的源 pad。
videoconvert
元素从一种颜色空间(例如 RGB)转换为另一种颜色空间(例如 YUV)。它还可以在不同的 YUV 格式(例如 I420、NV12、YUY2 …)或 RGB 格式排列(例如 RGBA、ARGB、BGRA …)之间进行转换。这通常是解决谈判问题时的首选。当不需要时,由于其上游和下游元素已经可以相互理解,因此它以直通模式运行,对性能的影响最小。根据经验,每当您使用大写字母在设计时未知的元素(如 autovideosink
)或可能因外部因素(如解码用户)而变化时,请始终使用 videoconvert
。提供的文件。
videorate
该元素接受带有时间戳的视频帧的传入流,并生成与源板的帧速率匹配的流。校正是通过丢弃和复制帧来执行的,没有使用花哨的算法来插值帧。这对于允许需要不同帧速率的元素进行链接非常有用。与其他适配器一样,如果不需要(因为两个 Pad 都可以同意帧速率),它会以直通模式运行并且不会影响性能。
videoscale
该元素调整视频帧的大小。默认情况下,元素会尝试在源焊盘和接收焊盘上协商到相同的大小,以便不需要缩放。因此,如果不需要扩展,则可以安全地将此元素插入管道中以获得更稳健的行为,而无需任何成本。该元素支持广泛的色彩空间,包括各种 YUV 和 RGB 格式,因此通常能够在管道中的任何位置运行。如果要将视频输出到大小由用户控制的窗口,则最好使用 videoscale
元素,因为并非所有视频接收器都能够执行缩放操作。
播放教程3:缩短管道
基础教程 8:缩短管道展示了应用程序如何使用名为 appsrc 和 appsink 的两个特殊元素手动提取数据或将数据注入到管道中。playbin
也允许使用这些元素,但连接它们的方法不同。要将 appsink
连接到 playbin
,请参阅播放教程 7:自定义 playbin 接收器。本教程展示 如何将 appsrc
与 playbin
连接并配置appsrc
要使用 appsrc
作为管道源,只需实例化 playbin
并将其 URI 设置为 appsrc://
1 | /* Create the playbin element */ |
playbin
将创建一个内部 appsrc
元素并触发 source-setup
信号以允许应用程序对其进行配置:
1 | g_signal_connect (data.pipeline, "source-setup", G_CALLBACK (source_setup), &data); |
设置 appsrc
的caps
属性非常重要,因为一旦信号处理程序返回, playbin
将根据这些上限实例化管道中的下一个元素:
1 | /* 当 playbin 创建 appsrc 元素时会调用此函数,因此我们有机会对其进行配置。*/ |
appsrc
的配置与基础教程 8:缩短管道完全相同:将 caps 设置为 audio/x-raw
,并注册了两个回调,因此元素可以告诉应用程序何时需要启动和停止推送数据。有关更多详细信息,请参阅基础教程 8:缩短管道。
从这一点开始, playbin
负责处理管道的其余部分,应用程序只需要担心在被告知时生成更多数据。
要了解如何使用 appsink
元素从 playbin
中提取数据,请参阅播放教程 7:自定义 playbin 接收器。
播放教程 7:自定义 playbin 接收器
playbin
可以通过手动选择其音频和视频接收器来进一步自定义。这允许应用程序依赖 playbin
来检索和解码媒体,然后自行管理最终的渲染/显示。
playbin
的两个属性允许选择所需的音频和视频接收器: audio-sink
和 video-sink
(分别)。应用程序只需要实例化适当的 GstElement
并通过这些属性将其传递给 playbin
。但是,此方法只允许使用单个元素作为接收器。如果需要更复杂的管道,例如均衡器加音频接收器,则需要将其包装在 Bin 中,因此它看起来 playbin
就好像它是单个 Element 一样。
Bin ( GstBin
) 是一个封装部分管道的容器,因此可以将它们作为单个元素进行管理。例如,我们在所有教程中使用的 GstPipeline
是 GstBin
的类型,它不与外部元素交互。 Bin 内的元素通过 Ghost Pad ( GstGhostPad
) 连接到外部元素,这是 Bin 表面上的 Pad,它只是将数据从外部 Pad 转发到内部元素上的给定 Pad。
GstBin
也是 GstElement
的一种类型,因此它们可以在需要 Element 的任何地方使用,特别是作为 playbin
的接收器(然后它们被称为水槽)。

图 1:带有两个 Elements 和一个 Ghost Pad 的 Bin。
GstBin
也是 GstElement
的一种类型,因此它们可以在需要 Element 的任何地方使用,特别是作为 playbin
的接收器(然后它们被称为水槽)。
1 | /* Create the elements inside the sink bin */ |
组成的 sink-bin 的所有元素都被实例化。我们使用 equalizer-3bands
和 autoaudiosink
,中间有 audioconvert
,因为我们不确定音频接收器的功能(因为它们是硬件 -依赖)。
1 | /* Create the sink bin, add the elements and link them */ |
这会将新元素添加到 Bin 中并将它们链接起来,就像我们在管道中所做的那样。
1 | // 获取bin的第一个元素 equalize r的 sink pad |
现在我们需要创建一个 Ghost Pad,以便 Bin 内的部分管道可以连接到外部。该 Ghost Pad 将连接到内部 Elements 之一的 Pad(均衡器的接收器 pad),因此我们使用 gst_element_get_static_pad()
检索该 Pad。请记住基础教程 7:多线程和 Pad 可用性,如果这是 Request Pad 而不是 Always Pad,我们将需要使用 gst_element_request_pad()
。
Ghost Pad 使用 gst_ghost_pad_new()
创建(指向我们刚刚获取的内部 Pad),并使用 gst_pad_set_active()
激活。然后使用 gst_element_add_pad()
将其添加到 Bin,将 Ghost Pad 的所有权转移到 bin,因此我们不必担心释放它。
最后,我们从均衡器 equalizer 获得的 sink Pad 需要用 gst_object_unref()
释放。
此时,我们有了一个功能性的 sink-bin,我们可以将其用作 playbin
中的音频接收器。我们只需要指示 playbin
使用它:
1 | /* Set playbin's audio sink to be our sink bin */ |
就像将 playbin
上的 audio-sink
属性设置为新创建的接收器一样简单。
1 | /* Configure the equalizer */ |
剩下的唯一一点就是配置均衡器。对于此示例,两个较高频段被设置为最大衰减,因此低音得到增强。稍微调整一下这些值以感受差异(请参阅 equalizer-3bands
元素的文档以了解允许的值范围)。
gst_app_src_push_buffer
1 | GstFlowReturn |
将缓冲区添加到缓冲区队列中,appsrc 元素将其推送到其源焊盘。该函数取得缓冲区的所有权。
当 block 属性为 TRUE 时,此函数可以阻塞,直到队列中出现可用空间。
gst_app_src_get_stream_type
1 | GstAppStreamType |
获取流类型。使用 gst_app_src_set_stream_type 控制 appsrc 的流类型。
参数:
appsrc
– a GstAppSrc
返回流类型。
gst_app_src_get_size
1 | gint64 |
获取流的大小(以字节为单位)。值 -1 表示大小未知。
参数:
appsrc
– a GstAppSrc
返回 – 之前使用 gst_app_src_set_size 设置的流的大小;
1 | gcc -g src/server.cc -o server `pkg-config --cflags --libs gstreamer-1.0 gstreamer-video-1.0` -lstdc++ -lpthread -lstdc++ -lstdc++fs -lstdc++ -lgstapp-1.0 -lgstvideo-1.0 -lgstbase-1.0 |