|
20 | 20 | void common_hal_audioi2sin_i2sin_construct(audioi2sin_i2sin_obj_t *self, |
21 | 21 | const mcu_pin_obj_t *bit_clock, const mcu_pin_obj_t *word_select, |
22 | 22 | const mcu_pin_obj_t *data, const mcu_pin_obj_t *main_clock, |
23 | | - uint32_t sample_rate, uint8_t bit_depth, bool mono, bool left_justified, |
24 | | - bool samples_signed) { |
| 23 | + uint32_t sample_rate, uint8_t bit_depth, uint8_t output_bit_depth, |
| 24 | + bool mono, bool left_justified, bool samples_signed) { |
25 | 25 |
|
26 | 26 | if (bit_depth != 8 && bit_depth != 16 && bit_depth != 24 && bit_depth != 32) { |
27 | 27 | mp_raise_ValueError(MP_ERROR_TEXT("bit_depth must be 8, 16, 24, or 32.")); |
@@ -66,6 +66,7 @@ void common_hal_audioi2sin_i2sin_construct(audioi2sin_i2sin_obj_t *self, |
66 | 66 | self->mclk = main_clock; |
67 | 67 | self->sample_rate = sample_rate; |
68 | 68 | self->bit_depth = bit_depth; |
| 69 | + self->output_bit_depth = output_bit_depth; |
69 | 70 | self->mono = mono; |
70 | 71 | self->samples_signed = samples_signed; |
71 | 72 |
|
@@ -113,6 +114,72 @@ void common_hal_audioi2sin_i2sin_deinit(audioi2sin_i2sin_obj_t *self) { |
113 | 114 | self->mclk = NULL; |
114 | 115 | } |
115 | 116 |
|
| 117 | +// Sign-extend a raw I2S sample (in the low `in_depth` bits of `raw`) to a |
| 118 | +// canonical int32 value. |
| 119 | +static inline int32_t i2sin_normalize_signed(uint32_t raw, uint8_t in_depth) { |
| 120 | + if (in_depth == 32) { |
| 121 | + return (int32_t)raw; |
| 122 | + } |
| 123 | + if (in_depth == 24) { |
| 124 | + uint32_t sign_bit = 0x800000u; |
| 125 | + return (int32_t)((raw ^ sign_bit) - sign_bit); |
| 126 | + } |
| 127 | + if (in_depth == 16) { |
| 128 | + return (int16_t)(raw & 0xffffu); |
| 129 | + } |
| 130 | + return (int8_t)(raw & 0xffu); |
| 131 | +} |
| 132 | + |
| 133 | +// Read a single sample from the DMA scratch at the given byte offset for the |
| 134 | +// configured input bit depth. |
| 135 | +static inline uint32_t i2sin_read_raw(const uint8_t *src, uint8_t in_depth) { |
| 136 | + if (in_depth == 8) { |
| 137 | + return (uint32_t)(*src); |
| 138 | + } |
| 139 | + if (in_depth == 16) { |
| 140 | + uint16_t v; |
| 141 | + memcpy(&v, src, sizeof(v)); |
| 142 | + return v; |
| 143 | + } |
| 144 | + uint32_t v; |
| 145 | + memcpy(&v, src, sizeof(v)); |
| 146 | + return v; |
| 147 | +} |
| 148 | + |
| 149 | +// Convert `raw` from `in_depth` to `out_depth` (shift-only semantics, sign- |
| 150 | +// preserving for signed) and write it to `buffer` at sample index `idx`. |
| 151 | +// Output element size: 1 byte at 8, 2 bytes at 16, 4 bytes at 24 or 32. |
| 152 | +static inline void i2sin_write_converted(void *buffer, uint32_t idx, |
| 153 | + uint32_t raw, uint8_t in_depth, uint8_t out_depth, bool samples_signed) { |
| 154 | + int32_t s = i2sin_normalize_signed(raw, in_depth); |
| 155 | + int32_t shifted; |
| 156 | + if (out_depth >= in_depth) { |
| 157 | + shifted = (int32_t)((uint32_t)s << (out_depth - in_depth)); |
| 158 | + } else { |
| 159 | + shifted = s >> (in_depth - out_depth); |
| 160 | + } |
| 161 | + uint32_t u = (uint32_t)shifted; |
| 162 | + if (!samples_signed) { |
| 163 | + if (out_depth >= 32) { |
| 164 | + u ^= 0x80000000u; |
| 165 | + } else { |
| 166 | + uint32_t mask = (1u << out_depth) - 1u; |
| 167 | + u = (u & mask) ^ (1u << (out_depth - 1)); |
| 168 | + } |
| 169 | + } |
| 170 | + switch (out_depth) { |
| 171 | + case 8: |
| 172 | + ((uint8_t *)buffer)[idx] = (uint8_t)(u & 0xffu); |
| 173 | + break; |
| 174 | + case 16: |
| 175 | + ((uint16_t *)buffer)[idx] = (uint16_t)(u & 0xffffu); |
| 176 | + break; |
| 177 | + default: // 24 or 32 |
| 178 | + ((uint32_t *)buffer)[idx] = u; |
| 179 | + break; |
| 180 | + } |
| 181 | +} |
| 182 | + |
116 | 183 | // I2S delivers signed PCM. When samples_signed is false, XOR each sample with |
117 | 184 | // the sign bit for its width to convert to unsigned PCM (WAV convention). |
118 | 185 | static void i2sin_convert_to_unsigned(void *buffer, uint32_t samples, |
@@ -158,6 +225,49 @@ uint32_t common_hal_audioi2sin_i2sin_record_to_buffer(audioi2sin_i2sin_obj_t *se |
158 | 225 | element_size = 4; |
159 | 226 | } |
160 | 227 |
|
| 228 | + if (self->output_bit_depth != self->bit_depth) { |
| 229 | + // Bit-depth conversion path: always read at input width into scratch, |
| 230 | + // convert each sample into the user's buffer at output width. |
| 231 | + const uint8_t in_depth = self->bit_depth; |
| 232 | + const uint8_t out_depth = self->output_bit_depth; |
| 233 | + const bool samples_signed = self->samples_signed; |
| 234 | + uint8_t scratch[256]; |
| 235 | + const size_t in_frame_bytes = 2 * element_size; |
| 236 | + const size_t scratch_frames = sizeof(scratch) / in_frame_bytes; |
| 237 | + uint32_t produced = 0; |
| 238 | + while (produced < length) { |
| 239 | + size_t want_frames; |
| 240 | + if (self->mono) { |
| 241 | + want_frames = length - produced; |
| 242 | + } else { |
| 243 | + want_frames = (length - produced + 1) / 2; |
| 244 | + } |
| 245 | + if (want_frames > scratch_frames) { |
| 246 | + want_frames = scratch_frames; |
| 247 | + } |
| 248 | + size_t got_bytes = 0; |
| 249 | + esp_err_t err = i2s_channel_read(self->rx_chan, scratch, |
| 250 | + want_frames * in_frame_bytes, &got_bytes, portMAX_DELAY); |
| 251 | + CHECK_ESP_RESULT(err); |
| 252 | + size_t got_frames = got_bytes / in_frame_bytes; |
| 253 | + for (size_t i = 0; i < got_frames && produced < length; i++) { |
| 254 | + const uint8_t *frame = scratch + i * in_frame_bytes; |
| 255 | + uint32_t left_raw = i2sin_read_raw(frame, in_depth); |
| 256 | + i2sin_write_converted(buffer, produced++, left_raw, |
| 257 | + in_depth, out_depth, samples_signed); |
| 258 | + if (!self->mono && produced < length) { |
| 259 | + uint32_t right_raw = i2sin_read_raw(frame + element_size, in_depth); |
| 260 | + i2sin_write_converted(buffer, produced++, right_raw, |
| 261 | + in_depth, out_depth, samples_signed); |
| 262 | + } |
| 263 | + } |
| 264 | + if (got_frames < want_frames) { |
| 265 | + break; |
| 266 | + } |
| 267 | + } |
| 268 | + return produced; |
| 269 | + } |
| 270 | + |
161 | 271 | uint32_t produced; |
162 | 272 | if (!self->mono) { |
163 | 273 | size_t result = 0; |
@@ -206,6 +316,10 @@ uint8_t common_hal_audioi2sin_i2sin_get_bit_depth(audioi2sin_i2sin_obj_t *self) |
206 | 316 | return self->bit_depth; |
207 | 317 | } |
208 | 318 |
|
| 319 | +uint8_t common_hal_audioi2sin_i2sin_get_output_bit_depth(audioi2sin_i2sin_obj_t *self) { |
| 320 | + return self->output_bit_depth; |
| 321 | +} |
| 322 | + |
209 | 323 | uint32_t common_hal_audioi2sin_i2sin_get_sample_rate(audioi2sin_i2sin_obj_t *self) { |
210 | 324 | return self->sample_rate; |
211 | 325 | } |
|
0 commit comments