audiocore: Integrate mp3 decoder
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -3,3 +3,5 @@ hardware/tonberry-pico/tonberry-pico-backups/
|
|||||||
*.kicad_sch-bak
|
*.kicad_sch-bak
|
||||||
*.kicad_sch.lck
|
*.kicad_sch.lck
|
||||||
software/build
|
software/build
|
||||||
|
compile_commands.json
|
||||||
|
.dir-locals.el
|
||||||
|
|||||||
@@ -1,27 +1,88 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
// Copyright (c) 2024 Matthias Blankertz <matthias@blankertz.org>
|
// Copyright (c) 2024-2025 Matthias Blankertz <matthias@blankertz.org>
|
||||||
|
|
||||||
#include "audiocore.h"
|
#include "audiocore.h"
|
||||||
#include "i2s.h"
|
#include "i2s.h"
|
||||||
|
#include "mp3.h"
|
||||||
|
|
||||||
#include "py/mperrno.h"
|
#include "py/mperrno.h"
|
||||||
|
|
||||||
void core1_main(void)
|
void __time_critical_func(volume_adjust)(int16_t *buf, size_t samples, uint16_t scalef)
|
||||||
{
|
{
|
||||||
if (!i2s_init(shared_context.out_pin, shared_context.sideset_base, shared_context.samplerate)) {
|
for (size_t pos = 0; pos < samples; ++pos) {
|
||||||
multicore_fifo_push_blocking(MP_EIO);
|
buf[pos] = ((int32_t)buf[pos] * scalef) >> 15;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
multicore_fifo_push_blocking(0);
|
|
||||||
uint32_t cmd;
|
void __time_critical_func(core1_main)(void)
|
||||||
while ((cmd = multicore_fifo_pop_blocking()) != AUDIOCORE_CMD_SHUTDOWN) {
|
{
|
||||||
switch (cmd) {
|
uint32_t ret = 0;
|
||||||
default:
|
bool running = true, playing = false;
|
||||||
break;
|
if (!i2s_init(shared_context.out_pin, shared_context.sideset_base)) {
|
||||||
}
|
ret = MP_EIO;
|
||||||
}
|
goto out;
|
||||||
|
}
|
||||||
i2s_deinit();
|
if (!mp3_init()) {
|
||||||
multicore_fifo_push_blocking(0);
|
ret = MP_ENOMEM;
|
||||||
|
goto out_i2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
multicore_fifo_push_blocking(0);
|
||||||
|
uint32_t current_volume = AUDIOCORE_MAX_VOLUME >> 4;
|
||||||
|
bool flushing = false;
|
||||||
|
while (running) {
|
||||||
|
uint32_t cmd;
|
||||||
|
uint32_t *buf;
|
||||||
|
if (multicore_fifo_rvalid()) {
|
||||||
|
cmd = multicore_fifo_pop_blocking();
|
||||||
|
switch (cmd) {
|
||||||
|
case AUDIOCORE_CMD_SHUTDOWN:
|
||||||
|
running = false;
|
||||||
|
break;
|
||||||
|
case AUDIOCORE_CMD_SET_VOLUME: {
|
||||||
|
const uint32_t new_volume = multicore_fifo_pop_blocking();
|
||||||
|
if (new_volume > AUDIOCORE_MAX_VOLUME) {
|
||||||
|
multicore_fifo_push_blocking(1);
|
||||||
|
} else {
|
||||||
|
current_volume = new_volume;
|
||||||
|
multicore_fifo_push_blocking(0);
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
case AUDIOCORE_CMD_FLUSH:
|
||||||
|
flushing = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((buf = i2s_next_buf()) != NULL) {
|
||||||
|
unsigned samplerate;
|
||||||
|
// decode one frame
|
||||||
|
if (mp3_decode(buf, &samplerate)) {
|
||||||
|
if (!playing) {
|
||||||
|
i2s_play(samplerate);
|
||||||
|
playing = true;
|
||||||
|
}
|
||||||
|
volume_adjust((int16_t *)buf, 2304, current_volume);
|
||||||
|
i2s_commit_buf(buf);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
/* mp3_decode returned false: not enough data in buffer */
|
||||||
|
if (flushing) {
|
||||||
|
mp3_reset();
|
||||||
|
i2s_stop();
|
||||||
|
playing = false;
|
||||||
|
flushing = false;
|
||||||
|
multicore_fifo_push_blocking(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
__wfe();
|
||||||
|
}
|
||||||
|
|
||||||
|
mp3_deinit();
|
||||||
|
out_i2s:
|
||||||
|
i2s_deinit();
|
||||||
|
out:
|
||||||
|
multicore_fifo_push_blocking(ret);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
// Copyright (c) 2024 Matthias Blankertz <matthias@blankertz.org>
|
// Copyright (c) 2024-2025 Matthias Blankertz <matthias@blankertz.org>
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
@@ -17,7 +17,12 @@
|
|||||||
* communication between the cores.
|
* communication between the cores.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#define AUDIO_BUFFER_SIZE 2048
|
#define MP3_BUFFER_ALIGN (sizeof(uintptr_t))
|
||||||
|
#define MP3_BUFFER_SIZE 4096
|
||||||
|
#define MP3_BUFFER_PREAREA 1048
|
||||||
|
|
||||||
|
_Static_assert(MP3_BUFFER_PREAREA % MP3_BUFFER_ALIGN == 0,
|
||||||
|
"Prearea must be a multiple of machine word size for alignment");
|
||||||
|
|
||||||
// Context shared between the micropython runtime on core0 and the audio task on
|
// Context shared between the micropython runtime on core0 and the audio task on
|
||||||
// core1 All access must hold "lock" unless otherwise noted
|
// core1 All access must hold "lock" unless otherwise noted
|
||||||
@@ -27,59 +32,59 @@ struct audiocore_shared_context {
|
|||||||
// Set by module.c before core1 is launched and then never changed, can be read without lock
|
// Set by module.c before core1 is launched and then never changed, can be read without lock
|
||||||
int out_pin, sideset_base, samplerate;
|
int out_pin, sideset_base, samplerate;
|
||||||
|
|
||||||
// Must hold lock
|
// Must hold lock. The indices 0..MP3_BUFFER_PREAREA-1 may only be read and written on core1 (no
|
||||||
uint32_t audio_buffer[AUDIO_BUFFER_SIZE];
|
// lock needed) The buffer is aligned to, and MP3_BUFFER_PREAREA is a multiple of, the machine
|
||||||
int audio_buffer_write, audio_buffer_read;
|
// word size to prevent these accesses from crossing word boundaries.
|
||||||
|
char mp3_buffer[MP3_BUFFER_PREAREA + MP3_BUFFER_SIZE] __attribute__((aligned(MP3_BUFFER_ALIGN)));
|
||||||
|
int mp3_buffer_write, mp3_buffer_read;
|
||||||
int underruns;
|
int underruns;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern struct audiocore_shared_context shared_context;
|
extern struct audiocore_shared_context shared_context;
|
||||||
|
|
||||||
static inline unsigned audiocore_get_audio_buffer_space(void)
|
/* Must hold audiocore_shared_context.lock */
|
||||||
|
static inline unsigned audiocore_get_buffer_space(void)
|
||||||
{
|
{
|
||||||
if (shared_context.audio_buffer_write >= shared_context.audio_buffer_read)
|
if (shared_context.mp3_buffer_write >= shared_context.mp3_buffer_read)
|
||||||
return AUDIO_BUFFER_SIZE - 1 - (shared_context.audio_buffer_write - shared_context.audio_buffer_read);
|
return MP3_BUFFER_SIZE - 1 - (shared_context.mp3_buffer_write - shared_context.mp3_buffer_read);
|
||||||
else
|
else
|
||||||
return shared_context.audio_buffer_read - shared_context.audio_buffer_write - 1;
|
return shared_context.mp3_buffer_read - shared_context.mp3_buffer_write - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline unsigned audiocore_get_audio_buffer_avail(void)
|
/* Must hold audiocore_shared_context.lock */
|
||||||
|
static inline unsigned audiocore_get_buffer_avail(void)
|
||||||
{
|
{
|
||||||
if (shared_context.audio_buffer_write >= shared_context.audio_buffer_read)
|
if (shared_context.mp3_buffer_write >= shared_context.mp3_buffer_read)
|
||||||
return shared_context.audio_buffer_write - shared_context.audio_buffer_read;
|
return shared_context.mp3_buffer_write - shared_context.mp3_buffer_read;
|
||||||
else
|
else
|
||||||
return AUDIO_BUFFER_SIZE - (shared_context.audio_buffer_read - shared_context.audio_buffer_write);
|
return MP3_BUFFER_SIZE - (shared_context.mp3_buffer_read - shared_context.mp3_buffer_write);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void audiocore_audio_buffer_put(const uint32_t *restrict src, const size_t len)
|
/* Must hold audiocore_shared_context.lock */
|
||||||
|
static inline void audiocore_buffer_put(const char *restrict src, const size_t len)
|
||||||
{
|
{
|
||||||
const unsigned end_samples = AUDIO_BUFFER_SIZE - shared_context.audio_buffer_write;
|
const unsigned end_bytes = MP3_BUFFER_SIZE - shared_context.mp3_buffer_write;
|
||||||
memcpy(shared_context.audio_buffer + shared_context.audio_buffer_write, src,
|
memcpy(shared_context.mp3_buffer + MP3_BUFFER_PREAREA + shared_context.mp3_buffer_write, src,
|
||||||
4 * ((end_samples >= len) ? len : end_samples));
|
((end_bytes >= len) ? len : end_bytes));
|
||||||
if (end_samples < len) {
|
if (end_bytes < len) {
|
||||||
memcpy(shared_context.audio_buffer, src + end_samples, 4 * (len - end_samples));
|
memcpy(shared_context.mp3_buffer + MP3_BUFFER_PREAREA, src + end_bytes, len - end_bytes);
|
||||||
shared_context.audio_buffer_write = len - end_samples;
|
shared_context.mp3_buffer_write = len - end_bytes;
|
||||||
} else {
|
} else {
|
||||||
shared_context.audio_buffer_write += len;
|
shared_context.mp3_buffer_write += len;
|
||||||
}
|
}
|
||||||
shared_context.audio_buffer_write %= AUDIO_BUFFER_SIZE;
|
shared_context.mp3_buffer_write %= MP3_BUFFER_SIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void audiocore_audio_buffer_get(uint32_t *restrict dst, const size_t len)
|
void __time_critical_func(volume_adjust)(int16_t *buf, size_t samples, uint16_t scalef);
|
||||||
{
|
|
||||||
const unsigned end_samples = AUDIO_BUFFER_SIZE - shared_context.audio_buffer_read;
|
|
||||||
memcpy(dst, shared_context.audio_buffer + shared_context.audio_buffer_read,
|
|
||||||
4 * ((end_samples >= len) ? len : end_samples));
|
|
||||||
if (end_samples < len) {
|
|
||||||
memcpy(dst + end_samples, shared_context.audio_buffer, 4 * (len - end_samples));
|
|
||||||
shared_context.audio_buffer_read = len - end_samples;
|
|
||||||
} else {
|
|
||||||
shared_context.audio_buffer_read += len;
|
|
||||||
}
|
|
||||||
shared_context.audio_buffer_read %= AUDIO_BUFFER_SIZE;
|
|
||||||
}
|
|
||||||
|
|
||||||
void core1_main(void);
|
void core1_main(void);
|
||||||
|
|
||||||
// SHUTDOWN - no arguments - return 0
|
// SHUTDOWN - no arguments - return 0
|
||||||
#define AUDIOCORE_CMD_SHUTDOWN 0xdeadc0de
|
#define AUDIOCORE_CMD_SHUTDOWN 0xdeadc0de
|
||||||
|
|
||||||
|
// FLUSH - signals end of file and stop decoding when buffer empty - no arguments - return 0 when decoding is finished
|
||||||
|
#define AUDIOCORE_CMD_FLUSH 0xdeadc0dd
|
||||||
|
|
||||||
|
#define AUDIOCORE_MAX_VOLUME 0x8000u
|
||||||
|
// SET VOLUME - one argument: uint32_t range 0 to AUDIOCORE_MAX_VOLUME - return 0 if volume changed, 1 on error
|
||||||
|
#define AUDIOCORE_CMD_SET_VOLUME 0xdead0001
|
||||||
|
|||||||
@@ -1,43 +1,53 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
// Copyright (c) 2024 Matthias Blankertz <matthias@blankertz.org>
|
// Copyright (c) 2024-2025 Matthias Blankertz <matthias@blankertz.org>
|
||||||
|
|
||||||
|
#include "i2s.h"
|
||||||
|
#include "i2s_max98357.pio.h"
|
||||||
|
|
||||||
#include "audiocore.h"
|
#include "audiocore.h"
|
||||||
#include "i2s_max98357.pio.h"
|
|
||||||
|
|
||||||
#include <hardware/dma.h>
|
#include <hardware/dma.h>
|
||||||
#include <hardware/sync.h>
|
#include <hardware/sync.h>
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#define audiocore_pio pio1
|
#define audiocore_pio pio1
|
||||||
|
|
||||||
#define I2S_DMA_BUF_SIZE 256
|
// Must be at least 2
|
||||||
|
#define AUDIO_BUFS 2
|
||||||
|
|
||||||
struct i2s_context {
|
struct i2s_context {
|
||||||
unsigned pio_program_offset;
|
unsigned pio_program_offset;
|
||||||
int pio_sm;
|
int pio_sm;
|
||||||
int dma_ch;
|
int dma_ch;
|
||||||
dma_channel_config dma_config;
|
dma_channel_config dma_config;
|
||||||
uint32_t dma_buf[I2S_DMA_BUF_SIZE];
|
uint32_t dma_buf[AUDIO_BUFS][I2S_DMA_BUF_SIZE];
|
||||||
|
int cur_playing;
|
||||||
|
int out_pin, sideset_base;
|
||||||
|
bool has_data[AUDIO_BUFS];
|
||||||
|
bool playback_active;
|
||||||
};
|
};
|
||||||
|
|
||||||
static struct i2s_context i2s_context;
|
static struct i2s_context i2s_context;
|
||||||
|
|
||||||
static void dma_isr(void)
|
static void __time_critical_func(dma_isr)(void)
|
||||||
{
|
{
|
||||||
if (!dma_channel_get_irq1_status(i2s_context.dma_ch))
|
if (get_core_num() != 1 || !dma_channel_get_irq1_status(i2s_context.dma_ch))
|
||||||
return;
|
return;
|
||||||
dma_channel_acknowledge_irq1(i2s_context.dma_ch);
|
dma_channel_acknowledge_irq1(i2s_context.dma_ch);
|
||||||
const uint32_t flags = spin_lock_blocking(shared_context.lock);
|
const int next_buf = (i2s_context.cur_playing + 1) % AUDIO_BUFS;
|
||||||
if (audiocore_get_audio_buffer_avail() >= I2S_DMA_BUF_SIZE) {
|
if (i2s_context.playback_active && i2s_context.has_data[next_buf]) {
|
||||||
audiocore_audio_buffer_get(i2s_context.dma_buf, I2S_DMA_BUF_SIZE);
|
i2s_context.cur_playing = next_buf;
|
||||||
spin_unlock(shared_context.lock, flags);
|
i2s_context.has_data[next_buf] = false;
|
||||||
} else {
|
} else {
|
||||||
++shared_context.underruns;
|
memset(i2s_context.dma_buf[i2s_context.cur_playing], 0, sizeof(uint32_t) * I2S_DMA_BUF_SIZE);
|
||||||
spin_unlock(shared_context.lock, flags);
|
if (i2s_context.playback_active) {
|
||||||
memset(i2s_context.dma_buf, 0, sizeof(uint32_t) * I2S_DMA_BUF_SIZE);
|
++shared_context.underruns;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
dma_channel_transfer_from_buffer_now(i2s_context.dma_ch, i2s_context.dma_buf, I2S_DMA_BUF_SIZE);
|
dma_channel_transfer_from_buffer_now(i2s_context.dma_ch, i2s_context.dma_buf[i2s_context.cur_playing],
|
||||||
|
I2S_DMA_BUF_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void setup_dma_config(void)
|
static void setup_dma_config(void)
|
||||||
@@ -46,41 +56,108 @@ static void setup_dma_config(void)
|
|||||||
channel_config_set_dreq(&i2s_context.dma_config, pio_get_dreq(pio1, i2s_context.pio_sm, true));
|
channel_config_set_dreq(&i2s_context.dma_config, pio_get_dreq(pio1, i2s_context.pio_sm, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool i2s_init(int out_pin, int sideset_base, int samplerate)
|
uint32_t *__time_critical_func(i2s_next_buf)(void)
|
||||||
{
|
{
|
||||||
memset(i2s_context.dma_buf, 0, sizeof(i2s_context.dma_buf[0]) * I2S_DMA_BUF_SIZE);
|
uint32_t *ret = NULL;
|
||||||
if (!pio_can_add_program(audiocore_pio, &i2s_max98357_program))
|
const long flags = save_and_disable_interrupts();
|
||||||
|
for (int i = 1; i < AUDIO_BUFS; ++i) {
|
||||||
|
const int next_buf = (i2s_context.cur_playing + i) % AUDIO_BUFS;
|
||||||
|
if (!i2s_context.has_data[next_buf]) {
|
||||||
|
ret = i2s_context.dma_buf[next_buf];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
restore_interrupts(flags);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void __time_critical_func(i2s_commit_buf)(uint32_t *buf)
|
||||||
|
{
|
||||||
|
const long flags = save_and_disable_interrupts();
|
||||||
|
for (int i = 1; i < AUDIO_BUFS; ++i) {
|
||||||
|
const int next_buf = (i2s_context.cur_playing + i) % AUDIO_BUFS;
|
||||||
|
if (i2s_context.dma_buf[next_buf] == buf) {
|
||||||
|
i2s_context.has_data[next_buf] = true;
|
||||||
|
if (i == AUDIO_BUFS - 1) {
|
||||||
|
i2s_context.playback_active = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
restore_interrupts(flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
void i2s_stop(void)
|
||||||
|
{
|
||||||
|
if (!i2s_context.playback_active)
|
||||||
|
return;
|
||||||
|
bool have_data = false;
|
||||||
|
do {
|
||||||
|
const long flags = save_and_disable_interrupts();
|
||||||
|
const int next_buf = (i2s_context.cur_playing + 1) % AUDIO_BUFS;
|
||||||
|
have_data = i2s_context.has_data[next_buf];
|
||||||
|
restore_interrupts(flags);
|
||||||
|
if (have_data)
|
||||||
|
__wfi();
|
||||||
|
} while (have_data);
|
||||||
|
const long flags = save_and_disable_interrupts();
|
||||||
|
i2s_context.playback_active = false;
|
||||||
|
restore_interrupts(flags);
|
||||||
|
// Workaround rp2040 E13
|
||||||
|
dma_channel_set_irq1_enabled(i2s_context.dma_ch, false);
|
||||||
|
dma_channel_abort(i2s_context.dma_ch);
|
||||||
|
dma_channel_acknowledge_irq1(i2s_context.dma_ch);
|
||||||
|
dma_channel_set_irq1_enabled(i2s_context.dma_ch, true);
|
||||||
|
pio_sm_set_enabled(audiocore_pio, i2s_context.pio_sm, false);
|
||||||
|
pio_sm_clear_fifos(audiocore_pio, i2s_context.pio_sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool i2s_init(int out_pin, int sideset_base)
|
||||||
|
{
|
||||||
|
memset(i2s_context.dma_buf, 0, sizeof(i2s_context.dma_buf[0][0]) * AUDIO_BUFS * I2S_DMA_BUF_SIZE);
|
||||||
|
if (!pio_can_add_program(audiocore_pio, (const pio_program_t *)&i2s_max98357_program))
|
||||||
return false;
|
return false;
|
||||||
i2s_context.pio_sm = pio_claim_unused_sm(audiocore_pio, false);
|
i2s_context.pio_sm = pio_claim_unused_sm(audiocore_pio, false);
|
||||||
|
i2s_context.out_pin = out_pin;
|
||||||
|
i2s_context.sideset_base = sideset_base;
|
||||||
if (i2s_context.pio_sm == -1)
|
if (i2s_context.pio_sm == -1)
|
||||||
return false;
|
return false;
|
||||||
i2s_context.pio_program_offset = pio_add_program(audiocore_pio, &i2s_max98357_program);
|
i2s_context.pio_program_offset = pio_add_program(audiocore_pio, (const pio_program_t *)&i2s_max98357_program);
|
||||||
i2s_max98357_program_init(audiocore_pio, i2s_context.pio_sm, i2s_context.pio_program_offset, out_pin, sideset_base,
|
|
||||||
samplerate);
|
|
||||||
|
|
||||||
i2s_context.dma_ch = dma_claim_unused_channel(false);
|
i2s_context.dma_ch = dma_claim_unused_channel(false);
|
||||||
if (i2s_context.dma_ch == -1)
|
if (i2s_context.dma_ch == -1)
|
||||||
goto out_dma_claim;
|
goto out_dma_claim;
|
||||||
|
i2s_context.playback_active = false;
|
||||||
setup_dma_config();
|
setup_dma_config();
|
||||||
irq_set_exclusive_handler(DMA_IRQ_1, &dma_isr);
|
irq_add_shared_handler(DMA_IRQ_1, &dma_isr, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY);
|
||||||
dma_channel_set_irq1_enabled(i2s_context.dma_ch, true);
|
dma_channel_set_irq1_enabled(i2s_context.dma_ch, true);
|
||||||
irq_set_enabled(DMA_IRQ_1, true);
|
irq_set_enabled(DMA_IRQ_1, true);
|
||||||
dma_channel_configure(i2s_context.dma_ch, &i2s_context.dma_config, &audiocore_pio->txf[i2s_context.pio_sm],
|
|
||||||
i2s_context.dma_buf, I2S_DMA_BUF_SIZE, true);
|
|
||||||
pio_sm_set_enabled(audiocore_pio, i2s_context.pio_sm, true);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
out_dma_claim:
|
out_dma_claim:
|
||||||
pio_remove_program(audiocore_pio, &i2s_max98357_program, i2s_context.pio_program_offset);
|
pio_remove_program(audiocore_pio, (const pio_program_t *)&i2s_max98357_program, i2s_context.pio_program_offset);
|
||||||
pio_sm_unclaim(audiocore_pio, i2s_context.pio_sm);
|
pio_sm_unclaim(audiocore_pio, i2s_context.pio_sm);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void i2s_play(int samplerate)
|
||||||
|
{
|
||||||
|
i2s_context.playback_active = false;
|
||||||
|
i2s_context.cur_playing = 0;
|
||||||
|
memset(i2s_context.dma_buf, 0, sizeof(i2s_context.dma_buf[0][0]) * AUDIO_BUFS * I2S_DMA_BUF_SIZE);
|
||||||
|
i2s_max98357_program_init(audiocore_pio, i2s_context.pio_sm, i2s_context.pio_program_offset, i2s_context.out_pin,
|
||||||
|
i2s_context.sideset_base, samplerate);
|
||||||
|
dma_channel_configure(i2s_context.dma_ch, &i2s_context.dma_config, &audiocore_pio->txf[i2s_context.pio_sm],
|
||||||
|
i2s_context.dma_buf, I2S_DMA_BUF_SIZE, true);
|
||||||
|
pio_sm_set_enabled(audiocore_pio, i2s_context.pio_sm, true);
|
||||||
|
}
|
||||||
|
|
||||||
void i2s_deinit(void)
|
void i2s_deinit(void)
|
||||||
{
|
{
|
||||||
pio_sm_set_enabled(audiocore_pio, i2s_context.pio_sm, false);
|
pio_sm_set_enabled(audiocore_pio, i2s_context.pio_sm, false);
|
||||||
dma_channel_set_irq1_enabled(i2s_context.dma_ch, false);
|
dma_channel_set_irq1_enabled(i2s_context.dma_ch, false);
|
||||||
|
dma_channel_abort(i2s_context.dma_ch);
|
||||||
|
irq_remove_handler(DMA_IRQ_1, &dma_isr);
|
||||||
dma_channel_unclaim(i2s_context.dma_ch);
|
dma_channel_unclaim(i2s_context.dma_ch);
|
||||||
pio_remove_program(audiocore_pio, &i2s_max98357_program, i2s_context.pio_program_offset);
|
pio_remove_program(audiocore_pio, &i2s_max98357_program, i2s_context.pio_program_offset);
|
||||||
pio_sm_unclaim(audiocore_pio, i2s_context.pio_sm);
|
pio_sm_unclaim(audiocore_pio, i2s_context.pio_sm);
|
||||||
|
|||||||
@@ -1,10 +1,18 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
// Copyright (c) 2024 Matthias Blankertz <matthias@blankertz.org>
|
// Copyright (c) 2024-2025 Matthias Blankertz <matthias@blankertz.org>
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
bool i2s_init(int out_pin, int sideset_base, int samplerate);
|
#define I2S_DMA_BUF_SIZE (1152)
|
||||||
|
|
||||||
|
bool i2s_init(int out_pin, int sideset_base);
|
||||||
void i2s_deinit(void);
|
void i2s_deinit(void);
|
||||||
|
|
||||||
|
void i2s_play(int samplerate);
|
||||||
|
void i2s_stop(void);
|
||||||
|
|
||||||
|
uint32_t *i2s_next_buf(void);
|
||||||
|
void i2s_commit_buf(uint32_t *buf);
|
||||||
|
|||||||
@@ -8,8 +8,9 @@ pico_generate_pio_header(usermod_audiocore ${CMAKE_CURRENT_LIST_DIR}/i2s_max9835
|
|||||||
|
|
||||||
target_sources(usermod_audiocore INTERFACE
|
target_sources(usermod_audiocore INTERFACE
|
||||||
${CMAKE_CURRENT_LIST_DIR}/audiocore.c
|
${CMAKE_CURRENT_LIST_DIR}/audiocore.c
|
||||||
${CMAKE_CURRENT_LIST_DIR}/module.c
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/i2s.c
|
${CMAKE_CURRENT_LIST_DIR}/i2s.c
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/module.c
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/mp3.c
|
||||||
${CMAKE_CURRENT_BINARY_DIR}/i2s_max98357.pio.h
|
${CMAKE_CURRENT_BINARY_DIR}/i2s_max98357.pio.h
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
// Copyright (c) 2024 Matthias Blankertz <matthias@blankertz.org>
|
// Copyright (c) 2024-2025 Matthias Blankertz <matthias@blankertz.org>
|
||||||
|
|
||||||
#include "audiocore.h"
|
#include "audiocore.h"
|
||||||
|
|
||||||
@@ -16,52 +16,54 @@
|
|||||||
struct audiocore_shared_context shared_context = {.lock = NULL};
|
struct audiocore_shared_context shared_context = {.lock = NULL};
|
||||||
static bool initialized = false;
|
static bool initialized = false;
|
||||||
|
|
||||||
struct audiocore_Context_obj {
|
struct audiocore_obj {
|
||||||
mp_obj_base_t base;
|
mp_obj_base_t base;
|
||||||
};
|
};
|
||||||
|
const mp_obj_type_t audiocore_type;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* audiocore.Context.deinit(self)
|
* audiocore.Context.deinit(self)
|
||||||
*
|
*
|
||||||
* Deinitializes the audiocore.Context and shuts down processing on core1
|
* Deinitializes the audiocore.Context and shuts down processing on core1
|
||||||
*/
|
*/
|
||||||
static mp_obj_t audiocore_Context_deinit(mp_obj_t self_in)
|
static mp_obj_t audiocore_deinit(mp_obj_t self_in)
|
||||||
{
|
{
|
||||||
struct audiocore_Context_obj *self = MP_OBJ_TO_PTR(self_in);
|
struct audiocore_obj *self = MP_OBJ_TO_PTR(self_in);
|
||||||
multicore_fifo_push_blocking(AUDIOCORE_CMD_SHUTDOWN);
|
multicore_fifo_push_blocking(AUDIOCORE_CMD_SHUTDOWN);
|
||||||
multicore_fifo_pop_blocking();
|
multicore_fifo_pop_blocking();
|
||||||
(void)self;
|
(void)self;
|
||||||
initialized = false;
|
initialized = false;
|
||||||
return mp_const_none;
|
return mp_const_none;
|
||||||
}
|
}
|
||||||
static MP_DEFINE_CONST_FUN_OBJ_1(audiocore_Context_deinit_obj, audiocore_Context_deinit);
|
static MP_DEFINE_CONST_FUN_OBJ_1(audiocore_deinit_obj, audiocore_deinit);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* (copied, buf_space, undderuns) = audiocore.Context.put(self, buffer)
|
* (copied, buf_space, undderuns) = audiocore.put(self, buffer)
|
||||||
*
|
*
|
||||||
* Copies as many integers as possible from the buffer to the audiocore ring buffer for playback.
|
* Copies as many bytes as possible from the buffer to the audiocore ring buffer for playback.
|
||||||
* 'buffer' must be any object supporting the buffer protocol with data in unsigned int (array typecode 'I')
|
* 'buffer' must be any object supporting the buffer protocol with data in byte (array typecode 'b' or 'B')
|
||||||
* format. The actual number of elements copied is returned in 'copied', the remaining free ring buffer space
|
* format. The actual number of elements copied is returned in 'copied', the remaining free buffer space
|
||||||
* is in 'buf_space', and the total number of buffer underruns since initialization of the audiocore Context
|
* is in 'buf_space', and the total number of audio underruns since initialization of the audiocore Context
|
||||||
* is in 'underruns'.
|
* is in 'underruns'.
|
||||||
*/
|
*/
|
||||||
static mp_obj_t audiocore_Context_put(mp_obj_t self_in, mp_obj_t buffer)
|
static mp_obj_t audiocore_put(mp_obj_t self_in, mp_obj_t buffer)
|
||||||
{
|
{
|
||||||
struct audiocore_Context_obj *self = MP_OBJ_TO_PTR(self_in);
|
struct audiocore_obj *self = MP_OBJ_TO_PTR(self_in);
|
||||||
(void)self;
|
(void)self;
|
||||||
mp_buffer_info_t bufinfo;
|
mp_buffer_info_t bufinfo;
|
||||||
if (!mp_get_buffer(buffer, &bufinfo, MP_BUFFER_READ))
|
if (!mp_get_buffer(buffer, &bufinfo, MP_BUFFER_READ))
|
||||||
mp_raise_ValueError("not a read buffer");
|
mp_raise_ValueError("not a read buffer");
|
||||||
if (bufinfo.typecode != 'I')
|
if (bufinfo.typecode != 'b' && bufinfo.typecode != 'B')
|
||||||
mp_raise_ValueError("unsupported buffer type");
|
mp_raise_ValueError("unsupported buffer type");
|
||||||
unsigned to_copy = bufinfo.len / 4;
|
unsigned to_copy = bufinfo.len;
|
||||||
|
|
||||||
const uint32_t flags = spin_lock_blocking(shared_context.lock);
|
const uint32_t flags = spin_lock_blocking(shared_context.lock);
|
||||||
const unsigned buf_space = audiocore_get_audio_buffer_space();
|
const unsigned buf_space = audiocore_get_buffer_space();
|
||||||
if (to_copy > buf_space)
|
if (to_copy > buf_space)
|
||||||
to_copy = buf_space;
|
to_copy = buf_space;
|
||||||
if (to_copy > 0) {
|
if (to_copy > 0) {
|
||||||
audiocore_audio_buffer_put(bufinfo.buf, to_copy);
|
audiocore_buffer_put(bufinfo.buf, to_copy);
|
||||||
|
__sev();
|
||||||
}
|
}
|
||||||
const unsigned underruns = shared_context.underruns;
|
const unsigned underruns = shared_context.underruns;
|
||||||
spin_unlock(shared_context.lock, flags);
|
spin_unlock(shared_context.lock, flags);
|
||||||
@@ -73,33 +75,68 @@ static mp_obj_t audiocore_Context_put(mp_obj_t self_in, mp_obj_t buffer)
|
|||||||
};
|
};
|
||||||
return mp_obj_new_tuple(3, items);
|
return mp_obj_new_tuple(3, items);
|
||||||
}
|
}
|
||||||
static MP_DEFINE_CONST_FUN_OBJ_2(audiocore_Context_put_obj, audiocore_Context_put);
|
static MP_DEFINE_CONST_FUN_OBJ_2(audiocore_put_obj, audiocore_put);
|
||||||
|
|
||||||
static uint32_t __scratch_y("core1_stack") core1_stack[1024];
|
|
||||||
static const mp_rom_map_elem_t audiocore_Context_locals_dict_table[] = {
|
|
||||||
{MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&audiocore_Context_deinit_obj)},
|
|
||||||
{MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&audiocore_Context_deinit_obj)},
|
|
||||||
{MP_ROM_QSTR(MP_QSTR_put), MP_ROM_PTR(&audiocore_Context_put_obj)},
|
|
||||||
};
|
|
||||||
static MP_DEFINE_CONST_DICT(audiocore_Context_locals_dict, audiocore_Context_locals_dict_table);
|
|
||||||
const mp_obj_type_t audiocore_Context_type;
|
|
||||||
|
|
||||||
MP_DEFINE_CONST_OBJ_TYPE(audiocore_Context_type, MP_QSTR_Context, MP_TYPE_FLAG_NONE, locals_dict,
|
|
||||||
&audiocore_Context_locals_dict);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* audiocore.init(pin, sideset, samplerate)
|
* audiocore.flush(self)
|
||||||
* Returns: audiocore.Context
|
|
||||||
*
|
*
|
||||||
* Initialize the audiocore module, starting the audio processing on core1 and initializing the I2S driver.
|
* Tells the audiocore to stop playback as soon as all MP3 frames still in the buffer have been decoded.
|
||||||
|
* This function blocks until playback has ended.
|
||||||
|
*/
|
||||||
|
static mp_obj_t audiocore_flush(mp_obj_t self_in)
|
||||||
|
{
|
||||||
|
struct audiocore_obj *self = MP_OBJ_TO_PTR(self_in);
|
||||||
|
(void)self;
|
||||||
|
multicore_fifo_push_blocking(AUDIOCORE_CMD_FLUSH);
|
||||||
|
__sev();
|
||||||
|
multicore_fifo_pop_blocking();
|
||||||
|
return mp_const_none;
|
||||||
|
}
|
||||||
|
static MP_DEFINE_CONST_FUN_OBJ_1(audiocore_flush_obj, audiocore_flush);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* audiocore.set_volume(self, volume)
|
||||||
|
*
|
||||||
|
* Set a volume between 0 (silence) and 255 (PCM values are output as they are decoded from the mp3 stream). Bewteen
|
||||||
|
* 0 and 255 the volume is scaled linearly.
|
||||||
|
* Raises a ValueError of the volume is not in the range [0, 255].
|
||||||
|
* Raises an OSError if the audiocore rejects the volume setting.
|
||||||
|
*/
|
||||||
|
static mp_obj_t audiocore_set_volume(mp_obj_t self_in, mp_obj_t volume_obj)
|
||||||
|
{
|
||||||
|
struct audiocore_obj *self = MP_OBJ_TO_PTR(self_in);
|
||||||
|
(void)self;
|
||||||
|
const int volume = mp_obj_get_int(volume_obj);
|
||||||
|
if (volume < 0 || volume > 255)
|
||||||
|
mp_raise_ValueError("volume out of range");
|
||||||
|
multicore_fifo_push_blocking(AUDIOCORE_CMD_SET_VOLUME);
|
||||||
|
multicore_fifo_push_blocking(AUDIOCORE_MAX_VOLUME * volume / 255);
|
||||||
|
__sev();
|
||||||
|
const uint32_t ret = multicore_fifo_pop_blocking();
|
||||||
|
if (ret != 0)
|
||||||
|
mp_raise_OSError(MP_EINVAL);
|
||||||
|
return mp_const_none;
|
||||||
|
}
|
||||||
|
static MP_DEFINE_CONST_FUN_OBJ_2(audiocore_set_volume_obj, audiocore_set_volume);
|
||||||
|
|
||||||
|
static uint32_t __scratch_y("core1_stack") core1_stack[1024];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Audiocore(pin, sideset)
|
||||||
|
*
|
||||||
|
* Initialize the audiocore module, starting the audio processing on core1.
|
||||||
* The pin parameter specifies the I2S data pin, the sideset parameter specifies the I2S LRCLK pin, the
|
* The pin parameter specifies the I2S data pin, the sideset parameter specifies the I2S LRCLK pin, the
|
||||||
* BCLK must be on pin sideset+1.
|
* BCLK must be on pin sideset+1.
|
||||||
* Samplerate should be 44100 or 48000.
|
* Only a single Audiocore may exist at a time.
|
||||||
* Only a single audiocore.Context may exist at a time.
|
* Constructs an Audiocore object on success, raises a OSError or ValueError on failure.
|
||||||
* Return a audiocore.Context object on success, raises a OSError or ValueError on failure.
|
|
||||||
*/
|
*/
|
||||||
static mp_obj_t audiocore_init(mp_obj_t pin_obj, mp_obj_t sideset_obj, mp_obj_t samplerate_obj)
|
static void audiocore_init(struct audiocore_obj *obj, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args)
|
||||||
{
|
{
|
||||||
|
enum { ARG_pin, ARG_sideset };
|
||||||
|
static const mp_arg_t allowed_args[] = {
|
||||||
|
{MP_QSTR_pin, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL}},
|
||||||
|
{MP_QSTR_sideset, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL}},
|
||||||
|
};
|
||||||
if (initialized)
|
if (initialized)
|
||||||
mp_raise_OSError(MP_EBUSY);
|
mp_raise_OSError(MP_EBUSY);
|
||||||
if (!shared_context.lock) {
|
if (!shared_context.lock) {
|
||||||
@@ -109,21 +146,15 @@ static mp_obj_t audiocore_init(mp_obj_t pin_obj, mp_obj_t sideset_obj, mp_obj_t
|
|||||||
mp_raise_OSError(MP_ENOMEM);
|
mp_raise_OSError(MP_ENOMEM);
|
||||||
shared_context.lock = spin_lock_init(lock);
|
shared_context.lock = spin_lock_init(lock);
|
||||||
}
|
}
|
||||||
shared_context.audio_buffer_write = shared_context.audio_buffer_read = shared_context.underruns = 0;
|
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
|
||||||
memset(shared_context.audio_buffer, 0, AUDIO_BUFFER_SIZE * 4);
|
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
|
||||||
|
const mp_hal_pin_obj_t pin = mp_hal_get_pin_obj(args[ARG_pin].u_obj);
|
||||||
|
const mp_hal_pin_obj_t sideset_pin = mp_hal_get_pin_obj(args[ARG_sideset].u_obj);
|
||||||
|
shared_context.mp3_buffer_write = shared_context.mp3_buffer_read = shared_context.underruns = 0;
|
||||||
|
memset(shared_context.mp3_buffer, 0, MP3_BUFFER_PREAREA + MP3_BUFFER_SIZE);
|
||||||
multicore_reset_core1();
|
multicore_reset_core1();
|
||||||
struct audiocore_Context_obj *context = m_malloc_with_finaliser(sizeof(struct audiocore_Context_obj));
|
|
||||||
context->base.type = &audiocore_Context_type;
|
|
||||||
mp_hal_pin_obj_t pin = pin_obj == MP_OBJ_NULL ? -1 : mp_hal_get_pin_obj(pin_obj);
|
|
||||||
if (pin == -1)
|
|
||||||
mp_raise_ValueError("Invalid out pin");
|
|
||||||
mp_hal_pin_obj_t sideset_pin = sideset_obj == MP_OBJ_NULL ? -1 : mp_hal_get_pin_obj(sideset_obj);
|
|
||||||
if (sideset_pin == -1)
|
|
||||||
mp_raise_ValueError("Invalid sideset base pin");
|
|
||||||
int samplerate = mp_obj_get_int(samplerate_obj);
|
|
||||||
shared_context.out_pin = pin;
|
shared_context.out_pin = pin;
|
||||||
shared_context.sideset_base = sideset_pin;
|
shared_context.sideset_base = sideset_pin;
|
||||||
shared_context.samplerate = samplerate;
|
|
||||||
initialized = true;
|
initialized = true;
|
||||||
multicore_launch_core1_with_stack(&core1_main, core1_stack, sizeof(core1_stack));
|
multicore_launch_core1_with_stack(&core1_main, core1_stack, sizeof(core1_stack));
|
||||||
uint32_t result = multicore_fifo_pop_blocking();
|
uint32_t result = multicore_fifo_pop_blocking();
|
||||||
@@ -132,15 +163,32 @@ static mp_obj_t audiocore_init(mp_obj_t pin_obj, mp_obj_t sideset_obj, mp_obj_t
|
|||||||
initialized = false;
|
initialized = false;
|
||||||
mp_raise_OSError(result);
|
mp_raise_OSError(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
return MP_OBJ_FROM_PTR(context);
|
|
||||||
}
|
}
|
||||||
static MP_DEFINE_CONST_FUN_OBJ_3(audiocore_init_obj, audiocore_init);
|
|
||||||
|
static mp_obj_t audiocore_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args)
|
||||||
|
{
|
||||||
|
struct audiocore_obj *audiocore = mp_obj_malloc(struct audiocore_obj, &audiocore_type);
|
||||||
|
mp_map_t kw_args;
|
||||||
|
mp_map_init_fixed_table(&kw_args, n_kw, args + n_args);
|
||||||
|
audiocore_init(audiocore, n_args, args, &kw_args);
|
||||||
|
return MP_OBJ_FROM_PTR(audiocore);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const mp_rom_map_elem_t audiocore_locals_dict_table[] = {
|
||||||
|
{MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&audiocore_deinit_obj)},
|
||||||
|
{MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&audiocore_deinit_obj)},
|
||||||
|
{MP_ROM_QSTR(MP_QSTR_put), MP_ROM_PTR(&audiocore_put_obj)},
|
||||||
|
{MP_ROM_QSTR(MP_QSTR_flush), MP_ROM_PTR(&audiocore_flush_obj)},
|
||||||
|
{MP_ROM_QSTR(MP_QSTR_set_volume), MP_ROM_PTR(&audiocore_set_volume_obj)},
|
||||||
|
};
|
||||||
|
static MP_DEFINE_CONST_DICT(audiocore_locals_dict, audiocore_locals_dict_table);
|
||||||
|
|
||||||
|
MP_DEFINE_CONST_OBJ_TYPE(audiocore_type, MP_QSTR_Audiocore, MP_TYPE_FLAG_NONE, locals_dict, &audiocore_locals_dict,
|
||||||
|
make_new, &audiocore_make_new);
|
||||||
|
|
||||||
static const mp_rom_map_elem_t audiocore_module_globals_table[] = {
|
static const mp_rom_map_elem_t audiocore_module_globals_table[] = {
|
||||||
{MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_audiocore)},
|
{MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_audiocore)},
|
||||||
{MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&audiocore_init_obj)},
|
{MP_ROM_QSTR(MP_QSTR_Audiocore), MP_ROM_PTR(&audiocore_type)},
|
||||||
{MP_ROM_QSTR(MP_QSTR_Context), MP_ROM_PTR(&audiocore_Context_type)},
|
|
||||||
};
|
};
|
||||||
static MP_DEFINE_CONST_DICT(audiocore_module_globals, audiocore_module_globals_table);
|
static MP_DEFINE_CONST_DICT(audiocore_module_globals, audiocore_module_globals_table);
|
||||||
|
|
||||||
|
|||||||
146
software/src/audiocore/mp3.c
Normal file
146
software/src/audiocore/mp3.c
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
// Copyright (c) 2025 Matthias Blankertz <matthias@blankertz.org>
|
||||||
|
|
||||||
|
#include "mp3.h"
|
||||||
|
#include "audiocore.h"
|
||||||
|
#include "mp3dec.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
static HMP3Decoder mp3dec;
|
||||||
|
static bool synced = false;
|
||||||
|
static size_t prearea_fill = 0;
|
||||||
|
#ifdef MP3_DEBUG
|
||||||
|
static first = true;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool mp3_init(void)
|
||||||
|
{
|
||||||
|
mp3dec = MP3InitDecoder();
|
||||||
|
return (mp3dec != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void mp3_deinit(void)
|
||||||
|
{
|
||||||
|
if (mp3dec) {
|
||||||
|
MP3FreeDecoder(mp3dec);
|
||||||
|
mp3dec = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t __time_critical_func(mp3_get_continuous_data)(unsigned char **readptr)
|
||||||
|
{
|
||||||
|
size_t bytes_left;
|
||||||
|
const uint32_t flags = spin_lock_blocking(shared_context.lock);
|
||||||
|
/* Check if remaining bytes at end of buffer should be moved to prearea */
|
||||||
|
const unsigned end_bytes = MP3_BUFFER_SIZE - shared_context.mp3_buffer_read;
|
||||||
|
if (shared_context.mp3_buffer_write < shared_context.mp3_buffer_read && end_bytes <= MP3_BUFFER_PREAREA) {
|
||||||
|
assert(prearea_fill == 0);
|
||||||
|
memcpy(shared_context.mp3_buffer + MP3_BUFFER_PREAREA - end_bytes,
|
||||||
|
shared_context.mp3_buffer + MP3_BUFFER_PREAREA + shared_context.mp3_buffer_read, end_bytes);
|
||||||
|
prearea_fill = end_bytes;
|
||||||
|
shared_context.mp3_buffer_read = 0;
|
||||||
|
}
|
||||||
|
/* Get start and length of valid continuous data */
|
||||||
|
if (shared_context.mp3_buffer_read == 0) {
|
||||||
|
*readptr = (unsigned char *)(shared_context.mp3_buffer + MP3_BUFFER_PREAREA - prearea_fill);
|
||||||
|
bytes_left = prearea_fill + shared_context.mp3_buffer_write;
|
||||||
|
} else if (shared_context.mp3_buffer_write < shared_context.mp3_buffer_read) {
|
||||||
|
*readptr = (unsigned char *)(shared_context.mp3_buffer + MP3_BUFFER_PREAREA + shared_context.mp3_buffer_read);
|
||||||
|
bytes_left = end_bytes;
|
||||||
|
} else {
|
||||||
|
*readptr = (unsigned char *)(shared_context.mp3_buffer + MP3_BUFFER_PREAREA + shared_context.mp3_buffer_read);
|
||||||
|
bytes_left = shared_context.mp3_buffer_write - shared_context.mp3_buffer_read;
|
||||||
|
}
|
||||||
|
spin_unlock(shared_context.lock, flags);
|
||||||
|
return bytes_left;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __time_critical_func(mp3_consume_data)(size_t bytes_used)
|
||||||
|
{
|
||||||
|
if (prearea_fill >= bytes_used) {
|
||||||
|
prearea_fill -= bytes_used;
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
bytes_used -= prearea_fill;
|
||||||
|
prearea_fill = 0;
|
||||||
|
}
|
||||||
|
const uint32_t flags = spin_lock_blocking(shared_context.lock);
|
||||||
|
shared_context.mp3_buffer_read += bytes_used;
|
||||||
|
shared_context.mp3_buffer_read %= MP3_BUFFER_SIZE;
|
||||||
|
spin_unlock(shared_context.lock, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool __time_critical_func(mp3_decode)(uint32_t pcm_buf[static 1152], unsigned *samplerate)
|
||||||
|
{
|
||||||
|
unsigned char *readptr;
|
||||||
|
size_t bytes_avail = mp3_get_continuous_data(&readptr);
|
||||||
|
if (!bytes_avail)
|
||||||
|
return false;
|
||||||
|
if (!synced) {
|
||||||
|
const int ofs = MP3FindSyncWord((unsigned char *)readptr, bytes_avail);
|
||||||
|
if (ofs == -1) {
|
||||||
|
#ifdef MP3_DEBUG
|
||||||
|
printf("MP3 sync word not found\n");
|
||||||
|
#endif
|
||||||
|
mp3_consume_data(bytes_avail);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
readptr += ofs;
|
||||||
|
bytes_avail -= ofs;
|
||||||
|
mp3_consume_data(ofs);
|
||||||
|
#ifdef MP3_DEBUG
|
||||||
|
printf("MP3 sync word found after %zu bytes\n", ofs);
|
||||||
|
#endif
|
||||||
|
synced = true;
|
||||||
|
}
|
||||||
|
if (!bytes_avail)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Decode one frame
|
||||||
|
int int_bytes_avail = bytes_avail;
|
||||||
|
const int status = MP3Decode(mp3dec, &readptr, &int_bytes_avail, (short *)pcm_buf, 0);
|
||||||
|
if (status) {
|
||||||
|
if (status == ERR_MP3_INDATA_UNDERFLOW) {
|
||||||
|
return false;
|
||||||
|
} else /*if (status== ERR_MP3_MAINDATA_UNDERFLOW)*/ {
|
||||||
|
mp3_consume_data(1);
|
||||||
|
synced = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mp3_consume_data(bytes_avail - int_bytes_avail);
|
||||||
|
MP3FrameInfo info;
|
||||||
|
MP3GetLastFrameInfo(mp3dec, &info);
|
||||||
|
#ifdef MP3_DEBUG
|
||||||
|
if (first) {
|
||||||
|
printf("bitrate %d, nChans %d, samprate %d, bitsPerSample %d, outputSamps %d, layer %d, version %d\n",
|
||||||
|
info.bitrate, info.nChans, info.samprate, info.bitsPerSample, info.outputSamps, info.layer,
|
||||||
|
info.version);
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
*samplerate = info.samprate;
|
||||||
|
|
||||||
|
if (info.outputSamps != 2304) {
|
||||||
|
#ifdef MP3_DEBUG
|
||||||
|
printf("Unexpected number of output samples: %d\n", info.outputSamps);
|
||||||
|
#endif
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void mp3_reset(void)
|
||||||
|
{
|
||||||
|
synced = false;
|
||||||
|
prearea_fill = 0;
|
||||||
|
#ifdef MP3_DEBUG
|
||||||
|
first = true;
|
||||||
|
#endif
|
||||||
|
const uint32_t flags = spin_lock_blocking(shared_context.lock);
|
||||||
|
shared_context.mp3_buffer_read = shared_context.mp3_buffer_write = 0;
|
||||||
|
spin_unlock(shared_context.lock, flags);
|
||||||
|
}
|
||||||
14
software/src/audiocore/mp3.h
Normal file
14
software/src/audiocore/mp3.h
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
// Copyright (c) 2025 Matthias Blankertz <matthias@blankertz.org>
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
bool mp3_init(void);
|
||||||
|
void mp3_deinit(void);
|
||||||
|
|
||||||
|
bool mp3_decode(uint32_t pcm_buf[static 1152], unsigned *samplerate);
|
||||||
|
|
||||||
|
void mp3_reset(void);
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
// Copyright (c) 2024 Matthias Blankertz <matthias@blankertz.org>
|
// Copyright (c) 2024-2025 Matthias Blankertz <matthias@blankertz.org>
|
||||||
|
|
||||||
#include "audiocore.h"
|
#include "audiocore.h"
|
||||||
#include "i2s.h"
|
#include "i2s.h"
|
||||||
@@ -15,7 +15,7 @@ static unsigned multicore_fifo_push_last;
|
|||||||
|
|
||||||
static unsigned (*multicore_fifo_pop_blocking_cb)(void);
|
static unsigned (*multicore_fifo_pop_blocking_cb)(void);
|
||||||
|
|
||||||
bool i2s_init(int out_pin, int sideset_base, int samplerate)
|
bool i2s_init(int out_pin, int sideset_base)
|
||||||
{
|
{
|
||||||
TEST_ASSERT_FALSE(i2s_initialized);
|
TEST_ASSERT_FALSE(i2s_initialized);
|
||||||
if (i2s_init_return)
|
if (i2s_init_return)
|
||||||
@@ -23,12 +23,28 @@ bool i2s_init(int out_pin, int sideset_base, int samplerate)
|
|||||||
return i2s_init_return;
|
return i2s_init_return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t *i2s_next_buf(void) { return NULL; }
|
||||||
|
|
||||||
|
void i2s_commit_buf(uint32_t *buf) {}
|
||||||
|
|
||||||
|
void i2s_play(int samplerate) {}
|
||||||
|
|
||||||
|
void i2s_stop(void) {}
|
||||||
|
|
||||||
void i2s_deinit(void)
|
void i2s_deinit(void)
|
||||||
{
|
{
|
||||||
TEST_ASSERT_TRUE(i2s_initialized);
|
TEST_ASSERT_TRUE(i2s_initialized);
|
||||||
i2s_initialized = false;
|
i2s_initialized = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool mp3_init(void) { return true; }
|
||||||
|
|
||||||
|
void mp3_deinit(void) {}
|
||||||
|
|
||||||
|
bool mp3_decode(uint32_t pcm_buf[static 1152], unsigned *samplerate) { return false; }
|
||||||
|
|
||||||
|
void mp3_reset(void) {}
|
||||||
|
|
||||||
void multicore_fifo_push_blocking(unsigned val) { multicore_fifo_push_last = val; }
|
void multicore_fifo_push_blocking(unsigned val) { multicore_fifo_push_last = val; }
|
||||||
|
|
||||||
unsigned multicore_fifo_pop_blocking(void)
|
unsigned multicore_fifo_pop_blocking(void)
|
||||||
@@ -38,6 +54,8 @@ unsigned multicore_fifo_pop_blocking(void)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool multicore_fifo_rvalid(void) { return multicore_fifo_pop_blocking_cb; }
|
||||||
|
|
||||||
void test_audiocore_handles_i2sinit_failure(void)
|
void test_audiocore_handles_i2sinit_failure(void)
|
||||||
{
|
{
|
||||||
i2s_init_return = false;
|
i2s_init_return = false;
|
||||||
@@ -65,78 +83,78 @@ void test_audiocore_init_deinit(void)
|
|||||||
void test_audiocore_buffer_space(void)
|
void test_audiocore_buffer_space(void)
|
||||||
{
|
{
|
||||||
// empty ring buffer
|
// empty ring buffer
|
||||||
shared_context.audio_buffer_read = shared_context.audio_buffer_write = 0;
|
shared_context.mp3_buffer_read = shared_context.mp3_buffer_write = 0;
|
||||||
TEST_ASSERT_EQUAL(AUDIO_BUFFER_SIZE - 1, audiocore_get_audio_buffer_space());
|
TEST_ASSERT_EQUAL(MP3_BUFFER_SIZE - 1, audiocore_get_buffer_space());
|
||||||
|
|
||||||
shared_context.audio_buffer_read = shared_context.audio_buffer_write = 23;
|
shared_context.mp3_buffer_read = shared_context.mp3_buffer_write = 23;
|
||||||
TEST_ASSERT_EQUAL(AUDIO_BUFFER_SIZE - 1, audiocore_get_audio_buffer_space());
|
TEST_ASSERT_EQUAL(MP3_BUFFER_SIZE - 1, audiocore_get_buffer_space());
|
||||||
|
|
||||||
shared_context.audio_buffer_read = shared_context.audio_buffer_write = AUDIO_BUFFER_SIZE - 1;
|
shared_context.mp3_buffer_read = shared_context.mp3_buffer_write = MP3_BUFFER_SIZE - 1;
|
||||||
TEST_ASSERT_EQUAL(AUDIO_BUFFER_SIZE - 1, audiocore_get_audio_buffer_space());
|
TEST_ASSERT_EQUAL(MP3_BUFFER_SIZE - 1, audiocore_get_buffer_space());
|
||||||
|
|
||||||
// full ring buffer
|
// full ring buffer
|
||||||
shared_context.audio_buffer_write = 0;
|
shared_context.mp3_buffer_write = 0;
|
||||||
shared_context.audio_buffer_read = 1;
|
shared_context.mp3_buffer_read = 1;
|
||||||
TEST_ASSERT_EQUAL(0, audiocore_get_audio_buffer_space());
|
TEST_ASSERT_EQUAL(0, audiocore_get_buffer_space());
|
||||||
|
|
||||||
shared_context.audio_buffer_write = AUDIO_BUFFER_SIZE - 1;
|
shared_context.mp3_buffer_write = MP3_BUFFER_SIZE - 1;
|
||||||
shared_context.audio_buffer_read = 0;
|
shared_context.mp3_buffer_read = 0;
|
||||||
TEST_ASSERT_EQUAL(0, audiocore_get_audio_buffer_space());
|
TEST_ASSERT_EQUAL(0, audiocore_get_buffer_space());
|
||||||
|
|
||||||
// write > read
|
// write > read
|
||||||
shared_context.audio_buffer_write = 10;
|
shared_context.mp3_buffer_write = 10;
|
||||||
shared_context.audio_buffer_read = 0;
|
shared_context.mp3_buffer_read = 0;
|
||||||
TEST_ASSERT_EQUAL(AUDIO_BUFFER_SIZE - 1 - 10, audiocore_get_audio_buffer_space());
|
TEST_ASSERT_EQUAL(MP3_BUFFER_SIZE - 1 - 10, audiocore_get_buffer_space());
|
||||||
|
|
||||||
// write < read
|
// write < read
|
||||||
shared_context.audio_buffer_write = 0;
|
shared_context.mp3_buffer_write = 0;
|
||||||
shared_context.audio_buffer_read = 10;
|
shared_context.mp3_buffer_read = 10;
|
||||||
TEST_ASSERT_EQUAL(9, audiocore_get_audio_buffer_space());
|
TEST_ASSERT_EQUAL(9, audiocore_get_buffer_space());
|
||||||
}
|
}
|
||||||
|
|
||||||
void test_audiocore_buffer_avail(void)
|
void test_audiocore_buffer_avail(void)
|
||||||
{
|
{
|
||||||
// empty ring buffer
|
// empty ring buffer
|
||||||
shared_context.audio_buffer_read = shared_context.audio_buffer_write = 0;
|
shared_context.mp3_buffer_read = shared_context.mp3_buffer_write = 0;
|
||||||
TEST_ASSERT_EQUAL(0, audiocore_get_audio_buffer_avail());
|
TEST_ASSERT_EQUAL(0, audiocore_get_buffer_avail());
|
||||||
|
|
||||||
shared_context.audio_buffer_read = shared_context.audio_buffer_write = 23;
|
shared_context.mp3_buffer_read = shared_context.mp3_buffer_write = 23;
|
||||||
TEST_ASSERT_EQUAL(0, audiocore_get_audio_buffer_avail());
|
TEST_ASSERT_EQUAL(0, audiocore_get_buffer_avail());
|
||||||
|
|
||||||
shared_context.audio_buffer_read = shared_context.audio_buffer_write = AUDIO_BUFFER_SIZE - 1;
|
shared_context.mp3_buffer_read = shared_context.mp3_buffer_write = MP3_BUFFER_SIZE - 1;
|
||||||
TEST_ASSERT_EQUAL(0, audiocore_get_audio_buffer_avail());
|
TEST_ASSERT_EQUAL(0, audiocore_get_buffer_avail());
|
||||||
|
|
||||||
// full ring buffer
|
// full ring buffer
|
||||||
shared_context.audio_buffer_write = 0;
|
shared_context.mp3_buffer_write = 0;
|
||||||
shared_context.audio_buffer_read = 1;
|
shared_context.mp3_buffer_read = 1;
|
||||||
TEST_ASSERT_EQUAL(AUDIO_BUFFER_SIZE - 1, audiocore_get_audio_buffer_avail());
|
TEST_ASSERT_EQUAL(MP3_BUFFER_SIZE - 1, audiocore_get_buffer_avail());
|
||||||
|
|
||||||
shared_context.audio_buffer_write = AUDIO_BUFFER_SIZE - 1;
|
shared_context.mp3_buffer_write = MP3_BUFFER_SIZE - 1;
|
||||||
shared_context.audio_buffer_read = 0;
|
shared_context.mp3_buffer_read = 0;
|
||||||
TEST_ASSERT_EQUAL(AUDIO_BUFFER_SIZE - 1, audiocore_get_audio_buffer_avail());
|
TEST_ASSERT_EQUAL(MP3_BUFFER_SIZE - 1, audiocore_get_buffer_avail());
|
||||||
|
|
||||||
// write > read
|
// write > read
|
||||||
shared_context.audio_buffer_write = 10;
|
shared_context.mp3_buffer_write = 10;
|
||||||
shared_context.audio_buffer_read = 0;
|
shared_context.mp3_buffer_read = 0;
|
||||||
TEST_ASSERT_EQUAL(10, audiocore_get_audio_buffer_avail());
|
TEST_ASSERT_EQUAL(10, audiocore_get_buffer_avail());
|
||||||
|
|
||||||
// write < read
|
// write < read
|
||||||
shared_context.audio_buffer_write = 0;
|
shared_context.mp3_buffer_write = 0;
|
||||||
shared_context.audio_buffer_read = 10;
|
shared_context.mp3_buffer_read = 10;
|
||||||
TEST_ASSERT_EQUAL(AUDIO_BUFFER_SIZE - 10, audiocore_get_audio_buffer_avail());
|
TEST_ASSERT_EQUAL(MP3_BUFFER_SIZE - 10, audiocore_get_buffer_avail());
|
||||||
}
|
}
|
||||||
|
|
||||||
static unsigned fill_buffer_helper(void)
|
static unsigned fill_buffer_helper(void)
|
||||||
{
|
{
|
||||||
uint32_t test_data[100];
|
char test_data[100];
|
||||||
uint32_t ctr = 0;
|
uint32_t ctr = 0;
|
||||||
|
|
||||||
unsigned avail, filled = 0;
|
unsigned avail, filled = 0;
|
||||||
while ((avail = audiocore_get_audio_buffer_space()) > 0) {
|
while ((avail = audiocore_get_buffer_space()) > 0) {
|
||||||
const unsigned todo = avail > 100 ? 100 : avail;
|
const unsigned todo = avail > 100 ? 100 : avail;
|
||||||
for (unsigned i = 0; i < todo; ++i)
|
for (unsigned i = 0; i < todo; ++i)
|
||||||
test_data[i] = ctr++;
|
test_data[i] = ctr++;
|
||||||
audiocore_audio_buffer_put(test_data, todo);
|
audiocore_buffer_put(test_data, todo);
|
||||||
filled += todo;
|
filled += todo;
|
||||||
}
|
}
|
||||||
return filled;
|
return filled;
|
||||||
@@ -144,49 +162,39 @@ static unsigned fill_buffer_helper(void)
|
|||||||
|
|
||||||
void test_audiocore_buffer_put(void)
|
void test_audiocore_buffer_put(void)
|
||||||
{
|
{
|
||||||
shared_context.audio_buffer_read = shared_context.audio_buffer_write = 0;
|
shared_context.mp3_buffer_read = shared_context.mp3_buffer_write = 0;
|
||||||
|
|
||||||
TEST_ASSERT_EQUAL(AUDIO_BUFFER_SIZE - 1, fill_buffer_helper());
|
TEST_ASSERT_EQUAL(MP3_BUFFER_SIZE - 1, fill_buffer_helper());
|
||||||
for (unsigned i = 0; i < AUDIO_BUFFER_SIZE - 1; ++i) {
|
for (unsigned i = 0; i < MP3_BUFFER_SIZE - 1; ++i) {
|
||||||
TEST_ASSERT_EQUAL(i, shared_context.audio_buffer[i]);
|
TEST_ASSERT_EQUAL((char)(i & 0xFF), shared_context.mp3_buffer[MP3_BUFFER_PREAREA + i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// test wraparound fill
|
// test wraparound fill
|
||||||
shared_context.audio_buffer_read = shared_context.audio_buffer_write = AUDIO_BUFFER_SIZE - 10;
|
shared_context.mp3_buffer_read = shared_context.mp3_buffer_write = MP3_BUFFER_SIZE - 10;
|
||||||
TEST_ASSERT_EQUAL(AUDIO_BUFFER_SIZE - 1, fill_buffer_helper());
|
TEST_ASSERT_EQUAL(MP3_BUFFER_SIZE - 1, fill_buffer_helper());
|
||||||
for (unsigned i = 0; i < AUDIO_BUFFER_SIZE - 1; ++i) {
|
for (unsigned i = 0; i < MP3_BUFFER_SIZE - 1; ++i) {
|
||||||
TEST_ASSERT_EQUAL(i, shared_context.audio_buffer[(i + AUDIO_BUFFER_SIZE - 10) % AUDIO_BUFFER_SIZE]);
|
TEST_ASSERT_EQUAL(
|
||||||
|
(char)(i & 0xFF),
|
||||||
|
shared_context.mp3_buffer[MP3_BUFFER_PREAREA + ((i + MP3_BUFFER_SIZE - 10) % MP3_BUFFER_SIZE)]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static unsigned get_buffer_helper(void)
|
void test_audiocore_volume_adjust(void)
|
||||||
{
|
{
|
||||||
uint32_t test_data[100];
|
const int16_t samples[] = {0, 1, -1, INT16_MIN, INT16_MAX, 100, -100, 99, -99};
|
||||||
uint32_t ctr = 0;
|
#define SAMPLE_COUNT (sizeof(samples) / sizeof(samples[0]))
|
||||||
|
const struct {
|
||||||
|
uint16_t scalefactor;
|
||||||
|
int16_t expected[SAMPLE_COUNT];
|
||||||
|
} expected[] = {
|
||||||
|
{.scalefactor = AUDIOCORE_MAX_VOLUME, .expected = {0, 1, -1, INT16_MIN, INT16_MAX, 100, -100, 99, -99}},
|
||||||
|
{.scalefactor = 0, .expected = {0}},
|
||||||
|
{.scalefactor = AUDIOCORE_MAX_VOLUME / 2, .expected = {0, 0, -1, -16384, 16383, 50, -50, 49, -50}}};
|
||||||
|
|
||||||
unsigned avail, gotten = 0;
|
for (int test = 0; test < sizeof(expected) / sizeof(expected[0]); ++test) {
|
||||||
while ((avail = audiocore_get_audio_buffer_avail()) > 0) {
|
int16_t buf[sizeof(samples) / sizeof(samples[0])];
|
||||||
const unsigned todo = avail > 100 ? 100 : avail;
|
memcpy(buf, samples, sizeof(samples));
|
||||||
audiocore_audio_buffer_get(test_data, todo);
|
volume_adjust(buf, SAMPLE_COUNT, expected[test].scalefactor);
|
||||||
for (unsigned i = 0; i < todo; ++i)
|
TEST_ASSERT_EQUAL_HEX16_ARRAY(expected[test].expected, buf, SAMPLE_COUNT);
|
||||||
TEST_ASSERT_EQUAL(ctr++, test_data[i]);
|
|
||||||
gotten += todo;
|
|
||||||
}
|
}
|
||||||
return gotten;
|
|
||||||
}
|
|
||||||
|
|
||||||
void test_audiocore_buffer_get(void)
|
|
||||||
{
|
|
||||||
shared_context.audio_buffer_read = 0;
|
|
||||||
shared_context.audio_buffer_write = AUDIO_BUFFER_SIZE - 1;
|
|
||||||
for (unsigned i = 0; i < AUDIO_BUFFER_SIZE - 1; ++i)
|
|
||||||
shared_context.audio_buffer[i] = i;
|
|
||||||
TEST_ASSERT_EQUAL(AUDIO_BUFFER_SIZE - 1, get_buffer_helper());
|
|
||||||
|
|
||||||
// test wraparound read
|
|
||||||
shared_context.audio_buffer_read = 10;
|
|
||||||
shared_context.audio_buffer_write = 9;
|
|
||||||
for (unsigned i = 0; i < AUDIO_BUFFER_SIZE - 1; ++i)
|
|
||||||
shared_context.audio_buffer[(i + 10) % AUDIO_BUFFER_SIZE] = i;
|
|
||||||
TEST_ASSERT_EQUAL(AUDIO_BUFFER_SIZE - 1, get_buffer_helper());
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,22 +3,21 @@
|
|||||||
|
|
||||||
import aiorepl
|
import aiorepl
|
||||||
import asyncio
|
import asyncio
|
||||||
import audiocore
|
|
||||||
import machine
|
import machine
|
||||||
import micropython
|
import micropython
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
from array import array
|
|
||||||
from machine import Pin
|
from machine import Pin
|
||||||
from math import pi, sin, pow
|
from math import pi, sin, pow
|
||||||
from micropython import const
|
from micropython import const
|
||||||
|
|
||||||
|
# Own modules
|
||||||
|
from audiocore import Audiocore
|
||||||
from rp2_neopixel import NeoPixel
|
from rp2_neopixel import NeoPixel
|
||||||
from rp2_sd import SDCard
|
from rp2_sd import SDCard
|
||||||
|
|
||||||
micropython.alloc_emergency_exception_buf(100)
|
micropython.alloc_emergency_exception_buf(100)
|
||||||
|
|
||||||
asyncio.create_task(aiorepl.task())
|
|
||||||
|
|
||||||
leds = const(10)
|
leds = const(10)
|
||||||
brightness = 0.5
|
brightness = 0.5
|
||||||
|
|
||||||
@@ -43,27 +42,35 @@ async def rainbow(np, period=10):
|
|||||||
await asyncio.sleep_ms(20 - (now - before))
|
await asyncio.sleep_ms(20 - (now - before))
|
||||||
|
|
||||||
|
|
||||||
samplerate = 44100
|
async def play_mp3(audiocore, mp3file):
|
||||||
hz = 441
|
_, avail, _ = audioctx.put(b'')
|
||||||
count = 100
|
|
||||||
amplitude = 0x1fff
|
|
||||||
buf = array('I', range(count))
|
|
||||||
for i in range(len(buf)):
|
|
||||||
val = int(sin(i * hz / samplerate * 2 * pi)*amplitude) & 0xffff
|
|
||||||
buf[i] = (val << 16) | val
|
|
||||||
|
|
||||||
|
|
||||||
async def output_sound(audioctx):
|
|
||||||
pos = 0
|
|
||||||
known_underruns = 0
|
known_underruns = 0
|
||||||
while True:
|
while True:
|
||||||
pushed, avail, underruns = audioctx.put(buf[pos:])
|
data = mp3file.read(avail)
|
||||||
pos = (pos + pushed) % len(buf)
|
if avail > 0 and len(data) == 0:
|
||||||
# print(f"pushed {pushed}, pos {pos}, avail {avail}")
|
# End of file
|
||||||
|
break
|
||||||
|
pos = 0
|
||||||
|
while pos < len(data):
|
||||||
|
pushed, avail, underruns = audioctx.put(data[pos:])
|
||||||
|
if pushed == 0:
|
||||||
|
await asyncio.sleep_ms(0)
|
||||||
|
else:
|
||||||
|
await asyncio.sleep_ms(0)
|
||||||
|
pos += pushed
|
||||||
if underruns > known_underruns:
|
if underruns > known_underruns:
|
||||||
print(f"{underruns:x}")
|
print(f"{underruns:x}")
|
||||||
known_underruns = underruns
|
known_underruns = underruns
|
||||||
await asyncio.sleep(0)
|
audioctx.flush()
|
||||||
|
print("Decoding ended")
|
||||||
|
|
||||||
|
|
||||||
|
async def play_mp3s(audiocore, mp3files):
|
||||||
|
for name in mp3files:
|
||||||
|
print(b'Playing ' + name)
|
||||||
|
with open(name, "rb") as testfile:
|
||||||
|
await play_mp3(audiocore, testfile)
|
||||||
|
await asyncio.sleep_ms(1000)
|
||||||
|
|
||||||
|
|
||||||
# Set 8 mA drive strength and fast slew rate
|
# Set 8 mA drive strength and fast slew rate
|
||||||
@@ -120,9 +127,14 @@ list_sd()
|
|||||||
asyncio.create_task(rainbow(np))
|
asyncio.create_task(rainbow(np))
|
||||||
|
|
||||||
# Test audio
|
# Test audio
|
||||||
audioctx = audiocore.init(Pin(8), Pin(6), samplerate)
|
audioctx = Audiocore(Pin(8), Pin(6))
|
||||||
asyncio.create_task(output_sound(audioctx))
|
|
||||||
|
|
||||||
asyncio.create_task(latency_test())
|
# high prio for proc 1
|
||||||
|
machine.mem32[0x40030000 + 0x00] = 0x10
|
||||||
|
|
||||||
|
testfiles = [b'/sd/' + name for name in os.listdir(b'/sd') if name.endswith(b'mp3')]
|
||||||
|
|
||||||
|
asyncio.create_task(play_mp3s(audioctx, testfiles))
|
||||||
|
|
||||||
|
asyncio.create_task(aiorepl.task())
|
||||||
asyncio.get_event_loop().run_forever()
|
asyncio.get_event_loop().run_forever()
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
// Copyright (c) 2024 Matthias Blankertz <matthias@blankertz.org>
|
// Copyright (c) 2024-2025 Matthias Blankertz <matthias@blankertz.org>
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
struct spin_lock;
|
struct spin_lock;
|
||||||
typedef struct spin_lock spin_lock_t;
|
typedef struct spin_lock spin_lock_t;
|
||||||
|
|
||||||
void multicore_fifo_push_blocking(unsigned val);
|
void multicore_fifo_push_blocking(unsigned val);
|
||||||
unsigned multicore_fifo_pop_blocking(void);
|
unsigned multicore_fifo_pop_blocking(void);
|
||||||
|
bool multicore_fifo_rvalid(void);
|
||||||
|
|
||||||
|
#define __time_critical_func(x) x
|
||||||
|
#define __wfe()
|
||||||
|
|||||||
@@ -3,3 +3,4 @@
|
|||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
|
||||||
#define MP_EIO EIO
|
#define MP_EIO EIO
|
||||||
|
#define MP_ENOMEM ENOMEM
|
||||||
|
|||||||
Reference in New Issue
Block a user