2025年3月15日 星期六 甲辰(龙)年 月十四 设为首页 加入收藏
rss
您当前的位置:首页 > 计算机 > 编程开发 > 安卓(android)开发

使用缓冲的方式采集视频

时间:02-09来源:作者:点击数:47

摄像头权限申请什么的就不说了,直接上关键代码:

  • 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毫秒根本就完成不了,所以解决方案就是多线程处理,可以使用双缓冲机制,比如视频采集、格式转换、旋转、加水印、编码、发送,每两个相连的步骤之间都可以加入双缓冲机制,这样就可以实现每一个步骤都是并行运行的。

方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门