ijkplayer框架的集成( 从开始到优化秒开)
ijkplayer是bibiliB站开源的一个三方,面向对象开发。
苹果提供了:AVPlayer播放不了直播文件。需要自己去基于ffmpeg播放。
ijkplayer
框架是专门用来做 视频直播 的开源框架,基于ffmpeg
,同时支持 Android 和 iOS 平台。对于 App 中的直播功能,集成
ijkplayer
,那么就算完成一半的工程了。接下来,只要获取到 拉流 URL,就能实现视频直播
功能。
2.克隆ijkplayer到桌面
cd Desktop/ git clone https://github.com/Bilibili/ijkplayer.git ijkplayer
或者直接下载zip.解压,切到解压的文件夹内
(请忽略背景😂,年轻时候弄得电脑。)
打开 IJKMediaDemo,并编译
提示: 'libavformat/avformat.h' file not found
ijkplayer
的 README.md。libavformat
是 ffmpeg
框架中的库,而 ijkplayer
又是基于 ffmpeg
框架的,因此需要编译 ffmpeg
。3.下载ffmpeg
cd ijkplayer
git checkout -B latest k0.8.8 ./init-ios.sh (下载ffmpeg)
注:下载最好使用FQ软件,本人使用蓝灯设置全局代理。下载很快,否则容易失败。
要下载一会,下载完成之后,在ios目录下就有了ffmpeg文件:
这时候我们的 ffmpeg 就下载好了,再次运行 IJKMediaDemo
发现还是报上面的错误,因为执行init-ios.sh,只是下载ffmpeg源码,但是源码并没有参与编译,需要把源码编译成.a文件:
4、编译 ffmpeg 库
进入 ios 文件的目录中
cd ios ./compile-ffmpeg.sh clean ./compile-ffmpeg.sh all
注:1.执行 ./compile-ffmpeg.sh clean
,目的是删除一些文件和文件夹,为编译ffmpeg.sh做准备,在编译ffmpeg.sh的时候,会自动创建刚刚
删除的那些文件,为避免文件名冲突,因此在编译ffmpeg.sh之前先删除等会会自动创建的文件夹或者文件
2.执行 ./compile-ffmpeg.sh all
目的是编译各个平台的ffmpeg库,并生成所以平台的通用库。
可能遇到的问题:
warning: optimization flag '-fomit-frame-pointer' is not supported for target 'armv7' [-Wignored-optimization-argument]
AS libavcodec/arm/aacpsdsp_neon.o
./libavutil/arm/asm.S:50:9: error: unknown directive
.arch armv7-a
^
make: *** [libavcodec/arm/aacpsdsp_neon.o] Error 1
make: *** Waiting for unfinished jobs....
不支持arm7
那么删除这个编译目标
解决办法:
打开ios目录下这个 compile-ffmpeg.sh 文件
第24行 改为: FF_ALL_ARCHS_IOS8_SDK="arm64 i386 x86_64"
第120行 改为: if [ "$FF_TARGET" = "armv7s" -o "$FF_TARGET" = "arm64" ]; then
第159行 改为: echo " compile-ffmpeg.sh arm64|i386|x86_64"
5.这时候编译成功已经可以运行列子了
如需打包framework参考:文章三
6.优化加速
延迟优化
发现拉流存在3-6秒延迟,这个根据七牛直播云大牛讲解的一些问题点。进行优化。
丢包处理方案参考
a),有音频流和视频流,或者只有音频流情况下,当audioq达到一定的duration,就丢掉前面一部分数据包,因为默认是AV_SYNC_AUDIO_MASTER,视频会追上来。
b),只有视频流情况,当videoq达到一定的duration,就丢掉前面一部分数据包。
开始本人修改丢失帧数过大,虽然延迟不存在了,但是同时测试局域网出现供不应求现象,就是画面过一混会卡顿。因此还是修改缓存时间比较好。
ff_ffplay.c read_thread 线程中,在每次 av_read_frame后去判断缓存队列有没有达到最大时长。这里需要把原来的realtime设置为0。
别的不说直接上代码
通过修改源文件,因为ijkplayer实际上是基于ffplay.c实现的:
ijkmedia>ijkplayer>ff_ffplay.c这个文件
static double vp_duration(VideoState *is, Frame *vp, Frame *nextvp) { if(vp->serial == nextvp->serial) { doubleduration = nextvp->pts - vp->pts; if(isnan(duration) || duration <=0|| duration > is->max_frame_duration) return vp->duration; else return duration; }else{ return 0.0; } }
改成直接返回duration
static double vp_duration(VideoState*is,Frame*vp,Frame*nextvp) { return vp->duration; }
接着改staticintffplay_video_thread这个方法:
static int ffplay_video_thread(void*arg){ FFPlayer*ffp = arg; VideoState*is = ffp->is; AVFrame*frame =av_frame_alloc(); doublepts; doubleduration; intret; AVRationaltb = is->video_st->time_base; //注释如下一行代码 //AVRational frame_rate = av_guess_frame_rate(is->ic, is->video_st, NULL); //......省略部分代码 //注释如下一行代码 //duration = (frame_rate.num && frame_rate.den ? av_q2d((AVRational) {frame_rate.den, frame_rate.num}) : 0); //直接这里写出 duration=0.01; //........ }
延迟明显降低,高分辨率开启硬解码,不支持的话会自动切换到软解,就算开启mediacodec,如果设备不支持,显示的解码器也是avcodec软解
参数配合设置如下(由于c码,安卓苹果参数基本一样)
修改后基本延迟很低了1秒左右。然后
直接上修改后的代码参数配合编译的包
//播放前的探测Size,默认是1M, 改小一点会出画面更快 [options setFormatOptionIntValue:1024 * 16 forKey:@"probesize"]; //播放前的探测时间 [options setFormatOptionIntValue:50000 forKey:@"analyzeduration"]; //默认好像是硬解开启软解 [options setPlayerOptionIntValue:0 forKey:@"videotoolbox"]; //解码参数,画面更清晰 [options setCodecOptionIntValue:IJK_AVDISCARD_DEFAULT forKey:@"skip_loop_filter"]; //这个目前理解应该是丢帧数设置 [options setCodecOptionIntValue:IJK_AVDISCARD_DEFAULT forKey:@"skip_frame"]; [options setPlayerOptionIntValue:3000 forKey:@"max_cached_duration"]; // 最大缓存大小是3秒,可以依据自己的需求修改 [options setPlayerOptionIntValue:1 forKey:@"infbuf"]; // 无限读 [options setPlayerOptionIntValue:0 forKey:@"packet-buffering"]; // 关闭播放器缓冲
7.如此设置已经基本可以实现0延迟播放了。
集成到自己项目见:其他一些优化本人也做了一些尝试。详情见下篇。
安卓端本人根据参数翻译代码设置如下:
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "probesize", 1024 * 16); ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "analyzeduration", 50000); ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_CODEC, "skip_loop_filter", 0); ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_CODEC, "skip_frame", 0); ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "max_cached_duration", 3000); ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "infbuf", 1); ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "packet-buffering", 0);
另外如果是后台播放可以参考改变设置
// Param for playback
//ios
[options setPlayerOptionIntValue:0 forKey:@"max_cached_duration"]; [options setPlayerOptionIntValue:0 forKey:@"infbuf"]; [options setPlayerOptionIntValue:1 forKey:@"packet-buffering"];
//android
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "max_cached_duration", 0); ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "infbuf", 0);
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "packet-buffering", 1);
另外一些其他参数参考定义如下,跟据需要修改。
//开启硬件解码 [options setPlayerOptionIntValue:1 forKey:@"videotoolbox"]; // 设置音量大小,256为标准音量。(要设置成两倍音量时则输入512,依此类推) [options setPlayerOptionIntValue:512 forKey:@"vol"]; // 最大fps [options setPlayerOptionIntValue:30 forKey:@"max-fps"]; // 跳帧开关,如果cpu解码能力不足,可以设置成5,否则 // 会引起音视频不同步,也可以通过设置它来跳帧达到倍速播放 [options setPlayerOptionIntValue:0 forKey:@"framedrop"]; // 指定最大宽度,我没试过 [options setPlayerOptionIntValue:960 forKey:@"videotoolbox-max-frame-width"]; // 自动转屏开关,我没试过 [options setFormatOptionIntValue:0 forKey:@"auto_convert"]; // 重连次数, 我没试过 [options setFormatOptionIntValue:1 forKey:@"reconnect"]; // 超时时间,timeout参数只对http设置有效,若果你用rtmp设置timeout,ijkplayer内部会忽略timeout参数。rtmp的timeout参数含义和http的不一样。 [options setFormatOptionIntValue:30 * 1000 * 1000 forKey:@"timeout"]; // 帧速率(fps) 我没试过(可以改,确认非标准桢率会导致音画不同步,所以只能设定为15或者29.97) [options setPlayerOptionIntValue:29.97 forKey:@"r"];
Note: 这里有个比较有用的参数,skip_loop_filter
// for codec option 'skip_loop_filter' and 'skip_frame' typedef enum IJKAVDiscard { /* We leave some space between them for extensions (drop some * keyframes for intra-only or drop just some bidir frames). */ IJK_AVDISCARD_NONE =-16, ///< discard nothing IJK_AVDISCARD_DEFAULT = 0, ///< discard useless packets like 0 size packets in avi IJK_AVDISCARD_NONREF = 8, ///< discard all non reference IJK_AVDISCARD_BIDIR = 16, ///< discard all bidirectional frames IJK_AVDISCARD_NONKEY = 32, ///< discard all frames except keyframes IJK_AVDISCARD_ALL = 48, ///< discard all } IJKAVDiscard; 前面两个都看得懂 第三个是抛弃非参考帧(I帧) 第四个是抛弃B帧 第五个是抛弃除关键帧以外的,比如B,P帧 第六个是抛弃所有的帧,这我就奇怪了,之前Android默认的就是48,难道把所有帧都丢了? 那就没有视频帧了, 所以应该不是这么理解, 应该是skip_loop_filter和skip_frame的对象要过滤哪些帧类型。 skip_loop_filter这个是解码的一个参数,叫环路滤波, 设置成48和0,图像清晰度对比,0比48清楚,理解起来就是, 0是开启了环路滤波,过滤的是大部分, 而48基本没启用环路滤波,所以清晰度更低,但是解码性能开销小 skip_loop_filter(环路滤波)简言之: a:环路滤波器可以保证不同水平的图像质量。 b:环路滤波器更能增加视频流的主客观质量,同时降低解码器的复杂度。
具体参考:
http://blog.csdn.net/h514434485/article/details/52241778
http://www.cnblogs.com/TaigaCon/p/5500110.html
skip_frame我没完全理解意思,应该是等同上面这个类似。
另外安卓参数基本一样
接口不同而已,注意属性的分类
eg:OPT_CATEGORY_PLAYER
//可以打开h265硬解; ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-hevc", 1); //开启mediacodec硬解 ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec", 1); ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-auto-rotate", 1); ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-handle-resolution-change", 1);
下篇
ijkplayer的一些优化
参考前人经验更新
轮子使用中
1、https://blog.csdn.net/ssy_1992/article/details/79191727 //编译流程
或 https://www.jianshu.com/p/9a69af13835e
注意FQ下载编译。
2、优化
觉得有用的同学点个关注,或者留言评论区,看到邮件提示消息尽快回复。