天天看點

MediaCodec寫死成H264視訊流

android提供了一個強大的編解碼類MediaCodec,可以實作對視訊資料的編解碼,下面講一下如何對原始視訊資料寫死成h264格式的流

MediaCodec提供兩種方式的輸入,一種是将資料寫入它的輸入緩沖隊列裡,一種是讓MediaCodec建立一個輸入Surface,MediaCodec會自動從這個輸入Surface中讀取資料,因為我做的是錄制螢幕的需求,是以我是使用一個Surface輸入資料給MediaCodec。MediaCodec編碼出來的頭兩幀是特殊的,分别是sps 和 pps這兩幀在解碼時要用來配置解碼器用的。下面貼出編碼器代碼,這裡我做的是視訊實時編碼傳輸,是以編碼後的資料我使用socket發送的,大家主要還是看看編碼部分的代碼就好了:

package com.seewo.seewoair.coder;

/**
 * @author zhangsutao
 * @file VideoCodec.java
 * @brief 視訊編解碼器基類
 * @date 2016/8/7
 */
public interface VideoCodec {

    String MIME_TYPE = "video/avc";
    int VIDEO_FRAME_PER_SECOND = 15;
    int VIDEO_I_FRAME_INTERVAL = 5;
    int VIDEO_BITRATE = 500 * 8 * 1000;
}
           
/**
 * @author zhangsutao
 * @file VideoEncoder.java
 * @brief 視訊編碼器
 * @date 2016/7/29
 */
public class VideoEncoder implements VideoCodec {

    private Worker mWorker;
    private MediaProjection mMediaProjection;
    private VirtualDisplay mVirtualDisplay;
    private Client mClient;
    //寫入本地的流,在調試的時候使用
    private DataOutputStream mOutput;
    private final boolean isDebug=true;
    private final String TAG="VideoEncoder";
    private byte[] mFrameByte;

    public VideoEncoder(MediaProjection mediaProjection,Client client) {
        mClient=client;
        mMediaProjection=mediaProjection;
        if(isDebug){
            try {
                mOutput=new  DataOutputStream(new FileOutputStream(new File("/sdcard/h264encode")));;
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
        }
    }

    protected void onSurfaceCreated(Surface surface, int mWidth, int mHeight) {
        //将螢幕資料與surface進行關聯
         mVirtualDisplay = mMediaProjection.createVirtualDisplay("-display",
                mWidth, mHeight, 1, DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC,
                surface, null, null);

    }

    protected void onSurfaceDestroyed(Surface surface) {
        mVirtualDisplay.release();
        surface.release();
    }



    public void start() {
        if (mWorker == null) {
            mWorker = new Worker();
            mWorker.setRunning(true);
            mWorker.start();
        }
    }

    public void stop() {
        if (mWorker != null) {
            mWorker.setRunning(false);
            mWorker = null;
        }
        if(mClient!=null){
            if(!mClient.hasRelease()){
                mClient.release();
            }
        }
    }


    private  class Worker extends Thread {
        private MediaCodec.BufferInfo mBufferInfo;
        private MediaCodec mCodec;
        private volatile boolean isRunning;
        private Surface mSurface;
        private final long mTimeoutUsec;
        private int mWidth;
        private int mHeight;

        public Worker() {
            mBufferInfo = new MediaCodec.BufferInfo();
            mTimeoutUsec = 10000l;
        }

        public void setRunning(boolean running) {
            isRunning = running;
        }

        protected void onEncodedSample(MediaCodec.BufferInfo info, ByteBuffer data) {
            if(mFrameByte==null||mFrameByte.length<info.size){
                mFrameByte=new byte[info.size];
            }
            data.get(mFrameByte,0,info.size);
            boolean isSuccess1=mClient.sendInt(info.size);
            boolean isSuccess2=mClient.send(mFrameByte,0,info.size);
            Log.d(TAG,"sending success:"+isSuccess1+"  "+isSuccess2);
            if(!(isSuccess1&&isSuccess2)){
                isRunning=false;
                mClient.release();
            }
            //在debug時在本地寫一份
            if(isDebug){
                try {
                    mOutput.writeInt(info.size);
                    mOutput.write(mFrameByte,0,info.size);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        @Override
        public void run() {
            if(!prepare()){
                isRunning=false;
            }
            while (isRunning) {
                encode();
            }
            release();
        }

        void encode() {
            if (!isRunning) {
                //編碼結束,發送結束信号,讓surface不在提供資料
                mCodec.signalEndOfInputStream();
            }
            int status = mCodec.dequeueOutputBuffer(mBufferInfo, mTimeoutUsec);
            if (status == MediaCodec.INFO_TRY_AGAIN_LATER) {
                    return;
            } else
                if (status >= 0) {
                    ByteBuffer data = mCodec.getOutputBuffer(status);
                    if (data != null) {
                        final int endOfStream = mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM;
                        //傳遞編碼資料
                        if (endOfStream == 0) {
                            onEncodedSample(mBufferInfo, data);
                        }
                        // 一定要記得釋放
                        mCodec.releaseOutputBuffer(status, false);
                        if (endOfStream == MediaCodec.BUFFER_FLAG_END_OF_STREAM){
                            return;
                        }
                    }
                }
        }

        private void release() {
            onSurfaceDestroyed(mSurface);
            if(mCodec!=null){
                mCodec.stop();
                mCodec.release();
            }
            if(mOutput!=null){
                try {
                    mOutput.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }

        private boolean prepare() {
            // configure video output
            mWidth= SpUtils.readInt(Constans.KEY_DEVICE_WIDTH,-1);
            mHeight=SpUtils.readInt(Constans.KEY_DEVICE_HEIGHT,-1);
            if(mWidth==-1||mHeight==-1){
                return false;
            }
            mClient.connectToServer();
            //發送寬高
            boolean isSuccess1=mClient.sendInt(mWidth);
            boolean isSuccess2=mClient.sendInt(mHeight);
            if(!(isSuccess1&&isSuccess2)){
                isRunning=false;
                mClient.release();
            }
            MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, mWidth, mHeight);
            format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
                              MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
            format.setInteger(MediaFormat.KEY_BIT_RATE, VIDEO_BITRATE);
            format.setInteger(MediaFormat.KEY_FRAME_RATE, VIDEO_FRAME_PER_SECOND);
            format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL,VIDEO_I_FRAME_INTERVAL);
            format.setInteger(MediaFormat.KEY_REPEAT_PREVIOUS_FRAME_AFTER,40);
            try {
                mCodec = MediaCodec.createEncoderByType(MIME_TYPE);
            } catch (IOException e) {
                e.printStackTrace();
                return false;

            }
            mCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
            //建立關聯的輸入surface
            mSurface = mCodec.createInputSurface();
            mCodec.start();
            onSurfaceCreated(mSurface,mWidth,mHeight);
            return true;
        }
    }
}
           

繼續閱讀