e35058f53d
Backfills the working firmware that had stayed uncommitted across the build sessions, so the branch is self-contained. - audio: audio_player, radio_pipeline, sd_player, calm_tone, aac_player - net: podcast (RSS), udp_log - modes: balloons, plus calm/games/radio tweaks - ui: fonts header, icons; build files (CMakeLists, idf_component.yml, sdkconfig.defaults) - docs/superpowers: Histoires design + plan Note: sdkconfig stays gitignored (Wi-Fi creds + tuned LFN/FS/mbedtls settings); a fresh checkout must reconfigure those.
193 lines
5.3 KiB
C
193 lines
5.3 KiB
C
// Shared audio facade for Lisael Box — see audio_player.h.
|
|
|
|
#include "audio/audio_player.h"
|
|
#include "audio/radio_pipeline.h"
|
|
#include "audio/sd_player.h"
|
|
#include "audio/aac_player.h"
|
|
#include "lisael_config.h"
|
|
|
|
#include "esp_log.h"
|
|
#include "nvs.h"
|
|
#include "bsp/esp-box-3.h"
|
|
#include "esp_codec_dev.h"
|
|
#include "driver/i2s_std.h"
|
|
#include <audio_player.h> // chmorgan/esp-audio-player (angle brackets target
|
|
// its include dir, NOT our audio/audio_player.h)
|
|
|
|
static const char *TAG = "audio";
|
|
|
|
static esp_codec_dev_handle_t s_codec;
|
|
static lisael_audio_source_t s_source = LISAEL_AUDIO_IDLE;
|
|
static int s_volume = LISAEL_VOLUME_DEFAULT;
|
|
static bool s_player_ready;
|
|
|
|
static int clamp_volume(int v)
|
|
{
|
|
if (v < 0) v = 0;
|
|
if (v > LISAEL_VOLUME_MAX) v = LISAEL_VOLUME_MAX;
|
|
return v;
|
|
}
|
|
|
|
static void load_volume(void)
|
|
{
|
|
nvs_handle_t nvs;
|
|
if (nvs_open(LISAEL_NVS_NS, NVS_READONLY, &nvs) == ESP_OK) {
|
|
int32_t v;
|
|
if (nvs_get_i32(nvs, LISAEL_NVS_VOLUME, &v) == ESP_OK) {
|
|
s_volume = clamp_volume((int)v);
|
|
}
|
|
nvs_close(nvs);
|
|
}
|
|
}
|
|
|
|
static void persist_volume(void)
|
|
{
|
|
nvs_handle_t nvs;
|
|
if (nvs_open(LISAEL_NVS_NS, NVS_READWRITE, &nvs) == ESP_OK) {
|
|
nvs_set_i32(nvs, LISAEL_NVS_VOLUME, s_volume);
|
|
nvs_commit(nvs);
|
|
nvs_close(nvs);
|
|
}
|
|
}
|
|
|
|
// --- esp-audio-player <-> BSP codec glue ------------------------------------
|
|
// The player decodes MP3 to PCM and pushes it through these callbacks, which we
|
|
// back with the BSP's ES8311 codec device (esp_codec_dev). We do NOT touch I2S
|
|
// directly — the BSP owns it; we only (re)open the codec at the stream's rate.
|
|
|
|
static esp_err_t ap_mute(AUDIO_PLAYER_MUTE_SETTING setting)
|
|
{
|
|
if (s_codec) {
|
|
esp_codec_dev_set_out_mute(s_codec, setting == AUDIO_PLAYER_MUTE);
|
|
}
|
|
return ESP_OK;
|
|
}
|
|
|
|
static esp_err_t ap_clk_set(uint32_t rate, uint32_t bits, i2s_slot_mode_t ch)
|
|
{
|
|
if (!s_codec) {
|
|
return ESP_FAIL;
|
|
}
|
|
esp_codec_dev_close(s_codec); // harmless if not open
|
|
esp_codec_dev_sample_info_t fs = {
|
|
.bits_per_sample = (uint8_t)bits,
|
|
.channel = (ch == I2S_SLOT_MODE_STEREO) ? 2 : 1,
|
|
.sample_rate = rate,
|
|
};
|
|
esp_err_t err = esp_codec_dev_open(s_codec, &fs);
|
|
esp_codec_dev_set_out_vol(s_codec, (float)s_volume); // re-apply volume
|
|
return err;
|
|
}
|
|
|
|
static esp_err_t ap_write(void *buf, size_t len, size_t *written, uint32_t timeout_ms)
|
|
{
|
|
(void)timeout_ms;
|
|
int r = esp_codec_dev_write(s_codec, buf, (int)len);
|
|
*written = (r == ESP_CODEC_DEV_OK) ? len : 0;
|
|
return (r == ESP_CODEC_DEV_OK) ? ESP_OK : ESP_FAIL;
|
|
}
|
|
|
|
static void ap_event_cb(audio_player_cb_ctx_t *ctx)
|
|
{
|
|
if (ctx->audio_event == AUDIO_PLAYER_CALLBACK_EVENT_IDLE) {
|
|
// Playback finished (or was stopped): close the codec and mark idle.
|
|
if (s_codec) {
|
|
esp_codec_dev_close(s_codec);
|
|
}
|
|
if (s_source == LISAEL_AUDIO_SD) {
|
|
s_source = LISAEL_AUDIO_IDLE;
|
|
}
|
|
}
|
|
}
|
|
|
|
esp_err_t lisael_audio_player_ensure(void)
|
|
{
|
|
if (s_player_ready) {
|
|
return ESP_OK;
|
|
}
|
|
if (!s_codec) {
|
|
ESP_LOGE(TAG, "codec not ready — call lisael_audio_init first");
|
|
return ESP_FAIL;
|
|
}
|
|
audio_player_config_t cfg = {
|
|
.mute_fn = ap_mute,
|
|
.clk_set_fn = ap_clk_set,
|
|
.write_fn = ap_write,
|
|
.priority = 5,
|
|
.coreID = 1,
|
|
.force_stereo = false,
|
|
};
|
|
esp_err_t err = audio_player_new(cfg);
|
|
if (err != ESP_OK) {
|
|
ESP_LOGE(TAG, "audio_player_new failed: %s", esp_err_to_name(err));
|
|
return err;
|
|
}
|
|
audio_player_callback_register(ap_event_cb, NULL);
|
|
s_player_ready = true;
|
|
ESP_LOGI(TAG, "esp-audio-player ready");
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t lisael_audio_init(void)
|
|
{
|
|
// The BSP returns a ready-to-use ES8311 speaker codec device.
|
|
// bsp_i2c_init() must already have been called (done in app_main).
|
|
s_codec = bsp_audio_codec_speaker_init();
|
|
if (!s_codec) {
|
|
ESP_LOGE(TAG, "bsp_audio_codec_speaker_init failed");
|
|
return ESP_FAIL;
|
|
}
|
|
load_volume();
|
|
s_volume = LISAEL_VOLUME_MAX; // user request: start at maximum volume
|
|
persist_volume();
|
|
esp_codec_dev_set_out_vol(s_codec, (float)s_volume);
|
|
ESP_LOGI(TAG, "codec ready, volume=%d%% (cap %d%%)", s_volume, LISAEL_VOLUME_MAX);
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_codec_dev_handle_t lisael_audio_codec(void)
|
|
{
|
|
return s_codec;
|
|
}
|
|
|
|
void lisael_audio_stop_all(void)
|
|
{
|
|
switch (s_source) {
|
|
case LISAEL_AUDIO_RADIO: lisael_radio_stop(); break;
|
|
case LISAEL_AUDIO_SD: lisael_sd_stop(); break;
|
|
case LISAEL_AUDIO_PODCAST: lisael_aac_stop(); break;
|
|
default: break;
|
|
}
|
|
s_source = LISAEL_AUDIO_IDLE;
|
|
}
|
|
|
|
void lisael_audio_set_source(lisael_audio_source_t src)
|
|
{
|
|
s_source = src;
|
|
}
|
|
|
|
lisael_audio_source_t lisael_audio_get_source(void)
|
|
{
|
|
return s_source;
|
|
}
|
|
|
|
int lisael_audio_get_volume(void)
|
|
{
|
|
return s_volume;
|
|
}
|
|
|
|
void lisael_audio_set_volume(int percent)
|
|
{
|
|
s_volume = clamp_volume(percent);
|
|
if (s_codec) {
|
|
esp_codec_dev_set_out_vol(s_codec, (float)s_volume);
|
|
}
|
|
persist_volume();
|
|
ESP_LOGI(TAG, "volume -> %d%%", s_volume);
|
|
}
|
|
|
|
void lisael_audio_volume_step(int delta)
|
|
{
|
|
lisael_audio_set_volume(s_volume + delta);
|
|
}
|