Skip to main content

Overview

This guide covers USB Audio Class (UAC) configuration for microphone input and speaker output. The library supports simultaneous operation of microphone and speaker streams with independent control of volume, mute, and audio parameters.

Audio Configuration Parameters

UAC streams are configured with three primary parameters:
  • Channel Number: Mono (1) or stereo (2)
  • Bit Resolution: 8, 16, 24, or 32 bits per sample
  • Sample Frequency: Common rates include 8000, 16000, 44100, 48000 Hz

Using Auto-Detection

Use wildcard values to auto-detect device capabilities:
UAC_CH_ANY          // Accept any channel number (0)
UAC_BITS_ANY        // Accept any bit resolution (__UINT16_MAX__)
UAC_FREQUENCY_ANY   // Accept any sample frequency (__UINT32_MAX__)

Microphone Configuration

1

Configure Microphone Parameters

USB_STREAM *usb = new USB_STREAM();

usb->uacConfiguration(
    1,                    // mic_ch_num - mono
    16,                   // mic_bit_resolution - 16 bits
    16000,                // mic_samples_frequency - 16 kHz
    6400,                 // mic_buf_size - buffer size in bytes
    0,                    // spk_ch_num - 0 to disable speaker
    0,                    // spk_bit_resolution
    0,                    // spk_samples_frequency
    0                     // spk_buf_size
);
Mic buffer size should accommodate at least 100-200ms of audio data. Calculate as: buffer_size = (sample_rate / 1000) * (bit_resolution / 8) * channels * duration_ms
2

Register Microphone Callback (Optional)

For real-time processing, register a callback:
void mic_callback(mic_frame_t *frame, void *user_ptr) {
    ESP_LOGV(TAG, "Mic data: %u bytes, %u Hz, %u bits",
             frame->data_bytes,
             frame->samples_frequence,
             frame->bit_resolution);
    
    // Process audio in frame->data
    // WARNING: Do NOT block in this callback!
}

usb->uacMicRegisterCb(&mic_callback, NULL);
The callback runs in the USB task context. Never block! For heavy processing, queue data to another task.
3

Start and Resume Microphone

usb->start();
usb->connectWait(10000);

// If using FLAG_UAC_MIC_SUSPEND_AFTER_START, resume manually:
usb->uacMicResume(NULL);

Reading Microphone Data

If not using a callback, read from the internal buffer:
#define BUFFER_SIZE (96 * 20)  // Adjust based on sample rate
uint8_t *audio_buffer = (uint8_t *)malloc(BUFFER_SIZE);
size_t bytes_read = 0;

// Read with timeout
usb->uacReadMic(audio_buffer, BUFFER_SIZE, &bytes_read, 1000);

if (bytes_read > 0) {
    ESP_LOGI(TAG, "Read %u bytes from microphone", bytes_read);
    // Process audio_buffer
}

free(audio_buffer);
The read operation blocks until data is available or timeout occurs. Use portMAX_DELAY to wait indefinitely.

Speaker Configuration

1

Configure Speaker Parameters

USB_STREAM *usb = new USB_STREAM();

usb->uacConfiguration(
    0,                    // mic_ch_num - 0 to disable mic
    0,                    // mic_bit_resolution
    0,                    // mic_samples_frequency
    0,                    // mic_buf_size
    1,                    // spk_ch_num - mono
    16,                   // spk_bit_resolution - 16 bits
    16000,                // spk_samples_frequency - 16 kHz
    6400                  // spk_buf_size - buffer size in bytes
);
Speaker buffer size should be a multiple of the endpoint max packet size (MPS). Larger buffers reduce underruns.
2

Start and Resume Speaker

usb->start();
usb->connectWait(10000);

// Resume if suspended
usb->uacSpkResume(NULL);
3

Write Audio Data

// Audio data buffer (16-bit samples)
uint16_t *audio_samples = get_audio_data();
size_t data_bytes = 320;  // Number of bytes to write

usb->uacWriteSpk(audio_samples, data_bytes, portMAX_DELAY);
Ensure continuous data supply to avoid audio stuttering. Use a buffer or queue to manage playback.

Simultaneous Microphone and Speaker

1

Configure Both Streams

USB_STREAM *usb = new USB_STREAM();

usb->uacConfiguration(
    1,                    // mic_ch_num
    16,                   // mic_bit_resolution
    16000,                // mic_samples_frequency
    3200,                 // mic_buf_size
    1,                    // spk_ch_num
    16,                   // spk_bit_resolution
    16000,                // spk_samples_frequency
    6400                  // spk_buf_size
);
2

Register Mic Callback for Audio Loop

Create an audio loop by forwarding mic data to speaker:
void mic_to_speaker_callback(mic_frame_t *frame, void *user_ptr) {
    // Forward mic input directly to speaker
    uac_spk_streaming_write(frame->data, frame->data_bytes, 0);
}

usb->uacMicRegisterCb(&mic_to_speaker_callback, (void *)1);
3

Start Both Streams

usb->start();
usb->connectWait(10000);

// Both streams are now active

Volume and Mute Control

Volume Control

Set volume level (0-100):
// Microphone volume
uint32_t mic_volume = 50;  // 50%
usb->uacMicVolume(&mic_volume);

// Speaker volume
uint32_t spk_volume = 75;  // 75%
usb->uacSpkVolume(&spk_volume);
Volume control depends on device support. Some devices may not support this feature.

Mute Control

Mute or unmute audio streams:
// Mute microphone
uint32_t mute = 1;  // 1 = mute, 0 = unmute
usb->uacMicMute(&mute);

// Unmute speaker
uint32_t unmute = 0;
usb->uacSpkMute(&unmute);

Suspend and Resume

1

Suspend Individual Streams

// Suspend microphone
usb->uacMicSuspend(NULL);

// Suspend speaker
usb->uacSpkSuspend(NULL);
2

Change Configuration (Optional)

While suspended, modify audio parameters:
// Change microphone to 44.1 kHz, stereo, 24-bit
usb->uacMicFrameReset(2, 24, 44100);

// Change speaker to 48 kHz, mono, 16-bit
usb->uacSpkFrameReset(1, 16, 48000);
3

Resume Streams

// Resume microphone
usb->uacMicResume(NULL);

// Resume speaker
usb->uacSpkResume(NULL);
New configurations take effect after resume.

Querying Audio Formats

Get supported audio formats from connected device:
// Get microphone format list
size_t mic_frame_size = 0;
size_t mic_frame_index = 0;
usb->uacMicGetFrameListSize(&mic_frame_size, &mic_frame_index);

uac_frame_size_t *mic_formats = 
    (uac_frame_size_t *)malloc(mic_frame_size * sizeof(uac_frame_size_t));
usb->uacMicGetFrameSize(mic_formats);

for (size_t i = 0; i < mic_frame_size; i++) {
    ESP_LOGI(TAG, "Mic[%u]: %u ch, %u bits, %u Hz (range: %u-%u)",
             i,
             mic_formats[i].ch_num,
             mic_formats[i].bit_resolution,
             mic_formats[i].samples_frequence,
             mic_formats[i].samples_frequence_min,
             mic_formats[i].samples_frequence_max);
}

free(mic_formats);

// Similar for speaker
size_t spk_frame_size = 0;
size_t spk_frame_index = 0;
usb->uacSpkGetFrameListSize(&spk_frame_size, &spk_frame_index);

uac_frame_size_t *spk_formats = 
    (uac_frame_size_t *)malloc(spk_frame_size * sizeof(uac_frame_size_t));
usb->uacSpkGetFrameSize(spk_formats);

// Process speaker formats...
free(spk_formats);

Complete Example: Audio Echo

#include "USB_STREAM.h"

static const char *TAG = "UAC_Echo";
USB_STREAM *usb_stream = nullptr;

// Echo callback: forward mic to speaker
void echo_callback(mic_frame_t *frame, void *user_ptr) {
    // Write mic data to speaker
    uac_spk_streaming_write(frame->data, frame->data_bytes, 0);
}

void state_callback(usb_stream_state_t event, void *arg) {
    if (event == STREAM_CONNECTED) {
        ESP_LOGI(TAG, "Audio device connected");
    } else {
        ESP_LOGI(TAG, "Audio device disconnected");
    }
}

void setup() {
    Serial.begin(115200);
    
    usb_stream = new USB_STREAM();
    
    // Configure mic and speaker with auto-detection
    usb_stream->uacConfiguration(
        UAC_CH_ANY,           // mic channels
        UAC_BITS_ANY,         // mic bit resolution
        UAC_FREQUENCY_ANY,    // mic frequency
        6400,                 // mic buffer size
        UAC_CH_ANY,           // speaker channels
        UAC_BITS_ANY,         // speaker bit resolution
        UAC_FREQUENCY_ANY,    // speaker frequency
        16000                 // speaker buffer size
    );
    
    // Register callbacks
    usb_stream->uacMicRegisterCb(&echo_callback, (void *)1);
    usb_stream->registerState(&state_callback, NULL);
    
    // Start streaming
    usb_stream->start();
    usb_stream->connectWait(10000);
    
    // Set volume
    uint32_t volume = 60;
    usb_stream->uacMicVolume(&volume);
    usb_stream->uacSpkVolume(&volume);
    
    ESP_LOGI(TAG, "Audio echo started");
}

void loop() {
    delay(1000);
}

Audio Buffer Size Recommendations

Sample RateBit DepthChannelsBuffer Size (100ms)Buffer Size (200ms)
8000 Hz16-bit11,600 bytes3,200 bytes
16000 Hz16-bit13,200 bytes6,400 bytes
44100 Hz16-bit217,640 bytes35,280 bytes
48000 Hz16-bit219,200 bytes38,400 bytes
Formula: buffer_size = (sample_rate / 1000) * (bits / 8) * channels * duration_ms
  • Microphone: Smaller buffers (100-200ms) for lower latency
  • Speaker: Larger buffers (200-500ms) to prevent underruns
  • Speaker buffer should be a multiple of endpoint MPS

Suspend After Start

Automatically suspend streams on startup:
src/original/include/usb_stream.h
// Using C API
uac_config_t uac_config = {
    .mic_ch_num = UAC_CH_ANY,
    .mic_bit_resolution = UAC_BITS_ANY,
    .mic_samples_frequence = UAC_FREQUENCY_ANY,
    .mic_buf_size = 6400,
    // ... other config ...
    .flags = FLAG_UAC_MIC_SUSPEND_AFTER_START | FLAG_UAC_SPK_SUSPEND_AFTER_START
};

Next Steps

Build docs developers (and LLMs) love