You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Camera2+MediaCodec相机流编码:无需SurfaceView仅用编码器Surface方案问询

解决方案:仅用MediaCodec输入Surface实现Camera2流编码

针对你遇到的核心问题,下面给出具体调整方案和代码修改要点:

核心问题原因分析

  1. 仅编码器Surface无输出:你错误使用了MediaCodec.createPersistentInputSurface(),这个Surface并未与当前配置的编码器实例绑定,导致相机输出的帧无法被编码器正确接收处理;同时相机CaptureRequest的模板选择和配置也存在适配问题。
  2. 依赖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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 15:12:31