USB接口替代设置(Alternate Setting)详解
一、概念定义
1.1 什么是Alternate Setting?
定义:一个USB接口可以有多种工作模式,每种模式称为一个替代设置(Alternate Setting)。
USB接口(Interface)
├─ 替代设置0(Alternate Setting 0)- 默认设置
├─ 替代设置1(Alternate Setting 1)- 可选设置
├─ 替代设置2(Alternate Setting 2)- 可选设置
└─ ...
核心思想:
同一个接口,在不同时刻可以使用不同的端点配置
→ 通过切换替代设置来改变接口行为
→ 不需要重新配置整个USB设备
1.2 LibUSB中的altsetting结构
// libusb中的接口结构
struct libusb_interface {
const struct libusb_interface_descriptor *altsetting;
// ↑
// 指向替代设置数组
int num_altsetting; // 该接口有多少个替代设置
};
// 接口描述符(每个替代设置)
struct libusb_interface_descriptor {
uint8_t bLength;
uint8_t bDescriptorType;
uint8_t bInterfaceNumber; // 接口编号
uint8_t bAlternateSetting; // 替代设置编号
uint8_t bNumEndpoints; // 该设置有多少个端点
uint8_t bInterfaceClass;
uint8_t bInterfaceSubClass;
uint8_t bInterfaceProtocol;
uint8_t iInterface;
const struct libusb_endpoint_descriptor *endpoint; // 端点数组
};
访问示例:
// 获取配置描述符
struct libusb_config_descriptor *config;
libusb_get_config_descriptor(dev, 0, &config);
// 访问接口0
const struct libusb_interface *interface0 = &config->interface[0];
// 访问接口0的所有替代设置
for (int i = 0; i < interface0->num_altsetting; i++) {
const struct libusb_interface_descriptor *altsetting =
&interface0->altsetting[i];
printf("替代设置 %d: %d个端点\n",
altsetting->bAlternateSetting,
altsetting->bNumEndpoints);
}
二、物理意义与客观描述
2.1 替代设置的物理意义
核心物理意义:USB总线带宽的动态分配。
USB总线带宽有限:
- USB 1.1 全速: 12 Mbps
- USB 2.0 高速: 480 Mbps
- USB 3.0 超速: 5 Gbps
问题:
多个设备共享同一条总线,如何避免带宽冲突?
解决方案:
替代设置允许设备根据需要动态调整占用的带宽
替代设置0(默认):通常不占用或占用极少带宽
替代设置1+:根据实际需求分配不同的带宽
2.2 客观描述:带宽占用的变化
以USB摄像头为例:
接口0 - 视频流接口(VideoStreaming)
替代设置0:
- 端点数:0个
- 带宽占用:0 KB/s
- 物理含义:摄像头不传输视频数据,空闲状态
- 用途:初始化、配置参数、待机
替代设置1:
- 端点数:1个(等时IN,端点0x81)
- 最大包大小:1024字节
- 带宽占用:约12 MB/s
- 物理含义:传输640×480分辨率的视频
- 用途:中等质量视频流
替代设置2:
- 端点数:1个(等时IN,端点0x81)
- 最大包大小:3072字节
- 带宽占用:约36 MB/s
- 物理含义:传输1920×1080分辨率的视频
- 用途:高清视频流
替代设置3:
- 端点数:1个(等时IN,端点0x81)
- 最大包大小:512字节
- 带宽占用:约6 MB/s
- 物理含义:传输320×240分辨率的视频
- 用途:低带宽/低功耗模式
关键点:
- 替代设置0总是0带宽(USB规范要求)
- 其它替代设置可以有不同的端点配置和带宽需求
- 设备根据实际应用场景切换替代设置
2.3 物理对应关系
客观对象 USB表示
──────────────────────────────────────────
摄像头硬件(物理设备) USB设备(Device)
视频采集功能 USB接口(Interface)
不同分辨率/帧率模式 替代设置(Alternate Setting)
实际数据传输通道 端点(Endpoint)
示例映射:
罗技C920摄像头
├─ 接口0(视频控制)
│ └─ 替代设置0(控制命令,无数据端点)
│
└─ 接口1(视频流)
├─ 替代设置0(空闲,0个端点)
├─ 替代设置1(640×480 @ 30fps,1个端点)
├─ 替代设置2(1280×720 @ 30fps,1个端点)
└─ 替代设置3(1920×1080 @ 30fps,1个端点)
三、为什么需要替代设置?
3.1 问题场景
场景1:USB总线带宽冲突
USB 2.0总线(480 Mbps)
├─ Hub 1
│ ├─ 摄像头A(占用200 Mbps,1080p)
│ ├─ 摄像头B(占用200 Mbps,1080p)
│ └─ 摄像头C(想要200 Mbps,1080p)← 带宽不足!
│
└─ 总带宽超限!
解决方案:
摄像头C切换到替代设置1(720p,只需100 Mbps)
→ 总带宽:200 + 200 + 100 = 500 Mbps(勉强可行)
或者所有摄像头降到720p
→ 总带宽:100 + 100 + 100 = 300 Mbps(充足)
场景2:设备功能模式切换
USB音频设备(扬声器)
替代设置0:
- 无音频端点
- 设备静音/待机
- 功耗:极低
替代设置1:
- 1个等时OUT端点
- 双声道,16位,44.1kHz
- 带宽:约176 KB/s
- 功耗:正常
替代设置2:
- 1个等时OUT端点
- 8声道,24位,192kHz(高保真)
- 带宽:约4.6 MB/s
- 功耗:较高
用户场景:
听音乐 → 切换到替代设置1(CD音质)
播放蓝光电影 → 切换到替代设置2(7.1环绕声)
暂停播放 → 切换到替代设置0(省电)
场景3:等时传输的硬性要求
等时传输(Isochronous Transfer)特点:
- 保证固定的传输速率
- 需要预留带宽
- 不能动态调整
问题:
如果摄像头一开机就分配1080p的带宽(200 Mbps),
但用户只需要480p(50 Mbps),
→ 浪费150 Mbps带宽,其它设备无法使用
解决方案:
1. 初始状态:替代设置0(0带宽)
2. 用户选择分辨率:应用程序切换到对应的替代设置
3. 开始传输:占用实际需要的带宽
4. 停止传输:切换回替代设置0,释放带宽
3.2 设计原则
替代设置的设计遵循:
1. 替代设置0必须是默认设置(设备枚举后的初始状态)
2. 替代设置0不应占用等时或中断带宽(最好0个端点)
3. 其它替代设置按带宽需求递增排列
4. 同一接口的所有替代设置功能相关
5. 切换替代设置不影响其它接口
四、如何使用替代设置
4.1 查询替代设置
#include <libusb-1.0/libusb.h>
void list_alternate_settings(libusb_device *dev) {
struct libusb_config_descriptor *config;
libusb_get_config_descriptor(dev, 0, &config);
for (int i = 0; i < config->bNumInterfaces; i++) {
const struct libusb_interface *interface = &config->interface[i];
printf("接口 %d 有 %d 个替代设置:\n",
i, interface->num_altsetting);
for (int j = 0; j < interface->num_altsetting; j++) {
const struct libusb_interface_descriptor *altsetting =
&interface->altsetting[j];
printf(" 替代设置 %d:\n", altsetting->bAlternateSetting);
printf(" 端点数: %d\n", altsetting->bNumEndpoints);
// 遍历端点
for (int k = 0; k < altsetting->bNumEndpoints; k++) {
const struct libusb_endpoint_descriptor *ep =
&altsetting->endpoint[k];
printf(" 端点 0x%02x: ", ep->bEndpointAddress);
// 判断传输类型
uint8_t type = ep->bmAttributes & 0x03;
switch (type) {
case LIBUSB_TRANSFER_TYPE_CONTROL:
printf("控制");
break;
case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS:
printf("等时");
break;
case LIBUSB_TRANSFER_TYPE_BULK:
printf("批量");
break;
case LIBUSB_TRANSFER_TYPE_INTERRUPT:
printf("中断");
break;
}
printf(", 最大包大小: %d 字节\n",
ep->wMaxPacketSize);
}
}
}
libusb_free_config_descriptor(config);
}
输出示例(罗技C920摄像头):
接口 0 有 1 个替代设置:
替代设置 0:
端点数: 1
端点 0x83: 中断, 最大包大小: 16 字节
接口 1 有 8 个替代设置:
替代设置 0:
端点数: 0
替代设置 1:
端点数: 1
端点 0x81: 等时, 最大包大小: 128 字节
替代设置 2:
端点数: 1
端点 0x81: 等时, 最大包大小: 512 字节
替代设置 3:
端点数: 1
端点 0x81: 等时, 最大包大小: 1024 字节
替代设置 4:
端点数: 1
端点 0x81: 等时, 最大包大小: 1536 字节
替代设置 5:
端点数: 1
端点 0x81: 最大包大小: 2048 字节
替代设置 6:
端点数: 1
端点 0x81: 等时, 最大包大小: 2688 字节
替代设置 7:
端点数: 1
端点 0x81: 等时, 最大包大小: 3072 字节
解读:
- 接口0(视频控制):只有1个替代设置,用于发送控制命令
- 接口1(视频流):8个替代设置,对应不同的带宽需求
- 替代设置0:空闲状态(0带宽)
- 替代设置1-7:带宽递增(从128字节到3072字节)
4.2 切换替代设置
#include <libusb-1.0/libusb.h>
int switch_to_alternate_setting(libusb_device_handle *dev_handle,
int interface_number,
int alternate_setting)
{
// 设置替代设置
int ret = libusb_set_interface_alt_setting(
dev_handle,
interface_number,
alternate_setting
);
if (ret < 0) {
printf("切换替代设置失败: %s\n", libusb_error_name(ret));
return ret;
}
printf("接口 %d 已切换到替代设置 %d\n",
interface_number, alternate_setting);
return 0;
}
4.3 完整示例:启动UVC摄像头
#include <libusb-1.0/libusb.h>
#include <stdio.h>
int start_uvc_camera(libusb_device_handle *dev_handle) {
int ret;
// 1. 声明占用接口
ret = libusb_claim_interface(dev_handle, 1); // 视频流接口
if (ret < 0) {
printf("无法占用接口: %s\n", libusb_error_name(ret));
return ret;
}
// 2. 协商视频参数(Probe/Commit)
// ... UVC协议的协商过程 ...
// 3. 切换到合适的替代设置(根据分辨率选择)
// 假设协商结果是720p,需要替代设置3
ret = libusb_set_interface_alt_setting(dev_handle, 1, 3);
if (ret < 0) {
printf("切换替代设置失败: %s\n", libusb_error_name(ret));
libusb_release_interface(dev_handle, 1);
return ret;
}
printf("摄像头已切换到720p模式(替代设置3)\n");
// 4. 创建等时传输接收视频数据
struct libusb_transfer *transfer = libusb_alloc_transfer(8); // 8个包
// ... 配置等时传输 ...
libusb_submit_transfer(transfer);
// 5. 事件循环
while (streaming) {
libusb_handle_events(NULL);
}
return 0;
}
int stop_uvc_camera(libusb_device_handle *dev_handle) {
// 1. 取消传输
// ... 取消所有传输 ...
// 2. 切换回替代设置0(释放带宽)
int ret = libusb_set_interface_alt_setting(dev_handle, 1, 0);
if (ret < 0) {
printf("切换回替代设置0失败: %s\n", libusb_error_name(ret));
}
// 3. 释放接口
libusb_release_interface(dev_handle, 1);
printf("摄像头已停止,带宽已释放\n");
return 0;
}
五、典型设备的替代设置
5.1 USB摄像头(UVC)
接口0 - 视频控制(VideoControl)
└─ 替代设置0(唯一)
└─ 1个中断IN端点(状态通知)
接口1 - 视频流(VideoStreaming)
├─ 替代设置0(空闲)
│ └─ 0个端点
│
├─ 替代设置1(低分辨率/低帧率)
│ └─ 1个等时IN端点,小包
│
├─ 替代设置2(中分辨率)
│ └─ 1个等时IN端点,中包
│
└─ 替代设置3(高分辨率/高帧率)
└─ 1个等时IN端点,大包
切换时机:
用户选择分辨率 → 应用选择对应的替代设置
暂停视频 → 切换回替代设置0
恢复视频 → 切换回之前的替代设置
5.2 USB音频设备
接口0 - 音频控制(AudioControl)
└─ 替代设置0(唯一)
└─ 0个端点(控制接口通常没有数据端点)
接口1 - 音频流(AudioStreaming)
├─ 替代设置0(静音/待机)
│ └─ 0个端点
│
├─ 替代设置1(标准音质)
│ └─ 1个等时OUT端点
│ 双声道,16位,48kHz
│ 带宽:约192 KB/s
│
└─ 替代设置2(高保真)
└─ 1个等时OUT端点
8声道,24位,96kHz
带宽:约1.8 MB/s
切换时机:
播放音乐 → 替代设置1
播放蓝光电影(7.1环绕声)→ 替代设置2
暂停播放 → 替代设置0(省电)
5.3 USB键盘/鼠标(HID)
接口0 - HID设备
└─ 替代设置0(唯一)
└─ 1个中断IN端点
说明:
HID设备通常只有1个替代设置
因为中断传输带宽占用小,不需要动态调整
5.4 USB以太网适配器(CDC-Ethernet)
接口0 - 通信接口(CDC Control)
└─ 替代设置0(唯一)
└─ 1个中断IN端点(网络状态通知)
接口1 - 数据接口(CDC Data)
├─ 替代设置0(未连接)
│ └─ 0个端点
│
└─ 替代设置1(已连接)
├─ 1个批量IN端点(接收网络数据)
└─ 1个批量OUT端点(发送网络数据)
切换时机:
网卡关闭/断开 → 替代设置0
网卡启动/连接 → 替代设置1
六、Linux内核中的对应
在Linux内核的include/linux/usb.h中,有对应的结构:
struct usb_host_interface {
struct usb_interface_descriptor desc; // 接口描述符
int extralen;
unsigned char *extra;
struct usb_host_endpoint *endpoint; // 端点数组
char *string;
};
struct usb_interface {
struct usb_host_interface *altsetting; // 替代设置数组
// ↑
// 与libusb的altsetting对应
struct usb_host_interface *cur_altsetting; // 当前激活的替代设置
// ↑
// 指向altsetting数组中的某一项
unsigned num_altsetting; // 替代设置数量
// ...
};
关键区别:
- LibUSB:
altsetting是数组,用户需要手动索引 - Linux内核:
altsetting是数组,cur_altsetting指向当前激活项
七、常见问题
Q1:为什么替代设置0必须是0带宽?
回答:USB规范要求。
原因:
1. 设备枚举时默认使用替代设置0
2. 此时主机还没有分配带宽
3. 如果替代设置0占用等时/中断带宽,会导致冲突
设计意图:
替代设置0是"安全状态"
→ 不占用带宽,不会与其它设备冲突
→ 设备就绪后,应用程序再切换到工作设置
Q2:可以跳过替代设置直接切换吗?
回答:可以。
// 从替代设置0直接跳到替代设置3
libusb_set_interface_alt_setting(dev_handle, 1, 3); // 合法
// 不需要按顺序:0 → 1 → 2 → 3
Q3:切换替代设置会中断其它接口吗?
回答:不会。
替代设置是接口级别的
→ 切换接口1的替代设置不影响接口0
→ 但会中断接口1上的所有传输
Q4:所有接口都有多个替代设置吗?
回答:不是。
常见模式:
- HID设备(键盘鼠标):1个替代设置
- U盘:1个替代设置
- 摄像头/音频:多个替代设置(需要动态带宽)
- 以太网适配器:2个替代设置(连接/未连接)
八、总结
8.1 核心要点
1. altsetting是什么?
- 接口的多种工作模式
- 每种模式有不同的端点配置
2. 物理意义?
- USB总线带宽的动态分配
- 根据实际需求切换模式
3. 为什么需要?
- 避免带宽冲突
- 支持设备的多种工作模式
- 节省功耗
4. 如何使用?
- 查询:libusb_get_config_descriptor()
- 切换:libusb_set_interface_alt_setting()
5. 典型场景?
- 摄像头:不同分辨率/帧率
- 音频:不同采样率/声道数
- 网卡:连接/未连接状态
8.2 对比表格
| 特性 | 单替代设置(HID) | 多替代设置(UVC) |
|---|---|---|
| 替代设置数 | 1个 | 多个(3-10个) |
| 带宽占用 | 固定 | 动态变化 |
| 切换频率 | 无需切换 | 频繁切换 |
| 典型传输类型 | 中断 | 等时 |
| 应用场景 | 键盘鼠标 | 摄像头音频 |
完成! 这份笔记详细讲解了USB替代设置的概念、物理意义、使用方法和典型应用场景。