C同學的工作筆記 螢幕錄製/暫停及音視訊混合編碼
最近做到螢幕錄製,費了些功夫,簡單記錄下開發過程
5.0中提供了MediaProjection類來實現錄屏,用起來也簡單
核心是MediaProjection、MediaCodec、MediaMuxer
我的理解就是:採集 -> 轉碼 -> 混合生存檔案
簡單來說,通過MediaCodec得到一個surface,這是MediaCodec的轉碼源,就是要進行轉碼的視訊的畫布,差不多能這麼認為吧,然後用MediaProjection的createVirtualDisplay方法開始對這個surface輸出,一個執行緒迴圈來使MediaCodec進行轉碼,獲得的資料交給MediaMuxer混合輸入到檔案中,然後就ok了
這部的例子很多,功能也很簡單,官方的個人的都很多,例如下面這個:
http://blog.csdn.net/l00149133/article/details/50483327
但是這樣出來的話,一個是沒有音訊,第二是暫停的問題,基本方法裡木有找到暫停的思路,也沒發現什麼有價值的參考
感覺暫停比較簡單,最初的思路就是,暫停的時間我不寫入MediaMuxer就行了,然後發現。。。例如錄3秒,暫停3秒,再錄3秒,最後的視訊是9秒,中間是沒有變化的同一幀
很明顯雖然沒寫入資料還是記了時間,然後第二個思路就是暫停的時候直接stop,然後start,然後就崩了。。。stop會直接放棄之前的設定
第三個思路,暫停的時候直接stop,然後重新配置,然後start,然後如果是MediaCodec這麼做,現象跟不寫入時一樣,有空白的一段,如果是MediaMuxer,視訊就重新開始了。。。
重新讀了下三個核心類的開發文件,首先放棄MediaProjection,功能單一,沒什麼可嘗試的,重點在於MediaCodec向MediaMuxer轉入的資料,裡面有個時間戳,是不是這個時間戳的問題,一番嘗試後搞定,時間戳就是個寫入位置的標記,只要減去中間暫停的時間就行了,順便一說,這個體系的時間單位是微秒
最後一個問題,音訊怎麼整,沒什麼思路啊,後來找到一篇介紹的
http://blog.csdn.net/jinzhuojun/article/details/32163149
這個傳的還挺廣,寫的真不錯,真想給作者贊幾個加個關注什麼的,不過連著幾個人的都標得原創。。也不知道到底是誰寫的,覺得這個最像吧。。。也是無語了。。轉人帖子很好,擴散技術,非寫自己原創就鄙視了。。
話題轉回來哈,寫的有點籠統,主要思路就是兩個執行緒,然後兩個MediaCodec,一個還做我們剛才事,另一個做錄音,用的是AudioRecord,暫停邏輯都相似,在原來那個執行緒裡每次迴圈的時候,都加個寫入音訊,然後錄音執行緒裡,加一個向MediaCodec傳資料,這個MediaCodec專門負責音訊,他的資料來源就要通過AudioRecord負責了
附錄制程式碼:
/** * 處理視訊 */ private void drainVideoEncoder(boolean endOfStream) { if (endOfStream) { //標誌輸入流的結束 videoEncoder.signalEndOfInputStream(); } int encoderStatus = videoEncoder.dequeueOutputBuffer(mBufferInfo, TIMEOUT_US); /* ====== 新的格式或格式改變,應當初始化 ====== */ if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { //加入視訊軌道 MediaFormat newFormat = videoEncoder.getOutputFormat(); mVideoTrackIndex = mMuxer.addTrack(newFormat); mNumTracksAdded++; if (mNumTracksAdded == TOTAL_NUM_TRACKS) { mMuxerStarted = true; mMuxer.start(); } } /* ====== 等待 ====== */ else if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } /* ====== 小於0不處理 ====== */ else if (encoderStatus < 0) { // let's ignore it } /* ====== 大於於0 正式開始紀錄 ====== */ else { if (!mMuxerStarted) { throw new IllegalStateException("MediaMuxer dose not call addTrack(format) "); } ByteBuffer encodedData = videoEncoder.getOutputBuffer(encoderStatus); //忽略 if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) { // The codec config data was pulled out and fed to the muxer when we got // Ignore it. mBufferInfo.size = 0; } //空值 if (mBufferInfo.size == 0) { encodedData = null; } //暫停不紀錄 if (mPause.get()) { encodedData = null; } if (encodedData != null) { //從暫停中恢復的第一次紀錄 if (videoFirstResume) { videoFirstResume = false; pauseTime += mBufferInfo.presentationTimeUs - videoLastRecordTime - 10000; videoEncoder.flush(); return; } else { videoLastRecordTime = mBufferInfo.presentationTimeUs; mBufferInfo.presentationTimeUs -= pauseTime; encodedData.position(mBufferInfo.offset); encodedData.limit(mBufferInfo.offset + mBufferInfo.size); mMuxer.writeSampleData(mVideoTrackIndex, encodedData, mBufferInfo); } } videoEncoder.releaseOutputBuffer(encoderStatus, false); } }