安卓特效相機(四) 視頻錄制

系列文章:

安卓特效相機(一) Camera2的使用
安卓特效相機(二) EGL基礎
安卓特效相機(三) OpenGL ES 特效渲染
安卓特效相機(四) 視頻錄制

前幾篇文章已經講完了攝像頭畫面的捕捉和特效渲染,這篇文章我們來講一講最后的視頻錄制部分。

我們這里將使用MediaRecorder去錄制視頻。MediaRecorder可以同時錄制視頻和音頻。我們將音頻源直接設置成攝像頭,讓它從攝像頭里面讀取音頻數據:

mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
mMediaRecorder.setAudioEncodingBitRate(AUDIO_BIT_RATE);

但是視頻源并不能直接設置成攝像頭,因為攝像頭捕捉到的畫面是原始的視頻畫面,我們上上一篇文章中講到了如何將這個原始畫面繪制到紋理,然后通過特效處理現實到TextureView上:

1.png

所以如果我們直接將MediaRecorder的視頻源設置成攝像頭的話錄制下來的視頻并沒有帶上特效。

那要怎么做呢? MediaRecorder有一種視頻源叫做MediaRecorder.VideoSource.SURFACE,意思是從Surface里面讀取畫面去錄制。那我們是不是直接吧TextureView的SurfaceTexture創建的Surface傳給MediaRecorder讓它捕捉TextureView的內容就行了呢?

可惜的是如果直接用MediaRecorder.setInputSurface將Surface設置進去,會拋出異常:

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)

原因是只能設置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);
}

好吧直接滴干活不行那我們就悄悄滴干活。

首先還是需要視頻源設置成MediaRecorder.VideoSource.SURFACE,然后配置一堆的視頻信息。這些設置項具體是什么意思講起來比較費勁,我就不展開了,大家感興趣的可以自行搜索:

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

上面的都是一些常規操作,大部分使用MediaRecorder的代碼都是這樣用的,下面我們來看正片:

return mGLRender.createEGLSurface(mMediaRecorder.getSurface());

這里拿到MediaRecorder的那個視頻源Surface,給它創建了一個EGLSurface。我們在之前那篇EGL基礎里面介紹過它。

我們可以用EGL14.eglMakeCurrent方法指定OpenGL往哪個Surface里面繪制,所以我們直接修改代碼將OpenGL的目標Suface設置成這個視頻源Surface就可以了嗎?

恭喜你,得到了一個BUG。

現在視頻是可以錄制了,但是預覽畫面黑了。為什么,回顧下這幅圖:

1.png

我們需要要將OpenGL的畫面繪制到TextureView上才能在屏幕上看到特效渲染后的預覽畫面。

那怎么辦?TextureView和MediaRecorder只能二選一了嗎?不,小孩子才做選擇題,成年人當然是全都要。

我們讓OpengGL辛苦點,畫兩次...

2.png

首先修改下GLRender.render方法, EGLSurface由外面傳進來,這樣我們就能在外面控制它往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);
}

然后在繪制的時候繪制兩次:

mCameraTexture.updateTexImage();
mCameraTexture.getTransformMatrix(mTransformMatrix);

mGLRender.render(mTransformMatrix, mGLRender.getDefaultEGLSurface());
if (mRecordSurface != null) {
    mGLRender.render(mTransformMatrix, mRecordSurface);
    mGLRender.setPresentationTime(mRecordSurface, mCameraTexture.getTimestamp());
}

這里需要注意的是我們需要給這一幀設置下時間戳,用于錄制視頻的時間同步:

public void setPresentationTime(EGLSurface eglSurface, long nsecs) {
    EGLExt.eglPresentationTimeANDROID(mEGLDisplay, eglSurface, nsecs);
}

好了,這個錄像的實現方法比較簡單。到此整個特效相機的教程就結束了,希望對大家有用。

這篇文章的demo依然在github(注意是feature_record分支)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,333評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,491評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,263評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,946評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,708評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,409評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,939評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,774評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,641評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,872評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,650評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373