1. 每个element/bin都有current,next,pending三个成员变量表示状态。current和next很好理解,pending一般就是我们给该element设置的最终的状态,比如调用gst_element_set_state函数设置PLAYING state,则这个pending一般就是PLAYING。代码中还看到有一个宏GST_STATE_TARGET,这个TARGET一般也和pending一样,表示最终要设置的state(final state)
2. gst_element_set_state_func函数(gst_element_set_state的缺省实现)会调用gst_element_change_state,change_state又会调用gst_element_continue_state。对于这里state的转变,关键问题在sink element和bin上。对于sink element来说,只要到达PAUSED状态,就需要preroll,同时返回ASYNC,此时sink的状态不会commit,就是说还是READY和PLAYING状态,next state是PAUSED,pending状态是我们给他设置的最终状态。而且_set_state/change_state在看到返回了ASYNC之后,就直接返回了,也就是说,set_state函数调用到PAUSED之后就结束了。下面的工作就是gstbin完成的。这就又牵扯到gstbasesink.c和gstbin.c了。
basesink中,在状态变成PAUSED的时候,会发出ASYNC_START的消息,这个消息会被gstbin捕获(一般element都需要加到bin中,或是加到pipeline中)并记录,而且如果这个bin不是toplevel的话(所谓toplevel指的是这个bin的parent是NULL),还会将ASYNC_START消息继续往上发送,如果是toplevel就不会发了。此外,bin会修改自己的state, next, pending这些变量,注意只是修改这些变量,并不会去调用每个element的change_state函数。设置这些变量显示bin当前是PAUSED状态。
OK,basesink中,如果preroll完成(第一个非event的buffer到达,由函数gst_base_sink_preroll_object完成),就会调用gst_base_sink_commit_state函数,这个函数会直接将该sink的state设置成最终的那个state,也就是保存在pending中的state,同时将next和pending state都设成VOID,这表示sink现在preroll完成了,对于状态转换已经没有其他特别的东西了,然后发送ASYNC_DONE消息。gstbin在收到ASYNC_DONE消息后,首先查看是不是bin中所有发送过ASYNC_START的element都已经发送了DONE了,如果是,则调用关键函数bin_handle_async_done,这个函数会将bin的状态修改过来,然后会向gThreadPool中push一个task,这个task就会负责去调用bin的change_state函数,这个函数就会去调用每个element的change_state函数修改状态了。有关gthreadpool请参考glib中的内容。
所以,综合上面来看,在我们对一个sink element设置PLAYING状态时,最终是由bin来负责PAUSED->PLAYING的,因为sink element有一个preroll的过程,sink通过ASYNC_START和ASYNC_DONE来和bin进行通信。
为了验证上面的一点,我写了一个程序,这个程序在timeout callback函数中创建了一个fakesrc和一个udpsink,link他们,但是不把他们加入pipeline,最后设置他们的状态为PLAYING。同时,我在gstpipeline.c的change_state函数和gstbasesink.c的change_state函数中,在PAUSED->PLAYING状态时加入了一条打印语句,来监视这些代码是否会被触发。结果是,这两个element的状态变成了PLAYING,但是pipeline的change_state函数和basesink的change_state函数都没有被触发,这说明preroll完成后,只是修改了element的state的三个变量,真正的change_state函数需要由bin来负责调用。一旦将他们加入pipeline,就能发现change_state函数被调用了(指的是change_state中PAUSED->PLAYING部分的代码)。附件1中是测试的代码。
最后说一下,这次研究上面这些东西,是因为发现我加在rtspsrc中的代码会被反复调用。我在gst_rtspsrc_play中加入了一段代码,这段代码每次会创建四个element,2个fakesrc和2个udpsink。gst_rtspsrc_play会在rtspsrc由PAUSED转向PLAYING时被调用。结果发现我的代码被执行了很多次,导致创建出来了一大堆的element。原因就是上面的原理:
我每次创建的2个udpsink,设置成PLAYING后,发送ASYNC_START, ASYNC_DONE的消息,rtspsrc作为bin收到这些消息,在DONE消息处理的时候,会去调用change_state的PAUSED->PLAYING部分的代码,一调用这部分代码,我的代码又被执行,如此循环,导致创建出了很多的element。所以,关键就是:
a. 给创建的udpsink设置async属性为FALSE,这样就取消了preroll,就不会有ASYNC_START, ASYNC_DONE消息产生了。
或者
b. 检测是否已经创建了element,不要每次都创建element,如果已创建过,则无需再创建。
其他更严谨一些,创建element,尤其是sink element,根本就不应该放在转向PLAYING状态的时候,而是应该在PAUSED状态的时候就完成。
2. gst_element_set_state_func函数(gst_element_set_state的缺省实现)会调用gst_element_change_state,change_state又会调用gst_element_continue_state。对于这里state的转变,关键问题在sink element和bin上。对于sink element来说,只要到达PAUSED状态,就需要preroll,同时返回ASYNC,此时sink的状态不会commit,就是说还是READY和PLAYING状态,next state是PAUSED,pending状态是我们给他设置的最终状态。而且_set_state/change_state在看到返回了ASYNC之后,就直接返回了,也就是说,set_state函数调用到PAUSED之后就结束了。下面的工作就是gstbin完成的。这就又牵扯到gstbasesink.c和gstbin.c了。
basesink中,在状态变成PAUSED的时候,会发出ASYNC_START的消息,这个消息会被gstbin捕获(一般element都需要加到bin中,或是加到pipeline中)并记录,而且如果这个bin不是toplevel的话(所谓toplevel指的是这个bin的parent是NULL),还会将ASYNC_START消息继续往上发送,如果是toplevel就不会发了。此外,bin会修改自己的state, next, pending这些变量,注意只是修改这些变量,并不会去调用每个element的change_state函数。设置这些变量显示bin当前是PAUSED状态。
OK,basesink中,如果preroll完成(第一个非event的buffer到达,由函数gst_base_sink_preroll_object完成),就会调用gst_base_sink_commit_state函数,这个函数会直接将该sink的state设置成最终的那个state,也就是保存在pending中的state,同时将next和pending state都设成VOID,这表示sink现在preroll完成了,对于状态转换已经没有其他特别的东西了,然后发送ASYNC_DONE消息。gstbin在收到ASYNC_DONE消息后,首先查看是不是bin中所有发送过ASYNC_START的element都已经发送了DONE了,如果是,则调用关键函数bin_handle_async_done,这个函数会将bin的状态修改过来,然后会向gThreadPool中push一个task,这个task就会负责去调用bin的change_state函数,这个函数就会去调用每个element的change_state函数修改状态了。有关gthreadpool请参考glib中的内容。
所以,综合上面来看,在我们对一个sink element设置PLAYING状态时,最终是由bin来负责PAUSED->PLAYING的,因为sink element有一个preroll的过程,sink通过ASYNC_START和ASYNC_DONE来和bin进行通信。
为了验证上面的一点,我写了一个程序,这个程序在timeout callback函数中创建了一个fakesrc和一个udpsink,link他们,但是不把他们加入pipeline,最后设置他们的状态为PLAYING。同时,我在gstpipeline.c的change_state函数和gstbasesink.c的change_state函数中,在PAUSED->PLAYING状态时加入了一条打印语句,来监视这些代码是否会被触发。结果是,这两个element的状态变成了PLAYING,但是pipeline的change_state函数和basesink的change_state函数都没有被触发,这说明preroll完成后,只是修改了element的state的三个变量,真正的change_state函数需要由bin来负责调用。一旦将他们加入pipeline,就能发现change_state函数被调用了(指的是change_state中PAUSED->PLAYING部分的代码)。附件1中是测试的代码。
最后说一下,这次研究上面这些东西,是因为发现我加在rtspsrc中的代码会被反复调用。我在gst_rtspsrc_play中加入了一段代码,这段代码每次会创建四个element,2个fakesrc和2个udpsink。gst_rtspsrc_play会在rtspsrc由PAUSED转向PLAYING时被调用。结果发现我的代码被执行了很多次,导致创建出来了一大堆的element。原因就是上面的原理:
我每次创建的2个udpsink,设置成PLAYING后,发送ASYNC_START, ASYNC_DONE的消息,rtspsrc作为bin收到这些消息,在DONE消息处理的时候,会去调用change_state的PAUSED->PLAYING部分的代码,一调用这部分代码,我的代码又被执行,如此循环,导致创建出了很多的element。所以,关键就是:
a. 给创建的udpsink设置async属性为FALSE,这样就取消了preroll,就不会有ASYNC_START, ASYNC_DONE消息产生了。
或者
b. 检测是否已经创建了element,不要每次都创建element,如果已创建过,则无需再创建。
其他更严谨一些,创建element,尤其是sink element,根本就不应该放在转向PLAYING状态的时候,而是应该在PAUSED状态的时候就完成。
#include <gst/gst.h>
GstElement *pipeline, *fakesrc1, *udpsink1, *fakesrc2, *udpsink2;
static gboolean on_timeout(gpointer data)
{
g_message("ENTERING TIMEOUT PART START...");
fakesrc2 = gst_element_factory_make("fakesrc", NULL);
g_object_set (G_OBJECT(fakesrc2), "filltype", 3, NULL);
g_object_set (G_OBJECT(fakesrc2), "num-buffers", 5, NULL);
gchar *rtpuri = g_strdup_printf ("udp://202.119.24.12:5000");
udpsink2 = gst_element_make_from_uri (GST_URI_SINK, rtpuri, NULL);
g_free (rtpuri);
// Add and link
// gst_bin_add_many(GST_BIN(pipeline), fakesrc2, udpsink2, NULL);
gst_element_link(fakesrc2, udpsink2);
gst_element_set_state(fakesrc2, GST_STATE_PLAYING);
gst_element_set_state(udpsink2, GST_STATE_PLAYING);
GstState cur, pending;
gst_element_get_state(fakesrc2, &cur, &pending, GST_CLOCK_TIME_NONE);
g_message("fakesrc2 cur state is %d, pending state is %d", cur, pending);
gst_element_get_state(udpsink2, &cur, &pending, GST_CLOCK_TIME_NONE);
g_message("udpsink2 cur state is %d, pending state is %d", cur, pending);
g_message("ENTERING TIMEOUT PART END...");
return FALSE;
}
static gboolean bus_call(GstBus *bus, GstMessage *msg, gpointer data)
{
GMainLoop *loop = (GMainLoop *)data;
switch(GST_MESSAGE_TYPE(msg)) {
case GST_MESSAGE_EOS:
g_print("End-of-stream\n");
g_print("The message's owner is: %s\n", GST_OBJECT_NAME(GST_MESSAGE_SRC(msg)));
// g_main_loop_quit(loop);
break;
case GST_MESSAGE_ERROR: {
gchar *debug;
GError *err;
gst_message_parse_error(msg, &err, &debug);
g_print("Error debug info: %s\n", debug);
g_free(debug);
g_print("Error: %s\n", err->message);
g_error_free(err);
g_main_loop_quit(loop);
break;
}
case GST_MESSAGE_NEW_CLOCK: {
GstClock *newclock;
gst_message_parse_new_clock(msg, &newclock);
g_message("Got new clock, clock name is %s", GST_OBJECT_NAME(GST_OBJECT(newclock)));
break;
}
case GST_MESSAGE_ASYNC_START: {
g_message("Got ASYNC_START message.");
break;
}
case GST_MESSAGE_ASYNC_DONE: {
g_message("Got ASYNC_DONE message.");
break;
}
default:
break;
}
return TRUE;
}
int main(int argc, char *argv[])
{
GMainLoop *loop;
GstBus *bus;
gst_init(&argc, &argv);
loop = g_main_loop_new(NULL, FALSE);
// create elements
pipeline = gst_element_factory_make("pipeline", "pipeline");
fakesrc1 = gst_element_factory_make("fakesrc", NULL);
g_object_set (G_OBJECT(fakesrc1), "filltype", 3, NULL);
g_object_set (G_OBJECT(fakesrc1), "num-buffers", 5, NULL);
gchar *rtpuri = g_strdup_printf ("udp://202.119.24.12:5000");
udpsink1 = gst_element_make_from_uri (GST_URI_SINK, rtpuri, NULL);
g_free (rtpuri);
if (!pipeline || !fakesrc1 || !udpsink1) {
g_print("Elements could not be created!\n");
return -1;
}
bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline));
gst_bus_add_watch(bus, bus_call, loop);
gst_object_unref(bus);
// Add and link
gst_bin_add_many(GST_BIN(pipeline), fakesrc1, udpsink1, NULL);
gst_element_link(fakesrc1, udpsink1);
// now play
g_print("Setting to PLAYING\n");
gst_element_set_state(pipeline, GST_STATE_PLAYING);
g_print("Running\n");
// add timeout callback
g_timeout_add(4000, (GSourceFunc)on_timeout, NULL);
g_main_loop_run(loop);
// clean up
g_print("Returned, stopping playback\n");
gst_element_set_state(pipeline, GST_STATE_NULL);
g_print("Deleting pipeline\n");
gst_object_unref(GST_OBJECT(pipeline));
return 0;
}