IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> VLC-Android起播丢帧问题分析 -> 正文阅读

[移动开发]VLC-Android起播丢帧问题分析

最近在搞MEMC优化过程中,经常能够发现视频在起播阶段会丢几帧,记录下问题分析过程。

对于AV同步及丢帧等问题的分析,最好就是将数据从demux到decoder最后到output的pts都打印出来,VLC还涉及到stream时间与系统时间的转换,因此clock_point也需要打印出来,这样能够快速地定位到问题原因。

对于这题,视频被丢的前几帧和后续的正常帧有一个区别,那就是它们在做时间转换时参考的clock_point不一样。为什么会发生这种情况呢?我们回顾下VLC-Android音画同步原理中关于buffering的延伸部分,VLC会在buffering结束后将buffering的这段时间补偿到clock_point中,导致clock_point发生了变化。起播阶段同样需要buffering,因此clock_point前后不一样也能够说通,但是仅仅因为这样就出现问题了,是否过于简单了?我们再详细看下EsOutDecodersStopBuffering函数:

static?void?EsOutDecodersStopBuffering( es_out_t *out, bool b_forced )

{

????es_out_sys_t *p_sys = out->p_sys;

????mtime_t i_stream_start;

????mtime_t i_system_start;

????mtime_t i_stream_duration;

????mtime_t i_system_duration;

????//获取已经缓存的数据量

????if?(input_clock_GetState( p_sys->p_pgrm->p_clock,

??????????????????????????????????&i_stream_start, &i_system_start,

??????????????????????????????????&i_stream_duration, &i_system_duration ))

????????return;

????mtime_t i_preroll_duration =?0;

????if( p_sys->i_preroll_end >=?0?)

????????i_preroll_duration = __MAX( p_sys->i_preroll_end - i_stream_start,?0?);

????//缓存上限,主要由i_pts_delay控制,可以通过options设置

????const?mtime_t i_buffering_duration = p_sys->i_pts_delay +

?????????????????????????????????????????i_preroll_duration +

?????????????????????????????????????????p_sys->i_buffering_extra_stream - p_sys->i_buffering_extra_initial;

????if( i_stream_duration <= i_buffering_duration && !b_forced )

????{

????????//计算缓冲进度并上报

????????double?f_level;

????????if?(i_buffering_duration ==?0)

????????????f_level =?0;

????????else

????????????f_level = __MAX( (double)i_stream_duration / i_buffering_duration,?0?);

????????input_SendEventCache( p_sys->p_input, f_level );

????????int?i_level = (int)(100?* f_level);

????????if( p_sys->i_prev_stream_level != i_level )

????????{

????????????msg_Dbg( p_sys->p_input,?"Buffering %d%%", i_level );

????????????p_sys->i_prev_stream_level = i_level;

????????}

????????return;

????}

????//缓冲完成并上报

????input_SendEventCache( p_sys->p_input,?1.0?);

????msg_Dbg( p_sys->p_input,?"Stream buffering done (%d ms in %d ms)",

??????????????(int)(i_stream_duration/1000), (int)(i_system_duration/1000) );

????p_sys->b_buffering =?false;

????p_sys->i_preroll_end = -1;

????p_sys->i_prev_stream_level = -1;

????if( p_sys->i_buffering_extra_initial >?0?)

????{

????????/* FIXME wrong ? */

????????return;

????}

????//等待解码器解出首帧

????const?mtime_t i_decoder_buffering_start = mdate();

????for(?int?i =?0; i < p_sys->i_es; i++ )

????{

????????es_out_id_t *p_es = p_sys->es[i];

????????if( !p_es->p_dec || p_es->fmt.i_cat == SPU_ES )

????????????continue;

????????input_DecoderWait( p_es->p_dec );

????????if( p_es->p_dec_record )

????????????input_DecoderWait( p_es->p_dec_record );

????}

????msg_Dbg( p_sys->p_input,?"Decoder wait done in %d ms",

??????????????(int)(mdate() - i_decoder_buffering_start)/1000?);

????/* Here is a good place to destroy unused vout with every demuxer */

????input_resource_TerminateVout( input_priv(p_sys->p_input)->p_resource );

????/* */

????const?mtime_t i_wakeup_delay =?10*1000;?/* FIXME CLEANUP thread wake up time*/

????const?mtime_t i_current_date = p_sys->b_paused ? p_sys->i_pause_date : mdate();

????//更新clock_point

????input_clock_ChangeSystemOrigin( p_sys->p_pgrm->p_clock,?true,

????????????????????????????????????i_current_date + i_wakeup_delay - i_buffering_duration );

????//解码器停止等待

????for(?int?i =?0; i < p_sys->i_es; i++ )

????{

????????es_out_id_t *p_es = p_sys->es[i];

????????if( !p_es->p_dec )

????????????continue;

????????input_DecoderStopWait( p_es->p_dec );

????????if( p_es->p_dec_record )

????????????input_DecoderStopWait( p_es->p_dec_record );

????}

}

之前对这个函数的理解不够透彻,可以看到这里不仅考虑了buffering需要的时间,还考虑了decoder解码首帧需要的时间,将这两部分时间都补偿到了clock_point中,才能保证解码出的前几帧不被丢掉。至此,VLC的逻辑还是无懈可击的,问题到底出在哪呢?通过对比正常和异常时的日志,发现了一些端倪:

//正常

libvlc input: Decoder wait done in?77?ms

//异常

libvlc input: Decoder wait done in?0?ms

出现异常时,解码器解出首帧用了不到1ms,这显然是不正常的,继续跟进下input_DecoderWait函数的实现:

void?input_DecoderWait( decoder_t *p_dec )

{

????decoder_owner_sys_t *p_owner = p_dec->p_owner;

????assert( p_owner->b_waiting );

????vlc_mutex_lock( &p_owner->lock );

????//如果b_has_data为false,就一直循环;b_has_data只有在DecoderPlayXxx函数中才会被置为true,此时代表解码器解出了首帧

????while( !p_owner->b_has_data )

????{

????????/* Don't need to lock p_owner->paused since it's only modified by the

?????????* owner */

????????//第一个提前break的条件,paused变量为true,即解码器在pause状态下,此时不会解码数据,如果不break会发生死锁

????????if( p_owner->paused )

????????????break;

????????vlc_fifo_Lock( p_owner->p_fifo );

????????//第二个提前break的条件,解码器fifo为空,此时没有要解码的数据,同样不会解码数据,如果不break也会发生死锁

????????if( p_owner->b_idle && vlc_fifo_IsEmpty( p_owner->p_fifo ) )

????????{

????????????msg_Err( p_dec,?"buffer deadlock prevented"?);

????????????vlc_fifo_Unlock( p_owner->p_fifo );

????????????break;

????????}

????????vlc_fifo_Unlock( p_owner->p_fifo );

????????vlc_cond_wait( &p_owner->wait_acknowledge, &p_owner->lock );

????}

????vlc_mutex_unlock( &p_owner->lock );

}

通过分析input_DecoderWait函数逻辑可以推测出现问题时该函数大概率是提前返回了,在返回时打印下上面几个布尔变量的值发现问题出在p_owner->paused变量。之后就是一步步加打印确认是谁把decoder pause了,最终定位到input.c的MainLoop函数:

/**

?* MainLoop

?* The main input loop.

?*/

static?void?MainLoop( input_thread_t *p_input, bool b_interactive )

{

????mtime_t i_intf_update =?0;

????mtime_t i_last_seek_mdate =?0;

????//这里在起播前将player pause住了

????if( b_interactive && var_InheritBool( p_input,?"start-paused"?) )

????????ControlPause( p_input, mdate() );

????......

}

设置"start-paused" option,是为了将VLC的状态机与Android MediaPlayer的状态机进行对齐。VLC默认是没有Prepared状态的,setDataSource后会自动开始播放,使用"start-paused"可以让player setDataSource后不自动播放,而是等待上层的start调用,和Android MediaPlayer的Prepared状态类似。

解决方案:如果不care VLC起播是否自动播放,强烈建议把这个option直接去掉,这样做风险最小;同样也可以在EsOutDecodersStopBuffering函数中对input_DecoderWait函数的持续时间进行判断,如果持续时间异常,就主动sleep一段时间确保首帧解码完成。这样做有个问题:sleep的时间不好确认,如果sleep过短,前几帧还是会被丢掉;如果sleep过长,会影响播放器起播速度。

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-11-26 08:57:51  更:2021-11-26 08:59:33 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 5:50:58-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码