當前位置: 妍妍網 > 碼農

Android 編譯 FFmpeg 6.0 - 支持MediaCodec編解碼

2024-03-06碼農

編譯環境

這次采用的交叉編譯環境是:Macos 13.2 + GCC + Cmake + NDK 21 編譯的第三方庫:x264 + mp3lame + fdk-aac + opencore-amr

交叉編譯

檢測FFmpeg配置是否支持MediaCodec的編碼,確實是支持的,不僅支持 h264 還支持 h265 編碼,結果如下:

./configure --list-encoders | grep mediacodec h264_mediacodec pcm_f64be wmav1 hevc_mediacodec pcm_s24le_planar zlib 在FFmpeg 6.0上不需要再開啟我們MediaCodec的硬體加速了(是哪個版本取消的,我也不知道 😊),可硬體加速的列表如下:

./configure --list-hwaccels av1_d3d11va hevc_d3d11va mpeg2_nvdec vp8_nvdec av1_d3d11va2 hevc_d3d11va2 mpeg2_vaapi vp8_vaapi av1_dxva2 hevc_dxva2 mpeg2_vdpau vp9_d3d11va av1_nvdec hevc_nvdec mpeg2_videotoolbox vp9_d3d11va2 av1_vaapi hevc_vaapi mpeg4_nvdec vp9_dxva2 av1_vdpau hevc_vdpau mpeg4_vaapi vp9_nvdec h263_vaapi hevc_videotoolbox mpeg4_vdpau vp9_vaapi h263_videotoolbox mjpeg_nvdec mpeg4_videotoolbox vp9_vdpau h264_d3d11va mjpeg_vaapi prores_videotoolbox vp9_videotoolbox h264_d3d11va2 mpeg1_nvdec vc1_d3d11va wmv3_d3d11va h264_dxva2 mpeg1_vdpau vc1_d3d11va2 wmv3_d3d11va2 h264_nvdec mpeg1_videotoolbox vc1_dxva2 wmv3_dxva2 h264_vaapi mpeg2_d3d11va vc1_nvdec wmv3_nvdec h264_vdpau mpeg2_d3d11va2 vc1_vaapi wmv3_vaapi h264_videotoolbox mpeg2_dxva2 vc1_vdpau wmv3_vdpau

  • _nvdec 結尾的是NVIDIA顯卡解碼硬體加速
  • _videotoolbox 結尾的是蘋果ios和Macos多媒體框架硬體加速
  • 那接下來就直接開始編譯,編譯的配置文件,網上很多,我這裏上一份我使用的配置(網上很多):

    ./configure \ --prefix=$PREFIX \ # 編譯之後的保存位置 --disable-encoders \ # 禁用所有編碼器 --disable-decoders \ # 禁用所有解碼器 --disable-doc \ # 禁用文件 --disable-htmlpages \ --disable-manpages \ --disable-podpages \ --disable-txtpages \ --disable-ffmpeg \ # 禁用 ffmpeg 可執行程式構建 --disable-ffplay \ # 禁用 ffplay 可執行程式構建 --disable-ffprobe \ # 禁用 ffprobe 可執行程式構建 --disable-symver \ --disable-shared \ # 禁用共享連結 --disable-asm \ --disable-x86asm \ --disable-avdevice \ # 禁用libavdevice構建 --disable-postproc \ # 禁用libpostproc構建 --disable-cuvid \ # 禁用Nvidia Cuvid --disable-nvenc \ # 禁用Nvidia視訊編碼 --disable-vaapi \ # 禁用視訊加速API程式碼(Unix/Intel) --disable-vdpau \ # 禁用禁用Nvidia解碼和API程式碼(Unix) --disable-videotoolbox \ # 禁用ios和macos的多媒體處理框架videotoolbox --disable-audiotoolbox \ # 禁用ios和macos的音訊處理框架audiotoolbox --disable-appkit \ # 禁用蘋果 appkit framework --disable-avfoundation \ 禁用蘋果 avfoundation framework --enable-static \ # 啟用靜態連結 --enable-nonfree \ # 啟用非免費的元件 --enable-gpl \ # 啟用公共授權元件 --enable-version3 \ --enable-pic \ --enable-pthreads \ # 啟用多執行緒 --enable-encoder=bmp \ --enable-encoder=flv \ --enable-encoder=gif \ --enable-encoder=mpeg4 \ --enable-encoder=rawvideo \ --enable-encoder=png \ --enable-encoder=mjpeg \ --enable-encoder=yuv4 \ --enable-encoder=aac \ --enable-encoder=pcm_s16le \ --enable-encoder=subrip \ --enable-encoder=text \ --enable-encoder=srt \ --enable-libx264 \ # 啟用支持h264 --enable-encoder=libx264 \ --enable-libfdk-aac \ # 啟用支持fdk-aac --enable-encoder=libfdk_aac \ --enable-decoder=libfdk_aac \ --enable-libmp3lame \ # 啟用支持mp3lame --enable-encoder=libmp3lame \ --enable-libopencore-amrnb \ # 啟用支持opencore-amrnb --enable-encoder=libopencore_amrnb \ --enable-decoder=libopencore_amrnb \ --enable-libopencore-amrwb \ # 啟用支持opencore-amrwb --enable-decoder=libopencore_amrwb \ --enable-mediacodec \ # 啟用支持mediacodec --enable-encoder=h264_mediacodec \ --enable-encoder=hevc_mediacodec \ --enable-decoder=h264_mediacodec \ --enable-decoder=hevc_mediacodec \ --enable-decoder=mpeg4_mediacodec \ --enable-decoder=vp8_mediacodec \ --enable-decoder=vp9_mediacodec \ --enable-decoder=bmp \ --enable-decoder=flv \ --enable-decoder=gif \ --enable-decoder=mpeg4 \ --enable-decoder=rawvideo \ --enable-decoder=h264 \ --enable-decoder=png \ --enable-decoder=mjpeg \ --enable-decoder=yuv4 \ --enable-decoder=aac \ --enable-decoder=aac_latm \ --enable-decoder=pcm_s16le \ --enable-decoder=mp3 \ --enable-decoder=flac \ --enable-decoder=srt \ --enable-decoder=xsub \ --enable-small \ --enable-neon \ --enable-hwaccels \ --enable-jni \ --enable-cross-compile \ --cross-prefix=$CROSS_PREFIX \ --target-os=android \ --arch=$COMPILE_ARCH \ --cpu=$ANDROID_CUP \ --cc=$CC \ --cxx=$CXX \ --nm=$NM \ --ar=$AR \ --as=$AS \ --strip=$STRIP \ --ranlib=$RANLIB \ --sysroot=$SYSROOT \ --extra-cflags="-Os -fpic $OPTIMIZE_CFLAGS" \ --extra-ldflags="$ADDI_LDFLAGS" 我這裏編譯的是靜態連結庫也就是 .a 檔,所以我禁用了共享連結庫,如果你編譯 .so 檔,那麽就修改如下:

    --disable-static \ # 禁用靜態連結 --enable-shared \ # 啟用共享連結 因為我們不需要使用到avdevice和postproc,所以我選擇禁用,這樣最終只會生成6個 .a

    --disable-avdevice \ # 禁用libavdevice構建 --disable-postproc \ # 禁用libpostproc構建

    生成的.a檔
    透過構建指令碼的方式我們將上面的 configure make 命令做成一個 shell 指令碼,如果需要可以在評論區留言。

    開始編譯

    然後我們就進入FFmpeg 6.0目錄,透過 terminal 執行我們的 shell 指令碼。編譯的時候只遇到了這麽一個問題: x264 pkg-config沒找到,報錯的內容如下:

    ➜ ffmpeg ./darwin_android_lite.sh compiling ffmpeg for armeabi-v7a ERROR: x264 not found using pkg-config If you think configure made a mistake, make sure you are using the latest version from Git. If the latest version fails, report the problem to the [email protected] mailing list or IRC #ffmpeg on irc.libera.chat. Include the log file "ffbuild/config.log" produced by configure as this will help solve the problem. 這時就需要我們在我們的配置裏面加上如下內容:

    --pkg-config="pkg-config --static" 這個就表示需要我們指定pkg-config位置

    export PKG_CONFIG_PATH=$(pwd)/../x264/android/$ANDROID_ABI/lib/pkgconfig 加上如上內容,基本在檢查配置的環節基本就不會有問題了,然後我們繼續執行就可以得到一個編譯結果。

  • include:放置的是 .h 的表頭檔。
  • lib:編譯好的 .so 或者 .a 檔以及pkg-config。
  • share:demo和一些api相關內容。
  • 我透過 ld 工具將所有的 .a 合成了一個 libffmpeg-org.so 的檔,就不需要對上面6個進行分別載入了,只需要載入一個就行。

    System.loadLibrary("ffmpeg-org") 那多個合成一個的配置,也在我們的 shell 指令碼中,大致就是如下:

    $TOOLCHAIN_EXECUTE/${CROSS_COMPILE}ld \ -rpath-link=$SYSROOT/usr/lib/$HOST/$API \ -L$SYSROOT/usr/lib/$HOST/$API \ -L$TOOLCHAIN/lib/gcc/$HOST/4.9.x \ -L$PREFIX/lib -soname libffmpeg-org.so \ -shared -Bsymbolic --whole-archive --no-undefined -o \ $PREFIX/libffmpeg-org.so \ $PREFIX/lib/libavcodec.a \ $PREFIX/lib/libavfilter.a \ $PREFIX/lib/libswresample.a \ $PREFIX/lib/libavformat.a \ $PREFIX/lib/libavutil.a \ $PREFIX/lib/libswscale.a \ $X264_LIB/libx264.a \ $FDK_LIB/libfdk-aac.a \ $LAME_LIB/libmp3lame.a \ $AMR_LIB/libopencore-amrnb.a \ $AMR_LIB/libopencore-amrwb.a \ -lc -lm -lz -ldl -llog -landroid --dynamic-linker=/system/bin/linker \ $TOOLCHAIN/lib/gcc/$HOST/4.9.x/libgcc_real.a || exit 1 上方配置有一個需要註意的點,我們一定要使用 libgcc_real.a ,而不是 libgcc.a ,這裏被誤導了好久。以前一直使用的是 libgcc.a ,也沒有問題,但這次使用NDK21就出現了這個問題。

    libgcc
    到這裏我們的交叉編譯就完成了,那我們接下來就看看怎麽使用。

    使用

    1. 我們新建一個native計畫,然後將我們編譯好的 libffmpeg-org.so 放置於jniLibs目錄下。
    將我們之前編譯的表頭檔,也就是上面提到的 include 下的所有檔匯入計畫 cpp 目錄下
    1. 將我們的之前的 libffmpeg-org.org 加入我們的CmakeLists.txt的配置中

    add_library( ffmpeg-org SHARED IMPORTED ) SET_TARGET_PROPERTIES( ffmpeg-org PROPERTIES IMPORTED_LOCATION ${PROJECT_SOURCE_DIR}/../jniLibs/${CMAKE_ANDROID_ARCH_ABI}/libffmpeg-org.so ) include_directories(ffmpeg) # 表頭檔相對路徑

    1. 新建我們自己的 ffmpeg-cmd.cpp 並加入CmakeLists.txt

    add_library( ffmpeg-cmd SHARED # List C/C++ source files with relative paths to this CMakeLists.txt. ffmpeg-cmd.cpp)

    1. 最後動態連結所有內容,完整CmakeLists.txt的配置如下

    cmake_minimum_required(VERSION 3.22.1) project("ffmpeg") add_library( ffmpeg-cmd SHARED # List C/C++ source files with relative paths to this CMakeLists.txt. ffmpeg-cmd.cpp) add_library( ffmpeg-org SHARED IMPORTED ) SET_TARGET_PROPERTIES( ffmpeg-org PROPERTIES IMPORTED_LOCATION ${PROJECT_SOURCE_DIR}/../jniLibs/${CMAKE_ANDROID_ARCH_ABI}/libffmpeg-org.so ) include_directories(ffmpeg) # 表頭檔相對路徑 target_link_libraries( ffmpeg-cmd ffmpeg-org # List libraries link to the target library android log)

    1. 新建我們的FFmpegCmd類,將 so 動態載入,並新增一個獲取所有編解碼器的方法。

    class FFmpegCmd { init { System.loadLibrary("ffmpeg-org") System.loadLibrary("ffmpeg-cmd") } externalfun getSupportCodecs():String? }

    1. 編輯我們的 ffmpeg-cmd.cpp ,在其內容關聯 getSupportCodecs 方法

    extern"C"JNIEXPORT jstring JNICALL Java_com_adaiyuns_ffmpeg_jni_FFmpegCmd_getSupportCodecs(JNIEnv *env, jobject thiz){ returnnullptr; } 這裏需要註意上面的函式名,函式名為FFmpegCmd的全路徑。那我們就實作一個獲取所有的編解碼器

    #include<jni.h>#include<string>extern"C"{ #include"ffmpeg/libavcodec/avcodec.h" } extern"C"JNIEXPORT jstring JNICALL Java_com_adaiyuns_ffmpeg_jni_FFmpegCmd_getSupportCodecs(JNIEnv *env, jobject thiz){ // 定義臨時緩存區char info[20000] = {0}; // 初始化編碼器遍歷器void *opaque = NULL; const AVCodec *avcodec = av_codec_iterate(&opaque); // 遍歷所有支持的編碼器while (avcodec != NULL) { sprintf(info, "%s%s,", info, avcodec->name); avcodec = av_codec_iterate(&opaque); } return env->NewStringUTF(info); } 可以看一下執行之後的效果 從圖上可以看到我們的ffmpeg已經支持 h264_mediacodec hevc_mediacodec 。到此基本就完成了簡單的整合。✿✿ヽ(°▽°)ノ✿

    開源庫

    基於簡單的音視訊處理,我也開發了FFmpeg的開源庫 FFmpegCommand ,大致支持如下特色功能: 開源庫中提供了常用的命令和方法 FFmpegUtils ,可直接使用其中的方法。

    MainScope().launch(Dispatchers.IO) { FFmpegCommand.runCmd(FFmpegUtils.transformAudio(audioPath, targetPath), callback("音訊轉碼完成", targetPath)) } 也可以自訂命令,以下是一個自訂使用 MediaCodec 進行解碼、編碼、轉格式的例子:

    // shell 命令: ffmpeg -y -c:v h264_mediacodec -i inputPath -c:v h264_mediacodec outputPathval command = CommandParams() .append("-c:v") // 設定解碼器 .append("h264_mediacodec") .append("-i") .append(inputPath) .append("-b") // 寫死一般需要設定視訊的位元率(bitrate) .append("1500k") .append("-c:v") // 設定編碼器 .append("h264_mediacodec") .append(outputPath) .get() MainScope().launch(Dispatchers.IO) { FFmpegCommand.runCmd(command, callback("格式轉換成功", targetPath)) } 需要註意:

  • 在使用 MediaCodec 進行編碼的時候,必須同時配置 MediaCodec 解碼,如上例子所示,不然會造成失敗!!!
  • H264編解碼器是 h264_mediacodec ,H265的編解碼器是 hevc_mediacodec 。同時可以使用H264解碼和H265編碼。
  • 寫死一般需要設定視訊的位元率,否則會出現畫面模糊不清晰的情況。
  • 最好使用 CommandParams 構建我們的命令參數,這樣能保證參數不被路徑中空格影響,導致命令執行不成功。
  • 最後用相同的一個視訊檔(4.07 MB (4,277,182 字節)),分別使用 h264_mediacodec libx264 進行轉碼流程,得出如下結果,所以小夥伴們快使用 MediaCodec 進行編碼吧~~
    編解器 耗時
    h264_mediacodec 4507毫秒 ≈ 4.5秒
    libx264 57264毫秒 ≈ 57.2秒

    原文連結: https://juejin.cn/post/7297838901090648118

    資源連結: https://github.com/AnJoiner/FFmpegCommand

    -- END -- 進技術交流群, 掃碼添加我的微信:Byte-Flow

    獲取相關資料和源碼

    學習音視訊、OpenGL ES、Vulkan 、Metal、影像濾鏡、視訊特效及相關渲染技術的付費社群,面試指導,1v1 簡歷服務,職業規劃。 我的付費社群 推薦: