前面两篇我们完成了两件很重要的事情,第一是建立了编写插件程式的环境和测试方法,第二是替插件装好了进出水阀(sinkpad和srcpad)的格式和属性,格式不合的资料进不来,也出不去。接下来我们要开始放水,让资料流进这个插件。
gstreamer在处理资料的流动有两种主要的模式,一个是「推」,一个是「拉」。两种模式需要实作的routine不同,在对资料的操作(manipulation)上的重点也不一样,很容易被搞得摸不清方向(其实我到现在还是有很多没搞懂的地方…)。首先先解释一下两者的不同。
「推」模式就是由上游的插件控制资料的大小、流速,向下「推」到下游的插件,所以下游的插件并不会事先知道有多少资料会被送进来,它就必须先准备一个缓冲区来承接资料,然后判断缓冲区里的资料是否足够拆解出一个压缩单位的资料,够的话就把资料切割出一个固定大小送给解码器,剩下的资料要留著和下一笔流进来的资料做连接。
「拉」模式则是需要自己控制资料大小、流速,告诉上游的插件说自己要多少资料,从几分几秒开始读,自己控制速度、大小等等变数,把资料「拉」进来。因为要流进来的资料量(举例来说,media-object的size、chunksize、packet size)自己可以控制,就不需要设计一个缓冲区来放资料。
通常,「拉」模式会用在 demuxer,而「推」模式用在其他插件,所以gst-template提供的例子是「推」模式的写法。_chain()函式就是让上游插件把资料送进来的接口,当资料开始流动的时候(完成启动阶段(activationstage)后,启动的部份留待后述。)会直接唤起初始阶段时向pad注册的chain 函式,这个函式的介面(GstPadChainFunction)是已经被定义好的,其中一个变数是GstBuffer的指标,资料就被塞在这个指标所指向的记忆体空间。我们便可以透过注册进去的函式,取得操作这段资料的handle。
Gstreamer 在处理资料流有四个状态:Null,Ready, Pause, Playing按顺序切换。也就是说,刚开始播放一个档案时状态变化是:Null –> Ready –> Pause –>Playing,当播放结束要释放pipeline的顺序就是原路走回去:Playing–> Pause –> Ready –> Null。我们写的这个mp3dec插件是要把mpegaudio decoder libmad包装为 gstreamer插件,所以在开始播放档案之前必须先把插件初始化(比如说,设定membervariable的初始值,初始化 gstreamer的其他元件等等),当然,也要先初始化libmad。初始化的动作一般来说,应该要放在Null转到Ready的阶段,或 Ready 转到Pause 的阶段,绝对不可能是在Pause转到Playing的阶段,因为 Pause 和Playing 两个状态是切换播放模式用的(如:暂停、快进、Seeking)。
到目前为止都很抽象,我们走进源码来看就会好一点。
为了处理刚提到的状态切换,我们要注册一个_change_state()函式。
1: static GstStateChangeReturn
2: gst_mp3dec_change_state(GstElement* element, GstStateChange transition)
3: {
4: GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
5: Gstmp3dec *dec;
6: dec = GST_MP3DEC(element);
7:
8: switch(transition)
9: {
10: case GST_STATE_CHANGE_NULL_TO_READY:
11: mad_frame_init(&dec->frame);
12: mad_stream_init(&dec->stream);
13: mad_synth_init(&dec->synth);
14: break;
15: default:
16: break;
17: }
18:
19: ret = parent_class->change_state(element, transition);
20: if(ret == GST_STATE_CHANGE_FAILURE)
21: return ret;
22:
23: switch(transition)
24: {
25: case GST_STATE_CHANGE_READY_TO_NULL:
26: gst_mp3dec_reset(dec);
27: break;
28: default:
29: break;
30: }
31: return ret;
32: }
33:
34: static void gst_mp3dec_clas_init()
35: {
36: ...
37: gstelement_class->change_state = gst_mp3dec_change_state;
38: ...
39: }
如刚所说,当状态从 NULL 转到READY时(GST_STATE_CHANGE_NULL_TO_READY),插件要做初始化,配置记忆体等。反过来当状态从READY转到NULL时(GST_STATE_CHANGE_READY_TO_NULL),就要释放资源。为了避免当主要的执行续(mainthread)还在运作时,就因为收到「停止」的指令,从PLAYING切进NULL,把资源都给释放掉,所以状态转换要分成两个switch-case来处理。
我们可以试著讨论一下 pipeline如此处理状态切换的理由是什么。想像你手上有一个滤水器,一个水桶的污水和一个干净的水壶