使用Go-usb调用索尼相机0x9201命令时出现PTP响应停滞问题
解决Go-usb实现索尼相机SDIOConnect命令端点停滞问题
针对你遇到的索尼相机PTP通信中SDIOConnect(0x9201)命令端点停滞问题,结合代码和PTP协议规范,以下是排查点和修复方案:
核心问题分析
PTP命令包结构错误
你提供的命令字节中,操作码位置和值不符合0x9201的要求。从代码看,buffSetPcModeSony里的操作码段是0x01,0x00,而非预期的0x92,0x01,这会导致相机收到错误命令,直接触发停滞。需要严格对照libgphoto2日志中的原始字节流,确保命令包的长度、操作码、会话ID、事务ID、参数完全一致(PTP采用大端字节序)。数据未完整发送
当前TransferOut仅调用一次Write,gousb的Write受USB传输限制,可能无法一次性发送全部数据。相机收到不完整的命令包后,会返回端点停滞错误。响应读取逻辑缺陷
- 依赖读取字节数是否为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
相关产品推荐
相关产品推荐

