2. 获取音频控制接口

任务说明

本案例实现一个Linux用户态程序,通过libusb枚举指定USB音频设备,识别其接口描述符中的音频控制接口(AC,Audio Control)与音频流接口(AS,Audio Streaming),并打印接口编号、接口类、接口子类及接口描述符原始字节。

环境与依赖

  • 操作系统:Linux(需root权限或配置udev规则以访问USB设备)
  • 依赖库:libusb-1.0(安装:sudo apt install libusb-1.0-0-dev)
  • 编译工具:gcc

核心实现

USB音频设备的接口类代码为0x01(Audio),接口子类0x01表示AC接口,0x02表示AS接口。程序通过libusb获取设备描述符和配置描述符,遍历所有接口,按上述规则分类并输出。

#include <stdio.h>
#include <stdint.h>
#include <libusb-1.0/libusb.h>

static const char* subclass_name(uint8_t sc) {
    switch (sc) {
        case 0x01: return "Audio Control (AC)";
        case 0x02: return "Audio Streaming (AS)";
        case 0x03: return "MIDI Streaming";
        default:   return "Unknown";
    }
}

static void print_interface_descriptor(const struct libusb_interface_descriptor* intf) {
    printf("  Interface #%u\n", intf->bInterfaceNumber);
    printf("    Class: 0x%02X, Subclass: 0x%02X (%s)\n",
           intf->bInterfaceClass, intf->bInterfaceSubClass,
           subclass_name(intf->bInterfaceSubClass));
    printf("    Protocol: 0x%02X, Endpoints: %u\n",
           intf->bInterfaceProtocol, intf->bNumEndpoints);
    printf("    Raw descriptor (%u bytes): ", intf->extra_length);
    for (int i = 0; i < intf->extra_length; i++) {
        printf("%02X ", intf->extra[i]);
    }
    printf("\n");
}

static void scan_config_descriptor(const struct libusb_config_descriptor* config) {
    printf("Configuration %u: %u interface(s)\n",
           config->bConfigurationValue, config->bNumInterfaces);
    for (int i = 0; i < config->bNumInterfaces; i++) {
        const struct libusb_interface* iface = &config->interface[i];
        for (int a = 0; a < iface->num_altsetting; a++) {
            const struct libusb_interface_descriptor* intf = &iface->altsetting[a];
            if (intf->bInterfaceClass == 0x01) {
                print_interface_descriptor(intf);
            }
        }
    }
}

int main(int argc, char* argv[]) {
    if (argc != 3) {
        fprintf(stderr, "Usage: %s <bus> <address>\n", argv[0]);
        fprintf(stderr, "  Find device with: lsusb\n");
        return 1;
    }
    uint8_t bus = (uint8_t)atoi(argv[1]);
    uint8_t addr = (uint8_t)atoi(argv[2]);

    libusb_device** devs;
    libusb_device* target = NULL;
    libusb_context* ctx = NULL;

    int ret = libusb_init(&ctx);
    if (ret < 0) {
        fprintf(stderr, "libusb_init failed: %s\n", libusb_error_name(ret));
        return 1;
    }

    ssize_t cnt = libusb_get_device_list(ctx, &devs);
    if (cnt < 0) {
        fprintf(stderr, "Failed to get device list\n");
        libusb_exit(ctx);
        return 1;
    }

    for (ssize_t i = 0; i < cnt; i++) {
        uint8_t b = libusb_get_bus_number(devs[i]);
        uint8_t a = libusb_get_device_address(devs[i]);
        if (b == bus && a == addr) {
            target = devs[i];
            break;
        }
    }

    if (!target) {
        fprintf(stderr, "Device %u:%u not found\n", bus, addr);
        libusb_free_device_list(devs, 1);
        libusb_exit(ctx);
        return 1;
    }

    struct libusb_device_descriptor desc;
    ret = libusb_get_device_descriptor(target, &desc);
    if (ret < 0) {
        fprintf(stderr, "Failed to get device descriptor: %s\n", libusb_error_name(ret));
        libusb_free_device_list(devs, 1);
        libusb_exit(ctx);
        return 1;
    }
    printf("Device %u:%u - VID:0x%04X PID:0x%04X\n",
           bus, addr, desc.idVendor, desc.idProduct);

    for (int c = 0; c < desc.bNumConfigurations; c++) {
        struct libusb_config_descriptor* config = NULL;
        ret = libusb_get_config_descriptor(target, c, &config);
        if (ret == 0) {
            scan_config_descriptor(config);
            libusb_free_config_descriptor(config);
        }
    }

    libusb_free_device_list(devs, 1);
    libusb_exit(ctx);
    return 0;
}

编译与运行

# 编译
gcc -o usb_audio_iface usb_audio_iface.c -lusb-1.0

# 查看设备总线号和地址(假设设备为 Bus 001 Device 004)
lsusb

# 运行(需要root权限)
sudo ./usb_audio_iface 001 004

验证方法

运行程序后,观察输出:

  • 若设备包含音频功能,应能看到至少一个Interface #N,其Subclass为0x01 (Audio Control)或0x02 (Audio Streaming)。
  • 对照lsusb -v -s 001:004输出中对应接口的bInterfaceClass和bInterfaceSubClass字段,两者应一致。
  • Raw descriptor字段打印的是接口描述符之后的类特殊描述符(如AC的音频控制头描述符、AS的音频流头描述符),其字节内容应与lsusb -v中对应接口的Audio Control/Audio Streaming描述符一致。

若设备无音频接口,程序将只打印设备信息而不输出任何接口行,此时可换用其他USB音频设备(如USB声卡、带音频的摄像头)测试。