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

 

你可能感兴趣的:(代码)