Skip to content

teensy4: add capture-side USB Audio Feature Unit (mute/volume) for recording devices#799

Open
JayShoe wants to merge 1 commit intoPaulStoffregen:masterfrom
JayShoe:teensy4-usb-audio-capture-feature-unit
Open

teensy4: add capture-side USB Audio Feature Unit (mute/volume) for recording devices#799
JayShoe wants to merge 1 commit intoPaulStoffregen:masterfrom
JayShoe:teensy4-usb-audio-capture-feature-unit

Conversation

@JayShoe
Copy link
Copy Markdown

@JayShoe JayShoe commented Apr 13, 2026

Summary

Adds a USB Audio Class 1.0 Feature Unit on the device-to-host (capture/recording) path so the host OS can control mute and volume on audio the Teensy sends. Previously the AudioControl descriptor only had a Feature Unit on the playback path (FU 0x31), so the host never exposed a recording-device volume slider — the Microphone level control in Windows Sound Settings had nothing to bind to.

Topology after the patch:

capture:   IT1 → FU 0x30 → OT2    ← new
playback:  IT3 → FU 0x31 → OT4    ← unchanged

Changes

usb_desc.c

  • Insert a 10-byte FEATURE_UNIT descriptor block (ID 0x30: master mute + L/R volume) between Input Terminal 1 and Output Terminal 2 on the capture path
  • Retarget OT2 bCSourceID from IT1 (0x01) to FU 0x30
  • Bump AC header wTotalLength from 62 to 72
  • Bump AUDIO_INTERFACE_DESC_SIZE to account for the new block
  • Both high-speed (480 Mbps) and full-speed (12 Mbps) descriptor copies are updated

usb_audio.h

  • Add static struct usb_audio_features_struct features to AudioOutputUSB, mirroring the existing AudioInputUSB::features
  • Add volume() and mute() accessors to AudioOutputUSB
  • Add mute() accessor to AudioInputUSB for parity (the data was already there — volume() reads features.mute internally but there was no standalone accessor)
  • Add friend declarations for usb_audio_set_feature/usb_audio_get_feature to AudioOutputUSB

usb_audio.cpp

  • Define AudioOutputUSB::features (initialized to same defaults as AudioInputUSB::features)
  • Add usb_audio_features_for_entity() helper that routes by the Entity ID in the wIndex high byte:
    • FU 0x31 → AudioInputUSB::features (playback, unchanged behavior)
    • FU 0x30 → AudioOutputUSB::features (capture, new)
  • Replace hardcoded AudioInputUSB::features references in usb_audio_set_feature() and usb_audio_get_feature() with the entity-routed pointer

Usage

// Playback volume (existing PJRC API — host speakers/output slider):
float playVol = usbIn.volume();   // 0.0–1.0, returns 0.0 when muted

// Capture volume (new — host recording/microphone slider):
float capVol  = usbOut.volume();  // 0.0–1.0, returns 0.0 when muted
bool  capMute = usbOut.mute();

// Or via the struct for separate volume/mute tracking:
int rawVol  = AudioOutputUSB::features.volume;  // 0 to 255
int rawMute = AudioOutputUSB::features.mute;    // 0 or 1

Notes

  • No changes to usb.c — the existing word1 match in the control-endpoint dispatcher deliberately ignores wIndex, so SET_CUR requests to either Feature Unit already fall through into usb_audio_set_feature(), which now disambiguates by entity ID.
  • The core does not auto-apply capture volume to the transmit path. The sketch decides how to use the value (same pattern as the existing playback-side AudioInputUSB::volume()).
  • bcdDevice is not bumped (it's used for board identification by teensy_ports). Windows re-enumerates on descriptor-length change; if it caches stale topology, unplug/replug clears it.
  • Tested on Teensy 4.1, Windows 11 — recording-device slider in Sound Settings correctly drives AudioOutputUSB::features.

…cording devices

Add a USB Audio Class 1.0 Feature Unit (ID 0x30) on the device-to-host
capture path so the host OS can control mute and volume on audio the
Teensy sends. Previously the AudioControl descriptor only had a Feature
Unit on the playback path (FU 0x31), so the host never exposed a
recording-device volume slider.

Topology after the patch:

  capture:   IT1 -> FU 0x30 -> OT2    <- new
  playback:  IT3 -> FU 0x31 -> OT4    <- unchanged

usb_desc.c:
- Insert 10-byte FEATURE_UNIT descriptor (ID 0x30: master mute + L/R
  volume) between IT1 and OT2 on the capture path
- Retarget OT2 bCSourceID from IT1 (0x01) to FU 0x30
- Bump AC header wTotalLength from 62 to 72
- Both high-speed and full-speed descriptor copies updated

usb_audio.h:
- Add AudioOutputUSB::features (mirrors AudioInputUSB::features)
- Add volume()/mute() accessors to AudioOutputUSB
- Add mute() accessor to AudioInputUSB for parity
- Add friend declarations for set/get_feature to AudioOutputUSB

usb_audio.cpp:
- Define AudioOutputUSB::features
- Add usb_audio_features_for_entity() to route by Entity ID:
  FU 0x31 -> AudioInputUSB::features (playback, unchanged)
  FU 0x30 -> AudioOutputUSB::features (capture, new)
- Replace hardcoded AudioInputUSB::features in set/get_feature
  with entity-routed pointer

The core does not auto-apply capture volume to the transmit path; the
sketch decides how to use the value (same pattern as the existing
AudioInputUSB::volume()). No changes to usb.c required.

Tested on Teensy 4.1, Windows 11.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant