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),此时需结合接口描述符确定各流接口编号,本案例聚焦控制接口本身的解析。