FFmpeg 使用MediaCodec

FFmpeg 使用MediaCodec

環(huán)境: ffmpeg branch-3.4 @ 2021-08-31

FFmpeg 使用方法

編譯的時候需要修改

include\libavutil\error.h

static inline <unknown> av_make_error_string(char *errbuf, size_t errbuf_size, int errnum)

將返回值類型改為 char *

static inline char * av_make_error_string(char *errbuf, size_t errbuf_size, int errnum)

ffmpeg中使用mediacodec的方法其實也就是常規(guī)的使用ffmpeg的方法,大致只是找查找codec的時候改成使用mediacodec插件

修改為使用MediaCodec的關(guān)鍵代碼為

    // 查找視頻解碼器
    AVCodec *vCodec = NULL;
    // 可以自動查找
    // avcodec_find_decoder(ic->streams[videoStream]->codecpar->codec_id);
    // 指定為MediaCodec
    switch (vS->codecpar->codec_id)
    {
        case AV_CODEC_ID_H264:
            vCodec = avcodec_find_decoder_by_name("h264_mediacodec");
            break;
        case AV_CODEC_ID_H265:
            vCodec = avcodec_find_decoder_by_name("h265_mediacodec");
            break;
        case AV_CODEC_ID_MPEG4:
            vCodec = avcodec_find_decoder_by_name("mpeg4_mediacodec");
            break;
        default:
            break;
    }

FFmpeg 中的 MediaCodec流程

以H264為例,在ffmpeg中可以看到其解碼組件的實現(xiàn)為如下結(jié)構(gòu)體

FFmpeg/libavcodec/mediacodecdec.c

#if CONFIG_H264_MEDIACODEC_DECODER
AVCodec ff_h264_mediacodec_decoder = {
    .name           = "h264_mediacodec",
    .long_name      = NULL_IF_CONFIG_SMALL("H.264 Android MediaCodec decoder"),
    .type           = AVMEDIA_TYPE_VIDEO,
    .id             = AV_CODEC_ID_H264,
    .priv_data_size = sizeof(MediaCodecH264DecContext),
    .init           = mediacodec_decode_init,
    .decode         = mediacodec_decode_frame,
    .flush          = mediacodec_decode_flush,
    .close          = mediacodec_decode_close,
    .capabilities   = AV_CODEC_CAP_DELAY | AV_CODEC_CAP_AVOID_PROBING,
    .caps_internal  = FF_CODEC_CAP_SETS_PKT_DTS,
    .bsfs           = "h264_mp4toannexb",
};
#endif

這里重點就是這幾個方法

    .init           = mediacodec_decode_init,
    .decode         = mediacodec_decode_frame,
    .flush          = mediacodec_decode_flush,
    .close          = mediacodec_decode_close,

1. 初始化 MediaCodec 解碼器

stativ av_cold int mediacodec_decode_init(AVCodecContext *avctx)
{
    // MediaCodec H.264 解碼相關(guān)屬性
    MediaCodecH264DecContext *s = avctx->priv_data;

    // 調(diào)用 jni 創(chuàng)建 MediaFormat
     FFAMediaFormat *format = ff_AMediaFormat_new();   

    // 根據(jù) codec_id 獲取 mimeType 
    const char *codec_mime = "video/avc";
    // 獲取 AVCodecContext 的 Extreadata 中的 sps & pps,存入H.264文件的頭部 
    ret = h264_set_extradata(avctx, format);

    // 設(shè)置 MediaFormat 屬性
    ff_AMediaFormat_setxxx(format, "xxx", xxx);

    ret = ff_mediacodec_dec_init(avctx, s->ctx)
}

2. 解碼方法

解碼方法入口為 mediacodec_process_data,如下可以看到是讀取數(shù)據(jù)并傳入 mediacodec_process_data處理,而 mediacodec_process_data 進一步調(diào)用了 ff_mediacodec_dec_decode

static int mediacodec_decode_frame(AVCodecContext *avctx, void *data, int *got_frame, AVPacket *avpkt)
{
    // mediacodec 私有參數(shù)
     MediaCodecH264DecContext *s = avctx->priv_data;
    // 解碼得到的數(shù)據(jù)
    AVFrame *frame    = data;
    int ret;   

    // 將要解碼的 AVPacket 寫入 fifo
    av_fifo_generic_write(s->fifo, &input_pkt, sizeof(input_pkt), NULL);

    while (!*got_frame) {
        /* prepare the input data */
        if (s->buffered_pkt.size <= 0) {
            // 從 fifo 讀到 buffered_pkt
            av_fifo_generic_read(s->fifo, &s->buffered_pkt, sizeof(s->buffered_pkt), NULL);
        }
         // 解碼
        ret = mediacodec_process_data(avctx, frame, got_frame, &s->buffered_pkt);
    }
}

3. mediacodec buffer 獲取及處理

在 ff_mediacodec_dec_decode 中的buffer 的獲取同mediacodec api的使用一致,這些是通過jni 反射調(diào)用的java 的mediacodec 方法,MediaCodec API 方法使用 https://zhuanlan.zhihu.com/p/45224834

int ff_mediacodec_dec_decode(AVCodecContext *avctx, MediaCodecDecContext *s,
                             AVFrame *frame, int *got_frame,
                             AVPacket *pkt)
{
    while(...) {
        // 查找是否有可用的 輸入緩沖區(qū)
        index = ff_AMediaCodec_dequeueInputBuffer(codec, input_dequeue_timeout_us);
        // 獲取輸入緩沖區(qū)
        data = ff_AMediaCodec_getInputBuffer(codec, index, &size);
        if (need_draining) {
            // 發(fā)送 結(jié)束信號
            status = ff_AMediaCodec_queueInputBuffer(codec, index, 0, 0, pts, flags);
        } else {
            // 傳送 要解碼的數(shù)據(jù)
            status = ff_AMediaCodec_queueInputBuffer(codec, index, 0, size, pts, 0);
        }
    }

    // 獲取輸出緩沖區(qū)
    index = ff_AMediaCodec_dequeueOutputBuffer(codec, &info, output_dequeue_timeout_us);
    if (index >= 0) {
        if (info.size) {
            if (s->surface) {
                // 拷貝buffer index數(shù)據(jù)
                ret = mediacodec_wrap_hw_buffer(avctx, s, index, &info, frame);
            } else {
                // 獲取解碼后的數(shù)據(jù)
                data = ff_AMediaCodec_getOutputBuffer(codec, index, &size);
             // 轉(zhuǎn)換格式
                ret = mediacodec_wrap_sw_buffer(avctx, s, data, size, index, &info, frame);
            }
        } else {
            // 沒有輸出數(shù)據(jù),釋放緩沖區(qū)
            status = ff_AMediaCodec_releaseOutputBuffer(codec, index, 0);
    }
}

如上這個方法可以看到 在獲取緩沖區(qū)這塊是有差別的:

在設(shè)置了Surface的前提下,是調(diào)用mediacodec_wrap_hw_buffer,在frame中填充的并不是圖像數(shù)據(jù),而只是buffer index的封裝。(反之,如果沒有設(shè)置Surface就可以拿到圖像數(shù)據(jù),只是效率較低)

使用FFmpeg 的顯示方法

1、可以透過ANativeWindow的方法,可以參考如下兩篇文檔

https://blog.csdn.net/Kennethdroid/article/details/107103315

https://blog.csdn.net/ericbar/article/details/80416328

這種方法有效率問題,內(nèi)存大小和拷貝的問題,不過好處是可以拿到解碼后的數(shù)據(jù)做需要的渲染

2、通過MediaCodec Surface的方法

在ffmpeg mediacodec.h中定義了幾個函數(shù),專門用于處理顯示(因為mediacodec在surface模式下比較特殊)。

int av_mediacodec_default_init(AVCodecContext *avctx, AVMediaCodecContext *ctx, void *surface)用于給用戶設(shè)置一個surfaceAV_CODEC_HW_CONFIG_METHOD_AD_HOC體現(xiàn)在這)

av_mediacodec_release_buffer(AVMediaCodecBuffer *buffer, int render)用于給用戶“顯示”一幀畫面。(實際是調(diào)用MediaCodec的releaseOutputBuffer,render為1顯示這個buffer,為0丟棄這個buffer不顯示)

小結(jié)

1、ffmpeg的 mediacodec 支持的是有3.4分支,而4.x的分支并沒有支持,所以支持性這塊還是有差異

2、mediacodec的調(diào)用通過反射和jni,新版本ndk支持natvice 調(diào)用方法 https://developer.android.google.cn/ndk/reference/group/media#amediacodec,可以優(yōu)化一些性能問題,需要移植

3、用來實現(xiàn)做播放器流程是需要做AV同步,用在轉(zhuǎn)碼的場景上應(yīng)該是有性能的提升,可以獲取硬解后的buffer 再對其編碼,且ffmpeg 對container的支持性比較好

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容