十分钟实现 Android Camera2 视频录制

这篇具有很好参考价值的文章主要介绍了十分钟实现 Android Camera2 视频录制。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1. 前言

因为工作中要使用Android Camera2 API,但因为Camera2比较复杂,网上资料也比较乱,有一定入门门槛,所以花了几天时间系统研究了下,并在CSDN上记录了下,希望能帮助到更多的小伙伴。
上两篇文章使用Camera2实现了相机预览和拍照的功能,这篇文章我们接着上文,来实现Camera2视频录制的功能。

2. 前置操作

2.1 声明相机参数和成员变量

首先还是声明相机参数和成员变量,比起前文增加了这些

private var mediaRecorder: MediaRecorder? = null
private var isRecordingVideo: Boolean = false
private val SENSOR_ORIENTATION_DEFAULT_DEGREES = 90
private val SENSOR_ORIENTATION_INVERSE_DEGREES = 270
private val DEFAULT_ORIENTATIONS = SparseIntArray().apply {
    append(Surface.ROTATION_0, 90)
    append(Surface.ROTATION_90, 0)
    append(Surface.ROTATION_180, 270)
    append(Surface.ROTATION_270, 180)
}
private val INVERSE_ORIENTATIONS = SparseIntArray().apply {
    append(Surface.ROTATION_0, 270)
    append(Surface.ROTATION_90, 180)
    append(Surface.ROTATION_180, 90)
    append(Surface.ROTATION_270, 0)
}

完整的需要声明的相机参数和成员变量如下

//后摄 : 0 ,前摄 : 1
private val cameraId = "0"
private val TAG = CameraActivity3::class.java.simpleName
private lateinit var cameraDevice: CameraDevice
private val cameraThread = HandlerThread("CameraThread").apply { start() }
private val cameraHandler = Handler(cameraThread.looper)
private val cameraManager: CameraManager by lazy {
    getSystemService(Context.CAMERA_SERVICE) as CameraManager
}
private val characteristics: CameraCharacteristics by lazy {
    cameraManager.getCameraCharacteristics(cameraId)
}
private lateinit var session: CameraCaptureSession

private lateinit var imageReader: ImageReader

//JPEG格式,所有相机必须支持JPEG输出,因此不需要检查
private val pixelFormat = ImageFormat.JPEG

//imageReader最大的图片缓存数
private val IMAGE_BUFFER_SIZE: Int = 3

//线程池
private val threadPool = Executors.newCachedThreadPool()
private val imageReaderThread = HandlerThread("imageReaderThread").apply { start() }
private val imageReaderHandler = Handler(imageReaderThread.looper)
/** Live data listener for changes in the device orientation relative to the camera */
private lateinit var relativeOrientation: OrientationLiveData

private var mediaRecorder: MediaRecorder? = null
private var isRecordingVideo: Boolean = false
private val SENSOR_ORIENTATION_DEFAULT_DEGREES = 90
private val SENSOR_ORIENTATION_INVERSE_DEGREES = 270
private val DEFAULT_ORIENTATIONS = SparseIntArray().apply {
    append(Surface.ROTATION_0, 90)
    append(Surface.ROTATION_90, 0)
    append(Surface.ROTATION_180, 270)
    append(Surface.ROTATION_270, 180)
}
private val INVERSE_ORIENTATIONS = SparseIntArray().apply {
    append(Surface.ROTATION_0, 270)
    append(Surface.ROTATION_90, 180)
    append(Surface.ROTATION_180, 90)
    append(Surface.ROTATION_270, 0)
}

2.2 添加布局

首先我们需要在XML中添加两个按钮,分别是录制按钮和停止录制按钮

<Button
    android:id="@+id/btn_capture_video"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom|center"
    android:layout_marginRight="16dp"
    android:text="录屏"
    android:visibility="visible"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent" />

<Button
    android:id="@+id/btn_stop_capture"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom|left"
    android:text="停止录屏"
    android:visibility="visible"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent" />

2.3 初始化MediaPlayer

我们需要在打开相机的时候,去初始化mediaRecorder

mediaRecorder = MediaRecorder()

完整代码如下

@SuppressLint("MissingPermission")
private fun openCamera(cameraId: String) {
    cameraManager.openCamera(cameraId, object : CameraDevice.StateCallback() {
        override fun onOpened(camera: CameraDevice) {
            cameraDevice = camera
            mediaRecorder = MediaRecorder()
            startPreview()
        }

        override fun onDisconnected(camera: CameraDevice) {
            this@CameraActivity3.finish()
        }

        override fun onError(camera: CameraDevice, error: Int) {
            Toast.makeText(application, "openCamera Failed:$error", Toast.LENGTH_SHORT).show()
        }
    }, cameraHandler)
}

3. 实现视频录制功能

3.1 关闭原本的Session

因为拍照和录制视频功能不好一起使用,所以需要先调用closePreviewSession,来关闭原来的session

private fun closePreviewSession() {
    session?.close()
}

3.2 给MediaRecorder设置参数

接着,需要调用setUpMediaRecorder()来初始化MediaRecorder
setUpMediaRecorder中,会给mediaRecorder设置很多预置参数

首先获取目标路径

 val nextVideoAbsolutePath = getVideoFilePath(cameraActivity)
 
fun getVideoFilePath(context: Context?): String {
    val filename = "VIDEO_${System.currentTimeMillis()}.mp4"
    val dir = context?.getExternalFilesDir("video")

    return if (dir == null) {
        filename
    } else {
        "${dir.absolutePath}/$filename"
    }
}

然后设置mediaRecorder方向

val sensorOrientation = characteristics?.get(SENSOR_ORIENTATION)
val rotation = cameraActivity.windowManager.defaultDisplay.rotation
when (sensorOrientation) {
    SENSOR_ORIENTATION_DEFAULT_DEGREES ->
        mediaRecorder?.setOrientationHint(DEFAULT_ORIENTATIONS.get(rotation))
    SENSOR_ORIENTATION_INVERSE_DEGREES ->
        mediaRecorder?.setOrientationHint(INVERSE_ORIENTATIONS.get(rotation))
}

最后给meidaRecorder设置若干参数项,这里我们默认给视频尺寸设置成了1920*1080,如果你的设备相机不支持这个分辨率,需要修改一下。

mediaRecorder?.apply {
    setAudioSource(MediaRecorder.AudioSource.MIC)
    setVideoSource(MediaRecorder.VideoSource.SURFACE)
    setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
    setOutputFile(nextVideoAbsolutePath)
    setVideoEncodingBitRate(10000000)
    setVideoFrameRate(30)
    setVideoSize(1920,1080)
    setVideoEncoder(MediaRecorder.VideoEncoder.H264)
    setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
    prepare()
}

再来看下完整的代码

private fun setUpMediaRecorder() {
    val cameraActivity = this

    val nextVideoAbsolutePath = getVideoFilePath(cameraActivity)

    val sensorOrientation = characteristics?.get(SENSOR_ORIENTATION)
    val rotation = cameraActivity.windowManager.defaultDisplay.rotation
    when (sensorOrientation) {
        SENSOR_ORIENTATION_DEFAULT_DEGREES ->
            mediaRecorder?.setOrientationHint(DEFAULT_ORIENTATIONS.get(rotation))
        SENSOR_ORIENTATION_INVERSE_DEGREES ->
            mediaRecorder?.setOrientationHint(INVERSE_ORIENTATIONS.get(rotation))
    }

    mediaRecorder?.apply {
        setAudioSource(MediaRecorder.AudioSource.MIC)
        setVideoSource(MediaRecorder.VideoSource.SURFACE)
        setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
        setOutputFile(nextVideoAbsolutePath)
        setVideoEncodingBitRate(10000000)
        setVideoFrameRate(30)
        setVideoSize(1920,1080) //FIXME 如果你的设备相机不支持这个分辨率,需要修改一下
        setVideoEncoder(MediaRecorder.VideoEncoder.H264)
        setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
        prepare()
    }
}

fun getVideoFilePath(context: Context?): String {
    val filename = "VIDEO_${System.currentTimeMillis()}.mp4"
    val dir = context?.getExternalFilesDir("video")

    return if (dir == null) {
        filename
    } else {
        "${dir.absolutePath}/$filename"
    }
}

3.3 重新创建Session

接着就将binding.surfaceViewrecorderSurface添加到surfaces

val recorderSurface = mediaRecorder!!.surface
val surfaces = ArrayList<Surface>().apply {
    add(binding.surfaceView.holder.surface)
    add(recorderSurface)
}

重新调用cameraDevice?.createCaptureSession,将surfaces传入

cameraDevice?.createCaptureSession(surfaces,
    object : CameraCaptureSession.StateCallback() {

        override fun onConfigured(cameraCaptureSession: CameraCaptureSession) {
            //待实现
        }

        override fun onConfigureFailed(cameraCaptureSession: CameraCaptureSession) {
            Toast.makeText(application, "onConfigureFailed", Toast.LENGTH_SHORT).show()
        }
    }, cameraHandler)

3.4 开始录制

onConfigured调用后,我们执行下面这些代码,主要执行了这些操作

  • cameraCaptureSession赋值给session
  • session?.setRepeatingRequest,这将不断地实时发送视频流,直到会话断开或调用session.stoprepeat()
  • 调用mediaRecorder?.start录制视频
session = cameraCaptureSession

val previewRequestBuilder = cameraDevice!!.createCaptureRequest(TEMPLATE_RECORD).apply {
    addTarget(binding.surfaceView.holder.surface)
    addTarget(recorderSurface)
}
session?.setRepeatingRequest(previewRequestBuilder!!.build(), null, cameraHandler)

isRecordingVideo = true
mediaRecorder?.start()

3.5 录制视频完整代码

binding.btnCaptureVideo.setOnClickListener {
    startRecordingVideo()
}

private fun startRecordingVideo() {
closePreviewSession()
setUpMediaRecorder()

val recorderSurface = mediaRecorder!!.surface
val surfaces = ArrayList<Surface>().apply {
    add(binding.surfaceView.holder.surface)
    add(recorderSurface)
}

cameraDevice?.createCaptureSession(
	    surfaces,
	    object : CameraCaptureSession.StateCallback() {
	
	        override fun onConfigured(cameraCaptureSession: CameraCaptureSession) {
	            session = cameraCaptureSession
	
	            val previewRequestBuilder =
	                cameraDevice!!.createCaptureRequest(TEMPLATE_RECORD).apply {
	                    addTarget(binding.surfaceView.holder.surface)
	                    addTarget(recorderSurface)
	                }
	            session?.setRepeatingRequest(
	                previewRequestBuilder!!.build(),
	                null,
	                cameraHandler
	            )
	
	            isRecordingVideo = true
	            mediaRecorder?.start()
	        }
	
	        override fun onConfigureFailed(cameraCaptureSession: CameraCaptureSession) {
	            Toast.makeText(application, "onConfigureFailed", Toast.LENGTH_SHORT).show()
	        }
	    }, cameraHandler
	)
}

private fun closePreviewSession() {
    session?.close()
}

private fun setUpMediaRecorder() {
    val cameraActivity = this

    val nextVideoAbsolutePath = getVideoFilePath(cameraActivity)

    val sensorOrientation = characteristics?.get(SENSOR_ORIENTATION)
    val rotation = cameraActivity.windowManager.defaultDisplay.rotation
    when (sensorOrientation) {
        SENSOR_ORIENTATION_DEFAULT_DEGREES ->
            mediaRecorder?.setOrientationHint(DEFAULT_ORIENTATIONS.get(rotation))

        SENSOR_ORIENTATION_INVERSE_DEGREES ->
            mediaRecorder?.setOrientationHint(INVERSE_ORIENTATIONS.get(rotation))
    }

    mediaRecorder?.apply {
        setAudioSource(MediaRecorder.AudioSource.MIC)
        setVideoSource(MediaRecorder.VideoSource.SURFACE)
        setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
        setOutputFile(nextVideoAbsolutePath)
        setVideoEncodingBitRate(10000000)
        setVideoFrameRate(30)
        setVideoSize(1920, 1080) //FIXME 如果你的设备相机不支持这个分辨率,需要修改一下
        setVideoEncoder(MediaRecorder.VideoEncoder.H264)
        setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
        prepare()
    }
}

十分钟实现 Android Camera2 视频录制

我们运行程序,点击录制视频,过几秒点击停止录制,然后打开文件管理器,在/sdcard/Android/data/包名/files/video文件夹下,可以看到这个视频了

十分钟实现 Android Camera2 视频录制

4. 停止录制视频

停止录制视频比较简单,只需要释放mediaRecorder
然后再调用startPreview重新开始预览就可以了

private fun stopRecordingVideo() {
    isRecordingVideo = false
    mediaRecorder?.apply {
        stop()
        reset()
    }
    //重新开始预览
    startPreview()
}

5. 实现动态设置分辨率

之前我们这是录制分辨率是写死的1920*1080,这样是不够动态灵活的,接下来我们来实现下动态设置分辨率

首先通过characteristics获取到可用的分辨率列表

val characteristics = manager.getCameraCharacteristics(cameraId)
val map = characteristics.get(SCALER_STREAM_CONFIGURATION_MAP) ?:
        throw RuntimeException("Cannot get available preview/video sizes")

然后通过这个map来选择出最适合的分辨率,这里的选择规则是返回宽高比3:4的分辨率中最高的分辨率

 videoSize = chooseVideoSize(map.getOutputSizes(MediaRecorder::class.java))
 
fun chooseVideoSize(choices: Array<Size>) = choices.firstOrNull {
        it.width == it.height * 4 / 3 } ?: choices[choices.size - 1]

最后,将该分辨率设置到mediaRecorder中就行了

mediaRecorder?.apply {
    setAudioSource(MediaRecorder.AudioSource.MIC)
    setVideoSource(MediaRecorder.VideoSource.SURFACE)
    setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
    setOutputFile(nextVideoAbsolutePath)
    setVideoEncodingBitRate(10000000)
    setVideoFrameRate(30)
    //setVideoSize(1920, 1080)
    setVideoSize(videoSize.width,videoSize.height)
    setVideoEncoder(MediaRecorder.VideoEncoder.H264)
    setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
    prepare()
}

6. 其他

6.1 本文源码下载

下载地址 : Android Camera2 Demo - 实现相机预览、拍照、录制视频功能

6.2 Android Camera2 系列

更多Camera2相关文章,请看
十分钟实现 Android Camera2 相机预览_氦客的博客-CSDN博客
十分钟实现 Android Camera2 相机拍照_氦客的博客-CSDN博客
十分钟实现 Android Camera2 视频录制_氦客的博客-CSDN博客

6.3 Android 相机相关文章

Android 使用CameraX实现预览/拍照/录制视频/图片分析/对焦/缩放/切换摄像头等操作_氦客的博客-CSDN博客
Android 从零开发一个简易的相机App_android开发简易app_氦客的博客-CSDN博客
Android 使用Camera1实现相机预览、拍照、录像_android 相机预览_氦客的博客-CSDN博客文章来源地址https://www.toymoban.com/news/detail-509882.html

到了这里,关于十分钟实现 Android Camera2 视频录制的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 还在苦恼如何开发一个Chrome插件吗?十分钟带你实现一个实用小插件

    你是否曾考虑过创建自己的 Chrome 插件,但又挠头毫无思路?那么在接下来的几分钟里,我不仅会介绍 Chrome 浏览器扩展的基本知识,还会指导你通过五个简单的步骤来制作自己的扩展。 知道怎么做吗?让我们一探究竟! 今年我们见证了人工智能能力的爆炸式增长。虽然cha

    2024年02月10日
    浏览(46)
  • NDK Android平台camera2采集视频

    《Android平台使用camera2采集视频 代码实现 》链接: https://edu.csdn.net/learn/38258/606148?spm=1003.2001.3001.4157 在Android平台开发实时音视频项目,摄像头的采集是一个必不可少的流程;通常在Android平台上采集摄像头数据可以使用Camera1接口、Camera2接口或者CameraX接口。Camera1接口只支持

    2024年02月11日
    浏览(59)
  • 零编程经验,通过 GPT-4 十分钟开发了一个浏览器插件,并成功运行,实现了需求目标!

    大佬蓝鸟ID: sundyme 零编程经验,通过 GPT-4 十分钟开发了一个浏览器插件,并成功运行,实现了需求目标!太不可思意了,真正体会到了自然语言编程的魅力! 下一步是利用Pinterest 的 API 接口实现自动发图,已经生成好了代码和步骤(看着挺靠谱),等明天开发者权限审核下

    2023年04月08日
    浏览(38)
  • 十分钟入门Zigbee

    大部分教程通常都是已Zigbee原理开始讲解和学习,各种概念让初学者难以理解。本教程从一个小白的角度出发,入门无需任何Zigbee底层原理知识,只需要基本的MCU研发经验就可以掌握,让您快速实现zigbee组网和节点之间通信。 本教程采用泰凌微TLSR8258芯片,芯片资料链接TLS

    2023年04月09日
    浏览(62)
  • 每天十分钟学会Spark

    Spark是什么 Spark是一种基于内存的快速、通用、可拓展的大数据分析计算引擎。 Spark官网:http://spark.apache.org/ Spark的特点 1、快速   一般情况下,对于迭代次数较多的应用程序,Spark程序在内存中的运行速度是Hadoop MapReduce运行速度的100多倍,在磁盘上的运行速度是Hadoop MapRedu

    2024年03月18日
    浏览(51)
  • 十分钟掌握Java本地缓存

    —————————— Yesterday is history, tomorrow is a mystery, but today is a gift. That is why it’s called the present. —————————— 缓存是Java开发中经常用到的组件,我们会使用缓存来存储一些 不经常改变 的 热点 数据,提高系统处理效率,其根本原因在于内存和硬盘读写速度的

    2024年02月05日
    浏览(52)
  • 通俗易懂,十分钟读懂DES,详解DES加密算法原理,DES攻击手段以及3DES原理。Python DES实现源码

    DES(Data Encryption Standard)是一种 对称加密 算法。它是在20世纪70年代初期由IBM研发的。它的设计目标是提供高度的数据安全性和性能,并且能够在各种硬件和软件平台上实现。 DES使用56位的密钥和64位的明文块进行加密。DES算法的分组大小是64位,因此,如果需要加密的明文长

    2024年02月03日
    浏览(67)
  • Django入门,十分钟学会登录网页

    我们假定你已经阅读了 安装 Django。你能知道 Django 已被安装,且安装的是哪个版本,通过在命令提示行输入命令 cmd黑窗口运行,不懂cmd百度一下 如果这是你第一次使用 Django 的话,你需要一些初始化设置。也就是说,你需要用一些自动生成的代码配置一个 Django project ——

    2024年01月24日
    浏览(52)
  • 十分钟python入门 正则表达式

    正则常见的三种功能,它们分别是:校验数据的有效性、查找符合要求的文本以及对文本进行切割和替换等操作。 所谓元字符就是指那些在正则表达式中具有特殊意义的专用字符 元字符大致分成这几类:表示单个特殊字符的,表示空白符的,表示某个范围的,表示次数的量

    2024年02月13日
    浏览(44)
  • 十分钟理解回归测试(Regression Testing)

    回归测试是一个系统的质量控制过程,用于验证最近对软件的更改或更新是否无意中引入了新错误或对以前的功能方面产生了负面影响(比如你在家中安装了新的空调系统,发现虽然新的空调系统可以按预期工作,但是本来亮的等却不亮了)。其主要目标是确保旨在改进的修

    2024年02月05日
    浏览(63)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包