1. 程式人生 > >C同學的工作筆記 螢幕錄製/暫停及音視訊混合編碼

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);
}

}