基于 FFmpeg 的播放器 demo
這里的播放器演示程序用于播放一個本地文件,因而不需要關(guān)心播放網(wǎng)絡(luò)上的媒體數(shù)據(jù)時的網(wǎng)絡(luò)傳輸問題。
對于播放本地媒體文件的播放器來說,所要完成的工作主要包括:解封裝 -> 音頻解碼/視頻解碼 -> 對于音頻來說,還需要做格式轉(zhuǎn)換和重采樣 -> 音頻渲染和視頻渲染。本文的代碼位于 GitHub ffmpeg-mediaplayer-android。
音頻渲染
音頻播放通過 Android 的 OpenSL ES 接口實現(xiàn)。具體的代碼基于 ndk-samples 中的 audio-echo 修改獲得。
ndk-samples 中的 audio-echo 在播放或錄制的緩沖區(qū) overflow 時,會自動停止播放或錄制,demo 中針對此問題做了一些修改。此外,修改 audio-echo 的代碼以支持定制播放的音頻內(nèi)容。
為 Android 應(yīng)用集成 ffmpeg 庫
通過 mobile-ffmpeg 編譯獲得 ffmpeg 的幾個庫文件,這個工程所基于的 ffmpeg 版本還算比較新,為 FFmpeg 4.4 的。在 Mac 平臺,要為 Android 平臺編譯 ffmpeg 的動態(tài)鏈接庫,首先需要安裝 Android 的 SDK 和 NDK 包。隨后,為 android 平臺編譯動態(tài)鏈接庫的方法為:
% export ANDROID_NDK_ROOT=/Users/henryhan/bin/android-ndk-r21e % export ANDROID_HOME=/Users/henryhan/Library/Android/sdk % ./android.sh編譯方法基本上就是設(shè)置環(huán)境變量 ANDROID_NDK_ROOT 和 ANDROID_HOME 分別只想 NDK 和 SDK 的路徑,然后執(zhí)行 mobile-ffmpeg 提供的腳本文件 android.sh,android.sh 腳本會完成所有的編譯動作。
android.sh 腳本的執(zhí)行過程大致為:mobile-ffmpeg/android.sh -> mobile-ffmpeg/build/main-android.sh -> mobile-ffmpeg/build/android-ffmpeg.sh,mobile-ffmpeg/build/android-ffmpeg.sh 腳本中可以看到比較詳細(xì)的編譯配置。編譯過程中,編譯日志都會輸出到 mobile-ffmpeg/build.log,從這個文件中可以看到比較詳細(xì)的編譯過程的信息。
在 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因而編譯出來的頭文件和動態(tài)鏈接庫文件都將被安裝到 mobile-ffmpeg/prebuilt/android-$(get_target_build)/ffmpeg,如為 x86_64 編譯出來的動態(tài)鏈接庫被安裝在了 mobile-ffmpeg/prebuilt/android-x86_64/ffmpeg/lib 目錄下,x86_64 平臺的頭文件被放在了 mobile-ffmpeg/prebuilt/android-x86_64/ffmpeg/include 目錄下,而為 arm64 編譯出來的動態(tài)鏈接庫被安裝在了 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開發(fā)(二)——使用CMake引入第三方so庫及頭文件 和 NDK–CMakeLists配置第三方so庫 在 CMake 中添加預(yù)編譯的庫,大體方法為:
- 將 ffmpeg 庫的幾個動態(tài)鏈接庫文件和頭文件拷貝到適當(dāng)?shù)奈恢谩?/li>
- 為 CMake 添加庫。當(dāng)需要添加多個 so 庫依賴時,也需要為 CMake 添加多個 add_library() 項。
- 設(shè)置庫二進(jìn)制文件的搜索路徑。需要添加多個 so 庫依賴時,也需要為 CMake 添加多個 set_target_properties() 項。
- 設(shè)置頭文件搜索路徑。為 JNI 的 target 添加頭文件搜索路徑,添加一個 target_include_directories(),但其中可以包含一個或多個路徑。
- 設(shè)置鏈接依賴。為 JNI 的 target 添加庫依賴,每個庫一個 target_link_libraries() 的項。
在通過 CMake 的自動打包預(yù)構(gòu)建依賴項時,不再需要在 build.gradle 中像下面這樣設(shè)置預(yù)構(gòu)建項的地址:
sourceSets {main {jniLibs.srcDirs = ["src/main/libs"]}}有了 Android Gradle 插件 4.0,上述配置不再是必需的,并且會導(dǎo)致構(gòu)建失敗:
----------- * 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 音頻流 -> 經(jīng)過樣本格式轉(zhuǎn)換(樣本格式由 32 位 float 型轉(zhuǎn)為 16 為有符號整型),重采樣(采樣率由 48 kHz 轉(zhuǎn)為 44.1 kHz),和通道數(shù)轉(zhuǎn)換(從立體聲雙通道轉(zhuǎn)為單通道)獲得適合于送給 Android 平臺播放的裸 PCM 音頻流。
解碼音頻直接獲得的是所謂 Non-interleaved 格式的裸 PCM 音頻流,即內(nèi)存中保存音頻數(shù)據(jù)的方式為 LLLL…RRRR…,先是所有的左聲道的數(shù)據(jù),后是右聲道的數(shù)據(jù)。這種格式保存的數(shù)據(jù)易于做數(shù)據(jù)處理,如編解碼等,一般情況下音頻數(shù)據(jù)處理都是針對某一個聲道的連續(xù)數(shù)據(jù)的。但這種格式保存的數(shù)據(jù)對于播放和傳輸?shù)葓鼍安皇呛苡押?#xff0c;這會要求播放設(shè)備或者是數(shù)據(jù)的接收端拿到所有數(shù)據(jù)才能開始播放。因而播放設(shè)備一般請求的裸 PCM 音頻格式為所謂的 interleaved 的,即以 LRLRLRLR… 這種方式保存的數(shù)據(jù)。
在 ffmpeg 的術(shù)語和概念體系中,用 AVSampleFormat 的 planar 來表示這種音頻數(shù)據(jù)保存格式的差異。對于 planar 的音頻 PCM 數(shù)據(jù)保存格式,其保存音頻數(shù)據(jù)的方式為 LLLL…RRRR…,如 AV_SAMPLE_FMT_S16P,AV_SAMPLE_FMT_FLTP 這些格式,而非 planar 的格式,其保存音頻數(shù)據(jù)的方式則為 LRLRLRLR… 這種。
對于音頻重采樣,需要注意保持輸入樣本格式與實際數(shù)據(jù)格式的一致性,如解碼出來的格式為 AV_SAMPLE_FMT_FLTP,但為了后面的處理方便,將解碼出來的音頻數(shù)據(jù)做了 interleave,將音頻樣本數(shù)據(jù)格式轉(zhuǎn)為了 AV_SAMPLE_FMT_FLT,則也應(yīng)該通過 av_opt_set_sample_fmt(swr_ctx_, "in_sample_fmt", format, 0); 將輸入樣本格式設(shè)置為 AV_SAMPLE_FMT_FLT。ffmpeg 提供了一個 API enum AVSampleFormat av_get_alt_sample_fmt(enum AVSampleFormat sample_fmt, int planar); 來做這種格式的轉(zhuǎn)換。
整個解封裝和解碼過程由播放線程驅(qū)動。播放線程中,請求播放數(shù)據(jù)的回調(diào)上來的時候,檢查緩沖區(qū)里是否有一定量的解碼音視頻數(shù)據(jù),當(dāng)數(shù)據(jù)不足時,通過 demuxer 和 decoder 解封裝及解碼音視頻數(shù)據(jù),并保存在緩沖區(qū)中。
解碼和解封裝過程的測試,可以將解碼出來的數(shù)據(jù)直接保存在文件中,然后通過 ffplay 之類的工具播放檢查。
采用 ffplay 查看YUV數(shù)據(jù)包括視頻或者圖片:
$ ffplay -f rawvideo -video_size 854x480 trailer.yuv注:
(1)-f rawvideo:這個選項可加可不加。
(2)YUV 文件不包涵寬高數(shù)據(jù),所以必須用 -video_size 指定寬和高,格式為:widthxheight
(3)test.yuv可以是一幀(圖片)或者多幀(視頻)數(shù)據(jù)
裸 PCM 數(shù)據(jù)播放命令如下:
$ ffplay -f f32le -ac 2 -ar 48000 trailer_.pcm $ ffplay -f s16le -ac 1 -ar 44100 trailer_441.pcm上面的命令用于播放樣本格式為 32 位 float,采樣率為 48 kHz,通道數(shù)為 2 的裸 PCM 音頻流。下面的命令用于播放樣本格式為 16 位有符號整型,采樣率為 44.1 kHz,通道數(shù)為 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 在當(dāng)前的開發(fā)環(huán)境下基本上沒法跑,這個 demo 的 gradle 版本過老,同時它包的 ffmpeg so 只有 armeabi ABI 的,但代碼還是有一定參考價值的。不過代碼里有非常多的資源泄漏,既有 android 的資源泄漏,如 native window handle,也有 ffmpeg 的對象的泄漏,如 AVFrame 和 SwsContext 等。
如前所述,解封裝和解碼過程由音頻播放線程驅(qū)動,音頻播放線程會把解碼后的音視頻數(shù)據(jù)放進(jìn)緩沖區(qū)中。應(yīng)用中會啟動專門的視頻播放線程,視頻播放線程定時向視頻緩沖區(qū)請求 YUV 視頻數(shù)據(jù),如果視頻 YUV 數(shù)據(jù)存在就拿去播放。
播放控制及音視頻同步
Android 調(diào)用系統(tǒng)文件選擇器并拿到文件路徑的方法參考了 Android 選擇文件并返回路徑 及其代碼 ForeverLibrary 。
對于拖拽的實現(xiàn),用戶在拖拽進(jìn)度條時,在一個全局狀態(tài)中設(shè)置了進(jìn)度值,在音頻播放線程中請求音頻數(shù)據(jù)的回調(diào)中檢查進(jìn)度值,并根據(jù)進(jìn)度值,通過 ffmpeg 的 API 實現(xiàn) seek。ffmpeg 的 seek API 用法參考了 How to seek in FFmpeg C/C++ 等。
對于暫停的實現(xiàn),用戶在點擊暫停鍵時,同樣在全局狀態(tài)中設(shè)置暫停狀態(tài),在音頻播放線程中請求音頻數(shù)據(jù)的回調(diào)中檢查暫停狀態(tài),并采取一定的措施。
測試文件下載
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
開發(fā)過程中的問題分析調(diào)查
解封裝出來的 H264 和 AAC 文件,由于 H264 有編碼幀的分割字節(jié)串,AAC 有 ADTS header,轉(zhuǎn)儲的這些文件可以直接用一些播放器播放,如 VLC player。
對于解碼出來的 YUV 和 PCM 數(shù)據(jù),ffplay 可以用于播放裸 YUV 和 PCM 的音視頻流,但需要正確指定裸流的參數(shù)。Audacity 可以用于播放裸的 PCM 音頻流并查看音頻信號的波形。
我們在開發(fā)調(diào)試時,也可以通過命令行方式顯示的給某一個包授予權(quán)限,如下所示
adb shell pm grant com.xxx.xxx android.permission.READ_EXTERNAL_STORAGE
總結(jié)
以上是生活随笔為你收集整理的基于 FFmpeg 的播放器 demo的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 用 70 行代码给你自己写一个 stra
- 下一篇: PulseAudio 设计和实现浅析