通过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. IOS 怎么修改Navigation Bar上的返回按钮文本颜色,箭头颜色以及导航栏按钮的颜色

    self.navigationController.navigationBar.barTintColor = [UIColor blackColor]; self.navigationControll ...

  2. poj3358数论(欧拉定理)

    http://poj.org/problem?id=3358 (初始状态为分数形式)小数点进制转换原理:n / m ; n /= gcd( n , m ) ; m/= gcd( n , m ) ; n ...

  3. EffectiveC#1--尽可能的使用属性(property),而不是数据成员(field)

    1.属性可以进行数据绑定 2.可以做数据安全校验.在对数据检测时,如果发现数据不满足条件,最好以抛出异常的形式来解决 如下代码不可取 public string Name { get { if(thi ...

  4. Android的ADB工具使用

    在SDK的Tools文件夹下包含着Android模拟器操作的重要命令ADB,ADB的全称为Android Debug Bridge,就是调试桥的作用,借助这个工具,我们可以管理设备或手机模拟器的状态  ...

  5. iOS方法类:CGAffineTransform的使用

    CoreGraphics框架中的CGAffineTransform类可用于设定UIView的transform属性,控制视图的缩放.旋转和平移操作: 另称放射变换矩阵,可参照线性代数的矩阵实现方式0. ...

  6. @synthesize

    @synthesize 相当于把属性当成成员变量来用,不用再写self.属性@synthesize myButton; 这样写了之后,那么编译器会自动生成myButton的实例变量,以及相应的gett ...

  7. jQuery Moblile Demos学习记录Theming、Button、Icons图标,脑子真的不好使。

    jQuery Moblile Demos学习记录Theming.Button.Icons图标,脑子真的不好使. 06. 二 / Jquery Mobile 前端 / 没有评论   本文来源于www.i ...

  8. 轻量级jquery框架之--布局(layout)

    布局需求 (1)支持横向生成布局项即可,不需要纵向生成布局. (2)支持布局项右侧收缩功能 (3)支持自定义布局项图标.标题,并提供动态修改布局项图片和标题的api (4)支持JSON/html/if ...

  9. java使用dom4j和XPath解析XML与.net 操作XML小结

    最近研究java的dom4j包,使用 dom4j包来操作了xml 文件 包括三个文件:studentInfo.xml(待解析的xml文件), Dom4jReadExmple.java(解析的主要类), ...

  10. 移动web前端的一些硬技能(二)动手前必须掌握的基本常识

    记得刚开始接触移动端web的时候,书和网上的资料都不多,查起来很费劲,现在比以前要好很多了,可是还是会有一些刚接触移动端的朋友会问我一些我最初会遇到的问题,或许是书本写的并不那么重,也或许是这些知识写 ...