开发Android Camera—使用Kotlin语言,完成第一个自定义相机

2023-05-16

对于首次使用Kotlin语言开发,在网上苦于寻找不到Kotlin语言编写的相机代码,故写下这篇博客。
好了,咱们进入主题

在Android 5.0(SDK 21)中,Google使用Camera2替代了Camera接口。Camera2在接口和架构上做了巨大的变动,但是基于众所周知的原因,我们还必须基于 Android 4.+ 系统进行开发。本文介绍的是Camera接口开发及其使用方法,通过本文章,你将全面地学会Camera接口的开发流程。

开发步骤

  • 添加相机相关权限
  • 通过Camera.open(int)获得一个相机实例,也可以使用默认的Camera.open()
  • 利用camera.parameters得到相机实例的默认设置Camera.parameters
  • 如果需要的话,修改Camera.parameters并调用camera.parameters=Camera.Parameters来修改相机设置
  • 调用camera.setDisplayOrientation(int)来设置正确的预览方向
  • 调用camera.setPreviewDisplay(SurfaceHolder)来设置预览,如果没有这一步,相机是无法开始预览的
  • 调用camera.startPreview()来开启预览,对于拍照,这一步是必须的
  • 在需要的时候调用camera.takePicture(Camera.ShutterCallback, Camera.PictureCallback, Camera.PictureCallback, Camera.PictureCallback)来拍摄照片
  • 拍摄照片后,相机会停止预览,如果需要再次拍摄多张照片,需要再次调用camera.startPreview()来重新开始预览
  • 调用camera.stopPreview()来停止预览
  • 一定要在onPause()的时候调用camera.release()来释放camera,在onResume中重新开始camera

相机权限

在使用相机进行拍照,需要添加以下权限:

<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />

拍摄的照片需要存储到内存卡的话,还需要内存卡读写的权限:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

拍照注意事项

根据拍照步骤就可以使用相机进行拍照,但是在使用过程中,有诸多需要注意的地方。

开启相机

开启相机直接调用Camera.open(),理论上就可以得到一个相机实例。但是在开启相机前,最好check一下。虽然目前基本上所有的手机都是前后摄像头,但是也不排斥有些奇葩的手机,只有后摄像头,或者干脆没有摄像头的。所有在open一个camera前,先检查是否存在该id的camera。Camera.getNumberOfCameras()可以获得当前设备的Camera的个数N,N为0表示不支持摄像头,否则对应的Camera的ID就是0—(N-1)。

检查是否支持相机:

    private fun isCameraAvailable(): Boolean {
        val numberOfCameras = Camera.getNumberOfCameras()
        if (numberOfCameras != 0) {
            return true
        }
        return false
    }

打开相机:

   private fun getCamera(): Camera? {
        var camera: Camera? = null
        try {
            camera = Camera.open()
        } catch (e: Exception) {
            e.printStackTrace()
        }

        return camera
    }

相机设置

Camera的Parameters提供了诸多属性,可以对相机进行多种设置,包括预览大小及格式、照片大小及格式、预览频率、拍摄场景、颜色效果、对焦方式等等,具体设置可参考官方手册。
拍照尤其需要注意的是对预览大小、照片大小以及对焦方式的设置。
在对预览大小、照片大小及对焦方式设置时,设置的值必须是当前设备所支持的。否则,预览大小和照片大小设置会无效,对焦方式设置会导致崩溃。它们都有相应的方法,获取相应的支持的列表。对应的依次为getSupportedPictureSizes(),getSupportedPictureSizes(),getSupportedFocusModes()。

相机设置代码:

private fun setParameters(camera: Camera?) {
        camera?.let {
            val params: Camera.Parameters = camera.parameters
            params.pictureFormat = ImageFormat.JPEG
            val size = Collections.max(params.supportedPictureSizes, object : Comparator<Camera.Size> {
                override fun compare(lhs: Camera.Size, rhs: Camera.Size): Int {
                    return lhs.width * lhs.height - rhs.width * rhs.height
                }
            })
            params.setPreviewSize(size.width, size.height);  
            params.focusMode = Camera.Parameters.FOCUS_MODE_AUTO
            try {
                camera.parameters = params;    //在设置属性时,如果遇到未支持的大小时将会直接报错,故需要捕捉一样,做异常处理
            } catch (e: Exception) {
                e.printStackTrace()
                try {
                    //遇到上面所说的情况,只能设置一个最小的预览尺寸
                    params.setPreviewSize(1920, 1080);
                    camera.parameters = params;
                } catch (e1: Exception) {
                    //到这里还有问题,就是拍照尺寸的锅了,同样只能设置一个最小的拍照尺寸
                    e1.printStackTrace();
                    try {
                        params.setPictureSize(1920, 1080);
                        camera.parameters = params;
                    } catch (ignored: Exception) {}}}
              }
    }

相机预览方向和预览View的设置

不对相机预览方向和应用方向设置,通常情况下得到的预览结果是无法接受的。一般应用设置的方向为固定竖向,预览设置旋转90度即可。严谨点来说,预览方向的设置是根据当前window的rotation来设置的,即((WindowManager)displayView.getContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getRotation()的值。在Surface.ROTATION_0和Surface.ROTATION_180时,Camera设置displayOrientation为90,否则设置为0。
相机预览前,必须调用camera.setPreviewDisplay(SurfaceHolder)来设置预览的承载。SurfaceHolder一般取自SurfaceView、SurfaceTexture、TextureView等。一般情况下,如果不对显示的View大小做合理的设置,预览中的场景都会被变形。

添加SurfaceHolder的Callback监听

 val holder = sv_camera.holder
        holder?.addCallback(object : SurfaceHolder.Callback {
            override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) {
            }

            override fun surfaceDestroyed(holder: SurfaceHolder?) {
            }

            override fun surfaceCreated(holder: SurfaceHolder?) {
                setStartPreview(mCamera, holder);
            }
        })

设置预览的方向:

  private fun setStartPreview(camera: Camera?, holder: SurfaceHolder?) {
        camera?.let {
            try {
                camera.setPreviewDisplay(holder)
                camera.setDisplayOrientation(Surface.ROTATION_90)
                camera.startPreview()
            } catch (e: IOException) {
                e.printStackTrace()
            }
        }
    }

拍照监听及图片处理

相机拍照时在预览时,调用takePicture(Camera.ShutterCallback, Camera.PictureCallback, Camera.PictureCallback, Camera.PictureCallback)(或其重载方法)来实现拍照的,其中第一个参数表示图像捕获时刻的回调。可以看到此方法的后三个参数类型是一样的,都是图像回调。分别表示原始图数据回调、展示图像数据的回调、JPEG图像数据的回调。图像回调中得到byte数组decode为image后,图片的方向都是摄像头原始的图像方向。
可以通过parameters.setRotation(int)来改变最后一个回调中图像数据的方向。个人推荐不设置,直接在回调中利用矩阵变换统一处理。因为利用parameters.setRotation(int)来旋转图像,在不同手机上会有差异。
与预览View设置类似,pictureSize设置的值,影响了最后的拍照结果,处理时需要对拍照的结果进行裁剪,使图片结果和在可视区域预览的结果相同。前摄像头拍摄的结果还需要做对称变换,以保证“所见即所得”。

拍照监听:

    private fun taskPicture() {
        mCamera?.autoFocus { success, camera ->
            run {
                mCamera?.takePicture(null, null, pictureCallback);
            }
        }
    }

图片处理:

 val pictureCallback = Camera.PictureCallback { data, camera ->
        val pictureFile = File(Environment.getExternalStorageDirectory(), "gaozhongkui-" + System.currentTimeMillis() + ".jpg")
        try {
            val fos = FileOutputStream(pictureFile)
            fos.write(data)
            fos.close();
        } catch (e: java.lang.Exception) {
            e.printStackTrace()
        }
    }

以下是完成的代码实例:

xml布局:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">


    <SurfaceView
        android:id="@+id/sv_camera"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <Button
        android:id="@+id/btn_capture"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="拍照" />
</FrameLayout>

代码逻辑:

import android.graphics.ImageFormat
import android.hardware.Camera
import android.os.Bundle
import android.os.Environment
import android.support.v7.app.AppCompatActivity
import android.view.Surface
import android.view.SurfaceHolder
import kotlinx.android.synthetic.main.activity_main.*
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.util.*
import kotlin.Comparator

class MainActivity : AppCompatActivity() {

    var mCamera: Camera? = null
    val pictureCallback = Camera.PictureCallback { data, camera ->
        val pictureFile = File(Environment.getExternalStorageDirectory(), "gaozhongkui-" + System.currentTimeMillis() + ".jpg")
        try {
            val fos = FileOutputStream(pictureFile)
            fos.write(data)
            fos.close();
        } catch (e: java.lang.Exception) {
            e.printStackTrace()
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        if (isCameraAvailable()) {
            mCamera = getCamera()
            setParameters(mCamera);
        }
        initViewListener();

    }

    private fun  initViewListener(){
        val holder = sv_camera.holder
        holder?.addCallback(object : SurfaceHolder.Callback {
            override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) {
            }

            override fun surfaceDestroyed(holder: SurfaceHolder?) {
            }

            override fun surfaceCreated(holder: SurfaceHolder?) {

                setStartPreview(mCamera, holder);
            }

        })
        btn_capture.setOnClickListener {
            taskPicture()
        }
    }

    private fun isCameraAvailable(): Boolean {
        val numberOfCameras = Camera.getNumberOfCameras()
        if (numberOfCameras != 0) {
            return true
        }
        return false
    }


    private fun getCamera(): Camera? {
        var camera: Camera? = null
        try {
            camera = Camera.open()
        } catch (e: Exception) {
            e.printStackTrace()
        }

        return camera
    }

    fun releaseCamera() {
        mCamera?.setPreviewCallback(null)
        mCamera?.stopPreview()
        mCamera?.release()
        mCamera = null
    }


    private fun setStartPreview(camera: Camera?, holder: SurfaceHolder?) {
        camera?.let {
            try {
                camera.setPreviewDisplay(holder)
                camera.setDisplayOrientation(Surface.ROTATION_90)
                camera.startPreview()
            } catch (e: IOException) {
                e.printStackTrace()
            }
        }
    }

    private fun setParameters(camera: Camera?) {
        camera?.let {
            val params: Camera.Parameters = camera.parameters
            params.pictureFormat = ImageFormat.JPEG

            val size = Collections.max(params.supportedPictureSizes, object : Comparator<Camera.Size> {
                override fun compare(lhs: Camera.Size, rhs: Camera.Size): Int {
                    return lhs.width * lhs.height - rhs.width * rhs.height
                }
            })
            params.setPreviewSize(size.width, size.height);

            params.focusMode = Camera.Parameters.FOCUS_MODE_AUTO
            try {
                camera.parameters = params;
            } catch (e: Exception) {
                e.printStackTrace()
                try {
                    //遇到上面所说的情况,只能设置一个最小的预览尺寸
                    params.setPreviewSize(1920, 1080);
                    camera.parameters = params;
                } catch (e1: Exception) {
                    //到这里还有问题,就是拍照尺寸的锅了,同样只能设置一个最小的拍照尺寸
                    e1.printStackTrace();
                    try {
                        params.setPictureSize(1920, 1080);
                        camera.parameters = params;
                    } catch (ignored: Exception) {
                    }
                }

            }
        }
    }

    private fun taskPicture() {
        mCamera?.autoFocus { success, camera ->
            run {
                mCamera?.takePicture(null, null, pictureCallback);
            }
        }
    }
}

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

开发Android Camera—使用Kotlin语言,完成第一个自定义相机 的相关文章

随机推荐

  • 1分钟教会你二进制撩妹(汉)读心术

    近些年来 xff0c 小魔发现 xff0c 对于年轻的男女而言 xff0c 一些传统的节日似乎都变成了情人节或者脱单节 xff0c 就连 光棍节 xff0c 实际上很多人都是抱着节前或者是当天脱单而过的 双11 光棍节 即将来临 xff0c
  • VLAN基础配置及Access接口、Trunk接口、Hybrid接口

    文章说明 xff1a 1 本文使用的软件是eNSP 2 退到下一层用命令quit xff0c 简写q 3 部分命令使用的是简写 和完整命令一样的作用 xff0c 按键盘上的 34 Tab 34 建可自动补全命令 用于查询的命令 xff1a
  • c++pthread多线程消费者问题

    分布式选修课上讲了多线程编程 xff0c 布置了一个生产者消费者的作业 xff0c 觉得挺有意思 xff0c 并且网络上的消费者问题多使用c语言编写 xff0c 故在此记录c 43 43 解决方法 由于是消费者线程各自计数 xff0c 故使
  • 红包动画

    lt DOCTYPE html gt lt html lang 61 34 en 34 data dpr 61 34 1 34 style 61 34 font size 32px 34 gt lt head gt lt meta char
  • Android中<xliff:g></xliff:g>的用法

    在查阅修改Android源码的过程中经常能在字符串资源中看到类似下面的标签 xff1a span class hljs tag lt span class hljs title resources span span class hljs
  • vue单文件组件的格式规范

    lt template gt lt template gt lt script gt export default name 39 39 mixins components props data return computed watch
  • Linux命令行安装weblogic12c

    Linux命令行安装weblogic12c 一 安装jdk 若已安装可跳过 1 Oracle官网下载jdk linux安装包 2 卸载linux系统中自带的jdk 使用rpm qa grep java查询出系统自带的jdk xff1b 使用
  • Ubuntu 远程免密码登录设置

    我们正常使用 ssh 远程登录服务器进行操作 xff0c 需要输入用户名 服务器ip以及密码 xff0c 当我们需要同时管理多个服务器的时候 xff0c 每次都需要重复输入这些东西会显得特别麻烦和浪费时间 xff0c 因此我们可以通过配置密
  • Mybatis resultMap启动时报错:Could not resolve type alias userResultMap Cannot find class: userResultMap

    MyBatis中在查询进行select映射的时候 xff0c 返回类型可以用resultType xff0c 也可以用resultMap xff0c resultType是直接 表示返回类型的 xff0c 而resultMap则是对外部Re
  • synchronized-锁总结

    目录 一 相关知识点 1 1 对象头 1 2 锁相关概念 1 3 查看对象头工具 二 锁流程 2 1 加锁 xff1a monitorenter 2 2 释放锁 xff1a monitorexit 一 相关知识点 在 JDK1 6 之前 x
  • 软件工程师校招面试救急包

    LeetCode牛人总结 xff08 手撕代码前看看 xff0c 抱佛脚 xff09 https github com labuladong fucking algorithm blob master README md 剑指offer x
  • 微信SDK中含有的支付功能怎么去掉?

    一 说在前面的话 这两天遇到一个特别让我DT的问题 xff0c 估计大家通过标题就能知道问题了 没错 xff0c 就是在应用中集成了微信SDK后 xff0c 它自动支持了微信分享 登录 收藏 支付等功能 这一点没啥 xff0c TM的关键点
  • Android中图片的镂空效果(不规则图形的镂空)

    一 说在前面的话 我们在做新手引导时 xff0c 经常会遇到凸显某一块功能时需求 xff0c 类似于下图 xff1a 看到这个功能点可能会有点头大 xff0c 不过好在Android为我们提供一个美好的工具 xff1a PorterDuff
  • 利用三层交换机实现VLAN间路由

    原理概述 xff1a VLAN将一个物理的LAN在逻辑上划分成多个广播域 VLAN内的主机间可以直接通信 xff0c 而VLAN间不能直接互通 在现实网络中 xff0c 经常会遇到需要跨VLAN相互访问的情况 xff0c 工程师通常会选择一
  • Failed to resolve attribute at index 6: TypedValue{t=0x2/d=0x7f0400cd a=7 r=0x7f06006e}

    今天在开发时 xff0c 遇到一个程序的Bug xff0c 记录一下 在Dialog中展示一个布局 xff0c 布局中包含了TextView报了下面的错误 xff0c 最开始还以为是颜色设置的问题 xff0c 反复核对了一下颜色设置并没有问
  • TextView的TextColor中使用selector的问题

    在TextView中如果设置选中 点击 获取焦点时 xff0c 文字颜色发生改变时 xff0c 一般我们会通过代码中设置 不过Android给我们提供了一个更简洁的方式 xff0c 就是通过selector去改变 只需要在Res目录下创建一
  • 彻底理解Java中堆和栈的区别

    1 概述 在Java中 xff0c 内存分为两部分 xff0c 一种是堆内存 xff0c 另一种就是栈内存 2 Java中变量在内存中的分配 1 类变量 static修饰的变量 xff1a 在程序加载时系统就为它在堆中开辟了内存 xff0c
  • 深入了解多线程的原理

    说在前面的话 使用多线程的目的 在多个CPU核心下 xff0c 多线程的好处是显而易见的 xff0c 不然多个CPU核心只跑一个线程其他的核心就都浪费了即便不考虑多核心 xff0c 在单核下 xff0c 多线程也是有意义的 xff0c 因为
  • An operation is not implemented: not implemented被坑之路[Kotlin]

    吐槽一下 xff0c 程序猿的辛酸史 今天在开发新项目时 xff0c 首次使用了Kotlin语言 xff0c 对于之前只在纸上谈兵 xff0c 未在项目中使用过的码农 xff0c 很是一脸懵逼 但是迫于公司的要求 xff0c 只能硬头皮冲吧
  • 开发Android Camera—使用Kotlin语言,完成第一个自定义相机

    对于首次使用Kotlin语言开发 xff0c 在网上苦于寻找不到Kotlin语言编写的相机代码 xff0c 故写下这篇博客 好了 xff0c 咱们进入主题 在Android 5 0 xff08 SDK 21 xff09 中 xff0c Go