權(quán)限
Android上打開攝像頭需要camera權(quán)限,在Android 6.0及以上的版本需要?jiǎng)討B(tài)申請(qǐng)權(quán)限,在AndroidManifest.xml中添加camera權(quán)限:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.arvr.sample">
<uses-permission android:name="android.permission.CAMERA"/>
<application>
...
</application>
<uses-permission android:name="android.permission.CAMERA"/> 是camera權(quán)限
動(dòng)態(tài)申請(qǐng)camera權(quán)限代碼如下:
class CameraActivity : AppCompatActivity(), SurfaceTexture.OnFrameAvailableListener {
private lateinit var mRenderer: MyRenderer
override fun onFrameAvailable(surfaceTexture: SurfaceTexture?) {
glSurfaceView.queueEvent {
surfaceTexture?.updateTexImage()
glSurfaceView.requestRender()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.surface)
glSurfaceView.setEGLContextClientVersion(2)
mRenderer = MyRenderer(context = baseContext, listener = this)
glSurfaceView.setRenderer(mRenderer)
glSurfaceView.renderMode = GLSurfaceView.RENDERMODE_WHEN_DIRTY
if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.CAMERA
) != PackageManager.PERMISSION_GRANTED
) {
//沒有權(quán)限
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA), 100)
} else {
mRenderer.cameraPermission = true
mRenderer.startCamera()
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == 100 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
mRenderer.cameraPermission = true
mRenderer.startCamera()
}
}
override fun onResume() {
super.onResume()
glSurfaceView.onResume()
}
override fun onPause() {
super.onPause()
glSurfaceView.onPause()
}
}
在onCreate中先判斷是否有camera權(quán)限,如果沒有則申請(qǐng)權(quán)限權(quán)限 , 如果有則打開camera。彈出權(quán)限申請(qǐng)對(duì)話框,用戶點(diǎn)擊是否允許,不管是同意還是拒絕都會(huì)回調(diào)onRequestPermissionsResult方法,用戶點(diǎn)擊同意后打開camera,和已經(jīng)有權(quán)限的操作是一樣的。
創(chuàng)建program并獲取參數(shù)句柄
頂點(diǎn)shader代碼如下:
attribute vec4 a_Position;
attribute vec2 a_TexCoordinate;
varying vec2 v_TexCoord;
void main()
{
v_TexCoord = a_TexCoordinate;
gl_Position = a_Position;
}
片段shader代碼如下:
#extension GL_OES_EGL_image_external : require
precision mediump float;
uniform samplerExternalOES u_Texture;
varying vec2 v_TexCoord;
void main()
{
gl_FragColor = texture2D(u_Texture, v_TexCoord);
}
<font color='red'>
注意:頂點(diǎn)和片段shader是單獨(dú)的文件,分別是camera_vs.glsl和camera_fs.glsl,存放于assets/glsl目錄下。
</font>
在onSurfaceCreated回調(diào)中創(chuàng)建program并獲取參數(shù)句柄,創(chuàng)建紋理,代碼如下:
override fun onSurfaceCreated(p0: GL10?, p1: EGLConfig?) {
createProgram()
//獲取vPosition索引
vPositionLoc = GLES20.glGetAttribLocation(mProgramHandle, "a_Position")
texCoordLoc = GLES20.glGetAttribLocation(mProgramHandle, "a_TexCoordinate")
textureLoc = GLES20.glGetUniformLocation(mProgramHandle, "u_Texture")
textureId = GLTools.createOESTextureId()
surfaceTexture = SurfaceTexture(textureId)
surfaceTexture?.setOnFrameAvailableListener(listener)
}
private fun createProgram() {
var vertexCode =
AssetsUtils.readAssetsTxt(
context = context,
filePath = "glsl/camera_vs.glsl"
)
var fragmentCode =
AssetsUtils.readAssetsTxt(
context = context,
filePath = "glsl/camera_fs.glsl"
)
mProgramHandle = GLTools.createAndLinkProgram(vertexCode, fragmentCode)
}
fun createOESTextureId(): Int {
val textures = IntArray(1)
GLES20.glGenTextures(1, textures, 0)
glCheck("texture generate")
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textures[0])
glCheck("texture bind")
GLES20.glTexParameterf(
GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
GLES20.GL_TEXTURE_MIN_FILTER,
GLES20.GL_LINEAR.toFloat()
)
GLES20.glTexParameterf(
GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
GLES20.GL_TEXTURE_MAG_FILTER,
GLES20.GL_LINEAR.toFloat()
)
GLES20.glTexParameteri(
GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
GLES20.GL_TEXTURE_WRAP_S,
GLES20.GL_CLAMP_TO_EDGE
)
GLES20.glTexParameteri(
GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
GLES20.GL_TEXTURE_WRAP_T,
GLES20.GL_CLAMP_TO_EDGE
)
return textures[0]
}
GLTools 為工具類,createOESTextureId方法是其中一個(gè)方法,創(chuàng)建一個(gè)OES紋理,OES紋理用于渲染相機(jī)、視頻。創(chuàng)建紋理id并創(chuàng)建SurfaceTexture,SurfaceTexture在打開相機(jī)方法中用到,用于預(yù)覽相機(jī)。setOnFrameAvailableListener的回調(diào)是從Activity中傳入,真正的實(shí)現(xiàn)在Activity中,
class CameraActivity : AppCompatActivity(), SurfaceTexture.OnFrameAvailableListener {
private lateinit var mRenderer: MyRenderer
override fun onFrameAvailable(surfaceTexture: SurfaceTexture?) {
glSurfaceView.queueEvent {
surfaceTexture?.updateTexImage()
glSurfaceView.requestRender()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
...
mRenderer = MyRenderer(context = baseContext, listener = this)
...
}
...
}
當(dāng)有新的一幀數(shù)據(jù)時(shí)會(huì)回調(diào)此方法,更新數(shù)據(jù)并調(diào)用glSurfaceView.requestRender() 重新繪制,也就是重新調(diào)用Renderer的onDrawFrame方法。
設(shè)置頂點(diǎn)坐標(biāo)、紋理坐標(biāo)、索引數(shù)據(jù)
設(shè)置頂點(diǎn)坐標(biāo),代碼如下:
var vertexBuffer = GLTools.array2Buffer(
floatArrayOf(
-1.0f, 1.0f, 0.0f, // top left
-1.0f, -1.0f, 0.0f, // bottom left
1.0f, -1.0f, 0.0f, // bottom right
1.0f, 1.0f, 0.0f // top right
)
)
設(shè)置紋理坐標(biāo),代碼如下:
var texBuffer = GLTools.array2Buffer(
floatArrayOf(
0.0f, 0.0f,
0.0f, 1.0f,
1.0f, 1.0f,
1.0f, 0.0f
)
)
設(shè)置索引數(shù)據(jù),代碼如下:
var index = shortArrayOf(3, 2, 0, 0, 1, 2)
val indexBuffer = GLTools.array2Buffer(index)
繪制
override fun onDrawFrame(p0: GL10?) {
GLES20.glUseProgram(mProgramHandle)
//設(shè)置頂點(diǎn)數(shù)據(jù)
vertexBuffer.position(0)
GLES20.glEnableVertexAttribArray(vPositionLoc)
GLES20.glVertexAttribPointer(vPositionLoc, 3, GLES20.GL_FLOAT, false, 0, vertexBuffer)
//設(shè)置紋理頂點(diǎn)數(shù)據(jù)
texBuffer.position(0)
GLES20.glEnableVertexAttribArray(texCoordLoc)
GLES20.glVertexAttribPointer(texCoordLoc, 2, GLES20.GL_FLOAT, false, 0, texBuffer)
//設(shè)置紋理
GLES20.glActiveTexture(GLES20.GL_TEXTURE0)
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId)
GLES20.glUniform1i(textureLoc, 0)
GLES20.glDrawElements(
GLES20.GL_TRIANGLES,
index.size,
GLES20.GL_UNSIGNED_SHORT,
indexBuffer
)
}
打開camera
打開camera有2個(gè)條件:
- 有相機(jī)權(quán)限。
- SurfaceTexture已經(jīng)創(chuàng)建完成。
相機(jī)權(quán)限申請(qǐng)的回調(diào)和Renderer中onSurfaceCreated(創(chuàng)建SurfaceTexture的方法)方法都是異步的,也就是說無法知道這2個(gè)方法回調(diào)的前后順序,因此需要保存相機(jī)權(quán)限狀態(tài)cameraPermission和SurfaceTexture變量surfaceTexture,在2個(gè)回調(diào)中都調(diào)用打開相機(jī)方法,在打開相機(jī)方法中判斷相機(jī)權(quán)限和SurfaceTexture是否都已經(jīng)準(zhǔn)備完成,是則打開,不是則返回,代碼如下:
override fun onSurfaceCreated(p0: GL10?, p1: EGLConfig?) {
...
startCamera()
}
fun startCamera() {
if (!cameraPermission || surfaceTexture == null) {
return
}
val cameraInfo = Camera.CameraInfo()
val cameraCount = Camera.getNumberOfCameras()
for (i in 0 until cameraCount) {
Camera.getCameraInfo(i, cameraInfo)
if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
val mCamera = Camera.open(i)
mCamera.setPreviewTexture(surfaceTexture)
//設(shè)置分辨率
val parameters = mCamera.parameters
parameters.setPreviewSize(1280, 720)
mCamera.parameters = parameters
//開始預(yù)覽
mCamera.startPreview()
return
}
}
}
運(yùn)行效果如下:
運(yùn)行后發(fā)現(xiàn)相機(jī)的畫面是倒的,這是因?yàn)閏amera本身輸出的預(yù)覽流就是倒的,下面通過矩陣旋轉(zhuǎn)解決此問題,頂點(diǎn)shader修改如下:
attribute vec4 a_Position;
attribute vec2 a_TexCoordinate;
uniform mat4 mMatrix;
varying vec2 v_TexCoord;
void main()
{
v_TexCoord = a_TexCoordinate;
gl_Position = mMatrix * a_Position;
}
增加了mMatrix矩陣。
獲取矩陣參數(shù)句柄,代碼如下:
override fun onSurfaceCreated(p0: GL10?, p1: EGLConfig?) {
createProgram()
...
matrixLoc = GLES20.glGetUniformLocation(mProgramHandle, "mMatrix")
...
}
旋轉(zhuǎn)90度,代碼如下:
var mMatrix = FloatArray(16)
override fun onSurfaceChanged(p0: GL10?, width: Int, height: Int) {
GLES20.glViewport(0, 0, width, height)
Matrix.setIdentityM(mMatrix, 0)
Matrix.rotateM(mMatrix,0,90F,0F,0F,1F)
}
設(shè)置矩陣參數(shù),代碼如下:
override fun onDrawFrame(p0: GL10?) {
...
GLES20.glUniformMatrix4fv(matrixLoc, 1, false, mMatrix, 0)
...
}
運(yùn)行后發(fā)現(xiàn)畫面調(diào)整正了,但左右鏡像,這個(gè)時(shí)候需要畫面繞y軸旋轉(zhuǎn)180度,這樣就解決了左右鏡像問題,代碼如下:
override fun onSurfaceChanged(p0: GL10?, width: Int, height: Int) {
GLES20.glViewport(0, 0, width, height)
Matrix.setIdentityM(mMatrix, 0)
Matrix.rotateM(mMatrix,0,180F,0F,1F,0F)
Matrix.rotateM(mMatrix,0,90F,0F,0F,1F)
}
<font color='red'>
注意,對(duì)預(yù)覽流的操作是先繞z軸旋轉(zhuǎn)90度,使畫面調(diào)正,然后再繞y軸旋轉(zhuǎn)180度,但寫代碼的時(shí)候要繞y軸旋轉(zhuǎn)180度寫在前面。
</font>
最終效果如下: