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

衆所周知,通過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);

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章