Android端的短視訊開發,我們該如何快速實現移動端短視訊功能?

Android入墳之路發表於2019-04-03

當下抖音非常火熱,是不是也很心動做一個類似的app嗎?

一.短視訊內容生產

優質短視訊內容的產生依賴於短視訊的採集和特效編輯,這就要求在進行抖音APP開發時,用到基礎的美顏、混音、濾鏡、變速、圖片視訊混剪、字幕等功能,在這些功能基礎上,進行預處理,結合OpenGL、AI、AR技術,產生很多有趣的動態貼紙玩法,使得短視訊內容更具創意。

Android端的短視訊開發,我們該如何快速實現移動端短視訊功能?

視訊錄製的大致實現流程是先由 Camera 、 AudioRecord 進行最原始的相機畫面以及聲音的採集,然後將採集的資料進行濾鏡、降噪等前處理,處理完成後由 MediaCodec 進行硬體編碼,最後採用 MediaMuxer 生成最終的 MP4 檔案。

二.短視訊處理播放

視訊的處理和播放主要是視訊的清晰度、觀看流暢度方面的體驗。在這方面來講,可以採用“窄帶高清”技術,在節省位元速率的同時能夠提供更加清晰的觀看體驗,經過測試,同等視訊質量下最高可以節省20-40%頻寬。除了頻寬之外,短視訊內容的儲存和CDN優化也尤為重要,通常我們需要上傳到雲端儲存伺服器的內容是短視訊內容和封面內容。

而CDN優化帶給短視訊平臺的則是進一步的短視訊首次載入和迴圈播放方面的體驗。比如針對首播慢的問題,像阿里雲播放器支援QUIC協議,基於CDN的排程,可以使短視訊首次播放秒開的成功率達到98%,此外在迴圈播放時還可以邊播放邊快取,使用者反覆觀看某一短視訊時就不用耗費流量了。

三.錄製視訊的方式

在Android系統當中,如果需要一臺Android裝置來獲取到一個MP4這樣的視訊檔案的話,主流的方式一共與三種:MediaRecorder、MediaCodec+MediaMuxer、FFmpeg。

MediaRecorder:是Android系統直接提供給我們的錄製類,用於錄製音訊和視訊的一個類,簡單方便,不需要理會中間錄製過程,結束錄製後可以直接得到音訊檔案進行播放,錄製的音訊檔案是經過壓縮的,需要設定編碼器,錄製的音訊檔案可以用系統自帶的播放器播放。

優點:大部分以及整合,直接呼叫相關介面即可,程式碼量小,簡單穩定;

缺點:無法實時處理音訊;輸出的音訊格式不是很多。

MediaCodec+MediaMuxer: MediaCodec 與 MediaMuxer結合使用同樣能夠實現錄製的功能。MediaCodec是Android提供的編解碼類,MediaMuxer則是複用類(生成視訊檔案)。從易用性的角度上來說肯定不如MediaRecorder,但是允許我們進行更加靈活的操作,比如需要給錄製的視訊新增水印等各種效果。

優點: 與MediaRecorder一樣低功耗速度快,並且更加靈活

缺點: 支援的格式有限,相容性問題

FFmpeg: FFmpeg(Fast forword mpeg,音視訊轉換器)是一個開源免費跨平臺的視訊和音訊流方案,它提供了錄製/音視訊編解碼、轉換以及流化音視訊的完整解決方案。主要的作用在於對多媒體資料進行解協議、解封裝、解碼以及轉碼等操作

優點:格式支援非常的強,十分的靈活,功能強大,相容性好;

缺點:C語言些的音視訊編解碼程式,使用起來不是很方便。

Android端的短視訊開發,我們該如何快速實現移動端短視訊功能?

雖然從資料看來FFmpeg是最好的,但是我們得首先排除這種,因為他的易用性是最差的;其次,MediaRecorder也是需要排除的,所以在這裡我比較推薦MediaCodec+MediaMuxer這種方式。

四.編碼器引數

位元速率:資料傳輸時單位時間傳送的資料位數,kbps:千位每秒。位元速率和質量成正比,也和檔案體積成正比。位元速率超過一定數值,對影象的質量沒有多大的影響。

幀數:每秒顯示多少個畫面,fps

關鍵幀間隔:在H.264編碼中,編碼後輸出的壓縮影象資料有多種,可以簡單的分為關鍵幀和非關鍵幀。關鍵幀能夠進行獨立解碼,看成是一個影象經過壓縮的產物。而非關鍵幀包含了與其他幀的“差異”資訊,也可以稱呼為“參考幀”,它的解碼需要參考關鍵幀才能夠解碼出一個影象。非關鍵幀擁有更高的壓縮率。

五、MediaCodec+MediaMuxer的使用

MediaMuxer和MediaCodec這兩個類,它們的參考文developer.android.com/reference/a…developer.android.com/reference/a…,裡邊有使用的框架。這個組合可以實現很多功能,比如音視訊檔案的編輯(結合MediaExtractor),用OpenGL繪製Surface並生成mp4檔案,螢幕錄影以及類似Camera app裡的錄影功能(雖然這個用MediaRecorder更合適)等。

它們一個是生成視訊,一個生成音訊,這裡把它們結合一下,同時生成音訊和視訊。基本框架和流程如下:

Android端的短視訊開發,我們該如何快速實現移動端短視訊功能?

首先是錄音執行緒,主要參考HWEncoderExperiments。通過AudioRecord類接收來自麥克風的取樣資料,然後丟給Encoder準備編碼:

AudioRecord audio_recorder; 
audio_recorder = new AudioRecord(MediaRecorder.AudioSource.MIC, 
 SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT, buffer_size); 
// ... 
audio_recorder.startRecording(); 
while (is_recording) { 
 byte[] this_buffer = new byte[frame_buffer_size]; 
 read_result = audio_recorder.read(this_buffer, 0, frame_buffer_size); // read audio raw data 
 // … 
 presentationTimeStamp = System.nanoTime() / 1000; 
 audioEncoder.offerAudioEncoder(this_buffer.clone(), presentationTimeStamp); // feed to audio encoder 

} 

複製程式碼

這裡也可以設定AudioRecord的回撥(通過setRecordPositionUpdateListener())來觸發音訊資料的讀取。offerAudioEncoder()裡主要是把audio取樣資料送入音訊MediaCodec的InputBuffer進行編碼:

ByteBuffer[] inputBuffers = mAudioEncoder.getInputBuffers(); 
int inputBufferIndex = mAudioEncoder.dequeueInputBuffer(-1); 
if (inputBufferIndex >= 0) { 
 ByteBuffer inputBuffer = inputBuffers[inputBufferIndex]; 
 inputBuffer.clear(); 
 inputBuffer.put(this_buffer); 
 ... 
 mAudioEncoder.queueInputBuffer(inputBufferIndex, 0, this_buffer.length, presentationTimeStamp, 0); 
} 

複製程式碼

下面,參考Grafika-SoftInputSurfaceActivity,並加入音訊處理。主迴圈大體分四部分:

try { 
 // Part 1 
 prepareEncoder(outputFile); 
 ... 
 // Part 2 
 for (int i = 0; i < NUM_FRAMES; i++) { 
 generateFrame(i); 
 drainVideoEncoder(false); 
 drainAudioEncoder(false); 
 } 
 // Part 3 
 ... 
 drainVideoEncoder(true); 
 drainAudioEncoder(true); 
} catch (IOException ioe) { 
 throw new RuntimeException(ioe); 
} finally { 
 // Part 4 
 releaseEncoder(); 
} 

複製程式碼

第1部分是準備工作,除了video的MediaCodec,這裡還初始化了audio的MediaCodec:

MediaFormat audioFormat = new MediaFormat(); 
audioFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, 44100); 
audioFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1); 
... 
mAudioEncoder = MediaCodec.createEncoderByType(AUDIO_MIME_TYPE); 
mAudioEncoder.configure(audioFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); 
mAudioEncoder.start(); 

複製程式碼

第2部分進入主迴圈,app在Surface上直接繪圖,由於這個Surface是從MediaCodec中用createInputSurface()申請來的,所以畫完後不用顯式用queueInputBuffer()交給Encoder。drainVideoEncoder()和drainAudioEncoder()分別將編碼好的音視訊從buffer中拿出來(通過dequeueOutputBuffer()),然後交由MediaMuxer進行混合(通過writeSampleData())。注意音視訊通過PTS(Presentation time stamp,決定了某一幀的音視訊資料何時顯示或播放)來同步,音訊的time stamp需在AudioRecord從MIC採集到資料時獲取並放到相應的bufferInfo中,視訊由於是在Surface上畫,因此直接用dequeueOutputBuffer()出來的bufferInfo中的就行,最後將編碼好的資料送去MediaMuxer進行多路混合。

注意這裡Muxer要等把audio track和video track都加入了再開始。MediaCodec在一開始呼叫dequeueOutputBuffer()時會返回一次INFO_OUTPUT_FORMAT_CHANGED訊息。我們只需在這裡獲取該MediaCodec的format,並註冊到MediaMuxer裡。接著判斷當前audio track和video track是否都已就緒,如果是的話就啟動Muxer。

總結來說,drainVideoEncoder()的主邏輯大致如下,drainAudioEncoder也是類似的,只是把video的MediaCodec換成audio的MediaCodec即可。

while(true) { 
 int encoderStatus = mVideoEncoder.dequeueOutputBuffer(mBufferInfo, TIMEOUT_USEC); 
 if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) { 
 ... 
 } else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { 
 encoderOutputBuffers = mVideoEncoder.getOutputBuffers(); 
 } else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { 
 MediaFormat newFormat = mAudioEncoder.getOutputFormat(); 
 mAudioTrackIndex = mMuxer.addTrack(newFormat); 
 mNumTracksAdded++; 
 if (mNumTracksAdded == TOTAL_NUM_TRACKS) { 
 mMuxer.start(); 
 } 
 } else if (encoderStatus < 0) { 
 ... 
 } else { 
 ByteBuffer encodedData = encoderOutputBuffers[encoderStatus]; 
 ... 
 if (mBufferInfo.size != 0) { 
 mMuxer.writeSampleData(mVideoTrackIndex, encodedData, mBufferInfo); 
 } 
 mVideoEncoder.releaseOutputBuffer(encoderStatus, false); 
 if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { 
 break; 
 } 
 } 

} 

複製程式碼

第3部分是結束錄製,傳送EOS資訊,這樣在drainVideoEncoder()和drainAudioEncoder中就可以根據EOS退出內迴圈。第4部分為清理工作。把audio和video的MediaCodec,MediaCodec用的Surface及MediaMuxer物件釋放。

最後幾點注意:

1. 在AndroidManifest.xml里加上錄音許可權,否則建立AudioRecord物件時鐵定失敗:

<uses-permission android:name="android.permission.RECORD_AUDIO"/>

2. 音視訊通過PTS同步,兩個的單位要一致。

3. MediaMuxer的使用要按照Constructor -> addTrack -> start -> writeSampleData -> stop 的順序。如果既有音訊又有視訊,在stop前兩個都要writeSampleData()過。

總結

以上就是抖音類APP的部分內容,其中的步驟和過程是我親自實踐過的,按照上述的過程應該都可以正常執行,寫這一篇文章花了很多時間,希望所有看了這篇文章的朋友們都能夠有一定的收穫。此外更多的Android短視訊詳細內容可見下方附帶資料:

【附】相關視訊及資料

連結:pan.baidu.com/s/17TwdsQiz…
提取碼:krzw



相關文章