MTK 下 使用Camera1 完成预览-拍照-录像-USBCamera功能

使用Camera1 API 实现相机基本功能:预览、拍照、录像 同时实现USBCamera 功能


一、目的

  • 这里核心重点讨论的是实现USBCamera 功能,预览-拍照-录像不作为重点介绍对象
  • 目的是为了实现USBCamera 软件,UVC太古老,偏framework层和底层,系统签名等局限性。如何实现Camera 框架下的Camera1 和 Camera2 来实现USBCamera 功能。

二、USBCamera 技术实现方案

  • 基于UVC协议 来实现:如之前笔记讨论介绍,强烈建议再熟悉熟悉
  • 基于Android 体系下的SDK API来实现,底层就是Camera1 或者 Camera2 框架下来实现

三、误区

基本常识是 USBCamera 是无驱的,那么跟底层系统不相关,USB相机插着就能用,系统本身支持的想法其实是错误的。
大家可以自行查阅相关资料,这里重点说明的是:
1)使用UVC协议来实现USBCamera ,作为一个系统应用 具备系统签名,不用考虑各种权限,是没有问题的。
2)使用Android SDK 框架下的API来实现USBCamera 是不可取的,官方文档明确说明Camera2 是不支持的。但是我们在不同半导体平台下不一样,比如RK上面是支持Camera2 开发USBCamera,
但是在MTK上面 Camera2 是不支持USBCamera,就是打不开相机的。 所以,要想实现Camera2 API来写一个USBCamera 务必驱动层、framework层在芯片平台支持的情况下适配好才行。

四、USBCamera 遇到项目难点

1)使用UVC协议来开发USBCamera 需要系统签名,特别在Android13 及以上版本开发,权限问题绕不过,系统获取节点权限在各个芯片平台上面都绕不过,无解。
2)UVC协议开发遇到困难、了解甚少,那么需要用Android 体系下的Camera API来开发。 Android 5 以后 都是用的Camera2,但是Camera2 在MTK平台上面不支持的 需要驱动层和framework层适配,当然RK平台是支持Camera2 API开发的。那么使用Camera1 API 虽然过时了,拓展性不强,但Camera1 的API 是支持USBCamera 开发的。
3)为什么要自己开发USBCamera,使用市场上写好的 OpenCamera、USBCamera 不好吗? 实际上 客户需求很多样,硬件环境很复杂、功能复杂、 不同平台不同soc 不同USB相机不适配、功能也不可用。
比如:遇到HDMIN 功能作为一个外置摄像头,但又不是真正意义上的Camera,那么不可能正常打开正常使用的。

五、基础补充、资源参考

对于相机开发,还是建议无论是UVC协议开发USBCamera,还是 Android SDK API框架下Camera api 来开发相机,都需要了解最基本的相关资料。

相机整个模块确实太专业、复杂了。 无论从硬件外设、驱动【USBCamera免驱】、相机 都比较专业、覆盖面及广,针对思考中的问题 给出自己认为比较好相关博客,方便了解,助于梳理流程、提升认知。

对于上层应用或者Framework 系统应用开发者,只需要了解基本的架构、API、使用方法,当然这些也不简单的
下面提供部分资源,方便快速了解,充电:

架构图了解

MTKCamera2相机架构
Camera2架构
Android Camera架构简析

Camera相关专栏

Camera Framework 专栏
小驰私房菜系列
小驰私房菜MTK系列
小驰Camera 开发系列
Camera 相机开发
展讯平台 Camera
官方文档:谷歌官方 API 描述

零散知识了解

MTK 相机UI介绍
Camera2 相机认知
Camera2学习笔记
camera2关于拍照预览方向旋转90度和拍照图片镜像功能实现
Camera2 预览集成、简单拍照:熟悉预览步骤流程比较有用
Camera镜像上下左右颠倒问题的解决办法
MTK相机成像质量差
Camera应用分析

部分相机源码参考,学习API使用,梳理流程,偏应用层

极客相机 Camera2 API
Camera2 API详解
极客相机源码
Camera2 相机Demo
Camera2 专业相机Demo
拍照、预览、录像Demo
使用Camera2 拍照

Camera2 系统相关

Camera2 Service 启动

六、经验参考

极客相机源码
Camera1 实现预览-拍照-录像

备注:

  • 极客相机源码在开源OpenCamera 相机修改而来实现的,在切换相机过程中默认Camera1 API 可以正常打开USBCamera相机,切换到Camera2 API 下无法正常打开USBCamera相机;
  • 使用Camera2 API,以OpenCamera 作为源码二次开发,发现在MTK平台下获取得到外设USBCamera的CameraId
    也打不开USBCamera,但是在RK平台上是可以正常打开的。所以 MTK平台上默认是不支持Camera2来实现USBCamera 功能的,但是RK平台做了适配 是可行的,直接修改RK平台的Camera2 就能实现USBCamera 和 MIPI 相机切换了。
  • 基于1 点的实践,那么就只能使用Camera1 API 来实现USBCamera了。 在RK和MTK平台上实际验证都是支持的。
  • 自己在MTK平台上面实践发现:在Camera1 API下,打开CameraId 2 就是 外设USBCamera 了。
    在这里插入图片描述

七、 功能实现

Camera1 API 相对非常简单,简要总结如下

声明cameraID 为前置、后置 或者 外设USBCamera

cameraId = CameraInfo.CAMERA_FACING_FRONT
cameraId = CameraInfo.CAMERA_FACING_BACK
cameraId = 2


选择 绘制组件 SurfaceView 或者 TextureView

打开相机

mCamera = Camera.open(cameraId)

打开后 设置Camera 属性:预览大小、方向、格式

   private fun open(cameraId: Int) {
        //获得camera对象
        mCamera = Camera.open(cameraId)

        mCamera?.let { camera ->
            //配置camera的属性
            val parameters = camera.parameters
            //设置预览数据格式为nv21
            parameters.previewFormat = ImageFormat.NV21
            //这是摄像头宽、高
            setPreviewSize(parameters!!)
            // 设置摄像头 图像传感器的角度、方向
            setPreviewOrientation(cameraId)
            camera.parameters = parameters
        }
    }

切换摄像头

 /**
     * 切换摄像头
     */
    fun switchCamera() {
        val cameraId = if (cameraId == Camera.CameraInfo.CAMERA_FACING_BACK) {
            Camera.CameraInfo.CAMERA_FACING_FRONT
        } else {
            Camera.CameraInfo.CAMERA_FACING_BACK
        }
        switchCamera(cameraId)
    }

其实就是把cameraId 换一下,然后重新预览

  private fun previewAlign() {
        stopPreview()
        if (surfaceHolder != null) {
            startPreview(surfaceHolder!!)
        } else {
            startPreview(surfaceTexture!!)
        }
    }

停止预览

 /**
     * 停止预览
     */
    fun stopPreview() {
        if (mCamera != null) {
            mCamera?.setPreviewCallback(null)
            mCamera?.stopPreview()
            mCamera?.release()
            mCamera = null
        }
    }

重新预览

    /**
     * 开始预览
     */
    fun startPreview(surfaceHolder: SurfaceHolder) {
        open(cameraId)
        this.surfaceHolder = surfaceHolder
        buffer = ByteArray(width * height * 3 / 2)
        bytes = ByteArray(buffer!!.size)
        //数据缓存区
        mCamera?.addCallbackBuffer(buffer)
        mCamera?.setPreviewCallbackWithBuffer(this)
        //设置预览画面
        mCamera?.setPreviewDisplay(surfaceHolder)
        mCamera?.startPreview()
    }


 fun startPreview(surfaceTexture: SurfaceTexture) {
        open(cameraId)
        this.surfaceTexture = surfaceTexture
        buffer = ByteArray(width * height * 3 / 2)
        bytes = ByteArray(buffer!!.size)
        //数据缓存区
        mCamera?.addCallbackBuffer(buffer)
        mCamera?.setPreviewCallbackWithBuffer(this)
        //设置预览画面
        mCamera?.setPreviewTexture(surfaceTexture)
        mCamera?.startPreview()
    }	
	

拍照片

其实就是调用的api,然后对数据进行矩阵变化、缩放、参数设置,最后保存。

  /**
     * 拍摄照片
     */
    fun takePicture(completedCallBack: () -> Unit, errorCallBack: (Exception) -> Unit) {
        mCamera?.takePicture(null, null, object : Camera.PictureCallback {
            override fun onPictureTaken(data: ByteArray?, camera: Camera?) {
                Log.i("ZZZZ", "isMainThread:" + (Looper.myLooper() == Looper.getMainLooper()))
                previewAlign()

                threadPool.execute {
                    //路径示例 : /storage/emulated/0/Pictures/MyCameraApp/IMG_20230726_135652.jpg
//                    val pictureFile: File = getOutputMediaFile(MEDIA_TYPE_IMAGE)!!
//                    Log.i("ZZZZ", "pictureFile:$pictureFile")
//                    try {
//                        val fos = FileOutputStream(pictureFile)
//                        fos.write(data)
//                        fos.close()
//
//                        /*//修正图片方向,这里只是示例,需要根据实际手机方位来决定图片角度
//                        val exif = ExifInterface(pictureFile.absolutePath)
//                        exif.setAttribute(
//                            ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_ROTATE_270.toString()
//                        )
//                        exif.saveAttributes()*/
//                        completedCallBack.invoke()
//                    } catch (e: FileNotFoundException) {
//                        Log.d(TAG, "File not found: ${e.message}")
//                        errorCallBack.invoke(e)
//                    } catch (e: IOException) {
//                        Log.d(TAG, "Error accessing file: ${e.message}")
//                        errorCallBack.invoke(e)
//                    }

                    val pictureFile: File = getOutputMediaFile(MEDIA_TYPE_IMAGE)!!
                    //路径示例 : /storage/emulated/0/Pictures/MyCameraApp/IMG_20230726_135652.jpg
                    val bitmap = BitmapFactory.decodeByteArray(data, 0, data!!.size)
                    val matrix = Matrix()
                    //修正图片方向,这里只是示例,需要根据实际手机方位来决定图片角度
                    matrix.postRotate(if (cameraId == 1) 270F else 90F)
                    if (cameraId == 1) {
                        //postScale在矩阵变换之后进行缩放
                        matrix.postScale(-1F, 1F, bitmap.width / 2F, bitmap.height / 2F)
                    }
                    val rotatedBitmap: Bitmap = Bitmap.createBitmap(
                        bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true
                    )
                    ImageUtils.save(rotatedBitmap, pictureFile, Bitmap.CompressFormat.JPEG)
                    completedCallBack.invoke()
                }
            }
        })
    }	

设置预览

 private fun setPreviewSize(parameters: Camera.Parameters) {
        //获取摄像头支持的宽、高
        val supportedPreviewSizes = parameters.supportedPreviewSizes
        var size = supportedPreviewSizes[0]
        Log.d(TAG, "Camera支持: " + size.width + "x" + size.height)
        //选择一个与设置的差距最小的支持分辨率
        var m: Int = Math.abs(size.height * size.width - width * height)
        supportedPreviewSizes.removeAt(0)
        val iterator: Iterator<Camera.Size> = supportedPreviewSizes.iterator()
        //遍历
        while (iterator.hasNext()) {
            val next = iterator.next()
            Log.d(TAG, "支持 " + next.width + "x" + next.height)
            val n: Int = Math.abs(next.height * next.width - width * height)
            if (n < m) {
                m = n
                size = next
            }
        }
        width = size.width
        height = size.height
        parameters.setPreviewSize(width, height)
        Log.d(TAG, "预览分辨率 width:" + size.width + " height:" + size.height)
    }

通过MediaRecorder 录像

fun startVideo(holder: SurfaceHolder) {
        mediaRecorder = MediaRecorder()
        //解锁相机,以供 MediaRecorder 使用
        camera?.unlock()
        //设置要用于视频捕获的相机
        mediaRecorder.setCamera(camera)
        //设置音频源
        mediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER)
        //设置视频源
        mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA)
        //设置视频的输出格式和编码
        mediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_1080P))
        //设置输出视频播放的方向
        mediaRecorder.setOrientationHint(270)
        //设置输出文件
        mediaRecorder.setOutputFile(getVideoFilePath(this))
        //指定 SurfaceView 预览布局元素
        mediaRecorder.setPreviewDisplay(holder.surface)

        try {
            mediaRecorder.prepare()
        } catch (e: IOException) {
            e.printStackTrace()
            releaseMediaRecorder()
        }

        Handler().postDelayed({
            try {
                mediaRecorder.start()
            } catch (e: IOException) {
                e.printStackTrace()
                releaseMediaRecorder()
            }
        }, 10)
    }

Demo效果

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

USBCamera 开发,和 MIPI开发完全一样的,方法参数设置也一样。 拿到如下Demo 源码后可以仔细需改参数,适配、定制基本功能了。

Demo下载地址

八、 总结

  • 通用平台底层都支持Camera1 的,那么Camera1 开发USBCamera 相机就能在不同平台通用了
  • 简单的预览-拍照-录像 需求可以使用Camera1 来处理 复杂的效果 性能 还是用Camera2 API 实现
  • Camera1 毕竟是在Android5 及 以前使用,已经废弃了,只是能用。 如果平台支持如RK 平台还是建议用Camera2API, 不支持的情况下可以选择Camera1 API来实现
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

野火少年

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值