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 編碼。
解碼流程:
-
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)
}
-
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);
}
}
-
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;
}
}
}