Android短影片系統硬編碼—實現音影片編碼(二)

雲豹科技曉彤發表於2021-07-01

影片編碼例項

短影片系統影片的編碼和上篇文章的短影片系統音訊的編碼也大同小異。攝像頭的資料回撥時間並不是確定的,就算你設定了攝像頭FPS 範圍為 30-30 幀,它也不會每秒就一定給你 30 幀資料。 Android 攝像頭的資料回撥,受光線的影響非常嚴重,這是由 HAL 層的 3A 演算法決定的,你可以將自動曝光補償、自動白平光等等給關掉,這樣你才有可能得到穩定的幀率。
而我們錄製並編碼影片的時候,肯定是希望得到一個固定幀率的短影片系統影片。所以在短影片系統影片錄製並進行編碼的過程中,需要自己想些法子,讓幀率固定下來。最簡單也是最有效的做法就是,按照固定時間編碼,如果沒有新的攝像頭資料回撥來就用上一幀的資料。
參考程式碼如下:

private String mime="video/avc";    // 編碼的 MIME

private int rate=256000;            // 波特率, 256kb

private int frameRate=24;           // 幀率, 24

private int frameInterval=1;        // 關鍵幀一秒一關鍵幀

 

// 和音訊編碼一樣,設定編碼格式,獲取編碼器例項

MediaFormat format=MediaFormat.createVideoFormat(mime,width,height);

format.setInteger(MediaFormat.KEY_BIT_RATE,rate);

format.setInteger(MediaFormat.KEY_FRAME_RATE,frameRate);

format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL,frameInterval);

// 這裡需要注意,為了簡單這裡是寫了個固定的 ColorFormat

// 實際上,並不是所有的手機都支援 COLOR_FormatYUV420Planar 顏色空間

// 所以正確的做法應該是,獲取當前裝置支援的顏色空間,並從中選取

format.setInteger(MediaFormat.KEY_COLOR_FORMAT,

            MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar);

mEnc=MediaCodec.createEncoderByType(mime);

mEnc.configure(format,null,null,MediaCodec.CONFIGURE_FLAG_ENCODE);

 

// 同樣,準備好了後,開始編碼器

mEnc.start();

 

// 編碼器正確開始後,在子執行緒中迴圈編碼,固定位元速率的話,就是一個迴圈加上執行緒休眠的時間固定

// 流程和音訊編碼一樣,取出空盒子,往空盒子裡面加原料,放回盒子到原處 ,

// 盒子中原料被加工,取出盒子,從盒子裡面取出成品,放回盒子到原處

int index=mEnc.dequeueInputBuffer(-1);

if(index>=0){

    if(hasNewData){

        if(yuv==null){

            yuv=new byte[width*height*3/2];

        }

        // 把傳入的 rgba 資料轉成 yuv 的資料,轉換在網上也是一大堆,不夠下面還是一起貼上吧

        rgbaToYuv(data,width,height,yuv);

    }

    ByteBuffer buffer=getInputBuffer(index);

    buffer.clear();

    buffer.put(yuv);

    // 把盒子和原料一起放回到傳送帶上原來的位置

    mEnc.queueInputBuffer(index,0,yuv.length,timeStep,0);

}

MediaCodec.BufferInfo mInfo=new MediaCodec.BufferInfo();

// 嘗試取出加工好的資料,和音訊編碼一樣, do while while 都行,覺得怎麼好怎麼寫

int outIndex=mEnc.dequeueOutputBuffer(mInfo,0);

while (outIndex>=0){

    ByteBuffer outBuf=getOutputBuffer(outIndex);

    byte[] temp=new byte[mInfo.size];

    outBuf.get(temp);

    if(mInfo.flags==MediaCodec.BUFFER_FLAG_CODEC_CONFIG){

        // 把編碼資訊儲存下來,關鍵幀上要用

        mHeadInfo=new byte[temp.length];

        mHeadInfo=temp;

    }else if(mInfo.flags%8==MediaCodec.BUFFER_FLAG_KEY_FRAME){

        // 關鍵幀比普通幀是多了個幀頭的,儲存了編碼的資訊

        byte[] keyframe = new byte[temp.length + mHeadInfo.length];

        System.arraycopy(mHeadInfo, 0, keyframe, 0, mHeadInfo.length);

        System.arraycopy(temp, 0, keyframe, mHeadInfo.length, temp.length);

        Log.e(TAG,"other->"+mInfo.flags);

        // 寫入檔案

        fos.write(keyframe,0,keyframe.length);

    }else if(mInfo.flags==MediaCodec.BUFFER_FLAG_END_OF_STREAM){

        // 結束的時候應該傳送結束訊號,在這裡處理

    }else{

        // 寫入檔案

        fos.write(temp,0,temp.length);

    }

    mEnc.releaseOutputBuffer(outIndex,false);

    outIndex=mEnc.dequeueOutputBuffer(mInfo,0);

}

 

// 資料的來源, GL 處理好後, readpix 出來的 RGBA 資料喂進來,

public void feedData(final byte[] data, final long timeStep){

    hasNewData=true;

    nowFeedData=data;

    nowTimeStep=timeStep;

}

 

//RGBA YUV 的方法,這是最簡單粗暴的方式,在使用的時候,一般不會選擇在 Java 層,用這種方式做轉換

private void rgbaToYuv(byte[] rgba,int width,int height,byte[] yuv){

    final int frameSize = width * height;

 

    int yIndex = 0;

    int uIndex = frameSize;

    int vIndex = frameSize + frameSize/4;

 

    int R, G, B, Y, U, V;

    int index = 0;

    for (int j = 0; j < height; j++) {

        for (int i = 0; i < width; i++) {

            index = j * width + i;

            if(rgba[index*4]>127||rgba[index*4]<-128){

                Log.e("color","-->"+rgba[index*4]);

            }

            R = rgba[index*4]&0xFF;

            G = rgba[index*4+1]&0xFF;

            B = rgba[index*4+2]&0xFF;

 

            Y = ((66 * R + 129 * G + 25 * B + 128) >> 8) + 16;

            U = ((-38 * R - 74 * G + 112 * B + 128) >> 8) + 128;

            V = ((112 * R - 94 * G - 18 * B + 128) >> 8) + 128;

 

            yuv[yIndex++] = (byte) ((Y < 0) ? 0 : ((Y > 255) ? 255 : Y));

            if (j % 2 == 0 && index % 2 == 0) {

                yuv[uIndex++] = (byte) ((U < 0) ? 0 : ((U > 255) ? 255 : U));

                yuv[vIndex++] = (byte) ((V < 0) ? 0 : ((V > 255) ? 255 : V));

            }

        }

    }

}

 

對於短影片系統其他格式的音訊影片編解碼也大同小異了,只要MediaCodec 支援就好。
————————————————
宣告:宣告:本文由雲豹科技轉發自CSDN 湖廣午王 】部落格,如有侵權請聯絡作者刪除
原文連結:https://blog.csdn.net/junzia/article/details/54018671

 


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70002045/viewspace-2779320/,如需轉載,請註明出處,否則將追究法律責任。

相關文章