<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)用 MediaCodec 硬解碼到 Surface 上

          共 3429字,需瀏覽 7分鐘

           ·

          2021-11-17 16:35

          這是關(guān)于 FFmpeg 和 MediaCodec 愛(ài)恨情仇系列的第三篇文章了。

          之前寫(xiě)了 FFmpeg 調(diào)用 MediaCodec 進(jìn)行硬解碼的內(nèi)容。

          另外也給出了 FFmpeg 的編譯腳本,輕松搞定編譯問(wèn)題。

          眾所周知,MediaCodec 的解碼能力不僅可以解碼出 YUV 數(shù)據(jù),還能直接解碼到 Surface 上。

          在短視頻領(lǐng)域中,MediaCodec 解碼到 Surface 上的能力反而更加常用,這樣就能將畫(huà)面轉(zhuǎn)到 OES 紋理上,從而進(jìn)行后續(xù)各種渲染操作。

          之前介紹的 FFmpeg 調(diào)用 MediaCodec 進(jìn)行硬解碼只是解碼出了 Buffer 數(shù)據(jù),沒(méi)有把解碼到 Surface 上的能力用起來(lái)。

          再看了更多資料之后,發(fā)現(xiàn) FFmpeg 調(diào)用 MediaCodec 已經(jīng)可以解碼到 Surface 上。

          具體參考的是這篇郵件內(nèi)容:

          http://mplayerhq.hu/pipermail/ffmpeg-devel/2016-March/191700.html

          在這里面詳細(xì)介紹了這種能力,挑重點(diǎn)截圖一下:

          圖片內(nèi)容介紹的很詳細(xì),按照步驟實(shí)踐就好了。

          代碼實(shí)踐

          如果熟悉了 FFmpeg 調(diào)用 MediaCodec 解碼 Buffer 數(shù)據(jù)的流程,那么解碼到 Surface 只是在流程上稍微改動(dòng)一點(diǎn)就行。

          首先要準(zhǔn)備好 Surface 對(duì)象,在 Java 上層構(gòu)建好 Surface 對(duì)象通過(guò) NDK 傳到 Native 層,傳下來(lái)的是一個(gè) jobject 對(duì)象。

          如果不熟悉 NDK 的話(huà),可以看看我在慕課網(wǎng)上的錄制的免費(fèi)課程:

          Android NDK 免費(fèi)視頻在線(xiàn)學(xué)習(xí)?。。?/a>

          其次是兩個(gè)新的函數(shù)方法:

          av_mediacodec_alloc_context 和 av_mediacodec_default_init 方法就是讓 Surface 和 AVMediaCodecContext 、AVCodecContext 三者之間產(chǎn)生關(guān)聯(lián)。

          具體就是 AVCodecContext 持有 AVMediaCodecContext ,AVMediaCodecContext 持有 Surface 。

          至于為什么要關(guān)聯(lián),因?yàn)樵?FFmpeg 源碼里要根據(jù) Surface 是否為 nullptr 對(duì) MediaCodec 的初始化和解碼后的數(shù)據(jù)做不同處理。

          感興趣的可以閱讀這塊的源碼,內(nèi)容不多通俗易懂。

          等到解碼之后,F(xiàn)Fmpeg 同樣會(huì)返回一個(gè) AVFrame 數(shù)據(jù),只不過(guò)它的 data 字段不再是 Buffer 內(nèi)容了。

          AVFrame 的格式不再是 NV12 (解碼 Buffer 數(shù)據(jù)的話(huà)就是 NV12),而是自定義的 AV_PIX_FMT_MEDIACODEC ,代表走的 Surface 模式。

          if?(s->surface)?{
          ???// surface 不為 null 的情況下:
          ???//?通過(guò)?mediacodec_wrap_hw_buffer?對(duì)數(shù)據(jù)進(jìn)行處理
          ??if?((ret?=?mediacodec_wrap_hw_buffer(avctx,?s,?index,?&info,?frame))?0)?{
          ????av_log(avctx,?AV_LOG_ERROR,?"Failed?to?wrap?MediaCodec?buffer\n");
          ???????return?ret;
          ??}
          }

          Surface 模式下對(duì)數(shù)據(jù)的處理是 mediacodec_wrap_hw_buffer 函數(shù),Buffer 模式就是 mediacodec_wrap_sw_buffer 函數(shù)了。

          同時(shí),真正解碼后的數(shù)據(jù)存儲(chǔ)在 AVFrame->data[3] 字段上,這個(gè)字段是個(gè)老員工了。

          一般解碼非 Buffer 數(shù)據(jù)的情況,都會(huì)將特殊的內(nèi)容保存到 data[3] 上,比如 Window 上的硬解,部分源碼如下:

          static?int?mediacodec_wrap_hw_buffer(AVCodecContext?*avctx,
          ??????????????????????????????????MediaCodecDecContext?*s,
          ??????????????????????????????????ssize_t?index,
          ??????????????????????????????????FFAMediaCodecBufferInfo?*info,
          ??????????????????????????????????AVFrame?*frame)

          //?省略部分源碼
          //?創(chuàng)建???AVMediaCodecBuffer?對(duì)象
          buffer?
          =?av_mallocz(sizeof(AVMediaCodecBuffer));
          frame->buf[0]?=?av_buffer_create(NULL,
          ???????????????????????0,
          ???????????????????????mediacodec_buffer_release,
          ???????????????????????buffer,
          ???????????????????????AV_BUFFER_FLAG_READONLY);
          //?buffer?相關(guān)屬性賦值
          buffer->ctx?=?s;
          buffer->serial?=?atomic_load(&s->serial);
          if?(s->delay_flush)
          ????ff_mediacodec_dec_ref(s);
          //?index?索引,代表?mediacodec?中?buffer?的索引
          buffer->index?=?index;
          buffer->pts?=?info->presentationTimeUs;
          //?將?buffer?賦值給?data[3]?字段
          frame->data[3]?=?(uint8_t?*)buffer;

          有了 AVFrame 數(shù)據(jù)之后,Surface 上還是沒(méi)有畫(huà)面。

          回想一下,在 MediaCodec 上想要數(shù)據(jù)渲染到 Surface 還得調(diào)用一個(gè) releaseOutputBuffer 方法,其中第二個(gè)參數(shù)要傳 true 才可以。

          public?void?releaseOutputBuffer?(int?index,?boolean?render)

          同樣,在 FFmpeg 中也有這么個(gè)方法。

          int?av_mediacodec_release_buffer(AVMediaCodecBuffer?*buffer,?int?render);

          buffer 就是 frame->data[3] 的內(nèi)容,render 的含義和 releaseOutputBuffer 中的含義一致。

          另外 releaseOutputBuffer 方法第一個(gè)參數(shù) index 其實(shí)就已經(jīng)在 buffer 中賦值過(guò)了。

          這樣一來(lái),解碼后就可以直接上屏渲染展示啦。

          ?AVMediaCodecBuffer?*buffer?=?(AVMediaCodecBuffer?*)?frame->data[3];
          ?av_mediacodec_release_buffer(buffer,?1);

          完整代碼實(shí)踐可以在公眾號(hào) 音視頻開(kāi)發(fā)進(jìn)階 回復(fù) 1019 獲取。

          經(jīng)過(guò)測(cè)試驗(yàn)證確實(shí)可行,不過(guò)直接不斷解碼上屏的速度是很快的,可不止視頻播放 30ms 一幀的速度哦,想要來(lái)做播放器的話(huà),還得自己管理控制一下了。

          另外,完整代碼演示中直接解碼到了 SurfaceView 的 Surface 上。

          除此之外,還可以解碼到 SurfaceTexture 構(gòu)造的 Surface 上,這樣就可以用到 SurfaceTexture 的 OnFrameAvailableListener 回調(diào)方法, 并且還可以用 attachToGLContext 方法關(guān)聯(lián)到 OpenGL 環(huán)境上,每次解碼時(shí)通過(guò) updateTexImage 更新畫(huà)面,實(shí)現(xiàn)解碼到 OES 紋理的目標(biāo),具體操作起來(lái)也是很容易方便。


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

          私信領(lǐng)取相關(guān)資料

          推薦閱讀:

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

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

          開(kāi)通專(zhuān)輯 | 細(xì)數(shù)那些年寫(xiě)過(guò)的技術(shù)文章專(zhuān)輯

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

          你想要的音視頻開(kāi)發(fā)資料庫(kù)來(lái)了

          推薦幾個(gè)堪稱(chēng)教科書(shū)級(jí)別的 Android 音視頻入門(mén)項(xiàng)目

          覺(jué)得不錯(cuò),點(diǎn)個(gè)在看唄~

          瀏覽 106
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  日韩免费色 | 高清无码在线免费观看视频 | 久操免费视频 | 久久伊人婷婷 | 97人妻人人揉人人躁人人 |