FFmpeg 中的 Android MediaCodec

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

mediacodec.png

一個編解碼器處理輸入數(shù)據(jù)以生成輸出數(shù)據(jù)。它異步地處理數(shù)據(jù),并使用一組輸入和輸出緩沖器。

調(diào)用的時候需要先初始化 MediaCodec 作為視頻的編碼器,然后只需要不停傳入原始的 YUV 數(shù)據(jù)進(jìn)入編碼器就可以直接輸出編碼好的 h264 流。解碼器則對應(yīng)相反。

數(shù)據(jù)類型(Data Types)

編解碼器對 3 種數(shù)據(jù)進(jìn)行操作:壓縮后的數(shù)據(jù),原始音頻數(shù)據(jù)和原始視頻數(shù)據(jù)??梢允褂?ByteBuffers 處理所有三種數(shù)據(jù),但對原始視頻數(shù)據(jù),可以使用 Surface 提高編解碼的性能。Surface 使用本地視頻緩沖區(qū)而不是映射或復(fù)制到 ByteBuffers,因此,效率更高。(通常在使用 Surface 時無法訪問原始視頻數(shù)據(jù),但可以使用 ImageReader 類訪問不安全的解碼(原始)視頻幀)。

壓縮緩沖區(qū)(Compressed Buffers)

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

FFmpeg 中的 MediaCodec

FFmpeg 中僅支持 Android MediaCodec 解碼,本文主要介紹 FFmpeg 中關(guān)于 MediaCodec 解碼流程。后面會介紹如何在 FFmpeg 中添加 MediaCodec 編碼。

解碼流程:

  1. mediacodec_deocde_init (初始化 MediaCodec 解碼器)
FFmpeg 中 MediaCodec 解碼器初始化.png
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)
}
  1. mediacodec_decode_frame (解碼方法)

    FFmpeg 中 MediaCodec 解碼.png
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);
    }
}
  1. ff_mediacodec_dec_decode (解碼)

    ff_mediacodec_dec_decode.png
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) {
                // 直接顯示
                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);
        }else if (ff_AMediaCodec_infoOutputFormatChanged(codec, index)) {
            // 輸出格式已更改
            
            // 刪除當(dāng)前輸出格式
            status = ff_AMediaFormat_delete(s->format);
            // 使用新的輸出格式
         s->format = ff_AMediaCodec_getOutputFormat(codec);
        } else if (ff_AMediaCodec_infoOutputBuffersChanged(codec, index)) {
            // 輸出緩沖區(qū)已更改
            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;
        }

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

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

  • 原文:https://developer.android.com/reference/android/media/...
    thebestofrocky閱讀 6,361評論 0 6
  • MediaCodec的官方文檔 一、Android MediaCodec簡單介紹 Android中可以使用Medi...
    黃海佳閱讀 6,333評論 1 17
  • FFmpeg 介紹 FFmpeg是一套可以用來記錄、轉(zhuǎn)換數(shù)字音頻、視頻,并能將其轉(zhuǎn)化為流的開源計算機(jī)程序。采用LG...
    Y了個J閱讀 11,577評論 0 28
  • 1、領(lǐng)導(dǎo)的Challenge:終于鼓起勇氣給領(lǐng)導(dǎo)反饋,我認(rèn)為他在最后一輪面試的問題上,過分blame招聘團(tuán)隊,而沒...
    羊兔子CD閱讀 257評論 0 1
  • 小時候覺得酒很苦,為什么大人們那么愛喝? 長大后發(fā)現(xiàn)比起生活酒確實(shí)甜許多。 小時候很喜歡吃西紅柿,興奮的說要吃一輩...
    幸運(yùn)女神_0294閱讀 2,760評論 0 0

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