Android Camera TextureView 获取预览帧

前文

上次写了用surfaceView实现相机画面预览,并取出了相机的预览帧,看起来没什么大问题了,但是实际运用中,很少有将nv21数据直接供算法使用,很多时候至少在我开发过程中没有遇到,一般都是将NV21数据转成BGR再使用,同时有可能还要进行旋转缩放,镜像等一系列操作,难不难受,本来算法就很慢,前面还搞这么多,这不得急死人,于是乎,就有了这篇文章的出现,接下来就能见识到TextureView的魔力

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吧一句代码就搞定,还不会影响效率。文章很多地方都是我自己的理解,如果有不对的地方,希望能帮我指出,以免误导大家,同时还有更好的方法也愿意共同探讨,对你有用的话记得点个赞哦

源码链接:(打不开记得手动给一下权限,懒癌患了,没写权限申请)

gitHub 地址
CSDN

  • 6
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值