Camera2+MediaCodec相机流编码:无需SurfaceView仅用编码器Surface方案问询
解决方案:仅用MediaCodec输入Surface实现Camera2流编码
针对你遇到的核心问题,下面给出具体调整方案和代码修改要点:
核心问题原因分析
- 仅编码器Surface无输出:你错误使用了
MediaCodec.createPersistentInputSurface(),这个Surface并未与当前配置的编码器实例绑定,导致相机输出的帧无法被编码器正确接收处理;同时相机CaptureRequest的模板选择和配置也存在适配问题。 - 依赖SurfaceView才能工作:
TEMPLATE_RECORD模板默认可能期望有可见预览Surface触发帧捕获,或者你的编码器初始化时序存在问题,添加SurfaceView后间接修正了这一问题。
具体代码修改步骤
1. 修正MediaCodec输入Surface的创建方式
删除MediaCodec.createPersistentInputSurface(),改用编码器配置后生成的专属输入Surface(必须在configure之后创建):
try { MediaFormat mediaFormat = new MediaFormatConfigurationBuilder("video/avc") .setSize(width, height) .setFrameRate(54) .setBitrate(BIT_RATE) .setIFrameInterval(2) .build(); videoCodec = MediaCodec.createEncoderByType("video/avc"); videoCodec.setCallback(new CameraStream(this)); // 配置编码器 videoCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); // 创建与当前编码器绑定的输入Surface Surface encoderSurface = videoCodec.createInputSurface(); HandlerThread bThread = new HandlerThread("Camera-Stream"); bThread.start(); Handler handler = new Handler(bThread.getLooper()); videoCodec.start(); cameraManager.openCamera( cameraId, new CameraDevice.StateCallback() { @Override public void onOpened(@NonNull CameraDevice camera) { System.out.println("Opening camera"); // 仅传入编码器Surface,无需UISurface startStreamingCamera(camera, encoderSurface); } @Override public void onDisconnected(@NonNull CameraDevice camera) { encoderSurface.release(); videoCodec.release(); bThread.quitSafely(); } @Override public void onError(@NonNull CameraDevice camera, int error) { encoderSurface.release(); videoCodec.release(); bThread.quitSafely(); } }, handler ); } catch (CameraAccessException | IOException e) { throw new RuntimeException(e); }
2. 简化相机CaptureSession配置
修改startStreamingCamera方法,移除对UISurface的依赖,改用TEMPLATE_PREVIEW模板(更适合后台无预览场景):
private void startStreamingCamera(CameraDevice camera, Surface encoderSurface) { System.out.println("Streaming camera..."); try { CameraCaptureSession.StateCallback captureSessionStateCallback = new CameraCaptureSession.StateCallback() { @Override public void onConfigured(@NonNull CameraCaptureSession session) { try { // 使用TEMPLATE_PREVIEW替代RECORD,适配后台无预览场景 CaptureRequest.Builder captureRequestBuilder = camera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); captureRequestBuilder.addTarget(encoderSurface); // 添加自动对焦、曝光等基础配置 captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); session.setRepeatingRequest(captureRequestBuilder.build(), null, null); } catch (CameraAccessException e) { e.printStackTrace(); } } @Override public void onConfigureFailed(@NonNull CameraCaptureSession session) { throw new RuntimeException("Failed to configure capture session"); } }; // 仅传入编码器Surface创建会话 camera.createCaptureSession(Collections.singletonList(encoderSurface), captureSessionStateCallback, null); } catch (CameraAccessException e) { throw new RuntimeException(e); } }
3. 完善MediaCodec回调实现
确保CameraStream回调正确处理编码器输出,尤其是初始的CSD数据(SPS/PPS,服务器解码必需):
private class CameraStream extends MediaCodec.Callback { private Context context; public CameraStream(Context context) { this.context = context; } @Override public void onInputBufferAvailable(@NonNull MediaCodec codec, int index) { // 输入Surface模式下此方法不会触发,无需处理 } @Override public void onOutputBufferAvailable(@NonNull MediaCodec codec, int index, @NonNull MediaCodec.BufferInfo info) { ByteBuffer buffer = codec.getOutputBuffer(index); if (buffer != null) { if ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) { // 处理SPS/PPS配置数据,发送至服务器 byte[] csdData = new byte[info.size]; buffer.get(csdData); // TODO: 实现CSD数据发送逻辑 } else if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) == 0) { // 处理普通编码帧,发送至服务器 byte[] frameData = new byte[info.size]; buffer.get(frameData); // TODO: 实现帧数据发送逻辑 } codec.releaseOutputBuffer(index, false); } } @Override public void onError(@NonNull MediaCodec codec, @NonNull MediaCodec.CodecException e) { e.printStackTrace(); } @Override public void onOutputFormatChanged(@NonNull MediaCodec codec, @NonNull MediaFormat format) { // 编码器格式变化,可在此获取SPS/PPS ByteBuffer sps = format.getByteBuffer("csd-0"); ByteBuffer pps = format.getByteBuffer("csd-1"); // TODO: 保存或发送SPS/PPS至服务器 } }
4. 后台服务运行注意事项
- 申请必要权限:
CAMERA、FOREGROUND_SERVICE、INTERNET,Android 12+还需POST_NOTIFICATIONS。 - 将相机逻辑放在前台服务中,必须创建前台通知,避免被系统杀死。
- 确保相机尺寸和格式为设备实际支持:通过
CameraCharacteristics获取支持的预览尺寸和输出格式,禁止硬编码。
关于ImageReader报错的补充
你之前用ImageReader报错错误码3(CAMERA_ERROR_INVALID_PARAMETER),是因为ImageReader的尺寸或格式与相机不兼容。正确做法是从相机特性中获取适配的参数:
CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraId); StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); Size[] supportedSizes = map.getOutputSizes(ImageFormat.YUV_420_888); // 选择与编码器一致的尺寸 Size targetSize = supportedSizes[0]; ImageReader reader = ImageReader.newInstance(targetSize.getWidth(), targetSize.getHeight(), ImageFormat.YUV_420_888, 5);
内容的提问来源于stack exchange,提问作者user3529460
相关产品推荐
相关产品推荐

