文章目录
前言
众所周知使用rtmp协议进行直播,在网络不好的情况会累计延迟,网络条件恢复延迟也不降低。因此我们要根据rtmp协议的特性,使用一定的策略,在网络恢复时优化rtmp协议延迟的累计,实现低延迟直播播放器
rtmp产生延迟的原因
rtmp基于tcp协议传输媒体数据,在弱网情况下也不会丢包,所以当网络状态比较差时,流媒体服务器会将数据包缓存起来,导致直播延迟。在网络回复后一起发送给服务端。
如何减少播放器播放延迟
- 检测到视频包队列缓冲区超过一定阈值后,播放器断开重连。优点简单粗暴,缺点用户体验不够好,重连会产生卡顿丢失一部分的直播内容。
- 检测到视频包队列缓冲区超过一定阈值后,开启倍速播放追赶直播进度,直到控制到延迟在指定时间内。优点过度比较平滑,用户体验比较好。缺点实现相对复杂。
追帧策略定义和工程实现细节
直播播放器追帧策略
- 定义播放器视频缓冲区总时长为 video_queue_time
- 定义视频缓存区最大时长 max_video_cached_duration
- 定义网络抖动大小 network_jitter_time
- 如果video_queue_time 大于 max_video_cached_duration + network_jitter_time,设置播放器1.1 or 1.2倍速播放
- 如果video_queue_time < max_video_cached_duration,设置播放器1.0倍速播放
基于ijkplayer1的工程实现
修改文件 ijkplayer-android\ijkmedia\ijkplayer\ff_ffplay_def.h
- MIN_FRAMES 需要改成1000,否则默认的包队列大小不够容纳网络恢复后读取的AVPacket数量
// ffplay.c
//校验是否有必要继续向缓冲区中添加 AVPacket 默认MIN_FRAMES 25
static int stream_has_enough_packets(AVStream *st, int stream_id, PacketQueue *queue)
{
return stream_id < 0 ||
queue->abort_request ||
(st->disposition & AV_DISPOSITION_ATTACHED_PIC) ||
queue->nb_packets > MIN_FRAMES && (!queue->duration || av_q2d(st->time_base) * queue->duration > 1.0);
}
- struct VideoState 添加三个字段,如下所示
#define MIN_FRAMES 1000
// struct VideoState 添加三个字段
typedef struct VideoState {
...
int max_delay_ms; // 默认: 5000ms
int network_jitter_ms; // 默认: 500ms
float new_play_rate; //默认: 1.2
...
}
ff_ffplay.h添加一个接口
ijkplayer-android\ijkmedia\ijkplayer\ff_ffplay.h
// 设置视频缓冲区最大延迟,抖动时长,倍速播放速度
void ffp_set_maxdelay_jitter_palyrate(FFPlayer *ffp, int max_delay_ms, int network_jitter_ms, float new_play_rate)
{
assert(ffp);
VideoState *is = ffp->is;
if (!is)
return;
is->max_delay_ms = max_delay_ms;
is->network_jitter_ms = network_jitter_ms;
is->new_play_rate = new_play_rate;
}
ff_ffplay.c添加两个辅助函数
ijkplayer-android\ijkmedia\ijkplayer\ff_ffplay.c
// 获取视频队列缓冲区时长
static int64_t get_video_queue_cached_duration(FFPlayer *ffp)
{
return is->videoq.duration;
}
// 控制视频缓冲区最大延迟在 [max_delay_ms, max_delay_ms+network_jitter_ms) 区间浮动
// 设置max_delay_ms = 0 不开启追帧策略
static void control_max_delay_duration(FFPlayer *ffp, int max_delay_ms, int network_jitter_ms, float new_play_rate)
{
if (max_delay_ms == 0)
return;
VideoState *is = (VideoState*)handle;
uint32_t cached_duration = get_video_queue_cached_duration(is);
if (cached_duration > max_delay_ms + network_jitter_ms && ffp->pf_playback_rate == 1.0)
{
//设置倍速播放 建议1.1 or 1.2 倍速
ffp_set_playback_rate(ffp, new_play_rate) ;
}
else if(cached_duration <= max_delay_ms && ffp->pf_playback_rate != 1.0)
{
//恢复正常播放速度
ffp_set_playback_rate(ffp, 1.0);
}else{
;// do nothing...
}
}
read_thread线程控制播放器最大缓冲区大小
//在解封装线程的for循环里面调用 control_max_delay_duration
//========read_thread============
static int read_thread(void *arg)
{
...
for(;;)
{
...
ret = av_read_frame(ic, pkt);
...
if(is->max_delay_ms > 0)
{
control_max_delay_duration(ffp, is->max_delay_ms, is->network_jitter_ms, is->new_play_rate);
}
}
}
总结
基于ijkplayer播放器只要稍作修改,就可以在网络恢复时减少rtmp的累计延迟。ijkplayer本质上是基于ffplay的二次开发,只要让ffplay支持倍速播放,也可以很容易的添加追帧策略,开发我们的低延迟直播播放器。
推荐免费直播学习课程: c/c++Linux后台服务器开发高级架构师学习视频
音视频流媒体权威资料整理,500+份文章,论文,视频,实践项目,协议,业界大神名单
https://github.com/bilibili/ijkplayer ↩︎