Android音視頻之MediaCodec

MediaCodec 可以用來獲得安卓底層的多媒體編碼,可以用來編碼和解碼,它是安卓 low-level 多媒體基礎(chǔ)框架的重要組成部分。那為什么不選擇FFmpeg來做視頻編解碼,由于在處理的過程中速率太慢,且需要在解碼后快速展示,所以選擇MediaCodec。通常和 MediaExtractor, MediaSync, MediaMuxer, MediaCrypto, MediaDrm, Image, SurfaceAudioTrack 一起使用。

圖片

MediaCodec大致來說處理輸入數(shù)據(jù)以生成輸出數(shù)據(jù)。 它異步處理數(shù)據(jù),并使用一組輸入和輸出緩沖區(qū)。 生成一個空的輸入緩沖區(qū),將其填充數(shù)據(jù)并將其發(fā)送到編解碼器進行處理。 編解碼器用完了數(shù)據(jù)并將其轉(zhuǎn)換為空的輸出緩沖區(qū)之一。 將已填充的輸出緩沖區(qū)給用戶,最后將其釋放回編解碼器。

一、數(shù)據(jù)類型

編解碼器支持的數(shù)據(jù)類型: 壓縮的音視頻據(jù)、原始音頻數(shù)據(jù)和原始視頻數(shù)據(jù)。

  • 數(shù)據(jù)通過ByteBuffers類來表示。
  • 可以設(shè)置Surface來獲取/呈現(xiàn)原始的視頻數(shù)據(jù),Surface使用本地的視頻buffer,不需要進行ByteBuffers拷貝??梢宰尵幗獯a器的效率更高。
  • 通常在使用Surface的時候,無法訪問原始的視頻數(shù)據(jù),但是可以使用ImageReader訪問解碼后的原始視頻幀。在使用ByteBuffer的模式下,可以使用Image類和getInput/OutputImage(int)獲取原始視頻幀。

壓縮數(shù)據(jù):

  • MediaFormat#KEY_MIME格式類型。
  • 對于視頻類型,通常是一個單獨的壓縮視頻幀。
  • 對于音頻數(shù)據(jù),通常是一個單獨的訪問單元(一個編碼的音頻段通常包含由格式類型決定的幾毫秒的音頻),但是這個要求稍微寬松一些,因為一個buffer可能包含多個編碼的音頻訪問單元。
  • 在這兩種情況下,buffer都不會在任意字節(jié)邊界上開始或結(jié)束,而是在幀/訪問單元邊界上開始或結(jié)束,除非它們被BUFFER_FLAG_PARTIAL_FRAME標記。

原始音頻buffers

原始音頻buffer包含PCM音頻數(shù)據(jù)的整個幀,這是每個通道按通道順序的一個樣本。每個樣本都是一個 AudioFormat#ENCODING_PCM_16BIT。

原始視頻buffers

在ByteBuffer模式下,視頻buffer根據(jù)它們的MediaFormat#KEY_COLOR_FORMAT進行布局??梢詮膅etCodecInfo(). MediaCodecInfo.getCapabilitiesForType.CodecCapability.colorFormats獲取支持的顏色格式。視頻編解碼器可以支持三種顏色格式:

  • native raw video format: CodecCapabilities.COLOR_FormatSurface,可以與輸入/輸出的Surface一起使用。
  • flexible YUV buffers 例如CodecCapabilities.COLOR_FormatYUV420Flexible, 可以使用getInput/OutputImage(int)與輸入/輸出Surface一起使用,也可以在ByteBuffer模式下使用。
  • other, specific formats: 通常只支持ByteBuffer模式。有些顏色格式是廠商特有的,其他定義在CodecCapabilities。對于等價于flexible格式的顏色格式,可以使用getInput/OutputImage(int)。

從Build.VERSION_CODES.LOLLIPOP_MR1.開始,所有視頻編解碼器都支持flexible的YUV 4:2:0 buffer。

二、MediaCodec API簡介

MediaCodec 主要的API做一個介紹:

  • MediaCodec創(chuàng)建:

    • createDecoderByType/createEncoderByType:根據(jù)特定MIME類型(如"video/avc")創(chuàng)建codec。
    • createByCodecName:知道組件的確切名稱(如OMX.google.mp3.decoder)的時候,根據(jù)組件名創(chuàng)建codec。使用MediaCodecList可以獲取組件的名稱。
  • configure:配置解碼器或者編碼器。

  • start:成功配置組件后調(diào)用start。

  • buffer處理的接口:

    • dequeueInputBuffer:從輸入流隊列中取數(shù)據(jù)進行編碼操作。
    • queueInputBuffer:輸入流入隊列。
    • dequeueOutputBuffer:從輸出隊列中取出編碼操作之后的數(shù)據(jù)。
    • releaseOutputBuffer:處理完成,釋放ByteBuffer數(shù)據(jù)。
    • getInputBuffers:獲取需要編碼數(shù)據(jù)的輸入流隊列,返回的是一個ByteBuffer數(shù)組。
    • getOutputBuffers:獲取編解碼之后的數(shù)據(jù)輸出流隊列,返回的是一個ByteBuffer數(shù)組。
  • flush:清空的輸入和輸出端口。

  • stop:終止decode/encode會話

  • release:釋放編解碼器實例使用的資源。

三、使用流程

  1. MediaCodec創(chuàng)建
  2. configure傳入配置數(shù)據(jù)
  3. MediaCodec.start()啟動轉(zhuǎn)碼
  4. dequeueInputBuffer填充數(shù)據(jù)
  5. getOutputBuffer獲取ByteBuffer數(shù)據(jù)
  6. releaseOutputBuffer釋放數(shù)據(jù)

MediaCodec創(chuàng)建

  1. 可以使用MediaCodecList為特定的媒體格式創(chuàng)建一個MediaCodec。
  • 可以從MediaExtractor#getTrackFormat獲得track的格式。
  • 使用MediaFormat#setFeatureEnabled注入想要添加的任何特性。
  • 然后調(diào)用MediaCodecList#findDecoderForFormat來獲取能夠處理該特定媒體格式的編解碼器的名稱。
  • 最后,使用createByCodecName(字符串)創(chuàng)建編解碼器。
  1. 還可以使用createDecoder/EncoderByType(java.lang.String)為特定MIME類型創(chuàng)建首選的編解碼器。但是,這不能用于注入特性,并且可能會創(chuàng)建一個不能處理特定媒體格式的編解碼器。

configure傳入配置數(shù)據(jù)

在創(chuàng)建好 MediaCodec 之后,需要對其進行設(shè)置,這樣 MediaCodec 的狀態(tài)就可以由 uninitialized 變成 configured

public void configure(
            @Nullable MediaFormat format,
            @Nullable Surface surface, @Nullable MediaCrypto crypto,
            @ConfigureFlag int flags) {
        configure(format, surface, crypto, null, flags);
}
public void configure(
            @Nullable MediaFormat format, @Nullable Surface surface,
            @ConfigureFlag int flags, @Nullable MediaDescrambler descrambler) {
        configure(format, surface, null,
                descrambler != null ? descrambler.getBinder() : null, flags);
}

MediaFormat format:輸入數(shù)據(jù)的格式(解碼器)或輸出數(shù)據(jù)的所需格式(編碼器)。傳null等同于傳遞MediaFormat#MediaFormat作為空的MediaFormat。

Surface surface:指定Surface,用于解碼器輸出的渲染。如果編解碼器不生成原始視頻輸出(例如,不是視頻解碼器)和/或想配置解碼器輸出ByteBuffer,則傳null。

MediaCrypto crypto:指定一個crypto對象,用于對媒體數(shù)據(jù)進行安全解密。對于非安全的編解碼器,傳null。

int flags:當組件是編碼器時,flags指定為常量CONFIGURE_FLAG_ENCODE。

MediaFormat, 如果某些參數(shù)沒有設(shè)置的話,會導致 MediaCodec 拋出 IllegalStateException.

Video 所必須的 Format Setting

Encoder Decoder
KEY_MIME ?? ??
KEY_BIT_RATE ?? ?
KEY_WIDTH ?? ??
KEY_HEIGHT ?? ??
KEY_COLOR_FORMAT ?? ?
KYE_FRAME_RATE ?? ?
KEY_I_FRAME_INTERVAL ?? ?

Audio 所必須的 Format Setting

Encoder Decoder
KEY_MIME ?? ??
KEY_BIT_RATE ?? ?
KEY_CHANNEL_COUNT ?? ??
KEY_SAMPLE_RATE ?? ??
 //視頻格式
        // 類型(avc高級編碼 h264) 編碼出的寬、高
        MediaFormat mediaFormat = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, mWidth, mHeight);
        //參數(shù)配置
        // 1500kbs碼率
        mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 1500_000);
        //幀率
        mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 20);
        //關(guān)鍵幀間隔
        mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 20);
        //顏色格式(RGB\YUV)
        //從surface當中回去
        mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
        //編碼器
        mMediaCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);
        //將參數(shù)配置給編碼器
        mMediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);

MediaCodec.start

mMediaCodec.start();

dequeueInputBuffer

//結(jié)合MediaMuxer去使用
//在初始化的時候調(diào)用
//封裝器 復用器
// 一個 mp4 的封裝器 將h.264 通過它寫出到文件就可以了
mMediaMuxer = new MediaMuxer(mPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);

//輸出緩沖區(qū)
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
//等待10 ms
int status = mMediaCodec.dequeueOutputBuffer(bufferInfo, 10_000);
//讓我們重試  1、需要更多數(shù)據(jù)  2、可能還沒編碼為完(需要更多時間)
            if (status == MediaCodec.INFO_TRY_AGAIN_LATER) {
            } else if (status == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
                //開始編碼 就會調(diào)用一次
                MediaFormat outputFormat = mMediaCodec.getOutputFormat();
                //配置封裝器
                // 增加一路指定格式的媒體流 視頻
                index = mMediaMuxer.addTrack(outputFormat);
                mMediaMuxer.start();
            } else if (status == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
                //忽略
            } else {
              //成功 取出一個有效的輸出
                ByteBuffer outputBuffer = mMediaCodec.getOutputBuffer(status);
                //如果獲取的ByteBuffer 是配置信息 ,不需要寫出到mp4
                if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
                    bufferInfo.size = 0;
                }

                if (bufferInfo.size != 0) {
                    //mSpeed是一個倍數(shù)默認是1
                    bufferInfo.presentationTimeUs = (long) (bufferInfo.presentationTimeUs / mSpeed);
                    //寫到mp4
                    //根據(jù)偏移定位
                    outputBuffer.position(bufferInfo.offset);
                    //ByteBuffer 可讀寫總長度
                    outputBuffer.limit(bufferInfo.offset + bufferInfo.size);
                    //寫出
                    mMediaMuxer.writeSampleData(index, outputBuffer, bufferInfo);
                }
            }
status 說明
MediaCodec.INFO_TRY_AGAIN_LATER 1、需要更多數(shù)據(jù) 2、可能還沒編碼為完(需要更多時間)
MediaCodec.INFO_OUTPUT_FORMAT_CHANGED 開始編碼 就會調(diào)用一次
MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED 這個方法過時了,忽略

getOutputBuffer

//成功 取出一個有效的輸出
ByteBuffer outputBuffer = mMediaCodec.getOutputBuffer(status);

使用此方法將輸出buffer返回給codec或?qū)⑵滗秩驹谳敵鰏urface。

releaseOutputBuffer

public void releaseOutputBuffer (int index, 
                boolean render)
  • boolean render:如果在配置codec時指定了一個有效的surface,則傳遞true會將此輸出buffer在surface上渲染。一旦不再使用buffer,該surface將把buffer釋放回codec。

總結(jié)

學完了MediaCodec和MediaMuxer我們可以結(jié)合使用,做一個視頻錄制的小功能,也可以加上OpenGL一起,主要是利用MediaCodec.createInputSurface();獲得一個Surface,就會自動編碼 inputSurface 中的圖像,交給虛擬屏幕 通過opengl 將預覽的紋理 繪制到這一個虛擬屏幕中

?著作權(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)容