如何基于Camera2与MediaCodec API实现视频录制与保存?
嘿,我明白你的困扰——MediaRecorder确实在灵活度上有局限,尤其是需要后期编辑或者要避免丢帧的场景,Camera2+MediaCodec确实是更合适的方案。下面我就针对你要的部分给出具体示例:
使用Camera2 API + MediaCodec实现视频录制(核心片段)
一、Camera2需传递给MediaCodec的关键参数示例
首先,你需要从Camera2的特性中获取适配的视频尺寸和格式,再以此配置MediaCodec的输入参数。核心代码如下:
// 1. 从相机特性中获取支持的视频输出尺寸 CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraId); StreamConfigurationMap configMap = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); Size[] videoSizes = configMap.getOutputSizes(MediaCodec.class); // 自定义方法:挑选最接近需求的尺寸(比如优先1080P,匹配宽高比) Size targetSize = chooseOptimalSize(videoSizes, 1920, 1080); // 2. 配置MediaCodec的视频格式参数 MediaFormat videoFormat = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, targetSize.getWidth(), targetSize.getHeight()); videoFormat.setInteger(MediaFormat.KEY_BIT_RATE, 10 * 1024 * 1024); // 10Mbps码率 videoFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 30); // 30帧每秒 videoFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5); // 每5秒生成一个关键帧 videoFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); // 3. 创建MediaCodec并获取输入Surface,这个Surface会传给Camera2作为录制输出目标 MediaCodec videoCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC); videoCodec.configure(videoFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); Surface codecSurface = videoCodec.createInputSurface(); // 4. 配置Camera2的录制请求,绑定codecSurface CaptureRequest.Builder recordRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD); recordRequestBuilder.addTarget(codecSurface); // 强制锁定帧率为30fps,避免丢帧 recordRequestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, new Range<>(30, 30)); recordRequestBuilder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO);
注:chooseOptimalSize是自定义工具方法,逻辑可以是优先匹配目标宽高比,再选取最接近目标分辨率的尺寸,你可以根据需求调整。
二、MediaCodec的编码实现(核心逻辑)
接下来是处理编码数据并写入文件的核心线程逻辑,这里结合MediaMuxer完成MP4封装:
private String outputFilePath = getExternalFilesDir(null) + "/recorded_video.mp4"; private MediaMuxer muxer; private int videoTrackIndex = -1; private boolean isRecordingStopped = false; private long frameCount = 0; // 初始化MediaMuxer用于封装MP4 muxer = new MediaMuxer(outputFilePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); // 启动MediaCodec编码 videoCodec.start(); // 开启编码处理线程 new Thread(() -> { MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); while (!isRecordingStopped) { int outputBufferId = videoCodec.dequeueOutputBuffer(bufferInfo, 10000); if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { // 编码格式就绪后,添加轨道并启动Muxer MediaFormat encodedFormat = videoCodec.getOutputFormat(); videoTrackIndex = muxer.addTrack(encodedFormat); muxer.start(); } else if (outputBufferId >= 0) { ByteBuffer outputBuffer = videoCodec.getOutputBuffer(outputBufferId); if (bufferInfo.size > 0 && videoTrackIndex != -1) { // 修正时间戳,保证视频播放时序正确 bufferInfo.presentationTimeUs = calculatePresentationTime(frameCount++); muxer.writeSampleData(videoTrackIndex, outputBuffer, bufferInfo); } videoCodec.releaseOutputBuffer(outputBufferId, false); } } // 停止录制后释放资源 videoCodec.stop(); videoCodec.release(); if (videoTrackIndex != -1) { muxer.stop(); muxer.release(); } }).start();
辅助方法:计算正确的帧时间戳(以30fps为例)
private long calculatePresentationTime(long frameIndex) { return frameIndex * 1000000 / 30; // 每帧间隔约33333微秒 }
三、视频保存的关键注意点
- 必须等待
MediaCodec.INFO_OUTPUT_FORMAT_CHANGED回调后再启动MediaMuxer,否则会触发格式错误 - 时间戳计算要精准,否则视频播放会出现跳帧、卡顿或音画不同步(如果后续加音频的话)
- 停止录制时要按顺序释放资源:先标记
isRecordingStopped为true,再停止MediaCodec,最后停止并释放MediaMuxer
如果后续需要添加音频录制,只需要再初始化一个音频MediaCodec,将音频编码数据同样写入MediaMuxer即可,逻辑和视频部分一致。
内容的提问来源于stack exchange,提问作者WebDiva
相关产品推荐
相关产品推荐

