summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--examples/host/audio_host/CMakeLists.txt30
-rw-r--r--examples/host/audio_host/Makefile14
-rw-r--r--examples/host/audio_host/README.md98
-rw-r--r--examples/host/audio_host/src/app.h26
-rw-r--r--examples/host/audio_host/src/audio_app.c226
-rw-r--r--examples/host/audio_host/src/main.c73
-rw-r--r--examples/host/audio_host/src/tusb_config.h103
-rw-r--r--src/CMakeLists.txt1
-rw-r--r--src/class/audio/audio_host.c809
-rw-r--r--src/class/audio/audio_host.h235
-rw-r--r--src/host/usbh.c12
-rw-r--r--src/tinyusb.mk1
-rw-r--r--src/tusb.h4
-rw-r--r--src/tusb_option.h4
14 files changed, 1636 insertions, 0 deletions
diff --git a/examples/host/audio_host/CMakeLists.txt b/examples/host/audio_host/CMakeLists.txt
new file mode 100644
index 000000000..0891f5829
--- /dev/null
+++ b/examples/host/audio_host/CMakeLists.txt
@@ -0,0 +1,30 @@
+cmake_minimum_required(VERSION 3.20)
+
+include(${CMAKE_CURRENT_SOURCE_DIR}/../../../hw/bsp/family_support.cmake)
+
+project(audio_host C CXX ASM)
+
+# Checks this example is valid for the family and initializes the project
+family_initialize_project(${PROJECT_NAME} ${CMAKE_CURRENT_LIST_DIR})
+
+# Espressif has its own cmake build system
+if(FAMILY STREQUAL "espressif")
+ return()
+endif()
+
+add_executable(${PROJECT_NAME})
+
+# Example source
+target_sources(${PROJECT_NAME} PUBLIC
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/audio_app.c
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/main.c
+ )
+
+# Example include
+target_include_directories(${PROJECT_NAME} PUBLIC
+ ${CMAKE_CURRENT_SOURCE_DIR}/src
+ )
+
+# Configure compilation flags and libraries for the example without RTOS.
+# See the corresponding function in hw/bsp/FAMILY/family.cmake for details.
+family_configure_host_example(${PROJECT_NAME} noos)
diff --git a/examples/host/audio_host/Makefile b/examples/host/audio_host/Makefile
new file mode 100644
index 000000000..5c2e23184
--- /dev/null
+++ b/examples/host/audio_host/Makefile
@@ -0,0 +1,14 @@
+include ../../../hw/bsp/family_support.mk
+
+INC += \
+ src \
+
+
+# Example source
+EXAMPLE_SOURCE += \
+ src/audio_app.c \
+ src/main.c
+
+SRC_C += $(addprefix $(EXAMPLE_PATH)/, $(EXAMPLE_SOURCE))
+
+include ../../../hw/bsp/family_rules.mk
diff --git a/examples/host/audio_host/README.md b/examples/host/audio_host/README.md
new file mode 100644
index 000000000..4d8f66b71
--- /dev/null
+++ b/examples/host/audio_host/README.md
@@ -0,0 +1,98 @@
+# USB Audio Host Example
+
+This example demonstrates how to use TinyUSB's USB Audio Host driver (TUH_AUDIO) to communicate with a UAC 1.0 compatible USB Audio Device.
+
+## Features
+
+- Enumerates and mounts USB Audio Class 1.0 devices
+- Receives audio data from IN endpoint (e.g., microphone)
+- Sends audio data to OUT endpoint (e.g., speaker)
+- Sets sampling frequency via control requests
+- Demonstrates isochronous transfer handling
+
+## Supported Devices
+
+This example supports any UAC 1.0 compliant USB Audio device, such as:
+- USB microphones
+- USB speakers/headphones
+- USB audio interfaces
+
+## Building
+
+### Using CMake (recommended)
+
+```bash
+cd examples/host/audio_host
+mkdir -p build && cd build
+cmake -DBOARD=<your_board> -G Ninja ..
+cmake --build .
+```
+
+Replace `<your_board>` with your target board name (e.g., `raspberry_pi_pico`, `stm32f407disco`, etc.)
+
+### Using Make
+
+```bash
+cd examples/host/audio_host
+make BOARD=<your_board> all
+```
+
+## Flashing
+
+```bash
+# Using CMake
+ninja flash
+
+# Using Make
+make BOARD=<your_board> flash
+```
+
+## Usage
+
+1. Build and flash the example to your board
+2. Connect a USB Audio device (UAC 1.0) to the USB host port
+3. Open a serial terminal to view output
+4. The example will:
+ - Print device information when mounted
+ - Set sampling frequency to 48kHz
+ - Receive audio samples from the device (IN endpoint)
+ - Send test sine wave audio to the device (OUT endpoint)
+
+## Serial Output Example
+
+```
+TinyUSB Host USB Audio Example
+Connect a USB Audio Device (UAC 1.0) to test
+Audio device mounted: idx=0, daddr=1
+ --- Microphone ---
+ IN EP: 0x81 (max size: 192)
+ Input Terminal: ID=1, Type=0x0201, Channels=1
+ Format Type: 1, Channels: 1, SubFrameSize: 2, BitResolution: 16
+ Sampling Freq: Discrete, count=4
+ Freq[0]: 44100 Hz
+ Freq[1]: 48000 Hz
+ Freq[2]: 96000 Hz
+ Freq[3]: 192000 Hz
+ --- Speaker ---
+ OUT EP: 0x02 (max size: 192)
+ Output Terminal: ID=2, Type=0x0301
+ Format Type: 1, Channels: 2, SubFrameSize: 2, BitResolution: 16
+ Sampling Freq: Continuous range 8000 Hz - 48000 Hz
+ Feature Unit: ID=3, SourceID=1
+ Setting IN sampling frequency to 48000 Hz
+ Setting OUT sampling frequency to 48000 Hz
+ Sampling frequency set OK, ready for isochronous transfer
+```
+
+## Configuration
+
+Edit `src/tusb_config.h` to modify:
+- `CFG_TUH_AUDIO_MAX`: Maximum number of audio devices supported
+- `CFG_TUH_AUDIO_EPIN_BUFSIZE`: IN endpoint buffer size
+- `CFG_TUH_AUDIO_EPOUT_BUFSIZE`: OUT endpoint buffer size
+
+## Notes
+
+- This example uses isochronous transfers which require precise timing
+- For production applications, synchronize audio transfers with the device's audio clock
+- The example sends a simple sine wave for testing; replace with actual audio data in real applications
diff --git a/examples/host/audio_host/src/app.h b/examples/host/audio_host/src/app.h
new file mode 100644
index 000000000..a807aeaa6
--- /dev/null
+++ b/examples/host/audio_host/src/app.h
@@ -0,0 +1,26 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2025 TinyUSB contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ */
+
+#ifndef TUSB_TINYUSB_EXAMPLES_APP_H
+#define TUSB_TINYUSB_EXAMPLES_APP_H
+
+#include <stdio.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+void audio_app_task(void);
+
+#endif
diff --git a/examples/host/audio_host/src/audio_app.c b/examples/host/audio_host/src/audio_app.c
new file mode 100644
index 000000000..ac7596d91
--- /dev/null
+++ b/examples/host/audio_host/src/audio_app.c
@@ -0,0 +1,226 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2025 TinyUSB contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ */
+
+#include <stdio.h>
+#include "bsp/board_api.h"
+#include "tusb.h"
+#include "app.h"
+
+//--------------------------------------------------------------------+
+// MACRO TYPEDEF CONSTANT ENUM DECLARATION
+//--------------------------------------------------------------------+
+
+static bool audio_mounted = false;
+static uint8_t audio_dev_addr = 0;
+static uint8_t audio_idx = 0;
+static uint8_t audio_ep_in = 0;
+static uint8_t audio_ep_out = 0;
+static uint32_t sampling_freq = 48000; // Default sampling frequency (Hz)
+static uint8_t audio_mic_channels = 1;
+static volatile bool audio_rx_busy = false; // Track IN endpoint transfer state
+static volatile bool audio_tx_busy = false; // Track OUT endpoint transfer state
+static volatile bool audio_ready = false; // Wait for sampling freq set before starting isochronous transfer
+static uint8_t audio_ac_itf = 0; // Audio Control interface number
+static uint8_t audio_feature_unit_id = 0; // Feature Unit ID
+
+static uint8_t audio_rx_buffer[CFG_TUH_AUDIO_EPIN_BUFSIZE] __attribute__((aligned(4)));
+static uint8_t audio_tx_buffer[CFG_TUH_AUDIO_EPOUT_BUFSIZE] __attribute__((aligned(4)));
+
+//--------------------------------------------------------------------+
+// Helper Functions
+//--------------------------------------------------------------------+
+
+// Mono (96 bytes, 48 samples) -> Stereo (192 bytes)
+static void mono_to_stereo(const uint8_t *mono, uint8_t *stereo, uint16_t mono_samples) {
+ for (uint16_t i = 0; i < mono_samples; i++) {
+ // Copy 2 bytes (one int16 sample) to left channel
+ stereo[i * 4] = mono[i * 2];
+ stereo[i * 4 + 1] = mono[i * 2 + 1];
+ // Copy same 2 bytes to right channel
+ stereo[i * 4 + 2] = mono[i * 2];
+ stereo[i * 4 + 3] = mono[i * 2 + 1];
+ }
+}
+
+// Print sampling frequency info for an AS interface
+static void print_sampling_freq(const tuh_audio_as_info_t *as) {
+ if (as->sam_freq_type == 0) {
+ printf(" Sampling Freq: Continuous range %lu Hz - %lu Hz\r\n", (unsigned long)as->sam_freq_lower,
+ (unsigned long)as->sam_freq_upper);
+ } else {
+ printf(" Sampling Freq: Discrete, count=%u\r\n", as->sam_freq_type);
+ for (uint8_t j = 0; j < as->sam_freq_type && j < CFG_TUH_AUDIO_MAX_SAM_FREQ; j++) {
+ printf(" Freq[%u]: %lu Hz\r\n", j, (unsigned long)as->sam_freq[j]);
+ }
+ }
+}
+
+// Print all AS interface info
+static void print_as_interfaces(const tuh_audio_mount_cb_t *mount_cb_data) {
+ for (uint8_t i = 0; i < mount_cb_data->as_count; i++) {
+ const tuh_audio_as_info_t *as = &mount_cb_data->as_info[i];
+ if (as->ep_dir == TUSB_DIR_IN) {
+ // Save microphone channel count for mono-to-stereo conversion
+ audio_mic_channels = as->num_channels;
+ printf(" --- Microphone (AS %u) ---\r\n", i);
+ printf(" IN EP: 0x%02x (max size: %u)\r\n", as->ep_addr, as->ep_size);
+ } else {
+ printf(" --- Speaker (AS %u) ---\r\n", i);
+ printf(" OUT EP: 0x%02x (max size: %u)\r\n", as->ep_addr, as->ep_size);
+ }
+ printf(" Interface: %u, Alt: %u\r\n", as->interface_num, as->alt_setting);
+ printf(" Format Type: %u, Channels: %u, SubFrameSize: %u, BitResolution: %u\r\n", as->format_type,
+ as->num_channels, as->sub_frame_size, as->bit_resolution);
+ print_sampling_freq(as);
+ }
+}
+
+// Find IN and OUT endpoints from AS interfaces, return IN sampling freq
+static uint32_t find_audio_endpoints(const tuh_audio_mount_cb_t *mount_cb_data) {
+ uint32_t in_sam_freq = 0;
+ audio_ep_in = 0;
+ audio_ep_out = 0;
+
+ for (uint8_t i = 0; i < mount_cb_data->as_count; i++) {
+ const tuh_audio_as_info_t *as = &mount_cb_data->as_info[i];
+ if (as->ep_dir == TUSB_DIR_IN) {
+ audio_ep_in = as->ep_addr;
+ if (as->sam_freq_type > 0) {
+ in_sam_freq = as->sam_freq[0];
+ }
+ } else {
+ audio_ep_out = as->ep_addr;
+ }
+ }
+ return in_sam_freq;
+}
+
+// Set Feature Unit volume to un-mute
+static void set_feature_unit_volume(void) {
+ if (audio_feature_unit_id == 0) {
+ return;
+ }
+ printf(" Setting Feature Unit %u volume to 0x0600\r\n", audio_feature_unit_id);
+ tuh_audio_feature_unit_set(audio_dev_addr, audio_ac_itf, audio_feature_unit_id, AUDIO10_FU_CTRL_VOLUME, 0, 0x0600,
+ NULL, 0);
+}
+
+//--------------------------------------------------------------------+
+// Application Task
+//--------------------------------------------------------------------+
+void audio_app_task(void) {
+ if (!audio_mounted || !audio_ready) {
+ return;
+ }
+
+ if (!audio_rx_busy) {
+ if (tuh_audio_receive(audio_dev_addr, audio_idx, audio_rx_buffer, CFG_TUH_AUDIO_EPIN_BUFSIZE)) {
+ audio_rx_busy = true;
+ }
+ }
+}
+
+//--------------------------------------------------------------------+
+// TinyUSB Callbacks
+//--------------------------------------------------------------------+
+
+
+// Callback after IN sampling frequency is set
+static void in_sampling_freq_set_cb(tuh_xfer_t *xfer) {
+ if (xfer->result != XFER_RESULT_SUCCESS) {
+ printf(" Sampling frequency set FAILED: result=%u\r\n", xfer->result);
+ return;
+ }
+ printf(" Sampling frequency set OK, ready for isochronous transfer\r\n");
+ // Set Feature Unit volume to un-mute
+ set_feature_unit_volume();
+ // Set OUT sampling frequency then send empty packet to kick-start device
+ tuh_audio_set_sampling_freq(audio_dev_addr, audio_ep_out, sampling_freq, NULL, 0);
+ audio_ready = true;
+}
+
+void tuh_audio_mount_cb(uint8_t idx, const tuh_audio_mount_cb_t *mount_cb_data) {
+ if (!mount_cb_data) {
+ return;
+ }
+
+ printf("Audio device mounted: idx=%u, daddr=%u, AS count=%u\r\n", idx, mount_cb_data->daddr, mount_cb_data->as_count);
+
+ print_as_interfaces(mount_cb_data);
+
+ // Feature Unit
+ if (mount_cb_data->feature_unit_id != 0) {
+ printf(" Feature Unit: ID=%u, SourceID=%u\r\n", mount_cb_data->feature_unit_id,
+ mount_cb_data->feature_unit_source_id);
+ }
+
+ // Save device info
+ audio_dev_addr = mount_cb_data->daddr;
+ audio_idx = idx;
+ audio_mounted = true;
+ audio_ac_itf = mount_cb_data->bInterfaceNumber;
+ audio_feature_unit_id = mount_cb_data->feature_unit_id;
+
+ // Find endpoints and IN sampling frequency
+ uint32_t in_sam_freq = find_audio_endpoints(mount_cb_data);
+
+ // Set IN sampling frequency before starting isochronous transfer
+ if (audio_ep_in != 0 && in_sam_freq != 0) {
+ sampling_freq = in_sam_freq;
+ printf(" Setting IN sampling frequency to %lu Hz\r\n", (unsigned long)sampling_freq);
+ tuh_audio_set_sampling_freq(mount_cb_data->daddr, audio_ep_in, sampling_freq, in_sampling_freq_set_cb, 0);
+ }
+}
+
+// Invoked when device with Audio interface is un-mounted
+void tuh_audio_umount_cb(uint8_t idx) {
+ printf("Audio device unmounted: idx=%u\r\n", idx);
+ if (audio_mounted && audio_idx == idx) {
+ audio_mounted = false;
+ audio_ready = false;
+ audio_rx_busy = false;
+ audio_tx_busy = false;
+ audio_dev_addr = 0;
+ audio_idx = 0;
+ }
+}
+
+// Invoked when an isochronous IN transfer is complete
+void tuh_audio_rx_cb(uint8_t idx, uint8_t ep_addr, uint16_t xferred_bytes) {
+ (void)idx;
+ (void)ep_addr;
+ audio_rx_busy = false;
+
+ if (xferred_bytes > 0) {
+ if (audio_mic_channels == 1) {
+ // Mono microphone, convert to stereo and send to OUT endpoint
+ uint16_t samples = xferred_bytes / 2;
+ mono_to_stereo(audio_rx_buffer, audio_tx_buffer, samples);
+ tuh_audio_send(audio_dev_addr, audio_idx, audio_tx_buffer, xferred_bytes * 2);
+ } else {
+ // Stereo microphone, send directly to OUT endpoint
+ tuh_audio_send(audio_dev_addr, audio_idx, audio_rx_buffer, xferred_bytes);
+ }
+ }
+}
+
+// Invoked when an isochronous OUT transfer is complete
+void tuh_audio_tx_cb(uint8_t idx, uint8_t ep_addr, uint16_t xferred_bytes) {
+ (void)idx;
+ (void)ep_addr;
+ (void)xferred_bytes;
+ audio_tx_busy = false;
+}
diff --git a/examples/host/audio_host/src/main.c b/examples/host/audio_host/src/main.c
new file mode 100644
index 000000000..b80cd2938
--- /dev/null
+++ b/examples/host/audio_host/src/main.c
@@ -0,0 +1,73 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2025 TinyUSB contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "bsp/board_api.h"
+#include "tusb.h"
+#include "app.h"
+
+//--------------------------------------------------------------------+
+// MACRO CONSTANT TYPEDEF PROTYPES
+//--------------------------------------------------------------------+
+void led_blinking_task(void);
+
+/*------------- MAIN -------------*/
+int main(void) {
+ board_init();
+
+ printf("TinyUSB Host USB Audio Example\r\n");
+ printf("Connect a USB Audio Device (UAC 1.0) to test\r\n");
+
+ // init host stack on configured roothub port
+ tusb_rhport_init_t host_init = {
+ .role = TUSB_ROLE_HOST,
+ .speed = TUSB_SPEED_AUTO
+ };
+ tusb_init(BOARD_TUH_RHPORT, &host_init);
+
+ board_init_after_tusb();
+
+ while (1) {
+ // tinyusb host task
+ tuh_task();
+ led_blinking_task();
+ audio_app_task();
+ }
+}
+
+//--------------------------------------------------------------------+
+// TinyUSB Callbacks
+//--------------------------------------------------------------------+
+
+//--------------------------------------------------------------------+
+// Blinking Task
+//--------------------------------------------------------------------+
+void led_blinking_task(void) {
+ const uint32_t interval_ms = 1000;
+ static uint32_t start_ms = 0;
+
+ static bool led_state = false;
+
+ // Blink every interval ms
+ if ( tusb_time_millis_api() - start_ms < interval_ms) return; // not enough time
+ start_ms += interval_ms;
+
+ board_led_write(led_state);
+ led_state = 1 - led_state; // toggle
+}
diff --git a/examples/host/audio_host/src/tusb_config.h b/examples/host/audio_host/src/tusb_config.h
new file mode 100644
index 000000000..a4fb17fa5
--- /dev/null
+++ b/examples/host/audio_host/src/tusb_config.h
@@ -0,0 +1,103 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2025 TinyUSB contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#ifndef TUSB_CONFIG_H_
+#define TUSB_CONFIG_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+//--------------------------------------------------------------------
+// Common Configuration
+//--------------------------------------------------------------------
+
+#ifndef CFG_TUSB_MCU
+ #error CFG_TUSB_MCU must be defined
+#endif
+
+#ifndef CFG_TUSB_OS
+ #define CFG_TUSB_OS OPT_OS_NONE
+#endif
+
+#ifndef CFG_TUSB_DEBUG
+ #define CFG_TUSB_DEBUG 0
+#endif
+
+#ifndef CFG_TUH_MEM_SECTION
+ #define CFG_TUH_MEM_SECTION
+#endif
+
+#ifndef CFG_TUH_MEM_ALIGN
+ #define CFG_TUH_MEM_ALIGN __attribute__((aligned(4)))
+#endif
+
+//--------------------------------------------------------------------
+// Host Configuration
+//--------------------------------------------------------------------
+
+#define CFG_TUH_ENABLED 1
+
+#if CFG_TUSB_MCU == OPT_MCU_RP2040
+ #if (defined(CFG_TUH_RPI_PIO_USB) && CFG_TUH_RPI_PIO_USB) || (defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421)
+ #define BOARD_TUH_RHPORT 1
+ #endif
+#endif
+
+#define CFG_TUH_MAX_SPEED BOARD_TUH_MAX_SPEED
+
+#ifndef BOARD_TUH_RHPORT
+ #define BOARD_TUH_RHPORT 1
+#endif
+
+#ifndef BOARD_TUH_MAX_SPEED
+ #define BOARD_TUH_MAX_SPEED OPT_MODE_DEFAULT_SPEED
+#endif
+
+//--------------------------------------------------------------------
+// Driver Configuration
+//--------------------------------------------------------------------
+
+#define CFG_TUH_ENUMERATION_BUFSIZE 512
+
+#define CFG_TUH_HUB 1
+#define CFG_TUH_CDC 0
+#define CFG_TUH_HID 1
+#define CFG_TUH_MSC 0
+#define CFG_TUH_VENDOR 0
+#define CFG_TUH_AUDIO 1
+
+// max device support (excluding hub device): 1 hub typically has 4 ports
+#define CFG_TUH_DEVICE_MAX (3 * CFG_TUH_HUB + 1)
+
+//------------- Audio Host Config -------------//
+#define CFG_TUH_AUDIO_MAX 2
+#define CFG_TUH_AUDIO_EPIN_BUFSIZE 192
+#define CFG_TUH_AUDIO_EPOUT_BUFSIZE 192
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* TUSB_CONFIG_H_ */
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index b3e05f60f..b5a5a0b3b 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -27,6 +27,7 @@ function(tinyusb_sources_get OUTPUT_VAR)
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/host/usbh.c
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/host/hub.c
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/cdc/cdc_host.c
+ ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/audio/audio_host.c
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/hid/hid_host.c
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/midi/midi_host.c
${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/midi/midi2_host.c
diff --git a/src/class/audio/audio_host.c b/src/class/audio/audio_host.c
new file mode 100644
index 000000000..9414c1ab4
--- /dev/null
+++ b/src/class/audio/audio_host.c
@@ -0,0 +1,809 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2025 TinyUSB contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * This file is part of the TinyUSB stack.
+ */
+
+#include "tusb_option.h"
+
+#if (CFG_TUH_ENABLED && CFG_TUH_AUDIO)
+
+#include "host/usbh.h"
+#include "host/usbh_pvt.h"
+#include "audio_host.h"
+
+// Level where CFG_TUSB_DEBUG must be at least for this driver is logged
+#ifndef CFG_TUH_AUDIO_LOG_LEVEL
+ #define CFG_TUH_AUDIO_LOG_LEVEL CFG_TUH_LOG_LEVEL
+#endif
+
+#define TU_LOG_DRV(...) TU_LOG(CFG_TUH_AUDIO_LOG_LEVEL, __VA_ARGS__)
+
+//--------------------------------------------------------------------+
+// Weak stubs: invoked if no strong implementation is available
+//--------------------------------------------------------------------+
+TU_ATTR_WEAK void tuh_audio_descriptor_cb(uint8_t idx, const tuh_audio_descriptor_cb_t *desc_cb_data) {
+ (void) idx;
+ (void) desc_cb_data;
+}
+
+TU_ATTR_WEAK void tuh_audio_mount_cb(uint8_t idx, const tuh_audio_mount_cb_t *mount_cb_data) {
+ (void) idx;
+ (void) mount_cb_data;
+}
+
+TU_ATTR_WEAK void tuh_audio_umount_cb(uint8_t idx) {
+ (void) idx;
+}
+
+TU_ATTR_WEAK void tuh_audio_rx_cb(uint8_t idx, uint8_t ep_addr, uint16_t xferred_bytes) {
+ (void) idx;
+ (void) ep_addr;
+ (void) xferred_bytes;
+}
+
+TU_ATTR_WEAK void tuh_audio_tx_cb(uint8_t idx, uint8_t ep_addr, uint16_t xferred_bytes) {
+ (void) idx;
+ (void) ep_addr;
+ (void) xferred_bytes;
+}
+
+//--------------------------------------------------------------------+
+// MACRO CONSTANT TYPEDEF
+//--------------------------------------------------------------------+
+
+// Per-AS interface internal storage
+typedef struct {
+ uint8_t interface_num;
+ uint8_t alt_setting;
+ uint8_t ep_addr;
+ uint16_t ep_size;
+ uint8_t ep_dir;
+
+ uint8_t format_type;
+ uint8_t num_channels;
+ uint8_t sub_frame_size;
+ uint8_t bit_resolution;
+ uint8_t sam_freq_type;
+ uint32_t sam_freq[CFG_TUH_AUDIO_MAX_SAM_FREQ];
+ uint32_t sam_freq_lower;
+ uint32_t sam_freq_upper;
+} audioh_as_t;
+
+typedef struct {
+ uint8_t daddr;
+ uint8_t bInterfaceNumber; // Audio Control interface number
+ uint8_t iInterface;
+ uint8_t itf_count; // number of interfaces (AC + AS)
+
+ // Audio Streaming Interface
+ uint8_t as_interface_num; // Audio Streaming interface number
+ uint8_t alt_setting; // current alt setting
+
+ // Terminal info (from Audio Control Interface)
+ uint16_t input_terminal_type; // wTerminalType of Input Terminal
+ uint8_t input_terminal_id; // bTerminalID of Input Terminal
+ uint8_t input_terminal_channels; // bNrChannels of Input Terminal
+ uint16_t output_terminal_type; // wTerminalType of Output Terminal
+ uint8_t output_terminal_id; // bTerminalID of Output Terminal
+
+ // Feature Unit info
+ uint8_t feature_unit_id; // bUnitID of Feature Unit (0 = none)
+ uint8_t feature_unit_source_id; // bSourceID of Feature Unit
+
+ // Isochronous IN endpoint
+ uint8_t ep_in;
+ uint16_t ep_in_size;
+ uint16_t ep_in_interval;
+
+ // Isochronous OUT endpoint
+ uint8_t ep_out;
+ uint16_t ep_out_size;
+ uint16_t ep_out_interval;
+
+ // Multiple AS interfaces support
+ uint8_t as_interfaces[CFG_TUH_AUDIO_MAX_AS];
+ uint8_t as_alt_settings[CFG_TUH_AUDIO_MAX_AS];
+ uint8_t as_count;
+ uint8_t as_set_idx;
+
+ // Per-AS interface independent storage (new)
+ audioh_as_t as[CFG_TUH_AUDIO_MAX_AS];
+
+ bool mounted;
+} audioh_interface_t;
+
+typedef struct {
+ TUH_EPBUF_DEF(epin, CFG_TUH_AUDIO_EPIN_BUFSIZE);
+ TUH_EPBUF_DEF(epout, CFG_TUH_AUDIO_EPOUT_BUFSIZE);
+} audioh_epbuf_t;
+
+static audioh_interface_t _audioh_itf[CFG_TUH_AUDIO_MAX];
+
+//--------------------------------------------------------------------+
+// Helper
+//--------------------------------------------------------------------+
+TU_ATTR_ALWAYS_INLINE static inline uint8_t find_new_audio_index(void) {
+ for (uint8_t idx = 0; idx < CFG_TUH_AUDIO_MAX; idx++) {
+ if (_audioh_itf[idx].daddr == 0) {
+ return idx;
+ }
+ }
+ return TUSB_INDEX_INVALID_8;
+}
+
+static inline uint8_t get_idx_by_ep_addr(uint8_t daddr, uint8_t ep_addr) {
+ for (uint8_t idx = 0; idx < CFG_TUH_AUDIO_MAX; idx++) {
+ const audioh_interface_t *p_audio = &_audioh_itf[idx];
+ if ((p_audio->daddr == daddr) &&
+ (ep_addr == p_audio->ep_in || ep_addr == p_audio->ep_out)) {
+ return idx;
+ }
+ }
+ return TUSB_INDEX_INVALID_8;
+}
+
+//--------------------------------------------------------------------+
+// USBH API
+//--------------------------------------------------------------------+
+bool audioh_init(void) {
+ tu_memclr(&_audioh_itf, sizeof(_audioh_itf));
+ return true;
+}
+
+bool audioh_deinit(void) {
+ return true;
+}
+
+void audioh_close(uint8_t daddr) {
+ for (uint8_t idx = 0; idx < CFG_TUH_AUDIO_MAX; idx++) {
+ audioh_interface_t *p_audio = &_audioh_itf[idx];
+ if (p_audio->daddr == daddr) {
+ TU_LOG_DRV(" AUDIO close addr = %u index = %u\r\n", daddr, idx);
+ tuh_audio_umount_cb(idx);
+
+ p_audio->bInterfaceNumber = 0;
+ p_audio->as_interface_num = 0;
+ p_audio->alt_setting = 0;
+ p_audio->daddr = 0;
+ p_audio->mounted = false;
+ p_audio->ep_in = 0;
+ p_audio->ep_out = 0;
+ p_audio->as_count = 0;
+ p_audio->as_set_idx = 0;
+ tu_memclr(p_audio->as_interfaces, sizeof(p_audio->as_interfaces));
+ tu_memclr(p_audio->as_alt_settings, sizeof(p_audio->as_alt_settings));
+ tu_memclr(p_audio->as, sizeof(p_audio->as));
+ }
+ }
+}
+
+bool audioh_xfer_cb(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) {
+ (void) result;
+ const uint8_t idx = get_idx_by_ep_addr(dev_addr, ep_addr);
+ TU_VERIFY(idx < CFG_TUH_AUDIO_MAX);
+ audioh_interface_t *p_audio = &_audioh_itf[idx];
+
+ if (ep_addr == p_audio->ep_in) {
+ tuh_audio_rx_cb(idx, ep_addr, (uint16_t) xferred_bytes);
+ } else if (ep_addr == p_audio->ep_out) {
+ tuh_audio_tx_cb(idx, ep_addr, (uint16_t) xferred_bytes);
+ }
+
+ return true;
+}
+
+//--------------------------------------------------------------------+
+// Enumeration
+//--------------------------------------------------------------------+
+uint16_t audioh_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_interface_t *desc_itf, uint16_t max_len) {
+ (void) rhport;
+
+ TU_VERIFY(TUSB_CLASS_AUDIO == desc_itf->bInterfaceClass, 0);
+ TU_VERIFY(AUDIO_SUBCLASS_CONTROL == desc_itf->bInterfaceSubClass, 0);
+
+ const uint8_t *desc_start = (const uint8_t *)desc_itf;
+ const uint8_t *p_desc = desc_start;
+ const uint8_t *desc_end = desc_start + max_len;
+
+ const uint8_t idx = find_new_audio_index();
+ TU_VERIFY(idx < CFG_TUH_AUDIO_MAX, 0);
+ audioh_interface_t *p_audio = &_audioh_itf[idx];
+ p_audio->itf_count = 0;
+
+ tuh_audio_descriptor_cb_t desc_cb = { 0 };
+
+ // Parse Audio Control Interface
+ TU_LOG_DRV("AUDIO opening AC Interface %u (addr = %u)\r\n", desc_itf->bInterfaceNumber, dev_addr);
+ p_audio->bInterfaceNumber = desc_itf->bInterfaceNumber;
+ p_audio->iInterface = desc_itf->iInterface;
+ p_audio->itf_count = 1;
+ desc_cb.desc_ac_interface = desc_itf;
+ desc_cb.ac_interface_num = desc_itf->bInterfaceNumber;
+
+ // Parse Audio Control interface descriptors (Input Terminal, Output Terminal, Feature Unit, etc.)
+ p_desc = tu_desc_next(p_desc);
+ while (tu_desc_in_bounds(p_desc, desc_end) && tu_desc_type(p_desc) != TUSB_DESC_INTERFACE) {
+ if (tu_desc_type(p_desc) == TUSB_DESC_CS_INTERFACE) {
+ switch (tu_desc_subtype(p_desc)) {
+ case AUDIO10_CS_AC_INTERFACE_INPUT_TERMINAL: {
+ const audio10_desc_input_terminal_t *it = (const audio10_desc_input_terminal_t *)p_desc;
+ p_audio->input_terminal_type = tu_le16toh(it->wTerminalType);
+ p_audio->input_terminal_id = it->bTerminalID;
+ p_audio->input_terminal_channels = it->bNrChannels;
+ TU_LOG_DRV(" Input Terminal: ID=%u, Type=0x%04x, Channels=%u\r\n",
+ it->bTerminalID, tu_le16toh(it->wTerminalType), it->bNrChannels);
+ break;
+ }
+ case AUDIO10_CS_AC_INTERFACE_OUTPUT_TERMINAL: {
+ const audio10_desc_output_terminal_t *ot = (const audio10_desc_output_terminal_t *)p_desc;
+ p_audio->output_terminal_type = tu_le16toh(ot->wTerminalType);
+ p_audio->output_terminal_id = ot->bTerminalID;
+ TU_LOG_DRV(" Output Terminal: ID=%u, Type=0x%04x\r\n",
+ ot->bTerminalID, tu_le16toh(ot->wTerminalType));
+ break;
+ }
+ case AUDIO10_CS_AC_INTERFACE_FEATURE_UNIT: {
+ const uint8_t *fu = p_desc;
+ p_audio->feature_unit_id = fu[3]; // bUnitID
+ p_audio->feature_unit_source_id = fu[4]; // bSourceID
+ TU_LOG_DRV(" Feature Unit: ID=%u, SourceID=%u\r\n", fu[3], fu[4]);
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ p_desc = tu_desc_next(p_desc);
+ }
+
+ // Parse all remaining descriptors in this configuration looking for Audio Streaming interfaces
+ while (tu_desc_in_bounds(p_desc, desc_end)) {
+ if (tu_desc_type(p_desc) == TUSB_DESC_INTERFACE) {
+ const tusb_desc_interface_t *itf = (const tusb_desc_interface_t *)p_desc;
+ if (itf->bInterfaceClass == TUSB_CLASS_AUDIO && itf->bInterfaceSubClass == AUDIO_SUBCLASS_STREAMING) {
+ // Found Audio Streaming Interface
+ TU_LOG_DRV(" Found AS Interface %u (alt = %u)\r\n", itf->bInterfaceNumber, itf->bAlternateSetting);
+
+ if (itf->bAlternateSetting == 0) {
+ // Interface descriptor with alt setting 0 (no endpoints)
+ // Add to AS interfaces array
+ if (p_audio->as_count < CFG_TUH_AUDIO_MAX_AS) {
+ p_audio->as_interface_num = itf->bInterfaceNumber;
+ p_audio->as_interfaces[p_audio->as_count] = itf->bInterfaceNumber;
+ desc_cb.desc_as_interface = itf;
+ desc_cb.as_interface_num = itf->bInterfaceNumber;
+ // Create new AS entry for per-interface storage
+ p_audio->as[p_audio->as_count].interface_num = itf->bInterfaceNumber;
+ p_audio->as[p_audio->as_count].alt_setting = 0;
+ p_audio->as_count++;
+ }
+ } else if (itf->bNumEndpoints > 0) {
+ // Interface descriptor with alt setting > 0 (has endpoints)
+ // Find matching AS interface and set alt_setting
+ uint8_t as_entry_idx = CFG_TUH_AUDIO_MAX_AS;
+ for (uint8_t as_idx = 0; as_idx < p_audio->as_count; as_idx++) {
+ if (p_audio->as_interfaces[as_idx] == itf->bInterfaceNumber) {
+ p_audio->alt_setting = itf->bAlternateSetting;
+ p_audio->as_alt_settings[as_idx] = itf->bAlternateSetting;
+ desc_cb.alt_setting = itf->bAlternateSetting;
+ desc_cb.desc_as_interface_alt = itf;
+ break;
+ }
+ }
+ // Find or create AS entry for per-interface storage
+ for (uint8_t i = 0; i < p_audio->as_count; i++) {
+ if (p_audio->as[i].interface_num == itf->bInterfaceNumber) {
+ as_entry_idx = i;
+ break;
+ }
+ }
+ if (as_entry_idx >= CFG_TUH_AUDIO_MAX_AS && p_audio->as_count < CFG_TUH_AUDIO_MAX_AS) {
+ as_entry_idx = p_audio->as_count;
+ p_audio->as[as_entry_idx].interface_num = itf->bInterfaceNumber;
+ p_audio->as_count++;
+ }
+ if (as_entry_idx < CFG_TUH_AUDIO_MAX_AS) {
+ p_audio->as[as_entry_idx].alt_setting = itf->bAlternateSetting;
+ }
+
+ // Parse the interface's descriptors
+ p_desc = tu_desc_next(p_desc);
+ // Temporary variables to hold format info until endpoint direction is known
+ uint8_t tmp_format_type = 0;
+ uint8_t tmp_num_channels = 0;
+ uint8_t tmp_sub_frame_size = 0;
+ uint8_t tmp_bit_resolution = 0;
+ uint8_t tmp_sam_freq_type = 0;
+ uint32_t tmp_sam_freq[CFG_TUH_AUDIO_MAX_SAM_FREQ] = {0};
+ uint32_t tmp_sam_freq_lower = 0;
+ uint32_t tmp_sam_freq_upper = 0;
+ while (tu_desc_in_bounds(p_desc, desc_end) && tu_desc_type(p_desc) != TUSB_DESC_INTERFACE) {
+ switch (tu_desc_type(p_desc)) {
+ case TUSB_DESC_CS_INTERFACE: {
+ switch (tu_desc_subtype(p_desc)) {
+ case AUDIO10_CS_AS_INTERFACE_AS_GENERAL: {
+ TU_LOG_DRV(" AS General descriptor\r\n");
+ desc_cb.desc_cs_as_general = p_desc;
+ break;
+ }
+ case AUDIO10_CS_AS_INTERFACE_FORMAT_TYPE: {
+ TU_LOG_DRV(" Format Type descriptor\r\n");
+ desc_cb.desc_format_type = p_desc;
+ // Parse UAC 1.0 Format Type I descriptor fields into temporary variables
+ tmp_format_type = p_desc[3]; // bFormatType
+ tmp_num_channels = p_desc[4]; // bNrChannels
+ tmp_sub_frame_size = p_desc[5]; // bSubFrameSize
+ tmp_bit_resolution = p_desc[6]; // bBitResolution
+
+ // Parse sampling frequencies
+ uint8_t bLength = p_desc[0];
+ if (bLength >= 8) {
+ tmp_sam_freq_type = p_desc[7]; // bSamFreqType
+ if (tmp_sam_freq_type == 0) {
+ // Continuous range: tLowerSamFreq, tUpperSamFreq (3 bytes each)
+ if (bLength >= 14) {
+ tmp_sam_freq_lower = ((uint32_t)p_desc[8] | ((uint32_t)p_desc[9] << 8) | ((uint32_t)p_desc[10] << 16));
+ tmp_sam_freq_upper = ((uint32_t)p_desc[11] | ((uint32_t)p_desc[12] << 8) | ((uint32_t)p_desc[13] << 16));
+ }
+ } else {
+ // Discrete sampling frequencies
+ uint8_t max_freqs = tmp_sam_freq_type < CFG_TUH_AUDIO_MAX_SAM_FREQ ? tmp_sam_freq_type : CFG_TUH_AUDIO_MAX_SAM_FREQ;
+ for (uint8_t i = 0; i < max_freqs && (8 + i * 3 + 2) < bLength; i++) {
+ tmp_sam_freq[i] = ((uint32_t)p_desc[8 + i * 3] |
+ ((uint32_t)p_desc[9 + i * 3] << 8) |
+ ((uint32_t)p_desc[10 + i * 3] << 16));
+ }
+ }
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ break;
+ }
+ case TUSB_DESC_ENDPOINT: {
+ const tusb_desc_endpoint_t *p_ep = (const tusb_desc_endpoint_t *)p_desc;
+ if (p_ep->bmAttributes.xfer == TUSB_XFER_ISOCHRONOUS) {
+ TU_LOG_DRV(" Isochronous EP %02x\r\n", p_ep->bEndpointAddress);
+ if (tu_edpt_dir(p_ep->bEndpointAddress) == TUSB_DIR_IN) {
+ p_audio->ep_in = p_ep->bEndpointAddress;
+ p_audio->ep_in_size = tu_edpt_packet_size(p_ep);
+ p_audio->ep_in_interval = p_ep->bInterval;
+ desc_cb.desc_ep_in = p_ep;
+ // Save to per-AS storage
+ if (as_entry_idx < CFG_TUH_AUDIO_MAX_AS) {
+ audioh_as_t *as = &p_audio->as[as_entry_idx];
+ as->ep_addr = p_ep->bEndpointAddress;
+ as->ep_size = tu_edpt_packet_size(p_ep);
+ as->ep_dir = TUSB_DIR_IN;
+ as->format_type = tmp_format_type;
+ as->num_channels = tmp_num_channels;
+ as->sub_frame_size = tmp_sub_frame_size;
+ as->bit_resolution = tmp_bit_resolution;
+ as->sam_freq_type = tmp_sam_freq_type;
+ as->sam_freq_lower = tmp_sam_freq_lower;
+ as->sam_freq_upper = tmp_sam_freq_upper;
+ for (uint8_t i = 0; i < CFG_TUH_AUDIO_MAX_SAM_FREQ; i++) {
+ as->sam_freq[i] = tmp_sam_freq[i];
+ }
+ }
+ } else {
+ p_audio->ep_out = p_ep->bEndpointAddress;
+ p_audio->ep_out_size = tu_edpt_packet_size(p_ep);
+ p_audio->ep_out_interval = p_ep->bInterval;
+ desc_cb.desc_ep_out = p_ep;
+ // Save to per-AS storage
+ if (as_entry_idx < CFG_TUH_AUDIO_MAX_AS) {
+ audioh_as_t *as = &p_audio->as[as_entry_idx];
+ as->ep_addr = p_ep->bEndpointAddress;
+ as->ep_size = tu_edpt_packet_size(p_ep);
+ as->ep_dir = TUSB_DIR_OUT;
+ as->format_type = tmp_format_type;
+ as->num_channels = tmp_num_channels;
+ as->sub_frame_size = tmp_sub_frame_size;
+ as->bit_resolution = tmp_bit_resolution;
+ as->sam_freq_type = tmp_sam_freq_type;
+ as->sam_freq_lower = tmp_sam_freq_lower;
+ as->sam_freq_upper = tmp_sam_freq_upper;
+ for (uint8_t i = 0; i < CFG_TUH_AUDIO_MAX_SAM_FREQ; i++) {
+ as->sam_freq[i] = tmp_sam_freq[i];
+ }
+ }
+ }
+ TU_ASSERT(tuh_edpt_open(dev_addr, p_ep), 0);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ p_desc = tu_desc_next(p_desc);
+ }
+ // Continue to parse other AS interfaces (don't break, device may have both IN and OUT)
+ // break; // Removed: allow parsing multiple AS interfaces (e.g. mic + speaker)
+ continue;
+ }
+ p_audio->itf_count++;
+ } else if (itf->bInterfaceClass == TUSB_CLASS_AUDIO && itf->bInterfaceSubClass == AUDIO_SUBCLASS_CONTROL) {
+ // Another Audio Control interface (shouldn't happen in normal UAC 1.0)
+ p_audio->itf_count++;
+ }
+ }
+ p_desc = tu_desc_next(p_desc);
+ }
+
+ p_audio->daddr = dev_addr;
+ tuh_audio_descriptor_cb(idx, &desc_cb);
+
+ return (uint16_t)((uintptr_t)p_desc - (uintptr_t)desc_start);
+}
+
+static void _audioh_mount(uint8_t dev_addr, uint8_t idx);
+
+static void audioh_set_interface_complete(tuh_xfer_t* xfer) {
+ uint8_t idx = (uint8_t) xfer->user_data;
+ audioh_interface_t *p_audio = &_audioh_itf[idx];
+
+ // Send SET_INTERFACE for next AS interface if any
+ p_audio->as_set_idx++;
+ if (p_audio->as_set_idx < p_audio->as_count) {
+ uint8_t as_idx = p_audio->as_set_idx;
+ uint8_t itf = p_audio->as_interfaces[as_idx];
+ uint8_t alt = p_audio->as_alt_settings[as_idx];
+ if (alt > 0) {
+ TU_LOG_DRV("AUDIO Set Interface %u Alt %u (addr = %u)\r\n", itf, alt, xfer->daddr);
+ tuh_interface_set(xfer->daddr, itf, alt, audioh_set_interface_complete, idx);
+ return;
+ }
+ }
+
+ // All SET_INTERFACE done, mount the device
+ _audioh_mount(xfer->daddr, idx);
+}
+
+static void _audioh_mount(uint8_t dev_addr, uint8_t idx) {
+ audioh_interface_t *p_audio = &_audioh_itf[idx];
+ p_audio->mounted = true;
+
+ tuh_audio_mount_cb_t mount_cb_data = {
+ .daddr = dev_addr,
+ .bInterfaceNumber = p_audio->bInterfaceNumber,
+ .bAltSetting = p_audio->alt_setting,
+ .input_terminal_type = p_audio->input_terminal_type,
+ .input_terminal_id = p_audio->input_terminal_id,
+ .input_terminal_channels = p_audio->input_terminal_channels,
+ .output_terminal_type = p_audio->output_terminal_type,
+ .output_terminal_id = p_audio->output_terminal_id,
+ .feature_unit_id = p_audio->feature_unit_id,
+ .feature_unit_source_id = p_audio->feature_unit_source_id,
+ .ep_in = p_audio->ep_in,
+ .ep_out = p_audio->ep_out,
+ .ep_in_size = p_audio->ep_in_size,
+ .ep_out_size = p_audio->ep_out_size,
+ };
+
+ // Fill per-AS interface info
+ mount_cb_data.as_count = p_audio->as_count;
+ for (uint8_t i = 0; i < p_audio->as_count && i < CFG_TUH_AUDIO_MAX_AS; i++) {
+ audioh_as_t *as = &p_audio->as[i];
+ mount_cb_data.as_info[i].interface_num = as->interface_num;
+ mount_cb_data.as_info[i].alt_setting = as->alt_setting;
+ mount_cb_data.as_info[i].ep_addr = as->ep_addr;
+ mount_cb_data.as_info[i].ep_size = as->ep_size;
+ mount_cb_data.as_info[i].ep_dir = as->ep_dir;
+ mount_cb_data.as_info[i].format_type = as->format_type;
+ mount_cb_data.as_info[i].num_channels = as->num_channels;
+ mount_cb_data.as_info[i].sub_frame_size = as->sub_frame_size;
+ mount_cb_data.as_info[i].bit_resolution = as->bit_resolution;
+ mount_cb_data.as_info[i].sam_freq_type = as->sam_freq_type;
+ mount_cb_data.as_info[i].sam_freq_lower = as->sam_freq_lower;
+ mount_cb_data.as_info[i].sam_freq_upper = as->sam_freq_upper;
+ for (uint8_t j = 0; j < CFG_TUH_AUDIO_MAX_SAM_FREQ; j++) {
+ mount_cb_data.as_info[i].sam_freq[j] = as->sam_freq[j];
+ }
+ }
+
+ tuh_audio_mount_cb(idx, &mount_cb_data);
+
+ usbh_driver_set_config_complete(dev_addr, p_audio->bInterfaceNumber);
+}
+
+bool audioh_set_config(uint8_t dev_addr, uint8_t itf_num) {
+ uint8_t idx = tuh_audio_itf_get_index(dev_addr, itf_num);
+
+ // If not found, check if this is an AS interface that belongs to a known AC interface
+ if (idx >= CFG_TUH_AUDIO_MAX) {
+ for (uint8_t i = 0; i < CFG_TUH_AUDIO_MAX; i++) {
+ if (_audioh_itf[i].daddr == dev_addr && _audioh_itf[i].as_interface_num == itf_num) {
+ // AS interface, already handled by AC interface's set_config
+ return true;
+ }
+ }
+ return false;
+ }
+
+ audioh_interface_t *p_audio = &_audioh_itf[idx];
+
+ // Send SET_INTERFACE for all AS interfaces with alt_setting > 0
+ if (p_audio->as_count > 0) {
+ p_audio->as_set_idx = 0;
+ uint8_t itf = p_audio->as_interfaces[0];
+ uint8_t alt = p_audio->as_alt_settings[0];
+ if (alt > 0) {
+ TU_LOG_DRV("AUDIO Set Interface %u Alt %u (addr = %u)\r\n", itf, alt, dev_addr);
+ tuh_interface_set(dev_addr, itf, alt, audioh_set_interface_complete, idx);
+ return true;
+ }
+ }
+
+ _audioh_mount(dev_addr, idx);
+ return true;
+}
+
+//--------------------------------------------------------------------+
+// Application API
+//--------------------------------------------------------------------+
+bool tuh_audio_mounted(uint8_t idx) {
+ TU_VERIFY(idx < CFG_TUH_AUDIO_MAX);
+ audioh_interface_t *p_audio = &_audioh_itf[idx];
+ return p_audio->mounted;
+}
+
+uint8_t tuh_audio_itf_get_index(uint8_t daddr, uint8_t itf_num) {
+ for (uint8_t idx = 0; idx < CFG_TUH_AUDIO_MAX; idx++) {
+ const audioh_interface_t *p_audio = &_audioh_itf[idx];
+ if (p_audio->daddr == daddr && p_audio->bInterfaceNumber == itf_num) {
+ return idx;
+ }
+ }
+ return TUSB_INDEX_INVALID_8;
+}
+
+bool tuh_audio_itf_get_info(uint8_t idx, tuh_itf_info_t *info) {
+ audioh_interface_t *p_audio = &_audioh_itf[idx];
+ TU_VERIFY(p_audio && info);
+
+ info->daddr = p_audio->daddr;
+
+ // re-construct descriptor
+ tusb_desc_interface_t *desc = &info->desc;
+ desc->bLength = sizeof(tusb_desc_interface_t);
+ desc->bDescriptorType = TUSB_DESC_INTERFACE;
+
+ desc->bInterfaceNumber = p_audio->bInterfaceNumber;
+ desc->bAlternateSetting = 0;
+ desc->bNumEndpoints = (uint8_t)((p_audio->ep_in ? 1u : 0u) + (p_audio->ep_out ? 1u : 0u));
+ desc->bInterfaceClass = TUSB_CLASS_AUDIO;
+ desc->bInterfaceSubClass = AUDIO_SUBCLASS_CONTROL;
+ desc->bInterfaceProtocol = 0;
+ desc->iInterface = p_audio->iInterface;
+
+ return true;
+}
+
+//--------------------------------------------------------------------+
+// Control Endpoint API
+//--------------------------------------------------------------------+
+bool tuh_audio_set_sampling_freq(uint8_t daddr, uint8_t ep_addr, uint32_t sampling_freq,
+ tuh_xfer_cb_t complete_cb, uintptr_t user_data) {
+ static uint8_t freq_buf[3] = {0};
+ tusb_control_request_t const request = {
+ .bmRequestType_bit = {
+ .recipient = TUSB_REQ_RCPT_ENDPOINT,
+ .type = TUSB_REQ_TYPE_CLASS,
+ .direction = TUSB_DIR_OUT
+ },
+ .bRequest = AUDIO10_CS_REQ_SET_CUR,
+ .wValue = tu_u16(AUDIO10_EP_CTRL_SAMPLING_FREQ, 0), // Control Selector = Sampling Freq, Channel = 0
+ .wIndex = tu_u16_low(ep_addr),
+ .wLength = 3
+ };
+
+ // UAC 1.0 sampling frequency is 3 bytes little-endian
+ // uint8_t freq_buf[3] = {
+ // (uint8_t)(sampling_freq & 0xFF),
+ // (uint8_t)((sampling_freq >> 8) & 0xFF),
+ // (uint8_t)((sampling_freq >> 16) & 0xFF)
+ // };
+ freq_buf[0] = (uint8_t)(sampling_freq & 0xFF);
+ freq_buf[1] = (uint8_t)((sampling_freq >> 8) & 0xFF);
+ freq_buf[2] = (uint8_t)((sampling_freq >> 16) & 0xFF);
+ tuh_xfer_t xfer = {
+ .daddr = daddr,
+ .ep_addr = 0,
+ .setup = &request,
+ .buffer = freq_buf,
+ .complete_cb = complete_cb,
+ .user_data = user_data
+ };
+
+ return tuh_control_xfer(&xfer);
+}
+
+bool tuh_audio_get_sampling_freq(uint8_t daddr, uint8_t ep_addr, uint32_t *sampling_freq,
+ tuh_xfer_cb_t complete_cb, uintptr_t user_data) {
+ tusb_control_request_t const request = {
+ .bmRequestType_bit = {
+ .recipient = TUSB_REQ_RCPT_ENDPOINT,
+ .type = TUSB_REQ_TYPE_CLASS,
+ .direction = TUSB_DIR_IN
+ },
+ .bRequest = AUDIO10_CS_REQ_GET_CUR,
+ .wValue = tu_u16(AUDIO10_EP_CTRL_SAMPLING_FREQ, 0), // Control Selector = Sampling Freq, Channel = 0
+ .wIndex = tu_u16_low(ep_addr),
+ .wLength = 3
+ };
+
+ // Application needs to parse 3-byte little-endian sampling frequency from buffer
+ tuh_xfer_t xfer = {
+ .daddr = daddr,
+ .ep_addr = 0,
+ .setup = &request,
+ .buffer = (uint8_t *)sampling_freq,
+ .complete_cb = complete_cb,
+ .user_data = user_data
+ };
+
+ return tuh_control_xfer(&xfer);
+}
+
+bool tuh_audio_feature_unit_set(uint8_t daddr, uint8_t itf_num, uint8_t unit_id,
+ uint8_t control_selector, uint8_t channel,
+ uint16_t value, tuh_xfer_cb_t complete_cb, uintptr_t user_data) {
+ tusb_control_request_t const request = {
+ .bmRequestType_bit = {
+ .recipient = TUSB_REQ_RCPT_INTERFACE,
+ .type = TUSB_REQ_TYPE_CLASS,
+ .direction = TUSB_DIR_OUT
+ },
+ .bRequest = AUDIO10_CS_REQ_SET_CUR,
+ .wValue = tu_u16(control_selector, channel),
+ .wIndex = tu_u16(itf_num, unit_id),
+ .wLength = 2
+ };
+
+ uint8_t val_buf[2] = { (uint8_t)(value & 0xFF), (uint8_t)((value >> 8) & 0xFF) };
+
+ tuh_xfer_t xfer = {
+ .daddr = daddr,
+ .ep_addr = 0,
+ .setup = &request,
+ .buffer = val_buf,
+ .complete_cb = complete_cb,
+ .user_data = user_data
+ };
+
+ return tuh_control_xfer(&xfer);
+}
+
+bool tuh_audio_feature_unit_get(uint8_t daddr, uint8_t itf_num, uint8_t unit_id,
+ uint8_t control_selector, uint8_t channel,
+ void *buffer, uint8_t len,
+ tuh_xfer_cb_t complete_cb, uintptr_t user_data) {
+ tusb_control_request_t const request = {
+ .bmRequestType_bit = {
+ .recipient = TUSB_REQ_RCPT_INTERFACE,
+ .type = TUSB_REQ_TYPE_CLASS,
+ .direction = TUSB_DIR_IN
+ },
+ .bRequest = AUDIO10_CS_REQ_GET_CUR,
+ .wValue = tu_u16(control_selector, channel),
+ .wIndex = tu_u16(itf_num, unit_id),
+ .wLength = len
+ };
+
+ tuh_xfer_t xfer = {
+ .daddr = daddr,
+ .ep_addr = 0,
+ .setup = &request,
+ .buffer = buffer,
+ .complete_cb = complete_cb,
+ .user_data = user_data
+ };
+
+ return tuh_control_xfer(&xfer);
+}
+
+//--------------------------------------------------------------------+
+// Multi-AS interface API
+//--------------------------------------------------------------------+
+uint8_t tuh_audio_as_get_count(uint8_t idx) {
+ TU_VERIFY(idx < CFG_TUH_AUDIO_MAX, 0);
+ return _audioh_itf[idx].as_count;
+}
+
+bool tuh_audio_as_get_info(uint8_t idx, uint8_t as_idx, tuh_audio_as_info_t *info) {
+ TU_VERIFY(idx < CFG_TUH_AUDIO_MAX, false);
+ TU_VERIFY(as_idx < _audioh_itf[idx].as_count, false);
+ TU_VERIFY(info, false);
+
+ audioh_as_t *as = &_audioh_itf[idx].as[as_idx];
+ info->interface_num = as->interface_num;
+ info->alt_setting = as->alt_setting;
+ info->ep_addr = as->ep_addr;
+ info->ep_size = as->ep_size;
+ info->ep_dir = as->ep_dir;
+ info->format_type = as->format_type;
+ info->num_channels = as->num_channels;
+ info->sub_frame_size = as->sub_frame_size;
+ info->bit_resolution = as->bit_resolution;
+ info->sam_freq_type = as->sam_freq_type;
+ info->sam_freq_lower = as->sam_freq_lower;
+ info->sam_freq_upper = as->sam_freq_upper;
+ memcpy(info->sam_freq, as->sam_freq, sizeof(info->sam_freq));
+ return true;
+}
+
+//--------------------------------------------------------------------+
+// Isochronous Endpoint API
+//--------------------------------------------------------------------+
+bool tuh_audio_receive(uint8_t daddr, uint8_t idx, uint8_t *buffer, uint16_t len) {
+ TU_VERIFY(idx < CFG_TUH_AUDIO_MAX);
+ audioh_interface_t *p_audio = &_audioh_itf[idx];
+ TU_VERIFY(p_audio->daddr == daddr);
+ TU_VERIFY(p_audio->ep_in != 0);
+
+ return usbh_edpt_xfer(daddr, p_audio->ep_in, buffer, len);
+}
+
+bool tuh_audio_send(uint8_t daddr, uint8_t idx, uint8_t *buffer, uint16_t len) {
+ TU_VERIFY(idx < CFG_TUH_AUDIO_MAX);
+ audioh_interface_t *p_audio = &_audioh_itf[idx];
+ TU_VERIFY(p_audio->daddr == daddr);
+ TU_VERIFY(p_audio->ep_out != 0);
+
+ return usbh_edpt_xfer(daddr, p_audio->ep_out, (uint8_t *)buffer, len);
+}
+
+//--------------------------------------------------------------------+
+// Set Interface
+//--------------------------------------------------------------------+
+bool tuh_audio_set_interface(uint8_t daddr, uint8_t itf_num, uint8_t alt_setting,
+ tuh_xfer_cb_t complete_cb, uintptr_t user_data) {
+ tusb_control_request_t const request = {
+ .bmRequestType_bit = {
+ .recipient = TUSB_REQ_RCPT_INTERFACE,
+ .type = TUSB_REQ_TYPE_STANDARD,
+ .direction = TUSB_DIR_OUT
+ },
+ .bRequest = TUSB_REQ_SET_INTERFACE,
+ .wValue = alt_setting,
+ .wIndex = itf_num,
+ .wLength = 0
+ };
+
+ tuh_xfer_t xfer = {
+ .daddr = daddr,
+ .ep_addr = 0,
+ .setup = &request,
+ .buffer = NULL,
+ .complete_cb = complete_cb,
+ .user_data = user_data
+ };
+
+ return tuh_control_xfer(&xfer);
+}
+
+#endif
diff --git a/src/class/audio/audio_host.h b/src/class/audio/audio_host.h
new file mode 100644
index 000000000..6dd44f830
--- /dev/null
+++ b/src/class/audio/audio_host.h
@@ -0,0 +1,235 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2025 TinyUSB contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * This file is part of the TinyUSB stack.
+ */
+
+#ifndef TUSB_AUDIO_HOST_H_
+#define TUSB_AUDIO_HOST_H_
+
+#include "audio.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+//--------------------------------------------------------------------+
+// Class Driver Configuration
+//--------------------------------------------------------------------+
+#ifndef CFG_TUH_AUDIO_MAX
+ #define CFG_TUH_AUDIO_MAX 1
+#endif
+
+#ifndef CFG_TUH_AUDIO_MAX_SAM_FREQ
+ #define CFG_TUH_AUDIO_MAX_SAM_FREQ 5
+#endif
+
+#ifndef CFG_TUH_AUDIO_MAX_AS
+ #define CFG_TUH_AUDIO_MAX_AS 4
+#endif
+
+//--------------------------------------------------------------------+
+// AS Interface Info (per-interface independent storage)
+//--------------------------------------------------------------------+
+typedef struct {
+ uint8_t interface_num; // AS interface number
+ uint8_t alt_setting; // Current alt setting
+ uint8_t ep_addr; // Endpoint address
+ uint16_t ep_size; // Max packet size
+ uint8_t ep_dir; // TUSB_DIR_IN or TUSB_DIR_OUT
+
+ // Format info
+ uint8_t format_type;
+ uint8_t num_channels;
+ uint8_t sub_frame_size;
+ uint8_t bit_resolution;
+ uint8_t sam_freq_type;
+ uint32_t sam_freq[CFG_TUH_AUDIO_MAX_SAM_FREQ];
+ uint32_t sam_freq_lower;
+ uint32_t sam_freq_upper;
+} tuh_audio_as_info_t;
+
+#ifndef CFG_TUH_AUDIO_EPIN_BUFSIZE
+ #define CFG_TUH_AUDIO_EPIN_BUFSIZE 192
+#endif
+
+#ifndef CFG_TUH_AUDIO_EPOUT_BUFSIZE
+ #define CFG_TUH_AUDIO_EPOUT_BUFSIZE 192
+#endif
+
+//--------------------------------------------------------------------+
+// Descriptor Information
+//--------------------------------------------------------------------+
+// Information about parsed UAC 1.0 descriptors passed to the application
+// during enumeration (via tuh_audio_descriptor_cb)
+typedef struct {
+ // Audio Control Interface descriptor
+ const tusb_desc_interface_t *desc_ac_interface;
+
+ // Audio Streaming Interface descriptor (alt setting 0)
+ const tusb_desc_interface_t *desc_as_interface;
+
+ // Audio Streaming Interface alt setting (with endpoints)
+ const tusb_desc_interface_t *desc_as_interface_alt;
+
+ // Format Type descriptor
+ const uint8_t *desc_format_type;
+
+ // Class-Specific AS Interface (AS General) descriptor
+ const uint8_t *desc_cs_as_general;
+
+ // Standard Isochronous Endpoint descriptor (IN)
+ const tusb_desc_endpoint_t *desc_ep_in;
+
+ // Standard Isochronous Endpoint descriptor (OUT)
+ const tusb_desc_endpoint_t *desc_ep_out;
+
+ // Audio function information
+ uint8_t ac_interface_num; // Audio Control interface number
+ uint8_t as_interface_num; // Audio Streaming interface number
+ uint8_t alt_setting; // Current alt setting with endpoints
+} tuh_audio_descriptor_cb_t;
+
+typedef struct {
+ uint8_t daddr;
+ uint8_t bInterfaceNumber;
+ uint8_t bAltSetting;
+
+ // Terminal info (from Audio Control Interface)
+ uint16_t input_terminal_type; // wTerminalType of Input Terminal (0x0201 = Mic, etc.)
+ uint8_t input_terminal_id; // bTerminalID of Input Terminal
+ uint8_t input_terminal_channels; // bNrChannels of Input Terminal
+ uint16_t output_terminal_type; // wTerminalType of Output Terminal (0x0301 = Speaker, etc.)
+ uint8_t output_terminal_id; // bTerminalID of Output Terminal
+
+ // Feature Unit info
+ uint8_t feature_unit_id; // bUnitID of Feature Unit (0 = none)
+ uint8_t feature_unit_source_id; // bSourceID of Feature Unit
+
+ // Endpoint info
+ uint8_t ep_in;
+ uint8_t ep_out;
+ uint16_t ep_in_size;
+ uint16_t ep_out_size;
+
+ // Multi-AS support (per AS interface independent storage)
+ uint8_t as_count;
+ tuh_audio_as_info_t as_info[CFG_TUH_AUDIO_MAX_AS];
+} tuh_audio_mount_cb_t;
+
+//--------------------------------------------------------------------+
+// Application API
+//--------------------------------------------------------------------+
+
+// Check if Audio interface is mounted
+bool tuh_audio_mounted(uint8_t idx);
+
+// Get Interface index from device address + interface number
+// return TUSB_INDEX_INVALID_8 (0xFF) if not found
+uint8_t tuh_audio_itf_get_index(uint8_t daddr, uint8_t itf_num);
+
+// Get Interface information
+// return true if index is correct and interface is currently mounted
+bool tuh_audio_itf_get_info(uint8_t idx, tuh_itf_info_t *info);
+
+// Get number of AS interfaces for an audio device
+uint8_t tuh_audio_as_get_count(uint8_t idx);
+
+// Get AS interface info by index
+// as_idx: 0 to (as_count - 1)
+bool tuh_audio_as_get_info(uint8_t idx, uint8_t as_idx, tuh_audio_as_info_t *info);
+
+// Set Audio Streaming interface alternate setting (to enable/disable endpoints)
+bool tuh_audio_set_interface(uint8_t daddr, uint8_t itf_num, uint8_t alt_setting,
+ tuh_xfer_cb_t complete_cb, uintptr_t user_data);
+
+//--------------------------------------------------------------------+
+// Control Endpoint API
+//--------------------------------------------------------------------+
+
+// Set current sampling frequency on an isochronous endpoint (UAC 1.0)
+// Sampling frequency is 3 bytes little-endian
+bool tuh_audio_set_sampling_freq(uint8_t daddr, uint8_t ep_addr, uint32_t sampling_freq,
+ tuh_xfer_cb_t complete_cb, uintptr_t user_data);
+
+// Get current sampling frequency from an isochronous endpoint (UAC 1.0)
+bool tuh_audio_get_sampling_freq(uint8_t daddr, uint8_t ep_addr, uint32_t *sampling_freq,
+ tuh_xfer_cb_t complete_cb, uintptr_t user_data);
+
+// Set current/mute/volume etc. for a feature unit (UAC 1.0)
+bool tuh_audio_feature_unit_set(uint8_t daddr, uint8_t itf_num, uint8_t unit_id,
+ uint8_t control_selector, uint8_t channel,
+ uint16_t value, tuh_xfer_cb_t complete_cb, uintptr_t user_data);
+
+// Get current/mute/volume etc. from a feature unit (UAC 1.0)
+bool tuh_audio_feature_unit_get(uint8_t daddr, uint8_t itf_num, uint8_t unit_id,
+ uint8_t control_selector, uint8_t channel,
+ void *buffer, uint8_t len,
+ tuh_xfer_cb_t complete_cb, uintptr_t user_data);
+
+//--------------------------------------------------------------------+
+// Interrupt/Isochronous Endpoint API
+//--------------------------------------------------------------------+
+
+// Submit an isochronous transfer to receive audio data from IN endpoint
+bool tuh_audio_receive(uint8_t daddr, uint8_t idx, uint8_t *buffer, uint16_t len);
+
+// Submit an isochronous transfer to send audio data to OUT endpoint
+bool tuh_audio_send(uint8_t daddr, uint8_t idx, uint8_t *buffer, uint16_t len);
+
+//--------------------------------------------------------------------+
+// Callbacks (Weak is optional)
+//--------------------------------------------------------------------+
+
+// Invoked when Audio interface descriptor is detected during enumeration.
+// Application can copy/parse descriptor if needed.
+// Note: may be fired before tuh_audio_mount_cb(), therefore audio interface is not mounted/ready.
+void tuh_audio_descriptor_cb(uint8_t idx, const tuh_audio_descriptor_cb_t *desc_cb_data);
+
+// Invoked when device with Audio interface is mounted
+void tuh_audio_mount_cb(uint8_t idx, const tuh_audio_mount_cb_t *mount_cb_data);
+
+// Invoked when device with Audio interface is un-mounted
+void tuh_audio_umount_cb(uint8_t idx);
+
+// Invoked when an isochronous IN transfer is complete
+void tuh_audio_rx_cb(uint8_t idx, uint8_t ep_addr, uint16_t xferred_bytes);
+
+// Invoked when an isochronous OUT transfer is complete
+void tuh_audio_tx_cb(uint8_t idx, uint8_t ep_addr, uint16_t xferred_bytes);
+
+//--------------------------------------------------------------------+
+// Internal Class Driver API
+//--------------------------------------------------------------------+
+bool audioh_init(void);
+bool audioh_deinit(void);
+uint16_t audioh_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_interface_t *desc_itf, uint16_t max_len);
+bool audioh_set_config(uint8_t dev_addr, uint8_t itf_num);
+bool audioh_xfer_cb(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes);
+void audioh_close(uint8_t daddr);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* TUSB_AUDIO_HOST_H_ */
diff --git a/src/host/usbh.c b/src/host/usbh.c
index e307bb5e5..6e16c0b72 100644
--- a/src/host/usbh.c
+++ b/src/host/usbh.c
@@ -258,6 +258,18 @@ static usbh_class_driver_t const usbh_class_drivers[] = {
},
#endif
+ #if CFG_TUH_AUDIO
+ {
+ .name = DRIVER_NAME("AUDIO"),
+ .init = audioh_init,
+ .deinit = audioh_deinit,
+ .open = audioh_open,
+ .set_config = audioh_set_config,
+ .xfer_cb = audioh_xfer_cb,
+ .close = audioh_close
+ },
+ #endif
+
#if CFG_TUH_HID
{
.name = DRIVER_NAME("HID"),
diff --git a/src/tinyusb.mk b/src/tinyusb.mk
index 365043927..ab8549bc8 100644
--- a/src/tinyusb.mk
+++ b/src/tinyusb.mk
@@ -19,6 +19,7 @@ TINYUSB_SRC_C += \
src/class/usbtmc/usbtmc_device.c \
src/class/video/video_device.c \
src/class/vendor/vendor_device.c \
+ src/class/audio/audio_host.c \
src/host/usbh.c \
src/host/hub.c \
src/class/cdc/cdc_host.c \
diff --git a/src/tusb.h b/src/tusb.h
index 6a30f7c13..219951692 100644
--- a/src/tusb.h
+++ b/src/tusb.h
@@ -28,6 +28,10 @@
#if CFG_TUH_ENABLED
#include "host/usbh.h"
+ #if CFG_TUH_AUDIO
+ #include "class/audio/audio_host.h"
+ #endif
+
#if CFG_TUH_HID
#include "class/hid/hid_host.h"
#endif
diff --git a/src/tusb_option.h b/src/tusb_option.h
index e19ee1629..7a636657f 100644
--- a/src/tusb_option.h
+++ b/src/tusb_option.h
@@ -807,6 +807,10 @@
{ 0x067b, 0x23f3 } /* GS */
#endif
+#ifndef CFG_TUH_AUDIO
+ #define CFG_TUH_AUDIO 0
+#endif
+
#ifndef CFG_TUH_HID
#define CFG_TUH_HID 0
#endif