30.Android Studio下FFmpeg的編譯和使用(四.Android Studio ndk開發環境和CMakeLists指令碼編寫)
FFmpegPlayer.git" target="_blank" rel="nofollow,noindex">專案原始碼
1.環境配置
ffmpeg庫已經編譯好了,接下來準備將so引入Android studio進行開發
我們建立一個新的專案,注意在建立過程中這幾個選項的勾選

6C~L[SE8UA3Z]JEAFB~CU24.png
勾選新增C++支援,Android studio會自動幫我們做一些配置,後邊進行簡單的解釋

Y%~`T`0D1PM`(`}}$ENC9G2.png
C++ Standard:使用哪種 C++ 標準。選擇 Toolchain Default 會使用預設的 CMake 設定。有C11和C14兩種,我們選擇C11
Exceptions Support:如果希望啟用對 C++ 異常處理的支援,請選中此複選框。如果啟用此複選框,Android Studio 會將 -fexceptions 標誌新增到模組級 build.gradle 檔案的 cppFlags 中,Gradle 會將其傳遞到 CMake。
Runtime Type Information Support:(Run-Time Type Identification),通過執行時型別資訊程式能夠使用基類的指標或引用來檢查這些指標或引用所指的物件的實際派生型別。如果希望支援 RTTI,請選中此複選框。如果啟用此複選框,Android Studio 會將 -frtti 標誌新增到模組級 build.gradle 檔案的 cppFlags 中,Gradle 會將其傳遞到 CMake。
專案創建出來之後,可以看到,專案預設建立了一個呼叫C++程式碼的小demo輸出一行字串。在app根目錄可以看到一個CMakeLists.txt的檔案,這是新增c++支援後預設建立的cmake指令碼,我們將使用這個指令碼對ffmpeg進行編譯
開啟app目錄下的build.gradle,可以看到下邊兩項配置
apply plugin: 'com.android.application' android { ... defaultConfig { ... externalNativeBuild { cmake { //C++標準選擇C11之後做的配置 cppFlags "-std=c++11" } } } externalNativeBuild { cmake { //指定的CMakeLists指令碼檔案的路徑 path "CMakeLists.txt" } } }
這是工具自動做好的配置,接下來還需要我們手動做一些處理,來完善ffmpeg編譯的環境。
第一. ffmpeg播放視訊會涉及到操作記憶體卡,所以需要配置儲存許可權,6.0及以上Android版本還要記得動態許可權獲取的配置,這裡不多說
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
第二. 在app main目錄下建立jniLibs目錄,將我們建立好的so放在這個目錄下,或者直接把so放在libs目錄下,但是這種方式需要我們在build.gradle中配置路徑,這裡選用第二種方式

dir.png.png
然後開啟app下build.gradle檔案,在android/defaultConfig節點下新增如下配置,指定so檔案的存放目錄,預設是jniLibs
sourceSets{ //將so放在libs資料夾下,需要指定這個路徑,因為預設路徑是jniLibs main{ jniLibs.srcDirs=['libs'] } }
還有一點,因為我們只編譯了armeabi-v7a版本的ffmpeg,所以需要指定過濾版本,在android/defaultConfig/externalNativeBuild節點中新增
ndk{ abiFilters "armeabi-v7a" }
此時,整個build.gradle檔案應該是這樣的(只留下了ndk相關的配置)
apply plugin: 'com.android.application' android { ... defaultConfig { ... externalNativeBuild { cmake { cppFlags "-std=c++11" } //預設情況下,Gradle 會針對 [NDK 支援的 ABI](https://developer.android.google.cn/ndk/guides/abis.html?hl=zh-cn#sa) //將原生庫構建到單獨的 .so檔案中,並將其全部打包到 APK 中。如果希望 Gradle 僅構建和打包原生 //庫的特定 ABI 配置,可以在模組級build.gradle檔案中使用 ndk.abiFilters標誌指定這些配置 ndk{ //abiFilters 'x86', 'x86_64', 'armeabi', 'armeabi-v7a', 'arm64-v8a' abiFilters "armeabi-v7a" } } sourceSets{ //將so放在libs資料夾下,需要指定這個路徑,因為預設路徑是jniLibs main{ jniLibs.srcDirs=['libs'] } } } ... //將 Gradle 關聯到原生庫,需要提供一個指向 CMake 或 ndk-build 指令碼檔案的路徑。在構建應用時,Gradle //會以依賴項的形式執行 CMake 或 ndk-build,並將共享的庫打包到 APK 中 externalNativeBuild { cmake { path "CMakeLists.txt" } } } dependencies { ... }
2.CMakeLists.txt指令碼檔案的編寫
CMake 構建指令碼是一個純文字檔案,必須將其命名為 CMakeLists.txt,一般放在專案根目錄(app的根目錄),後邊會附上我測試成功的CMakeLists.txt,
CMakeLists Android官方教程add_library():
該命令用於向CMake構建指令碼新增原始檔和庫,它有三個引數,每個引數的解釋如下
add_library( //這個引數指定你的原始檔被編譯或者庫被引入後的名字,可以指 //定任意你覺得合適的名字 native-lib //第二個引數有STATIC 和SHARED兩種選擇,SHARED表示會編 //譯成動態庫,STATIC 表示靜態庫 SHARED //這個位置用於指定原始檔的相對路徑(相對於CMakeLists.txt的 //路徑),或者如果你是在引入其他庫,那麼這裡指定IMPORTED //屬性 src/main/cpp/native-lib.cpp )
set_target_properties:
如果你add_library引入的是已經編譯好的庫檔案,那麼你需要通過set_target_properties指定被引入的庫檔案的路徑
//這兩個一一對應,這兩個命令結合可以引入一個so庫,一個so庫對應這兩個命令 add_library( avcodec SHARED IMPORTED) set_target_properties( //指定是給誰設定屬性,這裡是上邊add的avcodec avcodec //指定是設定什麼樣的屬性,這裡是引入的路徑,是一個相對路徑 PROPERTIES IMPORTED_LOCATION //這裡具體指定相對於指令碼檔案的路徑 ${FFMPEG_DIR}/libavcodec.so)
include_directories():
通過上邊兩個命令,庫檔案會被新增進來,這些庫一般會依賴一些標頭檔案,這時我們可以通過include_directories來指定標頭檔案的位置,確保 CMake 在編譯時可以定位到標頭檔案
include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/src/main/cpp/include)
以ffmpeg的編譯為例,我們會add很多的so庫,add_library了好多次,將所有需要的so新增,我們還add了自己的原始檔,最終我們指定了這些原始檔被編譯成ffmpeg的so
add_library( ffmpeg SHARED ${CMAKE_CURRENT_SOURCE_DIR}/src/main/cpp/native-lib.cpp )
這些原始檔依賴於新增的ffmpeg中的庫,所以最終我們要把我們自己編譯的庫和其他這些so連結到一塊,這時就需要命令
target_link_libraries( ffmpeg avcodec avfilter avformat avutil swresample swscale ${log-lib} )
看到這裡有個這樣的引入${log-lib},這個庫是ndk中提供的,通過find_library命令引入
find_library
Android NDK 提供了一套實用的原生 API 和庫。通過將NDK 庫包含到專案的 CMakeLists.txt指令碼檔案中,預構建的 NDK 庫已經存在於 Android 平臺上,因此,無需再構建或將其打包到 APK 中。由於 NDK 庫已經是 CMake 搜尋路徑的一部分,甚至不需要在本地 NDK 安裝中指定庫的位置 - 只需要向 CMake 提供希望使用的庫的名稱,並將其關聯到自己的原生庫上即可。
將 find_library()命令新增到您的 CMake 構建指令碼中以定位 NDK 庫,並將其路徑儲存為一個變數。可以使用此變數在構建指令碼的其他部分引用 NDK 庫。以下示例可以定位Android 特定的日誌支援庫並將其路徑儲存在 log-lib 中
find_library( log-lib log )
3.編譯ffmpeg的CMakeLists.txt完整指令碼
#指定Cmake構建工具的最低版本 cmake_minimum_required(VERSION 3.4.1) #設定標頭檔案路徑 include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src/main/cpp/include) #設定FFmpeg庫路徑變數 #CMAKE_CURRENT_SOURCE_DIR,指的是當前處理的 CMakeLists.txt 所在的路徑,CMAKE_SOURCE_DIR,不論採用何種編譯方式,都是工程頂層目錄 set(FFMPEG_DIR ${CMAKE_CURRENT_SOURCE_DIR}/libs/${ANDROID_ABI}) #新增avcodec add_library(avcodec SHARED IMPORTED) set_target_properties(avcodec PROPERTIES IMPORTED_LOCATION ${FFMPEG_DIR}/libavcodec.so) #新增avfilter add_library(avfilter SHARED IMPORTED) set_target_properties(avfilter PROPERTIES IMPORTED_LOCATION ${FFMPEG_DIR}/libavfilter.so) #新增avformat add_library(avformat SHARED IMPORTED) set_target_properties(avformat PROPERTIES IMPORTED_LOCATION ${FFMPEG_DIR}/libavformat.so) #新增avutil add_library(avutil SHARED IMPORTED) set_target_properties(avutil PROPERTIES IMPORTED_LOCATION ${FFMPEG_DIR}/libavutil.so) #新增swresample add_library(swresample SHARED IMPORTED) set_target_properties(swresample PROPERTIES IMPORTED_LOCATION ${FFMPEG_DIR}/libswresample.so) #新增swscale add_library(swscale SHARED IMPORTED) set_target_properties(swscale PROPERTIES IMPORTED_LOCATION ${FFMPEG_DIR}/libswscale.so) add_library( ffmpeg SHARED ${CMAKE_CURRENT_SOURCE_DIR}/src/main/cpp/native-lib.cpp ) find_library( log-lib log ) target_link_libraries( ffmpeg avcodec avfilter avformat avutil swresample swscale ${log-lib} )