【聲 明】
首先拓诸,這一系列文章均基于自己的理解和實(shí)踐,可能有不對(duì)的地方麻昼,歡迎大家指正奠支。
其次,這是一個(gè)入門系列涌献,涉及的知識(shí)也僅限于夠用胚宦,深入的知識(shí)網(wǎng)上也有許許多多的博文供大家學(xué)習(xí)了首有。
最后燕垃,寫文章過程中,會(huì)借鑒參考其他人分享的文章井联,會(huì)在文章最后列出卜壕,感謝這些作者的分享飒房。
碼字不易糊昙,轉(zhuǎn)載請(qǐng)注明出處!
教程代碼:【Github傳送門】 |
---|
目錄
一百炬、Android音視頻硬解碼篇:
二秦驯、使用OpenGL渲染視頻畫面篇
- 1,初步了解OpenGL ES
- 2挣棕,使用OpenGL渲染視頻畫面
- 3译隘,OpenGL渲染多視頻,實(shí)現(xiàn)畫中畫
- 4洛心,深入了解OpenGL之EGL
- 5固耘,OpenGL FBO數(shù)據(jù)緩沖區(qū)
- 6,Android音視頻硬編碼:生成一個(gè)MP4
三词身、Android FFmpeg音視頻解碼篇
- 1厅目,F(xiàn)Fmpeg so庫編譯
- 2,Android 引入FFmpeg
- 3法严,Android FFmpeg視頻解碼播放
- 4损敷,Android FFmpeg+OpenSL ES音頻解碼播放
- 5,Android FFmpeg+OpenGL ES播放視頻
- 6渐夸,Android FFmpeg簡單合成MP4:視屏解封與重新封裝
- 7嗤锉,Android FFmpeg視頻編碼
本文你可以了解到
本文將介紹如何將上一篇文章編譯出來的
FFmpeg so
庫,引入到Android
工程中墓塌,并驗(yàn)證so
是否可以正常使用瘟忱。
一奥额、開啟 Android 原生 C/C++ 支持
在過去,通常使用 makefile
的方式在項(xiàng)目中引入 C/C++
代碼支持访诱,隨著 Android Studio
的普及垫挨,makefile
的方式已經(jīng)基本被 CMake
替代。
有了 Android
官方的支持触菜,NDK
層代碼的開發(fā)變得更加容易九榔。以前一談到 Android NDK
,許多人就會(huì)大驚失色涡相,感覺是深不可測(cè)的東西哲泊,一方面是 makefile
的編寫很難,一方面是 C/C++
相比 Java
來說催蝗,比較晦澀切威。
但是不必?fù)?dān)心,一是有了 CMake
丙号,二是對(duì)于 C/C++
的基本使用其實(shí)和 Java
差不多先朦,本系列涉及到的,也都是對(duì) C/C++
的基礎(chǔ)使用犬缨,畢竟喳魏,高級(jí)的我也不會(huì)不是嗎?哈哈哈~~
1. 安裝 CMake
首先怀薛,需要下載 CMake
相關(guān)工具刺彩,在 Android Studio
中依次點(diǎn)擊 Tools->SDK Manager->SDK Tools
,然后勾選
CMake
: CMake 構(gòu)建工具
LLDB
: C/C++ 代碼調(diào)試工具
NDK
: NDK 環(huán)境
最后依次點(diǎn)擊 OK->OK->Finish
乾戏,開始下載(文件比較大鼓择,可能會(huì)比較慢,請(qǐng)耐心等待)摆出。
2. 添加 C/C++ 支持
有兩種方式:
一是,新建一個(gè)新的工程偎漫,并勾選
C/C++
支持選項(xiàng)爷恳,系統(tǒng)將自動(dòng)創(chuàng)建一個(gè)支持C/C++
編碼的工程栈虚。
二是曼验,在已有的項(xiàng)目上,手動(dòng)添加所有的添加項(xiàng)來支持
C/C++
編碼粘姜,其實(shí)就是自己手動(dòng)添加「第一種方式」
中Android Studio
為我們自動(dòng)創(chuàng)建的那些東西。
首先活喊,通過新建一個(gè)新工程的方式钾菊,看看 IDE
為我們生成了那些東西偎肃。
1)新建 C/C++ 工程
依次點(diǎn)擊 File -> New -> New Project
煞烫,進(jìn)入新建工程頁面,拉到最后累颂,選擇 Native C++
然后按照默認(rèn)配置滞详,一路 Next -> Next -> Finish
即可。
2)Android Studio 自動(dòng)生成了什么
生成的工程目錄如下:
重點(diǎn)關(guān)注上圖標(biāo)注的3個(gè)地方:
- 第一紊馏,最上層的
MainActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Example of a call to a native method
sample_text.text = stringFromJNI()
}
/**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
*/
external fun stringFromJNI(): String
companion object {
// Used to load the 'native-lib' library on application startup.
init {
System.loadLibrary("native-lib")
}
}
}
很簡單料饥,使用過 so
庫的應(yīng)該都看得懂,這里簡單說一下朱监。
代碼的最下面岸啡,companion object
在 Kotlin
中表示靜態(tài)代碼塊,類似 Java
中的 static { }
赫编,其中的代碼有且只會(huì)被執(zhí)行一次巡蘸。
接著在 init{}
方法中篇裁,加載了 C/C++
代碼編譯成的 so
庫: native-lib
。
往上一句代碼赡若,用 external
聲明了一個(gè)外部引用的方法 stringFromJNI()
达布,這個(gè)方法和 C/C++
層的代碼是對(duì)應(yīng)的。
最終在最上面的 onCreate
中逾冬,將從 C/C++
層返回的 String
顯示出來黍聂。
- 第二,創(chuàng)建了一個(gè)
cpp
文件包
其中有兩個(gè)文件非常重要身腻,分別是 native-lib.cpp
产还、 CMakeLists.txt
。
i. native-lib.cpp
:是一個(gè) C++ 接口文件嘀趟,在 MainActivity
中聲明的外部方法將在這里得到實(shí)現(xiàn)脐区。
自動(dòng)生成 native-lib.cpp
的內(nèi)容如下:
#include <jni.h>
#include <string>
extern "C" JNIEXPORT jstring JNICALL
Java_com_chenlittleping_mynativeapp_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
可以看到,這個(gè) cpp 文件中的方法命名非常的長她按,不過其實(shí)非常簡單牛隅。
首先是頭部固定寫法 extern "C" JNIEXPORT jstring JNICALL
:
extern "C"
表示以 C語言
的方式來編譯;
jstring
表示該方法返回類型是 Java
層的 String
類型酌泰,類似的還是有: void
jint
等媒佣;
然后是 Java 層對(duì)應(yīng)方法的映射,即整個(gè)方法命名其實(shí)是 Java
層對(duì)應(yīng)方法的絕對(duì)路徑陵刹。
其中默伍,最前面的 Java_
是固定寫法;
com_chenlittleping_mynativeapp_MainActivity_
: 對(duì)應(yīng)的是 com.chenlittleping.mynativeapp.MainActivity.
衰琐,其實(shí)就是 .
換為 _
也糊;
stringFromJNI
和 Java 層的方法一致。
最后是兩個(gè)參數(shù)羡宙, JNIEnv *env
和 jobject
狸剃,分別代表 JNI
的上下文環(huán)境和調(diào)用這個(gè)接口的 Java
的類的實(shí)例。
調(diào)用這個(gè)方法辛辨,將會(huì)在 C++
層創(chuàng)建一個(gè)字符串捕捂,并以 Java#String
的類型返回。
ii. CMakeLists.txt
: 也就是構(gòu)建腳本斗搞。內(nèi)容如下:
# cmake 最低版本
cmake_minimum_required(VERSION 3.4.1)
# 配置so庫編譯信息
add_library(
# 輸出so庫的名稱
native-lib
# 設(shè)置生成庫的方式指攒,默認(rèn)為SHARE動(dòng)態(tài)庫
SHARED
# 列出參與編譯的所有源文件
native-lib.cpp)
# 查找代碼中使用到的系統(tǒng)庫
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log)
# 指定編譯目標(biāo)庫時(shí),cmake要鏈接的庫
target_link_libraries(
# 指定目標(biāo)庫僻焚,native-lib 是在上面 add_library 中配置的目標(biāo)庫
native-lib
# 列出所有需要鏈接的庫
${log-lib})
這是最簡單的編譯配置允悦,具體見上面的注釋。
CMakeLists.txt
的目的就是配置可以編譯出 native-lib
so 庫的構(gòu)建信息虑啤。
說白了隙弛,就是告訴編譯器:
- 編譯的目標(biāo)是誰
- 依賴的源文件在哪里找
- 依賴的 `系統(tǒng)或第三方` 的 `動(dòng)態(tài)或靜態(tài)` 庫在哪里找架馋。
- 第三,在 Gradle 文件中注冊(cè) CMake 腳本
在 第二步
中全闷,已經(jīng)把構(gòu)建 so
庫的信息配置好了叉寂,接下來要把這些信息注冊(cè)到 Gradle
中,編譯器才會(huì)去編譯它总珠。
app 的 build.gradle
內(nèi)容如下:
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 28
buildToolsVersion "29.0.1"
defaultConfig {
applicationId "com.chenlittleping.mynativeapp"
minSdkVersion 19
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
// 1) CMake 編譯配置
externalNativeBuild {
cmake {
cppFlags ""
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
// 2) 配置 CMakeLists 路徑
externalNativeBuild {
cmake {
path "src/main/cpp/CMakeLists.txt"
version "3.10.2"
}
}
}
dependencies {
// 省略無關(guān)代碼
//......
}
最主要的兩個(gè)地方是兩個(gè) externalNativeBuild
屏鳍。
第 1 個(gè) externalNativeBuild
中,可以做一些優(yōu)化配置局服,比如只打包包含 armeabi
架構(gòu)的 so
:
externalNativeBuild {
cmake {
cppFlags ""
}
ndk {
abiFilters "armeabi" //, "armeabi-v7a"
}
}
第 2 個(gè) externalNativeBuild
钓瞭,主要是配置 CMakeLists.txt
的路徑和版本。
Android Studio
為我們生成的關(guān)于C/C++
支持的主要就是以上三個(gè)地方淫奔,有了以上配置山涡,就可以在MainActivity
頁面中正常的顯示出Hello from C++
。
3) 在已有工程上添加 C/C++
支持
前面就說過唆迁,在已有項(xiàng)目上添加 C/C++
支持鸭丛,就是由我們自己手動(dòng)添加整個(gè)配置。那么根據(jù)簽名介紹的三個(gè)步驟媒惕,依葫蘆畫瓢系吩,就可以添加了。
這里剛好就用添加 FFMpeg so
到本系列文章現(xiàn)有 Demo 工程中來演示一遍妒蔚。
二、引入 FFmpeg so
1. 新建 cpp 目錄
首先月弛,在 app/src/main/
目錄下肴盏,新建文件夾,并命名為 cpp
帽衙。
接著菜皂,在 cpp
目錄下,右鍵 New -> C/C++ Source File
厉萝,新建 native-lib.cpp
文件恍飘。
接著,在 cpp
目錄下谴垫,右鍵 New -> File
章母,新建 CMakeLists.txt
,先將上面 IDE
生成的那份代碼粘貼進(jìn)來翩剪, FFmpeg的配置在后面詳細(xì)講解乳怎。
# CMakeLists.txt
# cmake 最低版本
cmake_minimum_required(VERSION 3.4.1)
# 配置so庫編譯信息
add_library(
# 輸出so庫的名稱
native-lib
# 設(shè)置生成庫的方式,默認(rèn)為SHARE動(dòng)態(tài)庫
SHARED
# 列出參與編譯的所有源文件
native-lib.cpp)
# 查找代碼中使用到的系統(tǒng)庫
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log)
# 指定編譯目標(biāo)庫時(shí)前弯,cmake要鏈接的庫
target_link_libraries(
# 指定目標(biāo)庫蚪缀,native-lib 是在上面 add_library 中配置的目標(biāo)庫
native-lib
# 列出所有需要鏈接的庫
${log-lib})
2. 將 CMakeLists 配置到 build.gradle 中
android {
// ...
defaultConfig {
// ...
// 1) CMake 編譯配置
externalNativeBuild {
cmake {
cppFlags ""
}
}
}
// ...
// 2) 配置 CMakeLists 路徑
externalNativeBuild {
cmake {
path "src/main/cpp/CMakeLists.txt"
version "3.10.2"
}
}
}
// ...
如果只是簡單的編寫 C/C++
代碼秫逝,以上基礎(chǔ)配置就可以了。
接著來看看本文的重點(diǎn)询枚,如何使用 CMakeLists.txt
引入 FFmpeg
的動(dòng)態(tài)庫违帆。
3. 將 FFmpeg so 庫放到對(duì)應(yīng)的 CPU 架構(gòu)目錄
在 上一篇文章中,我們編譯的 FFmpeg so
庫的 CPU
架構(gòu)為 armv7-a
金蜀,所以前方,我們需要把所有的 so
庫放置到 armeabi-v7a
目錄下。
首先廉油,在 app/src/main/
目錄下惠险,新建文件夾,并命名為 jniLibs
抒线。
app/src/main/jniLibs
是 Android Studio 默認(rèn)的放置 so 動(dòng)態(tài)庫的目錄班巩。
接著,在 jniLibs
目錄下嘶炭,新建 armeabi-v7a
目錄抱慌。
最后把 FFmpeg
編譯得到的所有 so
庫粘貼到 armeabi-v7a
目錄。如下:
4. 添加 FFmpeg so 的頭文件
在編譯 FFmpeg
的時(shí)候眨猎,除了生成 so
外抑进,還會(huì)生成對(duì)應(yīng)的 .h
頭文件,也就是 FFmpeg
對(duì)外暴露的所有接口睡陪。
在 cpp
目錄下寺渗,新建 ffmpeg
目錄,然后把編譯時(shí)生成的 include
文件粘貼進(jìn)來兰迫。
5. 添加信殊、鏈接 FFmpeg so 庫
上面已經(jīng)把 so
和 頭文件
放置到對(duì)應(yīng)的目錄中了,但是編譯器是不會(huì)把它們編譯汁果、鏈接涡拘、并打包到 Apk
中的,我們還需要在 CMakeLists.txt
中顯性的把相關(guān)的 so
添加和鏈接起來据德。完整的 CMakeLists.txt
如下:
cmake_minimum_required(VERSION 3.4.1)
# 支持gnu++11
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11")
# 1. 定義so庫和頭文件所在目錄鳄乏,方面后面使用
set(ffmpeg_lib_dir ${CMAKE_SOURCE_DIR}/../jniLibs/${ANDROID_ABI})
set(ffmpeg_head_dir ${CMAKE_SOURCE_DIR}/ffmpeg)
# 2. 添加頭文件目錄
include_directories(${ffmpeg_head_dir}/include)
# 3. 添加ffmpeg相關(guān)的so庫
add_library( avutil
SHARED
IMPORTED )
set_target_properties( avutil
PROPERTIES IMPORTED_LOCATION
${ffmpeg_lib_dir}/libavutil.so )
add_library( swresample
SHARED
IMPORTED )
set_target_properties( swresample
PROPERTIES IMPORTED_LOCATION
${ffmpeg_lib_dir}/libswresample.so )
add_library( avcodec
SHARED
IMPORTED )
set_target_properties( avcodec
PROPERTIES IMPORTED_LOCATION
${ffmpeg_lib_dir}/libavcodec.so )
add_library( avfilter
SHARED
IMPORTED)
set_target_properties( avfilter
PROPERTIES IMPORTED_LOCATION
${ffmpeg_lib_dir}/libavfilter.so )
add_library( swscale
SHARED
IMPORTED)
set_target_properties( swscale
PROPERTIES IMPORTED_LOCATION
${ffmpeg_lib_dir}/libswscale.so )
add_library( avformat
SHARED
IMPORTED)
set_target_properties( avformat
PROPERTIES IMPORTED_LOCATION
${ffmpeg_lib_dir}/libavformat.so )
add_library( avdevice
SHARED
IMPORTED)
set_target_properties( avdevice
PROPERTIES IMPORTED_LOCATION
${ffmpeg_lib_dir}/libavdevice.so )
# 查找代碼中使用到的系統(tǒng)庫
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log )
# 配置目標(biāo)so庫編譯信息
add_library( # Sets the name of the library.
native-lib
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
native-lib.cpp
)
# 指定編譯目標(biāo)庫時(shí),cmake要鏈接的庫
target_link_libraries(
# 指定目標(biāo)庫棘利,native-lib 是在上面 add_library 中配置的目標(biāo)庫
native-lib
# 4. 連接 FFmpeg 相關(guān)的庫
avutil
swresample
avcodec
avfilter
swscale
avformat
avdevice
# Links the target library to the log library
# included in the NDK.
${log-lib} )
主要看看注釋中新加入的 1~4
點(diǎn)橱野。
1)通過 set
方法定義了 so
和 頭文件
所在目錄,方便后面使用赡译。
其中
CMAKE_SOURCE_DIR
為系統(tǒng)變量仲吏,指向CMakeLists.txt
所在目錄。ANDROID_ABI
也是系統(tǒng)變量,指向 so 對(duì)應(yīng)的CPU
框架目錄:armeabi裹唆、armeabi-v7a誓斥、x86 ...
set(ffmpeg_lib_dir ${CMAKE_SOURCE_DIR}/../jniLibs/${ANDROID_ABI})
set(ffmpeg_head_dir ${CMAKE_SOURCE_DIR}/ffmpeg)
2)通過 include_directories
設(shè)置頭文件查找目錄
include_directories(${ffmpeg_head_dir}/include)
3)通過 add_library
添加 FFmpeg 相關(guān)的 so
庫,以及 set_target_properties
設(shè)置 so
對(duì)應(yīng)的目錄许帐。
其中劳坑,add_library 第一個(gè)參數(shù)為 so 名字,
SHARED
表示引入方式為動(dòng)態(tài)庫引入成畦。
add_library( avcodec
SHARED
IMPORTED )
set_target_properties( avcodec
PROPERTIES IMPORTED_LOCATION
${ffmpeg_lib_dir}/libavcodec.so )
4)最后距芬,通過 target_link_libraries
把前面添加進(jìn)來的 FFMpeg so
庫都鏈接到目標(biāo)庫 native-lib
上。
這樣循帐,我們就將 FFMpeg
相關(guān)的 so
庫都引入到當(dāng)前工程中了框仔。下面就要來測(cè)試一下,是否可以正常調(diào)用到 FFmpeg
相關(guān)的方法了拄养。
三离斩、使用 FFmpeg
要檢查 FFmpeg
是否可以使用,可以通過獲取 FFmpeg
基礎(chǔ)信息來驗(yàn)證瘪匿。
1. 在 FFmpegAcrtivity 中添加一個(gè)外部方法 ffmpegInfo
把獲取到的 FFmpeg
信息顯示出來跛梗。
class FFmpegActivity: AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_ffmpeg_info)
tv.text = ffmpegInfo()
}
private external fun ffmpegInfo(): String
companion object {
init {
System.loadLibrary("native-lib")
}
}
}
2. 在 native-lib.cpp 中添加對(duì)應(yīng)的 JNI 層方法
#include <jni.h>
#include <string>
#include <unistd.h>
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavfilter/avfilter.h>
#include <libavcodec/jni.h>
JNIEXPORT jstring JNICALL
Java_com_cxp_learningvideo_FFmpegActivity_ffmpegInfo(JNIEnv *env, jobject /* this */) {
char info[40000] = {0};
AVCodec *c_temp = av_codec_next(NULL);
while (c_temp != NULL) {
if (c_temp->decode != NULL) {
sprintf(info, "%sdecode:", info);
switch (c_temp->type) {
case AVMEDIA_TYPE_VIDEO:
sprintf(info, "%s(video):", info);
break;
case AVMEDIA_TYPE_AUDIO:
sprintf(info, "%s(audio):", info);
break;
default:
sprintf(info, "%s(other):", info);
break;
}
sprintf(info, "%s[%10s]\n", info, c_temp->name);
} else {
sprintf(info, "%sencode:", info);
}
c_temp = c_temp->next;
}
return env->NewStringUTF(info);
}
}
首先,我們看到代碼被包裹在 extern "C" { }
當(dāng)中棋弥,和前面的系統(tǒng)創(chuàng)建的稍微有些不同核偿,通過這個(gè)大括號(hào)包裹,我們就不需要每個(gè)方法都添加單獨(dú)的 extern "C"
開頭了顽染。
另外漾岳,由于 FFmpeg
是使用 C
語言編寫的,所在 C++
文件中引用 #include
的時(shí)候家乘,也需要包裹在 extern "C" { }
蝗羊,才能正確的編譯。
方法的新建就不用說了仁锯,和前面介紹的命名方法一致。
在方法中翔悠,使用 FFmpeg
提供的方法 av_codec_next
业崖,獲取到 FFmpeg 的編解碼器,然后通過循環(huán)遍歷蓄愁,將所有的音視頻編解碼器信息拼接起來双炕,最后返回給 Java
層。
至此撮抓,FFmpeg
加入到工程中妇斤,并被調(diào)用。
如果一切正常,App運(yùn)行后站超,就會(huì)顯示出 FFmpeg
音視頻編解碼器的信息荸恕。
如果由提示
so
或者頭文件
找不到,需要檢查CMakeLists.txt
中設(shè)置的so
和頭文件
的路徑是否正確死相。