本文主要内容:讲述遇到集成 NDK 时遇到的「error: undefined reference to」的解决方案
集成 NDK 有很多方式,比如 ndk-build 后把 so 库直接链接使用,或者通过 CMake 的方式链接,通常推荐后者,因为这样编写调用 native 方法方便,不需要像前者每次需要重新打包。
在实现 FFmpeg 播放视频功能时,我参考 Android Studio FFmpeg视频解码播放 该文章操作后,始终无法成功编译,按照网上的种种解决方案(比如用 C 的方式引入头文件)都无法解决,最后一步一步跟能成功运行的项目对照后终于解决。
具体报错情况为一堆 FFmpeg 相关的方法提示 「error: undefined reference to XXXX」。网上的解决方案基本都为用「extern “C”{}」包裹,原因是 FFmpeg 是一共纯 C 的项目,C++ 想正确调用 C 代码则需使用这样的方式,具体示例如下:
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "libswresample/swresample.h"
#include "libavutil/opt.h"
#include "libavutil/imgutils.h"
}
该方案能解决一部分人问题,于我却无用,随后我检查了各种引入方式,以及 NDK 的语法后,确定问题其实是出在 CMakelists.txt 文件上。
先附上正确的 CMake 文件:
cmake_minimum_required(VERSION 3.4.1)
set(distribution_DIR ${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI})
include_directories(${CMAKE_SOURCE_DIR}/src/main/cpp/include)
add_library( avutil-55
SHARED
IMPORTED )
set_target_properties( avutil-55
PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/libavutil-55.so )
add_library( swresample-2
SHARED
IMPORTED )
set_target_properties( swresample-2
PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/libswresample-2.so )
add_library( avcodec-57
SHARED
IMPORTED )
set_target_properties( avcodec-57
PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/libavcodec-57.so )
add_library( avfilter-6
SHARED
IMPORTED)
set_target_properties( avfilter-6
PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/libavfilter-6.so )
add_library( swscale-4
SHARED
IMPORTED)
set_target_properties( swscale-4
PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/libswscale-4.so )
add_library( avformat-57
SHARED
IMPORTED)
set_target_properties( avformat-57
PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/libavformat-57.so )
#add_library( native-lib
# SHARED
# src/main/cpp/native-lib.cpp )
add_library( ffmpeg-lib
SHARED
${CMAKE_SOURCE_DIR}/src/main/cpp/ffmpeg-lib.cpp )
#find_library( log-lib
# log )
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11")
target_link_libraries(
ffmpeg-lib
avutil-55
swresample-2
avcodec-57
avfilter-6
swscale-4
avformat-57
log
android
)
我犯的错误之一是为了区分我自己新建了 ffmpeg-lib 来写相关的代码,却两次 add_library 了,实际上写法应该是同一个 add_library,最后一部分引入多个 cpp 文件。
犯的错误之二是第一次接触 CMake,因此代码是照着别人改的,但是 target_link_libraries 的时候少链接了一个 so库。
这里的错误其实挺隐蔽的,因为之前集成 FFmpeg 的时候项目 Hello world 时一切正常,而后具体写代码时却出问题了,因此一直把注意力集中在代码上面。我搜到的网上的所有相关资料都没有提示可能与 CMake 有关,因此写下此文供后人参考。
顺带补充一个尴尬的小错误,在运行的时候 loadLibrary 时提示「java.lang.UnsatisfiedLinkError: dlopen failed: library “libavcodec-58.so” not found」,我一度以为又是我 CMake 写错了,结果发现是因为最开始把 so 库放到了 libs 文件夹下,后来在排查上面错误时改到了 src/main/jniLibs 底下,而 gradle 文件没有同步更改,gradle 的 jniLibs.srcDirs 修改目录即可:
sourceSets {
main {
jniLibs.srcDirs = ['src/main/jniLibs']
}
}