mirror of
https://github.com/sle118/squeezelite-esp32.git
synced 2025-12-25 08:58:25 +03:00
Compare commits
23 Commits
SqueezeAmp
...
I2S-4MFlas
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
21c3ce1fba | ||
|
|
48e8525ba9 | ||
|
|
0db9631700 | ||
|
|
192cb975e2 | ||
|
|
d1f6085199 | ||
|
|
afaa5323d7 | ||
|
|
f92447e9b9 | ||
|
|
22f8d1d88b | ||
|
|
8136b7fd9a | ||
|
|
e588deb3af | ||
|
|
38ec8ac6f8 | ||
|
|
791167f794 | ||
|
|
c9b859ef8c | ||
|
|
db74419bd7 | ||
|
|
265403d364 | ||
|
|
d3dd8b9078 | ||
|
|
de41fb9583 | ||
|
|
2699216d25 | ||
|
|
c7b1c7cd83 | ||
|
|
f51af21b90 | ||
|
|
5ec63f400c | ||
|
|
1753e11698 | ||
|
|
58c6ca059b |
2
.github/workflows/CrossBuild.yml
vendored
2
.github/workflows/CrossBuild.yml
vendored
@@ -86,7 +86,7 @@ jobs:
|
||||
echo "${tag}" >version.txt
|
||||
docker pull sle118/idf:release-v4.0
|
||||
docker info
|
||||
docker run --env-file=${TARGET_BUILD_NAME}-env.txt -v $PWD:/project -w /project sle118/idf:release-v4.0 /bin/bash -c "cp build-scripts/${TARGET_BUILD_NAME}-sdkconfig.defaults sdkconfig && idf.py build -DAAC_ENABLE_SBR=1 -DDEPTH=${{ matrix.depth }} -DBUILD_NUMBER=${BUILD_NUMBER}-${{ matrix.depth }} && zip -r build_output.zip build && zip build/${artifact_file_name} partitions*.csv build/*.bin build/bootloader/bootloader.bin build/partition_table/partition-table.bin build/flash_project_args build/size_*.txt"
|
||||
docker run --env-file=${TARGET_BUILD_NAME}-env.txt -v $PWD:/project -w /project sle118/idf:release-v4.0 /bin/bash -c "cp build-scripts/${TARGET_BUILD_NAME}-sdkconfig.defaults sdkconfig && idf.py build -DDEPTH=${{ matrix.depth }} -DBUILD_NUMBER=${BUILD_NUMBER}-${{ matrix.depth }} && zip -r build_output.zip build && zip build/${artifact_file_name} partitions*.csv build/*.bin build/bootloader/bootloader.bin build/partition_table/partition-table.bin build/flash_project_args build/size_*.txt"
|
||||
# - name: Build Mock firmware
|
||||
# run: |
|
||||
# mkdir -p build
|
||||
|
||||
16
README.md
16
README.md
@@ -44,6 +44,8 @@ Now, when I did the porting of squeezelite to esp32, I've also made the core 16
|
||||
The esp32 must run at 240 MHz, with Quad-SPI I/O at 80 MHz and a clock of 40 Mhz. Still, it's a lot to run, especially knowing that it has a serial Flash and PSRAM, so kudos to Espressif for their chipset optimization. Now, to have all the decoding, resampling, equalizing, gain, display, spectrum/vu is a very (very) delicate equilibrium between use of internal /external RAM, tasks priorities and buffer handling. It is not perfect and the more you push the system to the limit, the higher the risk that some files would not play (see below). In general, the display will always have the lowest priority and you'll notice slowdown in scrolling and VU/Spectrum refresh rates. Now, even display thread has some critical section and impacts the capabilities. For example, a 16 bits-depth color display with low SPI speed might prevent 24/96 flac to work but still work with pcm 24/96
|
||||
|
||||
In 16 bits mode, although 192 kHz is reported as max rate, it's highly recommended to limit reported sampling rate to 96k (-Z 96000). Note that some high-speed 24/96k on-line streams might stutter because of TCP/IP stack performances. It is usually due to the fact that the server sends small packets of data and the esp32 cannot receive encoded audio fast enough, regardless of task priority settings (I've tried to tweak that a fair bit). The best option in that case is to let LMS proxy the stream as it will provide larger chunks and a "smoother" stream that can then be handled.
|
||||
|
||||
Note as well that some codecs consume more CPU than others or have not been optimized as much. I've done my best to tweak these, but that level of optimization includes writing some assembly which is painful. One very demanding codec is AAC when files are encoded with SBR. It allows reconstruction of upper part of spectrum and thus higher sampling rate, but the codec spec is such that this is optional, you can decode simply lower band and accept lower sampling rate - See the AAC_DISABLE_SBR option below.
|
||||
## Supported Hardware
|
||||
Any esp32-based hardware with at least 4MB of flash and 4MB of PSRAM will be capable of running squeezelite-esp32 and there are various boards that include such chip. A few are mentionned below, but any should work. You can find various help & instructions [here](https://forums.slimdevices.com/showthread.php?112697-ANNOUNCE-Squeezelite-ESP32-(dedicated-thread))
|
||||
|
||||
@@ -66,7 +68,7 @@ NB: You can use the pre-build binaries SqueezeAMP4MBFlash which has all the hard
|
||||
- spdif_config: bck=33,ws=25,do=15
|
||||
|
||||
### ESP32-A1S
|
||||
Works with [ESP32-A1S](https://docs.ai-thinker.com/esp32-a1s) module that includes audio codec and headset output. You still need to use a demo board like [this](https://www.aliexpress.com/item/4000765857347.html?spm=2114.12010615.8148356.11.5d963cd0j669ns) or an external amplifier if you want direct speaker connection.
|
||||
Works with [ESP32-A1S](https://docs.ai-thinker.com/esp32-a1s) module that includes audio codec and headset output. You still need to use a demo board like [this](https://www.aliexpress.com/item/4001060963585.html) or an external amplifier if you want direct speaker connection.
|
||||
|
||||
The board shown above has the following IO set
|
||||
- amplifier: GPIO21
|
||||
@@ -186,10 +188,10 @@ SPI,width=<pixels>,height=<pixels>,cs=<gpio>[,back=<gpio>][,reset=<gpio>][,speed
|
||||
- Default speed is 8000000 (8MHz) but SPI can work up to 26MHz or even 40MHz
|
||||
- SH1106 is 128x64 monochrome I2C/SPI [here]((https://www.waveshare.com/wiki/1.3inch_OLED_HAT))
|
||||
- SSD1306 is 128x32 monochrome I2C/SPI [here](https://www.buydisplay.com/i2c-blue-0-91-inch-oled-display-module-128x32-arduino-raspberry-pi)
|
||||
- SSD1322 is 128x128 16-level grayscale SPI [here](https://www.amazon.com/gp/product/B079N1LLG8/ref=ox_sc_act_title_1?smid=A1N6DLY3NQK2VM&psc=1) - artwork can be up to 96x96 with vertical vu-meter/spectrum
|
||||
- SSD1351 is 128x128 65k/262k color SPI [here](https://www.waveshare.com/product/displays/lcd-oled/lcd-oled-3/1.5inch-rgb-oled-module.htm)
|
||||
- SSD1322 is 256x64 grayscale 16-levels SPI in multiple sizes [here](https://www.buydisplay.com/oled-display/oled-display-module?resolution=159) - it is very nice
|
||||
- SSD1326 is 256x32 monochrome or grayscale 16-levels SPI [here](https://www.aliexpress.com/item/32833603664.html?spm=a2g0o.productlist.0.0.2d19776cyQvsBi&algo_pvid=c7a3db92-e019-4095-8a28-dfdf0a087f98&algo_expid=c7a3db92-e019-4095-8a28-dfdf0a087f98-1&btsid=0ab6f81e15955375483301352e4208&ws_ab_test=searchweb0_0,searchweb201602_,searchweb201603_)
|
||||
- SSD1327 is 256x64 grayscale 16-levels SPI in multiple sizes [here](https://www.buydisplay.com/oled-display/oled-display-module?resolution=159) - it is very nice
|
||||
- SSD1327 is 128x128 16-level grayscale SPI [here](https://www.amazon.com/gp/product/B079N1LLG8/ref=ox_sc_act_title_1?smid=A1N6DLY3NQK2VM&psc=1) - artwork can be up to 96x96 with vertical vu-meter/spectrum
|
||||
- SSD1351 is 128x128 65k/262k color SPI [here](https://www.waveshare.com/product/displays/lcd-oled/lcd-oled-3/1.5inch-rgb-oled-module.htm)
|
||||
- SSD1675 is an e-ink paper and is experimental as e-ink is really not suitable for LMS du to its very low refresh rate
|
||||
- ST7735 is a 128x160 65k color SPI [here](https://www.waveshare.com/product/displays/lcd-oled/lcd-oled-3/1.8inch-lcd-module.htm). This needs a backlight control
|
||||
- ST7789 is a 240x320 65k (262k not enabled) color SPI [here](https://www.waveshare.com/product/displays/lcd-oled/lcd-oled-3/2inch-lcd-module.htm). It also exist with 240x240 displays. See **rotate** for use in portrait mode
|
||||
@@ -359,9 +361,9 @@ The benefit of the "raw" mode is that you can build a player which is as close a
|
||||
There is no good or bad option, it's your choice. Use the NVS parameter "lms_ctrls_raw" to change that option
|
||||
|
||||
### Battery / ADC
|
||||
The NVS parameter "bat_config" sets the ADC1 channel used to measure battery/DC voltage. Scale is a float ratio applied to every sample of the 12 bits ADC. A measure is taken every 10s and an average is made every 5 minutes (not a sliding window). Syntax is
|
||||
The NVS parameter "bat_config" sets the ADC1 channel used to measure battery/DC voltage. The "atten" value attenuates the input voltage to the ADC input (the read value maintains a 0-1V rage) where: 0=no attenuation(0..800mV), 1=2.5dB attenuation(0..1.1V), 2=6dB attenuation(0..1.35V), 3=11dB attenuation(0..2.6V). Scale is a float ratio applied to every sample of the 12 bits ADC. A measure is taken every 10s and an average is made every 5 minutes (not a sliding window). Syntax is
|
||||
```
|
||||
channel=0..7,scale=<scale>,cells=<2|3>
|
||||
channel=0..7,scale=<scale>,cells=<2|3>,[atten=<0|1|2|3>]
|
||||
```
|
||||
NB: Set parameter to empty to disable battery reading. For well-known configuration, this is ignored (except for SqueezeAMP where number of cells is required)
|
||||
# Configuration
|
||||
@@ -449,7 +451,7 @@ Create and tweak your config using `idf.py menuconfig` then build binaries using
|
||||
```
|
||||
Use `idf.py monitor` to monitor the application (see esp-idf documentation)
|
||||
|
||||
Note: You can use `idf.py build -DDEPTH=32` to build the 32 bits version and add the `-DVERSION=<your_version>` to add a custom version name (it will be 0.0-<your_version>). If you want to change the whole version string, see squeezelite.h
|
||||
Note: You can use `idf.py build -DDEPTH=32` to build the 32 bits version and add the `-DVERSION=<your_version>` to add a custom version name (it will be 0.0-<your_version>). If you want to change the whole version string, see squeezelite.h. You can also disable the SBR extension of AAC codecs as it consumes a lot of CPU and might overload the esp32. Use `-DAAC_DISABLE_SBR=1` for that
|
||||
|
||||
If you have already cloned the repository and you are getting compile errors on one of the submodules (e.g. telnet), run the following git command in the root of the repository location: `git submodule update --init --recursive`
|
||||
### Rebuild codecs (highly recommended to NOT try that)
|
||||
|
||||
@@ -2,10 +2,10 @@ idf_component_register(
|
||||
INCLUDE_DIRS . ./inc inc/alac inc/FLAC inc/helix-aac inc/mad inc/ogg inc/opus inc/opusfile inc/resample16 inc/soxr inc/vorbis
|
||||
)
|
||||
|
||||
if (DEFINED AAC_ENABLE_SBR)
|
||||
add_prebuilt_library(libhelix-aac lib/libhelix-aac-sbr.a )
|
||||
else ()
|
||||
if (DEFINED AAC_DISABLE_SBR)
|
||||
add_prebuilt_library(libhelix-aac lib/libhelix-aac.a )
|
||||
else ()
|
||||
add_prebuilt_library(libhelix-aac lib/libhelix-aac-sbr.a )
|
||||
endif()
|
||||
|
||||
add_prebuilt_library(libmad lib/libmad.a)
|
||||
|
||||
@@ -133,12 +133,14 @@ static void bt_next(bool pressed) {
|
||||
}
|
||||
|
||||
const static actrls_t controls = {
|
||||
NULL, // power
|
||||
bt_volume_up, bt_volume_down, // volume up, volume down
|
||||
bt_toggle, bt_play, // toggle, play
|
||||
bt_pause, bt_stop, // pause, stop
|
||||
NULL, NULL, // rew, fwd
|
||||
bt_prev, bt_next, // prev, next
|
||||
NULL, NULL, NULL, NULL, // left, right, up, down
|
||||
NULL, NULL, NULL, NULL, NULL, NULL, // pre1-6
|
||||
bt_volume_down, bt_volume_up, bt_toggle// knob left, knob_right, knob push
|
||||
};
|
||||
|
||||
|
||||
@@ -81,12 +81,14 @@ static void raop_next(bool pressed) {
|
||||
}
|
||||
|
||||
const static actrls_t controls = {
|
||||
NULL, // power
|
||||
raop_volume_up, raop_volume_down, // volume up, volume down
|
||||
raop_toggle, raop_play, // toggle, play
|
||||
raop_pause, raop_stop, // pause, stop
|
||||
NULL, NULL, // rew, fwd
|
||||
raop_prev, raop_next, // prev, next
|
||||
NULL, NULL, NULL, NULL, // left, right, up, down
|
||||
NULL, NULL, NULL, NULL, NULL, NULL, NULL, // pre1-6
|
||||
raop_volume_down, raop_volume_up, raop_toggle// knob left, knob_right, knob push
|
||||
};
|
||||
|
||||
|
||||
@@ -55,6 +55,7 @@ static const actrls_config_map_t actrls_config_map[] =
|
||||
};
|
||||
|
||||
// BEWARE: the actions below need to stay aligned with the corresponding enum to properly support json parsing
|
||||
// along with the actrls_t controls in LMS_controls, bt_sink and raop_sink
|
||||
#define EP(x) [x] = #x /* ENUM PRINT */
|
||||
static const char * actrls_action_s[ ] = { EP(ACTRLS_POWER),EP(ACTRLS_VOLUP),EP(ACTRLS_VOLDOWN),EP(ACTRLS_TOGGLE),EP(ACTRLS_PLAY),
|
||||
EP(ACTRLS_PAUSE),EP(ACTRLS_STOP),EP(ACTRLS_REW),EP(ACTRLS_FWD),EP(ACTRLS_PREV),EP(ACTRLS_NEXT),
|
||||
|
||||
@@ -33,11 +33,12 @@ static struct {
|
||||
int channel;
|
||||
float sum, avg, scale;
|
||||
int count;
|
||||
int cells;
|
||||
int cells, attenuation;
|
||||
TimerHandle_t timer;
|
||||
} battery = {
|
||||
.channel = CONFIG_BAT_CHANNEL,
|
||||
.cells = 2,
|
||||
.attenuation = ADC_ATTEN_DB_0,
|
||||
};
|
||||
|
||||
/****************************************************************************************
|
||||
@@ -82,6 +83,7 @@ void battery_svc_init(void) {
|
||||
#ifndef CONFIG_BAT_LOCKED
|
||||
if ((p = strcasestr(nvs_item, "channel")) != NULL) battery.channel = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(nvs_item, "scale")) != NULL) battery.scale = atof(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(nvs_item, "atten")) != NULL) battery.attenuation = atoi(strchr(p, '=') + 1);
|
||||
#endif
|
||||
if ((p = strcasestr(nvs_item, "cells")) != NULL) battery.cells = atof(strchr(p, '=') + 1);
|
||||
free(nvs_item);
|
||||
@@ -89,7 +91,7 @@ void battery_svc_init(void) {
|
||||
|
||||
if (battery.channel != -1) {
|
||||
adc1_config_width(ADC_WIDTH_BIT_12);
|
||||
adc1_config_channel_atten(battery.channel, ADC_ATTEN_DB_0);
|
||||
adc1_config_channel_atten(battery.channel, battery.attenuation);
|
||||
|
||||
battery.avg = adc1_get_raw(battery.channel) * battery.scale / 4095.0;
|
||||
battery.timer = xTimerCreate("battery", BATTERY_TIMER / portTICK_RATE_MS, pdTRUE, NULL, battery_callback);
|
||||
|
||||
@@ -37,7 +37,7 @@ else()
|
||||
add_definitions(-DRESAMPLE16 -DBYTES_PER_FRAME=4)
|
||||
endif()
|
||||
|
||||
if (DEFINED AAC_ENABLE_SBR)
|
||||
if (NOT DEFINED AAC_DISABLED_SBR)
|
||||
add_definitions(-DAAC_ENABLE_SBR)
|
||||
endif()
|
||||
|
||||
|
||||
@@ -7,13 +7,14 @@
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include "platform_config.h"
|
||||
#include "squeezelite.h"
|
||||
#include "equalizer.h"
|
||||
#include "esp_equalizer.h"
|
||||
|
||||
#define EQ_BANDS 10
|
||||
|
||||
#define EQ_BANDS 10
|
||||
|
||||
static log_level loglevel = lINFO;
|
||||
|
||||
static struct {
|
||||
@@ -21,6 +22,25 @@ static struct {
|
||||
float gain[EQ_BANDS];
|
||||
bool update;
|
||||
} equalizer = { .update = true };
|
||||
|
||||
/****************************************************************************************
|
||||
* initialize equalizer
|
||||
*/
|
||||
void equalizer_init(void) {
|
||||
s8_t gain[EQ_BANDS] = { };
|
||||
char *config = config_alloc_get(NVS_TYPE_STR, "equalizer");
|
||||
char *p = strtok(config, ", !");
|
||||
|
||||
for (int i = 0; p && i < EQ_BANDS; i++) {
|
||||
gain[i] = atoi(p);
|
||||
p = strtok(NULL, ", :");
|
||||
}
|
||||
|
||||
free(config);
|
||||
equalizer_update(gain);
|
||||
|
||||
LOG_INFO("initializing equalizer");
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* open equalizer
|
||||
@@ -68,7 +88,18 @@ void equalizer_close(void) {
|
||||
* update equalizer gain
|
||||
*/
|
||||
void equalizer_update(s8_t *gain) {
|
||||
for (int i = 0; i < EQ_BANDS; i++) equalizer.gain[i] = gain[i];
|
||||
char config[EQ_BANDS * 4 + 1] = { };
|
||||
char *p = config;
|
||||
|
||||
for (int i = 0; i < EQ_BANDS; i++) {
|
||||
equalizer.gain[i] = gain[i];
|
||||
if (gain[i] < 0) *p++ = '-';
|
||||
*p++ = (gain[i] / 10) + 0x30;
|
||||
*p++ = (gain[i] % 10) + 0x30;
|
||||
if (i < EQ_BANDS - 1) *p++ = ',';
|
||||
}
|
||||
|
||||
config_set_value(NVS_TYPE_STR, "equalizer", config);
|
||||
equalizer.update = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
void equalizer_init(void);
|
||||
void equalizer_open(u32_t sample_rate);
|
||||
void equalizer_close(void);
|
||||
void equalizer_update(s8_t *gain);
|
||||
|
||||
@@ -60,7 +60,7 @@ frames_t _output_frames(frames_t avail) {
|
||||
silence = false;
|
||||
|
||||
// start when threshold met
|
||||
if (output.state == OUTPUT_BUFFER && frames > output.threshold * output.next_sample_rate / 10 && frames > output.start_frames) {
|
||||
if (output.state == OUTPUT_BUFFER && (frames * BYTES_PER_FRAME) > output.threshold * output.next_sample_rate / 10 && frames > output.start_frames) {
|
||||
output.state = OUTPUT_RUNNING;
|
||||
LOG_INFO("start buffer frames: %u", frames);
|
||||
wake_controller();
|
||||
|
||||
@@ -73,11 +73,15 @@ void output_init_embedded(log_level level, char *device, unsigned output_buf_siz
|
||||
slimp_handler_chain = slimp_handler;
|
||||
slimp_handler = handler;
|
||||
|
||||
// init equalizer before backends
|
||||
equalizer_init();
|
||||
|
||||
memset(&output, 0, sizeof(output));
|
||||
output_init_common(level, device, output_buf_size, rates, idle);
|
||||
output.start_frames = FRAME_BLOCK;
|
||||
output.rate_delay = rate_delay;
|
||||
|
||||
|
||||
if (strcasestr(device, "BT ") || !strcasecmp(device, "BT")) {
|
||||
LOG_INFO("init Bluetooth");
|
||||
close_cb = &output_close_bt;
|
||||
|
||||
@@ -169,7 +169,7 @@ static void jack_handler(bool inserted) {
|
||||
static void set_amp_gpio(int gpio, char *value) {
|
||||
char *p;
|
||||
|
||||
if (!strcasecmp(value, "amp")) {
|
||||
if (strcasestr(value, "amp")) {
|
||||
amp_control.gpio = gpio;
|
||||
if ((p = strchr(value, ':')) != NULL) amp_control.active = atoi(p + 1);
|
||||
|
||||
@@ -420,7 +420,7 @@ static int _i2s_write_frames(frames_t out_frames, bool silence, s32_t gainL, s32
|
||||
}
|
||||
|
||||
// don't update visu if we don't have enough data in buffer
|
||||
if (silence || _buf_used(outputbuf) > outputbuf->size >> 2) {
|
||||
if (silence || output.external || _buf_used(outputbuf) > outputbuf->size >> 2 ) {
|
||||
output_visu_export(obuf + oframes * BYTES_PER_FRAME, out_frames, output.current_sample_rate, silence, (gainL + gainR) / 2);
|
||||
}
|
||||
|
||||
@@ -573,10 +573,11 @@ static void output_thread_i2s(void *arg) {
|
||||
SET_MIN_MAX( TIME_MEASUREMENT_GET(timer_start),i2s_time);
|
||||
|
||||
}
|
||||
|
||||
vTaskDelete(NULL);
|
||||
|
||||
if (spdif.enabled) free(spdif.buf);
|
||||
ended = true;
|
||||
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
|
||||
@@ -326,6 +326,9 @@ void register_default_nvs(){
|
||||
ESP_LOGD(TAG,"Registering default value for key %s, value %s", "bypass_wm", "0");
|
||||
config_set_default(NVS_TYPE_STR, "bypass_wm", "0", 0);
|
||||
|
||||
ESP_LOGD(TAG,"Registering default value for equalizer");
|
||||
config_set_default(NVS_TYPE_STR, "equalizer", "", 0);
|
||||
|
||||
ESP_LOGD(TAG,"Registering default Audio control board type %s, value ","actrls_config");
|
||||
config_set_default(NVS_TYPE_STR, "actrls_config", "", 0);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user