大家好斜友,我系蒼王补鼻。
以下是我這個系列的相關(guān)文章胀葱,有興趣可以參考一下,可以給個喜歡或者關(guān)注我的文章唇牧。
[Android]如何做一個崩潰率少于千分之三噶應(yīng)用app--章節(jié)列表
相信很多人都用過相機功能罕扎,也開發(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打包時其排列也會有不同桃笙,可以查看此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é)主要介紹攝像頭幀緩沖夫偶,先來一個總圖吧界睁。
直接在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召夹,也可以掃碼進群岩喷。我在這里期待你們的加入!<嘣鳌纱意!