summaryrefslogtreecommitdiff
path: root/examples/host
diff options
context:
space:
mode:
authorHiFiPHile <[email protected]>2026-08-25 09:29:08 +0200
committerHiFiPHile <[email protected]>2026-08-25 09:29:08 +0200
commitb45f1292dd468a542197c48c3f87b471dd64a3ef (patch)
tree2e5e815476a8263fb1c3c51d24760a561db8f880 /examples/host
parentd494b7c5f1fb690ea559dd3f09bb9f84c0c6d80f (diff)
feat(audio): discover stream mute and volume controls
Inspect each associated Feature Unit during mount, cache master mute support and the common MIN, MAX, and RES volume range, and ignore units with neither control. Add typed asynchronous and synchronous mute and volume APIs and demonstrate them in the host example. Signed-off-by: HiFiPHile <[email protected]>
Diffstat (limited to 'examples/host')
-rw-r--r--examples/host/audio_host/README.md20
-rw-r--r--examples/host/audio_host/src/audio_app.c73
2 files changed, 61 insertions, 32 deletions
diff --git a/examples/host/audio_host/README.md b/examples/host/audio_host/README.md
index bbb3a7684..5413895a4 100644
--- a/examples/host/audio_host/README.md
+++ b/examples/host/audio_host/README.md
@@ -6,7 +6,7 @@ This example demonstrates how to use TinyUSB's USB Audio Host driver (TUH_AUDIO)
- Enumerates and mounts USB Audio Class 1.0 devices
- Discovers the device's logical streams (capture/playback) and their supported configurations (discrete tuples only)
-- Reports the Feature Unit ID associated with each stream and sets the master volume when one is available
+- Reports each stream's master mute/volume capabilities and cached volume range
- Configures and starts an S16_LE capture stream (48 kHz preferred, 44.1 kHz fallback; stereo preferred, mono accepted)
- Echoes captured audio to an S16_LE playback stream at the same sample rate (same channel count preferred, mono/stereo conversion otherwise)
- Frame-based FIFO API: `tuh_audio_read()` / `tuh_audio_write()` queue frames; the driver schedules transfers at the endpoint's polling interval
@@ -26,7 +26,7 @@ The echo needs a matching S16_LE playback stream at the capture sample rate; dev
- Explicit feedback endpoint data is ignored. Asynchronous playback still uses the nominal sample rate, but device/host clock drift is not corrected and may cause underruns, overruns, or audible pops and clicks. An implicit-feedback IN endpoint is treated as an ordinary audio-data endpoint and is not used to pace playback.
- UAC1 Type I Format descriptors with `bSamFreqType == 0` are unsupported; the driver requires a list of discrete sampling frequencies.
-- Feature Unit mute, bass, mid, treble, volume, delay, AGC, bass boost, and loudness controls are supported. Graphic EQ and unknown control widths are rejected, and only one Feature Unit request may be in flight per device.
+- Master mute and volume controls are discovered before the mount callback, including the volume MIN/MAX/RES range. Feature Units without master mute or volume are ignored. The typed API controls the master channel; the lower-level Feature Unit API remains available for fixed-width UAC1 controls on the associated unit.
- The UAC1 `MaxPacketsOnly` endpoint attribute is not supported. OUT transfers are not padded to `wMaxPacketSize`, and padding in IN transfers is not removed from the reported audio data.
## Building
@@ -65,10 +65,10 @@ make BOARD=<your_board> flash
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 each stream's Feature Unit ID and supported configurations when mounted
+ - Print each stream's Feature Unit ID, master mute/volume capabilities, cached volume range, and supported configurations when mounted
- Look for an S16_LE capture configuration at a preferred sample rate (48 kHz first, 44.1 kHz fallback; stereo preferred, mono accepted) and configure it
- Echo captured audio to an S16_LE playback configuration at the same sample rate (same channel count preferred, converted otherwise)
- - Set the microphone and speaker master volume to `0x0600` when their streams have a Feature Unit
+ - Read/unmute the microphone and speaker Feature Units and set supported master volumes near -6 dB
- Drain the capture FIFO in `audio_app_task_read()` and queue the frames into the playback FIFO; a sine test tone plays on the playback stream when no capture stream is echoing
- Cycle through the three phases (mic-only / spk-only / echo, 5 s each) with `tuh_audio_start()` / `tuh_audio_stop()`; a failed stream is restarted automatically 100 ms after the error callback
@@ -79,17 +79,25 @@ TinyUSB Host USB Audio Example
Connect a USB Audio Device (UAC 1.0) to test
Audio device mounted: idx=0 addr=1
capture stream 1 Feature Unit ID: 5, configurations: 2
+ master mute supported
+ master volume range: min=-23040 max=1536 res=256 (1/256 dB)
[0] format=1 rate=44100 channels=2
[1] format=1 rate=48000 channels=2
playback stream 0 Feature Unit ID: 2, configurations: 2
+ master mute supported
+ master volume range: min=-23040 max=1536 res=256 (1/256 dB)
[0] format=1 rate=44100 channels=2
[1] format=1 rate=48000 channels=2
Configuring 48 kHz S16_LE capture (2 channels)
Microphone configured
- Microphone Feature Unit 5 master volume set: 0x0600
+ Microphone Feature Unit 5 master mute: off
+ Microphone Feature Unit 5 master volume: 0 (1/256 dB)
+ Microphone master volume set: -1536 (1/256 dB)
Configuring 48 kHz S16_LE playback (2 channels)
Speaker configured
- Speaker Feature Unit 2 master volume set: 0x0600
+ Speaker Feature Unit 2 master mute: off
+ Speaker Feature Unit 2 master volume: 0 (1/256 dB)
+ Speaker master volume set: -1536 (1/256 dB)
```
## Configuration
diff --git a/examples/host/audio_host/src/audio_app.c b/examples/host/audio_host/src/audio_app.c
index 3102c28c9..9a1434284 100644
--- a/examples/host/audio_host/src/audio_app.c
+++ b/examples/host/audio_host/src/audio_app.c
@@ -28,11 +28,10 @@
// - AUDIO_MAX_FRAME_COUNT: buffer holds up to 48 frames (1 ms of 48 kHz)
// - AUDIO_MAX_CHANNELS: maximum channels of the capture/playback stream
// - SAMPLE_RATES: sample rates tried in order, first match wins (44.1 kHz stereo by default)
-#define AUDIO_MAX_FRAME_COUNT 48
-#define AUDIO_MAX_CHANNELS 2
-#define SAMPLE_RATES {48000, 44100}
-// UAC1 volume values are signed 1/256 dB; 0x0600 selects +6 dB.
-#define FEATURE_UNIT_VOLUME 0x0600
+#define AUDIO_MAX_FRAME_COUNT 48
+#define AUDIO_MAX_CHANNELS 2
+#define SAMPLE_RATES {48000, 44100}
+#define FEATURE_UNIT_VOLUME_DB (-6 * 256)
static uint8_t audio_idx = TUSB_INDEX_INVALID_8; // index of the selected audio device
static uint8_t cap_stream_idx = TUSB_INDEX_INVALID_8; // capture stream index
static uint8_t spk_stream_idx = TUSB_INDEX_INVALID_8; // playback stream index
@@ -255,17 +254,6 @@ void led_blinking_task(void) {
err_cb_count = 0;
#endif
-#if 0
- // Print the current Feature Unit volume, which is set to 0x0600 in mic_configured() and can be changed by the device.
- uint16_t volume = 0x0001;
- tuh_audio_feature_unit_get_sync(audio_idx, cap_stream_idx, AUDIO10_FU_CTRL_VOLUME, 0, &volume);
- printf(" Feature Unit volume get: 0x%04x\r\n", (unsigned int)volume);
- uint16_t mute = 0x0000;
- tuh_audio_feature_unit_get_sync(audio_idx, cap_stream_idx, AUDIO10_FU_CTRL_MUTE, 0, &mute);
- mute=!mute; // toggle mute for demonstration
- tuh_audio_feature_unit_set_sync(audio_idx, cap_stream_idx, AUDIO10_FU_CTRL_MUTE, 0, mute);
- printf(" Feature Unit mute set: 0x%04x\r\n", (unsigned int)mute);
-#endif
}
//--------------------------------------------------------------------+
@@ -377,6 +365,14 @@ static void print_stream_configs(uint8_t idx, uint8_t stream_idx) {
const uint8_t feature_unit_id = tuh_audio_get_feature_unit_id(idx, stream_idx);
printf(" %s stream %u Feature Unit ID: %u, configurations: %u\r\n", dir_name, stream_idx, feature_unit_id,
tuh_audio_config_count(idx, stream_idx));
+ tuh_audio_volume_range_t range;
+ if (tuh_audio_mute_supported(idx, stream_idx)) {
+ printf(" master mute supported\r\n");
+ }
+ if (tuh_audio_volume_range_get(idx, stream_idx, &range)) {
+ printf(" master volume range: min=%d max=%d res=%u (1/256 dB)\r\n", (int)range.min, (int)range.max,
+ (unsigned)range.res);
+ }
for (uint8_t i = 0; i < tuh_audio_config_count(idx, stream_idx); i++) {
tuh_audio_stream_config_t config;
if (tuh_audio_config_get(idx, stream_idx, i, &config)) {
@@ -386,19 +382,44 @@ static void print_stream_configs(uint8_t idx, uint8_t stream_idx) {
}
}
-static void set_stream_volume(uint8_t idx, uint8_t stream_idx, const char *stream_name) {
+static void configure_stream_controls(uint8_t idx, uint8_t stream_idx, const char *stream_name) {
const uint8_t feature_unit_id = tuh_audio_get_feature_unit_id(idx, stream_idx);
if (feature_unit_id == 0) {
- printf(" %s stream has no Feature Unit\r\n", stream_name);
+ printf(" %s stream has no master mute/volume Feature Unit\r\n", stream_name);
return;
}
- uint16_t volume = FEATURE_UNIT_VOLUME;
- tusb_xfer_result_t result = tuh_audio_feature_unit_set_sync(idx, stream_idx, AUDIO10_FU_CTRL_VOLUME, 0, volume);
- if (result == XFER_RESULT_SUCCESS) {
- printf(" %s Feature Unit %u master volume set: 0x%04x\r\n", stream_name, feature_unit_id, (unsigned int)volume);
- } else {
- printf(" Setting %s Feature Unit %u volume failed: result=%u\r\n", stream_name, feature_unit_id, result);
+ if (tuh_audio_mute_supported(idx, stream_idx)) {
+ bool mute;
+ tusb_xfer_result_t result = tuh_audio_mute_get_sync(idx, stream_idx, &mute);
+ if (result == XFER_RESULT_SUCCESS) {
+ printf(" %s Feature Unit %u master mute: %s\r\n", stream_name, feature_unit_id, mute ? "on" : "off");
+ result = tuh_audio_mute_set_sync(idx, stream_idx, false);
+ }
+ if (result != XFER_RESULT_SUCCESS) {
+ printf(" Accessing %s master mute failed: result=%u\r\n", stream_name, result);
+ }
+ }
+
+ tuh_audio_volume_range_t range;
+ if (tuh_audio_volume_range_get(idx, stream_idx, &range)) {
+ int16_t volume;
+ tusb_xfer_result_t result = tuh_audio_volume_get_sync(idx, stream_idx, &volume);
+ if (result == XFER_RESULT_SUCCESS) {
+ printf(" %s Feature Unit %u master volume: %d (1/256 dB)\r\n", stream_name, feature_unit_id, volume);
+ int32_t target = FEATURE_UNIT_VOLUME_DB;
+ target = TU_MAX(target, range.min);
+ target = TU_MIN(target, range.max);
+ target = range.min + ((target - range.min + range.res / 2) / range.res) * range.res;
+ target = TU_MIN(target, range.max);
+ result = tuh_audio_volume_set_sync(idx, stream_idx, (int16_t)target);
+ if (result == XFER_RESULT_SUCCESS) {
+ printf(" %s master volume set: %d (1/256 dB)\r\n", stream_name, (int)target);
+ }
+ }
+ if (result != XFER_RESULT_SUCCESS) {
+ printf(" Accessing %s master volume failed: result=%u\r\n", stream_name, result);
+ }
}
}
@@ -462,7 +483,7 @@ static void tuh_audio_mount_async(uintptr_t param) {
// one ms of audio at the selected rate, rounded down to whole frames
audio_frame_count = sample_rate / 1000;
printf(" Microphone configured\r\n");
- set_stream_volume(idx, stream_idx, "Microphone");
+ configure_stream_controls(idx, stream_idx, "Microphone");
mic_ready = tuh_audio_start(idx, stream_idx);
capture_found = true;
break;
@@ -519,7 +540,7 @@ static void tuh_audio_mount_async(uintptr_t param) {
// playback-only device: set the frame cadence from the selected rate
audio_frame_count = spk_config.sample_rate / 1000;
sine_phase = 0;
- set_stream_volume(idx, spk_stream_idx, "Speaker");
+ configure_stream_controls(idx, spk_stream_idx, "Speaker");
if (capture_found) {
// Start in the mic-only phase without briefly activating playback first.