Add standalone-mp3 test tool
This commit is contained in:
46
software/tools/standalone-mp3/CMakeLists.txt
Normal file
46
software/tools/standalone-mp3/CMakeLists.txt
Normal file
@@ -0,0 +1,46 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
|
||||
# Workaround for pico-sdk host toolchain issue, see directory for details
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/../../lib/micropython/ports/rp2/tools_patch")
|
||||
|
||||
# initialize pico-sdk from submodule
|
||||
# note: this must happen before project()
|
||||
include(../../lib/micropython/lib/pico-sdk/pico_sdk_init.cmake)
|
||||
|
||||
project(standalone_mp3)
|
||||
|
||||
# initialize the Raspberry Pi Pico SDK
|
||||
pico_sdk_init()
|
||||
|
||||
set(SD_LIB_DIR "${CMAKE_CURRENT_LIST_DIR}/../../modules/rp2_sd")
|
||||
|
||||
set(SD_LIB_SRCS
|
||||
"${SD_LIB_DIR}/sd.c"
|
||||
"${SD_LIB_DIR}/sd_spi.c"
|
||||
)
|
||||
|
||||
|
||||
add_executable(standalone_mp3
|
||||
main.c
|
||||
i2s.c
|
||||
${CMAKE_CURRENT_BINARY_DIR}/sd_spi_pio.pio.h
|
||||
${CMAKE_CURRENT_BINARY_DIR}/i2s_max98357.pio.h
|
||||
${SD_LIB_SRCS}
|
||||
)
|
||||
|
||||
pico_generate_pio_header(standalone_mp3 ${SD_LIB_DIR}/sd_spi_pio.pio)
|
||||
|
||||
pico_generate_pio_header(standalone_mp3 ${CMAKE_CURRENT_LIST_DIR}/i2s_max98357.pio)
|
||||
|
||||
add_subdirectory(../../lib/helix_mp3 helix_mp3)
|
||||
|
||||
target_link_libraries(standalone_mp3 PRIVATE pico_stdlib hardware_dma hardware_spi hardware_sync hardware_pio helix_mp3)
|
||||
target_include_directories(standalone_mp3 PRIVATE ${SD_LIB_DIR})
|
||||
target_compile_options(standalone_mp3 PRIVATE -Og)
|
||||
|
||||
|
||||
pico_add_extra_outputs(standalone_mp3)
|
||||
pico_enable_stdio_uart(standalone_mp3 1)
|
||||
|
||||
set_property(TARGET standalone_mp3 APPEND_STRING PROPERTY LINK_FLAGS "-Wl,--print-memory-usage")
|
||||
|
||||
116
software/tools/standalone-mp3/i2s.c
Normal file
116
software/tools/standalone-mp3/i2s.c
Normal file
@@ -0,0 +1,116 @@
|
||||
#include "i2s.h"
|
||||
#include "i2s_max98357.pio.h"
|
||||
|
||||
#include <hardware/dma.h>
|
||||
#include <hardware/sync.h>
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define audiocore_pio pio1
|
||||
|
||||
#define AUDIO_BUFS 3
|
||||
|
||||
struct i2s_context {
|
||||
unsigned pio_program_offset;
|
||||
int pio_sm;
|
||||
int dma_ch;
|
||||
dma_channel_config dma_config;
|
||||
uint32_t dma_buf[AUDIO_BUFS][I2S_DMA_BUF_SIZE];
|
||||
int cur_playing;
|
||||
bool has_data[AUDIO_BUFS];
|
||||
bool playback_active;
|
||||
};
|
||||
|
||||
static struct i2s_context i2s_context;
|
||||
|
||||
#define OUT_PIN 8
|
||||
#define SIDESET_BASE 6
|
||||
|
||||
static void dma_isr(void)
|
||||
{
|
||||
if (!dma_channel_get_irq1_status(i2s_context.dma_ch))
|
||||
return;
|
||||
dma_channel_acknowledge_irq1(i2s_context.dma_ch);
|
||||
const int next_buf = (i2s_context.cur_playing + 1) % AUDIO_BUFS;
|
||||
if (i2s_context.playback_active && i2s_context.has_data[next_buf]) {
|
||||
i2s_context.cur_playing = next_buf;
|
||||
i2s_context.has_data[next_buf] = false;
|
||||
} else {
|
||||
memset(i2s_context.dma_buf[i2s_context.cur_playing], 0, sizeof(uint32_t) * I2S_DMA_BUF_SIZE);
|
||||
if (i2s_context.playback_active)
|
||||
printf("x");
|
||||
}
|
||||
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)
|
||||
{
|
||||
i2s_context.dma_config = dma_channel_get_default_config(i2s_context.dma_ch);
|
||||
channel_config_set_dreq(&i2s_context.dma_config, pio_get_dreq(pio1, i2s_context.pio_sm, true));
|
||||
}
|
||||
|
||||
uint32_t *i2s_next_buf(void)
|
||||
{
|
||||
const long flags = save_and_disable_interrupts();
|
||||
const int next_buf = (i2s_context.cur_playing + 1) % AUDIO_BUFS;
|
||||
uint32_t *ret = NULL;
|
||||
if (!i2s_context.has_data[next_buf]) {
|
||||
ret = i2s_context.dma_buf[next_buf];
|
||||
} else {
|
||||
const int next_buf_2 = (next_buf + 1) % AUDIO_BUFS;
|
||||
if (!i2s_context.has_data[next_buf_2]) {
|
||||
ret = i2s_context.dma_buf[next_buf_2];
|
||||
}
|
||||
}
|
||||
restore_interrupts(flags);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void i2s_commit_buf(uint32_t *buf)
|
||||
{
|
||||
const long flags = save_and_disable_interrupts();
|
||||
const int next_buf = (i2s_context.cur_playing + 1) % AUDIO_BUFS;
|
||||
const int next_buf_2 = (next_buf + 1) % AUDIO_BUFS;
|
||||
if (i2s_context.dma_buf[next_buf] == buf) {
|
||||
i2s_context.has_data[next_buf] = true;
|
||||
} else if (i2s_context.dma_buf[next_buf_2] == buf) {
|
||||
i2s_context.has_data[next_buf_2] = true;
|
||||
i2s_context.playback_active = true;
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
restore_interrupts(flags);
|
||||
}
|
||||
|
||||
bool i2s_init(int samplerate)
|
||||
{
|
||||
memset(i2s_context.dma_buf, 0, sizeof(i2s_context.dma_buf[0][0]) * 2* I2S_DMA_BUF_SIZE);
|
||||
if (!pio_can_add_program(audiocore_pio, (const pio_program_t *)&i2s_max98357_program))
|
||||
return false;
|
||||
i2s_context.pio_sm = pio_claim_unused_sm(audiocore_pio, false);
|
||||
if (i2s_context.pio_sm == -1)
|
||||
return false;
|
||||
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);
|
||||
if (i2s_context.dma_ch == -1)
|
||||
goto out_dma_claim;
|
||||
i2s_context.playback_active = false;
|
||||
setup_dma_config();
|
||||
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);
|
||||
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;
|
||||
|
||||
out_dma_claim:
|
||||
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);
|
||||
return false;
|
||||
}
|
||||
11
software/tools/standalone-mp3/i2s.h
Normal file
11
software/tools/standalone-mp3/i2s.h
Normal file
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define I2S_DMA_BUF_SIZE (1152)
|
||||
|
||||
bool i2s_init(int samplerate);
|
||||
|
||||
uint32_t *i2s_next_buf(void);
|
||||
void i2s_commit_buf(uint32_t *buf);
|
||||
59
software/tools/standalone-mp3/i2s_max98357.pio
Normal file
59
software/tools/standalone-mp3/i2s_max98357.pio
Normal file
@@ -0,0 +1,59 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2024 Matthias Blankertz <matthias@blankertz.org>
|
||||
|
||||
.program i2s_max98357
|
||||
.side_set 2
|
||||
|
||||
.lang_opt python sideset_init = pico.PIO.OUT_LOW
|
||||
.lang_opt python out_init = pico.PIO.OUT_LOW
|
||||
.lang_opt python out_shiftdir = pcio.PIO.SHIFT_LEFT
|
||||
.lang_opt python autopull = True
|
||||
|
||||
// data - DOUT
|
||||
// sideset - 2-BCLK, 1-LRCLK
|
||||
|
||||
set x,15 side 0
|
||||
nop side 1
|
||||
startup_loop:
|
||||
nop side 2
|
||||
jmp x-- startup_loop side 3
|
||||
nop side 0
|
||||
set x, 14 side 1
|
||||
|
||||
left_loop:
|
||||
.wrap_target
|
||||
out pins, 1 side 0
|
||||
jmp x-- left_loop side 1
|
||||
out pins, 1 side 2
|
||||
set x, 14 side 3
|
||||
right_loop:
|
||||
out pins, 1 side 2
|
||||
jmp x-- right_loop side 3
|
||||
out pins, 1 side 0
|
||||
set x, 14 side 1
|
||||
.wrap
|
||||
|
||||
% c-sdk {
|
||||
#include "hardware/clocks.h"
|
||||
|
||||
static inline void i2s_max98357_program_init(PIO pio, uint sm, uint offset, uint pin, uint sideset, uint samplerate) {
|
||||
pio_gpio_init(pio, pin);
|
||||
pio_gpio_init(pio, sideset);
|
||||
pio_gpio_init(pio, sideset+1);
|
||||
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
|
||||
pio_sm_set_consecutive_pindirs(pio, sm, sideset, 2, true);
|
||||
|
||||
pio_sm_config c = i2s_max98357_program_get_default_config(offset);
|
||||
sm_config_set_out_pins(&c, pin, 1);
|
||||
sm_config_set_sideset_pins(&c, sideset);
|
||||
sm_config_set_out_shift(&c, false, true, 32);
|
||||
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
|
||||
|
||||
const unsigned i2s_freq = samplerate * 2 * 16 * 2;
|
||||
const float div = clock_get_hz(clk_sys) / (float)i2s_freq;
|
||||
sm_config_set_clkdiv(&c, div);
|
||||
|
||||
pio_sm_init(pio, sm, offset, &c);
|
||||
//pio_sm_set_enabled(pio, sm, true);
|
||||
}
|
||||
%}
|
||||
148
software/tools/standalone-mp3/main.c
Normal file
148
software/tools/standalone-mp3/main.c
Normal file
@@ -0,0 +1,148 @@
|
||||
#include "i2s.h"
|
||||
#include "sd.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <hardware/clocks.h>
|
||||
#include <hardware/gpio.h>
|
||||
#include <hardware/spi.h>
|
||||
#include <hardware/sync.h>
|
||||
#include <pico/stdio.h>
|
||||
#include <pico/stdlib.h>
|
||||
#include <pico/time.h>
|
||||
|
||||
#include "mp3dec.h"
|
||||
#include "sd_spi.h"
|
||||
|
||||
extern void sd_spi_dbg_clk(const int div, const int frac);
|
||||
|
||||
extern void sd_spi_dbg_loop(void);
|
||||
|
||||
#define MAX_VOLUME 0x8000u
|
||||
|
||||
void __time_critical_func(volume_adjust)(int16_t *restrict buf, size_t samples, uint16_t scalef)
|
||||
{
|
||||
for (size_t pos = 0; pos < samples; ++pos) {
|
||||
buf[pos] = ((int32_t)buf[pos] * scalef) >> 15;
|
||||
}
|
||||
}
|
||||
|
||||
int __time_critical_func(main)()
|
||||
{
|
||||
stdio_init_all();
|
||||
printf("sysclk is %d Hz\n", clock_get_hz(clk_sys));
|
||||
|
||||
struct sd_context sd_context;
|
||||
|
||||
if (!sd_init(&sd_context, 3, 4, 2, 5, 15000000)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
HMP3Decoder mp3dec = MP3InitDecoder();
|
||||
|
||||
if (!i2s_init(44100)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
uint8_t mp3buffer[4 * 512];
|
||||
for (int i = 0; i < sizeof(mp3buffer) / 512; ++i) {
|
||||
sd_readblock(&sd_context, i, mp3buffer + 512 * i);
|
||||
}
|
||||
size_t next_sector = sizeof(mp3buffer) / 512;
|
||||
|
||||
unsigned char *readptr = mp3buffer;
|
||||
int bytes_left = sizeof(mp3buffer);
|
||||
|
||||
int pos = 0;
|
||||
bool first = true;
|
||||
bool pending_read = false;
|
||||
bool synced = false;
|
||||
while (true) {
|
||||
/* Get some input data */
|
||||
if (pending_read && sd_readblock_is_complete(&sd_context)) {
|
||||
sd_readblock_complete(&sd_context);
|
||||
bytes_left += 512;
|
||||
pending_read = false;
|
||||
}
|
||||
if (!pending_read && (sizeof(mp3buffer) - bytes_left >= 512)) {
|
||||
// If there is not enough space for an mp3 frame, or if there is less than one SD block to the end, move
|
||||
// remaining data to start of buffer
|
||||
if (readptr - mp3buffer >= sizeof(mp3buffer) - 1044 ||
|
||||
readptr - mp3buffer > sizeof(mp3buffer) - 512 - bytes_left) {
|
||||
memmove(mp3buffer, readptr, bytes_left);
|
||||
readptr = mp3buffer;
|
||||
}
|
||||
sd_readblock_start(&sd_context, next_sector++, readptr + bytes_left);
|
||||
pending_read = true;
|
||||
}
|
||||
if (bytes_left == 0) {
|
||||
// Can't do anything without input, wait and try again
|
||||
__wfe();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Synchronize MP3 stream if neccessary
|
||||
if (!synced) {
|
||||
const int ofs = MP3FindSyncWord(readptr, bytes_left);
|
||||
if (ofs == -1) {
|
||||
printf("MP3 sync word not found\n");
|
||||
readptr += bytes_left;
|
||||
bytes_left = 0;
|
||||
continue; // try again
|
||||
}
|
||||
readptr += ofs;
|
||||
bytes_left -= ofs;
|
||||
printf("MP3 sync word found after %zu bytes\n", ofs);
|
||||
synced = true;
|
||||
}
|
||||
|
||||
// Get an output buffer
|
||||
uint32_t *const buf = i2s_next_buf();
|
||||
if (!buf) {
|
||||
// No output needed, wait and try again
|
||||
__wfe();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Decode one frame
|
||||
unsigned char *const old_readptr = readptr;
|
||||
const int old_bytes_left = bytes_left;
|
||||
const int status = MP3Decode(mp3dec, &readptr, &bytes_left, (short *)buf, 0);
|
||||
if (status) {
|
||||
if (status == ERR_MP3_INDATA_UNDERFLOW) {
|
||||
readptr = old_readptr;
|
||||
bytes_left = old_bytes_left;
|
||||
printf("INDATA_UNDERFLOW\n");
|
||||
sd_readblock_complete(&sd_context);
|
||||
continue;
|
||||
} else /*if (status== ERR_MP3_MAINDATA_UNDERFLOW)*/ {
|
||||
--bytes_left;
|
||||
++readptr;
|
||||
synced = false;
|
||||
continue;
|
||||
}
|
||||
printf("MP3Decode failed: %d\n", status);
|
||||
break;
|
||||
}
|
||||
|
||||
MP3FrameInfo info;
|
||||
MP3GetLastFrameInfo(mp3dec, &info);
|
||||
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;
|
||||
}
|
||||
if (info.outputSamps != 2304) {
|
||||
printf("Unexpected number of output samples: %d\n", info.outputSamps);
|
||||
return 1;
|
||||
}
|
||||
volume_adjust((int16_t *)buf, info.outputSamps, MAX_VOLUME >> 4);
|
||||
|
||||
i2s_commit_buf(buf);
|
||||
}
|
||||
|
||||
printf("Done.\n");
|
||||
}
|
||||
Reference in New Issue
Block a user