Linux驱动 2026年8月5日 38 分钟

USB接口替代设置详解

USB接口替代设置(Alternate Setting)详解 一、概念定义 1.1 什么是Alternate Setti…

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替代设置的概念、物理意义、使用方法和典型应用场景。

上一篇 USB异步传输相关问题汇总

适用场景:Linux 用户空间使用 libusb 进行异步 USB 传输开发。 重点 API:libusb_transf...

下一篇 USB子系统5-鼠标设备驱动开发