当前位置:网站首页>使用缓冲的方式采集视频
使用缓冲的方式采集视频
2022-07-28 19:04:00 【android_cai_niao】
摄像头权限申请什么的就不说了,直接上关键代码:
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毫秒根本就完成不了,所以解决方案就是多线程处理,可以使用双缓冲机制,比如视频采集、格式转换、旋转、加水印、编码、发送,每两个相连的步骤之间都可以加入双缓冲机制,这样就可以实现每一个步骤都是并行运行的。
边栏推荐
- JS drag and drop alert pop-up plug-in
- 查询oracle视图创建语句及如何向视图中插入数据[通俗易懂]
- Space game Lesson 12: shield
- unity-shader-1
- Ask if you don't understand, and quickly become an advanced player of container service!
- MySQL修改端口号(修改mysql的端口号会有问题吗)
- 不懂就问,快速成为容器服务进阶玩家!
- Understanding of C # delegate
- Integrating database Ecology: using eventbridge to build CDC applications
- ntp服务器 时间(查看服务器时间)
猜你喜欢

Redis 3.0 source code analysis - data structure and object SDS list Dict

How do we do full link grayscale on the database?

Explain mesh Collider in unity

研发效能的思考总结

EasyNLP中文文图生成模型带你秒变艺术家

Redis入门一:Redis实战读书笔记

What is "security"? Volvo tells you with its unique understanding and action

Introduction to redis I: redis practical reading notes

Integrating database Ecology: using eventbridge to build CDC applications

Confusing knowledge points of software designer examination
随机推荐
图书馆借阅系统「建议收藏」
mysql还有哪些自带的函数呢?别到处找了,看这个就够了。
Lvs+keepalived high availability deployment practical application
C# 读取 CSV文件内数据,导入DataTable后显示
Unity typewriter teaches you three ways
MoCo V2:MoCo系列再升级
[server data recovery] HP StorageWorks series storage RAID5 two disk failure offline data recovery case
Leetcode:2141. The longest time to run n computers at the same time [the maximum value is two points]
Prometheus complete process of configuring alertmanager
《软件设计师考试》易混淆知识点
2 enjoy yuan mode
GIS数据漫谈(六)— 投影坐标系统
一文读懂Okaleido Tiger近期动态,挖掘背后价值与潜力
How to build internal Wikipedia
Observer mode, object pool
Learn about the native application management platform of rainbow cloud
Unity foundation 6-rotation
Thinking and summary of R & D Efficiency
Understanding of C # delegate
prometheus配置alertmanager完整过程