Android 音視頻開發(七): 音視頻錄制流程總結


在前面我們學習和使用了AudioRecordAudioTrackCamera MediaExtractorMediaMuxer APIMediaCodec。 學習和使用了上述的API之后,相信對Android系統的音視頻處理有一定的經驗和心得了。本文及后面的幾篇文章做的事情就是將這些知識串聯起來,做一些稍微復雜的事情。

一、流程分析

1.1 需求說明

我們需要做的事情就是:串聯整個音視頻錄制流程,完成音視頻的采集、編碼、封包成 mp4 輸出。

1.2 實現方式

Android音視頻采集的方法:預覽用SurfaceView,視頻采集用Camera類,音頻采集用AudioRecord。

1.3 數據處理思路

使用MediaCodec 類進行編碼壓縮,視頻壓縮為H.264,音頻壓縮為aac,使用MediaMuxer 將音視頻合成為MP4。

二、 實現過程

2.1 收集Camera數據,並轉碼為H264存儲到文件

在收集數據之前,對Camera設置一些參數,方便收集后進行數據處理:

Camera.Parameters parameters = camera.getParameters();
parameters.setPreviewFormat(ImageFormat.NV21);
parameters.setPreviewSize(1280, 720);

然后設置PreviewCallback:

camera.setPreviewCallback(this);

就可以獲取到Camera的原始NV21數據:

onPreviewFrame(byte[] bytes, Camera camera)

在創建一個H264Encoder類,在里面進行編碼操作,並將編碼后的數據存儲到文件:

new Thread(new Runnable() {

    @Override
    public void run() {
        isRuning = true;
        byte[] input = null;
        long pts = 0;
        long generateIndex = 0;

        while (isRuning) {
            if (yuv420Queue.size() > 0) {
                input = yuv420Queue.poll();
                byte[] yuv420sp = new byte[width * height * 3 / 2];
                // 必須要轉格式,否則錄制的內容播放出來為綠屏
                NV21ToNV12(input, yuv420sp, width, height);
                input = yuv420sp;
            }
            if (input != null) {
                try {
                    ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers();
                    ByteBuffer[] outputBuffers = mediaCodec.getOutputBuffers();
                    int inputBufferIndex = mediaCodec.dequeueInputBuffer(-1);
                    if (inputBufferIndex >= 0) {
                      pts = computePresentationTime(generateIndex);
                      ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
                      inputBuffer.clear();
                      inputBuffer.put(input);
                      mediaCodec.queueInputBuffer(inputBufferIndex, 0, input.length, System.currentTimeMillis(), 0);
                      generateIndex += 1;
                    }

                    MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
                    int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, TIMEOUT_USEC);
                    while (outputBufferIndex >= 0) {
                        ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];
                        byte[] outData = new byte[bufferInfo.size];
                        outputBuffer.get(outData);
                        if (bufferInfo.flags == MediaCodec.BUFFER_FLAG_CODEC_CONFIG) {
                            configbyte = new byte[bufferInfo.size];
                            configbyte = outData;
                        } else if (bufferInfo.flags == MediaCodec.BUFFER_FLAG_SYNC_FRAME) {
                            byte[] keyframe = new byte[bufferInfo.size + configbyte.length];
                            System.arraycopy(configbyte, 0, keyframe, 0, configbyte.length);
                            System.arraycopy(outData, 0, keyframe, configbyte.length, outData.length);
                            outputStream.write(keyframe, 0, keyframe.length);
                        } else {
                            outputStream.write(outData, 0, outData.length);
                        }

                        mediaCodec.releaseOutputBuffer(outputBufferIndex, false);
                        outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, TIMEOUT_USEC);
                    }

                } catch (Throwable t) {
                    t.printStackTrace();
                }
            } else {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

        // 停止編解碼器並釋放資源
        try {
            mediaCodec.stop();
            mediaCodec.release();
        } catch (Exception e) {
            e.printStackTrace();
        }

        // 關閉數據流
        try {
            outputStream.flush();
            outputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}).start(); 

當結束編碼的時候,需要將相關的資源釋放掉:

// 停止編解碼器並釋放資源
try {
    mediaCodec.stop();
    mediaCodec.release();
} catch (Exception e) {
    e.printStackTrace();
}

// 關閉數據流
try {
    outputStream.flush();
    outputStream.close();
} catch (IOException e) {
    e.printStackTrace();
}

此時,我們做到了將視頻內容采集-->編碼-->存儲文件。但這個僅僅是對Android 音視頻開發(四):使用 Camera API 采集視頻數據的延伸,但是很有必要。因為在前面學習了如何采集音頻,如何使用MediaCodec去處理音視頻,如何使用MediaMuxer去混合音視頻。

示例代碼:https://github.com/renhui/AndroidRecorder/releases/tag/only_h264_video

下面我們在當前的的基礎上繼續完善,即將音視頻采集並混合為音視頻。

2.2 音視頻采集+混合,存儲到文件

基本完成思路已經在2.1的結尾處坐了說明,下面貼一下demo的鏈接:

示例代碼:https://github.com/renhui/AndroidRecorder/releases/tag/h264_video_audio

 

想做更多的事情,只有學習了OpenGL之后才能繼續了。錄制音視頻的學習先到此為止了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM