arduino-audio-tools
Loading...
Searching...
No Matches
USBAudioDeviceBase.h
Go to the documentation of this file.
1#pragma once
2#include <cmath>
3#include <cstddef>
4#include <cstdint>
5#include <cstring>
6#include <functional>
7#include <mutex>
8#include <vector>
9
12
13#ifdef ESP32
14#ifndef ARDUINO_USB_MODE
15#error This Microcontroller has no Native USB interface
16#else
17#if ARDUINO_USB_MODE == 1
18#error This sketch should be used when USB is in OTG mode
19#endif
20#endif
21#else
22#if !defined(USE_TINYUSB) && !defined(ARDUINO_ARCH_STM32) && !defined(IS_ZEPHYR)
23#error This Microcontroller has no Native USB interface
24#endif
25#endif
26
27#include "AudioLogger.h"
30
31extern "C" {
32#include "device/usbd.h"
33#include "device/usbd_pvt.h"
34#include "tusb.h"
35}
36
37#define USB_DESCR_MAX_LEN 512
38
39// TinyUSB >= 0.15 (ESP32 IDF v5+) renamed UAC2 symbols with an AUDIO20_ prefix
40// and changed three function signatures. These shims let the driver build
41// against both versions; detect the new API by the presence of the new define.
42#ifdef TUD_AUDIO20_DESC_IAD_LEN
43#define TUD_AUDIO_DESC_IAD_LEN TUD_AUDIO20_DESC_IAD_LEN
44#define AUDIO_CS_AC_INTERFACE_INPUT_TERMINAL \
45 AUDIO20_CS_AC_INTERFACE_INPUT_TERMINAL
46#define AUDIO_CS_AC_INTERFACE_OUTPUT_TERMINAL \
47 AUDIO20_CS_AC_INTERFACE_OUTPUT_TERMINAL
48#define AUDIO_CS_CTRL_SAM_FREQ AUDIO20_CS_CTRL_SAM_FREQ
49#define AUDIO_CS_CTRL_CLK_VALID AUDIO20_CS_CTRL_CLK_VALID
50#define AUDIO_CS_REQ_CUR AUDIO20_CS_REQ_CUR
51#define AUDIO_CS_REQ_RANGE AUDIO20_CS_REQ_RANGE
52#define AUDIO_CS_AS_INTERFACE_AS_GENERAL AUDIO20_CS_AS_INTERFACE_AS_GENERAL
53#define AUDIO_CS_AS_INTERFACE_FORMAT_TYPE AUDIO20_CS_AS_INTERFACE_FORMAT_TYPE
54#define audio_desc_cs_ac_interface_t audio20_desc_cs_ac_interface_t
55#define audio_desc_cs_as_interface_t audio20_desc_cs_as_interface_t
56#define audio_desc_type_I_format_t audio20_desc_type_I_format_t
57// New: is_isr parameter added; fifo element-size arg removed
58#define TUSB_EDPT_XFER(rp, ep, buf, sz) usbd_edpt_xfer(rp, ep, buf, sz, false)
59#define TUSB_EDPT_XFER_FIFO(rp, ep, ff, sz) \
60 usbd_edpt_xfer_fifo(rp, ep, ff, sz, false)
61#define TUSB_FIFO_CONFIG(f, buf, d, ov) tu_fifo_config(f, buf, d, ov)
62#else
63// Old TinyUSB (RP2040 / Adafruit bundle)
64#define TUSB_EDPT_XFER(rp, ep, buf, sz) usbd_edpt_xfer(rp, ep, buf, sz)
65#define TUSB_EDPT_XFER_FIFO(rp, ep, ff, sz) usbd_edpt_xfer_fifo(rp, ep, ff, sz)
66#define TUSB_FIFO_CONFIG(f, buf, d, ov) tu_fifo_config(f, buf, d, 1, ov)
67// Ensure control selector and request constants are available on old TinyUSB
68// too
69#ifndef AUDIO10_CS_AC_INTERFACE_INPUT_TERMINAL
70#define AUDIO10_CS_AC_INTERFACE_INPUT_TERMINAL \
71 AUDIO_CS_AC_INTERFACE_INPUT_TERMINAL
72#endif
73#ifndef AUDIO_CS_CTRL_CLK_VALID
74#define AUDIO_CS_CTRL_CLK_VALID 0x02u
75#endif
76#ifndef AUDIO_CS_REQ_RANGE
77#define AUDIO_CS_REQ_RANGE 0x02u
78#endif
79#endif
80
81// Feature Unit control selectors (UAC2 Table A-23)
82#ifndef AUDIO_FU_CTRL_MUTE
83#define AUDIO_FU_CTRL_MUTE 0x01u
84#endif
85#ifndef AUDIO_FU_CTRL_VOLUME
86#define AUDIO_FU_CTRL_VOLUME 0x02u
87#endif
88
89namespace audio_tools {
90
91// Discrete sample rates supported by the UAC2 clock source.
92static constexpr uint32_t kSupportedSampleRates[] = {
93 8000, 11025, 16000, 22050, 24000, 32000, 44100, 48000};
94static constexpr uint8_t kNumSupportedSampleRates =
96
117 enum audio_format_type_t {
118 AUDIO_FORMAT_TYPE_I,
119 AUDIO_FORMAT_TYPE_II,
120 AUDIO_FORMAT_TYPE_III,
121 };
122
129 enum audio_feedback_method_t {
130 AUDIO_FEEDBACK_METHOD_DISABLED,
131 AUDIO_FEEDBACK_METHOD_FREQUENCY_FIXED,
132 AUDIO_FEEDBACK_METHOD_FREQUENCY_FLOAT,
133 AUDIO_FEEDBACK_METHOD_FREQUENCY_POWER_OF_2, // For driver internal use only
134 AUDIO_FEEDBACK_METHOD_FIFO_COUNT
135 };
136
143 struct audiod_function_t {
144 uint8_t rhport;
145 uint8_t const* p_desc; // Pointer to Standard AC Interface Descriptor
146 uint8_t ep_in; // TX audio data EP.
147 uint16_t ep_in_sz; // Current size of TX EP
148 uint8_t
149 ep_in_as_intf_num; // Standard AS Interface Descriptor number for IN
150 uint8_t ep_out; // RX audio data EP.
151 uint16_t ep_out_sz; // Current size of RX EP
152 uint8_t
153 ep_out_as_intf_num; // Standard AS Interface Descriptor number for OUT
154 uint8_t ep_fb; // Feedback EP.
155 uint8_t ep_int; // Audio control interrupt EP.
156 bool mounted; // Device opened
157 uint16_t desc_length; // Length of audio function descriptor
158 struct {
159 uint32_t value;
160 uint32_t min_value;
161 uint32_t max_value;
162 uint8_t frame_shift;
163 uint8_t compute_method;
164 bool format_correction;
165 union {
166 uint8_t power_of_2;
167 float float_const;
168 struct {
169 uint32_t sample_freq;
170 uint32_t mclk_freq;
171 } fixed;
172 struct {
173 uint32_t nom_value;
174 uint32_t fifo_lvl_avg;
175 uint16_t fifo_lvl_thr;
176 uint16_t rate_const[2];
177 } fifo_count;
178 } compute;
179 } feedback;
180 uint32_t sample_rate_tx;
181 uint16_t packet_sz_tx[3];
182 uint8_t bclock_id_tx;
183 uint8_t interval_tx;
184 audio_format_type_t format_type_tx;
185 uint8_t n_channels_tx;
186 uint8_t n_bytes_per_sample_tx;
187 // Fractional sample accumulator for IN-endpoint flow control. Carries the
188 // sub-frame remainder (e.g. the 0.1 sample/frame of 44100 Hz) so the
189 // long-term average packet size matches the real sample rate.
190 uint32_t tx_sample_acc;
191 // From this point, data is not cleared by bus reset
192 uint8_t ctrl_buf_sz;
193 tu_fifo_t ep_out_ff;
194 tu_fifo_t ep_in_ff;
195 std::vector<uint8_t> ctrl_buf;
196 std::vector<uint8_t> alt_setting;
197 std::vector<uint8_t> lin_buf_out;
198 std::vector<uint8_t> lin_buf_in;
199 std::vector<uint32_t> fb_buf;
200 std::vector<uint8_t> ep_in_sw_buf; // For feedback EP
201 std::vector<uint8_t> ep_out_sw_buf; // For feedback EP
202 };
203
210 struct audio_feedback_params_t {
211 uint8_t method;
212 uint32_t sample_freq; // sample frequency in Hz
213
214 union {
215 struct {
216 uint32_t mclk_freq; // Main clock frequency in Hz i.e. master clock to
217 // which sample clock is based on
218 } frequency;
219 };
220 };
221
222 public:
225
230
237 USBAudioConfig cfg;
238 switch (mode) {
239 case RX_MODE:
240 cfg.enable_ep_out = true;
241 cfg.enable_ep_in = false;
242 break;
243 case TX_MODE:
244 cfg.enable_ep_out = false;
245 cfg.enable_ep_in = true;
246 break;
247 case RXTX_MODE:
248 cfg.enable_ep_out = true;
249 cfg.enable_ep_in = true;
250 break;
251 default:
252 break;
253 }
254 return cfg;
255 }
256
258 void setAudioInfo(AudioInfo info) override {
260 LOGE("Unsupported bits_per_sample: %d (must be 16, 24, or 32)",
262 return;
263 }
264 // full flexibility when not started yet
265 if (!is_started_) {
269 return;
270 }
271
272 // when started only sample rate can be changed on the fly
274 if (config_.channels != info.channels) {
275 LOGE(
276 "Could not change channel count from %d to %d: channel count is "
277 "fixed at startup!",
279 }
281 LOGE(
282 "Could not change bits per sample from %d to %d: bits per sample is "
283 "fixed at startup!",
285 }
286 // notifiy subscribed entities
288 // notify host about sample rate change via control request callback;
290 }
291
300 bool begin(const USBAudioConfig& cfg) {
301 if (!is_started_) {
302 config_ = cfg;
303 } else if (!configChanged(cfg)) {
304 return true; // already running with same config
305 } else {
307 }
308 return begin();
309 }
310
317 bool begin() {
318 if (!is_started_) {
320 LOGE("Unsupported bits_per_sample: %d (must be 16, 24, or 32)",
322 return false;
323 }
324
325 // Resize platform buffers — virtual so each platform uses the right API.
327
328 const int n = getAudioCount();
329 // 192 bytes needed for multi-rate RANGE (2+14*12=170); 64 suffices for single rate
330 uint16_t cb_sz = config_.enable_multi_sample_rate ? 192u : 64u;
331 ctrl_buf_sz_.assign(n, cb_sz);
332
333 const uint16_t sw_buf = fifoSize();
334 ep_in_sw_buf_sz_.assign(n, sw_buf);
335 ep_out_sw_buf_sz_.assign(n, sw_buf);
336
337 uint8_t desc[USB_DESCR_MAX_LEN];
338 uint16_t desc_len = descr_builder.buildFullDescriptor(desc);
339 desc_len_.assign(n, desc_len);
340
341 // master (index 0) + one per channel
342 const size_t vol_sz = (size_t)config_.channels + 1;
343 volume_.assign(vol_sz, 1.0f);
344 mute_.assign(vol_sz, false);
345
346
347 audiod_init();
348
349 if (!beginUSB()) {
350 LOGE("beginUSB failed");
351 return false;
352 }
353 is_started_ = true;
354 }
355
356 // Push current state to the host. sendInterruptNotification()
357 // is a no-op when not yet mounted, so this is harmless on first boot.
359 for (uint8_t ch = 0; ch < (uint8_t)volume_.size(); ch++) {
360 setVolume(volume(ch), ch);
361 setMute(isMute(ch), ch);
362 }
363 is_active_ = true;
364 return true;
365 }
373
375 inline bool isEpInEnabled() const { return config_.enable_ep_in; }
376
378 inline bool isEpOutEnabled() const { return config_.enable_ep_out; }
379
384
391
392 // ── Volume / Mute / Sample-rate API ─────────────────────────────────────
393
395 float volume() override { return volume(0); }
396
398 bool setVolume(float volume) override { return setVolume(volume, 0); }
399
403 float volume(uint8_t channel) {
404 return (channel < volume_.size()) ? volume_[channel] : 0.0f;
405 }
406
411 bool setVolume(float vol, uint8_t channel) {
412 LOGW("setVolume %f channel: %d", vol, channel);
413 if (channel >= volume_.size()) return false;
414 volume_[channel] = vol;
415 if (volume_cb_) volume_cb_(vol, channel);
418 return true;
419 }
420
423 bool isMute(uint8_t channel = 0) const {
424 return (channel < mute_.size()) ? mute_[channel] : false;
425 }
426
431 bool setMute(bool m, uint8_t channel = 0) {
432 LOGW("setMute %s channel: %d", m ? "true" : "false", channel);
433 if (channel >= mute_.size()) return false;
434 mute_[channel] = m;
435 if (mute_cb_) mute_cb_(m, channel);
438 return true;
439 }
440
444 void setVolumeCallback(std::function<void(float, uint8_t)> cb) {
445 volume_cb_ = std::move(cb);
446 }
447
451 void setMuteCallback(std::function<void(bool, uint8_t)> cb) {
452 mute_cb_ = std::move(cb);
453 }
454
458 void setSampleRateCallback(std::function<void(uint32_t)> cb) {
459 sample_rate_cb_ = std::move(cb);
460 }
461
466 void setStreamingStateCallback(std::function<void(bool, bool)> cb) {
467 streaming_state_cb_ = std::move(cb);
468 }
469
471 bool isStreamingActive() const {
473 }
474
476 bool isStreamingActiveTx() const {
477 for (const auto& fct : audiod_fct_) {
478 if (fct.ep_in != 0) return true;
479 }
480 return false;
481 }
482
484 bool isStreamingActiveRx() const {
485 for (const auto& fct : audiod_fct_) {
486 if (fct.ep_out != 0) return true;
487 }
488 return false;
489 }
492
494 bool isFifoMutexEnabled() const { return true; }
495
497 uint8_t getAudioCount() const { return 1; }
498
500 bool mounted() const { return tud_mounted(); }
501
506 void setGetReqItfCallback(std::function<bool(USBAudioDeviceBase*, uint8_t,
507 tusb_control_request_t const*)>
508 cb) {
509 get_req_itf_cb_ = cb;
510 }
511
516 void setGetReqEpCallback(std::function<bool(USBAudioDeviceBase*, uint8_t,
517 tusb_control_request_t const*)>
518 cb) {
519 get_req_ep_cb_ = cb;
520 }
521
526 void setFbDoneCallback(std::function<void(USBAudioDeviceBase*, uint8_t)> cb) {
527 fb_done_cb_ = cb;
528 }
529
535 std::function<void(USBAudioDeviceBase*, uint8_t)> cb) {
536 int_done_cb_ = cb;
537 }
538
544 std::function<bool(USBAudioDeviceBase*, uint8_t, audiod_function_t*)>
545 cb) {
546 tx_done_cb_ = cb;
547 }
548
553 void setRxDoneCallback(std::function<bool(USBAudioDeviceBase*, uint8_t,
554 audiod_function_t*, uint16_t)>
555 cb) {
556 rx_done_cb_ = cb;
557 }
558
564 std::function<bool(USBAudioDeviceBase*, uint8_t)> cb) {
565 req_entity_cb_ = cb;
566 }
567
573 std::function<bool(USBAudioDeviceBase*, uint8_t,
574 tusb_control_request_t const*)>
575 cb) {
577 }
578
584 std::function<bool(USBAudioDeviceBase*, uint8_t,
585 tusb_control_request_t const*, uint8_t*)>
586 cb) {
588 }
589
595 std::function<bool(USBAudioDeviceBase*, uint8_t,
596 tusb_control_request_t const*, uint8_t*)>
597 cb) {
599 }
600
606 std::function<bool(USBAudioDeviceBase*, uint8_t,
607 tusb_control_request_t const*, uint8_t*)>
608 cb) {
610 }
611
616 void setItfCloseEpCallback(std::function<bool(USBAudioDeviceBase*, uint8_t,
617 tusb_control_request_t const*)>
618 cb) {
620 }
621
627 std::function<void(USBAudioDeviceBase*, uint8_t, uint8_t,
628 audio_feedback_params_t*)>
629 cb) {
631 }
632
641
645 size_t write(const uint8_t* data, size_t len) {
646 if (!is_started_) return 0;
648
649 // disregard data if the host has not opened the capture device (alt=0)
650 if (!isStreamingActiveTx()) return len;
651
652 // update the volume
653 if (config_.volume_active) processVolume((uint8_t*)data, len);
654
655 // Write all data, retrying if the buffer is full. On single-core
656 // platforms (RP2040), serviceTinyUSB() drains the buffer by running
657 // tud_task() → xfer_cb. On dual-core (ESP32), the USB task drains
658 // independently and the SynchronizedNBufferRTOS blocks internally.
659 size_t written = 0;
660 while (written < len) {
661 int n = bufferTx().writeArray(data + written, len - written);
662 written += n;
663 if (written < len) {
664 serviceTinyUSB(); // drain buffer to make space
665 if (!isStreamingActiveTx()) break; // host stopped
666 }
667 }
668 return written;
669 }
670
673 size_t readBytes(uint8_t* buffer, size_t bufsize) {
674 if (!is_started_) return 0;
676 // get the data from the buffer
677 size_t ret = bufferRx().readArray(buffer, bufsize);
678 // upate the volume
679 if (config_.volume_active) processVolume(buffer, ret);
680
681 return ret;
682 }
683
685 int available() override {
686 if (!is_started_) return 0;
687 return bufferRx().available();
688 }
689
691 int availableForWrite() override {
692 if (!is_started_) return 0;
693 return bufferTx().availableForWrite();
694 }
695
697 operator bool() override { return is_started_ && mounted(); }
698
700 void end() {
701 for (auto& audio : audiod_fct_) {
702 tu_fifo_clear(&audio.ep_in_ff);
703 tu_fifo_clear(&audio.ep_out_ff);
704 std::fill(audio.lin_buf_in.begin(), audio.lin_buf_in.end(), 0);
705 std::fill(audio.lin_buf_out.begin(), audio.lin_buf_out.end(), 0);
706 }
707 bufferTx().reset();
708 bufferRx().reset();
709 is_started_ = false;
710 }
711
714 uint16_t audioPacketSize() const { return packetSize(); }
715
727 uint16_t getDescriptor(uint8_t* desc) {
728 active_config_ = config_; // save active config
730 }
731
737 uint8_t numInterfaces() const {
738 return (uint8_t)(1 + (config_.enable_ep_out ? 1 : 0) +
739 (config_.enable_ep_in ? 1 : 0));
740 }
741
747 usbd_class_driver_t const* getClassDriver(uint8_t* count) {
748 static usbd_class_driver_t driver;
749 driver.name = "AUDIO";
750 driver.init = [](void) {
752 };
753 driver.deinit = [](void) {
755 };
756 driver.reset = [](uint8_t rhport) {
758 };
759 driver.open = [](uint8_t rhport, tusb_desc_interface_t const* itf_desc,
760 uint16_t max_len) {
761 return USBAudioDeviceBase::activeInstance().audiod_open(rhport, itf_desc,
762 max_len);
763 };
764 driver.control_xfer_cb = [](uint8_t rhport, uint8_t stage,
765 tusb_control_request_t const* request) {
767 rhport, stage, request);
768 };
769 driver.xfer_cb = [](uint8_t rhport, uint8_t ep_addr, xfer_result_t result,
770 uint32_t xferred_bytes) {
772 rhport, ep_addr, result, xferred_bytes);
773 };
774 driver.sof = [](uint8_t rhport, uint32_t frame_count) {
776 };
777
778 *count = 1;
779 return &driver;
780 }
781
783 bool isTxXferArmed() const { return tx_xfer_armed_; }
784
786 uint32_t getTxXferCount() const { return xfer_cb_tx_count_; }
788 uint32_t getRxXferCount() const { return xfer_cb_rx_count_; }
790 uint32_t getRxTotalBytes() const { return rx_total_bytes_; }
793 uint32_t getTxFifoReadTotal() const { return tx_fifo_read_total_; }
796 uint16_t getTxFrameBytesLast() const { return tx_frame_bytes_last_; }
799 uint32_t getTxXferredLast() const { return tx_xferred_last_; }
801 uint32_t getTxSampleRate() const {
802 return audiod_fct_.empty() ? 0 : audiod_fct_[0].sample_rate_tx;
803 }
805 uint8_t getTxChannels() const {
806 return audiod_fct_.empty() ? 0 : audiod_fct_[0].n_channels_tx;
807 }
809 uint8_t getTxBytesPerSample() const {
810 return audiod_fct_.empty() ? 0 : audiod_fct_[0].n_bytes_per_sample_tx;
811 }
813 uint8_t getTxInterval() const {
814 return audiod_fct_.empty() ? 0 : audiod_fct_[0].interval_tx;
815 }
816
817 protected:
818 bool is_started_ = false;
819 bool usb_task_active_ = false; // true while a dedicated tud_task() FreeRTOS task is running
820 bool tx_xfer_armed_ = false;
821 volatile uint32_t xfer_cb_tx_count_ = 0;
822 volatile uint32_t tx_fifo_read_total_ = 0;
823 volatile uint32_t xfer_cb_rx_count_ = 0;
824 volatile uint32_t rx_total_bytes_ = 0;
825 volatile uint16_t tx_frame_bytes_last_ = 0;
826 volatile uint32_t tx_xferred_last_ = 0;
827 bool is_active_ = false;
828 // ── Volume / mute state (sized to channels+1 in begin()) ─────────────────
829 std::vector<float> volume_;
830 std::vector<bool> mute_;
831 std::function<void(float, uint8_t)> volume_cb_;
832 std::function<void(bool, uint8_t)> mute_cb_;
833 std::function<void(uint32_t)> sample_rate_cb_;
834 std::function<void(bool, bool)> streaming_state_cb_;
838 std::function<void(USBAudioDeviceBase*, uint8_t rhport)> int_done_cb_;
839 std::function<bool(USBAudioDeviceBase*, uint8_t rhport, audiod_function_t*)>
841 std::function<bool(USBAudioDeviceBase*, uint8_t rhport, audiod_function_t*,
842 uint16_t xferred_bytes)>
844 // Callback for interface GET requests
845 std::function<bool(USBAudioDeviceBase*, uint8_t rhport,
846 tusb_control_request_t const*)>
848 // Callback for endpoint GET requests
849 std::function<bool(USBAudioDeviceBase*, uint8_t rhport,
850 tusb_control_request_t const*)>
852
853 // Callback for feedback done event
854 std::function<void(USBAudioDeviceBase*, uint8_t func_id)> fb_done_cb_;
855 std::function<bool(USBAudioDeviceBase*, uint8_t func_id)> req_entity_cb_;
856 std::function<bool(USBAudioDeviceBase*, uint8_t rhport,
857 tusb_control_request_t const* p_request)>
859 std::function<bool(USBAudioDeviceBase*, uint8_t rhport,
860 tusb_control_request_t const* p_request, uint8_t* pBuff)>
862
863 std::function<bool(USBAudioDeviceBase*, uint8_t rhport,
864 tusb_control_request_t const* p_request, uint8_t* pBuff)>
866
867 std::function<bool(USBAudioDeviceBase*, uint8_t rhport,
868 tusb_control_request_t const* p_request, uint8_t* pBuff)>
870
871 std::function<bool(USBAudioDeviceBase*, uint8_t rhport,
872 tusb_control_request_t const* p_request)>
874
875 std::function<void(USBAudioDeviceBase*, uint8_t func_id, uint8_t alt_itf,
876 audio_feedback_params_t* feedback_param)>
878
879 std::function<bool(USBAudioDeviceBase*, uint8_t func_id)>
881 uint8_t int_notify_buf_[6] = {};
882
883 std::vector<uint16_t> ep_out_sw_buf_sz_;
884 // (TUD_OPT_HIGH_SPEED ? 32 : 4) * CFG_TUD_AUDIO_EP_SZ_IN // Example write
885 // FIFO every 1ms, so it should be 8 times larger for HS device
886 std::vector<uint16_t> ep_in_sw_buf_sz_;
887 // calculate!
888 std::vector<uint16_t> desc_len_;
889 // 64
890 std::vector<uint16_t> ctrl_buf_sz_;
891
892 std::vector<audiod_function_t> audiod_fct_;
893 // std::vector<osal_mutex_def_t> ep_in_ff_mutex_wr_;
894 // std::vector<osal_mutex_def_t> ep_in_ff_mutex_rd_;
895 std::vector<osal_mutex_def_t> ep_out_ff_mutex_rd_;
896
897 // s_active_ lets getClassDriver() and the static process() trampoline
898 // reach the last-constructed instance without a singleton.
899 inline static USBAudioDeviceBase* s_active_ = nullptr;
900
902 void setConfig(const USBAudioConfig& cfg) { config_ = cfg; }
903
914 virtual void serviceTinyUSB() {
915 if (!usb_task_active_) tud_task();
916 }
917
920
923
925 void processVolume(uint8_t* data, size_t len) {
926 switch (config_.bits_per_sample) {
927 case 8:
928 processVolume<int8_t>((int8_t*)data, len);
929 break;
930 case 16:
931 processVolume<int16_t>((int16_t*)data, len / 2);
932 break;
933 case 24:
934 processVolume<int24_3bytes_t>((int24_3bytes_t*)data, len / 3);
935 break;
936 case 32:
937 processVolume<int32_t>((int32_t*)data, len / 4);
938 break;
939 default:
940 // Unsupported bit depth; do nothing.
941 break;
942 }
943 }
944
947 float getVolumeExt(uint8_t channel) const {
948 if (volume_.empty()) return 1.0f;
949 if (mute_[0]) return 0.0f; // master mute
950 float master = volume_[0];
951 if (channel >= volume_.size()) return master; // no per-channel entry
952 if (mute_[channel]) return 0.0f; // per-channel mute
953 return master * volume_[channel];
954 }
955
956 template <typename T>
957 void processVolume(T* data, size_t sample_count) {
958 uint8_t ch_count = config_.channels;
959 for (size_t i = 0; i < sample_count; i++) {
960 uint8_t ch = (uint8_t)(i % ch_count) + 1; // 1-based per-channel index
961 float vol = getVolumeExt(ch);
962 data[i] = (T)(data[i] * vol);
963 }
964 }
965
974 void setSampleRate(uint32_t rate) {
975 bool rate_updated = rate != config_.sample_rate;
976 config_.sample_rate = rate;
977 LOGW("Sample rate changed to %u Hz", rate);
978 if (rate_updated) {
981 }
982 for (auto& fct : audiod_fct_) fct.sample_rate_tx = rate;
984 sendInterruptNotification(AUDIO_CS_CTRL_SAM_FREQ, 0,
986 }
987
992 virtual bool beginUSB() = 0;
993
999 virtual void resizeBuffers() = 0;
1000
1003 static constexpr int16_t kVolumeMinDb256 = -25600; // -100 dB in 1/256 dB
1004
1007 static int16_t floatToUac2(float vol) {
1008 if (vol <= 0.0f) return (int16_t)0x8000;
1009 if (vol >= 1.0f) return 0;
1010 return (int16_t)((1.0f - vol) * kVolumeMinDb256);
1011 }
1012
1015 static float uac2ToFloat(int16_t v) {
1016 if (v == (int16_t)0x8000) return 0.0f;
1017 if (v >= 0) return 1.0f;
1018 if (v <= kVolumeMinDb256) return 0.0f;
1019 return 1.0f - (float)v / (float)kVolumeMinDb256;
1020 }
1021
1024 bool isFeatureUnit(uint8_t id) const {
1027 }
1028
1036 void sendInterruptNotification(uint8_t ctrlSel, uint8_t channel,
1037 uint8_t entityID) {
1038 if (!tud_mounted()) return;
1039 for (uint8_t i = 0; i < (uint8_t)audiod_fct_.size(); i++) {
1040 if (audiod_fct_[i].ep_int == 0) continue;
1041 int_notify_buf_[0] = 0x00; // bInfo: interface, not vendor
1042 int_notify_buf_[1] = AUDIO_CS_REQ_CUR; // bAttribute: CUR changed
1043 int_notify_buf_[2] = channel; // wValue low = CN
1044 int_notify_buf_[3] = ctrlSel; // wValue high = CS
1045 int_notify_buf_[4] = config_.itf_num_ac; // wIndex low = interface
1046 int_notify_buf_[5] = entityID; // wIndex high = entity ID
1047 (void)TUSB_EDPT_XFER(0, audiod_fct_[i].ep_int, int_notify_buf_, 6);
1048 break;
1049 }
1050 }
1051
1052 bool configChanged(const USBAudioConfig& n) { return config_ != n; }
1053
1054 // Returns the control buffer size for a given function number
1055 uint16_t getCtrlBufSz(uint8_t fn) const {
1056 return (fn < ctrl_buf_sz_.size()) ? ctrl_buf_sz_[fn] : 64;
1057 }
1058
1059 // Returns the OUT software buffer size for a given function number
1060 uint16_t getEpOutSwBufSz(uint8_t fn) const {
1061 return (fn < ep_out_sw_buf_sz_.size()) ? ep_out_sw_buf_sz_[fn] : 0;
1062 }
1063
1064 // Returns the IN software buffer size for a given function number
1065 uint16_t getEpInSwBufSz(uint8_t fn) const {
1066 return (fn < ep_in_sw_buf_sz_.size()) ? ep_in_sw_buf_sz_[fn] : 0;
1067 }
1068
1069 // Returns the descriptor length for a given function number
1070 uint16_t getDescLen(uint8_t fn) const {
1071 return (fn < desc_len_.size()) ? desc_len_[fn] : 0;
1072 }
1073
1075
1077
1078 static bool isValidBitsPerSample(uint8_t bps) {
1079 return bps == 16 || bps == 24 || bps == 32;
1080 }
1081
1086
1087 // Max Bytes for one 1 ms isochronous USB packet.
1088 uint16_t packetSize() const { return descr_builder.calcMaxPacketSize(); }
1089
1090 // Total audio FIFO size in bytes.
1091 uint16_t fifoSize() const {
1092 uint16_t sz = (uint16_t)(packetSize() * config_.fifo_packets);
1093 // // Round up to next power of 2 — some tu_fifo implementations use
1094 // // idx & (depth-1) for index wrapping, which requires power-of-2 depth.
1095 uint16_t p = 256;
1096 while (p < sz) p <<= 1;
1097 return p;
1098 }
1099
1100 // Returns the reset size for audiod_function_t up to and including
1101 // ctrl_buf_sz
1102 static constexpr size_t getResetSize() {
1103 return offsetof(audiod_function_t, ctrl_buf_sz) +
1104 sizeof(((audiod_function_t*)0)->ctrl_buf_sz);
1105 }
1106
1107 // Called by audiod_sof_isr() at the feedback interval.
1108 // Computes the current feedback value then claims the EP and sends it.
1110 uint32_t /*frame_count*/,
1111 uint8_t frame_shift) {
1112 audiod_function_t* audio = &audiod_fct_[func_id];
1113
1114 switch (audio->feedback.compute_method) {
1115 case AUDIO_FEEDBACK_METHOD_FIFO_COUNT: {
1116 // In linear-buffer mode, audio data flows lin_buf_out → bufferRx(),
1117 // completely bypassing ep_out_ff. Reading ep_out_ff would always
1118 // return 0, driving the feedback to min_value and causing the host
1119 // to gradually reduce its send rate until the buffer drains (audible
1120 // periodic drop every 5-10 s). Use the platform RX ring buffer level.
1121 uint32_t ff_count = isUseLinearBufferRx()
1122 ? (uint32_t)bufferRx().available()
1123 : tu_fifo_count(&audio->ep_out_ff);
1124 // Exponential weighted average keeps the level estimate stable
1125 audio->feedback.compute.fifo_count.fifo_lvl_avg =
1126 audio->feedback.compute.fifo_count.fifo_lvl_avg -
1127 (audio->feedback.compute.fifo_count.fifo_lvl_avg >> 8) +
1128 ((uint32_t)ff_count << 8);
1129 uint32_t avg = audio->feedback.compute.fifo_count.fifo_lvl_avg >> 8;
1130 uint32_t thr = audio->feedback.compute.fifo_count.fifo_lvl_thr;
1131 uint32_t nom = audio->feedback.compute.fifo_count.nom_value;
1132 if (avg > thr) {
1133 audio->feedback.value =
1134 nom + (uint32_t)audio->feedback.compute.fifo_count.rate_const[0] *
1135 (avg - thr);
1136 } else {
1137 uint32_t drop =
1138 (uint32_t)audio->feedback.compute.fifo_count.rate_const[1] *
1139 (thr - avg);
1140 audio->feedback.value =
1141 (nom > drop) ? nom - drop : audio->feedback.min_value;
1142 }
1143 audio->feedback.value =
1144 TU_MIN(TU_MAX(audio->feedback.value, audio->feedback.min_value),
1145 audio->feedback.max_value);
1146 } break;
1147
1148 case AUDIO_FEEDBACK_METHOD_FREQUENCY_POWER_OF_2:
1149 audio->feedback.value = 1UL << audio->feedback.compute.power_of_2;
1150 break;
1151
1152 case AUDIO_FEEDBACK_METHOD_FREQUENCY_FLOAT:
1153 audio->feedback.value =
1154 (uint32_t)(audio->feedback.compute.float_const *
1155 (float)(1UL << (16u - (frame_shift - 1u))));
1156 break;
1157
1158 case AUDIO_FEEDBACK_METHOD_FREQUENCY_FIXED: {
1159 uint32_t frame_div =
1160 (TUSB_SPEED_FULL == tud_speed_get()) ? 1000u : 8000u;
1161 audio->feedback.value =
1162 (audio->feedback.compute.fixed.sample_freq << 16) / frame_div;
1163 } break;
1164
1165 default:
1166 break;
1167 }
1168
1169 if (usbd_edpt_claim(audio->rhport, audio->ep_fb)) {
1170 audiod_fb_send(audio);
1171 }
1172 }
1173
1174 // USBD Driver API
1175 void audiod_init(void) {
1176 audiod_fct_.resize(getAudioCount());
1177 alloc_mutex();
1178
1179 // Initialize control buffers
1180 for (uint8_t i = 0; i < getAudioCount(); i++) {
1181 audiod_function_t* audio = &audiod_fct_[i];
1182 // Initialize control buffers
1183 int size = getCtrlBufSz(i);
1184 audio->ctrl_buf.resize(size);
1185 audio->ctrl_buf_sz = size;
1186 // Initialize active alternate interface buffers
1187 audio->alt_setting.resize(descr_builder.audioFunctionsCount());
1188 // Initialize IN EP — lin_buf_in is the DMA staging buffer (one frame).
1189 // Audio data flows through bufferTx() (resized in begin()), not ep_in_ff.
1190 if (isEpInEnabled()) {
1191 // Max packet across all supported rates (192 kHz):
1192 uint16_t max_pkt = descr_builder.calcPacketSizeForRate(192000);
1193 audio->lin_buf_in.resize(max_pkt);
1194 }
1195 // Initialize OUT EP — always set up both FIFO and linear buffer.
1196 // The FIFO is needed by TinyUSB internals even in linear buffer mode.
1197 if (isEpOutEnabled()) {
1198 audio->ep_out_sw_buf.resize(getEpOutSwBufSz(i));
1199 TUSB_FIFO_CONFIG(&audio->ep_out_ff, audio->ep_out_sw_buf.data(),
1200 getEpOutSwBufSz(i), true);
1201 if (isFifoMutexEnabled()) {
1202 tu_fifo_config_mutex(&audio->ep_out_ff, NULL,
1203 osal_mutex_create(&ep_out_ff_mutex_rd_[i]));
1204 }
1205 uint16_t max_pkt = descr_builder.calcPacketSizeForRate(192000);
1206 audio->lin_buf_out.resize(max_pkt);
1207 }
1208 if (isFeedbackEpEnabled()) {
1209 audio->fb_buf.resize(1); // one uint32_t = 4 bytes of feedback data
1210 }
1211 }
1212 }
1213
1215 if (isFifoMutexEnabled()) {
1216 if (isEpOutEnabled()) {
1218 }
1219 }
1220 }
1221
1222 bool audiod_deinit(void) {
1223 return false; // TODO not implemented yet
1224 }
1225
1226 void audiod_reset(uint8_t rhport) {
1227 (void)rhport;
1228 for (uint8_t i = 0; i < getAudioCount(); i++) {
1229 audiod_function_t* audio = &audiod_fct_[i];
1230 memset(audio, 0, getResetSize());
1231 if (isEpInEnabled()) {
1232 tu_fifo_clear(&audio->ep_in_ff);
1233 bufferTx().reset();
1234 }
1235 if (isEpOutEnabled()) {
1236 tu_fifo_clear(&audio->ep_out_ff);
1237 bufferRx().reset();
1238 }
1239 }
1240 }
1241
1242 uint16_t audiod_open(uint8_t rhport, tusb_desc_interface_t const* itf_desc,
1243 uint16_t max_len) {
1244 (void)max_len;
1245 TU_VERIFY(TUSB_CLASS_AUDIO == itf_desc->bInterfaceClass &&
1246 AUDIO_SUBCLASS_CONTROL == itf_desc->bInterfaceSubClass);
1247 TU_VERIFY(itf_desc->bInterfaceProtocol == AUDIO_INT_PROTOCOL_CODE_V2);
1248 TU_ASSERT(itf_desc->bNumEndpoints <= 1);
1249 if (itf_desc->bNumEndpoints == 1) {
1250 TU_ASSERT(isInterruptEpEnabled());
1251 }
1252 TU_VERIFY(itf_desc->bAlternateSetting == 0);
1253 uint8_t i;
1254 for (i = 0; i < getAudioCount(); i++) {
1255 if (!audiod_fct_[i].p_desc) {
1256 audiod_fct_[i].p_desc = (uint8_t const*)itf_desc;
1257 audiod_fct_[i].rhport = rhport;
1258 audiod_fct_[i].desc_length = getDescLen(i);
1259 // audiod_reset() zeroes ctrl_buf_sz via memset — restore it so
1260 // tud_control_xfer() receives the correct buffer length.
1261 audiod_fct_[i].ctrl_buf_sz = getCtrlBufSz(i);
1263 uint8_t ep_in = 0, ep_out = 0, ep_fb = 0;
1264 uint16_t ep_in_size = 0, ep_out_size = 0;
1265 tusb_desc_endpoint_t const* desc_ep_out = nullptr;
1266 uint8_t const* p_desc = audiod_fct_[i].p_desc;
1267 uint8_t const* p_desc_end =
1268 p_desc + audiod_fct_[i].desc_length - TUD_AUDIO_DESC_IAD_LEN;
1269 while (p_desc_end - p_desc > 0) {
1270 if (tu_desc_type(p_desc) == TUSB_DESC_ENDPOINT) {
1271 tusb_desc_endpoint_t const* desc_ep =
1272 (tusb_desc_endpoint_t const*)p_desc;
1273 if (desc_ep->bmAttributes.xfer == TUSB_XFER_ISOCHRONOUS) {
1274 if (isFeedbackEpEnabled() && desc_ep->bmAttributes.usage == 1) {
1275 ep_fb = desc_ep->bEndpointAddress;
1276 }
1277 if (desc_ep->bmAttributes.usage == 0) {
1278 if (isEpInEnabled() &&
1279 tu_edpt_dir(desc_ep->bEndpointAddress) == TUSB_DIR_IN) {
1280 ep_in = desc_ep->bEndpointAddress;
1281 ep_in_size =
1282 TU_MAX(tu_edpt_packet_size(desc_ep), ep_in_size);
1283 } else if (isEpOutEnabled() &&
1284 tu_edpt_dir(desc_ep->bEndpointAddress) ==
1285 TUSB_DIR_OUT) {
1286 ep_out = desc_ep->bEndpointAddress;
1287 ep_out_size =
1288 TU_MAX(tu_edpt_packet_size(desc_ep), ep_out_size);
1289 desc_ep_out = desc_ep;
1290 }
1291 }
1292 }
1293 }
1294 p_desc = tu_desc_next(p_desc);
1295 }
1296 if (isEpInEnabled() && ep_in) {
1297 bool alloc_ok = usbd_edpt_iso_alloc(rhport, ep_in, ep_in_size);
1298 LOGD("iso_alloc IN ep=0x%02x sz=%u: %s", ep_in, ep_in_size,
1299 alloc_ok ? "OK" : "FAIL");
1300 }
1301 if (isEpOutEnabled() && ep_out) {
1302 bool alloc_ok = usbd_edpt_iso_alloc(rhport, ep_out, ep_out_size);
1303 LOGD("iso_alloc OUT ep=0x%02x sz=%u: %s", ep_out, ep_out_size,
1304 alloc_ok ? "OK" : "FAIL");
1305#ifdef TUP_DCD_EDPT_ISO_ALLOC
1306 // Pre-activate during enumeration (no isochronous traffic).
1307 // Cannot be done in SET_INTERFACE because iso_activate blocks
1308 // on ESP32's DWC2 when the host is already sending.
1309 // release clears the busy flag so XFER can arm later.
1310 if (desc_ep_out) {
1311 usbd_edpt_iso_activate(rhport, desc_ep_out);
1312 usbd_edpt_release(rhport, ep_out);
1313 LOGD("iso_activate+release OUT: done");
1314 }
1315#endif
1316 }
1317 if (isFeedbackEpEnabled() && ep_fb) {
1318 usbd_edpt_iso_alloc(rhport, ep_fb, 4);
1319 }
1320 }
1321 // Scan for bclock_id_tx (clock entity referenced by the USB-streaming
1322 // terminal) and interval_tx. Runs in TX, RX, and RXTX mode so that
1323 // clock-validity/frequency GET requests always succeed.
1324 // TX/RXTX: Output Terminal type=USB_STREAMING → bCSourceID at [8]
1325 // RX: Input Terminal type=USB_STREAMING → bCSourceID at [7]
1326 // interval_tx is only meaningful for the ISO IN endpoint (TX/RXTX).
1327 if (isEpInEnabled() || isEpOutEnabled()) {
1328 uint8_t const* p_desc = audiod_fct_[i].p_desc;
1329 uint8_t const* p_desc_end =
1330 p_desc + audiod_fct_[i].desc_length - TUD_AUDIO_DESC_IAD_LEN;
1331 while (p_desc_end - p_desc > 0) {
1332 if (tu_desc_type(p_desc) == TUSB_DESC_ENDPOINT) {
1333 if (isEpInEnabled()) {
1334 tusb_desc_endpoint_t const* desc_ep =
1335 (tusb_desc_endpoint_t const*)p_desc;
1336 if (desc_ep->bmAttributes.xfer == TUSB_XFER_ISOCHRONOUS &&
1337 desc_ep->bmAttributes.usage == 0 &&
1338 tu_edpt_dir(desc_ep->bEndpointAddress) == TUSB_DIR_IN) {
1339 audiod_fct_[i].interval_tx = desc_ep->bInterval;
1340 }
1341 }
1342 } else if (tu_desc_type(p_desc) == TUSB_DESC_CS_INTERFACE) {
1343 if (tu_desc_subtype(p_desc) ==
1344 AUDIO_CS_AC_INTERFACE_OUTPUT_TERMINAL) {
1345 if (tu_unaligned_read16(p_desc + 4) ==
1346 AUDIO_TERM_TYPE_USB_STREAMING) {
1347 audiod_fct_[i].bclock_id_tx = p_desc[8]; // OT bCSourceID
1348 }
1349 } else if (tu_desc_subtype(p_desc) ==
1350 AUDIO_CS_AC_INTERFACE_INPUT_TERMINAL) {
1351 if (tu_unaligned_read16(p_desc + 4) ==
1352 AUDIO_TERM_TYPE_USB_STREAMING) {
1353 audiod_fct_[i].bclock_id_tx = p_desc[7]; // IT bCSourceID
1354 }
1355 }
1356 }
1357 p_desc = tu_desc_next(p_desc);
1358 }
1359 }
1360
1361 if (isInterruptEpEnabled()) {
1362 uint8_t const* p_desc = audiod_fct_[i].p_desc;
1363 uint8_t const* p_desc_end =
1364 p_desc + audiod_fct_[i].desc_length - TUD_AUDIO_DESC_IAD_LEN;
1365 while (p_desc_end - p_desc > 0) {
1366 if (tu_desc_type(p_desc) == TUSB_DESC_ENDPOINT) {
1367 tusb_desc_endpoint_t const* desc_ep =
1368 (tusb_desc_endpoint_t const*)p_desc;
1369 uint8_t const ep_addr = desc_ep->bEndpointAddress;
1370 if (tu_edpt_dir(ep_addr) == TUSB_DIR_IN &&
1371 desc_ep->bmAttributes.xfer == TUSB_XFER_INTERRUPT) {
1372 if (usbd_edpt_open(audiod_fct_[i].rhport, desc_ep)) {
1373 audiod_fct_[i].ep_int = ep_addr;
1374 } else {
1375 LOGE(" UAC2: interrupt EP 0x%02x open failed", ep_addr);
1376
1377 }
1378 }
1379 }
1380 p_desc = tu_desc_next(p_desc);
1381 }
1382 }
1383 audiod_fct_[i].mounted = true;
1384 break;
1385 }
1386 }
1387 TU_ASSERT(i < getAudioCount());
1388 uint16_t drv_len = audiod_fct_[i].desc_length - TUD_AUDIO_DESC_IAD_LEN;
1389 return drv_len;
1390 }
1391
1392 bool audiod_control_xfer_cb(uint8_t rhport, uint8_t stage,
1393 tusb_control_request_t const* request) {
1394 if (stage == CONTROL_STAGE_SETUP) {
1395 return audiod_control_request(rhport, request);
1396 } else if (stage == CONTROL_STAGE_DATA) {
1397 return audiod_control_complete(rhport, request);
1398 }
1399 return true;
1400 }
1401 // Invoked when class request DATA stage is finished.
1402 // return false to stall control EP (e.g Host send non-sense DATA)
1403 bool audiod_control_complete(uint8_t rhport,
1404 tusb_control_request_t const* p_request) {
1405 // Handle audio class specific set requests
1406 if (p_request->bmRequestType_bit.type == TUSB_REQ_TYPE_CLASS &&
1407 p_request->bmRequestType_bit.direction == TUSB_DIR_OUT) {
1408 uint8_t func_id;
1409
1410 switch (p_request->bmRequestType_bit.recipient) {
1411 case TUSB_REQ_RCPT_INTERFACE: {
1412 uint8_t itf = TU_U16_LOW(p_request->wIndex);
1413 uint8_t entityID = TU_U16_HIGH(p_request->wIndex);
1414
1415 if (entityID != 0) {
1416 func_id = 0;
1417 uint8_t ctrlSel = TU_U16_HIGH(p_request->wValue);
1418 uint8_t* cb = audiod_fct_[func_id].ctrl_buf.data();
1419
1420 // ── Clock Source SET_CUR (sample rate) ──────────────
1422 ctrlSel == AUDIO_CS_CTRL_SAM_FREQ &&
1423 p_request->bRequest == AUDIO_CS_REQ_CUR) {
1424 setSampleRate(tu_unaligned_read32(cb));
1425 }
1426
1427 // ── Feature Unit SET_CUR (mute / volume) ────────────
1428 if (isFeatureUnit(entityID) &&
1429 p_request->bRequest == AUDIO_CS_REQ_CUR) {
1430 uint8_t channel = TU_U16_LOW(p_request->wValue);
1431 if (ctrlSel == AUDIO_FU_CTRL_MUTE) {
1432 setMute(cb[0] != 0, channel);
1433 } else if (ctrlSel == AUDIO_FU_CTRL_VOLUME) {
1434 int16_t v;
1435 memcpy(&v, cb, 2);
1436 setVolume(uac2ToFloat(v), channel);
1437 }
1438 }
1439
1440 // Invoke callback
1442 return tud_audio_set_req_entity_cb_(this, rhport, p_request, cb);
1443 }
1444 } else {
1445 // Find index of audio driver structure and verify interface really
1446 // exists
1447 TU_VERIFY(audiod_verify_itf_exists(itf, &func_id));
1448
1449 // Invoke callback
1452 this, rhport, p_request,
1453 audiod_fct_[func_id].ctrl_buf.data());
1454 }
1455 }
1456 } break;
1457
1458 case TUSB_REQ_RCPT_ENDPOINT: {
1459 uint8_t ep = TU_U16_LOW(p_request->wIndex);
1460
1461 // Check if entity is present and get corresponding driver index
1462 TU_VERIFY(audiod_verify_ep_exists(ep, &func_id));
1463
1464 // Invoke callback
1467 this, rhport, p_request, audiod_fct_[func_id].ctrl_buf.data());
1468 }
1469 } break;
1470 // Unknown/Unsupported recipient
1471 default:
1472 TU_BREAKPOINT();
1473 return false;
1474 }
1475 }
1476 return true;
1477 }
1478
1480 bool audiod_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result,
1481 uint32_t xferred_bytes) {
1482 (void)result;
1483 (void)xferred_bytes;
1484 for (uint8_t func_id = 0; func_id < getAudioCount(); func_id++) {
1485 audiod_function_t* audio = &audiod_fct_[func_id];
1486 if (isInterruptEpEnabled() && audio->ep_int == ep_addr) {
1487 if (int_done_cb_) int_done_cb_(this, rhport);
1488 return true;
1489 }
1490 if (isEpInEnabled() && audio->ep_in == ep_addr &&
1491 audio->alt_setting.size() != 0) {
1493 if (tx_done_cb_) tx_done_cb_(this, rhport, audio);
1494
1495 uint16_t frame_bytes = isEpInFlowControlEnabled()
1497 : audio->ep_in_sz;
1498 if (frame_bytes > audio->ep_in_sz) frame_bytes = audio->ep_in_sz;
1499 tx_frame_bytes_last_ = frame_bytes;
1500 tx_xferred_last_ = xferred_bytes;
1501
1502 // Drain platform buffer into lin_buf_in, zero-pad, send via DMA.
1503 {
1504 uint8_t* dst = audio->lin_buf_in.data();
1505 uint16_t n = (uint16_t)bufferTx().readArray(dst, frame_bytes);
1507 if (n < frame_bytes) memset(dst + n, 0, frame_bytes - n);
1508 (void)TUSB_EDPT_XFER(rhport, audio->ep_in, dst, frame_bytes);
1509 }
1510 return true;
1511 }
1512 if (isEpOutEnabled() && audio->ep_out == ep_addr) {
1514 rx_total_bytes_ += xferred_bytes;
1515 if (isUseLinearBufferRx()) {
1516 // Copy DMA-received data into the platform buffer, re-arm DMA.
1517 if (xferred_bytes > 0)
1518 bufferRx().writeArray(audio->lin_buf_out.data(),
1519 (int)xferred_bytes);
1520 if (rx_done_cb_)
1521 rx_done_cb_(this, rhport, audio, (uint16_t)xferred_bytes);
1522 (void)TUSB_EDPT_XFER(rhport, audio->ep_out, audio->lin_buf_out.data(),
1523 audio->ep_out_sz);
1524 } else {
1525 // FIFO mode: data is already in ep_out_ff from DMA, copy to buffer
1526 if (xferred_bytes > 0) {
1527 uint8_t tmp[768];
1528 uint16_t n =
1529 tu_fifo_read_n(&audio->ep_out_ff, tmp, (uint16_t)xferred_bytes);
1530 if (n > 0) bufferRx().writeArray(tmp, n);
1531 }
1532 if (rx_done_cb_)
1533 rx_done_cb_(this, rhport, audio, (uint16_t)xferred_bytes);
1534 (void)TUSB_EDPT_XFER_FIFO(rhport, audio->ep_out, &audio->ep_out_ff,
1535 audio->ep_out_sz);
1536 }
1537 return true;
1538 }
1539 if (isFeedbackEpEnabled() && audio->ep_fb == ep_addr) {
1540 // SOF ISR owns re-sending; just notify the application.
1541 if (fb_done_cb_) fb_done_cb_(this, func_id);
1542 return true;
1543 }
1544 }
1545 return false;
1546 }
1547
1548 TU_ATTR_FAST_FUNC void audiod_sof_isr(uint8_t rhport, uint32_t frame_count) {
1549 (void)rhport;
1550 (void)frame_count;
1552 for (uint8_t i = 0; i < getAudioCount(); i++) {
1553 audiod_function_t* audio = &audiod_fct_[i];
1554 if (audio->ep_fb != 0) {
1555 uint8_t const hs_adjust =
1556 (TUSB_SPEED_HIGH == tud_speed_get()) ? 3 : 0;
1557 uint32_t const interval =
1558 1UL << (audio->feedback.frame_shift - hs_adjust);
1559 if (0 == (frame_count & (interval - 1))) {
1560 tud_audio_feedback_interval_isr(i, frame_count,
1561 audio->feedback.frame_shift);
1562 }
1563 }
1564 }
1565 }
1566 }
1567
1568 // ── Clock Source GET handler ────────────────────────────────────────────
1569 bool handleClockSourceGet(uint8_t rhport,
1570 tusb_control_request_t const* p_request,
1571 uint8_t* cb) {
1572 uint8_t ctrlSel = TU_U16_HIGH(p_request->wValue);
1573 if (ctrlSel == AUDIO_CS_CTRL_CLK_VALID &&
1574 p_request->bRequest == AUDIO_CS_REQ_CUR) {
1575 cb[0] = 1;
1576 return tud_control_xfer(rhport, p_request, cb, 1);
1577 }
1578 if (ctrlSel == AUDIO_CS_CTRL_SAM_FREQ) {
1579 uint32_t rate = (uint32_t)config_.sample_rate;
1580 if (p_request->bRequest == AUDIO_CS_REQ_CUR) {
1581 memcpy(cb, &rate, 4);
1582 return tud_control_xfer(rhport, p_request, cb, 4);
1583 }
1584 if (p_request->bRequest == AUDIO_CS_REQ_RANGE) {
1586 // List all supported discrete rates
1587 uint16_t cnt = kNumSupportedSampleRates;
1588 memcpy(cb, &cnt, 2);
1589 for (uint8_t i = 0; i < kNumSupportedSampleRates; i++) {
1590 uint32_t r = kSupportedSampleRates[i];
1591 uint32_t z = 0;
1592 memcpy(cb + 2 + i * 12, &r, 4);
1593 memcpy(cb + 2 + i * 12 + 4, &r, 4);
1594 memcpy(cb + 2 + i * 12 + 8, &z, 4);
1595 }
1596 return tud_control_xfer(rhport, p_request, cb,
1597 (uint16_t)(2 + kNumSupportedSampleRates * 12));
1598 } else {
1599 // Single fixed rate from config
1600 uint16_t cnt = 1;
1601 uint32_t z = 0;
1602 memcpy(cb, &cnt, 2);
1603 memcpy(cb + 2, &rate, 4); // dMIN
1604 memcpy(cb + 6, &rate, 4); // dMAX
1605 memcpy(cb + 10, &z, 4); // dRES = 0 (fixed)
1606 return tud_control_xfer(rhport, p_request, cb, 14);
1607 }
1608 }
1609 }
1610 return false;
1611 }
1612
1613 // ── Feature Unit GET handler ──────────────────────────────────────────
1614 bool handleFeatureUnitGet(uint8_t rhport,
1615 tusb_control_request_t const* p_request,
1616 uint8_t* cb) {
1617 uint8_t ctrlSel = TU_U16_HIGH(p_request->wValue);
1618 uint8_t channel = TU_U16_LOW(p_request->wValue);
1619 if (ctrlSel == AUDIO_FU_CTRL_MUTE &&
1620 p_request->bRequest == AUDIO_CS_REQ_CUR) {
1621 cb[0] = isMute(channel) ? 1 : 0;
1622 return tud_control_xfer(rhport, p_request, cb, 1);
1623 }
1624 if (ctrlSel == AUDIO_FU_CTRL_VOLUME) {
1625 if (p_request->bRequest == AUDIO_CS_REQ_CUR) {
1626 int16_t v = floatToUac2(volume(channel));
1627 memcpy(cb, &v, 2);
1628 return tud_control_xfer(rhport, p_request, cb, 2);
1629 }
1630 if (p_request->bRequest == AUDIO_CS_REQ_RANGE) {
1631 uint16_t cnt = 1;
1632 int16_t vmin = -25600, vmax = 0, vres = 256;
1633 memcpy(cb + 0, &cnt, 2);
1634 memcpy(cb + 2, &vmin, 2);
1635 memcpy(cb + 4, &vmax, 2);
1636 memcpy(cb + 6, &vres, 2);
1637 return tud_control_xfer(rhport, p_request, cb, 8);
1638 }
1639 }
1640 return false;
1641 }
1642
1643 // ── Entity request handler (Clock Source + Feature Unit) ──────────────
1644 bool handleEntityRequest(uint8_t rhport,
1645 tusb_control_request_t const* p_request,
1646 uint8_t entityID) {
1647 uint8_t func_id = 0;
1648 uint8_t* cb = audiod_fct_[func_id].ctrl_buf.data();
1649 bool is_get = (p_request->bmRequestType_bit.direction == TUSB_DIR_IN);
1650
1652 if (is_get && handleClockSourceGet(rhport, p_request, cb)) return true;
1653 // SET — schedule data receive for audiod_control_complete()
1654 return tud_control_xfer(rhport, p_request, cb,
1655 audiod_fct_[func_id].ctrl_buf_sz);
1656 }
1657
1658 if (isFeatureUnit(entityID)) {
1659 if (is_get && handleFeatureUnitGet(rhport, p_request, cb)) return true;
1660 // SET — schedule data receive
1661 return tud_control_xfer(rhport, p_request, cb,
1662 audiod_fct_[func_id].ctrl_buf_sz);
1663 }
1664
1665 // Unknown entity — try generic verify
1666 uint8_t itf = TU_U16_LOW(p_request->wIndex);
1667 if (!audiod_verify_entity_exists(itf, entityID, &func_id)) {
1668 tud_control_status(rhport, p_request);
1669 return true;
1670 }
1671 if (is_get && req_entity_cb_) return req_entity_cb_(this, func_id);
1672 return tud_control_xfer(rhport, p_request,
1673 audiod_fct_[func_id].ctrl_buf.data(),
1674 audiod_fct_[func_id].ctrl_buf_sz);
1675 }
1676
1677 // ── Interface request handler (entityID == 0) ─────────────────────────
1678 bool handleInterfaceRequest(uint8_t rhport,
1679 tusb_control_request_t const* p_request) {
1680 uint8_t itf = TU_U16_LOW(p_request->wIndex);
1681 uint8_t func_id;
1682 TU_VERIFY(audiod_verify_itf_exists(itf, &func_id));
1683 if (p_request->bmRequestType_bit.direction == TUSB_DIR_IN) {
1684 if (get_req_itf_cb_) return get_req_itf_cb_(this, rhport, p_request);
1685 return false;
1686 }
1687 return tud_control_xfer(rhport, p_request,
1688 audiod_fct_[func_id].ctrl_buf.data(),
1689 audiod_fct_[func_id].ctrl_buf_sz);
1690 }
1691
1692 // ── Endpoint request handler ──────────────────────────────────────────
1693 bool handleEndpointRequest(uint8_t rhport,
1694 tusb_control_request_t const* p_request) {
1695 uint8_t ep = TU_U16_LOW(p_request->wIndex);
1696 uint8_t func_id;
1697 TU_VERIFY(audiod_verify_ep_exists(ep, &func_id));
1698 if (p_request->bmRequestType_bit.direction == TUSB_DIR_IN) {
1699 if (get_req_ep_cb_) return get_req_ep_cb_(this, rhport, p_request);
1700 return false;
1701 }
1702 return tud_control_xfer(rhport, p_request,
1703 audiod_fct_[func_id].ctrl_buf.data(),
1704 audiod_fct_[func_id].ctrl_buf_sz);
1705 }
1706
1707 // ── Main control request dispatcher ───────────────────────────────────
1708 bool audiod_control_request(uint8_t rhport,
1709 tusb_control_request_t const* p_request) {
1710 if (p_request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD) {
1711 switch (p_request->bRequest) {
1712 case TUSB_REQ_GET_INTERFACE:
1713 return audiod_get_interface(rhport, p_request);
1714 case TUSB_REQ_SET_INTERFACE:
1715 return audiod_set_interface(rhport, p_request);
1716 case TUSB_REQ_CLEAR_FEATURE:
1717 return true;
1718 default:
1719 return false;
1720 }
1721 }
1722
1723 if (p_request->bmRequestType_bit.type == TUSB_REQ_TYPE_CLASS) {
1724 switch (p_request->bmRequestType_bit.recipient) {
1725 case TUSB_REQ_RCPT_INTERFACE: {
1726 uint8_t entityID = TU_U16_HIGH(p_request->wIndex);
1727 return (entityID != 0)
1728 ? handleEntityRequest(rhport, p_request, entityID)
1729 : handleInterfaceRequest(rhport, p_request);
1730 }
1731 case TUSB_REQ_RCPT_ENDPOINT:
1732 return handleEndpointRequest(rhport, p_request);
1733 default:
1734 return false;
1735 }
1736 }
1737
1738 return false;
1739 }
1740
1741 // Verify an entity with the given ID exists and returns also the
1742 // corresponding driver index
1743 bool audiod_verify_entity_exists(uint8_t itf, uint8_t entityID,
1744 uint8_t* func_id) {
1745 uint8_t i;
1746 for (i = 0; i < getAudioCount(); i++) {
1747 // Look for the correct driver by checking if the unique standard AC
1748 // interface number fits
1749 if (audiod_fct_[i].p_desc &&
1750 ((tusb_desc_interface_t const*)audiod_fct_[i].p_desc)
1751 ->bInterfaceNumber == itf) {
1752 // Get pointers after class specific AC descriptors and end of AC
1753 // descriptors - entities are defined in between
1754 uint8_t const* p_desc =
1755 tu_desc_next(audiod_fct_[i].p_desc); // Points to CS AC descriptor
1756 uint8_t const* p_desc_end =
1757 ((audio_desc_cs_ac_interface_t const*)p_desc)->wTotalLength +
1758 p_desc;
1759 p_desc = tu_desc_next(p_desc); // Get past CS AC descriptor
1760
1761 // Condition modified from p_desc < p_desc_end to prevent gcc>=12
1762 // strict-overflow warning
1763 while (p_desc_end - p_desc > 0) {
1764 if (p_desc[3] == entityID) // Entity IDs are always at offset 3
1765 {
1766 *func_id = i;
1767 return true;
1768 }
1769 p_desc = tu_desc_next(p_desc);
1770 }
1771 }
1772 }
1773 return false;
1774 }
1775
1776 bool audiod_verify_ep_exists(uint8_t ep, uint8_t* func_id) {
1777 uint8_t i;
1778 for (i = 0; i < getAudioCount(); i++) {
1779 if (audiod_fct_[i].p_desc) {
1780 // Get pointer at end
1781 uint8_t const* p_desc_end =
1782 audiod_fct_[i].p_desc + audiod_fct_[i].desc_length;
1783
1784 // Advance past AC descriptors - EP we look for are streaming EPs
1785 uint8_t const* p_desc = tu_desc_next(audiod_fct_[i].p_desc);
1786 p_desc += ((audio_desc_cs_ac_interface_t const*)p_desc)->wTotalLength;
1787
1788 // Condition modified from p_desc < p_desc_end to prevent gcc>=12
1789 // strict-overflow warning
1790 while (p_desc_end - p_desc > 0) {
1791 if (tu_desc_type(p_desc) == TUSB_DESC_ENDPOINT &&
1792 ((tusb_desc_endpoint_t const*)p_desc)->bEndpointAddress == ep) {
1793 *func_id = i;
1794 return true;
1795 }
1796 p_desc = tu_desc_next(p_desc);
1797 }
1798 }
1799 }
1800 return false;
1801 }
1802
1803 bool audiod_verify_itf_exists(uint8_t itf, uint8_t* func_id) {
1804 uint8_t i;
1805 for (i = 0; i < getAudioCount(); i++) {
1806 if (audiod_fct_[i].p_desc) {
1807 // Get pointer at beginning and end
1808 uint8_t const* p_desc = audiod_fct_[i].p_desc;
1809 uint8_t const* p_desc_end = audiod_fct_[i].p_desc +
1810 audiod_fct_[i].desc_length -
1811 TUD_AUDIO_DESC_IAD_LEN;
1812 // Condition modified from p_desc < p_desc_end to prevent gcc>=12
1813 // strict-overflow warning
1814 while (p_desc_end - p_desc > 0) {
1815 if (tu_desc_type(p_desc) == TUSB_DESC_INTERFACE &&
1816 ((tusb_desc_interface_t const*)audiod_fct_[i].p_desc)
1817 ->bInterfaceNumber == itf) {
1818 *func_id = i;
1819 return true;
1820 }
1821 p_desc = tu_desc_next(p_desc);
1822 }
1823 }
1824 }
1825 return false;
1826 }
1827
1828 void audiod_parse_flow_control_params(audiod_function_t* audio,
1829 uint8_t const* p_desc) {
1830 // Seed the TX sample rate from the configured AudioInfo so packet-size
1831 // calculation works even when the host never issues a SET_CUR(SAM_FREQ)
1832 // request (typical for single-frequency clock sources). The host may still
1833 // override this later via audiod_control_complete().
1834 if (audio->sample_rate_tx == 0)
1835 audio->sample_rate_tx = (uint32_t)config_.sample_rate;
1836
1837 p_desc = tu_desc_next(p_desc); // Exclude standard AS interface descriptor
1838 // of current alternate interface descriptor
1839
1840 // Look for a Class-Specific AS Interface Descriptor(4.9.2) to verify format
1841 // type and format and also to get number of physical channels
1842 if (tu_desc_type(p_desc) == TUSB_DESC_CS_INTERFACE &&
1843 tu_desc_subtype(p_desc) == AUDIO_CS_AS_INTERFACE_AS_GENERAL) {
1844 audio->n_channels_tx =
1845 ((audio_desc_cs_as_interface_t const*)p_desc)->bNrChannels;
1846 audio->format_type_tx =
1847 (audio_format_type_t)(((audio_desc_cs_as_interface_t const*)p_desc)
1848 ->bFormatType);
1849 // Look for a Type I Format Type Descriptor(2.3.1.6 - Audio Formats)
1850 p_desc = tu_desc_next(p_desc);
1851 if (tu_desc_type(p_desc) == TUSB_DESC_CS_INTERFACE &&
1852 tu_desc_subtype(p_desc) == AUDIO_CS_AS_INTERFACE_FORMAT_TYPE &&
1853 ((audio_desc_type_I_format_t const*)p_desc)->bFormatType ==
1854 AUDIO_FORMAT_TYPE_I) {
1855 audio->n_bytes_per_sample_tx =
1856 ((audio_desc_type_I_format_t const*)p_desc)->bSubslotSize;
1857 }
1858 }
1859
1860 // Fallback from config if descriptor parsing missed any field.
1861 // The descriptor struct layout may differ across TinyUSB versions.
1862 if (audio->n_channels_tx == 0) audio->n_channels_tx = config_.channels;
1863 if (audio->n_bytes_per_sample_tx == 0)
1864 audio->n_bytes_per_sample_tx = config_.bits_per_sample / 8;
1865 if (audio->format_type_tx == 0) audio->format_type_tx = AUDIO_FORMAT_TYPE_I;
1866 }
1867
1868 // This helper function finds for a given audio function and AS interface
1869 // number the index of the attached driver structure, the index of the
1870 // interface in the audio function
1871 // (e.g. the std. AS interface with interface number 15 is the first AS
1872 // interface for the given audio function and thus gets index zero), and
1873 // finally a pointer to the std. AS interface, where the pointer always points
1874 // to the first alternate setting i.e. alternate interface zero.
1875 bool audiod_get_AS_interface_index(uint8_t itf, audiod_function_t* audio,
1876 uint8_t* idxItf,
1877 uint8_t const** pp_desc_int) {
1878 if (audio->p_desc) {
1879 // Get pointer at end
1880 uint8_t const* p_desc_end =
1881 audio->p_desc + audio->desc_length - TUD_AUDIO_DESC_IAD_LEN;
1882
1883 // Advance past AC descriptors
1884 uint8_t const* p_desc = tu_desc_next(audio->p_desc);
1885 p_desc += ((audio_desc_cs_ac_interface_t const*)p_desc)->wTotalLength;
1886
1887 uint8_t tmp = 0;
1888 // Condition modified from p_desc < p_desc_end to prevent gcc>=12
1889 // strict-overflow warning
1890 while (p_desc_end - p_desc > 0) {
1891 // We assume the number of alternate settings is increasing thus we
1892 // return the index of alternate setting zero!
1893 if (tu_desc_type(p_desc) == TUSB_DESC_INTERFACE &&
1894 ((tusb_desc_interface_t const*)p_desc)->bAlternateSetting == 0) {
1895 if (((tusb_desc_interface_t const*)p_desc)->bInterfaceNumber == itf) {
1896 *idxItf = tmp;
1897 *pp_desc_int = p_desc;
1898 return true;
1899 }
1900 // Increase index, bytes read, and pointer
1901 tmp++;
1902 }
1903 p_desc = tu_desc_next(p_desc);
1904 }
1905 }
1906 return false;
1907 }
1908
1909 // This helper function finds for a given AS interface number the index of the
1910 // attached driver structure, the index of the interface in the audio function
1911 // (e.g. the std. AS interface with interface number 15 is the first AS
1912 // interface for the given audio function and thus gets index zero), and
1913 // finally a pointer to the std. AS interface, where the pointer always points
1914 // to the first alternate setting i.e. alternate interface zero.
1915 bool audiod_get_AS_interface_index_global(uint8_t itf, uint8_t* func_id,
1916 uint8_t* idxItf,
1917 uint8_t const** pp_desc_int) {
1918 // Loop over audio driver interfaces
1919 uint8_t i;
1920 for (i = 0; i < getAudioCount(); i++) {
1921 if (audiod_get_AS_interface_index(itf, &audiod_fct_[i], idxItf,
1922 pp_desc_int)) {
1923 *func_id = i;
1924 return true;
1925 }
1926 }
1927
1928 return false;
1929 }
1930
1931 bool audiod_get_interface(uint8_t rhport,
1932 tusb_control_request_t const* p_request) {
1933 uint8_t const itf = tu_u16_low(p_request->wIndex);
1934
1935 // Find index of audio streaming interface
1936 uint8_t func_id, idxItf;
1937 uint8_t const* dummy;
1938
1939 TU_VERIFY(
1940 audiod_get_AS_interface_index_global(itf, &func_id, &idxItf, &dummy));
1941 TU_VERIFY(tud_control_xfer(rhport, p_request,
1942 &audiod_fct_[func_id].alt_setting[idxItf], 1));
1943
1944 LOGI(" Get itf: %u - current alt: %u", itf,
1945 audiod_fct_[func_id].alt_setting[idxItf]);
1946
1947 return true;
1948 }
1949
1950 bool audiod_fb_send(audiod_function_t* audio) {
1951 bool apply_correction = (TUSB_SPEED_FULL == tud_speed_get()) &&
1952 audio->feedback.format_correction;
1953 // Format the feedback value
1954 if (apply_correction) {
1955 uint8_t* fb = (uint8_t*)audio->fb_buf.data();
1956
1957 // For FS format is 10.14
1958 *(fb++) = (audio->feedback.value >> 2) & 0xFF;
1959 *(fb++) = (audio->feedback.value >> 10) & 0xFF;
1960 *(fb++) = (audio->feedback.value >> 18) & 0xFF;
1961 *fb = 0;
1962 } else {
1963 audio->fb_buf[0] = audio->feedback.value;
1964 }
1965
1966 // About feedback format on FS
1967 //
1968 // 3 variables: Format | packetSize | sendSize | Working OS:
1969 // 16.16 4 4 Linux, Windows
1970 // 16.16 4 3 Linux
1971 // 16.16 3 4 Linux
1972 // 16.16 3 3 Linux
1973 // 10.14 4 4 Linux
1974 // 10.14 4 3 Linux
1975 // 10.14 3 4 Linux, OSX
1976 // 10.14 3 3 Linux, OSX
1977 //
1978 // We send 3 bytes since sending packet larger than wMaxPacketSize is pretty
1979 // ugly
1980 return TUSB_EDPT_XFER(audio->rhport, audio->ep_fb,
1981 (uint8_t*)audio->fb_buf.data(),
1982 apply_correction ? 3 : 4);
1983 }
1984
1985 // ── Close existing EPs for this interface ──────────────────────────────
1986 void closeEpIn(uint8_t rhport, audiod_function_t* audio, uint8_t itf,
1987 tusb_control_request_t const* p_request) {
1988 if (!isEpInEnabled() || audio->ep_in_as_intf_num != itf) return;
1989 audio->ep_in_as_intf_num = 0;
1990#ifndef TUP_DCD_EDPT_ISO_ALLOC
1991 usbd_edpt_close(rhport, audio->ep_in);
1992#endif
1993 tu_fifo_clear(&audio->ep_in_ff);
1994 bufferTx().reset();
1996 tud_audio_set_itf_close_EP_cb_(this, rhport, p_request);
1997 audio->ep_in = 0;
1999 audio->packet_sz_tx[0] = 0;
2000 audio->packet_sz_tx[1] = 0;
2001 audio->packet_sz_tx[2] = 0;
2002 }
2004 }
2005
2006 void closeEpOut(uint8_t rhport, audiod_function_t* audio, uint8_t itf,
2007 tusb_control_request_t const* p_request) {
2008 if (!isEpOutEnabled() || audio->ep_out_as_intf_num != itf) return;
2009 audio->ep_out_as_intf_num = 0;
2010#ifndef TUP_DCD_EDPT_ISO_ALLOC
2011 usbd_edpt_close(rhport, audio->ep_out);
2012#endif
2013 tu_fifo_clear(&audio->ep_out_ff);
2015 tud_audio_set_itf_close_EP_cb_(this, rhport, p_request);
2016 audio->ep_out = 0;
2017 if (isFeedbackEpEnabled()) {
2018 audio->ep_fb = 0;
2019 tu_memclr(&audio->feedback, sizeof(audio->feedback));
2020 }
2022 }
2023
2024 // ── Activate a single endpoint found in the descriptor ────────────────
2025 bool activateEndpoint(uint8_t rhport, tusb_desc_endpoint_t const* desc_ep,
2026 uint8_t dir = TUSB_DIR_IN) {
2027#ifdef TUP_DCD_EDPT_ISO_ALLOC
2028 // Skip iso_activate for isochronous OUT — on ESP32's DWC2 it blocks
2029 // for the entire playback duration. The endpoint DPRAM was already
2030 // allocated by iso_alloc in audiod_open(). The XFER call in
2031 // openEpOut will configure the DCD to receive.
2032 if (dir == TUSB_DIR_OUT &&
2033 desc_ep->bmAttributes.xfer == TUSB_XFER_ISOCHRONOUS)
2034 return true;
2035 return usbd_edpt_iso_activate(rhport, desc_ep);
2036#else
2037 (void)dir;
2038 return usbd_edpt_open(rhport, desc_ep);
2039#endif
2040 }
2041
2042 // ── Open the IN (TX) data endpoint ────────────────────────────────────
2043 void openEpIn(uint8_t rhport, audiod_function_t* audio, uint8_t itf,
2044 tusb_desc_endpoint_t const* desc_ep,
2045 uint8_t const* p_desc_for_params) {
2046 audio->ep_in = desc_ep->bEndpointAddress;
2047 audio->ep_in_as_intf_num = itf;
2048 audio->ep_in_sz = tu_edpt_packet_size(desc_ep);
2049 if (audio->ep_in_sz == 0) return;
2050
2052 audiod_parse_flow_control_params(audio, p_desc_for_params);
2053
2054 // Arm initial transfer (silence — copier fills the buffer).
2055 uint16_t first_pkt = packetSize();
2056 if (first_pkt > audio->ep_in_sz) first_pkt = audio->ep_in_sz;
2057 audio->lin_buf_in.assign(audio->ep_in_sz, 0);
2058 tx_xfer_armed_ = TUSB_EDPT_XFER(rhport, audio->ep_in,
2059 audio->lin_buf_in.data(), first_pkt);
2061 }
2062
2063 // ── Open the OUT (RX) data endpoint ───────────────────────────────────
2064 void openEpOut(uint8_t rhport, audiod_function_t* audio, uint8_t itf,
2065 tusb_desc_endpoint_t const* desc_ep) {
2066 LOGD("openEpOut: ep=0x%02x sz=%u", desc_ep->bEndpointAddress,
2067 tu_edpt_packet_size(desc_ep));
2068 audio->ep_out = desc_ep->bEndpointAddress;
2069 audio->ep_out_as_intf_num = itf;
2070 audio->ep_out_sz = tu_edpt_packet_size(desc_ep);
2071 if (audio->ep_out_sz == 0) return;
2072
2073 // iso_activate was done in audiod_open() (no traffic, instant).
2074 // Just arm the XFER here.
2075 if (isUseLinearBufferRx()) {
2076 if (audio->lin_buf_out.size() < audio->ep_out_sz)
2077 audio->lin_buf_out.assign(audio->ep_out_sz, 0);
2078 bool xfer_ok = TUSB_EDPT_XFER(rhport, audio->ep_out,
2079 audio->lin_buf_out.data(),
2080 audio->ep_out_sz);
2081 LOGD(" XFER armed: %s, buf=%p sz=%u", xfer_ok ? "OK" : "FAIL",
2082 audio->lin_buf_out.data(), audio->ep_out_sz);
2083 } else {
2084 bool xfer_ok = TUSB_EDPT_XFER_FIFO(rhport, audio->ep_out,
2085 &audio->ep_out_ff,
2086 audio->ep_out_sz);
2087 LOGD(" XFER_FIFO armed: %s", xfer_ok ? "OK" : "FAIL");
2088 }
2090 }
2091
2092 // ── Open the explicit feedback endpoint ───────────────────────────────
2093 void openEpFeedback(audiod_function_t* audio,
2094 tusb_desc_endpoint_t const* desc_ep) {
2095 audio->ep_fb = desc_ep->bEndpointAddress;
2096 audio->feedback.frame_shift = desc_ep->bInterval - 1;
2097 }
2098
2099 // ── Configure feedback computation parameters ─────────────────────────
2100 void setupFeedback(audiod_function_t* audio, uint8_t func_id, uint8_t alt) {
2101 if (!isFeedbackEpEnabled() || audio->ep_fb == 0) return;
2102
2103 audio_feedback_params_t fb_param = {};
2104 fb_param.method = AUDIO_FEEDBACK_METHOD_FIFO_COUNT;
2105 fb_param.sample_freq = config_.sample_rate;
2107 tud_audio_feedback_params_cb_(this, func_id, alt, &fb_param);
2108 audio->feedback.compute_method = fb_param.method;
2109
2110 if (TUSB_SPEED_FULL == tud_speed_get() &&
2112 audio->feedback.format_correction =
2114
2115 uint32_t const frame_div =
2116 (TUSB_SPEED_FULL == tud_speed_get()) ? 1000 : 8000;
2117 audio->feedback.min_value = ((fb_param.sample_freq - 1) / frame_div) << 16;
2118 audio->feedback.max_value = (fb_param.sample_freq / frame_div + 1) << 16;
2119
2120 switch (fb_param.method) {
2121 case AUDIO_FEEDBACK_METHOD_FREQUENCY_FIXED:
2122 case AUDIO_FEEDBACK_METHOD_FREQUENCY_FLOAT:
2123 case AUDIO_FEEDBACK_METHOD_FREQUENCY_POWER_OF_2:
2124 audiod_set_fb_params_freq(audio, fb_param.sample_freq,
2125 fb_param.frequency.mclk_freq);
2126 break;
2127 case AUDIO_FEEDBACK_METHOD_FIFO_COUNT: {
2128 // Use bufferRx() size — ep_out_ff may be uninitialized in linear buffer mode
2129 uint16_t fifo_depth = bufferRx().size();
2130 if (fifo_depth == 0) fifo_depth = 1; // guard against div-by-zero
2131 uint16_t fifo_lvl_thr = fifo_depth / 2;
2132 audio->feedback.compute.fifo_count.fifo_lvl_thr = fifo_lvl_thr;
2133 audio->feedback.compute.fifo_count.fifo_lvl_avg =
2134 ((uint32_t)fifo_lvl_thr) << 16;
2135 uint32_t nominal =
2136 ((fb_param.sample_freq / 100) << 16) / (frame_div / 100);
2137 audio->feedback.compute.fifo_count.nom_value = nominal;
2138 audio->feedback.compute.fifo_count.rate_const[0] =
2139 (uint16_t)((audio->feedback.max_value - nominal) / fifo_lvl_thr);
2140 audio->feedback.compute.fifo_count.rate_const[1] =
2141 (uint16_t)((nominal - audio->feedback.min_value) / fifo_lvl_thr);
2142 if (tud_speed_get() == TUSB_SPEED_HIGH) {
2143 audio->feedback.compute.fifo_count.rate_const[0] /= 8;
2144 audio->feedback.compute.fifo_count.rate_const[1] /= 8;
2145 }
2146 } break;
2147 default:
2148 break;
2149 }
2150 }
2151
2152 // ── Scan descriptor for endpoints and open them ───────────────────────
2153 bool openEndpointsForAltSetting(uint8_t rhport, audiod_function_t* audio,
2154 uint8_t func_id, uint8_t itf, uint8_t alt) {
2155 uint8_t const* p_desc = audio->p_desc;
2156 uint8_t const* p_desc_end =
2157 p_desc + audio->desc_length - TUD_AUDIO_DESC_IAD_LEN;
2158 LOGD(" openEPs: p_desc=%p end=%p len=%u itf=%u alt=%u",
2159 p_desc, p_desc_end, audio->desc_length, itf, alt);
2160
2161 while (p_desc_end - p_desc > 0) {
2162 if (tu_desc_type(p_desc) == TUSB_DESC_INTERFACE &&
2163 ((tusb_desc_interface_t const*)p_desc)->bInterfaceNumber == itf &&
2164 ((tusb_desc_interface_t const*)p_desc)->bAlternateSetting == alt) {
2165 uint8_t const* p_desc_for_params =
2166 (isEpInEnabled() && isEpInFlowControlEnabled()) ? p_desc : nullptr;
2167 uint8_t foundEPs = 0;
2168 uint8_t nEps = ((tusb_desc_interface_t const*)p_desc)->bNumEndpoints;
2169 LOGD(" matched itf=%u alt=%u nEps=%u", itf, alt, nEps);
2170
2171 while (foundEPs < nEps && (p_desc_end - p_desc > 0)) {
2172 LOGD(" scan: type=0x%02x len=%u offset=%d",
2173 p_desc[1], p_desc[0], (int)(p_desc - audio->p_desc));
2174 if (tu_desc_type(p_desc) == TUSB_DESC_ENDPOINT) {
2175 tusb_desc_endpoint_t const* desc_ep =
2176 (tusb_desc_endpoint_t const*)p_desc;
2177
2178 LOGD(" activating ep=0x%02x type=%u...",
2179 desc_ep->bEndpointAddress, desc_ep->bmAttributes.xfer);
2180 if (!activateEndpoint(rhport, desc_ep,
2181 tu_edpt_dir(desc_ep->bEndpointAddress))) {
2182 LOGD(" activateEndpoint FAILED");
2183 p_desc = tu_desc_next(p_desc);
2184 continue;
2185 }
2186 LOGD(" activated OK");
2187 // Skip clear_stall for isochronous OUT (iso_activate was also
2188 // skipped). For other endpoints, clear the stall as usual.
2189 if (!(tu_edpt_dir(desc_ep->bEndpointAddress) == TUSB_DIR_OUT &&
2190 desc_ep->bmAttributes.xfer == TUSB_XFER_ISOCHRONOUS))
2191 usbd_edpt_clear_stall(rhport, desc_ep->bEndpointAddress);
2192
2193 uint8_t ep_addr = desc_ep->bEndpointAddress;
2194 if (isEpInEnabled() && tu_edpt_dir(ep_addr) == TUSB_DIR_IN &&
2195 desc_ep->bmAttributes.usage == 0x00)
2196 openEpIn(rhport, audio, itf, desc_ep, p_desc_for_params);
2197
2198 if (isEpOutEnabled()) {
2199 if (tu_edpt_dir(ep_addr) == TUSB_DIR_OUT)
2200 openEpOut(rhport, audio, itf, desc_ep);
2201 if (isFeedbackEpEnabled() &&
2202 tu_edpt_dir(ep_addr) == TUSB_DIR_IN &&
2203 desc_ep->bmAttributes.usage == 1)
2204 openEpFeedback(audio, desc_ep);
2205 }
2206 foundEPs += 1;
2207 }
2208 p_desc = tu_desc_next(p_desc);
2209 }
2210
2211 if (foundEPs != nEps) return true; // ZLP already sent
2212
2213 if (tud_audio_set_itf_cb_) tud_audio_set_itf_cb_(this, rhport, nullptr);
2214
2215 setupFeedback(audio, func_id, alt);
2216 return true;
2217 }
2218 p_desc = tu_desc_next(p_desc);
2219 }
2220 return true;
2221 }
2222
2223 // ── Main SET_INTERFACE handler ────────────────────────────────────────
2224 bool audiod_set_interface(uint8_t rhport,
2225 tusb_control_request_t const* p_request) {
2226 uint8_t const itf = tu_u16_low(p_request->wIndex);
2227 uint8_t const alt = tu_u16_low(p_request->wValue);
2228 LOGD("SET_ITF itf=%u alt=%u [start]", itf, alt);
2229
2230 uint8_t func_id, idxItf;
2231 uint8_t const* p_desc;
2232 if (!audiod_get_AS_interface_index_global(itf, &func_id, &idxItf,
2233 &p_desc)) {
2234 LOGD(" AS interface %u not found", itf);
2235 tud_control_status(rhport, p_request);
2236 return true;
2237 }
2238 LOGD(" found func=%u idx=%u", func_id, idxItf);
2239
2240 audiod_function_t* audio = &audiod_fct_[func_id];
2241
2242 // 1. Close existing EPs
2243 LOGD(" close EPs");
2244 closeEpIn(rhport, audio, itf, p_request);
2245 closeEpOut(rhport, audio, itf, p_request);
2246
2247 // 2. Save alt setting and acknowledge
2248 audio->alt_setting[idxItf] = alt;
2249
2250 tud_control_status(rhport, p_request);
2251 openEndpointsForAltSetting(rhport, audio, func_id, itf, alt);
2252
2253 // 4. Update SOF and flow control
2254 if (isFeedbackEpEnabled()) {
2255 bool enable_sof = false;
2256 for (uint8_t i = 0; i < getAudioCount(); i++) {
2257 if (audiod_fct_[i].ep_fb != 0) {
2258 enable_sof = true;
2259 break;
2260 }
2261 }
2262 usbd_sof_enable(rhport, SOF_CONSUMER_AUDIO, enable_sof);
2263 }
2266
2267 return true;
2268 }
2269
2270 bool audiod_set_fb_params_freq(audiod_function_t* audio, uint32_t sample_freq,
2271 uint32_t mclk_freq) {
2272 // Check if frame interval is within sane limits
2273 // The interval value n_frames was taken from the descriptors within
2274
2275 // n_frames_min is ceil(2^10 * f_s / f_m) for full speed and ceil(2^13 * f_s
2276 // / f_m) for high speed this lower limit ensures the measures feedback
2277 // value has sufficient precision
2278 uint32_t const k = (TUSB_SPEED_FULL == tud_speed_get()) ? 10 : 13;
2279 uint32_t const n_frame = (1UL << audio->feedback.frame_shift);
2280
2281 if ((((1UL << k) * sample_freq / mclk_freq) + 1) > n_frame) {
2282 LOGE(" UAC2 feedback interval too small");
2283 TU_BREAKPOINT();
2284 return false;
2285 }
2286
2287 // Check if parameters really allow for a power of two division
2288 if ((mclk_freq % sample_freq) == 0 &&
2289 tu_is_power_of_two(mclk_freq / sample_freq)) {
2290 audio->feedback.compute_method =
2291 AUDIO_FEEDBACK_METHOD_FREQUENCY_POWER_OF_2;
2292 audio->feedback.compute.power_of_2 =
2293 (uint8_t)(16 - (audio->feedback.frame_shift - 1) -
2294 tu_log2(mclk_freq / sample_freq));
2295 } else if (audio->feedback.compute_method ==
2296 AUDIO_FEEDBACK_METHOD_FREQUENCY_FLOAT) {
2297 audio->feedback.compute.float_const =
2298 (float)sample_freq / (float)mclk_freq *
2299 (1UL << (16 - (audio->feedback.frame_shift - 1)));
2300 } else {
2301 audio->feedback.compute.fixed.sample_freq = sample_freq;
2302 audio->feedback.compute.fixed.mclk_freq = mclk_freq;
2303 }
2304
2305 return true;
2306 }
2307
2308 bool audiod_calc_tx_packet_sz(audiod_function_t* audio) {
2309 TU_VERIFY(audio->format_type_tx == AUDIO_FORMAT_TYPE_I);
2310 TU_VERIFY(audio->n_channels_tx);
2311 TU_VERIFY(audio->n_bytes_per_sample_tx);
2312 TU_VERIFY(audio->interval_tx);
2313 TU_VERIFY(audio->sample_rate_tx);
2314
2315 // Restart the fractional accumulator for this streaming session.
2316 audio->tx_sample_acc = 0;
2317
2318 const uint8_t interval = (tud_speed_get() == TUSB_SPEED_FULL)
2319 ? audio->interval_tx
2320 : 1 << (audio->interval_tx - 1);
2321
2322 const uint16_t sample_normimal =
2323 (uint16_t)(audio->sample_rate_tx * interval /
2324 ((tud_speed_get() == TUSB_SPEED_FULL) ? 1000 : 8000));
2325 const uint16_t sample_reminder =
2326 (uint16_t)(audio->sample_rate_tx * interval %
2327 ((tud_speed_get() == TUSB_SPEED_FULL) ? 1000 : 8000));
2328
2329 const uint16_t packet_sz_tx_min =
2330 (uint16_t)((sample_normimal - 1) * audio->n_channels_tx *
2331 audio->n_bytes_per_sample_tx);
2332 const uint16_t packet_sz_tx_norm =
2333 (uint16_t)(sample_normimal * audio->n_channels_tx *
2334 audio->n_bytes_per_sample_tx);
2335 const uint16_t packet_sz_tx_max =
2336 (uint16_t)((sample_normimal + 1) * audio->n_channels_tx *
2337 audio->n_bytes_per_sample_tx);
2338
2339 // Endpoint size must larger than packet size
2340 TU_ASSERT(packet_sz_tx_max <= audio->ep_in_sz);
2341
2342 // Frmt20.pdf 2.3.1.1 USB Packets
2343 if (sample_reminder) {
2344 // All virtual frame packets must either contain INT(nav) audio slots
2345 // (small VFP) or INT(nav)+1 (large VFP) audio slots
2346 audio->packet_sz_tx[0] = packet_sz_tx_norm;
2347 audio->packet_sz_tx[1] = packet_sz_tx_norm;
2348 audio->packet_sz_tx[2] = packet_sz_tx_max;
2349 } else {
2350 // In the case where nav = INT(nav), ni may vary between INT(nav)-1 (small
2351 // VFP), INT(nav) (medium VFP) and INT(nav)+1 (large VFP).
2352 audio->packet_sz_tx[0] = packet_sz_tx_min;
2353 audio->packet_sz_tx[1] = packet_sz_tx_norm;
2354 audio->packet_sz_tx[2] = packet_sz_tx_max;
2355 }
2356
2357 return true;
2358 }
2359
2360 // Number of audio bytes to transmit in the current (micro)frame when IN
2361 // flow control is enabled. A fractional accumulator distributes the
2362 // sub-frame sample remainder over successive frames so the long-term average
2363 // matches the configured sample rate (e.g. alternating 176/180 bytes for
2364 // 44100 Hz stereo 16-bit, averaging 176.4 bytes = 44.1 samples per frame).
2365 uint16_t audiod_tx_packet_size_fc(audiod_function_t* audio) {
2366 if (audio->sample_rate_tx == 0 || audio->n_channels_tx == 0 ||
2367 audio->n_bytes_per_sample_tx == 0) {
2368 // Not enough info to size precisely: fall back to the max packet.
2369 return audio->ep_in_sz;
2370 }
2371 const uint32_t denom = (tud_speed_get() == TUSB_SPEED_FULL) ? 1000u : 8000u;
2372 const uint8_t iv = audio->interval_tx ? audio->interval_tx : 1;
2373 const uint32_t interval =
2374 (tud_speed_get() == TUSB_SPEED_FULL) ? iv : (1u << (iv - 1));
2375
2376 audio->tx_sample_acc += audio->sample_rate_tx * interval;
2377 const uint32_t samples = audio->tx_sample_acc / denom;
2378 audio->tx_sample_acc -= samples * denom;
2379
2380 uint32_t bytes =
2381 samples * audio->n_channels_tx * audio->n_bytes_per_sample_tx;
2382 if (bytes > audio->ep_in_sz) bytes = audio->ep_in_sz;
2383 return (uint16_t)bytes;
2384 }
2385
2386 uint16_t tud_audio_n_write(uint8_t func_id, const void* data, uint16_t len) {
2387 TU_VERIFY(func_id < getAudioCount() && audiod_fct_[func_id].p_desc != NULL);
2388 // Always write to the FIFO. In linear-buffer mode the xfer_cb drains the
2389 // FIFO into lin_buf_in after DMA has finished, so there is no race.
2390 return tu_fifo_write_n(&audiod_fct_[func_id].ep_in_ff, data, len);
2391 }
2392
2393 uint16_t tud_audio_n_available(uint8_t func_id) {
2394 TU_VERIFY(func_id < getAudioCount() && audiod_fct_[func_id].p_desc != NULL);
2395 return tu_fifo_count(&audiod_fct_[func_id].ep_out_ff);
2396 }
2397
2398 uint16_t tud_audio_n_read(uint8_t func_id, void* buffer, uint16_t bufsize) {
2399 TU_VERIFY(func_id < getAudioCount() && audiod_fct_[func_id].p_desc != NULL);
2400 return tu_fifo_read_n(&audiod_fct_[func_id].ep_out_ff, buffer, bufsize);
2401 }
2402};
2403
2404} // namespace audio_tools
2405
2406// Custom driver registration — routes to whichever USBAudioDeviceBase subclass
2407// was constructed (base or derived; set via s_active_ in the constructor).
2408extern "C" usbd_class_driver_t const* usbd_app_driver_get_cb(uint8_t* count) {
2410 count);
2411}
#define LOGW(...)
Definition AudioLoggerIDF.h:29
#define LOGI(...)
Definition AudioLoggerIDF.h:28
#define LOGD(...)
Definition AudioLoggerIDF.h:27
#define LOGE(...)
Definition AudioLoggerIDF.h:30
#define AUDIO_CS_CTRL_CLK_VALID
Definition USBAudioDeviceBase.h:74
#define TUSB_EDPT_XFER(rp, ep, buf, sz)
Definition USBAudioDeviceBase.h:64
#define TUSB_FIFO_CONFIG(f, buf, d, ov)
Definition USBAudioDeviceBase.h:66
#define AUDIO_FU_CTRL_VOLUME
Definition USBAudioDeviceBase.h:86
#define AUDIO_FU_CTRL_MUTE
Definition USBAudioDeviceBase.h:83
#define AUDIO_CS_REQ_RANGE
Definition USBAudioDeviceBase.h:77
#define USB_DESCR_MAX_LEN
Definition USBAudioDeviceBase.h:37
usbd_class_driver_t const * usbd_app_driver_get_cb(uint8_t *count)
Definition USBAudioDeviceBase.h:2408
#define TUSB_EDPT_XFER_FIFO(rp, ep, ff, sz)
Definition USBAudioDeviceBase.h:65
void notifyAudioChange(AudioInfo info)
Definition AudioTypes.h:174
Base class for all Audio Streams. It support the boolean operator to test if the object is ready with...
Definition BaseStream.h:120
AudioInfo info
Definition BaseStream.h:171
virtual void setAudioInfo(AudioInfo newInfo) override
Defines the input AudioInfo.
Definition BaseStream.h:128
Shared functionality of all buffers.
Definition Buffers.h:23
virtual int readArray(T data[], int len)
reads multiple values
Definition Buffers.h:34
virtual void reset()=0
clears the buffer
virtual int writeArray(const T data[], int len)
Fills the buffer data.
Definition Buffers.h:56
virtual size_t size()=0
virtual int availableForWrite()=0
provides the number of entries that are available to write
virtual int available()=0
provides the number of entries that are available to read
USB Audio Class 2.0 descriptor generator.
Definition USBAudio2DescriptorBuilder.h:30
const uint16_t buildFullDescriptor(uint8_t *desc)
Definition USBAudio2DescriptorBuilder.h:54
static constexpr uint8_t ENTITY_FU2
second Feature Unit (RXTX)
Definition USBAudio2DescriptorBuilder.h:40
static constexpr uint8_t ENTITY_FU1
first Feature Unit
Definition USBAudio2DescriptorBuilder.h:37
int audioFunctionsCount() const
Definition USBAudio2DescriptorBuilder.h:145
static constexpr uint8_t ENTITY_CLOCK
Definition USBAudio2DescriptorBuilder.h:35
uint16_t calcPacketSizeForRate(uint32_t rate) const
Definition USBAudio2DescriptorBuilder.h:163
uint16_t calcMaxPacketSize() const
Definition USBAudio2DescriptorBuilder.h:175
bool enableFeedbackEp() const
Definition USBAudio2DescriptorBuilder.h:154
USB Audio Device class for audio streaming over USB.
Definition USBAudioDeviceBase.h:111
std::function< void(float, uint8_t)> volume_cb_
Definition USBAudioDeviceBase.h:831
std::function< void(USBAudioDeviceBase *, uint8_t func_id, uint8_t alt_itf, audio_feedback_params_t *feedback_param)> tud_audio_feedback_params_cb_
Definition USBAudioDeviceBase.h:877
bool isUseLinearBufferRx() const
Definition USBAudioDeviceBase.h:1074
bool begin(const USBAudioConfig &cfg)
Apply a config and start the USB audio device.
Definition USBAudioDeviceBase.h:300
uint16_t audiod_open(uint8_t rhport, tusb_desc_interface_t const *itf_desc, uint16_t max_len)
Definition USBAudioDeviceBase.h:1242
void setRxDoneCallback(std::function< bool(USBAudioDeviceBase *, uint8_t, audiod_function_t *, uint16_t)> cb)
Register a callback for RX done events.
Definition USBAudioDeviceBase.h:553
bool setVolume(float vol, uint8_t channel)
Set the volume for a channel and notify the host.
Definition USBAudioDeviceBase.h:411
void openEpFeedback(audiod_function_t *audio, tusb_desc_endpoint_t const *desc_ep)
Definition USBAudioDeviceBase.h:2093
volatile uint32_t xfer_cb_rx_count_
Definition USBAudioDeviceBase.h:823
volatile uint32_t xfer_cb_tx_count_
Definition USBAudioDeviceBase.h:821
USBAudioDeviceBase(USBAudioConfig cfg)
Constructor which provides configuration at construction time.
Definition USBAudioDeviceBase.h:227
static constexpr int16_t kVolumeMinDb256
Convert AudioTools volume (0.0–1.0) to UAC2 int16 (1/256 dB). 0.0 maps to 0x8000 (silence),...
Definition USBAudioDeviceBase.h:1003
std::vector< float > volume_
Definition USBAudioDeviceBase.h:829
bool isUseLinearBufferTx() const
Definition USBAudioDeviceBase.h:1076
bool isEpInEnabled() const
Returns true if the IN endpoint is enabled.
Definition USBAudioDeviceBase.h:375
std::function< bool(USBAudioDeviceBase *, uint8_t rhport, tusb_control_request_t const *p_request)> tud_audio_set_itf_close_EP_cb_
Definition USBAudioDeviceBase.h:873
bool audiod_fb_send(audiod_function_t *audio)
Definition USBAudioDeviceBase.h:1950
void setVolumeCallback(std::function< void(float, uint8_t)> cb)
Register a callback invoked when the host (or device) changes the volume.
Definition USBAudioDeviceBase.h:444
void setTxDoneCallback(std::function< bool(USBAudioDeviceBase *, uint8_t, audiod_function_t *)> cb)
Register a callback for TX done events.
Definition USBAudioDeviceBase.h:543
bool isMute(uint8_t channel=0) const
Returns the current mute state for the given channel.
Definition USBAudioDeviceBase.h:423
bool audiod_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request)
Definition USBAudioDeviceBase.h:1392
void setReqEntityCallback(std::function< bool(USBAudioDeviceBase *, uint8_t)> cb)
Register a callback for entity requests.
Definition USBAudioDeviceBase.h:563
std::function< bool(USBAudioDeviceBase *, uint8_t rhport, tusb_control_request_t const *p_request, uint8_t *pBuff)> tud_audio_set_req_entity_cb_
Definition USBAudioDeviceBase.h:861
bool isFeatureUnit(uint8_t id) const
Returns true if the given entity ID is a Feature Unit (FU1 or FU2).
Definition USBAudioDeviceBase.h:1024
void setAudioFeedbackFormatCorrectionCallback(std::function< bool(USBAudioDeviceBase *, uint8_t)> cb)
Register a callback for audio feedback format correction events.
Definition USBAudioDeviceBase.h:637
static int16_t floatToUac2(float vol)
Convert linear volume (0.0–1.0) to UAC2 int16 (1/256 dB). Linear mapping: 0.0 → -100 dB (min),...
Definition USBAudioDeviceBase.h:1007
uint32_t getTxSampleRate() const
TX sample rate parsed from the descriptor (must be non-zero for flow control).
Definition USBAudioDeviceBase.h:801
uint16_t getEpInSwBufSz(uint8_t fn) const
Definition USBAudioDeviceBase.h:1065
std::function< bool(USBAudioDeviceBase *, uint8_t rhport, audiod_function_t *)> tx_done_cb_
Definition USBAudioDeviceBase.h:840
std::function< void(USBAudioDeviceBase *, uint8_t func_id)> fb_done_cb_
Definition USBAudioDeviceBase.h:854
void setGetReqItfCallback(std::function< bool(USBAudioDeviceBase *, uint8_t, tusb_control_request_t const *)> cb)
Register a callback for GET requests on the interface.
Definition USBAudioDeviceBase.h:506
float volume(uint8_t channel)
Returns the current volume for the given channel.
Definition USBAudioDeviceBase.h:403
bool audiod_get_AS_interface_index_global(uint8_t itf, uint8_t *func_id, uint8_t *idxItf, uint8_t const **pp_desc_int)
Definition USBAudioDeviceBase.h:1915
static USBAudioDeviceBase * s_active_
Definition USBAudioDeviceBase.h:899
bool isStreamingActiveTx() const
Returns true if the host has opened the IN (capture) stream.
Definition USBAudioDeviceBase.h:476
uint16_t getDescLen(uint8_t fn) const
Definition USBAudioDeviceBase.h:1070
virtual BaseBuffer< uint8_t > & bufferTx()=0
Returns the TX audio buffer. Must be overridden by subclasses.
virtual void serviceTinyUSB()
Process pending USB events on platforms where the application drives the stack (RP2040,...
Definition USBAudioDeviceBase.h:914
USBAudioConfig config_
Definition USBAudioDeviceBase.h:835
void audiod_parse_flow_control_params(audiod_function_t *audio, uint8_t const *p_desc)
Definition USBAudioDeviceBase.h:1828
void setIntDoneCallback(std::function< void(USBAudioDeviceBase *, uint8_t)> cb)
Register a callback for interrupt done events.
Definition USBAudioDeviceBase.h:534
void setConfig(const USBAudioConfig &cfg)
Set the USB audio configuration (use begin(cfg) instead).
Definition USBAudioDeviceBase.h:902
volatile uint32_t tx_fifo_read_total_
Definition USBAudioDeviceBase.h:822
uint32_t getRxXferCount() const
Number of times audiod_xfer_cb fired for the OUT endpoint.
Definition USBAudioDeviceBase.h:788
bool is_started_
Definition USBAudioDeviceBase.h:818
void audiod_init(void)
Definition USBAudioDeviceBase.h:1175
void setReqItfCallback(std::function< bool(USBAudioDeviceBase *, uint8_t, tusb_control_request_t const *, uint8_t *)> cb)
Register a callback for interface set requests.
Definition USBAudioDeviceBase.h:594
volatile uint32_t rx_total_bytes_
Definition USBAudioDeviceBase.h:824
void setItfCloseEpCallback(std::function< bool(USBAudioDeviceBase *, uint8_t, tusb_control_request_t const *)> cb)
Register a callback for interface close endpoint events.
Definition USBAudioDeviceBase.h:616
bool usb_task_active_
Definition USBAudioDeviceBase.h:819
bool tx_xfer_armed_
Definition USBAudioDeviceBase.h:820
void setTudAudioSetItfCallback(std::function< bool(USBAudioDeviceBase *, uint8_t, tusb_control_request_t const *)> cb)
Register a callback for interface set requests.
Definition USBAudioDeviceBase.h:572
volatile uint16_t tx_frame_bytes_last_
Definition USBAudioDeviceBase.h:825
uint32_t getRxTotalBytes() const
Total bytes received from host via OUT endpoint.
Definition USBAudioDeviceBase.h:790
bool begin()
(Re-)start the USB audio device with the current config.
Definition USBAudioDeviceBase.h:317
void closeEpOut(uint8_t rhport, audiod_function_t *audio, uint8_t itf, tusb_control_request_t const *p_request)
Definition USBAudioDeviceBase.h:2006
bool isStreamingActiveRx() const
Returns true if the host has opened the OUT (playback) stream.
Definition USBAudioDeviceBase.h:484
bool setVolume(float volume) override
sets the volume for the master channel (channel 0)
Definition USBAudioDeviceBase.h:398
TU_ATTR_FAST_FUNC void audiod_sof_isr(uint8_t rhport, uint32_t frame_count)
Definition USBAudioDeviceBase.h:1548
uint8_t getAudioCount() const
Returns the number of audio functions (always 1).
Definition USBAudioDeviceBase.h:497
int available() override
Bytes of received audio waiting in the RX buffer.
Definition USBAudioDeviceBase.h:685
bool audiod_deinit(void)
Definition USBAudioDeviceBase.h:1222
bool is_active_
Definition USBAudioDeviceBase.h:827
uint16_t tud_audio_n_read(uint8_t func_id, void *buffer, uint16_t bufsize)
Definition USBAudioDeviceBase.h:2398
std::function< void(bool, bool)> streaming_state_cb_
Definition USBAudioDeviceBase.h:834
void sendInterruptNotification(uint8_t ctrlSel, uint8_t channel, uint8_t entityID)
Send a UAC2 status/change notification via the AC interrupt EP.
Definition USBAudioDeviceBase.h:1036
float volume() override
gets the volume for the master channel (channel 0)
Definition USBAudioDeviceBase.h:395
bool audiod_get_interface(uint8_t rhport, tusb_control_request_t const *p_request)
Definition USBAudioDeviceBase.h:1931
static bool isValidBitsPerSample(uint8_t bps)
Definition USBAudioDeviceBase.h:1078
bool audiod_calc_tx_packet_sz(audiod_function_t *audio)
Definition USBAudioDeviceBase.h:2308
std::function< bool(USBAudioDeviceBase *, uint8_t func_id)> req_entity_cb_
Definition USBAudioDeviceBase.h:855
void setReqEpCallback(std::function< bool(USBAudioDeviceBase *, uint8_t, tusb_control_request_t const *, uint8_t *)> cb)
Register a callback for endpoint set requests.
Definition USBAudioDeviceBase.h:605
void setMuteCallback(std::function< void(bool, uint8_t)> cb)
Register a callback invoked when the host (or device) changes the mute state.
Definition USBAudioDeviceBase.h:451
virtual BaseBuffer< uint8_t > & bufferRx()=0
Returns the RX audio buffer. Must be overridden by subclasses.
void setFbDoneCallback(std::function< void(USBAudioDeviceBase *, uint8_t)> cb)
Register a callback for feedback done events.
Definition USBAudioDeviceBase.h:526
std::function< void(uint32_t)> sample_rate_cb_
Definition USBAudioDeviceBase.h:833
USBAudioDeviceBase()
Default Constructor.
Definition USBAudioDeviceBase.h:224
void alloc_mutex()
Definition USBAudioDeviceBase.h:1214
int availableForWrite() override
Bytes of free space in the TX buffer.
Definition USBAudioDeviceBase.h:691
bool audiod_verify_entity_exists(uint8_t itf, uint8_t entityID, uint8_t *func_id)
Definition USBAudioDeviceBase.h:1743
bool isFifoMutexEnabled() const
Returns true if FIFO mutex is enabled.
Definition USBAudioDeviceBase.h:494
void processVolume(uint8_t *data, size_t len)
Process audio data for volume control.
Definition USBAudioDeviceBase.h:925
bool mounted() const
Returns true if the device is mounted by the USB host.
Definition USBAudioDeviceBase.h:500
virtual bool beginUSB()=0
Override in platform subclasses to register descriptors and start the USB host-controller stack (e....
bool handleFeatureUnitGet(uint8_t rhport, tusb_control_request_t const *p_request, uint8_t *cb)
Definition USBAudioDeviceBase.h:1614
uint16_t getDescriptor(uint8_t *desc)
Returns the audio-function descriptor block for use in tud_descriptor_configuration_cb().
Definition USBAudioDeviceBase.h:727
static constexpr size_t getResetSize()
Definition USBAudioDeviceBase.h:1102
void setSampleRate(uint32_t rate)
Device-initiated sample rate change.
Definition USBAudioDeviceBase.h:974
void tud_audio_feedback_interval_isr(uint8_t func_id, uint32_t, uint8_t frame_shift)
Definition USBAudioDeviceBase.h:1109
uint16_t tud_audio_n_write(uint8_t func_id, const void *data, uint16_t len)
Definition USBAudioDeviceBase.h:2386
std::function< void(bool, uint8_t)> mute_cb_
Definition USBAudioDeviceBase.h:832
static float uac2ToFloat(int16_t v)
Convert UAC2 int16 (1/256 dB) to linear volume (0.0–1.0). Linear mapping within the -100....
Definition USBAudioDeviceBase.h:1015
void audiod_reset(uint8_t rhport)
Definition USBAudioDeviceBase.h:1226
std::function< bool(USBAudioDeviceBase *, uint8_t func_id)> tud_audio_feedback_format_correction_cb_
Definition USBAudioDeviceBase.h:880
usbd_class_driver_t const * getClassDriver(uint8_t *count)
Get the USB device class driver for TinyUSB integration.
Definition USBAudioDeviceBase.h:747
bool openEndpointsForAltSetting(uint8_t rhport, audiod_function_t *audio, uint8_t func_id, uint8_t itf, uint8_t alt)
Definition USBAudioDeviceBase.h:2153
bool audiod_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes)
TODO refactor control request handling to separate function and reduce nesting.
Definition USBAudioDeviceBase.h:1480
bool isInterruptEpEnabled() const
Returns true if the interrupt endpoint is enabled.
Definition USBAudioDeviceBase.h:491
uint32_t getTxFifoReadTotal() const
Definition USBAudioDeviceBase.h:793
uint8_t numInterfaces() const
Total number of USB interfaces claimed by the audio function (1 AC + 1 or 2 AS), for use in the bNumI...
Definition USBAudioDeviceBase.h:737
std::vector< uint16_t > desc_len_
Definition USBAudioDeviceBase.h:888
std::function< bool(USBAudioDeviceBase *, uint8_t rhport, tusb_control_request_t const *p_request)> tud_audio_set_itf_cb_
Definition USBAudioDeviceBase.h:858
virtual void resizeBuffers()=0
Resize the platform audio buffers. Both platforms use NBuffer-style block pools: block size = max USB...
std::function< bool(USBAudioDeviceBase *, uint8_t rhport, audiod_function_t *, uint16_t xferred_bytes)> rx_done_cb_
Definition USBAudioDeviceBase.h:843
bool isEpInFlowControlEnabled() const
Returns true if IN endpoint flow control is enabled. When on, the per-frame isochronous packet size i...
Definition USBAudioDeviceBase.h:388
std::function< bool(USBAudioDeviceBase *, uint8_t rhport, tusb_control_request_t const *p_request, uint8_t *pBuff)> tud_audio_set_req_itf_cb_
Definition USBAudioDeviceBase.h:865
void end()
Stop audio streaming and clear FIFOs. Does not disconnect USB.
Definition USBAudioDeviceBase.h:700
uint16_t fifoSize() const
Definition USBAudioDeviceBase.h:1091
void openEpIn(uint8_t rhport, audiod_function_t *audio, uint8_t itf, tusb_desc_endpoint_t const *desc_ep, uint8_t const *p_desc_for_params)
Definition USBAudioDeviceBase.h:2043
USBAudioConfig active_config_
Definition USBAudioDeviceBase.h:836
std::function< bool(USBAudioDeviceBase *, uint8_t rhport, tusb_control_request_t const *)> get_req_itf_cb_
Definition USBAudioDeviceBase.h:847
bool handleEntityRequest(uint8_t rhport, tusb_control_request_t const *p_request, uint8_t entityID)
Definition USBAudioDeviceBase.h:1644
bool isTxXferArmed() const
True if the initial isochronous IN transfer was armed successfully.
Definition USBAudioDeviceBase.h:783
void setSampleRateCallback(std::function< void(uint32_t)> cb)
Register a callback invoked when the host (or device) changes the sample rate.
Definition USBAudioDeviceBase.h:458
uint32_t getTxXferCount() const
Number of times audiod_xfer_cb fired for the IN endpoint.
Definition USBAudioDeviceBase.h:786
USBAudioConfig defaultConfig(RxTxMode mode=RXTX_MODE)
Returns a default configuration pre-filled for the requested direction (RX_MODE, TX_MODE,...
Definition USBAudioDeviceBase.h:236
bool audiod_set_interface(uint8_t rhport, tusb_control_request_t const *p_request)
Definition USBAudioDeviceBase.h:2224
void setupFeedback(audiod_function_t *audio, uint8_t func_id, uint8_t alt)
Definition USBAudioDeviceBase.h:2100
std::vector< osal_mutex_def_t > ep_out_ff_mutex_rd_
Definition USBAudioDeviceBase.h:895
void openEpOut(uint8_t rhport, audiod_function_t *audio, uint8_t itf, tusb_desc_endpoint_t const *desc_ep)
Definition USBAudioDeviceBase.h:2064
static USBAudioDeviceBase & activeInstance()
Returns the most-recently-constructed instance (base or subclass).
Definition USBAudioDeviceBase.h:372
bool audiod_get_AS_interface_index(uint8_t itf, audiod_function_t *audio, uint8_t *idxItf, uint8_t const **pp_desc_int)
Definition USBAudioDeviceBase.h:1875
uint16_t getEpOutSwBufSz(uint8_t fn) const
Definition USBAudioDeviceBase.h:1060
uint8_t getTxInterval() const
TX isochronous interval (bInterval) parsed from the descriptor.
Definition USBAudioDeviceBase.h:813
bool handleClockSourceGet(uint8_t rhport, tusb_control_request_t const *p_request, uint8_t *cb)
Definition USBAudioDeviceBase.h:1569
std::function< bool(USBAudioDeviceBase *, uint8_t rhport, tusb_control_request_t const *)> get_req_ep_cb_
Definition USBAudioDeviceBase.h:851
uint16_t audiod_tx_packet_size_fc(audiod_function_t *audio)
Definition USBAudioDeviceBase.h:2365
bool audiod_control_complete(uint8_t rhport, tusb_control_request_t const *p_request)
Definition USBAudioDeviceBase.h:1403
void setAudioInfo(AudioInfo info) override
Change the sample rate and notify the host.
Definition USBAudioDeviceBase.h:258
bool audiod_control_request(uint8_t rhport, tusb_control_request_t const *p_request)
Definition USBAudioDeviceBase.h:1708
std::vector< uint16_t > ep_in_sw_buf_sz_
Definition USBAudioDeviceBase.h:886
volatile uint32_t tx_xferred_last_
Definition USBAudioDeviceBase.h:826
std::vector< uint16_t > ctrl_buf_sz_
Definition USBAudioDeviceBase.h:890
std::vector< uint16_t > ep_out_sw_buf_sz_
Definition USBAudioDeviceBase.h:883
uint16_t getTxFrameBytesLast() const
Definition USBAudioDeviceBase.h:796
std::function< void(USBAudioDeviceBase *, uint8_t rhport)> int_done_cb_
Definition USBAudioDeviceBase.h:838
uint16_t packetSize() const
Definition USBAudioDeviceBase.h:1088
bool isFeedbackEpEnabled() const
Returns true if the feedback endpoint is enabled. Only meaningful in pure RX (OUT-only) mode: with an...
Definition USBAudioDeviceBase.h:383
bool handleEndpointRequest(uint8_t rhport, tusb_control_request_t const *p_request)
Definition USBAudioDeviceBase.h:1693
float getVolumeExt(uint8_t channel) const
Returns the effective volume for a per-channel index (1-based). Combines master volume (index 0) with...
Definition USBAudioDeviceBase.h:947
uint16_t tud_audio_n_available(uint8_t func_id)
Definition USBAudioDeviceBase.h:2393
void setGetReqEpCallback(std::function< bool(USBAudioDeviceBase *, uint8_t, tusb_control_request_t const *)> cb)
Register a callback for GET requests on an endpoint.
Definition USBAudioDeviceBase.h:516
std::vector< bool > mute_
Definition USBAudioDeviceBase.h:830
uint8_t int_notify_buf_[6]
Definition USBAudioDeviceBase.h:881
bool audiod_verify_itf_exists(uint8_t itf, uint8_t *func_id)
Definition USBAudioDeviceBase.h:1803
bool isStreamingActive() const
Returns true if either IN or OUT streaming endpoint is open.
Definition USBAudioDeviceBase.h:471
void setReqEntityCallback(std::function< bool(USBAudioDeviceBase *, uint8_t, tusb_control_request_t const *, uint8_t *)> cb)
Register a callback for entity set requests.
Definition USBAudioDeviceBase.h:583
void setAudioFeedbackParamsCallback(std::function< void(USBAudioDeviceBase *, uint8_t, uint8_t, audio_feedback_params_t *)> cb)
Register a callback for audio feedback parameter events.
Definition USBAudioDeviceBase.h:626
void setStreamingStateCallback(std::function< void(bool, bool)> cb)
Register a callback invoked when the streaming state changes. Fires when the host opens or closes a s...
Definition USBAudioDeviceBase.h:466
bool audiod_set_fb_params_freq(audiod_function_t *audio, uint32_t sample_freq, uint32_t mclk_freq)
Definition USBAudioDeviceBase.h:2270
bool audiod_verify_ep_exists(uint8_t ep, uint8_t *func_id)
Definition USBAudioDeviceBase.h:1776
void notifyStreamingState()
Definition USBAudioDeviceBase.h:1082
std::vector< audiod_function_t > audiod_fct_
Definition USBAudioDeviceBase.h:892
bool activateEndpoint(uint8_t rhport, tusb_desc_endpoint_t const *desc_ep, uint8_t dir=TUSB_DIR_IN)
Definition USBAudioDeviceBase.h:2025
uint16_t getCtrlBufSz(uint8_t fn) const
Definition USBAudioDeviceBase.h:1055
uint32_t getTxXferredLast() const
Definition USBAudioDeviceBase.h:799
uint8_t getTxBytesPerSample() const
TX bytes per sample parsed from the descriptor.
Definition USBAudioDeviceBase.h:809
bool handleInterfaceRequest(uint8_t rhport, tusb_control_request_t const *p_request)
Definition USBAudioDeviceBase.h:1678
bool isEpOutEnabled() const
Returns true if the OUT endpoint is enabled.
Definition USBAudioDeviceBase.h:378
bool configChanged(const USBAudioConfig &n)
Definition USBAudioDeviceBase.h:1052
size_t readBytes(uint8_t *buffer, size_t bufsize)
Receive audio data from the host (host → device, speaker/playback).
Definition USBAudioDeviceBase.h:673
void processVolume(T *data, size_t sample_count)
Definition USBAudioDeviceBase.h:957
void closeEpIn(uint8_t rhport, audiod_function_t *audio, uint8_t itf, tusb_control_request_t const *p_request)
Definition USBAudioDeviceBase.h:1986
bool setMute(bool m, uint8_t channel=0)
Set the mute state for a channel and notify the host.
Definition USBAudioDeviceBase.h:431
uint8_t getTxChannels() const
TX channel count parsed from the descriptor.
Definition USBAudioDeviceBase.h:805
uint16_t audioPacketSize() const
One isochronous USB packet size in bytes (same formula as the descriptor builder).
Definition USBAudioDeviceBase.h:714
size_t write(const uint8_t *data, size_t len)
Send audio data to the host (device → host, microphone/capture). Silently discards data when the host...
Definition USBAudioDeviceBase.h:645
USBAudio2DescriptorBuilder descr_builder
Definition USBAudioDeviceBase.h:837
std::function< bool(USBAudioDeviceBase *, uint8_t rhport, tusb_control_request_t const *p_request, uint8_t *pBuff)> tud_audio_set_req_ep_cb_
Definition USBAudioDeviceBase.h:869
Supports the setting and getting of the volume.
Definition AudioTypes.h:187
24bit integer which is used for I2S sound processing. The values are really using 3 bytes....
Definition Int24_3bytes_t.h:21
RxTxMode
The Microcontroller is the Audio Source (TX_MODE) or Audio Sink (RX_MODE). RXTX_MODE is Source and Si...
Definition AudioTypes.h:26
@ RXTX_MODE
Definition AudioTypes.h:26
@ TX_MODE
Definition AudioTypes.h:26
@ RX_MODE
Definition AudioTypes.h:26
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition LMSEchoCancellationStream.h:6
static constexpr uint8_t kNumSupportedSampleRates
Definition USBAudioDeviceBase.h:94
static constexpr uint32_t kSupportedSampleRates[]
Definition USBAudioDeviceBase.h:92
Basic Audio information which drives e.g. I2S.
Definition AudioTypes.h:51
sample_rate_t sample_rate
Sample Rate: e.g 44100.
Definition AudioTypes.h:53
uint16_t channels
Number of channels: 2=stereo, 1=mono.
Definition AudioTypes.h:55
uint8_t bits_per_sample
Number of bits per sample (int16_t = 16 bits)
Definition AudioTypes.h:57
Configuration for USB Audio (inherits sample_rate / channels / bits_per_sample from AudioInfo).
Definition USBAudioConfig.h:89
bool enable_interrupt_ep
Definition USBAudioConfig.h:146
bool use_linear_buffer_tx
Definition USBAudioConfig.h:160
bool enable_multi_sample_rate
Definition USBAudioConfig.h:141
bool enable_ep_in
device → host (capture / microphone)
Definition USBAudioConfig.h:92
uint8_t itf_num_ac
Definition USBAudioConfig.h:112
uint8_t fifo_packets
Definition USBAudioConfig.h:120
bool enable_ep_in_flow_control
Definition USBAudioConfig.h:153
bool volume_active
Definition USBAudioConfig.h:186
bool use_linear_buffer_rx
Definition USBAudioConfig.h:157
bool enable_ep_out
host → device (playback / speaker)
Definition USBAudioConfig.h:93