1.简介
最近项目需要rtsp协议流视频播放,也参考使用了主流的视频播放框架,诸如 饺子,GSY等,这类播放器实际上是对播放UI的封装,底层播放引擎都是基于或者扩展自ijkplayer的。经过学习与使用,使用这类视频UI框架能够美化我们的视频播放UI界面,更好的进行视频播放的控制,但对于饺子而言其基础引擎支持的编码格式有限,对于我们复杂的工程需求往往需要支持直播协议的rtsp,rtmp协议流,故而对于这类需求的开发者需要使用ijkplayer引擎,通过编译源码的方式生成so库,来支持达到特定协议的目的。后面也将详细的介绍mac下编译ijkplayer源码生成so库的详细步骤,以及特定的工程问题技巧,希望对各位有一定的帮助。
2.编译ijkplayer源码
2.1下载ijkPlayer源码
下载地址:https://github.com/Bilibili/ijkplayer,直接下载下来解压即可。
2.2 环境搭建
像基本的java JDK Android Studio我这里就不说明了,主要说明下用于编译源码的ndk的配置。
查看 /ijkplayer-master/android/contrib/tools/do-detect-env.sh 如下有一段会检查NDK版本,可以发现检索的是11 12 13 14的版本,然而AS默认提供的NDK下载支持一般都比较新,造成检索到的预期要求编译的不匹配,而报“You need the NDKr10e or later”。
case "$IJK_NDK_REL" in
11*|12*|13*|14*)
if test -d ${ANDROID_NDK}/toolchains/arm-linux-androideabi-4.9
then
echo "NDKr$IJK_NDK_REL detected"
else
echo "You need the NDKr10e or later"
exit 1
fi
;;
*)
echo "You need the NDKr10e or later"
exit 1
;;
esac
那么查看下AS的可以下载的NDK版本已经到了18了,明显不是我们想要的,那么需要我们自己去下载ndk,并进行环境变量的配置
ndk_14环境变量的下载
ndk下载地址: https://developer.android.google.cn/ndk/downloads/
请在历史ndk版本中选择版本14对应的mac版本进行下载。(文末有网盘下载)
ndk_14 环境变量配置
1.控制台命令行 开发配置文件:
cd ~
open -e .bash_profile
2.
# 变量名为ANDROID_NDK 路径填你解压到的路径即可
export ANDROID_NDK=/Users/apple/Library/Android/android-ndk-r14b
# 注册变量
export PATH=${PATH}:${ANDROID_NDK}
3.保存配置文件:
source .bash_profile
4. 检查ndk配置
ndk-build
效果如下即配置OK,重新启动一个控制台。
2.3 编译ijkplayer源码步骤
a. 打开 /ijkplayer-master/config/module-lite.sh
添加如下配置:用以支持rtsp流协议以及mpeg4硬解码
# 支持rtsp流
export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --enable-protocol=rtp"
export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --enable-protocol=tcp"
export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --enable-demuxer=rtsp"
export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --enable-demuxer=sdp"
export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --enable-demuxer=rtp"
# 支持mpeg4解码
export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --enable-decoder=mpeg4"
export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --enable-demuxer=mpeg4"
b. 创建ndk编译软链接
rm module.sh
ln -s module-lite.sh module.sh
c. 返回源码根目录执行清理
cd android/contrib
./compile-ffmpeg.sh clean
d. 返回源码根目录,下载ffmpeg
cd ~/ijkplayer-master
./init-android.sh
f. 执行编译
cd android/contrib
./compile-ffmpeg.sh clean
./compile-ffmpeg.sh all
cd ..
./compile-ijk.sh all
经过以上步骤,就会在/ijkplayer-master/android/ijkplayer 中的module中生成对应的so库,我们通过Android Studio打开 ijkplayer 会看到如下.so库:
OK了,这样我们就能播放对应的资源流视频了。(文末有编译好的线程的so库)
3. 饺子视频播放器中切换ijkplayer引擎
引入饺子播放器我这里就不涉及了,主要讲引擎切换,饺子默认的引擎支持的协议有限(硬解码格式),拓展的需要自己去使用ijkplayer引擎通过添加。
a. Gradle引入:
implementation 'tv.danmaku.ijk.media:ijkplayer-java:0.8.8'
implementation 'tv.danmaku.ijk.media:ijkplayer-armv7a:0.8.4'
implementation 'com.google.android.exoplayer:exoplayer:2.8.4'
b. 新建 JZMediaIjkplayer.java类,这是饺子demo里的,我直接给出来
public class JZMediaIjkplayer extends JZMediaInterface implements IMediaPlayer.OnPreparedListener, IMediaPlayer.OnVideoSizeChangedListener, IMediaPlayer.OnCompletionListener, IMediaPlayer.OnErrorListener, IMediaPlayer.OnInfoListener, IMediaPlayer.OnBufferingUpdateListener, IMediaPlayer.OnSeekCompleteListener, IMediaPlayer.OnTimedTextListener {
IjkMediaPlayer ijkMediaPlayer;
@Override
public void start() {
ijkMediaPlayer.start();
}
@Override
public void prepare() {
ijkMediaPlayer = new IjkMediaPlayer();
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "framedrop", 60);
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "max-fps", 0);
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "fps", 30);
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_CODEC, "skip_loop_filter", 48);
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "overlay-format", IjkMediaPlayer.SDL_FCC_YV12);
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "packet-buffering", 0);
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "fflags", "nobuffer");
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "max-buffer-size", 1024);
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "min-frames", 3);
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "start-on-prepared", 1);
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "probsize", "4096");
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "analyzeduration", "2000000");
ijkMediaPlayer.setOnPreparedListener(JZMediaIjkplayer.this);
ijkMediaPlayer.setOnVideoSizeChangedListener(JZMediaIjkplayer.this);
ijkMediaPlayer.setOnCompletionListener(JZMediaIjkplayer.this);
ijkMediaPlayer.setOnErrorListener(JZMediaIjkplayer.this);
ijkMediaPlayer.setOnInfoListener(JZMediaIjkplayer.this);
ijkMediaPlayer.setOnBufferingUpdateListener(JZMediaIjkplayer.this);
ijkMediaPlayer.setOnSeekCompleteListener(JZMediaIjkplayer.this);
ijkMediaPlayer.setOnTimedTextListener(JZMediaIjkplayer.this);
try {
ijkMediaPlayer.setDataSource(jzDataSource.getCurrentUrl().toString());
ijkMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
ijkMediaPlayer.setScreenOnWhilePlaying(true);
ijkMediaPlayer.prepareAsync();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void pause() {
ijkMediaPlayer.pause();
}
@Override
public boolean isPlaying() {
return ijkMediaPlayer.isPlaying();
}
@Override
public void seekTo(long time) {
ijkMediaPlayer.seekTo(time);
}
@Override
public void release() {
if (ijkMediaPlayer != null)
ijkMediaPlayer.release();
}
@Override
public long getCurrentPosition() {
return ijkMediaPlayer.getCurrentPosition();
}
@Override
public long getDuration() {
return ijkMediaPlayer.getDuration();
}
@Override
public void setSurface(Surface surface) {
ijkMediaPlayer.setSurface(surface);
}
@Override
public void setVolume(float leftVolume, float rightVolume) {
ijkMediaPlayer.setVolume(leftVolume, rightVolume);
}
@Override
public void onPrepared(IMediaPlayer iMediaPlayer) {
ijkMediaPlayer.start();
if (jzDataSource.getCurrentUrl().toString().toLowerCase().contains("mp3")) {
JZMediaManager.instance().mainThreadHandler.post(new Runnable() {
@Override
public void run() {
if (JzvdMgr.getCurrentJzvd() != null) {
JzvdMgr.getCurrentJzvd().onPrepared();
}
}
});
}
}
@Override
public void onVideoSizeChanged(IMediaPlayer iMediaPlayer, int i, int i1, int i2, int i3) {
JZMediaManager.instance().currentVideoWidth = iMediaPlayer.getVideoWidth();
JZMediaManager.instance().currentVideoHeight = iMediaPlayer.getVideoHeight();
JZMediaManager.instance().mainThreadHandler.post(new Runnable() {
@Override
public void run() {
if (JzvdMgr.getCurrentJzvd() != null) {
JzvdMgr.getCurrentJzvd().onVideoSizeChanged();
}
}
});
}
@Override
public void onCompletion(IMediaPlayer iMediaPlayer) {
JZMediaManager.instance().mainThreadHandler.post(new Runnable() {
@Override
public void run() {
if (JzvdMgr.getCurrentJzvd() != null) {
JzvdMgr.getCurrentJzvd().onAutoCompletion();
}
}
});
}
@Override
public boolean onError(IMediaPlayer iMediaPlayer, final int what, final int extra) {
JZMediaManager.instance().mainThreadHandler.post(new Runnable() {
@Override
public void run() {
if (JzvdMgr.getCurrentJzvd() != null) {
JzvdMgr.getCurrentJzvd().onError(what, extra);
}
}
});
return true;
}
@Override
public boolean onInfo(IMediaPlayer iMediaPlayer, final int what, final int extra) {
JZMediaManager.instance().mainThreadHandler.post(new Runnable() {
@Override
public void run() {
if (JzvdMgr.getCurrentJzvd() != null) {
if (what == MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START) {
JzvdMgr.getCurrentJzvd().onPrepared();
} else {
JzvdMgr.getCurrentJzvd().onInfo(what, extra);
}
}
}
});
return false;
}
@Override
public void onBufferingUpdate(IMediaPlayer iMediaPlayer, final int percent) {
JZMediaManager.instance().mainThreadHandler.post(new Runnable() {
@Override
public void run() {
if (JzvdMgr.getCurrentJzvd() != null) {
JzvdMgr.getCurrentJzvd().setBufferProgress(percent);
}
}
});
}
@Override
public void onSeekComplete(IMediaPlayer iMediaPlayer) {
JZMediaManager.instance().mainThreadHandler.post(new Runnable() {
@Override
public void run() {
if (JzvdMgr.getCurrentJzvd() != null) {
JzvdMgr.getCurrentJzvd().onSeekComplete();
}
}
});
}
@Override
public void onTimedText(IMediaPlayer iMediaPlayer, IjkTimedText ijkTimedText) {
}
}
c. 加入so库
将上一步编译的so库,复制到我们现在的项目中
d. 使用
Jzvd.setMediaInterface(new JZMediaIjkplayer());
mJzVideoPlayer.setUp(url,
"水产监控",
Jzvd.SCREEN_WINDOW_NORMAL);
好了已经支持我们自己定义的引擎了,那么对应的协议编码问题也是能解决的。
4. 积累的一个排错技巧
阐述下我的项目问题,就是我们后台提供给我的是rtsp协议流的直播视频,在主流播放器上是可以播放的,但是我一开始的播放器是不支持的,其实问题不是出现在协议上,出现在的是编解码的问题。
下面看下BUG
下面我们解决下 No codec could be found with id 13 以及 tv.danmaku.ijk.media.player.IjkMediaPlayer: Error (-10000,0)
这里提示的是我们缺少硬解码支持,那么怎么添加对应的解码支持呢。
ijkPlayer-master文件夹搜索 avcodec.h 文件,如下所示。
从video codecs开始,ID从1开始自增,那么13对应的是 AV_CODEC_ID_MPEG4,
所以在编译生成.so文件前在 module-lite.sh中加入如下:
# 支持mpeg4解码
export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --enable-decoder=mpeg4"
export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --enable-demuxer=mpeg4"
enum AVCodecID {
AV_CODEC_ID_NONE,
/* video codecs */
AV_CODEC_ID_MPEG1VIDEO,
AV_CODEC_ID_MPEG2VIDEO, ///< preferred ID for MPEG-1/2 video decoding
#if FF_API_XVMC
AV_CODEC_ID_MPEG2VIDEO_XVMC,
#endif /* FF_API_XVMC */
AV_CODEC_ID_H261,
AV_CODEC_ID_H263,
AV_CODEC_ID_RV10,
AV_CODEC_ID_RV20,
AV_CODEC_ID_MJPEG,
AV_CODEC_ID_MJPEGB,
AV_CODEC_ID_LJPEG,
AV_CODEC_ID_SP5X,
AV_CODEC_ID_JPEGLS,
AV_CODEC_ID_MPEG4,
AV_CODEC_ID_RAWVIDEO,
AV_CODEC_ID_MSMPEG4V1,
AV_CODEC_ID_MSMPEG4V2,
AV_CODEC_ID_MSMPEG4V3,
AV_CODEC_ID_WMV1,
AV_CODEC_ID_WMV2,
AV_CODEC_ID_H263P,
AV_CODEC_ID_H263I,
AV_CODEC_ID_FLV1,
AV_CODEC_ID_SVQ1,
AV_CODEC_ID_SVQ3,
AV_CODEC_ID_DVVIDEO,
AV_CODEC_ID_HUFFYUV,
AV_CODEC_ID_CYUV,
AV_CODEC_ID_H264,
AV_CODEC_ID_INDEO3,
AV_CODEC_ID_VP3,
AV_CODEC_ID_THEORA,
AV_CODEC_ID_ASV1,
AV_CODEC_ID_ASV2,
AV_CODEC_ID_FFV1,
AV_CODEC_ID_4XM,
AV_CODEC_ID_VCR1,
AV_CODEC_ID_CLJR,
AV_CODEC_ID_MDEC,
。。。。。
那么以后我们也可以通过报错信息来查询缺少哪一个硬解码,自由的进行编译生成库。
参考博客
编译ijkplayer,并添加rtsp、rtmp支持,解决无法播放、unknown、延迟问题
ndk_14_mac 网盘下载:
链接:https://pan.baidu.com/s/1MCWnRPBxBTZMhi5RKAnv1A 密码:0tot
已编译的支持rtsp协议so库的demo地址: