Skip to content

Commit d195bd3

Browse files
committed
新增 mock_webcam 复合虚拟设备(UVC 摄像头 + UAC 麦克风一体)并修复 Windows 复合枚举
- mock_webcam:UVC/UAC 双功能组 IAD 布局单设备示例;AudioVideoFileSource 音画同步文件源(--video 选项,主时钟=音频样本,双 demuxer 独立游标) - 复合设备装配 API:InterfaceHandler 增加 get_interface() 访问器;UAC AC/AS 互持 handler(baInterfaceNr 不再猜接口号)、RNDIS 通信/数据 handler set_data_handler 显式关联;示例 dynamic_pointer_cast 收尾为 static_pointer_cast - UAC IAD:UacDeviceHelper 为 AC 接口声明 IAD(CC_AUDIO),修复 0xEF 复合设备下 usbccgp 拆分 AC/AS 导致 Windows 音频 CM_PROB_FAILED_START (实测麦克风/扬声器/复合设备枚举与功能均正常) - 测试随 example 走:test_webcam 迁入 examples/mock_webcam(同 mock_audio 约定),其 CMakeLists 注册 test_mock_webcam;e2e test_webcam.sh - CLAUDE.md 补 Windows usbip.exe 与 Linux usbip -t 用法一致的说明
1 parent 0ca5475 commit d195bd3

27 files changed

Lines changed: 1505 additions & 34 deletions

‎CLAUDE.md‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,32 @@
44
禁止用 std::vector、std::array + 手算字节偏移读写(偏移极易算错、字段增删时无法自动发现错误)。
55
多字节字段按网络序存内存,访问时用 bswap/htons 类函数显式转换。
66

7+
## 测试汇总查看
8+
9+
跑 ctest 必须一条命令看到通过/失败数,禁止跑两遍:输出 tail 至少留 15 行
10+
(汇总行 `100% tests passed out of N` 在输出最后两行,tail 太短会把汇总
11+
截掉,还得再跑一次):
12+
13+
```bash
14+
ctest --test-dir build --output-on-failure -j12 2>&1 | tail -15
15+
```
16+
17+
## 注释风格
18+
19+
函数/方法/结构体的文档注释一律用 Doxygen 风格(`/** ... */`,@brief 简述 +
20+
@param 参数说明 + @return 返回值说明),格式对齐 InterfaceHandler.h 的
21+
AbstInterfaceHandler::on_new_connection:
22+
23+
```cpp
24+
/**
25+
* @brief 新的客户端连接时会调这个函数
26+
* @param responder 传输应答接口(提交应答/停止传输用)
27+
* @param ec 发生的ec
28+
*/
29+
```
30+
31+
禁止用 `///` 或 `//` 给函数/方法/结构体写文档(行内注释说明"为什么"不受限)。
32+
733
## spdlog 日志级别被编译期裁剪时的调试手法
834

935
Release/RelWithDebInfo 构建下 SPDLOG_ACTIVE_LEVEL 编译期定为 INFO,TRACE/DEBUG 日志
@@ -53,6 +79,15 @@ WSL 默认用户非 root 时权限不足会报 `usbip: error: import device`(
5379
服务器/协议问题)。list 只读不需要 sudo,但为统一可直接
5480
`sudo usbip -t 53240 ...`。验证导入状态用 `sudo usbip port`。
5581
82+
**Windows 的 usbip.exe 与 Linux usbip 工具命令完全一样**:-t/--tcp-port 是
83+
两者共同的顶层全局选项(紧跟 usbip 之后、所有子命令之前),Windows 与 Linux
84+
的 attach/list/port 语法一一对应。Windows 侧连非 3240 端口服务器的命令就是
85+
`usbip -t 53240 list -r <ip>` / `usbip -t 53240 attach -r <ip> -b 1-1`,
86+
不要因为 attach 子命令帮助里 -t 显示为 --terse 就以为不支持端口
87+
(--terse 是 attach 子命令自己的短选项,顶层 -t 才是端口,看 `usbip --help`
88+
的顶层 OPTIONS)。Linux 中导入 Windows 侧导出的设备(usbipd-win)写法与此
89+
完全相同:`usbip -t <端口> attach -r <Windows 主机 ip> -b <busid>`。
90+
5691
## mock 设备本地验证的坑
5792
5893
在 WSL 里起 mock 服务器并 attach 验证时,注意:

‎CMakeLists.txt‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ option(USBIPDCPP_BUILD_EXAMPLE_MOCK_PIPE "Build example mock generic pipe applic
5858
option(USBIPDCPP_BUILD_EXAMPLE_MOCK_PIPE_HID "Build example mock HID keyboard via generic pipe applications" ${IS_TOP_LEVEL})
5959
option(USBIPDCPP_BUILD_EXAMPLE_MOCK_ECM "Build example mock CDC ECM (virtual ethernet adapter) applications" ${IS_TOP_LEVEL})
6060
option(USBIPDCPP_BUILD_EXAMPLE_MOCK_RNDIS "Build example mock CDC RNDIS (virtual ethernet adapter, driverless on Windows) applications" ${IS_TOP_LEVEL})
61+
option(USBIPDCPP_BUILD_EXAMPLE_MOCK_WEBCAM "Build example mock UVC+UAC webcam (virtual camera + microphone) applications" ${IS_TOP_LEVEL})
6162

6263
if (IS_TOP_LEVEL AND WIN32)
6364
set(USBIPDCPP_BUILD_EXAMPLE_LIBUSB_WINDOWS_SERVICE_DEFAULT ON)

‎README.md‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ A C++ library for creating usbip servers
66
77
> ✅ USBIP server: Platform-independent implementation via libusb (works wherever libusb is supported)
88
> ✅ All four USB transfer types (control, bulk, interrupt, isochronous) tested and working via libusb backend
9-
> ✅ Virtual devices: HID (mouse, keyboard, gamepad, digitizer), MSC (USB flash drive), CDC ACM (serial port), CDC ECM/RNDIS (ethernet), UVC (camera), UAC (microphone, speaker) — no libusb dependency
9+
> ✅ Virtual devices: HID (mouse, keyboard, gamepad, digitizer), MSC (USB flash drive), CDC ACM (serial port), CDC ECM/RNDIS (ethernet), UVC (camera), UAC (microphone, speaker), webcam (UVC camera + UAC microphone composite) — no libusb dependency
1010
> ✅ Hot-plug support: Automatic device insertion/removal detection (LibusbServer)
1111
1212
Contributions welcome! 🚀
@@ -560,6 +560,16 @@ All `change_string_*` methods delegate to `StringPool::change_string()` and will
560560
Usage: `mock_rndis --tun usbip%d` then on the host `ip addr add 192.168.53.2/24 dev usbX`
561561
and `ping 192.168.53.1`.
562562
563+
**22. mock_webcam**
564+
565+
A composite device combining a virtual **UVC camera + UAC microphone** in a single USB device — the
566+
real-world form of an external webcam, **driverless on Windows** (a "USB camera" and a "microphone"
567+
enumerate at the same time, like a physical webcam). The UVC group declares an IAD; the camera
568+
(ISO IN video frames) and the microphone (ISO IN audio) stream from the server simultaneously.
569+
570+
Usage: `mock_webcam --width 640 --height 480 --fps 30 --rates 48000` (mock_uvc options
571+
`--width/--height/--fps` combined with mock_audio options `--freq/--rates/--channels/--amp/--harmonics/--audio`).
572+
563573
---
564574
565575
## Architecture Overview

‎README.zh.md‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
- ✅ **USBIP 服务器**: 基于 libusb 实现,支持所有 libusb 兼容平台
1010
- ✅ **四种 USB 传输类型**(控制、批量、中断、同步)均通过 libusb 后端测试
11-
- ✅ **虚拟设备**: HID(鼠标、键盘、手柄、触摸屏)、MSC(U盘)、CDC ACM(串口)、CDC ECM/RNDIS(网卡)、UVC(摄像头)、UAC(麦克风、扬声器)—— 无需 libusb
11+
- ✅ **虚拟设备**: HID(鼠标、键盘、手柄、触摸屏)、MSC(U盘)、CDC ACM(串口)、CDC ECM/RNDIS(网卡)、UVC(摄像头)、UAC(麦克风、扬声器)、webcam(UVC 摄像头 + UAC 麦克风复合设备)—— 无需 libusb
1212
- 🔌 **热插拔支持**: 自动检测设备插入/拔出(LibusbServer)
1313
- 🧩 **可扩展设计**: 提供完善的抽象接口供开发者扩展
1414

@@ -544,6 +544,16 @@ interface_handler->change_string_interface(L"我的 HID 接口");
544544
用法:`mock_rndis --tun usbip%d`,主机侧 `ip addr add 192.168.53.2/24 dev usbX` 后
545545
`ping 192.168.53.1`。
546546
547+
**22. mock_webcam**
548+
549+
虚拟 **UVC 摄像头 + UAC 麦克风** 复合设备——单个 USB 设备同时呈现摄像头与麦克风,
550+
即外接摄像头的真实形态,**Windows 免驱**(同一时间枚举出"USB 摄像头"和"麦克风",
551+
与物理摄像头一致)。UVC 功能组声明 IAD;摄像头(ISO IN 视频帧)与麦克风(ISO IN
552+
音频)同时从服务器推流。
553+
554+
用法:`mock_webcam --width 640 --height 480 --fps 30 --rates 48000`(mock_uvc 的
555+
`--width/--height/--fps` 与 mock_audio 的 `--freq/--rates/--channels/--amp/--harmonics/--audio` 合并)。
556+
547557
---
548558
549559
## 架构设计

‎examples/CMakeLists.txt‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ if (cxxopts_FOUND)
6060
if (USBIPDCPP_BUILD_VIRTUAL_DEVICE AND USBIPDCPP_BUILD_EXAMPLE_MOCK_RNDIS)
6161
add_subdirectory(mock_rndis)
6262
endif ()
63+
if (USBIPDCPP_BUILD_VIRTUAL_DEVICE AND USBIPDCPP_BUILD_EXAMPLE_MOCK_WEBCAM)
64+
add_subdirectory(mock_webcam)
65+
endif ()
6366

6467
if (USBIPDCPP_BUILD_LIBUSB_COMPONENTS AND USBIPDCPP_BUILD_EXAMPLE_LIBUSB_SERVER)
6568
add_subdirectory(libusb_server)
@@ -131,6 +134,9 @@ if(USBIPDCPP_INSTALL_EXAMPLES)
131134
if(TARGET mock_rndis)
132135
install(TARGETS mock_rndis RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
133136
endif()
137+
if(TARGET mock_webcam)
138+
install(TARGETS mock_webcam RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
139+
endif()
134140
if(TARGET libevdev_mouse)
135141
install(TARGETS libevdev_mouse RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
136142
endif()

‎examples/absolute_mouse/absolute_mouse_main.cpp‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ int main(int argc, char **argv) {
5252
mouse_device->interfaces[0].with_handler<AbsoluteMouseHandler>(string_pool);
5353
mouse_device->with_handler<SimpleVirtualDeviceHandler>(string_pool)->setup_interface_handlers();
5454

55-
auto mouse = std::dynamic_pointer_cast<AbsoluteMouseHandler>(mouse_device->interfaces[0].handler);
55+
auto mouse = std::static_pointer_cast<AbsoluteMouseHandler>(mouse_device->interfaces[0].handler);
5656

5757
Server server;
5858
server.add_device(std::move(mouse_device));

‎examples/libevdev_mouse/libevdev_mouse_main.cpp‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ int main(int argc, char **argv) {
8282
device_handler->setup_interface_handlers();
8383

8484
LibevdevMouseInterfaceHandler &mouse_interface_handler =
85-
*std::dynamic_pointer_cast<LibevdevMouseInterfaceHandler>(libevdev_mouse->interfaces[0].handler);
85+
*std::static_pointer_cast<LibevdevMouseInterfaceHandler>(libevdev_mouse->interfaces[0].handler);
8686

8787
Server server;
8888

‎examples/mock_cdc_acm/mock_cdc_acm_main.cpp‎

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,14 @@ int main(int argc, char **argv) {
2929
1, 1, static_cast<std::uint8_t>(ClassCode::CDC), "/usbipdcpp/mock_cdc_acm");
3030
// 接口从 0 连续编号:按下标依次填 interface_number(多接口设备的 bInterfaceNumber 依赖它)
3131
mock_cdc_acm->assign_interface_numbers();
32-
// 设置接口处理器
33-
mock_cdc_acm->interfaces[0].with_handler<MockCdcAcmCommunicationInterfaceHandler>(string_pool);
34-
mock_cdc_acm->interfaces[1].with_handler<MockCdcAcmDataInterfaceHandler>(string_pool);
32+
// 设置接口处理器(with_handler 返回具体类型的 shared_ptr,直接用,无需 cast)
33+
auto comm_handler = mock_cdc_acm->interfaces[0].with_handler<MockCdcAcmCommunicationInterfaceHandler>(string_pool);
34+
auto data_handler = mock_cdc_acm->interfaces[1].with_handler<MockCdcAcmDataInterfaceHandler>(string_pool);
3535
mock_cdc_acm->with_handler<SimpleVirtualDeviceHandler>(string_pool)->setup_interface_handlers();
3636

37-
// 关联通信接口和数据接口处理器
38-
auto &comm_handler =
39-
*std::dynamic_pointer_cast<MockCdcAcmCommunicationInterfaceHandler>(mock_cdc_acm->interfaces[0].handler);
40-
auto &data_handler =
41-
*std::dynamic_pointer_cast<MockCdcAcmDataInterfaceHandler>(mock_cdc_acm->interfaces[1].handler);
42-
comm_handler.set_data_handler(&data_handler);
43-
data_handler.set_comm_handler(&comm_handler);
37+
// 关联通信接口和数据接口处理器(描述符引用数据接口号 + 数据面状态查询)
38+
comm_handler->set_data_handler(data_handler.get());
39+
data_handler->set_comm_handler(comm_handler.get());
4440

4541
Server server;
4642
server.add_device(std::move(mock_cdc_acm));

‎examples/mock_cdc_throttle/mock_cdc_throttle_main.cpp‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,9 @@ int main(int argc, char **argv) {
5151

5252
// 关联通信接口和数据接口处理器
5353
auto &comm_handler =
54-
*std::dynamic_pointer_cast<CdcAcmCommunicationInterfaceHandler>(device->interfaces[0].handler);
54+
*std::static_pointer_cast<CdcAcmCommunicationInterfaceHandler>(device->interfaces[0].handler);
5555
auto &data_handler =
56-
*std::dynamic_pointer_cast<ThrottleCdcAcmDataInterfaceHandler>(device->interfaces[1].handler);
56+
*std::static_pointer_cast<ThrottleCdcAcmDataInterfaceHandler>(device->interfaces[1].handler);
5757
comm_handler.set_data_handler(&data_handler);
5858
data_handler.set_comm_handler(&comm_handler);
5959

‎examples/mock_ecm/mock_ecm_main.cpp‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,10 @@ int main(int argc, char **argv) {
115115
1, 1, static_cast<std::uint8_t>(ClassCode::CDC), "/usbipdcpp/mock_ecm");
116116
// 接口从 0 连续编号:按下标依次填 interface_number(多接口设备的 bInterfaceNumber 依赖它)
117117
device->assign_interface_numbers();
118-
device->interfaces[0].with_handler<EcmCommunicationInterfaceHandler>(string_pool, mac);
119-
device->interfaces[1].with_handler<EcmDataInterfaceHandler>(string_pool, backend.get());
118+
auto comm = device->interfaces[0].with_handler<EcmCommunicationInterfaceHandler>(string_pool, mac);
119+
auto data = device->interfaces[1].with_handler<EcmDataInterfaceHandler>(string_pool, backend.get());
120+
// 通信接口描述符的 Union 引用数据接口号(bSlaveInterface0),装配时关联
121+
comm->set_data_handler(data.get());
120122
device->with_handler<SimpleVirtualDeviceHandler>(string_pool)->setup_interface_handlers();
121123

122124
Server server;

0 commit comments

Comments
 (0)