系列文章:
安卓特效相機(jī)(一) Camera2的使用
安卓特效相機(jī)(二) EGL基礎(chǔ)
安卓特效相機(jī)(三) OpenGL ES 特效渲染
安卓特效相機(jī)(四) 視頻錄制
前幾篇文章已經(jīng)講完了攝像頭畫面的捕捉和特效渲染,這篇文章我們來講一講最后的視頻錄制部分。
我們這里將使用MediaRecorder去錄制視頻。MediaRecorder可以同時(shí)錄制視頻和音頻沮榜。我們將音頻源直接設(shè)置成攝像頭,讓它從攝像頭里面讀取音頻數(shù)據(jù):
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
mMediaRecorder.setAudioEncodingBitRate(AUDIO_BIT_RATE);
但是視頻源并不能直接設(shè)置成攝像頭,因?yàn)閿z像頭捕捉到的畫面是原始的視頻畫面,我們上上一篇文章中講到了如何將這個(gè)原始畫面繪制到紋理,然后通過特效處理現(xiàn)實(shí)到TextureView上:
所以如果我們直接將MediaRecorder的視頻源設(shè)置成攝像頭的話錄制下來的視頻并沒有帶上特效胸完。
那要怎么做呢? MediaRecorder有一種視頻源叫做MediaRecorder.VideoSource.SURFACE,意思是從Surface里面讀取畫面去錄制伤为。那我們是不是直接吧TextureView的SurfaceTexture創(chuàng)建的Surface傳給MediaRecorder讓它捕捉TextureView的內(nèi)容就行了呢?
可惜的是如果直接用MediaRecorder.setInputSurface將Surface設(shè)置進(jìn)去,會(huì)拋出異常:
09-22 14:53:47.473 897 943 E AndroidRuntime: java.lang.IllegalArgumentException: not a PersistentSurface
09-22 14:53:47.473 897 943 E AndroidRuntime: at android.media.MediaRecorder.setInputSurface(MediaRecorder.java:165)
原因是只能設(shè)置MediaCodec.PersistentSurface類型的Surface:
/**
* Configures the recorder to use a persistent surface when using SURFACE video source.
* <p> May only be called before {@link #prepare}. If called, {@link #getSurface} should
* not be used and will throw IllegalStateException. Frames rendered to the Surface
* before {@link #start} will be discarded.</p>
* @param surface a persistent input surface created by
* {@link MediaCodec#createPersistentInputSurface}
* @throws IllegalStateException if it is called after {@link #prepare} and before
* {@link #stop}.
* @throws IllegalArgumentException if the surface was not created by
* {@link MediaCodec#createPersistentInputSurface}.
* @see MediaCodec#createPersistentInputSurface
* @see MediaRecorder.VideoSource
*/
public void setInputSurface(@NonNull Surface surface) {
if (!(surface instanceof MediaCodec.PersistentSurface)) {
throw new IllegalArgumentException("not a PersistentSurface");
}
native_setInputSurface(surface);
}
好吧直接滴干活不行那我們就悄悄滴干活啊送。
首先還是需要視頻源設(shè)置成MediaRecorder.VideoSource.SURFACE,然后配置一堆的視頻信息隅茎。這些設(shè)置項(xiàng)具體是什么意思講起來比較費(fèi)勁,我就不展開了,大家感興趣的可以自行搜索:
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
mMediaRecorder.setOutputFile(mLastVideo.getPath());
mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
mMediaRecorder.setVideoEncodingBitRate(VIDEO_BIT_RATE);
mMediaRecorder.setVideoSize(mPreview.getWidth(), mPreview.getHeight());
mMediaRecorder.setVideoFrameRate(VIDEO_FRAME_RATE);
mMediaRecorder.setOrientationHint(0);
配置完之后開啟錄制:
try {
mMediaRecorder.prepare();
} catch (IOException e) {
Toast.makeText(this, "failed to prepare MediaRecorder", Toast.LENGTH_LONG)
.show();
}
mMediaRecorder.start();
上面的都是一些常規(guī)操作,大部分使用MediaRecorder的代碼都是這樣用的,下面我們來看正片:
return mGLRender.createEGLSurface(mMediaRecorder.getSurface());
這里拿到MediaRecorder的那個(gè)視頻源Surface,給它創(chuàng)建了一個(gè)EGLSurface鞠眉。我們?cè)谥澳瞧?a target="_blank">EGL基礎(chǔ)里面介紹過它薯鼠。
我們可以用EGL14.eglMakeCurrent方法指定OpenGL往哪個(gè)Surface里面繪制,所以我們直接修改代碼將OpenGL的目標(biāo)Suface設(shè)置成這個(gè)視頻源Surface就可以了嗎?
恭喜你,得到了一個(gè)BUG。
現(xiàn)在視頻是可以錄制了,但是預(yù)覽畫面黑了械蹋。為什么,回顧下這幅圖:
我們需要要將OpenGL的畫面繪制到TextureView上才能在屏幕上看到特效渲染后的預(yù)覽畫面出皇。
那怎么辦?TextureView和MediaRecorder只能二選一了嗎?不,小孩子才做選擇題,成年人當(dāng)然是全都要哗戈。
我們讓OpengGL辛苦點(diǎn),畫兩次...
首先修改下GLRender.render方法, EGLSurface由外面?zhèn)鬟M(jìn)來,這樣我們就能在外面控制它往TextureView和MediaRecord繪制了:
public void render(float[] matrix, EGLSurface eglSurface) {
makeCurrent(eglSurface);
GLES20.glUniformMatrix4fv(mTransformMatrixId, 1, false, matrix, 0);
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES11Ext.GL_SAMPLER_EXTERNAL_OES, mGLTextureId);
GLES20.glUniform1i(mTexPreviewId, 0);
GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
GLES20.glDrawElements(GLES20.GL_TRIANGLES, ORDERS.length, GLES20.GL_UNSIGNED_SHORT, mOrder);
EGL14.eglSwapBuffers(mEGLDisplay, eglSurface);
}
然后在繪制的時(shí)候繪制兩次:
mCameraTexture.updateTexImage();
mCameraTexture.getTransformMatrix(mTransformMatrix);
mGLRender.render(mTransformMatrix, mGLRender.getDefaultEGLSurface());
if (mRecordSurface != null) {
mGLRender.render(mTransformMatrix, mRecordSurface);
mGLRender.setPresentationTime(mRecordSurface, mCameraTexture.getTimestamp());
}
這里需要注意的是我們需要給這一幀設(shè)置下時(shí)間戳,用于錄制視頻的時(shí)間同步:
public void setPresentationTime(EGLSurface eglSurface, long nsecs) {
EGLExt.eglPresentationTimeANDROID(mEGLDisplay, eglSurface, nsecs);
}
好了,這個(gè)錄像的實(shí)現(xiàn)方法比較簡(jiǎn)單郊艘。到此整個(gè)特效相機(jī)的教程就結(jié)束了,希望對(duì)大家有用。
這篇文章的demo依然在github(注意是feature_record分支)