Merge remote-tracking branch 'origin/master' into httpd

Conflicts:
	Makefile
	components/wifi-manager/index.html
This commit is contained in:
Sebastien
2020-02-19 08:14:49 -05:00
40 changed files with 1009 additions and 387 deletions

View File

@@ -117,7 +117,7 @@ static void displayer_task(void *args) {
if (displayer.state == DISPLAYER_IDLE) display->line(2, 0, DISPLAY_CLEAR | DISPLAY_UPDATE, displayer.string);
vTaskSuspend(NULL);
scroll_sleep = 0;
display->clear();
display->clear(true);
display->line(1, DISPLAY_LEFT, DISPLAY_UPDATE, displayer.header);
} else if (displayer.refresh) {
// little trick when switching master while in IDLE and missing it
@@ -301,16 +301,19 @@ void displayer_control(enum displayer_cmd_e cmd, ...) {
displayer.string[0] = '\0';
displayer.elapsed = displayer.duration = 0;
displayer.offset = displayer.boundary = 0;
display_bus(&displayer, DISPLAY_BUS_TAKE);
vTaskResume(displayer.task);
break;
}
case DISPLAYER_SUSPEND:
// task will display the line 2 from beginning and suspend
displayer.state = DISPLAYER_IDLE;
display_bus(&displayer, DISPLAY_BUS_GIVE);
break;
case DISPLAYER_SHUTDOWN:
// let the task self-suspend (we might be doing i2c_write)
displayer.state = DISPLAYER_DOWN;
display_bus(&displayer, DISPLAY_BUS_GIVE);
break;
case DISPLAYER_TIMER_RUN:
if (!displayer.timer) {

View File

@@ -28,6 +28,9 @@
So it can conflict with other display direct writes that have been made during
sleep. Note that if DISPLAY_SHUTDOWN has been called meanwhile, it (almost)
never happens
The display_bus() shall be subscribed by other displayers so that at least
when this one (the main) wants to take control over display, it can signal
that to others
*/
#define DISPLAY_CLEAR 0x01
@@ -51,8 +54,9 @@ enum displayer_time_e { DISPLAYER_ELAPSED, DISPLAYER_REMAINING };
// don't change anything there w/o changing all drivers init code
extern struct display_s {
int width, height;
bool dirty;
bool (*init)(char *config, char *welcome);
void (*clear)(void);
void (*clear)(bool full, ...);
bool (*set_font)(int num, enum display_font_e font, int space);
void (*on)(bool state);
void (*brightness)(uint8_t level);
@@ -60,11 +64,16 @@ extern struct display_s {
bool (*line)(int num, int x, int attribute, char *text);
int (*stretch)(int num, char *string, int max);
void (*update)(void);
void (*draw)(int x1, int y1, int x2, int y2, bool by_column, uint8_t *data);
void (*draw_cbr)(uint8_t *data, int height); // height is the # of columns in data, as oppoosed to display height (0 = display height)
void (*draw_raw)(int x1, int y1, int x2, int y2, bool by_column, bool MSb, uint8_t *data);
void (*draw_cbr)(uint8_t *data, int width, int height); // width and height is the # of rows/columns in data, as opposed to display height (0 = display width, 0 = display height)
void (*draw_line)(int x1, int y1, int x2, int y2);
void (*draw_box)( int x1, int y1, int x2, int y2, bool fill);
} *display;
enum display_bus_cmd_e { DISPLAY_BUS_TAKE, DISPLAY_BUS_GIVE };
bool (*display_bus)(void *from, enum display_bus_cmd_e cmd);
void displayer_scroll(char *string, int speed);
void displayer_control(enum displayer_cmd_e cmd, ...);
void displayer_metadata(char *artist, char *album, char *title);
void displayer_timer(enum displayer_time_e mode, int elapsed, int duration);
void displayer_timer(enum displayer_time_e mode, int elapsed, int duration);

View File

@@ -38,25 +38,26 @@ static const char *TAG = "display";
// handlers
static bool init(char *config, char *welcome);
static void clear(void);
static void clear(bool full, ...);
static bool set_font(int num, enum display_font_e font, int space);
static void text(enum display_font_e font, enum display_pos_e pos, int attribute, char *text, ...);
static bool line(int num, int x, int attribute, char *text);
static int stretch(int num, char *string, int max);
static void draw_cbr(u8_t *data, int height);
static void draw(int x1, int y1, int x2, int y2, bool by_column, u8_t *data);
static void draw_cbr(u8_t *data, int width, int height);
static void draw_raw(int x1, int y1, int x2, int y2, bool by_column, bool MSb, u8_t *data);
static void draw_line(int x1, int y1, int x2, int y2);
static void draw_box( int x1, int y1, int x2, int y2, bool fill);
static void brightness(u8_t level);
static void on(bool state);
static void update(void);
// display structure for others to use
struct display_s SSD13x6_display = { 0, 0,
struct display_s SSD13x6_display = { 0, 0, true,
init, clear, set_font, on, brightness,
text, line, stretch, update, draw, draw_cbr, NULL };
text, line, stretch, update, draw_raw, draw_cbr, draw_line, draw_box };
// SSD13x6 specific function
static struct SSD13x6_Device Display;
static SSD13x6_AddressMode AddressMode = AddressMode_Invalid;
static const unsigned char BitReverseTable256[] =
{
@@ -150,9 +151,24 @@ static bool init(char *config, char *welcome) {
/****************************************************************************************
*
*/
static void clear(void) {
SSD13x6_Clear( &Display, SSD_COLOR_BLACK );
SSD13x6_Update( &Display );
static void clear(bool full, ...) {
bool commit = true;
if (full) {
SSD13x6_Clear( &Display, SSD_COLOR_BLACK );
} else {
va_list args;
va_start(args, full);
commit = va_arg(args, int);
int x1 = va_arg(args, int), y1 = va_arg(args, int), x2 = va_arg(args, int), y2 = va_arg(args, int);
if (x2 < 0) x2 = display->width - 1;
if (y2 < 0) y2 = display->height - 1;
SSD13x6_ClearWindow( &Display, x1, y1, x2, y2, SSD_COLOR_BLACK );
va_end(args);
}
SSD13x6_display.dirty = true;
if (commit) update();
}
/****************************************************************************************
@@ -209,12 +225,6 @@ static bool line(int num, int x, int attribute, char *text) {
// counting 1..n
num--;
// always horizontal mode for text display
if (AddressMode != AddressMode_Horizontal) {
AddressMode = AddressMode_Horizontal;
SSD13x6_SetDisplayAddressMode( &Display, AddressMode );
}
SSD13x6_SetFont( &Display, lines[num].font );
if (attribute & DISPLAY_MONOSPACE) SSD13x6_FontForceMonospace( &Display, true );
@@ -237,8 +247,9 @@ static bool line(int num, int x, int attribute, char *text) {
ESP_LOGD(TAG, "displaying %s line %u (x:%d, attr:%u)", text, num+1, x, attribute);
// update whole display if requested
if (attribute & DISPLAY_UPDATE) SSD13x6_Update( &Display );
SSD13x6_display.dirty = true;
if (attribute & DISPLAY_UPDATE) update();
return width + x < Display.Width;
}
@@ -278,13 +289,14 @@ static int stretch(int num, char *string, int max) {
static void text(enum display_font_e font, enum display_pos_e pos, int attribute, char *text, ...) {
va_list args;
va_start(args, text);
TextAnchor Anchor = TextAnchor_Center;
if (attribute & DISPLAY_CLEAR) SSD13x6_Clear( &Display, SSD_COLOR_BLACK );
if (!text) return;
va_start(args, text);
switch(font) {
case DISPLAY_FONT_LINE_1:
SSD13x6_SetFont( &Display, &Font_line_1 );
@@ -327,127 +339,83 @@ static void text(enum display_font_e font, enum display_pos_e pos, int attribute
ESP_LOGD(TAG, "SSDD13x6 displaying %s at %u with attribute %u", text, Anchor, attribute);
if (AddressMode != AddressMode_Horizontal) {
AddressMode = AddressMode_Horizontal;
SSD13x6_SetDisplayAddressMode( &Display, AddressMode );
}
SSD13x6_FontDrawAnchoredString( &Display, Anchor, text, SSD_COLOR_WHITE );
if (attribute & DISPLAY_UPDATE) SSD13x6_Update( &Display );
SSD13x6_display.dirty = true;
if (attribute & DISPLAY_UPDATE) update();
va_end(args);
}
/****************************************************************************************
* Process graphic display data from column-oriented bytes, MSbit first
* Process graphic display data from column-oriented data (MSbit first)
*/
static void draw_cbr(u8_t *data, int height) {
#ifndef FULL_REFRESH
// force addressing mode by rows
if (AddressMode != AddressMode_Horizontal) {
AddressMode = AddressMode_Horizontal;
SSD13x6_SetDisplayAddressMode( &Display, AddressMode );
static void draw_cbr(u8_t *data, int width, int height) {
if (!height) height = Display.Height;
if (!width) width = Display.Width;
// need to do row/col swap and bit-reverse
int rows = height / 8;
for (int r = 0; r < rows; r++) {
uint8_t *optr = Display.Framebuffer + r*Display.Width, *iptr = data + r;
for (int c = width; --c >= 0;) {
*optr++ = BitReverseTable256[*iptr];;
iptr += rows;
}
}
// try to minimize I2C traffic which is very slow
int rows = (height ? height : Display.Height) / 8;
for (int r = 0; r < rows; r++) {
uint8_t first = 0, last;
uint8_t *optr = Display.Framebuffer + r*Display.Width, *iptr = data + r;
// row/col swap, frame buffer comparison and bit-reversing
for (int c = 0; c < Display.Width; c++) {
u8_t byte = BitReverseTable256[*iptr];
if (byte != *optr) {
if (!first) first = c + 1;
last = c ;
}
*optr++ = byte;
iptr += rows;
}
// now update the display by "byte rows"
if (first--) {
SSD13x6_SetColumnAddress( &Display, first, last );
SSD13x6_SetPageAddress( &Display, r, r);
SSD13x6_WriteRawData( &Display, Display.Framebuffer + r*Display.Width + first, last - first + 1);
}
}
#else
if (!height) height = Display->Height;
SSD13x6_SetPageAddress( &Display, 0, height / 8 - 1);
// force addressing mode by columns (if we can)
if (SSD13x6_GetCaps( &Display ) & CAPS_ADDRESS_VERTICAL) {
// just copy data in frame buffer with bit-reverse
for (int c = 0; c < Display.Width; c++)
for (int r = 0; r < height / 8; r++)
Display.Framebuffer[c*Display.Height/8 + r] = BitReverseTable256[data[c*height/8 +r]];
if (AddressMode != AddressMode_Vertical) {
AddressMode = AddressMode_Vertical;
SSD13x6_SetDisplayAddressMode( &Display, AddressMode );
}
} else {
// need to do rwo/col swap and bit-reverse
int rows = (height ? height : Display.Height) / 8;
for (int r = 0; r < rows; r++) {
uint8_t *optr = Display.Framebuffer + r*Display.Width, *iptr = data + r;
for (int c = 0; c < Display.Width; c++) {
*optr++ = BitReverseTable256[*iptr];;
iptr += rows;
}
}
ESP_LOGW(TAG, "Can't set addressing mode to vertical, swapping");
}
SSD13x6_WriteRawData(&Display, Display.Framebuffer, Display.Width * Display.Height/8);
#endif
SSD13x6_display.dirty = true;
}
/****************************************************************************************
* Process graphic display data MSBit first
* WARNING: this has not been tested yet
*/
static void draw(int x1, int y1, int x2, int y2, bool by_column, u8_t *data) {
if (y1 % 8 || y2 % 8) {
ESP_LOGW(TAG, "must write rows on byte boundaries (%u,%u) to (%u,%u)", x1, y1, x2, y2);
return;
}
static void draw_raw(int x1, int y1, int x2, int y2, bool by_column, bool MSb, u8_t *data) {
// default end point to display size
if (x2 == -1) x2 = Display.Width - 1;
if (y2 == -1) y2 = Display.Height - 1;
// set addressing mode to match data
if (by_column) {
if (AddressMode != AddressMode_Vertical) {
AddressMode = AddressMode_Vertical;
SSD13x6_SetDisplayAddressMode( &Display, AddressMode );
}
// copy the window and do row/col exchange
for (int r = y1/8; r <= y2/8; r++) {
uint8_t *optr = Display.Framebuffer + r*Display.Width + x1, *iptr = data + r;
for (int c = x1; c <= x2; c++) {
*optr++ = *iptr;
iptr += (y2-y1)/8 + 1;
display->dirty = true;
// not a boundary draw
if (y1 % 8 || y2 % 8 || x1 % 8 | x2 % 8) {
ESP_LOGW(TAG, "can't write on non cols/rows boundaries for now");
} else {
// set addressing mode to match data
if (by_column) {
// copy the window and do row/col exchange
for (int r = y1/8; r <= y2/8; r++) {
uint8_t *optr = Display.Framebuffer + r*Display.Width + x1, *iptr = data + r;
for (int c = x1; c <= x2; c++) {
*optr++ = MSb ? BitReverseTable256[*iptr] : *iptr;
iptr += (y2-y1)/8 + 1;
}
}
} else {
// just copy the window inside the frame buffer
for (int r = y1/8; r <= y2/8; r++) {
uint8_t *optr = Display.Framebuffer + r*Display.Width + x1, *iptr = data + r*(x2-x1+1);
for (int c = x1; c <= x2; c++) *optr++ = *iptr++;
}
}
SSD13x6_SetColumnAddress( &Display, x1, x2);
SSD13x6_SetPageAddress( &Display, y1/8, y2/8);
SSD13x6_WriteRawData( &Display, data, (x2-x1 + 1) * ((y2-y1)/8 + 1));
}
} else {
// just copy the window inside the frame buffer
for (int r = y1/8; r <= y2/8; r++) {
uint8_t *optr = Display.Framebuffer + r*Display.Width + x1, *iptr = data + r*(x2-x1+1);
for (int c = x1; c <= x2; c++) *optr++ = *iptr++;
}
}
}
}
/****************************************************************************************
* Draw line
*/
static void draw_line( int x1, int y1, int x2, int y2) {
SSD13x6_DrawLine( &Display, x1, y1, x2, y2, SSD_COLOR_WHITE );
SSD13x6_display.dirty = true;
}
/****************************************************************************************
* Draw Box
*/
static void draw_box( int x1, int y1, int x2, int y2, bool fill) {
SSD13x6_DrawBox( &Display, x1, y1, x2, y2, SSD_COLOR_WHITE, fill );
SSD13x6_display.dirty = true;
}
/****************************************************************************************
@@ -470,7 +438,8 @@ static void on(bool state) {
* Update
*/
static void update(void) {
SSD13x6_Update( &Display );
if (SSD13x6_display.dirty) SSD13x6_Update( &Display );
SSD13x6_display.dirty = false;
}

View File

@@ -15,6 +15,8 @@
#include "ssd13x6.h"
#define SHADOW_BUFFER
// used by both but different
static uint8_t SSDCmd_Set_Display_Start_Line;
static uint8_t SSDCmd_Set_Display_Offset;
@@ -111,8 +113,32 @@ void SSD13x6_SetDisplayAddressMode( struct SSD13x6_Device* DeviceHandle, SSD13x6
}
void SSD13x6_Update( struct SSD13x6_Device* DeviceHandle ) {
#ifdef SHADOW_BUFFER
// not sure the compiler does not have to redo all calculation in for loops, so local it is
int width = DeviceHandle->Width, rows = DeviceHandle->Height / 8;
uint8_t *optr = DeviceHandle->Shadowbuffer, *iptr = DeviceHandle->Framebuffer;
// by row, find first and last columns that have been updated
for (int r = 0; r < rows; r++) {
uint8_t first = 0, last;
for (int c = 0; c < width; c++) {
if (*iptr != *optr) {
if (!first) first = c + 1;
last = c ;
}
*optr++ = *iptr++;
}
// now update the display by "byte rows"
if (first--) {
SSD13x6_SetColumnAddress( DeviceHandle, first, last );
SSD13x6_SetPageAddress( DeviceHandle, r, r);
SSD13x6_WriteData( DeviceHandle, DeviceHandle->Shadowbuffer + r*width + first, last - first + 1);
}
}
#else
if (DeviceHandle->Model == SH1106) {
// SH1106 requires a page-by-page update and ahs no end Page/Column
// SH1106 requires a page-by-page update and has no end Page/Column
for (int i = 0; i < DeviceHandle->Height / 8 ; i++) {
SSD13x6_SetPageAddress( DeviceHandle, i, 0);
SSD13x6_SetColumnAddress( DeviceHandle, 0, 0);
@@ -124,6 +150,7 @@ void SSD13x6_Update( struct SSD13x6_Device* DeviceHandle ) {
SSD13x6_SetPageAddress( DeviceHandle, 0, DeviceHandle->Height / 8 - 1);
SSD13x6_WriteData( DeviceHandle, DeviceHandle->Framebuffer, DeviceHandle->FramebufferSize );
}
#endif
}
void SSD13x6_WriteRawData( struct SSD13x6_Device* DeviceHandle, uint8_t* Data, size_t DataLength ) {
@@ -214,6 +241,11 @@ static bool SSD13x6_Init( struct SSD13x6_Device* DeviceHandle, int Width, int He
DeviceHandle->Width = Width;
DeviceHandle->Height = Height;
#ifdef SHADOW_BUFFER
DeviceHandle->Shadowbuffer = heap_caps_malloc( DeviceHandle->FramebufferSize, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA );
memset( DeviceHandle->Shadowbuffer, 0xFF, DeviceHandle->FramebufferSize );
#endif
SSD13x6_HWReset( DeviceHandle );
SSD13x6_DisplayOff( DeviceHandle );
@@ -308,7 +340,11 @@ bool SSD13x6_Init_SPI( struct SSD13x6_Device* DeviceHandle, int Width, int Heigh
DeviceHandle->CSPin = CSPin;
DeviceHandle->FramebufferSize = ( Width * Height ) / 8;
#ifdef SHADOW_BUFFER
DeviceHandle->Framebuffer = calloc( 1, DeviceHandle->FramebufferSize );
#else
DeviceHandle->Framebuffer = heap_caps_calloc( 1, DeviceHandle->FramebufferSize, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA );
#endif
NullCheck( DeviceHandle->Framebuffer, return false );
return SSD13x6_Init( DeviceHandle, Width, Height );

View File

@@ -58,7 +58,7 @@ struct SSD13x6_Device {
enum { SSD1306, SSD1326, SH1106 } Model;
uint8_t ReMap;
uint8_t* Framebuffer;
uint8_t* Framebuffer, *Shadowbuffer;
int FramebufferSize;
WriteCommandProc WriteCommand;

View File

@@ -16,6 +16,9 @@
#include "ssd13x6.h"
#include "ssd13x6_draw.h"
#undef NullCheck
#define NullCheck(X,Y)
__attribute__( ( always_inline ) ) static inline bool IsPixelVisible( struct SSD13x6_Device* DeviceHandle, int x, int y ) {
bool Result = (
( x >= 0 ) &&
@@ -75,7 +78,7 @@ void IRAM_ATTR SSD13x6_DrawHLine( struct SSD13x6_Device* DeviceHandle, int x, in
NullCheck( DeviceHandle, return );
NullCheck( DeviceHandle->Framebuffer, return );
for ( ; x <= XEnd; x++ ) {
for ( ; x < XEnd; x++ ) {
if ( IsPixelVisible( DeviceHandle, x, y ) == true ) {
SSD13x6_DrawPixelFast( DeviceHandle, x, y, Color );
} else {
@@ -90,7 +93,7 @@ void IRAM_ATTR SSD13x6_DrawVLine( struct SSD13x6_Device* DeviceHandle, int x, in
NullCheck( DeviceHandle, return );
NullCheck( DeviceHandle->Framebuffer, return );
for ( ; y <= YEnd; y++ ) {
for ( ; y < YEnd; y++ ) {
if ( IsPixelVisible( DeviceHandle, x, y ) == true ) {
SSD13x6_DrawPixel( DeviceHandle, x, y, Color );
} else {
@@ -114,7 +117,7 @@ static inline void IRAM_ATTR DrawWideLine( struct SSD13x6_Device* DeviceHandle,
Error = ( dy * 2 ) - dx;
for ( ; x <= x1; x++ ) {
for ( ; x < x1; x++ ) {
if ( IsPixelVisible( DeviceHandle, x, y ) == true ) {
SSD13x6_DrawPixelFast( DeviceHandle, x, y, Color );
}
@@ -219,3 +222,32 @@ void SSD13x6_Clear( struct SSD13x6_Device* DeviceHandle, int Color ) {
memset( DeviceHandle->Framebuffer, Color, DeviceHandle->FramebufferSize );
}
void SSD13x6_ClearWindow( struct SSD13x6_Device* DeviceHandle, int x1, int y1, int x2, int y2, int Color ) {
NullCheck( DeviceHandle, return );
NullCheck( DeviceHandle->Framebuffer, return );
/*
int xr = ((x1 - 1) / 8) + 1 ) * 8;
int xl = (x2 / 8) * 8;
for (int y = y1; y <= y2; y++) {
for (int x = x1; x < xr; x++) SSD13x6_DrawPixelFast( DeviceHandle, x, y, Color);
if (xl > xr) memset( DeviceHandle->Framebuffer + (y / 8) * DeviceHandle->Width + xr, 0, xl - xr );
for (int x = xl; x <= x2; x++) SSD13x6_DrawPixelFast( DeviceHandle, x, y, Color);
}
return;
*/
// cheap optimization on boundaries
if (x1 == 0 && x2 == DeviceHandle->Width - 1 && y1 % 8 == 0 && (y2 + 1) % 8 == 0) {
memset( DeviceHandle->Framebuffer + (y1 / 8) * DeviceHandle->Width, 0, (y2 - y1 + 1) / 8 * DeviceHandle->Width );
} else {
for (int y = y1; y <= y2; y++) {
for (int x = x1; x <= x2; x++) {
SSD13x6_DrawPixelFast( DeviceHandle, x, y, Color);
}
}
}
}

View File

@@ -39,6 +39,7 @@ extern "C" {
#define SSD_COLOR_XOR 2
void SSD13x6_Clear( struct SSD13x6_Device* DeviceHandle, int Color );
void SSD13x6_ClearWindow( struct SSD13x6_Device* DeviceHandle, int x1, int y1, int x2, int y2, int Color );
void SSD13x6_DrawPixel( struct SSD13x6_Device* DeviceHandle, int X, int Y, int Color );
void SSD13x6_DrawPixelFast( struct SSD13x6_Device* DeviceHandle, int X, int Y, int Color );
void SSD13x6_DrawHLine( struct SSD13x6_Device* DeviceHandle, int x, int y, int Width, int Color );

View File

@@ -55,7 +55,7 @@ typedef struct raop_ctx_s {
short unsigned port; // RTSP port for AirPlay
int sock; // socket of the above
struct in_addr peer; // IP of the iDevice (airplay sender)
bool running;
bool running, abort;
#ifdef WIN32
pthread_t thread, search_thread;
#else
@@ -83,6 +83,7 @@ typedef struct raop_ctx_s {
TaskHandle_t thread, joiner;
StaticTask_t *xTaskBuffer;
StackType_t xStack[SEARCH_STACK_SIZE] __attribute__ ((aligned (4)));;
SemaphoreHandle_t destroy_mutex;
#endif
} active_remote;
void *owner;
@@ -93,6 +94,7 @@ extern log_level raop_loglevel;
static log_level *loglevel = &raop_loglevel;
static void* rtsp_thread(void *arg);
static void abort_rtsp(raop_ctx_t *ctx);
static bool handle_rtsp(raop_ctx_t *ctx, int sock);
static char* rsa_apply(unsigned char *input, int inlen, int *outlen, int mode);
@@ -198,6 +200,12 @@ struct raop_ctx_s *raop_create(struct in_addr host, char *name,
ESP_ERROR_CHECK( mdns_service_add(id, "_raop", "_tcp", ctx->port, txt, sizeof(txt) / sizeof(mdns_txt_item_t)) );
ctx->xTaskBuffer = (StaticTask_t*) heap_caps_malloc(sizeof(StaticTask_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
ctx->thread = xTaskCreateStatic( (TaskFunction_t) rtsp_thread, "RTSP_thread", RTSP_STACK_SIZE, ctx, ESP_TASK_PRIO_MIN + 1, ctx->xStack, ctx->xTaskBuffer);
#endif
return ctx;
}
/*----------------------------------------------------------------------------*/
void raop_abort(struct raop_ctx_s *ctx) {
LOG_INFO("[%p]: aborting RTSP session at next select() wakeup", ctx);
@@ -270,7 +278,7 @@ if (!ctx) return;
NFREE(ctx->rtsp.aeskey);
NFREE(ctx->rtsp.aesiv);
NFREE(ctx->rtsp.fmtp);
NFREE(ctx->rtsp.fmtp);
free(ctx);
}
@@ -323,7 +331,7 @@ void raop_cmd(struct raop_ctx_s *ctx, raop_event_t event, void *param) {
break;
}
default:
break;
break;
}
// no command to send to remote or no remote found yet
@@ -354,6 +362,8 @@ void raop_cmd(struct raop_ctx_s *ctx, raop_event_t event, void *param) {
LOG_INFO("[%p]: sending airplay remote\n%s<== received ==>\n%s", ctx, buf, resp);
NFREE(method);
NFREE(buf);
kd_free(headers);
}
free(command);
@@ -373,6 +383,7 @@ static void *rtsp_thread(void *arg) {
int n;
bool res = false;
if (sock == -1) {
struct sockaddr_in peer;
socklen_t addrlen = sizeof(struct sockaddr_in);
@@ -383,12 +394,13 @@ static void *rtsp_thread(void *arg) {
if (sock != -1 && ctx->running) {
LOG_INFO("got RTSP connection %u", sock);
} else continue;
}
}
FD_ZERO(&rfds);
FD_SET(sock, &rfds);
n = select(sock + 1, &rfds, NULL, NULL, &timeout);
n = select(sock + 1, &rfds, NULL, NULL, &timeout);
if (!n && !ctx->abort) continue;
if (n > 0) res = handle_rtsp(ctx, sock);
@@ -460,27 +472,6 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock)
kd_add(resp, "Public", "ANNOUNCE, SETUP, RECORD, PAUSE, FLUSH, TEARDOWN, OPTIONS, GET_PARAMETER, SET_PARAMETER");
} else if (!strcmp(method, "ANNOUNCE")) {
char *padded, *p;
NFREE(ctx->rtsp.aeskey);
NFREE(ctx->rtsp.aesiv);
NFREE(ctx->rtsp.fmtp);
// LMS might has taken over the player, leaving us with a running RTP session (should not happen)
if (ctx->rtp) {
LOG_WARN("[%p]: closing unfinished RTP session", ctx);
rtp_end(ctx->rtp);
}
// same, should not happen unless we have missed a teardown ...
if (ctx->active_remote.running) {
ctx->active_remote.joiner = xTaskGetCurrentTaskHandle();
ctx->active_remote.running = false;
vTaskResume(ctx->active_remote.thread);
ulTaskNotifyTake(pdFALSE, portMAX_DELAY);
vTaskDelete(ctx->active_remote.thread);
} else if (!strcmp(method, "ANNOUNCE")) {
char *padded, *p;
@@ -522,6 +513,7 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock)
// on announce, search remote
if ((buf = kd_lookup(headers, "DACP-ID")) != NULL) strcpy(ctx->active_remote.DACPid, buf);
if ((buf = kd_lookup(headers, "Active-Remote")) != NULL) strcpy(ctx->active_remote.id, buf);
#ifdef WIN32
ctx->active_remote.handle = init_mDNS(false, ctx->host);
pthread_create(&ctx->search_thread, NULL, &search_remote, ctx);
@@ -600,11 +592,10 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock)
// need to make sure no search is on-going and reclaim pthread memory
#ifdef WIN32
if (ctx->active_remote.handle) close_mDNS(ctx->active_remote.handle);
pthread_join(ctx->search_thread, NULL);
#else
if (ctx->active_remote.handle) close_mDNS(ctx->active_remote.handle);
pthread_join(ctx->search_thread, NULL);
ctx->active_remote.running = false;
#else
ctx->active_remote.joiner = xTaskGetCurrentTaskHandle();
ctx->active_remote.running = false;
xSemaphoreTake(ctx->active_remote.destroy_mutex, portMAX_DELAY);
@@ -681,6 +672,35 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock)
NFREE(body);
NFREE(buf);
kd_free(resp);
kd_free(headers);
return true;
}
/*----------------------------------------------------------------------------*/
void abort_rtsp(raop_ctx_t *ctx) {
// first stop RTP process
if (ctx->rtp) {
rtp_end(ctx->rtp);
ctx->rtp = NULL;
LOG_INFO("[%p]: RTP thread aborted", ctx);
}
if (ctx->active_remote.running) {
// need to make sure no search is on-going and reclaim task memory
ctx->active_remote.joiner = xTaskGetCurrentTaskHandle();
ctx->active_remote.running = false;
xSemaphoreTake(ctx->active_remote.destroy_mutex, portMAX_DELAY);
vTaskDelete(ctx->active_remote.thread);
vSemaphoreDelete(ctx->active_remote.thread);
heap_caps_free(ctx->active_remote.xTaskBuffer);
memset(&ctx->active_remote, 0, sizeof(ctx->active_remote));
LOG_INFO("[%p]: Remote search thread aborted", ctx);
}
NFREE(ctx->rtsp.aeskey);
NFREE(ctx->rtsp.aesiv);
@@ -746,20 +766,10 @@ static void* search_remote(void *args) {
ctx->active_remote.host.s_addr = a->addr.u_addr.ip4.addr;
ctx->active_remote.port = r->port;
LOG_INFO("found remote %s %s:%hu", r->instance_name, inet_ntoa(ctx->active_remote.host), ctx->active_remote.port);
}
}
mdns_query_results_free(results);
}
/*
for some reason which is beyond me, if that tasks gives the semaphore
before the RTSP tasks waits for it, then freeRTOS crashes in queue
management caused by LWIP stack once the RTSP socket is closed. I have
no clue why, but so we'll suspend the tasks as soon as we're done with
search and wait for the resume then give the semaphore
}
}
// PS: I know this is not fully race-condition free
mdns_query_results_free(results);
}
// can't use xNotifyGive as it seems LWIP is using it as well

View File

@@ -27,6 +27,7 @@
struct raop_ctx_s* raop_create(struct in_addr host, char *name, unsigned char mac[6], int latency,
raop_cmd_cb_t cmd_cb, raop_data_cb_t data_cb);
void raop_delete(struct raop_ctx_s *ctx);
void raop_cmd(struct raop_ctx_s *ctx, raop_event_t event, void *param);
void raop_abort(struct raop_ctx_s *ctx);
bool raop_cmd(struct raop_ctx_s *ctx, raop_event_t event, void *param);
#endif

View File

@@ -178,6 +178,7 @@ void raop_sink_init(raop_cmd_vcb_t cmd_cb, raop_data_cb_t data_cb) {
void raop_disconnect(void) {
LOG_INFO("forced disconnection");
displayer_control(DISPLAYER_SHUTDOWN);
raop_cmd(raop, RAOP_STOP, NULL);
// in case we can't communicate with AirPlay controller, abort session
if (!raop_cmd(raop, RAOP_STOP, NULL)) raop_abort(raop);
actrls_unset();
}

View File

@@ -146,4 +146,10 @@ void monitor_svc_init(void) {
xTimerStart(monitor_timer, portMAX_DELAY);
}
free(p);
ESP_LOGI(TAG, "Heap internal:%zu (min:%zu) external:%zu (min:%zu)",
heap_caps_get_free_size(MALLOC_CAP_INTERNAL),
heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL),
heap_caps_get_free_size(MALLOC_CAP_SPIRAM),
heap_caps_get_minimum_free_size(MALLOC_CAP_SPIRAM));
}

View File

@@ -46,7 +46,7 @@ static bool enable_airplay;
#define SYNC_NB 5
static raop_event_t raop_state;
static bool raop_expect_stop = false;
static struct {
bool enabled, start;
s32_t error[SYNC_NB];
@@ -123,9 +123,8 @@ static bool bt_sink_cmd_handler(bt_sink_cmd_t cmd, va_list args)
LOG_INFO("BT sink started");
break;
case BT_SINK_AUDIO_STOPPED:
// do we still need that?
if (output.external == DECODE_BT) {
output.state = OUTPUT_OFF;
if (output.state > OUTPUT_STOPPED) output.state = OUTPUT_STOPPED;
LOG_INFO("BT sink stopped");
}
break;
@@ -136,6 +135,7 @@ static bool bt_sink_cmd_handler(bt_sink_cmd_t cmd, va_list args)
case BT_SINK_STOP:
_buf_flush(outputbuf);
output.state = OUTPUT_STOPPED;
output.stop_time = gettime_ms();
LOG_INFO("BT sink stopped");
break;
case BT_SINK_PAUSE:
@@ -253,18 +253,14 @@ static bool raop_sink_cmd_handler(raop_event_t event, va_list args)
output.next_sample_rate = output.current_sample_rate = RAOP_SAMPLE_RATE;
break;
case RAOP_STOP:
LOG_INFO("Stop", NULL);
output.state = OUTPUT_OFF;
output.frames_played = 0;
raop_state = event;
break;
case RAOP_FLUSH:
LOG_INFO("Flush", NULL);
raop_expect_stop = true;
if (event == RAOP_FLUSH) { LOG_INFO("Flush", NULL); }
else { LOG_INFO("Stop", NULL); }
raop_state = event;
_buf_flush(outputbuf);
output.state = OUTPUT_STOPPED;
if (output.state > OUTPUT_STOPPED) output.state = OUTPUT_STOPPED;
output.frames_played = 0;
output.stop_time = gettime_ms();
break;
case RAOP_PLAY: {
LOG_INFO("Play", NULL);

View File

@@ -18,6 +18,8 @@
*/
#include <ctype.h>
#include <math.h>
#include "esp_dsp.h"
#include "squeezelite.h"
#include "slimproto.h"
#include "display.h"
@@ -54,6 +56,27 @@ struct grfg_packet {
u16_t width; // # of pixels of scrollable
};
struct visu_packet {
char opcode[4];
u8_t which;
u8_t count;
union {
struct {
u32_t bars;
u32_t spectrum_scale;
} full;
struct {
u32_t width;
u32_t height;
s32_t col;
s32_t row;
u32_t border;
u32_t bars;
u32_t spectrum_scale;
};
};
};
struct ANIC_header {
char opcode[4];
u32_t length;
@@ -64,20 +87,62 @@ struct ANIC_header {
extern struct outputstate output;
static struct {
TaskHandle_t task;
SemaphoreHandle_t mutex;
int width, height;
bool dirty;
bool owned;
} displayer = { .dirty = true, .owned = true };
#define LONG_WAKE (10*1000)
#define SB_HEIGHT 32
// lenght are number of frames, i.e. 2 channels of 16 bits
#define FFT_LEN_BIT 6
#define FFT_LEN (1 << FFT_LEN_BIT)
#define RMS_LEN_BIT 6
#define RMS_LEN (1 << RMS_LEN_BIT)
#define DISPLAY_BW 20000
static struct scroller_s {
// copy of grfs content
u8_t screen, direction;
u32_t pause, speed;
u16_t by, mode, full_width, window_width;
u16_t max, size;
u8_t screen;
u32_t pause, speed;
int wake;
u16_t mode;
s16_t by;
// scroller management & sharing between grfg and scrolling task
TaskHandle_t task;
u8_t *scroll_frame, *back_frame;
bool active, updated;
u8_t *scroll_ptr;
int scroll_len, scroll_step;
bool active, first;
int scrolled;
struct {
u8_t *frame;
u32_t width;
u32_t max, size;
} scroll;
struct {
u8_t *frame;
u32_t width;
} back;
u8_t *frame;
u32_t width;
} scroller;
#define MAX_BARS 32
static EXT_RAM_ATTR struct {
int bar_gap, bar_width, bar_border;
struct {
int current, max;
int limit;
} bars[MAX_BARS];
float spectrum_scale;
int n, col, row, height, width, border;
enum { VISU_BLANK, VISU_VUMETER, VISU_SPECTRUM, VISU_WAVEFORM } mode;
int speed, wake;
float fft[FFT_LEN*2], samples[FFT_LEN*2], hanning[FFT_LEN];
} visu;
#define ANIM_NONE 0x00
#define ANIM_TRANSITION 0x01 // A transition animation has finished
#define ANIM_SCROLL_ONCE 0x02
@@ -91,23 +156,27 @@ static u8_t SETD_width;
#define LINELEN 40
static log_level loglevel = lINFO;
static SemaphoreHandle_t display_mutex;
static bool (*slimp_handler_chain)(u8_t *data, int len);
static void (*slimp_loop_chain)(void);
static void (*notify_chain)(in_addr_t ip, u16_t hport, u16_t cport);
static int display_width, display_height;
static bool (*display_bus_chain)(void *from, enum display_bus_cmd_e cmd);
#define max(a,b) (((a) > (b)) ? (a) : (b))
static void server(in_addr_t ip, u16_t hport, u16_t cport);
static void send_server(void);
static bool handler(u8_t *data, int len);
static bool display_bus_handler(void *from, enum display_bus_cmd_e cmd);
static void vfdc_handler( u8_t *_data, int bytes_read);
static void grfe_handler( u8_t *data, int len);
static void grfb_handler(u8_t *data, int len);
static void grfs_handler(u8_t *data, int len);
static void grfg_handler(u8_t *data, int len);
static void scroll_task(void* arg);
static void visu_handler(u8_t *data, int len);
static void displayer_task(void* arg);
/* scrolling undocumented information
grfs
@@ -150,18 +219,25 @@ bool sb_display_init(void) {
}
// need to force height to 32 maximum
display_width = display->width;
display_height = min(display->height, 32);
displayer.width = display->width;
displayer.height = min(display->height, SB_HEIGHT);
SETD_width = display->width;
// create visu configuration
visu.bar_gap = 1;
visu.speed = 100;
dsps_fft2r_init_fc32(visu.fft, FFT_LEN);
dsps_wind_hann_f32(visu.hanning, FFT_LEN);
// create scroll management task
display_mutex = xSemaphoreCreateMutex();
scroller.task = xTaskCreateStatic( (TaskFunction_t) scroll_task, "scroll_thread", SCROLL_STACK_SIZE, NULL, ESP_TASK_PRIO_MIN + 1, xStack, &xTaskBuffer);
displayer.mutex = xSemaphoreCreateMutex();
displayer.task = xTaskCreateStatic( (TaskFunction_t) displayer_task, "displayer_thread", SCROLL_STACK_SIZE, NULL, ESP_TASK_PRIO_MIN + 1, xStack, &xTaskBuffer);
// size scroller
scroller.max = (display_width * display_height / 8) * 10;
scroller.scroll_frame = malloc(scroller.max);
scroller.back_frame = malloc(display_width * display_height / 8);
scroller.scroll.max = (displayer.width * displayer.height / 8) * 10;
scroller.scroll.frame = malloc(scroller.scroll.max);
scroller.back.frame = malloc(displayer.width * displayer.height / 8);
scroller.frame = malloc(displayer.width * displayer.height / 8);
// chain handlers
slimp_handler_chain = slimp_handler;
@@ -173,9 +249,39 @@ bool sb_display_init(void) {
notify_chain = server_notify;
server_notify = server;
display_bus_chain = display_bus;
display_bus = display_bus_handler;
return true;
}
/****************************************************************************************
* Receive display bus commands
*/
static bool display_bus_handler(void *from, enum display_bus_cmd_e cmd) {
// don't answer to own requests
if (from == &displayer) return false ;
LOG_INFO("Display bus command %d", cmd);
xSemaphoreTake(displayer.mutex, portMAX_DELAY);
switch (cmd) {
case DISPLAY_BUS_TAKE:
displayer.owned = false;
break;
case DISPLAY_BUS_GIVE:
displayer.owned = true;
break;
}
xSemaphoreGive(displayer.mutex);
if (display_bus_chain) return (*display_bus_chain)(from, cmd);
else return true;
}
/****************************************************************************************
* Send message to server (ANIC at that time)
*/
@@ -224,7 +330,9 @@ static void send_server(void) {
static void server(in_addr_t ip, u16_t hport, u16_t cport) {
char msg[32];
sprintf(msg, "%s:%hu", inet_ntoa(ip), hport);
display->text(DISPLAY_FONT_DEFAULT, DISPLAY_CENTERED, DISPLAY_CLEAR | DISPLAY_UPDATE, msg);
if (displayer.owned) display->text(DISPLAY_FONT_DEFAULT, DISPLAY_CENTERED, DISPLAY_CLEAR | DISPLAY_UPDATE, msg);
SETD_width = display->width;
displayer.dirty = true;
if (notify_chain) (*notify_chain)(ip, hport, cport);
}
@@ -234,22 +342,21 @@ static void server(in_addr_t ip, u16_t hport, u16_t cport) {
static bool handler(u8_t *data, int len){
bool res = true;
// don't do anything if we dont own the display (no lock needed)
if (!output.external || output.state < OUTPUT_STOPPED) {
if (!strncmp((char*) data, "vfdc", 4)) {
vfdc_handler(data, len);
} else if (!strncmp((char*) data, "grfe", 4)) {
grfe_handler(data, len);
} else if (!strncmp((char*) data, "grfb", 4)) {
grfb_handler(data, len);
} else if (!strncmp((char*) data, "grfs", 4)) {
grfs_handler(data, len);
} else if (!strncmp((char*) data, "grfg", 4)) {
grfg_handler(data, len);
} else {
res = false;
}
}
if (!strncmp((char*) data, "vfdc", 4)) {
vfdc_handler(data, len);
} else if (!strncmp((char*) data, "grfe", 4)) {
grfe_handler(data, len);
} else if (!strncmp((char*) data, "grfb", 4)) {
grfb_handler(data, len);
} else if (!strncmp((char*) data, "grfs", 4)) {
grfs_handler(data, len);
} else if (!strncmp((char*) data, "grfg", 4)) {
grfg_handler(data, len);
} else if (!strncmp((char*) data, "visu", 4)) {
visu_handler(data, len);
} else {
res = false;
}
// chain protocol handlers (bitwise or is fine)
if (*slimp_handler_chain) res |= (*slimp_handler_chain)(data, len);
@@ -374,12 +481,30 @@ static void vfdc_handler( u8_t *_data, int bytes_read) {
* Process graphic display data
*/
static void grfe_handler( u8_t *data, int len) {
xSemaphoreTake(display_mutex, portMAX_DELAY);
xSemaphoreTake(displayer.mutex, portMAX_DELAY);
scroller.active = false;
display->draw_cbr(data + sizeof(struct grfe_packet), display_height);
xSemaphoreGive(display_mutex);
// we are not in control or we are displaying visu on a small screen, do not do screen update
if (visu.mode && !visu.col && visu.row < SB_HEIGHT) {
xSemaphoreGive(displayer.mutex);
return;
}
if (displayer.owned) {
// did we have something that might have write on the bottom of a SB_HEIGHT+ display
if (displayer.dirty) {
display->clear(true);
displayer.dirty = false;
}
// draw new frame
display->draw_cbr(data + sizeof(struct grfe_packet), displayer.width, displayer.height);
display->update();
}
xSemaphoreGive(displayer.mutex);
LOG_DEBUG("grfe frame %u", len);
}
@@ -415,36 +540,46 @@ static void grfs_handler(u8_t *data, int len) {
htonl(pkt->pause), // in ms
htonl(pkt->speed), // in ms
htons(pkt->by), // # of pixel of scroll step
htons(pkt->mode), // 0=continuous, 1=once and stop, 2=once and end
htons(pkt->width), // total width of animation
htons(pkt->mode), // 0=continuous, 1=once and stop, 2=once and end
htons(pkt->width), // last column of animation that contains a "full" screen
htons(pkt->offset) // offset if multiple packets are sent
);
// new grfs frame, build scroller info
if (!offset) {
// use the display as a general lock
xSemaphoreTake(display_mutex, portMAX_DELAY);
xSemaphoreTake(displayer.mutex, portMAX_DELAY);
// copy & set scroll parameters
scroller.screen = pkt->screen;
scroller.direction = pkt->direction;
scroller.pause = htonl(pkt->pause);
scroller.speed = htonl(pkt->speed);
scroller.by = htons(pkt->by);
scroller.mode = htons(pkt->mode);
scroller.full_width = htons(pkt->width);
scroller.updated = scroller.active = true;
scroller.scroll.width = htons(pkt->width);
scroller.first = true;
// background excludes space taken by visu (if any)
scroller.back.width = displayer.width - ((visu.mode && visu.row < SB_HEIGHT) ? visu.width : 0);
xSemaphoreGive(display_mutex);
// set scroller steps & beginning
if (pkt->direction == 1) {
scroller.scrolled = 0;
scroller.by = htons(pkt->by);
} else {
scroller.scrolled = scroller.scroll.width;
scroller.by = -htons(pkt->by);
}
xSemaphoreGive(displayer.mutex);
}
// copy scroll frame data (no semaphore needed)
if (scroller.size + size < scroller.max) {
memcpy(scroller.scroll_frame + offset, data + sizeof(struct grfs_packet), size);
scroller.size = offset + size;
LOG_INFO("scroller current size %u", scroller.size);
if (scroller.scroll.size + size < scroller.scroll.max) {
memcpy(scroller.scroll.frame + offset, data + sizeof(struct grfs_packet), size);
scroller.scroll.size = offset + size;
LOG_INFO("scroller current size %u", scroller.scroll.size);
} else {
LOG_INFO("scroller too larger %u/%u", scroller.size + size, scroller.max);
LOG_INFO("scroller too larger %u/%u", scroller.scroll.size + size, scroller.scroll.max);
}
}
@@ -456,109 +591,309 @@ static void grfg_handler(u8_t *data, int len) {
LOG_DEBUG("gfrg s:%hu w:%hu (len:%u)", htons(pkt->screen), htons(pkt->width), len);
memcpy(scroller.back_frame, data + sizeof(struct grfg_packet), len - sizeof(struct grfg_packet));
scroller.window_width = htons(pkt->width);
xSemaphoreTake(displayer.mutex, portMAX_DELAY);
xSemaphoreTake(display_mutex, portMAX_DELAY);
// can't be in grfs as we need full size & scroll_width
if (scroller.updated) {
scroller.scroll_len = display_width * display_height / 8 - (display_width - scroller.window_width) * display_height / 8;
if (scroller.direction == 1) {
scroller.scroll_ptr = scroller.scroll_frame;
scroller.scroll_step = scroller.by * display_height / 8;
} else {
scroller.scroll_ptr = scroller.scroll_frame + scroller.size - scroller.scroll_len;
scroller.scroll_step = -scroller.by * display_height / 8;
}
// size of scrollable area (less than background)
scroller.width = htons(pkt->width);
memcpy(scroller.back.frame, data + sizeof(struct grfg_packet), len - sizeof(struct grfg_packet));
scroller.updated = false;
}
// update display asynchronously (frames are oganized by columns)
memcpy(scroller.frame, scroller.back.frame, scroller.back.width * displayer.height / 8);
for (int i = 0; i < scroller.width * displayer.height / 8; i++) scroller.frame[i] |= scroller.scroll.frame[scroller.scrolled * displayer.height / 8 + i];
if (!scroller.active) {
// this is a background update and scroller has been finished, so need to update here
u8_t *frame = malloc(display_width * display_height / 8);
memcpy(frame, scroller.back_frame, display_width * display_height / 8);
for (int i = 0; i < scroller.scroll_len; i++) frame[i] |= scroller.scroll_ptr[i];
display->draw_cbr(frame, display_height);
free(frame);
LOG_DEBUG("direct drawing");
// can only write if we really own display
if (displayer.owned) {
display->draw_cbr(scroller.frame, scroller.back.width, displayer.height);
display->update();
}
else {
// if we just got a content update, let the scroller manage the screen
LOG_DEBUG("resuming scrolling task");
vTaskResume(scroller.task);
}
// now we can active scrolling, but only if we are not on a small screen
if (!visu.mode || visu.col || visu.row >= SB_HEIGHT) scroller.active = true;
// if we just got a content update, let the scroller manage the screen
LOG_DEBUG("resuming scrolling task");
xSemaphoreGive(displayer.mutex);
xSemaphoreGive(display_mutex);
// resume task once we have background, not in grfs
vTaskResume(displayer.task);
}
/****************************************************************************************
* Scroll task
* Update visualization bars
*/
static void scroll_task(void *args) {
u8_t *frame = NULL;
int len = display_width * display_height / 8;
static void visu_update(void) {
// no need to protect against no woning the display as we are playing
if (pthread_mutex_trylock(&visu_export.mutex)) return;
// not enough samples
if (visu_export.level < (visu.mode == VISU_VUMETER ? RMS_LEN : FFT_LEN) * 2 && visu_export.running) {
pthread_mutex_unlock(&visu_export.mutex);
return;
}
while (1) {
xSemaphoreTake(display_mutex, portMAX_DELAY);
// suspend ourselves if nothing to do, grfg will wake us up
if (!scroller.active) {
xSemaphoreGive(display_mutex);
vTaskSuspend(NULL);
xSemaphoreTake(display_mutex, portMAX_DELAY);
}
// lock screen & active status
frame = malloc(display_width * display_height / 8);
// scroll required amount of columns (within the window)
while (scroller.direction == 1 ? (scroller.scroll_ptr <= scroller.scroll_frame + scroller.size - scroller.scroll_step - len) :
(scroller.scroll_ptr + scroller.scroll_step >= scroller.scroll_frame) ) {
// don't do anything if we have aborted
if (!scroller.active) break;
// reset bars for all cases first
for (int i = visu.n; --i >= 0;) visu.bars[i].current = 0;
if (visu_export.running && visu_export.running) {
// scroll required amount of columns (within the window)
memcpy(frame, scroller.back_frame, display_width * display_height / 8);
for (int i = 0; i < scroller.scroll_len; i++) frame[i] |= scroller.scroll_ptr[i];
scroller.scroll_ptr += scroller.scroll_step;
display->draw_cbr(frame, display_height);
if (visu.mode == VISU_VUMETER) {
s16_t *iptr = visu_export.buffer;
xSemaphoreGive(display_mutex);
vTaskDelay(scroller.speed / portTICK_PERIOD_MS);
xSemaphoreTake(display_mutex, portMAX_DELAY);
}
// calculate sum(L²+R²), try to not overflow at the expense of some precision
for (int i = RMS_LEN; --i >= 0;) {
visu.bars[0].current += (*iptr * *iptr + (1 << (RMS_LEN_BIT - 2))) >> (RMS_LEN_BIT - 1);
iptr++;
visu.bars[1].current += (*iptr * *iptr + (1 << (RMS_LEN_BIT - 2))) >> (RMS_LEN_BIT - 1);
iptr++;
}
// done with scrolling cycle reset scroller ptr
scroller.scroll_ptr = scroller.scroll_frame + (scroller.direction == 2 ? scroller.size - scroller.scroll_len : 0);
// scrolling done, update screen and see if we need to continue
if (scroller.active) {
memcpy(frame, scroller.back_frame, len);
for (int i = 0; i < scroller.scroll_len; i++) frame[i] |= scroller.scroll_ptr[i];
display->draw_cbr(frame, display_height);
free(frame);
// see if we need to pause or if we are done
if (scroller.mode) {
scroller.active = false;
xSemaphoreGive(display_mutex);
// can't call directly send_packet from slimproto as it's not re-entrant
ANIC_resp = ANIM_SCROLL_ONCE | ANIM_SCREEN_1;
LOG_INFO("scroll-once terminated");
} else {
xSemaphoreGive(display_mutex);
vTaskDelay(scroller.pause / portTICK_PERIOD_MS);
LOG_DEBUG("scroll cycle done, pausing for %u (ms)", scroller.pause);
// convert to dB (1 bit remaining for getting X²/N, 60dB dynamic starting from 0dBFS = 3 bits back-off)
for (int i = visu.n; --i >= 0;) {
visu.bars[i].current = 32 * (0.01667f*10*log10f(0.0000001f + (visu.bars[i].current >> 1)) - 0.2543f);
if (visu.bars[i].current > 31) visu.bars[i].current = 31;
else if (visu.bars[i].current < 0) visu.bars[i].current = 0;
}
} else {
free(frame);
xSemaphoreGive(display_mutex);
LOG_INFO("scroll aborted");
// on xtensa/esp32 the floating point FFT takes 1/2 cycles of the fixed point
for (int i = 0 ; i < FFT_LEN ; i++) {
// don't normalize here, but we are due INT16_MAX and FFT_LEN / 2 / 2
visu.samples[i * 2 + 0] = (float) (visu_export.buffer[2*i] + visu_export.buffer[2*i + 1]) * visu.hanning[i];
visu.samples[i * 2 + 1] = 0;
}
// actual FFT that might be less cycle than all the crap below
dsps_fft2r_fc32_ae32(visu.samples, FFT_LEN);
dsps_bit_rev_fc32_ansi(visu.samples, FFT_LEN);
float rate = visu_export.rate;
// now arrange the result with the number of bar and sampling rate (don't want DC)
for (int i = 0, j = 1; i < visu.n && j < (FFT_LEN / 2); i++) {
float power, count;
// find the next point in FFT (this is real signal, so only half matters)
for (count = 0, power = 0; j * visu_export.rate < visu.bars[i].limit * FFT_LEN && j < FFT_LEN / 2; j++, count += 1) {
power += visu.samples[2*j] * visu.samples[2*j] + visu.samples[2*j+1] * visu.samples[2*j+1];
}
// due to sample rate, we have reached the end of the available spectrum
if (j >= (FFT_LEN / 2)) {
// normalize accumulated data
if (count) power /= count * 2.;
} else if (count) {
// how much of what remains do we need to add
float ratio = j - (visu.bars[i].limit * FFT_LEN) / rate;
power += (visu.samples[2*j] * visu.samples[2*j] + visu.samples[2*j+1] * visu.samples[2*j+1]) * ratio;
// normalize accumulated data
power /= (count + ratio) * 2;
} else {
// no data for that band (sampling rate too high), just assume same as previous one
power = (visu.samples[2*j] * visu.samples[2*j] + visu.samples[2*j+1] * visu.samples[2*j+1]) / 2.;
}
// convert to dB and bars, same back-off
if (power) visu.bars[i].current = 32 * (0.01667f*10*(log10f(power) - log10f(FFT_LEN/2*2)) - 0.2543f);
if (visu.bars[i].current > 31) visu.bars[i].current = 31;
else if (visu.bars[i].current < 0) visu.bars[i].current = 0;
}
}
}
// we took what we want, we can release the buffer
visu_export.level = 0;
pthread_mutex_unlock(&visu_export.mutex);
display->clear(false, false, visu.col, visu.row, visu.col + visu.width - 1, visu.row + visu.height - 1);
for (int i = visu.n; --i >= 0;) {
int x1 = visu.col + visu.border + visu.bar_border + i*(visu.bar_width + visu.bar_gap);
int y1 = visu.row + visu.height - 1;
if (visu.bars[i].current > visu.bars[i].max) visu.bars[i].max = visu.bars[i].current;
else if (visu.bars[i].max) visu.bars[i].max--;
for (int j = 0; j <= visu.bars[i].current; j += 2)
display->draw_line( x1, y1 - j, x1 + visu.bar_width - 1, y1 - j);
if (visu.bars[i].max > 2) {
display->draw_line( x1, y1 - visu.bars[i].max, x1 + visu.bar_width - 1, y1 - visu.bars[i].max);
display->draw_line( x1, y1 - visu.bars[i].max + 1, x1 + visu.bar_width - 1, y1 - visu.bars[i].max + 1);
}
}
}
/****************************************************************************************
* Visu packet handler
*/
void spectrum_limits(int min, int n, int pos) {
if (n / 2) {
int i;
float step = (DISPLAY_BW - min) * visu.spectrum_scale / (n/2);
visu.bars[pos].limit = min + step;
for (i = 1; i < n/2; i++) visu.bars[pos+i].limit = visu.bars[pos+i-1].limit + step;
spectrum_limits(visu.bars[pos + n/2 - 1].limit, n/2, pos + n/2);
} else {
visu.bars[pos].limit = DISPLAY_BW;
}
}
/****************************************************************************************
* Visu packet handler
*/
static void visu_handler( u8_t *data, int len) {
struct visu_packet *pkt = (struct visu_packet*) data;
int bars = 0;
LOG_DEBUG("visu %u with %u parameters", pkt->which, pkt->count);
/*
If width is specified, then respect all coordinates, otherwise we try to
use the bottom part of the display and if it is a small display, we overwrite
text
*/
xSemaphoreTake(displayer.mutex, portMAX_DELAY);
visu.mode = pkt->which;
// little trick to clean the taller screens when switching visu
if (visu.row >= SB_HEIGHT) display->clear(false, true, visu.col, visu.row, visu.col + visu.width - 1, visu.row - visu.height - 1);
if (visu.mode) {
if (pkt->count >= 4) {
// small visu, then go were we are told to
pkt->height = htonl(pkt->height);
pkt->row = htonl(pkt->row);
pkt->col = htonl(pkt->col);
visu.width = htonl(pkt->width);
visu.height = pkt->height ? pkt->height : SB_HEIGHT;
visu.col = pkt->col < 0 ? display->width + pkt->col : pkt->col;
visu.row = pkt->row < 0 ? display->height + pkt->row : pkt->row;
visu.border = htonl(pkt->border);
bars = htonl(pkt->bars);
visu.spectrum_scale = htonl(pkt->spectrum_scale) / 100.;
} else {
// full screen visu, try to use bottom screen if available
visu.width = display->width;
visu.height = display->height > SB_HEIGHT ? display->height - SB_HEIGHT : display->height;
visu.col = visu.border = 0;
visu.row = display->height - visu.height;
bars = htonl(pkt->full.bars);
visu.spectrum_scale = htonl(pkt->full.spectrum_scale) / 100.;
}
// try to adapt to what we have
if (visu.mode == VISU_SPECTRUM) {
visu.n = bars ? bars : MAX_BARS;
if (visu.spectrum_scale <= 0 || visu.spectrum_scale > 0.5) visu.spectrum_scale = 0.5;
spectrum_limits(0, visu.n, 0);
} else {
visu.n = 2;
}
do {
visu.bar_width = (visu.width - visu.border - visu.bar_gap * (visu.n - 1)) / visu.n;
if (visu.bar_width > 0) break;
} while (--visu.n);
visu.bar_border = (visu.width - visu.border - (visu.bar_width + visu.bar_gap) * visu.n + visu.bar_gap) / 2;
// give up if not enough space
if (visu.bar_width < 0) {
visu.mode = VISU_BLANK;
LOG_WARN("Not enough room for displaying visu");
} else {
// de-activate scroller if we are taking main screen
if (visu.row < SB_HEIGHT) scroller.active = false;
vTaskResume(displayer.task);
}
visu.wake = 0;
// reset bars maximum
for (int i = visu.n; --i >= 0;) visu.bars[i].max = 0;
display->clear(false, true, visu.col, visu.row, visu.col + visu.width - 1, visu.row - visu.height - 1);
LOG_INFO("Visualizer with %u bars of width %d:%d:%d:%d (%w:%u,h:%u,c:%u,r:%u,s:%.02f)", visu.n, visu.bar_border, visu.bar_width, visu.bar_gap, visu.border, visu.width, visu.height, visu.col, visu.row, visu.spectrum_scale);
} else {
LOG_INFO("Stopping visualizer");
}
xSemaphoreGive(displayer.mutex);
}
/****************************************************************************************
* Scroll task
* - with the addition of the visualizer, it's a bit a 2-headed beast not easy to
* maintain, so som better separation between the visu and scroll is probably needed
*/
static void displayer_task(void *args) {
int sleep;
while (1) {
xSemaphoreTake(displayer.mutex, portMAX_DELAY);
// suspend ourselves if nothing to do, grfg or visu will wake us up
if (!scroller.active && !visu.mode) {
xSemaphoreGive(displayer.mutex);
vTaskSuspend(NULL);
xSemaphoreTake(displayer.mutex, portMAX_DELAY);
scroller.wake = visu.wake = 0;
}
// go for long sleep when either item is disabled
if (!visu.mode) visu.wake = LONG_WAKE;
if (!scroller.active) scroller.wake = LONG_WAKE;
// scroll required amount of columns (within the window)
if (scroller.active && scroller.wake <= 0) {
// by default go for the long sleep, will change below if required
scroller.wake = LONG_WAKE;
// do we have more to scroll (scroll.width is the last column from which we havea full zone)
if (scroller.by > 0 ? (scroller.scrolled <= scroller.scroll.width) : (scroller.scrolled >= 0)) {
memcpy(scroller.frame, scroller.back.frame, scroller.back.width * displayer.height / 8);
for (int i = 0; i < scroller.width * displayer.height / 8; i++) scroller.frame[i] |= scroller.scroll.frame[scroller.scrolled * displayer.height / 8 + i];
scroller.scrolled += scroller.by;
if (displayer.owned) display->draw_cbr(scroller.frame, scroller.width, displayer.height);
// short sleep & don't need background update
scroller.wake = scroller.speed;
} else if (scroller.first || !scroller.mode) {
// at least one round done
scroller.first = false;
// see if we need to pause or if we are done
if (scroller.mode) {
// can't call directly send_packet from slimproto as it's not re-entrant
ANIC_resp = ANIM_SCROLL_ONCE | ANIM_SCREEN_1;
LOG_INFO("scroll-once terminated");
} else {
scroller.wake = scroller.pause;
LOG_DEBUG("scroll cycle done, pausing for %u (ms)", scroller.pause);
}
// need to reset pointers for next scroll
scroller.scrolled = scroller.by < 0 ? scroller.scroll.width : 0;
}
}
// update visu if active
if (visu.mode && visu.wake <= 0) {
visu_update();
visu.wake = 100;
}
// need to make sure we own display
if (displayer.owned) display->update();
// release semaphore and sleep what's needed
xSemaphoreGive(displayer.mutex);
sleep = min(visu.wake, scroller.wake);
vTaskDelay(sleep / portTICK_PERIOD_MS);
scroller.wake -= sleep;
visu.wake -= sleep;
}
}

View File

@@ -54,6 +54,17 @@ void register_external(void);
void deregister_external(void);
void decode_restore(int external);
// to be defined to nothing if you don't want to support these
extern struct visu_export_s {
pthread_mutex_t mutex;
u32_t level, size, rate;
s16_t *buffer;
bool running;
} visu_export;
void output_visu_export(s16_t *frames, frames_t out_frames, u32_t rate, bool silence);
void output_visu_init(log_level level);
void output_visu_close(void);
// optional, please chain if used
bool (*slimp_handler)(u8_t *data, int len);
void (*slimp_loop)(void);

View File

@@ -120,6 +120,8 @@ static int _write_frames(frames_t out_frames, bool silence, s32_t gainL, s32_t g
u8_t *buf = silencebuf;
memcpy(btout + oframes * BYTES_PER_FRAME, buf, out_frames * BYTES_PER_FRAME);
}
output_visu_export((s16_t*) (btout + oframes * BYTES_PER_FRAME), out_frames, output.current_sample_rate, silence);
return (int)out_frames;
}

View File

@@ -68,6 +68,8 @@ void output_init_embedded(log_level level, char *device, unsigned output_buf_siz
output_init_i2s(level, device, output_buf_size, params, rates, rate_delay, idle);
}
output_visu_init(level);
LOG_INFO("init completed.");
}
@@ -75,6 +77,7 @@ void output_close_embedded(void) {
LOG_INFO("close output");
if (close_cb) (*close_cb)();
output_close_common();
output_visu_close();
}
void set_volume(unsigned left, unsigned right) {

View File

@@ -365,8 +365,10 @@ static int _i2s_write_frames(frames_t out_frames, bool silence, s32_t gainL, s32
_scale_and_pack_frames(obuf + oframes * bytes_per_frame, optr, out_frames, gainL, gainR, output.format);
#endif
oframes += out_frames;
output_visu_export((s16_t*) (obuf + oframes * bytes_per_frame), out_frames, output.current_sample_rate, silence);
oframes += out_frames;
return out_frames;
}

View File

@@ -0,0 +1,76 @@
/*
* Squeezelite - lightweight headless squeezebox emulator
*
* (c) Adrian Smith 2012-2015, triode1@btinternet.com
* Ralph Irving 2015-2017, ralph_irving@hotmail.com
* Philippe_44 2020, philippe_44@outloook.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 <http://www.gnu.org/licenses/>.
*
*/
#include "squeezelite.h"
#define VISUEXPORT_SIZE 2048
EXT_BSS struct visu_export_s visu_export;
static struct visu_export_s *visu = &visu_export;
static log_level loglevel = lINFO;
void output_visu_export(s16_t *frames, frames_t out_frames, u32_t rate, bool silence) {
// no data to process
if (silence) {
visu->running = false;
return;
}
// do not block, try to stuff data put wait for consumer to have used them
if (!pthread_mutex_trylock(&visu->mutex)) {
// don't mix sample rates
if (visu->rate != rate) visu->level = 0;
// stuff buffer up and wait for consumer to read it (should reset level)
if (visu->level < visu->size) {
u32_t space = min(visu->size - visu->level, out_frames * 2) * 2;
memcpy(visu->buffer + visu->level, frames, space);
visu->level += space / 2;
visu->running = true;
visu->rate = rate ? rate : 44100;
}
// mutex must be released
pthread_mutex_unlock(&visu->mutex);
}
}
void output_visu_close(void) {
pthread_mutex_lock(&visu->mutex);
visu->running = false;
free(visu->buffer);
pthread_mutex_unlock(&visu->mutex);
}
void output_visu_init(log_level level) {
loglevel = level;
pthread_mutex_init(&visu->mutex, NULL);
visu->size = VISUEXPORT_SIZE;
visu->running = false;
visu->rate = 44100;
visu->buffer = malloc(VISUEXPORT_SIZE * sizeof(s16_t) * 2);
LOG_INFO("Initialize VISUEXPORT %u 16 bits samples", VISUEXPORT_SIZE);
}

View File

@@ -690,46 +690,47 @@ static void slimproto_run() {
UNLOCK_D;
LOCK_O;
status.output_full = _buf_used(outputbuf);
status.output_size = outputbuf->size;
status.frames_played = output.frames_played_dmp;
status.current_sample_rate = output.current_sample_rate;
status.updated = output.updated;
status.device_frames = output.device_frames;
if (output.track_started) {
_sendSTMs = true;
output.track_started = false;
status.stream_start = output.track_start_time;
}
if (!output.external) {
status.output_full = _buf_used(outputbuf);
status.output_size = outputbuf->size;
status.frames_played = output.frames_played_dmp;
status.current_sample_rate = output.current_sample_rate;
status.updated = output.updated;
status.device_frames = output.device_frames;
if (output.track_started) {
_sendSTMs = true;
output.track_started = false;
status.stream_start = output.track_start_time;
}
#if PORTAUDIO
if (output.pa_reopen) {
_pa_open();
output.pa_reopen = false;
}
if (output.pa_reopen) {
_pa_open();
output.pa_reopen = false;
}
#endif
if (_start_output && (output.state == OUTPUT_STOPPED || output.state == OUTPUT_OFF)) {
output.state = OUTPUT_BUFFER;
}
if (!output.external && output.state == OUTPUT_RUNNING && !sentSTMu && status.output_full == 0 && status.stream_state <= DISCONNECT &&
_decode_state == DECODE_STOPPED) {
if (_start_output && (output.state == OUTPUT_STOPPED || output.state == OUTPUT_OFF)) {
output.state = OUTPUT_BUFFER;
}
if (output.state == OUTPUT_RUNNING && !sentSTMu && status.output_full == 0 && status.stream_state <= DISCONNECT &&
_decode_state == DECODE_STOPPED) {
_sendSTMu = true;
sentSTMu = true;
LOG_DEBUG("output underrun");
output.state = OUTPUT_STOPPED;
output.stop_time = now;
}
if (output.state == OUTPUT_RUNNING && !sentSTMo && status.output_full == 0 && status.stream_state == STREAMING_HTTP) {
_sendSTMo = true;
sentSTMo = true;
}
_sendSTMu = true;
sentSTMu = true;
LOG_DEBUG("output underrun");
output.state = OUTPUT_STOPPED;
output.stop_time = now;
}
if (output.state == OUTPUT_RUNNING && !sentSTMo && status.output_full == 0 && status.stream_state == STREAMING_HTTP) {
_sendSTMo = true;
sentSTMo = true;
}
}
if (output.state == OUTPUT_STOPPED && output.idle_to && (now - output.stop_time > output.idle_to)) {
output.state = OUTPUT_OFF;
LOG_DEBUG("output timeout");
}
if (!output.external && output.state == OUTPUT_RUNNING && now - status.last > 1000) {
}
if (output.state == OUTPUT_RUNNING && now - status.last > 1000) {
_sendSTMt = true;
status.last = now;
}

View File

@@ -387,6 +387,21 @@ typedef BOOL bool;
#endif
typedef u32_t frames_t;
typedef int sockfd;
// logging
typedef enum { lERROR = 0, lWARN, lINFO, lDEBUG, lSDEBUG } log_level;
const char *logtime(void);
void logprint(const char *fmt, ...);
#define LOG_ERROR(fmt, ...) logprint("%s %s:%d " fmt "\n", logtime(), __FUNCTION__, __LINE__, ##__VA_ARGS__)
#define LOG_WARN(fmt, ...) if (loglevel >= lWARN) logprint("%s %s:%d " fmt "\n", logtime(), __FUNCTION__, __LINE__, ##__VA_ARGS__)
#define LOG_INFO(fmt, ...) if (loglevel >= lINFO) logprint("%s %s:%d " fmt "\n", logtime(), __FUNCTION__, __LINE__, ##__VA_ARGS__)
#define LOG_DEBUG(fmt, ...) if (loglevel >= lDEBUG) logprint("%s %s:%d " fmt "\n", logtime(), __FUNCTION__, __LINE__, ##__VA_ARGS__)
#define LOG_SDEBUG(fmt, ...) if (loglevel >= lSDEBUG) logprint("%s %s:%d " fmt "\n", logtime(), __FUNCTION__, __LINE__, ##__VA_ARGS__)
#if EMBEDDED
#include "embedded.h"
#endif
@@ -395,9 +410,6 @@ typedef BOOL bool;
#define MSG_NOSIGNAL 0
#endif
typedef u32_t frames_t;
typedef int sockfd;
#if EVENTFD
#include <sys/eventfd.h>
#define event_event int
@@ -473,18 +485,6 @@ void _wake_create(event_event*);
#define min(a,b) (((a) < (b)) ? (a) : (b))
// logging
typedef enum { lERROR = 0, lWARN, lINFO, lDEBUG, lSDEBUG } log_level;
const char *logtime(void);
void logprint(const char *fmt, ...);
#define LOG_ERROR(fmt, ...) logprint("%s %s:%d " fmt "\n", logtime(), __FUNCTION__, __LINE__, ##__VA_ARGS__)
#define LOG_WARN(fmt, ...) if (loglevel >= lWARN) logprint("%s %s:%d " fmt "\n", logtime(), __FUNCTION__, __LINE__, ##__VA_ARGS__)
#define LOG_INFO(fmt, ...) if (loglevel >= lINFO) logprint("%s %s:%d " fmt "\n", logtime(), __FUNCTION__, __LINE__, ##__VA_ARGS__)
#define LOG_DEBUG(fmt, ...) if (loglevel >= lDEBUG) logprint("%s %s:%d " fmt "\n", logtime(), __FUNCTION__, __LINE__, ##__VA_ARGS__)
#define LOG_SDEBUG(fmt, ...) if (loglevel >= lSDEBUG) logprint("%s %s:%d " fmt "\n", logtime(), __FUNCTION__, __LINE__, ##__VA_ARGS__)
// utils.c (non logging)
typedef enum { EVENT_TIMEOUT = 0, EVENT_READ, EVENT_WAKE } event_type;
#if WIN && USE_SSL

View File

@@ -78,6 +78,7 @@ void logprint(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
fflush(stderr);
}