你們好,今天給你們介紹Android
上如何利用OpenGL
進行相機特效渲染。git
相機特效渲染是什麼呢?所謂特效是一個比較寬泛的概念,對相機採集到的畫面作必定的修改,加上必定的效果再展現出來,均可以叫特效,好比咱們有時候會用一些app來進行自拍,有美顏、提亮等效果,還能夠在畫面上添加各類貼紙,或者是讓畫面放大、縮小、抖動等,都是特效。github
要實現相機特效渲染,總的來講有3步,首先須要採集到相機圖像,而後對它進行特效處理,最後顯示出來。數組
咱們先來看看若是從相機採集數據,在Android
上,相機有2種返回幀數據的方式,一種是以byte
數組的方式返回,一種是以texture
的方式返回。app
前一種方式返回的byte
數組能夠直接在CPU
上操做,處理後可轉成bitmap
最終顯示到ImageView
上,但這種方式效率相對來講比較低,由於對於圖像處理和渲染,CPU
遠沒有GPU
效率高,可是這種方式學習門檻低,不須要學習OpenGL
。ide
後一種方式由於直接經過texture
返回,所以從返回數據,到特效處理,到最後顯示,所有能夠經過OpenGL
在GPU
上作,效率很是高,如今的商業app都是用這種方式,這篇文章也將介紹這種方式,若是你對OpenGL
還不瞭解,能夠參考個人Android OpenGL ES 2.0 手把手教學系列文章及OpenGL ES 高級進階系列文章。oop
在使用相機時,首先須要打開相機,固然,相機權限必不可少,這裏不哆嗦了,有不少文章講解相機權限:post
...
val cameraId = getCameraId(Camera.CameraInfo.CAMERA_FACING_BACK)
camera = Camera.open(cameraId)
...
private fun getCameraId(facing : Int) : Int {
val numberOfCameras = Camera.getNumberOfCameras()
for (i in 0 until numberOfCameras) {
val info = Camera.CameraInfo()
Camera.getCameraInfo(i, info)
if (info.facing == facing) {
return i
}
}
return -1
}
複製代碼
首先須要獲取要打開的相機的id
,通常來講,手機上有前置和後置兩個相機,這裏獲取了後置相機的id
並打開了它,注意這裏的打開並非說相機就開始工做取景了,而僅僅獲得這個相機對像而已,此時相機並無開始工做,雖然沒有開始工做,但若是不釋放的話,別的應用再去打開會失敗。學習
獲得相機對象後,下面須要設置一些參數,有不少參數能夠設置,如預覽分辨率、對焦方式、拍照分辨率等等,這裏咱們只設置預覽分辨率和顯示角度。ui
相機的預覽分辨率只能從支持的列表中選一個設置,不能設置任意值,實際使用時,通常會作些篩選邏輯,好比一個720P的屏幕,選了一個1080P的分辨率,其實沒什麼意義,會浪費資源,這裏簡單起見,我就直接取支持列表中的第0
個。this
private fun setPreviewSize(parameters: Camera.Parameters) {
parameters.setPreviewSize(
parameters.supportedPreviewSizes[0].width,
parameters.supportedPreviewSizes[0].height
)
}
複製代碼
下面是設置旋轉角度:
val info = Camera.CameraInfo()
Camera.getCameraInfo(cameraId, info)
camera.setDisplayOrientation(info.orientation)
複製代碼
這個旋轉角度會影響到咱們看到的圖像的旋轉,通常狀況下這樣設置就能夠了,不過有些機型會有兼容性問題,須要特殊機型特殊設置。
前面說到,咱們會讓相機採集到的幀圖像經過texture
返回,這是經過setPreviewTexture
方法設置的:
camera.setPreviewTexture(surfaceTexture)
複製代碼
注意這裏的surfaceTexture
它不是texture
,texture
是一個int
類的值,這裏的surfaceTexture
是SurfaceTexture
類的一個對象,它是經過texture
建立出來的,能夠認爲它是將texture
包了一層,這裏的texture
和surfaceTexture
都是咱們本身建立的,而後設置給camera
。
既然要建立texture
,那就須要OpenGL
環境,關於OpenGL
環境能夠參考個人一篇文章《OpenGL ES 高級進階:EGL及GL線程》,作OpenGL
渲染,通常會用GLSurfaceView
,它自帶了OpenGL
環境,不須要咱們再去建立,這裏我用TextureView
,它是不帶OpenGL
環境的,我本身封裝了一個OpenGL
環境,使它的功能和GLSurfaceView
同樣,TextureView
相比於GLSurfaceView
來講還有一大好處就是,它同時有和普通view
同樣的功能,好比咱們能夠將它像一個普通的view
同樣放到一個RecyclerView
中的item
中顯示,不會有問題,而若是把GLSurfaceView
這樣作,會有問題,相似的,若是你去移動一個GLSurfaceView
,也會發現有這樣那樣的問題,根源就在於GLSurfaceView
它不是在view
樹上的,它和普通的view
是不同的。所以,我這裏封裝了一個帶有OpenGL
環境的TextureView
,叫GLTextureView
,它比GLSurfaceView
的功能更強大,而用於顯示相機特效渲染的View
繼承了GLTextureView
,叫GLCameraView
,它能夠綁定一個實現了ICamera
接口的自定義camera
類,用於向GLCameraView
提供一些操做camera
的方法以及一些獲取心要信息的方法。
咱們的texture
也就是從這裏的OpenGL
環境建立出來的,注意這裏建立的texture
,不是普通的texture
,是OES
類型的texture
,相機和視頻硬解碼出來的內容,都須要用OES
類型的texture
承載,不然會報錯。
OES
類型texture
建立:
fun createOESTexture(): Int {
val textures = IntArray(1)
GLES30.glGenTextures(textures.size, textures, 0)
GLES30.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textures[0])
GLES30.glTexParameteri(
GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
GLES30.GL_TEXTURE_WRAP_S,
GLES30.GL_CLAMP_TO_EDGE
)
GLES30.glTexParameteri(
GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_CLAMP_TO_EDGE
)
GLES30.glTexParameteri(
GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_LINEAR
)
GLES30.glTexParameteri(
GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR
)
GLES30.glBindTexture(GLES30.GL_TEXTURE_EXTERNAL_OES, 0)
return textures[0]
}
複製代碼
設置好SurfaceTexture
以後,咱們還須要給SurfaceTexture
設置回調來感知相機給咱們返回數據了:
...
st?.setOnFrameAvailableListener(this)
...
override fun onFrameAvailable(surfaceTexture: SurfaceTexture?) {
...
surfaceTexture?.updateTexImage()
...
}
複製代碼
當相機給咱們返回一幀數據時,onFrameAvailable
就會回調一次告訴咱們有數據了,注意這只是告訴咱們有數據了,並非告訴咱們已經把數據放到texture
上了,此時咱們須要調用updateTexImage
,相機幀數據纔會更新到texture
上,因爲這一步是對texture
進行操做,因此updateTexImage
須要在GL
線程調用。
onFrameAvailable
是從GL
線程回調過來嗎?不必定,這處決於你在哪裏建立SurfaceTexture
,若是建立SurfaceTexture
的線程有Looper
,它就會持有這個Looper
,以後回調onFrameAvailable
就會經過這個Looper
建立一個handler
來在這個線程回調,若是建立SurfaceTexture
的線程有沒有Looper
,它就會在主線程中回調過來。所以,若是建立SurfaceTexture
的線程有Looper
而且是GL
線程,那麼onFrameAvailable
回調過來就是GL
線程,不然就不是,此時就不能在onFrameAvailable
裏調用updateTexImage
。
在個人代碼中,我確保onFrameAvailable
回調過來就是GL
線程,所以我直接在裏面調用updateTexImage
。
這些都設置好以後,咱們就調用startPreview
,這時相機就真正開始工做了:
camera.startPreview()
複製代碼
此至,咱們就完成了將相機幀數據採集到一個texture
上,那麼特效處理就以這個texture
作爲輸入,利用OpenGL
進行渲染,這裏我使用個人一個庫FunRenderer
進行渲染,它將OpenGL
進行了封裝,使用起來很方便。
前面提到咱們封裝了一個GLCameraView
用於顯示渲染的結果,這裏我經過callback
回調3個方法,分別用於初始化、渲染、釋放:
interface RenderCallback {
fun onInit()
fun onRenderFrame(oesTexture: Int, stMatrix: FloatArray, cameraPreviewSize: Size, surfaceSize: Size)
fun onRelease()
}
複製代碼
咱們也就是在這三個方法中使用FunRenderer
:
val cameraWrapper = CameraWrapper()
cameraView.bindCamera(cameraWrapper)
cameraView.renderCallback = object : GLCameraView.RenderCallback {
private val oes2RGBARenderer = OES2RGBARenderer()
private val cropRenderer = CropRenderer()
private val effectRenderer = TestEffectRenderer()
private val screenRenderer = ScreenRenderer()
private lateinit var renderChain: RenderChain
override fun onInit() {
renderChain = RenderChain.create()
.addRenderer(oes2RGBARenderer)
.addRenderer(cropRenderer)
.addRenderer(effectRenderer)
.addRenderer(screenRenderer)
renderChain.init()
}
override fun onRenderFrame(oesTexture: Int, stMatrix: FloatArray, cameraPreviewSize: Size, surfaceSize: Size) {
GLES30.glClearColor(0f, 0f, 0f, 1f)
GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT)
val input = Texture(oesTexture, cameraPreviewSize.height, cameraPreviewSize.width, false)
val data = mutableMapOf<String, Any>()
data[Keys.ST_MATRIX] = stMatrix
data[Keys.CROP_RATIO] = surfaceSize.width.toFloat() / surfaceSize.height
data[Keys.SURFACE_WIDTH] = surfaceSize.width
data[Keys.SURFACE_HEIGHT] = surfaceSize.height
renderChain.render(input, data)
}
override fun onRelease() {
renderChain.release()
}
}
複製代碼
這裏每幀作的操做有OES
轉RGBA
、裁剪、一個簡單的特效、上屏。
前面提到,相機返回的幀數據不是用普通的texture
承載的,而是用OES
類型的texture
承載的,所以第一步須要經過OES2RGBARenderer
將它轉換成RGBA
的texture
。
而後再經過CropRenderer
進行一步裁剪,由於咱們選的預覽分辨率,可能和咱們實現顯示的區域的比例是不同的,若是不裁剪,強行徹底填充,就會變形。
而後咱們作一個簡單的特效,TestEffectRenderer
繼承於SimpleRenderer
,我用一個簡單的shader
實現一個簡單的特效:
#version 300 es
precision mediump float;
in vec2 v_textureCoordinate;
layout(location = 0) out vec4 fragColor;
uniform sampler2D u_texture;
void main() {
vec4 c = texture(u_texture, v_textureCoordinate);
c.b = 0.5;
fragColor = c;
}
複製代碼
這裏我簡單地將藍色經過的值設爲0.5
,獲得的效果就是有點偏藍,就像作了一個濾鏡了同樣。固然,實際使用的顏色效果濾鏡比這個要複雜得多,通常用LUT
實現,這裏只是作一個簡單的示例。
最後經過ScreenRenderer
上屏,這樣咱們就完成了相機特效渲染,實際中你們看到的相機特效,無非就是本身寫各類各樣的shader
,組合各類渲染步驟獲得的。
咱們來看下效果:
我封裝了一個庫HiCamera
(github.com/kenneycode/…),能夠方便地綁定相機進行特效渲染,demo
裏我用個人FunRenderer
(github.com/kenneycode/…)庫來渲染,能夠本身繼承FunRenderer
的SimpleRenderer
進行擴展,或者用別的,本文中的代碼也都在HiCamera
的demo
中。
感謝閱讀!