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

使用ImageWriter无法将图像传入RecordingSession Surface的问题排查

Camera2 二维码检测+录像整合问题:ImageWriter 传递帧到 MediaRecorder 失败

我正在开发一款基于Camera2的应用,需要同时实现二维码检测和录像功能。由于Camera2无法同时挂载多个输出,我采用ImageReader接收相机帧→ML处理(二维码检测)→ImageWriter传递到MediaRecorder的Surface的流程,把帧处理和录像整合到单个相机输出。

当前实现代码

class VideoPipeline(width: Int,
                    height: Int,
                    format: Int): ImageReader.OnImageAvailableListener {
  private var passThroughImageWriter: ImageWriter? = null

  init {
    imageReader = ImageReader.newInstance(width, height, format, 5)
    imageReader.setOnImageAvailableListener(this, videoThreadHandler)
    setupCamera(imageReader.surface, previewSurface, photoOutputSurface)
  }

  fun startRecording() {
    val recorderSurface = MediaCodec.createPersistentInputSurface()
    val recorder = MediaRecorder(...)
    recorder.setVideoSource(MediaRecorder.VideoSource.SURFACE)
    // 省略MediaRecorder其他配置代码
    recorder.setInputSurface(surface)
    recorder.prepare()
    recorder.start()

    passThroughImageWriter = ImageWriter.newInstance(recorderSurface, 5)
  }
  
  override fun onImageAvailable(reader: ImageReader) {
    val image = reader.acquireLatestImage()
    
    if (passThroughImageWriter != null) {
      // 将Image传递给ImageWriter,无需手动关闭
      passThroughImageWriter?.queueInputImage(image)
    } else {
      // 无录制任务时关闭图像
      image.close()
    }
  }
}

报错信息

VideoPipeline           D  --> writing 1280 x 960 34 frame..
GraphicBufferSource     D  got buffer with new dataSpace #104
GraphicBufferSource     W  released unpopulated slots: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63]
VideoPipeline           D  --> writing 1280 x 960 34 frame..
GraphicBufferSource     W  released unpopulated slots: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63]
RecordingSession        E  MediaRecorder Error: 268435556 (-1007)
RecordingSession        I  Stopping RecordingSession..
VideoPipeline           D  --> writing 1280 x 960 34 frame..
GraphicBufferSource     W  released unpopulated slots: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63]
MediaRecorder           E  stop failed: -1007
AndroidRuntime          D  Shutting down VM
AndroidRuntime          E  FATAL EXCEPTION: main
                            Process: com.mrousavy.camera.example, PID: 6406
                            java.lang.RuntimeException: stop failed.
                              at android.media.MediaRecorder.stop(Native Method)
                              at com.mrousavy.camera.core.RecordingSession.stop(RecordingSession.kt:103)
                              at com.mrousavy.camera.core.RecordingSession._init_$lambda$0(RecordingSession.kt:75)
                              at com.mrousavy.camera.core.RecordingSession.$r8$lambda$enBPf4dA0r-MTevnd6ChW75ZCDg(Unknown Source:0)
                              at com.mrousavy.camera.core.RecordingSession$$ExternalSyntheticLambda0.onError(Unknown Source:2)
                              at android.media.MediaRecorder$EventHandler.handleMessage(MediaRecorder.java:1615)
                              at android.os.Handler.dispatchMessage(Handler.java:106)
                              at android.os.Looper.loopOnce(Looper.java:201)
                              at android.os.Looper.loop(Looper.java:288)
                              at android.app.ActivityThread.main(ActivityThread.java:7872)
                              at java.lang.reflect.Method.invoke(Native Method)
                              at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
                              at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936)

已尝试ImageFormat.YUV_420_888、ImageFormat.PRIVATE、PixelFormat.RGB888三种格式,均失败。之前用OpenGL管线将画面流传输到MediaRecorder的Surface完全正常,因此MediaRecorder配置没问题,问题出在ImageWriter的使用逻辑上。


排查思路与解决方案

1. 严格匹配格式与数据空间

MediaRecorder的Surface仅支持特定格式,尤其是使用VideoSource.SURFACE时,必须与相机输出的格式、dataSpace完全对齐。可通过Image.getFormat()和Image.getPlanes()[0].getBuffer()验证传递给ImageWriter的帧属性是否符合要求,避免跨格式直接传递。

2. 调整ImageWriter初始化时机

当前先启动MediaRecorder再初始化ImageWriter,会导致MediaRecorder无法提前感知输入Surface的格式信息。应先初始化ImageWriter,再调用MediaRecorder的prepare()和start():

fun startRecording() {
  val recorderSurface = MediaCodec.createPersistentInputSurface()
  // 先初始化ImageWriter
  passThroughImageWriter = ImageWriter.newInstance(recorderSurface, 5)
  
  val recorder = MediaRecorder(...)
  recorder.setVideoSource(MediaRecorder.VideoSource.SURFACE)
  // 省略其他配置
  recorder.setInputSurface(recorderSurface)
  recorder.prepare()
  recorder.start()
}

3. 确保时间戳连续有效

Camera2输出的帧带有时间戳,直接传递时需保证时间戳递增且符合MediaRecorder要求。可通过Image.getTimestamp()获取时间戳,在queueInputImage前调用ImageWriter.setTimestamp()(API 26+),避免因时间戳异常触发报错。

4. 统一缓冲区队列配置

报错中的released unpopulated slots提示缓冲区队列异常,建议将ImageReader和ImageWriter的缓冲区数量设为相同值(比如都设为3),同时确保相机输出帧速率与MediaRecorder配置一致,避免队列溢出或空转。

5. 避免直接传递PRIVATE格式帧

ImageFormat.PRIVATE是厂商自定义格式,可能无法直接通过ImageWriter传递给MediaRecorder。建议将PRIVATE格式帧转换为YUV_420_888格式后再传递,或直接让Camera2输出YUV_420_888格式的帧。


内容的提问来源于stack exchange,提问作者mrousavy

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 07:37:00