arduino-audio-tools
Loading...
Searching...
No Matches
USBAudio2DescriptorBuilder.h
Go to the documentation of this file.
1#pragma once
2#include <cstdint>
3#include <cstring>
4
5#include "tusb.h"
6#include "USBAudioConfig.h"
7
8namespace audio_tools {
9
31 public:
32 // UAC2 entity IDs — assigned sequentially from 1.
33 // Chain 1 is used for the OUT path (or the only path in pure-IN mode).
34 // Chain 2 is used for the IN path in RXTX mode.
35 static constexpr uint8_t ENTITY_CLOCK = 1;
36 static constexpr uint8_t ENTITY_IT1 = 2;
37 static constexpr uint8_t ENTITY_FU1 = 3;
38 static constexpr uint8_t ENTITY_OT1 = 4;
39 static constexpr uint8_t ENTITY_IT2 = 5;
40 static constexpr uint8_t ENTITY_FU2 = 6;
41 static constexpr uint8_t ENTITY_OT2 = 7;
42
44
45
46 // Kept for backward compatibility with existing call sites.
47 const uint16_t buildDescriptor(uint8_t /*itf*/, uint8_t /*alt*/, uint8_t* desc) {
49 }
50
51 // Build the complete audio-function descriptor using interface numbers from
52 // config. Returns a pointer to an internal static buffer; *outLen receives
53 // the total byte count.
57
58 // Same but with an explicit first (AC) interface number.
60 uint8_t* p = desc;
61
62 const uint8_t itf_ac = first_itf;
65
66 // ── IAD ──────────────────────────────────────────────────────────────────
67 p = writeIAD(p, itf_ac, (uint8_t)(1 + num_as));
68
69 // ── Standard AC Interface ─────────────────────────────────────────────
71 p = writeStdIface(p, itf_ac, 0, ac_nEps, 0x01 /*AUDIO*/, 0x01 /*CONTROL*/, 0x20 /*UAC2*/);
72
73 // ── CS AC entities — patch wTotalLength after ────────────────────────────
75 p = writeCsAcHeader(p);
77
79 // OUT path: USB Streaming input → Feature Unit → Speaker output
81 0x0101 /*USB Streaming*/, ENTITY_CLOCK);
84 0x0301 /*Speaker*/, ENTITY_FU1,
86 }
87
89 // IN path: Microphone input → Feature Unit → USB Streaming output.
90 // Pure-IN mode reuses chain 1 IDs; RXTX mode uses chain 2 IDs.
91 const bool rxtx = p_config->enable_ep_out;
92 const uint8_t it = rxtx ? ENTITY_IT2 : ENTITY_IT1;
95 p = writeInputTerminal(p, it, 0x0201 /*Microphone*/, ENTITY_CLOCK);
96 p = writeFeatureUnit(p, fu, it);
97 p = writeOutputTerminal(p, ot, 0x0101 /*USB Streaming*/, fu,
99 }
100
101 // Patch wTotalLength (bytes 6-7 of CS AC Header) — covers only the
102 // class-specific descriptors above, not the interrupt endpoint below.
103 const uint16_t cs_ac_len = (uint16_t)(p - cs_ac_start);
104 cs_ac_start[6] = (uint8_t)(cs_ac_len & 0xFF);
105 cs_ac_start[7] = (uint8_t)(cs_ac_len >> 8);
106
107 // Interrupt EP must follow the CS AC block (USB spec §9.6.6:
108 // endpoint descriptors come after class-specific interface descriptors).
111 }
112
113 // ── OUT AS Interface ─────────────────────────────────────────────────────
114 if (p_config->enable_ep_out) {
115 const uint8_t nEps = enableFeedbackEp() ? 2 : 1;
116 p = writeStdIface(p, itf_as, 0, 0, 0x01, 0x02, 0x20); // alt=0 zero BW
117 p = writeStdIface(p, itf_as, 1, nEps, 0x01, 0x02, 0x20); // alt=1 active
118 p = writeCsAsInterface(p, ENTITY_IT1); // links to USB Streaming IT
119 p = writeFormatType(p);
121 p = writeCsIsoEndpoint(p);
122 if (enableFeedbackEp()) {
124 }
125 ++itf_as;
126 }
127
128 // ── IN AS Interface ──────────────────────────────────────────────────────
129 if (p_config->enable_ep_in) {
130 // Links to the Output Terminal that has USB Streaming type
132 : ENTITY_OT1;
133 p = writeStdIface(p, itf_as, 0, 0, 0x01, 0x02, 0x20); // alt=0 zero BW
134 p = writeStdIface(p, itf_as, 1, 1, 0x01, 0x02, 0x20); // alt=1 active
135 p = writeCsAsInterface(p, ot);
136 p = writeFormatType(p);
138 p = writeCsIsoEndpoint(p);
139 }
140
141 uint16_t outLen = (uint16_t)(p - desc);
142 return outLen;
143 }
144
146 return (p_config->enable_ep_in ? 1 : 0) + (p_config->enable_ep_out ? 1 : 0);
147 }
148
149
150 // True when the explicit-feedback endpoint should appear in the descriptor.
151 // Mirrors isFeedbackEpEnabled() in USBAudioDeviceBase: feedback is only valid
152 // for a pure OUT (speaker) path — with an IN endpoint present the host uses
153 // the IN stream as implicit feedback, and TX-only mode has no OUT EP at all.
154 bool enableFeedbackEp() const {
158 }
159
160 // Isochronous packet size for one 1 ms frame at a given rate.
163 ((rate + 999) / 1000));
164 }
165
166 // Isochronous packet size for one 1 ms frame at the configured rate/format.
169 ((p_config->sample_rate + 999) / 1000));
170 }
171
172 protected:
174
175 // ── helpers ─────────────────────────────────────────────────────────────────
176
178 // Stereo: FL+FR = bits 0-1; mono: FL = bit 0; >2ch: lower N bits.
179 if (p_config->channels == 1) return 0x00000001u;
180 if (p_config->channels == 2) return 0x00000003u;
181 return (1u << p_config->channels) - 1u;
182 }
183
184 // ── descriptor writers ──────────────────────────────────────────────────────
185
186 // Interface Association Descriptor (8 bytes)
188 *p++ = 8;
189 *p++ = 0x0B; // INTERFACE_ASSOCIATION
190 *p++ = first_itf;
191 *p++ = count;
192 *p++ = 0x01; // bFunctionClass AUDIO
193 *p++ = 0x00; // bFunctionSubClass
194 *p++ = 0x20; // bFunctionProtocol UAC2
195 *p++ = 0x00; // iFunction
196 return p;
197 }
198
199 // Standard Interface Descriptor (9 bytes)
202 *p++ = 9;
203 *p++ = 0x04; // INTERFACE
204 *p++ = itf;
205 *p++ = alt;
206 *p++ = nEps;
207 *p++ = cls;
208 *p++ = sub;
209 *p++ = proto;
210 *p++ = 0; // iInterface
211 return p;
212 }
213
214 // UAC2 CS AC Interface Header (9 bytes).
215 // Caller patches wTotalLength at offsets [6] and [7].
217 *p++ = 9;
218 *p++ = 0x24; // CS_INTERFACE
219 *p++ = 0x01; // HEADER
220 *p++ = 0x00;
221 *p++ = 0x02; // bcdADC 2.00
222 *p++ = 0x08; // bCategory IO_BOX (generic)
223 *p++ = 0x00; // wTotalLength LSB — patched by caller
224 *p++ = 0x00; // wTotalLength MSB — patched by caller
225 *p++ = 0x00; // bmControls
226 return p;
227 }
228
229 // UAC2 Clock Source Descriptor (8 bytes)
230 //
231 // bmAttributes D1..D0 — clock type:
232 // 0x00 = external, 0x01 = internal fixed, 0x02 = internal variable,
233 // 0x03 = internal programmable
234 // bmControls — bit-pair encoding:
235 // D1..D0 = Clock Frequency (01=read-only, 11=host-programmable)
236 // D3..D2 = Clock Validity (01=read-only)
237 //
238 // GET_RANGE returns 14 discrete sample rates (8 kHz – 192 kHz).
239 // Host can SET_CUR to any of those rates.
241 *p++ = 8;
242 *p++ = 0x24; // CS_INTERFACE
243 *p++ = 0x0A; // CLOCK_SOURCE
244 *p++ = clock_id;
246 *p++ = 0x03; // bmAttributes: internal programmable clock
247 *p++ = 0x07; // bmControls: freq host-programmable (11b), validity read-only (01b)
248 } else {
249 *p++ = 0x01; // bmAttributes: internal fixed clock
250 *p++ = 0x05; // bmControls: freq read-only (01b), validity read-only (01b)
251 }
252 *p++ = 0x00; // bAssocTerminal
253 *p++ = 0x00; // iClockSource
254 return p;
255 }
256
257 // UAC2 Input Terminal Descriptor (17 bytes)
260 const uint32_t ch_cfg = channelConfig();
261 *p++ = 17;
262 *p++ = 0x24; // CS_INTERFACE
263 *p++ = 0x02; // INPUT_TERMINAL
264 *p++ = term_id;
265 *p++ = (uint8_t)(term_type & 0xFF);
266 *p++ = (uint8_t)(term_type >> 8);
267 *p++ = 0x00; // bAssocTerminal
268 *p++ = clock_id; // bCSourceID
269 *p++ = p_config->channels;
270 *p++ = (uint8_t)(ch_cfg & 0xFF);
271 *p++ = (uint8_t)((ch_cfg >> 8) & 0xFF);
272 *p++ = (uint8_t)((ch_cfg >> 16) & 0xFF);
273 *p++ = (uint8_t)((ch_cfg >> 24) & 0xFF);
274 *p++ = 0; // iChannelNames
275 *p++ = 0x00;
276 *p++ = 0x00; // bmControls
277 *p++ = 0; // iTerminal
278 return p;
279 }
280
281 // UAC2 Feature Unit Descriptor (6 + (channels+1)*4 bytes)
282 //
283 // bmaControls[] is a 32-bit bitmap per channel (+ master at index 0).
284 // Each control is a 2-bit pair:
285 // 00 = not present, 01 = read-only, 11 = host-programmable
286 //
287 // D1..D0 = Mute (FU_CTRL_MUTE = 0x01)
288 // D3..D2 = Volume (FU_CTRL_VOLUME = 0x02)
289 //
290 // 0x0F = Mute host-programmable (11b) | Volume host-programmable (11b)
291 //
292 // Volume uses int16 in 1/256 dB units (0x8000 = silence, 0 = 0 dB).
293 // AudioTools maps this to float 0.0 (silence) – 1.0 (0 dB).
294 // GET_RANGE reports -100 dB .. 0 dB in 1 dB steps.
296 const uint8_t len = (uint8_t)(6 + (p_config->channels + 1) * 4);
297 *p++ = len;
298 *p++ = 0x24; // CS_INTERFACE
299 *p++ = 0x06; // FEATURE_UNIT
300 *p++ = unit_id;
301 *p++ = src_id;
302 // Master bmaControls[0]: Mute + Volume, host-programmable
303 *p++ = 0x0F; *p++ = 0x00; *p++ = 0x00; *p++ = 0x00;
304 // Per-channel bmaControls[1..N]: same controls
305 for (uint8_t i = 0; i < p_config->channels; i++) {
306 *p++ = 0x0F; *p++ = 0x00; *p++ = 0x00; *p++ = 0x00;
307 }
308 *p++ = 0x00; // iFeature
309 return p;
310 }
311
312 // UAC2 Output Terminal Descriptor (12 bytes)
315 *p++ = 12;
316 *p++ = 0x24; // CS_INTERFACE
317 *p++ = 0x03; // OUTPUT_TERMINAL
318 *p++ = term_id;
319 *p++ = (uint8_t)(term_type & 0xFF);
320 *p++ = (uint8_t)(term_type >> 8);
321 *p++ = 0x00; // bAssocTerminal
322 *p++ = src_id; // bSourceID
323 *p++ = clock_id; // bCSourceID
324 *p++ = 0x00;
325 *p++ = 0x00; // bmControls
326 *p++ = 0; // iTerminal
327 return p;
328 }
329
330 // UAC2 CS AS Interface Descriptor (16 bytes)
332 const uint32_t ch_cfg = channelConfig();
333 *p++ = 16;
334 *p++ = 0x24; // CS_INTERFACE
335 *p++ = 0x01; // AS_GENERAL
336 *p++ = terminal_link;
337 *p++ = 0x00; // bmControls
338 *p++ = 0x01; // bFormatType TYPE_I
339 // bmFormats: PCM = 0x00000001
340 *p++ = 0x01; *p++ = 0x00; *p++ = 0x00; *p++ = 0x00;
341 *p++ = p_config->channels;
342 *p++ = (uint8_t)(ch_cfg & 0xFF);
343 *p++ = (uint8_t)((ch_cfg >> 8) & 0xFF);
344 *p++ = (uint8_t)((ch_cfg >> 16) & 0xFF);
345 *p++ = (uint8_t)((ch_cfg >> 24) & 0xFF);
346 *p++ = 0; // iChannelNames
347 return p;
348 }
349
350 // UAC2 Type I Format Type Descriptor (6 bytes)
352 *p++ = 6;
353 *p++ = 0x24; // CS_INTERFACE
354 *p++ = 0x02; // FORMAT_TYPE
355 *p++ = 0x01; // FORMAT_TYPE_I
356 *p++ = (uint8_t)(p_config->bits_per_sample / 8); // bSubslotSize
357 *p++ = p_config->bits_per_sample; // bBitResolution
358 return p;
359 }
360
361 // Standard Isochronous Endpoint Descriptor (7 bytes)
362 //
363 // wMaxPacketSize must be large enough for the highest rate the device
364 // advertises in the Clock Source GET_RANGE response. The actual per-frame
365 // byte count varies with the current sample rate; the host will never
366 // send or expect more than wMaxPacketSize in a single (micro)frame.
368 // Fixed clock: wMaxPacketSize matches the configured rate.
369 // Multi-rate: covers the highest supported rate (192 kHz).
371 ? calcPacketSizeForRate(192000)
373 // bmAttributes: Isochronous (01) + sync type (bits[3:2]) + usage=data (00)
374 // bits 3:2: 00=None, 01=Async, 10=Adaptive, 11=Sync
375 // IN endpoints: Asynchronous (0x05) — device drives the clock.
376 // OUT endpoints: Asynchronous (0x05) when a feedback EP is present,
377 // Adaptive (0x09) otherwise (device adapts to host rate).
378 bool const is_in = (ep_addr & 0x80u);
379 bool const is_out = !is_in;
381 if (is_in) bmAttr = 0x05u; // ISO + Async
382 else if (is_out && enableFeedbackEp()) bmAttr = 0x05u; // ISO + Async
383 else bmAttr = 0x09u; // ISO + Adaptive
384 *p++ = 7;
385 *p++ = 0x05; // ENDPOINT
386 *p++ = ep_addr;
387 *p++ = bmAttr;
388 *p++ = (uint8_t)(pkt & 0xFF);
389 *p++ = (uint8_t)(pkt >> 8);
390 *p++ = 0x01; // bInterval (1 = every frame for FS; host uses 125 µs for HS)
391 return p;
392 }
393
394 // Explicit Feedback Endpoint Descriptor (7 bytes, IN, no CS descriptor)
396 *p++ = 7;
397 *p++ = 0x05; // ENDPOINT
398 *p++ = ep_addr; // must be an IN address (bit 7 set)
399 *p++ = 0x11; // Isochronous, No Sync, Explicit Feedback usage
400 *p++ = 0x04; // wMaxPacketSize LSB (4 bytes)
401 *p++ = 0x00; // wMaxPacketSize MSB
402 *p++ = 0x01; // bInterval
403 return p;
404 }
405
406 // Audio Control Interrupt IN Endpoint Descriptor (7 bytes)
407 //
408 // Carries 6-byte UAC2 status/change notifications (bInfo, bAttribute,
409 // wValue = CS<<8|CN, wIndex = EntityID<<8|Itf) so the device can push
410 // volume, mute, or sample-rate changes to the host.
412 *p++ = 7;
413 *p++ = 0x05; // ENDPOINT
414 *p++ = ep_addr; // IN address (bit 7 set)
415 *p++ = 0x03; // bmAttributes: Interrupt
416 *p++ = 0x06; // wMaxPacketSize LSB (6 bytes for UAC2 notification)
417 *p++ = 0x00; // wMaxPacketSize MSB
418 *p++ = 0x10; // bInterval: 2^(16-1) = 32768 frames ≈ poll only when needed
419 return p;
420 }
421
422 // UAC2 CS ISO Endpoint Descriptor (8 bytes)
424 *p++ = 8;
425 *p++ = 0x25; // CS_ENDPOINT
426 *p++ = 0x01; // EP_GENERAL
427 *p++ = 0x00; // bmAttributes
428 *p++ = 0x00; // bmControls
429 *p++ = 0x00; // bLockDelayUnits (no locking)
430 *p++ = 0x00;
431 *p++ = 0x00; // wLockDelay
432 return p;
433 }
434};
435
436} // namespace audio_tools
USB Audio Class 2.0 descriptor generator.
Definition USBAudio2DescriptorBuilder.h:30
uint8_t * writeInputTerminal(uint8_t *p, uint8_t term_id, uint16_t term_type, uint8_t clock_id)
Definition USBAudio2DescriptorBuilder.h:258
uint8_t * writeFeedbackEndpoint(uint8_t *p, uint8_t ep_addr)
Definition USBAudio2DescriptorBuilder.h:395
uint8_t * writeIsoEndpoint(uint8_t *p, uint8_t ep_addr)
Definition USBAudio2DescriptorBuilder.h:367
uint8_t * writeClockSource(uint8_t *p, uint8_t clock_id)
Definition USBAudio2DescriptorBuilder.h:240
static constexpr uint8_t ENTITY_OT1
first Output Terminal
Definition USBAudio2DescriptorBuilder.h:38
uint8_t * writeCsIsoEndpoint(uint8_t *p)
Definition USBAudio2DescriptorBuilder.h:423
const uint16_t buildFullDescriptor(uint8_t *desc)
Definition USBAudio2DescriptorBuilder.h:54
static constexpr uint8_t ENTITY_FU2
second Feature Unit (RXTX)
Definition USBAudio2DescriptorBuilder.h:40
static constexpr uint8_t ENTITY_FU1
first Feature Unit
Definition USBAudio2DescriptorBuilder.h:37
uint8_t * writeInterruptEndpoint(uint8_t *p, uint8_t ep_addr)
Definition USBAudio2DescriptorBuilder.h:411
uint8_t * writeStdIface(uint8_t *p, uint8_t itf, uint8_t alt, uint8_t nEps, uint8_t cls, uint8_t sub, uint8_t proto)
Definition USBAudio2DescriptorBuilder.h:200
int audioFunctionsCount() const
Definition USBAudio2DescriptorBuilder.h:145
static constexpr uint8_t ENTITY_CLOCK
Definition USBAudio2DescriptorBuilder.h:35
uint8_t * writeOutputTerminal(uint8_t *p, uint8_t term_id, uint16_t term_type, uint8_t src_id, uint8_t clock_id)
Definition USBAudio2DescriptorBuilder.h:313
const uint16_t buildFullDescriptor(uint8_t first_itf, uint8_t *desc)
Definition USBAudio2DescriptorBuilder.h:59
static constexpr uint8_t ENTITY_IT2
second Input Terminal (RXTX)
Definition USBAudio2DescriptorBuilder.h:39
uint32_t channelConfig() const
Definition USBAudio2DescriptorBuilder.h:177
uint8_t * writeIAD(uint8_t *p, uint8_t first_itf, uint8_t count)
Definition USBAudio2DescriptorBuilder.h:187
uint8_t * writeCsAcHeader(uint8_t *p)
Definition USBAudio2DescriptorBuilder.h:216
static constexpr uint8_t ENTITY_OT2
second Output Terminal (RXTX)
Definition USBAudio2DescriptorBuilder.h:41
static constexpr uint8_t ENTITY_IT1
first Input Terminal
Definition USBAudio2DescriptorBuilder.h:36
uint16_t calcPacketSizeForRate(uint32_t rate) const
Definition USBAudio2DescriptorBuilder.h:161
const uint16_t buildDescriptor(uint8_t, uint8_t, uint8_t *desc)
Definition USBAudio2DescriptorBuilder.h:47
USBAudioConfig * p_config
Definition USBAudio2DescriptorBuilder.h:173
uint8_t * writeFormatType(uint8_t *p)
Definition USBAudio2DescriptorBuilder.h:351
uint16_t calcMaxPacketSize() const
Definition USBAudio2DescriptorBuilder.h:167
bool enableFeedbackEp() const
Definition USBAudio2DescriptorBuilder.h:154
uint8_t * writeFeatureUnit(uint8_t *p, uint8_t unit_id, uint8_t src_id)
Definition USBAudio2DescriptorBuilder.h:295
uint8_t * writeCsAsInterface(uint8_t *p, uint8_t terminal_link)
Definition USBAudio2DescriptorBuilder.h:331
USBAudio2DescriptorBuilder(USBAudioConfig &cfg)
Definition USBAudio2DescriptorBuilder.h:43
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition AudioCodecsBase.h:10
size_t writeData(Print *p_out, T *data, int samples, int maxSamples=512)
Definition AudioTypes.h:512
sample_rate_t sample_rate
Sample Rate: e.g 44100.
Definition AudioTypes.h:57
uint16_t channels
Number of channels: 2=stereo, 1=mono.
Definition AudioTypes.h:59
uint8_t bits_per_sample
Number of bits per sample (int16_t = 16 bits)
Definition AudioTypes.h:61
Configuration for USB Audio (inherits sample_rate / channels / bits_per_sample from AudioInfo).
Definition USBAudioConfig.h:27
uint8_t ep_fb
ISO IN (explicit feedback, RX-only mode)
Definition USBAudioConfig.h:40
bool enable_interrupt_ep
Definition USBAudioConfig.h:77
uint8_t ep_in
ISO IN (device → host, capture/microphone)
Definition USBAudioConfig.h:38
bool enable_multi_sample_rate
Definition USBAudioConfig.h:72
uint8_t ep_int
INT IN (AC status/change notifications)
Definition USBAudioConfig.h:41
bool enable_feedback_ep
Enable isochronous feedback endpoint so the host can adjust its clock.
Definition USBAudioConfig.h:64
bool enable_ep_in
device → host (capture / microphone)
Definition USBAudioConfig.h:31
uint8_t ep_out
ISO OUT (host → device, playback/speaker)
Definition USBAudioConfig.h:39
uint8_t itf_num_ac
Definition USBAudioConfig.h:46
bool enable_ep_out
host → device (playback / speaker)
Definition USBAudioConfig.h:32