在前面我們學習和使用了AudioRecord、AudioTrack、Camera、 MediaExtractor、MediaMuxer API、MediaCodec。 學習和使用了上述的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之后才能繼續了。錄制音視頻的學習先到此為止了。
