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声卡、带音频的摄像头)测试。