通过ndk-gdb跟踪调试vlc-android来分析从连接到RTSP服务器并接收到音视频数据包后的处理过程。

首先,从前面的文章有分析过vlc-android的处理过程通过线程函数Run()(Src/input/input.c)来处理的,代码如下:

  1. static void *Run( void *obj )
  2. {
  3. input_thread_t *p_input = (input_thread_t *)obj;
  4. const int canc = vlc_savecancel();
  5. if( Init( p_input ) )
  6. goto exit;
  7. MainLoop( p_input, true ); /* FIXME it can be wrong (like with VLM) */
  8. /* Clean up */
  9. End( p_input );
  10. exit:
  11. /* Tell we're dead */
  12. vlc_mutex_lock( &p_input->p->lock_control );
  13. const bool b_abort = p_input->p->b_abort;
  14. vlc_mutex_unlock( &p_input->p->lock_control );
  15. if( b_abort )
  16. input_SendEventAbort( p_input );
  17. input_SendEventDead( p_input );
  18. vlc_restorecancel( canc );
  19. return NULL;
  20. }

而Init()函数中关于live555连接服务器的处理在前面的一篇文章中已经初略分析,这里我们主要从MainLoop()函数入手,由于代码量过大(后续分篇补上),暂时仅分析关于通过live555接收数据包到发出信号通知解码器可以进行解码的过程。

下面为MainLoop()函数中的一段代码

  1. if( !b_paused )
  2. {
  3. if( !p_input->p->input.b_eof )
  4. {
  5. MainLoopDemux( p_input, &b_force_update, &b_demux_polled, i_start_mdate );
  6. i_wakeup = es_out_GetWakeup( p_input->p->p_es_out );
  7. }
  8. else if( !es_out_GetEmpty( p_input->p->p_es_out ) )
  9. {
  10. msg_Dbg( p_input, "waiting decoder fifos to empty" );
  11. i_wakeup = mdate() + INPUT_IDLE_SLEEP;
  12. }
  13. ...
  14. ...
  15. ...

从上面代码可知将会调用MainLoopDemux(),下面截取该函数中的一段代码

  1. if( ( p_input->p->i_stop > 0 && p_input->p->i_time >= p_input->p->i_stop ) ||
  2. ( p_input->p->i_run > 0 && i_start_mdate+p_input->p->i_run < mdate() ) )
  3. i_ret = 0; /* EOF */
  4. else
  5. i_ret = demux_Demux( p_input->p->input.p_demux );
  6. ...
  7. ...
  8. ...

继续调用demux_Demux()(src/input/Demux.h)

  1. static inline int demux_Demux( demux_t *p_demux )
  2. {
  3. if( !p_demux->pf_demux )
  4. return 1;
  5. return p_demux->pf_demux( p_demux );
  6. }

这里的p_demux->pf_demux()所指向的函数是通过live555.cpp中的open()函数来指定,下面为一段open()函数的截取代码:

  1. ...
  2. ...
  3. p_demux->pf_demux  = Demux;
  4. p_demux->pf_control= Control;
  5. ...
  6. ...

即指向live555.cpp文件中的Demux()函数,从上面的分析可知最终调用了这里的Demux()函数,这里截取该函数的一段代码如下:

  1. ...
  2. ...
  3. /* First warn we want to read data */
  4. p_sys->event_data = 0;
  5. for( i = 0; i < p_sys->i_track; i++ )
  6. {
  7. live_track_t *tk = p_sys->track[i];
  8. if( tk->waiting == 0 )
  9. {
  10. tk->waiting = 1;
  11. tk->sub->readSource()->getNextFrame( tk->p_buffer, tk->i_buffer,
  12. StreamRead, tk, StreamClose, tk );
  13. }
  14. }
  15. ...
  16. ...

通过前文的分析,我们知道这里的tk->p_buffer即为我们需要的待解码数据,而对于该数据的处理我们继续跟踪,由前文分析,接下来将会执行StreamRead()函数,如下:

  1. ...
  2. ...
  3. if( tk->fmt.i_codec == VLC_CODEC_AMR_NB ||
  4. tk->fmt.i_codec == VLC_CODEC_AMR_WB )
  5. {
  6. AMRAudioSource *amrSource = (AMRAudioSource*)tk->sub->readSource();
  7. p_block = block_New( p_demux, i_size + 1 );
  8. p_block->p_buffer[0] = amrSource->lastFrameHeader();
  9. memcpy( p_block->p_buffer + 1, tk->p_buffer, i_size );
  10. }
  11. else if( tk->fmt.i_codec == VLC_CODEC_H261 )
  12. {
  13. H261VideoRTPSource *h261Source = (H261VideoRTPSource*)tk->sub->rtpSource();
  14. uint32_t header = h261Source->lastSpecialHeader();
  15. p_block = block_New( p_demux, i_size + 4 );
  16. memcpy( p_block->p_buffer, &header, 4 );
  17. memcpy( p_block->p_buffer + 4, tk->p_buffer, i_size );
  18. if( tk->sub->rtpSource()->curPacketMarkerBit() )
  19. p_block->i_flags |= BLOCK_FLAG_END_OF_FRAME;
  20. }
  21. else if( tk->fmt.i_codec == VLC_CODEC_H264 )
  22. {
  23. if( (tk->p_buffer[0] & 0x1f) >= 24 )
  24. msg_Warn( p_demux, "unsupported NAL type for H264" );
  25. /* Normal NAL type */
  26. p_block = block_New( p_demux, i_size + 4 );
  27. p_block->p_buffer[0] = 0x00;
  28. p_block->p_buffer[1] = 0x00;
  29. p_block->p_buffer[2] = 0x00;
  30. p_block->p_buffer[3] = 0x01;
  31. memcpy( &p_block->p_buffer[4], tk->p_buffer, i_size );
  32. }
  33. else if( tk->b_asf )
  34. {
  35. p_block = StreamParseAsf( p_demux, tk,
  36. tk->sub->rtpSource()->curPacketMarkerBit(),
  37. tk->p_buffer, i_size );
  38. }
  39. else
  40. {
  41. p_block = block_New( p_demux, i_size );
  42. memcpy( p_block->p_buffer, tk->p_buffer, i_size );
  43. }
  44. ...
  45. ...

从上面的部分代码可知,这里根据不同的格式进行不同的处理,但都会调用memcpy()函数将上面获取的tk->p_buffer数据复制到p_block->p_buffer中去,这样我们就保存下来了通过live555从socket中读取的数据,继续下面的代码:

  1. if( p_block )
  2. {
  3. if( !tk->b_muxed && !tk->b_asf )
  4. {
  5. if( i_pts != tk->i_pts )
  6. p_block->i_pts = VLC_TS_0 + i_pts;
  7. /*FIXME: for h264 you should check that packetization-mode=1 in sdp-file */
  8. p_block->i_dts = ( tk->fmt.i_codec == VLC_CODEC_MPGV ) ? VLC_TS_INVALID : (VLC_TS_0 + i_pts);
  9. }
  10. if( tk->b_muxed )
  11. stream_DemuxSend( tk->p_out_muxed, p_block );
  12. else if( tk->b_asf )
  13. stream_DemuxSend( p_sys->p_out_asf, p_block );
  14. else
  15. es_out_Send( p_demux->out, tk->p_es, p_block );
  16. }

接下来会调用es_out_Send()(vlc/include/Vlc_es_out.h)函数

  1. static inline int es_out_Send( es_out_t *out, es_out_id_t *id,
  2. block_t *p_block )
  3. {
  4. return out->pf_send( out, id, p_block );
  5. }

通过单步跟踪,这里out->pf_send()所指向的正是src/input/es_out_timeshift.c文件中的Send()函数

  1. static int Send( es_out_t *p_out, es_out_id_t *p_es, block_t *p_block )
  2. {
  3. es_out_sys_t *p_sys = p_out->p_sys;
  4. ts_cmd_t cmd;
  5. int i_ret = VLC_SUCCESS;
  6. vlc_mutex_lock( &p_sys->lock );
  7. TsAutoStop( p_out );
  8. CmdInitSend( &cmd, p_es, p_block );
  9. if( p_sys->b_delayed )
  10. TsPushCmd( p_sys->p_ts, &cmd );
  11. else
  12. i_ret = CmdExecuteSend( p_sys->p_out, &cmd) ;
  13. vlc_mutex_unlock( &p_sys->lock );
  14. return i_ret;
  15. }

继续跟踪,先执行CmdInitSend()将p_block数据保存下来到cmd中,接下来将会执行CmdExcuteSend()函数

  1. static int CmdExecuteSend( es_out_t *p_out, ts_cmd_t *p_cmd )
  2. {
  3. block_t *p_block = p_cmd->u.send.p_block;
  4. p_cmd->u.send.p_block = NULL;
  5. if( p_block )
  6. {
  7. if( p_cmd->u.send.p_es->p_es )
  8. return es_out_Send( p_out, p_cmd->u.send.p_es->p_es, p_block );
  9. block_Release( p_block );
  10. }
  11. return VLC_EGENERIC;
  12. }

继续执行es_out_Send()(vlc/include/Vlc_es_out.h)函数

  1. static inline int es_out_Send( es_out_t *out, es_out_id_t *id,
  2. block_t *p_block )
  3. {
  4. return out->pf_send( out, id, p_block );
  5. }

继续跟踪,这里的out->pf_send()函数指向src/input/es_out.c文件中的EsOutSend()函数,截取该函数的一段代码:

  1. ...
  2. ...
  3. /* Decode */
  4. if( es->p_dec_record )
  5. {
  6. block_t *p_dup = block_Duplicate( p_block );
  7. if( p_dup )
  8. input_DecoderDecode( es->p_dec_record, p_dup,
  9. p_input->p->b_out_pace_control );
  10. }
  11. input_DecoderDecode( es->p_dec, p_block,
  12. p_input->p->b_out_pace_control );
  13. ...
  14. ...

这里将会调用第二个input_DecoderDecode()(src/input/Decoder.c)函数,如下:

  1. void input_DecoderDecode( decoder_t *p_dec, block_t *p_block, bool b_do_pace )
  2. {
  3. decoder_owner_sys_t *p_owner = p_dec->p_owner;
  4. if( b_do_pace )
  5. {
  6. /* The fifo is not consummed when buffering and so will
  7. * deadlock vlc.
  8. * There is no need to lock as b_buffering is never modify
  9. * inside decoder thread. */
  10. if( !p_owner->b_buffering )
  11. block_FifoPace( p_owner->p_fifo, 10, SIZE_MAX );
  12. }
  13. #ifdef __arm__
  14. else if( block_FifoSize( p_owner->p_fifo ) > 50*1024*1024 /* 50 MiB */ )
  15. #else
  16. else if( block_FifoSize( p_owner->p_fifo ) > 400*1024*1024 /* 400 MiB, ie ~ 50mb/s for 60s */ )
  17. #endif
  18. {
  19. /* FIXME: ideally we would check the time amount of data
  20. * in the FIFO instead of its size. */
  21. msg_Warn( p_dec, "decoder/packetizer fifo full (data not "
  22. "consumed quickly enough), resetting fifo!" );
  23. block_FifoEmpty( p_owner->p_fifo );
  24. }
  25. block_FifoPut( p_owner->p_fifo, p_block );
  26. }

最后,调用src/misc/block.c文件中的block_FifoPut()函数,如下:

  1. size_t block_FifoPut( block_fifo_t *p_fifo, block_t *p_block )
  2. {
  3. size_t i_size = 0, i_depth = 0;
  4. block_t *p_last;
  5. if (p_block == NULL)
  6. return 0;
  7. for (p_last = p_block; ; p_last = p_last->p_next)
  8. {
  9. i_size += p_last->i_buffer;
  10. i_depth++;
  11. if (!p_last->p_next)
  12. break;
  13. }
  14. vlc_mutex_lock (&p_fifo->lock);
  15. *p_fifo->pp_last = p_block;
  16. p_fifo->pp_last = &p_last->p_next;
  17. p_fifo->i_depth += i_depth;
  18. p_fifo->i_size += i_size;
  19. /* We queued at least one block: wake up one read-waiting thread */
  20. vlc_cond_signal( &p_fifo->wait );
  21. vlc_mutex_unlock( &p_fifo->lock );
  22. return i_size;
  23. }

这里,即通过调用vlc_cond_signal( &p_fifo->wait )(此为VLC所封装的一个系统的线程条件信号函数),通知正在等待p_fifo->wait条件的函数,而正在等待该条件的函数在得到这个信号后即调用解码函数开始解码。

总结:VLC实际上是一个多线程密集的工程,其中封装了系统的线程函数来实现各个数据包的同步问题。

vlc-android对于通过Live555接收到音视频数据包后的处理分析的更多相关文章

  1. 网络摄像机IPCamera RTSP直播播放网络/权限/音视频数据/花屏问题检测与分析助手EasyRTSPClient

    前言 最近在项目中遇到一个奇怪的问题,同样的SDK调用,访问海康摄像机的RTSP流,发保活OPTIONS命令保活,一个正常,而另一个一发就会被IPC断开,先看现场截图: 图1:发OPTIONS,摄像机 ...

  2. Android多媒体框架总结(1) - 利用MediaMuxer合成音视频数据流程分析

    场景介绍: 设备端通过服务器传向客户端(Android手机)实时发送视频数据(H.264)和音频数据(g711a或g711u), 需要在客户端将音视频数据保存为MP4文件存放在本地,用户可以通过APP ...

  3. [工具]利用EasyRTSPClient工具检查摄像机RTSP流不能播放原因以及排查音视频数据无法播放问题

    出现问题 我们在做流媒体开发的过程中,进程会出现摄像机RTSP流莫名其妙无法播放的问题,而我们常用的vlc经常是直接弹出一个无法播放的提示框就完事了,没有说明出错的原因,或者在vlc的消息里面能看到日 ...

  4. Android利用tcpdump和wireshark抓取网络数据包

    Android利用tcpdump和wireshark抓取网络数据包 主要介绍如何利用tcpdump抓取andorid手机上网络数据请求,利用Wireshark可以清晰的查看到网络请求的各个过程包括三次 ...

  5. FFmpeg采集音视频数据命令

    文章转自:https://www.jianshu.com/p/4709ccbda3f9 1.ffmpeg 把文件当做直播推送至服务器 (RTMP + FLV) ffmpeg - re -i demo. ...

  6. EasyCamera海康摄像机向EasyDarwin云平台推送音视频数据的缓存设计

    本文转自EasyDarwin团队成员Alex的博客:http://blog.csdn.net/cai6811376 EasyCamera在向EasyDarwin云平台推送音视频数据时,有时一个I帧会很 ...

  7. 史上最全的音视频SDK包分享给大家

    史上最全的音视频SDK包分享给大家 概述一下SDK功能: 项目 详情视频通信  支持多种分辨率的视频通信语音通信  提供语音通信,可支持高清宽带语音动态创建房间  可以根据需要,随时创建房间H5 支持 ...

  8. Android Webview中解决H5的音视频不能自动播放的问题

    在开发webview的时候,当加载有声音的网页的时候,声音不会自动播放, 解决方法:在webview中调用js方法.这个方法需要在webview的setWebViewClient方法之后在onPage ...

  9. WebRTC音视频引擎研究(1)--整体架构分析

    WebRTC技术交流群:234795279 原文地址:http://blog.csdn.net/temotemo/article/details/7530504     1.WebRTC目的     ...

随机推荐

  1. hadoop多文件输出

    现实环境中,经常遇到一个问题就是想使用多个Reduce,可是迫于setup和cleanup在每个Reduce中会调用一次,仅仅能设置一个Reduce,无法是实现负载均衡. 问题,假设要在reduce中 ...

  2. Makefile 工程管理

    Makefile 工程管理 Makefile 规则 --变量 在Makefile中,用户除了可以自己定义变量外,还可以使用存在系统已经定义好的默认变量 $^:代表所有的依赖文件 $@:代表目标 $&l ...

  3. git学习笔记(五)

    ---恢复内容开始--- 嗯 今天又看到一个非常不错的入门git教程 Mark一下 阮一峰Git操作详解 主要讲解了五个常用的git命令 git clone git remote git fetch ...

  4. GridView控件中插入自定义删除按钮并弹出确认框

    GridView控件中插入自定义删除按钮,要实现这个功能其实有多种方法,这里先记下我使用的方法,以后再添加其他方法. 一.实现步骤 1.在GridView中添加模板列(TemplateField). ...

  5. (转)swfobject.js 详细解说

    一直想对这个应用做个总结,今天偶然百度到这个效果,为此做个笔记. 用这个js的好处: 1.IE中没有讨厌的虚框问题了.2.提供了完善的版本检测功能,如果版本不够则显示其他东西,比如图片或文字.3.易于 ...

  6. Android SQLite的使用2(非原创)

    1.数据库的增.删.改.查:execSQL方法 public void insertAction() {//添加信息 db.execSQL("insert into Emp(name,sal ...

  7. SQL日期形式转换

    在SQL Server中,有时存储在数据库中的日期格式和我们需要显示在页面上的格式不相同,我们需要转化成需要的格式. 特在此总结了一下常用的日期格式. --当前时间 SELECT GETDATE(); ...

  8. CSS属性值定义语法中的符号说名

    我们通常看到一个CSS语法,总是有很多符号在其中,这些符号是什么鬼呢,且看下面道来 这些符号可以大致分为2类:分组类与数量类. 1.分组类,就是分成一堆一堆啦: 符号 名称 描述 示例   并置 各部 ...

  9. (原)编译caffe时提示未定义的引用(undefined reference to)

    转载请注明出处: http://www.cnblogs.com/darkknightzh/p/5864715.html 参考网址: https://github.com/BVLC/caffe/issu ...

  10. 数据库操作CURD

    JDBCCURD操作实例 19. 五 / J2EE / 没有评论   代码目录结构: domain   javabean: util   工具类  jdbcUtil是连接数据mysql数据库的工具类 ...