Fix RP2040 I2S: always copy to output buffer
This commit is contained in:
parent
119cc8488b
commit
d2d0bd289f
|
@ -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.
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue