您现在的位置是:首页 >其他 >vlc推流过程网站首页其他
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也挺多,有些视频文件总会出现音视频不同步,没声音等问题