[OpenGL]未來視覺1-Android攝像頭採集基礎
相信很多人都用過相機功能,也開發過簡單排程相機功能,但是相機採集功能。是影象訊號輸入的重要來源。
SurfaceView/">SurfaceView和View的不同之處:

SurfaceView和View對比
相機影象取樣,需要維持一個比較穩定的幀數來維持影象實時性,需要頻繁重新整理,建立一個子執行緒來進行畫面更新,會被佔用主執行緒效率好很多,而且雙緩衝機制可以在畫面從前臺重新整理到後臺時才佔用主執行緒操作,所以選用SurfaceView作繪製是最好的。
而GLSurfaceView是SurfaceView的一個子類,專用於openGL繪製,其執行效率遠高於SurfaceView是因為使用了GPU參與繪製。
這一節介紹Android攝像頭取樣,還是採用了SurfaceView來做取樣
1.需要申請相機許可權。
<uses-permission android:name="android.permission.CAMERA" />
2.開啟攝像頭,先檢查攝像和前置攝像頭,然後通過攝像頭Id,來返回攝像頭物件。
fun openCamera(cameraId:Int):Camera?{ if (!haveFeature(PackageManager.FEATURE_CAMERA)){ Log.e(TAG,"no camera!") return null } if (cameraId == Camera.CameraInfo.CAMERA_FACING_FRONT && !haveFeature(PackageManager.FEATURE_CAMERA_FRONT)){ Log.e(TAG,"no front camera!") return null } val camera = Camera.open(cameraId) if (camera == null){ Log.e(TAG, "openCamera failed") return null } return camera }
3.設定畫面比例
/** * 獲取最大的圖片大小 */ fun getLargePictureSize(camera: Camera?): Camera.Size? { if (camera != null) { //獲取可選比例 val sizes = camera.parameters.supportedPictureSizes var temp: Camera.Size = sizes[0] for (i in 1 until sizes.size) { val scale = sizes[i].height.toFloat() / sizes[i].width if (temp.width < sizes[i].width && scale < 0.6f && scale > 0.5f) temp = sizes[i] } return temp } return null } /** * 獲取最大的預覽大小 */ fun getLargePreviewSize(camera: Camera?): Camera.Size? { if (camera != null) { //獲取可選比例 val sizes = camera.parameters.supportedPreviewSizes var temp: Camera.Size = sizes[0] for (i in 1 until sizes.size) { if (temp.width < sizes[i].width) temp = sizes[i] } return temp } return null } /** * 相機取樣引數大小 */ fun setOptimalSize(camera:Camera,aspectRatio:Float,maxWidth:Int,maxHeight:Int){ val parameters= camera.parameters //使用自動對焦 if (parameters.supportedFocusModes.contains( Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) { parameters.focusMode = Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE } val size = getLargePreviewSize(camera) size?.let { //設定相機預覽大小 parameters.setPreviewSize(it.width,it.height) Log.d(TAG, "input max: (" + maxWidth + ", " + maxHeight + "), output size: (" + it.width + ", " + it.height + ")") } val pictureSize = getLargePictureSize(camera) pictureSize?.let { //圖片引數 parameters.setPictureSize(it.width,it.height) Log.d(TAG, "picture max: (" + maxWidth + ", " + maxHeight + "), output size: (" + it.width + ", " + it.height + ")") } camera.parameters = parameters }
3.設定相機影象角度
fun setDisplayOritation(activity: Activity, camera: Camera, cameraId: Int) { //獲取window的角度 val rotation = activity.windowManager.defaultDisplay.rotation var degress = 0 when (rotation) { Surface.ROTATION_0 -> degress = 0 Surface.ROTATION_90 -> degress = 90 Surface.ROTATION_180 -> degress = 180 Surface.ROTATION_270 -> degress = 270 } val info = Camera.CameraInfo() Camera.getCameraInfo(cameraId, info) var result: Int //前置攝像頭 if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { result = (info.orientation + degress) % 360 result = (360 - result) % 360// compensate the mirror } else {//後置攝像頭 result = (info.orientation - degress + 360) % 360 // back-facing } Log.d(TAG, "window rotation: $degress, camera oritation: $result") camera.setDisplayOrientation(result) }
4.設定完攝像頭引數後,需要設定一個SurfaceHolder.CallBack。
有三個必須的方法
//建立時呼叫 override fun surfaceCreated(holder: SurfaceHolder?) { } //當surface發生任何結構性的變化時(格式或者大小),該方法就會被立即呼叫。 override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) { } //被移除時呼叫 override fun surfaceDestroyed(holder: SurfaceHolder?) {}
這裡建立相機的呼叫surfaceCreated,之後會立刻呼叫一次surfaceChanged
這裡在surfaceChanged上呼叫openGL的init操作
fun initOpenGL(surface: Surface, width: Int, height: Int){ //新開一個之後一個執行緒的執行緒池 mExecutor.execute { //獲取紋理id val textureId = OpenGLJniLib.magicBaseInit(surface,width,height,BaseApplication.context.assets) if (textureId < 0){ Log.e(TAG, "surfaceCreated init OpenGL ES failed!") return@execute } //需要使用surfaceTexture來做紋理裝載 mSurfaceTexture = SurfaceTexture(textureId) //新增紋理變化回撥 mSurfaceTexture?.setOnFrameAvailableListener { drawOpenGL() } try { //把攝像頭取樣關聯到紋理 mCamera?.setPreviewTexture(mSurfaceTexture) //開始攝像頭取樣 doStartPreview() }catch (e:IOException){ Log.e(TAG,e.localizedMessage) releaseOpenGL() } } }
5.開始預覽,並開始自動對焦。
fun doStartPreview(){ mCamera?.startPreview() cameraFocus(width/2.0f,height/2.0f) }
6.opengl繪製,先要強制更新紋理影象,再更新獲取紋理矩陣,然後讓opengl繪製
fun drawOpenGL(){ mExecutor.execute { mSurfaceTexture?.updateTexImage() mSurfaceTexture?.getTransformMatrix(mMatrix) OpenGLJniLib.magicBaseDraw(mMatrix) } }
7.在destroySurfaceView的是否釋放資源
fun releaseOpenGL(){ mExecutor.execute { mSurfaceTexture?.release() mSurfaceTexture=null OpenGLJniLib.magicBaseRelease() } }
介紹了Camera取樣配置和,surfaceTexture紋理獲取了和載入,下一節,將會介紹native層的opengl繪製程式碼。
近來在寫一個有趣的專案,大家熟知攝像頭繪製和opengl的人應該有看過MagicCamera這個Android opengl2.0的開源工程,但是已經很多年沒人維護了,這邊正在將其重構為opengl3.0的的版本。命名為MagicCamera3以供大家學習,現在還在重構當中,致敬作者,也希望可以有機會和作者多交流,如果有認識的可以告知我一聲,謝謝。