Gstreamer 学习笔记(3):GstElement状态

注:这里同样是以gstreamer 1.14.0为蓝本

一、状态定义

在gstreamer中GstElement的状态定义如下:

typedef enum {
  GST_STATE_VOID_PENDING        = 0, 
  GST_STATE_NULL                = 1,
  GST_STATE_READY               = 2,
  GST_STATE_PAUSED              = 3,
  GST_STATE_PLAYING             = 4
} GstState;
  • GST_STATE_VOID_PENDING:没有等待状态,这只是作为一个枚举的起始
  • GST_STATE_NULL:     NULL或初始化状态
  • GST_STATE_READY:    准备好状态
  • GST_STATE_PAUSED:    暂停状态
  • GST_STATE_PLAYING:    播放状态

二、状态转换关系

gstreamer的每一个element当中都有一个状态机,不过比较有意思的一点是: gstreamer中使用枚举变量的方式定义好了状态之间的转换关系(或者叫状态转换映射),其枚举定义如下:

typedef enum /*< flags=0 >*/
{
	GST_STATE_CHANGE_NULL_TO_READY        = (GST_STATE_NULL<<3) | GST_STATE_READY,
	GST_STATE_CHANGE_READY_TO_PAUSED      = (GST_STATE_READY<<3) | GST_STATE_PAUSED,
	GST_STATE_CHANGE_PAUSED_TO_PLAYING    = (GST_STATE_PAUSED<<3) | GST_STATE_PLAYING,
	GST_STATE_CHANGE_PLAYING_TO_PAUSED    = (GST_STATE_PLAYING<<3) | GST_STATE_PAUSED,
	GST_STATE_CHANGE_PAUSED_TO_READY      = (GST_STATE_PAUSED<<3) | GST_STATE_READY,
	GST_STATE_CHANGE_READY_TO_NULL        = (GST_STATE_READY<<3) | GST_STATE_NULL
} GstStateChange;

由上面的定义可以看出,gstreamer的element从初始的NULL状态,到正确运行时的PLAYING状态的过程是:

在这里插入图片描述

而从PLAYING状态到NULL状态就正好是上面过程的一个反向过程:

在这里插入图片描述

接下来就来看一下这些状态变换过程当中,元件都需要做些什么工作:

A. GST_STATE_CHANGE_NULL_TO_READY:状态从NULL变为READY

  • 元件必须检测其所需资源是否可用,此时设备的接收器(sink)和源(src)通常都会探测设备自身的能力,以约束sink和src向外提供的能力;
  • 元件打开设备(在需要探测设备能力的情况下)。

B. GST_STATE_CHANGE_READY_TO_PAUSED:状态从READY变为PAUSED

  • 为了在PAUSED状态下能够接受数据,元件将激活其相应衬垫(pad),并启动流相关的线程;
  • 有些元件可能需要返回GST_STATE_CHANGE_ASYNC并在他们获取到足够信息之后才完成状态的转变。这也是sink衬垫(pad)的一个需求,因为他们在收到第一个buffer或者GST_EVENT_EOS之后才能完成状态的转变,当在PAUSED状态时,sink衬垫(pad)也会阻塞数据流;
  • 管道(pipeline)将会重置running_time为0;
  • 直播源(live sources)将会返回GST_STATE_CHANGE_NO_PREROLL而且不会产生数据。

C. GST_STATE_CHANGE_PAUSED_TO_PLAYING:状态从PAUSED变为PLAYING

  • 大多数元件会忽略这个状态的转换;
  • 管道(pipeline)在将其包含的所有子元件都置为PLAYING状态之前,将会选择一个时钟(GstClock )并分发给这些子元件,这就意味着,在PLAYING状态下,管道只允许使用之前选择的GstClock进行同步;
  • 管道使用GstClock和running_time来计算基准时间(base_time),同时管道也会在执行状态转换的时候将这个基准时间设置给所有的子元件;
  • 接收器元件(sink element)停止与接收数据或事件,开始渲染数据;
  • 接收器(sinks)可以在PLAYING状态下发送GST_MESSAGE_EOS消息,如果不在PLAYING状态则不允许发送此消息;
  • 当流(streams)在PAUSED或PLAYGING状态时,元件可以创建或移除不定衬垫(sometimes pad);
  • 直播源(live sources)开始产生数据并返回GST_STATE_CHANGE_SUCCESS。

D. GST_STATE_CHANGE_PLAYING_TO_PAUSED:状态从PLAYING变为PAUSED

  • 大多数元件会忽略这个状态的转换;
  • 管道将会使用之前得到的base_time和GstClock来计算当前的running_time,并保存这个时间以便在恢复播放(转到PLAYING状态)时可以使用;
  • 接收器(sinks)将取消任何GstClock调用的等待阻塞;
  • 如果一个sink当前没有持有一个buffer进行播放的话,就将返回GST_STATE_CHANGE_ASYNC,并在接收到一个新buffer或者GST_EVENT_EOS时完成状态转换;
  • 所有队列中的GST_MESSAGE_EOS消息都将被移除,因为当状态从PAUSED回到PLAYING时,这些队列中的消息将被重新发送,这个EOS消息是保存在GstBin容器的队列当中;
  • 直播源(live sources)停止产生数据并返回GST_STATE_CHANGE_NO_PREROLL。

E. GST_STATE_CHANGE_PAUSED_TO_READY:状态从PAUSED变为READY

  • sinks释放所有预填充的等待阻塞;
  • 元件释放所有对设备的等待阻塞;
  • 元件的Chain和get_range函数均将返回GST_FLOW_FLUSHING;
  • 元件的所有衬垫将被停用以便流变为不可用状态并停止流的相关线程;
  • 接收(sink)衬垫将移除所有之前协商好的格式(formats);
  • 元件将移除所有的不定衬垫(sometimes pad)。

F. GST_STATE_CHANGE_READY_TO_NULL:状态从READ变为NULL

  • 元件关闭其包含的所有设备;
  • 元件重置其所有内部状态。

三、GstElement状态设置API的实现原理

3.1 涉及的函数与成员变量

GstElement的API中提供了一个名为gst_element_set_state接口来实现元件的状态设置功能,其定义为:

GstStateChangeReturn gst_element_set_state (GstElement * element, GstState state)
{
	GstElementClass *oclass;
	GstStateChangeReturn result = GST_STATE_CHANGE_FAILURE;
	
	g_return_val_if_fail (GST_IS_ELEMENT (element), GST_STATE_CHANGE_FAILURE);
	
	oclass = GST_ELEMENT_GET_CLASS (element);
	
	if (oclass->set_state)
	  result = (oclass->set_state) (element, state);
	
	return result;
}

在其函数注释当中有一条特别的(完整注释请看源代码或参考文档):

 /**
  * .....
  * State changes to %GST_STATE_READY or %GST_STATE_NULL never return
  * #GST_STATE_CHANGE_ASYNC.
  */

大概意思就是,在将状态设置为READY或NULL时不会异步完成,而是会同步完成,在具体应用中可能需要注意这一点。

从函数的定义(实现)可以看出,这个接口其实只是起到封装作用,实际调用的是GstElementClass中的set_state成员方法,这也是一个“虚函数”,子类可以重实现这个接口,其默认实现为GstElement中的gst_element_set_state_func函数,定原型如下:

static GstStateChangeReturn gst_element_set_state_func (GstElement * element, GstState state)

因为此函数的代码较多,就不完全贴出来了,可以到gstreamer源代码中去查看。

在说gst_element_set_state_func函数的具体实现之前,有必要先说明一下GstElement结构体中关于状态信息的几个变量:

  • target_state: 应用程序(Element的使用者)向元件设置的目标状态;

  • current_state: 元件的当前状态;

  • next_state: 元件的下一个状态,如果元件已经处于正确的状态了,这个成员可以被设置为GST_STATE_VOID_PENDING

  • pending_state: 元件将要到达的最终状态,如果元件已经处于正确的状态了,这个成员可以被设置为GST_STATE_VOID_PENDING

在这里可能会比较疑惑,target_state与next_state以及pending_state有什么区别或关联,要区分它们,就先要了解gsteamer中状态设置的特点。

3.2 状态转换规则与特点

在一般的状态机里,定义好状态转换规则之后,如果设置下来的目标状态是无法到达的状态,那么就会返回失败,例如:

已经定义好了状态 A,B,C,D的转换规则了:
在这里插入图片描述

假设当前状态为A,如果上层这个时候想将状态设置为C,对于一般的状态机来说,这是不允许的是会出错的,因为状态A只能转换到状态B。

这一点在gstreamer中的状态机就不会返回错误,而是在可能的情况下一步一步转换到目标状态,还是以上面的例子为例,在转换之前:

注: -1 表示无效状态

  • current_state = A
  • target_state = A
  • pending_state = -1
  • next_state = -1

然后转换过程为:

  • 1、 将target_state = C, pending_state = C
  • 2、计算出next_state,其计算规则为:
if (target_state > current_state)
{
    next_state = current_state + 1
}
else if (target_state < current_state)
{
    next_state = current_state - 1;
}
else
{
    next_state = current_state;
}
  • 3、将状态转换到next_state,其结果为current_state = next_state (这里应该就是状态B)

  • 4、判断 current_state是否等于 pending_state? 如果不等,回到第二步,再次走一遍步骤2,3,4,如果相等,表示完成状态切换需求,将pending_state与next_state重新置为-1。

3.3 函数实现

首先是gst_element_set_state_func函数(只贴出主要代码)

static GstStateChangeReturn
gst_element_set_state_func (GstElement * element, GstState state)
{
	//首先判断传入的是否是一个Element的handle
	g_return_val_if_fail (GST_IS_ELEMENT (element), GST_STATE_CHANGE_FAILURE);
	
	//为整个状态变换过程加锁
	GST_STATE_LOCK (element);
	//为状态计算加锁
	GST_OBJECT_LOCK (element);
	
	//获取上一次状态变换的返回值,如果失败了,就会清空所有pending和next 状态
	//也就是说,结束本次状态变换。
	old_ret = GST_STATE_RETURN (element);
	
	//获取当前,下一个以及pending的状态
	current = GST_STATE (element);
	next = GST_STATE_NEXT (element);
	old_pending = GST_STATE_PENDING (element);
	
	
	//state是需要转换过去的新状态,TARGET是上一次转换到的状态
	if (state != GST_STATE_TARGET (element)) {
		GST_STATE_TARGET (element) = state; //设置新的目标状态
		/* increment state cookie so that we can track each state change. We only do
		* this if this is actually a new state change. */
		element->state_cookie++;
	}
	
	GST_STATE_PENDING (element) = state; //设置新的等待状态
	
	/* if the element was busy doing a state change, we just update the
	* target state, it'll get to it async then. */
	if (old_pending != GST_STATE_VOID_PENDING) {
		/* upwards state change will happen ASYNC */
		if (old_pending <= state)
			goto was_busy;
		/* element is going to this state already */
		else if (next == state)
			goto was_busy;
		/* element was performing an ASYNC upward state change and
		* we request to go downward again. Start from the next pending
		* state then. */
		else if (next > state && GST_STATE_RETURN (element) == GST_STATE_CHANGE_ASYNC) {
			current = next;
		}
	}
	
	next = GST_STATE_GET_NEXT (current, state); //获取下一个状态
	/* now we store the next state */
	GST_STATE_NEXT (element) = next;
	
	//如果当前状态和下一个状态不同,那么就打上一个标记,表示元件正在一步的变换状态
	if (current != next)
		GST_STATE_RETURN (element) = GST_STATE_CHANGE_ASYNC;
	
	//得到下一个状态变化的命令或信号,就是GstStateChange定义的内容
	transition = (GstStateChange) GST_STATE_TRANSITION (current, next);
	
	/* now signal any waiters, they will error since the cookie was incremented */
	GST_STATE_BROADCAST (element);
	
	GST_OBJECT_UNLOCK (element);
	
	//真正的转换状态,内部会去调用元件的虚函数change_state来执行元件状态
	//变化具体要做的事情(如:初始化元件调用的第三方库等),并根据 change_state
	//函数的返回值决定接下来的行为
	ret = gst_element_change_state (element, transition);
	
	GST_STATE_UNLOCK (element);
	
}

接下来就是上面函数中调用到的gst_element_change_state函数:

GstStateChangeReturn
gst_element_change_state (GstElement * element, GstStateChange transition)
{
	/* call the state change function so it can set the state */
	if (oclass->change_state)
		ret = (oclass->change_state) (element, transition);
	else
		ret = GST_STATE_CHANGE_FAILURE;
	
	switch (ret) {
	case GST_STATE_CHANGE_FAILURE: //状态转换失败
		/* state change failure */
		gst_element_abort_state (element);
	    break;
	case GST_STATE_CHANGE_ASYNC:
	{
		target = GST_STATE_TARGET (element);  //获取需要变换到的最终目标状态
	
		//因为 gstreamer中规定变换到READY或NULL状态不会异步进行,所以这里需要判断
		//如果目标状态是在READY之上,那么就返回,让元件去异步变换状态。
	    if (target > GST_STATE_READY) 
			goto async;
	
		//如果目标状态是READY或NULL,那么就会调用gst_element_continue_state继续
		//进行状态变换,这个函数中会再次去计算下一个状态是什么,最终得到     
		// GstStateChange中定义的相应的枚举变量,然后再嵌套调用
		// gst_element_change_state再次让元件去做状态变换
		ret = gst_element_continue_state (element, GST_STATE_CHANGE_SUCCESS);
	}
		break;
	case GST_STATE_CHANGE_SUCCESS://状态变换成功,继续进行下一次状态变换
		ret = gst_element_continue_state (element, ret);
		break;
	case GST_STATE_CHANGE_NO_PREROLL:
		/*the state change succeeded but the element
	     * cannot produce data in %GST_STATE_PAUSED.
	     * This typically happens with live sources. */
		//上面这段是定义此返回值的注释,大概就是,状态变换成功了,但是元件还不能产生
		//数据, 而这个状态就是PAUSED,一般在播放直播源的时候会出现这个返回值。       
		ret = gst_element_continue_state (element, ret);
		break;
	default:
		goto invalid_return;
	}
async:
	return ret;
	/* ERROR */
invalid_return:
	/* we are in error now */
	ret = GST_STATE_CHANGE_FAILURE;
	GST_STATE_RETURN (element) = ret;
	GST_OBJECT_UNLOCK (element);
	
	return ret;
}

好的,我会尽力回答你的问题。关于通过UDP传输音视频,我了解一些相关的知识,下面是一些学习笔记: 1. gstreamer是一个流媒体框架,用于创建、处理和播放多媒体流。它支持多种音视频格式,可以通过插件扩展功能。 2. 通过gstreamer可以使用UDP协议传输音视频数据。UDP协议是一种无连接的协议,不保证数据传输的可靠性和顺序性,但是传输效率高。 3. 首先需要创建一个gstreamer的pipeline,包括音视频源、编码器、UDP发送端等组件。例如: ``` gst-launch-1.0 -v filesrc location=test.mp4 ! decodebin ! x264enc ! rtph264pay ! udpsink host=192.168.1.100 port=5000 ``` 这个pipeline的作用是从test.mp4文件读取音视频流,解码后使用x264编码器进行压缩,然后使用rtph264pay将数据打包成RTP数据包,最后通过udpsink发送到指定的IP地址和端口。 4. 接收端需要创建一个gstreamer的pipeline,包括UDP接收端、解包器、解码器等组件。例如: ``` gst-launch-1.0 -v udpsrc port=5000 ! application/x-rtp, payload=96 ! rtpjitterbuffer ! rtph264depay ! avdec_h264 ! autovideosink ``` 这个pipeline的作用是从UDP端口5000接收音视频数据,使用rtpjitterbuffer解决网络抖动问题,使用rtph264depay将RTP数据包解包成原始的H.264数据流,然后使用avdec_h264解码器进行解码,最后使用autovideosink播放视频。 5. 在实际使用过程中,还需要考虑数据的带宽限制、网络延迟等问题,以保证音视频传输的效果。 希望这些笔记能对你有帮助。如果你还有其他问题,可以继续问我。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值