摄像头权限申请什么的就不说了,直接上关键代码:
class MainActivity : AppCompatActivity() {
private val camera = Camera.open()
private val surfaceTexture = SurfaceTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
camera.setPreviewTexture(surfaceTexture)
camera.setPreviewCallback { data, camera ->
println("摄像头正在采集图像")
}
camera.startPreview()
}
override fun onDestroy() {
super.onDestroy()
camera.setPreviewCallback(null)
camera.stopPreview()
camera.release()
}
}
这是一个无预览摄像头视频采集,只是一个非常简单的代码,camera.setPreviewCallback中的data即为图像数据,我们没有做保存处理,每次得到的data都会是一个新的数组对象,试验如下:
class MainActivity : AppCompatActivity() {
private val camera = Camera.open()
private val surfaceTexture = SurfaceTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES)
private var oldData = ByteArray(0)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
camera.setPreviewTexture(surfaceTexture)
camera.setPreviewCallback { data, camera ->
if (oldData !== data) {
oldData = data
println("新data")
} else {
println("旧data")
}
}
camera.startPreview()
}
override fun onDestroy() {
super.onDestroy()
camera.setPreviewCallback(null)
camera.stopPreview()
camera.release()
}
}
运行代码,打印的都是"新data"。如果是25帧/秒,则每秒要创建25个新的Byte数组,不停地创建新的对象性能是比较低的,所以可以使用缓冲对象,每次都用同一个数组,代码如下:
camera.setPreviewCallbackWithBuffer { data, camera ->
if (oldData !== data) {
oldData = data
println("新data")
} else {
println("旧data")
}
}
这里把setPreviewCallback函数替换成了setPreviewCallbackWithBuffer,运行代码,会发现没有任何输出,这是因为还需要我们提供一个缓冲数组,如下:
class MainActivity : AppCompatActivity() {
private val camera = Camera.open()
private val surfaceTexture = SurfaceTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES)
private val previewSize = camera.parameters.previewSize
private val width = previewSize.width
private val height = previewSize.height
private val callbackBuffer = ByteArray((width * height * 3) shr 1)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
camera.setPreviewTexture(surfaceTexture)
camera.addCallbackBuffer(callbackBuffer)
camera.setPreviewCallbackWithBuffer { data, camera ->
if (callbackBuffer !== data) {
println("新data")
} else {
println("旧data")
}
}
camera.startPreview()
}
override fun onDestroy() {
super.onDestroy()
camera.setPreviewCallback(null)
camera.stopPreview()
camera.release()
}
}
运行代码,会发现只打印了一次"旧data",只是因为系统只会在我们调用了camera.addCallbackBuffer(callbackBuffer)之后,才使用我们给的buffer对象装一帧的图像给我们,所以,在我们需要数据的时候就需要调用camera.addCallbackBuffer(callbackBuffer),如果一直需要,就需要一直调用camera.addCallbackBuffer(callbackBuffer),示例如下:
class MainActivity : AppCompatActivity() {
private val camera = Camera.open()
private val surfaceTexture = SurfaceTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES)
private val previewSize = camera.parameters.previewSize
private val width = previewSize.width
private val height = previewSize.height
private val callbackBuffer = ByteArray((width * height * 3) shr 1)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
camera.setPreviewTexture(surfaceTexture)
camera.addCallbackBuffer(callbackBuffer)
camera.setPreviewCallbackWithBuffer { data, camera ->
if (callbackBuffer !== data) {
println("新data")
} else {
println("旧data")
}
// TODO 处理data
// data处理完了,再把缓冲对象给到框架,让其再填充图像数据在里面
//camera.addCallbackBuffer(data) // 这个方式也可以,反正都是同一个对象
camera.addCallbackBuffer(callbackBuffer)
}
camera.startPreview()
}
override fun onDestroy() {
super.onDestroy()
camera.setPreviewCallback(null)
camera.stopPreview()
camera.release()
}
}
再次运行代码,会看到一直在输出"旧data",这样就避免了每一帧都创建新的data对象,但是需要注意,我们在处理data数据的时候一定要快,假设需要25帧/秒,则每一帧的处理时间为40毫秒,我们必须在40毫秒内处理完data,然后再把data设置到addCallbackBuffer中,如果处理的时间慢,就会导致丢帧。比如,我们加入帧率的统计代码,如下:
class MainActivity : AppCompatActivity() {
private val camera = Camera.open()
private val surfaceTexture = SurfaceTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES)
private val previewSize = camera.parameters.previewSize
private val width = previewSize.width
private val height = previewSize.height
private val callbackBuffer = ByteArray((width * height * 3) shr 1)
private var fps = 0
private var start = 0L
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
println("width = $width height = $height")
camera.setPreviewTexture(surfaceTexture)
camera.addCallbackBuffer(callbackBuffer)
camera.setPreviewCallbackWithBuffer { data, camera ->
fps++
val current = System.currentTimeMillis()
if (start == 0L) {
start = current
}
if (current - start >= 1000) {
println("帧速:${fps}帧/秒")
fps = 0
start = current
}
camera.addCallbackBuffer(data)
}
camera.startPreview()
}
override fun onDestroy() {
super.onDestroy()
camera.setPreviewCallback(null)
camera.stopPreview()
camera.release()
}
}
在小米11 pro运行结果如下:
width = 1920 height = 1080
帧速:28帧/秒
帧速:24帧/秒
帧速:30帧/秒
帧速:30帧/秒
帧速:31帧/秒
帧速:30帧/秒
帧速:31帧/秒
帧速:30帧/秒
帧速:31帧/秒
帧速:30帧/秒
可以看到,帧速并不是稳定输出的,也会有偏差。
接下来,模拟一下对data数据的处理,假设这个处里需要80毫秒:
class MainActivity : AppCompatActivity() {
private val camera = Camera.open()
private val surfaceTexture = SurfaceTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES)
private val previewSize = camera.parameters.previewSize
private val width = previewSize.width
private val height = previewSize.height
private val callbackBuffer = ByteArray((width * height * 3) shr 1)
private var fps = 0
private var start = 0L
private val handler = object: Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
camera.addCallbackBuffer(callbackBuffer)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
println("width = $width height = $height")
camera.setPreviewTexture(surfaceTexture)
camera.addCallbackBuffer(callbackBuffer)
camera.setPreviewCallbackWithBuffer { data, camera ->
fps++
val current = System.currentTimeMillis()
if (start == 0L) {
start = current
}
if (current - start >= 1000) {
println("帧速:${fps}帧/秒")
fps = 0
start = current
}
// 模拟一个80毫秒的耗时操作
handler.sendEmptyMessageDelayed(0, 80L)
}
camera.startPreview()
}
override fun onDestroy() {
super.onDestroy()
camera.setPreviewCallback(null)
camera.stopPreview()
camera.release()
}
}
运行效果如下:
width = 1920 height = 1080
帧速:7帧/秒
帧速:8帧/秒
帧速:6帧/秒
帧速:6帧/秒
帧速:8帧/秒
帧速:7帧/秒
帧速:8帧/秒
帧速:8帧/秒
帧速:9帧/秒
帧速:8帧/秒
可以看到,由于处理数据花费的时间太多,帧速极速下降,所以看到的视频就会很卡,不是卡住不动,而是视频不流畅,画面不连贯。
接下来,我们把耗时时间改成30毫秒,再次运行,结果如下:
width = 1920 height = 1080
帧速:13帧/秒
帧速:13帧/秒
帧速:14帧/秒
帧速:14帧/秒
帧速:14帧/秒
帧速:14帧/秒
帧速:14帧/秒
帧速:13帧/秒
帧速:14帧/秒
理论上1帧需要30毫秒,1秒(1000毫秒)可以处理33帧啊,但是结果显示只有14帧左右,那我们把延时再改小为10毫秒,再次运行,结果如下:
width = 1920 height = 1080
帧速:20帧/秒
帧速:22帧/秒
帧速:31帧/秒
帧速:30帧/秒
帧速:31帧/秒
帧速:31帧/秒
帧速:30帧/秒
帧速:30帧/秒
帧速:30帧/秒
帧速:31帧/秒
帧速:29帧/秒
帧速:31帧/秒
帧速:30帧/秒
帧速:31帧/秒
可以看到,帧速恢复到了30帧/秒,这样视频就不会卡了,但是10毫秒的处理时间,在真实开发中根本就不够用,每一帧图像要经过旋转、加水印、H264编码、网络发送等一系列操作,10毫秒根本就完成不了,所以解决方案就是多线程处理,可以使用双缓冲机制,比如视频采集、格式转换、旋转、加水印、编码、发送,每两个相连的步骤之间都可以加入双缓冲机制,这样就可以实现每一个步骤都是并行运行的。