Kotlin中UVC FileOutputStream写入失败:IOException: EINVAL(无效参数)
问题场景
在Android平台使用FileOutputStream将相机流帧写入UVC缓冲区,设备UVC驱动正常且搭载自定义内核。通过ImageAnalyzer以24帧/秒获取YUV_420_888格式帧,构建UVC帧头后写入/dev/video3时,持续触发EINVAL(无效参数)错误。
相关代码片段
1. 获取相机帧
imageAnalyzer = ImageAnalysis.Builder() .setTargetAspectRatio(screenAspectRatio) .setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_YUV_420_888) // 省略其他配置 imageAnalysis.setAnalyzer(cameraExecutor) { image -> val buffer = image.planes[0].buffer val data = buffer.toByteArray() // 后续帧头构建与写入逻辑 }
2. 构建UVC帧头
val header = ByteBuffer.allocate(26) val frameSize = image.width * image.height * ImageFormat.getBitsPerPixel(image.format) / 8 val EOH = 0x01 val ERR = 0x00 val STI = 0x01 val REST = 0x00 val SRC = 0x00 val PTS = (System.currentTimeMillis() - referenceTime) * 10000 val endOfFrame = 0x01 val FID = (frameId).toByte() // 写入帧头参数 header.putInt(frameSize) header.putShort(image.width.toShort()) header.putShort(image.height.toShort()) header.put(image.format.toByte()) header.put(((EOH shl 7) or (ERR shl 6) or (STI shl 5) or (REST shl 4) or SRC).toByte()) header.putLong(PTS) header.put(endOfFrame.toByte()) header.put(FID)
3. 写入UVC设备节点
val uvcFileOutputStream = FileOutputStream("/dev/video3", true) uvcFileOutputStream.write(header.toByteArray() + data) uvcFileOutputStream.close()
错误日志
java.io.IOException: write failed: EINVAL (Invalid argument) at libcore.io.IoBridge.write(IoBridge.java:654) at java.io.FileOutputStream.write(FileOutputStream.java:401) at java.io.FileOutputStream.write(FileOutputStream.java:379)
问题排查与修正建议
1. 帧头字节序不匹配
UVC规范或自定义驱动通常要求小端字节序,但ByteBuffer默认采用大端序。直接写入的int/short类型参数会被驱动解析为错误值,触发无效参数错误。
- 修正:在分配ByteBuffer后设置字节序:
val header = ByteBuffer.allocate(26).order(ByteOrder.LITTLE_ENDIAN)
2. 帧数据不完整
当前代码仅提取了YUV_420_888格式的Y平面数据(planes[0]),缺少U和V平面的数据,导致实际写入的帧数据长度与frameSize计算值不符,驱动检测到数据异常。
- 修正:拼接所有平面的数据:
fun Image.toByteArray(): ByteArray { val planes = this.planes val bufferSize = planes.sumOf { it.buffer.remaining() } val result = ByteArray(bufferSize) var offset = 0 for (plane in planes) { val buffer = plane.buffer val length = buffer.remaining() buffer.get(result, offset, length) offset += length } return result } // 在Analyzer中调用 val data = image.toByteArray()
3. ByteBuffer未翻转
写入帧头参数后,ByteBuffer的position位于末尾,直接调用toByteArray()会返回从position到末尾的空数据,导致帧头内容不完整。
- 修正:写入参数后调用
flip()切换为读模式:// 写完所有帧头参数后 header.flip() val headerBytes = ByteArray(header.remaining()) header.get(headerBytes)
4. frameSize计算错误
ImageFormat.getBitsPerPixel(ImageFormat.YUV_420_888)返回值为12(每像素12位),计算时需确保整除逻辑正确;或直接按YUV420的格式特性计算(Y占WH,U/V各占WH/4,总长度为WH3/2),避免依赖API可能的兼容性问题。
- 修正:
// 方式一:基于格式特性计算 val frameSize = image.width * image.height * 3 / 2 // 方式二:确保API计算正确 val bitPerPixel = ImageFormat.getBitsPerPixel(image.format) val frameSize = image.width * image.height * bitPerPixel / 8
5. 文件流打开模式错误
UVC设备节点不需要追加模式(true),追加模式会导致写入位置偏移,驱动无法正确识别帧边界。
- 修正:使用默认的覆盖模式打开流:
val uvcFileOutputStream = FileOutputStream("/dev/video3")
6. 帧头长度与驱动预期不匹配
当前分配了26字节的帧头,但按写入的字段计算总长度为20字节(4+2+2+1+1+8+1+1),多余的6字节空数据可能被驱动判定为无效。需核对自定义内核中UVC帧头的具体结构要求,确保长度和字段完全匹配。
内容的提问来源于stack exchange,提问作者Filip Luchianenco

