跳到主要内容

UAC 设备协议栈

本文档描述 XRUSB 的 USB Audio Class 1.0(UAC1) 麦克风设备类实现:LibXR::USB::UAC1MicrophoneQ。该类面向 UAC1 录音(Device → Host) 场景,使用字节队列驱动(queue-driven)将上层产生的 PCM 数据持续送入 Isochronous IN 端点输出,并实现 UAC1 典型控制面(采样率、静音、音量)。

支持能力概览:

  • IAD + AC + AS 的标准 UAC1 描述符组织(AC/AS 两接口)
  • AS 接口 Alt Setting 0/1 切换,按主机选择启动/停止等时流
  • Iso IN 端点 Sampling Frequency Control(3 字节小端)
  • Feature Unit Mute / Volume 控制(UAC1 语义:Volume 单位 1/256 dB)
  • 根据采样率与服务周期自动计算 每次等时服务的发送字节数(FS=1ms;HS 由 bInterval 决定服务频率)

1. 类与模板参数

LibXR::USB::UAC1MicrophoneQ<CHANNELS, BITS_PER_SAMPLE>

模板参数:

  • CHANNELS:通道数(1..8
  • BITS_PER_SAMPLE:位深(8 / 16 / 24

内部常量:

  • K_SUBFRAME_SIZE:每通道每采样的字节数
    • 8-bit → 1
    • 16-bit → 2
    • 24-bit → 3(按 3 字节打包,不是 32-bit 容器)

2. 构造参数与运行时状态

构造函数(核心参数):

  • sample_rate_hz:采样率(Hz)
  • vol_min / vol_max / vol_res:音量范围与步进,单位 1/256 dB
  • speed:USB 速度(Speed::FULL / Speed::HIGH
  • queue_bytes:PCM 队列容量(字节,默认 8192)
  • interval:Iso IN 端点 bInterval
    • Full-Speed:必须为 1(代码中强制)
    • High-Speed:允许 1..16(规范含义为微帧指数调度)
  • iso_in_ep_num:Iso IN 端点号(默认自动分配)

初始化后关键状态:

  • streaming_:AS Alt=1 时为 true(正在流式输出)
  • sr_hz_:当前采样率(可由主机 SET_CUR 动态修改)
  • w_max_packet_size_:运行期计算得到的 wMaxPacketSize(受 FS/HS 上限约束)
  • pcm_queue_:PCM 字节队列(LibXR::LockFreeQueue<uint8_t>

3. 上层数据输入接口(Producer API)

对上层暴露的最小接口如下:

ErrorCode WritePcm(const void* data, size_t nbytes);
size_t QueueSize() const;
size_t QueueSpace();
void ResetQueue();

3.1 PCM 数据格式要求

  • WritePcm() 写入的是交错(interleaved)PCM 字节流,由上层决定具体格式(需与描述符宣告一致):
    • 16-bit:S16LE(小端)
    • 24-bit:S24_3LE(三字节小端)
  • 通道交错:例如 2ch 时为 L0 R0 L1 R1 ...

3.2 队列容量建议

为了避免短时间抖动造成欠载(underflow),建议 queue_bytes 至少覆盖数十毫秒缓冲:

  • 估算:bytes_per_ms ≈ sample_rate_hz * CHANNELS * K_SUBFRAME_SIZE / 1000
  • 示例:48kHz / 2ch / 16-bit → 48000 * 2 * 2 / 1000 = 192 bytes/ms
    8192 bytes ≈ 42 ms 缓冲

4. UAC1 接口与描述符布局

该设备暴露 2 个接口并通过 IAD 关联:

  • Audio Control(AC)接口:实体拓扑与控制(Feature Unit)
  • Audio Streaming(AS)接口:承载音频流(Alt 0/Alt 1)

实现约束:

  • GetInterfaceNum() 返回 2
  • HasIAD() 返回 true

4.1 实体拓扑(Entity Graph)

实体 ID(代码固定):

EntityID说明
Input Terminal (Microphone)1wTerminalType = 0x0201
Feature Unit (Mute/Volume)2控制选择器:Mute(0x01)、Volume(0x02)
Output Terminal (USB Streaming)3wTerminalType = 0x0101

连接关系:

IT_MIC (1) → FU (2) → OT_USB (3) → AS Interface

4.2 AS 接口的 Alternate Setting

  • Alt 0:无端点(不传输)
  • Alt 1:包含 1 个 Isochronous IN 端点(开始传输)

主机切换 Alt Setting 是启动/停止音频流的唯一触发条件(见第 6 节)。

4.3 通道配置 wChannelConfig

当前实现仅对 CHANNELS == 2 显式声明:

  • wChannelConfig = 0x0003(Left Front | Right Front)

其余通道数默认 0x0000(未声明)。如需更严格的多声道位置声明,建议按 UAC1 规范扩展映射表。


5. Isochronous IN 端点与包长约束

端点类型与属性:

  • 类型:Isochronous IN
  • bmAttributes = 0x05(Isochronous + Asynchronous + Data)
  • bInterval
    • FS:固定为 1(1ms 帧)
    • HS:使用构造参数 interval

wMaxPacketSize 的运行时边界:

  • Full-Speed:单事务 ≤ 1023
  • High-Speed:单事务 ≤ 1024

    说明:当前实现只按“单事务/每次服务”计算与限制,未使用 HS 的 multiplier(多事务/微帧)


6. 流控制:SetAltSetting() 的行为

SetAltSetting(itf, alt) 仅对 AS 接口生效:

  • alt = 0(停止传输)
    • streaming_ = false
    • ep_iso_in_->SetActiveLength(0)
    • ep_iso_in_->Close()
  • alt = 1(开始传输)
    • 以当前 w_max_packet_size_ 重新配置端点
    • 重置余数累加器 acc_rem_ = 0
    • streaming_ = true
    • 立即调用 KickOneFrame() 投递首帧

GetAltSetting() 返回值与 streaming_ 一致(true→1,false→0)。


7. 时序与每帧字节数计算(RecomputeTiming()

该函数在两类时机调用:

  • 构造时(初始化采样率)
  • 主机通过端点 SET_CUR 修改采样率后(见第 9 节)

7.1 服务频率 service_hz_

  • Full-Speed:固定 1000 Hz(1ms 帧);并强制 interval_ == 1
  • High-Speed:按 bInterval 计算服务频率(每秒微帧 8000)
    • service_hz_ = 8000 / 2^(bInterval-1)
    • bInterval 被钳制到 1..16

7.2 每秒字节数 bytes_per_sec_

bytes_per_sec_ = sr_hz_ * CHANNELS * K_SUBFRAME_SIZE

7.3 每次服务的目标字节数(base + remainder)

base_bytes_per_service = bytes_per_sec_ / service_hz_
rem_bytes_per_service = bytes_per_sec_ % service_hz_

实现使用余数累加器均匀分布 rem

  • 每次先发送 base_bytes_per_service
  • acc_rem_ += rem_bytes_per_service
  • acc_rem_ >= service_hz_,本次额外 +1 字节并 acc_rem_ -= service_hz_

随后对结果进行包长钳制(FS≤1023,HS≤1024),形成 w_max_packet_size_


8. 数据发送策略(队列取数 + 等时 IN)

8.1 核心流程:KickOneFrame()

触发条件:

  • streaming_ == true
  • ep_iso_in_ 状态为 IDLE

处理步骤:

  1. 计算本次服务的目标发送字节数 to_send
  2. to_send 钳制到 w_max_packet_size_ 与端点缓冲区大小
  3. pcm_queue_ 取数据:
    • take = min(queue_size, to_send)
    • PopBatch(buf, take)
  4. 调用 ep_iso_in_->Transfer(take) 提交传输

8.2 欠载行为(Underflow)

当前实现 实际提交长度为 take,当队列不足时将发送 短包(short packet)


9. 类请求处理(控制面)

实现覆盖两类控制:

  1. 端点采样率控制(Recipient = Endpoint)
  2. Feature Unit 控制(Recipient = Interface / Entity)

9.1 端点采样率控制(Sampling Frequency Control)

匹配条件(实现逻辑):

  • wIndex & 0xFF 等于 Iso IN 端点地址
  • (wValue >> 8) == 0x01(Sampling Freq Control Selector)
  • 数据长度必须为 3 字节(24-bit little-endian)

支持请求:

RequestwLength行为
GET_CUR3返回 sf_cur_[3]
SET_CUR3数据阶段写入 sf_cur_,并置 pending_set_sf_ = true
GET_MIN / GET_MAX3返回 sf_cur_(单频点实现)
GET_RES3返回 {1,0,0}(1 Hz)

数据阶段 OnClassData()

  • bRequest == SET_CUR && pending_set_sf_
    • 解析 sf_cur_ 得到 NEW_SR
    • NEW_SR > 0 且与当前不同则更新 sr_hz_RecomputeTiming()
    • pending_set_sf_

9.2 Feature Unit:Mute / Volume

匹配条件:

  • ITF = (wIndex & 0xFF) 必须等于 AC 接口号 itf_ac_num_
  • ENT = (wIndex >> 8) 必须等于 Feature Unit ID(ID_FU = 2
  • CH = (wValue & 0xFF):0=Master,1..CHANNELS

Mute(1 字节)

  • SET_CUR (wLength=1):写入 mute_
  • GET_CUR (wLength=1):读出 mute_

Volume(2 字节,单位 1/256 dB)

  • SET_CUR (wLength=2):写入 vol_cur_
  • GET_CUR (wLength=2):读出 vol_cur_
  • GET_MIN / GET_MAX / GET_RES (wLength=2):返回构造参数 vol_min_ / vol_max_ / vol_res_

10. 使用示例

#include "uac_mic.hpp"

using Mic = LibXR::USB::UAC1MicrophoneQ<2, 16>; // 2ch, 16-bit

Mic mic(/*sample_rate*/48000,
/*vol_min*/-90*256, /*vol_max*/0, /*vol_res*/256,
/*speed*/LibXR::USB::Speed::FULL,
/*queue_bytes*/8192,
/*interval*/1);

// USB Device 初始化时将 &mic 放入 class 列表:{{&mic}}
// usb_dev.Init();
// usb_dev.Start();

// 上层:持续写入 S16LE 交错 PCM
mic.WritePcm(pcm_bytes, pcm_len);

主机在 AS 接口选择 Alt=1 后开始取流;切回 Alt=0 即停止。