2017年5月3日 星期三

錄製手機螢幕聲音MediaCodec異步處理


MediaCodec異步處理,之前那方法是同步處理,之前文章我說錯了,寫入影音文件應該說編碼器才對,處理讀取影音文件才是解碼器,編碼解碼原理都一樣,
試著將錄取手機螢幕影像,之前程式改成MediaCodec異步處理,異步處理比同步處理更為直覺,寫法比較清楚,比較符合物件導向,我比較喜歡這樣的寫法.

說一下MediaCodec異步處理寫法原理,我喜歡用extends然後覆寫其中方法,總覺得這樣寫法才優雅,我們首先extends  MediaCodec.Callback,然後覆寫其中4個方法Method(如下面),在啟動MediaCodec前MediaCodec.setCallback(this)設定回傳Class為this,MediaCodec.start()開始後,這4個覆寫方法,就會開始時時監聽MediaCodec狀態,

1. onInputBufferAvailable監聽輸入端buffer可獲取,
2. onOutputBufferAvailable監聽輸出端buffer可獲取,
3. onOutputFormatChanged監聽格式發生變化,
4. onError監聽發生錯誤,

當然在這之前要先格式化MediaCodec,準備好代為處理輸入端的VirtualDisplay,方法中codec參數和index參數,info參數,就是監聽MediaCodec後的數據值,用這些參數來處理我們想要的資料,codec參數指該MediaCodec,index參數指MediaCodec索引值,info參數指MediaCodec buffer資訊,參數format指MediaCodec 格式發生變化format(onOutputFormatChange)
譬如codec.getOutputBuffer(index)就能取得輸出buffer,info.size()取得buffer size值


 //1 監聽輸入端buffer可獲取
    @Override
    public void onInputBufferAvailable(MediaCodec codec, int index) {    
        //codec索引值index,處理codec
        //這裡沒用到,Virtualdisplay已取代輸入端
    }
 //2 監聽輸出端buffer可獲取
    @Override
    public void onOutputBufferAvailable(MediaCodec codec, int index, MediaCodec.BufferInfo info) {
        Log.d("TAG","Output buffer begin.");
        ByteBuffer encodeData = codec.getOutputBuffer(index);
        ........................................................
        codec.releaseOutputBuffer(index, false);//觸理完一回,一定要釋放輸出buffer
    }
 
    //3 監聽codec發生錯誤重設codec
    @Override
    public void onError(MediaCodec codec, MediaCodec.CodecException e) {
                Log.d("TAG","Error:"+e);
                codec.reset();
    }
    //4 監聽格式發生變化重設格式,啟動合成器MediaMuxer
    @Override
    public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
        Log.d("TAG","output format already changed!");
        //vedio resetOutputFormat
        //MediaFormat newFormat = codec.getOutputFormat();
        //MediaMuxer addtrack
        mVideoTrackIndex = mMediaMuxer.addTrack(format);
        mMediaMuxer.start();
    }


關鍵碼,部份拿掉,這樣流程較清楚




public class surface_mediacodec_advance extends  MediaCodec.Callback{
    //private Boolean MUXER_START = false;
    private MediaProjection mp;
    private AtomicBoolean mQuit = new AtomicBoolean(false);
    private int mscreendi;
    private VirtualDisplay vp;
    private String save_path = "/sdcard/capture.mp4";
    private check_file_save_finish mcheck_file_save_finish;
    private long lastpresentationTimeUs;
    private int VIDEO_KEY_BIT_RATE = 4000000;//2000000 4000000
    private static final String MIME_TYPE = "video/avc"; // H.264 Advanced Video Coding
    private static final int FRAME_RATE = 30; // 30 fps
    private static final int IFRAME_INTERVAL = 10; // 10 seconds between I-frames
    private static final int TIMEOUT_US = 10000;
    private int CH_TYPE;
    //使用者已許可MediaProjection mp
    public surface_mediacodec_advance(int windowWidth, int windowHeight, int sdi, MediaProjection m, int audio_sample_rate, int ch) {
        CH_TYPE=ch;
        //初始化MediaCodec
        mwindowWidth = windowWidth;
        mwindowHeight = windowHeight;
        mp = m;

        mscreendi = sdi;
        try {
            mMediaMuxer= new MediaMuxer(save_path, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        Log.d("TAG"," CH TYPE :"+CH_TYPE);
    }
    //開始錄製
    public final void START_RECORDER() {
         preparerecorder_other();//設置MediaCodec video格式
        //preapareaudioencoder();//設置MediaCodec audio
        vp = mp.createVirtualDisplay("record_screen",
                mwindowWidth, mwindowHeight, mscreendi, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
                mSurface, null, null);
    }
    //停止錄製
    public final void STOP_RECORDER() {
        release();//停止要release
    }
    //設置mMediaCodec格式
    private void preparerecorder_other() {
        MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, mwindowWidth, mwindowHeight);
        //format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
        //MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible);
        format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
                MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
        format.setInteger(MediaFormat.KEY_BIT_RATE, VIDEO_KEY_BIT_RATE);
        format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
        format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
        Log.d("TAG", "created video format: " + format);
        try {
            mMediaCodec = MediaCodec.createEncoderByType(MIME_TYPE);
        } catch (IOException e) {
        }
        mMediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
        mSurface = mMediaCodec.createInputSurface();
        Log.d("TAG", "created input surface: " + mSurface);
        //設定MediaCodec回傳Class
        mMediaCodec.setCallback(this);
        mMediaCodec.start();
    }
    //release Mediacodec這順序要對否則格式會錯
    private void release() {
        mcheck_file_save_finish=new check_file_save_finish(save_path);
        while (!mcheck_file_save_finish.check_file_save_finish()){
            Log.d("TAG_NO","NO FINISH");
        }

        if (vp != null) {
            vp.release();
        }

        if (mMediaMuxer != null) {
            mMediaMuxer.stop();
            mMediaMuxer.release();
            mMediaMuxer = null;
        }
        //
        if (mMediaCodec != null) {
            mMediaCodec.stop();
            mMediaCodec.release();
            mMediaCodec= null;
        }
    //監聽輸入buffer可獲取
    @Override
    public void onInputBufferAvailable(MediaCodec codec, int index) {
        //這裡沒用到,Virtualdisplay已取代輸入端
    }
    //監聽輸出buffer可獲取
    @Override
    public void onOutputBufferAvailable(MediaCodec codec, int index, MediaCodec.BufferInfo info) {
        Log.d("TAG","Output buffer begin.");
        ByteBuffer encodeData = codec.getOutputBuffer(index);
        if ((info.flags & codec.BUFFER_FLAG_CODEC_CONFIG) != 0) {//編碼數據,非媒體數據,忽略它
            info.size = 0;
        }
        if (info.size == 0) {//編碼數據,非媒體數據,忽略它
            encodeData = null;
            Log.d("TAG", "Ignore Data");
        } else {                  //非媒體數據,產生提示
            Log.d("TAG", "Get Info: size="
                    + info.size + " Video presentationTimeUs="
                    + info.presentationTimeUs + " offset="
                    + info.offset);
        }
   
        if (encodeData != null) {                             //媒體數據寫入
            encodeData.position(info.offset);
            encodeData.limit(info.offset + info.size);
            mMediaMuxer.writeSampleData(mVideoTrackIndex, encodeData, info);
            Log.d("TAG", "Send Info: size=" + info.size);
        }
   
        codec.releaseOutputBuffer(index, false);//釋放輸出buffer
    }
    //codec發生錯誤重設codec
    @Override
    public void onError(MediaCodec codec, MediaCodec.CodecException e) {
                Log.d("TAG","Error:"+e);
                codec.reset();
    }
    //格式發生變化重設格式
    @Override
    public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {

        Log.d("TAG","output format already changed!");
        //vedio resetOutputFormat
        //MediaFormat newFormat = codec.getOutputFormat();
        //MediaMuxer addtrack
        mVideoTrackIndex = mMediaMuxer.addTrack(format);
        mMediaMuxer.start();
    }

}

據上面錄製手機改影像編碼器為異步處理後,接著又完成了MediaCodec異步處理音頻編碼,音頻編碼異步流程,同影像編碼只是多處理輸入端

監聽輸入onInputBufferAvailable中去讀取MIC麥克風buffer資料,getInputBuffer(index)取得MediaCodec輸入buffer,將資料填入這buffer,然後queueInputBuffer(),此時onOutputBufferAvailable監聽輸出buffer可獲取,透過界面送到影像編碼寫入合成器,然後釋放此buffer,然後監聽輸入onInputBufferAvailable變又可獲取,重複上面,又去讀取MIC麥克風buffer資料.................

音頻編碼器,設計一個界面capture_audio_outpu_buffer,含實作2個方法
        //捕捉輸出buffer資料,以便寫入合成器
        void on_capture_audio_outpu_buffer(MediaCodec codec,ByteBuffer encodedAudioData,MediaCodec.BufferInfo info);
        //audio MediaCodec初始化MediaFormat,以便產生AudioTrackIndex,使能夠啟動合成
        void on_capture_mediaformat(MediaFormat mMediaFormat);

影像編碼器,實作上面界面的2個方法,影像編碼器在監聽onOutputFormatChanged中實例化音頻編碼器,set_capture_audio_outpu_buffer_listener監聽this,start()後on_capture_mediaformat會產生AudioTrackIndex,on_capture_audio_outpu_buffer開始監聽音頻編碼器的輸出資料,寫入合成器,並啟動合成器

後記:遇到有些手機AudioRecoderformat 設置立體聲,聲道數不同錄製完,回放影片聲音會變快,看一下程式就知道,是因為Audio取樣時,buffer length比getInputBuffer(index)的剩餘長度,還長,沒完全填入,只要分次全部填入就解掉了

影像編碼

public class surface_mediacodec_advance extends  MediaCodec.Callback implements audio_encoder_advance.capture_audio_outpu_buffer{
.........................................
   ///////////////////實作 audio_encoder_advance.capture_audio_outpu_buffer Method///////////////////
    //監聽audio_encoder_advance MediaFormat初始化一次
    @Override
    public void on_capture_mediaformat(MediaFormat mMediaFormat){
        //產生AudioTrackIndex
        mAudioTrackIndex = mMediaMuxer.addTrack(mMediaFormat);
    }
    //時時監聽audio_encoder_advance outpu buffer
    @Override
    public void on_capture_audio_outpu_buffer(MediaCodec codec,ByteBuffer encodedAudioData,MediaCodec.BufferInfo info){
        //audio_encoder_advance輸出,寫入合成器
        mMediaMuxer.writeSampleData(mAudioTrackIndex, encodedAudioData, info);
    }
   ........................................................................................
   //格式發生變化實例化音頻編碼器,set_capture_audio_outpu_buffer_listener,start()後會產生AudioTrackIndex,
   //開始監聽audio_encoder_advance outpu buffer寫入合成器
    @Override
    public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {

        Log.d("TAG_C","output format already changed!");
        //vedio resetOutputFormat
        //MediaFormat newFormat = codec.getOutputFormat();
        //MediaMuxer addtrack
        mVideoTrackIndex = mMediaMuxer.addTrack(format);
        //這要通知其他mediacodec reset
        if(maudio_encoder_advance==null){
             Log.d("TAG"," SAMPLE RATE AUDO :"+maudio_sample_rate);
             //實例化audio_encoder_advance
             maudio_encoder_advance=new audio_encoder_advance(maudio_sample_rate,CH_TYPE);
             //設定傾聽事件
             maudio_encoder_advance.set_capture_audio_outpu_buffer_listener(this);
        }
        //audio_encoder_advance開始編碼
        maudio_encoder_advance.start();

        //若存在,resetOutputFormat,要在這reset其他format
        //if(maudio_encoder_advance!=null){
        //   maudio_encoder_advance.resetOutputFormat();
        //}

        //啟動合成器
        mMediaMuxer.start();


    }
...............................................
}

音頻編碼

audio_encoder_advance class

public class audio_encoder_advance extends  MediaCodec.Callback{
    public void start(){
          preapareaudioencoder
    }
    private void preapareaudioencoder() {
       //設置audio MediaCodec格式
       .............................
       if(mlistener!=null) {
         //設新newAudioFormat給監聽者,以便產生AudioTrackIndex
         mlistener.on_capture_mediaformat(newAudioFormat);
        }
        //設回call
        mAudioEncoder.setCallback(this);
        mAudioEncoder.start();
    }
    ....................................................................................
    //時時監聽輸出buffer可獲取
    @Override
    public void onOutputBufferAvailable(MediaCodec codec, int index, MediaCodec.BufferInfo info) {
       ...............................................................................

       if(mlistener!=null) {
                            //設輸出資料給監聽者
                            mlistener.on_capture_audio_outpu_buffer(codec, encodedAudioData, info);
                        }

    }
    /////////////////////////////////界面////////////////////////////////////////////////////////////////
    //interface變數
    private capture_audio_outpu_buffer mlistener=null;
    //設定listener
    public void set_capture_audio_outpu_buffer_listener(capture_audio_outpu_buffer ca){
        mlistener=ca;
    }
    //設計界面
    public interface  capture_audio_outpu_buffer{
        //捕捉輸出buffer資料
        void on_capture_audio_outpu_buffer(MediaCodec codec,ByteBuffer encodedAudioData,MediaCodec.BufferInfo info);
        //audio MediaCodec初始化MediaFormat
        void on_capture_mediaformat(MediaFormat mMediaFormat);
    }
}

沒有留言: