基于 FFmpeg 的播放器 demo
這里的播放器演示程序用于播放一個本地文件,因而不需要關心播放網絡上的媒體數據時的網絡傳輸問題。
對于播放本地媒體文件的播放器來說,所要完成的工作主要包括:解封裝 -> 音頻解碼/視頻解碼 -> 對于音頻來說,還需要做格式轉換和重采樣 -> 音頻渲染和視頻渲染。本文的代碼位于 GitHub ffmpeg-mediaplayer-android。
音頻渲染
音頻播放通過 Android 的 OpenSL ES 接口實現。具體的代碼基于 ndk-samples 中的 audio-echo 修改獲得。
ndk-samples 中的 audio-echo 在播放或錄制的緩沖區 overflow 時,會自動停止播放或錄制,demo 中針對此問題做了一些修改。此外,修改 audio-echo 的代碼以支持定制播放的音頻內容。
為 Android 應用集成 ffmpeg 庫
通過 mobile-ffmpeg 編譯獲得 ffmpeg 的幾個庫文件,這個工程所基于的 ffmpeg 版本還算比較新,為 FFmpeg 4.4 的。在 Mac 平臺,要為 Android 平臺編譯 ffmpeg 的動態鏈接庫,首先需要安裝 Android 的 SDK 和 NDK 包。隨后,為 android 平臺編譯動態鏈接庫的方法為:
% export ANDROID_NDK_ROOT=/Users/henryhan/bin/android-ndk-r21e % export ANDROID_HOME=/Users/henryhan/Library/Android/sdk % ./android.sh編譯方法基本上就是設置環境變量 ANDROID_NDK_ROOT 和 ANDROID_HOME 分別只想 NDK 和 SDK 的路徑,然后執行 mobile-ffmpeg 提供的腳本文件 android.sh,android.sh 腳本會完成所有的編譯動作。
android.sh 腳本的執行過程大致為:mobile-ffmpeg/android.sh -> mobile-ffmpeg/build/main-android.sh -> mobile-ffmpeg/build/android-ffmpeg.sh,mobile-ffmpeg/build/android-ffmpeg.sh 腳本中可以看到比較詳細的編譯配置。編譯過程中,編譯日志都會輸出到 mobile-ffmpeg/build.log,從這個文件中可以看到比較詳細的編譯過程的信息。
在 mobile-ffmpeg/build/android-ffmpeg.sh 腳本中可以看到:
LIB_NAME="ffmpeg" . . . . . . ./configure \--cross-prefix="${BUILD_HOST}-" \--sysroot="${ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/${TOOLCHAIN}/sysroot" \--prefix="${BASEDIR}/prebuilt/android-$(get_target_build)/${LIB_NAME}" \ . . . . . . rm -rf ${BASEDIR}/prebuilt/android-$(get_target_build)/${LIB_NAME} make install 1>>${BASEDIR}/build.log 2>&1因而編譯出來的頭文件和動態鏈接庫文件都將被安裝到 mobile-ffmpeg/prebuilt/android-$(get_target_build)/ffmpeg,如為 x86_64 編譯出來的動態鏈接庫被安裝在了 mobile-ffmpeg/prebuilt/android-x86_64/ffmpeg/lib 目錄下,x86_64 平臺的頭文件被放在了 mobile-ffmpeg/prebuilt/android-x86_64/ffmpeg/include 目錄下,而為 arm64 編譯出來的動態鏈接庫被安裝在了 mobile-ffmpeg/prebuilt/android-arm64/ffmpeg/lib 目錄下,arm64 平臺的頭文件被放在了 mobile-ffmpeg/prebuilt/android-arm64/ffmpeg/include 目錄下,其它 ABI 依此類推。
mobile-ffmpeg 的 android.sh 還會編譯生成 ffmpeg 庫的 AAR 文件,位于 mobile-ffmpeg/prebuilt/android-aar/mobile-ffmpeg/mobile-ffmpeg.aar。
可以參考文章 Android studio中NDK開發(二)——使用CMake引入第三方so庫及頭文件 和 NDK–CMakeLists配置第三方so庫 在 CMake 中添加預編譯的庫,大體方法為:
- 將 ffmpeg 庫的幾個動態鏈接庫文件和頭文件拷貝到適當的位置。
- 為 CMake 添加庫。當需要添加多個 so 庫依賴時,也需要為 CMake 添加多個 add_library() 項。
- 設置庫二進制文件的搜索路徑。需要添加多個 so 庫依賴時,也需要為 CMake 添加多個 set_target_properties() 項。
- 設置頭文件搜索路徑。為 JNI 的 target 添加頭文件搜索路徑,添加一個 target_include_directories(),但其中可以包含一個或多個路徑。
- 設置鏈接依賴。為 JNI 的 target 添加庫依賴,每個庫一個 target_link_libraries() 的項。
在通過 CMake 的自動打包預構建依賴項時,不再需要在 build.gradle 中像下面這樣設置預構建項的地址:
sourceSets {main {jniLibs.srcDirs = ["src/main/libs"]}}有了 Android Gradle 插件 4.0,上述配置不再是必需的,并且會導致構建失敗:
----------- * What went wrong: Execution failed for task ':app:mergeReleaseNativeLibs'. > A failure occurred while executing com.android.build.gradle.internal.tasks.MergeJavaResWorkAction> 2 files found with path 'lib/x86/libswscale.so' from inputs:- /Users/henryhan/Projects/Shopee/henry-entrytask/app/build/intermediates/merged_jni_libs/release/out- /Users/henryhan/Projects/Shopee/henry-entrytask/app/build/intermediates/cmake/release/objIf you are using jniLibs and CMake IMPORTED targets, seehttps://developer.android.com/r/tools/jniLibs-vs-imported-targets更多信息,可以參考 Google 的官方文檔。
通過 System.loadLibrary() 加載 ffmpeg 和 JNI 的 so 文件時,需要注意加載 so 的順序,被依賴的 so 文件需要先加載,依賴的 so 需要后加載,如 swscale 庫依賴 avutil 庫,則需要 System.loadLibrary("avutil"); 放在 System.loadLibrary("swscale"); 的前面,否則會報錯說符號找不到。
ffmpeg 是一個純 C 的庫,就像在任何 C++ 代碼中使用 FFmpeg 的 API 那樣,如果 JNI 代碼用 C++ 寫,ffmpeg 頭文件的 include 要放在 extern "C" 里。
音視頻解封裝及解碼
代碼參考了 HelloFFmpeg 和 goffmpeg 這些工程的代碼。
視頻:解封裝獲得 H264 編碼幀 -> 解碼獲得裸 YUV 視頻流。
音頻:解封裝獲得 AAC 編碼音頻幀 -> 解碼獲得裸 PCM 音頻流 -> 經過樣本格式轉換(樣本格式由 32 位 float 型轉為 16 為有符號整型),重采樣(采樣率由 48 kHz 轉為 44.1 kHz),和通道數轉換(從立體聲雙通道轉為單通道)獲得適合于送給 Android 平臺播放的裸 PCM 音頻流。
解碼音頻直接獲得的是所謂 Non-interleaved 格式的裸 PCM 音頻流,即內存中保存音頻數據的方式為 LLLL…RRRR…,先是所有的左聲道的數據,后是右聲道的數據。這種格式保存的數據易于做數據處理,如編解碼等,一般情況下音頻數據處理都是針對某一個聲道的連續數據的。但這種格式保存的數據對于播放和傳輸等場景不是很友好,這會要求播放設備或者是數據的接收端拿到所有數據才能開始播放。因而播放設備一般請求的裸 PCM 音頻格式為所謂的 interleaved 的,即以 LRLRLRLR… 這種方式保存的數據。
在 ffmpeg 的術語和概念體系中,用 AVSampleFormat 的 planar 來表示這種音頻數據保存格式的差異。對于 planar 的音頻 PCM 數據保存格式,其保存音頻數據的方式為 LLLL…RRRR…,如 AV_SAMPLE_FMT_S16P,AV_SAMPLE_FMT_FLTP 這些格式,而非 planar 的格式,其保存音頻數據的方式則為 LRLRLRLR… 這種。
對于音頻重采樣,需要注意保持輸入樣本格式與實際數據格式的一致性,如解碼出來的格式為 AV_SAMPLE_FMT_FLTP,但為了后面的處理方便,將解碼出來的音頻數據做了 interleave,將音頻樣本數據格式轉為了 AV_SAMPLE_FMT_FLT,則也應該通過 av_opt_set_sample_fmt(swr_ctx_, "in_sample_fmt", format, 0); 將輸入樣本格式設置為 AV_SAMPLE_FMT_FLT。ffmpeg 提供了一個 API enum AVSampleFormat av_get_alt_sample_fmt(enum AVSampleFormat sample_fmt, int planar); 來做這種格式的轉換。
整個解封裝和解碼過程由播放線程驅動。播放線程中,請求播放數據的回調上來的時候,檢查緩沖區里是否有一定量的解碼音視頻數據,當數據不足時,通過 demuxer 和 decoder 解封裝及解碼音視頻數據,并保存在緩沖區中。
解碼和解封裝過程的測試,可以將解碼出來的數據直接保存在文件中,然后通過 ffplay 之類的工具播放檢查。
采用 ffplay 查看YUV數據包括視頻或者圖片:
$ ffplay -f rawvideo -video_size 854x480 trailer.yuv注:
(1)-f rawvideo:這個選項可加可不加。
(2)YUV 文件不包涵寬高數據,所以必須用 -video_size 指定寬和高,格式為:widthxheight
(3)test.yuv可以是一幀(圖片)或者多幀(視頻)數據
裸 PCM 數據播放命令如下:
$ ffplay -f f32le -ac 2 -ar 48000 trailer_.pcm $ ffplay -f s16le -ac 1 -ar 44100 trailer_441.pcm上面的命令用于播放樣本格式為 32 位 float,采樣率為 48 kHz,通道數為 2 的裸 PCM 音頻流。下面的命令用于播放樣本格式為 16 位有符號整型,采樣率為 44.1 kHz,通道數為 1 的裸 PCM 音頻流
ffmpeg -i trailer.mp4 -ar 44100 -ac 1 output_1.mp4參考了如下文檔:
利用av_read_frame解碼h264、mp4多媒體文件為yuv
ffmpeg 查看YUV圖片/視頻
FFmpeg解碼MP4文件為h264和YUV文件
FFmpeg —— 9.示例程序(三):音視頻分離(分離為PCM、YUV格式)
FFmpeg 重采樣示例代碼
音視頻播放
視頻渲染參考了 Android 基于FFmpeg的視頻播放渲染 CMake + ANativeWindow,也參考了其代碼 NDKtest,這個 demo 在當前的開發環境下基本上沒法跑,這個 demo 的 gradle 版本過老,同時它包的 ffmpeg so 只有 armeabi ABI 的,但代碼還是有一定參考價值的。不過代碼里有非常多的資源泄漏,既有 android 的資源泄漏,如 native window handle,也有 ffmpeg 的對象的泄漏,如 AVFrame 和 SwsContext 等。
如前所述,解封裝和解碼過程由音頻播放線程驅動,音頻播放線程會把解碼后的音視頻數據放進緩沖區中。應用中會啟動專門的視頻播放線程,視頻播放線程定時向視頻緩沖區請求 YUV 視頻數據,如果視頻 YUV 數據存在就拿去播放。
播放控制及音視頻同步
Android 調用系統文件選擇器并拿到文件路徑的方法參考了 Android 選擇文件并返回路徑 及其代碼 ForeverLibrary 。
對于拖拽的實現,用戶在拖拽進度條時,在一個全局狀態中設置了進度值,在音頻播放線程中請求音頻數據的回調中檢查進度值,并根據進度值,通過 ffmpeg 的 API 實現 seek。ffmpeg 的 seek API 用法參考了 How to seek in FFmpeg C/C++ 等。
對于暫停的實現,用戶在點擊暫停鍵時,同樣在全局狀態中設置暫停狀態,在音頻播放線程中請求音頻數據的回調中檢查暫停狀態,并采取一定的措施。
測試文件下載
1、地址:http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4 1分鐘
2、地址:http://vjs.zencdn.net/v/oceans.mp4
3、地址:https://media.w3.org/2010/05/sintel/trailer.mp4 52秒
4、地址:http://mirror.aarnet.edu.au/pub/TED-talks/911Mothers_2010W-480p.mp4 10分鐘
其他各種格式,MP4, flv, mkv, 3gp 視頻下載
https://www.sample-videos.com/index.php#sample-mp4-video
MPlayer 官方提供的各種視頻格式的測試文件,載地址:http://samples.mplayerhq.hu/
測試視頻的下載地址
http://ultravideo.cs.tut.fi/#testsequences
http://www.tanimoto.nuee.nagoya-u.ac.jp/~fukushima/mpegftv/
http://www.tanimoto.nuee.nagoya-u.ac.jp/~fukushima/mpegftv/Akko.htm
測試視頻的下載地址
測試視頻,音頻,圖片下載
測試用視頻下載
測試用在線視頻地址
常用各種視頻測試文件
sample-videos
開發過程中的問題分析調查
解封裝出來的 H264 和 AAC 文件,由于 H264 有編碼幀的分割字節串,AAC 有 ADTS header,轉儲的這些文件可以直接用一些播放器播放,如 VLC player。
對于解碼出來的 YUV 和 PCM 數據,ffplay 可以用于播放裸 YUV 和 PCM 的音視頻流,但需要正確指定裸流的參數。Audacity 可以用于播放裸的 PCM 音頻流并查看音頻信號的波形。
我們在開發調試時,也可以通過命令行方式顯示的給某一個包授予權限,如下所示
adb shell pm grant com.xxx.xxx android.permission.READ_EXTERNAL_STORAGE
總結
以上是生活随笔為你收集整理的基于 FFmpeg 的播放器 demo的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 用 70 行代码给你自己写一个 stra
- 下一篇: PulseAudio 设计和实现浅析