音频主机方案

概述

USB 音频类 (USB Audio Class, UAC) 协议定义了通过 USB 接口传输音频数据的工业标准。在主机 (Host) 模式下,Ameba 平台能够通过该协议识别并驱动外部 USB 音频设备。

当前 Ameba 平台的 UAC 主机协议栈专注于 音频播放 (Playback) 场景。它集成了符合 USB-IF 标准的 UAC 1.0 协议,将外接的 USB UAC 设备抽象为系统本地音频输出接口。该方案支持即插即用,能够与系统内置音频处理框架无缝对接,为设备提供便捷、高质量的音频输出扩展能力。

Ameba USB UAC 主机

特性

Ameba UAC Host 驱动旨在提供稳定、兼容的音频输出能力,主要特性如下:

  • 广泛的设备兼容性:支持符合 UAC 1.0 标准的 USB 设备(如 USB 音箱、USB 耳机、USB 转 3.5mm 适配器)。

  • 自动化枚举配置:自动解析设备描述符,识别音频流端点,并建立等时 (Isochronous) 传输通道。

  • 支持主流音频格式:参考 支持的音频格式 了解更多信息。

  • 系统级深度集成:向上层应用暴露统一的 API 接口,屏蔽底层 USB 传输细节。

  • 热插拔支持 (Hot-Plug):支持 USB 外设的即插即用与动态移除,无需重启系统。

应用场景

作为 USB 主机,Ameba 负责枚举 UAC 设备、解析音频描述符,并建立稳定的数据传输通道。该方案适用于对音频播放质量有要求,同时追求低开发复杂度的嵌入式应用,例如:

  • 智能音频播报终端:Ameba 通过外接 UAC 设备(如有源音箱或耳机),用于语音提示、广告播报或公共广播系统。

  • 数字标牌与信息亭:结合本地存储或网络流媒体,通过 USB 高保真设备播放背景音乐或多媒体解说,提升用户体验。

  • IoT 音频网关:作为轻量级音频响应节点,接收来自 Wi-Fi 或云端的音频指令/内容,并通过通用 USB 音频外设进行输出。

协议简介

备注

当前主机方案仅支持 UAC 1.0

UAC (USB Audio Class) 是由 USB-IF 定义的通用音频设备类标准,旨在规范数字音频数据流在 USB 接口上的封装与传输方式。 USB 音频设备(如 USB 音响、耳机、麦克风)通过 数据传输功能控制 的标准接口,能够在主机系统中被自动识别为标准的音频输入/输出终端,无需安装专有驱动。

版本对比

USB-IF 官方已发布多个 UAC 协议版本。各主流版本的规范文档下载链接如下表所示:

版本

文档

1.0

Audio Device Document 1.0

2.0

Audio Device Rev. 2.0

术语定义

本文档涉及的通用 UAC 技术术语定义如下:

术语

描述

AC Interface (Audio Control Interface)

音频控制接口。负责管理音频设备的拓扑结构(如 Input/Output Terminal、Feature Unit),并通过控制端点(Control Endpoint)实现音量调节、静音开关等控制指令的下发。

AS Interface (Audio Streaming Interface)

音频流接口。负责音频负载数据的实际传输,通常使用等时(Isochronous)端点。一个 AS 接口可以包含多个备用设置(Alternate Settings),分别对应不同的采样率、位宽或通道配置。

Terminal Type

终端类型。用于标识音频信号源或接收器的物理/逻辑属性(例如:USB Streaming Terminal 代表 USB 数据流,Speaker Terminal 代表扬声器)。

Feature Unit

功能单元。音频拓扑中的处理节点,提供具体的音频控制能力(如主音量调节、各声道增益、静音控制)。

Sample Rate / Bit Resolution

采样率与位宽。U 音频格式的核心参数。UAC 1.0 常见组合包括 48 kHz / 16-bit、44.1 kHz / 16-bit 等。

本文档涉及的通用音频技术术语定义如下:

术语

描述

PCM

脉冲编码调制(Pulse Code Modulation),一种将模拟音频信号转换为数字信号的编码方式。

声道数(Channel)

指独立的音频信号流。声道数通常对应声源或扬声器的数量(如单声道、立体声)。

位深(Bit Depth)

音频采样时的量化精度,表示每个采样点所占用的位数。位深越大,动态范围越大,音质越精细。

采样率(Sampling Rate)

每秒钟对音频信号进行采样的次数。采样率越高,能还原的高频信号越多,音频保真度越高。

协议框架

UAC 系统架构通过定义不同的接口集合来支持音频数据的传输与控制。从逻辑功能上,UAC 设备接口主要分为两大类:音频控制接口 (Audio Control Interface)音频流接口 (Audio Streaming Interface)

UAC 2.0:
../../../_images/usb_uac20_audio_control_topology.png
  • 音频控制接口(AC Interface)

    • 负责管理音频设备的整体功能行为,例如音量调节、静音控制、输入源选择等。

    • 一个 AC 接口内部包含定义的拓扑结构(Topology),用于描述音频信号从输入终端(Input Terminal)到输出终端(Output Terminal)的流向及处理过程。

  • 音频流接口 (AS Interface)

    • 负责传输实际的音频负载数据。

    • 一个 UAC 设备可包含多个 AS 接口,每个接口可配置为传输不同格式、不同采样率或不同位深的音频数据。

协议交互示例:

../../../_images/usb_uac_spec.svg

描述符结构

UAC 设备除遵循标准的 USB 描述符(如设备描述符、配置描述符、端点描述符)外,还定义了类特定描述符 (Class-Specific Descriptors)。这些描述符依据其所属接口,分为类特定控制接口描述符和类特定音频流接口描述符。

不同协议版本在描述符的定义上存在差异:

UAC 2.0:

Descriptor Topology

Device Descriptor
└── Identifies basic device information (USB Version 2.00)

Configuration Descriptor
├── Contains total length of the entire configuration, power supply information, etc.
│
├── Interface Association Descriptor (IAD)
│       └── Binds audio control and streaming interfaces into a single functional unit
│
├── Audio Control (AC) Interface Descriptor (Interface 0)
│       ├── Standard Interface Descriptor (Interface 0, Control Class)
│       └── Class-Specific Descriptor Collection
│               ├── Audio Control Interface Header (declares UAC version)
│               ├── Clock Source (internal clock or external clock)
│               ├── Clock Source (internal clock or external clock)
│               ├── Input Terminal (source of audio stream)
│               ├── Feature Unit (volume/mute controls, etc.)
│               ├── Output Terminal (destination of audio stream)
│               ├── Input Terminal (source of audio stream)
│               ├── Feature Unit (volume/mute controls, etc.)
│               └── Output Terminal (destination of audio stream)
│
├── Audio Streaming (AS) Interface Descriptor (Interface 1)
│       ├── Alternate Setting 0: Control transfer active state (control transfer only)
│       │
│       ├── Alternate Setting 1: Data transfer active state (with data endpoint)
│       │ ├── Standard Interface Descriptor (Interface 1, Streaming Class)
│       │ ├── Class-Specific AS Interface (associated USB streaming terminal, audio format, number of channels)
│       │ ├── Format Descriptor (audio format and bit width)
│       │ ├── Standard Endpoint Descriptor (ISO OUT endpoint)
│       │ └── Class-Specific Endpoint Descriptor (no special control)
│       │
│       ├── Alternate Setting 2
│       │ ...... Can configure multiple different setting as needed
│
└── Audio Streaming (AS) Interface Descriptor (Interface 2)
        ├── Alternate Setting 0: Control transfer active state (control transfer only)
        │
        ├── Alternate Setting 1: Data transfer active state (with data endpoint)
        │ ├── Standard Interface Descriptor (Interface 2, Streaming Class)
        │ ├── Class-Specific AS Interface (associated USB streaming terminal, audio format, number of channels)
        │ ├── Format Descriptor (audio format and bit width)
        │ ├── Standard Endpoint Descriptor (ISO IN endpoint)
        │ └── Class-Specific Endpoint Descriptor (no special control)
        │
        ├── Alternate Setting 2
        │ ...... Can configure multiple different setting as needed

Device Qualifier Descriptor
└── Device information while running in another speed mode

Other Speed Configuration Descriptor
├── Configuration information while running in another speed mode.
│
├── Interface Association Descriptor (IAD)
│       └── Binds audio control and streaming interfaces into a single functional unit
│
├── Audio Control (AC) Interface Descriptor (Interface 0)
│       ├── Standard Interface Descriptor (Interface 0, Control Class)
│       └── Class-Specific Descriptor Collection
│               ├── Audio Control Interface Header (declares UAC version)
│               ├── Clock Source (internal clock or external clock)
│               ├── Clock Source (internal clock or external clock)
│               ├── Input Terminal (source of audio stream)
│               ├── Feature Unit (volume/mute controls, etc.)
│               ├── Output Terminal (destination of audio stream)
│               ├── Input Terminal (source of audio stream)
│               ├── Feature Unit (volume/mute controls, etc.)
│               └── Output Terminal (destination of audio stream)
│
├── Audio Streaming (AS) Interface Descriptor (Interface 1)
│       ├── Alternate Setting 0: Control transfer active state (control transfer only)
│       │
│       ├── Alternate Setting 1: Data transfer active state (with data endpoint)
│       │ ├── Standard Interface Descriptor (Interface 1, Streaming Class)
│       │ ├── Class-Specific AS Interface (associated USB streaming terminal, audio format, number of channels)
│       │ ├── Format Descriptor (audio format and bit width)
│       │ ├── Standard Endpoint Descriptor (ISO OUT endpoint)
│       │ └── Class-Specific Endpoint Descriptor (no special control)
│       │
│       ├── Alternate Setting 2
│       │ ...... Can configure multiple different setting as needed
│
└── Audio Streaming (AS) Interface Descriptor (Interface 2)
        ├── Alternate Setting 0: Control transfer active state (control transfer only)
        │
        ├── Alternate Setting 1: Data transfer active state (with data endpoint)
        │ ├── Standard Interface Descriptor (Interface 2, Streaming Class)
        │ ├── Class-Specific AS Interface (associated USB streaming terminal, audio format, number of channels)
        │ ├── Format Descriptor (audio format and bit width)
        │ ├── Standard Endpoint Descriptor (ISO IN endpoint)
        │ └── Class-Specific Endpoint Descriptor (no special control)
        │
        ├── Alternate Setting 2
        │ ...... Can configure multiple different setting as needed

UAC Audio Control (AC) Interface Descriptor

  • Audio Control Interface Header

Audio Control Interface Header Descriptor
├── bLength            : 1 byte  → Total descriptor length (fixed = 9)
├── bDescriptorType    : 1 byte  → 0x24 (CS_INTERFACE)
├── bDescriptorSubtype : 1 byte  → 0x01 (HEADER)
├── bcdADC             : 2 bytes → Audio Device Class Specification Release Number
├── bCategory          : 1 byte  → Indicates the classification/function of the device
├── wTotalLength       : 2 bytes → Total length of all AC Class-Specific descriptors, including this one
└── bmControls         : 1 byte  → Bitmap indicating the availability of non-addressable control functions
  • Clock Source Descriptor

Clock Source Descriptor
├── bLength            : 1 byte  → Total descriptor length (fixed = 8)
├── bDescriptorType    : 1 byte  → 0x24 (CS_INTERFACE)
├── bDescriptorSubtype : 1 byte  → 0x0A (Clock Source)
├── bClockID           : 1 byte  → Unique Clock ID, ranging from 1 to 255
├── bmAttributes       : 1 byte  → Clock type (0=Internal, 1=External)
├── bmControls         : 1 byte  → Bitmap indicating the control attributes of the clock
└── iClockSource       : 1 byte  → String descriptor index
  • Input Terminal Descriptor

Input Terminal Descriptor
├─ bLength                : 1 byte   → Total descriptor length (fixed = 17)
├─ bDescriptorType        : 1 byte   → 0x24 (CS_INTERFACE)
├─ bDescriptorSubtype     : 1 byte   → 0x02 (INPUT_TERMINAL)
├─ bTerminalID            : 1 byte   → Unique ID of this terminal (referenced in topology)
├─ wTerminalType          : 2 bytes  → Terminal type (little-endian)
│                           ├─ 0x0101 = USB Streaming (Host audio stream input)
│                           └─ Other values refer to UAC2.0 Appendix B (e.g., Microphone)
├─ bAssocTerminal         : 1 byte   → Associated Output Terminal ID (0 = no pairing)
├─ bCSourceID             : 1 byte   → Associated Clock Source ID
├─ bNrChannels            : 1 byte   → Number of logical output channels (e.g., 2 = stereo)
├─ bmChannelConfig        : 4 bytes  → Spatial location bitmap for channels
├─ iChannelNames          : 1 byte   → String index for channel names
├─ bmControls             : 2 byte   → Control bitmap
└─ iTerminal              : 1 byte   → String index for describing this terminal
  • Feature Unit Descriptor

Feature Unit Descriptor
├─ bLength                : 1 byte   → otal descriptor length in bytes
│                                      = 6 + (1 + bNrChannels) × 4
├─ bDescriptorType        : 1 byte   → = 0x24 (CS_INTERFACE)
├─ bDescriptorSubtype     : 1 byte   → = 0x06 (FEATURE_UNIT)
├─ bUnitID                : 1 byte   → Unique ID of this Feature Unit
├─ bSourceID              : 1 byte   → ID of the connected Source Unit or Terminal
├─ bmaControls[0]         : 4 bytes  → Master Channel Control Bitmap
├─ bmaControls[1]         : 4 bytes  → Logical Channel 1 Control Bitmap
├─ bmaControls[2]         : 4 bytes  → Logical Channel 2 Control Bitmap
│    ⋮
├─ bmaControls[N]         : 4 bytes  → Logical Channel N Control Bitmap (Total bNrChannels entries)
└─ iFeature               : 1 byte   → String descriptor index
  • Output Terminal Descriptor

Output Terminal Descriptor
├─ bLength                : 1 byte   → Total descriptor length (fixed = 12 bytes)
├─ bDescriptorType        : 1 byte   → 0x24 (CS_INTERFACE)
├─ bDescriptorSubtype     : 1 byte   → 0x03 (OUTPUT_TERMINAL)
├─ bTerminalID            : 1 byte   → Unique ID of this terminal
├─ wTerminalType          : 2 bytes  → Terminal type
│                           ├─ 0x0301 = Speaker
│                           ├─ 0x0302 = Headphones
│                           ├─ 0x0603 = SPDIF
│                           └─ Other values refer to Appendix B
├─ bAssocTerminal         : 1 byte   → Associated Input Terminal ID
├─ bSourceID              : 1 byte   → ID of the connected Source Unit or Terminal
├─ bCSourceID             : 1 byte   → Associated Clock Source ID
└─ iTerminal              : 1 byte   → String index for describing this terminal

Audio Streaming Interface Descriptor

  • Class-Specific AS Interface Descriptor

Class-Specific AS Interface Descriptor
├─ bLength                : 1 byte   → Fixed as 0x10 (16 bytes)
├─ bDescriptorType        : 1 byte   → 0x24(CS_INTERFACE)
├─ bDescriptorSubtype     : 1 byte   → 0x01(AS_GENERAL)
├─ bTerminalLink          : 1 byte   → Associated Terminal ID (Input or Output Terminal)
├─ bmControls             : 1 bytes  → Bitmap of endpoint control capabilities
├─ bFormatType            : 1 byte   → Format type
│                           └─ 0x01 = FORMAT_TYPE_I(PCM)
├─ bmFormats              : 4 bytes  → Bitmap of supported audio formats
├─ bNrChannels            : 1 bytes  → Number of supported audio channels
├─ bmChannelConfig        : 4 bytes  → Supported audio channel configuration bitmap
└─ iChannelNames          : 1 byte   → String index for channel names
  • Audio Streaming Format Type Descriptor

Audio Streaming Format Type Descriptor
├─ bLength            : 1 byte   → Total length of descriptor in bytes (6 bytes)
├─ bDescriptorType    : 1 byte   → = 0x24(CS_INTERFACE)
├─ bDescriptorSubtype : 1 byte   → = 0x02(FORMAT_TYPE)
├─ bFormatType        : 1 byte   → = 0x01(FORMAT_TYPE_I)
├─ bSubslotSize       : 1 byte   → Container size for each audio sample (in bytes)
│                                   • Typical values: 1, 2, 3, 4
├─ bBitResolution     : 1 byte   → Number of valid bits in each sample(≤ bSubslotSize × 8)
└─                                  • Example: 16 represents 16-bit PCM

备注

详细的字段定义请参考 USB-IF 官方 UAC 协议文档。

类特定请求

UAC 主机对设备的控制请求分为 标准请求(Standard Requests)类特定请求 (Class-Specific Requests)

本节主要介绍 UAC 特有的 类特定请求,这些请求用于实现音频设备的特有功能,主要包括音频控制请求(针对 AC 接口)和音频流请求(针对 AS 接口)

UAC 2.0:
  • 音频控制请求 (AC 请求)

    音频控制请求是主机通过端点 0 发送的类专用控制传输,用于在 USB 音频设备内动态配置和管理音频功能。

音频控制类型

要求

描述

Mute Control Request

可选

操作音频功能中功能单元的静音控制

Volume Control Request

可选

操作音频功能中功能单元的音量控制

Sampling Frequency Control

可选

操作时钟信号的实际采样频率

Mixer Unit Control Request

可选

操作音频功能中混音单元的内部控制

Terminal Control Request

可选

操作音频功能中混音单元的内部控制

Selector Unit Control Request

可选

操作音频功能中选择器单元的内部控制

Effect Unit Control Request

可选

操作音频功能中效果单元的内部控制

Processing Unit Control Request

可选

操作音频功能中处理单元的内部控制

Extension Unit Control Requests

可选

操作音频功能中扩展单元的内部控制

  • 音频流请求(AS 请求)

    音频流请求是主机通过端点 0 发起的类专用控制传输,用于配置和管理音频数据流相关参数的核心类请求,其作用聚焦于音频流的建立、参数配置、状态管理等。

音频控制类型

要求

描述

Interface Control Request

可选

操作音频功能中音频流接口内的控制项

Encoder Control Request

可选

操作音频功能中音频流接口内的编码器控制项

Decoder Control Request

可选

操作音频功能中音频流接口内的解码器控制项

Endpoint Control Request

可选

操作音频功能中音频流端点内的控制项

数据传输格式

UAC 音频数据流通常采用 线性 PCM (Linear PCM) 编码,并以 多声道交错 (Interleaved) 的方式进行封装。具体的数据排列顺序取决于通道数量和位深。

更多支持格式细节请参考 支持的音频格式 了解更多细节。

两声道 (2-Channel) 交错数据示例如下图:

../../../_images/usb_uac_audio_2_channel_data_interleaved.svg

四声道 (4-Channel) 交错数据示例:

../../../_images/usb_uac_audio_4_channel_data_interleaved.svg

N 声道 (N-Channel) 交错数据示例:

../../../_images/usb_uac_audio_n_channel_data_interleaved.svg

备注

声道对齐规则

UAC 协议要求传输的声道数通常应为 2 的幂次(如 2, 4, 8, 16 等)。如果实际物理声道数(如 10 声道)不符合此规则,则必须向上取整至最近的 2 的幂次(配置为 16 声道)进行传输,多余的通道位置填充无效数据。

类驱动

本节详细阐述了 USB Host UAC 1.0 驱动栈的内部架构、关键模块职责、类特定请求的支持情况以及底层通道资源的分配策略。

驱动框架

USB Host UAC 1.0 驱动栈采用分层模块化设计,通过清晰定义的接口实现音频子系统与 USB 硬件控制器之间的高效交互。该架构重点优化了 等时传输 (ISOC Transfer) 的实时性处理,确保高保真音频流的稳定输出。

系统自上而下分为以下核心功能模块:

音频适配层 (Audio Adapter Layer)

作为 USB 驱动与上层音频框架的中间件,该层主要负责:

  • 写音频数据 (Write):当数据的生产者 (Producer),从上层音频缓冲区读取 PCM 数据,并写到 UAC 类驱动的环形缓冲区。

  • 控制交互:将上层应用的音量调节、静音开关等操作,映射为 UAC 类驱动的控制 API 调用。

UAC 类驱动架构

这是 UAC 类驱动的核心组件,由协议处理逻辑与 环形缓冲管理 (Ringbuffer Management) 协同构成。

  • 枚举与配置解析:自动识别 Audio Control (AC) 和 Audio Streaming (AS) 接口,解析 Terminal 类型、Feature Unit 及采样率/位宽等参数。

  • 流管理:根据目标音频参数,动态选择 AS 接口的备用设置 (Alternate Setting),激活对应的等时 OUT 端点。

  • 缓冲调度:维护环形缓冲区,处理数据的封包与调度,确保持续、无抖动的数据供给。

  • 协议封装:封装标准 USB 请求与 UAC 类特定请求(如 SET_CUR),实现对设备的控制。

USB Core 驱动

实时响应硬件中断,负责处理 USB 标准枚举、传输管理以及底层的物理数据传输调度等。

核心交互接口

UAC Host 类驱动在系统架构中起着承上启下的作用,其实现逻辑主要围绕以下三个核心交互接口展开:

  • 主机类驱动回调 API:类驱动通过定义并注册一个标准的 usbh_class_driver_t 结构体与底层 USB Core 进行交互。

  • 面向应用的回调 API:类驱动通过 usbh_uac_cb_t 回调结构体向上层应用提供异步事件通知机制。

  • 面向应用的 API:应用层调用这些 API 后,驱动会切换内部状态机的状态,开启数据传输的调度。

驱动回调机制:

../../../_images/usb_host_uac.svg

加载与卸载类驱动

这两个函数负责内存资源的分配与释放,以及类驱动向 USB 核心的注册与注销。

Init:

usbh_uac_init() 是加载 UAC 主机类驱动的顶层函数,主要完成以下任务:

  • 保存用户提供的回调函数,并调用用户 init 回调。

  • 保存用户配置的环形缓冲区的帧数 frame_cnt

  • 分配内存,包括用于控制传输的缓冲和用于 TX 音频传输的缓冲。

  • 调用 usbh_register_class() 注册 UAC 类驱动到 USB 主机核心。

示例:

int usbh_uac_init(usbh_uac_cb_t *cb, int frame_cnt)
{
    /* 1. Save the frame count param */
    uac->frame_cnt = frame_cnt;

    /* 2. Allocate memory */
    uac->audio_ctrl_buf = (u8 *)usb_os_malloc(UBSH_UAC_AUDIO_CTRL_BUF_MAX_LEN);
    uac->isoc_tx_buf = (u8 *)usb_os_malloc(USBH_UAC_ISOC_BUF_LENGTH);

    /* 3. Save the user callback and call the user's ``init`` callback */
    if (cb != NULL) {
      uac->cb = cb;
      if (cb->init != NULL) {
        ret = cb->init();
        if (ret != HAL_OK) {
          RTK_LOGS(TAG, RTK_LOG_ERROR, "User init err %d\n", ret);
          return ret;
        }
      }
    }

    /* 4. Register class driver*/
    usbh_register_class(&usbh_uac_driver);

    return HAL_OK;
}

连接与断连处理

当 USB 核心检测到匹配 UAC 类的设备插入或拔出时,会触发相应的回调函数。

Attach:

usbh_uac_cb_attach 是设备枚举的关键步骤,负责解析接口描述符并分配管道资源:

  • 查找音频控制接口 (AC Interface):解析获取音频控制信息,包括音量范围、静音控制能力等。

  • 查找音频数据接口 (AS Interface):解析获取音频格式信息及端点描述符。

  • 打开管道:根据获取到的描述符信息,分配并打开 ISOC OUT 管道。

  • 初始化状态机:将状态置为 UAC_STATE_GET_MUTE 初始状态,准备获取音频信息。

  • 通知应用层:调用用户 attach 回调,告知应用层连接状态。

static int usbh_uac_cb_attach(usb_host_t *host)
{
  /* 1. Parse descriptors to get AC and AS information */
  status = usbh_uac_parse_interface_desc(host);
  if (status) {
    return status;
  }

  /* 2. Open the pipe for steaming transfer */
  if (uac->as_isoc_out) {
      as_itf = uac->as_isoc_out;
      as_itf->choose_alt_idx = 0;

      pipe = &(as_itf->pipe);
      ep_desc = &(as_itf->itf_info_array[as_itf->choose_alt_idx].ep_desc);

      usbh_open_pipe(host, pipe, ep_desc);
  }

  /* 3. Initialize the state machine */
  uac->ctrl_state = UAC_STATE_GET_MUTE;

  /* 4. Notify the user layer */
  if ((uac->cb != NULL) && (uac->cb->attach != NULL)) {
      uac->cb->attach();
  }
  return HAL_OK;
}

类驱动状态机

usbh_uac_cb_process 回调函数是主机端 UAC 类的核心状态机处理函数。

与设备端被动响应请求不同,主机端驱动需要主动维护设备状态。它的核心职责是维护类驱动的生命周期状态,并分发传输任务。

状态机管理与调度

usbh_uac_cb_process 根据当前类驱动状态管理控制传输(如采样率配置)和数据传输的调度。

状态枚举

描述

关键动作

IDLE

空闲状态

等待用户指令或数据传输请求。

TRANSFER

数据传输中

根据管道号分发任务到具体的 TX/RX 处理函数。

ERROR

错误状态

尝试清除端点特征 (Clear Feature) 以恢复通信。

传输处理分发示例

当处于 TRANSFER 状态时,根据触发事件的管道号(Pipe ID),将处理分发给具体的传输处理函数。

示例:

case UAC_STATE_TRANSFER:
     /* Distribute transmission tasks according to pipe numbers */
     if (event.msg.pipe_num == 0) {
         ret = usbh_uac_ctrl_setting(host, 0);      // Handle ctrl message transfer
     }
  break;

错误恢复

在其他状态处理发生错误时,驱动会尝试发送 Clear Feature 请求并恢复到 IDLE 状态。

示例:

/* ... Error state ... */
case UAC_STATE_ERROR:
    /* Error recovery mechanism */
    ret = usbh_ctrl_clear_feature(host, 0x00U);
    if (ret == HAL_OK) {
        uac->xfer_state = UAC_STATE_IDLE;
    }
break;

数据传输处理

UAC 数据传输主要分为两类:音频控制接口数据传输音频数据流传输

音频控制传输(Control Transfer)

负责管理音频设备的功能行为,驱动实现了如下状态机流程来处理配置请求:

SetInterface:
case UAC_STATE_SET_OUT_ALT:
    ret = usbh_uac_process_set_out_alt(host);
    if (ret == HAL_OK) {
        uac->ctrl_state = UAC_STATE_SET_OUT_FREQ;
    } else if (ret != HAL_BUSY) {
        RTK_LOGS(TAG, RTK_LOG_ERROR, "OUT alt err\n");
        uac->ctrl_state = UAC_STATE_SET_OUT_FREQ;
    }
    break;

音频数据流传输(ISOC Transfer)

为确保音频流的实时性与低延迟,UAC 类驱动通过 USB 硬件中断机制(帧首中断 SOF 或传输完成中断 Complete)来精准触发下一帧数据的传输调度。

  • Completed (传输完成):传输完成中断,表示当前数据已成功发送至总线。

当端点传输间隔 (bInterval) 为 1 时(即每帧都需要传输),为了最大化带宽利用率并降低延迟,驱动采取 紧凑调度策略:在上一帧数据的 Completed 回调中,立即填装并提交下一帧的数据传输请求。

  • SOF (Start Of Frame):帧起始中断。

当端点传输间隔 (bInterval) 大于 1 时(即每隔 N 帧传输一次),驱动采取 间隔调度策略,利用 SOF 中断计数器跟踪帧号,一旦达到预定的发送间隔,立即准备并提交下一帧数据传输请求。

紧凑调度:

代码示例 (Completed 回调逻辑):

static usbh_class_driver_t usbh_uac_driver = {
    .completed = usbh_uac_cb_completed,
};

static int usbh_uac_cb_completed(usb_host_t *host, u8 pipe_num)
{
    u32 cur_frame = usbh_get_current_frame_number(host);

    if (pdata_ctrl->next_xfer == 1) {
        /* Check if this is the audio streaming pipe */
        if ((uac->as_isoc_out) && (pipe_num == uac->as_isoc_out->pipe.pipe_num)) {
            pipe->xfer_state = USBH_EP_XFER_IDLE;

            /* If ring buffer has data, prepare next transfer */
            if (!usb_ringbuf_is_empty(&(pdata_ctrl->buf_manager))) {

                /* Trigger next xfer logic: check if interval is met */
                if (usbh_uac_frame_num_dec(usbh_uac_frame_num_inc(cur_frame, 1), pipe->frame_num) >= pipe->ep_interval) {
                    usbh_uac_isoc_out_process_xfer(host, cur_frame);
                } else {
                    /* Wait for next SOF if interval not met yet */
                    pipe->xfer_state = USBH_EP_XFER_WAIT_SOF;
                }
            } else {
                /* Buffer empty, go idle */
                pipe->xfer_state = USBH_EP_XFER_IDLE;
            }
        }
    }

    return HAL_OK;
}

面向应用的 API

提供给上层应用的接口,用于获取信息、配置设备和传输数据。

配置类

数据流类

  • usbh_uac_write():将 PCM 数据写入驱动内部的环形缓冲区。类驱动会自动根据音频格式将大数据包分割为符合 USB 帧大小的小包。

  • usbh_uac_start_play():启动播放流程,驱动开始从环形缓冲区提取数据并发送。

  • usbh_uac_stop_play():停止播放,终止 ISOC OUT 传输。

类特定请求实现

本驱动栈已内置 USB Audio Class 1.0 规范定义的核心请求封装。当前实现重点支持音频播放场景,默认启用了针对 Feature Unit 的控制请求。

开发者可在 {SDK}/component/usb/host/uac 路径下查看源码,并根据需求扩展更多请求类型。

类特定请求类型

备注

Mute Control

静音控制。发送 SET_CUR 请求至 Feature Unit 的 Mute Control Selector,实现静音/取消静音。

Volume Control

音量控制。发送 SET_CUR 请求至 Feature Unit 的 Volume Control Selector,设置增益值。

通道配置

在设备枚举阶段,驱动会解析配置描述符。当检测到音频流接口 (AS Interface) 处于活动状态时,驱动将自动申请相应的 USB 管道 (Pipe) 资源。

管道类型

用途描述

控制 IN/OUT 通道

用于发送标准设备请求(枚举、配置)以及 UAC 类特定请求(音量、采样率设置)。

等时 OUT 通道

归属于 Audio Streaming (AS) 接口的 Active Alternate Setting(通常为 Alt 1)。用于主机向 UAC 设备发送 PCM 音频数据流(TX Data)。

API 说明

驱动 API

应用示例

应用设计

本节详细介绍开发一个完整的 USB UAC 1.0 主机应用所需的流程,涵盖驱动初始化、热插拔事件处理、音频数据写入机制以及资源释放策略。

驱动初始化

在使用 UAC 1.0 Host 驱动前,必须按顺序完成硬件参数配置、回调函数注册以及核心协议栈的初始化。

步骤说明:

  • 硬件配置:设置 USB 速度模式(Full Speed)及中断/任务优先级。

  • 回调注册:定义用户回调结构体,挂载各个阶段(连接、断开、数据传输)的处理函数。

  • 核心启动:依次调用 usbh_init()usbh_uac_init() 启动协议栈。

/*
 * 1. Configure USB speed (Full Speed) ,isr priority and main task priority.
 */
static usbh_config_t usbh_cfg = {
    .speed = USB_SPEED_FULL,
    .isr_priority = INT_PRI_MIDDLE,
    .main_task_priority = USBH_UAC_MAIN_THREAD_PRIORITY,
    .ext_intr_enable = USBH_SOF_INTR,
};
/*
 * Define USB user-level callbacks.
 */
static usbh_user_cb_t usbh_usr_cb = {
    .process = usbh_uac_cb_process,            /* USB callback to handle class-independent events in the application */
};

/*
 * 2. Define user callbacks for UAC events.
 */
static usbh_uac_cb_t usbh_uac_cfg = {
    .init   = usbh_uac_cb_init,                /* USB init callback */
    .deinit = usbh_uac_cb_deinit,              /* USB deinit callback */
    .attach = usbh_uac_cb_attach,              /* USB attach callback */
    .detach = usbh_uac_cb_detach,              /* USB detach callback */
    .setup  = usbh_uac_cb_setup,               /* USB setup callback */
    .isoc_transmitted     = usbh_uac_cb_isoc_transmitted,    /* Data transmission complet callback */
};

int ret = 0;

/**
 * 3. Initialize USB host core driver with configuration.
 */
ret = usbh_init(&usbh_cfg, &usbh_usr_cb);
if (ret != HAL_OK) {
    return;
}

/*
 * 4. Initialize UAC class driver.
 */
ret = usbh_uac_init(&usbh_uac_cfg, USBH_UAC_FRAME_CNT);
if (ret != HAL_OK) {
    /* If class driver init fails, clean up the core driver */
    usbh_deinit();

    return;
}

热插拔事件处理

系统必须具备健壮的热插拔处理机制,以应对 UAC 设备的动态移除与重新插入。SDK 提供了标准的状态机与回调机制来通知上层应用。

处理逻辑:

  • 设备拔出 (Detach):触发回调释放信号量,应用线程捕获后执行反初始化,并释放堆内存。

  • 设备插入 (Attach):USB 核心检测到设备,重新执行枚举和驱动加载流程。

/* USB detach callback */
static usbh_uac_cb_t usbh_uac_cfg = {
    .detach = usbh_uac_cb_detach,
};

/* Callback executed in main task */
static int usbh_uac_cb_detach(void)
{
    RTK_LOGS(TAG, RTK_LOG_INFO, "DETACH\n");
    rtos_sema_give(usbh_uac_detach_sema);
    usbh_uac_is_ready = 0;
    return HAL_OK;
}

/* Thread Context: Handle the state machine */
static void usbh_uac_hotplug_thread(void *param)
{
    int ret = 0;

    UNUSED(param);

    for (;;) {
        usb_os_sema_take(usbh_uac_detach_sema, USB_OS_SEMA_TIMEOUT);
        RTK_LOGS(TAG, RTK_LOG_INFO, "Hot plug\n");

        /* Stop transfer, release resource */
        usbh_uac_deinit();
        usbh_deinit();

        rtos_time_delay_ms(10);
        RTK_LOGS(TAG, RTK_LOG_INFO, "Free heap size: 0x%08x\n", usb_os_get_free_heap_size());

        /* Re-init */
        ret = usbh_init(&usbh_cfg, &usbh_usr_cb);
        if (ret != HAL_OK) {
            break;
        }

        ret = usbh_uac_init(&usbh_uac_cfg, USBH_UAC_FRAME_CNT);
        if (ret < 0) {
            usbh_deinit();
            break;
        }
    }
}

音频数据流机制

UAC 类驱动采用环形缓冲区 (Ring Buffer) 机制来缓冲上层应用产生的音频数据,并通过 SOF (Start of Frame) 和传输完成中断来驱动数据的持续发送。

  • 数据写入 (Audio Write)

上层应用调用 usbh_uac_write() 接口将 PCM 数据填入环形缓冲区。如果缓冲区已满,该函数将根据设定的 timeout_ms 进行阻塞等待。

static int usbh_uac_write_ring_buf(usbh_uac_buf_ctrl_t *pdata_ctrl, u8 *buffer, u32 size, u32 *written_len)
{
    u32 written_size = handle->written;

    /* Fill it into the end of the data that was not completed last time to form a whole package */
    if (written_size) {
        xfer_len = usbh_uac_next_packet_size(pdata_ctrl);
        can_copy_len = xfer_len - written_size;
        copy_len = size < can_copy_len ? size : can_copy_len;

        usb_ringbuf_write_partial(handle, buffer, copy_len);
        offset += copy_len;
        *written_len += copy_len;

        if (size >= can_copy_len) {
            size -= copy_len;
            usb_ringbuf_finish_write(handle);
            pdata_ctrl->sample_accum = pdata_ctrl->last_sample_accum;
        } else {
            return 0;
        }
    }

    /* Fill the entire package in a cycle */
    do {
        if (usb_ringbuf_is_full(handle)) {
            return 1;
        }

        xfer_len = usbh_uac_next_packet_size(pdata_ctrl);

        if (size >= xfer_len) {
            usb_ringbuf_add_tail(handle, buffer + offset, xfer_len);

            *written_len += xfer_len;
            size -= xfer_len;
            offset += xfer_len;

            pdata_ctrl->sample_accum = pdata_ctrl->last_sample_accum;
        } else {
            break;
        }
    } while (1);

    /* Write the remaining data at the end */
    if (size > 0) {
        if (usb_ringbuf_is_full(handle)) {
            return 1;
        }

        usb_ringbuf_write_partial(handle, buffer + offset, size);

        *written_len += size;
    }
    return 0;
}

/* Tansfer APi, used for Audio */
u32 usbh_uac_write(u8 *buffer, u32 size, u32 timeout_ms)
{
    /* check usb status */
    if (usbh_uac_usb_status_check() != HAL_OK) {
        return 0;
    }

    /* loop to write data to the ringbuffer */
    while (written_len < size && pdata_ctrl->next_xfer) {

        if (usb_ringbuf_is_full()) {
            if (usbh_uac_wait_isoc_with_status_check(pdata_ctrl, timeout_ms) != HAL_OK) {
                break;
            }
        }

        try_len = size - written_len;
        just_written = 0;

        usbh_uac_write_ring_buf(pdata_ctrl, buffer + written_len, try_len, &just_written);

        if (just_written > 0) {
            written_len += just_written;
            last_zero = 0;
        } else {
            //wait sema and retry
            last_zero = 1;
        }
    }

    return written_len;
}
  • 数据发送 (Audio Output)

底层驱动通过中断回调自动从环形缓冲区取出数据并发送至 USB 总线。

  • SOF 中断:周期性触发。检查当前帧号是否达到发送间隔(Interval),若是,则从 RingBuffer 取数据并提交传输。

  • Commplete 中断:上一次传输完成后触发。检查 RingBuffer 是否有剩余数据,如有则安排下一帧的发送任务。

static usbh_class_driver_t usbh_uac_driver = {
    .sof = usbh_uac_cb_sof,
    .completed = usbh_uac_cb_completed,
};

static void usbh_uac_isoc_out_process_xfer(usb_host_t *host, u32 cur_frame)
{
    if (!usb_ringbuf_is_empty(&(pdata_ctrl->buf_list))) {
        /* check valid data */
        pbuf = usb_ringbuf_get_head(&(pdata_ctrl->buf_list));
        if (pbuf && pbuf->buf_len > 0) {
            pipe->frame_num = usbh_uac_frame_num_inc(cur_frame, 1);
            pipe->xfer_buf = pbuf->buf;
            pipe->xfer_len = pbuf->buf_len;
            usbh_transfer_data(host, pipe);
            pipe->xfer_state = USBH_EP_XFER_BUSY;
        }
}

static int usbh_uac_cb_sof(usb_host_t *host)
{
    /* this class right not just support isoc out */
    if (pdata_ctrl->next_xfer == 1) {
        /* check the condition for transmission */
        if ((usbh_get_elapsed_frame_cnt(host, pipe->frame_num) >= pipe->ep_interval) ||
            ((pipe->xfer_state == USBH_EP_XFER_WAIT_SOF) &&
             (usbh_uac_frame_num_dec(usbh_uac_frame_num_inc(cur_frame, 1), pipe->frame_num) >= pipe->ep_interval))) {
            usbh_uac_isoc_out_process_xfer(host, cur_frame);
        }
    }
    return HAL_OK;
}

static int usbh_uac_cb_completed(usb_host_t *host, u8 pipe_num)
{
    if (pdata_ctrl->next_xfer == 1) {
        if ((uac->as_isoc_out) && (pipe_num == uac->as_isoc_out->pipe.pipe_num)) {
            usbh_uac_isoc_out_process_complete(host);
            if (!usb_ringbuf_is_empty(&(pdata_ctrl->buf_list))) {
                /* trigger next xfer after binterval */
                if (usbh_uac_frame_num_dec(usbh_uac_frame_num_inc(cur_frame, 1), pipe->frame_num) >= pipe->ep_interval) {
                    usbh_uac_isoc_out_process_xfer(host, cur_frame);
                } else {
                    pipe->xfer_state = USBH_EP_XFER_WAIT_SOF;
                }
            } else {
                /* TX ISOC OUT token only when play*/
                pipe->xfer_state = USBH_EP_XFER_IDLE;
            }
        }
    }

    return HAL_OK;
}

驱动卸载

在系统关闭或需要完全重置 USB 栈时,必须严格按照 先类驱动,后核心驱动 的反向顺序释放资源,以避免内存泄漏或指针错误。

/* 1. Deinitialize UAC class driver first */
usbh_uac_deinit();

/* 2. Deinitialize USB host core driver */
usbh_deinit();

运行方式

本节以 Ameba 连接 USB 耳机播放音频 为例,演示如何将 Ameba 开发板配置为 USB UAC 1.0 主机,并通过外接标准 USB 耳机输出音频。

该示例默认行为:识别 UAC 设备 -> 配置为 48 kHz / 16-bit / 2-channels -> 循环播放一段预置的 PCM 音频。

该示例路径: {SDK}/example/usb/usbh_uac,可为开发者设计语音播报、音频网关等产品提供完整的设计参考。

备注

本示例需要 XDK (Extended Development Kit) 支持。XDK 下载请参考 SDK Download.

配置与编译

  • 编译与烧录

    在 SDK 根目录下执行以下命令以配置环境,选择目标 SoC,编译工程,然后将生成的 Image 文件烧录至开发板。

    # Initialize environment (required for every new terminal)
    source env.sh or env.bat(Windows system)
    
    # Select Target SoC (replace xxx with your specific SoCs)
    ameba.py soc xxx
    
    ameba.py build -a usbh_uac -p
    
  • Menuconfig 配置确认

    若编译失败,请执行 ameba.py menuconfig,确认已选择 USBH UAC

    - Choose `CONFIG USB --->`:
    
        [*] Enable USB
            USB Mode (Host)  --->
        [*] UAC
    

结果验证

  • 启动设备

    复位开发板,观察串口日志(Log UART)。当出现以下 Log 时,表示 USB Host 初始化成功:

[UAC-I] USBH UAC demo start
  • 连接设备

    将兼容 UAC 1.0 的 USB 耳机插入开发板。

  • 功能测试

    • 自动播放测试

      连接成功后,系统会自动开始音频流传输。

      预期结果:耳机中听到预置的音频片段(默认循环播放 60 次,每次 1 秒)。

      备注

      可在 example_usbh_uac.c 中修改循环次数配置。

    • 静音控制测试 (Mute)

      在串口控制台输入命令:

      • uach_mute 1:耳机静音。

      • uach_mute 0:取消静音,恢复声音。

    • 音量控制测试 (Volume)

      在串口控制台输入命令:

      • uach_vol 10:设置音量为 10%(声音变小)。

      • uach_vol 90:设置音量为 90%(声音变大)。