如何从YUV420 ByteBuffer创建org.webrtc.JavaI420Buffer修复视频帧失真
问题根源
你当前代码的核心问题是:调用ByteBuffer.slice()时没有为每个平面设置正确的读取上限(limit),仅指定了起始位置,WebRTC底层读取平面数据时可能会读到超出当前平面的冗余数据,进而导致色彩或画面失真。
你给出的参数中所有平面的rowStride等于对应平面的宽度、pixelStride为1,符合I420格式的标准对齐要求,偏移量计算也正确,格式本身没有问题。
修复后的代码
public static I420Buffer convert(ImageProcessResult result) { int frameWidth = result.getWidth(); int frameHeight = result.getHeight(); int rowStrideY = result.getRowStride(0); int rowStrideU = result.getRowStride(1); int rowStrideV = result.getRowStride(2); int offsetY = result.getPlaneOffset(0); int offsetU = result.getPlaneOffset(1); int offsetV = result.getPlaneOffset(2); // 计算每个平面的实际数据长度 int planeSizeY = rowStrideY * result.getPlaneHeight(0); int planeSizeU = rowStrideU * result.getPlaneHeight(1); int planeSizeV = rowStrideV * result.getPlaneHeight(2); ByteBuffer i420ByteBuffer = result.getBuffer(); // 先重置原Buffer的状态,避免之前的操作遗留的position/limit影响 i420ByteBuffer.clear(); // 切Y平面:指定起始位置和结束位置 i420ByteBuffer.position(offsetY); i420ByteBuffer.limit(offsetY + planeSizeY); final ByteBuffer dataY = i420ByteBuffer.slice(); // 切U平面 i420ByteBuffer.position(offsetU); i420ByteBuffer.limit(offsetU + planeSizeU); final ByteBuffer dataU = i420ByteBuffer.slice(); // 切V平面 i420ByteBuffer.position(offsetV); i420ByteBuffer.limit(offsetV + planeSizeV); final ByteBuffer dataV = i420ByteBuffer.slice(); // 重置原Buffer的状态,避免释放时出问题 i420ByteBuffer.clear(); JavaI420Buffer frame = JavaI420Buffer.wrap(frameWidth, frameHeight, dataY, rowStrideY, dataU, rowStrideU, dataV, rowStrideV, () -> { JniCommon.nativeFreeByteBuffer(i420ByteBuffer); }); return frame; }
其他排查点
如果修改后仍有失真,可按以下顺序排查:
- 确认视频处理引擎返回的确实是I420平面格式,而非NV12/NV21半平面格式,半平面格式的UV数据交叉存储,无法直接按当前偏移拆分。
- 确认从引擎拿到的ByteBuffer是直接内存(DirectByteBuffer),如果是堆内存的ByteBuffer,可先复制到直接内存再传给WebRTC,避免JNI层读取堆内存出现异常。
- 验证引擎返回的时间戳是否正确,避免帧错乱导致的画面异常。
内容的提问来源于stack exchange,提问作者Anuj J Pandey
相关产品推荐
相关产品推荐

