用 CMake 爲 ijkplayer 增長 android native debug 支持

給 ijkplayer 增長了 cmake 構建方式,支持在 android studio 中進行 c 代碼斷點調試。
直接上手請前往 github.com/befovy/ijkp…java

若是想了解一下整個過程請繼續閱讀本文。android

ijkplayer 簡介

ijkplayer 是由 B 站開源的一款移動端播放器。基於 ffmpeg 中 ffplay 開發,並添加 android MediaCodec、iOS VideotTolbox 視頻硬解碼支持以及 opengles、NativeWindow(android) 渲染,在當前的移動直播熱潮中,被大量使用。 但使用 ijkplayer 進行 android 播放器開發的過程當中,調試起來稍微有些麻煩,官方提供了ndk 調試的幾個patch 文件,而且有一段描述,是基於 ndk-build android.mk 工具鏈構建的。說實話因爲沒有作成開箱即用,我也沒認真看過這部份內容。 因爲我對 cmake 的瞭解更多一些,加之 google 官方也逐漸將 ndk 的工具鏈由 ndk-build 轉向 cmake,並在 android studio 中對 cmake 提供了更好的支持。同時 cmake 也是跨平臺的構建工具,方便後續將 ijkplayer 擴展至其它平臺,因此我用 cmake 從新實現了 ijkplayer 的構建過程。git

只要不是修改 ffmpeg 的源代碼,其它內容包括 ijkplayer 的 c 代碼修改, java 層修改,均可以在 android studio 中一鍵運行、調試。github

關於 ijkplayer 內部核心代碼的分析,多個線程的工做內容,解碼部分,音視頻同步控制等部分的更深刻的介紹,能夠參考金山雲視頻的分析文章 www.jianshu.com/p/daf0a61cc… , 我最開始也是從這一篇文章入手逐漸讀懂 ijkplayer 源代碼的。shell

題外話:ijkplayer 在 github 上的提交記錄自 k0.8.8 版本以後就再也沒有了,好多人評論說項目已經放棄維護了,可是細心尋找會發如今另外一個項目 Bilibili/ffmpeg 中,還不斷有提交記錄。說明項目仍是在不斷開發,只是重心放在實現新的 ffmpeg protocol 上,沒有及時維護 ijkplayer 項目上的 issues。bash

下面具體說說一些具體的修改內容。架構

android 工程結構調整

ijkplayer 的項目自己具備多個 android module。app

ijkplayer-arm64
ijkplayer-armv5
ijkplayer-armv7a
ijkplayer-x86
ijkplayer-x86_64   # 上面幾個都是一個module 殼,shell 命令編譯好 so 庫放在指定目錄中
ijkplayer-example  # 示例APP項目
ijkplayer-exo      # 對 google exoplayer 的一層封裝適,配合ijkplayer 定義的 mediaplyer 接口
ijkplayer-java     # ijkplayer java 層代碼,對接 native 層代碼,以及硬解碼器的選擇
複製代碼

項目中爲不一樣的 cpu 架構都單獨設置了 module,這幾個 module 最大的區別就是在 Application.mk 中 APP_ABIAPP_PLATFORM 的值不一樣,其中 APP_PLATFORM 取值和 build.gradle 中定義的 minSdkVersion 相同。 ijkplayer 最低居然支持 sdk version 9,確實感謝 B 站項目組初期在這方面的辛苦付出。ide

這種 module 確實方便採用 ndk-build 工具的項目管理,可是我在使用過程當中仍是以爲有些不太方便之處:工具

  1. 在 android studio 中搜索 c 代碼,搜索結果中重複出現屢次
  2. android studio 中的 c 代碼沒有語法高亮。

對於問題1,我找到的解決方案是:

//NOTE:: run `./gradlew ideaModule` to apply exclude dirs
apply plugin: 'idea'

idea.module {
    excludeDirs += file('ijkplayer-armv7a/src/')
    excludeDirs += file('ijkplayer-arm64/src/')
    excludeDirs += file('ijkplayer-armv5/src/')
    excludeDirs += file('ijkplayer-x86/src/')
    excludeDirs += file('ijkplayer-x86_64/src/')
}
複製代碼

在工程根 build.gradle 中增長這樣的配置,並執行 ./gradlew ideaModule, 就會在搜索結果中排除來自這幾個文件夾的內容。

對於問題2,我固然是用 cmake 解決了,新建一個 module, 取名 fijkplayer-full。換個名字方便和原項目進行區分,同時 f 也表示full, 我在這個 module 中囊括了原來 6 個 module 提供的內容。 f 還有別的含義,有機會在其它地方會提到。 在 fijkplayer-full 的 build.gradel 中增長 cmake 的配置以及引入 ijkplayer-java module 的 java 源代碼。

android {
    .......
    .......
    sourceSets.main {
        java.srcDirs = ["$rootProject.rootDir/ijkplayer-java/src/main/java/"]
        jni.srcDirs = [] // This prevents the auto generation of Android.mk
    }

    externalNativeBuild {
        cmake {
            path 'src/main/CMakeLists.txt'
        }
    }
}
複製代碼

修改後,fijkplayer-full 就成了 "六神合體" 了。

對於不一樣cpu 架構區分不一樣的 minSdkVersion 能夠經過 productFlavors 實現,這一塊我尚未具體考慮充分,目前是統一設置了你們用得最多的 minSdkVersion 16。

此部分修改的完整內容點我查看, github.com/befovy/ijkplayer

CMakeLists.txt

編寫 CMakeLists.txt 徹底是個翻譯工做,將 Android.mk 中的規則翻譯過來就行。完成後經過 CMake 方式最終生成的 aar 和原始方式生成的aar 文件大小几乎沒什麼差異,見下圖。 fijkplayer-full 只編譯了 armv7a 架構,和 ijkplayer-armv7a 模塊編譯結果的對比。 fijkplayer-full 中 classes.jar 較大是由於包含了 ijkplayer-java module 中的內容。 還能夠調整CMake 參數進一步減少生成庫的大小。 圖片

轉到 CMake 工具鏈的過程還遇到下面幾個問題。

libffmpeg.so 找不到

libffmpeg.so 是事先經過 shell 命令編譯成的,在 ffmpeg build 文件夾中,而且幾乎不會改變,除非是要修改 ffmpeg。 在 CMake 中經過

add_library(ijkffmpeg SHARED IMPORTED)
set_target_properties( # Specifies the target library.
    ijkffmpeg
    PROPERTIES
    IMPORTED_LOCATION ${FFMPAG_SHARED_DIR}/libijkffmpeg.so
)
複製代碼

引入預編譯好的 ijkffmpeg,在編譯 ijkplayer 的過程能夠順利完成連接。可是打包運行找不到 libijkffmpeg.so。分析最終的 apk 以及 fijkplayer-full.aar ,發現其中都沒有 libijkffmpeg.so 文件,因此是打包 aar 的時候沒有將這個文件打包進去。查看工程目錄結構,發現編譯好的 libijkplayer.so 和 libijksdl.so 都在文件夾 /build/intermediates/cmake/debug/obj/armeabi-v7a 中,因此還須要修改 CMakeLists.txt 在每次編譯的時候將 libijkffmpeg.so 複製過去。最後的解決方案是

add_library(ijkffmpeg SHARED IMPORTED)
set_target_properties( # Specifies the target library.
    ijkffmpeg
    PROPERTIES
    IMPORTED_LOCATION ${FFMPAG_SHARED_DIR}/libijkffmpeg.so
    )

add_custom_target(cpffmpeg
    COMMAND mkdir -p ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}
    COMMAND cp ${FFMPAG_SHARED_DIR}/libijkffmpeg.so ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}
)

add_dependencies(ijkffmpeg cpffmpeg)
複製代碼

沒有找到一條命令可以解決的,因此麻煩了一點多寫了點,用兩個cmake 命令解決。

cpufeatures 編譯出錯

原項目中用到了 ndk 中的 cpufeatures,我在 CMake 文件中也引入並編譯了 cpufeatures,但在編譯 x86 以及 x86_64 架構的時候卻出現了編譯錯誤,具體是一處未識別的彙編指令。 最後發現是 ndk r15c 版本中 文件 $ANDROID_NDK/sources/android/cpufeatures/cpu-features.c 中少了幾個換行符,補上以後(下面內容)編譯就沒問題了。

static __inline__ void x86_cpuid(int func, int values[4]) {
    int a, b, c, d;
    /* We need to preserve ebx since we're compiling PIC code */
    /* this means we can't use "=b" for the second output register */
    __asm__ __volatile__ ( \
      "push %%ebx"
      "cpuid\n" \
      "mov %%ebx, %1\n"
      "pop %%ebx\n"
      : "=a" (a), "=r" (b), "=c" (c), "=d" (d) \
      : "a" (func) \
    );
    values[0] = a;
    values[1] = b;
    values[2] = c;
    values[3] = d;
}
複製代碼

意外的是我最終發現 ijkplayer 項目中沒有用到 cpufeatures 的地方,多是 gpl 協議的 android-ndk-profiler 部分用了吧,但項目中默認使用了一個假的 android-ndk-profiler,此部分沒有去深究,性能分析使用 simpleperf 就足夠了。 最後仍是去掉 CMake 中 cpufeatures 的部分。

此部分 CMakeLists.txt 的完整內容請看
github.com/befovy/ijkp…
github.com/befovy/ijkp…
github.com/befovy/ijkp…
github.com/befovy/ijkp…

結束語

😄 附一張 NDK 斷點調試的大圖 👏

image

剛開始寫博客,可能有些囉嗦,多多包涵。或是沒有說明白的地方,請大方的前往原始地址進行評論。

最近對 Flutter 產生了興趣,並打算用 ijkplayer 開發一個 Flutter 的播放器 Plugin, 祝我早日完成。

本文經過 mirror 和 hugo 生成,原始地址 github.com/befovy/befo…

相關文章
相關標籤/搜索