diff --git a/README.md b/README.md index 8ff7d61a..7b86caa4 100644 --- a/README.md +++ b/README.md @@ -330,6 +330,7 @@ See squeezlite command line, but keys options are - per mad & few others, edit configure and change $ac_link to add -c (faking link) - change ac_files to remove '' - add DEPS_CFLAGS and DEPS_LIBS to avoid pkg-config to be required + - stack consumption can be very high with some codec variants, so set NONTHREADSAFE_PSEUDOSTACK and GLOBAL_STACK_SIZE=32000 and unset VAR_ARRAYS in config.h - better use helixaac - set IDF_PATH=/home/esp-idf - set ESPPORT=COM9 diff --git a/components/audio/component.mk b/components/audio/component.mk new file mode 100644 index 00000000..3d8e364d --- /dev/null +++ b/components/audio/component.mk @@ -0,0 +1,10 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) +COMPONENT_ADD_LDFLAGS=-l$(COMPONENT_NAME) \ + $(COMPONENT_PATH)/lib/libesp_processing.a + + + + diff --git a/components/audio/inc/esp_equalizer.h b/components/audio/inc/esp_equalizer.h new file mode 100644 index 00000000..e1f815d1 --- /dev/null +++ b/components/audio/inc/esp_equalizer.h @@ -0,0 +1,78 @@ +// Copyright 2018 Espressif Systems (Shanghai) PTE LTD +// All rights reserved. + +#ifndef _ESP_EQUALIZER_H +#define _ESP_EQUALIZER_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** +* @brief Initialize the equalizer handle +* +* @param nch The audio channel number +* @param g_rate The audio sample rate. Four sample rates are supported: 11025Hz, 22050Hz, 44100Hz and 48000Hz. +* @param n_band The number of audio sub-bands. Fixed number of 10 sub-bands is supported and this value should be set to 10. +* @param use_xmms_original_freqs Currently should be set 0 +* +* @return The equalizer handle. +*/ +void *esp_equalizer_init(int nch, int g_rate, int n_band, int use_xmms_original_freqs); + +/** +* @brief Uninitialize the equalizer handle. +* +* @param handle The the equalizer handle +*/ +void esp_equalizer_uninit(void *handle); + +/** +* @brief Process the data through the equalizer +* +* @param handle The the equalizer handle +* @param pcm_buf The audio pcm input & output buffer +* @param length The length of current bytes in pcm_buf +* @param g_rate The audio sample rate. Four sample rates are supported: 11025Hz, 22050Hz, 44100Hz and 48000Hz. +* @param nch The audio channel number +* +* @return Length of pcm_buf after processing +*/ +int esp_equalizer_process(void *handle, unsigned char *pcm_buf, int length, int g_rate, int nch); + +/** +* @brief Set the number of sub-bands for the equalizer +* +* @param handle The the equalizer handle +* @param value The audio sub-bands gain. unit:db. 0 means no gain. +* @param index The index of audio sub-bands. e.g. 0, 1, 2, 3, 4, 5, 6, 7, 8, 9. +* @param nch The audio channel number +*/ +void esp_equalizer_set_band_value(void *handle, float value, int index, int nch); + +/** +* @brief Get the number of the equalizer sub-bands +* +* @param handle The the equalizer handle +* +* @return The number of the equalizer sub-bands +*/ +int esp_equalizer_get_band_count(void *handle); + +/** +* @brief Get the value of the equalizer sub-bands +* +* @param handle The the equalizer handle +* @param index The index of audio sub-bands. Currently only support 10 sub-bands, so it should be 0-9. +* @param nch The audio channel number +* +* @return The number of the equalizer sub-bands +*/ +float esp_equalizer_get_band_value(void *handle, int index, int nch); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/components/audio/lib/libesp_processing.a b/components/audio/lib/libesp_processing.a new file mode 100644 index 00000000..df02f06a Binary files /dev/null and b/components/audio/lib/libesp_processing.a differ diff --git a/components/squeezelite/component.mk b/components/squeezelite/component.mk index 16d89dcf..c6c4d5f1 100644 --- a/components/squeezelite/component.mk +++ b/components/squeezelite/component.mk @@ -15,7 +15,8 @@ CFLAGS += -O3 -DLINKALL -DLOOPBACK -DNO_FAAD -DRESAMPLE16 -DEMBEDDED -DTREMOR_ON -I$(COMPONENT_PATH)/../codecs/inc/opusfile \ -I$(COMPONENT_PATH)/../driver_bt \ -I$(COMPONENT_PATH)/../raop \ - -I$(COMPONENT_PATH)/../services + -I$(COMPONENT_PATH)/../services \ + -I$(COMPONENT_PATH)/../audio/inc # -I$(COMPONENT_PATH)/../codecs/inc/faad2 diff --git a/components/squeezelite/equalizer.c b/components/squeezelite/equalizer.c new file mode 100644 index 00000000..3a5517fc --- /dev/null +++ b/components/squeezelite/equalizer.c @@ -0,0 +1,89 @@ +/* + * Squeezelite for esp32 + * + * (c) Philippe G. 2020, philippe_44@outlook.com + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "squeezelite.h" +#include "equalizer.h" +#include "esp_equalizer.h" + +#define EQ_BANDS 10 + +static log_level loglevel = lINFO; + +static struct { + void *handle; + float gain[EQ_BANDS]; + bool update; +} equalizer = { .update = true }; + +/**************************************************************************************** + * open equalizer + */ +void equalizer_open(u32_t sample_rate) { + if (sample_rate != 11025 && sample_rate != 22050 && sample_rate != 44100 && sample_rate != 48000) { + LOG_WARN("equalizer only supports 11025, 22050, 44100 and 48000 sample rates, not %u", sample_rate); + return; + } + + equalizer.handle = esp_equalizer_init(2, sample_rate, EQ_BANDS, 0); + equalizer.update = false; + + if (equalizer.handle) { + LOG_INFO("equalizer initialized"); + for (int i = 0; i < EQ_BANDS; i++) { + esp_equalizer_set_band_value(equalizer.handle, equalizer.gain[i], i, 0); + esp_equalizer_set_band_value(equalizer.handle, equalizer.gain[i], i, 1); + } + } else { + LOG_WARN("can't init equalizer"); + } +} + +/**************************************************************************************** + * close equalizer + */ +void equalizer_close(void) { + if (equalizer.handle) { + esp_equalizer_uninit(equalizer.handle); + equalizer.handle = NULL; + } +} + +/**************************************************************************************** + * update equalizer gain + */ +void equalizer_update(s8_t *gain) { + for (int i = 0; i < EQ_BANDS; i++) equalizer.gain[i] = gain[i]; + equalizer.update = true; +} + +/**************************************************************************************** + * process equalizer + */ +void equalizer_process(u8_t *buf, u32_t bytes, u32_t sample_rate) { + // don't want to process with output locked, so tak ethe small risk to miss one parametric update + if (equalizer.update) { + equalizer_close(); + equalizer_open(sample_rate); + } + + if (equalizer.handle) { + esp_equalizer_process(equalizer.handle, buf, bytes, sample_rate, 2); + } +} \ No newline at end of file diff --git a/components/squeezelite/equalizer.h b/components/squeezelite/equalizer.h new file mode 100644 index 00000000..c67ac328 --- /dev/null +++ b/components/squeezelite/equalizer.h @@ -0,0 +1,26 @@ +/* + * Squeezelite for esp32 + * + * (c) Philippe G. 2020, philippe_44@outlook.com + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#pragma once + +void equalizer_open(u32_t sample_rate); +void equalizer_close(void); +void equalizer_update(s8_t *gain); +void equalizer_process(u8_t *buf, u32_t bytes, u32_t sample_rate); diff --git a/components/squeezelite/output_bt.c b/components/squeezelite/output_bt.c index 72cf6d39..2def7c9c 100644 --- a/components/squeezelite/output_bt.c +++ b/components/squeezelite/output_bt.c @@ -21,6 +21,7 @@ #include "driver/gpio.h" #include "squeezelite.h" +#include "equalizer.h" #include "perf_trace.h" #include "config.h" @@ -84,6 +85,7 @@ void output_close_bt(void) { running = false; UNLOCK; hal_bluetooth_stop(); + equalizer_close(); } static int _write_frames(frames_t out_frames, bool silence, s32_t gainL, s32_t gainR, @@ -155,6 +157,8 @@ int32_t output_bt_data(uint8_t *data, int32_t len) { SET_MIN_MAX(wanted_len, under); } output.frames_in_process = len-wanted_len; + + equalizer_process(data, (len - wanted_len) * BYTES_PER_FRAME, output.current_sample_rate); UNLOCK; SET_MIN_MAX(TIME_MEASUREMENT_GET(start_timer),lock_out_time); diff --git a/components/squeezelite/output_embedded.c b/components/squeezelite/output_embedded.c index cfd1a544..d66e2284 100644 --- a/components/squeezelite/output_embedded.c +++ b/components/squeezelite/output_embedded.c @@ -19,10 +19,13 @@ * */ #include "squeezelite.h" +#include "equalizer.h" extern struct outputstate output; extern struct buffer *outputbuf; +static bool (*slimp_handler_chain)(u8_t *data, int len); + #define FRAME_BLOCK MAX_SILENCE_FRAMES #define LOCK mutex_lock(outputbuf->mutex) @@ -47,11 +50,39 @@ static log_level loglevel; static bool (*volume_cb)(unsigned left, unsigned right); static void (*close_cb)(void); +#pragma pack(push, 1) +struct eqlz_packet { + char opcode[4]; +}; +#pragma pack(pop) + +static bool handler(u8_t *data, int len){ + bool res = true; + + if (!strncmp((char*) data, "eqlz", 4)) { + s8_t *gain = (s8_t*) (data + sizeof(struct eqlz_packet)); + LOG_INFO("got equalizer %d", len); + // update will be done at next opportunity + equalizer_update(gain); + } else { + res = false; + } + + // chain protocol handlers (bitwise or is fine) + if (*slimp_handler_chain) res |= (*slimp_handler_chain)(data, len); + + return res; +} + void output_init_embedded(log_level level, char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay, unsigned idle) { loglevel = level; LOG_INFO("init device: %s", device); + // chain handlers + slimp_handler_chain = slimp_handler; + slimp_handler = handler; + memset(&output, 0, sizeof(output)); output_init_common(level, device, output_buf_size, rates, idle); output.start_frames = FRAME_BLOCK; diff --git a/components/squeezelite/output_i2s.c b/components/squeezelite/output_i2s.c index a56ee7fe..e57a6abf 100644 --- a/components/squeezelite/output_i2s.c +++ b/components/squeezelite/output_i2s.c @@ -53,6 +53,7 @@ sure that using rate_delay would fix that #include "monitor.h" #include "config.h" #include "accessors.h" +#include "equalizer.h" #include "globdefs.h" #define LOCK mutex_lock(outputbuf->mutex) @@ -137,7 +138,7 @@ static void jack_handler(bool inserted) { /**************************************************************************************** * amp GPIO */ -void set_amp_gpio(int gpio, char *value) { +static void set_amp_gpio(int gpio, char *value) { if (!strcasecmp(value, "amp")) { amp_gpio = gpio; @@ -314,6 +315,8 @@ void output_close_i2s(void) { i2s_driver_uninstall(CONFIG_I2S_NUM); free(obuf); + equalizer_close(); + adac->deinit(); } @@ -483,9 +486,15 @@ static void *output_thread_i2s(void *arg) { i2s_config.sample_rate = output.current_sample_rate; i2s_set_sample_rates(CONFIG_I2S_NUM, spdif ? i2s_config.sample_rate * 2 : i2s_config.sample_rate); i2s_zero_dma_buffer(CONFIG_I2S_NUM); + + equalizer_close(); + equalizer_open(output.current_sample_rate); //return; } + // run equalizer + equalizer_process(obuf, oframes * bytes_per_frame, output.current_sample_rate); + // we assume that here we have been able to entirely fill the DMA buffers if (spdif) { spdif_convert((ISAMPLE_T*) obuf, oframes, (u32_t*) sbuf, &count); diff --git a/plugin/SqueezeESP32.zip b/plugin/SqueezeESP32.zip index 329291ce..605a0139 100644 Binary files a/plugin/SqueezeESP32.zip and b/plugin/SqueezeESP32.zip differ diff --git a/plugin/SqueezeESP32/Player.pm b/plugin/SqueezeESP32/Player.pm index 7e638201..a2024151 100644 --- a/plugin/SqueezeESP32/Player.pm +++ b/plugin/SqueezeESP32/Player.pm @@ -15,6 +15,12 @@ sub hasIR { 0 } sub init { my $client = shift; + + $prefs->client($client)->init( { + eq => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + } + ); + $client->SUPER::init(@_); Plugins::SqueezeESP32::Plugin::config_artwork($client); } @@ -55,6 +61,7 @@ sub reconnect { my $client = shift; $client->pluginData('artwork_md5', ''); $client->SUPER::reconnect(@_); + Plugins::SqueezeESP32::Plugin::send_equalizer($client); } 1; diff --git a/plugin/SqueezeESP32/PlayerSettings.pm b/plugin/SqueezeESP32/PlayerSettings.pm index ce41c608..86bc1929 100644 --- a/plugin/SqueezeESP32/PlayerSettings.pm +++ b/plugin/SqueezeESP32/PlayerSettings.pm @@ -30,7 +30,7 @@ sub page { sub prefs { my ($class, $client) = @_; - my @prefs = qw(width small_VU spectrum artwork); + my @prefs = qw(width small_VU spectrum artwork eq); return ($prefs->client($client), @prefs); } @@ -55,6 +55,13 @@ sub handler { $client->display->modes($client->display->build_modes); $client->display->update; + my $eq = $cprefs->get('eq'); + for my $i (0 .. $#{$eq}) { + $eq->[$i] = $paramRef->{"pref_eq.$i"}; + } + $cprefs->set('eq', $eq); + Plugins::SqueezeESP32::Plugin::send_equalizer($client); + # force update or disable artwork if ($artwork->{'enable'}) { Plugins::SqueezeESP32::Plugin::update_artwork($client, 1); @@ -72,6 +79,7 @@ sub handler { # logic of "Settings" is beyond me and I really hate it $paramRef->{'pref_spectrum'} = $cprefs->get('spectrum'); $paramRef->{'pref_artwork'} = $cprefs->get('artwork'); + $paramRef->{'pref_eq'} = $cprefs->get('eq'); return $class->SUPER::handler($client, $paramRef); } diff --git a/plugin/SqueezeESP32/Plugin.pm b/plugin/SqueezeESP32/Plugin.pm index dd9aee47..3c2ad5b5 100644 --- a/plugin/SqueezeESP32/Plugin.pm +++ b/plugin/SqueezeESP32/Plugin.pm @@ -56,8 +56,6 @@ sub onNotification { my $client = $request->client; my $reqstr = $request->getRequestString(); - $log->info("artwork update notification $reqstr"); - #my $path = $request->getParam('_path'); update_artwork($client); } @@ -108,6 +106,14 @@ sub send_artwork { } } +sub send_equalizer { + my ($client) = @_; + my $equalizer = $prefs->client($client)->get('eq'); + my $size = @$equalizer; + my $data = pack("c[$size]", @{$equalizer}); + $client->sendFrame( eqlz => \$data ); +} + sub config_artwork { my ($client) = @_; my $artwork = $prefs->client($client)->get('artwork'); diff --git a/plugin/SqueezeESP32/install.xml b/plugin/SqueezeESP32/install.xml index f1dbce20..2126e68f 100644 --- a/plugin/SqueezeESP32/install.xml +++ b/plugin/SqueezeESP32/install.xml @@ -10,6 +10,6 @@ PLUGIN_SQUEEZEESP32 PLUGIN_SQUEEZEESP32_DESC Plugins::SqueezeESP32::Plugin - 0.72 + 0.80 Philippe diff --git a/plugin/SqueezeESP32/strings.txt b/plugin/SqueezeESP32/strings.txt index d5ed82c2..5c03547c 100644 --- a/plugin/SqueezeESP32/strings.txt +++ b/plugin/SqueezeESP32/strings.txt @@ -14,7 +14,7 @@ PLUGIN_SQUEEZEESP32_DESC EN Adds a new player id (100) to enable display with SqueezeESP32 PLUGIN_SQUEEZEESP32_PLAYERSETTINGS - EN Display (ESP32) + EN ESP32 settings PLUGIN_SQUEEZEESP32_WIDTH EN Screen width @@ -69,3 +69,7 @@ PLUGIN_SQUEEZEESP32_ARTWORK_X PLUGIN_SQUEEZEESP32_ARTWORK_Y EN Y + +PLUGIN_SQUEEZEESP32_EQUALIZER + EN Parametric equalizer + diff --git a/plugin/repo.xml b/plugin/repo.xml index ffb006d5..36a5ec2f 100644 --- a/plugin/repo.xml +++ b/plugin/repo.xml @@ -1,10 +1,10 @@ - + https://github.com/sle118/squeezelite-esp32 Philippe - 50679aff5e938359342d3a4d8251dcd25fab3eff + 91e29c4380ce11728692f1cdabab080d1924a8d3 philippe_44@outlook.com SqueezeESP32 additional player id (100) http://github.com/sle118/squeezelite-esp32/raw/master/plugin/SqueezeESP32.zip