Android音视频 - Camera+AudioRecord采集数据
前情提要
在上一篇文章中,放了一张音视频学习路线图,后面我会尽量按照这个路线图进行博客更新,首先我们来谈一谈输入,在Android上的输入源无非是本地已有的音视频文件或者通过音视频设备(相机 录音)采集的原生数据。本章我们来分析如何在Android上通过Camera以及录音设备采集数据。
Camera
在Android上的视频采集设备无疑就是Camera了,在Android SDK API21之前的版本只能使用Camera1 ,在 API 21之后Camera1已经被标记为Deprecated ,Google 推荐使用Camera2,下面我们来分别看一下
Camera1
我们先来看一下Camera1体系的部分类图
Camera类是Camera1体系的核心类,该类还有好多内部类,打开Camera、设置参数、设置预览等都是使用该类的API,下面我们来看使用Camera API打开系统照相机的流程。
有了上面的流程图之后,我们上代码
//启动Cameraprivate fun setUpCamera() { //【1】 获取Camera Id val id = getCurrentCameraId() try { //【2】 根据Camera Id 获取Camera 实例 cameraInstance = getCameraInstance(id) } catch (e: IllegalAccessError) { Log.e(TAG, "Camera not found") return } //【3】 调用Camera API 设置Camera参数 val parameters = cameraInstance!!.parameters if (parameters.supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) { parameters.focusMode = Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE } cameraInstance!!.parameters = parameters //【4】 调用Camera API 设置预览Surface surfaceHolder?.let { cameraInstance!!.setPreviewDisplay(it) } //【5】 调用Camera API设置预览回调 cameraInstance!!.setPreviewCallback { data, camera -> if (data == null || camera == null) { [email protected] } val size = camera.parameters.previewSize onPreviewFrame?.invoke(data, size.width, size.height) } //【6】 调用Camera API开启预览 cameraInstance!!.startPreview() }
上面代码中的【3】【4】【5】【6】都是调用Camera类的API来完成,我们来看【1】【2】的具体步骤
/***获取Camera Id*/ private fun getCurrentCameraId(): Int { val cameraInfo = Camera.CameraInfo() //遍历所有的Camera id,比较CameraInfo facing for (id in 0 until Camera.getNumberOfCameras()) { Camera.getCameraInfo(id, cameraInfo) if (cameraInfo.facing == cameraFacing) { return id } } return 0 }/***获取Camera 实例*/ private fun getCameraInstance(id: Int): Camera { return try { //调用Camera的open函数获取Camera的实例 Camera.open(id) } catch (e: Exception) { throw IllegalAccessError("Camera not found") } }/***释放Camera*/ private fun releaseCamera() { cameraInstance!!.setPreviewCallback(null) cameraInstance!!.release() cameraInstance = null }
经过上面的流程之后,Camera的预览会显示在传入的Surface上,并且在Camera停止前会一直回调函数onPreviewFrame(byte[] data,Camera camera)
,其中byte[] data中存储的就是实时的YUV图像数据,关于YUV图像数据我们在Android音视频开篇的时候就已经说过了,我们知道YUV也有很多格式,byte[] data的格式是YUV格式中的NV21
Camera2
在Andorid SDK API 21之后呢,Google就推荐使用Camera2体系来管理设备,Camera2还是与Camera1有很大的不同的。
一样的,我们先来看一下Camera2体系的部分类图
Camera2要比Camera1复杂的多,CameraManager CameraCaptureSession是Camera2体系的核心类,CameraManager 用来管理摄像头的打开和关闭 Camera2 引入了CameraCaptureSession来管理拍摄会话。
我们下面来看一下更详细的流程图
同样的上代码:
private fun setUpCamera() { //【1】获取Camera Id val cameraId = getCameraId(cameraFacing) ?: return try { //【2】打开Camera,传入的 CameraDeviceCallback()是摄像机设备状态回调 cameraManager.openCamera(cameraId, CameraDeviceCallback(), null) } catch (e: CameraAccessException) { Log.e(TAG, "Opening camera (ID: $cameraId) failed.") } } //设备状态回调 private inner class CameraDeviceCallback : CameraDevice.StateCallback() { override fun onOpened(camera: CameraDevice) { cameraInstance = camera //【3】开启拍摄会话 startCaptureSession() } override fun onDisconnected(camera: CameraDevice) { camera.close() cameraInstance = null } override fun onError(camera: CameraDevice, error: Int) { camera.close() cameraInstance = null } }
PS
ImageRender可以直接访问呈现在Surface上得图像数据,ImageRender的工作原理是创建实例并设置回调,这个回调会在ImageRender所关联的Surface上的图像可用时调用
//【3】开启拍摄会话 private fun startCaptureSession() { val size = chooseOptimalSize() //创建ImageRender并设置回调 imageReader = ImageReader.newInstance(size.width, size.height, ImageFormat.YUV_420_888, 2).apply { setOnImageAvailableListener({ reader -> val image = reader?.acquireNextImage() ?: [email protected] onPreviewFrame?.invoke(image.generateNV21Data(), image.width, image.height) image.close() }, null) } try { if (surfaceHolder == null) { //设置ImageRender的surface给cameraInstance,以便后面预览的时候数据呈现到ImageRender的surface,从而触发ImageRender的回调 cameraInstance?.createCaptureSession( listOf(imageReader!!.surface), //【4】CaptureStateCallback是CameraCaptureSession的内部类,是摄像机会话状态的回调 CaptureStateCallback(), null ) } else { cameraInstance?.createCaptureSession( listOf(imageReader!!.surface, surfaceHolder!!.surface), CaptureStateCallback(), null ) } } catch (e: CameraAccessException) { Log.e(TAG, "Failed to start camera session") } } //摄像机会话状态的回调 private inner class CaptureStateCallback : CameraCaptureSession.StateCallback() { override fun onConfigureFailed(session: CameraCaptureSession) { Log.e(TAG, "Failed to configure capture session.") } //摄像机配置完成 override fun onConfigured(session: CameraCaptureSession) { cameraInstance ?: return captureSession = session //设置预览CaptureRequest.Builder val builder = cameraInstance!!.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW) builder.addTarget(imageReader!!.surface) surfaceHolder?.let { builder.addTarget(it.surface) } try { //开启会话 session.setRepeatingRequest(builder.build(), null, null) } catch (e: CameraAccessException) { Log.e(TAG, "Failed to start camera preview because it couldn't access camera", e) } catch (e: IllegalStateException) { Log.e(TAG, "Failed to start camera preview.", e) } } }
/** *【1】 获取Camera Id */ private fun getCameraId(facing: Int): String? { return cameraManager.cameraIdList.find { id -> cameraManager.getCameraCharacteristics(id).get(CameraCharacteristics.LENS_FACING) == facing } }
我们分析了上面的Camera采集数据,完整的代码请看文末的Github 地址
AudioRecord
上面分析完了视频,我们接着来看音频,录音API我们使用AudioRecord,录音的流程相对于视频而言要简单许多,一样的,我们先来看一下简单类图
下面上代码
public void startRecord() { //开启录音 mAudioRecord.startRecording(); mIsRecording = true; //开启新线程轮询 ExecutorService executorService = Executors.newSingleThreadExecutor(); executorService.execute(new Runnable() { @Override public void run() { byte[] buffer = new byte[DEFAULT_BUFFER_SIZE_IN_BYTES]; while (mIsRecording) { int len = mAudioRecord.read(buffer, 0, DEFAULT_BUFFER_SIZE_IN_BYTES); if (len > 0) { byte[] data = new byte[len]; System.arraycopy(buffer, 0, data, 0, len); //处理data } } } }); } public void stopRecord() { mIsRecording = false; mAACMediaCodecEncoder.stopEncoder(); mAudioRecord.stop(); }
AudioRecord生成的 byte[] data即PCM音频数据
小结
本章我们对音视频的原生输入API进行了详细的介绍,这个也是我们后面博客的基础,有了YUV和PCM数据之后,就可以编码了,下一篇我们再来分析MediaCodec,用MediaCodec对原生音视频数据进行硬编码生成Mp4.
Camera
AudioRecord
更多相关文章
- 抖音数据采集教程,Android群控黑盒调用,Sekiro使用手册
- Android开发指南!带你全面解析Android框架体系架构view篇,已拿offe
- [Android]使用Spring for Android改善数据交互流程
- Android 设备兼容
- 打造android ORM框架opendroid(七)——数据库升级方案
- 基于AOA协议实现Android设备的USB通信
- 解决AndroidStudio中AVD虚拟机设备空间不足,调试过程出现的黑屏问