diff --git a/shared-bindings/usb_audio/USBMicrophone.c b/shared-bindings/usb_audio/USBMicrophone.c index ff53d812e49..c66c78942dd 100644 --- a/shared-bindings/usb_audio/USBMicrophone.c +++ b/shared-bindings/usb_audio/USBMicrophone.c @@ -12,6 +12,7 @@ #include "shared-bindings/usb_audio/USBMicrophone.h" #include "shared-bindings/util.h" #include "shared-module/usb_audio/__init__.h" +#include "shared-module/usb_audio/usb_audio_descriptors.h" //| class USBMicrophone: //| """Streams an audio sample to the host computer as a USB Audio Class microphone. diff --git a/shared-bindings/usb_audio/__init__.c b/shared-bindings/usb_audio/__init__.c index 49eaa9716f2..00ec82f10ae 100644 --- a/shared-bindings/usb_audio/__init__.c +++ b/shared-bindings/usb_audio/__init__.c @@ -19,9 +19,9 @@ //| Audio Class (UAC2) microphone: the board is the audio *source* and streams //| samples to the host over a USB isochronous IN endpoint. //| -//| This mode requires 1 IN endpoint and 2 interfaces. Generally, microcontrollers -//| have a limit on the number of endpoints. If you exceed the number of endpoints, -//| CircuitPython will automatically enter Safe Mode. Even in this case, you may be +//| This mode requires 2 IN endpoints and 2 interfaces. +//| Generally, microcontrollers have a limit on the number of endpoints. If you exceed the number +//| of endpoints, CircuitPython will automatically enter Safe Mode. Even in this case, you may be //| able to enable USB audio by also disabling other USB functions, such as //| `usb_hid` or `usb_midi`. //| @@ -32,7 +32,7 @@ //| //| # boot.py //| import usb_audio -//| usb_audio.enable(sample_rate=16000, channel_count=1, bits_per_sample=16) +//| usb_audio.enable(sample_rate=16000, bits_per_sample=16) //| //| .. code-block:: py //| @@ -61,9 +61,8 @@ //| pass //| mic.stop() //| -//| The ``sample_rate`` and ``channel_count`` of the sample played must match the -//| values passed to `enable`, and the sample must be 16-bit signed; otherwise -//| ``play`` raises a ``ValueError``. +//| The ``sample_rate`` and ``channel_count`` of the sample played must match the values passed to `enable`, +//| and the sample must be 16-bit signed; otherwise ``play`` raises a ``ValueError``. //| //| This interface is experimental and may change without notice even in stable //| versions of CircuitPython.""" @@ -84,7 +83,6 @@ //| def enable( //| sample_rate: int = 16000, //| channel_count: int = 1, -//| bits_per_sample: int = 16, //| microphone: bool = True, //| speaker: bool = False, //| ) -> None: @@ -93,7 +91,7 @@ //| This function may only be used from ``boot.py``. //| //| :param int sample_rate: Samples per second of the streamed audio. -//| :param int channel_count: Number of channels. Only mono (1) is supported initially. +//| :param int channel_count: Number of channels. Either mono (1) or stereo (2) is supported. //| :param int bits_per_sample: Bits per signed PCM sample. Only 16 is supported initially. //| :param bool microphone: Present a microphone (audio flows board -> host). Enabled by default. //| :param bool speaker: Present a speaker (audio flows host -> board). @@ -103,11 +101,10 @@ //| //| static mp_obj_t usb_audio_enable(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { ARG_sample_rate, ARG_channel_count, ARG_bits_per_sample, ARG_microphone, ARG_speaker }; + enum { ARG_sample_rate, ARG_channel_count, ARG_microphone, ARG_speaker }; static const mp_arg_t allowed_args[] = { { MP_QSTR_sample_rate, MP_ARG_INT, { .u_int = 16000 } }, { MP_QSTR_channel_count, MP_ARG_INT, { .u_int = 1 } }, - { MP_QSTR_bits_per_sample, MP_ARG_INT, { .u_int = 16 } }, { MP_QSTR_microphone, MP_ARG_BOOL, { .u_bool = true } }, { MP_QSTR_speaker, MP_ARG_BOOL, { .u_bool = false } }, }; @@ -116,7 +113,6 @@ static mp_obj_t usb_audio_enable(size_t n_args, const mp_obj_t *pos_args, mp_map mp_int_t sample_rate = mp_arg_validate_int_range(args[ARG_sample_rate].u_int, 1, USB_AUDIO_MAX_SAMPLE_RATE, MP_QSTR_sample_rate); mp_int_t channel_count = mp_arg_validate_int_range(args[ARG_channel_count].u_int, 1, USB_AUDIO_N_CHANNELS, MP_QSTR_channel_count); - mp_int_t bits_per_sample = mp_arg_validate_int(args[ARG_bits_per_sample].u_int, USB_AUDIO_BITS_PER_SAMPLE, MP_QSTR_bits_per_sample); bool microphone = args[ARG_microphone].u_bool; bool speaker = args[ARG_speaker].u_bool; @@ -124,7 +120,7 @@ static mp_obj_t usb_audio_enable(size_t n_args, const mp_obj_t *pos_args, mp_map mp_raise_ValueError(MP_ERROR_TEXT("At least one of microphone and speaker must be enabled")); } - if (!shared_module_usb_audio_enable(sample_rate, channel_count, bits_per_sample, microphone, speaker)) { + if (!shared_module_usb_audio_enable(sample_rate, channel_count, microphone, speaker)) { mp_raise_RuntimeError(MP_ERROR_TEXT("Cannot change USB devices now")); } diff --git a/shared-module/usb_audio/USBMicrophone.c b/shared-module/usb_audio/USBMicrophone.c index fd5309abeb9..9d2a46b7985 100644 --- a/shared-module/usb_audio/USBMicrophone.c +++ b/shared-module/usb_audio/USBMicrophone.c @@ -11,6 +11,7 @@ #include "shared-bindings/usb_audio/USBMicrophone.h" #include "shared-module/usb_audio/__init__.h" +#include "shared-module/usb_audio/usb_audio_descriptors.h" #include "shared-module/audiocore/__init__.h" // Only one microphone may feed the single USB IN endpoint at a time. This points @@ -53,7 +54,7 @@ void common_hal_usb_audio_usbmicrophone_play(usb_audio_usbmicrophone_obj_t *self if (audiosample_get_channel_count(sample_base) != usb_audio_channel_count) { mp_raise_ValueError_varg(MP_ERROR_TEXT("The sample's %q does not match"), MP_QSTR_channel_count); } - if (audiosample_get_bits_per_sample(sample_base) != usb_audio_bits_per_sample) { + if (audiosample_get_bits_per_sample(sample_base) != USB_AUDIO_BITS_PER_SAMPLE) { mp_raise_ValueError_varg(MP_ERROR_TEXT("The sample's %q does not match"), MP_QSTR_bits_per_sample); } if (!sample_base->samples_signed) { @@ -101,15 +102,33 @@ bool common_hal_usb_audio_usbmicrophone_get_paused(usb_audio_usbmicrophone_obj_t return self->playing && self->paused; } +static inline uint32_t copy16lsb(uint32_t val) { + #if (defined(__ARM_ARCH_7EM__) && (__ARM_ARCH_7EM__ == 1)) + return __PKHBT(val, val, 16); + #else + val &= 0x0000ffff; + return val | (val << 16); + #endif +} + +static inline uint32_t copy16msb(uint32_t val) { + #if (defined(__ARM_ARCH_7EM__) && (__ARM_ARCH_7EM__ == 1)) + return __PKHTB(val, val, 16); + #else + val &= 0xffff0000; + return val | (val >> 16); + #endif +} + size_t usb_audio_usbmicrophone_background_fill(uint8_t *out, size_t max_bytes) { usb_audio_usbmicrophone_obj_t *self = active_microphone; if (self == NULL || !self->playing || self->paused || self->sample == MP_OBJ_NULL) { return 0; } - // The negotiated USB format is 16-bit signed mono PCM. For this step the + // The negotiated USB format is 16-bit signed PCM. For this step the // bound sample is assumed to already be in that format (e.g. a 16-bit signed - // mono audiocore.RawSample), so its bytes are copied straight through. + // audiocore.RawSample), so its bytes are copied straight through. size_t filled = 0; while (filled < max_bytes) { if (self->buffer_length == 0) { @@ -138,8 +157,20 @@ size_t usb_audio_usbmicrophone_background_fill(uint8_t *out, size_t max_bytes) { } } - size_t n = MIN(self->buffer_length, max_bytes - filled); - memcpy(out + filled, self->buffer, n); + size_t n; + if (MP_LIKELY(usb_audio_channel_count == USB_AUDIO_N_CHANNELS)) { + n = MIN(self->buffer_length, max_bytes - filled); + memcpy(out + filled, self->buffer, n); + } else { + n = MIN(self->buffer_length << 1, max_bytes - filled); + uint32_t *word_buffer = (uint32_t *)self->buffer; + uint32_t *word_out = (uint32_t *)(out + filled); + for (size_t i = 0; i < n / sizeof(uint32_t); i += 2) { + uint32_t v = word_buffer[i >> 1]; + word_out[i] = copy16lsb(v); + word_out[i + 1] = copy16msb(v); + } + } self->buffer += n; self->buffer_length -= n; filled += n; diff --git a/shared-module/usb_audio/USBSpeaker.c b/shared-module/usb_audio/USBSpeaker.c index 12921430bc3..310eb3cd7d2 100644 --- a/shared-module/usb_audio/USBSpeaker.c +++ b/shared-module/usb_audio/USBSpeaker.c @@ -32,12 +32,12 @@ void common_hal_usb_audio_usbspeaker_construct(usb_audio_usbspeaker_obj_t *self) // format we present is 16-bit signed LE PCM, which is exactly what the // CircuitPython audio pipeline carries, so no conversion is needed. self->base.sample_rate = usb_audio_sample_rate; - self->base.bits_per_sample = usb_audio_bits_per_sample; + self->base.bits_per_sample = USB_AUDIO_BITS_PER_SAMPLE; self->base.channel_count = usb_audio_channel_count; self->base.samples_signed = true; self->base.single_buffer = false; self->base.max_buffer_length = USB_AUDIO_SPEAKER_OUTPUT_BUFFER_SIZE; - + self->ring_head = 0; self->ring_tail = 0; self->ring_count = 0; @@ -193,6 +193,8 @@ audioio_get_buffer_result_t usb_audio_usbspeaker_get_buffer(usb_audio_usbspeaker uint32_t half = self->base.max_buffer_length / 2; uint8_t *out = self->output_buffer + half * self->output_index; + int16_t *word_out = (int16_t *)out; + int16_t *word_ring = (int16_t *)self->ring; self->output_index = 1 - self->output_index; // Consumer side of the SPSC ring (runs in the output backend's refill ISR). @@ -200,9 +202,20 @@ audioio_get_buffer_result_t usb_audio_usbspeaker_get_buffer(usb_audio_usbspeaker size_t to_copy = MIN(self->ring_count, (size_t)half); size_t first = MIN(to_copy, USB_AUDIO_SPEAKER_RING_SIZE - self->ring_tail); - memcpy(out, &self->ring[self->ring_tail], first); - if (to_copy > first) { - memcpy(out + first, &self->ring[0], to_copy - first); + if (MP_LIKELY(usb_audio_channel_count == USB_AUDIO_N_CHANNELS)) { + memcpy(out, &self->ring[self->ring_tail], first); + if (to_copy > first) { + memcpy(out + first, &self->ring[0], to_copy - first); + } + } else { + for (size_t i = self->ring_tail / USB_AUDIO_N_BYTES_PER_SAMPLE; i < first / USB_AUDIO_N_BYTES_PER_SAMPLE; i += 2) { + word_out[i >> 1] = word_ring[i]; + } + if (to_copy > first) { + for (size_t i = 0; i < (to_copy - first) / USB_AUDIO_N_BYTES_PER_SAMPLE; i += 2) { + word_out[(first / USB_AUDIO_N_BYTES_PER_SAMPLE) + (i >> 1)] = word_ring[i]; + } + } } self->ring_tail = (self->ring_tail + to_copy) % USB_AUDIO_SPEAKER_RING_SIZE; self->ring_count -= to_copy; @@ -211,17 +224,25 @@ audioio_get_buffer_result_t usb_audio_usbspeaker_get_buffer(usb_audio_usbspeaker // Underrun: pad the remainder with silence. Samples are signed, so // silence is 0. This is the consume-side of the pacing failure mode // tracked in the usb-audio-artifact-pacing memory: we never spin. - memset(out + to_copy, 0, half - to_copy); + if (MP_LIKELY(usb_audio_channel_count == USB_AUDIO_N_CHANNELS)) { + memset(out + to_copy, 0, half - to_copy); + } else { + memset(out + (to_copy >> 1), 0, (half - to_copy) >> 1); + } } - // Mono only for v1, so the single-channel offset is always 0; computed the - // same way as audiocore.RawSample so stereo (interleaved ring) can extend it. + // Computed the same way as audiocore.RawSample so stereo (interleaved ring) + // can extend it. if (single_channel_output) { out += (channel % self->base.channel_count) * (self->base.bits_per_sample / 8); } *buffer = out; - *buffer_length = half; + if (MP_LIKELY(usb_audio_channel_count == USB_AUDIO_N_CHANNELS)) { + *buffer_length = half; + } else { + *buffer_length = half >> 1; + } // A live USB stream is infinite; never report DONE or the backend would stop. return GET_BUFFER_MORE_DATA; } diff --git a/shared-module/usb_audio/USBSpeaker.h b/shared-module/usb_audio/USBSpeaker.h index 8c06cff519d..0760676b53a 100644 --- a/shared-module/usb_audio/USBSpeaker.h +++ b/shared-module/usb_audio/USBSpeaker.h @@ -22,7 +22,7 @@ // comfortably inside the ring. #define USB_AUDIO_SPEAKER_FRAMES_PER_BUFFER (128) -// Bytes per audio frame in the negotiated UAC2 format (mono 16-bit for v1). +// Bytes per audio frame in the negotiated UAC2 format (stereo 16-bit). #define USB_AUDIO_SPEAKER_BYTES_PER_FRAME (USB_AUDIO_N_BYTES_PER_SAMPLE * USB_AUDIO_N_CHANNELS) // Full owned double-buffer: two halves of FRAMES_PER_BUFFER frames each. Matches diff --git a/shared-module/usb_audio/__init__.c b/shared-module/usb_audio/__init__.c index cac47bbc5fe..2a5948bb74f 100644 --- a/shared-module/usb_audio/__init__.c +++ b/shared-module/usb_audio/__init__.c @@ -20,6 +20,9 @@ #include "supervisor/shared/tick.h" #include "tusb.h" +int16_t *usb_audio_mic_samples; +size_t usb_audio_mic_samples_len; + static bool usb_audio_is_enabled = false; // The host opens each AudioStreaming interface independently (alt 0 = idle, alt 1 @@ -36,7 +39,6 @@ static uint8_t usb_audio_spk_as_itf = 0xff; uint32_t usb_audio_sample_rate; uint8_t usb_audio_channel_count; -uint8_t usb_audio_bits_per_sample; bool usb_audio_microphone_enabled; bool usb_audio_speaker_enabled; @@ -44,14 +46,23 @@ bool usb_audio_speaker_enabled; static int8_t usb_audio_mute[USB_AUDIO_N_CHANNELS + 1]; static int16_t usb_audio_volume[USB_AUDIO_N_CHANNELS + 1]; -bool shared_module_usb_audio_enable(mp_int_t sample_rate, mp_int_t channel_count, mp_int_t bits_per_sample, bool microphone, bool speaker) { +bool shared_module_usb_audio_enable(mp_int_t sample_rate, mp_int_t channel_count, bool microphone, bool speaker) { if (tud_connected()) { return false; } + // One scratch chunk (1 ms at the sample rate); we loop until the FIFO reaches + // the setpoint. + usb_audio_mic_samples_len = sample_rate / 1000 * channel_count * USB_AUDIO_N_BYTES_PER_SAMPLE; + usb_audio_mic_samples = (int16_t *)m_malloc_without_collect(usb_audio_mic_samples_len); + if (usb_audio_mic_samples == NULL) { + m_malloc_fail(usb_audio_mic_samples_len); + return false; + } + memset(usb_audio_mic_samples, 0, usb_audio_mic_samples_len); + usb_audio_sample_rate = sample_rate; usb_audio_channel_count = channel_count; - usb_audio_bits_per_sample = bits_per_sample; usb_audio_microphone_enabled = microphone; usb_audio_speaker_enabled = speaker; usb_audio_is_enabled = true; @@ -126,8 +137,8 @@ void usb_audio_setup_singletons(void) { speaker; } -// Hand-rolled UAC2 mono speaker (host -> board) descriptor WITHOUT an async -// feedback endpoint. This mirrors TinyUSB's TUD_AUDIO20_SPEAKER_MONO_FB_DESCRIPTOR +// Hand-rolled UAC2 speaker (host -> board) descriptor WITHOUT an async +// feedback endpoint. This mirrors TinyUSB's TUD_AUDIO20_SPEAKER_STEREO_FB_DESCRIPTOR // (lib/tinyusb/src/device/usbd.h) but drops the trailing feedback endpoint, so // the streaming alt-setting declares a single OUT endpoint (_nEPs = 0x01). The // entity IDs match the mic descriptor (see usb_audio_descriptors.h); only the @@ -140,7 +151,7 @@ void usb_audio_setup_singletons(void) { /* Standard AC Interface Descriptor(4.7.1) */ \ TUD_AUDIO20_DESC_STD_AC(/*_itfnum*/ _itfnum, /*_nEPs*/ 0x00, /*_stridx*/ _stridx), \ /* Class-Specific AC Interface Header Descriptor(4.7.2) */ \ - TUD_AUDIO20_DESC_CS_AC(/*_bcdADC*/ 0x0200, /*_category*/ AUDIO20_FUNC_DESKTOP_SPEAKER, /*_totallen*/ TUD_AUDIO20_DESC_CLK_SRC_LEN + TUD_AUDIO20_DESC_INPUT_TERM_LEN + TUD_AUDIO20_DESC_OUTPUT_TERM_LEN + TUD_AUDIO20_DESC_FEATURE_UNIT_LEN(1), /*_ctrl*/ AUDIO20_CS_AS_INTERFACE_CTRL_LATENCY_POS), \ + TUD_AUDIO20_DESC_CS_AC(/*_bcdADC*/ 0x0200, /*_category*/ AUDIO20_FUNC_DESKTOP_SPEAKER, /*_totallen*/ TUD_AUDIO20_DESC_CLK_SRC_LEN + TUD_AUDIO20_DESC_INPUT_TERM_LEN + TUD_AUDIO20_DESC_OUTPUT_TERM_LEN + TUD_AUDIO20_DESC_FEATURE_UNIT_LEN(2), /*_ctrl*/ AUDIO20_CS_AS_INTERFACE_CTRL_LATENCY_POS), \ /* Clock Source Descriptor(4.7.2.1) */ \ TUD_AUDIO20_DESC_CLK_SRC(/*_clkid*/ USB_AUDIO_ENTITY_CLOCK_SOURCE, /*_attr*/ AUDIO20_CLOCK_SOURCE_ATT_INT_FIX_CLK, /*_ctrl*/ (AUDIO20_CTRL_R << AUDIO20_CLOCK_SOURCE_CTRL_CLK_FRQ_POS), /*_assocTerm*/ USB_AUDIO_ENTITY_INPUT_TERMINAL, /*_stridx*/ 0x00), \ /* Input Terminal Descriptor(4.7.2.4) -- USB streaming in from the host */ \ @@ -148,7 +159,7 @@ void usb_audio_setup_singletons(void) { /* Output Terminal Descriptor(4.7.2.5) -- desktop speaker */ \ TUD_AUDIO20_DESC_OUTPUT_TERM(/*_termid*/ USB_AUDIO_ENTITY_OUTPUT_TERMINAL, /*_termtype*/ AUDIO_TERM_TYPE_OUT_DESKTOP_SPEAKER, /*_assocTerm*/ USB_AUDIO_ENTITY_INPUT_TERMINAL, /*_srcid*/ USB_AUDIO_ENTITY_FEATURE_UNIT, /*_clkid*/ USB_AUDIO_ENTITY_CLOCK_SOURCE, /*_ctrl*/ 0x0000, /*_stridx*/ 0x00), \ /* Feature Unit Descriptor(4.7.2.8) */ \ - TUD_AUDIO20_DESC_FEATURE_UNIT(/*_unitid*/ USB_AUDIO_ENTITY_FEATURE_UNIT, /*_srcid*/ USB_AUDIO_ENTITY_INPUT_TERMINAL, /*_stridx*/ 0x00, /*_ctrlch0master*/ AUDIO20_CTRL_RW << AUDIO20_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO20_CTRL_RW << AUDIO20_FEATURE_UNIT_CTRL_VOLUME_POS, /*_ctrlch1*/ AUDIO20_CTRL_RW << AUDIO20_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO20_CTRL_RW << AUDIO20_FEATURE_UNIT_CTRL_VOLUME_POS), \ + TUD_AUDIO20_DESC_FEATURE_UNIT(/*_unitid*/ USB_AUDIO_ENTITY_FEATURE_UNIT, /*_srcid*/ USB_AUDIO_ENTITY_INPUT_TERMINAL, /*_stridx*/ 0x00, /*_ctrlch0master*/ AUDIO20_CTRL_RW << AUDIO20_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO20_CTRL_RW << AUDIO20_FEATURE_UNIT_CTRL_VOLUME_POS, /*_ctrlch1*/ AUDIO20_CTRL_RW << AUDIO20_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO20_CTRL_RW << AUDIO20_FEATURE_UNIT_CTRL_VOLUME_POS, /*_ctrlch2*/ AUDIO20_CTRL_RW << AUDIO20_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO20_CTRL_RW << AUDIO20_FEATURE_UNIT_CTRL_VOLUME_POS), \ /* Standard AS Interface Descriptor(4.9.1) -- alt 0, zero bandwidth */ \ TUD_AUDIO20_DESC_STD_AS_INT(/*_itfnum*/ (uint8_t)((_itfnum) + 1), /*_altset*/ 0x00, /*_nEPs*/ 0x00, /*_stridx*/ 0x00), \ /* Standard AS Interface Descriptor(4.9.1) -- alt 1, one OUT endpoint */ \ @@ -162,10 +173,45 @@ void usb_audio_setup_singletons(void) { /* Class-Specific AS Isochronous Audio Data Endpoint Descriptor(4.10.1.2) */ \ TUD_AUDIO20_DESC_CS_AS_ISO_EP(/*_attr*/ AUDIO20_CS_AS_ISO_DATA_EP_ATT_NON_MAX_PACKETS_OK, /*_ctrl*/ AUDIO20_CTRL_NONE, /*_lockdelayunit*/ AUDIO20_CS_AS_ISO_DATA_EP_LOCK_DELAY_UNIT_UNDEFINED, /*_lockdelay*/ 0x0000) -// Hand-rolled UAC2 mono headset (microphone + speaker both enabled): one audio function +// Hand-rolled UAC2 microphone (board -> host) descriptor WITHOUT an async +// feedback endpoint. This mirrors TinyUSB's TUD_AUDIO20_MIC_ONE_CH_DESCRIPTOR +// (lib/tinyusb/src/device/usbd.h) but drops the trailing feedback endpoint, so +// the streaming alt-setting declares a single IN endpoint (_nEPs = 0x00). Async +// feedback for true clock matching is a later step. +#define USB_AUDIO_MIC_DESCRIPTOR(_itfnum, _stridx, _nBytesPerSample, _nBitsUsedPerSample, _epin, _epsize) \ + /* Standard Interface Association Descriptor (IAD) */ \ + TUD_AUDIO20_DESC_IAD(/*_firstitf*/ _itfnum, /*_nitfs*/ 0x02, /*_stridx*/ 0x00), \ + /* Standard AC Interface Descriptor(4.7.1) */ \ + TUD_AUDIO20_DESC_STD_AC(/*_itfnum*/ _itfnum, /*_nEPs*/ 0x00, /*_stridx*/ _stridx), \ + /* Class-Specific AC Interface Header Descriptor(4.7.2) */ \ + TUD_AUDIO20_DESC_CS_AC(/*_bcdADC*/ 0x0200, /*_category*/ AUDIO20_FUNC_MICROPHONE, /*_totallen*/ TUD_AUDIO20_DESC_CLK_SRC_LEN + TUD_AUDIO20_DESC_INPUT_TERM_LEN + TUD_AUDIO20_DESC_OUTPUT_TERM_LEN + TUD_AUDIO20_DESC_FEATURE_UNIT_LEN(2), /*_ctrl*/ AUDIO20_CS_AS_INTERFACE_CTRL_LATENCY_POS), \ + /* Clock Source Descriptor(4.7.2.1) */ \ + TUD_AUDIO20_DESC_CLK_SRC(/*_clkid*/ USB_AUDIO_ENTITY_CLOCK_SOURCE, /*_attr*/ AUDIO20_CLOCK_SOURCE_ATT_INT_FIX_CLK, /*_ctrl*/ (AUDIO20_CTRL_R << AUDIO20_CLOCK_SOURCE_CTRL_CLK_FRQ_POS), /*_assocTerm*/ USB_AUDIO_ENTITY_INPUT_TERMINAL, /*_stridx*/ 0x00), \ + /* Input Terminal Descriptor(4.7.2.4) -- microphone */ \ + TUD_AUDIO20_DESC_INPUT_TERM(/*_termid*/ USB_AUDIO_ENTITY_INPUT_TERMINAL, /*_termtype*/ AUDIO_TERM_TYPE_IN_GENERIC_MIC, /*_assocTerm*/ USB_AUDIO_ENTITY_OUTPUT_TERMINAL, /*_clkid*/ USB_AUDIO_ENTITY_CLOCK_SOURCE, /*_nchannelslogical*/ USB_AUDIO_N_CHANNELS, /*_channelcfg*/ AUDIO20_CHANNEL_CONFIG_NON_PREDEFINED, /*_idxchannelnames*/ 0x00, /*_ctrl*/ AUDIO20_CTRL_R << AUDIO20_IN_TERM_CTRL_CONNECTOR_POS, /*_stridx*/ 0x00), \ + /* Output Terminal Descriptor(4.7.2.5) -- USB streaming */ \ + TUD_AUDIO20_DESC_OUTPUT_TERM(/*_termid*/ USB_AUDIO_ENTITY_OUTPUT_TERMINAL, /*_termtype*/ AUDIO_TERM_TYPE_USB_STREAMING, /*_assocTerm*/ USB_AUDIO_ENTITY_INPUT_TERMINAL, /*_srcid*/ USB_AUDIO_ENTITY_FEATURE_UNIT, /*_clkid*/ USB_AUDIO_ENTITY_CLOCK_SOURCE, /*_ctrl*/ 0x0000, /*_stridx*/ 0x00), \ + /* Feature Unit Descriptor(4.7.2.8) */ \ + TUD_AUDIO20_DESC_FEATURE_UNIT(/*_unitid*/ USB_AUDIO_ENTITY_FEATURE_UNIT, /*_srcid*/ USB_AUDIO_ENTITY_INPUT_TERMINAL, /*_stridx*/ 0x00, /*_ctrlch0master*/ AUDIO20_CTRL_RW << AUDIO20_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO20_CTRL_RW << AUDIO20_FEATURE_UNIT_CTRL_VOLUME_POS, /*_ctrlch1*/ AUDIO20_CTRL_RW << AUDIO20_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO20_CTRL_RW << AUDIO20_FEATURE_UNIT_CTRL_VOLUME_POS, /*_ctrlch2*/ AUDIO20_CTRL_RW << AUDIO20_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO20_CTRL_RW << AUDIO20_FEATURE_UNIT_CTRL_VOLUME_POS), \ + /* Standard AS Interface Descriptor(4.9.1) */ \ + /* Interface 1, Alternate 0 - default alternate setting with 0 bandwidth */ \ + TUD_AUDIO20_DESC_STD_AS_INT(/*_itfnum*/ (uint8_t)((_itfnum) + 1), /*_altset*/ 0x00, /*_nEPs*/ 0x00, /*_stridx*/ 0x00),\ + /* Standard AS Interface Descriptor(4.9.1) */ \ + /* Interface 1, Alternate 1 - alternate interface for data streaming */ \ + TUD_AUDIO20_DESC_STD_AS_INT(/*_itfnum*/ (uint8_t)((_itfnum) + 1), /*_altset*/ 0x01, /*_nEPs*/ 0x01, /*_stridx*/ 0x00),\ + /* Class-Specific AS Interface Descriptor(4.9.2) */ \ + TUD_AUDIO20_DESC_CS_AS_INT(/*_termid*/ USB_AUDIO_ENTITY_OUTPUT_TERMINAL, /*_ctrl*/ AUDIO20_CTRL_NONE, /*_formattype*/ AUDIO20_FORMAT_TYPE_I, /*_formats*/ AUDIO20_DATA_FORMAT_TYPE_I_PCM, /*_nchannelsphysical*/ USB_AUDIO_N_CHANNELS, /*_channelcfg*/ AUDIO20_CHANNEL_CONFIG_NON_PREDEFINED, /*_stridx*/ 0x00),\ + /* Type I Format Type Descriptor(2.3.1.6 - Audio Formats) */ \ + TUD_AUDIO20_DESC_TYPE_I_FORMAT(_nBytesPerSample, _nBitsUsedPerSample), \ + /* Standard AS Isochronous Audio Data Endpoint Descriptor(4.10.1.1) */ \ + TUD_AUDIO20_DESC_STD_AS_ISO_EP(/*_ep*/ _epin, /*_attr*/ (uint8_t) ((uint8_t)TUSB_XFER_ISOCHRONOUS | (uint8_t)TUSB_ISO_EP_ATT_ASYNCHRONOUS | (uint8_t)TUSB_ISO_EP_ATT_DATA), /*_maxEPsize*/ _epsize, /*_interval*/ 0x01), \ + /* Class-Specific AS Isochronous Audio Data Endpoint Descriptor(4.10.1.2) */ \ + TUD_AUDIO20_DESC_CS_AS_ISO_EP(/*_attr*/ AUDIO20_CS_AS_ISO_DATA_EP_ATT_NON_MAX_PACKETS_OK, /*_ctrl*/ AUDIO20_CTRL_NONE, /*_lockdelayunit*/ AUDIO20_CS_AS_ISO_DATA_EP_LOCK_DELAY_UNIT_UNDEFINED, /*_lockdelay*/ 0x0000) + +// Hand-rolled UAC2 headset (microphone + speaker both enabled): one audio function // presenting both a speaker (host -> board OUT) and a microphone (board -> host // IN) at once. This combines USB_AUDIO_SPEAKER_DESCRIPTOR's speaker chain with -// TUD_AUDIO20_MIC_ONE_CH_DESCRIPTOR's mic chain under a single IAD. The two chains +// USB_AUDIO_MIC_DESCRIPTOR's mic chain under a single IAD. The two chains // must use distinct entity IDs (USB_AUDIO_HS_ENTITY_*; see usb_audio_descriptors.h) // because they live in the same AudioControl interface, and they share one clock // source. The function spans three interfaces: AudioControl (_itfnum), the @@ -178,21 +224,21 @@ void usb_audio_setup_singletons(void) { /* Standard AC Interface Descriptor(4.7.1) */ \ TUD_AUDIO20_DESC_STD_AC(/*_itfnum*/ _itfnum, /*_nEPs*/ 0x00, /*_stridx*/ _stridx), \ /* Class-Specific AC Interface Header Descriptor(4.7.2) -- clock + both chains */ \ - TUD_AUDIO20_DESC_CS_AC(/*_bcdADC*/ 0x0200, /*_category*/ AUDIO20_FUNC_HEADSET, /*_totallen*/ TUD_AUDIO20_DESC_CLK_SRC_LEN + 2 * (TUD_AUDIO20_DESC_INPUT_TERM_LEN + TUD_AUDIO20_DESC_FEATURE_UNIT_LEN(1) + TUD_AUDIO20_DESC_OUTPUT_TERM_LEN), /*_ctrl*/ AUDIO20_CS_AS_INTERFACE_CTRL_LATENCY_POS), \ + TUD_AUDIO20_DESC_CS_AC(/*_bcdADC*/ 0x0200, /*_category*/ AUDIO20_FUNC_HEADSET, /*_totallen*/ TUD_AUDIO20_DESC_CLK_SRC_LEN + 2 * (TUD_AUDIO20_DESC_INPUT_TERM_LEN + TUD_AUDIO20_DESC_FEATURE_UNIT_LEN(2) + TUD_AUDIO20_DESC_OUTPUT_TERM_LEN), /*_ctrl*/ AUDIO20_CS_AS_INTERFACE_CTRL_LATENCY_POS), \ /* Clock Source Descriptor(4.7.2.1) -- shared by both chains */ \ TUD_AUDIO20_DESC_CLK_SRC(/*_clkid*/ USB_AUDIO_HS_ENTITY_CLOCK_SOURCE, /*_attr*/ AUDIO20_CLOCK_SOURCE_ATT_INT_FIX_CLK, /*_ctrl*/ (AUDIO20_CTRL_R << AUDIO20_CLOCK_SOURCE_CTRL_CLK_FRQ_POS), /*_assocTerm*/ 0x00, /*_stridx*/ 0x00), \ /* --- Speaker chain (host -> board) --- */ \ /* Input Terminal Descriptor(4.7.2.4) -- USB streaming in from the host */ \ TUD_AUDIO20_DESC_INPUT_TERM(/*_termid*/ USB_AUDIO_HS_ENTITY_SPK_INPUT_TERMINAL, /*_termtype*/ AUDIO_TERM_TYPE_USB_STREAMING, /*_assocTerm*/ 0x00, /*_clkid*/ USB_AUDIO_HS_ENTITY_CLOCK_SOURCE, /*_nchannelslogical*/ USB_AUDIO_N_CHANNELS, /*_channelcfg*/ AUDIO20_CHANNEL_CONFIG_NON_PREDEFINED, /*_idxchannelnames*/ 0x00, /*_ctrl*/ 0 * (AUDIO20_CTRL_R << AUDIO20_IN_TERM_CTRL_CONNECTOR_POS), /*_stridx*/ 0x00), \ /* Feature Unit Descriptor(4.7.2.8) */ \ - TUD_AUDIO20_DESC_FEATURE_UNIT(/*_unitid*/ USB_AUDIO_HS_ENTITY_SPK_FEATURE_UNIT, /*_srcid*/ USB_AUDIO_HS_ENTITY_SPK_INPUT_TERMINAL, /*_stridx*/ 0x00, /*_ctrlch0master*/ AUDIO20_CTRL_RW << AUDIO20_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO20_CTRL_RW << AUDIO20_FEATURE_UNIT_CTRL_VOLUME_POS, /*_ctrlch1*/ AUDIO20_CTRL_RW << AUDIO20_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO20_CTRL_RW << AUDIO20_FEATURE_UNIT_CTRL_VOLUME_POS), \ + TUD_AUDIO20_DESC_FEATURE_UNIT(/*_unitid*/ USB_AUDIO_HS_ENTITY_SPK_FEATURE_UNIT, /*_srcid*/ USB_AUDIO_HS_ENTITY_SPK_INPUT_TERMINAL, /*_stridx*/ 0x00, /*_ctrlch0master*/ AUDIO20_CTRL_RW << AUDIO20_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO20_CTRL_RW << AUDIO20_FEATURE_UNIT_CTRL_VOLUME_POS, /*_ctrlch1*/ AUDIO20_CTRL_RW << AUDIO20_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO20_CTRL_RW << AUDIO20_FEATURE_UNIT_CTRL_VOLUME_POS, /*_ctrlch2*/ AUDIO20_CTRL_RW << AUDIO20_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO20_CTRL_RW << AUDIO20_FEATURE_UNIT_CTRL_VOLUME_POS), \ /* Output Terminal Descriptor(4.7.2.5) -- desktop speaker */ \ TUD_AUDIO20_DESC_OUTPUT_TERM(/*_termid*/ USB_AUDIO_HS_ENTITY_SPK_OUTPUT_TERMINAL, /*_termtype*/ AUDIO_TERM_TYPE_OUT_DESKTOP_SPEAKER, /*_assocTerm*/ 0x00, /*_srcid*/ USB_AUDIO_HS_ENTITY_SPK_FEATURE_UNIT, /*_clkid*/ USB_AUDIO_HS_ENTITY_CLOCK_SOURCE, /*_ctrl*/ 0x0000, /*_stridx*/ 0x00), \ /* --- Mic chain (board -> host) --- */ \ /* Input Terminal Descriptor(4.7.2.4) -- generic microphone */ \ TUD_AUDIO20_DESC_INPUT_TERM(/*_termid*/ USB_AUDIO_HS_ENTITY_MIC_INPUT_TERMINAL, /*_termtype*/ AUDIO_TERM_TYPE_IN_GENERIC_MIC, /*_assocTerm*/ 0x00, /*_clkid*/ USB_AUDIO_HS_ENTITY_CLOCK_SOURCE, /*_nchannelslogical*/ USB_AUDIO_N_CHANNELS, /*_channelcfg*/ AUDIO20_CHANNEL_CONFIG_NON_PREDEFINED, /*_idxchannelnames*/ 0x00, /*_ctrl*/ AUDIO20_CTRL_R << AUDIO20_IN_TERM_CTRL_CONNECTOR_POS, /*_stridx*/ 0x00), \ /* Feature Unit Descriptor(4.7.2.8) */ \ - TUD_AUDIO20_DESC_FEATURE_UNIT(/*_unitid*/ USB_AUDIO_HS_ENTITY_MIC_FEATURE_UNIT, /*_srcid*/ USB_AUDIO_HS_ENTITY_MIC_INPUT_TERMINAL, /*_stridx*/ 0x00, /*_ctrlch0master*/ AUDIO20_CTRL_RW << AUDIO20_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO20_CTRL_RW << AUDIO20_FEATURE_UNIT_CTRL_VOLUME_POS, /*_ctrlch1*/ AUDIO20_CTRL_RW << AUDIO20_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO20_CTRL_RW << AUDIO20_FEATURE_UNIT_CTRL_VOLUME_POS), \ + TUD_AUDIO20_DESC_FEATURE_UNIT(/*_unitid*/ USB_AUDIO_HS_ENTITY_MIC_FEATURE_UNIT, /*_srcid*/ USB_AUDIO_HS_ENTITY_MIC_INPUT_TERMINAL, /*_stridx*/ 0x00, /*_ctrlch0master*/ AUDIO20_CTRL_RW << AUDIO20_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO20_CTRL_RW << AUDIO20_FEATURE_UNIT_CTRL_VOLUME_POS, /*_ctrlch1*/ AUDIO20_CTRL_RW << AUDIO20_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO20_CTRL_RW << AUDIO20_FEATURE_UNIT_CTRL_VOLUME_POS, /*_ctrlch2*/ AUDIO20_CTRL_RW << AUDIO20_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO20_CTRL_RW << AUDIO20_FEATURE_UNIT_CTRL_VOLUME_POS), \ /* Output Terminal Descriptor(4.7.2.5) -- USB streaming out to the host */ \ TUD_AUDIO20_DESC_OUTPUT_TERM(/*_termid*/ USB_AUDIO_HS_ENTITY_MIC_OUTPUT_TERMINAL, /*_termtype*/ AUDIO_TERM_TYPE_USB_STREAMING, /*_assocTerm*/ 0x00, /*_srcid*/ USB_AUDIO_HS_ENTITY_MIC_FEATURE_UNIT, /*_clkid*/ USB_AUDIO_HS_ENTITY_CLOCK_SOURCE, /*_ctrl*/ 0x0000, /*_stridx*/ 0x00), \ /* --- Speaker AudioStreaming interface (_itfnum + 1) --- */ \ @@ -240,7 +286,7 @@ size_t usb_audio_descriptor_length(void) { if (usb_audio_direction_is_output()) { return USB_AUDIO_SPEAKER_DESC_LEN; } - return TUD_AUDIO20_MIC_ONE_CH_DESC_LEN; + return USB_AUDIO_MIC_DESC_LEN; } size_t usb_audio_add_descriptor(uint8_t *descriptor_buf, descriptor_counts_t *descriptor_counts, uint8_t *current_interface_string) { @@ -268,6 +314,7 @@ size_t usb_audio_add_descriptor(uint8_t *descriptor_buf, descriptor_counts_t *de const uint8_t ep_in = descriptor_counts->current_endpoint + 1; usb_add_interface_string(*current_interface_string, "CircuitPython Headset"); + const uint8_t usb_audio_descriptor[] = { USB_AUDIO_HEADSET_DESCRIPTOR( /*_itfnum*/ descriptor_counts->current_interface, @@ -285,11 +332,11 @@ size_t usb_audio_add_descriptor(uint8_t *descriptor_buf, descriptor_counts_t *de (*current_interface_string)++; // One IAD wrapping an AudioControl + two AudioStreaming interfaces, plus - // one OUT and one IN endpoint. + // two OUT and two IN endpoints. descriptor_counts->current_interface += 3; - descriptor_counts->num_out_endpoints++; - descriptor_counts->num_in_endpoints++; - descriptor_counts->current_endpoint += 2; + descriptor_counts->num_out_endpoints += USB_AUDIO_N_CHANNELS; + descriptor_counts->num_in_endpoints += USB_AUDIO_N_CHANNELS; + descriptor_counts->current_endpoint += USB_AUDIO_N_CHANNELS * 2; memcpy(descriptor_buf, usb_audio_descriptor, sizeof(usb_audio_descriptor)); @@ -298,6 +345,10 @@ size_t usb_audio_add_descriptor(uint8_t *descriptor_buf, descriptor_counts_t *de if (usb_audio_direction_is_output()) { usb_add_interface_string(*current_interface_string, "CircuitPython Speaker"); + + // The AudioStreaming interface follows the AudioControl interface. + usb_audio_spk_as_itf = descriptor_counts->current_interface + 1; + const uint8_t usb_audio_descriptor[] = { USB_AUDIO_SPEAKER_DESCRIPTOR( /*_itfnum*/ descriptor_counts->current_interface, @@ -308,15 +359,12 @@ size_t usb_audio_add_descriptor(uint8_t *descriptor_buf, descriptor_counts_t *de /*_epsize*/ CFG_TUD_AUDIO_FUNC_1_EP_OUT_SZ_MAX) }; - // The AudioStreaming interface follows the AudioControl interface. - usb_audio_spk_as_itf = descriptor_counts->current_interface + 1; - (*current_interface_string)++; - // One IAD wrapping an AudioControl + an AudioStreaming interface, plus one OUT endpoint. + // One IAD wrapping an AudioControl + an AudioStreaming interface, plus two OUT endpoints. descriptor_counts->current_interface += 2; if (!forced_iso_ep) { - descriptor_counts->num_out_endpoints++; - descriptor_counts->current_endpoint++; + descriptor_counts->num_out_endpoints += USB_AUDIO_N_CHANNELS; + descriptor_counts->current_endpoint += USB_AUDIO_N_CHANNELS; } memcpy(descriptor_buf, usb_audio_descriptor, sizeof(usb_audio_descriptor)); @@ -325,8 +373,12 @@ size_t usb_audio_add_descriptor(uint8_t *descriptor_buf, descriptor_counts_t *de } usb_add_interface_string(*current_interface_string, "CircuitPython Microphone"); + + // The AudioStreaming interface follows the AudioControl interface. + usb_audio_mic_as_itf = descriptor_counts->current_interface + 1; + const uint8_t usb_audio_descriptor[] = { - TUD_AUDIO20_MIC_ONE_CH_DESCRIPTOR( + USB_AUDIO_MIC_DESCRIPTOR( /*_itfnum*/ descriptor_counts->current_interface, /*_stridx*/ *current_interface_string, /*_nBytesPerSample*/ USB_AUDIO_N_BYTES_PER_SAMPLE, @@ -335,15 +387,12 @@ size_t usb_audio_add_descriptor(uint8_t *descriptor_buf, descriptor_counts_t *de /*_epsize*/ CFG_TUD_AUDIO_FUNC_1_EP_IN_SZ_MAX) }; - // The AudioStreaming interface follows the AudioControl interface. - usb_audio_mic_as_itf = descriptor_counts->current_interface + 1; - (*current_interface_string)++; - // One IAD wrapping an AudioControl + an AudioStreaming interface, plus one IN endpoint. + // One IAD wrapping an AudioControl + an AudioStreaming interface, plus two IN endpoints. descriptor_counts->current_interface += 2; if (!forced_iso_ep) { - descriptor_counts->num_in_endpoints++; - descriptor_counts->current_endpoint++; + descriptor_counts->num_in_endpoints += USB_AUDIO_N_CHANNELS; + descriptor_counts->current_endpoint += USB_AUDIO_N_CHANNELS; } memcpy(descriptor_buf, usb_audio_descriptor, sizeof(usb_audio_descriptor)); @@ -397,24 +446,20 @@ static void usb_audio_microphone_task(void) { tu_fifo_t *ep_in_ff = tud_audio_get_ep_in_ff(); uint16_t const target = tu_fifo_depth(ep_in_ff) / 2; - // One scratch chunk (1 ms at the max rate); we loop until the FIFO reaches - // the setpoint. Sized for the highest rate enable() accepts. - static int16_t samples[USB_AUDIO_MAX_SAMPLE_RATE / 1000 * USB_AUDIO_N_CHANNELS]; - uint16_t count; while ((count = tu_fifo_count(ep_in_ff)) < target) { size_t want = target - count; - if (want > sizeof(samples)) { - want = sizeof(samples); + if (want > usb_audio_mic_samples_len) { + want = usb_audio_mic_samples_len; } // Pull the next chunk from whichever USBMicrophone is playing. bool underran = false; - size_t filled = usb_audio_usbmicrophone_background_fill((uint8_t *)samples, want); + size_t filled = usb_audio_usbmicrophone_background_fill((uint8_t *)usb_audio_mic_samples, want); if (filled == 0) { // No source attached, paused, or fully drained: keep the endpoint // alive with silence so the host never sees a starved stream. - memset((uint8_t *)samples, 0, want); + memset((uint8_t *)usb_audio_mic_samples, 0, want); } else if (filled < want) { // The source momentarily underran. Send just what it produced and // let the FIFO cushion ride until it catches up next pass, rather @@ -423,7 +468,7 @@ static void usb_audio_microphone_task(void) { underran = true; } - if (tud_audio_write((uint8_t *)samples, (uint16_t)want) == 0) { + if (tud_audio_write((uint8_t *)usb_audio_mic_samples, (uint16_t)want) == 0) { break; // FIFO unexpectedly full / host not ready } if (underran) { @@ -496,7 +541,7 @@ bool tud_audio_set_req_entity_cb(uint8_t rhport, tusb_control_request_t const *p // A headset exposes a feature unit per direction; the speaker's id matches the // single-direction USB_AUDIO_ENTITY_FEATURE_UNIT, the mic adds a second one. - // Mute/volume state is shared across them (mono, cosmetic for now). + // Mute/volume state is shared across them. if (entityID == USB_AUDIO_ENTITY_FEATURE_UNIT || entityID == USB_AUDIO_HS_ENTITY_MIC_FEATURE_UNIT) { if (channelNum > USB_AUDIO_N_CHANNELS) { diff --git a/shared-module/usb_audio/__init__.h b/shared-module/usb_audio/__init__.h index 8ec13f90853..5730e6ef60b 100644 --- a/shared-module/usb_audio/__init__.h +++ b/shared-module/usb_audio/__init__.h @@ -17,7 +17,7 @@ // called before USB is connected (i.e. from boot.py); they return false // otherwise. At least one of microphone/speaker must be true; enabling both // presents a combined headset. -bool shared_module_usb_audio_enable(mp_int_t sample_rate, mp_int_t channel_count, mp_int_t bits_per_sample, bool microphone, bool speaker); +bool shared_module_usb_audio_enable(mp_int_t sample_rate, mp_int_t channel_count, bool microphone, bool speaker); bool shared_module_usb_audio_disable(void); // True once enable() has been called successfully. @@ -36,7 +36,6 @@ bool usb_audio_speaker_streaming(void); // Negotiated audio format, valid when usb_audio_enabled() is true. extern uint32_t usb_audio_sample_rate; extern uint8_t usb_audio_channel_count; -extern uint8_t usb_audio_bits_per_sample; // Which streams were requested in enable(), valid when usb_audio_enabled() is // true. Both true presents a combined headset. diff --git a/shared-module/usb_audio/usb_audio_descriptors.h b/shared-module/usb_audio/usb_audio_descriptors.h index 18d72dcabc5..40691a17529 100644 --- a/shared-module/usb_audio/usb_audio_descriptors.h +++ b/shared-module/usb_audio/usb_audio_descriptors.h @@ -26,10 +26,10 @@ size_t usb_audio_descriptor_length(void); // The isochronous IN endpoint's wMaxPacketSize in the USB descriptor is computed // for this rate, so it is the highest rate usb_audio.enable() will accept. #define USB_AUDIO_MAX_SAMPLE_RATE (48000) +#define USB_AUDIO_N_CHANNELS (2) -// 16-bit signed LE PCM, mono. +// 16-bit signed LE PCM. #define USB_AUDIO_N_BYTES_PER_SAMPLE (2) -#define USB_AUDIO_N_CHANNELS (1) #define USB_AUDIO_BITS_PER_SAMPLE (USB_AUDIO_N_BYTES_PER_SAMPLE * 8) // Endpoint number for the single isochronous audio data endpoint. Most device @@ -44,7 +44,7 @@ size_t usb_audio_descriptor_length(void); #define USB_AUDIO_ISO_EP_NUM (0) #endif -// Fixed UAC2 entity IDs baked into TUD_AUDIO20_MIC_ONE_CH_DESCRIPTOR and the +// Fixed UAC2 entity IDs baked into USB_AUDIO_MIC_DESCRIPTOR and the // hand-rolled speaker descriptor (USB_AUDIO_SPEAKER_DESCRIPTOR in __init__.c). // The speaker reuses the same IDs as the mic; only the terminal roles reverse // (input terminal = USB streaming, output terminal = desktop speaker). @@ -67,10 +67,10 @@ size_t usb_audio_descriptor_length(void); #define USB_AUDIO_HS_ENTITY_MIC_FEATURE_UNIT (0x06) #define USB_AUDIO_HS_ENTITY_MIC_OUTPUT_TERMINAL (0x07) // USB streaming out to host -// Length of the no-feedback mono speaker descriptor. It uses the same set of -// sub-descriptors as TUD_AUDIO20_MIC_ONE_CH_DESCRIPTOR (one isochronous data +// Length of the no-feedback speaker descriptor. It uses the same set of +// sub-descriptors as USB_AUDIO_MIC_DESCRIPTOR (except one isochronous data // endpoint, no feedback endpoint), so this is identical to -// TUD_AUDIO20_MIC_ONE_CH_DESC_LEN -- but spell it out independently so the two +// USB_AUDIO_MIC_DESC_LEN -- but spell it out independently so the two // can diverge later (e.g. stereo) without silently mis-sizing the descriptor. // These TUD_AUDIO20_DESC_*_LEN macros come from TinyUSB's usbd.h; this expression // is only expanded where that header is already included (never at the point @@ -81,7 +81,23 @@ size_t usb_audio_descriptor_length(void); + TUD_AUDIO20_DESC_CLK_SRC_LEN \ + TUD_AUDIO20_DESC_INPUT_TERM_LEN \ + TUD_AUDIO20_DESC_OUTPUT_TERM_LEN \ - + TUD_AUDIO20_DESC_FEATURE_UNIT_LEN(1) \ + + TUD_AUDIO20_DESC_FEATURE_UNIT_LEN(2) \ + + TUD_AUDIO20_DESC_STD_AS_LEN \ + + TUD_AUDIO20_DESC_STD_AS_LEN \ + + TUD_AUDIO20_DESC_CS_AS_INT_LEN \ + + TUD_AUDIO20_DESC_TYPE_I_FORMAT_LEN \ + + TUD_AUDIO20_DESC_STD_AS_ISO_EP_LEN \ + + TUD_AUDIO20_DESC_CS_AS_ISO_EP_LEN) + +#define USB_AUDIO_MIC_DESC_LEN (TUD_AUDIO20_DESC_IAD_LEN \ + + TUD_AUDIO20_DESC_STD_AC_LEN \ + + TUD_AUDIO20_DESC_CS_AC_LEN \ + + TUD_AUDIO20_DESC_CLK_SRC_LEN \ + /* mic chain: microphone input terminal -> feature unit -> USB-streaming out */ \ + + TUD_AUDIO20_DESC_INPUT_TERM_LEN \ + + TUD_AUDIO20_DESC_OUTPUT_TERM_LEN \ + + TUD_AUDIO20_DESC_FEATURE_UNIT_LEN(2) \ + /* mic AudioStreaming interface (alt 0 + alt 1 with IN endpoint) */ \ + TUD_AUDIO20_DESC_STD_AS_LEN \ + TUD_AUDIO20_DESC_STD_AS_LEN \ + TUD_AUDIO20_DESC_CS_AS_INT_LEN \ @@ -104,11 +120,11 @@ size_t usb_audio_descriptor_length(void); + TUD_AUDIO20_DESC_CLK_SRC_LEN \ /* speaker chain: USB-streaming input terminal -> feature unit -> speaker */ \ + TUD_AUDIO20_DESC_INPUT_TERM_LEN \ - + TUD_AUDIO20_DESC_FEATURE_UNIT_LEN(1) \ + + TUD_AUDIO20_DESC_FEATURE_UNIT_LEN(2) \ + TUD_AUDIO20_DESC_OUTPUT_TERM_LEN \ /* mic chain: microphone input terminal -> feature unit -> USB-streaming out */ \ + TUD_AUDIO20_DESC_INPUT_TERM_LEN \ - + TUD_AUDIO20_DESC_FEATURE_UNIT_LEN(1) \ + + TUD_AUDIO20_DESC_FEATURE_UNIT_LEN(2) \ + TUD_AUDIO20_DESC_OUTPUT_TERM_LEN \ /* speaker AudioStreaming interface (alt 0 + alt 1 with OUT endpoint) */ \ + TUD_AUDIO20_DESC_STD_AS_LEN \