GstBin和sink组件的状态转换代码分析和相关逻辑

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状态的时候就完成。 

 

 #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;
}

 

posted @ 2011-01-03 11:08  super119  阅读(1819)  评论(0编辑  收藏  举报