【ijkplayer】 如何降低直播延迟?

设置低延迟选项(播放参数)

java 复制代码
IjkMediaPlayer ijkMediaPlayer = new IjkMediaPlayer();

// 解码方式:硬解码更低延迟
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec", 1);

// 缓冲区设置为最小 
// `1`:启用包缓冲,播放器将会在缓冲不足时暂停输出。
// `0`:禁用包缓冲,播放器可能会继续播放,可能导致出现图像卡顿或音频中断。
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "packet-buffering", 0);
//音频的AVPacket
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "max-buffer-size", 1024);
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "analyzeduration", 100);
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "probesize", 1024);

// 设置帧丢弃策略
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "framedrop", 1);

// 设置最小延迟缓存
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "min-frames", 2);
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "start-on-prepared", 1);

// 网络优化
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "flush_packets", 1);
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "fflags", "nobuffer");

// 音视频同步优化
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "sync", "ext");

减少缓冲帧数量

java 复制代码
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "min-frames", 2);
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "max_cached_duration", 500); // in ms

推荐设置组合(最小延迟)

java 复制代码
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "packet-buffering", 0);
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "fflags", "nobuffer");
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "flush_packets", 1);
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "framedrop", 1);
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "probesize", 1024);
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "analyzeduration", 100);

调整 是否 缓存AvPacket

packet_buffering

  • ijkplayer 开启packet_buffering 相关
c 复制代码
{ "packet-buffering",                   "pause output until enough packets have been read after stalling",
    OPTION_OFFSET(packet_buffering),    OPTION_INT(1, 0, 1) },
  • 注意 Queue中的 packet 的数量和 MIN_MIN_FRAMES 做数据

read-thread 的buffer区间设置

  • 设置缓冲点
  • 如果队列已满,则不需要再读取更多数据。
c 复制代码
  /* if the queue are full, no need to read more */
        if (ffp->infinite_buffer<1 && !is->seek_req &&// 1. 检查是否不使用无限缓冲,2:是否有seek 请求
#ifdef FFP_MERGE
// 队列大小检查:根据不同的编译选项(FFP_MERGE),判断音频、视频和字幕队列的总大小是否超过最大限制(MAX_QUEUE_SIZE 或 ffp->dcc.max_buffer_size)。
              (is->audioq.size + is->videoq.size + is->subtitleq.size > MAX_QUEUE_SIZE
#else
              (is->audioq.size + is->videoq.size + is->subtitleq.size > ffp->dcc.max_buffer_size
#endif
            || (
                    // 流是否足够:检查音频、视频和字幕流是否都有足够的包(大于等于 MIN_FRAMES)。如果都足够,表示不需要再读取更多数据。
                    stream_has_enough_packets(is->audio_st, is->audio_stream, &is->audioq, MIN_FRAMES)
                && stream_has_enough_packets(is->video_st, is->video_stream, &is->videoq, MIN_FRAMES)
                && stream_has_enough_packets(is->subtitle_st, is->subtitle_stream, &is->subtitleq, MIN_FRAMES))))
                {
            if (!is->eof) {
                // 不需要缓冲
                ffp_toggle_buffering(ffp, 0);
            }
             ALOGW("     [%s] the queue are full don`t need to read more",__func__)
             // 等待机制:通过互斥锁和条件变量,等待 10 毫秒,避免 CPU 占用过高。然后继续下一轮循环。
            /* wait 10 ms */
            SDL_LockMutex(wait_mutex);
            SDL_CondWaitTimeout(is->continue_read_thread, wait_mutex, 10);
            SDL_UnlockMutex(wait_mutex);
            continue;
        }

调整卡顿 : HIGH_WATER_MARK

C 复制代码
/*
 * START: buffering after prepared/seeked
 * NEXT:  buffering for the second time after START
 * MAX:   ...
 *
 * 分级缓冲策略:
 * 1> 首次缓冲要求最低(快速起播)
 * 2> 播放中逐步提高缓冲安全阈值
 * 3> 最终稳定在1秒缓冲保障流畅性
 *
 * 1. DEFAULT_FIRST_HIGH_WATER_MARK_IN_MS (6 ms)
用途:这个水位标记定义了播放器在初始缓冲阶段需要达到的高水位。
解决的问题:确保在开始播放之前,缓冲区中有足够的数据,以避免播放初期的卡顿。
2. DEFAULT_NEXT_HIGH_WATER_MARK_IN_MS (100 ms)
用途:在播放过程中,定义了播放器需要保持的高水位,以避免缓冲不足。
解决的问题:确保播放器在播放过程中持续有足够的数据,以防止因数据不足而导致的中断或卡顿。
3. DEFAULT_LAST_HIGH_WATER_MARK_IN_MS (500 ms)
用途:当播放器接近结尾时,定义了最后一个高水位标记,确保在接近播放结束时有足够的数据。
解决的问题:避免在接近播放结束时发生卡顿,确保用户能够顺利观看完毕。
 */
#ifdef UES_NEW_HIGH_WATER_MARK
// 首次缓冲50ms,首次缓冲达到50ms时长后开始播放
#define DEFAULT_FIRST_HIGH_WATER_MARK_IN_MS     (50)       //  50ms
// 后续缓冲200ms:播放中首次卡顿恢复缓冲
#define DEFAULT_NEXT_HIGH_WATER_MARK_IN_MS      (2 * 100)  //  200ms
// 最终阈值1s,稳定播放阶段的缓冲目标
#define DEFAULT_LAST_HIGH_WATER_MARK_IN_MS      (1 * 1000)  // 1000ms

检查缓冲,快速消费

c 复制代码
// 常规模式:平衡CPU消耗与响应速度
// 快速模式:用于起播/卡顿时更频繁检测
// 每 500 毫秒检查一次缓冲状态,确保播放器能够及时检测缓冲情况,避免播放中断,缓冲检查频率
// 过大:如果设置得过大,可能导致缓冲状态更新不及时,用户可能会遇到延迟或卡顿,
// 过小:频繁检查可能增加 CPU 占用,影响性能,尤其是在低性能设备上
// 常规检查间隔500ms
#define BUFFERING_CHECK_PER_MILLISECONDS        (500)
// 快速检查模式50ms
// 在快速缓冲模式下,每 50 毫秒检查一次缓冲状态。
// 解决的问题:用于处理网络波动或快速缓冲场景,确保流畅播放。
// 副作用:
// 过大:缓冲检测不够频繁,可能导致播放延迟。
// 过小:同样会增加系统负担,可能引起性能下降。
#define FAST_BUFFERING_CHECK_PER_MILLISECONDS   (50)
相关推荐
卸任5 小时前
Vibe coding工具使用心得
前端·ai编程
brien96 小时前
为什么模型标称 1M,Google Antigravity 聊到 256K 却突然失忆?我们给它做了个原生行内监控圆环
前端
修炼的dance6 小时前
录屏关键帧间隔多少合适?六档实测,2秒到10秒小了75.6%
前端
Lank_M6 小时前
关键帧从1秒拉到10秒,录屏小87.3%,摄像头小10.8%
前端
补码缩写6 小时前
用 ffprobe 拆录屏,八成字节在 29 个关键帧上
前端
百思不得小李6 小时前
一个复杂的动态表单是如何实现的
前端·设计模式
陈随易7 小时前
Cmder 停更 17 个月,2.0 支持 Windows Terminal
前端·后端·程序员
风骏时光牛马7 小时前
前沿视界
前端
kyriewen7 小时前
我扫了 814 个依赖,97 个还敢要 Node 10
前端·javascript·程序员
陈随易7 小时前
3000 万人在用的翻译插件,重写一遍就是一门生意
前端·后端·程序员