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

使用Go-usb调用索尼相机0x9201命令时出现PTP响应停滞问题

解决Go-usb实现索尼相机SDIOConnect命令端点停滞问题

针对你遇到的索尼相机PTP通信中SDIOConnect(0x9201)命令端点停滞问题,结合代码和PTP协议规范,以下是排查点和修复方案:

核心问题分析

  1. PTP命令包结构错误
    你提供的命令字节中,操作码位置和值不符合0x9201的要求。从代码看,buffSetPcModeSony里的操作码段是0x01,0x00,而非预期的0x92,0x01,这会导致相机收到错误命令,直接触发停滞。需要严格对照libgphoto2日志中的原始字节流,确保命令包的长度、操作码、会话ID、事务ID、参数完全一致(PTP采用大端字节序)。

  2. 数据未完整发送
    当前TransferOut仅调用一次Write,gousb的Write受USB传输限制,可能无法一次性发送全部数据。相机收到不完整的命令包后,会返回端点停滞错误。

  3. 响应读取逻辑缺陷

    • 依赖读取字节数是否为12来终止循环,而非根据PTP响应头中的总长度读取完整数据,容易导致读取不完整或误判。
    • 遇到端点停滞错误时未执行端点重置,无法恢复后续通信。

修复后的代码实现

修正命令包

首先对照libgphoto2日志,修正SDIOConnect命令的字节流,确保操作码为0x92,0x01,其他字段(会话ID、事务ID、参数)与gphoto2发送的完全一致:

// 修正后的SDIOConnect命令包(以libgphoto2日志为准)
buffSetPcModeSony := []byte{0x18, 0x00, 0x00, 0x00, 0x92, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
log.Printf("buffSetPcModeSony: [% x]", buffSetPcModeSony)

修复TransferOut函数(确保数据完整发送)

循环写入直到所有字节发送完毕,避免部分数据丢失:

func TransferOut(epOut *gousb.OutEndpoint, data []byte) error {
    log.Println("===StartTransferOut===")
    written := 0
    totalLen := len(data)
    for written < totalLen {
        n, err := epOut.Write(data[written:])
        if err != nil {
            log.Printf("Write failed at %d bytes: %v", written, err)
            return err
        }
        written += n
        log.Printf("Sent %d bytes, total: %d/%d", n, written, totalLen)
    }
    log.Printf("OUT endpoint %d sent all %d bytes", epOut.Desc.Number, totalLen)
    log.Println("===EndTransferOut===")
    return nil
}

修复TransferIn函数(正确读取响应并处理停滞)

根据PTP响应头的总长度读取完整数据,同时处理端点停滞并重置:

import (
    "encoding/binary"
    "io"
    "log"

    "github.com/google/gousb"
)

func TransferIn(epIn *gousb.InEndpoint) ([]byte, error) {
    log.Println("===StartTransferIn===")
    // 读取PTP响应头(固定12字节:长度4 + 响应码2 + 会话ID2 + 事务ID2)
    header := make([]byte, 12)
    _, err := io.ReadFull(epIn, header)
    if err != nil {
        // 处理端点停滞错误,尝试重置端点恢复通信
        if usbErr, ok := err.(*gousb.Error); ok && usbErr.Code == gousb.ErrorStall {
            log.Println("Endpoint stalled, resetting...")
            if resetErr := epIn.Reset(); resetErr != nil {
                log.Printf("Failed to reset endpoint: %v", resetErr)
                return nil, resetErr
            }
            return nil, err
        }
        log.Printf("Failed to read response header: %v", err)
        return nil, err
    }

    // 解析响应总长度(大端字节序)
    respLen := binary.BigEndian.Uint32(header[:4])
    log.Printf("Expected response length: %d", respLen)

    // 读取完整响应数据
    totalData := make([]byte, respLen)
    copy(totalData, header)
    remaining := int(respLen) - 12
    if remaining > 0 {
        _, err := io.ReadFull(epIn, totalData[12:])
        if err != nil {
            log.Printf("Failed to read remaining data: %v", err)
            return nil, err
        }
    }

    log.Printf("IN endpoint %d received full data: [% x]", epIn.Desc.Number, totalData)
    log.Println("===EndTransferIn===")
    return totalData, nil
}

调用代码调整

增加错误处理,确保命令发送和响应读取的异常被捕获:

if err := TransferOut(epOut, buffSetPcModeSony); err != nil {
    log.Fatalf("Failed to send SDIOConnect command: %v", err)
}
respData, err := TransferIn(epIn)
if err != nil {
    log.Fatalf("Failed to read SDIOConnect response: %v", err)
}
// 后续解析响应数据...

额外排查点

  • 确认USB设备的配置、接口、端点与libgphoto2使用的完全一致,避免选错通信通道。
  • 检查PTP会话ID是否与之前开启会话时获取的一致,会话ID不匹配会导致命令被拒绝。
  • 确保每次PTP命令的事务ID唯一且递增,重复的事务ID会触发相机错误响应。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 15:25:36