概述
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
我這里是通過官網下載的,解壓后如下:
配置腳本
配置configure
由于默認configure腳本編譯出來的動態庫版本號在文件名后綴.so之后,在Android上是識別不了的,比如長下面這樣:
這時候需要對源碼根目錄下的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)'
修改后的配置如下圖,我這里選擇的是注釋掉:
配置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):
這里有個警告可以先忽略,我們在終端繼續先執行make
和再make install
大約二十分鐘左右以后,可以看到已經生成動態庫(lib目錄)和頭文件(include目錄):
注意:
這里有個小技巧,就是在第一次運行你的腳本還不確定有沒有錯時,可以先把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();
}
運行結果
最后運行如下: