Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions shared-bindings/usb_audio/USBMicrophone.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
24 changes: 10 additions & 14 deletions shared-bindings/usb_audio/__init__.c
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
//|
Expand All @@ -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
//|
Expand Down Expand Up @@ -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."""
Expand All @@ -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:
Expand All @@ -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).
Expand All @@ -103,28 +101,26 @@
//|
//|
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 } },
};
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);

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);
mp_int_t channel_count = mp_arg_validate_int_range(args[ARG_channel_count].u_int, 1, USB_AUDIO_MAX_CHANNELS, MP_QSTR_channel_count);
bool microphone = args[ARG_microphone].u_bool;
bool speaker = args[ARG_speaker].u_bool;

if (!microphone && !speaker) {
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"));
}

Expand Down
41 changes: 36 additions & 5 deletions shared-module/usb_audio/USBMicrophone.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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_MAX_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;
Expand Down
39 changes: 30 additions & 9 deletions shared-module/usb_audio/USBSpeaker.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -193,16 +193,29 @@ 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).
// It is never preempted by the producer, so no interrupt guard is required.
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_MAX_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_BYTES_PER_SAMPLE; i < first / USB_AUDIO_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_BYTES_PER_SAMPLE; i += 2) {
word_out[(first / USB_AUDIO_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;
Expand All @@ -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_MAX_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_MAX_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;
}
4 changes: 2 additions & 2 deletions shared-module/usb_audio/USBSpeaker.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
// 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).
#define USB_AUDIO_SPEAKER_BYTES_PER_FRAME (USB_AUDIO_N_BYTES_PER_SAMPLE * USB_AUDIO_N_CHANNELS)
// Bytes per audio frame in the negotiated UAC2 format (stereo 16-bit).
#define USB_AUDIO_SPEAKER_BYTES_PER_FRAME (USB_AUDIO_BYTES_PER_SAMPLE * USB_AUDIO_MAX_CHANNELS)

// Full owned double-buffer: two halves of FRAMES_PER_BUFFER frames each. Matches
// audiocore.RawSample's convention where base.max_buffer_length is the whole
Expand Down
Loading
Loading