FFmpeg 中的 Android MediaCodec

MediaCodec 類可以用來訪問底層媒體編解碼器,即編碼器/解碼器的組件。它是 Android 底層多媒體支持架構的一部分。

一個編解碼器處理輸入數據以生成輸出數據。它異步地處理數據,並使用一組輸入和輸出緩衝器。

調用的時候需要先初始化 MediaCodec 作爲視頻的編碼器,然後只需要不停傳入原始的 YUV 數據進入編碼器就可以直接輸出編碼好的 h264 流。解碼器則對應相反。

數據類型(Data Types)

編解碼器對 3 種數據進行操作:壓縮後的數據,原始音頻數據和原始視頻數據。可以使用 ByteBuffers 處理所有三種數據,但對原始視頻數據,可以使用 Surface 提高編解碼的性能。Surface 使用本地視頻緩衝區而不是映射或複製到 ByteBuffers,因此,效率更高。(通常在使用 Surface 時無法訪問原始視頻數據,但可以使用 ImageReader 類訪問不安全的解碼(原始)視頻幀)。

壓縮緩衝區(Compressed Buffers)

輸入緩衝器和輸出緩衝器根據格式的類型來存放已壓縮的數據。對於視頻類型,這是一個單一的壓縮後的視頻幀。對於音頻數據,通常是單個訪問單元(一個編碼後的音頻片段通常包含幾毫秒音頻)。

FFmpeg 中的 MediaCodec

FFmpeg 中僅支持 Android MediaCodec 解碼,本文主要介紹 FFmpeg 中關於 MediaCodec 解碼流程。後面會介紹如何在 FFmpeg 中添加 MediaCodec 編碼。

解碼流程:

  1. mediacodec_deocde_init (初始化 MediaCodec 解碼器)
stativ av_cold int mediacodec_decode_init(AVCodecContext *avctx)
{
    // MediaCodec H.264 解碼相關屬性
    MediaCodecH264DecContext *s = avctx->priv_data;
    
    // 調用 jni 創建 MediaFormat
     FFAMediaFormat *format = ff_AMediaFormat_new();   
    
    // 根據 codec_id 獲取 mimeType 
    const char *codec_mime = "video/avc";
    // 獲取 AVCodecContext 的 Extreadata 中的 sps & pps,存入H.264文件的頭部 
    ret = h264_set_extradata(avctx, format);
    
    // 設置 MediaFormat 屬性
    ff_AMediaFormat_setxxx(format, "xxx", xxx);
    
    ret = ff_mediacodec_dec_init(avctx, s->ctx)
}
  1. mediacodec_decode_frame (解碼方法)

static int mediacodec_decode_frame(AVCodecContext *avctx, void *data, int *got_frame, AVPacket *avpkt)
{
    // mediacodec 私有參數
     MediaCodecH264DecContext *s = avctx->priv_data;
    // 解碼得到的數據
    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);
    }
}
  1. ff_mediacodec_dec_decode (解碼)

int ff_mediacodec_dec_decode(AVCodecContext *avctx, MediaCodecDecContext *s,
                             AVFrame *frame, int *got_frame,
                             AVPacket *pkt)
{
    while(...) {
        // 查找是否有可用的 輸入緩衝區
        index = ff_AMediaCodec_dequeueInputBuffer(codec, input_dequeue_timeout_us);
        
        // 獲取輸入緩衝區
        data = ff_AMediaCodec_getInputBuffer(codec, index, &size);
        
        if (need_draining) {
            // 發送 結束信號
            status = ff_AMediaCodec_queueInputBuffer(codec, index, 0, 0, pts, flags);
        } else {
            // 傳送 要解碼的數據
            status = ff_AMediaCodec_queueInputBuffer(codec, index, 0, size, pts, 0);
        }
    }
    
    // 獲取輸出緩衝區
    index = ff_AMediaCodec_dequeueOutputBuffer(codec, &info, output_dequeue_timeout_us);
    if (index >= 0) {
        if (info.size) {
            if (s->surface) {
                // 直接顯示
                ret = mediacodec_wrap_hw_buffer(avctx, s, index, &info, frame);
            } else {
                // 獲取解碼後的數據
                data = ff_AMediaCodec_getOutputBuffer(codec, index, &size);
             // 轉換格式
                ret = mediacodec_wrap_sw_buffer(avctx, s, data, size, index, &info, frame);
            }
        } else {
            // 沒有輸出數據,釋放緩衝區
            status = ff_AMediaCodec_releaseOutputBuffer(codec, index, 0);
        }else if (ff_AMediaCodec_infoOutputFormatChanged(codec, index)) {
            // 輸出格式已更改
            
            // 刪除當前輸出格式
            status = ff_AMediaFormat_delete(s->format);
            // 使用新的輸出格式
         s->format = ff_AMediaCodec_getOutputFormat(codec);
        } else if (ff_AMediaCodec_infoOutputBuffersChanged(codec, index)) {
            // 輸出緩衝區已更改
            ff_AMediaCodec_cleanOutputBuffers(codec);
        } else if (ff_AMediaCodec_infoTryAgainLater(codec, index)) {
            // 超時
        } else {
            // 解碼錯誤
            av_log(avctx, AV_LOG_ERROR, "Failed to dequeue output buffer (status=%zd)\n", index);
            return AVERROR_EXTERNAL;
        }

    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章