如何从Bitmap创建YUV_420格式android.media.Image模拟Camera API
Android端Mock Camera API对接ImageReader的可行实现
你之前尝试的两个方案失败基本都是踩了格式对齐、帧有效性标记的坑,下面是经过实际验证的落地方案,不需要修改上层Camera数据消费逻辑,完全保留原有数据源。
常见失败原因先排查
- 手动构建YUV缓冲区失败:绝大多数情况是没有匹配
Image对象各个Plane的行步长(rowStride)、像素步长(pixelStride)对齐规则,不同设备上ImageReader分配的内存对齐要求存在差异,整块直接写入字节会出现花屏、帧被丢弃甚至native崩溃。 - MediaCodec解码复制帧失败:核心是MediaCodec输出Image的格式、色彩空间和ImageWriter初始化声明的格式不匹配,另外漏写帧时间戳、数据有效性标记也会导致帧被系统直接丢弃,上层acquire不到有效数据。
方案1:ImageWriter直连(适配静态JPEG/固定测试帧场景,稳定性最高)
不需要手动做YUV转换,直接对接业务侧的ImageReader即可:
- 初始化ImageWriter时,直接传入业务层使用的ImageReader持有的Surface,第三个参数必须和ImageReader配置的图像格式完全一致,队列深度不要小于ImageReader配置的最大缓存帧数:
// 示例:对接消费JPEG数据的ImageReader ImageWriter mockWriter = ImageWriter.newInstance( businessImageReader.getSurface(), 5, // 队列深度,和ImageReader保持一致即可 ImageFormat.JPEG // 必须和ImageReader的format完全匹配 );
- 投递JPEG测试帧时,直接把JPEG字节流写入Image的Plane即可,不需要转码:
// 读取本地测试JPEG文件 byte[] testJpegData = Files.readAllBytes(testJpegFile.toPath()); Image inputImage = mockWriter.dequeueInputImage(); // JPEG格式只有1个Plane,直接写入数据 ByteBuffer planeBuffer = inputImage.getPlanes()[0].getBuffer(); planeBuffer.clear(); planeBuffer.put(testJpegData); // 必须设置纳秒级时间戳,为0的帧会被判定为无效直接丢弃 inputImage.setTimestamp(System.nanoTime()); // 入队后上层即可通过acquireLatestImage()获取到该帧 mockWriter.queueInputImage(inputImage);
如果需要投递YUV_420_888格式的预览帧,不要手动写YUV转换逻辑,用系统自带的
YuvImage将JPEG转成NV21字节数组,再按照每个Plane的步长偏移分别填充Y、U、V三个通道的数据,不要整块内存直接拷贝,填充完成后记得调用ByteBuffer的rewind()方法把指针移到起始位置。
方案2:MediaCodec零拷贝对接(适配视频源连续帧模拟场景)
不要手动拷贝MediaCodec输出的Image数据,直接走Surface链路零传输,彻底规避格式不匹配问题:
- 初始化MediaCodec解码器时,不要使用ByteBuffer输出模式,直接配置为Surface输出模式,输出Surface直接传入上一步初始化好的mockWriter对应的Surface。
- 将MediaCodec的输出色彩格式配置为
COLOR_FormatYUV420Flexible,和Camera预览输出的格式保持一致。 - 正常启动解码、喂入视频数据即可,MediaCodec解码完成的帧会自动通过ImageWriter投递到业务侧的ImageReader,全程不需要手动操作Image对象,不会出现格式兼容问题。
注意给MediaCodec配置和真实Camera一致的输出帧率,避免上层做帧间隔计算、丢帧逻辑判断时出现异常。
方案3:依赖隔离方案(适配端到端自动化测试场景)
如果是跑instrumentation E2E测试,不需要在业务代码里嵌入mock逻辑:
- 在androidTest目录下实现虚拟CameraDevice、虚拟CameraCaptureSession类,虚拟Session内部直接持有绑定业务ImageReader Surface的ImageWriter,按测试节奏投递测试帧即可。
- 通过测试框架的Hook能力替换系统CameraManager的实例,所有Camera相关的系统调用都会走到你的虚拟实现中,业务代码零修改,测试环境和生产环境完全隔离。
必看踩坑点
- 所有入队的Image必须设置合法的纳秒级时间戳,不能为0,不然会被系统直接丢弃。
- ImageWriter的队列深度不要小于ImageReader的最大缓存帧数,不然会出现无提示丢帧。
- 帧投递线程要和真实Camera的回调线程保持一致,不要在UI线程投递,避免触发业务侧的线程校验逻辑。
内容的提问来源于stack exchange,提问作者Adjorno
相关产品推荐
相关产品推荐

