IJKPlayer编译及其应用
1.IJKPlayer简介
简介
IjkPlayer 是BiliBili公司维护的一个开源工程,是基于ffmpeg开发的一个播放器软件,目前支持Android和iOS两种平台,ffmpeg堪称自由软件中最完备的一套多媒体支持库,它几乎实现了所有当下常见的数据封装格式、多媒体传输协议以及音视频编解码器,所以ijkplayer采用ffmpeg作为音视频播放器的基础也是可以理解的; ffmpeg中提供了ffplay 和 SDL(Simple DirectMedia Layer) 功能模块,是ffmpeg 视频播放的集大成;整个ijkplayer就是以ffplay为基础展开的;IjkPlayer的架构以FFplay为核心,FFplay基于FFmpeg,FFmpeg具备强大的扩展性,所以Ijkplayer的拓展能力主要是FFmpeg的拓展能力;
优缺点
优点:
1.ijkplayer结构比较简单,基本以ffplay展开,开发难度较小;
2.ijkplayer包比VLC小一点,编译配置可裁剪,方便控制安装包大小;
3.持硬件加速解码,更加省电
缺点:
1.ijkplayer可扩展性较差,基本上没有提供modules供开发者二次开发;
2.ijkplayer 官方维护力度小,版本更新很慢;
2.环境搭建
Ubuntu系统搭建
- 下载VMware Workstation Pro虚拟机并安装。
- 下载Ubuntu 系统并安装 链接: 下载地址.
- 安装流程百度一把
JDK安装
- 下载jdk 链接: 下载地址.
- 选择Linux系统版本的jdk,下载后并在相应的目录解压 例如:/home/zxy/Android/jdk/ zxy是自己的用户名
- 配置环境变量,Ctrl+Alt+T 打开终端输入:
sudo gedit /etc/profile
在打开的文件中配置下面参数并保存
export JAVA_HOME=/home/zxy/Android/jdk/jdk1.8.0_111
export JRE_HOME=${JAVA_HOME}/jre
export CLASSPATH=.:${JAVA_HOME}/lib:${JRE_HOME}/lib
export PATH=${JAVA_HOME}/bin:$PATH
最后执行一下
source /etc/profile
最后输入 java -version 查看一下是否配置成功
NDK安装
- 下载ndk 链接: 下载地址.
- 选择Linux系统版本的ndk,下载后并在相应的目录解压 ,ndk版本有限制不能太高也不能太低不然会报错 (r10以上)
- 配置环境变量,Ctrl+Alt+T 打开终端输入:
sudo gedit /etc/profile
在打开的文件中配置下面参数并保存
#NDK
export ANDROID_NDK=/home/zxy/Android/ndk/android-ndk-r13-linux-x86_64/android-ndk-r13
#export ANDROID_NDK=/home/zxy/Android/ndk/android-ndk-r23b-linux/android-ndk-r23b
export PATH=$PATH:$ANDROID_NDK
最后执行一下
source /etc/profile
最后输入 ./ndk-build 查看一下是否配置成功
SDK安装
- 下载sdk 链接: 下载地址.
- 选择Linux系统版本的sdk,下载后并在相应的目录解压
- 配置环境变量,Ctrl+Alt+T 打开终端输入:
sudo gedit /etc/profile
在打开的文件中配置下面参数并保存
#SDK
export ANDROID_SDK=/home/zxy/Android/sdk/android-sdk_r24.4.1-linux/android-sdk-linux
export PATH=${PATH}:$ANDROID_SDK/tools:$ANDROID_SDK/platform-tools
最后执行一下
source /etc/profile
工具安装
dev-sidecar 配置是为了顺利clone 代码不然 大概率会出现443的错误
dev-sidecar安装成功后直接打开后安装证书

选择开启项

查看端口和证书路径

配置代理端口和证书,这一步很重要
git config --global http.proxy "127.0.0.1:31181" //31181 是自己工具中的端口号
需要手动导入一下证书到系统中
root@ubuntu:/home/zxy/.dev-sidecar# cp dev-sidecar.ca.crt /usr/local/share/ca-certificates/
root@ubuntu:/home/zxy/.dev-sidecar# cp dev-sidecar.ca.key.pem /usr/local/share/ca-certificates
root@ubuntu:/home/zxy/.dev-sidecar# update-ca-certificates
- 安装git和yasm,在终端中分别输入
sudo apt-get install git
sudo apt-get install yasm
3.代码拉取和编译
- clone ijkplayer 代码:
sudo git clone https://github.com/Bilibili/ijkplayer ijkplayer
- 初始化,初始化过程主要在下载ffmpeg的代码,代码包比较大,如果没有配置dev-sidecar 大概率是443错误 无法成功下载代码
cd ijkplayer
./init-android.sh
./init-android-openssl.sh // 初始化ssl
- 配置编译
cd config
rm module.sh
ln -s module-default.sh module.sh
在module-default.sh中可以根据自己的需求去配置方案,这里比较重要的是要加上一个配置,不然编译ffmpeg时会报错误
export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --disable-linux-perf"
- 完成编译
cd android/contrib
./compile-openssl.sh clean
./compile-ffmpeg.sh clean
./compile-openssl.sh all // 编译openssl库
./compile-ffmpeg.sh all // 编译ffmpeg库
cd..
./compile-ijk.sh all // 编译ijk库
编译成功的截图
4.编译遇到问题及其解决办法
- clone 代码失败 返回443 等问题,需要安装一个 dev-sidecar 设置代理后解决
- ndk版本不支持问题,ndk版本不能太高不然会报错
- 编译ffmpeg 报错问题,需要配置module.sh 屏蔽
#export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --disable-ffserver"
#export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --disable-vda"
解决 Unknown option “–disable-ffserver” 错误
4. 编译ffmpeg 报错问题,需要配置module-default.sh
export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --disable-linux-perf"
解决:
libavcodec/hevc_mvs.c: In function 'derive_spatial_merge_candidates':
libavcodec/hevc_mvs.c:208:15: error: 'y0000000' undeclared (first use in this function)
((y ## v) >> s->ps.sps->log2_min_pu_size))
^
libavcodec/hevc_mvs.c:204:14: note: in definition of macro 'TAB_MVF'
tab_mvf[(y) * min_pu_width + x]
^
libavcodec/hevc_mvs.c:274:16: note: in expansion of macro 'TAB_MVF_PU'
(cand && !(TAB_MVF_PU(v).pred_flag == PF_INTRA))
^
libavcodec/hevc_mvs.c:368:23: note: in expansion of macro 'AVAILABLE'
is_available_b0 = AVAILABLE(cand_up_right, B0) &&
^
libavcodec/hevc_mvs.c:208:15: note: each undeclared identifier is reported only once for each function it appears in
((y ## v) >> s->ps.sps->log2_min_pu_size))
^
libavcodec/hevc_mvs.c:204:14: note: in definition of macro 'TAB_MVF'
tab_mvf[(y) * min_pu_width + x]
^
libavcodec/hevc_mvs.c:274:16: note: in expansion of macro 'TAB_MVF_PU'
(cand && !(TAB_MVF_PU(v).pred_flag == PF_INTRA))
^
libavcodec/hevc_mvs.c:368:23: note: in expansion of macro 'AVAILABLE'
is_available_b0 = AVAILABLE(cand_up_right, B0) &&
^
libavcodec/hevc_mvs.c:207:15: error: 'x0000000' undeclared (first use in this function)
TAB_MVF(((x ## v) >> s->ps.sps->log2_min_pu_size),
^
libavcodec/hevc_mvs.c:204:34: note: in definition of macro 'TAB_MVF'
tab_mvf[(y) * min_pu_width + x]
^
libavcodec/hevc_mvs.c:274:16: note: in expansion of macro 'TAB_MVF_PU'
(cand && !(TAB_MVF_PU(v).pred_flag == PF_INTRA))
^
libavcodec/hevc_mvs.c:368:23: note: in expansion of macro 'AVAILABLE'
is_available_b0 = AVAILABLE(cand_up_right, B0) &&
^
libavcodec/hevc_mvs.c: In function 'ff_hevc_luma_mv_mvp_mode':
libavcodec/hevc_mvs.c:208:15: error: 'y0000000' undeclared (first use in this function)
((y ## v) >> s->ps.sps->log2_min_pu_size))
^
libavcodec/hevc_mvs.c:204:14: note: in definition of macro 'TAB_MVF'
tab_mvf[(y) * min_pu_width + x]
^
libavcodec/hevc_mvs.c:274:16: note: in expansion of macro 'TAB_MVF_PU'
(cand && !(TAB_MVF_PU(v).pred_flag == PF_INTRA))
^
libavcodec/hevc_mvs.c:683:24: note: in expansion of macro 'AVAILABLE'
is_available_b0 = AVAILABLE(cand_up_right, B0) &&
^
libavcodec/hevc_mvs.c:207:15: error: 'x0000000' undeclared (first use in this function)
TAB_MVF(((x ## v) >> s->ps.sps->log2_min_pu_size),
^
libavcodec/hevc_mvs.c:204:34: note: in definition of macro 'TAB_MVF'
tab_mvf[(y) * min_pu_width + x]
^
libavcodec/hevc_mvs.c:274:16: note: in expansion of macro 'TAB_MVF_PU'
(cand && !(TAB_MVF_PU(v).pred_flag == PF_INTRA))
^
libavcodec/hevc_mvs.c:683:24: note: in expansion of macro 'AVAILABLE'
is_available_b0 = AVAILABLE(cand_up_right, B0) &&
^
make: *** [libavcodec/hevc_mvs.o] Error 1
5.Android项目导入及其应用
1.build.gradle 配置和so库导入
1.gradle 引入包
ndk {
abiFilters "armeabi" // 指定要ndk需要兼容的架构(这样其他依赖包里mips,x86,armeabi,arm-v8之类的so会被过滤掉)
}
sourceSets {
main {
jniLibs.srcDirs 'src/main/jniLibs'//这个必须加,因为我们把.so文件放在了libs文件夹中
}
}
// 如果需要在x86上运行就导入x86的库
implementation "tv.danmaku.ijk.media:ijkplayer-java:0.8.8"
implementation "tv.danmaku.ijk.media:ijkplayer-armv7a:0.8.8"

1.播放一个简单的mp4文件
- xml中添加一个SurfaceView控件
<SurfaceView
android:id="@+id/video_surface"
android:layout_width="match_parent"
android:layout_height="300dp"
app:layout_constraintBottom_toBottomOf="parent"/>
- 给SurfaceView添加一个监听器
mDataBinding.videoSurface.getHolder().addCallback (new SurfaceHolder.Callback () {
@Override
public void surfaceCreated(SurfaceHolder holder) {
// 创建完成加载视频
load("/sdcard/test.mp4", holder);
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
Log.d(TAG, "surfaceDestroyed: ");
if (mediaPlayer.isPlaying()){
mediaPlayer.pause();
}
}
});
- 初始化一个IjkMediaPlayer 播放器设置一些监听
// 创建一个播放器
mediaPlayer = new IjkMediaPlayer();
//mediaPlayer准备工作
mediaPlayer.setOnPreparedListener (new IMediaPlayer.OnPreparedListener () {
@Override
public void onPrepared(IMediaPlayer iMediaPlayer) {
// 播放器准备就绪回调
}
});
//MediaPlayer完成
mediaPlayer.setOnCompletionListener (new IMediaPlayer.OnCompletionListener () {
@Override
public void onCompletion(IMediaPlayer iMediaPlayer) {
// 播放完成回调
}
});
//当播放网络的数据流的buffer发生变化的时候
mediaPlayer.setOnBufferingUpdateListener (new IMediaPlayer.OnBufferingUpdateListener () {
@Override
public void onBufferingUpdate(IMediaPlayer iMediaPlayer, int i) {
}
});
//IJK 的 Mediaplayer api 类似 Android 系统播放器api
//IMediaPlayer.class
public interface IMediaPlayer{
void setOnPreparedListener(IMediaPlayer.OnPreparedListener var1); //prepareAsync 之后回调方法
void setOnCompletionListener(IMediaPlayer.OnCompletionListener var1); //播放完成回调方法
void setOnBufferingUpdateListener(IMediaPlayer.OnBufferingUpdateListener var1);//当播放网络的数据流的buffer发生变化的时候
void setOnSeekCompleteListener(IMediaPlayer.OnSeekCompleteListener var1);//当seek定位操作完成后
void setOnVideoSizeChangedListener(IMediaPlayer.OnVideoSizeChangedListener var1);//当视频的大小第一次被知道或者发生改变时
void setOnErrorListener(IMediaPlayer.OnErrorListener var1);//当有发生错误
void setOnInfoListener(IMediaPlayer.OnInfoListener var1);//当有信息或者警告
void setOnTimedTextListener(IMediaPlayer.OnTimedTextListener var1); //当媒体的时间数据需要被显示
}
- 加载数据资源并设置surfaceHolder
private void load(String url, SurfaceHolder surfaceHolder) {
Log.d(TAG, "load: ");
try {
Log.d(TAG, "load: url = " + url);
mediaPlayer.setDataSource(url);
mediaPlayer.setDisplay(surfaceHolder);
//开启异步准备
mediaPlayer.prepareAsync();
Log.d(TAG, "load: prepareAsync");
} catch (IOException e) {
Log.e(TAG, "load: 出错");
e.printStackTrace();
}
}
4.ijkplayer播放器控制方法
/**
* 开始播放
*/
public void start() {
if (mediaPlayer != null) {
mediaPlayer.start();
}
}
/**
* 暂停播放
*/
public void pause() {
if (mediaPlayer != null) {
mediaPlayer.pause();
}
}
/**
* 停止播放
*/
public void stop() {
if (mediaPlayer != null) {
mediaPlayer.stop();
}
}
/**
* 重置
*/
public void reset() {
if (mediaPlayer != null) {
mediaPlayer.reset ();
}
}
/**
* 释放资源
*/
public void release() {
if (mediaPlayer != null) {
mediaPlayer.reset();
mediaPlayer.release();
mediaPlayer = null;
}
}
/**
* 获取总时间
* @return
*/
public long getDuration() {
if (mediaPlayer != null) {
return mediaPlayer.getDuration();
} else {
return 0;
}
}
/**
* 获取当前时间点
* @return
*/
public long getCurrentPosition() {
if (mediaPlayer != null) {
return mediaPlayer.getCurrentPosition();
} else {
return 0;
}
}
/**
* seekTo
* @param l
*/
public void seekTo(long l) {
if (mediaPlayer != null) {
mediaPlayer.seekTo(l);
}
}