3. 解析音频控制描述符
任务目标
本案例实现USB音频控制接口(AudioControl Interface)类特定描述符的解析器,能够从原始描述符字节流中提取头描述符(CS_INTERFACE)、输入终端(IT)、输出终端(OT)和功能单元(FU),并以结构化方式输出各描述符的关键字段。
必要背景
USB音频设备通过类特定描述符描述其拓扑结构。音频控制接口的类特定描述符以0x24(CS_INTERFACE)为bDescriptorType起始,后续跟随若干终端(Terminal)和功能单元(Feature Unit)。解析这些描述符是驱动开发中枚举音频拓扑、建立控制通路的基础步骤。
核心代码
#include <stdio.h>
#include <stdint.h>
#include <string.h>
// 音频类特定描述符类型
#define CS_INTERFACE 0x24
#define HEADER_DESCRIPTOR 0x01
#define INPUT_TERMINAL 0x02
#define OUTPUT_TERMINAL 0x03
#define FEATURE_UNIT 0x06
// 描述符子类型名称
static const char* subtype_name(uint8_t st) {
switch(st) {
case HEADER_DESCRIPTOR: return "Header";
case INPUT_TERMINAL: return "Input Terminal";
case OUTPUT_TERMINAL: return "Output Terminal";
case FEATURE_UNIT: return "Feature Unit";
default: return "Unknown";
}
}
// 解析音频控制接口描述符集合
void parse_audio_control_desc(const uint8_t* data, int len) {
int offset = 0;
int count = 0;
printf("=== Audio Control Descriptor Parse ===\n");
while (offset + 2 <= len) {
uint8_t bLength = data[offset];
uint8_t bDescriptorType = data[offset + 1];
if (bLength == 0 || offset + bLength > len) {
printf("[Error] Invalid descriptor at offset %d\n", offset);
break;
}
// 只处理类特定接口描述符
if (bDescriptorType == CS_INTERFACE) {
uint8_t bDescriptorSubtype = data[offset + 2];
printf("\n[%d] %s (len=%d)\n",
count++, subtype_name(bDescriptorSubtype), bLength);
switch(bDescriptorSubtype) {
case HEADER_DESCRIPTOR: {
// bLength=7: bcdADC(2) + wTotalLength(2) + bInCollection(1)
if (bLength < 7) break;
uint16_t bcdADC = data[offset+3] | (data[offset+4] << 8);
uint16_t wTotalLen = data[offset+5] | (data[offset+6] << 8);
printf(" bcdADC: 0x%04X (v%d.%d)\n", bcdADC,
(bcdADC >> 8) & 0xFF, bcdADC & 0xFF);
printf(" wTotalLength: %d bytes\n", wTotalLen);
printf(" bInCollection: %d streaming ifaces\n", data[offset+7]);
break;
}
case INPUT_TERMINAL: {
// bLength=12: bTerminalID + wTerminalType(2) + bAssocTerminal + bCSourceID + bNrChannels + wChannelConfig(2) + wChannelNames + bmControls(2)
if (bLength < 12) break;
uint8_t id = data[offset+3];
uint16_t type = data[offset+4] | (data[offset+5] << 8);
uint8_t assoc = data[offset+6];
uint8_t csrc = data[offset+7];
uint8_t ch = data[offset+8];
uint16_t chcfg = data[offset+9] | (data[offset+10] << 8);
printf(" TerminalID: %d\n", id);
printf(" TerminalType: 0x%04X\n", type);
printf(" AssocTerminal: %d\n", assoc);
printf(" ClockSourceID: %d\n", csrc);
printf(" Channels: %d, Config: 0x%04X\n", ch, chcfg);
break;
}
case OUTPUT_TERMINAL: {
// bLength=9: bTerminalID + wTerminalType(2) + bAssocTerminal + bSourceID + bCSourceID
if (bLength < 9) break;
uint8_t id = data[offset+3];
uint16_t type = data[offset+4] | (data[offset+5] << 8);
uint8_t assoc = data[offset+6];
uint8_t src = data[offset+7];
uint8_t csrc = data[offset+8];
printf(" TerminalID: %d\n", id);
printf(" TerminalType: 0x%04X\n", type);
printf(" AssocTerminal: %d\n", assoc);
printf(" SourceID: %d\n", src);
printf(" ClockSourceID: %d\n", csrc);
break;
}
case FEATURE_UNIT: {
// bLength可变: bUnitID + bSourceID + bControlSize + bmControls(n) + bmaControls(2*n) + iFeature
if (bLength < 6) break;
uint8_t uid = data[offset+3];
uint8_t src = data[offset+4];
uint8_t csize = data[offset+5];
printf(" UnitID: %d\n", uid);
printf(" SourceID: %d\n", src);
printf(" ControlSize: %d bytes\n", csize);
// 打印主控制位图
if (csize > 0 && offset+6+csize <= len) {
printf(" MasterControls: 0x");
for (int i = csize-1; i >= 0; i--)
printf("%02X", data[offset+6+i]);
printf("\n");
}
break;
}
default:
printf(" (skipping unknown subtype)\n");
break;
}
}
offset += bLength;
}
printf("\n=== Parse Complete: %d descriptors ===\n", count);
}
// 示例:一个简化的音频控制描述符集合
int main() {
// 手工构造:Header + IT(USB Streaming) + FU + OT
uint8_t ac_desc[] = {
0x07, 0x24, 0x01, 0x00, 0x01, 0x2C, 0x00, 0x01, // Header: v1.0, total=44, 1 streaming
0x0C, 0x24, 0x02, 0x01, 0x01, 0x01, 0x00, 0x02, // IT: ID=1, type=0x0101, assoc=0, CS=2
0x02, 0x00, 0x03, 0x00, 0x00, 0x00, // ch=2, cfg=0x0003, names=0, ctrl=0
0x09, 0x24, 0x03, 0x02, 0x03, 0x01, 0x00, 0x01, // OT: ID=2, type=0x0301, assoc=0, src=1
0x01, // CS=1
0x08, 0x24, 0x06, 0x03, 0x01, 0x01, 0x01, 0x00, // FU: ID=3, src=1, csize=1, mute=1
0x00 // ch1 ctrl=0, iFeature=0
};
parse_audio_control_desc(ac_desc, sizeof(ac_desc));
return 0;
}
编译与运行
gcc -o ac_parser ac_parser.c
./ac_parser
验证方法
程序应输出4个描述符的解析结果:
- Header:显示
bcdADC=0x0100(v1.0)、wTotalLength=44、bInCollection=1; - Input Terminal:ID=1、类型
0x0101(USB Streaming)、2通道、通道配置0x0003(左右声道); - Output Terminal:ID=2、类型
0x0301(Speaker)、源ID=1; - Feature Unit:ID=3、源ID=1、控制大小1字节、主控制位图
0x01(Mute控制存在)。
若输出与上述字段一致,说明解析逻辑正确。可修改ac_desc数组中的字段值(如通道数、终端类型)来验证解析器对不同输入的适应性。
常见问题
- 描述符长度校验:解析器依赖
bLength字段跳转,若数据损坏会导致越界,代码中已做边界检查。 - Feature Unit变长结构:其长度随通道数变化,解析时需根据
bControlSize和bLength动态计算,本实现仅打印主控制位图,完整解析需遍历每个通道的控制位图。 - 多接口集合:实际设备可能包含多个音频流接口(
bInCollection> 1),此时需结合接口描述符确定各流接口编号,本案例聚焦控制接口本身的解析。