Skip to content

Commit 2ea3305

Browse files
authored
Merge pull request #5310 from dhalbert/rsp2040-i2s-fix
Fix RP2040 I2S: always copy to output buffer
2 parents daad0d0 + d2d0bd2 commit 2ea3305

File tree

2 files changed

+64
-86
lines changed

2 files changed

+64
-86
lines changed

ports/raspberrypi/audio_dma.c

Lines changed: 56 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -49,92 +49,74 @@ void audio_dma_reset(void) {
4949
}
5050

5151

52-
STATIC void audio_dma_convert_samples(
53-
audio_dma_t *dma,
54-
uint8_t *input, uint32_t input_length,
55-
uint8_t *available_output_buffer, uint32_t available_output_buffer_length,
56-
uint8_t **output, uint32_t *output_length) {
57-
52+
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) {
5853
#pragma GCC diagnostic push
5954
#pragma GCC diagnostic ignored "-Wcast-align"
6055

61-
// Check whether a conversion is necessary
62-
if (dma->signed_to_unsigned ||
63-
dma->unsigned_to_signed ||
64-
dma->sample_spacing > 1 ||
65-
(dma->sample_resolution != dma->output_resolution)) {
56+
uint32_t output_length_used = input_length / dma->sample_spacing;
6657

67-
// Must convert.
68-
// Write the conversion into the passed-in output buffer
69-
*output = available_output_buffer;
70-
*output_length = input_length / dma->sample_spacing;
58+
if (output_length_used > output_length) {
59+
mp_raise_RuntimeError(translate("Internal audio buffer too small"));
60+
}
7161

72-
if (*output_length > available_output_buffer_length) {
62+
uint32_t out_i = 0;
63+
if (dma->sample_resolution <= 8 && dma->output_resolution > 8) {
64+
// reading bytes, writing 16-bit words, so output buffer will be bigger.
65+
66+
output_length_used = output_length * 2;
67+
if (output_length_used > output_length) {
7368
mp_raise_RuntimeError(translate("Internal audio buffer too small"));
7469
}
7570

76-
uint32_t out_i = 0;
77-
if (dma->sample_resolution <= 8 && dma->output_resolution > 8) {
78-
// reading bytes, writing 16-bit words, so output buffer will be bigger.
71+
size_t shift = dma->output_resolution - dma->sample_resolution;
7972

80-
*output_length = *output_length * 2;
81-
if (*output_length > available_output_buffer_length) {
82-
mp_raise_RuntimeError(translate("Internal audio buffer too small"));
73+
for (uint32_t i = 0; i < input_length; i += dma->sample_spacing) {
74+
if (dma->signed_to_unsigned) {
75+
((uint16_t *)output)[out_i] = ((uint16_t)((int8_t *)input)[i] + 0x80) << shift;
76+
} else if (dma->unsigned_to_signed) {
77+
((int16_t *)output)[out_i] = ((int16_t)((uint8_t *)input)[i] - 0x80) << shift;
78+
} else {
79+
((uint16_t *)output)[out_i] = ((uint16_t)((uint8_t *)input)[i]) << shift;
8380
}
84-
85-
size_t shift = dma->output_resolution - dma->sample_resolution;
86-
87-
for (uint32_t i = 0; i < input_length; i += dma->sample_spacing) {
88-
if (dma->signed_to_unsigned) {
89-
((uint16_t *)*output)[out_i] = ((uint16_t)((int8_t *)input)[i] + 0x80) << shift;
90-
} else if (dma->unsigned_to_signed) {
91-
((int16_t *)*output)[out_i] = ((int16_t)((uint8_t *)input)[i] - 0x80) << shift;
92-
} else {
93-
((uint16_t *)*output)[out_i] = ((uint16_t)((uint8_t *)input)[i]) << shift;
94-
}
95-
out_i += 1;
81+
out_i += 1;
82+
}
83+
} else if (dma->sample_resolution <= 8 && dma->output_resolution <= 8) {
84+
for (uint32_t i = 0; i < input_length; i += dma->sample_spacing) {
85+
if (dma->signed_to_unsigned) {
86+
((uint8_t *)output)[out_i] = ((int8_t *)input)[i] + 0x80;
87+
} else if (dma->unsigned_to_signed) {
88+
((int8_t *)output)[out_i] = ((uint8_t *)input)[i] - 0x80;
89+
} else {
90+
((uint8_t *)output)[out_i] = ((uint8_t *)input)[i];
9691
}
97-
} else if (dma->sample_resolution <= 8 && dma->output_resolution <= 8) {
98-
for (uint32_t i = 0; i < input_length; i += dma->sample_spacing) {
99-
if (dma->signed_to_unsigned) {
100-
((uint8_t *)*output)[out_i] = ((int8_t *)input)[i] + 0x80;
101-
} else if (dma->unsigned_to_signed) {
102-
((int8_t *)*output)[out_i] = ((uint8_t *)input)[i] - 0x80;
103-
} else {
104-
((uint8_t *)*output)[out_i] = ((uint8_t *)input)[i];
105-
}
106-
out_i += 1;
92+
out_i += 1;
93+
}
94+
} else if (dma->sample_resolution > 8 && dma->output_resolution > 8) {
95+
size_t shift = 16 - dma->output_resolution;
96+
for (uint32_t i = 0; i < input_length / 2; i += dma->sample_spacing) {
97+
if (dma->signed_to_unsigned) {
98+
((uint16_t *)output)[out_i] = ((int16_t *)input)[i] + 0x8000;
99+
} else if (dma->unsigned_to_signed) {
100+
((int16_t *)output)[out_i] = ((uint16_t *)input)[i] - 0x8000;
101+
} else {
102+
((uint16_t *)output)[out_i] = ((uint16_t *)input)[i];
107103
}
108-
} else if (dma->sample_resolution > 8 && dma->output_resolution > 8) {
109-
size_t shift = 16 - dma->output_resolution;
110-
for (uint32_t i = 0; i < input_length / 2; i += dma->sample_spacing) {
111-
if (dma->signed_to_unsigned) {
112-
((uint16_t *)*output)[out_i] = ((int16_t *)input)[i] + 0x8000;
113-
} else if (dma->unsigned_to_signed) {
114-
((int16_t *)*output)[out_i] = ((uint16_t *)input)[i] - 0x8000;
104+
if (dma->output_resolution < 16) {
105+
if (dma->output_signed) {
106+
((int16_t *)output)[out_i] = ((int16_t *)output)[out_i] >> shift;
115107
} else {
116-
((uint16_t *)*output)[out_i] = ((uint16_t *)input)[i];
117-
}
118-
if (dma->output_resolution < 16) {
119-
if (dma->output_signed) {
120-
((int16_t *)*output)[out_i] = ((int16_t *)*output)[out_i] >> shift;
121-
} else {
122-
((uint16_t *)*output)[out_i] = ((uint16_t *)*output)[out_i] >> shift;
123-
}
108+
((uint16_t *)output)[out_i] = ((uint16_t *)output)[out_i] >> shift;
124109
}
125-
out_i += 1;
126110
}
127-
} else {
128-
// (dma->sample_resolution > 8 && dma->output_resolution <= 8)
129-
// Not currently used, but might be in the future.
130-
mp_raise_RuntimeError(translate("Audio conversion not implemented"));
111+
out_i += 1;
131112
}
132113
} else {
133-
// No conversion necessary. Designate the input buffer as the output buffer.
134-
*output = input;
135-
*output_length = input_length;
114+
// (dma->sample_resolution > 8 && dma->output_resolution <= 8)
115+
// Not currently used, but might be in the future.
116+
mp_raise_RuntimeError(translate("Audio conversion not implemented"));
136117
}
137118
#pragma GCC diagnostic pop
119+
return output_length_used;
138120
}
139121

140122
// 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) {
154136

155137
// Convert the sample format resolution and signedness, as necessary.
156138
// The input sample buffer is what was read from a file, Mixer, or a raw sample buffer.
157-
// The output buffer is one of the DMA buffers (passed in), or if no conversion was done,
158-
// the original sample buffer (to save copying).
159-
160-
// audio_dma_convert_samples() will write the converted samples into the given output
161-
// buffer if necessary. If no conversion was needed, it will return the sample buffer
162-
// as the output buffer.
163-
uint8_t *output_buffer;
164-
uint32_t output_buffer_length;
139+
// The output buffer is one of the DMA buffers (passed in).
165140

166-
audio_dma_convert_samples(dma, sample_buffer, sample_buffer_length,
167-
dma->buffer[buffer_idx], dma->buffer_length[buffer_idx],
168-
&output_buffer, &output_buffer_length);
141+
size_t output_length_used = audio_dma_convert_samples(
142+
dma, sample_buffer, sample_buffer_length,
143+
dma->buffer[buffer_idx], dma->buffer_length[buffer_idx]);
169144

170-
dma_channel_set_read_addr(dma_channel, output_buffer, false /* trigger */);
171-
dma_channel_set_trans_count(dma_channel, output_buffer_length / dma->output_size, false /* trigger */);
145+
dma_channel_set_read_addr(dma_channel, dma->buffer[buffer_idx], false /* trigger */);
146+
dma_channel_set_trans_count(dma_channel, output_length_used / dma->output_size, false /* trigger */);
172147

173148
if (get_buffer_result == GET_BUFFER_DONE) {
174149
if (dma->loop) {
@@ -180,7 +155,7 @@ STATIC void audio_dma_load_next_block(audio_dma_t *dma, size_t buffer_idx) {
180155
(c->al1_ctrl & ~DMA_CH0_CTRL_TRIG_CHAIN_TO_BITS) |
181156
(dma_channel << DMA_CH0_CTRL_TRIG_CHAIN_TO_LSB);
182157

183-
if (output_buffer_length == 0 &&
158+
if (output_length_used == 0 &&
184159
!dma_channel_is_busy(dma->channel[0]) &&
185160
!dma_channel_is_busy(dma->channel[1])) {
186161
// No data has been read, and both DMA channels have now finished, so it's safe to stop.

ports/raspberrypi/common-hal/audiobusio/I2SOut.c

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -174,14 +174,19 @@ void common_hal_audiobusio_i2sout_play(audiobusio_i2sout_obj_t *self,
174174
uint16_t bits_per_sample_output = bits_per_sample * 2;
175175
size_t clocks_per_bit = 6;
176176
uint32_t frequency = bits_per_sample_output * audiosample_sample_rate(sample);
177-
common_hal_rp2pio_statemachine_set_frequency(&self->state_machine, clocks_per_bit * frequency);
178-
common_hal_rp2pio_statemachine_restart(&self->state_machine);
179-
180177
uint8_t channel_count = audiosample_channel_count(sample);
181178
if (channel_count > 2) {
182179
mp_raise_ValueError(translate("Too many channels in sample."));
183180
}
184181

182+
common_hal_rp2pio_statemachine_set_frequency(&self->state_machine, clocks_per_bit * frequency);
183+
common_hal_rp2pio_statemachine_restart(&self->state_machine);
184+
185+
// On the RP2040, output registers are always written with a 32-bit write.
186+
// If the write is 8 or 16 bits wide, the data will be replicated in upper bytes.
187+
// See section 2.1.4 Narrow IO Register Writes in the RP2040 datasheet.
188+
// This means that identical 16-bit audio data will be written in both halves of the incoming PIO
189+
// FIFO register. Thus we get mono-to-stereo conversion for the I2S output for free.
185190
audio_dma_result result = audio_dma_setup_playback(
186191
&self->dma,
187192
sample,
@@ -201,8 +206,6 @@ void common_hal_audiobusio_i2sout_play(audiobusio_i2sout_obj_t *self,
201206
mp_raise_RuntimeError(translate("Unable to allocate buffers for signed conversion"));
202207
}
203208

204-
// Turn on the state machine's clock.
205-
206209
self->playing = true;
207210
}
208211

0 commit comments

Comments
 (0)