小工具知识网

当前位置: 首页 > 行业知识 > Android 使用Camera1实现相机预览、拍照、录像

行业知识

Android 使用Camera1实现相机预览、拍照、录像

2026-11-04 15:01:09 来源:互联网转载

1. 前言

本文介绍如何从零开始,在Android中实现Camera1的接入,并在文末提供Camera1Manager工具类,可以用于快速接入Camera1。

Android Camera1 API虽然已经被Google废弃,但有些场景下不得不使用。

并且Camera1返回的帧数据是NV21,不像Camera2、CameraX那样,需要自己再转一层,才能得到NV21。

Camera1的API调用也比Camera2简单不少,和CameraX的简单程度差不多,所以在一定的场景下,Camera1还是有其用途的。

2. 前置操作

2.1 添加权限

在AndroidManifest中添加如下权限

 

2.2 申请权限

别忘了申请权限

ActivityCompat.requestPermissions(    this@WelComeActivity,    arrayOf(        android.Manifest.permission.WRITE_EXTERNAL_STORAGE,        android.Manifest.permission.RECORD_AUDIO,        android.Manifest.permission.CAMERA    ),    123)

2.3 声明XML布局

新建一个Activity,在其XML中声明SurfaceView

    

3. 实现预览功能

3.1 添加SurfaceView的回调

binding.surfaceView.holder.addCallback(surfaceCallback)private var surfaceCallback: SurfaceHolder.Callback = object : SurfaceHolder.Callback {// Surface创建时    override fun surfaceCreated(holder: SurfaceHolder) {    }// Surface改变时    override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {    }// Surface销毁时    override fun surfaceDestroyed(holder: SurfaceHolder) {    }}

3.2 打开相机

当Surface创建时,也就是在surfaceCreated的时候,打开相机

private var camera: Camera? = nullprivate fun openCamera(holder: SurfaceHolder) {    try {        camera = Camera.open(cameraId)    } catch (e: Exception) {        e.printStackTrace()    }}

3.3 开始预览

当我们打开相机后,就可以开始预览了

这里首先将设置camera1预览的尺寸,一般来说,通过camera!!.parameters.supportedPreviewSizes获取到的列表中,第一项就是最推荐的尺寸了。

private fun setPreviewSize() {    //获取摄像头支持的宽、高    val supportedPreviewSizes: List = camera!!.parameters.supportedPreviewSizes    supportedPreviewSizes.forEach {        Log.i("ZZZZ", "${it.width}*${it.height}")    }    val parameters = camera?.parameters    val size = supportedPreviewSizes[0]    parameters?.setPreviewSize(size.width, size.height)    camera?.setParameters(parameters)}

接着,将SurfaceHolder设置到camera中。setPreviewDisplay接受一个SurfaceHolder对象作为参数,该对象表示预览显示的表面。通过调用setPreviewDisplay方法,可以将相机的预览数据输出到指定的表面对象上,从而在预览界面中显示出相机的拍摄画面。

camera?.setPreviewDisplay(holder)

接着调用setDisplayOrientation方法来设置相机的预览方向。该方法接受一个参数,即预览方向的度数。例如,如果要在竖直模式下使用相机,而默认的预览方向是水平的,那么就可以通过调用setDisplayOrientation方法将预览方向顺时针旋转90度。

camera?.setDisplayOrientation(90)

最后,调用startPreview()就可以启动相机的预览了

camera?.startPreview()

来看一下完整代码

private fun startPreview(holder: SurfaceHolder) {    try {        setPreviewSize()        camera?.setPreviewDisplay(holder)        camera?.setDisplayOrientation(90)        camera?.startPreview()    } catch (e: IOException) {        e.printStackTrace()    }}

3.4 效果如下

4. 实现拍照功能

4.1 调用拍照接口

要进行拍照,调用camera.takePicture即可,它共有3个回调参数

  • ShutterCallback shutter(捕获图片瞬间的回调):快门回调是在拍照时快门按下的瞬间调用的回调。它允许您在拍照时执行一些自定义操作,例如触发闪光灯或显示自定义的拍照界面。
  • PictureCallback raw(原始图像数据回调):原始图像数据回调是在拍照后,获取到原始未压缩的数据时调用的回调。您可以在这个回调中对图像数据进行处理或保存。
  • PictureCallback jpeg(JPEG图像数据回调):JPEG图像数据回调是在拍照后,获取到图像的JPEG格式数据时调用的回调。您可以在这个回调中对JPEG图像数据进行处理或保存。

    这里我们只需要用到jpeg回调

    private val threadPool = Executors.newCachedThreadPool()binding.btnTakePicture.setOnClickListener {    camera?.takePicture(    null,null,{ data, camera ->            //jpeg回调        })}

    4.2 在jpeg回调中保存图片

    //MediaFileUtils类详见本文附录val pictureFile: File = MediaFileUtils.getOutputMediaFile(MEDIA_TYPE_IMAGE)!!try {    val fos = FileOutputStream(pictureFile)    fos.write(data)    fos.close()} 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)}

    来查看下效果,可以看到图片已经被保存了,但是图片的方向目前是有问题的。

    4.3 解决图片保存的方向问题

    所以,我们需要先将图片转成bitmap,旋转角度后,再保存

    修改代码为如下代码

    //路径示例 : /storage/emulated/0/Pictures/MyCameraApp/IMG_20230726_135652.jpgval pictureFile: File = MediaFileUtils.getOutputMediaFile(MediaFileUtils.MEDIA_TYPE_IMAGE)!!val bitmap = BitmapFactory.decodeByteArray(data, 0, data.size)val matrix = Matrix()matrix.postRotate(270F)val rotatedBitmap: Bitmap = Bitmap.createBitmap(    bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)//ImageUtils需要依赖 implementation 'com.blankj:utilcodex:1.31.1'ImageUtils.save(rotatedBitmap, pictureFile, Bitmap.CompressFormat.JPEG)

    来看一下效果,可以看到现在图片方向是对了,但是图片左右的内容是相反的

    4.4 解决图片保存镜像问题

    要解决图片的镜像问题,就调用一下matrix.postScale左右水平变换就好了

    matrix.postScale(-1F, 1F, bitmap.width / 2F, bitmap.height / 2F)

    完整代码如下

    val pictureFile: File =MediaFileUtils.getOutputMediaFile(MediaFileUtils.MEDIA_TYPE_IMAGE)!!//路径示例 : /storage/emulated/0/Pictures/MyCameraApp/IMG_20230726_135652.jpgval bitmap = BitmapFactory.decodeByteArray(data, 0, data.size)val matrix = Matrix()matrix.postRotate(270F)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需要依赖 implementation 'com.blankj:utilcodex:1.31.1'ImageUtils.save(rotatedBitmap, pictureFile, Bitmap.CompressFormat.JPEG)

    5. 实现录像功能

    要录制视频,需要使用MediaRecorder,若要使用 Camera1 拍摄视频,需要谨慎管理 Camera 和 MediaRecorder,并且必须按特定顺序调用相应方法。您必须遵循以下顺序,才能使您的应用正常工作:

    • 打开相机。
    • 做好准备,并开始预览(如果您的应用会显示正在录制的视频,而通常情况下都是如此)。
    • 通过调用 Camera.unlock() 解锁相机,以供 MediaRecorder 使用。
    • 通过在 MediaRecorder 上调用以下方法来配置录制:
      • 通过 setCamera(camera) 关联您的 Camera 实例。
      • 调用 setAudioSource(MediaRecorder.AudioSource.CAMCORDER)。
      • 调用 setVideoSource(MediaRecorder.VideoSource.CAMERA)。
      • 调用 setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_1080P)) 以设置质量。
      • 调用 setOutputFile(getOutputMediaFile(MEDIA_TYPE_VIDEO).toString())。
      • 如果您的应用提供视频预览,请调用 setPreviewDisplay(preview?.holder?.surface)。
      • 调用 setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)。
      • 调用 setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT)。
      • 调用 setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT)。
      • 调用 prepare() 以完成 MediaRecorder 配置。
      • 如需开始录制,请调用 MediaRecorder.start()。
      • 如需停止录制,请按以下顺序调用以下方法:
        • 调用 MediaRecorder.stop()。
        • (可选)通过调用 MediaRecorder.reset() 移除当前的 MediaRecorder 配置。
        • 调用 MediaRecorder.release()。
        • 通过调用 Camera.lock() 锁定相机,以便将来的 MediaRecorder 会话可以使用它。
        • 如需停止预览,请调用 Camera.stopPreview()。
        • 最后,如需释放 Camera 以供其他进程使用,请调用 Camera.release()。

          具体可以见 Camera1 录制视频

          下面直接附上代码,直接如下代码就好了

          5.1 开始录制

          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)}fun getVideoFilePath(context: Context?): String {    val filename = "VIDEO_${System.currentTimeMillis()}.mp4"    val dir = context?.getExternalFilesDir("video")    return "${dir!!.path}/$filename"}

          5.2 停止播放

          fun stopVideo() {    mediaRecorder.stop()    mediaRecorder.release()    camera?.lock()}

          5.3 释放资源

          fun releaseMediaRecorder() {    if (mediaRecorder != null) {        mediaRecorder.reset() // 清除配置        mediaRecorder.release()        //mediaRecorder = null        camera?.lock()    }}

          6. CameraHelper工具类

          可以直接使用这个工具类,来快速接入Camera1

          class CameraHelper(    private val activity: AppCompatActivity,    private var cameraId: Int,    private var width: Int = 720,    private var height: Int = 1280,) : Camera.PreviewCallback {    private var surfaceHolder: SurfaceHolder? = null    private var surfaceTexture: SurfaceTexture? = null    private var mCamera: Camera? = null    private var buffer: ByteArray? = null    private var bytes: ByteArray? = null    /**     * 打开相机     *     * @param cameraId 后摄 Camera.CameraInfo.CAMERA_FACING_BACK     *                 前摄 Camera.CameraInfo.CAMERA_FACING_FRONT     */    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)    }    /**     * 切换摄像头     * @param cameraId 指定摄像头ID     */    fun switchCamera(cameraId: Int) {        this.cameraId = cameraId        previewAlign()    }    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()    }    private val threadPool = Executors.newCachedThreadPool()    /**     * 拍摄照片     */    fun takePicture(completedCallBack: () -> Unit, errorCallBack: (Exception) -> Unit) {        mCamera?.takePicture(null, null, object : Camera.PictureCallback {            override fun onPictureTaken(data: ByteArray?, camera: Camera?) {                previewAlign()                threadPool.execute {                    val pictureFile: File = getOutputMediaFile(MEDIA_TYPE_IMAGE)!!                    val bitmap : Bitmap                    try {                        //路径示例 : /storage/emulated/0/Pictures/MyCameraApp/IMG_20230726_135652.jpg                        bitmap = BitmapFactory.decodeByteArray(data, 0, data!!.size)                    }catch (e:Exception){                        errorCallBack.invoke(e)                        return@execute                    }                    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                    )                    //需要依赖"com.blankj:utilcodex:1.31.1"                    ImageUtils.save(rotatedBitmap, pictureFile, Bitmap.CompressFormat.JPEG)                    completedCallBack.invoke()                }            }        })    }    override fun onPreviewFrame(data: ByteArray, camera: Camera?) {        onPreviewListener?.onPreviewFrame(data, camera)        camera!!.addCallbackBuffer(data)    }    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 = 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  {                degrees = 0                mOnChangedSizeListener?.onChanged(height, width)            }            Surface.ROTATION_90 -> {                degrees = 90                mOnChangedSizeListener?.onChanged(width, height)            }            Surface.ROTATION_180 -> {                degrees = 180                mOnChangedSizeListener?.onChanged(height, width)            }            Surface.ROTATION_270 -> {                degrees = 270                mOnChangedSizeListener?.onChanged(width, height)            }        }        var result: Int        if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {            result = (info.orientation + degrees) % 360            result = (360 - result) % 360 // compensate the mirror        } else { // back-facing            result = (info.orientation - degrees + 360) % 360        }        //设置角度, 参考源码注释        mCamera!!.setDisplayOrientation(result)    }    private lateinit var mediaRecorder: MediaRecorder    private val handle = Handler(Looper.getMainLooper())    /**     * 开始录像     */    fun startVideo(path: String) {        mediaRecorder = MediaRecorder()        //解锁相机,以供 MediaRecorder 使用        mCamera?.unlock()        //设置要用于视频捕获的相机        mediaRecorder.setCamera(mCamera)        //设置音频源        mediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER)        //设置视频源        mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA)        //设置视频的输出格式和编码        mediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH))        //设置输出视频播放的方向,这里只是示例,需要根据实际手机方位来决定角度        mediaRecorder.setOrientationHint(if (cameraId == 1) 270 else 90)        //设置输出文件        mediaRecorder.setOutputFile(path)        //指定 SurfaceView 预览布局元素        mediaRecorder.setPreviewDisplay(surfaceHolder!!.surface)        try {            mediaRecorder.prepare()        } catch (e: IOException) {            e.printStackTrace()            releaseMediaRecorder()        }        handle.postDelayed({            try {                mediaRecorder.start()            } catch (e: IOException) {                e.printStackTrace()                releaseMediaRecorder()            }        }, 10)    }    /**     * 释放资源     */    fun releaseMediaRecorder() {        if (mediaRecorder != null) {            mediaRecorder.reset() // 清除配置            mediaRecorder.release()            //mediaRecorder = null            mCamera?.lock()        }    }    /**     * 停止录像     */    fun stopVideo() {        mediaRecorder.stop()        mediaRecorder.release()        mCamera?.lock()    }    interface OnChangedSizeListener {        fun onChanged(width: Int, height: Int)    }    interface OnPreviewListener {        fun onPreviewFrame(data: ByteArray, camera: Camera?)    }    private var onPreviewListener: OnPreviewListener? = null    /**     * 设置预览监听     */    fun setOnPreviewListener(listener: OnPreviewListener) {        this.onPreviewListener = listener    }    companion object {        private const val TAG = "CAMERA_HELPER"    }}

          进行使用

          //这里的Activity是横屏的class MainActivity : AppCompatActivity(), SurfaceHolder.Callback {    private lateinit var binding: ActivityMainBinding    private lateinit var cameraHelper: CameraHelper    private val cameraId = 1    private val nativeLib = NativeLib()    override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)        binding = ActivityMainBinding.inflate(layoutInflater)        setContentView(binding.root)        ActivityCompat.requestPermissions(            this,            arrayOf(                Manifest.permission.CAMERA,                Manifest.permission.RECORD_AUDIO,                Manifest.permission.WRITE_EXTERNAL_STORAGE            ),            1        )        nativeLib.load(assets, 0, 0)        cameraHelper = CameraHelper(this, cameraId,1920,1080)        cameraHelper.setOnPreviewListener(object : CameraHelper.OnPreviewListener {            override fun onPreviewFrame(data: ByteArray, camera: Camera?) {                //预览回调            }        })        binding.surfaceView.holder.addCallback(this)        binding.btnTakePicture.setOnClickListener {            cameraHelper.takePicture({                 //拍照成功            },{                //拍照失败            })        }        binding.btnVideoCapture.setOnClickListener {            //开始录制            val path = MediaFileUtils.getOutputMediaFile(MediaFileUtils.MEDIA_TYPE_VIDEO)!!.path            cameraHelper.startVideo(path)            //cameraHelper.stopVideo() //结束录制        }    }    override fun surfaceCreated(holder: SurfaceHolder) {    //开始预览        cameraHelper.startPreview(holder)    }    override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {    }    override fun surfaceDestroyed(holder: SurfaceHolder) {    //停止预览        cameraHelper.stopPreview()    }}

          7. 附录

          7.1 MediaFileUtils

          获取媒体文件路径的工具类

          object MediaFileUtils {    val MEDIA_TYPE_IMAGE = 1    val MEDIA_TYPE_VIDEO = 2    /** Create a file Uri for saving an image or video */    fun getOutputMediaFileUri(type: Int): Uri {        return Uri.fromFile(getOutputMediaFile(type))    }    /** Create a File for saving an image or video */    fun getOutputMediaFile(type: Int): File? {        // To be safe, you should check that the SDCard is mounted        // using Environment.getExternalStorageState() before doing this.        val mediaStorageDir = File(            Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),            "MyCameraApp"        )        // This location works best if you want the created images to be shared        // between applications and persist after your app has been uninstalled.        // Create the storage directory if it does not exist        mediaStorageDir.apply {            if (!exists()) {                if (!mkdirs()) {                    Log.d("MyCameraApp", "failed to create directory")                    return null                }            }        }        // Create a media file name        val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date())        return when (type) {            MEDIA_TYPE_IMAGE -> {                File("${mediaStorageDir.path}${File.separator}IMG_$timeStamp.jpg")            }            MEDIA_TYPE_VIDEO -> {                File("${mediaStorageDir.path}${File.separator}VID_$timeStamp.mp4")            }            else -> null        }    }}

          7.2. 本文源码下载

          Android Camera1 Demo - 实现预览、拍照、录制视频功能

上一篇:ps怎么格栅化?

下一篇:Dedecms视频发布教程,轻松上手,视频上传步骤详解