Audio Host Solution

Overview

The USB Audio Class (UAC) specification defines the industry standard for transmitting audio data over USB interfaces. In Host mode, the Ameba platform utilizes this protocol to identify, configure, and drive external USB audio devices.

The current UAC Host stack on the Ameba platform is optimized for Audio Playback scenarios. It integrates a UAC 1.0 compliant protocol stack (verified against USB-IF standards), abstracting external USB UAC devices into local system audio output interfaces. This solution supports plug-and-play functionality and seamlessly integrates with the system’s built-in audio processing framework, providing convenient and high-quality audio output expansion capabilities for embedded devices.

Ameba USB UAC Host

Features

The Ameba UAC Host driver is designed to deliver stable and compatible audio output. Key features include:

  • Broad Device Compatibility: Fully supports USB devices compliant with the UAC 1.0 standard, such as USB speakers, USB headsets, and USB-to-3.5mm audio adapters.

  • Automatic Enumeration & Configuration: Automatically parses device descriptors, identifies audio streaming endpoints, and establishes Isochronous transfer channels without manual intervention.

  • Mainstream Audio Format Support: Please refer to the Supported Audio Formats section for detailed specifications.

  • Deep System Integration: Exposes a unified API to upper-layer applications, effectively abstracting and masking underlying USB protocol details.

  • Hot-Plug Support: Supports dynamic plug-and-play and removal of USB peripherals during runtime without requiring a system reboot.

Application Scenarios

As a USB Host, the Ameba platform handles the complexity of enumerating UAC devices, parsing audio descriptors, and maintaining stable data transmission channels. This solution is ideal for embedded applications that demand high-quality audio playback with minimal development overhead, including:

  • Smart Audio Terminals: Devices that require voice prompts, advertisement broadcasting, or public address functionality via external UAC devices (e.g., active speakers or headphones).

  • Digital Signage & Kiosks: Systems that combine local storage or network streaming to play background music or multimedia commentary through high-fidelity USB audio peripherals to enhance user experience.

  • IoT Audio Gateways: Lightweight audio response nodes that receive audio commands or content from Wi-Fi/Cloud sources and output them through generic USB audio devices.

Protocol Introduction

Note

The current host solution only supports UAC 1.0

UAC (USB Audio Class) is a universal audio device class standard defined by USB-IF, aimed at standardizing the encapsulation and transmission of digital audio data streams over USB interfaces. USB audio devices (such as USB speakers, headphones, and microphones) can be automatically recognized as standard audio input/output terminals in the host system through standard interfaces for data transmission and function control, without the need to install proprietary drivers.

Version Comparison

USB-IF has officially released multiple versions of the UAC protocol. The download links for the specification documents of the mainstream versions are shown in the table below:

Version

Specification Document

1.0

Audio Device Document 1.0

2.0

Audio Device Rev. 2.0

Terminology Definitions

The following table defines common technical terms related to UAC used in this document:

Term

Description

AC Interface (Audio Control Interface)

Responsible for managing the topology of the audio device (e.g., Input/Output Terminals, Feature Units) and issuing control commands (such as volume adjustment and mute) via the Control Endpoint.

AS Interface (Audio Streaming Interface)

Responsible for the actual transmission of audio payload data, typically using Isochronous endpoints. An AS Interface can contain multiple Alternate Settings, each corresponding to different sample rates, bit depths, or channel configurations.

Terminal Type

Identifies the physical or logical attributes of an audio signal source or sink (e.g., USB Streaming Terminal represents a USB data stream, Speaker Terminal represents a physical speaker).

Feature Unit

A processing node within the audio topology that provides specific audio control capabilities (e.g., master volume adjustment, channel gain, mute control).

Sample Rate / Bit Resolution

Core parameters of the audio format. Common combinations in UAC 1.0 include 48 kHz / 16-bit, 44.1 kHz / 16-bit, etc.

Definitions of common audio technology terms used in this document are as follows:

Terms

Introduction

PCM

Pulse Code Modulation, audio data is a raw stream.

Channel

A channel sound is an independent audio signal captured or played in different positions, so the number of channels is the number of sound sources.

Bit Depth

Bit depth represents the bits effectively used in the process of audio signals.

Sampling depth shows the resolution of the sound.

The larger the value, the higher the resolution.

Sample rate

The audio sampling rate refers to the number of frames that the signal is sampled per second.

The higher the sampling frequency, the higher quality the sound will be.

Protocol Framework

The UAC system architecture supports audio data transmission and control by defining different interface sets. Logically, UAC device interfaces are primarily divided into two major categories: Audio Control Interface and Audio Streaming Interface.

UAC 2.0:
../../../_images/usb_uac20_audio_control_topology.png
  • Audio Control (AC) Interface:

    • Responsible for managing the overall functional behavior of the audio device, such as volume adjustment, mute control, input source selection, etc.

    • An AC interface contains a defined internal topology that describes the flow and processing of audio signals from the Input Terminal to the Output Terminal.

  • Audio Streaming (AS) Interface:

    • Responsible for transmitting the actual audio payload data.

    • A UAC device can contain multiple AS interfaces, each configurable to transmit audio data with different formats, sampling rates, or bit depths.

Protocol Interaction Example:

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

Descriptor Structure

In addition to adhering to standard USB descriptors (such as Device Descriptor, Configuration Descriptor, Endpoint Descriptor), UAC devices also define Class-Specific Descriptors. These descriptors are categorized based on their associated interface into Class-Specific Control Interface Descriptors and Class-Specific Audio Streaming Interface Descriptors.

There are differences in descriptor definitions between different protocol versions:

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

Note

For detailed field definitions, please refer to the official USB-IF UAC protocol documentation.

Class-Specific Requests

The control requests issued by the UAC host to the device are categorized into Standard Requests and Class-Specific Requests.

This section primarily introduces the Class-Specific Requests unique to UAC. These requests are used to implement the distinctive features of audio devices and mainly include Audio Control Requests (targeting AC interfaces) and Audio Streaming Requests (targeting AS interfaces).

UAC 2.0:
  • Audio Control Requests (AC Requests)

    AC Requests are class-specific control transfers sent by the host over Endpoint 0 to dynamically configure and manage audio functionality within a USB audio device.

AudioControl Type

Required

Description

Mute Control Request

Optional

Manipulate the mute control in the Feature Unit of the audio function

Volume Control Request

Optional

Manipulate the volume control in the Feature Unit of the audio function

Sampling Frequency Control

Optional

Manipulate the actual sampling frequency of the clock signal.

Mixer Unit Control Request

Optional

Manipulate the control inside a Mixer Unit of the audio function

Terminal Control Request

Optional

Manipulate the control inside a Mixer Unit of the audio function

Selector Unit Control Request

Optional

Manipulate the control inside a Selector Unit of the audio function

Effect Unit Control Request

Optional

Manipulate the Controls inside an Effect Unit of the audio function

Processing Unit Control Request

Optional

Manipulate the Controls inside a Processing Unit of the audio function

Extension Unit Control Requests

Optional

Manipulate the Controls inside an Extension Unit of the audio function

  • Audio Streaming Requests (AS Requests)

    AS Requests are class-specific control transfers issued by the host over Endpoint 0 to configure and manage parameters related to audio data streams, pecifically those associated with an Audio Streaming Interface.

AudioStreaming Type

Required

Description

Interface Control Request

Optional

Manipulate the Controls inside an AudioStreaming interface of the audio function

Encoder Control Request

Optional

Manipulate the Encoder Controls inside an AudioStreaming interface of the audio function

Decoder Control Request

Optional

Manipulate the Decoder Controls inside an AudioStreaming interface of the audio function

Endpoint Control Request

Optional

Manipulate the Controls inside an AudioStreaming endpoint of the audio function

Data Transmission Format

UAC audio data streams typically use Linear PCM encoding and are encapsulated in a Multi-Channel Interleaved format. The specific data arrangement depends on the number of channels and bit depth.

For more details on supported formats, please refer to Supported Audio Formats .

An example of 2-Channel interleaved data is shown below:

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

An example of 4-Channel interleaved data is shown below:

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

An example of N-Channel interleaved data is shown below:

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

Note

Channel Alignment Rule:

The UAC protocol typically requires the transmitted number of channels to be a power of two (e.g., 2, 4, 8, 16, etc.). If the actual physical channel count (e.g., 10 channels) does not comply with this rule, it must be rounded up to the nearest power of two (configured for transmission as 16 channels). The extra channel positions are filled with invalid data.

Class Driver

This section details the internal architecture of the USB Host UAC 1.0 driver stack, the responsibilities of key modules, support for class-specific requests, and the allocation strategy for underlying channel resources.

Driver Framework

The USB Host UAC 1.0 driver stack adopts a layered modular design, facilitating efficient interaction between the audio subsystem and the USB hardware controller through clearly defined interfaces. The architecture is specifically optimized for real-time Isochronous Transfer (ISOC) processing to ensure the stable output of high-fidelity audio streams.

The system is organized from top to bottom into the following core functional modules:

Audio Adapter Layer

Acting as middleware between the USB driver and the upper-layer audio framework, this layer is primarily responsible for:

  • Write Operations: Functioning as a data producer, it reads PCM data from the upper-layer audio buffer and writes it into the UAC Class Driver’s ring buffer.

  • Control Interaction: Maps upper-layer application operations, such as volume adjustment and mute toggling, to the corresponding UAC Class Driver control APIs.

UAC Class Driver Architecture

This is the core component of the UAC implementation, composed of protocol handling logic and Ringbuffer Management.

  • Enumeration & Configuration Parsing: Automatically identifies Audio Control (AC) and Audio Streaming (AS) interfaces, parsing parameters such as Terminal Types, Feature Units, sample rates, and bit depths.

  • Stream Management: Dynamically selects the appropriate Alternate Setting for the AS interface based on target audio parameters and activates the corresponding Isochronous OUT endpoint.

  • Buffer Scheduling: Maintains a ring buffer to handle data packetization and scheduling, ensuring a continuous, jitter-free data supply.

  • Protocol Encapsulation: Encapsulates standard USB requests and UAC class-specific requests (e.g., SET_CUR) to control the device.

USB Core Driver

Handles real-time hardware interrupts, standard USB enumeration, transfer management, and low-level physical data transmission scheduling.

Core Interaction Interfaces

The UAC Host Class Driver serves as a bridge within the system architecture. Its implementation logic revolves around three core interaction interfaces:

  • Host Class Driver Callback API: The class driver interacts with the underlying USB Core by defining and registering a standard usbh_class_driver_t structure.

  • Application-Facing Callback API: The class driver provides an asynchronous event notification mechanism to upper-layer applications via the usbh_uac_cb_t callback structure.

  • Application-Facing API: When the application layer calls these APIs, the driver switches its internal state machine to initiate data transmission scheduling.

Driver Callback Mechanism:

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

Init and Deinit the Class Driver

These functions manage memory resource allocation and deallocation, as well as the registration and deregistration of the class driver with the USB Core.

Init:

usbh_uac_init() is the top-level function for loading the UAC Host Class Driver. It performs the following tasks:

  • Save Callbacks: Stores the user-provided callback functions and invokes the user’s init callback.

  • Save Parameters: Saves the user-configured frame count frame_cnt for the ring buffer.

  • Allocate Memory: Allocates memory buffers for control transfers and TX audio transmission.

  • Register Driver: Calls usbh_register_class() to register the UAC class driver with the USB Host Core.

Example:

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;
}

Attach & Detach Handling

Callbacks are triggered when the USB Core detects the insertion or removal of a device matching the UAC class.

Attach:

usbh_uac_cb_attach is a critical step in device enumeration, responsible for parsing interface descriptors and allocating pipe resources:

  • Find AC Interface: Parses and retrieves audio control information, including volume ranges and mute capabilities.

  • Find AS Interface: Parses and retrieves audio format information and endpoint descriptors.

  • Open Pipe: Allocates and opens the ISOC OUT pipe based on the retrieved descriptor information.

  • Initialize State Machine: Sets the initial state to UAC_STATE_GET_MUTE to prepare for fetching audio information.

  • Notify Application: Calls the user attach callback to inform the application layer of the connection status.

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;
}

Class Driver State Machine

The usbh_uac_cb_process callback is the core state machine handler for the UAC class on the Host side.

Unlike the Device side, which passively responds to requests, the Host driver must actively maintain the device state. Its primary responsibilities are managing the lifecycle state of the class driver and dispatching transfer tasks.

State Management and Scheduling

usbh_uac_cb_process manages the scheduling of control transfers (e.g., sample rate configuration) and data transfers based on the current class driver state.

State Enum

Description

Key Action

IDLE

Idle State

Waits for user commands or data transfer requests.

TRANSFER

Data Transferring

Dispatches tasks to specific TX/RX handlers based on pipe numbers.

ERROR

Error State

Attempts to Clear Feature on endpoints to restore communication.

Transfer Dispatch Example

When in the TRANSFER state, processing is dispatched to specific transfer handlers based on the pipe ID triggering the event.

Example:

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;

Error Recovery

If an error occurs during state processing, the driver attempts to send a Clear Feature request and reset to the IDLE state.

Example:

/* ... 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;

Data Transfer Processing

UAC data transfer is categorized into two types: Audio Control Transfer and Audio Streaming Transfer .

Audio Control Transfer

Responsible for managing the functional behavior of the audio device. The driver implements a state machine flow to handle configuration requests in the following sequence:

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;

Audio Streaming Transfer (ISOC):

To ensure real-time performance and low latency for audio streams, the UAC Class Driver utilizes USB hardware interrupt mechanisms (Start of Frame SOF or Transfer Completed) to precisely trigger the scheduling of the next frame of data.

  • Completed Interrupt: Triggered when the current data has been successfully sent to the bus.

Compact Scheduling: When the endpoint transfer interval (bInterval) is 1 (transmission required every frame), the driver maximizes bandwidth utilization and minimizes latency by immediately filling and submitting the request for the next frame within the Completed callback of the previous frame.

  • SOF (Start Of Frame) Interrupt: Triggered at the start of each USB frame.

Interval Scheduling: When the endpoint transfer interval (bInterval) is greater than 1 (transmission every N frames), the driver uses the SOF interrupt counter to track frame numbers. Once the scheduled interval is reached, it immediately prepares and submits the next data transfer request.

Compact Scheduling:

Code Example (Completed Callback Logic):

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;
}

Application-Facing APIs

These interfaces are provided to the upper-layer application for information retrieval, device configuration, and data transmission.

Configuration APIs

Data Streaming APIs

  • usbh_uac_write():Writes PCM data into the driver’s internal ring buffer. The class driver automatically fragments large data packets into smaller packets that fit the USB frame size based on the audio format.

  • usbh_uac_start_play():Initiates the playback process; the driver begins extracting data from the ring buffer and sending it.

  • usbh_uac_stop_play():Stops playback and terminates the ISOC OUT transmission.

Class-Specific Request Implementation

The driver stack includes built-in encapsulation for core requests defined by the USB Audio Class 1.0 specification. The current implementation focuses on Audio Playback scenarios and enables control requests targeting Feature Units by default.

Developers can view the source code at {SDK}/component/usb/host/uac and extend it to support additional request types as needed.

Request Type

Note

Mute Control

Sends a SET_CUR request to the Mute Control Selector of a Feature Unit to mute/unmute audio.

Volume Control

Sends a SET_CUR request to the Volume Control Selector of a Feature Unit to set gain values.

Pipe Configuration

During the device enumeration phase, the driver parses the configuration descriptor. When an Audio Streaming (AS) Interface is detected as active, the driver automatically requests the corresponding USB pipe resources.

Pipe Type

Usage Description

Control IN/OUT Pipe

Used for standard device requests (enumeration, configuration) and UAC class-specific requests (volume, sample rate settings).

Isochronous OUT Pipe

Belongs to the Active Alternate Setting (typically Alt 1) of the Audio Streaming (AS) Interface. Used by the Host to send PCM audio data streams (TX Data) to the UAC device.

API Reference

Driver API

Application Example

Application Design

This section details the process required to develop a complete USB UAC 1.0 Host application, covering driver initialization, hot-plug event handling, audio data writing mechanisms, and resource release strategies.

Driver Initialization

Before using the UAC 1.0 Host driver, hardware parameter configuration, callback registration, and core protocol stack initialization must be completed in the specified sequence.

Step-by-Step Description:

  • Hardware Configuration: Configure the USB speed mode (Full Speed) and interrupt/task priorities.

  • Callback Registration: Define user callback structures and mount handlers for various stages (connection, disconnection, data transmission).

  • Core Startup:Call usbh_init() and usbh_uac_init() sequentially to start the protocol stack.

/*
 * 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;
}

Hot-Plug Event Handling

The system must possess a robust hot-plug handling mechanism to cope with the dynamic removal and re-insertion of UAC devices. The SDK provides standard state machine and callback mechanisms to notify the upper-layer application.

Handling Logic:

  • Detach: Triggers a callback to release a semaphore. The application thread captures this, executes de-initialization, and frees heap memory.

  • Attach: The USB Core detects the device and re-executes the enumeration and driver loading process.

/* 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;
        }
    }
}

Audio Data Stream Mechanism

The UAC Class Driver utilizes a Ring Buffer mechanism to buffer audio data generated by the upper-layer application and drives continuous data transmission via SOF (Start of Frame) or Transfer Complete interrupts.

  • Audio Write

The upper-layer application calls the usbh_uac_write() interface to fill PCM data into the ring buffer. If the buffer is full, this function will block and wait based on the configured 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

The underlying driver automatically retrieves data from the ring buffer via interrupt callbacks and sends it to the USB bus.

  • SOF Interrupt: Triggered periodically (every 1ms). It checks if the current frame number reaches the transmission Interval. If so, it retrieves data from the RingBuffer and submits the transfer.

  • Commplete Interrupt: Triggered after the previous transfer completes. It checks if the RingBuffer has remaining data; if so, it schedules the transmission task for the next frame.

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;
}

Driver Deinit

When the system shuts down or a full reset of the USB stack is required, resources must be released strictly in the reverse order: Class Driver first, then Core Driver, to avoid memory leaks or pointer errors.

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

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

Operation method

This section uses the scenario of Ameba connecting to a USB Headset for audio playback to demonstrate how to configure the Ameba development board as a USB UAC 1.0 Host and output audio through an external standard USB headset.

  • Default Behavior: Identify UAC Device -> Configure as 48 kHz / 16-bit / 2-channels -> Loop playback of a preset PCM audio clip.

  • Path: {SDK}/example/usb/usbh_uac, This provides a complete design reference for developers creating products such as voice announcement systems or audio gateways.

Note

This example requires XDK (Extended Development Kit) support. For XDK download, please refer to SDK Download.

Configuration and Compilation

  • Compilation and Flashing

    Execute the following commands in the SDK root directory to configure the environment, select the target SoC, compile the project, and flash the generated Image file to the development board.

    # 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
    
  • Confirmation of Menuconfig configuration

    If compilation fails, please execute ameba.py menuconfig and confirm that USBH UAC has been selected.

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

Result Verification

  • Start Device

    Reset the development board and observe the serial log (Log UART). When the following log appears, the USB Host initialization is successful:

[UAC-I] USBH UAC demo start
  • Connect Device

    Insert a UAC 1.0 compatible USB headset into the development board.

  • Functional Tests

    • Auto Play Test

      Upon successful connection, the system will automatically begin audio stream transmission.

      Expected Result: The preset audio clip is heard through the headphones (defaults to looping 60 times, 1 second per loop).

      Note

      The loop count configuration can be modified in example_usbh_uac.c.

    • Mute Control Test

      Enter the following commands in the serial console:

      • uach_mute 1:Mute the headphones.

      • uach_mute 0:Unmute and restore sound.

    • Volume Control Test

      Enter the following commands in the serial console:

      • uach_vol 10:Set volume to 10% (lower volume).

      • uach_vol 90:Set volume to 90% (higher volume).