arduino-audio-tools
Loading...
Searching...
No Matches
USBAudioDeviceBase.h
Go to the documentation of this file.
1#pragma once
2
3// Some cores (e.g. Arduino's Renesas UNO R4 core, Arduino.h) #define abs(x)
4// as a function-like macro for AVR-era C compatibility. Since Arduino.h is
5// always included ahead of library headers in a .ino, that macro is already
6// active here and breaks libstdc++'s own templated abs(duration<...>)
7// overload inside <chrono> (pulled in by <mutex>) -- the preprocessor sees
8// the comma inside duration<_Rep, _Period> as a second macro argument.
9// Suppress the macro for just this header's STL includes, then restore it
10// so nothing else in the translation unit is affected.
11#pragma push_macro("abs")
12#undef abs
13
14#include <cmath>
15#include <cstddef>
16#include <cstdint>
17#include <cstring>
18#include <functional>
19#include <mutex>
20#include <vector>
21
22#pragma pop_macro("abs")
23
26
27#ifdef ESP32
28#ifndef ARDUINO_USB_MODE
29#error This Microcontroller has no Native USB interface
30#else
31#if ARDUINO_USB_MODE == 1
32#error This sketch should be used when USB is in OTG mode
33#endif
34#endif
35#else
36#if !defined(USE_TINYUSB) && !defined(ARDUINO_ARCH_STM32) && \
37 !defined(ARDUINO_ARCH_RENESAS) && !defined(IS_ZEPHYR)
38#error This Microcontroller has no Native USB interface
39#endif
40#endif
41
42#include "AudioLogger.h"
46
47#define USB_DESCR_MAX_LEN 512
48
49namespace audio_tools {
50
51// ── UAC2 / USB 2.0 spec constants ───────────────────────────────────────────
52// These are fixed byte values defined by the USB 2.0 and UAC2 specs, not
53// TinyUSB API surface (TinyUSB's own headers just give them names). Owned
54// here directly so USBAudioDeviceBase has zero dependency on any particular
55// USB stack's headers.
56
57// Descriptor types (USB 2.0 spec Table 9-5).
58static constexpr uint8_t kUsbDescTypeInterface = 0x04;
59static constexpr uint8_t kUsbDescTypeEndpoint = 0x05;
60static constexpr uint8_t kUsbDescTypeCsInterface = 0x24;
61
62// Audio class/subclass/protocol codes (UAC2 spec Appendix A).
63static constexpr uint8_t kUsbClassAudio = 0x01;
64static constexpr uint8_t kAudioSubclassControl = 0x01;
65static constexpr uint8_t kAudioIntProtocolCodeV2 = 0x20;
66
67// AC interface descriptor subtypes (UAC2 Table A-9).
68static constexpr uint8_t kAudioCsAcInputTerminal = 0x02;
69static constexpr uint8_t kAudioCsAcOutputTerminal = 0x03;
70// AS interface descriptor subtypes (UAC2 Table A-11).
71static constexpr uint8_t kAudioCsAsGeneral = 0x01;
72static constexpr uint8_t kAudioCsAsFormatType = 0x02;
73// Control request codes (UAC2 Table A-15).
74static constexpr uint8_t kAudioCsReqCur = 0x01;
75static constexpr uint8_t kAudioCsReqRange = 0x02;
76// Clock Source control selectors (UAC2 Table A-18).
77static constexpr uint8_t kAudioCsCtrlSamFreq = 0x01;
78static constexpr uint8_t kAudioCsCtrlClkValid = 0x02;
79// Feature Unit control selectors (UAC2 Table A-23).
80static constexpr uint8_t AUDIO_FU_CTRL_MUTE = 0x01;
81static constexpr uint8_t AUDIO_FU_CTRL_VOLUME = 0x02;
82// Terminal type: USB Streaming (UAC2 Table 2-1).
83static constexpr uint16_t kAudioTermTypeUsbStreaming = 0x0101;
84// Interface Association Descriptor length — fixed 8 bytes.
85static constexpr uint16_t kAudioDescIadLen = 8;
86
87// USB control-transfer state-machine stages (universal to any USB stack's
88// control-transfer handling, not TinyUSB-specific).
89static constexpr uint8_t kControlStageSetup = 1;
90static constexpr uint8_t kControlStageData = 2;
91
92static inline uint8_t u16Low(uint16_t v) { return (uint8_t)(v & 0xFF); }
93static inline uint8_t u16High(uint16_t v) { return (uint8_t)(v >> 8); }
94
115 enum audio_format_type_t {
116 AUDIO_FORMAT_TYPE_I,
117 AUDIO_FORMAT_TYPE_II,
118 AUDIO_FORMAT_TYPE_III,
119 };
120
127 enum audio_feedback_method_t {
128 AUDIO_FEEDBACK_METHOD_DISABLED,
129 AUDIO_FEEDBACK_METHOD_FREQUENCY_FIXED,
130 AUDIO_FEEDBACK_METHOD_FREQUENCY_FLOAT,
131 AUDIO_FEEDBACK_METHOD_FREQUENCY_POWER_OF_2, // For driver internal use only
132 AUDIO_FEEDBACK_METHOD_FIFO_COUNT
133 };
134
141 struct audiod_function_t {
142 uint8_t rhport;
143 uint8_t const* p_desc; // Pointer to Standard AC Interface Descriptor
144 // ep_in/ep_out are written from the USB interrupt handler (openEpIn/
145 // openEpOut/closeEpIn/closeEpOut, reached via audiod_set_interface() in
146 // ISR context) and read from normal application code (write()/
147 // readBytes()/isStreamingActiveTx()/isStreamingActiveRx(), called from
148 // loop()). Without a memory-visibility guarantee across that boundary, a
149 // compiler is free to cache a stale read indefinitely -- observed in
150 // practice on the arm-none-eabi-gcc 7.2.1 toolchain used by the Arduino
151 // Renesas UNO R4 core: isStreamingActiveTx() kept reporting false while
152 // real ISO-IN transfers were actively completing, so write() silently
153 // discarded all audio data (see the "disregard data" branch below) while
154 // still reporting success.
155 volatile uint8_t ep_in; // TX audio data EP.
156 uint16_t ep_in_sz; // Current size of TX EP
157 uint8_t
158 ep_in_as_intf_num; // Standard AS Interface Descriptor number for IN
159 volatile uint8_t ep_out; // RX audio data EP.
160 uint16_t ep_out_sz; // Current size of RX EP
161 uint8_t
162 ep_out_as_intf_num; // Standard AS Interface Descriptor number for OUT
163 uint8_t ep_fb; // Feedback EP.
164 uint8_t ep_int; // Audio control interrupt EP.
165 bool mounted; // Device opened
166 uint16_t desc_length; // Length of audio function descriptor
167 struct {
168 uint32_t value;
169 uint32_t min_value;
170 uint32_t max_value;
171 uint8_t frame_shift;
172 uint8_t compute_method;
173 bool format_correction;
174 union {
175 uint8_t power_of_2;
176 float float_const;
177 struct {
178 uint32_t sample_freq;
179 uint32_t mclk_freq;
180 } fixed;
181 struct {
182 uint32_t nom_value;
183 uint32_t fifo_lvl_avg;
184 uint16_t fifo_lvl_thr;
185 uint16_t rate_const[2];
186 } fifo_count;
187 } compute;
188 } feedback;
189 uint32_t sample_rate_tx;
190 uint16_t packet_sz_tx[3];
191 uint8_t bclock_id_tx;
192 uint8_t interval_tx;
193 audio_format_type_t format_type_tx;
194 uint8_t n_channels_tx;
195 uint8_t n_bytes_per_sample_tx;
196 // Fractional sample accumulator for IN-endpoint flow control. Carries the
197 // sub-frame remainder (e.g. the 0.1 sample/frame of 44100 Hz) so the
198 // long-term average packet size matches the real sample rate.
199 uint32_t tx_sample_acc;
200 // Cached OUT endpoint descriptor (populated by audiod_open()'s descriptor
201 // scan) so closeEpOut() can re-activate the endpoint -- resetting its
202 // busy/claimed state for the next SET_INTERFACE -- without needing to
203 // re-walk the descriptor. See closeEpOut() for why this is needed.
204 UsbEndpointDescriptorView ep_out_view;
205 // From this point, data is not cleared by bus reset
206 uint8_t ctrl_buf_sz;
207 std::vector<uint8_t> ctrl_buf;
208 std::vector<uint8_t> alt_setting;
209 std::vector<uint8_t> lin_buf_out;
210 std::vector<uint8_t> lin_buf_in;
211 std::vector<uint32_t> fb_buf;
212 };
213
220 struct audio_feedback_params_t {
221 uint8_t method;
222 uint32_t sample_freq; // sample frequency in Hz
223
224 union {
225 struct {
226 uint32_t mclk_freq; // Main clock frequency in Hz i.e. master clock to
227 // which sample clock is based on
228 } frequency;
229 };
230 };
231
232 public:
235
240
247 USBAudioConfig cfg;
248 switch (mode) {
249 case RX_MODE:
250 cfg.enable_ep_out = true;
251 cfg.enable_ep_in = false;
252 break;
253 case TX_MODE:
254 cfg.enable_ep_out = false;
255 cfg.enable_ep_in = true;
256 break;
257 case RXTX_MODE:
258 cfg.enable_ep_out = true;
259 cfg.enable_ep_in = true;
260 break;
261 default:
262 break;
263 }
264 return cfg;
265 }
266
268 void setAudioInfo(AudioInfo info) override {
270 LOGE("Unsupported bits_per_sample: %d (must be 16, 24, or 32)",
272 return;
273 }
274 // full flexibility when not started yet
275 if (!is_started_) {
279 return;
280 }
281
282 // when started only sample rate can be changed on the fly
284 if (config_.channels != info.channels) {
285 LOGE(
286 "Could not change channel count from %d to %d: channel count is "
287 "fixed at startup!",
289 }
291 LOGE(
292 "Could not change bits per sample from %d to %d: bits per sample is "
293 "fixed at startup!",
295 }
296 // notifiy subscribed entities
298 // notify host about sample rate change via control request callback;
300 }
301
310 bool begin(const USBAudioConfig& cfg) {
311 if (!is_started_) {
312 config_ = cfg;
313 } else if (!configChanged(cfg)) {
314 return true; // already running with same config
315 } else {
317 }
318 return begin();
319 }
320
327 bool begin() {
328 if (!is_started_) {
330 LOGE("Unsupported bits_per_sample: %d (must be 16, 24, or 32)",
332 return false;
333 }
334
335 // Resize platform buffers — virtual so each platform uses the right API.
337
338 const int n = getAudioCount();
339 // GET_RANGE response is 2 + count*12 bytes; 64 is the floor needed for
340 // everything else sharing this buffer (Feature Unit GET_RANGE, etc.).
341 uint16_t cb_sz = 64u;
343 uint16_t needed =
344 (uint16_t)(2 + config_.supported_sample_rates.size() * 12);
345 if (needed > cb_sz) cb_sz = needed;
346 }
347 ctrl_buf_sz_.assign(n, cb_sz);
348
349 uint8_t desc[USB_DESCR_MAX_LEN];
350 uint16_t desc_len = descr_builder.buildFullDescriptor(desc);
351 desc_len_.assign(n, desc_len);
352
353 // master (index 0) + one per channel
354 const size_t vol_sz = (size_t)config_.channels + 1;
355 volume_.assign(vol_sz, 1.0f);
356 mute_.assign(vol_sz, false);
357
358
359 audiod_init();
360
361 if (!beginUSB()) {
362 LOGE("beginUSB failed");
363 return false;
364 }
365 is_started_ = true;
366 }
367
368 // Push current state to the host. sendInterruptNotification()
369 // is a no-op when not yet mounted, so this is harmless on first boot.
371 for (uint8_t ch = 0; ch < (uint8_t)volume_.size(); ch++) {
372 setVolume(volume(ch), ch);
373 setMute(isMute(ch), ch);
374 }
375 is_active_ = true;
376 return true;
377 }
385
387 inline bool isEpInEnabled() const { return config_.enable_ep_in; }
388
390 inline bool isEpOutEnabled() const { return config_.enable_ep_out; }
391
396
403
404 // ── Volume / Mute / Sample-rate API ─────────────────────────────────────
405
407 float volume() override { return volume(0); }
408
410 bool setVolume(float volume) override { return setVolume(volume, 0); }
411
415 float volume(uint8_t channel) {
416 return (channel < volume_.size()) ? volume_[channel] : 0.0f;
417 }
418
423 bool setVolume(float vol, uint8_t channel) {
424 LOGW("setVolume %f channel: %d", vol, channel);
425 if (channel >= volume_.size()) return false;
426 volume_[channel] = vol;
427 if (volume_cb_) volume_cb_(vol, channel);
430 return true;
431 }
432
435 bool isMute(uint8_t channel = 0) const {
436 return (channel < mute_.size()) ? mute_[channel] : false;
437 }
438
443 bool setMute(bool m, uint8_t channel = 0) {
444 LOGW("setMute %s channel: %d", m ? "true" : "false", channel);
445 if (channel >= mute_.size()) return false;
446 mute_[channel] = m;
447 if (mute_cb_) mute_cb_(m, channel);
450 return true;
451 }
452
456 void setVolumeCallback(std::function<void(float, uint8_t)> cb) {
457 volume_cb_ = std::move(cb);
458 }
459
463 void setMuteCallback(std::function<void(bool, uint8_t)> cb) {
464 mute_cb_ = std::move(cb);
465 }
466
470 void setSampleRateCallback(std::function<void(uint32_t)> cb) {
471 sample_rate_cb_ = std::move(cb);
472 }
473
478 void setStreamingStateCallback(std::function<void(bool, bool)> cb) {
479 streaming_state_cb_ = std::move(cb);
480 }
481
483 bool isStreamingActive() const {
485 }
486
488 bool isStreamingActiveTx() const {
489 for (const auto& fct : audiod_fct_) {
490 if (fct.ep_in != 0) return true;
491 }
492 return false;
493 }
494
496 bool isStreamingActiveRx() const {
497 for (const auto& fct : audiod_fct_) {
498 if (fct.ep_out != 0) return true;
499 }
500 return false;
501 }
504
506 uint8_t getAudioCount() const { return 1; }
507
509 bool mounted() { return backend().mounted(); }
510
515 void setGetReqItfCallback(std::function<bool(USBAudioDeviceBase*, uint8_t,
516 UsbSetupPacket const&)>
517 cb) {
518 get_req_itf_cb_ = cb;
519 }
520
525 void setGetReqEpCallback(std::function<bool(USBAudioDeviceBase*, uint8_t,
526 UsbSetupPacket const&)>
527 cb) {
528 get_req_ep_cb_ = cb;
529 }
530
535 void setFbDoneCallback(std::function<void(USBAudioDeviceBase*, uint8_t)> cb) {
536 fb_done_cb_ = cb;
537 }
538
544 std::function<void(USBAudioDeviceBase*, uint8_t)> cb) {
545 int_done_cb_ = cb;
546 }
547
553 std::function<bool(USBAudioDeviceBase*, uint8_t, audiod_function_t*)>
554 cb) {
555 tx_done_cb_ = cb;
556 }
557
562 void setRxDoneCallback(std::function<bool(USBAudioDeviceBase*, uint8_t,
563 audiod_function_t*, uint16_t)>
564 cb) {
565 rx_done_cb_ = cb;
566 }
567
573 std::function<bool(USBAudioDeviceBase*, uint8_t)> cb) {
574 req_entity_cb_ = cb;
575 }
576
582 std::function<bool(USBAudioDeviceBase*, uint8_t,
583 UsbSetupPacket const&)>
584 cb) {
586 }
587
593 std::function<bool(USBAudioDeviceBase*, uint8_t,
594 UsbSetupPacket const&, uint8_t*)>
595 cb) {
597 }
598
604 std::function<bool(USBAudioDeviceBase*, uint8_t,
605 UsbSetupPacket const&, uint8_t*)>
606 cb) {
608 }
609
615 std::function<bool(USBAudioDeviceBase*, uint8_t,
616 UsbSetupPacket const&, uint8_t*)>
617 cb) {
619 }
620
625 void setItfCloseEpCallback(std::function<bool(USBAudioDeviceBase*, uint8_t,
626 UsbSetupPacket const&)>
627 cb) {
629 }
630
636 std::function<void(USBAudioDeviceBase*, uint8_t, uint8_t,
637 audio_feedback_params_t*)>
638 cb) {
640 }
641
650
654 size_t write(const uint8_t* data, size_t len) {
655 if (!is_started_) return 0;
657
658 // disregard data if the host has not opened the capture device (alt=0)
659 if (!isStreamingActiveTx()) return len;
660
661 // update the volume
662 if (config_.volume_active) processVolume((uint8_t*)data, len);
663
664 // Write all data, retrying if the buffer is full. On single-core
665 // platforms (RP2040), serviceTinyUSB() drains the buffer by running
666 // tud_task() → xfer_cb. On dual-core (ESP32), the USB task drains
667 // independently and the SynchronizedNBufferRTOS blocks internally.
668 size_t written = 0;
669 while (written < len) {
670 int n = bufferTx().writeArray(data + written, len - written);
671 written += n;
672 if (written < len) {
673 serviceTinyUSB(); // drain buffer to make space
674 if (!isStreamingActiveTx()) break; // host stopped
675 }
676 }
677 return written;
678 }
679
682 size_t readBytes(uint8_t* buffer, size_t bufsize) {
683 if (!is_started_) return 0;
685 // get the data from the buffer
686 size_t ret = bufferRx().readArray(buffer, bufsize);
687 // upate the volume
688 if (config_.volume_active) processVolume(buffer, ret);
689
690 return ret;
691 }
692
694 int available() override {
695 if (!is_started_) return 0;
696 return bufferRx().available();
697 }
698
700 int availableForWrite() override {
701 if (!is_started_) return 0;
702 return bufferTx().availableForWrite();
703 }
704
706 operator bool() override { return is_started_ && mounted(); }
707
709 void end() {
710 for (auto& audio : audiod_fct_) {
711 std::fill(audio.lin_buf_in.begin(), audio.lin_buf_in.end(), 0);
712 std::fill(audio.lin_buf_out.begin(), audio.lin_buf_out.end(), 0);
713 }
714 bufferTx().reset();
715 bufferRx().reset();
716 is_started_ = false;
717 }
718
721 uint16_t audioPacketSize() const { return packetSize(); }
722
734 uint16_t getDescriptor(uint8_t* desc) {
735 active_config_ = config_; // save active config
737 }
738
744 uint8_t numInterfaces() const {
745 return (uint8_t)(1 + (config_.enable_ep_out ? 1 : 0) +
746 (config_.enable_ep_in ? 1 : 0));
747 }
748
750 bool isTxXferArmed() const { return tx_xfer_armed_; }
751
753 uint32_t getTxXferCount() const { return xfer_cb_tx_count_; }
755 uint32_t getRxXferCount() const { return xfer_cb_rx_count_; }
757 uint32_t getRxTotalBytes() const { return rx_total_bytes_; }
760 uint32_t getTxFifoReadTotal() const { return tx_fifo_read_total_; }
761
764 uint16_t getTxFrameBytesLast() const { return tx_frame_bytes_last_; }
767 uint32_t getTxXferredLast() const { return tx_xferred_last_; }
769 uint32_t getTxSampleRate() const {
770 return audiod_fct_.empty() ? 0 : audiod_fct_[0].sample_rate_tx;
771 }
773 uint8_t getTxChannels() const {
774 return audiod_fct_.empty() ? 0 : audiod_fct_[0].n_channels_tx;
775 }
777 uint8_t getTxBytesPerSample() const {
778 return audiod_fct_.empty() ? 0 : audiod_fct_[0].n_bytes_per_sample_tx;
779 }
781 uint8_t getTxInterval() const {
782 return audiod_fct_.empty() ? 0 : audiod_fct_[0].interval_tx;
783 }
784
785 protected:
786 bool is_started_ = false;
787 bool usb_task_active_ = false; // true while a dedicated tud_task() FreeRTOS task is running
788 bool tx_xfer_armed_ = false;
789 volatile uint32_t xfer_cb_tx_count_ = 0;
790 volatile uint32_t tx_fifo_read_total_ = 0;
791 volatile uint32_t xfer_cb_rx_count_ = 0;
792 volatile uint32_t rx_total_bytes_ = 0;
793 volatile uint16_t tx_frame_bytes_last_ = 0;
794 volatile uint32_t tx_xferred_last_ = 0;
795
796 bool is_active_ = false;
797 // ── Volume / mute state (sized to channels+1 in begin()) ─────────────────
798 std::vector<float> volume_;
799 std::vector<bool> mute_;
800 std::function<void(float, uint8_t)> volume_cb_;
801 std::function<void(bool, uint8_t)> mute_cb_;
802 std::function<void(uint32_t)> sample_rate_cb_;
803 std::function<void(bool, bool)> streaming_state_cb_;
807 std::function<void(USBAudioDeviceBase*, uint8_t rhport)> int_done_cb_;
808 std::function<bool(USBAudioDeviceBase*, uint8_t rhport, audiod_function_t*)>
810 std::function<bool(USBAudioDeviceBase*, uint8_t rhport, audiod_function_t*,
811 uint16_t xferred_bytes)>
813 // Callback for interface GET requests
814 std::function<bool(USBAudioDeviceBase*, uint8_t rhport,
815 UsbSetupPacket const&)>
817 // Callback for endpoint GET requests
818 std::function<bool(USBAudioDeviceBase*, uint8_t rhport,
819 UsbSetupPacket const&)>
821
822 // Callback for feedback done event
823 std::function<void(USBAudioDeviceBase*, uint8_t func_id)> fb_done_cb_;
824 std::function<bool(USBAudioDeviceBase*, uint8_t func_id)> req_entity_cb_;
825 std::function<bool(USBAudioDeviceBase*, uint8_t rhport,
826 UsbSetupPacket const& p_request)>
828 std::function<bool(USBAudioDeviceBase*, uint8_t rhport,
829 UsbSetupPacket const& p_request, uint8_t* pBuff)>
831
832 std::function<bool(USBAudioDeviceBase*, uint8_t rhport,
833 UsbSetupPacket const& p_request, uint8_t* pBuff)>
835
836 std::function<bool(USBAudioDeviceBase*, uint8_t rhport,
837 UsbSetupPacket const& p_request, uint8_t* pBuff)>
839
840 std::function<bool(USBAudioDeviceBase*, uint8_t rhport,
841 UsbSetupPacket const& p_request)>
843
844 std::function<void(USBAudioDeviceBase*, uint8_t func_id, uint8_t alt_itf,
845 audio_feedback_params_t* feedback_param)>
847
848 std::function<bool(USBAudioDeviceBase*, uint8_t func_id)>
850 uint8_t int_notify_buf_[6] = {};
851
852 // calculate!
853 std::vector<uint16_t> desc_len_;
854 // 64
855 std::vector<uint16_t> ctrl_buf_sz_;
856
857 std::vector<audiod_function_t> audiod_fct_;
858
859 // s_active_ lets the class-driver trampolines and the static process()
860 // trampoline reach the last-constructed instance without a singleton.
861 inline static USBAudioDeviceBase* s_active_ = nullptr;
862
864 void setConfig(const USBAudioConfig& cfg) { config_ = cfg; }
865
880 virtual void serviceTinyUSB() = 0;
881
885 virtual USBAudioBackend& backend() = 0;
886
889
892
894 void processVolume(uint8_t* data, size_t len) {
895 switch (config_.bits_per_sample) {
896 case 8:
897 processVolume<int8_t>((int8_t*)data, len);
898 break;
899 case 16:
900 processVolume<int16_t>((int16_t*)data, len / 2);
901 break;
902 case 24:
903 processVolume<int24_3bytes_t>((int24_3bytes_t*)data, len / 3);
904 break;
905 case 32:
906 processVolume<int32_t>((int32_t*)data, len / 4);
907 break;
908 default:
909 // Unsupported bit depth; do nothing.
910 break;
911 }
912 }
913
916 float getVolumeExt(uint8_t channel) const {
917 if (volume_.empty()) return 1.0f;
918 if (mute_[0]) return 0.0f; // master mute
919 float master = volume_[0];
920 if (channel >= volume_.size()) return master; // no per-channel entry
921 if (mute_[channel]) return 0.0f; // per-channel mute
922 return master * volume_[channel];
923 }
924
925 template <typename T>
926 void processVolume(T* data, size_t sample_count) {
927 uint8_t ch_count = config_.channels;
928 for (size_t i = 0; i < sample_count; i++) {
929 uint8_t ch = (uint8_t)(i % ch_count) + 1; // 1-based per-channel index
930 float vol = getVolumeExt(ch);
931 data[i] = (T)(data[i] * vol);
932 }
933 }
934
943 void setSampleRate(uint32_t rate) {
944 bool rate_updated = rate != config_.sample_rate;
945 config_.sample_rate = rate;
946 LOGW("Sample rate changed to %u Hz", rate);
947 if (rate_updated) {
950 }
951 for (auto& fct : audiod_fct_) fct.sample_rate_tx = rate;
955 }
956
961 virtual bool beginUSB() = 0;
962
968 virtual void resizeBuffers() = 0;
969
972 static constexpr int16_t kVolumeMinDb256 = -25600; // -100 dB in 1/256 dB
973
976 static int16_t floatToUac2(float vol) {
977 if (vol <= 0.0f) return (int16_t)0x8000;
978 if (vol >= 1.0f) return 0;
979 return (int16_t)((1.0f - vol) * kVolumeMinDb256);
980 }
981
984 static float uac2ToFloat(int16_t v) {
985 if (v == (int16_t)0x8000) return 0.0f;
986 if (v >= 0) return 1.0f;
987 if (v <= kVolumeMinDb256) return 0.0f;
988 return 1.0f - (float)v / (float)kVolumeMinDb256;
989 }
990
993 bool isFeatureUnit(uint8_t id) const {
996 }
997
1005 void sendInterruptNotification(uint8_t ctrlSel, uint8_t channel,
1006 uint8_t entityID) {
1007 if (!backend().mounted()) return;
1008 for (uint8_t i = 0; i < (uint8_t)audiod_fct_.size(); i++) {
1009 if (audiod_fct_[i].ep_int == 0) continue;
1010 int_notify_buf_[0] = 0x00; // bInfo: interface, not vendor
1011 int_notify_buf_[1] = kAudioCsReqCur; // bAttribute: CUR changed
1012 int_notify_buf_[2] = channel; // wValue low = CN
1013 int_notify_buf_[3] = ctrlSel; // wValue high = CS
1014 int_notify_buf_[4] = config_.itf_num_ac; // wIndex low = interface
1015 int_notify_buf_[5] = entityID; // wIndex high = entity ID
1016 (void)backend().transfer(0, audiod_fct_[i].ep_int, int_notify_buf_, 6);
1017 break;
1018 }
1019 }
1020
1021 bool configChanged(const USBAudioConfig& n) { return config_ != n; }
1022
1023 // Returns the control buffer size for a given function number
1024 uint16_t getCtrlBufSz(uint8_t fn) const {
1025 return (fn < ctrl_buf_sz_.size()) ? ctrl_buf_sz_[fn] : 64;
1026 }
1027
1028 // Returns the descriptor length for a given function number
1029 uint16_t getDescLen(uint8_t fn) const {
1030 return (fn < desc_len_.size()) ? desc_len_[fn] : 0;
1031 }
1032
1033 static bool isValidBitsPerSample(uint8_t bps) {
1034 return bps == 16 || bps == 24 || bps == 32;
1035 }
1036
1041
1042 // Max Bytes for one 1 ms isochronous USB packet.
1043 uint16_t packetSize() const { return descr_builder.calcMaxPacketSize(); }
1044
1045 // Returns the reset size for audiod_function_t up to and including
1046 // ctrl_buf_sz
1047 static constexpr size_t getResetSize() {
1048 return offsetof(audiod_function_t, ctrl_buf_sz) +
1049 sizeof(((audiod_function_t*)0)->ctrl_buf_sz);
1050 }
1051
1052 // Called by audiod_sof_isr() at the feedback interval.
1053 // Computes the current feedback value then claims the EP and sends it.
1055 uint32_t /*frame_count*/,
1056 uint8_t frame_shift) {
1057 audiod_function_t* audio = &audiod_fct_[func_id];
1058
1059 switch (audio->feedback.compute_method) {
1060 case AUDIO_FEEDBACK_METHOD_FIFO_COUNT: {
1061 // In linear-buffer mode, audio data flows lin_buf_out → bufferRx(),
1062 // completely bypassing ep_out_ff. Reading ep_out_ff would always
1063 // return 0, driving the feedback to min_value and causing the host
1064 // to gradually reduce its send rate until the buffer drains (audible
1065 // periodic drop every 5-10 s). Use the platform RX ring buffer level.
1066 uint32_t ff_count = (uint32_t)bufferRx().available();
1067 // Exponential weighted average keeps the level estimate stable
1068 audio->feedback.compute.fifo_count.fifo_lvl_avg =
1069 audio->feedback.compute.fifo_count.fifo_lvl_avg -
1070 (audio->feedback.compute.fifo_count.fifo_lvl_avg >> 8) +
1071 ((uint32_t)ff_count << 8);
1072 uint32_t avg = audio->feedback.compute.fifo_count.fifo_lvl_avg >> 8;
1073 uint32_t thr = audio->feedback.compute.fifo_count.fifo_lvl_thr;
1074 uint32_t nom = audio->feedback.compute.fifo_count.nom_value;
1075 if (avg > thr) {
1076 audio->feedback.value =
1077 nom + (uint32_t)audio->feedback.compute.fifo_count.rate_const[0] *
1078 (avg - thr);
1079 } else {
1080 uint32_t drop =
1081 (uint32_t)audio->feedback.compute.fifo_count.rate_const[1] *
1082 (thr - avg);
1083 audio->feedback.value =
1084 (nom > drop) ? nom - drop : audio->feedback.min_value;
1085 }
1086 uint32_t clamped = audio->feedback.value < audio->feedback.min_value
1087 ? audio->feedback.min_value
1088 : audio->feedback.value;
1089 audio->feedback.value = clamped > audio->feedback.max_value
1090 ? audio->feedback.max_value
1091 : clamped;
1092 } break;
1093
1094 case AUDIO_FEEDBACK_METHOD_FREQUENCY_POWER_OF_2:
1095 audio->feedback.value = 1UL << audio->feedback.compute.power_of_2;
1096 break;
1097
1098 case AUDIO_FEEDBACK_METHOD_FREQUENCY_FLOAT:
1099 audio->feedback.value =
1100 (uint32_t)(audio->feedback.compute.float_const *
1101 (float)(1UL << (16u - (frame_shift - 1u))));
1102 break;
1103
1104 case AUDIO_FEEDBACK_METHOD_FREQUENCY_FIXED: {
1105 uint32_t frame_div = backend().isFullSpeed() ? 1000u : 8000u;
1106 audio->feedback.value =
1107 (audio->feedback.compute.fixed.sample_freq << 16) / frame_div;
1108 } break;
1109
1110 default:
1111 break;
1112 }
1113
1114 if (backend().claimEndpoint(audio->rhport, audio->ep_fb)) {
1115 audiod_fb_send(audio);
1116 }
1117 }
1118
1119 // USBD Driver API — public so a backend's class-driver registration glue
1120 // (e.g. USBAudioBackendTinyUSB.h's trampoline into TinyUSB's
1121 // usbd_class_driver_t) can call these without being a member of this
1122 // class. A native-HAL backend with no such registration concept would
1123 // instead call these directly from its own ISR/init code.
1124 public:
1125 void audiod_init(void) {
1126 audiod_fct_.resize(getAudioCount());
1127
1128 // Initialize control buffers
1129 for (uint8_t i = 0; i < getAudioCount(); i++) {
1130 audiod_function_t* audio = &audiod_fct_[i];
1131 // Initialize control buffers
1132 int size = getCtrlBufSz(i);
1133 audio->ctrl_buf.resize(size);
1134 audio->ctrl_buf_sz = size;
1135 // Initialize active alternate interface buffers
1136 audio->alt_setting.resize(descr_builder.audioFunctionsCount());
1137 // Initialize IN EP — lin_buf_in is the DMA staging buffer (one frame).
1138 // Audio data flows through bufferTx() (resized in begin()), not ep_in_ff.
1139 if (isEpInEnabled()) {
1140 // Max packet across all advertised rates (see supported_sample_rates).
1141 uint16_t max_pkt = descr_builder.calcPacketSizeForRate(
1143 audio->lin_buf_in.resize(max_pkt);
1144 }
1145 // Initialize OUT EP linear (DMA staging) buffer.
1146 if (isEpOutEnabled()) {
1147 uint16_t max_pkt = descr_builder.calcPacketSizeForRate(
1149 audio->lin_buf_out.resize(max_pkt);
1150 }
1151 if (isFeedbackEpEnabled()) {
1152 audio->fb_buf.resize(1); // one uint32_t = 4 bytes of feedback data
1153 }
1154 }
1155 }
1156
1157 bool audiod_deinit(void) {
1158 return false; // TODO not implemented yet
1159 }
1160
1161 void audiod_reset(uint8_t rhport) {
1162 (void)rhport;
1163 for (uint8_t i = 0; i < getAudioCount(); i++) {
1164 audiod_function_t* audio = &audiod_fct_[i];
1165 memset(audio, 0, getResetSize());
1166 if (isEpInEnabled()) {
1167 bufferTx().reset();
1168 }
1169 if (isEpOutEnabled()) {
1170 bufferRx().reset();
1171 }
1172 }
1173 }
1174
1175 uint16_t audiod_open(uint8_t rhport, UsbInterfaceDescriptorView const& itf_desc,
1176 uint8_t const* raw_desc, uint16_t max_len) {
1177 (void)max_len;
1178 if (!(kUsbClassAudio == itf_desc.bInterfaceClass &&
1180 return 0;
1181 if (itf_desc.bInterfaceProtocol != kAudioIntProtocolCodeV2) return 0;
1182 if (itf_desc.bNumEndpoints > 1) return 0;
1183 if (itf_desc.bNumEndpoints == 1 && !isInterruptEpEnabled()) return 0;
1184 if (itf_desc.bAlternateSetting != 0) return 0;
1185 uint8_t i;
1186 for (i = 0; i < getAudioCount(); i++) {
1187 if (!audiod_fct_[i].p_desc) {
1188 audiod_fct_[i].p_desc = raw_desc;
1189 audiod_fct_[i].rhport = rhport;
1190 audiod_fct_[i].desc_length = getDescLen(i);
1191 // audiod_reset() zeroes ctrl_buf_sz via memset — restore it so
1192 // controlTransfer() receives the correct buffer length.
1193 audiod_fct_[i].ctrl_buf_sz = getCtrlBufSz(i);
1195 uint8_t ep_in = 0, ep_out = 0, ep_fb = 0;
1196 uint16_t ep_in_size = 0, ep_out_size = 0;
1197 UsbEndpointDescriptorView desc_ep_out{};
1198 bool has_ep_out_view = false;
1199 uint8_t const* p_desc = audiod_fct_[i].p_desc;
1200 uint8_t const* p_desc_end =
1201 p_desc + audiod_fct_[i].desc_length - kAudioDescIadLen;
1202 while (p_desc_end - p_desc > 0) {
1203 if (descType(p_desc) == kUsbDescTypeEndpoint) {
1204 UsbEndpointDescriptorView desc_ep = decodeEndpoint(p_desc);
1205 if (desc_ep.xferType == UsbXferType::Isochronous) {
1206 if (isFeedbackEpEnabled() && desc_ep.usage == 1) {
1207 ep_fb = desc_ep.bEndpointAddress;
1208 }
1209 if (desc_ep.usage == 0) {
1210 if (isEpInEnabled() && desc_ep.direction() == UsbDir::In) {
1211 ep_in = desc_ep.bEndpointAddress;
1212 uint16_t sz = desc_ep.packetSize();
1213 ep_in_size = sz > ep_in_size ? sz : ep_in_size;
1214 } else if (isEpOutEnabled() &&
1215 desc_ep.direction() == UsbDir::Out) {
1216 ep_out = desc_ep.bEndpointAddress;
1217 uint16_t sz = desc_ep.packetSize();
1218 ep_out_size = sz > ep_out_size ? sz : ep_out_size;
1219 desc_ep_out = desc_ep;
1220 has_ep_out_view = true;
1221 }
1222 }
1223 }
1224 }
1225 p_desc = descNext(p_desc);
1226 }
1227 if (isEpInEnabled() && ep_in) {
1228 bool alloc_ok = backend().isoAllocEndpoint(rhport, ep_in, ep_in_size);
1229 LOGD("iso_alloc IN ep=0x%02x sz=%u: %s", ep_in, ep_in_size,
1230 alloc_ok ? "OK" : "FAIL");
1231 }
1232 if (isEpOutEnabled() && ep_out) {
1233 bool alloc_ok = backend().isoAllocEndpoint(rhport, ep_out, ep_out_size);
1234 LOGD("iso_alloc OUT ep=0x%02x sz=%u: %s", ep_out, ep_out_size,
1235 alloc_ok ? "OK" : "FAIL");
1236 if (has_ep_out_view) audiod_fct_[i].ep_out_view = desc_ep_out;
1237 if (backend().usesIsoAlloc() && has_ep_out_view) {
1238 // Pre-activate during enumeration (no isochronous traffic).
1239 // Cannot be done in SET_INTERFACE because iso_activate blocks
1240 // on ESP32's DWC2 when the host is already sending. Activation
1241 // itself resets the endpoint's busy/claimed state to a known
1242 // (not busy) baseline, so nothing further is needed here for
1243 // the first open -- see closeEpOut() for why re-activation is
1244 // also needed on every subsequent re-open.
1245 backend().isoActivateEndpoint(rhport, desc_ep_out);
1246 LOGD("iso_activate OUT: done");
1247 }
1248 }
1249 if (isFeedbackEpEnabled() && ep_fb) {
1250 backend().isoAllocEndpoint(rhport, ep_fb, 4);
1251 }
1252 }
1253 // Scan for bclock_id_tx (clock entity referenced by the USB-streaming
1254 // terminal) and interval_tx. Runs in TX, RX, and RXTX mode so that
1255 // clock-validity/frequency GET requests always succeed.
1256 // TX/RXTX: Output Terminal type=USB_STREAMING → bCSourceID at [8]
1257 // RX: Input Terminal type=USB_STREAMING → bCSourceID at [7]
1258 // interval_tx is only meaningful for the ISO IN endpoint (TX/RXTX).
1259 if (isEpInEnabled() || isEpOutEnabled()) {
1260 uint8_t const* p_desc = audiod_fct_[i].p_desc;
1261 uint8_t const* p_desc_end =
1262 p_desc + audiod_fct_[i].desc_length - kAudioDescIadLen;
1263 while (p_desc_end - p_desc > 0) {
1264 if (descType(p_desc) == kUsbDescTypeEndpoint) {
1265 if (isEpInEnabled()) {
1266 UsbEndpointDescriptorView desc_ep = decodeEndpoint(p_desc);
1267 if (desc_ep.xferType == UsbXferType::Isochronous &&
1268 desc_ep.usage == 0 && desc_ep.direction() == UsbDir::In) {
1269 audiod_fct_[i].interval_tx = desc_ep.bInterval;
1270 }
1271 }
1272 } else if (descType(p_desc) == kUsbDescTypeCsInterface) {
1273 if (descSubtype(p_desc) == kAudioCsAcOutputTerminal) {
1274 if (descU16(p_desc + 4) == kAudioTermTypeUsbStreaming) {
1275 audiod_fct_[i].bclock_id_tx = p_desc[8]; // OT bCSourceID
1276 }
1277 } else if (descSubtype(p_desc) == kAudioCsAcInputTerminal) {
1278 if (descU16(p_desc + 4) == kAudioTermTypeUsbStreaming) {
1279 audiod_fct_[i].bclock_id_tx = p_desc[7]; // IT bCSourceID
1280 }
1281 }
1282 }
1283 p_desc = descNext(p_desc);
1284 }
1285 }
1286
1287 if (isInterruptEpEnabled()) {
1288 uint8_t const* p_desc = audiod_fct_[i].p_desc;
1289 uint8_t const* p_desc_end =
1290 p_desc + audiod_fct_[i].desc_length - kAudioDescIadLen;
1291 while (p_desc_end - p_desc > 0) {
1292 if (descType(p_desc) == kUsbDescTypeEndpoint) {
1293 UsbEndpointDescriptorView desc_ep = decodeEndpoint(p_desc);
1294 uint8_t const ep_addr = desc_ep.bEndpointAddress;
1295 if (desc_ep.direction() == UsbDir::In &&
1296 desc_ep.xferType == UsbXferType::Interrupt) {
1297 if (backend().openEndpoint(audiod_fct_[i].rhport, desc_ep)) {
1298 audiod_fct_[i].ep_int = ep_addr;
1299 } else {
1300 LOGE(" UAC2: interrupt EP 0x%02x open failed", ep_addr);
1301
1302 }
1303 }
1304 }
1305 p_desc = descNext(p_desc);
1306 }
1307 }
1308 audiod_fct_[i].mounted = true;
1309 break;
1310 }
1311 }
1312 if (i >= getAudioCount()) return 0;
1313 uint16_t drv_len = audiod_fct_[i].desc_length - kAudioDescIadLen;
1314 return drv_len;
1315 }
1316
1317 bool audiod_control_xfer_cb(uint8_t rhport, uint8_t stage,
1318 UsbSetupPacket const& request) {
1319 if (stage == kControlStageSetup) {
1320 return audiod_control_request(rhport, request);
1321 } else if (stage == kControlStageData) {
1322 return audiod_control_complete(rhport, request);
1323 }
1324 return true;
1325 }
1326 // Invoked when class request DATA stage is finished.
1327 // return false to stall control EP (e.g Host send non-sense DATA)
1328 bool audiod_control_complete(uint8_t rhport,
1329 UsbSetupPacket const& p_request) {
1330 // Handle audio class specific set requests
1331 if (p_request.type() == UsbSetupPacket::Type::Class &&
1332 p_request.direction() == UsbDir::Out) {
1333 uint8_t func_id;
1334
1335 switch (p_request.recipient()) {
1337 uint8_t itf = u16Low(p_request.wIndex);
1338 uint8_t entityID = u16High(p_request.wIndex);
1339
1340 if (entityID != 0) {
1341 func_id = 0;
1342 uint8_t ctrlSel = u16High(p_request.wValue);
1343 uint8_t* cb = audiod_fct_[func_id].ctrl_buf.data();
1344
1345 // ── Clock Source SET_CUR (sample rate) ──────────────
1347 ctrlSel == kAudioCsCtrlSamFreq &&
1348 p_request.bRequest == kAudioCsReqCur) {
1350 }
1351
1352 // ── Feature Unit SET_CUR (mute / volume) ────────────
1353 if (isFeatureUnit(entityID) &&
1354 p_request.bRequest == kAudioCsReqCur) {
1355 uint8_t channel = u16Low(p_request.wValue);
1356 if (ctrlSel == AUDIO_FU_CTRL_MUTE) {
1357 setMute(cb[0] != 0, channel);
1358 } else if (ctrlSel == AUDIO_FU_CTRL_VOLUME) {
1359 int16_t v;
1360 memcpy(&v, cb, 2);
1361 setVolume(uac2ToFloat(v), channel);
1362 }
1363 }
1364
1365 // Invoke callback
1367 return tud_audio_set_req_entity_cb_(this, rhport, p_request, cb);
1368 }
1369 } else {
1370 // Find index of audio driver structure and verify interface really
1371 // exists
1372 if (!audiod_verify_itf_exists(itf, &func_id)) return false;
1373
1374 // Invoke callback
1377 this, rhport, p_request,
1378 audiod_fct_[func_id].ctrl_buf.data());
1379 }
1380 }
1381 } break;
1382
1384 uint8_t ep = u16Low(p_request.wIndex);
1385
1386 // Check if entity is present and get corresponding driver index
1387 if (!audiod_verify_ep_exists(ep, &func_id)) return false;
1388
1389 // Invoke callback
1392 this, rhport, p_request, audiod_fct_[func_id].ctrl_buf.data());
1393 }
1394 } break;
1395 // Unknown/Unsupported recipient
1396 default:
1397 return false;
1398 }
1399 }
1400 return true;
1401 }
1402
1404 bool audiod_xfer_cb(uint8_t rhport, uint8_t ep_addr, UsbXferResult result,
1405 uint32_t xferred_bytes) {
1406 (void)result;
1407 (void)xferred_bytes;
1408 for (uint8_t func_id = 0; func_id < getAudioCount(); func_id++) {
1409 audiod_function_t* audio = &audiod_fct_[func_id];
1410 if (isInterruptEpEnabled() && audio->ep_int == ep_addr) {
1411 if (int_done_cb_) int_done_cb_(this, rhport);
1412 return true;
1413 }
1414 if (isEpInEnabled() && audio->ep_in == ep_addr &&
1415 audio->alt_setting.size() != 0) {
1417 if (tx_done_cb_) tx_done_cb_(this, rhport, audio);
1418
1419 uint16_t frame_bytes = isEpInFlowControlEnabled()
1421 : audio->ep_in_sz;
1422 if (frame_bytes > audio->ep_in_sz) frame_bytes = audio->ep_in_sz;
1423 tx_frame_bytes_last_ = frame_bytes;
1424 tx_xferred_last_ = xferred_bytes;
1425
1426 // Drain platform buffer into lin_buf_in, zero-pad, send via DMA.
1427 {
1428 uint8_t* dst = audio->lin_buf_in.data();
1429 uint16_t n = (uint16_t)bufferTx().readArray(dst, frame_bytes);
1431 if (n < frame_bytes) memset(dst + n, 0, frame_bytes - n);
1432 (void)backend().transfer(rhport, audio->ep_in, dst, frame_bytes);
1433 }
1434 return true;
1435 }
1436 if (isEpOutEnabled() && audio->ep_out == ep_addr) {
1438 rx_total_bytes_ += xferred_bytes;
1439 // Copy DMA-received data into the platform buffer, re-arm DMA.
1440 if (xferred_bytes > 0)
1441 bufferRx().writeArray(audio->lin_buf_out.data(),
1442 (int)xferred_bytes);
1443 if (rx_done_cb_)
1444 rx_done_cb_(this, rhport, audio, (uint16_t)xferred_bytes);
1445 (void)backend().transfer(rhport, audio->ep_out,
1446 audio->lin_buf_out.data(), audio->ep_out_sz);
1447 return true;
1448 }
1449 if (isFeedbackEpEnabled() && audio->ep_fb == ep_addr) {
1450 // SOF ISR owns re-sending; just notify the application.
1451 if (fb_done_cb_) fb_done_cb_(this, func_id);
1452 return true;
1453 }
1454 }
1455 return false;
1456 }
1457
1458 void audiod_sof_isr(uint8_t rhport, uint32_t frame_count) {
1459 (void)rhport;
1460 (void)frame_count;
1462 for (uint8_t i = 0; i < getAudioCount(); i++) {
1463 audiod_function_t* audio = &audiod_fct_[i];
1464 if (audio->ep_fb != 0) {
1465 uint8_t const hs_adjust = backend().isHighSpeed() ? 3 : 0;
1466 uint32_t const interval =
1467 1UL << (audio->feedback.frame_shift - hs_adjust);
1468 if (0 == (frame_count & (interval - 1))) {
1469 tud_audio_feedback_interval_isr(i, frame_count,
1470 audio->feedback.frame_shift);
1471 }
1472 }
1473 }
1474 }
1475 }
1476
1477 protected:
1478 // ── Clock Source GET handler ────────────────────────────────────────────
1479 bool handleClockSourceGet(uint8_t rhport,
1480 UsbSetupPacket const& p_request,
1481 uint8_t* cb) {
1482 uint8_t ctrlSel = u16High(p_request.wValue);
1483 if (ctrlSel == kAudioCsCtrlClkValid &&
1484 p_request.bRequest == kAudioCsReqCur) {
1485 cb[0] = 1;
1486 return backend().controlTransfer(rhport, p_request, cb, 1);
1487 }
1488 if (ctrlSel == kAudioCsCtrlSamFreq) {
1489 uint32_t rate = (uint32_t)config_.sample_rate;
1490 if (p_request.bRequest == kAudioCsReqCur) {
1491 memcpy(cb, &rate, 4);
1492 return backend().controlTransfer(rhport, p_request, cb, 4);
1493 }
1494 if (p_request.bRequest == kAudioCsReqRange) {
1496 // List all supported discrete rates
1497 const auto& rates = config_.supported_sample_rates;
1498 uint16_t cnt = (uint16_t)rates.size();
1499 memcpy(cb, &cnt, 2);
1500 for (size_t i = 0; i < rates.size(); i++) {
1501 uint32_t r = rates[i];
1502 uint32_t z = 0;
1503 memcpy(cb + 2 + i * 12, &r, 4);
1504 memcpy(cb + 2 + i * 12 + 4, &r, 4);
1505 memcpy(cb + 2 + i * 12 + 8, &z, 4);
1506 }
1507 return backend().controlTransfer(
1508 rhport, p_request, cb,
1509 (uint16_t)(2 + rates.size() * 12));
1510 } else {
1511 // Single fixed rate from config
1512 uint16_t cnt = 1;
1513 uint32_t z = 0;
1514 memcpy(cb, &cnt, 2);
1515 memcpy(cb + 2, &rate, 4); // dMIN
1516 memcpy(cb + 6, &rate, 4); // dMAX
1517 memcpy(cb + 10, &z, 4); // dRES = 0 (fixed)
1518 return backend().controlTransfer(rhport, p_request, cb, 14);
1519 }
1520 }
1521 }
1522 return false;
1523 }
1524
1525 // ── Feature Unit GET handler ──────────────────────────────────────────
1526 bool handleFeatureUnitGet(uint8_t rhport,
1527 UsbSetupPacket const& p_request,
1528 uint8_t* cb) {
1529 uint8_t ctrlSel = u16High(p_request.wValue);
1530 uint8_t channel = u16Low(p_request.wValue);
1531 if (ctrlSel == AUDIO_FU_CTRL_MUTE &&
1532 p_request.bRequest == kAudioCsReqCur) {
1533 cb[0] = isMute(channel) ? 1 : 0;
1534 return backend().controlTransfer(rhport, p_request, cb, 1);
1535 }
1536 if (ctrlSel == AUDIO_FU_CTRL_VOLUME) {
1537 if (p_request.bRequest == kAudioCsReqCur) {
1538 int16_t v = floatToUac2(volume(channel));
1539 memcpy(cb, &v, 2);
1540 return backend().controlTransfer(rhport, p_request, cb, 2);
1541 }
1542 if (p_request.bRequest == kAudioCsReqRange) {
1543 uint16_t cnt = 1;
1544 int16_t vmin = -25600, vmax = 0, vres = 256;
1545 memcpy(cb + 0, &cnt, 2);
1546 memcpy(cb + 2, &vmin, 2);
1547 memcpy(cb + 4, &vmax, 2);
1548 memcpy(cb + 6, &vres, 2);
1549 return backend().controlTransfer(rhport, p_request, cb, 8);
1550 }
1551 }
1552 return false;
1553 }
1554
1555 // ── Entity request handler (Clock Source + Feature Unit) ──────────────
1556 bool handleEntityRequest(uint8_t rhport,
1557 UsbSetupPacket const& p_request,
1558 uint8_t entityID) {
1559 uint8_t func_id = 0;
1560 uint8_t* cb = audiod_fct_[func_id].ctrl_buf.data();
1561 bool is_get = (p_request.direction() == UsbDir::In);
1562
1564 if (is_get && handleClockSourceGet(rhport, p_request, cb)) return true;
1565 // SET — schedule data receive for audiod_control_complete()
1566 return backend().controlTransfer(rhport, p_request, cb,
1567 audiod_fct_[func_id].ctrl_buf_sz);
1568 }
1569
1570 if (isFeatureUnit(entityID)) {
1571 if (is_get && handleFeatureUnitGet(rhport, p_request, cb)) return true;
1572 // SET — schedule data receive
1573 return backend().controlTransfer(rhport, p_request, cb,
1574 audiod_fct_[func_id].ctrl_buf_sz);
1575 }
1576
1577 // Unknown entity — try generic verify
1578 uint8_t itf = u16Low(p_request.wIndex);
1579 if (!audiod_verify_entity_exists(itf, entityID, &func_id)) {
1580 backend().controlStatus(rhport, p_request);
1581 return true;
1582 }
1583 if (is_get && req_entity_cb_) return req_entity_cb_(this, func_id);
1584 return backend().controlTransfer(rhport, p_request,
1585 audiod_fct_[func_id].ctrl_buf.data(),
1586 audiod_fct_[func_id].ctrl_buf_sz);
1587 }
1588
1589 // ── Interface request handler (entityID == 0) ─────────────────────────
1590 bool handleInterfaceRequest(uint8_t rhport,
1591 UsbSetupPacket const& p_request) {
1592 uint8_t itf = u16Low(p_request.wIndex);
1593 uint8_t func_id;
1594 if (!audiod_verify_itf_exists(itf, &func_id)) return false;
1595 if (p_request.direction() == UsbDir::In) {
1596 if (get_req_itf_cb_) return get_req_itf_cb_(this, rhport, p_request);
1597 return false;
1598 }
1599 return backend().controlTransfer(rhport, p_request,
1600 audiod_fct_[func_id].ctrl_buf.data(),
1601 audiod_fct_[func_id].ctrl_buf_sz);
1602 }
1603
1604 // ── Endpoint request handler ──────────────────────────────────────────
1605 bool handleEndpointRequest(uint8_t rhport,
1606 UsbSetupPacket const& p_request) {
1607 uint8_t ep = u16Low(p_request.wIndex);
1608 uint8_t func_id;
1609 if (!audiod_verify_ep_exists(ep, &func_id)) return false;
1610 if (p_request.direction() == UsbDir::In) {
1611 if (get_req_ep_cb_) return get_req_ep_cb_(this, rhport, p_request);
1612 return false;
1613 }
1614 return backend().controlTransfer(rhport, p_request,
1615 audiod_fct_[func_id].ctrl_buf.data(),
1616 audiod_fct_[func_id].ctrl_buf_sz);
1617 }
1618
1619 // ── Main control request dispatcher ───────────────────────────────────
1620 bool audiod_control_request(uint8_t rhport,
1621 UsbSetupPacket const& p_request) {
1622 if (p_request.type() == UsbSetupPacket::Type::Standard) {
1623 switch (p_request.bRequest) {
1624 case (uint8_t)UsbStdRequest::GetInterface:
1625 return audiod_get_interface(rhport, p_request);
1626 case (uint8_t)UsbStdRequest::SetInterface:
1627 return audiod_set_interface(rhport, p_request);
1628 case (uint8_t)UsbStdRequest::ClearFeature:
1629 return true;
1630 default:
1631 return false;
1632 }
1633 }
1634
1635 if (p_request.type() == UsbSetupPacket::Type::Class) {
1636 switch (p_request.recipient()) {
1638 uint8_t entityID = u16High(p_request.wIndex);
1639 return (entityID != 0)
1640 ? handleEntityRequest(rhport, p_request, entityID)
1641 : handleInterfaceRequest(rhport, p_request);
1642 }
1644 return handleEndpointRequest(rhport, p_request);
1645 default:
1646 return false;
1647 }
1648 }
1649
1650 return false;
1651 }
1652
1653 // Verify an entity with the given ID exists and returns also the
1654 // corresponding driver index
1655 bool audiod_verify_entity_exists(uint8_t itf, uint8_t entityID,
1656 uint8_t* func_id) {
1657 uint8_t i;
1658 for (i = 0; i < getAudioCount(); i++) {
1659 // Look for the correct driver by checking if the unique standard AC
1660 // interface number fits
1661 if (audiod_fct_[i].p_desc &&
1662 decodeInterface(audiod_fct_[i].p_desc).bInterfaceNumber == itf) {
1663 // Get pointers after class specific AC descriptors and end of AC
1664 // descriptors - entities are defined in between
1665 uint8_t const* p_desc =
1666 descNext(audiod_fct_[i].p_desc); // Points to CS AC descriptor
1667 // AC interface header wTotalLength lives at byte offset 6 (UAC2
1668 // Table 4-5: bLength,bDescriptorType,bDescriptorSubType,bcdADC(2),
1669 // bCategory,wTotalLength(2),bmControls).
1670 uint8_t const* p_desc_end = descU16(p_desc + 6) + p_desc;
1671 p_desc = descNext(p_desc); // Get past CS AC descriptor
1672
1673 // Condition modified from p_desc < p_desc_end to prevent gcc>=12
1674 // strict-overflow warning
1675 while (p_desc_end - p_desc > 0) {
1676 if (p_desc[3] == entityID) // Entity IDs are always at offset 3
1677 {
1678 *func_id = i;
1679 return true;
1680 }
1681 p_desc = descNext(p_desc);
1682 }
1683 }
1684 }
1685 return false;
1686 }
1687
1688 bool audiod_verify_ep_exists(uint8_t ep, uint8_t* func_id) {
1689 uint8_t i;
1690 for (i = 0; i < getAudioCount(); i++) {
1691 if (audiod_fct_[i].p_desc) {
1692 // Get pointer at end
1693 uint8_t const* p_desc_end =
1694 audiod_fct_[i].p_desc + audiod_fct_[i].desc_length;
1695
1696 // Advance past AC descriptors - EP we look for are streaming EPs
1697 uint8_t const* p_desc = descNext(audiod_fct_[i].p_desc);
1698 p_desc += descU16(p_desc + 6); // AC header wTotalLength, see above
1699
1700 // Condition modified from p_desc < p_desc_end to prevent gcc>=12
1701 // strict-overflow warning
1702 while (p_desc_end - p_desc > 0) {
1703 if (descType(p_desc) == kUsbDescTypeEndpoint &&
1704 decodeEndpoint(p_desc).bEndpointAddress == ep) {
1705 *func_id = i;
1706 return true;
1707 }
1708 p_desc = descNext(p_desc);
1709 }
1710 }
1711 }
1712 return false;
1713 }
1714
1715 bool audiod_verify_itf_exists(uint8_t itf, uint8_t* func_id) {
1716 uint8_t i;
1717 for (i = 0; i < getAudioCount(); i++) {
1718 if (audiod_fct_[i].p_desc) {
1719 // Get pointer at beginning and end
1720 uint8_t const* p_desc = audiod_fct_[i].p_desc;
1721 uint8_t const* p_desc_end = audiod_fct_[i].p_desc +
1722 audiod_fct_[i].desc_length -
1724 // Condition modified from p_desc < p_desc_end to prevent gcc>=12
1725 // strict-overflow warning
1726 while (p_desc_end - p_desc > 0) {
1727 if (descType(p_desc) == kUsbDescTypeInterface &&
1728 decodeInterface(audiod_fct_[i].p_desc).bInterfaceNumber == itf) {
1729 *func_id = i;
1730 return true;
1731 }
1732 p_desc = descNext(p_desc);
1733 }
1734 }
1735 }
1736 return false;
1737 }
1738
1739 void audiod_parse_flow_control_params(audiod_function_t* audio,
1740 uint8_t const* p_desc) {
1741 // Seed the TX sample rate from the configured AudioInfo so packet-size
1742 // calculation works even when the host never issues a SET_CUR(SAM_FREQ)
1743 // request (typical for single-frequency clock sources). The host may still
1744 // override this later via audiod_control_complete().
1745 if (audio->sample_rate_tx == 0)
1746 audio->sample_rate_tx = (uint32_t)config_.sample_rate;
1747
1748 p_desc = descNext(p_desc); // Exclude standard AS interface descriptor
1749 // of current alternate interface descriptor
1750
1751 // Look for a Class-Specific AS Interface Descriptor(4.9.2) to verify format
1752 // type and format and also to get number of physical channels.
1753 // Layout (UAC2 Table 4-27): bLength,bDescriptorType,bDescriptorSubType,
1754 // bTerminalLink,bmControls,bFormatType[5],bmFormats(4),bNrChannels[10],...
1755 if (descType(p_desc) == kUsbDescTypeCsInterface &&
1756 descSubtype(p_desc) == kAudioCsAsGeneral) {
1757 audio->n_channels_tx = p_desc[10];
1758 audio->format_type_tx = (audio_format_type_t)p_desc[5];
1759 // Look for a Type I Format Type Descriptor(2.3.1.6 - Audio Formats)
1760 // Layout: bLength,bDescriptorType,bDescriptorSubType,bFormatType,
1761 // bSubslotSize[4],bBitResolution.
1762 p_desc = descNext(p_desc);
1763 if (descType(p_desc) == kUsbDescTypeCsInterface &&
1764 descSubtype(p_desc) == kAudioCsAsFormatType &&
1765 p_desc[3] == AUDIO_FORMAT_TYPE_I) {
1766 audio->n_bytes_per_sample_tx = p_desc[4];
1767 }
1768 }
1769
1770 // Fallback from config if descriptor parsing missed any field.
1771 // The descriptor struct layout may differ across TinyUSB versions.
1772 if (audio->n_channels_tx == 0) audio->n_channels_tx = config_.channels;
1773 if (audio->n_bytes_per_sample_tx == 0)
1774 audio->n_bytes_per_sample_tx = config_.bits_per_sample / 8;
1775 if (audio->format_type_tx == 0) audio->format_type_tx = AUDIO_FORMAT_TYPE_I;
1776 }
1777
1778 // This helper function finds for a given audio function and AS interface
1779 // number the index of the attached driver structure, the index of the
1780 // interface in the audio function
1781 // (e.g. the std. AS interface with interface number 15 is the first AS
1782 // interface for the given audio function and thus gets index zero), and
1783 // finally a pointer to the std. AS interface, where the pointer always points
1784 // to the first alternate setting i.e. alternate interface zero.
1785 bool audiod_get_AS_interface_index(uint8_t itf, audiod_function_t* audio,
1786 uint8_t* idxItf,
1787 uint8_t const** pp_desc_int) {
1788 if (audio->p_desc) {
1789 // Get pointer at end
1790 uint8_t const* p_desc_end =
1791 audio->p_desc + audio->desc_length - kAudioDescIadLen;
1792
1793 // Advance past AC descriptors
1794 uint8_t const* p_desc = descNext(audio->p_desc);
1795 p_desc += descU16(p_desc + 6); // AC header wTotalLength
1796
1797 uint8_t tmp = 0;
1798 // Condition modified from p_desc < p_desc_end to prevent gcc>=12
1799 // strict-overflow warning
1800 while (p_desc_end - p_desc > 0) {
1801 // We assume the number of alternate settings is increasing thus we
1802 // return the index of alternate setting zero!
1803 if (descType(p_desc) == kUsbDescTypeInterface &&
1804 decodeInterface(p_desc).bAlternateSetting == 0) {
1805 if (decodeInterface(p_desc).bInterfaceNumber == itf) {
1806 *idxItf = tmp;
1807 *pp_desc_int = p_desc;
1808 return true;
1809 }
1810 // Increase index, bytes read, and pointer
1811 tmp++;
1812 }
1813 p_desc = descNext(p_desc);
1814 }
1815 }
1816 return false;
1817 }
1818
1819 // This helper function finds for a given AS interface number the index of the
1820 // attached driver structure, the index of the interface in the audio function
1821 // (e.g. the std. AS interface with interface number 15 is the first AS
1822 // interface for the given audio function and thus gets index zero), and
1823 // finally a pointer to the std. AS interface, where the pointer always points
1824 // to the first alternate setting i.e. alternate interface zero.
1825 bool audiod_get_AS_interface_index_global(uint8_t itf, uint8_t* func_id,
1826 uint8_t* idxItf,
1827 uint8_t const** pp_desc_int) {
1828 // Loop over audio driver interfaces
1829 uint8_t i;
1830 for (i = 0; i < getAudioCount(); i++) {
1831 if (audiod_get_AS_interface_index(itf, &audiod_fct_[i], idxItf,
1832 pp_desc_int)) {
1833 *func_id = i;
1834 return true;
1835 }
1836 }
1837
1838 return false;
1839 }
1840
1841 bool audiod_get_interface(uint8_t rhport,
1842 UsbSetupPacket const& p_request) {
1843 uint8_t const itf = u16Low(p_request.wIndex);
1844
1845 // Find index of audio streaming interface
1846 uint8_t func_id, idxItf;
1847 uint8_t const* dummy;
1848
1849 if (!audiod_get_AS_interface_index_global(itf, &func_id, &idxItf, &dummy))
1850 return false;
1851 if (!backend().controlTransfer(
1852 rhport, p_request, &audiod_fct_[func_id].alt_setting[idxItf], 1))
1853 return false;
1854
1855 LOGI(" Get itf: %u - current alt: %u", itf,
1856 audiod_fct_[func_id].alt_setting[idxItf]);
1857
1858 return true;
1859 }
1860
1861 bool audiod_fb_send(audiod_function_t* audio) {
1862 bool apply_correction =
1863 backend().isFullSpeed() && audio->feedback.format_correction;
1864 // Format the feedback value
1865 if (apply_correction) {
1866 uint8_t* fb = (uint8_t*)audio->fb_buf.data();
1867
1868 // For FS format is 10.14
1869 *(fb++) = (audio->feedback.value >> 2) & 0xFF;
1870 *(fb++) = (audio->feedback.value >> 10) & 0xFF;
1871 *(fb++) = (audio->feedback.value >> 18) & 0xFF;
1872 *fb = 0;
1873 } else {
1874 audio->fb_buf[0] = audio->feedback.value;
1875 }
1876
1877 // About feedback format on FS
1878 //
1879 // 3 variables: Format | packetSize | sendSize | Working OS:
1880 // 16.16 4 4 Linux, Windows
1881 // 16.16 4 3 Linux
1882 // 16.16 3 4 Linux
1883 // 16.16 3 3 Linux
1884 // 10.14 4 4 Linux
1885 // 10.14 4 3 Linux
1886 // 10.14 3 4 Linux, OSX
1887 // 10.14 3 3 Linux, OSX
1888 //
1889 // We send 3 bytes since sending packet larger than wMaxPacketSize is pretty
1890 // ugly
1891 return backend().transfer(audio->rhport, audio->ep_fb,
1892 (uint8_t*)audio->fb_buf.data(),
1893 apply_correction ? 3 : 4);
1894 }
1895
1896 // ── Close existing EPs for this interface ──────────────────────────────
1897 void closeEpIn(uint8_t rhport, audiod_function_t* audio, uint8_t itf,
1898 UsbSetupPacket const& p_request) {
1899 if (!isEpInEnabled() || audio->ep_in_as_intf_num != itf) return;
1900 audio->ep_in_as_intf_num = 0;
1901 if (!backend().usesIsoAlloc()) backend().closeEndpoint(rhport, audio->ep_in);
1902 bufferTx().reset();
1904 tud_audio_set_itf_close_EP_cb_(this, rhport, p_request);
1905 audio->ep_in = 0;
1907 audio->packet_sz_tx[0] = 0;
1908 audio->packet_sz_tx[1] = 0;
1909 audio->packet_sz_tx[2] = 0;
1910 }
1912 }
1913
1914 void closeEpOut(uint8_t rhport, audiod_function_t* audio, uint8_t itf,
1915 UsbSetupPacket const& p_request) {
1916 if (!isEpOutEnabled() || audio->ep_out_as_intf_num != itf) return;
1917 audio->ep_out_as_intf_num = 0;
1918 if (!backend().usesIsoAlloc()) {
1919 backend().closeEndpoint(rhport, audio->ep_out);
1920 } else {
1921 // usesIsoAlloc() backends never close the OUT endpoint between
1922 // sessions (DPRAM stays allocated), so nothing resets the DCD's
1923 // internal busy/claimed bookkeeping for it. If the transfer armed by
1924 // the *previous* openEpOut() never completed (no host data arrived,
1925 // or the session was torn down mid-flight), that endpoint stays
1926 // "busy" forever and every later usbd_edpt_xfer() call in openEpOut()
1927 // silently fails, permanently breaking RX after the first
1928 // open/close cycle. Re-activating here -- while the host is quiet
1929 // (it just asked to close the stream) rather than right as a new
1930 // session starts -- resets busy/claimed to a known-good state for
1931 // the next open, matching the reasoning in audiod_open() for why
1932 // this can't be done at SET_INTERFACE(alt=1) time instead.
1933 backend().isoActivateEndpoint(rhport, audio->ep_out_view);
1934 }
1936 tud_audio_set_itf_close_EP_cb_(this, rhport, p_request);
1937 audio->ep_out = 0;
1938 if (isFeedbackEpEnabled()) {
1939 audio->ep_fb = 0;
1940 memset(&audio->feedback, 0, sizeof(audio->feedback));
1941 }
1943 }
1944
1945 // ── Activate a single endpoint found in the descriptor ────────────────
1946 bool activateEndpoint(uint8_t rhport, const UsbEndpointDescriptorView& desc_ep,
1947 UsbDir dir = UsbDir::In) {
1948 if (backend().usesIsoAlloc()) {
1949 // Skip iso_activate for isochronous OUT — on ESP32's DWC2 it blocks
1950 // for the entire playback duration. The endpoint DPRAM was already
1951 // allocated by iso_alloc in audiod_open(). The XFER call in
1952 // openEpOut will configure the DCD to receive.
1953 if (dir == UsbDir::Out && desc_ep.xferType == UsbXferType::Isochronous)
1954 return true;
1955 return backend().isoActivateEndpoint(rhport, desc_ep);
1956 }
1957 (void)dir;
1958 return backend().openEndpoint(rhport, desc_ep);
1959 }
1960
1961 // ── Open the IN (TX) data endpoint ────────────────────────────────────
1962 void openEpIn(uint8_t rhport, audiod_function_t* audio, uint8_t itf,
1963 const UsbEndpointDescriptorView& desc_ep,
1964 uint8_t const* p_desc_for_params) {
1965 audio->ep_in = desc_ep.bEndpointAddress;
1966 audio->ep_in_as_intf_num = itf;
1967 audio->ep_in_sz = desc_ep.packetSize();
1968 if (audio->ep_in_sz == 0) return;
1969
1971 audiod_parse_flow_control_params(audio, p_desc_for_params);
1972
1973 // Arm initial transfer (silence — copier fills the buffer).
1974 uint16_t first_pkt = packetSize();
1975 if (first_pkt > audio->ep_in_sz) first_pkt = audio->ep_in_sz;
1976 audio->lin_buf_in.assign(audio->ep_in_sz, 0);
1977 tx_xfer_armed_ = backend().transfer(rhport, audio->ep_in,
1978 audio->lin_buf_in.data(), first_pkt);
1980 }
1981
1982 // ── Open the OUT (RX) data endpoint ───────────────────────────────────
1983 void openEpOut(uint8_t rhport, audiod_function_t* audio, uint8_t itf,
1984 const UsbEndpointDescriptorView& desc_ep) {
1985 audio->ep_out = desc_ep.bEndpointAddress;
1986 audio->ep_out_as_intf_num = itf;
1987 audio->ep_out_sz = desc_ep.packetSize();
1988 if (audio->ep_out_sz == 0) return;
1989
1990 // iso_activate was done in audiod_open() (no traffic, instant).
1991 // Just arm the transfer here.
1992 if (audio->lin_buf_out.size() < audio->ep_out_sz)
1993 audio->lin_buf_out.assign(audio->ep_out_sz, 0);
1994 backend().transfer(rhport, audio->ep_out, audio->lin_buf_out.data(),
1995 audio->ep_out_sz);
1997 }
1998
1999 // ── Open the explicit feedback endpoint ───────────────────────────────
2000 void openEpFeedback(audiod_function_t* audio,
2001 const UsbEndpointDescriptorView& desc_ep) {
2002 audio->ep_fb = desc_ep.bEndpointAddress;
2003 audio->feedback.frame_shift = desc_ep.bInterval - 1;
2004 }
2005
2006 // ── Configure feedback computation parameters ─────────────────────────
2007 void setupFeedback(audiod_function_t* audio, uint8_t func_id, uint8_t alt) {
2008 if (!isFeedbackEpEnabled() || audio->ep_fb == 0) return;
2009
2010 audio_feedback_params_t fb_param = {};
2011 fb_param.method = AUDIO_FEEDBACK_METHOD_FIFO_COUNT;
2012 fb_param.sample_freq = config_.sample_rate;
2014 tud_audio_feedback_params_cb_(this, func_id, alt, &fb_param);
2015 audio->feedback.compute_method = fb_param.method;
2016
2018 audio->feedback.format_correction =
2020
2021 uint32_t const frame_div = backend().isFullSpeed() ? 1000 : 8000;
2022 audio->feedback.min_value = ((fb_param.sample_freq - 1) / frame_div) << 16;
2023 audio->feedback.max_value = (fb_param.sample_freq / frame_div + 1) << 16;
2024
2025 switch (fb_param.method) {
2026 case AUDIO_FEEDBACK_METHOD_FREQUENCY_FIXED:
2027 case AUDIO_FEEDBACK_METHOD_FREQUENCY_FLOAT:
2028 case AUDIO_FEEDBACK_METHOD_FREQUENCY_POWER_OF_2:
2029 audiod_set_fb_params_freq(audio, fb_param.sample_freq,
2030 fb_param.frequency.mclk_freq);
2031 break;
2032 case AUDIO_FEEDBACK_METHOD_FIFO_COUNT: {
2033 // Use bufferRx() size — ep_out_ff may be uninitialized in linear buffer mode
2034 uint16_t fifo_depth = bufferRx().size();
2035 if (fifo_depth == 0) fifo_depth = 1; // guard against div-by-zero
2036 uint16_t fifo_lvl_thr = fifo_depth / 2;
2037 audio->feedback.compute.fifo_count.fifo_lvl_thr = fifo_lvl_thr;
2038 // fifo_lvl_avg is a Q8 exponential moving average (see the update
2039 // formula in tud_audio_feedback_interval_isr: `avg = fifo_lvl_avg
2040 // >> 8`, fed by `ff_count << 8` each tick) -- seed it in the same
2041 // Q8 scale. It was previously seeded as `fifo_lvl_thr << 16` (Q16,
2042 // 256x too large), which pinned the very first ~1400 feedback
2043 // updates (~1.4s at 1kHz) at feedback.max_value before decaying
2044 // into a sane range.
2045 audio->feedback.compute.fifo_count.fifo_lvl_avg =
2046 ((uint32_t)fifo_lvl_thr) << 8;
2047 uint32_t nominal =
2048 ((fb_param.sample_freq / 100) << 16) / (frame_div / 100);
2049 audio->feedback.compute.fifo_count.nom_value = nominal;
2050 audio->feedback.compute.fifo_count.rate_const[0] =
2051 (uint16_t)((audio->feedback.max_value - nominal) / fifo_lvl_thr);
2052 audio->feedback.compute.fifo_count.rate_const[1] =
2053 (uint16_t)((nominal - audio->feedback.min_value) / fifo_lvl_thr);
2054 if (backend().isHighSpeed()) {
2055 audio->feedback.compute.fifo_count.rate_const[0] /= 8;
2056 audio->feedback.compute.fifo_count.rate_const[1] /= 8;
2057 }
2058 } break;
2059 default:
2060 break;
2061 }
2062 }
2063
2064 // ── Scan descriptor for endpoints and open them ───────────────────────
2065 bool openEndpointsForAltSetting(uint8_t rhport, audiod_function_t* audio,
2066 uint8_t func_id, uint8_t itf, uint8_t alt) {
2067 uint8_t const* p_desc = audio->p_desc;
2068 uint8_t const* p_desc_end =
2069 p_desc + audio->desc_length - kAudioDescIadLen;
2070 LOGD(" openEPs: p_desc=%p end=%p len=%u itf=%u alt=%u",
2071 p_desc, p_desc_end, audio->desc_length, itf, alt);
2072
2073 while (p_desc_end - p_desc > 0) {
2074 if (descType(p_desc) == kUsbDescTypeInterface &&
2075 decodeInterface(p_desc).bInterfaceNumber == itf &&
2076 decodeInterface(p_desc).bAlternateSetting == alt) {
2077 uint8_t const* p_desc_for_params =
2078 (isEpInEnabled() && isEpInFlowControlEnabled()) ? p_desc : nullptr;
2079 uint8_t foundEPs = 0;
2080 uint8_t nEps = decodeInterface(p_desc).bNumEndpoints;
2081 LOGD(" matched itf=%u alt=%u nEps=%u", itf, alt, nEps);
2082
2083 while (foundEPs < nEps && (p_desc_end - p_desc > 0)) {
2084 LOGD(" scan: type=0x%02x len=%u offset=%d",
2085 p_desc[1], p_desc[0], (int)(p_desc - audio->p_desc));
2086 if (descType(p_desc) == kUsbDescTypeEndpoint) {
2087 UsbEndpointDescriptorView desc_ep = decodeEndpoint(p_desc);
2088
2089 LOGD(" activating ep=0x%02x type=%u...",
2090 desc_ep.bEndpointAddress, (unsigned)desc_ep.xferType);
2091 if (!activateEndpoint(rhport, desc_ep, desc_ep.direction())) {
2092 LOGD(" activateEndpoint FAILED");
2093 p_desc = descNext(p_desc);
2094 continue;
2095 }
2096 LOGD(" activated OK");
2097 // Skip clear_stall for isochronous OUT (iso_activate was also
2098 // skipped). For other endpoints, clear the stall as usual.
2099 if (!(desc_ep.direction() == UsbDir::Out &&
2101 backend().clearStall(rhport, desc_ep.bEndpointAddress);
2102
2103 uint8_t ep_addr = desc_ep.bEndpointAddress;
2104 if (isEpInEnabled() && desc_ep.direction() == UsbDir::In &&
2105 desc_ep.usage == 0x00)
2106 openEpIn(rhport, audio, itf, desc_ep, p_desc_for_params);
2107
2108 if (isEpOutEnabled()) {
2109 if (desc_ep.direction() == UsbDir::Out)
2110 openEpOut(rhport, audio, itf, desc_ep);
2111 if (isFeedbackEpEnabled() &&
2112 desc_ep.direction() == UsbDir::In && desc_ep.usage == 1)
2113 openEpFeedback(audio, desc_ep);
2114 }
2115 foundEPs += 1;
2116 }
2117 p_desc = descNext(p_desc);
2118 }
2119
2120 if (foundEPs != nEps) return true; // ZLP already sent
2121
2123 tud_audio_set_itf_cb_(this, rhport, UsbSetupPacket{});
2124
2125 setupFeedback(audio, func_id, alt);
2126 return true;
2127 }
2128 p_desc = descNext(p_desc);
2129 }
2130 return true;
2131 }
2132
2133 // ── Main SET_INTERFACE handler ────────────────────────────────────────
2134 bool audiod_set_interface(uint8_t rhport,
2135 UsbSetupPacket const& p_request) {
2136 uint8_t const itf = u16Low(p_request.wIndex);
2137 uint8_t const alt = u16Low(p_request.wValue);
2138 LOGD("SET_ITF itf=%u alt=%u [start]", itf, alt);
2139
2140 uint8_t func_id, idxItf;
2141 uint8_t const* p_desc;
2142 if (!audiod_get_AS_interface_index_global(itf, &func_id, &idxItf,
2143 &p_desc)) {
2144 LOGD(" AS interface %u not found", itf);
2145 backend().controlStatus(rhport, p_request);
2146 return true;
2147 }
2148 LOGD(" found func=%u idx=%u", func_id, idxItf);
2149
2150 audiod_function_t* audio = &audiod_fct_[func_id];
2151
2152 // 1. Close existing EPs
2153 LOGD(" close EPs");
2154 closeEpIn(rhport, audio, itf, p_request);
2155 closeEpOut(rhport, audio, itf, p_request);
2156
2157 // 2. Save alt setting and acknowledge
2158 audio->alt_setting[idxItf] = alt;
2159
2160 backend().controlStatus(rhport, p_request);
2161 openEndpointsForAltSetting(rhport, audio, func_id, itf, alt);
2162
2163 // 4. Update SOF and flow control
2164 if (isFeedbackEpEnabled()) {
2165 bool enable_sof = false;
2166 for (uint8_t i = 0; i < getAudioCount(); i++) {
2167 if (audiod_fct_[i].ep_fb != 0) {
2168 enable_sof = true;
2169 break;
2170 }
2171 }
2172 backend().enableSof(rhport, enable_sof);
2173 }
2176
2177 return true;
2178 }
2179
2180 static bool isPowerOfTwo(uint32_t value) {
2181 return value != 0 && (value & (value - 1)) == 0;
2182 }
2183
2184 static uint8_t log2Floor(uint32_t value) {
2185 uint8_t result = 0;
2186 while ((value >>= 1u) != 0u) result++;
2187 return result;
2188 }
2189
2190 bool audiod_set_fb_params_freq(audiod_function_t* audio, uint32_t sample_freq,
2191 uint32_t mclk_freq) {
2192 // Check if frame interval is within sane limits
2193 // The interval value n_frames was taken from the descriptors within
2194
2195 // n_frames_min is ceil(2^10 * f_s / f_m) for full speed and ceil(2^13 * f_s
2196 // / f_m) for high speed this lower limit ensures the measures feedback
2197 // value has sufficient precision
2198 uint32_t const k = backend().isFullSpeed() ? 10 : 13;
2199 uint32_t const n_frame = (1UL << audio->feedback.frame_shift);
2200
2201 if ((((1UL << k) * sample_freq / mclk_freq) + 1) > n_frame) {
2202 LOGE(" UAC2 feedback interval too small");
2203 return false;
2204 }
2205
2206 // Check if parameters really allow for a power of two division
2207 if ((mclk_freq % sample_freq) == 0 &&
2208 isPowerOfTwo(mclk_freq / sample_freq)) {
2209 audio->feedback.compute_method =
2210 AUDIO_FEEDBACK_METHOD_FREQUENCY_POWER_OF_2;
2211 audio->feedback.compute.power_of_2 =
2212 (uint8_t)(16 - (audio->feedback.frame_shift - 1) -
2213 log2Floor(mclk_freq / sample_freq));
2214 } else if (audio->feedback.compute_method ==
2215 AUDIO_FEEDBACK_METHOD_FREQUENCY_FLOAT) {
2216 audio->feedback.compute.float_const =
2217 (float)sample_freq / (float)mclk_freq *
2218 (1UL << (16 - (audio->feedback.frame_shift - 1)));
2219 } else {
2220 audio->feedback.compute.fixed.sample_freq = sample_freq;
2221 audio->feedback.compute.fixed.mclk_freq = mclk_freq;
2222 }
2223
2224 return true;
2225 }
2226
2227 bool audiod_calc_tx_packet_sz(audiod_function_t* audio) {
2228 if (audio->format_type_tx != AUDIO_FORMAT_TYPE_I) return false;
2229 if (!audio->n_channels_tx) return false;
2230 if (!audio->n_bytes_per_sample_tx) return false;
2231 if (!audio->interval_tx) return false;
2232 if (!audio->sample_rate_tx) return false;
2233
2234 // Restart the fractional accumulator for this streaming session.
2235 audio->tx_sample_acc = 0;
2236
2237 bool full_speed = backend().isFullSpeed();
2238 const uint8_t interval =
2239 full_speed ? audio->interval_tx : 1 << (audio->interval_tx - 1);
2240
2241 const uint16_t sample_normimal = (uint16_t)(audio->sample_rate_tx *
2242 interval / (full_speed ? 1000 : 8000));
2243 const uint16_t sample_reminder = (uint16_t)(audio->sample_rate_tx *
2244 interval % (full_speed ? 1000 : 8000));
2245
2246 const uint16_t packet_sz_tx_min =
2247 (uint16_t)((sample_normimal - 1) * audio->n_channels_tx *
2248 audio->n_bytes_per_sample_tx);
2249 const uint16_t packet_sz_tx_norm =
2250 (uint16_t)(sample_normimal * audio->n_channels_tx *
2251 audio->n_bytes_per_sample_tx);
2252 const uint16_t packet_sz_tx_max =
2253 (uint16_t)((sample_normimal + 1) * audio->n_channels_tx *
2254 audio->n_bytes_per_sample_tx);
2255
2256 // Endpoint size must larger than packet size
2257 if (packet_sz_tx_max > audio->ep_in_sz) return false;
2258
2259 // Frmt20.pdf 2.3.1.1 USB Packets
2260 if (sample_reminder) {
2261 // All virtual frame packets must either contain INT(nav) audio slots
2262 // (small VFP) or INT(nav)+1 (large VFP) audio slots
2263 audio->packet_sz_tx[0] = packet_sz_tx_norm;
2264 audio->packet_sz_tx[1] = packet_sz_tx_norm;
2265 audio->packet_sz_tx[2] = packet_sz_tx_max;
2266 } else {
2267 // In the case where nav = INT(nav), ni may vary between INT(nav)-1 (small
2268 // VFP), INT(nav) (medium VFP) and INT(nav)+1 (large VFP).
2269 audio->packet_sz_tx[0] = packet_sz_tx_min;
2270 audio->packet_sz_tx[1] = packet_sz_tx_norm;
2271 audio->packet_sz_tx[2] = packet_sz_tx_max;
2272 }
2273
2274 return true;
2275 }
2276
2277 // Number of audio bytes to transmit in the current (micro)frame when IN
2278 // flow control is enabled. A fractional accumulator distributes the
2279 // sub-frame sample remainder over successive frames so the long-term average
2280 // matches the configured sample rate (e.g. alternating 176/180 bytes for
2281 // 44100 Hz stereo 16-bit, averaging 176.4 bytes = 44.1 samples per frame).
2282 uint16_t audiod_tx_packet_size_fc(audiod_function_t* audio) {
2283 if (audio->sample_rate_tx == 0 || audio->n_channels_tx == 0 ||
2284 audio->n_bytes_per_sample_tx == 0) {
2285 // Not enough info to size precisely: fall back to the max packet.
2286 return audio->ep_in_sz;
2287 }
2288 bool full_speed = backend().isFullSpeed();
2289 const uint32_t denom = full_speed ? 1000u : 8000u;
2290 const uint8_t iv = audio->interval_tx ? audio->interval_tx : 1;
2291 const uint32_t interval = full_speed ? iv : (1u << (iv - 1));
2292
2293 audio->tx_sample_acc += audio->sample_rate_tx * interval;
2294 const uint32_t samples = audio->tx_sample_acc / denom;
2295 audio->tx_sample_acc -= samples * denom;
2296
2297 uint32_t bytes =
2298 samples * audio->n_channels_tx * audio->n_bytes_per_sample_tx;
2299 if (bytes > audio->ep_in_sz) bytes = audio->ep_in_sz;
2300 return (uint16_t)bytes;
2301 }
2302
2303};
2304
2305} // namespace audio_tools
#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 USB_DESCR_MAX_LEN
Definition USBAudioDeviceBase.h:47
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:29
const uint16_t buildFullDescriptor(uint8_t *desc)
Definition USBAudio2DescriptorBuilder.h:53
static constexpr uint8_t ENTITY_FU2
second Feature Unit (RXTX)
Definition USBAudio2DescriptorBuilder.h:39
static constexpr uint8_t ENTITY_FU1
first Feature Unit
Definition USBAudio2DescriptorBuilder.h:36
int audioFunctionsCount() const
Definition USBAudio2DescriptorBuilder.h:144
static constexpr uint8_t ENTITY_CLOCK
Definition USBAudio2DescriptorBuilder.h:34
uint16_t calcPacketSizeForRate(uint32_t rate) const
Definition USBAudio2DescriptorBuilder.h:162
uint16_t calcMaxPacketSize() const
Definition USBAudio2DescriptorBuilder.h:174
bool enableFeedbackEp() const
Definition USBAudio2DescriptorBuilder.h:153
Abstract seam for every raw USB-stack call the UAC2 driver (USBAudioDeviceBase) needs....
Definition USBAudioBackend.h:160
virtual bool isoActivateEndpoint(uint8_t rhport, const UsbEndpointDescriptorView &ep)=0
virtual bool controlTransfer(uint8_t rhport, const UsbSetupPacket &request, uint8_t *buffer, uint16_t length)=0
Complete a GET/SET control transfer's data stage with buffer.
bool isFullSpeed() const
Convenience: speed() == UsbSpeed::Full.
Definition USBAudioBackend.h:218
virtual bool claimEndpoint(uint8_t rhport, uint8_t ep_addr)=0
virtual bool closeEndpoint(uint8_t rhport, uint8_t ep_addr)=0
virtual bool openEndpoint(uint8_t rhport, const UsbEndpointDescriptorView &ep)=0
virtual bool controlStatus(uint8_t rhport, const UsbSetupPacket &request)=0
Send a zero-length-packet status acknowledgement.
virtual bool usesIsoAlloc() const =0
virtual bool transfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t length)=0
bool isHighSpeed() const
Convenience: speed() == UsbSpeed::High.
Definition USBAudioBackend.h:220
virtual bool clearStall(uint8_t rhport, uint8_t ep_addr)=0
Clear a halted/stalled condition on an endpoint.
virtual bool mounted() const =0
True once the device has completed USB enumeration.
virtual void enableSof(uint8_t rhport, bool enable)=0
virtual bool isoAllocEndpoint(uint8_t rhport, uint8_t ep_addr, uint16_t max_packet_size)=0
USB Audio Device class for audio streaming over USB.
Definition USBAudioDeviceBase.h:109
std::function< void(float, uint8_t)> volume_cb_
Definition USBAudioDeviceBase.h:800
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:846
bool begin(const USBAudioConfig &cfg)
Apply a config and start the USB audio device.
Definition USBAudioDeviceBase.h:310
std::function< bool(USBAudioDeviceBase *, uint8_t rhport, UsbSetupPacket const &p_request)> tud_audio_set_itf_cb_
Definition USBAudioDeviceBase.h:827
void setRxDoneCallback(std::function< bool(USBAudioDeviceBase *, uint8_t, audiod_function_t *, uint16_t)> cb)
Register a callback for RX done events.
Definition USBAudioDeviceBase.h:562
bool setVolume(float vol, uint8_t channel)
Set the volume for a channel and notify the host.
Definition USBAudioDeviceBase.h:423
volatile uint32_t xfer_cb_rx_count_
Definition USBAudioDeviceBase.h:791
volatile uint32_t xfer_cb_tx_count_
Definition USBAudioDeviceBase.h:789
USBAudioDeviceBase(USBAudioConfig cfg)
Constructor which provides configuration at construction time.
Definition USBAudioDeviceBase.h:237
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:972
bool audiod_control_xfer_cb(uint8_t rhport, uint8_t stage, UsbSetupPacket const &request)
Definition USBAudioDeviceBase.h:1317
std::vector< float > volume_
Definition USBAudioDeviceBase.h:798
bool handleInterfaceRequest(uint8_t rhport, UsbSetupPacket const &p_request)
Definition USBAudioDeviceBase.h:1590
bool isEpInEnabled() const
Returns true if the IN endpoint is enabled.
Definition USBAudioDeviceBase.h:387
void audiod_sof_isr(uint8_t rhport, uint32_t frame_count)
Definition USBAudioDeviceBase.h:1458
bool audiod_fb_send(audiod_function_t *audio)
Definition USBAudioDeviceBase.h:1861
bool audiod_set_interface(uint8_t rhport, UsbSetupPacket const &p_request)
Definition USBAudioDeviceBase.h:2134
void setVolumeCallback(std::function< void(float, uint8_t)> cb)
Register a callback invoked when the host (or device) changes the volume.
Definition USBAudioDeviceBase.h:456
void setTxDoneCallback(std::function< bool(USBAudioDeviceBase *, uint8_t, audiod_function_t *)> cb)
Register a callback for TX done events.
Definition USBAudioDeviceBase.h:552
bool isMute(uint8_t channel=0) const
Returns the current mute state for the given channel.
Definition USBAudioDeviceBase.h:435
void setReqEntityCallback(std::function< bool(USBAudioDeviceBase *, uint8_t)> cb)
Register a callback for entity requests.
Definition USBAudioDeviceBase.h:572
std::function< bool(USBAudioDeviceBase *, uint8_t rhport, UsbSetupPacket const &p_request, uint8_t *pBuff)> tud_audio_set_req_itf_cb_
Definition USBAudioDeviceBase.h:834
bool isFeatureUnit(uint8_t id) const
Returns true if the given entity ID is a Feature Unit (FU1 or FU2).
Definition USBAudioDeviceBase.h:993
void setAudioFeedbackFormatCorrectionCallback(std::function< bool(USBAudioDeviceBase *, uint8_t)> cb)
Register a callback for audio feedback format correction events.
Definition USBAudioDeviceBase.h:646
uint16_t audiod_open(uint8_t rhport, UsbInterfaceDescriptorView const &itf_desc, uint8_t const *raw_desc, uint16_t max_len)
Definition USBAudioDeviceBase.h:1175
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:976
uint32_t getTxSampleRate() const
TX sample rate parsed from the descriptor (must be non-zero for flow control).
Definition USBAudioDeviceBase.h:769
std::function< bool(USBAudioDeviceBase *, uint8_t rhport, audiod_function_t *)> tx_done_cb_
Definition USBAudioDeviceBase.h:809
std::function< void(USBAudioDeviceBase *, uint8_t func_id)> fb_done_cb_
Definition USBAudioDeviceBase.h:823
bool mounted()
Returns true if the device is mounted by the USB host.
Definition USBAudioDeviceBase.h:509
void openEpIn(uint8_t rhport, audiod_function_t *audio, uint8_t itf, const UsbEndpointDescriptorView &desc_ep, uint8_t const *p_desc_for_params)
Definition USBAudioDeviceBase.h:1962
void closeEpOut(uint8_t rhport, audiod_function_t *audio, uint8_t itf, UsbSetupPacket const &p_request)
Definition USBAudioDeviceBase.h:1914
void setGetReqEpCallback(std::function< bool(USBAudioDeviceBase *, uint8_t, UsbSetupPacket const &)> cb)
Register a callback for GET requests on an endpoint.
Definition USBAudioDeviceBase.h:525
float volume(uint8_t channel)
Returns the current volume for the given channel.
Definition USBAudioDeviceBase.h:415
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:1825
static USBAudioDeviceBase * s_active_
Definition USBAudioDeviceBase.h:861
virtual USBAudioBackend & backend()=0
Returns the backend used for all raw USB-stack calls (TinyUSB today, or a future native-HAL backend)....
bool isStreamingActiveTx() const
Returns true if the host has opened the IN (capture) stream.
Definition USBAudioDeviceBase.h:488
std::function< bool(USBAudioDeviceBase *, uint8_t rhport, UsbSetupPacket const &)> get_req_ep_cb_
Definition USBAudioDeviceBase.h:820
uint16_t getDescLen(uint8_t fn) const
Definition USBAudioDeviceBase.h:1029
virtual BaseBuffer< uint8_t > & bufferTx()=0
Returns the TX audio buffer. Must be overridden by subclasses.
USBAudioConfig config_
Definition USBAudioDeviceBase.h:804
void audiod_parse_flow_control_params(audiod_function_t *audio, uint8_t const *p_desc)
Definition USBAudioDeviceBase.h:1739
void setIntDoneCallback(std::function< void(USBAudioDeviceBase *, uint8_t)> cb)
Register a callback for interrupt done events.
Definition USBAudioDeviceBase.h:543
void setConfig(const USBAudioConfig &cfg)
Set the USB audio configuration (use begin(cfg) instead).
Definition USBAudioDeviceBase.h:864
volatile uint32_t tx_fifo_read_total_
Definition USBAudioDeviceBase.h:790
std::function< bool(USBAudioDeviceBase *, uint8_t rhport, UsbSetupPacket const &p_request)> tud_audio_set_itf_close_EP_cb_
Definition USBAudioDeviceBase.h:842
uint32_t getRxXferCount() const
Number of times audiod_xfer_cb fired for the OUT endpoint.
Definition USBAudioDeviceBase.h:755
void setItfCloseEpCallback(std::function< bool(USBAudioDeviceBase *, uint8_t, UsbSetupPacket const &)> cb)
Register a callback for interface close endpoint events.
Definition USBAudioDeviceBase.h:625
bool is_started_
Definition USBAudioDeviceBase.h:786
void audiod_init(void)
Definition USBAudioDeviceBase.h:1125
volatile uint32_t rx_total_bytes_
Definition USBAudioDeviceBase.h:792
bool activateEndpoint(uint8_t rhport, const UsbEndpointDescriptorView &desc_ep, UsbDir dir=UsbDir::In)
Definition USBAudioDeviceBase.h:1946
bool usb_task_active_
Definition USBAudioDeviceBase.h:787
bool tx_xfer_armed_
Definition USBAudioDeviceBase.h:788
void setGetReqItfCallback(std::function< bool(USBAudioDeviceBase *, uint8_t, UsbSetupPacket const &)> cb)
Register a callback for GET requests on the interface.
Definition USBAudioDeviceBase.h:515
bool handleClockSourceGet(uint8_t rhport, UsbSetupPacket const &p_request, uint8_t *cb)
Definition USBAudioDeviceBase.h:1479
volatile uint16_t tx_frame_bytes_last_
Definition USBAudioDeviceBase.h:793
uint32_t getRxTotalBytes() const
Total bytes received from host via OUT endpoint.
Definition USBAudioDeviceBase.h:757
bool begin()
(Re-)start the USB audio device with the current config.
Definition USBAudioDeviceBase.h:327
bool isStreamingActiveRx() const
Returns true if the host has opened the OUT (playback) stream.
Definition USBAudioDeviceBase.h:496
bool setVolume(float volume) override
sets the volume for the master channel (channel 0)
Definition USBAudioDeviceBase.h:410
bool handleEndpointRequest(uint8_t rhport, UsbSetupPacket const &p_request)
Definition USBAudioDeviceBase.h:1605
uint8_t getAudioCount() const
Returns the number of audio functions (always 1).
Definition USBAudioDeviceBase.h:506
int available() override
Bytes of received audio waiting in the RX buffer.
Definition USBAudioDeviceBase.h:694
bool audiod_deinit(void)
Definition USBAudioDeviceBase.h:1157
bool is_active_
Definition USBAudioDeviceBase.h:796
std::function< void(bool, bool)> streaming_state_cb_
Definition USBAudioDeviceBase.h:803
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:1005
float volume() override
gets the volume for the master channel (channel 0)
Definition USBAudioDeviceBase.h:407
static bool isValidBitsPerSample(uint8_t bps)
Definition USBAudioDeviceBase.h:1033
bool audiod_calc_tx_packet_sz(audiod_function_t *audio)
Definition USBAudioDeviceBase.h:2227
std::function< bool(USBAudioDeviceBase *, uint8_t func_id)> req_entity_cb_
Definition USBAudioDeviceBase.h:824
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:463
virtual BaseBuffer< uint8_t > & bufferRx()=0
Returns the RX audio buffer. Must be overridden by subclasses.
void openEpOut(uint8_t rhport, audiod_function_t *audio, uint8_t itf, const UsbEndpointDescriptorView &desc_ep)
Definition USBAudioDeviceBase.h:1983
void setFbDoneCallback(std::function< void(USBAudioDeviceBase *, uint8_t)> cb)
Register a callback for feedback done events.
Definition USBAudioDeviceBase.h:535
void setTudAudioSetItfCallback(std::function< bool(USBAudioDeviceBase *, uint8_t, UsbSetupPacket const &)> cb)
Register a callback for interface set requests.
Definition USBAudioDeviceBase.h:581
std::function< void(uint32_t)> sample_rate_cb_
Definition USBAudioDeviceBase.h:802
bool audiod_control_complete(uint8_t rhport, UsbSetupPacket const &p_request)
Definition USBAudioDeviceBase.h:1328
USBAudioDeviceBase()
Default Constructor.
Definition USBAudioDeviceBase.h:234
int availableForWrite() override
Bytes of free space in the TX buffer.
Definition USBAudioDeviceBase.h:700
void setReqEpCallback(std::function< bool(USBAudioDeviceBase *, uint8_t, UsbSetupPacket const &, uint8_t *)> cb)
Register a callback for endpoint set requests.
Definition USBAudioDeviceBase.h:614
bool audiod_verify_entity_exists(uint8_t itf, uint8_t entityID, uint8_t *func_id)
Definition USBAudioDeviceBase.h:1655
void processVolume(uint8_t *data, size_t len)
Process audio data for volume control.
Definition USBAudioDeviceBase.h:894
virtual bool beginUSB()=0
Override in platform subclasses to register descriptors and start the USB host-controller stack (e....
uint16_t getDescriptor(uint8_t *desc)
Returns the audio-function descriptor block for use in tud_descriptor_configuration_cb().
Definition USBAudioDeviceBase.h:734
static constexpr size_t getResetSize()
Definition USBAudioDeviceBase.h:1047
void setSampleRate(uint32_t rate)
Device-initiated sample rate change.
Definition USBAudioDeviceBase.h:943
void tud_audio_feedback_interval_isr(uint8_t func_id, uint32_t, uint8_t frame_shift)
Definition USBAudioDeviceBase.h:1054
std::function< void(bool, uint8_t)> mute_cb_
Definition USBAudioDeviceBase.h:801
void setReqEntityCallback(std::function< bool(USBAudioDeviceBase *, uint8_t, UsbSetupPacket const &, uint8_t *)> cb)
Register a callback for entity set requests.
Definition USBAudioDeviceBase.h:592
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:984
void audiod_reset(uint8_t rhport)
Definition USBAudioDeviceBase.h:1161
std::function< bool(USBAudioDeviceBase *, uint8_t func_id)> tud_audio_feedback_format_correction_cb_
Definition USBAudioDeviceBase.h:849
bool openEndpointsForAltSetting(uint8_t rhport, audiod_function_t *audio, uint8_t func_id, uint8_t itf, uint8_t alt)
Definition USBAudioDeviceBase.h:2065
bool isInterruptEpEnabled() const
Returns true if the interrupt endpoint is enabled.
Definition USBAudioDeviceBase.h:503
std::function< bool(USBAudioDeviceBase *, uint8_t rhport, UsbSetupPacket const &p_request, uint8_t *pBuff)> tud_audio_set_req_entity_cb_
Definition USBAudioDeviceBase.h:830
uint32_t getTxFifoReadTotal() const
Definition USBAudioDeviceBase.h:760
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:744
std::vector< uint16_t > desc_len_
Definition USBAudioDeviceBase.h:853
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:812
bool isEpInFlowControlEnabled() const
Returns true if IN endpoint flow control is enabled. When on, the per-frame isochronous packet size i...
Definition USBAudioDeviceBase.h:400
void end()
Stop audio streaming and clear buffers. Does not disconnect USB.
Definition USBAudioDeviceBase.h:709
USBAudioConfig active_config_
Definition USBAudioDeviceBase.h:805
bool isTxXferArmed() const
True if the initial isochronous IN transfer was armed successfully.
Definition USBAudioDeviceBase.h:750
bool audiod_control_request(uint8_t rhport, UsbSetupPacket const &p_request)
Definition USBAudioDeviceBase.h:1620
void setSampleRateCallback(std::function< void(uint32_t)> cb)
Register a callback invoked when the host (or device) changes the sample rate.
Definition USBAudioDeviceBase.h:470
uint32_t getTxXferCount() const
Number of times audiod_xfer_cb fired for the IN endpoint.
Definition USBAudioDeviceBase.h:753
USBAudioConfig defaultConfig(RxTxMode mode=RXTX_MODE)
Returns a default configuration pre-filled for the requested direction (RX_MODE, TX_MODE,...
Definition USBAudioDeviceBase.h:246
bool audiod_get_interface(uint8_t rhport, UsbSetupPacket const &p_request)
Definition USBAudioDeviceBase.h:1841
void setupFeedback(audiod_function_t *audio, uint8_t func_id, uint8_t alt)
Definition USBAudioDeviceBase.h:2007
void closeEpIn(uint8_t rhport, audiod_function_t *audio, uint8_t itf, UsbSetupPacket const &p_request)
Definition USBAudioDeviceBase.h:1897
static USBAudioDeviceBase & activeInstance()
Returns the most-recently-constructed instance (base or subclass).
Definition USBAudioDeviceBase.h:384
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:1785
uint8_t getTxInterval() const
TX isochronous interval (bInterval) parsed from the descriptor.
Definition USBAudioDeviceBase.h:781
uint16_t audiod_tx_packet_size_fc(audiod_function_t *audio)
Definition USBAudioDeviceBase.h:2282
void setAudioInfo(AudioInfo info) override
Change the sample rate and notify the host.
Definition USBAudioDeviceBase.h:268
bool handleEntityRequest(uint8_t rhport, UsbSetupPacket const &p_request, uint8_t entityID)
Definition USBAudioDeviceBase.h:1556
volatile uint32_t tx_xferred_last_
Definition USBAudioDeviceBase.h:794
std::vector< uint16_t > ctrl_buf_sz_
Definition USBAudioDeviceBase.h:855
uint16_t getTxFrameBytesLast() const
Definition USBAudioDeviceBase.h:764
std::function< void(USBAudioDeviceBase *, uint8_t rhport)> int_done_cb_
Definition USBAudioDeviceBase.h:807
uint16_t packetSize() const
Definition USBAudioDeviceBase.h:1043
bool isFeedbackEpEnabled() const
Returns true if the feedback endpoint is enabled. Only meaningful in pure RX (OUT-only) mode: with an...
Definition USBAudioDeviceBase.h:395
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:916
void setReqItfCallback(std::function< bool(USBAudioDeviceBase *, uint8_t, UsbSetupPacket const &, uint8_t *)> cb)
Register a callback for interface set requests.
Definition USBAudioDeviceBase.h:603
void openEpFeedback(audiod_function_t *audio, const UsbEndpointDescriptorView &desc_ep)
Definition USBAudioDeviceBase.h:2000
std::vector< bool > mute_
Definition USBAudioDeviceBase.h:799
uint8_t int_notify_buf_[6]
Definition USBAudioDeviceBase.h:850
bool audiod_verify_itf_exists(uint8_t itf, uint8_t *func_id)
Definition USBAudioDeviceBase.h:1715
bool isStreamingActive() const
Returns true if either IN or OUT streaming endpoint is open.
Definition USBAudioDeviceBase.h:483
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:635
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:478
bool audiod_set_fb_params_freq(audiod_function_t *audio, uint32_t sample_freq, uint32_t mclk_freq)
Definition USBAudioDeviceBase.h:2190
bool audiod_verify_ep_exists(uint8_t ep, uint8_t *func_id)
Definition USBAudioDeviceBase.h:1688
void notifyStreamingState()
Definition USBAudioDeviceBase.h:1037
static uint8_t log2Floor(uint32_t value)
Definition USBAudioDeviceBase.h:2184
std::function< bool(USBAudioDeviceBase *, uint8_t rhport, UsbSetupPacket const &)> get_req_itf_cb_
Definition USBAudioDeviceBase.h:816
std::vector< audiod_function_t > audiod_fct_
Definition USBAudioDeviceBase.h:857
std::function< bool(USBAudioDeviceBase *, uint8_t rhport, UsbSetupPacket const &p_request, uint8_t *pBuff)> tud_audio_set_req_ep_cb_
Definition USBAudioDeviceBase.h:838
uint16_t getCtrlBufSz(uint8_t fn) const
Definition USBAudioDeviceBase.h:1024
uint32_t getTxXferredLast() const
Definition USBAudioDeviceBase.h:767
uint8_t getTxBytesPerSample() const
TX bytes per sample parsed from the descriptor.
Definition USBAudioDeviceBase.h:777
bool audiod_xfer_cb(uint8_t rhport, uint8_t ep_addr, UsbXferResult result, uint32_t xferred_bytes)
TODO refactor control request handling to separate function and reduce nesting.
Definition USBAudioDeviceBase.h:1404
bool isEpOutEnabled() const
Returns true if the OUT endpoint is enabled.
Definition USBAudioDeviceBase.h:390
bool configChanged(const USBAudioConfig &n)
Definition USBAudioDeviceBase.h:1021
size_t readBytes(uint8_t *buffer, size_t bufsize)
Receive audio data from the host (host → device, speaker/playback).
Definition USBAudioDeviceBase.h:682
static bool isPowerOfTwo(uint32_t value)
Definition USBAudioDeviceBase.h:2180
void processVolume(T *data, size_t sample_count)
Definition USBAudioDeviceBase.h:926
bool setMute(bool m, uint8_t channel=0)
Set the mute state for a channel and notify the host.
Definition USBAudioDeviceBase.h:443
uint8_t getTxChannels() const
TX channel count parsed from the descriptor.
Definition USBAudioDeviceBase.h:773
uint16_t audioPacketSize() const
One isochronous USB packet size in bytes (same formula as the descriptor builder).
Definition USBAudioDeviceBase.h:721
virtual void serviceTinyUSB()=0
Process pending USB events on platforms where the application drives the stack (RP2040,...
bool handleFeatureUnitGet(uint8_t rhport, UsbSetupPacket const &p_request, uint8_t *cb)
Definition USBAudioDeviceBase.h:1526
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:654
USBAudio2DescriptorBuilder descr_builder
Definition USBAudioDeviceBase.h:806
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 kAudioSubclassControl
Definition USBAudioDeviceBase.h:64
uint8_t descSubtype(uint8_t const *p)
Definition USBAudioBackend.h:107
static constexpr uint8_t kAudioCsCtrlSamFreq
Definition USBAudioDeviceBase.h:77
uint32_t descU32(uint8_t const *p)
Definition USBAudioBackend.h:113
static constexpr uint16_t kAudioDescIadLen
Definition USBAudioDeviceBase.h:85
UsbDir
USB transfer direction, mirrors TinyUSB's TUSB_DIR_OUT/TUSB_DIR_IN (0/1).
Definition USBAudioBackend.h:8
static constexpr uint8_t kUsbClassAudio
Definition USBAudioDeviceBase.h:63
static constexpr uint8_t kAudioIntProtocolCodeV2
Definition USBAudioDeviceBase.h:65
UsbEndpointDescriptorView decodeEndpoint(uint8_t const *p)
Definition USBAudioBackend.h:121
static constexpr uint8_t kAudioCsReqCur
Definition USBAudioDeviceBase.h:74
UsbInterfaceDescriptorView decodeInterface(uint8_t const *p)
Definition USBAudioBackend.h:136
static constexpr uint8_t kAudioCsAcOutputTerminal
Definition USBAudioDeviceBase.h:69
UsbXferResult
Definition USBAudioBackend.h:24
static constexpr uint8_t kControlStageData
Definition USBAudioDeviceBase.h:90
static constexpr uint8_t AUDIO_FU_CTRL_VOLUME
Definition USBAudioDeviceBase.h:81
static uint8_t u16Low(uint16_t v)
Definition USBAudioDeviceBase.h:92
static constexpr uint8_t kUsbDescTypeEndpoint
Definition USBAudioDeviceBase.h:59
uint16_t descU16(uint8_t const *p)
Definition USBAudioBackend.h:109
uint8_t descType(uint8_t const *p)
Definition USBAudioBackend.h:106
static uint8_t u16High(uint16_t v)
Definition USBAudioDeviceBase.h:93
uint8_t const * descNext(uint8_t const *p)
Definition USBAudioBackend.h:105
static constexpr uint8_t kAudioCsCtrlClkValid
Definition USBAudioDeviceBase.h:78
static constexpr uint8_t AUDIO_FU_CTRL_MUTE
Definition USBAudioDeviceBase.h:80
static constexpr uint16_t kAudioTermTypeUsbStreaming
Definition USBAudioDeviceBase.h:83
static constexpr uint8_t kAudioCsAcInputTerminal
Definition USBAudioDeviceBase.h:68
uint32_t highestSupportedSampleRate(const USBAudioConfig &cfg)
Definition USBAudioConfig.h:278
static constexpr uint8_t kUsbDescTypeCsInterface
Definition USBAudioDeviceBase.h:60
static constexpr uint8_t kUsbDescTypeInterface
Definition USBAudioDeviceBase.h:58
static constexpr uint8_t kAudioCsAsGeneral
Definition USBAudioDeviceBase.h:71
static constexpr uint8_t kAudioCsAsFormatType
Definition USBAudioDeviceBase.h:72
static constexpr uint8_t kControlStageSetup
Definition USBAudioDeviceBase.h:89
static constexpr uint8_t kAudioCsReqRange
Definition USBAudioDeviceBase.h:75
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:117
bool enable_interrupt_ep
Definition USBAudioConfig.h:190
bool enable_multi_sample_rate
Definition USBAudioConfig.h:170
std::vector< uint32_t > supported_sample_rates
Definition USBAudioConfig.h:184
bool enable_ep_in
device → host (capture / microphone)
Definition USBAudioConfig.h:120
uint8_t itf_num_ac
Definition USBAudioConfig.h:140
bool enable_ep_in_flow_control
Definition USBAudioConfig.h:197
bool volume_active
Definition USBAudioConfig.h:223
bool enable_ep_out
host → device (playback / speaker)
Definition USBAudioConfig.h:121
Backend-agnostic mirror of tusb_desc_endpoint_t (USB 2.0 spec Table 9-13). wMaxPacketSize is kept in ...
Definition USBAudioBackend.h:86
uint8_t bEndpointAddress
Definition USBAudioBackend.h:87
UsbXferType xferType
Definition USBAudioBackend.h:88
UsbDir direction() const
Definition USBAudioBackend.h:94
uint16_t packetSize() const
Definition USBAudioBackend.h:97
uint8_t usage
Definition USBAudioBackend.h:90
uint8_t bInterval
Definition USBAudioBackend.h:91
Backend-agnostic mirror of tusb_desc_interface_t — only the fields the UAC2 driver reads (USB 2....
Definition USBAudioBackend.h:66
uint8_t bInterfaceSubClass
Definition USBAudioBackend.h:71
uint8_t bInterfaceNumber
Definition USBAudioBackend.h:67
uint8_t bAlternateSetting
Definition USBAudioBackend.h:68
uint8_t bInterfaceProtocol
Definition USBAudioBackend.h:72
uint8_t bNumEndpoints
Definition USBAudioBackend.h:69
uint8_t bInterfaceClass
Definition USBAudioBackend.h:70
Backend-agnostic mirror of the standard 8-byte USB SETUP packet (TinyUSB's tusb_control_request_t)....
Definition USBAudioBackend.h:34
uint8_t bRequest
Definition USBAudioBackend.h:36
Recipient recipient() const
Definition USBAudioBackend.h:51
uint16_t wValue
Definition USBAudioBackend.h:37
UsbDir direction() const
Definition USBAudioBackend.h:50
uint16_t wIndex
Definition USBAudioBackend.h:38
Type type() const
Definition USBAudioBackend.h:49