Android 集成 ijkplayer 编译及替换 cmake 方式编译

ijkplayer 编译步骤:
1.编译libijkffmpeg.so
2.编译cpufeatures yuv_static ijkj4a静态库
3.编译libijksdl.so
4.编译android-ndk-profiler ijksoundtouch 静态库
5.编译libijkplayer.so

ijkplayer 的常规编译步骤记录:

编译并运行demo:
1.clone
git clone https://github.com/Bilibili/ijkplayer.git ijkplayer-android
2.初始化,下载ffmpeg等源码
cd ijkplayer-android
./init-android.sh
3.编译ffmpeg(需要配置ANDROID_NDK,ANDROID_SDK环境变量)
cd android/contrib
./compile-ffmpeg.sh clean
./compile-ffmpeg.sh all
4.编译.so
cd ..
./compile-ijk.sh all
5.运行
AS打开 ijkplayer-android/android/ijkplayer 即可

ijk结构:
libijkffmpeg.so
libijksdl.so
libijkplayer.so

1.libijkplayer.so
LOCAL_SHARED_LIBRARIES := ijkffmpeg ijksdl
LOCAL_STATIC_LIBRARIES := android-ndk-profiler ijksoundtouch

2.libijksdl.so
LOCAL_SHARED_LIBRARIES := ijkffmpeg
LOCAL_STATIC_LIBRARIES := cpufeatures yuv_static ijkj4a

3.libijkffmpeg.so

MTXXVideoEditor步骤
1.cd videoeditor-generate_so/ffmpeg
2.sh compile.sh

j4a安装测试
1.git clone https://github.com/Bilibili/jni4android.git jni4android
2.cd jni4android
3.

build dependencies

you don’t have to run this if you have bison 3.x installed

./get-deps.sh
报错:configure: error: no acceptable m4 could be found in $PATH
解决:http://blog.csdn.net/ldl22847/article/details/8575140
报错: Makefile:64: recipe for target ‘src/flex.j4a.yy.cpp’ failed flex:命令未找到
解决:sudo apt-get install flex bison
4.
./configure
5.
make
报错:clang++:命令未找到
解决:sudo apt-get install clang

ijkj4a: cmake 没体现 : $(call import-module,android/cpufeatures), j4a中没有用到cpufeatures,为什么要导入?

libyuv: 大小不一样
cpufeatures: 什么时候生成的? 执行: ./compile-ijk.sh armv7a时生成 即 ndk-build ,关键语法:call import-module

cmake中可以通过include(AndroidNdkModules)  android_ndk_import_module_cpufeatures() 导入,但导入后的模块为android.mk编译方式
解决方案:将这个模块源码拷贝到工程中,转为cmake编译方式,不再需要android_ndk_import_module_cpufeatures导入

静态库拷贝:

#是否需要从externalNativeBuild目录中拷贝静态库到输出目录, 设置为”true”时才会拷贝

#暂时用此方式解决静态库无法输出到指定目录的问题
set(static_cp
“false”
)
if(${static_cp} STREQUAL “true”)
add_custom_command(TARGET ${lib_name}
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${CMAKE_SOURCE_DIR}/.externalNativeBuild/cmake/debug/${ANDROID_ABI}/${lib_name}/lib${lib_name}.a
${CMAKE_SOURCE_DIR}/libs/${abi_library_name})
endif()

sdl编译问题:
x86成功编译,armv7a报错:Error:(201) undefined reference to ‘I422ToYUY2Row_NEON’ 等等等等

查看对应的实现源文件,全部褐色

问题:set(cxx_flag
${cxx_flag}
-DLIBYUV_NEON
)
解决:set(cxx_flag
${cxx_flag}
-LIBYUV_NEON
)

application.h

ijkffmpeg.so 2.2M
ijkplayer 200+k
ijksdl 200+k

问题:
Error:Execution failed for task ‘:videoeditor-armv7a:transformNativeLibsWithMergeJniLibsForDebug’.

More than one file was found with OS independent path ‘lib/armeabi-v7a/libijkplayer.so’
原因:
videoeditor_armv7a 为cmake工程,自动把gradle中的ijksdl、ijkplayer打到apk中了,与 jniLibs.srcDirs冲突
解决:
jniLibs.srcDirs只指向 libffmpeg.so

IjkPlayer 源码是用 Android.mk 方式编译的,为了方便开发,需要将其编译方式替换为 CMake,过程中遇到了很多让人头疼的问题,好在最后还是成功替换了~
举个例子,在编译 IjkPlayer 源码中的 sdl 动态库时,其源码中的 Android.mk 方式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
LOCAL_PATH := $(call my-dir)

# 以预编译库的方式引入编译 sdl 库依赖的动态/静态库
include $(CLEAR_VARS)
LOCAL_MODULE := ijkffmpeg
LOCAL_SRC_FILES := $(LOCAL_PATH)/../libffmpeg.so
#LOCAL_SRC_FILES := /home/yhao/ijkplayer-android/android/contrib/build/ffmpeg-armv7a/output/libijkffmpeg.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := cpufeatures
LOCAL_SRC_FILES := $(LOCAL_PATH)/../libcpufeatures.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := yuv_static
LOCAL_SRC_FILES := $(LOCAL_PATH)/../libyuv_static.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := ijkj4a
LOCAL_SRC_FILES := $(LOCAL_PATH)/../libijkj4a.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)

# 编译选项
LOCAL_CFLAGS += -std=c99


# 需要链接的库
LOCAL_LDLIBS += -llog -landroid -lOpenSLES -lEGL -lGLESv2

# 需要引入的头文件
sdl_dir=/home/yhao/mtxxvideoeditor/videoeditor-generate_so/ijksdl
ffmpeg_include_dir=/home/yhao/mtxxvideoeditor/videoeditor-generate_so/ffmpeg/output/armv7a/include
ijkyuv_include_dir=/home/yhao/mtxxvideoeditor/videoeditor-generate_so/ijkyuv/include
ijkj4a_include_dir=/home/yhao/mtxxvideoeditor/videoeditor-generate_so/ijkj4a
LOCAL_C_INCLUDES += $(sdl_dir)
LOCAL_C_INCLUDES += $(realpath $(sdl_dir)/..)
LOCAL_C_INCLUDES += $(ffmpeg_include_dir)
LOCAL_C_INCLUDES += $(ijkyuv_include_dir)
LOCAL_C_INCLUDES += $(ijkj4a_include_dir)

# 需要编译的源码文件
LOCAL_SRC_FILES += ijksdl_aout.c
LOCAL_SRC_FILES += ijksdl_audio.c
LOCAL_SRC_FILES += ijksdl_egl.c
LOCAL_SRC_FILES += ijksdl_error.c
LOCAL_SRC_FILES += ijksdl_mutex.c
LOCAL_SRC_FILES += ijksdl_stdinc.c
LOCAL_SRC_FILES += ijksdl_thread.c
LOCAL_SRC_FILES += ijksdl_timer.c
LOCAL_SRC_FILES += ijksdl_vout.c
LOCAL_SRC_FILES += ijksdl_extra_log.c
LOCAL_SRC_FILES += gles2/color.c
LOCAL_SRC_FILES += gles2/common.c
LOCAL_SRC_FILES += gles2/renderer.c
LOCAL_SRC_FILES += gles2/renderer_rgb.c
LOCAL_SRC_FILES += gles2/renderer_yuv420p.c
LOCAL_SRC_FILES += gles2/renderer_yuv444p10le.c
LOCAL_SRC_FILES += gles2/shader.c
LOCAL_SRC_FILES += gles2/fsh/rgb.fsh.c
LOCAL_SRC_FILES += gles2/fsh/yuv420p.fsh.c
LOCAL_SRC_FILES += gles2/fsh/yuv444p10le.fsh.c
LOCAL_SRC_FILES += gles2/vsh/mvp.vsh.c
LOCAL_SRC_FILES += dummy/ijksdl_vout_dummy.c
LOCAL_SRC_FILES += ffmpeg/ijksdl_vout_overlay_ffmpeg.c
LOCAL_SRC_FILES += ffmpeg/abi_all/image_convert.c
LOCAL_SRC_FILES += android/android_audiotrack.c
LOCAL_SRC_FILES += android/android_nativewindow.c
LOCAL_SRC_FILES += android/ijksdl_android_jni.c
LOCAL_SRC_FILES += android/ijksdl_aout_android_audiotrack.c
LOCAL_SRC_FILES += android/ijksdl_aout_android_opensles.c
LOCAL_SRC_FILES += android/ijksdl_codec_android_mediacodec_dummy.c
LOCAL_SRC_FILES += android/ijksdl_codec_android_mediacodec_internal.c
LOCAL_SRC_FILES += android/ijksdl_codec_android_mediacodec_java.c
LOCAL_SRC_FILES += android/ijksdl_codec_android_mediacodec.c
LOCAL_SRC_FILES += android/ijksdl_codec_android_mediadef.c
LOCAL_SRC_FILES += android/ijksdl_codec_android_mediaformat_java.c
LOCAL_SRC_FILES += android/ijksdl_codec_android_mediaformat.c
LOCAL_SRC_FILES += android/ijksdl_vout_android_nativewindow.c
LOCAL_SRC_FILES += android/ijksdl_vout_android_surface.c
LOCAL_SRC_FILES += android/ijksdl_vout_overlay_android_mediacodec.c

# 依赖编译 sdl 库所需的动态/静态库
LOCAL_SHARED_LIBRARIES := ijkffmpeg
LOCAL_STATIC_LIBRARIES := cpufeatures yuv_static ijkj4a

LOCAL_MODULE := ijksdl
include $(BUILD_SHARED_LIBRARY)

替换为 CMake 方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
cmake_minimum_required(VERSION 3.4.1)

# 获取当前目录
set(pwd_dir ${CMAKE_SOURCE_DIR}/ijksdl)

# 设置库名称
set(lib_name ijksdl)

# 获取库输出目录
set(lib_output_dir
${global_lib_output_dir}
)

# 设置静态库输出目录(参考googlesamples/android-ndk)
set(static_output
"false"
)

# 设置头文件
set(include_dir
${pwd_dir}
${pwd_dir}/..
${ffmpeg_output_include_dir}
${CMAKE_SOURCE_DIR}/ijkyuv/include
${CMAKE_SOURCE_DIR}/ijkj4a
)

# 以预编译库的方式引入编译 sdl 库依赖的动态/静态库
add_library(cpufeatures-lib STATIC IMPORTED)
set_target_properties(cpufeatures-lib PROPERTIES IMPORTED_LOCATION
${lib_output_dir}/libcpufeatures.a)
add_library(yuv_static-lib STATIC IMPORTED)
set_target_properties(yuv_static-lib PROPERTIES IMPORTED_LOCATION
${lib_output_dir}/libyuv_static.a)
add_library(ijkj4a-lib STATIC IMPORTED)
set_target_properties(ijkj4a-lib PROPERTIES IMPORTED_LOCATION
${lib_output_dir}/libijkj4a.a)
add_library(ffmpeg-lib SHARED IMPORTED)
set_target_properties(ffmpeg-lib PROPERTIES IMPORTED_LOCATION
${ffmpeg_output_shared_lib})

# 兼容gl版本
message(FATAL_DEBUG "兼容gl版本 \
(currently using ${ANDROID_PLATFORM_LEVEL}).")
if (${ANDROID_PLATFORM_LEVEL} LESS 12)
message(FATAL_ERROR "OpenGL 2 is not supported before API level 11")
return()
elseif (${ANDROID_PLATFORM_LEVEL} LESS 18)
message(FATAL_DEBUG "add_definitions DDYNAMIC_ES3 ")
add_definitions("-DDYNAMIC_ES3")
set(GL3STUB_SRC ${pwd_dir}/gles2/gl3stub.c)
set(OPENGL_LIB GLESv2)
else ()
message(FATAL_DEBUG "GLESv3 无法提前获取gl版本,无法动态引入头文件,故平台版本需固定,低于18 ")
set(OPENGL_LIB GLESv3)
endif (${ANDROID_PLATFORM_LEVEL} LESS 12)

#将若干库链接到目标库文件
set(link_lib
${OPENGL_LIB}
log
android
OpenSLES
EGL
ffmpeg-lib
cpufeatures-lib
yuv_static-lib
ijkj4a-lib
)

#设置编译选项
set(cmake_c_flag_debug
-std=c99
)
set(cmake_cxx_flag_debug

)

# 设置要编译的源文件
set(source_files
${GL3STUB_SRC}
${pwd_dir}/gles2/gl_util.c
${pwd_dir}/gles2/ff_ffmux_soft.c
${pwd_dir}/gles2/ff_ffmux_hard.c
${pwd_dir}/gles2/ff_print_util.c
${pwd_dir}/gles2/ff_converter.c
${pwd_dir}/gles2/ff_ffmusic_decode.c
${pwd_dir}/gles2/ff_audio_converter.c
${pwd_dir}/ijksdl_aout.c
${pwd_dir}/ijksdl_audio.c
${pwd_dir}/ijksdl_egl.c
${pwd_dir}/ijksdl_error.c
${pwd_dir}/ijksdl_mutex.c
${pwd_dir}/ijksdl_stdinc.c
${pwd_dir}/ijksdl_thread.c
${pwd_dir}/ijksdl_timer.c
${pwd_dir}/ijksdl_vout.c
${pwd_dir}/ijksdl_extra_log.c
${pwd_dir}/gles2/color.c
${pwd_dir}/gles2/common.c
${pwd_dir}/gles2/renderer.c
${pwd_dir}/gles2/renderer_rgb.c
${pwd_dir}/gles2/renderer_yuv420p.c
${pwd_dir}/gles2/renderer_yuv444p10le.c
${pwd_dir}/gles2/shader.c
${pwd_dir}/gles2/fsh/rgb.fsh.c
${pwd_dir}/gles2/fsh/yuv420p.fsh.c
${pwd_dir}/gles2/fsh/yuv420pmeitu.fsh.c
${pwd_dir}/gles2/fsh/yuv444p10le.fsh.c
${pwd_dir}/gles2/vsh/mvp.vsh.c
${pwd_dir}/dummy/ijksdl_vout_dummy.c
${pwd_dir}/ffmpeg/ijksdl_vout_overlay_ffmpeg.c
${pwd_dir}/ffmpeg/abi_all/image_convert.c
${pwd_dir}/android/android_audiotrack.c
${pwd_dir}/android/android_nativewindow.c
${pwd_dir}/android/ijksdl_android_jni.c
${pwd_dir}/android/ijksdl_aout_android_audiotrack.c
${pwd_dir}/android/ijksdl_aout_android_opensles.c
${pwd_dir}/android/ijksdl_codec_android_mediacodec_dummy.c
${pwd_dir}/android/ijksdl_codec_android_mediacodec_internal.c
${pwd_dir}/android/ijksdl_codec_android_mediacodec_java.c
${pwd_dir}/android/ijksdl_codec_android_mediacodec.c
${pwd_dir}/android/ijksdl_codec_android_mediadef.c
${pwd_dir}/android/ijksdl_codec_android_mediaformat_java.c
${pwd_dir}/android/ijksdl_codec_android_mediaformat.c
${pwd_dir}/android/ijksdl_vout_android_nativewindow.c
${pwd_dir}/android/ijksdl_vout_android_surface.c
${pwd_dir}/android/ijksdl_vout_overlay_android_mediacodec.c
)


# 设置动态库输出路径
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${lib_output_dir})


# 需要引入的头文件
include_directories(${include_dir})


# 设置编译选项
set(CMAKE_C_FLAGS_DEBUG "${cmake_c_flag_debug} -s")
set(CMAKE_CXX_FLAGS_DEBUG "${cmake_cxx_flag_debug} -s")


# 设置要编译的源文件
add_library(${lib_name} SHARED ${source_files})


# 设置依赖的库
target_link_libraries(${lib_name} ${link_lib})

# 是否需要输出静态库
if(${static_output} STREQUAL "true")
set_target_properties(${lib_name}
PROPERTIES
ARCHIVE_OUTPUT_DIRECTORY
${lib_output_dir})
endif()

CMake 编译方式中添加了一些兼容 gl 的处理及源文件,可以暂时忽略,语法上与 Android.mk 方式大同小异。
除了 sdl 库外,还需要编译 ijkyuv、ijk4a、ijkplayer 等库,这些库和 sdl 库同级目录,每个库中都需配置自己的 CMakeLists.txt 文件,在最外层的配置 CMakeLists.txt 文件中添加编译子目录,如下:

1
2
3
4
5
6
7
add_subdirectory(${CMAKE_SOURCE_DIR}/cpufeatures)
add_subdirectory(${CMAKE_SOURCE_DIR}/ijkj4a)
add_subdirectory(${CMAKE_SOURCE_DIR}/ijkyuv)
add_subdirectory(${CMAKE_SOURCE_DIR}/ijksdl)
add_subdirectory(${CMAKE_SOURCE_DIR}/android-ndk-prof)
add_subdirectory(${CMAKE_SOURCE_DIR}/ijksoundtouch)
add_subdirectory(${CMAKE_SOURCE_DIR}/ijkplayer)

然后在应用的 build.gradle 中指定最外层的 CMakeLists.txt 即可,另外也可以配置编译选项:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
defaultConfig {

省略...


// 配置编译选项
externalNativeBuild {
cmake {
cppFlags " -O3 -Wall -pipe " +
" -ffast-math " +
" -fstrict-aliasing -Werror=strict-aliasing" +
" -Wno-psabi -Wa,--noexecstack " +
" -DANDROID -DNDEBUG"

cFlags " -O3 -Wall -pipe " +
" -ffast-math " +
" -fstrict-aliasing -Werror=strict-aliasing" +
" -Wno-psabi -Wa,--noexecstack " +
" -DANDROID -DNDEBUG"

arguments "-DANDROID_STL=stlport_static",
"-DANDROID_PIE=OFF",
"-DANDROID_ARM_NEON=TRUE",
"-DANDROID_PLATFORM=android-15"

targets "cpufeatures",
"ijkj4a",
"yuv_static",
"ijksdl",
"android-ndk-profiler",
"ijksoundtouch",
"ijkplayer"
}
}
ndk {
abiFilters 'armeabi-v7a'
}
}

编译选项记录:
-Wall 是打开警告开关
-O1 提供基础级别的优化
-O2 提供更加高级的代码优化,会占用更长的编译时间
-O3 提供最高级的代码优化
-ffast-math 对于这些以速度为重的应用,该选项定义了预处理器宏 FAST_MATH, 指示编译不必遵循 IEEE 和 ISO 的浮点运算标准
-fstrict-aliasing 在编译选项中加入-fstrict-aliasing的优势在于向编译器说明不同类型的lvalue将指向不相关的内存区域,编译器可以做大量的优化。
-fno-strict-aliasing:在编译内核的编译选项CFLAGS中,加入了-fno-strict-aliasing,向编译器表明不同类型的lvalue可能指向相关的内存区域,
因此编译器不会做出一些极端的优化而造成不安全(内核编译中优化选项为-O2, -O2优化时默认是-fstrict-aliasing,因此需要显式的指出编译参数是-fno-strict-aliasing)
-Werror=strict-aliasing : http://clhjoe.blogspot.hk/2012/06/gcc-strict-aliasing.html
-Wno-psabi 可以防止ndk-build编译时出现的警告。
-Wall 选项意思是编译后显示所有警告。
-W 选项类似-Wall,会显示警告,但是只显示编译器认为会出现错误的警告。

CMake 问题记录:
Q:编译 libyuv 库时报错:Error:(201) undefined reference to ‘I422ToYUY2Row_NEON’
A:编译选项 -DLIBYUV_NEON 改为 -LIBYUV_NEON,CMake 方式不同于 Android.mk 方式。
Q:相比与 Android.mk 方式,CMake 方式编译出的动态库体积很大,怎么办?
A:编译选项末尾加上 -s,形如: set(CMAKE_C_FLAGS_DEBUG “${cmake_c_flag_debug} -s”),可去除冗余的二进制数据。

待续…