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

系列文章:

安卓特效相機(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上:

1.png

所以如果我們直接將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ù)覽畫面黑了械蹋。為什么,回顧下這幅圖:

1.png

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

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

我們讓OpengGL辛苦點(diǎn),畫兩次...

2.png

首先修改下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分支)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末唯咬,一起剝皮案震驚了整個(gè)濱河市纱注,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌胆胰,老刑警劉巖狞贱,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異蜀涨,居然都是意外死亡瞎嬉,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門勉盅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來佑颇,“玉大人,你說我怎么就攤上這事草娜√粜兀” “怎么了?”我有些...
    開封第一講書人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵宰闰,是天一觀的道長(zhǎng)茬贵。 經(jīng)常有香客問我簿透,道長(zhǎng),這世上最難降的妖魔是什么解藻? 我笑而不...
    開封第一講書人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任老充,我火速辦了婚禮,結(jié)果婚禮上螟左,老公的妹妹穿的比我還像新娘啡浊。我一直安慰自己,他們只是感情好胶背,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開白布巷嚣。 她就那樣靜靜地躺著,像睡著了一般钳吟。 火紅的嫁衣襯著肌膚如雪廷粒。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,679評(píng)論 1 305
  • 那天红且,我揣著相機(jī)與錄音坝茎,去河邊找鬼。 笑死暇番,一個(gè)胖子當(dāng)著我的面吹牛嗤放,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播奔誓,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼斤吐,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了厨喂?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤庄呈,失蹤者是張志新(化名)和其女友劉穎蜕煌,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體诬留,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡斜纪,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了文兑。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片盒刚。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖绿贞,靈堂內(nèi)的尸體忽然破棺而出因块,到底是詐尸還是另有隱情,我是刑警寧澤籍铁,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布涡上,位于F島的核電站趾断,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏吩愧。R本人自食惡果不足惜芋酌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望雁佳。 院中可真熱鬧脐帝,春花似錦、人聲如沸糖权。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)温兼。三九已至秸滴,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間募判,已是汗流浹背荡含。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留届垫,地道東北人释液。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像装处,于是被迫代替她去往敵國(guó)和親误债。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355