2017年4月18日 星期二

MediaProjection MediaCodec解碼器聲音加影像全螢幕錄屏




htc e9+plus




htc derise 820( 錄影加截圖)





影片是錄製結果,聲音是透過麥克風會有一些雜音,效果還可以比MediaRecorder好很多
剛開始htc e9+跑很OK,換到htc derise 820 MediaMuxer.stop()會崩貴,和presentationTimeUs error 錄製就中斷,弄好幾天,終於解掉錯誤正常了

MediaCodec解碼器流程

會寫程式,應該都知道解碼器一定必須要用執行緒Thread去跑

public void run()

{

 //解碼器
 while(true){………}
}
1.
使用者授權取得mMediaProjection後
2.
MediaCodec定義格式preparerecorder取得surface,建立VirtualDisplay
3解碼流程
while (!mQuit.get()) {
       //根據MediaCodec,dequeueOutputBuffer()  傳回整數值     
int index =   mMediaCodec.dequeueOutputBuffer(mBufferInfo, TIMEOUT_US);
//後續輸出格式產生變化,譬如放音樂,暫停音樂,
        
if (index == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
              
//重設格式resoutputform
at      
 } else
           
 //INFO_TRY_AGAIN_LATER指請求超時
            
if (index == MediaCodec.INFO_TRY_AGAIN_LATER) {
             等待一下sleep
          
             
} else
                
//大於0有效輸出
               
  if (index >= 0) {
                 開始解碼encode
  
  mMediaCodec.releaseOutputBuffer(index, false);//釋放輸出buffer
         
 }
    

}
   
 release();//最後一定要釋放掉,否則格式會錯
}
從暫存器MediaCodec.dequeueOutputBuffer()會傳回整數值,去判斷資料內容,會有三種狀況,當一組buffer處理完就要釋放releaseOutputBuffer(index, false),在處理下 一組 
A.整數值MediaCodec.INFO_OUTPUT_FORMAT_CHANGED代表輸出格式產生變化
B.整數值MediaCodec.INFO_TRY_AGAIN_LATER代表請求超時
C.整數值大於等於0代表有效輸出
處理A狀況:
      需要重設MediaCodec,使用getOutputFormat()重設,此使就可addTrack給MediaMuxer後續做寫入動作    
MediaFormat newFormat = mMediaCodec.getOutputFormat();    mVideoTrackIndex = mMediaMuxer.addTrack(newFormat);
   mMediaMuxer.start();

處理B狀況:
      等待一下sleep就可
處理C狀況:
      把整數值餵給解碼器,開始解碼

為什會發生A狀況輸出格式產生變化,舉個例當解碼時使用者暫停或播放音樂,輸出格式就會產生變化
輸出格式產生變化,為什要重設格式,輸出格式產生變化getOutputFormat()會不一樣,
若不getOutputFormat()加入MediaMuxer add track,資料是寫不進去的,會產生error

4.至於後續解碼器根據索引值index整數值,取得buffer dat,這裡沒列出來,這部分較複雜,網路上有例子,影像解碼器比較單純只處理OUTPUT端,INPUT端 已交給VirtualDisplay,會些程式的,應該都看得懂,聲音解碼器須用另一個Thread去repeat流程

MediaCodec原理
1.
MediaCodec有一輸出端和輸入輸入端共用一組Buffer
2.
repeat流程

開始先準備給輸入端的buffer data,也就是採樣,譬如從麥克風或SPEAKER採樣
準備好輸入端buffer data,就開始第一次buffer data丟給MediaCodec,首先向MediaCodec申請一組空的buffer,準備好輸入端buffer data添進申請的buffer,再把buffer送回MediaCodec, MediaCodec就會處理,然後MediaCodec處理資料添進這buffer送到輸出端,使用者拿到buffeer data,處理完資料,再把這組buffer送回MediaCodec然後release,這樣就完成一回INPUT和OUTPUT,只要一直重複這動作,這裡只介紹解碼流程

MediaCodec根據機器不同,會有不同狀況發生,A手機跑沒問題,B手機可能有問題,就要解BUG

解碼器流程關鍵代碼

public class surface_mediacodec extends Thread {
………………………….
@Override

public void run()
{
     startRecord();//開始錄屏

}
public void startRecord() {

    preparerecorder();
  //設置MediaCodec video

  //preapareaudioencoder();
  //設置MediaCodec audio

  vp = mp.createVirtualDisplay("record_screen",
            mwindowWidth, mwindowHeight,          mscreendi,
        DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
            mSurface,  null, null);

  //開始錄製

  recorder_VirtualDisplay();

}
private void recorder_VirtualDisplay() {

//根據根據MediaCodec,dequeueOutputBuffer()  傳回整數值  
while (!mQuit.get()) {    
  int index = mMediaCodec.dequeueOutputBuffer(mBufferInfo, TIMEOUT_US);  
  //INFO_OUTPUT_FORMAT_CHANGED指後續輸出格式產生變化,譬如放音樂,暫停音樂,
  // 呼叫resetOutputFormat()會同時啟動音頻audio_encoder Thread解碼  
  // 若audio_encoder thread已不在會重新建立,譬如暫停音樂,音源解完,audio_encoder會等於null
   //若再啟動音樂,又會重新建立新的audio_encoder Thread
 if (index == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
            resetOutputFormat();//重設格式,啟動MediaMuxer
        } else
        //INFO_TRY_AGAIN_LATER指請求超時,等待一下
        if (index == MediaCodec.INFO_TRY_AGAIN_LATER) {      
          try {
                Thread.sleep(time_out_sleep);      
                Log.d("TAG","time delay");  
            } catch (InterruptedException e) {

                e.printStackTrace();
            }

       } else        
       //大於0有效輸出,開始解碼
      if (index >= 0) {

            if (!MUXER_START) {
               throw new IllegalStateException("MediaMuxer do not call addTrack(format)");

            }

            encodecToVideoTrack(index);
            mMediaCodec.releaseOutputBuffer(index, false);//釋放輸出buffer
                }

     }

     release();//最後一定要釋放掉,否則格式會錯

}
//影像解碼器
private void encodecToVideoTrack(int index) {

   ………………

}
//重設格式
private void resetOutputFormat() {
     //產生Thread音源解碼器,處理音源,並啟動,第一次是null會被建立
       if(maudio_encoder==null){

         maudio_encoder=new audio_encoder(TIMEOUT_US,time_out_sleep,this);

         maudio_encoder.start();

       }

      //若Thread存在,resetOutputFormat

      if(maudio_encoder!=null){

         maudio_encoder.resetOutputFormat();

      }
      //vedio resetOutputFormat

     MediaFormat newFormat = mMediaCodec.getOutputFormat();

     //MediaMuxer add Track
     mVideoTrackIndex = mMediaMuxer.addTrack(newFormat);

     //啟動MediaMuxer
     mMediaMuxer.start();

    ……………  

}

private void release(){
    ……………………
 }

沒有留言: