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# 进行 VLC 远程控制

    我正在尝试从 C 控制 VLC 媒体播放器 我尝试使用 Net 中的 FindWindow 命令获取窗口句柄 但我发现每次播放文件时窗口的名称都会发生变化 我遇到的最大问题是将 wm commands 发送到 vlc 这种方法适用于 Win
  • libvlc 流屏幕的一部分

    我想使用 vlc 库流式传输屏幕的一部分 我写了一个小例子 include
  • python-vlc 不会启动播放器

    好的 开始吧 我正在尝试播放在线视频 我得到了网址 如下所示 http fsi stanford edu sites default files video 4 mp4它不是我将在我的应用程序中使用的东西 但它只是一个示例文件 阅读 pyt
  • 具有 setuid/功能的 stdbuf

    我正在读取另一个生成输出的进程的输出 缓慢且无限 因为我想实时读取这些数据 所以我使用 stdbuf oL 行缓冲 数据是文本 我无法控制生成过程 因此无法修改源以强制刷新 到目前为止 stdbuf 工作得很好 但是该进程使用 SOCK R
  • 读取缓冲的二进制文件(带查找)

    假设我需要读取巨大的整数二进制文件 一个方便的方法是 FileInputStream fi new FileInputStream file BufferedInputStream bi new BufferedInputStream fi
  • HLS 流无法在 Apple 设备上运行

    我有一个实时 RTSP 流 我已设法通过 VLC 将其转码为 HLS 现在它可以在 Android 和桌面浏览器上完美运行 通过 Flash 但在 Apple 上不行 我可以在我的虚拟机上的 iPad 和桌面 Safari 上进行测试 我可
  • 使用英特尔 ifort 编译器启用标准输出的缓冲 I/O

    我读过有关使用 ifort 启用缓冲 I O 的英特尔文档 使用 assume buffered io or FORT BUFFERED true 这适用于直接输出到文件 然而 我们有大型应用程序正在写入标准输出 例如write or wr
  • 使用 Gstreamer 提供 RTSP 流,寻求工作示例

    我们正在尝试让 Gstreamer 在 DM368 Leopardboard 上运行 我们已成功说服它创建测试视频 videotestsrc 对其进行编码并将其转储到文件中 有效的管道是 gst launch v videotestsrc
  • 即使使用调用方法也出现“跨线程操作无效”

    我在这里得到 跨线程操作无效 if vlc State VlcPlayerControlState PLAYING if vlc InvokeRequired vlc Invoke new MediaPlayerNoParameterDel
  • 了解 Ruby 和操作系统 I/O 缓冲

    Ruby 中的 IO 缓冲如何工作 使用时数据刷新到底层流的频率是多少IO and File课程 这与操作系统缓冲相比如何 在自信地读回数据进行处理之前 需要做什么来保证给定的数据已写入磁盘 Ruby IO 文档并不是 100 清楚地说明了
  • 如何播放 mp3

    我的问题从这里开始 pyttsx 和 gTTS 模块错误 https stackoverflow com questions 36323564 pyttsx and gtts module errors gTTS 效果很好 从文本文件中获取
  • VLC 语法转码并流式传输到标准输出?

    Goal 我正在尝试使用VLC http www videolan org vlc index html作为本地服务器来扩展使用以下命令创建的应用程序的视频功能Adobe AIR Flex and Actionscript 我在用VLC流式
  • 构建 VLC 时需要 NDKv8b 或更高版本

    我已经在 android 中构建了 VLC 并使用代码实现 jack export ANDROID SDK android sdk jack export ANDROID NDK android ndk r9d jack export PA
  • 使用 VLC imem 从内存播放 h264 视频文件但收到错误“主流错误:无法预填充缓冲区”

    我有一个加载到内存中的 h264 视频文件 我尝试使用参数 imem cat 4 使用 imem 播放它 以便 vlc 将使用访问模块来解复用视频 并且 vlc 启动并接收我的 imem参数成功 0x7f38a0000e28 access
  • 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 命令客户端发送端口号 我正在使用该
  • 有没有办法获得 LibVlc 加载的缓冲区百分比?

    我目前正在制作一个城市网络摄像头应用程序 它使用 libVlc 来显示来自城市网络摄像头的 rtsp 流 我的问题是 是否可以获得流的实际加载状态 我想展示给用户 我可以在 Android Studio runLog 中看到存在某种缓冲 但
  • 将数据写入文件:fflush()需要大量时间

    我有一个要求 其中我必须缓冲大量数据 以 GB 为单位 以供将来使用 由于没有足够的 RAM 来缓冲如此大量的数据 我决定将数据存储在文件中 现在的陷阱是 当我将数据写入文件时 其他线程可能需要 缓冲 数据 因此每次向文件流写入内容时 我都
  • 使用 Servlet 启动 VLC HTTP Stream 时出现问题

    我正在为自己开发一个 VLC 项目 我的目标是创建一个 HTML 前端来启动流 我通过使用 Java Servlet 来完成此操作 概述 乌班图13 04 Java 7 21 冰茶 2 3 9 Eclipse JAVAEE IDE 雄猫7

随机推荐

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

    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