相信很多人都用過(guò)相機(jī)功能昌阿,也開發(fā)過(guò)簡(jiǎn)單調(diào)度相機(jī)功能殖告,但是相機(jī)采集功能烘挫。是圖像信號(hào)輸入的重要來(lái)源诀艰。
SurfaceView和View的不同之處:
相機(jī)圖像采樣,需要維持一個(gè)比較穩(wěn)定的幀數(shù)來(lái)維持圖像實(shí)時(shí)性饮六,需要頻繁刷新其垄,創(chuàng)建一個(gè)子線程來(lái)進(jìn)行畫面更新,會(huì)被占用主線程效率好很多卤橄,而且雙緩沖機(jī)制可以在畫面從前臺(tái)刷新到后臺(tái)時(shí)才占用主線程操作绿满,所以選用SurfaceView作繪制是最好的。
而GLSurfaceView是SurfaceView的一個(gè)子類窟扑,專用于openGL繪制喇颁,其運(yùn)行效率遠(yuǎn)高于SurfaceView是因?yàn)槭褂昧薌PU參與繪制漏健。
這一節(jié)介紹Android攝像頭采樣,還是采用了SurfaceView來(lái)做采樣
1.需要申請(qǐng)相機(jī)權(quán)限橘霎。
<uses-permission android:name="android.permission.CAMERA" />
2.打開攝像頭蔫浆,先檢查攝像和前置攝像頭,然后通過(guò)攝像頭Id茎毁,來(lái)返回?cái)z像頭對(duì)象克懊。
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.設(shè)置畫面比例
/**
* 獲取最大的圖片大小
*/
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
}
/**
* 獲取最大的預(yù)覽大小
*/
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
}
/**
* 相機(jī)采樣參數(shù)大小
*/
fun setOptimalSize(camera:Camera,aspectRatio:Float,maxWidth:Int,maxHeight:Int){
val parameters= camera.parameters
//使用自動(dòng)對(duì)焦
if (parameters.supportedFocusModes.contains(
Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
parameters.focusMode = Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE
}
val size = getLargePreviewSize(camera)
size?.let {
//設(shè)置相機(jī)預(yù)覽大小
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 {
//圖片參數(shù)
parameters.setPictureSize(it.width,it.height)
Log.d(TAG, "picture max: (" + maxWidth + ", " + maxHeight + "), output size: ("
+ it.width + ", " + it.height + ")")
}
camera.parameters = parameters
}
3.設(shè)置相機(jī)圖像角度
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.設(shè)置完攝像頭參數(shù)后,需要設(shè)置一個(gè)SurfaceHolder.CallBack七蜘。
有三個(gè)必須的方法
//創(chuàng)建時(shí)調(diào)用
override fun surfaceCreated(holder: SurfaceHolder?) { }
//當(dāng)surface發(fā)生任何結(jié)構(gòu)性的變化時(shí)(格式或者大刑犯取),該方法就會(huì)被立即調(diào)用橡卤。
override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) { }
//被移除時(shí)調(diào)用
override fun surfaceDestroyed(holder: SurfaceHolder?) {}
這里創(chuàng)建相機(jī)的調(diào)用surfaceCreated扮念,之后會(huì)立刻調(diào)用一次surfaceChanged
這里在surfaceChanged上調(diào)用openGL的init操作
fun initOpenGL(surface: Surface, width: Int, height: Int){
//新開一個(gè)之后一個(gè)線程的線程池
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來(lái)做紋理裝載
mSurfaceTexture = SurfaceTexture(textureId)
//添加紋理變化回調(diào)
mSurfaceTexture?.setOnFrameAvailableListener { drawOpenGL() }
try {
//把攝像頭采樣關(guān)聯(lián)到紋理
mCamera?.setPreviewTexture(mSurfaceTexture)
//開始攝像頭采樣
doStartPreview()
}catch (e:IOException){
Log.e(TAG,e.localizedMessage)
releaseOpenGL()
}
}
}
5.開始預(yù)覽,并開始自動(dòng)對(duì)焦碧库。
fun doStartPreview(){
mCamera?.startPreview()
cameraFocus(width/2.0f,height/2.0f)
}
6.opengl繪制柜与,先要強(qiáng)制更新紋理圖像,再更新獲取紋理矩陣嵌灰,然后讓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紋理獲取了和加載,下一節(jié)沽瞭,將會(huì)介紹native層的opengl繪制代碼迁匠。
近來(lái)在寫一個(gè)有趣的項(xiàng)目,大家熟知攝像頭繪制和opengl的人應(yīng)該有看過(guò)MagicCamera這個(gè)Android opengl2.0的開源工程驹溃,但是已經(jīng)很多年沒(méi)人維護(hù)了城丧,這邊正在將其重構(gòu)為opengl3.0的的版本。命名為MagicCamera3以供大家學(xué)習(xí)豌鹤,現(xiàn)在還在重構(gòu)當(dāng)中亡哄,致敬作者,也希望可以有機(jī)會(huì)和作者多交流布疙,如果有認(rèn)識(shí)的可以告知我一聲蚊惯,謝謝。
開源地址:MagicCamera3灵临,這節(jié)的用例在CameraActivity中