arduino-audio-tools
Loading...
Searching...
No Matches
USBAudioDeviceZephyr.h
Go to the documentation of this file.
1#pragma once
2#include <zephyr/device.h>
3#include <zephyr/kernel.h>
4#include <zephyr/logging/log.h>
5#include <zephyr/usb/class/usbd_uac2.h>
6#include <zephyr/usb/usbd.h>
7
8#include <cstring>
9#include <functional>
10#include <vector>
11
13
14LOG_MODULE_DECLARE(usb_audio_device_zephyr, LOG_LEVEL_INF);
15
16namespace audio_tools {
17
41 public:
43 USBAudioDeviceZephyr(USBAudioConfig cfg) : config_(cfg) {}
44
45 // ── Duck-typing interface required by USBAudioStream<Device> ───────────────
46
49 cfg.sample_rate = 48000;
50 cfg.channels = 2;
51 cfg.bits_per_sample = 16;
52 cfg.enable_ep_in = (mode == TX_MODE || mode == RXTX_MODE);
53 cfg.enable_ep_out = (mode == RX_MODE || mode == RXTX_MODE);
54 cfg.enable_feedback_ep = (mode == RX_MODE);
55 cfg.terminal_id = 1;
56 return cfg;
57 }
58
59 bool begin(const USBAudioConfig& cfg) {
60 config_ = cfg;
61 return begin();
62 }
63
64 bool begin() {
65 uint16_t frame = audioPacketSize();
66 block_sz_ = (uint16_t)((frame + 3u) & ~3u); // round up to 4-byte boundary
67 uint8_t count = (uint8_t)(kPipelineDepth * 2u);
68
69 slab_buf_.resize((size_t)block_sz_ * count);
70 if (k_mem_slab_init(&slab_, slab_buf_.data(), block_sz_, count) != 0) {
71 LOG_ERR("USBAudioDeviceZephyr: k_mem_slab_init failed");
72 slab_buf_.resize(0);
73 return false;
74 }
75
76 uac2_dev_ = DEVICE_DT_GET(DT_ALIAS(uac2_dev));
77 if (!device_is_ready(uac2_dev_)) {
78 LOG_ERR("USBAudioDeviceZephyr: UAC2 device not ready");
79 slab_buf_.resize(0);
80 return false;
81 }
82
83 static const struct uac2_ops ops = {
84 .terminal_update_cb = s_terminal_update,
85 .get_recv_buf = s_get_recv_buf,
86 .data_recv_cb = s_data_recv,
87 .buf_release_cb = s_buf_release,
88 .get_explicit_feedback = s_get_feedback,
89 };
90 usbd_uac2_set_ops(uac2_dev_, &ops, this);
91
92 is_active_ = true;
93 LOG_INF("USBAudioDeviceZephyr: started (%u Hz, %u ch, %u bit)",
94 config_.sample_rate, config_.channels, config_.bits_per_sample);
95 return true;
96 }
97
98 void end() {
99 is_active_ = false;
100 streaming_ = false;
101 slab_buf_.resize(0);
102 LOG_INF("USBAudioDeviceZephyr: stopped");
103 }
104
105 void setRxCallback(std::function<void(const uint8_t*, uint16_t)> cb) {
106 rx_cb_ = std::move(cb);
107 }
108
109 void setTxCallback(std::function<uint16_t(uint8_t*, uint16_t)> cb) {
110 tx_cb_ = std::move(cb);
111 }
112
113 void process() { k_yield(); }
114
115 uint16_t audioPacketSize() const {
116 return (uint16_t)((config_.sample_rate / 1000U) * config_.channels *
117 (config_.bits_per_sample / 8U));
118 }
119
124 if (cfg.sample_rate != config_.sample_rate ||
125 cfg.channels != config_.channels ||
126 cfg.bits_per_sample != config_.bits_per_sample) {
127 LOG_WRN(
128 "USBAudioDeviceZephyr: audio format change ignored — "
129 "DTS descriptor is fixed at build time");
130 }
131 }
132
134 bool mounted() const { return streaming_; }
135
136 private:
138 BaseBuffer<uint8_t>& bufferTx() override { return default_buffer_tx_; }
139
141 BaseBuffer<uint8_t>& bufferRx() override { return default_buffer_rx_; }
142
143 // ── Default audio buffers (RingBuffer, suitable for single-core platforms) ─
144 RingBuffer<uint8_t> default_buffer_tx_{1};
145 RingBuffer<uint8_t> default_buffer_rx_{1};
146
147 // ── Static trampolines (user_data == this) ────────────────────────────────
148
149 static void s_terminal_update(const struct device* /*dev*/,
150 uint8_t /*terminal*/, bool enabled,
151 bool /*microframes*/, void* user_data) {
152 auto* self = static_cast<USBAudioDeviceZephyr*>(user_data);
153 self->streaming_ = enabled;
154 if (enabled && self->config_.enable_ep_in) {
155 self->primeTxPipeline();
156 }
157 LOG_INF("USBAudioDeviceZephyr: streaming %s",
158 enabled ? "started" : "stopped");
159 }
160
161 static void* s_get_recv_buf(const struct device* /*dev*/,
162 uint8_t /*terminal*/, uint16_t /*size*/,
163 void* user_data) {
164 auto* self = static_cast<USBAudioDeviceZephyr*>(user_data);
165 if (!self->is_active_ || !self->config_.enable_ep_out) return nullptr;
166 void* buf = nullptr;
167 if (k_mem_slab_alloc(&self->slab_, &buf, K_NO_WAIT) != 0) {
168 LOG_WRN("USBAudioDeviceZephyr: slab alloc failed (overrun)");
169 return nullptr;
170 }
171 return buf;
172 }
173
174 static void s_data_recv(const struct device* /*dev*/, uint8_t /*terminal*/,
175 void* buf, uint16_t size, void* user_data) {
176 auto* self = static_cast<USBAudioDeviceZephyr*>(user_data);
177 if (!self->is_active_ || !buf) {
178 if (buf) k_mem_slab_free(&self->slab_, buf);
179 return;
180 }
181 if (self->rx_cb_) {
182 self->rx_cb_(static_cast<const uint8_t*>(buf), size);
183 }
184 k_mem_slab_free(&self->slab_, buf);
185 }
186
187 static void s_buf_release(const struct device* /*dev*/, uint8_t /*terminal*/,
188 void* buf, void* user_data) {
189 auto* self = static_cast<USBAudioDeviceZephyr*>(user_data);
190 if (!buf) return;
191 k_mem_slab_free(&self->slab_, buf);
192
193 if (!self->is_active_ || !self->streaming_ || !self->tx_cb_) return;
194
195 void* next = nullptr;
196 if (k_mem_slab_alloc(&self->slab_, &next, K_NO_WAIT) != 0) return;
197
198 uint16_t filled =
199 self->tx_cb_(static_cast<uint8_t*>(next), self->block_sz_);
200 if (filled < self->block_sz_) {
201 memset(static_cast<uint8_t*>(next) + filled, 0,
202 (size_t)(self->block_sz_ - filled));
203 }
204 if (usbd_uac2_send(self->uac2_dev_, self->config_.terminal_id, next,
205 self->block_sz_) != 0) {
206 k_mem_slab_free(&self->slab_, next);
207 }
208 }
209
210 static uint32_t s_get_feedback(const struct device* /*dev*/,
211 uint8_t /*terminal*/, void* user_data) {
212 auto* self = static_cast<USBAudioDeviceZephyr*>(user_data);
213 uint32_t spf = self->config_.sample_rate / 1000U;
214 return (spf << 14) & 0x00FFFFFFu;
215 }
216
217 // ── TX pipeline priming ───────────────────────────────────────────────────
218
219 void primeTxPipeline() {
220 if (!tx_cb_) return;
221 for (uint8_t i = 0; i < kPipelineDepth; ++i) {
222 void* buf = nullptr;
223 if (k_mem_slab_alloc(&slab_, &buf, K_NO_WAIT) != 0) break;
224 uint16_t filled = tx_cb_(static_cast<uint8_t*>(buf), block_sz_);
225 if (filled < block_sz_) {
226 memset(static_cast<uint8_t*>(buf) + filled, 0,
227 (size_t)(block_sz_ - filled));
228 }
229 if (usbd_uac2_send(uac2_dev_, config_.terminal_id, buf, block_sz_) != 0) {
230 k_mem_slab_free(&slab_, buf);
231 break;
232 }
233 }
234 }
235
236 // ── Members ───────────────────────────────────────────────────────────────
237
238 USBAudioConfig config_;
239 const struct device* uac2_dev_ = nullptr;
240 struct k_mem_slab slab_{};
241 std::vector<uint8_t> slab_buf_;
242 bool is_active_ = false;
243 bool streaming_ = false;
244 uint16_t block_sz_ = 0;
245
246 std::function<void(const uint8_t*, uint16_t)> rx_cb_;
247 std::function<uint16_t(uint8_t*, uint16_t)> tx_cb_;
248
249 static constexpr uint8_t kPipelineDepth = 3;
250};
251
259using USBAudioStream = USBAudioDeviceZephyr;
260
261} // namespace audio_tools
LOG_MODULE_DECLARE(usb_audio_device_zephyr, LOG_LEVEL_INF)
Shared functionality of all buffers.
Definition Buffers.h:23
USB Audio 2.0 device backend for Zephyr RTOS.
Definition USBAudioDeviceZephyr.h:40
bool begin(const USBAudioConfig &cfg)
Definition USBAudioDeviceZephyr.h:59
void setTxCallback(std::function< uint16_t(uint8_t *, uint16_t)> cb)
Definition USBAudioDeviceZephyr.h:109
void process()
Definition USBAudioDeviceZephyr.h:113
void reenumerateUSBOnChange(const USBAudioConfig &cfg)
Definition USBAudioDeviceZephyr.h:123
bool begin()
Definition USBAudioDeviceZephyr.h:64
bool mounted() const
True while the USB host is actively streaming.
Definition USBAudioDeviceZephyr.h:134
void end()
Definition USBAudioDeviceZephyr.h:98
USBAudioConfig defaultConfig(RxTxMode mode=RXTX_MODE)
Definition USBAudioDeviceZephyr.h:47
void setRxCallback(std::function< void(const uint8_t *, uint16_t)> cb)
Definition USBAudioDeviceZephyr.h:105
USBAudioDeviceZephyr(USBAudioConfig cfg)
Definition USBAudioDeviceZephyr.h:43
uint16_t audioPacketSize() const
Definition USBAudioDeviceZephyr.h:115
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
USBAudioDeviceESP32 USBAudioStream
USBAudioStream type alias for ESP32.
Definition USBAudioDeviceESP32.h:207
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition LMSEchoCancellationStream.h:6
sample_rate_t sample_rate
Sample Rate: e.g 44100.
Definition AudioTypes.h:53
uint16_t channels
Number of channels: 2=stereo, 1=mono.
Definition AudioTypes.h:55
uint8_t bits_per_sample
Number of bits per sample (int16_t = 16 bits)
Definition AudioTypes.h:57
Configuration for USB Audio (inherits sample_rate / channels / bits_per_sample from AudioInfo).
Definition USBAudioConfig.h:89
bool enable_feedback_ep
Enable isochronous feedback endpoint so the host can adjust its clock.
Definition USBAudioConfig.h:133
bool enable_ep_in
device → host (capture / microphone)
Definition USBAudioConfig.h:92
uint8_t terminal_id
Definition USBAudioConfig.h:199
bool enable_ep_out
host → device (playback / speaker)
Definition USBAudioConfig.h:93