「前提」前一段时间在安卓定制项目中遇到了平板客户端直播延迟达到15s的情况,经过长时间调研现将ijkplayer拉流优化做相关总结
安卓客户端播放器的相关耗时和优化
ijkpler是基于开源框架FFmpeg二次开发的播放框架.
当设置一个数据源给播放器后,播放器需要「open_input」这个数流,设置相关的「options」,与服务端建立长连接,找到对应视频流和音频流。解析视频与音频「Packet」,解码「Packet」 转换成「Frame」.由安卓「nativeWindow」渲染画面「OpenSLES」播放声音.
我们可以按照安卓视频播放的流程进行如下方向优化:
拉流时网络请求用时 解复用 (avformat_find_stream_info(formatContext, NULL)
用时解码用时 渲染到安卓设备屏幕用时
数据请求
网络比较畅通的情况视频流能及时发送,不会在缓冲区阻塞,直播画面流畅,网络不好的情况下,要合理设置缓冲区,做好丢帧,降码率方案. 因为音视频同步一般以音频时钟为基准,人们对音频更加敏感,所以我们优先丢掉视频队列的包。无论使用那种播放协议,都主要是基于tcp的,必然遵守tcp协议的特点,通过如下优化。
「下面部分是优化的点:」
static int tcp_read(URLContext *h, uint8_t *buf, int size)
{
av_log(NULL, AV_LOG_INFO, "tcp_read begin %d\n", size);
TCPContext *s = h->priv_data;
int ret;
if (!(h->flags & AVIO_FLAG_NONBLOCK)) {
ret = ff_network_wait_fd_timeout(s->fd, 0, h->rw_timeout, &h->interrupt_callback);
if (ret)
return ret;
}
ret = recv(s->fd, buf, size, 0);
if (ret == 0)
return AVERROR_EOF;
//if (ret > 0)
// av_application_did_io_tcp_read(s->app_ctx, (void*)h, ret);
av_log(NULL, AV_LOG_INFO, "tcp_read end %d\n", ret);
return ret < 0 ? ff_neterrno() : ret;
}
我们可以把上面两行注释掉,因为在ff_network_wait_fd_timeout等回来后,数据可以放到buf中,下面av_application_did_io_tcp_read就没必要去执行了。原来每次ret>0,都会执行av_application_did_io_tcp_read这个函数。
解复用耗时
拿到「AVFormatContext」,分离视频流与音频流时,首先需要匹配对应demuxer,ffmpeg的av_find_input_format和avformat_find_stream_info这两个函数执行花费时间比较长,前者简单理解就是打开某中请求到数据,后者就是探测流的一些信息,做一些样本检测,读取一定长度的码流数据,来分析码流的基本信息,为视频中各个媒体流的 AVStream 结构体填充好相应的数据。这个函数中做了查找合适的解码器、打开解码器、读取一定的音视频帧数据、尝试解码音视频帧等工作,基本上完成了解码的整个流程。该流程比较耗时
这两个函数调用都在ff_ffplay.c的read_thread函数中:
if (ffp->iformat_name) {
av_log(ffp, AV_LOG_INFO, "av_find_input_format noraml begin");
is->iformat = av_find_input_format(ffp->iformat_name);
av_log(ffp, AV_LOG_INFO, "av_find_input_format normal end");
}
else if (av_stristart(is->filename, "rtmp", NULL)) {
av_log(ffp, AV_LOG_INFO, "av_find_input_format rtmp begin");
is->iformat = av_find_input_format("flv");
av_log(ffp, AV_LOG_INFO, "av_find_input_format rtmp end");
ic->probesize = 4096;
ic->max_analyze_duration = 2000000;
ic->flags |= AVFMT_FLAG_NOBUFFER;
}
av_log(ffp, AV_LOG_INFO, "avformat_open_input begin");
err = avformat_open_input(&ic, is->filename, is->iformat, &ffp->format_opts);
av_log(ffp, AV_LOG_INFO, "avformat_open_input end");
if (err < 0) {
print_error(is->filename, err);
ret = -1;
goto fail;
}
ffp_notify_msg1(ffp, FFP_MSG_OPEN_INPUT);
if (scan_all_pmts_set)
av_dict_set(&ffp->format_opts, "scan_all_pmts", NULL, AV_DICT_MATCH_CASE);
if ((t = av_dict_get(ffp->format_opts, "", NULL, AV_DICT_IGNORE_SUFFIX))) {
av_log(NULL, AV_LOG_ERROR, "Option %s not found.\n", t->key);
#ifdef FFP_MERGE
ret = AVERROR_OPTION_NOT_FOUND;
goto fail;
#endif
}
is->ic = ic;
if (ffp->genpts)
ic->flags |= AVFMT_FLAG_GENPTS;
av_format_inject_global_side_data(ic);
if (ffp->find_stream_info) {
AVDictionary **opts = setup_find_stream_info_opts(ic, ffp->codec_opts);
int orig_nb_streams = ic->nb_streams;
do {
if (av_stristart(is->filename, "data:", NULL) && orig_nb_streams > 0) {
for (i = 0; i < orig_nb_streams; i++) {
if (!ic->streams[i] || !ic->streams[i]->codecpar || ic->streams[i]->codecpar->profile == FF_PROFILE_UNKNOWN) {
break;
}
}
if (i == orig_nb_streams) {
break;
}
}
ic->probesize=100*1024;
ic->max_analyze_duration=5*AV_TIME_BASE;
ic->fps_probe_size=0;
av_log(ffp, AV_LOG_INFO, "avformat_find_stream_info begin");
err = avformat_find_stream_info(ic, opts);
av_log(ffp, AV_LOG_INFO, "avformat_find_stream_info end");
} while(0);
ffp_notify_msg1(ffp, FFP_MSG_FIND_STREAM_INFO);
这样,avformat_find_stream_info 的耗时就可以缩减到 100ms 以内。
解码耗时和渲染出图耗时
最后就是解码耗时和渲染出图耗时,这块优化空间很少,大头都在前面。
直播一般都会做首屏秒开处理,当手机信号比较弱,遇到网络波动时,缓冲区没有足够的数据刷到播放队列就会视频就会产生卡顿,ijkplayer也做了比较好的缓冲机制来处理这个问题.
「主要是有几个宏控制:」
把BUFFERING_CHECK_PER_MILLISECONDS设置为50,默认是500:
#define DEFAULT_HIGH_WATER_MARK_IN_BYTES (30 * 1024)
#define DEFAULT_FIRST_HIGH_WATER_MARK_IN_MS (100)
#define DEFAULT_NEXT_HIGH_WATER_MARK_IN_MS (1 * 1000)
#define DEFAULT_LAST_HIGH_WATER_MARK_IN_MS (1 * 1000)
#define BUFFERING_CHECK_PER_BYTES (512)
#define BUFFERING_CHECK_PER_MILLISECONDS (50)
「修改播放器的option参数」用比较低的分辨率来减少数据量也能达到流畅的目的
/丢帧阈值
mediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "framedrop", 30);
//视频帧率
mediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "fps", 30);
//环路滤波
mediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_CODEC, "skip_loop_filter", 48);
//设置无packet缓存
mediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "packet-buffering", 0);
mediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "fflags", "nobuffer");
//不限制拉流缓存大小
mediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "infbuf", 1);
//设置最大缓存数量
mediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "max-buffer-size", 1024);
//设置最小解码帧数
mediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "min-frames", 3);
//启动预加载
mediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "start-on-prepared", 1);
//设置探测包数量
mediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "probsize", "4096");
//设置分析流时长
mediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "analyzeduration", "2000000");




