天天看點

30.FFmpeg+OpenGLES+OpenSLES播放器實作(四.Android Studio ndk開發環境和CMakeLists腳本編寫)

項目源碼 FFmpeg開發文檔
1.環境配置

ffmpeg庫已經編譯好了,接下來準備将so引入Android studio進行開發

我們建立一個新的項目,注意在建立過程中這幾個選項的勾選

30.FFmpeg+OpenGLES+OpenSLES播放器實作(四.Android Studio ndk開發環境和CMakeLists腳本編寫)

6C~L[SE8UA3Z]JEAFB~CU24.png

勾選添加C++支援,Android studio會自動幫我們做一些配置,後邊進行簡單的解釋

30.FFmpeg+OpenGLES+OpenSLES播放器實作(四.Android Studio ndk開發環境和CMakeLists腳本編寫)

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中配置路徑,這裡選用第二種方式

30.FFmpeg+OpenGLES+OpenSLES播放器實作(四.Android Studio ndk開發環境和CMakeLists腳本編寫)

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} )
           

繼續閱讀