[OpenGL]未來視覺3-攝像頭幀緩沖

大家好斜友,我系蒼王补鼻。
以下是我這個系列的相關(guān)文章胀葱,有興趣可以參考一下,可以給個喜歡或者關(guān)注我的文章唇牧。

[Android]如何做一個崩潰率少于千分之三噶應(yīng)用app--章節(jié)列表

Android組件化架構(gòu)熱賣中

相信很多人都用過相機功能罕扎,也開發(fā)過簡單調(diào)度相機功能基茵。相機采集功能是圖像信號輸入的重要來源。
我們首先了解一下Android相機采集數(shù)據(jù)的格式一般為YUV格式的數(shù)據(jù)壳影。YUV 表示三個分量拱层, Y 表示 亮度(Luminance),即灰度值宴咧,UV表示色度(Chrominance)根灯,描述圖像色彩和飽和度,指定顏色掺栅。YUV格式有YUV444烙肺、 YUV422 和 YUV420 三種,差別在于:
YUV444: 每個Y分量對應(yīng)一組UV分量
YUV422:每兩個Y分量共用一組UV分量
YUV420:每四個Y分量共用一組UV分量
一張圖讓你理解理解這三種比例的區(qū)別氧卧。


YUV解析圖

而YUV打包時其排列也會有不同桃笙,可以查看此Android中的YUV格式解析

如果你英文好的沙绝,可以看著篇搏明,這篇更加詳細介紹排列和RGB等轉(zhuǎn)換計算Video Rendering with 8-Bit YUV Formats

為何使用YUV呢?

因為普通的RGB排布大小是width* height* 3,RGBA排布大小是width* height* 4,而YUV420的大小是width* height* 1.5闪檬,在移動端內(nèi)存利用非常重要的情況下星著,在采集效果不變的情況下,YUV420的數(shù)據(jù)量會比RGB排列少將近一倍的大小粗悯,這有利于數(shù)據(jù)的采集速度和傳輸速度虚循。

但是這樣就會有另外一個問題出現(xiàn),就是我們的屏幕是RGBA排列著色的样傍,但是采集的數(shù)據(jù)卻是YUV數(shù)據(jù)横缔,那他是如何轉(zhuǎn)換到屏幕上顯示呢?這里需要利用Opengl調(diào)用GPU計算資源來渲染繪制衫哥,而比較高效的調(diào)用GPU的渲染的有SurfaceView和GLSurfaceView茎刚。但有一個差異GLSurfaceView是已經(jīng)內(nèi)置了單獨線程用于更新,而SurfaceView可以開發(fā)者去自定義線程做任務(wù)管理炕檩。

然而我在一些demo中看到了別人是采集了數(shù)據(jù)之后斗蒋,通過幀緩沖(FBO)獲取到幀圖,然后再做濾鏡處理笛质,為何要這樣做呢?

是因為如果你使用攝像圖采集幀圖是yuv數(shù)據(jù)捞蚂,Android中shader(glsl)需要使用GL_OES的擴展庫來對數(shù)據(jù)做特殊處理妇押,glsl文件是類C文件,不存在覆寫和擴展姓迅。如果你想將圖片和攝像頭采集的數(shù)據(jù)做同一種轉(zhuǎn)換敲霍,那就需要兩個不同的文件俊马,意思就是你需要維護兩份shader的代碼,這是一件很蛋痛的事肩杈。那要怎么做呢柴我,最好的方式是先用GL_OES采集數(shù)據(jù),然后通過幀緩沖來緩沖圖轉(zhuǎn)變?yōu)镽GBA數(shù)據(jù)扩然,這樣再做濾鏡操作艘儒,這樣濾鏡的shader就只需要一份就了事了。

這節(jié)主要介紹攝像頭幀緩沖夫偶,先來一個總圖吧界睁。


攝像頭幀緩沖.png

直接在native代碼中分析吧,下面是創(chuàng)建攝像頭采集和繪制的代碼兵拢。

//surfaceView初始化的時候創(chuàng)建
JNIEXPORT jint JNICALL
Java_com_cangwang_magic_util_OpenGLJniLib_magicFilterCreate(JNIEnv *env, jobject obj,
                                                        jobject surface,jobject assetManager) {
    std::unique_lock<std::mutex> lock(gMutex);
    if(glCameraFilter){ //停止攝像頭采集并銷毀
        glCameraFilter->stop();
        delete glCameraFilter;
    }

    //初始化native window
    ANativeWindow *window = ANativeWindow_fromSurface(env,surface);
    //初始化app內(nèi)獲取數(shù)據(jù)管理
    aAssetManager= AAssetManager_fromJava(env,assetManager);
    //初始化相機采集
    glCameraFilter = new CameraFilter(window,aAssetManager);
    //創(chuàng)建
    return glCameraFilter->create();
}

//窗口大小設(shè)置翻斟,SurfaceView初始化后會觸發(fā)一次
JNIEXPORT void JNICALL
Java_com_cangwang_magic_util_OpenGLJniLib_magicFilterChange(JNIEnv *env, jobject obj,jint width,jint height) {
    std::unique_lock<std::mutex> lock(gMutex);
    //視口變換,可視區(qū)域
    if (!glCameraFilter){
        ALOGE("change error, glCameraFilter is null");
        return;
    }
    //更改窗口大小
    glCameraFilter->change(width,height);
}

JNIEXPORT void JNICALL
Java_com_cangwang_magic_util_OpenGLJniLib_magicFilterDraw(JNIEnv *env, jobject obj,jfloatArray matrix_,jstring address) {
    //獲取攝像頭矩陣
    jfloat *matrix = env->GetFloatArrayElements(matrix_,NULL);
    //加鎖
    std::unique_lock<std::mutex> lock(gMutex);
    //如果為空说铃,就判斷錯誤访惜,中斷
    if (!glCameraFilter){ 
        ALOGE("draw error, glCameraFilter is null");
        return;
    }
    //攝像頭采集畫圖
    glCameraFilter->draw(matrix);  
    //釋放矩陣數(shù)據(jù)
    env->ReleaseFloatArrayElements(matrix_,matrix,0); 
}

初始化幀緩沖,涉及到視口的地方都需要設(shè)定長寬腻扇,這里glTexImage2D需要長寬疾牲,在SurfaceView的surfaceChanged的時候可以獲取到surfaceview的長寬,來作為輸入大小衙解。

void CameraFilter::change(int width, int height) {
    //設(shè)置視口
    glViewport(0,0,width,height);
    mWidth = width;
    mHeight = height;
    if (cameraInputFilter!= nullptr){
        if (cameraInputFilter!= nullptr){
            //觸發(fā)輸入大小更新
            cameraInputFilter->onInputSizeChanged(width, height);
            //初始化幀緩沖
            cameraInputFilter->initCameraFrameBuffer(width,height);
        }
        if (filter != nullptr){
            //初始化濾鏡的大小
            filter->onInputSizeChanged(width,height);
        } else{
            cameraInputFilter->destroyCameraFrameBuffers();
        }
    }
}

void CameraInputFilter::initCameraFrameBuffer(int width, int height) {
    //比對大小
    if ( mFrameWidth != width || mFrameHeight !=height){
        destroyCameraFrameBuffers();
    }
    mFrameWidth = width;
    mFrameHeight = height;
    mFrameBuffer=0;
    mFrameBufferTextures=0;
    //生成幀緩沖id
    glGenFramebuffers(1,&mFrameBuffer);
    //生成紋理id
    glGenTextures(1,&mFrameBufferTextures);
    //綁定紋理
    glBindTexture(GL_TEXTURE_2D,mFrameBufferTextures);
    //紋理賦值為空阳柔,先紋理占位
    glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,width,height,0,GL_RGBA,GL_UNSIGNED_BYTE, nullptr);
    //設(shè)定紋理參數(shù)
    glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
    glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP_TO_EDGE);
    //綁定幀圖
    glBindFramebuffer(GL_FRAMEBUFFER,mFrameBuffer);
    //綁定紋理到幀圖
    glFramebufferTexture2D(GL_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,GL_TEXTURE_2D,mFrameBufferTextures,0);
    //切換回默認紋理
    glBindTexture(GL_TEXTURE_2D,0);
    //切換回默認的幀緩沖
    glBindFramebuffer(GL_FRAMEBUFFER,0);
}

畫幀圖,并回調(diào)幀緩沖紋理id蚓峦。這里幀圖使用的是GL_TEXTURE_2D舌剂,但是采集圖像的時候需要使用GL_TEXTURE_EXTERNAL_OES。

void CameraFilter::draw(GLfloat *matrix) {
    //清屏
    glClearColor(0,0,0,0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    if (cameraInputFilter != nullptr){
//        cameraInputFilter->onDrawFrame(mTextureId,matrix,VERTICES,TEX_COORDS);
        //獲取幀緩沖id
        GLuint id = cameraInputFilter->onDrawToTexture(mTextureId,matrix);
        if (filter != nullptr) 
            //通過濾鏡filter繪制
            filter->onDrawFrame(id,matrix);
        //緩沖區(qū)交換
        glFlush();
        mEGLCore->swapBuffer();
    }
}

GLuint CameraInputFilter::onDrawToTexture(const GLuint textureId, GLfloat *matrix) {
    //視口切換
    glViewport(0,0,mFrameWidth,mFrameHeight);
    //綁定幀緩沖id
    glBindFramebuffer(GL_FRAMEBUFFER,mFrameBuffer);
    glUseProgram(mGLProgId);
    if (!mIsInitialized){
        return (GLuint) NOT_INIT;
    }
    //頂點緩沖
    glVertexAttribPointer(mGLAttribPosition,2,GL_FLOAT,GL_FALSE,0,mGLCubeBuffer);
    glEnableVertexAttribArray(mGLAttribPosition);
    glVertexAttribPointer(mGLAttribTextureCoordinate,2,GL_FLOAT,GL_FALSE,0,mGLTextureBuffer);
    glEnableVertexAttribArray(mGLAttribTextureCoordinate);
    glUniformMatrix4fv(mTexturetransformMatrixlocation,1,GL_FALSE,matrix);
    //設(shè)置美顏等級
    setBeautyLevelOnDraw(beautyLevel);
    setTexelSize(mInputWidth,mInputHeight);
    //加載矩陣
//    glUniformMatrix4fv(mMatrixLoc,1,GL_FALSE,matrix);

    if (textureId != NO_TEXTURE){
        //綁定紋理
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_EXTERNAL_OES,textureId);
        glUniform1i(mGLUniformTexture,0);
    }
    //繪制圖像(長方形)
    glDrawArrays(GL_TRIANGLE_STRIP,0,4);
    //關(guān)閉頂點緩沖
    glDisableVertexAttribArray(mGLAttribPosition);
    glDisableVertexAttribArray(mGLAttribTextureCoordinate);
    //切換回默認紋理
    glBindTexture(GL_TEXTURE_EXTERNAL_OES,0);
    //切換回默認幀緩沖
    glBindFramebuffer(GL_FRAMEBUFFER,0);
    return mFrameBufferTextures;
}

返回來的幀緩沖id暑椰,用于濾鏡操作
再看一下shader中輸出圖像采集代碼的轉(zhuǎn)換霍转,需要使用擴展庫已經(jīng)smplerExternalOES

#version 300 es
//加入opengles擴展庫
#extension GL_OES_EGL_image_external_essl3 : require

precision highp float;
//來自攝像頭預(yù)覽的外部紋理
uniform samplerExternalOES sTexture;

in vec2 vTexCoord;

out vec4 fragColor;

void main() {
    fragColor = texture(sTexture, vTexCoord);
}

這節(jié)就到這里,下一節(jié)再介紹濾鏡基礎(chǔ)介紹一汽。
本節(jié)的例子的代碼在(MagicCamera3)[https://github.com/cangwang/MagicCamera3] 的CameraFilterV2Activity當(dāng)中可以查看避消,歡迎大家Star。

群號是316556016召夹,也可以掃碼進群岩喷。我在這里期待你們的加入!<嘣鳌纱意!

Android組件化群1
Android組件化群2
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市鲸阔,隨后出現(xiàn)的幾起案子偷霉,更是在濱河造成了極大的恐慌迄委,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件类少,死亡現(xiàn)場離奇詭異叙身,居然都是意外死亡,警方通過查閱死者的電腦和手機硫狞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門信轿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人妓忍,你說我怎么就攤上這事虏两。” “怎么了世剖?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵定罢,是天一觀的道長。 經(jīng)常有香客問我旁瘫,道長祖凫,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任酬凳,我火速辦了婚禮惠况,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘宁仔。我一直安慰自己稠屠,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布翎苫。 她就那樣靜靜地躺著权埠,像睡著了一般。 火紅的嫁衣襯著肌膚如雪煎谍。 梳的紋絲不亂的頭發(fā)上攘蔽,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天,我揣著相機與錄音呐粘,去河邊找鬼满俗。 笑死,一個胖子當(dāng)著我的面吹牛作岖,可吹牛的內(nèi)容都是我干的唆垃。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼鳍咱,長吁一口氣:“原來是場噩夢啊……” “哼降盹!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起谤辜,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤蓄坏,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后丑念,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體涡戳,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年脯倚,在試婚紗的時候發(fā)現(xiàn)自己被綠了渔彰。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡推正,死狀恐怖恍涂,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情植榕,我是刑警寧澤再沧,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站尊残,受9級特大地震影響炒瘸,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜寝衫,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一顷扩、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧慰毅,春花似錦隘截、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至统台,卻和暖如春雕擂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背贱勃。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工井赌, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人贵扰。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓仇穗,卻偏偏與公主長得像,于是被迫代替她去往敵國和親戚绕。 傳聞我的和親對象是個殘疾皇子纹坐,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,722評論 2 345

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