playbin3的调用流程如图:
最上层可以分为三个部分 ,playbin的任务负责创建下面三个bin, 然后进行连接,连接的机制依赖于gsingal。这三个bin分别为urisourcebin 、decodebin3、playsink。其中urisourcebin 的任务 负责从文件或者网络流中读取文件 并识别封装格式。decodebin3 则负责将urisourcebin读取的数据解码成raw video audio text。playsink 则负责将音视频进行同步 并输出到对应的设备上。
5.1 urisourcebin
这里面创建两个element,根据url的情况创建相对应的source elemnt。 随后创建一个typefind element 去探测封装格式。playbin创建时候 会注册pad_added消息给urisourcebin, 当typefind 的element 探测到某一个格式后 会创建一个pad。创建pad后 发送消息给playbin,然后playbin就将urisourcebin和decodebin连接起来。
5.2 decodebin3
decodecbin创建parsebin 和multiqueue 和 音频 视频 字幕的解码器。parsebin 的任务是 解封装和解析 multiqueue的任务是起线程,让读数据解析和解码分离开来。parsebin也包含多个element,parsebin会创建typefind 重新探测一遍封装格式, 探测到之后,创建对应的demux elemnet, demux element开始工作,当demux发现新的流的时候,会在element上创建一个srcpad, 创建srcpad 会通知到parsbin。parsebin根据srcpad的caps 创建对应的pasre element(如 h264parse, aac pase 等)。 这个时候parse的 任务也完成了。创建之后,在decodebin3中会去请求multiqueue的一个sink pad, 每请求一个sink pad,mutliqueue就会分配一个队列给这个pad。 然后将parse的src pad 和multiqueue 的sink pad 连在一起。
multiqueue 将上游(parse)的数据 缓冲到队列里面,然后起一个task负责将队列中的数据push到下游的decoder去。缓冲区的大小会综合考虑所有队列缓冲的时长和数据大小。decodebin会监听mutiqueue的src pad,当接收到cap event(表明解封装到一个新流),会根据cap的情况创建不同的解码器element。
5.2.1 parsebin(Autiopluging)
gstreamer 中存在各种各样不同的插件,同时也有不同插件动态组装的pipeline如playbin, decodebin 等。pipeline 是 通过gst的autopluging来实现的, gst中自动化插件组装核心 是glib的 信号注册函数g_signal_connect。它会让你在你感兴趣的事情发生时收到通知。
以decodebin中的parsebin为例, 这个bin的任务是将src elment读上来的数据解封装 解析(假设现在播放一个ts格式的视频)
1、定义消息和回调函数
parsebin中定义了这几类消息和相对于的回调函数
- autoplug-continue ------- autoplug_continue
- autoplug-factories -------- autoplug_factories
- have-type------------ type_found
其中前两个消息是在类结构体初始化的时候的定义
最后一个消息是在parsebin state从READY变成PAUSED的时候 定义的
autoplug_continue: 都是返回TRUE
autoplug_factories:根据传入的caps返回 gst factories 的名字,用于创建后续的element使用
2、跟autoplug有关的是两个plugin
- typefind
- tsdumux
typefind
1、plugin init/element init
typefind: 识别封装格式,运行在pull 模式,在typefind的插件的初始化的时候即调用plugin_init的时候
多次调用了类似下面的函数来注册不同封装格式的type find
TYPE_FIND_REGISTER_RIFF (plugin, "video/x-msvideo", GST_RANK_PRIMARY,
"avi", "AVI ");
在gst_type_find_element_init 实例结构体初始化函数当中,将当前插件的运行模式设置为MODE_TYPEFIND。
2、type_find_element_loop
从后面可以知道typefind数据流动方式是pull mode。也即会起一个task 线程,在线程的loop里面
首先会判断是不是MODE_TYPEFIND,假设是这种模式的话,会调用之前init时候注册好的函数一个个进行判断,并对probability进行赋值。
最后返回probability最大的那个封装格式,并emit have_type 的消息给到parsebin 这边。
从1知道在parsebin中注册了 have_type信号的回调函数是type_found。
type_found主要完成
analyze_new_pad:
1、判断typefind 返回的cap是不是fixed(固定的只有一个)
2、emit autoplug_continue(默认返回TURE)
3、emit autoplug-factories (返回factories)
connect_pad:
4、根据factories 创建element(这个例子中是tsdemux)并加入到parsebin 中,并对sinkpad进行link
5、获取element的source pad,如果获取不到的话,那么注册pad-added 的信息到回调函数pad_added_cb(这个例子会注册这个回调)
pad_added_cb:在tsdemux 解析到一个视频流,音频流或者字幕流的时候 会调用到这里。在这个回调中 调用analyze_new_pad,同样是完成创建element 和完成连接的过程。
总的一个运行的机制就是:通过g_signal_connect注册事件和回调,然后随着数据的读取,不同的插件会依据解析到的数据 发送的不同信号,接收到信号后,会调用之前注册好的回调函数进行处理。
5.2.2 multiqueue(probes)
decodebin 中parsebin的功能我们已经了解,那么什么时候decodebin会去请求一个multiqueue的队列呢?
这个需要用到gst的一个probe的机制,通过gst_pad_add_probe可以对pad设置回调。
1、在decodebin中对parsebin的pad-added设置回调
input->pad_added_sigid =
g_signal_connect (input->parsebin, "pad-added",
(GCallback) parsebin_pad_added_cb, input);
从parasebin上面知道当demux 到视频流,音频流或者字幕流的时候,会emit pad-added信号,emit之后会调用parsebin_pad_added_cb。
2、对pad设置probe
parsebin_pad_added_cb中,注册了buffer和event的两个probe的事件。
ppad->event_probe =
gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM,
(GstPadProbeCallback) parsebin_pending_event_probe, ppad, NULL);
ppad->buffer_probe =
gst_pad_add_probe (pad,
GST_PAD_PROBE_TYPE_BLOCK | GST_PAD_PROBE_TYPE_BUFFER,
(GstPadProbeCallback) parsebin_buffer_probe, input, NULL);
- GST_PAD_PROBE_TYPE_BUFFER
如果有这个发生时,表示有buffer来了,probe注册的函数可以就这个buffer进行操作如检查,改变,丢弃等。
- GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM
有事件通过pad传送时。
3、probe的回调
parsebin_buffer_probe:这里面根据pad的stream情况创建一个流,流创建好之后就从mutliqueue那边取到一个slot(这个就是相当于是一个pad),将流和slot link在一起。
slot = get_slot_for_input (dbin, input_stream);
link_input_to_slot (input_stream, slot);
其中get_slot_for_input (GstDecodebin3 * dbin, DecodebinInputStream * input)
会做一堆的判断, 判断之后创建一个slot
create_new_slot (dbin, input_type);
5.3 playsink
将解码出来的音视频 数据做同步,然后分别放到各自的队列里面,同时后处理也在这个element里面。