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

Kotlin中UVC FileOutputStream写入失败:IOException: EINVAL(无效参数)

Android写入UVC缓冲区报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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 10:30:45