开发人脸检测相机应用:如何用CameraSource结合MediaRecorder录制视频?
我之前也碰到过这个问题——CameraSource把底层的Camera实例封装得严严实实,没法直接传给MediaRecorder.setCamera(),确实挺头疼的。下面给你几个可行的解决方案,从临时应急到长期适配的都有,你可以根据需求选:
这个方法不依赖CameraSource的内部实现,通过监听它的预览回调获取每一帧数据,再用MediaCodec手动编码成视频,是最稳妥的方案。
步骤拆解:
给CameraSource设置预览回调
初始化CameraSource时,调用setPreviewCallback来获取每一帧的YUV格式数据:CameraSource cameraSource = new CameraSource.Builder(context, detector) .setRequestedPreviewSize(1280, 720) .setRequestedFps(30.0f) .setAutoFocusEnabled(true) .build(); cameraSource.setPreviewCallback(new Camera.PreviewCallback() { @Override public void onPreviewFrame(byte[] data, Camera camera) { // 这里拿到每一帧的YUV数据,传给编码器 encodeFrame(data, camera.getParameters().getPreviewSize()); } });初始化MediaCodec编码器
配置视频编码参数,比如H.264格式,指定分辨率、帧率、比特率等:private MediaCodec mediaCodec; private MediaMuxer mediaMuxer; private int videoTrackIndex = -1; private boolean isRecording = false; private void initEncoder(int width, int height) throws IOException { MediaFormat format = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, width, height); format.setInteger(MediaFormat.KEY_BIT_RATE, 2 * 1024 * 1024); // 2Mbps format.setInteger(MediaFormat.KEY_FRAME_RATE, 30); format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5); format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible); mediaCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC); mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); mediaCodec.start(); // 初始化MediaMuxer写入文件 String outputPath = getExternalFilesDir(null) + "/recorded_video.mp4"; mediaMuxer = new MediaMuxer(outputPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); }编码并写入帧数据
在encodeFrame方法里处理每一帧,将YUV数据传给编码器,再把编码后的数据包写入MediaMuxer:private void encodeFrame(byte[] data, Size previewSize) { if (!isRecording) return; int inputBufferIndex = mediaCodec.dequeueInputBuffer(-1); if (inputBufferIndex >= 0) { ByteBuffer inputBuffer = mediaCodec.getInputBuffer(inputBufferIndex); inputBuffer.clear(); inputBuffer.put(data); long presentationTimeUs = System.nanoTime() / 1000; mediaCodec.queueInputBuffer(inputBufferIndex, 0, data.length, presentationTimeUs, 0); } MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0); while (outputBufferIndex >= 0) { ByteBuffer outputBuffer = mediaCodec.getOutputBuffer(outputBufferIndex); // 第一次拿到数据时,添加视频轨道 if (videoTrackIndex == -1) { MediaFormat format = mediaCodec.getOutputFormat(); videoTrackIndex = mediaMuxer.addTrack(format); mediaMuxer.start(); } // 写入数据到文件 if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) { bufferInfo.size = 0; } if (bufferInfo.size != 0) { outputBuffer.position(bufferInfo.offset); outputBuffer.limit(bufferInfo.offset + bufferInfo.size); mediaMuxer.writeSampleData(videoTrackIndex, outputBuffer, bufferInfo); } mediaCodec.releaseOutputBuffer(outputBufferIndex, false); outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0); } }停止录制的收尾处理
private void stopRecording() { isRecording = false; mediaCodec.signalEndOfInputStream(); // 等待编码器处理完剩余数据 MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 10000); while (outputBufferIndex >= 0) { // 处理剩余输出 mediaCodec.releaseOutputBuffer(outputBufferIndex, false); outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0); } mediaCodec.stop(); mediaCodec.release(); mediaMuxer.stop(); mediaMuxer.release(); videoTrackIndex = -1; }
如果不想自己处理编码,可以通过反射拿到CameraSource内部的mCamera字段,直接传给MediaRecorder.setCamera()。但要注意:这个方法依赖CameraSource的内部实现,Google后续更新库可能会改掉这个字段名,兼容性没保障,适合临时测试或者特定版本场景。
代码示例:
private Camera getCameraFromCameraSource(CameraSource cameraSource) throws Exception { Field cameraField = CameraSource.class.getDeclaredField("mCamera"); cameraField.setAccessible(true); return (Camera) cameraField.get(cameraSource); } // 使用方法 try { Camera camera = getCameraFromCameraSource(cameraSource); MediaRecorder mediaRecorder = new MediaRecorder(); // 配置MediaRecorder的参数(音频、视频源、输出格式等) mediaRecorder.setCamera(camera); mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); mediaRecorder.setOutputFile(getExternalFilesDir(null) + "/recorded.mp4"); mediaRecorder.setPreviewDisplay(surfaceHolder.getSurface()); // 需要关联预览Surface mediaRecorder.prepare(); mediaRecorder.start(); } catch (Exception e) { e.printStackTrace(); }
com.google.android.gms.vision里的CameraSource其实是比较旧的API了,Google现在更推荐用CameraX(Jetpack组件)来处理相机逻辑,配合ML Kit人脸检测,不仅原生支持视频录制,API更稳定,兼容性也更好,不用再折腾这些封装问题。
大致步骤:
引入CameraX和ML Kit的依赖到build.gradle:
dependencies { def camerax_version = "1.3.0" implementation "androidx.camera:camera-core:$camerax_version" implementation "androidx.camera:camera-camera2:$camerax_version" implementation "androidx.camera:camera-lifecycle:$camerax_version" implementation "androidx.camera:camera-video:$camerax_version" // ML Kit人脸检测 implementation "com.google.mlkit:face-detection:16.1.5" }用CameraX的
VideoCapture组件实现视频录制,同时绑定ML Kit的人脸检测器:
CameraX的预览和录制可以同时进行,人脸检测可以通过分析器(ImageAnalysis)来处理每一帧,完全不用关心底层相机实例的问题。
内容的提问来源于stack exchange,提问作者Sayed Shalan

