Merge pull request #5310 from dhalbert/rsp2040-i2s-fix

Fix RP2040 I2S: always copy to output buffer
This commit is contained in:
Kattni 2021-09-07 18:18:30 -04:00 committed by GitHub
commit 2ea330581d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 66 additions and 88 deletions

View File

@ -49,92 +49,74 @@ void audio_dma_reset(void) {
} }
STATIC void audio_dma_convert_samples( STATIC size_t audio_dma_convert_samples(audio_dma_t *dma, uint8_t *input, uint32_t input_length, uint8_t *output, uint32_t output_length) {
audio_dma_t *dma,
uint8_t *input, uint32_t input_length,
uint8_t *available_output_buffer, uint32_t available_output_buffer_length,
uint8_t **output, uint32_t *output_length) {
#pragma GCC diagnostic push #pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcast-align" #pragma GCC diagnostic ignored "-Wcast-align"
// Check whether a conversion is necessary uint32_t output_length_used = input_length / dma->sample_spacing;
if (dma->signed_to_unsigned ||
dma->unsigned_to_signed ||
dma->sample_spacing > 1 ||
(dma->sample_resolution != dma->output_resolution)) {
// Must convert. if (output_length_used > output_length) {
// Write the conversion into the passed-in output buffer mp_raise_RuntimeError(translate("Internal audio buffer too small"));
*output = available_output_buffer; }
*output_length = input_length / dma->sample_spacing;
if (*output_length > available_output_buffer_length) { uint32_t out_i = 0;
if (dma->sample_resolution <= 8 && dma->output_resolution > 8) {
// reading bytes, writing 16-bit words, so output buffer will be bigger.
output_length_used = output_length * 2;
if (output_length_used > output_length) {
mp_raise_RuntimeError(translate("Internal audio buffer too small")); mp_raise_RuntimeError(translate("Internal audio buffer too small"));
} }
uint32_t out_i = 0; size_t shift = dma->output_resolution - dma->sample_resolution;
if (dma->sample_resolution <= 8 && dma->output_resolution > 8) {
// reading bytes, writing 16-bit words, so output buffer will be bigger.
*output_length = *output_length * 2; for (uint32_t i = 0; i < input_length; i += dma->sample_spacing) {
if (*output_length > available_output_buffer_length) { if (dma->signed_to_unsigned) {
mp_raise_RuntimeError(translate("Internal audio buffer too small")); ((uint16_t *)output)[out_i] = ((uint16_t)((int8_t *)input)[i] + 0x80) << shift;
} else if (dma->unsigned_to_signed) {
((int16_t *)output)[out_i] = ((int16_t)((uint8_t *)input)[i] - 0x80) << shift;
} else {
((uint16_t *)output)[out_i] = ((uint16_t)((uint8_t *)input)[i]) << shift;
} }
out_i += 1;
size_t shift = dma->output_resolution - dma->sample_resolution; }
} else if (dma->sample_resolution <= 8 && dma->output_resolution <= 8) {
for (uint32_t i = 0; i < input_length; i += dma->sample_spacing) { for (uint32_t i = 0; i < input_length; i += dma->sample_spacing) {
if (dma->signed_to_unsigned) { if (dma->signed_to_unsigned) {
((uint16_t *)*output)[out_i] = ((uint16_t)((int8_t *)input)[i] + 0x80) << shift; ((uint8_t *)output)[out_i] = ((int8_t *)input)[i] + 0x80;
} else if (dma->unsigned_to_signed) { } else if (dma->unsigned_to_signed) {
((int16_t *)*output)[out_i] = ((int16_t)((uint8_t *)input)[i] - 0x80) << shift; ((int8_t *)output)[out_i] = ((uint8_t *)input)[i] - 0x80;
} else {
((uint8_t *)output)[out_i] = ((uint8_t *)input)[i];
}
out_i += 1;
}
} else if (dma->sample_resolution > 8 && dma->output_resolution > 8) {
size_t shift = 16 - dma->output_resolution;
for (uint32_t i = 0; i < input_length / 2; i += dma->sample_spacing) {
if (dma->signed_to_unsigned) {
((uint16_t *)output)[out_i] = ((int16_t *)input)[i] + 0x8000;
} else if (dma->unsigned_to_signed) {
((int16_t *)output)[out_i] = ((uint16_t *)input)[i] - 0x8000;
} else {
((uint16_t *)output)[out_i] = ((uint16_t *)input)[i];
}
if (dma->output_resolution < 16) {
if (dma->output_signed) {
((int16_t *)output)[out_i] = ((int16_t *)output)[out_i] >> shift;
} else { } else {
((uint16_t *)*output)[out_i] = ((uint16_t)((uint8_t *)input)[i]) << shift; ((uint16_t *)output)[out_i] = ((uint16_t *)output)[out_i] >> shift;
} }
out_i += 1;
} }
} else if (dma->sample_resolution <= 8 && dma->output_resolution <= 8) { out_i += 1;
for (uint32_t i = 0; i < input_length; i += dma->sample_spacing) {
if (dma->signed_to_unsigned) {
((uint8_t *)*output)[out_i] = ((int8_t *)input)[i] + 0x80;
} else if (dma->unsigned_to_signed) {
((int8_t *)*output)[out_i] = ((uint8_t *)input)[i] - 0x80;
} else {
((uint8_t *)*output)[out_i] = ((uint8_t *)input)[i];
}
out_i += 1;
}
} else if (dma->sample_resolution > 8 && dma->output_resolution > 8) {
size_t shift = 16 - dma->output_resolution;
for (uint32_t i = 0; i < input_length / 2; i += dma->sample_spacing) {
if (dma->signed_to_unsigned) {
((uint16_t *)*output)[out_i] = ((int16_t *)input)[i] + 0x8000;
} else if (dma->unsigned_to_signed) {
((int16_t *)*output)[out_i] = ((uint16_t *)input)[i] - 0x8000;
} else {
((uint16_t *)*output)[out_i] = ((uint16_t *)input)[i];
}
if (dma->output_resolution < 16) {
if (dma->output_signed) {
((int16_t *)*output)[out_i] = ((int16_t *)*output)[out_i] >> shift;
} else {
((uint16_t *)*output)[out_i] = ((uint16_t *)*output)[out_i] >> shift;
}
}
out_i += 1;
}
} else {
// (dma->sample_resolution > 8 && dma->output_resolution <= 8)
// Not currently used, but might be in the future.
mp_raise_RuntimeError(translate("Audio conversion not implemented"));
} }
} else { } else {
// No conversion necessary. Designate the input buffer as the output buffer. // (dma->sample_resolution > 8 && dma->output_resolution <= 8)
*output = input; // Not currently used, but might be in the future.
*output_length = input_length; mp_raise_RuntimeError(translate("Audio conversion not implemented"));
} }
#pragma GCC diagnostic pop #pragma GCC diagnostic pop
return output_length_used;
} }
// buffer_idx is 0 or 1. // buffer_idx is 0 or 1.
@ -154,21 +136,14 @@ STATIC void audio_dma_load_next_block(audio_dma_t *dma, size_t buffer_idx) {
// Convert the sample format resolution and signedness, as necessary. // Convert the sample format resolution and signedness, as necessary.
// The input sample buffer is what was read from a file, Mixer, or a raw sample buffer. // The input sample buffer is what was read from a file, Mixer, or a raw sample buffer.
// The output buffer is one of the DMA buffers (passed in), or if no conversion was done, // The output buffer is one of the DMA buffers (passed in).
// the original sample buffer (to save copying).
// audio_dma_convert_samples() will write the converted samples into the given output size_t output_length_used = audio_dma_convert_samples(
// buffer if necessary. If no conversion was needed, it will return the sample buffer dma, sample_buffer, sample_buffer_length,
// as the output buffer. dma->buffer[buffer_idx], dma->buffer_length[buffer_idx]);
uint8_t *output_buffer;
uint32_t output_buffer_length;
audio_dma_convert_samples(dma, sample_buffer, sample_buffer_length, dma_channel_set_read_addr(dma_channel, dma->buffer[buffer_idx], false /* trigger */);
dma->buffer[buffer_idx], dma->buffer_length[buffer_idx], dma_channel_set_trans_count(dma_channel, output_length_used / dma->output_size, false /* trigger */);
&output_buffer, &output_buffer_length);
dma_channel_set_read_addr(dma_channel, output_buffer, false /* trigger */);
dma_channel_set_trans_count(dma_channel, output_buffer_length / dma->output_size, false /* trigger */);
if (get_buffer_result == GET_BUFFER_DONE) { if (get_buffer_result == GET_BUFFER_DONE) {
if (dma->loop) { if (dma->loop) {
@ -180,7 +155,7 @@ STATIC void audio_dma_load_next_block(audio_dma_t *dma, size_t buffer_idx) {
(c->al1_ctrl & ~DMA_CH0_CTRL_TRIG_CHAIN_TO_BITS) | (c->al1_ctrl & ~DMA_CH0_CTRL_TRIG_CHAIN_TO_BITS) |
(dma_channel << DMA_CH0_CTRL_TRIG_CHAIN_TO_LSB); (dma_channel << DMA_CH0_CTRL_TRIG_CHAIN_TO_LSB);
if (output_buffer_length == 0 && if (output_length_used == 0 &&
!dma_channel_is_busy(dma->channel[0]) && !dma_channel_is_busy(dma->channel[0]) &&
!dma_channel_is_busy(dma->channel[1])) { !dma_channel_is_busy(dma->channel[1])) {
// No data has been read, and both DMA channels have now finished, so it's safe to stop. // No data has been read, and both DMA channels have now finished, so it's safe to stop.

View File

@ -174,14 +174,19 @@ void common_hal_audiobusio_i2sout_play(audiobusio_i2sout_obj_t *self,
uint16_t bits_per_sample_output = bits_per_sample * 2; uint16_t bits_per_sample_output = bits_per_sample * 2;
size_t clocks_per_bit = 6; size_t clocks_per_bit = 6;
uint32_t frequency = bits_per_sample_output * audiosample_sample_rate(sample); uint32_t frequency = bits_per_sample_output * audiosample_sample_rate(sample);
common_hal_rp2pio_statemachine_set_frequency(&self->state_machine, clocks_per_bit * frequency);
common_hal_rp2pio_statemachine_restart(&self->state_machine);
uint8_t channel_count = audiosample_channel_count(sample); uint8_t channel_count = audiosample_channel_count(sample);
if (channel_count > 2) { if (channel_count > 2) {
mp_raise_ValueError(translate("Too many channels in sample.")); mp_raise_ValueError(translate("Too many channels in sample."));
} }
common_hal_rp2pio_statemachine_set_frequency(&self->state_machine, clocks_per_bit * frequency);
common_hal_rp2pio_statemachine_restart(&self->state_machine);
// On the RP2040, output registers are always written with a 32-bit write.
// If the write is 8 or 16 bits wide, the data will be replicated in upper bytes.
// See section 2.1.4 Narrow IO Register Writes in the RP2040 datasheet.
// This means that identical 16-bit audio data will be written in both halves of the incoming PIO
// FIFO register. Thus we get mono-to-stereo conversion for the I2S output for free.
audio_dma_result result = audio_dma_setup_playback( audio_dma_result result = audio_dma_setup_playback(
&self->dma, &self->dma,
sample, sample,
@ -201,8 +206,6 @@ void common_hal_audiobusio_i2sout_play(audiobusio_i2sout_obj_t *self,
mp_raise_RuntimeError(translate("Unable to allocate buffers for signed conversion")); mp_raise_RuntimeError(translate("Unable to allocate buffers for signed conversion"));
} }
// Turn on the state machine's clock.
self->playing = true; self->playing = true;
} }