VLC Buffering机制介绍

2023-05-16

一、简介

了解一定播放器知识的同学应该都知道,播放器内部是有缓存的(非直播场景)。缓存的作用主要是解决生产者和消费者速度的不匹配,给用户更好的使用体验。例如,在网络不稳定的情况下,可以提前缓存部分数据确保视频流畅播放;在网络差的情况下,可以及时弹出缓冲标志提示用户当前网络不好。下面简单介绍下VLC的Buffering机制。

我们在使用VLC播放视频时,能够明确的看到类似如下日志:

08-06 13:48:51.845 11495 12427 D VLC     : [894f9830/308b] libvlc input: Buffering 9%

显然这就是VLC的缓冲日志了,我们可以简单地通过这行日志定位到VLC的缓冲逻辑是在es_out.c的EsOutDecodersStopBuffering()函数中:

static void EsOutDecodersStopBuffering( es_out_t *out, bool b_forced )

{

    es_out_sys_t *p_sys = container_of(out, es_out_sys_t, out);

    es_out_id_t *p_es;

    vlc_tick_t i_stream_start;

    vlc_tick_t i_system_start;

    vlc_tick_t i_stream_duration;

    vlc_tick_t i_system_duration;

    //通过clock中stream的第一帧和最后一帧数据的pts计算i_stream_duration,即缓存的数据量

    if (input_clock_GetState( p_sys->p_pgrm->p_input_clock,

                                  &i_stream_start, &i_system_start,

                                  &i_stream_duration, &i_system_duration ))

        return;

    vlc_tick_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_buffering_duration其实主要是由i_pts_delay决定

    const vlc_tick_t i_buffering_duration = p_sys->i_pts_delay +

                                         p_sys->i_pts_jitter +

                                         p_sys->i_tracks_pts_delay +

                                         i_preroll_duration +

                                         p_sys->i_buffering_extra_stream - p_sys->i_buffering_extra_initial;

    //如果缓存的数据不够就直接返回;如果参数b_forced置为true,强制结束缓存

    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;

    }

    //缓存的数据足够,结束缓存,上报702

    input_SendEventCache( p_sys->p_input, 1.0 );

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

              (int)MS_FROM_VLC_TICK(i_stream_duration), (int)MS_FROM_VLC_TICK(i_system_duration) );

    p_sys->b_buffering = false;

    p_sys->i_preroll_end = -1;

    p_sys->i_prev_stream_level = -1;

    ......

}

也就是说,VLC只有在走到EsOutDecodersStopBuffering函数的时候才会计算内部缓存数据,只需要继续倒追调用的地方就可以了。幸运的是,这个函数只被调用了两次,下面分别分析下:

1)EsOutVaControlLocked函数中

demux在解到数据后,会通过数据的dts来更新pcr,然后通过es_out_SetPCR()通知es_out(这部分也不过多展开)。如果此时在buffering状态,因为有新数据到来,所以要检查下buffering是否应该结束。

case ES_OUT_SET_PCR:

case ES_OUT_SET_GROUP_PCR:

{

    ......

    /* TODO do not use vlc_tick_now() but proper stream acquisition date */

    const bool b_low_delay = priv->b_low_delay;

    bool b_extra_buffering_allowed = !b_low_delay && EsOutIsExtraBufferingAllowed( out );

    vlc_tick_t i_late = input_clock_Update(

                        p_pgrm->p_input_clock, VLC_OBJECT(p_sys->p_input),

                        input_CanPaceControl(p_sys->p_input) || p_sys->b_buffering,

                        b_extra_buffering_allowed,

                        i_pcr, vlc_tick_now() );

    if( !p_sys->p_pgrm )

        return VLC_SUCCESS;

    //如果es_out处于buffering状态,需要检查下buffering是否应该结束

    if( p_sys->b_buffering )

    {

        /* Check buffering state on master clock update */

        EsOutDecodersStopBuffering( out, false );

    }

    else if( p_pgrm == p_sys->p_pgrm )

    {

        /* Last pcr/clock update was late. We need to compensate by offsetting

           from the clock the rendering dates */

        if( i_late > 0 && ( !priv->p_sout ||

                        !priv->b_out_pace_control ) )

        {

            /* input_clock_GetJitter returns compound delay:

             * - initial pts delay (buffering/caching)

             * - jitter compensation

             * - track offset pts delay

             * updated on input_clock_Update

             * Late/jitter amount is updated from median of late values */

            vlc_tick_t i_clock_total_delay = input_clock_GetJitter( p_pgrm->p_input_clock );

            /* Current jitter */

            vlc_tick_t i_new_jitter = i_clock_total_delay

                                    - p_sys->i_tracks_pts_delay

                                    - p_sys->i_pts_delay;

            /* If the clock update is late, we have 2 possibilities:

             *  - offset rendering a bit more by increasing the total pts-delay

             *  - ignore, set clock to a new reference ahead of previous one

             *    and flush buffers (because all previous pts will now be late) */

            /* Avoid dangerously high value */

            /* If the jitter increase is over our max or the total hits the maximum */

            if( i_new_jitter > priv->i_jitter_max ||

                i_clock_total_delay > INPUT_PTS_DELAY_MAX ||

                /* jitter is always 0 due to median calculation first output

                   and low delay can't allow non reversible jitter increase

                   in branch below */

                (b_low_delay && i_late > priv->i_jitter_max) )

            {

                msg_Err( p_sys->p_input,

                         "ES_OUT_SET_(GROUP_)PCR  is called %d ms late (jitter of %d ms ignored)",

                         (int)MS_FROM_VLC_TICK(i_late),

                         (int)MS_FROM_VLC_TICK(i_new_jitter) );

                /* don't change the current jitter */

                i_new_jitter = p_sys->i_pts_jitter;

            }

            else

            {

                msg_Err( p_sys->p_input,

                         "ES_OUT_SET_(GROUP_)PCR  is called %d ms late (pts_delay increased to %d ms)",

                         (int)MS_FROM_VLC_TICK(i_late),

                         (int)MS_FROM_VLC_TICK(i_clock_total_delay) );

            }

            /* Force a rebufferization when we are too late */

            EsOutControlLocked( out, source, ES_OUT_RESET_PCR );

            EsOutPrivControlLocked( out, ES_OUT_PRIV_SET_JITTER,

                                    p_sys->i_pts_delay, i_new_jitter,

                                    p_sys->i_cr_average );

        }

    }

    return VLC_SUCCESS;

}

那么buffering是从什么时候开始的呢?再追下b_buffering变量,运气很好,也只有两个位置将b_buffering置为true。第一个地方在input_EsOutNew()函数,这个很好理解,起播阶段创建es_out肯定要buffering;第二个地方是在EsOutChangePosition函数中,而EsOutChangePosition函数只有在es_out收到ES_OUT_RESET_PCR control时才会调用(这里就不贴具体代码了),下发ES_OUT_RESET_PCR control的地方就在上面贴的代码中。绕了一圈又绕回来了。。。代码中的注释已经说明了,如果pcr更新晚了,就强制开始缓冲。至于pcr更新到底晚没晚,是通过input_clock_Update()函数判断的:

/*****************************************************************************

 * input_clock_Update: manages a clock reference

 *

 *  i_ck_stream: date in stream clock

 *  i_ck_system: date in system clock

 *****************************************************************************/

vlc_tick_t input_clock_Update( input_clock_t *cl, vlc_object_t *p_log,

                         bool b_can_pace_control, bool b_buffering_allowed,

                         vlc_tick_t i_ck_stream, vlc_tick_t i_ck_system )

{

    ......

    /* */

    cl->last = clock_point_Create( i_ck_system, i_ck_stream );

    /* It does not take the decoder latency into account but it is not really

     * the goal of the clock here */

    /*这里首先是将pcr从stream clock参考系转换到了system clock参考系(VLC的clock系统和AVSync机制详见VLC音画同步原理)。换句话说,i_system_expected就是期望当前pcr到达的系统时间。如果播放器没有缓

      存,那么i_system_expected比当前系统时间(i_ck_system)小就说明pcr来晚了,即i_ck_system - i_system_expected > 0就代表pcr来晚了;考虑到播放器还有缓存,而缓存的时间正好是i_pts_delay 

     (EsOutDecodersStopBuffering()函数中介绍过),因此还要减掉这部分缓存,即(i_ck_system - i_system_expected - cl->i_pts_delay) > 0代表pcr来晚了。*/

    const vlc_tick_t i_system_expected = ClockStreamToSystem( cl, i_ck_stream + AvgGet( &cl->drift ) );

    const vlc_tick_t i_late = __MAX(0, ( i_ck_system - cl->i_pts_delay ) - i_system_expected);

    if( i_late > 0 )

    {

        cl->late.pi_value[cl->late.i_index] = i_late;

        cl->late.i_index = ( cl->late.i_index + 1 ) % INPUT_CLOCK_LATE_COUNT;

    }

    UpdateListener( cl );

    return i_late;

}

2)EsOutDecodersIsEmpty函数中

static bool EsOutDecodersIsEmpty( es_out_t *out )

{

    es_out_sys_t *p_sys = container_of(out, es_out_sys_t, out);

    es_out_id_t *es;

    if( p_sys->b_buffering && p_sys->p_pgrm )

    {

        //注意,这里b_force参数是true,即强制结束buffering

        EsOutDecodersStopBuffering( out, true );

        if( p_sys->b_buffering )

            return true;

    }

    foreach_es_then_es_slaves(es)

    {

        if( es->p_dec && !vlc_input_decoder_IsEmpty( es->p_dec ) )

            return false;

        if( es->p_dec_record && !vlc_input_decoder_IsEmpty( es->p_dec_record ) )

            return false;

    }

    return true;

}

一路追函数调用最终定位到了input.c的MainLoop里,这里应该是为了在视频播放接近eos的时候,防止demux不再送数据而卡在buffering状态下,强制结束buffering让播放器将缓存的数据播放完。

while( !input_Stopped( p_input ) && input_priv(p_input)->i_state != ERROR_S )

{

    vlc_tick_t i_wakeup = -1;

    bool b_paused = input_priv(p_input)->i_state == PAUSE_S;

    /* FIXME if input_priv(p_input)->i_state == PAUSE_S the access/access_demux

     * is paused -> this may cause problem with some of them

     * The same problem can be seen when seeking while paused */

    if( b_paused )

        b_paused = !es_out_GetBuffering( input_priv(p_input)->p_es_out )

                || input_priv(p_input)->master->b_eof;

    if( !b_paused )

    {

        if( !input_priv(p_input)->master->b_eof )

        {

            bool b_force_update = false;

            MainLoopDemux( p_input, &b_force_update );

            if( b_can_demux )

                i_wakeup = es_out_GetWakeup( input_priv(p_input)->p_es_out );

            if( b_force_update )

                i_intf_update = 0;

            b_paused_at_eof = false;

        }

        else if( !es_out_GetEmpty( input_priv(p_input)->p_es_out ) )

        {

            msg_Dbg( p_input, "waiting decoder fifos to empty" );

            i_wakeup = vlc_tick_now() + INPUT_IDLE_SLEEP;

        }

二、总结

这里简单总结下,VLC的Buffering机制是通过demux数据驱动的,demux解封装数据后更新pcr和clock,es_out通过pcr和当前系统时间计算pcr是否来晚(数据是否欠载)。如果数据欠载便开始buffering,此时demux每次更新pcr es_out都会检测buffering是否完成。

这里分析代码发现两个问题:

1.input_clock_Update函数中没有考虑视频的播放速度,这里的i_pts_delay代表的是视频1倍速播放的缓存数据量,如果切换到两倍速播放,i_pts_delay应该减半。这应该是VLC的一个Bug,会导致播放器倍速播放时画面卡主但不显示缓冲(播放器对内部缓存数据量判断有误)。我使用VLC 3.3.2版本还是能复现这个问题。

2.Buffering机制完全依赖demux,假设在弱网环境下,demux超过i_pts_delay时间没有更新pcr,此时播放器内部的数据已经消耗完,但由于pcr没有更新所以不能及时进入buffering逻辑,同样会出现视频卡主但不显示缓冲提示的问题。这里我认为可以起一个线程一直进行监测。

最后,VLC的缓存数据量是可以设置的,可以通过添加如下Options进行设置(单位为ms):

Media.addOption(":network-caching=1000");//网络缓存

Media.addOption(":file-caching=1000");//本地缓存

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

VLC Buffering机制介绍 的相关文章

  • 在 C++ 中处理许多进程的中央数据缓冲区

    我遇到了以下问题 无法决定如何继续 我有一堂课 Reader 每1 T秒获取一块数据 实际上数据来自视频帧 每秒30帧 这些块将被传递给多个对象 Detectors处理块并输出决策 然而 每个检测器在做出决定之前需要读取的块数量各不相同 例
  • 如何设置面板的透明不透明度

    我如何将面板设置为透明 如不透明度为0 我通过程序设置面板 它位于视频播放器的顶部 代码是这样的 Private Sub Button1 Click sender As Object e As EventArgs Handles Butto
  • Python VLC - 获取位置轮询率解决方法

    我使用 Python VLC 在 pyqt 中构建自定义播放应用程序 我画了一个漂亮的自定义滑块来跟踪视频 但遇到了一些恼人的问题 无论我多久告诉我的滑块更新一次 它都会出现故障 每 1 4 秒左右跳跃一次 并且看起来不稳定 只是时间线 而
  • 即使使用调用方法也出现“跨线程操作无效”

    我在这里得到 跨线程操作无效 if vlc State VlcPlayerControlState PLAYING if vlc InvokeRequired vlc Invoke new MediaPlayerNoParameterDel
  • 在 HTML 页面上嵌入 VLC 插件

    我有一个 html 文件 getStream html 从某个 url 获取流并显示它 代码如下
  • 缓冲随机访问文件 java

    RandomAccessFile 对于随机访问文件来说相当慢 您经常阅读有关在其上实现缓冲层的信息 但无法在网上找到执行此操作的代码 所以我的问题是 知道这个类的任何开源实现的你们会共享一个指针还是共享您自己的实现 如果这个问题能成为关于这
  • python中有COMMIT模拟用于写入文件吗?

    我有一个打开的文件可供写入 并且有一个运行了数天的进程 在相对随机的时刻将某些内容写入文件中 我的理解是 直到我执行 file close 之前 有可能没有任何内容真正保存到磁盘上 真的吗 如果主进程尚未完成时系统崩溃怎么办 有没有一种方法
  • 在Java中启动VLC并通过rc接口连接到它

    我已经看过这个帖子了 但我仍然遇到一个问题 在java中启动vlc播放器 https stackoverflow com questions 1731299 starting vlc player in java看来 VLC 的 Java
  • 如何在命令行中使用VLC保存视频流?

    我正在尝试在 Window 7 Basic 的命令行中使用 VLC 保存在线视频 以下是我尝试过并部分起作用的一些事情 I movies gt vlc http media ch9 ms ch9 7492 a92ae0a6 7b81 411
  • FileChannel#force 和缓冲

    我现在想澄清一下 并在 FileOutputStream 和 FileChannel 之间画出一些相似之处 所以首先 似乎使用标准 Java io 写入文件的最有效方法是使用用 BufferedOutputStream 包装的 FileOu
  • VLC 流至 MP4 WEBM 和 Flash

    我正在尝试将视频从 IP 摄像机流式传输到我的 WordPress 网站 我希望我的流可以通过常见设备 Windows Mac Android 和 IOS 访问 目前我正在使用 VLC 进行流式传输 但我只能使用 flash 流 但我想做
  • 将 RTSP 流转换为 HTTP 并使用 VLC 客户端进行流传输

    我有一个通过 RTSP 提供流的 IP 摄像机 我可以使用以下 URL 在带有 VLC 播放器的 PC 上播放它 rtsp 192 168 1 52 554 user admin password channel 1 stream 0 sd
  • 使用 stdout 和 stderr 禁用缓冲是否安全?

    有时我们会以这种方式在代码中添加一些调试打印 printf successfully reached at debug point 1 n some code is here printf successfully reached at d
  • C# 中的 StreamReader 和缓冲区

    我对 StreamReader 的缓冲区使用有疑问 这里 http msdn microsoft com en us library system io streamreader aspx http msdn microsoft com e
  • C# 嵌入vlc控件

    我尝试将 VLC 嵌入到我的 WPF 项目中 我已经注册了 axvlc dll 还下载了 VLC nightly build 版本 2 2 2 System Windows Markup XamlParseException 类型的第一次机
  • RTSP服务器java实现问题:(

    我正在编写 RTSP 服务器并遇到一些问题 我使用 VLC 作为客户端 服务器从客户端 VLC 播放器 接收 OPTIONS DESCRIBE SETUP 和 PLAY 命令并回答该命令 通过 SETUP 命令客户端发送端口号 我正在使用该
  • C# - 捕获 RTP 流并发送到语音识别

    我正在努力实现的目标 在 C 中捕获 RTP 流 将该流转发到 System Speech SpeechRecognitionEngine 我正在创建一个基于 Linux 的机器人 它将接受麦克风输入 将其发送给 Windows 机器 Wi
  • 从命令行运行 vlc 扩展

    我有一个用 Lua 编写的 vlc 扩展 我知道如何从 GUI 运行它 查看 gt 我的扩展 我想从命令行运行它 这样我就不需要每次都启动X 它还没有实施 查看门票 3883 https trac videolan org vlc tick
  • 为什么 printf() 在 sleep() 之前不打印任何内容?

    我刚刚通过 Kernighan 和 Ritchie 的书学习 C 我正在学习第四章 函数和程序结构 的基础知识 有一天我开始好奇sleep 函数 所以尝试像这样使用它 include
  • 从 RTSP 流传输 WebRTC

    目前 我有一个来自 IP 摄像机的 RTSP 流 我当然有 IP 如果我尝试在 vlc 上显示它 一切都很好 rtsp IP PORT channel 下一步是在我的网站上展示它 能够将其集成为 js 视频组件 有什么方法可以将其转换为 W

随机推荐

  • 串口传输数据错位 的几种解决办法

    1 代码优化等级 2 使用晶振 晶振自身产生时钟信号 xff0c 为各种微处理芯片作时钟参考 无源晶振需要用CPU内部的振荡器信号差接线麻烦石英 gt 陶瓷有源晶振是一个完整的振荡器信号好接线简单灵活性较差 3 使用降低传输速率 xff1f
  • sip 认证分析

    SIP类似Http协议 其认证模式也一样 Http协议 xff08 RFC 2616 xff09 规定可以采用Basic模式和摘要模式 xff08 Digest schema xff09 RFC 2617 专门对两种认证模式做了规定 RFC
  • MicroPython移植

    MicroPython移植 1 目标板 stm32f407zgt6 2 下载移植准备 micropython源码 arm交叉编译工具 sudo apt get install git sudo apt get install gcc arm
  • 了解ESP32睡眠模式及其功耗

    陈拓翻译 2022 05 30 2022 05 30 原文 https lastminuteengineers com esp32 sleep modes power consumption 毫无疑问 xff0c ESP32是许多WiFi
  • 浅谈布隆过滤器

    什么是布隆过滤器 布隆过滤器是一种数据结构 xff0c 比较巧妙的概率型数据结构 xff08 probabilistic data structure xff09 xff0c 特点是高效地插入和查询 xff0c 可以用来告诉你 某样东西一定
  • 浅谈CGI基本原理和底层基本实现

    历史来由 xff1a 早期的Web服务器 xff0c 只能响应浏览器发来的HTTP静态资源的请求 xff0c 并将存储在服务器中的静态资源返回给浏览器 随着Web技术的发展 xff0c 逐渐出现了动态技术 xff0c 但是Web服务器并不能
  • linux的两种共享内存方式---mmap和shmat区别

    linux中的两种共享内存 一种是我们的IPC通信System V版本的共享内存 xff0c 另外的一种就是我们今天提到的存储映射I O xff08 mmap函数 xff09 在说mmap之前我们先说一下普通的读写文件的原理 xff0c 进
  • tcp发送窗口(滑动窗口)、拥塞窗口

    TCP发送窗口拥塞窗口试题分析 题目一 xff1a 来源2015年408计算机综合 试题链接 xff1a https www nowcoder com questionTerminal 3241441c88f04ab58585a187716
  • mktime函数性能分析

    mktime函数性能分析 1月 02 2019 in Linux环境高级编程 mktime函数性能分析 mktime是一个将break down时间 struct tm 转化为日历时间 time t 的转换函数 它的转换与struct tm
  • iptables原理和防火墙主要命令使用场景

    https www zsythink net archives 1764 朱双印的个人日志 xff0c 写的非常的通俗易懂 xff0c 好文章 https blog csdn net u011277123 article details 8
  • 链路mtu

    常常见到交换机和网卡说明中提到支持Jumbo Frame xff0c 但我一直对以太网的Jumbo Frame xff08 巨帧 xff09 如何使用不太理解 xff0c 今日在网上找到2则现摘录下来 xff0c 相信看了以后大家会有收获
  • eggjs

    https editor csdn net md not checkout 61 1 amp spm 61 1001 2014 3001 4503 https blog csdn net weixin 42304193 article de
  • mini6410上HelloQt4运行出现libQtGui.so.4: cannot open shared的原因

    主要原因是在3 3 3节中 xff0c 编写的环境变量搭建文件setqt4env中设置路径不对 export LD LIBRARY PATH 61 xff08 看看有没有文件中的目录 xff09 应该改成你所在的qt4 7目录中的lib目录
  • VINS技术路线与代码详解

    VINS技术路线 写在前面 xff1a 本文整和自己的思路 xff0c 希望对学习VINS或者VIO的同学有所帮助 xff0c 如果你觉得文章写的对你的理解有一点帮助 xff0c 可以推荐给周围的小伙伴们 xff0c 当然 xff0c 如果
  • 用MicroPython开发ESP32- 用Thonny写程序

    陈拓 2022 06 11 2022 06 12 1 简介 在 用MicroPython开发ESP32 固件烧写与测试 https zhuanlan zhihu com p 527291091 https blog csdn net che
  • 单片机 stm32 接收数据和处理

    背景 1 单片机串口接收数据处理 xff0c 这个代码已经过很多项目验证 xff0c 没有问题 用这个代码帮了好几个同事解决数据接收久了就异常 2 这个代码做到接收和处理分开 避免不必要的处理逻辑问题 3 也可用于网口tcp xff0c u
  • odroid Xu4介绍

    Odroid xu4介绍 下面对这块开发板做一下简单的介绍 xff0c 共需要用到的人参考 从参数上来看 xff0c ODROID XU4的整体性能基本和目前的中端智能手机差不多 xff0c 它搭载了主频
  • OdroidXu4开发环境搭建

    OdroidXu4开发环境搭建 一 烧录镜像 1 SD卡烧录 首先准备一张至少16G的sd卡 镜像可以在官网 xff1a http odroid com dokuwiki doku php id 61 en odroid xu4 softw
  • 大小端:字节序与比特序

    https blog csdn net fzy0201 article details 26876711 https blog csdn net qq 40334837 article details 89042607 前言 前两天被问到一
  • VLC Buffering机制介绍

    一 简介 了解一定播放器知识的同学应该都知道 xff0c 播放器内部是有缓存的 xff08 非直播场景 xff09 缓存的作用主要是解决生产者和消费者速度的不匹配 xff0c 给用户更好的使用体验 例如 xff0c 在网络不稳定的情况下 x