概述
FFmpeg是一套非常强大的音视频处理工具,许多开发过多媒体的朋友都绕不开它,围绕着FFmpeg可以进行诸如音视频解码,裁剪,拼接,音视频合并,以及支持多种流媒体的协议等等
今天就用目前最新的ffmpeg3.3.4
源码,使用NDK进行交叉编译,生成Android项目上可以使用的库,然后在APP上输出当前FFmpeg的配置
我的编译环境和IDE如下:
- ffmpeg 3.3.4 版本源码
- macOS 10.12.5
- NDK 13.1.3345770
- Android Studio 2.3.3
编译FFmpeg类库
下载FFmpeg源码
下载源码的方式有两种:
- 在GitHub上项目主页clone下来:https://github.com/FFmpeg/FFmpeg
- 在FFmpeg官网上下载源码:http://ffmpeg.org/download.html
我这里是通过官网下载的,解压后如下:
build_ffmpeg_1.png配置脚本
配置configure
由于默认configure脚本编译出来的动态库版本号在文件名后缀.so之后,在Android上是识别不了的,比如长下面这样:
build_ffmpeg_2.png这时候需要对源码根目录下的configure进行一下小修改,我这个FFmpeg版本是在3305行开始,把下面四行
SLIBNAME_WITH_MAJOR='$(SLIBNAME).$(LIBMAJOR)'
LIB_INSTALL_EXTRA_CMD='$$(RANLIB) "$(LIBDIR)/$(LIBNAME)"'
SLIB_INSTALL_NAME='$(SLIBNAME_WITH_VERSION)'
SLIB_INSTALL_LINKS='$(SLIBNAME_WITH_MAJOR) $(SLIBNAME)'
注释掉或者替换成下面这段
SLIBNAME_WITH_MAJOR='$(SLIBPREF)$(FULLNAME)-$(LIBMAJOR)$(SLIBSUF)'
LIB_INSTALL_EXTRA_CMD='$$(RANLIB)"$(LIBDIR)/$(LIBNAME)"'
SLIB_INSTALL_NAME='$(SLIBNAME_WITH_MAJOR)'
SLIB_INSTALL_LINKS='$(SLIBNAME)'
修改后的配置如下图,我这里选择的是注释掉:
build_ffmpeg_3.png配置my_build_android.sh
这个文件源码是没有的,需要我们在源码根目录下手动新建一个。此脚本配置网上有很多,但是因为不同的人编译时用的系统,用的NDK版本,都不可能完全一样,所以这里一定要根据自己的实际情况,一步步找到自己系统ndk所在的目录和arm-linux-androideabi-xx
的版本,写到配置上面
另外如果你复制网上的脚本时候有带上空格等情况,运行脚本的时候也有可能报错,这时候需要好好检查
这里提供一份我自己编译时用到的脚本供参考:
# ndk环境
export NDK=/Users/Lyh/Library/Android/sdk/ndk-bundle
export SYSROOT=$NDK/platforms/android-21/arch-arm
export TOOLCHAIN=$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64
CPU=armv7-a
# 要保存动态库的目录,这里保存在源码根目录下的android/armv7-a
export PREFIX=$(pwd)/android/$CPU
ADDI_CFLAGS="-marm"
function build_android
{
./configure --target-os=linux --prefix=$PREFIX \
--enable-cross-compile \
--enable-shared \
--disable-static \
--disable-doc \
--disable-ffmpeg \
--disable-ffplay \
--disable-ffprobe \
--disable-ffserver \
--disable-avdevice \
--disable-doc \
--disable-symver \
--cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \
--arch=arm \
--sysroot=$SYSROOT \
--extra-cflags="-Os -fpic $ADDI_CFLAGS" \
--extra-ldflags="$ADDI_LDFLAGS" \
$ADDITIONAL_CONFIGURE_FLAG
make clean
# 不确定自己上面的目录或者环境有没有错误时
# 可以先注释一下下面两个命令
make
make install
}
build_android
执行脚本生成so和头文件
进入源码所在目录,建好my_build_android.sh
后,第一次执行会提示权限不够,分配权限后重新执行即可,如下图:
NDK环境和目录执行完正常如下(此时因为不确定环境有无错误,还未执行make):
build_ffmpeg_5.png这里有个警告可以先忽略,我们在终端继续先执行make
和再make install
大约二十分钟左右以后,可以看到已经生成动态库(lib目录)和头文件(include目录):
build_ffmpeg_6.png注意:
这里有个小技巧,就是在第一次运行你的脚本还不确定有没有错时,可以先把make
和make install
注释掉,这样就不会因为你脚本配置的目录或者环境有问题时,仍然去继续make
和make install
的执行,然后把你环境的错误给冲掉,给查找问题带来困难
此外make
执行的时间比较长,如果是到生成.o文件时出错被中断了,或者make
执行完了,才发现没有正确生成so的时候再回去检查原因,就浪费非常多时间了
总结:
先确保NDK环境和目录没报错,再去执行make
命令
在Android项目上使用
创建Android项目
跟创建一般的项目稍有不同的是下面两个勾选
添加C++支持
把生成的头文件和so导入到Android项目
这里我们参照NDK-Sample的hello-libs写法,smaple的下载地址是:https://github.com/googlesamples/android-ndk ,已经有的可以不用再下
把第三方的so放到项目根目录中,结构如下:
编写CmakeList和Gradle
把hello-libs项目里的CmakeList.txt
文件复制到我们的项目中进行改造,原sample的cmakelist内容如下:
#
# Copyright (C) The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
cmake_minimum_required(VERSION 3.4.1)
# configure import libs
set(distribution_DIR ${CMAKE_SOURCE_DIR}/../../../../distribution)
add_library(lib_gmath STATIC IMPORTED)
set_target_properties(lib_gmath PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/gmath/lib/${ANDROID_ABI}/libgmath.a)
# shared lib will also be tucked into APK and sent to target
# refer to app/build.gradle, jniLibs section for that purpose.
# ${ANDROID_ABI} is handy for our purpose here. Probably this ${ANDROID_ABI} is
# the most valuable thing of this sample, the rest are pretty much normal cmake
add_library(lib_gperf SHARED IMPORTED)
set_target_properties(lib_gperf PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/gperf/lib/${ANDROID_ABI}/libgperf.so)
# build application's shared lib
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11")
add_library(hello-libs SHARED
hello-libs.cpp)
target_include_directories(hello-libs PRIVATE
${distribution_DIR}/gmath/include
${distribution_DIR}/gperf/include)
target_link_libraries(hello-libs
android
lib_gmath
lib_gperf
log)
把大段的注释删掉,再改写后变成了下面这样
cmake_minimum_required(VERSION 3.4.1)
# configure import libs
# 这里跟原Demo写法不一样要注意一下,因为Demo的CMakelist文件是在app下面的cpp目录中
set(distribution_DIR ${CMAKE_SOURCE_DIR}/../ffmpeg)
add_library(avcodec-57 SHARED IMPORTED)
set_target_properties(avcodec-57 PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/lib/armeabi-v7a/libavcodec-57.so)
# build application's shared lib
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11")
add_library(native-lib SHARED
src/main/cpp/native-lib.cpp)
include_directories(native-lib PRIVATE
${distribution_DIR}/include)
target_link_libraries(native-lib
android
avcodec-57
log)
说明:
- 有个set方法设置so路径的地方需要格外注意一下,这个错了下面都会影响到
- Demo的hello-libs是同时添加静态库和动态库,这里不需要,就把添加静态库部分删掉了
这里我们暂且只加入avcodec-57这个动态库
另外app目录下的gradle文件也要配置一下:
android {
compileSdkVersion 26
buildToolsVersion "26.0.0"
defaultConfig {
applicationId "org.lyh.ffmpegdemo"
minSdkVersion 14
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
ndk {
abiFilters 'armeabi-v7a'
}
externalNativeBuild {
cmake {
cppFlags "-frtti -fexceptions"
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
sourceSets {
main {
// 注意路径要写对
jniLibs.srcDirs = ['../ffmpeg/lib']
}
}
externalNativeBuild {
cmake {
path 'CMakeLists.txt'
}
}
}
重点注意的是jniLibs.srcDirs
的目录要配置正确,架构这里只生成armv7-a
编写C代码和Java代码
C代码如下:
#include <jni.h>
#include <string>
extern "C" {
#include "libavcodec/avcodec.h"
JNIEXPORT jstring JNICALL
Java_org_lyh_ffmpegdemo_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
// std::string hello = "Hello from C++";
char info[10000] = {0};
sprintf(info, "%s\n", avcodec_configuration());
return env->NewStringUTF(info);
}
}
原先自动生成的项目是运行后屏幕显示”Hello from C++“。这里修改一下返回的字符串,改成得到FFmpeg的配置信息返回到Java层,然后显示到Textview中。
Java代码没做修改,如下:
public class MainActivity extends AppCompatActivity {
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Example of a call to a native method
TextView tv = (TextView) findViewById(R.id.sample_text);
tv.setText(stringFromJNI());
}
/**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
*/
public native String stringFromJNI();
}
运行结果
最后运行如下:
build_ffmpeg_10.png
网友评论