1. 前言
因为工作中要使用Android Camera2 API
,但因为Camera2
比较复杂,网上资料也比较乱,有一定入门门槛,所以花了几天时间系统研究了下,并在CSDN
上记录了下,希望能帮助到更多的小伙伴。
上两篇文章使用Camera2
实现了相机预览和拍照的功能,这篇文章我们接着上文,来实现Camera2
视频录制的功能。
2. 前置操作
2.1 声明相机参数和成员变量
首先还是声明相机参数和成员变量,比起前文增加了这些
private var mediaRecorder: MediaRecorder? = nullprivate var isRecordingVideo: Boolean = falseprivate val SENSOR_ORIENTATION_DEFAULT_DEGREES = 90private val SENSOR_ORIENTATION_INVERSE_DEGREES = 270private 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 ,前摄 : 1private val cameraId = "0"private val TAG = CameraActivity3::class.java.simpleNameprivate lateinit var cameraDevice: CameraDeviceprivate 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: CameraCaptureSessionprivate 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)private lateinit var relativeOrientation: OrientationLiveDataprivate var mediaRecorder: MediaRecorder? = nullprivate var isRecordingVideo: Boolean = falseprivate val SENSOR_ORIENTATION_DEFAULT_DEGREES = 90private val SENSOR_ORIENTATION_INVERSE_DEGREES = 270private 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.rotationwhen (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.surfaceView
和recorderSurface
添加到surfaces
val recorderSurface = mediaRecorder!!.surfaceval 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 = cameraCaptureSessionval previewRequestBuilder = cameraDevice!!.createCaptureRequest(TEMPLATE_RECORD).apply { addTarget(binding.surfaceView.holder.surface) addTarget(recorderSurface)}session?.setRepeatingRequest(previewRequestBuilder!!.build(), null, cameraHandler)isRecordingVideo = truemediaRecorder?.start()
3.5 录制视频完整代码
binding.btnCaptureVideo.setOnClickListener { startRecordingVideo()}private fun startRecordingVideo() {closePreviewSession()setUpMediaRecorder()val recorderSurface = mediaRecorder!!.surfaceval 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() }}
我们运行程序,点击录制视频,过几秒点击停止录制,然后打开文件管理器,在/sdcard/Android/data/包名/files/video
文件夹下,可以看到这个视频了
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博客
来源地址:https://blog.csdn.net/EthanCo/article/details/131418829