4. 解析音频流描述符

任务目标

本案例实现一个USB音频流接口描述符解析器,能够从原始描述符字节流中提取音频流接口描述符(Audio Streaming Interface Descriptor)、格式类型描述符(Format Type Descriptor)以及特定格式描述符(Specific Format Descriptor),并以可读结构体输出关键字段。

必要背景

USB音频类设备在标准接口描述符之后,会附加类特定描述符。对于音频流(AudioStreaming, AS)接口,其类特定描述符遵循如下顺序:

  • AS接口描述符:包含bTerminalLink、bDelay、wFormatTag等字段,用于描述接口与终端之间的连接关系及默认格式。
  • 格式类型描述符:包含bNrChannels、bSubframeSize、bBitResolution、bSamFreqType等字段,描述音频数据格式与采样率。
  • 特定格式描述符:当bSamFreqType为连续采样率(值为0)时,包含tLowerSamFreq和tUpperSamFreq;当为离散采样率时,包含一组tSamFreq数组。

解析这些描述符是驱动开发中枚举音频接口、配置采样参数的基础步骤。

完整实现代码

#include <stdint.h>
#include <stdio.h>
#include <string.h>

// 音频流接口描述符(类特定)
struct as_interface_desc {
    uint8_t bLength;
    uint8_t bDescriptorType;
    uint8_t bDescriptorSubtype;
    uint8_t bTerminalLink;
    uint8_t bDelay;
    uint16_t wFormatTag;
} __attribute__((packed));

// 格式类型描述符
struct format_type_desc {
    uint8_t bLength;
    uint8_t bDescriptorType;
    uint8_t bDescriptorSubtype;
    uint8_t bFormatType;
    uint8_t bNrChannels;
    uint8_t bSubframeSize;
    uint8_t bBitResolution;
    uint8_t bSamFreqType;
    // 后面跟随采样频率(连续2个uint32或离散n个uint32)
} __attribute__((packed));

// 解析结果容器
struct parsed_as_info {
    struct as_interface_desc as_if;
    struct format_type_desc fmt;
    uint32_t sam_freqs[8];      // 最多支持8个离散采样率
    uint8_t sam_freq_count;     // 实际采样率个数(0表示连续)
    uint32_t lower_freq, upper_freq; // 连续模式上下限
};

// 从描述符缓冲区解析音频流接口
// buf: 指向类特定描述符起始位置(紧跟在标准接口描述符之后)
// len: 缓冲区剩余长度
// out: 输出解析结果
// 返回值: 解析成功返回1,失败返回0
int parse_as_descriptors(const uint8_t *buf, int len, struct parsed_as_info *out) {
    if (!buf || !out || len < 8) return 0;
    
    int offset = 0;
    
    // 1. 解析AS接口描述符(子类型1)
    if (buf[offset] < 8 || buf[offset] > len) return 0;
    if (buf[offset+2] != 0x01) return 0; // 必须是AS接口描述符子类型
    
    out->as_if.bLength = buf[offset];
    out->as_if.bDescriptorType = buf[offset+1];
    out->as_if.bDescriptorSubtype = buf[offset+2];
    out->as_if.bTerminalLink = buf[offset+3];
    out->as_if.bDelay = buf[offset+4];
    memcpy(&out->as_if.wFormatTag, buf+offset+5, 2);
    
    offset += out->as_if.bLength;
    
    // 2. 解析格式类型描述符(子类型2)
    if (offset + 8 > len) return 0;
    if (buf[offset+2] != 0x02) return 0; // 必须是格式类型描述符
    
    out->fmt.bLength = buf[offset];
    out->fmt.bDescriptorType = buf[offset+1];
    out->fmt.bDescriptorSubtype = buf[offset+2];
    out->fmt.bFormatType = buf[offset+3];
    out->fmt.bNrChannels = buf[offset+4];
    out->fmt.bSubframeSize = buf[offset+5];
    out->fmt.bBitResolution = buf[offset+6];
    out->fmt.bSamFreqType = buf[offset+7];
    
    offset += out->fmt.bLength;
    
    // 3. 解析采样频率
    out->sam_freq_count = 0;
    if (out->fmt.bSamFreqType == 0) {
        // 连续模式:3字节低位在前,共6字节
        if (offset + 6 > len) return 0;
        out->lower_freq = buf[offset] | (buf[offset+1] << 8) | (buf[offset+2] << 16);
        out->upper_freq = buf[offset+3] | (buf[offset+4] << 8) | (buf[offset+5] << 16);
        out->sam_freq_count = 0;
    } else {
        // 离散模式:每个采样率3字节
        int count = out->fmt.bSamFreqType;
        if (count > 8 || offset + count*3 > len) return 0;
        for (int i = 0; i < count; i++) {
            out->sam_freqs[i] = buf[offset + i*3] | 
                                (buf[offset + i*3 + 1] << 8) | 
                                (buf[offset + i*3 + 2] << 16);
        }
        out->sam_freq_count = count;
    }
    
    return 1;
}

// 打印解析结果
void print_as_info(const struct parsed_as_info *info) {
    printf("=== AS接口描述符 ===\n");
    printf("bTerminalLink: %u\n", info->as_if.bTerminalLink);
    printf("bDelay: %u\n", info->as_if.bDelay);
    printf("wFormatTag: 0x%04X\n", info->as_if.wFormatTag);
    
    printf("=== 格式类型描述符 ===\n");
    printf("bFormatType: %u\n", info->fmt.bFormatType);
    printf("bNrChannels: %u\n", info->fmt.bNrChannels);
    printf("bSubframeSize: %u\n", info->fmt.bSubframeSize);
    printf("bBitResolution: %u\n", info->fmt.bBitResolution);
    printf("bSamFreqType: %u\n", info->fmt.bSamFreqType);
    
    printf("=== 采样频率 ===\n");
    if (info->sam_freq_count == 0) {
        printf("连续范围: %u - %u Hz\n", info->lower_freq, info->upper_freq);
    } else {
        for (int i = 0; i < info->sam_freq_count; i++) {
            printf("采样率[%d]: %u Hz\n", i, info->sam_freqs[i]);
        }
    }
}

// 测试入口
int main() {
    // 模拟一个USB音频流接口的类特定描述符数据
    // 包含: AS接口描述符(8字节) + 格式类型描述符(19字节, 含3个离散采样率)
    uint8_t test_data[] = {
        // AS接口描述符
        0x08, 0x24, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00,
        // 格式类型描述符 (PCM, 2通道, 16位, 3个离散采样率)
        0x13, 0x24, 0x02, 0x01, 0x02, 0x02, 0x10, 0x03,
        // 采样率: 44100, 48000, 96000 (3字节小端)
        0x44, 0xAC, 0x00,  // 44100
        0x80, 0xBB, 0x00,  // 48000
        0x00, 0x77, 0x01   // 96000
    };
    
    struct parsed_as_info result;
    memset(&result, 0, sizeof(result));
    
    if (parse_as_descriptors(test_data, sizeof(test_data), &result)) {
        print_as_info(&result);
    } else {
        printf("解析失败\n");
    }
    
    return 0;
}

编译与运行

gcc -o as_parser as_parser.c
./as_parser

验证方法

程序使用内置的测试描述符数据(模拟典型USB音频设备:PCM格式、2通道、16位、支持44.1/48/96kHz采样率)。运行后应输出:

  • AS接口描述符:bTerminalLink=1,bDelay=1,wFormatTag=0x0001(PCM)
  • 格式类型描述符:bFormatType=1(线性PCM),bNrChannels=2,bSubframeSize=2,bBitResolution=16,bSamFreqType=3
  • 采样频率:依次输出44100、48000、96000 Hz

若输出与上述一致,说明解析器正确提取了各描述符字段。可修改test_data数组中的采样率值或通道数,观察输出是否同步变化,以验证解析逻辑的通用性。

关键说明

  • 描述符中多字节数值(如wFormatTag、采样频率)均为小端序存储,代码使用位运算手动拼接,避免依赖主机字节序。
  • 解析器通过bDescriptorSubtype字段区分描述符类型(1=AS接口,2=格式类型),并依据bSamFreqType判断后续是连续范围还是离散采样率列表。
  • 实际驱动开发中,描述符数据来自USB控制传输的GET_DESCRIPTOR请求响应,本案例的test_data模拟了该数据布局,解析函数可直接复用。