您现在的位置是:首页 >其他 >vlc推流过程网站首页其他

vlc推流过程

Code Lyoko 2025-03-26 12:01:02
简介csdn vlc

版本3.0.20

命令./vlc file://path --sout="#transcode{acodec=aac}:http{mux=flv,dst=:8888/}"

入口:

bin/vlc.c,linux端的入口文件,其他端的入口文件也在同一个目录下

playlist线程的启动过程:

- libvlc_new(lib/core.c):新建一个vlc实例,并创建playlist线程

        - Thread(src/playlist/thread.c):playlist线程的入口函数

- libvlc_playlist_play(lib/playlist.c):触发playlist线程开始播放

        - libvlc_InternalPlay(src/interface/interface.c)

                - playlist_Control(src/playlist/control.c):通过控制字PLAYLIST_PLAY触发playlist线程开始播放

        playlist线程启动进入主循环,等待控制字,当playlist_Control函数发送了PLAYLIST_PLAY控制字,p_sys->request.b_request字段会被置为true,并触发p_sys->signal信号,playlist线程进入二重循环,在Next函数中启动一个input线程开始播放,然后LoopInput函数一直等待input线程播放结束

static void *Thread ( void *data )
{
    playlist_t *p_playlist = data;
    playlist_private_t *p_sys = pl_priv(p_playlist);
    bool played = false;

    while( !p_sys->killed )
    {
        ...
        if( !p_sys->request.b_request )
        {
            vlc_cond_wait( &p_sys->signal, &p_sys->lock );
            continue;
        }

        /* Playlist in running state */
        while( !p_sys->killed && Next( p_playlist ) )
        {
            LoopInput( p_playlist );
            ...
        }
        ...
    }
    ...
}

 

input线程的启动过程:

- Next(src/playlist/thread.c)

         - PlayItem(src/playlist/thread.c)

                  - input_Create(src/input/input.c):创建input线程实例

                  - input_Start(src/input/input.c):创建input线程

                           - Run(src/input/input.c):input线程的入口函数

        Run调用MainLoop进入主循环,循环主要做两件事,调用MainLoopDemux函数对输入进行解复用,调用Control函数处理控制字。控制字有个for循环,这里主要是seek等控制字要延迟处理,要先等待buffering完成,再执行控制操作

static void MainLoop( input_thread_t *p_input, bool b_interactive )
{
    ...
    while( !input_Stopped( p_input ) && input_priv(p_input)->i_state != ERROR_S )
    {
        ...
        if( !b_paused )
        {
            if( !input_priv(p_input)->master->b_eof )
            {
                ...
                MainLoopDemux( p_input, &b_force_update );
                ...
            }
        }
        ...

        /* Handle control */
        for( ;; )
        {
            ...
            if( ControlPop( p_input, &i_type, &val, i_deadline, b_postpone ) )
            {
                if( b_postpone )
                    continue;
                break; /* Wake-up time reached */
            }

            if( Control( p_input, i_type, val ) )
            {
                if( ControlIsSeekRequest( i_type ) )
                    i_last_seek_mdate = mdate();
                i_intf_update = 0;
            }
        }
    }
}

 

解复用的过程:

- MainLoopDemux(src/input/input.c)

     - demux_Demux(include/vlc_demux.h):这个函数直接调用解复用器实例的pf_demux函数指针进行解复用,类似c++的多态,虽然都是调用pf_demux,但是具体的逻辑由具体解复用器设置给该指针的函数决定

         - Demux(modules/demux/avformat/demux.c):假设vlc使用ffmpeg进行解复用,那么pf_demux函数指针就指向了avfomat目录中的Demux函数

                  - es_out_Send(include/vlc_es_out.h):这里又是调用了pf_send函数指针,由es_out实例决定具体实现。这里又多了一个es_out的概念,其实这是一个管理类,用来管理解复用得倒的es数据的。es_out实例由input线程实例创建,并且赋给demux实例,两者共同持有,demux实例会根据输入源设置es实例给es_out,每路音频和视频都设置一个es实例,相应的,每个es都实例持用它自身的解码器实例

                  - EsOutSend(src/input/es_out.c):一般情况pf_send函数指针调用的是es_out实例的EsOutSend函数

                           - input_DecoderDecode(src/input/decoder.c):把es数据发给解码器,实际上是送入解码器的fifo队列

解码器线程的启动过程:

- DecoderThread(src/input/decoder.c):前面说了es会持有解码器实例,在解码器被创建时就会启动解码器线程

         - DecoderProcess(src/input/decoder.c):从fifo中取出一个block_t,进行解码

                  - DecoderProcessSout(src/input/decoder.c):因为是sout推流,所以调用Sout版本的解码函数

                           - DecoderPlaySout(src/input/decoder.c)

                                - sout_InputSendBuffer(src/stream_output/stream_output.c):层层调用,最后又调用到pf_send函数指针,这里的指针是sout_stream实例的指针,注意跟上面解复用的区别开

                               - Send(modules/stream_out/transcode/transcode.c):我们对音频进行了转码,所以pf_send函数指针指向的是转码模块的Send函数(具体是怎么构建sout_stream对象链的没有去深究)

sout的过程:

- Send(modules/stream_out/transcode/transcode.c):如果需要转码,音视频字幕都会在这个函数里完成转码,否则就直接输出到下一个sout_stream(同样调用的是pf_send函数指针)

- Send(modules/stream_out/standard.c):实际上调用到标准sout_stream的Send函数,这个sout_stream实例持有复用器实例,并且会创建access实例赋给复用器

         - sout_MuxSendBuffer(src/stream_output/stream_output.c):把数据输入复用器,实际上也是放到fifo中,并调用复用器的pf_mux函数指针进行具体的操作

            - Mux(modules/demux/avformat/mux.c):我们是封装成flv格式,所以vlc使用的是ffmpeg模块的Mux函数

                 - sout_AccessOutWrite(src/stream_output/stream_output.c):完成封装后,这里又是调用access实例pf_write函数指针进行输出,也就是上面所说的标准sout_stream创建复用器时所创建的access实例

                 - Write(modules/access_output/http.c):我们是通过http输出,所以pf_write函数指针实际上就是http模块的Write函数,该函数实际上调用了httpd模块的能力,实现网络层的收发

大致的数据流就是:

        Input->demux->decode->sout_transcode->standard_sout_stream->mux->http

        整个过程做了非常多的复杂的处理,像时间戳修正,音视频同步,音视频检索等,所以bug也挺多,有些视频文件总会出现音视频不同步,没声音等问题

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。