mirror of
https://github.com/sle118/squeezelite-esp32.git
synced 2025-12-09 21:17:18 +03:00
big merge
This commit is contained in:
@@ -8,26 +8,18 @@ idf_component_register( SRC_DIRS . external ac101 tas57xx wm8978
|
||||
platform_config
|
||||
driver_bt
|
||||
services
|
||||
spotify
|
||||
raop
|
||||
display
|
||||
tools
|
||||
audio
|
||||
EMBED_FILES vu.data
|
||||
EMBED_FILES vu_s.data arrow.data
|
||||
)
|
||||
|
||||
set_source_files_properties(mad.c
|
||||
set_source_files_properties(mad.c pcm.c flac.c alac.c helix-aac.c vorbis.c opus.c
|
||||
PROPERTIES COMPILE_FLAGS
|
||||
-Wno-maybe-uninitialized
|
||||
)
|
||||
set_source_files_properties(pcm.c
|
||||
PROPERTIES COMPILE_FLAGS
|
||||
-Wno-maybe-uninitialized
|
||||
)
|
||||
|
||||
set_source_files_properties(flac.c
|
||||
PROPERTIES COMPILE_FLAGS
|
||||
-Wno-maybe-uninitialized
|
||||
)
|
||||
|
||||
add_definitions(-DLINKALL -DLOOPBACK -DNO_FAAD -DEMBEDDED -DTREMOR_ONLY -DCUSTOM_VERSION=${BUILD_NUMBER})
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ extern const struct adac_s dac_external;
|
||||
|
||||
int adac_init(char *config, int i2c_port);
|
||||
void adac_deinit(void);
|
||||
esp_err_t adac_write(int i2c_addr, uint8_t reg, uint8_t *data, size_t count);
|
||||
esp_err_t adac_write_byte(int i2c_addr, uint8_t reg, uint8_t val);
|
||||
esp_err_t adac_write_word(int i2c_addr, uint8_t reg, uint16_t val);
|
||||
uint8_t adac_read_byte(int i2c_addr, uint8_t reg);
|
||||
|
||||
@@ -17,6 +17,11 @@
|
||||
#include "esp_log.h"
|
||||
#include "adac.h"
|
||||
|
||||
#define PARSE_PARAM(S,P,C,V) do { \
|
||||
char *__p; \
|
||||
if ((__p = strcasestr(S, P)) && (__p = strchr(__p, C))) V = atoi(__p+1); \
|
||||
} while (0)
|
||||
|
||||
static const char TAG[] = "DAC core";
|
||||
static int i2c_port = -1;
|
||||
|
||||
@@ -46,9 +51,9 @@ int adac_init(char *config, int i2c_port_num) {
|
||||
.master.clk_speed = 250000,
|
||||
};
|
||||
|
||||
if ((p = strcasestr(config, "i2c")) != NULL) i2c_addr = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(config, "sda")) != NULL) i2c_config.sda_io_num = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(config, "scl")) != NULL) i2c_config.scl_io_num = atoi(strchr(p, '=') + 1);
|
||||
PARSE_PARAM(config, "i2c", '=', i2c_addr);
|
||||
PARSE_PARAM(config, "sda", '=', i2c_config.sda_io_num);
|
||||
PARSE_PARAM(config, "scl", '=', i2c_config.scl_io_num);
|
||||
|
||||
if (i2c_config.sda_io_num == -1 || i2c_config.scl_io_num == -1) {
|
||||
ESP_LOGW(TAG, "DAC does not use i2c");
|
||||
@@ -150,8 +155,7 @@ uint16_t adac_read_word(int i2c_addr, uint8_t reg) {
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
esp_err_t adac_write_word(int i2c_addr, uint8_t reg, uint16_t val)
|
||||
{
|
||||
esp_err_t adac_write_word(int i2c_addr, uint8_t reg, uint16_t val) {
|
||||
uint8_t data[] = { i2c_addr << 1, reg,
|
||||
val >> 8, val & 0xff };
|
||||
|
||||
@@ -169,4 +173,26 @@ esp_err_t adac_write_word(int i2c_addr, uint8_t reg, uint16_t val)
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
esp_err_t adac_write(int i2c_addr, uint8_t reg, uint8_t *data, size_t count) {
|
||||
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
|
||||
i2c_master_start(cmd);
|
||||
|
||||
i2c_master_write_byte(cmd, (i2c_addr << 1) | I2C_MASTER_WRITE, I2C_MASTER_NACK);
|
||||
i2c_master_write_byte(cmd, reg, I2C_MASTER_NACK);
|
||||
i2c_master_write(cmd, data, count, I2C_MASTER_NACK);
|
||||
|
||||
i2c_master_stop(cmd);
|
||||
esp_err_t ret = i2c_master_cmd_begin(i2c_port, cmd, 200 / portTICK_RATE_MS);
|
||||
i2c_cmd_link_delete(cmd);
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGW(TAG, "I2C write failed");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
BIN
components/squeezelite/arrow.data
Normal file
BIN
components/squeezelite/arrow.data
Normal file
Binary file not shown.
@@ -73,13 +73,11 @@ void _buf_flush(struct buffer *buf) {
|
||||
|
||||
// adjust buffer to multiple of mod bytes so reading in multiple always wraps on frame boundary
|
||||
void buf_adjust(struct buffer *buf, size_t mod) {
|
||||
size_t size;
|
||||
mutex_lock(buf->mutex);
|
||||
size = ((unsigned)(buf->base_size / mod)) * mod;
|
||||
buf->readp = buf->buf;
|
||||
buf->writep = buf->buf;
|
||||
buf->wrap = buf->buf + size;
|
||||
buf->size = size;
|
||||
buf->base_size = ((size_t)(buf->size / mod)) * mod;
|
||||
buf->readp = buf->writep = buf->buf;
|
||||
buf->wrap = buf->buf + buf->base_size;
|
||||
buf->size = buf->base_size;
|
||||
mutex_unlock(buf->mutex);
|
||||
}
|
||||
|
||||
@@ -90,16 +88,23 @@ void _buf_resize(struct buffer *buf, size_t size) {
|
||||
buf->buf = malloc(size);
|
||||
if (!buf->buf) {
|
||||
size = buf->size;
|
||||
buf->buf= malloc(size);
|
||||
if (!buf->buf) {
|
||||
size = 0;
|
||||
}
|
||||
buf->buf = malloc(size);
|
||||
if (!buf->buf) size = 0;
|
||||
}
|
||||
buf->readp = buf->buf;
|
||||
buf->writep = buf->buf;
|
||||
buf->writep = buf->readp = buf->buf;
|
||||
buf->wrap = buf->buf + size;
|
||||
buf->size = size;
|
||||
buf->base_size = size;
|
||||
buf->true_size = buf->base_size = buf->size = size;
|
||||
}
|
||||
|
||||
size_t _buf_limit(struct buffer *buf, size_t limit) {
|
||||
if (limit) {
|
||||
buf->size = limit;
|
||||
buf->readp = buf->writep = buf->buf;
|
||||
} else {
|
||||
buf->size = buf->base_size;
|
||||
}
|
||||
buf->wrap = buf->buf + buf->size;
|
||||
return buf->base_size - buf->size;
|
||||
}
|
||||
|
||||
void _buf_unwrap(struct buffer *buf, size_t cont) {
|
||||
@@ -130,7 +135,9 @@ void _buf_unwrap(struct buffer *buf, size_t cont) {
|
||||
if (len > by) {
|
||||
memmove(buf->buf, buf->buf + by, len - by);
|
||||
buf->writep -= by;
|
||||
} else buf->writep += buf->size - by;
|
||||
} else {
|
||||
buf->writep += buf->size - by;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -157,8 +164,7 @@ void buf_init(struct buffer *buf, size_t size) {
|
||||
buf->readp = buf->buf;
|
||||
buf->writep = buf->buf;
|
||||
buf->wrap = buf->buf + size;
|
||||
buf->size = size;
|
||||
buf->base_size = size;
|
||||
buf->true_size = buf->base_size = buf->size = size;
|
||||
mutex_create_p(buf->mutex);
|
||||
}
|
||||
|
||||
@@ -166,8 +172,7 @@ void buf_destroy(struct buffer *buf) {
|
||||
if (buf->buf) {
|
||||
free(buf->buf);
|
||||
buf->buf = NULL;
|
||||
buf->size = 0;
|
||||
buf->base_size = 0;
|
||||
buf->size = buf->base_size = buf->true_size = 0;
|
||||
mutex_destroy(buf->mutex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,7 +218,6 @@ static void notify(in_addr_t ip, u16_t hport, u16_t cport) {
|
||||
|
||||
// close existing CLI connection and open new one
|
||||
if (cli_sock >= 0) closesocket(cli_sock);
|
||||
cli_sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||
connect_cli_socket();
|
||||
|
||||
LOG_INFO("notified server %s hport %hu cport %hu", inet_ntoa(ip), hport, cport);
|
||||
|
||||
@@ -11,24 +11,20 @@
|
||||
|
||||
#include "platform_config.h"
|
||||
#include "squeezelite.h"
|
||||
#include "bt_app_sink.h"
|
||||
#include "raop_sink.h"
|
||||
#include <math.h>
|
||||
|
||||
#define LOCK_O mutex_lock(outputbuf->mutex)
|
||||
#define UNLOCK_O mutex_unlock(outputbuf->mutex)
|
||||
#define LOCK_D mutex_lock(decode.mutex);
|
||||
#define UNLOCK_D mutex_unlock(decode.mutex);
|
||||
|
||||
enum { DECODE_BT = 1, DECODE_RAOP };
|
||||
|
||||
extern struct outputstate output;
|
||||
extern struct decodestate decode;
|
||||
extern struct buffer *outputbuf;
|
||||
// this is the only system-wide loglevel variable
|
||||
extern log_level loglevel;
|
||||
|
||||
#if CONFIG_BT_SINK
|
||||
#include "bt_app_sink.h"
|
||||
static bool enable_bt_sink;
|
||||
#endif
|
||||
|
||||
#if CONFIG_CSPOT_SINK
|
||||
#include "cspot_sink.h"
|
||||
static bool enable_cspot;
|
||||
#endif
|
||||
|
||||
#if CONFIG_AIRPLAY_SINK
|
||||
#include "raop_sink.h"
|
||||
static bool enable_airplay;
|
||||
|
||||
#define RAOP_OUTPUT_SIZE (((RAOP_SAMPLE_RATE * BYTES_PER_FRAME * 2 * 120) / 100) & ~BYTES_PER_FRAME)
|
||||
@@ -44,6 +40,22 @@ static EXT_RAM_ATTR struct {
|
||||
s32_t len;
|
||||
u32_t start_time, playtime;
|
||||
} raop_sync;
|
||||
#endif
|
||||
|
||||
static bool abort_sink ;
|
||||
|
||||
#define LOCK_O mutex_lock(outputbuf->mutex)
|
||||
#define UNLOCK_O mutex_unlock(outputbuf->mutex)
|
||||
#define LOCK_D mutex_lock(decode.mutex);
|
||||
#define UNLOCK_D mutex_unlock(decode.mutex);
|
||||
|
||||
enum { DECODE_BT = 1, DECODE_RAOP, DECODE_CSPOT };
|
||||
|
||||
extern struct outputstate output;
|
||||
extern struct decodestate decode;
|
||||
extern struct buffer *outputbuf;
|
||||
// this is the only system-wide loglevel variable
|
||||
extern log_level loglevel;
|
||||
|
||||
/****************************************************************************************
|
||||
* Common sink data handler
|
||||
@@ -58,11 +70,12 @@ static void sink_data_handler(const uint8_t *data, uint32_t len)
|
||||
LOG_SDEBUG("Cannot use external sink while LMS is controlling player");
|
||||
return;
|
||||
}
|
||||
|
||||
// there will always be room at some point
|
||||
while (len) {
|
||||
LOCK_O;
|
||||
|
||||
LOCK_O;
|
||||
abort_sink = false;
|
||||
|
||||
// there will always be room at some point
|
||||
while (len && wait && !abort_sink) {
|
||||
bytes = min(_buf_space(outputbuf), _buf_cont_write(outputbuf)) / (BYTES_PER_FRAME / 4);
|
||||
bytes = min(len, bytes);
|
||||
#if BYTES_PER_FRAME == 4
|
||||
@@ -81,11 +94,14 @@ static void sink_data_handler(const uint8_t *data, uint32_t len)
|
||||
len -= bytes;
|
||||
data += bytes;
|
||||
|
||||
UNLOCK_O;
|
||||
|
||||
// allow i2s to empty the buffer if needed
|
||||
if (len && !space && wait--) usleep(20000);
|
||||
if (len && !space) {
|
||||
wait--;
|
||||
UNLOCK_O; usleep(50000); LOCK_O;
|
||||
}
|
||||
}
|
||||
|
||||
UNLOCK_O;
|
||||
|
||||
if (!wait) {
|
||||
LOG_WARN("Waited too long, dropping frames");
|
||||
@@ -95,12 +111,12 @@ static void sink_data_handler(const uint8_t *data, uint32_t len)
|
||||
/****************************************************************************************
|
||||
* BT sink command handler
|
||||
*/
|
||||
|
||||
#if CONFIG_BT_SINK
|
||||
static bool bt_sink_cmd_handler(bt_sink_cmd_t cmd, va_list args)
|
||||
{
|
||||
// don't LOCK_O as there is always a chance that LMS takes control later anyway
|
||||
if (output.external != DECODE_BT && output.state > OUTPUT_STOPPED) {
|
||||
LOG_WARN("Cannot use BT sink while LMS/AirPlay is controlling player");
|
||||
LOG_WARN("Cannot use BT sink while LMS/AirPlay/CSpot are controlling player");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -110,11 +126,12 @@ static bool bt_sink_cmd_handler(bt_sink_cmd_t cmd, va_list args)
|
||||
|
||||
switch(cmd) {
|
||||
case BT_SINK_AUDIO_STARTED:
|
||||
_buf_flush(outputbuf);
|
||||
_buf_limit(outputbuf, 0);
|
||||
output.next_sample_rate = output.current_sample_rate = va_arg(args, u32_t);
|
||||
output.external = DECODE_BT;
|
||||
output.state = OUTPUT_STOPPED;
|
||||
output.frames_played = 0;
|
||||
_buf_flush(outputbuf);
|
||||
if (decode.state != DECODE_STOPPED) decode.state = DECODE_ERROR;
|
||||
LOG_INFO("BT sink started");
|
||||
break;
|
||||
@@ -127,17 +144,18 @@ static bool bt_sink_cmd_handler(bt_sink_cmd_t cmd, va_list args)
|
||||
break;
|
||||
case BT_SINK_PLAY:
|
||||
output.state = OUTPUT_RUNNING;
|
||||
LOG_INFO("BT playing");
|
||||
LOG_INFO("BT play");
|
||||
break;
|
||||
case BT_SINK_STOP:
|
||||
_buf_flush(outputbuf);
|
||||
output.state = OUTPUT_STOPPED;
|
||||
output.stop_time = gettime_ms();
|
||||
LOG_INFO("BT stopped");
|
||||
abort_sink = true;
|
||||
LOG_INFO("BT stop");
|
||||
break;
|
||||
case BT_SINK_PAUSE:
|
||||
output.stop_time = gettime_ms();
|
||||
LOG_INFO("BT paused, just silence");
|
||||
LOG_INFO("BT pause, just silence");
|
||||
break;
|
||||
case BT_SINK_RATE:
|
||||
output.next_sample_rate = output.current_sample_rate = va_arg(args, u32_t);
|
||||
@@ -158,10 +176,12 @@ static bool bt_sink_cmd_handler(bt_sink_cmd_t cmd, va_list args)
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
/****************************************************************************************
|
||||
* raop sink data handler
|
||||
*/
|
||||
#if CONFIG_AIRPLAY_SINK
|
||||
static void raop_sink_data_handler(const uint8_t *data, uint32_t len, u32_t playtime) {
|
||||
|
||||
raop_sync.playtime = playtime;
|
||||
@@ -177,7 +197,7 @@ static bool raop_sink_cmd_handler(raop_event_t event, va_list args)
|
||||
{
|
||||
// don't LOCK_O as there is always a chance that LMS takes control later anyway
|
||||
if (output.external != DECODE_RAOP && output.state > OUTPUT_STOPPED) {
|
||||
LOG_WARN("Cannot use Airplay sink while LMS/BT is controlling player");
|
||||
LOG_WARN("Cannot use Airplay sink while LMS/BT/CSpot are controlling player");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -237,15 +257,21 @@ static bool raop_sink_cmd_handler(raop_event_t event, va_list args)
|
||||
|
||||
break;
|
||||
}
|
||||
case RAOP_SETUP:
|
||||
// we need a fair bit of space for RTP process
|
||||
_buf_resize(outputbuf, RAOP_OUTPUT_SIZE);
|
||||
case RAOP_SETUP: {
|
||||
uint8_t **buffer = va_arg(args, uint8_t**);
|
||||
size_t *size = va_arg(args, size_t*);
|
||||
|
||||
// steal buffer tail from outputbuf but do not reallocate
|
||||
*size = _buf_limit(outputbuf, RAOP_OUTPUT_SIZE);
|
||||
*buffer = outputbuf->writep + RAOP_OUTPUT_SIZE;
|
||||
|
||||
output.frames_played = 0;
|
||||
output.external = DECODE_RAOP;
|
||||
output.state = OUTPUT_STOPPED;
|
||||
if (decode.state != DECODE_STOPPED) decode.state = DECODE_ERROR;
|
||||
LOG_INFO("resizing buffer %u", outputbuf->size);
|
||||
break;
|
||||
}
|
||||
case RAOP_STREAM:
|
||||
LOG_INFO("Stream", NULL);
|
||||
raop_state = event;
|
||||
@@ -257,11 +283,11 @@ static bool raop_sink_cmd_handler(raop_event_t event, va_list args)
|
||||
break;
|
||||
case RAOP_STOP:
|
||||
case RAOP_FLUSH:
|
||||
if (event == RAOP_FLUSH) { LOG_INFO("Flush", NULL); }
|
||||
else { LOG_INFO("Stop", NULL); }
|
||||
LOG_INFO("%s", event == RAOP_FLUSH ? "Flush" : "Stop");
|
||||
_buf_flush(outputbuf);
|
||||
raop_state = event;
|
||||
_buf_flush(outputbuf);
|
||||
if (output.state > OUTPUT_STOPPED) output.state = OUTPUT_STOPPED;
|
||||
abort_sink = true;
|
||||
output.frames_played = 0;
|
||||
output.stop_time = gettime_ms();
|
||||
break;
|
||||
@@ -292,6 +318,83 @@ static bool raop_sink_cmd_handler(raop_event_t event, va_list args)
|
||||
UNLOCK_D;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
/****************************************************************************************
|
||||
* cspot sink command handler
|
||||
*/
|
||||
#if CONFIG_CSPOT_SINK
|
||||
static bool cspot_cmd_handler(cspot_event_t cmd, va_list args)
|
||||
{
|
||||
// don't LOCK_O as there is always a chance that LMS takes control later anyway
|
||||
if (output.external != DECODE_CSPOT && output.state > OUTPUT_STOPPED) {
|
||||
LOG_WARN("Cannot use CSpot sink while LMS/BT/Airplay are controlling player");
|
||||
return false;
|
||||
}
|
||||
|
||||
LOCK_D;
|
||||
|
||||
if (cmd != CSPOT_VOLUME) LOCK_O;
|
||||
|
||||
switch(cmd) {
|
||||
case CSPOT_SETUP:
|
||||
output.current_sample_rate = output.next_sample_rate = va_arg(args, u32_t);
|
||||
output.external = DECODE_CSPOT;
|
||||
output.frames_played = 0;
|
||||
output.state = OUTPUT_STOPPED;
|
||||
_buf_flush(outputbuf);
|
||||
_buf_limit(outputbuf, 0);
|
||||
if (decode.state != DECODE_STOPPED) decode.state = DECODE_ERROR;
|
||||
LOG_INFO("CSpot connected");
|
||||
break;
|
||||
case CSPOT_DISC:
|
||||
_buf_flush(outputbuf);
|
||||
abort_sink = true;
|
||||
output.state = OUTPUT_STOPPED;
|
||||
output.stop_time = gettime_ms();
|
||||
LOG_INFO("CSpot disconnected");
|
||||
break;
|
||||
case CSPOT_TRACK:
|
||||
LOG_INFO("CSpot sink new track rate %d", output.next_sample_rate);
|
||||
break;
|
||||
case CSPOT_PLAY:
|
||||
output.state = OUTPUT_RUNNING;
|
||||
LOG_INFO("CSpot play");
|
||||
break;
|
||||
case CSPOT_SEEK:
|
||||
//TODO: can it be merged with flush (shall we stop)
|
||||
_buf_flush(outputbuf);
|
||||
abort_sink = true;
|
||||
LOG_INFO("CSpot seek by %d", va_arg(args, int));
|
||||
break;
|
||||
case CSPOT_FLUSH:
|
||||
_buf_flush(outputbuf);
|
||||
abort_sink = true;
|
||||
__attribute__ ((fallthrough));
|
||||
case CSPOT_PAUSE:
|
||||
output.state = OUTPUT_STOPPED;
|
||||
output.stop_time = gettime_ms();
|
||||
LOG_INFO("CSpot pause/flush");
|
||||
break;
|
||||
case CSPOT_VOLUME: {
|
||||
u32_t volume = va_arg(args, u32_t);
|
||||
LOG_INFO("CSpot volume %u", volume);
|
||||
//volume = 65536 * powf(volume / 32768.0f, 3);
|
||||
// TODO spotify seems to volume normalize crazy high
|
||||
volume = 4096 * powf(volume / 32768.0f, 3);
|
||||
set_volume(volume, volume);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (cmd != CSPOT_VOLUME) UNLOCK_O;
|
||||
UNLOCK_D;
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
/****************************************************************************************
|
||||
* We provide the generic codec register option
|
||||
@@ -299,16 +402,28 @@ static bool raop_sink_cmd_handler(raop_event_t event, va_list args)
|
||||
void register_external(void) {
|
||||
char *p;
|
||||
|
||||
#if CONFIG_BT_SINK
|
||||
if ((p = config_alloc_get(NVS_TYPE_STR, "enable_bt_sink")) != NULL) {
|
||||
enable_bt_sink = strcmp(p,"1") == 0 || strcasecmp(p,"y") == 0;
|
||||
free(p);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if CONFIG_AIRPLAY_SINK
|
||||
if ((p = config_alloc_get(NVS_TYPE_STR, "enable_airplay")) != NULL) {
|
||||
enable_airplay = strcmp(p,"1") == 0 || strcasecmp(p,"y") == 0;
|
||||
free(p);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if CONFIG_CSPOT_SINK
|
||||
if ((p = config_alloc_get(NVS_TYPE_STR, "enable_cspot")) != NULL) {
|
||||
enable_cspot = strcmp(p,"1") == 0 || strcasecmp(p,"y") == 0;
|
||||
free(p);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if CONFIG_BT_SINK
|
||||
if (!strcasestr(output.device, "BT ") ) {
|
||||
if(enable_bt_sink){
|
||||
bt_sink_init(bt_sink_cmd_handler, sink_data_handler);
|
||||
@@ -317,32 +432,61 @@ void register_external(void) {
|
||||
} else {
|
||||
LOG_WARN("Cannot be a BT sink and source");
|
||||
}
|
||||
#endif
|
||||
|
||||
#if CONFIG_AIRPLAY_SINK
|
||||
if (enable_airplay){
|
||||
raop_sink_init(raop_sink_cmd_handler, raop_sink_data_handler);
|
||||
LOG_INFO("Initializing AirPlay sink");
|
||||
}
|
||||
#endif
|
||||
|
||||
#if CONFIG_CSPOT_SINK
|
||||
if (enable_cspot){
|
||||
cspot_sink_init(cspot_cmd_handler, sink_data_handler);
|
||||
LOG_INFO("Initializing CSpot sink");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void deregister_external(void) {
|
||||
#if CONFIG_BT_SINK
|
||||
if (!strcasestr(output.device, "BT ") && enable_bt_sink) {
|
||||
LOG_INFO("Stopping BT sink");
|
||||
bt_sink_deinit();
|
||||
}
|
||||
#endif
|
||||
#if CONFIG_AIRPLAY_SINK
|
||||
if (enable_airplay){
|
||||
LOG_INFO("Stopping AirPlay sink");
|
||||
raop_sink_deinit();
|
||||
}
|
||||
#endif
|
||||
#if CONFIG_CSPOT_SINK
|
||||
if (enable_cspot){
|
||||
LOG_INFO("Stopping CSpot sink");
|
||||
cspot_sink_deinit();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void decode_restore(int external) {
|
||||
switch (external) {
|
||||
#if CONFIG_BT_SINK
|
||||
case DECODE_BT:
|
||||
bt_disconnect();
|
||||
break;
|
||||
#endif
|
||||
#if CONFIG_AIRPLAY_SINK
|
||||
case DECODE_RAOP:
|
||||
raop_disconnect();
|
||||
raop_state = RAOP_STOP;
|
||||
break;
|
||||
#endif
|
||||
#if CONFIG_CSPOT_SINK
|
||||
case DECODE_CSPOT:
|
||||
cspot_disconnect();
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,6 +146,7 @@ static uint32_t *grayMap;
|
||||
#define VU_WIDTH 160
|
||||
#define VU_HEIGHT SB_HEIGHT
|
||||
#define VU_COUNT 48
|
||||
#define ARROW_WIDTH 11
|
||||
|
||||
#define DISPLAY_BW 20000
|
||||
|
||||
@@ -210,7 +211,12 @@ static EXT_RAM_ATTR struct {
|
||||
struct bar_s bars[MAX_BARS] ;
|
||||
} led_visu;
|
||||
|
||||
extern const uint8_t vu_bitmap[] asm("_binary_vu_data_start");
|
||||
static EXT_RAM_ATTR uint8_t vu_bitmap[VU_WIDTH * VU_HEIGHT];
|
||||
extern const uint8_t vu_base[] asm("_binary_vu_s_data_start");
|
||||
extern const struct {
|
||||
uint8_t offset;
|
||||
uint8_t data[VU_HEIGHT * ARROW_WIDTH];
|
||||
} vu_arrow[VU_COUNT] asm("_binary_arrow_data_start");
|
||||
|
||||
#define ANIM_NONE 0x00
|
||||
#define ANIM_TRANSITION 0x01 // A transition animation has finished
|
||||
@@ -328,6 +334,9 @@ bool sb_displayer_init(void) {
|
||||
visu.bar_gap = 1;
|
||||
visu.back.frame = calloc(1, (displayer.width * displayer.height) / 8);
|
||||
|
||||
// prepare the VU raw data in PSRAM
|
||||
memcpy(vu_bitmap, vu_base, sizeof(vu_bitmap));
|
||||
|
||||
// size scroller (width + current screen)
|
||||
scroller.scroll.max = (displayer.width * displayer.height / 8) * (15 + 1);
|
||||
scroller.scroll.frame = malloc(scroller.scroll.max);
|
||||
@@ -601,9 +610,13 @@ static void vfdc_handler( u8_t *_data, int bytes_read) {
|
||||
/****************************************************************************************
|
||||
* Display VU-Meter (lots of hard-coding)
|
||||
*/
|
||||
void draw_VU(struct GDS_Device * display, const uint8_t *data, int level, int x, int y, int width, bool rotate) {
|
||||
void draw_VU(struct GDS_Device * display, int level, int x, int y, int width, bool rotate) {
|
||||
// VU data is by columns and vertical flip to allow block offset
|
||||
data += level * VU_WIDTH * VU_HEIGHT;
|
||||
uint8_t *data = vu_bitmap;
|
||||
int offset = level > 0 ? vu_arrow[level].offset * VU_HEIGHT : 0;
|
||||
|
||||
// place the arrow in base VU
|
||||
memcpy(data + offset, vu_arrow[level].data, sizeof(vu_arrow[level].data));
|
||||
|
||||
// adjust to current display window
|
||||
if (width > VU_WIDTH) {
|
||||
@@ -649,6 +662,9 @@ void draw_VU(struct GDS_Device * display, const uint8_t *data, int level, int x,
|
||||
}
|
||||
}
|
||||
|
||||
// restore base VU
|
||||
memcpy(vu_bitmap + offset, vu_base + offset, sizeof(vu_arrow[level].data));
|
||||
|
||||
// need to manually set dirty flag as DrawPixel does not do it
|
||||
GDS_SetDirty(display);
|
||||
}
|
||||
@@ -978,15 +994,15 @@ void visu_draw(void) {
|
||||
}
|
||||
} else if (displayer.width / 2 >= 3 * VU_WIDTH / 4) {
|
||||
if (visu.rotate) {
|
||||
draw_VU(display, vu_bitmap, visu.bars[0].current, 0, visu.row, visu.height / 2, visu.rotate);
|
||||
draw_VU(display, vu_bitmap, visu.bars[1].current, 0, visu.row + visu.height / 2, visu.height / 2, visu.rotate);
|
||||
draw_VU(display, visu.bars[0].current, 0, visu.row, visu.height / 2, visu.rotate);
|
||||
draw_VU(display, visu.bars[1].current, 0, visu.row + visu.height / 2, visu.height / 2, visu.rotate);
|
||||
} else {
|
||||
draw_VU(display, vu_bitmap, visu.bars[0].current, 0, visu.row, visu.width / 2, visu.rotate);
|
||||
draw_VU(display, vu_bitmap, visu.bars[1].current, visu.width / 2, visu.row, visu.width / 2, visu.rotate);
|
||||
draw_VU(display, visu.bars[0].current, 0, visu.row, visu.width / 2, visu.rotate);
|
||||
draw_VU(display, visu.bars[1].current, visu.width / 2, visu.row, visu.width / 2, visu.rotate);
|
||||
}
|
||||
} else {
|
||||
int level = (visu.bars[0].current + visu.bars[1].current) / 2;
|
||||
draw_VU(display, vu_bitmap, level, 0, visu.row, visu.rotate ? visu.height : visu.width, visu.rotate);
|
||||
draw_VU(display, level, 0, visu.row, visu.rotate ? visu.height : visu.width, visu.rotate);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
65
components/squeezelite/external/dac_external.c
vendored
65
components/squeezelite/external/dac_external.c
vendored
@@ -20,8 +20,8 @@
|
||||
|
||||
static const char TAG[] = "DAC external";
|
||||
|
||||
static void speaker(bool active) { }
|
||||
static void headset(bool active) { }
|
||||
static void speaker(bool active);
|
||||
static void headset(bool active);
|
||||
static bool volume(unsigned left, unsigned right) { return false; }
|
||||
static void power(adac_power_e mode);
|
||||
static bool init(char *config, int i2c_port_num, i2s_config_t *i2s_config);
|
||||
@@ -95,6 +95,22 @@ static void power(adac_power_e mode) {
|
||||
else i2c_json_execute("poweron");
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* speaker
|
||||
*/
|
||||
static void speaker(bool active) {
|
||||
if (active) i2c_json_execute("speakeron");
|
||||
else i2c_json_execute("speakeroff");
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* headset
|
||||
*/
|
||||
static void headset(bool active) {
|
||||
if (active) i2c_json_execute("headseton");
|
||||
else i2c_json_execute("headsetoff");
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
@@ -104,25 +120,40 @@ bool i2c_json_execute(char *set) {
|
||||
|
||||
if (!json_set) return true;
|
||||
|
||||
cJSON_ArrayForEach(item, json_set)
|
||||
{
|
||||
cJSON_ArrayForEach(item, json_set) {
|
||||
cJSON *reg = cJSON_GetObjectItemCaseSensitive(item, "reg");
|
||||
cJSON *val = cJSON_GetObjectItemCaseSensitive(item, "val");
|
||||
cJSON *mode = cJSON_GetObjectItemCaseSensitive(item, "mode");
|
||||
|
||||
if (cJSON_IsArray(val)) {
|
||||
cJSON *value;
|
||||
uint8_t *data = malloc(cJSON_GetArraySize(val));
|
||||
int count = 0;
|
||||
|
||||
if (!data) continue;
|
||||
|
||||
cJSON_ArrayForEach(value, val) {
|
||||
data[count++] = value->valueint;
|
||||
}
|
||||
|
||||
adac_write(i2c_addr, reg->valueint, data, count);
|
||||
free(data);
|
||||
} else {
|
||||
cJSON *mode = cJSON_GetObjectItemCaseSensitive(item, "mode");
|
||||
|
||||
if (!reg || !val) continue;
|
||||
if (!reg || !val) continue;
|
||||
|
||||
if (!mode) {
|
||||
adac_write_byte(i2c_addr, reg->valueint, val->valueint);
|
||||
} else if (!strcasecmp(mode->valuestring, "or")) {
|
||||
uint8_t data = adac_read_byte(i2c_addr,reg->valueint);
|
||||
data |= (uint8_t) val->valueint;
|
||||
adac_write_byte(i2c_addr, reg->valueint, data);
|
||||
} else if (!strcasecmp(mode->valuestring, "and")) {
|
||||
uint8_t data = adac_read_byte(i2c_addr, reg->valueint);
|
||||
data &= (uint8_t) val->valueint;
|
||||
adac_write_byte(i2c_addr, reg->valueint, data);
|
||||
}
|
||||
if (!mode) {
|
||||
adac_write_byte(i2c_addr, reg->valueint, val->valueint);
|
||||
} else if (!strcasecmp(mode->valuestring, "or")) {
|
||||
uint8_t data = adac_read_byte(i2c_addr,reg->valueint);
|
||||
data |= (uint8_t) val->valueint;
|
||||
adac_write_byte(i2c_addr, reg->valueint, data);
|
||||
} else if (!strcasecmp(mode->valuestring, "and")) {
|
||||
uint8_t data = adac_read_byte(i2c_addr, reg->valueint);
|
||||
data &= (uint8_t) val->valueint;
|
||||
adac_write_byte(i2c_addr, reg->valueint, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -354,7 +354,6 @@ void output_init_common(log_level level, const char *device, unsigned output_buf
|
||||
loglevel = level;
|
||||
|
||||
output_buf_size = output_buf_size - (output_buf_size % BYTES_PER_FRAME);
|
||||
output.init_size = output_buf_size;
|
||||
LOG_DEBUG("outputbuf size: %u", output_buf_size);
|
||||
|
||||
buf_init(outputbuf, output_buf_size);
|
||||
|
||||
@@ -81,12 +81,14 @@ void output_init_embedded(log_level level, char *device, unsigned output_buf_siz
|
||||
output.start_frames = FRAME_BLOCK;
|
||||
output.rate_delay = rate_delay;
|
||||
|
||||
|
||||
#if CONFIG_BT_SINK
|
||||
if (strcasestr(device, "BT ") || !strcasecmp(device, "BT")) {
|
||||
LOG_INFO("init Bluetooth");
|
||||
close_cb = &output_close_bt;
|
||||
output_init_bt(level, device, output_buf_size, params, rates, rate_delay, idle);
|
||||
} else {
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
LOG_INFO("init I2S/SPDIF");
|
||||
close_cb = &output_close_i2s;
|
||||
volume_cb = &output_volume_i2s;
|
||||
|
||||
@@ -43,6 +43,7 @@ sure that using rate_delay would fix that
|
||||
#include "led.h"
|
||||
#include "monitor.h"
|
||||
#include "platform_config.h"
|
||||
#include "gpio_exp.h"
|
||||
#include "accessors.h"
|
||||
#include "equalizer.h"
|
||||
#include "globdefs.h"
|
||||
@@ -129,8 +130,13 @@ static bool handler(u8_t *data, int len){
|
||||
jack_mutes_amp = pkt->config == 0;
|
||||
config_set_value(NVS_TYPE_STR, "jack_mutes_amp", jack_mutes_amp ? "y" : "n");
|
||||
|
||||
if (jack_mutes_amp && jack_inserted_svc()) adac->speaker(false);
|
||||
else adac->speaker(true);
|
||||
if (jack_mutes_amp && jack_inserted_svc()) {
|
||||
adac->speaker(false);
|
||||
if (amp_control.gpio != -1) gpio_set_level_x(amp_control.gpio, !amp_control.active);
|
||||
} else {
|
||||
adac->speaker(true);
|
||||
if (amp_control.gpio != -1) gpio_set_level_x(amp_control.gpio, amp_control.active);
|
||||
}
|
||||
}
|
||||
|
||||
LOG_INFO("got AUDO %02x", pkt->config);
|
||||
@@ -151,13 +157,12 @@ static void jack_handler(bool inserted) {
|
||||
// jack detection bounces a bit but that seems fine
|
||||
if (jack_mutes_amp) {
|
||||
LOG_INFO("switching amplifier %s", inserted ? "OFF" : "ON");
|
||||
if (inserted) adac->speaker(false);
|
||||
else adac->speaker(true);
|
||||
adac->speaker(!inserted);
|
||||
if (amp_control.gpio != -1) gpio_set_level_x(amp_control.gpio, inserted ? !amp_control.active : amp_control.active);
|
||||
}
|
||||
|
||||
// activate headset
|
||||
if (inserted) adac->headset(true);
|
||||
else adac->headset(false);
|
||||
adac->headset(inserted);
|
||||
|
||||
// and chain if any
|
||||
if (jack_handler_chain) (jack_handler_chain)(inserted);
|
||||
@@ -173,9 +178,9 @@ static void set_amp_gpio(int gpio, char *value) {
|
||||
amp_control.gpio = gpio;
|
||||
if ((p = strchr(value, ':')) != NULL) amp_control.active = atoi(p + 1);
|
||||
|
||||
gpio_pad_select_gpio(amp_control.gpio);
|
||||
gpio_set_direction(amp_control.gpio, GPIO_MODE_OUTPUT);
|
||||
gpio_set_level(amp_control.gpio, !amp_control.active);
|
||||
gpio_pad_select_gpio_x(amp_control.gpio);
|
||||
gpio_set_direction_x(amp_control.gpio, GPIO_MODE_OUTPUT);
|
||||
gpio_set_level_x(amp_control.gpio, !amp_control.active);
|
||||
|
||||
LOG_INFO("setting amplifier GPIO %d (active:%d)", amp_control.gpio, amp_control.active);
|
||||
}
|
||||
@@ -185,11 +190,10 @@ static void set_amp_gpio(int gpio, char *value) {
|
||||
* Set pin from config string
|
||||
*/
|
||||
static void set_i2s_pin(char *config, i2s_pin_config_t *pin_config) {
|
||||
char *p;
|
||||
pin_config->bck_io_num = pin_config->ws_io_num = pin_config->data_out_num = pin_config->data_in_num = -1;
|
||||
if ((p = strcasestr(config, "bck")) != NULL) pin_config->bck_io_num = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(config, "ws")) != NULL) pin_config->ws_io_num = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(config, "do")) != NULL) pin_config->data_out_num = atoi(strchr(p, '=') + 1);
|
||||
PARSE_PARAM(config, "bck", '=', pin_config->bck_io_num);
|
||||
PARSE_PARAM(config, "ws", '=', pin_config->ws_io_num);
|
||||
PARSE_PARAM(config, "do", '=', pin_config->data_out_num);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
@@ -343,12 +347,13 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
|
||||
jack_handler_chain = jack_handler_svc;
|
||||
jack_handler_svc = jack_handler;
|
||||
|
||||
parse_set_GPIO(set_amp_gpio);
|
||||
|
||||
if (jack_mutes_amp && jack_inserted_svc()) adac->speaker(false);
|
||||
else adac->speaker(true);
|
||||
|
||||
adac->headset(jack_inserted_svc());
|
||||
|
||||
parse_set_GPIO(set_amp_gpio);
|
||||
|
||||
// create task as a FreeRTOS task but uses stack in internal RAM
|
||||
{
|
||||
@@ -365,10 +370,11 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
|
||||
|
||||
// memory still used but at least task is not created
|
||||
if (stats) {
|
||||
static DRAM_ATTR StaticTask_t xTaskBuffer __attribute__ ((aligned (4)));
|
||||
// we allocate TCB but stack is static to avoid SPIRAM fragmentation
|
||||
StaticTask_t* xTaskBuffer = (StaticTask_t*) heap_caps_malloc(sizeof(StaticTask_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
|
||||
static EXT_RAM_ATTR StackType_t xStack[STAT_STACK_SIZE] __attribute__ ((aligned (4)));
|
||||
stats_task = xTaskCreateStatic( (TaskFunction_t) output_thread_i2s_stats, "output_i2s_sts", STAT_STACK_SIZE,
|
||||
NULL, ESP_TASK_PRIO_MIN, xStack, &xTaskBuffer);
|
||||
NULL, ESP_TASK_PRIO_MIN, xStack, xTaskBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -449,13 +455,16 @@ static void output_thread_i2s(void *arg) {
|
||||
LOG_INFO("Output state is %d", output.state);
|
||||
if (output.state == OUTPUT_OFF) {
|
||||
led_blink(LED_GREEN, 100, 2500);
|
||||
if (amp_control.gpio != -1) gpio_set_level(amp_control.gpio, !amp_control.active);
|
||||
if (amp_control.gpio != -1) gpio_set_level_x(amp_control.gpio, !amp_control.active);
|
||||
LOG_INFO("switching off amp GPIO %d", amp_control.gpio);
|
||||
} else if (output.state == OUTPUT_STOPPED) {
|
||||
adac->speaker(false);
|
||||
led_blink(LED_GREEN, 200, 1000);
|
||||
} else if (output.state == OUTPUT_RUNNING) {
|
||||
if (!jack_mutes_amp || !jack_inserted_svc()) adac->speaker(true);
|
||||
if (!jack_mutes_amp || !jack_inserted_svc()) {
|
||||
if (amp_control.gpio != -1) gpio_set_level_x(amp_control.gpio, amp_control.active);
|
||||
adac->speaker(true);
|
||||
}
|
||||
led_on(LED_GREEN);
|
||||
}
|
||||
}
|
||||
@@ -513,7 +522,6 @@ static void output_thread_i2s(void *arg) {
|
||||
i2s_zero_dma_buffer(CONFIG_I2S_NUM);
|
||||
i2s_start(CONFIG_I2S_NUM);
|
||||
adac->power(ADAC_ON);
|
||||
if (amp_control.gpio != -1) gpio_set_level(amp_control.gpio, amp_control.active);
|
||||
}
|
||||
|
||||
// this does not work well as set_sample_rates resets the fifos (and it's too early)
|
||||
|
||||
@@ -391,7 +391,7 @@ static void process_strm(u8_t *pkt, int len) {
|
||||
#if EMBEDDED
|
||||
if (output.external) decode_restore(output.external);
|
||||
output.external = 0;
|
||||
_buf_resize(outputbuf, output.init_size);
|
||||
_buf_limit(outputbuf, 0);
|
||||
#endif
|
||||
output.threshold = strm->output_threshold;
|
||||
output.next_replay_gain = unpackN(&strm->replay_gain);
|
||||
|
||||
@@ -532,6 +532,7 @@ struct buffer {
|
||||
u8_t *wrap;
|
||||
size_t size;
|
||||
size_t base_size;
|
||||
size_t true_size;
|
||||
mutex_type mutex;
|
||||
};
|
||||
|
||||
@@ -547,6 +548,7 @@ void _buf_flush(struct buffer *buf);
|
||||
void _buf_unwrap(struct buffer *buf, size_t cont);
|
||||
void buf_adjust(struct buffer *buf, size_t mod);
|
||||
void _buf_resize(struct buffer *buf, size_t size);
|
||||
size_t _buf_limit(struct buffer *buf, size_t limit);
|
||||
void buf_init(struct buffer *buf, size_t size);
|
||||
void buf_destroy(struct buffer *buf);
|
||||
|
||||
@@ -665,7 +667,6 @@ struct outputstate {
|
||||
u8_t channels;
|
||||
const char *device;
|
||||
int external;
|
||||
u32_t init_size;
|
||||
#if ALSA
|
||||
unsigned buffer;
|
||||
unsigned period;
|
||||
|
||||
BIN
components/squeezelite/vu.bmp
Normal file
BIN
components/squeezelite/vu.bmp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 241 KiB |
BIN
components/squeezelite/vu_s.data
Normal file
BIN
components/squeezelite/vu_s.data
Normal file
Binary file not shown.
Reference in New Issue
Block a user