上一篇已經(jīng)編譯出FFmpeg動(dòng)態(tài)庫,這一篇就來介紹如何在Android項(xiàng)目中使用動(dòng)態(tài)庫州泊。
新建一個(gè)項(xiàng)目丧蘸,在Android Studio 3.4.1中發(fā)現(xiàn)新建項(xiàng)目發(fā)生了一些改變
如果是要support c++,那么應(yīng)該選Native c++,然后一路next力喷,發(fā)現(xiàn)AS幫我們把cmake配置好了刽漂,cmake是c++代碼構(gòu)建工具,以前的老項(xiàng)目可能還是用mk的構(gòu)建方式弟孟,我們要與時(shí)俱進(jìn)贝咙,用cmake準(zhǔn)沒錯(cuò)。
我電腦已經(jīng)裝好cmake工具了拂募,如果沒有裝的打開setting->sdk 進(jìn)行打鉤下載
LLDB是c++調(diào)試需要的
CMake 不要用最新版庭猩,會(huì)報(bào)錯(cuò)
然后這樣就可以編譯跑到手機(jī)上了,這一步就不演示了陈症。
直接進(jìn)入正題
一蔼水、拷貝動(dòng)態(tài)庫
怎么把FFmpeg的幾個(gè)so搞進(jìn)去
1、新建jniLibs目錄录肯,把上一篇編譯成功的FFmpeg 的8個(gè)so放到armeabi目錄趴腋,把頭文件include目錄拷貝到j(luò)niLibs目錄下
然后需要修改CMakeLists.txt,為了看起來清晰點(diǎn)论咏,先把CMakeLists.txt移動(dòng)到app目錄下优炬,(默認(rèn)是在app/src/main/cpp),然后修改一下app下的build.gradle
android {
compileSdkVersion 28
buildToolsVersion = '28.0.3'
defaultConfig {
applicationId "com.lanshifu.ffmpegdemo"
minSdkVersion 15
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
cppFlags "-fexceptions" //使用的c++標(biāo)準(zhǔn)
abiFilters "armeabi"
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
externalNativeBuild {
cmake {
path "CMakeLists.txt" //CMakeLists 配置文件路徑
}
}
}
就是修改 externalNativeBuild ,有注釋的兩個(gè)地方
進(jìn)入下一步
二厅贪、修改MakeLists.txt
直接加注釋貼上來吧
# cmake參考 http://www.reibang.com/p/528eeb266f83
cmake_minimum_required(VERSION 3.4.1)
# 需要引入我們頭文件,以這個(gè)配置的目錄為基準(zhǔn)
include_directories(src/main/jniLibs/include)
# 添加共享庫(so)搜索路徑
LINK_DIRECTORIES(${CMAKE_SOURCE_DIR}/src/main/jniLibs/armeabi)
# 指定源文件目錄穿剖,把當(dāng)前工程目錄下的 src/main/cpp 目錄的下的所有 .cpp 和 .c 文件賦值給 SRC_LIST
AUX_SOURCE_DIRECTORY(${CMAKE_SOURCE_DIR}/src/main/cpp SRC_LIST)
#這個(gè)一般不用怎么改,最終把cpp編譯成共享庫 native-lib
add_library(
# 編譯生成的庫的名稱叫native-lib卦溢,對(duì)應(yīng)System.loadLibrary("native-lib");
native-lib
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
${SRC_LIST}
)
#為native-lib 鏈接額外的庫糊余,例如ffmpeg、ndk庫
target_link_libraries(
native-lib
# 編解碼(最重要的庫)
avcodec-57
# 設(shè)備信息
avdevice-57
# 濾鏡特效處理庫
avfilter-6
# 封裝格式處理庫
avformat-57
# 工具庫(大部分庫都需要這個(gè)庫的支持)
avutil-55
# 后期處理
postproc-54
# 音頻采樣數(shù)據(jù)格式轉(zhuǎn)換庫
swresample-2
# 視頻像素?cái)?shù)據(jù)格式轉(zhuǎn)換
swscale-4
# 鏈接 android ndk 自帶的一些庫
android
# Links the target library to the log library
# included in the NDK.
log)
當(dāng)然单寂,這些是最簡單的贬芥,實(shí)際項(xiàng)目應(yīng)該會(huì)很復(fù)雜。
然后直接run宣决,如果沒有報(bào)錯(cuò)蘸劈,說明FFmpeg動(dòng)態(tài)庫已經(jīng)集成到Android項(xiàng)目里了
驗(yàn)證一下:
使用 APK Analyzer查看。
選擇 Build > Analyze APK > 選擇apk打開
發(fā)現(xiàn)里so已經(jīng)打包進(jìn)去了
然后就可以通過JNI來調(diào)用FFmpeg了
三尊沸、來個(gè)簡單的例子
一個(gè)mp3文件威沫,使用FFmpeg來獲取音頻信息
1、在sd卡中放一個(gè) input1.mp3
2洼专、Activity中處理棒掠,讀取sd卡動(dòng)態(tài)權(quán)限就不貼了
File mMusicFile = new File(Environment.getExternalStorageDirectory(), "input1.mp3");
//onCreate調(diào)用
printAudioInfo(mMusicFile.getAbsolutePath());
public native void printAudioInfo(String url);
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");
}
printAudioInfo 是 native方法,按option+enter屁商,自動(dòng)生成對(duì)應(yīng)c++方法
extern "C"
JNIEXPORT void JNICALL
Java_com_lanshifu_ffmpegdemo_MainActivity_printAudioInfo(JNIEnv *env,jobject instance,jstring url_) {
}
用cmake 的好處之一就是c++方法可以自動(dòng)生成烟很,不用什么javah獲取頭文件,然后復(fù)制啥的...
上代碼吧
#include <jni.h>
#include <string>
#include <android/log.h>
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG,__VA_ARGS__)
extern "C"
JNIEXPORT void JNICALL
Java_com_lanshifu_ffmpegdemo_MainActivity_printAudioInfo(JNIEnv *env, jobject instance,
jstring url_) {
const char *url = env->GetStringUTFChars(url_, 0);
//1、初始化所有組件雾袱,只有調(diào)用了該函數(shù)恤筛,才能使用復(fù)用器和編解碼器(源碼)
av_register_all();
AVFormatContext *avFormatContext = NULL;
int audio_stream_idx;
AVStream *audio_stream;
//讀文件頭,對(duì) mp4 文件而言芹橡,它會(huì)解析所有的 box毒坛。但它知識(shí)把讀到的結(jié)果保存在對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu)下。
// 這個(gè)時(shí)候林说,AVStream 中的很多字段都是空白的粘驰。
int open_res = avformat_open_input(&avFormatContext, url, NULL, NULL);
if (open_res != 0) {
LOGE("lxb->Can't open file: %s", av_err2str(open_res));
return ;
}
//獲取文件信息
//讀取一部分視音頻數(shù)據(jù)并且獲得一些相關(guān)的信息,會(huì)檢測一些重要字段述么,如果是空白的蝌数,就設(shè)法填充它們。
// 因?yàn)槲覀兘馕鑫募^的時(shí)候度秘,已經(jīng)掌握了大量的信息顶伞,avformat_find_stream_info 就是通過這些信息來填充自己的成員,
// 當(dāng)重要的成員都填充完畢后剑梳,該函數(shù)就返回了唆貌。這中情況下,該函數(shù)效率很高垢乙。但對(duì)于某些文件锨咙,單純的從文件頭中獲取信息是不夠的,
// 比如 video 的 pix_fmt 是需要調(diào)用 h264_decode_frame 才可以獲取其pix_fmt的追逮。
int find_stream_info_res = avformat_find_stream_info(avFormatContext, NULL);
if (find_stream_info_res < 0) {
LOGE("lxb->Find stream info error: %s", av_err2str(find_stream_info_res));
goto __avformat_close;
}
// 獲取采樣率和通道
//av_find_best_stream:獲取音視頻及字幕的 stream_index , 以前沒有這個(gè)函數(shù)時(shí)酪刀,我們一般都是寫的 for 循環(huán)。
audio_stream_idx = av_find_best_stream(avFormatContext, AVMediaType::AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
if (audio_stream_idx < 0) {
LOGE("lxb->Find audio stream info error: %s", av_err2str(find_stream_info_res));
goto __avformat_close;
}
audio_stream = avFormatContext->streams[audio_stream_idx];
LOGE("采樣率:%d", audio_stream->codecpar->sample_rate);
LOGE("通道數(shù): %d", audio_stream->codecpar->channels);
LOGE("format: %d", audio_stream->codecpar->format);
LOGE("extradata_size: %d", audio_stream->codecpar->extradata_size);
__avformat_close:
avformat_close_input(&avFormatContext);
}
解碼流程參考
FFmpeg API說明:
1:av_register_all() 初始化所有組件钮孵,只有調(diào)用了該函數(shù)骂倘,才能使用復(fù)用器和編解碼器
2:avformat_open_input:讀文件頭,對(duì) mp4 文件而言巴席,它會(huì)解析所有的 box历涝。但它只是把讀到的結(jié)果保存在對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu)下
3:avformat_find_stream_info:讀取一部分視音頻數(shù)據(jù)并且獲得一些相關(guān)的信息。因?yàn)槲覀兘馕鑫募^的時(shí)候漾唉,已經(jīng)掌握了大量的信息荧库,avformat_find_stream_info 就是通過這些信息來填充自己的成員
4:av_find_best_stream:獲取音視頻及字幕的 stream_index , 以前沒有這個(gè)函數(shù)時(shí),我們一般都是寫的 for 循環(huán)
5:AVStream中保存了音視頻文件的一堆信息赵刑,在內(nèi)部AVCodecParameters 結(jié)構(gòu)體中
然后run一下分衫,可以看到打印
06-19 21:36:57.732 29875-29875/? E/JNI_TAG: 采樣率:44100
06-19 21:36:57.732 29875-29875/? E/JNI_TAG: 通道數(shù): 2
06-19 21:36:57.732 29875-29875/? E/JNI_TAG: format: 6
06-19 21:36:57.732 29875-29875/? E/JNI_TAG: extradata_size: 0
也就說明已經(jīng)成功在項(xiàng)目中使用FFmpeg了。