通过ndk-gdb跟踪调试vlc-android来分析从连接到RTSP服务器并接收到音视频数据包后的处理过程。
首先,从前面的文章有分析过vlc-android的处理过程通过线程函数Run()(Src/input/input.c)来处理的,代码如下:
static void *Run( void *obj ) { input_thread_t *p_input = (input_thread_t *)obj; const int canc = vlc_savecancel(); if( Init( p_input ) ) goto exit; MainLoop( p_input, true ); /* FIXME it can be wrong (like with VLM) */ /* Clean up */ End( p_input ); exit: /* Tell we're dead */ vlc_mutex_lock( &p_input->p->lock_control ); const bool b_abort = p_input->p->b_abort; vlc_mutex_unlock( &p_input->p->lock_control ); if( b_abort ) input_SendEventAbort( p_input ); input_SendEventDead( p_input ); vlc_restorecancel( canc ); return NULL; }
而Init()函数中关于live555连接服务器的处理在前面的一篇文章中已经初略分析,这里我们主要从MainLoop()函数入手,由于代码量过大(后续分篇补上),暂时仅分析关于通过live555接收数据包到发出信号通知解码器可以进行解码的过程。
下面为MainLoop()函数中的一段代码
if( !b_paused ) { if( !p_input->p->input.b_eof ) { MainLoopDemux( p_input, &b_force_update, &b_demux_polled, i_start_mdate ); i_wakeup = es_out_GetWakeup( p_input->p->p_es_out ); } else if( !es_out_GetEmpty( p_input->p->p_es_out ) ) { msg_Dbg( p_input, "waiting decoder fifos to empty" ); i_wakeup = mdate() + INPUT_IDLE_SLEEP; } ... ... ...
从上面代码可知将会调用MainLoopDemux(),下面截取该函数中的一段代码
if( ( p_input->p->i_stop > 0 && p_input->p->i_time >= p_input->p->i_stop ) || ( p_input->p->i_run > 0 && i_start_mdate+p_input->p->i_run < mdate() ) ) i_ret = 0; /* EOF */ else i_ret = demux_Demux( p_input->p->input.p_demux ); ... ... ...
继续调用demux_Demux()(src/input/Demux.h)
static inline int demux_Demux( demux_t *p_demux ) { if( !p_demux->pf_demux ) return 1; return p_demux->pf_demux( p_demux ); }
这里的p_demux->pf_demux()所指向的函数是通过live555.cpp中的open()函数来指定,下面为一段open()函数的截取代码:
... ... p_demux->pf_demux = Demux; p_demux->pf_control= Control; ... ...
即指向live555.cpp文件中的Demux()函数,从上面的分析可知最终调用了这里的Demux()函数,这里截取该函数的一段代码如下:
... ... /* First warn we want to read data */ p_sys->event_data = 0; for( i = 0; i < p_sys->i_track; i++ ) { live_track_t *tk = p_sys->track[i]; if( tk->waiting == 0 ) { tk->waiting = 1; tk->sub->readSource()->getNextFrame( tk->p_buffer, tk->i_buffer, StreamRead, tk, StreamClose, tk ); } } ... ...
通过前文的分析,我们知道这里的tk->p_buffer即为我们需要的待解码数据,而对于该数据的处理我们继续跟踪,由前文分析,接下来将会执行StreamRead()函数,如下:
... ... if( tk->fmt.i_codec == VLC_CODEC_AMR_NB || tk->fmt.i_codec == VLC_CODEC_AMR_WB ) { AMRAudioSource *amrSource = (AMRAudioSource*)tk->sub->readSource(); p_block = block_New( p_demux, i_size + 1 ); p_block->p_buffer[0] = amrSource->lastFrameHeader(); memcpy( p_block->p_buffer + 1, tk->p_buffer, i_size ); } else if( tk->fmt.i_codec == VLC_CODEC_H261 ) { H261VideoRTPSource *h261Source = (H261VideoRTPSource*)tk->sub->rtpSource(); uint32_t header = h261Source->lastSpecialHeader(); p_block = block_New( p_demux, i_size + 4 ); memcpy( p_block->p_buffer, &header, 4 ); memcpy( p_block->p_buffer + 4, tk->p_buffer, i_size ); if( tk->sub->rtpSource()->curPacketMarkerBit() ) p_block->i_flags |= BLOCK_FLAG_END_OF_FRAME; } else if( tk->fmt.i_codec == VLC_CODEC_H264 ) { if( (tk->p_buffer[0] & 0x1f) >= 24 ) msg_Warn( p_demux, "unsupported NAL type for H264" ); /* Normal NAL type */ p_block = block_New( p_demux, i_size + 4 ); p_block->p_buffer[0] = 0x00; p_block->p_buffer[1] = 0x00; p_block->p_buffer[2] = 0x00; p_block->p_buffer[3] = 0x01; memcpy( &p_block->p_buffer[4], tk->p_buffer, i_size ); } else if( tk->b_asf ) { p_block = StreamParseAsf( p_demux, tk, tk->sub->rtpSource()->curPacketMarkerBit(), tk->p_buffer, i_size ); } else { p_block = block_New( p_demux, i_size ); memcpy( p_block->p_buffer, tk->p_buffer, i_size ); } ... ...
从上面的部分代码可知,这里根据不同的格式进行不同的处理,但都会调用memcpy()函数将上面获取的tk->p_buffer数据复制到p_block->p_buffer中去,这样我们就保存下来了通过live555从socket中读取的数据,继续下面的代码:
if( p_block ) { if( !tk->b_muxed && !tk->b_asf ) { if( i_pts != tk->i_pts ) p_block->i_pts = VLC_TS_0 + i_pts; /*FIXME: for h264 you should check that packetization-mode=1 in sdp-file */ p_block->i_dts = ( tk->fmt.i_codec == VLC_CODEC_MPGV ) ? VLC_TS_INVALID : (VLC_TS_0 + i_pts); } if( tk->b_muxed ) stream_DemuxSend( tk->p_out_muxed, p_block ); else if( tk->b_asf ) stream_DemuxSend( p_sys->p_out_asf, p_block ); else es_out_Send( p_demux->out, tk->p_es, p_block ); }
接下来会调用es_out_Send()(vlc/include/Vlc_es_out.h)函数
static inline int es_out_Send( es_out_t *out, es_out_id_t *id, block_t *p_block ) { return out->pf_send( out, id, p_block ); }
通过单步跟踪,这里out->pf_send()所指向的正是src/input/es_out_timeshift.c文件中的Send()函数
static int Send( es_out_t *p_out, es_out_id_t *p_es, block_t *p_block ) { es_out_sys_t *p_sys = p_out->p_sys; ts_cmd_t cmd; int i_ret = VLC_SUCCESS; vlc_mutex_lock( &p_sys->lock ); TsAutoStop( p_out ); CmdInitSend( &cmd, p_es, p_block ); if( p_sys->b_delayed ) TsPushCmd( p_sys->p_ts, &cmd ); else i_ret = CmdExecuteSend( p_sys->p_out, &cmd) ; vlc_mutex_unlock( &p_sys->lock ); return i_ret; }
继续跟踪,先执行CmdInitSend()将p_block数据保存下来到cmd中,接下来将会执行CmdExcuteSend()函数
static int CmdExecuteSend( es_out_t *p_out, ts_cmd_t *p_cmd ) { block_t *p_block = p_cmd->u.send.p_block; p_cmd->u.send.p_block = NULL; if( p_block ) { if( p_cmd->u.send.p_es->p_es ) return es_out_Send( p_out, p_cmd->u.send.p_es->p_es, p_block ); block_Release( p_block ); } return VLC_EGENERIC; }
继续执行es_out_Send()(vlc/include/Vlc_es_out.h)函数
static inline int es_out_Send( es_out_t *out, es_out_id_t *id, block_t *p_block ) { return out->pf_send( out, id, p_block ); }
继续跟踪,这里的out->pf_send()函数指向src/input/es_out.c文件中的EsOutSend()函数,截取该函数的一段代码:
... ... /* Decode */ if( es->p_dec_record ) { block_t *p_dup = block_Duplicate( p_block ); if( p_dup ) input_DecoderDecode( es->p_dec_record, p_dup, p_input->p->b_out_pace_control ); } input_DecoderDecode( es->p_dec, p_block, p_input->p->b_out_pace_control ); ... ...
这里将会调用第二个input_DecoderDecode()(src/input/Decoder.c)函数,如下:
void input_DecoderDecode( decoder_t *p_dec, block_t *p_block, bool b_do_pace ) { decoder_owner_sys_t *p_owner = p_dec->p_owner; if( b_do_pace ) { /* The fifo is not consummed when buffering and so will * deadlock vlc. * There is no need to lock as b_buffering is never modify * inside decoder thread. */ if( !p_owner->b_buffering ) block_FifoPace( p_owner->p_fifo, 10, SIZE_MAX ); } #ifdef __arm__ else if( block_FifoSize( p_owner->p_fifo ) > 50*1024*1024 /* 50 MiB */ ) #else else if( block_FifoSize( p_owner->p_fifo ) > 400*1024*1024 /* 400 MiB, ie ~ 50mb/s for 60s */ ) #endif { /* FIXME: ideally we would check the time amount of data * in the FIFO instead of its size. */ msg_Warn( p_dec, "decoder/packetizer fifo full (data not " "consumed quickly enough), resetting fifo!" ); block_FifoEmpty( p_owner->p_fifo ); } block_FifoPut( p_owner->p_fifo, p_block ); }
最后,调用src/misc/block.c文件中的block_FifoPut()函数,如下:
size_t block_FifoPut( block_fifo_t *p_fifo, block_t *p_block ) { size_t i_size = 0, i_depth = 0; block_t *p_last; if (p_block == NULL) return 0; for (p_last = p_block; ; p_last = p_last->p_next) { i_size += p_last->i_buffer; i_depth++; if (!p_last->p_next) break; } vlc_mutex_lock (&p_fifo->lock); *p_fifo->pp_last = p_block; p_fifo->pp_last = &p_last->p_next; p_fifo->i_depth += i_depth; p_fifo->i_size += i_size; /* We queued at least one block: wake up one read-waiting thread */ vlc_cond_signal( &p_fifo->wait ); vlc_mutex_unlock( &p_fifo->lock ); return i_size; }
这里,即通过调用vlc_cond_signal( &p_fifo->wait )(此为VLC所封装的一个系统的线程条件信号函数),通知正在等待p_fifo->wait条件的函数,而正在等待该条件的函数在得到这个信号后即调用解码函数开始解码。
总结:VLC实际上是一个多线程密集的工程,其中封装了系统的线程函数来实现各个数据包的同步问题。