Android平臺Camera實時濾鏡實現方法探討(一)--JNI操作Bitmap

程式設計師扛把子發表於2015-11-16

眾所周知,通過setPreviewHolder可以將預覽資料顯示在一個SurfaceView上,即可實現相機拍照時的預覽功能,通過新增各個控制元件和介面即可實現簡單相機應用,但如果需要對預覽畫面進行處理,例如類似美圖秀秀等相機APP的實時濾鏡功能,此種方案無法達到目的,需要另外需找辦法,本系列旨在探討Android平臺相機開發,結合影象處理,UI設計,實現類似於美圖秀秀,Instagram,aillias等優秀相機APP的效果。


目前Android平臺優質的預覽資料實時處理開原始碼不多,例如android-gpuimage,採用將YUV資料在NDK層轉化為RGB資料,由OpenGL渲染到螢幕中,濾鏡演算法由Shader實現


其他方面,經過研究,目前主要有以下思路實現


1.不轉換,直接由OpenGL繪製,採用Shader實現影象處理(因處理演算法和渲染圖片大部分採用RGB格式,此方案暫不考慮,僅提出可能性);

2.通過C/C++實現YUV->RGB和影象處理,合成Bitmap,由CPU繪製在Canvas上;

3.通過C/C++實現YUV->RGB和影象處理,在NDK層直接繪製在SurfaceView上;

4.通過C/C++實現YUV->RGB,採用Shader實現影象處理,採用OpenGL繪製(android-gpuimage);

5.通過Shader實現YUV->RGB和影象處理,採用OpenGL繪製。(最終採用方案)


由於方案1暫不考慮,首先從方案2探討,Android平臺的Camera控制很多部落格說過了,直接跳過,在onPreviewFrame(byte[] data, Camera camera)中,我們可以獲得相機預覽,格式為YUV格式,通過C++方案轉換成RGB,通過BitmapFactory合成Bitmap,通過getHolder().lockCanvas()獲得canvas,再通過canvas.drawBitmap將bitmap繪製在螢幕當中。


通過BitmapFactory建立bitmap是一個很耗時的過程,如果每一幀都建立一個bitmap,將出現嚴重卡頓,所以我們只需要建立一個Bitmap,將該Bitmap傳遞給C++層,通過JNI操作Bitmap的畫素資料,即通過AndroidBitmap_lockPixels獲得指標,將YUV資料轉換後填充到該指標中(具體轉換演算法見android-gpuimage來修改該Bitmap,避免了Bitmap的建立,經過華為Mate7試驗,ARGB_8888格式1280X720大小的Bitmap每次繪製耗時6ms左右,每幀間隔50ms~60ms左右,若將影象處理演算法控制在40ms~50ms內(例如YUV轉換RGB演算法),該方案基本可行。


另外,可以通過方案2的思路,放棄建立Bitmap,將SurfaceView格式設定為RGB,通過JNI操作Surface,直接將資料顯示在SurfaceView中,該方案僅理論思考,由於上述6ms基本達到理論要求,因此方案2並未實踐驗證,若有錯誤或者驗證的同學,歡迎交流。


關鍵程式碼示例:

            jbyte* yuv = (jbyte*) (*env)->GetPrimitiveArrayCritical(env, yuv420sp, 0);//獲取Java層傳遞的YUV
	    
	    int* rgbData = NULL;//Bitmap畫素資料
	    if(AndroidBitmap_lockPixels(env,bitmap,(void**)&rgbData))
	    	return -1;
	    for(j = 0; j < h; j++) {//YUV轉RGB演算法,在此新增自己的影象處理
	             pixPtr = j * w;
	             jDiv2 = j >> 1;
	             for(i = 0; i < w; i++) {
	                     Y = yuv[pixPtr];
	                     if(Y < 0) Y += 255;
	                     if((i & 0x1) != 1) {
	                             cOff = sz + jDiv2 * w + (i >> 1) * 2;
	                             Cb = yuv[cOff];
	                             if(Cb < 0) Cb += 127; else Cb -= 128;
	                             Cr = yuv[cOff + 1];
	                             if(Cr < 0) Cr += 127; else Cr -= 128;
	                     }
	                     Y = Y + (Y >> 3) + (Y >> 5) + (Y >> 7);
	                     R = Y + (Cr << 1) + (Cr >> 6);
	                     if(R < 0) R = 0; else if(R > 255) R = 255;
	                     G = Y - Cb + (Cb >> 3) + (Cb >> 4) - (Cr >> 1) + (Cr >> 3);
	                     if(G < 0) G = 0; else if(G > 255) G = 255;
	                     B = Y + Cb + (Cb >> 1) + (Cb >> 4) + (Cb >> 5);
	                     if(B < 0) B = 0; else if(B > 255) B = 255;
	                     rgbData[pixPtr++] = 0xff000000 + (R << 16) + (G << 8) + B;//填充Bitmap
	             }
	    }
	    AndroidBitmap_unlockPixels(env,bitmap);//釋放鎖
	    (*env)->ReleasePrimitiveArrayCritical(env, yuv420sp, yuv, 0);

相關文章