Android OpenGLES2.0(十一)——利用OpenGLES做Camera預(yù)覽

OpenGLES在Android上除了可以用來(lái)做游戲朽褪、處理圖片也可以用來(lái)處理視頻圖像诬留、做相機(jī)預(yù)覽美顏等等。本篇博客將介紹利用OpenGLES做相機(jī)預(yù)覽的基本實(shí)現(xiàn)汇荐。

預(yù)覽方案

前面我們有介紹過(guò)利用OpenGLES顯示圖片處理圖片日熬。視頻每一幀其實(shí)也是一張圖片棍厌,Camera預(yù)覽時(shí),每一幀自然也是一幅圖片竖席,我們可以把每張圖片按照時(shí)間順序顯示出來(lái)耘纱,就完成了Camera預(yù)覽的實(shí)現(xiàn)。
那么問(wèn)題來(lái)了毕荐,在前面我們都是直接傳入一個(gè)Bitmap束析,難道我們要把Camera每幀的數(shù)據(jù)轉(zhuǎn)為Bitmap再作為紋理傳入OpenGLES程序繪制出來(lái)么?
顯示一張圖片东跪,我們使用的是諸如GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, mBitmap, 0);這樣的代碼,前面竟然不是GLES20開(kāi)頭的鹰溜,這顯然是“不科學(xué)”的虽填。OpenGLES提供的綁定紋理貼圖的方法為GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D,0,GLES20.GL_RGBA,width,height,0,GLES20.GL_RGBA,GLES20.GL_UNSIGNED_BYTE,buffer);用這個(gè)方法可以直接傳入Buffer數(shù)據(jù)。
那么問(wèn)題又來(lái)了曹动,雖然OpenGLES給我們提供的入口是傳入Buffer斋日,然而,它卻限制了Buffer的格式為單一通道墓陈,或者是RGBA恶守、RGB等格式,而Camera的幀數(shù)據(jù)卻只能為NV21或者YV21的贡必,不在OpenGLES貼圖數(shù)據(jù)格式的支持范圍兔港。雖然我們可以將NV21、YV12的數(shù)據(jù)轉(zhuǎn)換成RGBA的仔拟,但是對(duì)于預(yù)覽來(lái)說(shuō)衫樊,這個(gè)時(shí)間消耗實(shí)在是太大了。
我們可以直接將NV21的數(shù)據(jù)按照YUV三個(gè)分量直接拆分作為三張紋理傳入利花,然后利用GPU來(lái)將它們轉(zhuǎn)換為RGB顯示出來(lái)科侈,這樣時(shí)間消耗會(huì)小的多。然而這種方式也不是最簡(jiǎn)單的炒事,我們還有更簡(jiǎn)單的方式去實(shí)現(xiàn)臀栈。
Android的Camera及Camera2都允許使用SurfaceTexture作為預(yù)覽載體,但是它們所使用的SurfaceTexture傳入的OpenGL texture object name必須為GLES11Ext.GL_TEXTURE_EXTERNAL_OES挠乳。這種方式权薯,實(shí)際上就是兩個(gè)OpenGL Thread共享一個(gè)Texture姑躲,不再需要數(shù)據(jù)導(dǎo)入導(dǎo)出,從Camera采集的數(shù)據(jù)直接在GPU中完成轉(zhuǎn)換和渲染崭闲。

Camera預(yù)覽實(shí)現(xiàn)

在之前的博客Android Camera的使用(一)中以SurfaceHolder做預(yù)覽來(lái)介紹了Camera的使用肋联。利用OpenGLES和之前使用步驟大同小異,使用時(shí)刁俭,我們不在使用camera.setPreviewDisplay(SurfaceHolder)來(lái)設(shè)置預(yù)覽載體橄仍,轉(zhuǎn)而使用camera.setPreviewTexture(SurfaceTexture)來(lái)使用SurfaceTexture來(lái)設(shè)置。我們也不在需要使用camera.setDisplayOrientation(int)來(lái)設(shè)置正確的預(yù)覽方向牍戚,直接通過(guò)計(jì)算OpenGLES 程序的頂點(diǎn)坐標(biāo)或者紋理坐標(biāo)侮繁,或者直接計(jì)算出合適的變換矩陣,能夠?qū)㈩A(yù)覽的大小和方向一步到位的設(shè)置好如孝。

紋理的創(chuàng)建

相機(jī)預(yù)覽使用EXTERNAL_OES紋理宪哩,創(chuàng)建方式與2D紋理創(chuàng)建基本相同:

private int createTextureID(){
    int[] texture = new int[1];
    GLES20.glGenTextures(1, texture, 0);
    GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texture[0]);
    GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
        GL10.GL_TEXTURE_MIN_FILTER,GL10.GL_LINEAR);
    GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
        GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
    GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
        GL10.GL_TEXTURE_WRAP_S, GL10.GL_CLAMP_TO_EDGE);
    GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
        GL10.GL_TEXTURE_WRAP_T, GL10.GL_CLAMP_TO_EDGE);
    return texture[0];
}

由于我們創(chuàng)建的是擴(kuò)展紋理,所以綁定的時(shí)候我們也需要綁定到擴(kuò)展紋理上才可以正常使用第晰,
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,texture[0])锁孟。

修改著色器

預(yù)覽相機(jī)的著色器,頂點(diǎn)著色器不變茁瘦,需要修改片元著色器品抽,不再用sampler2D采樣,需要使勇samplerExternalOES 紋理采樣器甜熔,并且要在頭部增加使用擴(kuò)展紋理的聲明#extension GL_OES_EGL_image_external : require圆恤。

#extension GL_OES_EGL_image_external : require
precision mediump float;
varying vec2 textureCoordinate;
uniform samplerExternalOES vTexture;
void main() {
    gl_FragColor = texture2D( vTexture, textureCoordinate );
}

計(jì)算變換矩陣

依照之前繪制圖片的經(jīng)驗(yàn)+利用SurfaceHolder做相機(jī)預(yù)覽的經(jīng)驗(yàn),應(yīng)該很容易就可以看到預(yù)覽效果了腔稀。這時(shí)候預(yù)覽的方向和大小比例一般不是我們所期望的樣子盆昙。頂點(diǎn)坐標(biāo)我們以(-1.0,-1.0)-(1.0焊虏,1.0)的范圍淡喜,紋理坐標(biāo)以(0.0,0.0)-(1.0诵闭,1.0)的范圍拆火,且上下左右各個(gè)角一一對(duì)應(yīng)。這時(shí)候涂圆,我們需要得到一個(gè)合適的變換矩陣们镜,傳入頂點(diǎn)著色器,以得到我們期望的預(yù)覽效果润歉。
根據(jù)大多數(shù)的Android手機(jī)模狭,前攝像頭預(yù)覽數(shù)據(jù)旋轉(zhuǎn)了90度,并且左右鏡像了踩衩,后攝像頭旋轉(zhuǎn)了270度嚼鹉。我們需要將其旋轉(zhuǎn)回來(lái)贩汉。另外,相機(jī)數(shù)據(jù)的高寬比和預(yù)覽區(qū)域的高寬比也許并不相等锚赤,所以我們還需要進(jìn)行適當(dāng)?shù)牟眉簦?/p>

//通過(guò)傳入圖片寬高和預(yù)覽寬高匹舞,計(jì)算變換矩陣,得到的變換矩陣是預(yù)覽類似ImageView的centerCrop效果
public static float[] getShowMatrix(int imgWidth,int imgHeight,int viewWidth,int viewHeight){
    float[] projection=new float[16];
    float[] camera=new float[16];
    float[] matrix=new float[16];

    float sWhView=(float)viewWidth/viewHeight;
    float sWhImg=(float)imgWidth/imgHeight;
    if(sWhImg>sWhView){
        Matrix.orthoM(projection,0,-sWhView/sWhImg,sWhView/sWhImg,-1,1,1,3);
    }else{
        Matrix.orthoM(projection,0,-1,1,-sWhImg/sWhView,sWhImg/sWhView,1,3);
    }
    Matrix.setLookAtM(camera,0,0,0,1,0,0,0,0,1,0);
    Matrix.multiplyMM(matrix,0,projection,0,camera,0);
    return matrix;
}

//旋轉(zhuǎn)
public static float[] rotate(float[] m,float angle){
    Matrix.rotateM(m,0,angle,0,0,1);
    return m;
}

//鏡像
public static float[] flip(float[] m,boolean x,boolean y){
    if(x||y){
        Matrix.scaleM(m,0,x?-1:1,y?-1:1,1);
    }
    return m;
}
利用上面三個(gè)方法线脚,針對(duì)前后攝像頭赐稽,我們可以計(jì)算我們需要的變換矩陣:

float[] matrix=new float[16];
Gl2Utils.getShowMatrix(matrix,this.dataWidth,this.dataHeight,this.width,this.height);
if(cameraId==1){
    Gl2Utils.flip(matrix,true,false);
    Gl2Utils.rotate(matrix,90);
}else{
    Gl2Utils.rotate(matrix,270);
}
mOesFilter.setMatrix(matrix);

渲染

然后我們可以像渲染圖片將攝像頭每一幀都渲染出來(lái)了。和圖片不同的是浑侥,圖片數(shù)據(jù)是相同的姊舵,而攝像頭數(shù)據(jù)是變換的,所以每當(dāng)攝像頭有新的數(shù)據(jù)來(lái)時(shí)寓落,我們需要通過(guò)surfaceTexture.updateTexImage()更新預(yù)覽上的圖像括丁。怎樣知道攝像頭有新的數(shù)據(jù)到來(lái)呢?答案是SurfaceTexture的OnFrameAvailableListener監(jiān)聽(tīng)伶选。通常updateTexImage不應(yīng)該在OnFrameAvailableLister的回調(diào)方法中直接調(diào)用史飞,而應(yīng)該在onDrawFrame中執(zhí)行。而調(diào)用requestRender仰税,可以觸發(fā)onDrawFrame构资。即通常我們的寫法應(yīng)該為:

surfaceTexture.setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener() {
    @Override
    public void onFrameAvailable(SurfaceTexture surfaceTexture) {
        ...
        requestRender();
        ...
    }
});

其他

雖然預(yù)覽效果OK了,但是我們花費(fèi)如此大的精力肖卧,僅僅只是顯示出攝像頭的數(shù)據(jù)那多不值得蚯窥。在之前處理圖片的方式掸鹅,我們同樣可以用在處理攝像頭數(shù)據(jù)上塞帐,黑白、冷暖色調(diào)巍沙、放大鏡效果葵姥,等等。我們所用的美顏相機(jī)句携,美白榔幸、大眼、瘦臉等等效果基本都是基于GPU實(shí)現(xiàn)的矮嫉,大眼和瘦臉等和人臉特征相關(guān)的還需要用到人臉特征點(diǎn)檢測(cè)削咆。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市蠢笋,隨后出現(xiàn)的幾起案子拨齐,更是在濱河造成了極大的恐慌,老刑警劉巖昨寞,帶你破解...
    沈念sama閱讀 212,454評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件瞻惋,死亡現(xiàn)場(chǎng)離奇詭異厦滤,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)歼狼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門掏导,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人羽峰,你說(shuō)我怎么就攤上這事趟咆。” “怎么了限寞?”我有些...
    開(kāi)封第一講書人閱讀 157,921評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵忍啸,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我履植,道長(zhǎng)计雌,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 56,648評(píng)論 1 284
  • 正文 為了忘掉前任玫霎,我火速辦了婚禮凿滤,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘庶近。我一直安慰自己翁脆,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布鼻种。 她就那樣靜靜地躺著反番,像睡著了一般。 火紅的嫁衣襯著肌膚如雪叉钥。 梳的紋絲不亂的頭發(fā)上罢缸,一...
    開(kāi)封第一講書人閱讀 49,950評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音投队,去河邊找鬼枫疆。 笑死,一個(gè)胖子當(dāng)著我的面吹牛敷鸦,可吹牛的內(nèi)容都是我干的息楔。 我是一名探鬼主播,決...
    沈念sama閱讀 39,090評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼扒披,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼值依!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起碟案,我...
    開(kāi)封第一講書人閱讀 37,817評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤愿险,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后蟆淀,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體拯啦,經(jīng)...
    沈念sama閱讀 44,275評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡澡匪,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了褒链。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片唁情。...
    茶點(diǎn)故事閱讀 38,724評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖甫匹,靈堂內(nèi)的尸體忽然破棺而出甸鸟,到底是詐尸還是另有隱情蛤迎,我是刑警寧澤励稳,帶...
    沈念sama閱讀 34,409評(píng)論 4 333
  • 正文 年R本政府宣布,位于F島的核電站昔期,受9級(jí)特大地震影響恍箭,放射性物質(zhì)發(fā)生泄漏刻恭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評(píng)論 3 316
  • 文/蒙蒙 一扯夭、第九天 我趴在偏房一處隱蔽的房頂上張望鳍贾。 院中可真熱鬧,春花似錦交洗、人聲如沸骑科。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,815評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)咆爽。三九已至,卻和暖如春置森,著一層夾襖步出監(jiān)牢的瞬間斗埂,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,043評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工暇藏, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蜜笤,地道東北人濒蒋。 一個(gè)月前我還...
    沈念sama閱讀 46,503評(píng)論 2 361
  • 正文 我出身青樓盐碱,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親沪伙。 傳聞我的和親對(duì)象是個(gè)殘疾皇子瓮顽,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容