赞
踩
上次写了用surfaceView实现相机画面预览,并取出了相机的预览帧,看起来没什么大问题了,但是实际运用中,很少有将nv21数据直接供算法使用,很多时候至少在我开发过程中没有遇到,一般都是将NV21数据转成BGR再使用,同时有可能还要进行旋转缩放,镜像等一系列操作,难不难受,本来算法就很慢,前面还搞这么多,这不得急死人,于是乎,就有了这篇文章的出现,接下来就能见识到TextureView的魔力
概念性的东西,就从网上扒一个吧,需要深入的可以自行百度一哈
在4.0(API level 14)中引入。它可以将内容流直接投影到View中,可以用于实现Live preview等功能。和SurfaceView不同,它不会在WMS中单独创建窗口,而是作为View hierachy中的一个普通View,因此可以和其它普通View一样进行移动,旋转,缩放,动画等变化。值得注意的是TextureView必须在硬件加速的窗口中。
这儿我写了个Camera的辅助类,同时这次我用了kotlin写demo,所以代码就超级简单,仅需几句话就能完成预览功能了
textureView.surfaceTextureListener = object :TextureView.SurfaceTextureListener{ override fun onSurfaceTextureAvailable(p0: SurfaceTexture?, p1: Int, p2: Int) { CameraManager.getInstance().openCamera(this@MainActivity) CameraManager.getInstance().startPreview(p0) } override fun onSurfaceTextureSizeChanged(p0: SurfaceTexture?, p1: Int, p2: Int) { } override fun onSurfaceTextureUpdated(p0: SurfaceTexture?) { } override fun onSurfaceTextureDestroyed(p0: SurfaceTexture?): Boolean { CameraManager.getInstance().stopCamera() return false } }
有了画面,当然就得要数据了,想想之前怎么获取的,通过camera的回调函数,拿到对应的数据buffer。这次就不一样了,我们需要从TextureView下手,通过它来拿到我们想要的数据。
不知道大家有没有发现,TextureView的回调方法里面,多了一个onSurfaceTextureUpdated,拿去百度瞅瞅,偌大的网上,居然没有关于对这个函数说明的文章,但是顾名思意 翻译就是“表面纹理更新”,这下就清晰了,就是当该View的纹理数据更新的时候执行的回调,为了验证我的猜想,特意打印了一下日志
2019-05-21 16:55:11.992 8274-8274/huaan.com.camerademo2 I/onSurfaceTextureUpdated: 1111111111111111111
2019-05-21 16:55:12.041 8274-8274/huaan.com.camerademo2 I/onSurfaceTextureUpdated: 1111111111111111111
2019-05-21 16:55:12.110 8274-8274/huaan.com.camerademo2 I/onSurfaceTextureUpdated: 1111111111111111111
2019-05-21 16:55:12.159 8274-8274/huaan.com.camerademo2 I/onSurfaceTextureUpdated: 1111111111111111111
2019-05-21 16:55:12.226 8274-8274/huaan.com.camerademo2 I/onSurfaceTextureUpdated: 1111111111111111111
2019-05-21 16:55:12.273 8274-8274/huaan.com.camerademo2 I/onSurfaceTextureUpdated: 1111111111111111111
2019-05-21 16:55:12.343 8274-8274/huaan.com.camerademo2 I/onSurfaceTextureUpdated: 1111111111111111111
2019-05-21 16:55:12.407 8274-8274/huaan.com.camerademo2 I/onSurfaceTextureUpdated: 1111111111111111111
2019-05-21 16:55:12.458 8274-8274/huaan.com.camerademo2 I/onSurfaceTextureUpdated: 1111111111111111111
2019-05-21 16:55:12.527 8274-8274/huaan.com.camerademo2 I/onSurfaceTextureUpdated: 1111111111111111111
2019-05-21 16:55:12.594 8274-8274/huaan.com.camerademo2 I/onSurfaceTextureUpdated: 1111111111111111111
2019-05-21 16:55:12.655 8274-8274/huaan.com.camerademo2 I/onSurfaceTextureUpdated: 1111111111111111111
算了一下,每秒大概有20到25帧左右,那就没错了,也就是说每渲染一帧,就会执行一次这个方法,查了一下官方文档,可以通过getBitmap() 获取当前渲染在界面上的bitmap数据,这一下,就很好解决了,上代码
textureView.surfaceTextureListener = object :TextureView.SurfaceTextureListener{ override fun onSurfaceTextureAvailable(p0: SurfaceTexture?, p1: Int, p2: Int) { CameraManager.getInstance().openCamera(this@MainActivity) CameraManager.getInstance().startPreview(p0) //获取预览图像宽高 val cameraInfo = CameraManager.getInstance().cameraInfo ?: return captureWidth = cameraInfo.previewWidth captureHeight = cameraInfo.previewHeight //设置需要取出的bitmap大小,这儿缩小一半,大图检测速度慢 captureWidth /= 3 captureHeight /= 3 //当有旋转角度时需要将图片的宽高互换 if (cameraInfo.orientation == 90 || cameraInfo.orientation == 270) { val tempH = captureHeight captureHeight = captureWidth captureWidth = tempH } captureBitmap = Bitmap.createBitmap(captureWidth, captureHeight, Bitmap.Config.ARGB_8888) } override fun onSurfaceTextureSizeChanged(p0: SurfaceTexture?, p1: Int, p2: Int) { } override fun onSurfaceTextureUpdated(p0: SurfaceTexture?) { textureView.getBitmap(captureBitmap) //获取到渲染在textureView上面的图像 } override fun onSurfaceTextureDestroyed(p0: SurfaceTexture?): Boolean { CameraManager.getInstance().stopCamera() return false } }
上面获取预览图像宽高先不管,主要通过
captureBitmap = Bitmap.createBitmap(captureWidth, captureHeight, Bitmap.Config.ARGB_8888)
创建了一个bitmap,并指定为ARGB格式,再通过
textureView.getBitmap(captureBitmap) //获取到渲染在textureView上面的图像
就能获取到渲染的每一帧数据了。
这个地方说几个点:1,onSurfaceTextureUpdated执行在主线程,所以不能在里面执行耗时操作,否则后果会很严重
2,可能不同手机,预览图像的分辨率不同会导致onSurfaceTextureUpdated执行速度发生变化,并且
getBitmap()方法也会变慢,这儿能理解,毕竟图片越大渲染的数据量就越大,当然慢了
3,取出来的图片,就和你肉眼看到的图片方向一模一样,所以你不用考虑图片的旋转,同时
getBitmap时能制定图片大小,所以你不需要再对图片进行缩放
既然前面说了,不能在onSurfaceTextureUpdated里面执行耗时操作,所以我们需要把后续操作放到子线程里面来做,onSurfaceTextureUpdated就拿个数据就OK了
本来想分开写两篇文章的,但是看没多少内容,所以委屈你们多看一会儿了,前面拿到预览数据之后,还有很多问题没解决,回调整了个bitmap,还跑到了主线程对于对线程用法不熟练的宝宝们一定很头大了。之前一个客户就是这样,我给他讲onSurfaceTextureUpdated 不能执行耗时操作,要在子线程中做,然后他居然真的在onSurfaceTextureUpdated里面new 了个 Thread,我尼玛,,,,还告诉我这样做让他的app很卡,心里一万个草泥马不知向谁讲。懂的人相信看到就懂了,不懂的给你们解释一下,onSurfaceTextureUpdated这个回掉函数前面测试了,每秒能执行20-25次,所以new Thread每秒也会执行这么多次,疯狂new 线程 不卡有鬼了。废话有点多哈,下面直接看代码了
internal inner class FaceWork : Runnable { private var argbIn: ByteArray? = null private var bgrOut: ByteArray? = null private var captureBuffer: ByteBuffer? = null override fun run() { dataConversion() /** do something */ frameGet = false } /** * 数据转换,将bmp数据转换成bgr,供算法使用 */ private fun dataConversion() { synchronized(MainActivity::class.java) { //将抓拍bmp中的数据读到buffer中 if (captureBuffer == null) { val count = captureBitmap!!.byteCount captureBuffer = ByteBuffer.allocate(count) } captureBuffer!!.position(0) captureBitmap!!.copyPixelsToBuffer(captureBuffer) } //用来存放bmp中的argb数据 if (argbIn == null) argbIn = ByteArray(captureWidth * captureHeight * 4) captureBuffer!!.position(0) captureBuffer!!.get(argbIn) } }
后面的argbIn 就是最终的byte数据了,因为这是个无限循环,所以尽量别在里面创建对象,免得造成GC压力,最好是复用已有的对象继续操作。
最后就是把argb转成bgr了,我放在下一次的文章中吧,这次讲的废话够多了
TextureView预览相机并不难,预览数据的获取方法是我在项目中实际使用的,当面对比之前的surfaceView有利有弊,至于取舍得看自己项目的运用了,如果需要对相机的角度,镜像,平移等View的变换操作,尽量选择用TextureView吧一句代码就搞定,还不会影响效率。文章很多地方都是我自己的理解,如果有不对的地方,希望能帮我指出,以免误导大家,同时还有更好的方法也愿意共同探讨,对你有用的话记得点个赞哦
源码链接:(打不开记得手动给一下权限,懒癌患了,没写权限申请)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。