【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)
相关推荐
步行cgn12 分钟前
Vue 中的数据代理机制
前端·javascript·vue.js
GH小杨16 分钟前
JS之Dom模型和Bom模型
前端·javascript·html
星月心城1 小时前
JS深入之从原型到原型链
前端·javascript
你的人类朋友2 小时前
🤔Token 存储方案有哪些
前端·javascript·后端
烛阴2 小时前
从零开始:使用Node.js和Cheerio进行轻量级网页数据提取
前端·javascript·后端
liuyang___2 小时前
日期的数据格式转换
前端·后端·学习·node.js·node
贩卖纯净水.3 小时前
webpack其余配置
前端·webpack·node.js
码上奶茶3 小时前
HTML 列表、表格、表单
前端·html·表格·标签·列表·文本·表单
抹茶san3 小时前
和 Trae 一起开发可视化拖拽编辑项目(1) :迈出第一步
前端·trae