共计 5636 个字符,预计需要花费 15 分钟才能阅读完成。
前情提要
在上一篇文章中,放了一张音视频学习路线图,前面我会尽量依照这个路线图进行博客更新,首先咱们来谈一谈输出,在 Android 上的输出源无非是本地已有的音视频文件或者通过音视频设施 (相机 录音) 采集的原生数据。本章咱们来剖析如何在 Android 上通过 Camera 以及录音设施采集数据。
Camera
在 Android 上的视频采集设施无疑就是 Camera 了,在 Android SDK API21 之前的版本只能应用 Camera1,在 API 21 之后 Camera1 曾经被标记为Deprecated ,Google 举荐应用 Camera2, 上面咱们来别离看一下
Camera1
咱们先来看一下 Camera1 体系的局部类图
Camera 类是 Camera1 体系的外围类,该类还有好多外部类,关上 Camera、设置参数、设置预览等都是应用该类的 API, 上面咱们来看应用 Camera API 关上零碎照相机的流程。
有了下面的流程图之后,咱们上代码
// 启动 Camera
private 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) {return@setPreviewCallback}
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() ?: return@setOnImageAvailableListener
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,录音的流程绝对于视频而言要简略许多,一样的,咱们先来看一下简略类图
就一个类,API 也简单明了,咱们来看一下流程
上面上代码
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