<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          FFmpeg 調(diào)用 Android MediaCodec 進(jìn)行硬解碼(附源碼)

          共 4968字,需瀏覽 10分鐘

           ·

          2021-10-24 02:06

          FFmpeg 在 3.1 版本之后支持調(diào)用平臺硬件進(jìn)行解碼,也就是說可以通過 FFmpeg 的 C 代碼去調(diào)用 Android 上的 MediaCodec 了。

          在官網(wǎng)上有對應(yīng)說明,地址如下:

          https://trac.ffmpeg.org/wiki/HWAccelIntro

          從圖中可以看到,不僅僅是 Android 上支持 MediaCodec,iOS 上也支持 VideoToolbox,連 Windows 上的 Direct3D 11 都有支持了。

          注意:Android MediaCodec 目前僅支持解碼,還不支持編碼呢。

          不過,為了驗(yàn)證是否可行,做個簡單的演示,最后會有完整的的代碼給出。

          首先是 FFmpeg 的編譯。它的編譯有很多開關(guān)選項(xiàng),要確保打開了 mediacodec 相關(guān)的選項(xiàng),具體如下:

          --enable-mediacodec
          --enable-decoder=h264_mediacodec
          --enable-decoder=hevc_mediacodec
          --enable-decoder=mpeg4_mediacodec
          --enable-hwaccel=h264_mediacodec

          可以看出 mediacodec 支持的編碼格式有 h264、hevc、mpeg4 三種可選,不在范圍內(nèi)的就還是考慮軟解吧。

          關(guān)于如何編譯,就不詳細(xì)闡述了,后面再專門寫一篇來介紹。

          編譯出對應(yīng)的 so 之后,可以打印一下 AVCodec 支持的格式列表,看看有沒有 mediacodec 。

          具體代碼如下:

          char?info[40000]?=?{0};

          AVCodec?*c_temp?=?av_codec_next(NULL);
          while?(c_temp?!=?NULL)?{
          ????if?(c_temp->decode?!=?NULL)?{
          ????????sprintf(info,?"%s[Dec]",?info);
          ????}?else?{
          ????????sprintf(info,?"%s[Enc]",?info);
          ????}

          ????switch?(c_temp->type)?{
          ????????case?AVMEDIA_TYPE_VIDEO:
          ????????????sprintf(info,?"%s[Video]",?info);
          ????????????break;
          ????????case?AVMEDIA_TYPE_AUDIO:
          ????????????sprintf(info,?"%s[Audio]",?info);
          ????????????break;
          ????????default:
          ????????????sprintf(info,?"%s[Other]",?info);
          ????????????break;
          ????}
          ????sprintf(info,?"%s?%10s\n",?info,?c_temp->name);
          ????c_temp?=?c_temp->next;
          }

          通過 AVCodec 的 next 指針進(jìn)行遍歷,然后打印出結(jié)果,看到下面的內(nèi)容說明編譯成功了。

          持的格式里面已經(jīng)有了 h264_mediacodec 和 mpeg4_mediacodec 了。

          接下來就進(jìn)行解碼了。關(guān)于 FFmpeg 解碼的 API 調(diào)用,在公眾號以前發(fā)布的文章中說過多次,就不詳細(xì)講解流程了。

          FFmpeg源碼世界:命令篇

          FFmpeg音頻處理——音頻混合、拼接、剪切、轉(zhuǎn)碼

          如何給 FFmpeg 添加自定義 Codec 編碼器

          簡單概況一下:

          1. 首先通過 avformat_open_input 方法打開文件,得到 AVFormatContext 。
          2. 然后通過 avformat_find_stream_info 查找文件的視頻流信息。
          3. 得到文件相關(guān)信息和視頻流信息,主要還是為了得到編碼格式信息,然后好找到對應(yīng)的解碼器。也可以通過 avcodec_find_decoder_by_name 方法直接找具體的解碼器。
          4. 有了解碼器就可以創(chuàng)建解碼上下文 AVCodecContext,并通過 avcodec_open2 方法打開解碼器
          5. 然后通過 av_read_frame 讀取文件的內(nèi)容好進(jìn)行下一步的解碼。
          6. 接下來就是熟悉的 avcodec_send_packet 發(fā)送給解碼器,avcodec_receive_frame 從解碼器取回解碼后的數(shù)據(jù)。

          重點(diǎn)講解一下調(diào)用硬件解碼和普通解碼的一些區(qū)別:

          第一步是要在 so 加載的 JNI_OnLoad 方法中將 JavaVM 設(shè)置給 FFmpeg 。

          jint?JNI_OnLoad(JavaVM?*vm,?void?*res)?{
          ????av_jni_set_java_vm(vm,?0);
          ????return?JNI_VERSION_1_4;
          }

          缺少這一步就不能反射調(diào)用 Java 方法了。


          接下來還是判斷硬件解碼類型支不支持,上面是通過 AVCodec 來判斷的,實(shí)際上 FFmpeg 都給出了硬件類型的定義,在 AVHWDeviceType 枚舉變量中。

          enum?AVHWDeviceType?{
          ????AV_HWDEVICE_TYPE_NONE,
          ????AV_HWDEVICE_TYPE_VDPAU,
          ????AV_HWDEVICE_TYPE_CUDA,
          ????AV_HWDEVICE_TYPE_VAAPI,
          ????AV_HWDEVICE_TYPE_DXVA2,
          ????AV_HWDEVICE_TYPE_QSV,
          ????AV_HWDEVICE_TYPE_VIDEOTOOLBOX,
          ????AV_HWDEVICE_TYPE_D3D11VA,
          ????AV_HWDEVICE_TYPE_DRM,
          ????AV_HWDEVICE_TYPE_OPENCL,
          ????AV_HWDEVICE_TYPE_MEDIACODEC,
          ????AV_HWDEVICE_TYPE_VULKAN,
          };

          通過 av_hwdevice_get_type_name 方法可以將這些枚舉值轉(zhuǎn)換成對應(yīng)的字符串,比如 AV_HWDEVICE_TYPE_MEDIACODEC 對應(yīng)的字符串就是 mediacodec ,其實(shí)在源碼里面也是有的:

          static?const?char?*const?hw_type_names[]?=?{
          ????[AV_HWDEVICE_TYPE_CUDA]???=?"cuda",
          ????[AV_HWDEVICE_TYPE_DRM]????=?"drm",
          ????[AV_HWDEVICE_TYPE_DXVA2]??=?"dxva2",
          ????[AV_HWDEVICE_TYPE_D3D11VA]?=?"d3d11va",
          ????[AV_HWDEVICE_TYPE_OPENCL]?=?"opencl",
          ????[AV_HWDEVICE_TYPE_QSV]????=?"qsv",
          ????[AV_HWDEVICE_TYPE_VAAPI]??=?"vaapi",
          ????[AV_HWDEVICE_TYPE_VDPAU]??=?"vdpau",
          ????[AV_HWDEVICE_TYPE_VIDEOTOOLBOX]?=?"videotoolbox",
          ????[AV_HWDEVICE_TYPE_MEDIACODEC]?=?"mediacodec",
          ????[AV_HWDEVICE_TYPE_VULKAN]?=?"vulkan",
          };

          和遍歷 AVCodec 一樣,也要遍歷 FFmpeg 是否支持 mediacodec 。

          type?=?av_hwdevice_find_type_by_name(mediacodec);
          if?(type?==?AV_HWDEVICE_TYPE_NONE)?{
          ????LOGE("Device?type?%s?is?not?supported.\n",?mediacodec);
          ????LOGE("Available?device?types:");
          ????while((type?=?av_hwdevice_iterate_types(type))?!=?AV_HWDEVICE_TYPE_NONE)
          ????????LOGE("?%s",?av_hwdevice_get_type_name(type));
          ????LOGE("\n");
          ????return?-1;
          }

          確定支持 mediacodec ,那么解碼就可以用了。前面提到,獲取文件信息主要是為了打開解碼器的,但比如文件編碼格式的 H.264 ,而支持 H.264 的解碼器除了軟解,還有 mediacodec 要怎么選擇呢?

          為了方便,直接 avcodec_find_decoder_by_name 找到 mediacodec 的解碼器就行。

          if?(!(decoder?=?avcodec_find_decoder_by_name("h264_mediacodec")))?{
          ????LOGE("avcodec_find_decoder_by_name?failed.\n");
          ????return?-1;
          }

          找到解碼器之后,還要得到該解碼器的一些配置信息,比如解碼出的格式是什么樣子的?mediacodec 解碼就是 NV21 這種。

          for?(i?=?0;;?i++)?{
          ????//?解碼器的配置
          ????const?AVCodecHWConfig?*config?=?avcodec_get_hw_config(decoder,?i);
          ????if?(!config)?{
          ????????LOGE("Decoder?%s?does?not?support?device?type?%s.\n",
          ????????????????decoder->name,?av_hwdevice_get_type_name(type));
          ????????return?-1;
          ????}
          ????if?(config->methods?&?AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX?&&
          ????????config->device_type?==?type)?{
          ????????//?硬解的格式
          ????????hw_pix_fmt?=?config->pix_fmt;
          ????????break;
          ????}
          }

          目前 mediacodec 解碼還只有 buffer 模式,沒有直接解紋理的那種。

          接下來就是給解碼上下文 AVCodecContext 添加一些硬件解碼的上下文。

          static?int?hw_decoder_init(AVCodecContext?*ctx,?const?enum?AVHWDeviceType?type)
          {
          ????int?err?=?0;
          ????if?((err?=?av_hwdevice_ctx_create(&hw_device_ctx,?type,
          ??????????????????????????????????????NULL,?NULL,?0))?0)?{
          ????????LOGE("Failed?to?create?specified?HW?device.\n");
          ????????return?err;
          ????}
          ????//?硬解解碼的上下文
          ????ctx->hw_device_ctx?=?av_buffer_ref(hw_device_ctx);
          ????return?err;
          }

          完成了這一系列操作之后,就是正常的解碼了,拿到解碼后的 AVFrame 內(nèi)容。

          如果 AVFrame 格式和硬件解碼的配置格式一樣,那么要用 av_hwframe_transfer_data 方法將它做一下轉(zhuǎn)換,轉(zhuǎn)成正常的 YUV 格式。

          if?(frame->format?==?hw_pix_fmt)?{
          ????/*?retrieve?data?from?GPU?to?CPU?*/
          ????if?((ret?=?av_hwframe_transfer_data(sw_frame,?frame,?0))?0)?{
          ????????LOGE("Error?transferring?the?data?to?system?memory\n");
          ????????goto?fail;
          ????}
          ????tmp_frame?=?sw_frame;
          }?else
          ????tmp_frame?=?frame;

          等完成這一些操作之后,就已經(jīng)解碼成功了,實(shí)際運(yùn)行也是 OK 的。

          獲取完整源碼的話,可以關(guān)注微信公眾號:音視頻開發(fā)進(jìn)階,回復(fù) 1019 獲取下載地址。


          技術(shù)交流,歡迎加我微信:ezglumes ,拉你入技術(shù)交流群。

          推薦閱讀:

          音視頻開發(fā)工作經(jīng)驗(yàn)分享 || 視頻版

          OpenGL ES 學(xué)習(xí)資源分享

          開通專輯 | 細(xì)數(shù)那些年寫過的技術(shù)文章專輯

          NDK 學(xué)習(xí)進(jìn)階免費(fèi)視頻來了

          推薦幾個堪稱教科書級別的 Android 音視頻入門項(xiàng)目

          覺得不錯,點(diǎn)個在看唄~


          瀏覽 64
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  中文无码视屏 | 欧美一级黄色大片 | 啪啪视频免费看 | 亚洲精品第一页 | 亚洲黄色天堂 |