mirror of
https://github.com/sle118/squeezelite-esp32.git
synced 2025-12-24 16:38:44 +03:00
Compare commits
19 Commits
Muse.16.16
...
Muse.16.16
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
58840f894f | ||
|
|
94109ebf38 | ||
|
|
fc20618fa2 | ||
|
|
70720d3445 | ||
|
|
4abe1304e8 | ||
|
|
be7fb4b669 | ||
|
|
3554af1460 | ||
|
|
02d422c1f4 | ||
|
|
343b728323 | ||
|
|
9679c5c104 | ||
|
|
5c90086bbd | ||
|
|
807a0e547a | ||
|
|
1d54331224 | ||
|
|
e85a3cddf1 | ||
|
|
d4d97d5a60 | ||
|
|
2afbee7cb1 | ||
|
|
bbf9a3af70 | ||
|
|
a9ca4c4450 | ||
|
|
55bce084eb |
4
.github/workflows/Platform_build.yml
vendored
4
.github/workflows/Platform_build.yml
vendored
@@ -88,7 +88,7 @@ jobs:
|
||||
git commit -m "Update prebuilt objects [skip actions]"
|
||||
git push https://${{secrets.github_token}}@github.com/sle118/squeezelite-esp32.git
|
||||
- name: Locally store commonly built objects
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: prebuilt_objects
|
||||
path: |
|
||||
@@ -130,7 +130,7 @@ jobs:
|
||||
git status
|
||||
build_tools.py environment --build ${{ needs.bootstrap.outputs.build_number }} --env_file "$GITHUB_ENV" --node "${{matrix.node}}" --depth ${{matrix.depth}} --major 2 --docker sle118/squeezelite-esp32-idfv435
|
||||
|
||||
- uses: actions/download-artifact@master
|
||||
- uses: actions/download-artifact@v4
|
||||
name: Restore common objects
|
||||
with:
|
||||
name: prebuilt_objects
|
||||
|
||||
12
CHANGELOG
12
CHANGELOG
@@ -1,3 +1,15 @@
|
||||
2024-01-01
|
||||
- ogg stream are parsed to foward metadata to LMS
|
||||
- fix some ogg parsing on multi-stream containers
|
||||
|
||||
2023-11-19
|
||||
- more robust (?) airplay RTP frame recovery
|
||||
- initialize of scratch string in monitor (trying to figure out random reboot)
|
||||
|
||||
2023-11-16
|
||||
- add SH1122 support
|
||||
- optimize GDS DrawPixel function
|
||||
|
||||
2023-11-09
|
||||
- force gpio_pad_select_gpio in dac_controlset in case somebody uses UART gpio's (or other pre-programmed)
|
||||
|
||||
|
||||
176
components/display/SH1122.c
Normal file
176
components/display/SH1122.c
Normal file
@@ -0,0 +1,176 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2018 Tara Keeling
|
||||
* 2020 Philippe G.
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <esp_heap_caps.h>
|
||||
#include <esp_log.h>
|
||||
|
||||
#include "gds.h"
|
||||
#include "gds_private.h"
|
||||
|
||||
#define SHADOW_BUFFER
|
||||
#define PAGE_BLOCK 1024
|
||||
|
||||
#define min(a,b) (((a) < (b)) ? (a) : (b))
|
||||
|
||||
static char TAG[] = "SH1122";
|
||||
|
||||
struct PrivateSpace {
|
||||
uint8_t *iRAM, *Shadowbuffer;
|
||||
uint8_t PageSize;
|
||||
};
|
||||
|
||||
// Functions are not declared to minimize # of lines
|
||||
|
||||
static void SetColumnAddress( struct GDS_Device* Device, uint8_t Start, uint8_t End ) {
|
||||
Device->WriteCommand( Device, 0x10 | (Start >> 4) );
|
||||
Device->WriteCommand( Device, 0x00 | (Start & 0x0f) );
|
||||
}
|
||||
|
||||
static void SetRowAddress( struct GDS_Device* Device, uint8_t Start, uint8_t End ) {
|
||||
Device->WriteCommand( Device, 0xB0 );
|
||||
Device->WriteCommand( Device, Start );
|
||||
}
|
||||
|
||||
static void Update( struct GDS_Device* Device ) {
|
||||
struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private;
|
||||
|
||||
// RAM is by columns of 4 pixels ...
|
||||
SetColumnAddress( Device, 0, Device->Width / 4 - 1);
|
||||
|
||||
#ifdef SHADOW_BUFFER
|
||||
uint16_t *optr = (uint16_t*) Private->Shadowbuffer, *iptr = (uint16_t*) Device->Framebuffer;
|
||||
bool dirty = false;
|
||||
|
||||
for (int r = 0, page = 0; r < Device->Height; r++) {
|
||||
// look for change and update shadow (cheap optimization = width always / by 2)
|
||||
for (int c = Device->Width / 2 / 2; --c >= 0;) {
|
||||
if (*optr != *iptr) {
|
||||
dirty = true;
|
||||
*optr = *iptr;
|
||||
}
|
||||
iptr++; optr++;
|
||||
}
|
||||
|
||||
// one line done, check for page boundary
|
||||
if (++page == Private->PageSize) {
|
||||
if (dirty) {
|
||||
SetRowAddress( Device, r - page + 1, r );
|
||||
if (Private->iRAM) {
|
||||
memcpy(Private->iRAM, Private->Shadowbuffer + (r - page + 1) * Device->Width / 2, page * Device->Width / 2 );
|
||||
Device->WriteData( Device, Private->iRAM, Device->Width * page / 2 );
|
||||
} else {
|
||||
Device->WriteData( Device, Private->Shadowbuffer + (r - page + 1) * Device->Width / 2, page * Device->Width / 2);
|
||||
}
|
||||
dirty = false;
|
||||
}
|
||||
page = 0;
|
||||
}
|
||||
}
|
||||
#else
|
||||
SetRowAddress( Device, 0, Device->Height - 1 );
|
||||
for (int r = 0; r < Device->Height; r += Private->PageSize) {
|
||||
if (Private->iRAM) {
|
||||
memcpy(Private->iRAM, Device->Framebuffer + r * Device->Width / 2, Private->PageSize * Device->Width / 2 );
|
||||
Device->WriteData( Device, Private->iRAM, Private->PageSize * Device->Width / 2 );
|
||||
} else {
|
||||
Device->WriteData( Device, Device->Framebuffer + r * Device->Width / 2, Private->PageSize * Device->Width / 2 );
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static void SetLayout( struct GDS_Device* Device, struct GDS_Layout *Layout ) {
|
||||
if (Layout->HFlip) {
|
||||
Device->WriteCommand( Device, 0x40 + 0x20 );
|
||||
Device->WriteCommand( Device, 0xA1 );
|
||||
} else {
|
||||
Device->WriteCommand( Device, 0x40 + 0x00 );
|
||||
Device->WriteCommand( Device, 0xA0 );
|
||||
}
|
||||
Device->WriteCommand( Device, Layout->VFlip ? 0xC8 : 0xC0 );
|
||||
Device->WriteCommand( Device, Layout->Invert ? 0xA7 : 0xA6 );
|
||||
}
|
||||
|
||||
static void DisplayOn( struct GDS_Device* Device ) { Device->WriteCommand( Device, 0xAF ); }
|
||||
static void DisplayOff( struct GDS_Device* Device ) { Device->WriteCommand( Device, 0xAE ); }
|
||||
|
||||
static void SetContrast( struct GDS_Device* Device, uint8_t Contrast ) {
|
||||
Device->WriteCommand( Device, 0x81 );
|
||||
Device->WriteCommand( Device, Contrast );
|
||||
}
|
||||
|
||||
static bool Init( struct GDS_Device* Device ) {
|
||||
struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private;
|
||||
|
||||
// find a page size that is not too small is an integer of height
|
||||
Private->PageSize = min(8, PAGE_BLOCK / (Device->Width / 2));
|
||||
while (Private->PageSize && Device->Height != (Device->Height / Private->PageSize) * Private->PageSize) Private->PageSize--;
|
||||
|
||||
#ifdef SHADOW_BUFFER
|
||||
Private->Shadowbuffer = malloc( Device->FramebufferSize );
|
||||
memset(Private->Shadowbuffer, 0xFF, Device->FramebufferSize);
|
||||
#endif
|
||||
|
||||
// only use iRAM for SPI
|
||||
if (Device->IF == GDS_IF_SPI) {
|
||||
Private->iRAM = heap_caps_malloc( Private->PageSize * Device->Width / 2, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA );
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "SH1122 page %u, iRAM %p", Private->PageSize, Private->iRAM);
|
||||
|
||||
// need to be off and disable display RAM
|
||||
Device->DisplayOff( Device );
|
||||
Device->WriteCommand( Device, 0xA5 );
|
||||
|
||||
// Display Offset
|
||||
Device->WriteCommand( Device, 0xD3 );
|
||||
Device->WriteCommand( Device, 0 );
|
||||
|
||||
// set flip modes
|
||||
struct GDS_Layout Layout = { };
|
||||
Device->SetLayout( Device, &Layout );
|
||||
|
||||
// set Clocks => check value
|
||||
Device->WriteCommand( Device, 0xD5 );
|
||||
Device->WriteCommand( Device, ( 0x04 << 4 ) | 0x00 );
|
||||
|
||||
// MUX Ratio => fixed
|
||||
Device->WriteCommand( Device, 0xA8 );
|
||||
Device->WriteCommand( Device, Device->Height - 1);
|
||||
|
||||
// no Display Inversion
|
||||
Device->WriteCommand( Device, 0xA6 );
|
||||
|
||||
// gone with the wind
|
||||
Device->WriteCommand( Device, 0xA4 );
|
||||
Device->DisplayOn( Device );
|
||||
Device->Update( Device );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static const struct GDS_Device SH1122 = {
|
||||
.DisplayOn = DisplayOn, .DisplayOff = DisplayOff, .SetContrast = SetContrast,
|
||||
.SetLayout = SetLayout,
|
||||
.Update = Update, .Init = Init,
|
||||
.Mode = GDS_GRAYSCALE, .Depth = 4,
|
||||
.HighNibble = true,
|
||||
};
|
||||
|
||||
struct GDS_Device* SH1122_Detect(char *Driver, struct GDS_Device* Device) {
|
||||
if (!strcasestr(Driver, "SH1122")) return NULL;
|
||||
|
||||
if (!Device) Device = calloc(1, sizeof(struct GDS_Device));
|
||||
*Device = SH1122;
|
||||
|
||||
return Device;
|
||||
}
|
||||
@@ -71,8 +71,8 @@ static void Update( struct GDS_Device* Device ) {
|
||||
if (dirty) {
|
||||
uint16_t *optr = (uint16_t*) Private->iRAM, *iptr = (uint16_t*) (Private->Shadowbuffer + (r - page + 1) * Device->Width / 2);
|
||||
SetRowAddress( Device, r - page + 1, r );
|
||||
// need byte swapping
|
||||
for (int i = page * Device->Width / 2 / 2; --i >= 0; iptr++) *optr++ = (*iptr >> 8) | (*iptr << 8);
|
||||
//memcpy(Private->iRAM, Private->Shadowbuffer + (r - page + 1) * Device->Width / 2, page * Device->Width / 2 );
|
||||
Device->WriteCommand( Device, 0x5c );
|
||||
Device->WriteData( Device, Private->iRAM, Device->Width * page / 2 );
|
||||
dirty = false;
|
||||
@@ -84,14 +84,10 @@ static void Update( struct GDS_Device* Device ) {
|
||||
for (int r = 0; r < Device->Height; r += Private->PageSize) {
|
||||
SetRowAddress( Device, r, r + Private->PageSize - 1 );
|
||||
Device->WriteCommand( Device, 0x5c );
|
||||
if (Private->iRAM) {
|
||||
uint16_t *optr = (uint16_t*) Private->iRAM, *iptr = (uint16_t*) (Device->Framebuffer + r * Device->Width / 2);
|
||||
for (int i = Private->PageSize * Device->Width / 2 / 2; --i >= 0; iptr++) *optr++ = (*iptr >> 8) | (*iptr << 8);
|
||||
//memcpy(Private->iRAM, Device->Framebuffer + r * Device->Width / 2, Private->PageSize * Device->Width / 2 );
|
||||
Device->WriteData( Device, Private->iRAM, Private->PageSize * Device->Width / 2 );
|
||||
} else {
|
||||
Device->WriteData( Device, Device->Framebuffer + r * Device->Width / 2, Private->PageSize * Device->Width / 2 );
|
||||
}
|
||||
// need byte swapping
|
||||
uint16_t *optr = (uint16_t*) Private->iRAM, *iptr = (uint16_t*) (Device->Framebuffer + r * Device->Width / 2);
|
||||
for (int i = Private->PageSize * Device->Width / 2 / 2; --i >= 0; iptr++) *optr++ = (*iptr >> 8) | (*iptr << 8);
|
||||
Device->WriteData( Device, Private->iRAM, Private->PageSize * Device->Width / 2 );
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2,
|
||||
int c = x1;
|
||||
// for a row that is not on a boundary, no optimization possible
|
||||
while (r & 0x07 && r <= y2) {
|
||||
for (c = x1; c <= x2; c++) DrawPixelFast( Device, c, r, Color );
|
||||
for (c = x1; c <= x2; c++) Device->DrawPixelFast( Device, c, r, Color );
|
||||
r++;
|
||||
}
|
||||
// go fast if we have more than 8 lines to write
|
||||
@@ -117,7 +117,7 @@ void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2,
|
||||
memset(optr + Width * r + x1, _Color, x2 - x1 + 1);
|
||||
r += 8;
|
||||
} else while (r <= y2) {
|
||||
for (c = x1; c <= x2; c++) DrawPixelFast( Device, c, r, Color );
|
||||
for (c = x1; c <= x2; c++) Device->DrawPixelFast( Device, c, r, Color );
|
||||
r++;
|
||||
}
|
||||
}
|
||||
@@ -133,10 +133,10 @@ void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2,
|
||||
// try to do byte processing as much as possible
|
||||
for (int r = y1; r <= y2; r++) {
|
||||
int c = x1;
|
||||
if (c & 0x01) DrawPixelFast( Device, c++, r, Color);
|
||||
if (c & 0x01) Device->DrawPixelFast( Device, c++, r, Color);
|
||||
int chunk = (x2 - c + 1) >> 1;
|
||||
memset(optr + ((r * Width + c) >> 1), _Color, chunk);
|
||||
if (c + chunk <= x2) DrawPixelFast( Device, x2, r, Color);
|
||||
if (c + chunk <= x2) Device->DrawPixelFast( Device, x2, r, Color);
|
||||
}
|
||||
}
|
||||
} else if (Device->Depth == 8) {
|
||||
@@ -148,7 +148,7 @@ void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2,
|
||||
} else {
|
||||
for (int y = y1; y <= y2; y++) {
|
||||
for (int x = x1; x <= x2; x++) {
|
||||
DrawPixelFast( Device, x, y, Color);
|
||||
Device->DrawPixelFast( Device, x, y, Color);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -171,10 +171,76 @@ bool GDS_Reset( struct GDS_Device* Device ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static void IRAM_ATTR DrawPixel1Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
|
||||
uint32_t YBit = ( Y & 0x07 );
|
||||
uint8_t* FBOffset;
|
||||
|
||||
/*
|
||||
* We only need to modify the Y coordinate since the pitch
|
||||
* of the screen is the same as the width.
|
||||
* Dividing Y by 8 gives us which row the pixel is in but not
|
||||
* the bit position.
|
||||
*/
|
||||
Y>>= 3;
|
||||
|
||||
FBOffset = Device->Framebuffer + ( ( Y * Device->Width ) + X );
|
||||
|
||||
if ( Color == GDS_COLOR_XOR ) {
|
||||
*FBOffset ^= BIT( YBit );
|
||||
} else {
|
||||
*FBOffset = ( Color == GDS_COLOR_BLACK ) ? *FBOffset & ~BIT( YBit ) : *FBOffset | BIT( YBit );
|
||||
}
|
||||
}
|
||||
|
||||
static void IRAM_ATTR DrawPixel4Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
|
||||
uint8_t* FBOffset = Device->Framebuffer + ( (Y * Device->Width >> 1) + (X >> 1));
|
||||
*FBOffset = X & 0x01 ? (*FBOffset & 0x0f) | ((Color & 0x0f) << 4) : ((*FBOffset & 0xf0) | (Color & 0x0f));
|
||||
}
|
||||
|
||||
static void IRAM_ATTR DrawPixel4FastHigh( struct GDS_Device* Device, int X, int Y, int Color ) {
|
||||
uint8_t* FBOffset = Device->Framebuffer + ( (Y * Device->Width >> 1) + (X >> 1));
|
||||
*FBOffset = X & 0x01 ? ((*FBOffset & 0xf0) | (Color & 0x0f)) : (*FBOffset & 0x0f) | ((Color & 0x0f) << 4);
|
||||
}
|
||||
|
||||
static void IRAM_ATTR DrawPixel8Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
|
||||
Device->Framebuffer[Y * Device->Width + X] = Color;
|
||||
}
|
||||
|
||||
// assumes that Color is 16 bits R..RG..GB..B from MSB to LSB and FB wants 1st serialized byte to start with R
|
||||
static void IRAM_ATTR DrawPixel16Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
|
||||
uint16_t* FBOffset = (uint16_t*) Device->Framebuffer + Y * Device->Width + X;
|
||||
*FBOffset = __builtin_bswap16(Color);
|
||||
}
|
||||
|
||||
// assumes that Color is 18 bits RGB from MSB to LSB RRRRRRGGGGGGBBBBBB, so byte[0] is B
|
||||
// FB is 3-bytes packets and starts with R for serialization so 0,1,2 ... = xxRRRRRR xxGGGGGG xxBBBBBB
|
||||
static void IRAM_ATTR DrawPixel18Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
|
||||
uint8_t* FBOffset = Device->Framebuffer + (Y * Device->Width + X) * 3;
|
||||
*FBOffset++ = Color >> 12; *FBOffset++ = (Color >> 6) & 0x3f; *FBOffset = Color & 0x3f;
|
||||
}
|
||||
|
||||
// assumes that Color is 24 bits RGB from MSB to LSB RRRRRRRRGGGGGGGGBBBBBBBB, so byte[0] is B
|
||||
// FB is 3-bytes packets and starts with R for serialization so 0,1,2 ... = RRRRRRRR GGGGGGGG BBBBBBBB
|
||||
static void IRAM_ATTR DrawPixel24Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
|
||||
uint8_t* FBOffset = Device->Framebuffer + (Y * Device->Width + X) * 3;
|
||||
*FBOffset++ = Color >> 16; *FBOffset++ = Color >> 8; *FBOffset = Color;
|
||||
}
|
||||
|
||||
bool GDS_Init( struct GDS_Device* Device ) {
|
||||
|
||||
if (Device->Depth > 8) Device->FramebufferSize = Device->Width * Device->Height * ((8 + Device->Depth - 1) / 8);
|
||||
else Device->FramebufferSize = (Device->Width * Device->Height) / (8 / Device->Depth);
|
||||
|
||||
// set the proper DrawPixel function if not already set by driver
|
||||
if (!Device->DrawPixelFast) {
|
||||
if (Device->Depth == 1) Device->DrawPixelFast = DrawPixel1Fast;
|
||||
else if (Device->Depth == 4 && Device->HighNibble) Device->DrawPixelFast = DrawPixel4FastHigh;
|
||||
else if (Device->Depth == 4) Device->DrawPixelFast = DrawPixel4Fast;
|
||||
else if (Device->Depth == 8) Device->DrawPixelFast = DrawPixel8Fast;
|
||||
else if (Device->Depth == 16) Device->DrawPixelFast = DrawPixel16Fast;
|
||||
else if (Device->Depth == 24 && Device->Mode == GDS_RGB666) Device->DrawPixelFast = DrawPixel18Fast;
|
||||
else if (Device->Depth == 24 && Device->Mode == GDS_RGB888) Device->DrawPixelFast = DrawPixel24Fast;
|
||||
}
|
||||
|
||||
// allocate FB unless explicitely asked not to
|
||||
if (!(Device->Alloc & GDS_ALLOC_NONE)) {
|
||||
|
||||
@@ -45,7 +45,7 @@ __attribute__( ( always_inline ) ) static inline void SwapInt( int* a, int* b )
|
||||
}
|
||||
|
||||
void IRAM_ATTR GDS_DrawPixelFast( struct GDS_Device* Device, int X, int Y, int Color ) {
|
||||
DrawPixelFast( Device, X, Y, Color );
|
||||
Device->DrawPixelFast( Device, X, Y, Color );
|
||||
}
|
||||
|
||||
void IRAM_ATTR GDS_DrawPixel( struct GDS_Device* Device, int X, int Y, int Color ) {
|
||||
@@ -63,7 +63,7 @@ void GDS_DrawHLine( struct GDS_Device* Device, int x, int y, int Width, int Colo
|
||||
if (y < 0) y = 0;
|
||||
else if (y >= Device->Height) y = Device->Height - 1;
|
||||
|
||||
for ( ; x < XEnd; x++ ) DrawPixelFast( Device, x, y, Color );
|
||||
for ( ; x < XEnd; x++ ) Device->DrawPixelFast( Device, x, y, Color );
|
||||
}
|
||||
|
||||
void GDS_DrawVLine( struct GDS_Device* Device, int x, int y, int Height, int Color ) {
|
||||
@@ -97,7 +97,7 @@ static inline void DrawWideLine( struct GDS_Device* Device, int x0, int y0, int
|
||||
|
||||
for ( ; x < x1; x++ ) {
|
||||
if ( IsPixelVisible( Device, x, y ) == true ) {
|
||||
DrawPixelFast( Device, x, y, Color );
|
||||
Device->DrawPixelFast( Device, x, y, Color );
|
||||
}
|
||||
|
||||
if ( Error > 0 ) {
|
||||
@@ -126,7 +126,7 @@ static inline void DrawTallLine( struct GDS_Device* Device, int x0, int y0, int
|
||||
|
||||
for ( ; y < y1; y++ ) {
|
||||
if ( IsPixelVisible( Device, x, y ) == true ) {
|
||||
DrawPixelFast( Device, x, y, Color );
|
||||
Device->DrawPixelFast( Device, x, y, Color );
|
||||
}
|
||||
|
||||
if ( Error > 0 ) {
|
||||
@@ -213,37 +213,65 @@ void GDS_DrawBitmapCBR(struct GDS_Device* Device, uint8_t *Data, int Width, int
|
||||
iptr += Height;
|
||||
}
|
||||
}
|
||||
} else if (Device->Depth == 4) {
|
||||
} else if (Device->Depth == 4) {
|
||||
uint8_t *optr = Device->Framebuffer;
|
||||
int LineLen = Device->Width >> 1;
|
||||
|
||||
Height >>= 3;
|
||||
Color &= 0x0f;
|
||||
|
||||
for (int i = Width * Height, r = 0, c = 0; --i >= 0;) {
|
||||
uint8_t Byte = BitReverseTable256[*Data++];
|
||||
// we need to linearize code to let compiler better optimize
|
||||
if (c & 0x01) {
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen;
|
||||
} else {
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen;
|
||||
}
|
||||
// end of a column, move to next one
|
||||
if (++r == Height) { c++; r = 0; optr = Device->Framebuffer + (c >> 1); }
|
||||
|
||||
if (Device->HighNibble) {
|
||||
for (int i = Width * Height, r = 0, c = 0; --i >= 0;) {
|
||||
uint8_t Byte = BitReverseTable256[*Data++];
|
||||
// we need to linearize code to let compiler better optimize
|
||||
if (c & 0x01) {
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen;
|
||||
} else {
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen;
|
||||
}
|
||||
// end of a column, move to next one
|
||||
if (++r == Height) { c++; r = 0; optr = Device->Framebuffer + (c >> 1); }
|
||||
}
|
||||
} else {
|
||||
for (int i = Width * Height, r = 0, c = 0; --i >= 0;) {
|
||||
uint8_t Byte = BitReverseTable256[*Data++];
|
||||
// we need to linearize code to let compiler better optimize
|
||||
if (c & 0x01) {
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen;
|
||||
} else {
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
|
||||
*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen;
|
||||
}
|
||||
// end of a column, move to next one
|
||||
if (++r == Height) { c++; r = 0; optr = Device->Framebuffer + (c >> 1); }
|
||||
}
|
||||
}
|
||||
} else if (Device->Depth == 8) {
|
||||
uint8_t *optr = Device->Framebuffer;
|
||||
@@ -320,14 +348,14 @@ void GDS_DrawBitmapCBR(struct GDS_Device* Device, uint8_t *Data, int Width, int
|
||||
// don't know bitdepth, use brute-force solution
|
||||
for (int i = Width * Height, r = 0, c = 0; --i >= 0;) {
|
||||
uint8_t Byte = *Data++;
|
||||
DrawPixelFast( Device, c, (r << 3) + 7, (Byte & 0x01) * Color ); Byte >>= 1;
|
||||
DrawPixelFast( Device, c, (r << 3) + 6, (Byte & 0x01) * Color ); Byte >>= 1;
|
||||
DrawPixelFast( Device, c, (r << 3) + 5, (Byte & 0x01) * Color ); Byte >>= 1;
|
||||
DrawPixelFast( Device, c, (r << 3) + 4, (Byte & 0x01) * Color ); Byte >>= 1;
|
||||
DrawPixelFast( Device, c, (r << 3) + 3, (Byte & 0x01) * Color ); Byte >>= 1;
|
||||
DrawPixelFast( Device, c, (r << 3) + 2, (Byte & 0x01) * Color ); Byte >>= 1;
|
||||
DrawPixelFast( Device, c, (r << 3) + 1, (Byte & 0x01) * Color ); Byte >>= 1;
|
||||
DrawPixelFast( Device, c, (r << 3) + 0, (Byte & 0x01) * Color );
|
||||
Device->DrawPixelFast( Device, c, (r << 3) + 7, (Byte & 0x01) * Color ); Byte >>= 1;
|
||||
Device->DrawPixelFast( Device, c, (r << 3) + 6, (Byte & 0x01) * Color ); Byte >>= 1;
|
||||
Device->DrawPixelFast( Device, c, (r << 3) + 5, (Byte & 0x01) * Color ); Byte >>= 1;
|
||||
Device->DrawPixelFast( Device, c, (r << 3) + 4, (Byte & 0x01) * Color ); Byte >>= 1;
|
||||
Device->DrawPixelFast( Device, c, (r << 3) + 3, (Byte & 0x01) * Color ); Byte >>= 1;
|
||||
Device->DrawPixelFast( Device, c, (r << 3) + 2, (Byte & 0x01) * Color ); Byte >>= 1;
|
||||
Device->DrawPixelFast( Device, c, (r << 3) + 1, (Byte & 0x01) * Color ); Byte >>= 1;
|
||||
Device->DrawPixelFast( Device, c, (r << 3) + 0, (Byte & 0x01) * Color );
|
||||
if (++r == Height) { c++; r = 0; }
|
||||
}
|
||||
/* for better understanding, here is the mundane version
|
||||
|
||||
@@ -98,6 +98,7 @@ struct GDS_Device {
|
||||
uint16_t Width, TextWidth;
|
||||
uint16_t Height;
|
||||
uint8_t Depth, Mode;
|
||||
bool HighNibble;
|
||||
|
||||
uint8_t Alloc;
|
||||
uint8_t* Framebuffer;
|
||||
@@ -155,69 +156,9 @@ static inline bool IsPixelVisible( struct GDS_Device* Device, int x, int y ) {
|
||||
return Result;
|
||||
}
|
||||
|
||||
static inline void DrawPixel1Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
|
||||
uint32_t YBit = ( Y & 0x07 );
|
||||
uint8_t* FBOffset;
|
||||
|
||||
/*
|
||||
* We only need to modify the Y coordinate since the pitch
|
||||
* of the screen is the same as the width.
|
||||
* Dividing Y by 8 gives us which row the pixel is in but not
|
||||
* the bit position.
|
||||
*/
|
||||
Y>>= 3;
|
||||
|
||||
FBOffset = Device->Framebuffer + ( ( Y * Device->Width ) + X );
|
||||
|
||||
if ( Color == GDS_COLOR_XOR ) {
|
||||
*FBOffset ^= BIT( YBit );
|
||||
} else {
|
||||
*FBOffset = ( Color == GDS_COLOR_BLACK ) ? *FBOffset & ~BIT( YBit ) : *FBOffset | BIT( YBit );
|
||||
}
|
||||
}
|
||||
|
||||
static inline void DrawPixel4Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
|
||||
uint8_t* FBOffset = Device->Framebuffer + ( (Y * Device->Width >> 1) + (X >> 1));
|
||||
*FBOffset = X & 0x01 ? (*FBOffset & 0x0f) | ((Color & 0x0f) << 4) : ((*FBOffset & 0xf0) | (Color & 0x0f));
|
||||
}
|
||||
|
||||
static inline void DrawPixel8Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
|
||||
Device->Framebuffer[Y * Device->Width + X] = Color;
|
||||
}
|
||||
|
||||
// assumes that Color is 16 bits R..RG..GB..B from MSB to LSB and FB wants 1st serialized byte to start with R
|
||||
static inline void DrawPixel16Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
|
||||
uint16_t* FBOffset = (uint16_t*) Device->Framebuffer + Y * Device->Width + X;
|
||||
*FBOffset = __builtin_bswap16(Color);
|
||||
}
|
||||
|
||||
// assumes that Color is 18 bits RGB from MSB to LSB RRRRRRGGGGGGBBBBBB, so byte[0] is B
|
||||
// FB is 3-bytes packets and starts with R for serialization so 0,1,2 ... = xxRRRRRR xxGGGGGG xxBBBBBB
|
||||
static inline void DrawPixel18Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
|
||||
uint8_t* FBOffset = Device->Framebuffer + (Y * Device->Width + X) * 3;
|
||||
*FBOffset++ = Color >> 12; *FBOffset++ = (Color >> 6) & 0x3f; *FBOffset = Color & 0x3f;
|
||||
}
|
||||
|
||||
// assumes that Color is 24 bits RGB from MSB to LSB RRRRRRRRGGGGGGGGBBBBBBBB, so byte[0] is B
|
||||
// FB is 3-bytes packets and starts with R for serialization so 0,1,2 ... = RRRRRRRR GGGGGGGG BBBBBBBB
|
||||
static inline void DrawPixel24Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
|
||||
uint8_t* FBOffset = Device->Framebuffer + (Y * Device->Width + X) * 3;
|
||||
*FBOffset++ = Color >> 16; *FBOffset++ = Color >> 8; *FBOffset = Color;
|
||||
}
|
||||
|
||||
static inline void IRAM_ATTR DrawPixelFast( struct GDS_Device* Device, int X, int Y, int Color ) {
|
||||
if (Device->DrawPixelFast) Device->DrawPixelFast( Device, X, Y, Color );
|
||||
else if (Device->Depth == 4) DrawPixel4Fast( Device, X, Y, Color );
|
||||
else if (Device->Depth == 1) DrawPixel1Fast( Device, X, Y, Color );
|
||||
else if (Device->Depth == 16) DrawPixel16Fast( Device, X, Y, Color );
|
||||
else if (Device->Depth == 24 && Device->Mode == GDS_RGB666) DrawPixel18Fast( Device, X, Y, Color );
|
||||
else if (Device->Depth == 24 && Device->Mode == GDS_RGB888) DrawPixel24Fast( Device, X, Y, Color );
|
||||
else if (Device->Depth == 8) DrawPixel8Fast( Device, X, Y, Color );
|
||||
}
|
||||
|
||||
static inline void IRAM_ATTR DrawPixel( struct GDS_Device* Device, int x, int y, int Color ) {
|
||||
if ( IsPixelVisible( Device, x, y ) == true ) {
|
||||
DrawPixelFast( Device, x, y, Color );
|
||||
Device->DrawPixelFast( Device, x, y, Color );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@ bool GDS_TextLine(struct GDS_Device* Device, int N, int Pos, int Attr, char *Tex
|
||||
int Y_min = max(0, Device->Lines[N].Y), Y_max = max(0, Device->Lines[N].Y + Device->Lines[N].Font->Height);
|
||||
for (int c = (Attr & GDS_TEXT_CLEAR_EOL) ? X : 0; c < Device->TextWidth; c++)
|
||||
for (int y = Y_min; y < Y_max; y++)
|
||||
DrawPixelFast( Device, c, y, GDS_COLOR_BLACK );
|
||||
Device->DrawPixelFast( Device, c, y, GDS_COLOR_BLACK );
|
||||
}
|
||||
|
||||
GDS_FontDrawString( Device, X, Device->Lines[N].Y, Text, GDS_COLOR_WHITE );
|
||||
|
||||
@@ -63,6 +63,7 @@ static EXT_RAM_ATTR struct {
|
||||
} displayer;
|
||||
|
||||
static const char *known_drivers[] = {"SH1106",
|
||||
"SH1122",
|
||||
"SSD1306",
|
||||
"SSD1322",
|
||||
"SSD1326",
|
||||
@@ -79,8 +80,8 @@ static void displayer_task(void *args);
|
||||
static void display_sleep(void);
|
||||
|
||||
struct GDS_Device *display;
|
||||
extern GDS_DetectFunc SSD1306_Detect, SSD132x_Detect, SH1106_Detect, SSD1675_Detect, SSD1322_Detect, SSD1351_Detect, ST77xx_Detect, ILI9341_Detect;
|
||||
GDS_DetectFunc *drivers[] = { SH1106_Detect, SSD1306_Detect, SSD132x_Detect, SSD1675_Detect, SSD1322_Detect, SSD1351_Detect, ST77xx_Detect, ILI9341_Detect, NULL };
|
||||
extern GDS_DetectFunc SSD1306_Detect, SSD132x_Detect, SH1106_Detect, SH1122_Detect, SSD1675_Detect, SSD1322_Detect, SSD1351_Detect, ST77xx_Detect, ILI9341_Detect;
|
||||
GDS_DetectFunc *drivers[] = { SH1106_Detect, SH1122_Detect, SSD1306_Detect, SSD132x_Detect, SSD1675_Detect, SSD1322_Detect, SSD1351_Detect, ST77xx_Detect, ILI9341_Detect, NULL };
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
|
||||
@@ -461,7 +461,7 @@ static void buffer_put_packet(rtp_t *ctx, seq_t seqno, unsigned rtptime, bool fi
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (seqno == (u16_t) (ctx->ab_write+1)) {
|
||||
// expected packet
|
||||
abuf = ctx->audio_buffer + BUFIDX(seqno);
|
||||
@@ -572,17 +572,25 @@ static void buffer_push_packet(rtp_t *ctx) {
|
||||
}
|
||||
|
||||
LOG_SDEBUG("playtime %u %d [W:%hu R:%hu] %d", playtime, playtime - now, ctx->ab_write, ctx->ab_read, curframe->ready);
|
||||
|
||||
// try to request resend missing packet in order, explore up to 32 frames
|
||||
for (int step = max((ctx->ab_write - ctx->ab_read + 1) / 32, 1),
|
||||
i = 0, first = 0;
|
||||
seq_order(ctx->ab_read + i, ctx->ab_write); i += step) {
|
||||
|
||||
abuf_t* frame = ctx->audio_buffer + BUFIDX(ctx->ab_read + i);
|
||||
|
||||
// each missing packet will be requested up to (latency_frames / 16) times
|
||||
for (int i = 0; seq_order(ctx->ab_read + i, ctx->ab_write); i += 16) {
|
||||
abuf_t *frame = ctx->audio_buffer + BUFIDX(ctx->ab_read + i);
|
||||
if (!frame->ready && now - frame->last_resend > RESEND_TO) {
|
||||
// stop if one fails
|
||||
if (!rtp_request_resend(ctx, ctx->ab_read + i, ctx->ab_read + i)) break;
|
||||
frame->last_resend = now;
|
||||
}
|
||||
}
|
||||
|
||||
// stop when we reach a ready frame or a recent pending resend
|
||||
if (first && (frame->ready || now - frame->last_resend <= RESEND_TO)) {
|
||||
if (!rtp_request_resend(ctx, first, ctx->ab_read + i - 1)) break;
|
||||
first = 0;
|
||||
i += step - 1;
|
||||
} else if (!frame->ready && now - frame->last_resend > RESEND_TO) {
|
||||
if (!first) first = ctx->ab_read + i;
|
||||
frame->last_resend = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
|
||||
@@ -62,7 +62,7 @@ static void task_stats( cJSON* top ) {
|
||||
current.n = uxTaskGetSystemState( current.tasks, current.n, ¤t.total );
|
||||
cJSON_AddNumberToObject(top,"ntasks",current.n);
|
||||
|
||||
char scratch[SCRATCH_SIZE] = { };
|
||||
char scratch[SCRATCH_SIZE] = {0};
|
||||
|
||||
#ifdef CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS
|
||||
#pragma message("Compiled with runtime stats")
|
||||
@@ -143,11 +143,13 @@ static void monitor_trace(uint32_t now) {
|
||||
heap_caps_get_minimum_free_size(MALLOC_CAP_DMA));
|
||||
|
||||
task_stats(top);
|
||||
|
||||
char * top_a= cJSON_PrintUnformatted(top);
|
||||
if(top_a){
|
||||
messaging_post_message(MESSAGING_INFO, MESSAGING_CLASS_STATS,top_a);
|
||||
FREE_AND_NULL(top_a);
|
||||
}
|
||||
|
||||
cJSON_Delete(top);
|
||||
}
|
||||
|
||||
|
||||
@@ -361,7 +361,7 @@ void services_init(void) {
|
||||
}
|
||||
#endif
|
||||
|
||||
// set potential power GPIO on chip first in case expanders are power using these
|
||||
// set potential power GPIO on chip first in case expanders are powered using these
|
||||
parse_set_GPIO(set_chip_power_gpio);
|
||||
|
||||
// shared I2C bus
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -227,7 +227,7 @@ int main(int argc, char *argv[]) {
|
||||
} else if (!strcasecmp(arg, "-i")) {
|
||||
identity = *++argv;
|
||||
} else {
|
||||
// nothing let's try to be smart and handle legacy crappy
|
||||
// nothing let's try to be smart and handle legacy crap
|
||||
if (!identity) identity = *argv;
|
||||
else if (!type) (void) !asprintf(&type, "%s.local", *argv);
|
||||
else if (!port) port = atoi(*argv);
|
||||
@@ -235,6 +235,7 @@ int main(int argc, char *argv[]) {
|
||||
txt = (const char**) malloc((argc + 1) * sizeof(char**));
|
||||
memcpy(txt, argv, argc * sizeof(char**));
|
||||
txt[argc] = NULL;
|
||||
break;
|
||||
}
|
||||
argc--;
|
||||
}
|
||||
@@ -250,13 +251,14 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
mdnsd_set_hostname(svr, hostname, host);
|
||||
svc = mdnsd_register_svc(svr, identity, type, port, NULL, txt);
|
||||
mdns_service_destroy(svc);
|
||||
// mdns_service_destroy(svc);
|
||||
|
||||
#ifdef _WIN32
|
||||
Sleep(INFINITE);
|
||||
#else
|
||||
pause();
|
||||
#endif
|
||||
mdns_service_remove(svr, svc);
|
||||
mdnsd_stop(svr);
|
||||
} else {
|
||||
printf("Can't start server");
|
||||
@@ -264,7 +266,7 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
|
||||
free(type);
|
||||
free(txt);
|
||||
if (txt) free(txt);
|
||||
|
||||
#ifdef _WIN32
|
||||
winsock_close();
|
||||
|
||||
@@ -144,11 +144,10 @@ static int create_recv_sock(uint32_t host) {
|
||||
}
|
||||
|
||||
#if !defined(_WIN32)
|
||||
on = sizeof(on);
|
||||
socklen_t len;
|
||||
if (!getsockopt(sd, SOL_SOCKET, SO_REUSEPORT,(char*) &on, &len)) {
|
||||
socklen_t len = sizeof(on);
|
||||
if (!getsockopt(sd, SOL_SOCKET, SO_REUSEPORT, &on, &len)) {
|
||||
on = 1;
|
||||
if ((r = setsockopt(sd, SOL_SOCKET, SO_REUSEPORT,(char*) &on, sizeof(on))) < 0) {
|
||||
if ((r = setsockopt(sd, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on))) < 0) {
|
||||
log_message(LOG_ERR, "recv setsockopt(SO_REUSEPORT): %m\n", r);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <unistd.h>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
|
||||
#if __has_include("avahi-client/client.h")
|
||||
#include <avahi-client/client.h>
|
||||
|
||||
@@ -201,21 +201,23 @@ void SpircHandler::handleFrame(std::vector<uint8_t>& data) {
|
||||
break;
|
||||
}
|
||||
case MessageType_kMessageTypeReplace: {
|
||||
CSPOT_LOG(debug, "Got replace frame");
|
||||
CSPOT_LOG(debug, "Got replace frame %d",
|
||||
playbackState->remoteTracks.size());
|
||||
playbackState->syncWithRemote();
|
||||
|
||||
// 1st track is the current one, but update the position
|
||||
bool cleared = trackQueue->updateTracks(
|
||||
playbackState->remoteFrame.state.position_ms +
|
||||
ctx->timeProvider->getSyncedTimestamp() -
|
||||
playbackState->innerFrame.state.position_measured_at);
|
||||
ctx->timeProvider->getSyncedTimestamp() -
|
||||
playbackState->innerFrame.state.position_measured_at,
|
||||
false);
|
||||
|
||||
this->notify();
|
||||
|
||||
// need to re-load all if streaming track is completed
|
||||
if (cleared) {
|
||||
sendEvent(EventType::FLUSH);
|
||||
trackPlayer->resetState();
|
||||
sendEvent(EventType::FLUSH);
|
||||
trackPlayer->resetState();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -591,15 +591,14 @@ bool TrackQueue::updateTracks(uint32_t requestedPosition, bool initial) {
|
||||
std::scoped_lock lock(tracksMutex);
|
||||
bool cleared = true;
|
||||
|
||||
// Copy requested track list
|
||||
currentTracks = playbackState->remoteTracks;
|
||||
currentTracksIndex = playbackState->innerFrame.state.playing_track_index;
|
||||
|
||||
if (initial) {
|
||||
// Clear preloaded tracks
|
||||
preloadedTracks.clear();
|
||||
|
||||
// Copy requested track list
|
||||
currentTracks = playbackState->remoteTracks;
|
||||
|
||||
currentTracksIndex = playbackState->innerFrame.state.playing_track_index;
|
||||
|
||||
if (currentTracksIndex < currentTracks.size()) {
|
||||
// Push a song on the preloaded queue
|
||||
queueNextTrack(0, requestedPosition);
|
||||
@@ -609,28 +608,24 @@ bool TrackQueue::updateTracks(uint32_t requestedPosition, bool initial) {
|
||||
notifyPending = true;
|
||||
|
||||
playableSemaphore->give();
|
||||
} else {
|
||||
// Copy requested track list
|
||||
currentTracks = playbackState->remoteTracks;
|
||||
|
||||
} else if (preloadedTracks[0]->loading) {
|
||||
// try to not re-load track if we are still loading it
|
||||
if (preloadedTracks[0]->loading) {
|
||||
// remove everything except first track
|
||||
preloadedTracks.erase(preloadedTracks.begin() + 1, preloadedTracks.end());
|
||||
|
||||
// Push a song on the preloaded queue
|
||||
CSPOT_LOG(info, "Keeping current track");
|
||||
queueNextTrack(1);
|
||||
// remove everything except first track
|
||||
preloadedTracks.erase(preloadedTracks.begin() + 1, preloadedTracks.end());
|
||||
|
||||
cleared = false;
|
||||
} else {
|
||||
// Clear preloaded tracks
|
||||
preloadedTracks.clear();
|
||||
// Push a song on the preloaded queue
|
||||
CSPOT_LOG(info, "Keeping current track %d", currentTracksIndex);
|
||||
queueNextTrack(1);
|
||||
|
||||
// Push a song on the preloaded queue
|
||||
CSPOT_LOG(info, "Re-loading current track");
|
||||
queueNextTrack(0, requestedPosition);
|
||||
}
|
||||
cleared = false;
|
||||
} else {
|
||||
// Clear preloaded tracks
|
||||
preloadedTracks.clear();
|
||||
|
||||
// Push a song on the preloaded queue
|
||||
CSPOT_LOG(info, "Re-loading current track");
|
||||
queueNextTrack(0, requestedPosition);
|
||||
}
|
||||
|
||||
return cleared;
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
#define MAX_OPUS_FRAMES 5760
|
||||
|
||||
struct opus {
|
||||
enum {OGG_SYNC, OGG_ID_HEADER, OGG_COMMENT_HEADER} status;
|
||||
enum { OGG_ID_HEADER, OGG_COMMENT_HEADER } status;
|
||||
ogg_stream_state state;
|
||||
ogg_packet packet;
|
||||
ogg_sync_state sync;
|
||||
@@ -131,95 +131,109 @@ static opus_uint32 parse_uint32(const unsigned char* _data) {
|
||||
(opus_uint32)_data[2] << 16 | (opus_uint32)_data[3] << 24;
|
||||
}
|
||||
|
||||
static int get_opus_packet(void) {
|
||||
static int get_audio_packet(void) {
|
||||
int status, packet = -1;
|
||||
|
||||
LOCK_S;
|
||||
size_t bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf));
|
||||
|
||||
|
||||
while (!(status = OG(&go, stream_packetout, &u->state, &u->packet)) && bytes) {
|
||||
|
||||
// if sync_pageout (or sync_pageseek) is not called here, sync builds ups
|
||||
while (!(status = OG(&go, sync_pageout, &u->sync, &u->page)) && bytes) {
|
||||
|
||||
// if sync_pageout (or sync_pageseek) is not called here, sync buffers build up
|
||||
while (!(status = OG(&go, sync_pageout, &u->sync, &u->page)) && bytes) {
|
||||
size_t consumed = min(bytes, 4096);
|
||||
char* buffer = OG(&gu, sync_buffer, &u->sync, consumed);
|
||||
char* buffer = OG(&go, sync_buffer, &u->sync, consumed);
|
||||
memcpy(buffer, streambuf->readp, consumed);
|
||||
OG(&gu, sync_wrote, &u->sync, consumed);
|
||||
OG(&go, sync_wrote, &u->sync, consumed);
|
||||
|
||||
_buf_inc_readp(streambuf, consumed);
|
||||
bytes -= consumed;
|
||||
}
|
||||
}
|
||||
|
||||
// if we have a new page, put it in
|
||||
if (status) OG(&go, stream_pagein, &u->state, &u->page);
|
||||
}
|
||||
|
||||
// only return a negative value when true end of streaming is reached
|
||||
if (status > 0) packet = status;
|
||||
else if (stream.state > DISCONNECT || _buf_used(streambuf)) packet = 0;
|
||||
// if we have a new page, put it in and reset serialno at BoS
|
||||
if (status) {
|
||||
OG(&go, stream_pagein, &u->state, &u->page);
|
||||
if (OG(&go, page_bos, &u->page)) OG(&go, stream_reset_serialno, &u->state, OG(&go, page_serialno, &u->page));
|
||||
}
|
||||
}
|
||||
|
||||
/* discard header packets. With no packet, we return a negative value
|
||||
* when there is really nothing more to proceed */
|
||||
if (status > 0 && memcmp(u->packet.packet, "OpusHead", 8) && memcmp(u->packet.packet, "OpusTags", 8)) packet = status;
|
||||
else if (stream.state > DISCONNECT || _buf_used(streambuf)) packet = 0;
|
||||
|
||||
UNLOCK_S;
|
||||
return packet;
|
||||
}
|
||||
|
||||
static int read_opus_header(void) {
|
||||
int status = 0;
|
||||
bool fetch = true;
|
||||
int done = 0;
|
||||
bool fetch = true;
|
||||
|
||||
LOCK_S;
|
||||
size_t bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf));
|
||||
|
||||
while (bytes && !status) {
|
||||
// first fetch a page if we need one
|
||||
if (fetch) {
|
||||
while (bytes && !done) {
|
||||
int status;
|
||||
|
||||
// get aligned to a page and ready to bring it in
|
||||
do {
|
||||
size_t consumed = min(bytes, 4096);
|
||||
char* buffer = OG(&gu, sync_buffer, &u->sync, consumed);
|
||||
|
||||
char* buffer = OG(&go, sync_buffer, &u->sync, consumed);
|
||||
memcpy(buffer, streambuf->readp, consumed);
|
||||
OG(&gu, sync_wrote, &u->sync, consumed);
|
||||
OG(&go, sync_wrote, &u->sync, consumed);
|
||||
|
||||
_buf_inc_readp(streambuf, consumed);
|
||||
bytes -= consumed;
|
||||
|
||||
if (!OG(&gu, sync_pageseek, &u->sync, &u->page)) continue;
|
||||
}
|
||||
status = fetch ? OG(&go, sync_pageout, &u->sync, &u->page) :
|
||||
OG(&go, sync_pageseek, &u->sync, &u->page);
|
||||
} while (bytes && status <= 0);
|
||||
|
||||
switch (u->status) {
|
||||
case OGG_SYNC:
|
||||
u->status = OGG_ID_HEADER;
|
||||
OG(&gu, stream_reset_serialno, &u->state, OG(&gu, page_serialno, &u->page));
|
||||
fetch = false;
|
||||
break;
|
||||
case OGG_ID_HEADER:
|
||||
status = OG(&gu, stream_pagein, &u->state, &u->page);
|
||||
if (OG(&gu, stream_packetout, &u->state, &u->packet)) {
|
||||
if (u->packet.bytes < 19 || memcmp(u->packet.packet, "OpusHead", 8)) {
|
||||
LOG_ERROR("wrong opus header packet (size:%u)", u->packet.bytes);
|
||||
status = -100;
|
||||
break;
|
||||
}
|
||||
u->status = OGG_COMMENT_HEADER;
|
||||
// nothing has been found and we have no more bytes, come back later
|
||||
if (status <= 0) break;
|
||||
|
||||
// always set stream serialno if we have a new one
|
||||
if (OG(&go, page_bos, &u->page)) OG(&go, stream_reset_serialno, &u->state, OG(&go, page_serialno, &u->page));
|
||||
|
||||
// bring new page in if we want it (otherwise we're just skipping)
|
||||
if (fetch) OG(&go, stream_pagein, &u->state, &u->page);
|
||||
|
||||
// no need for a switch...case
|
||||
if (u->status == OGG_ID_HEADER) {
|
||||
// we need the id packet, get more pages if we don't
|
||||
if (OG(&go, stream_packetout, &u->state, &u->packet) <= 0) continue;
|
||||
|
||||
// make sure this is a valid packet
|
||||
if (u->packet.bytes < 19 || memcmp(u->packet.packet, "OpusHead", 8)) {
|
||||
LOG_ERROR("wrong header packet (size:%u)", u->packet.bytes);
|
||||
done = -100;
|
||||
} else {
|
||||
u->status = OGG_COMMENT_HEADER;
|
||||
u->channels = u->packet.packet[9];
|
||||
u->pre_skip = parse_uint16(u->packet.packet + 10);
|
||||
u->rate = parse_uint32(u->packet.packet + 12);
|
||||
u->gain = parse_int16(u->packet.packet + 16);
|
||||
u->decoder = OP(&gu, decoder_create, 48000, u->channels, &status);
|
||||
fetch = false;
|
||||
if (!u->decoder || status != OPUS_OK) {
|
||||
LOG_ERROR("can't create decoder %d (channels:%u)", status, u->channels);
|
||||
}
|
||||
else {
|
||||
LOG_INFO("codec up and running");
|
||||
}
|
||||
}
|
||||
fetch = true;
|
||||
break;
|
||||
case OGG_COMMENT_HEADER:
|
||||
// skip packets to consume VorbisComment. With opus, header packets align on pages
|
||||
status = OG(&gu, page_packets, &u->page);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
} else if (u->status == OGG_COMMENT_HEADER) {
|
||||
// don't consume VorbisComment which could be a huge packet, just skip it
|
||||
if (!OG(&go, page_packets, &u->page)) continue;
|
||||
LOG_INFO("comment skipped successfully");
|
||||
done = 1;
|
||||
}
|
||||
}
|
||||
|
||||
UNLOCK_S;
|
||||
return status;
|
||||
return done;
|
||||
}
|
||||
|
||||
static decode_state opus_decompress(void) {
|
||||
@@ -271,7 +285,7 @@ static decode_state opus_decompress(void) {
|
||||
memcpy(write_buf, u->overbuf, u->overframes * BYTES_PER_FRAME);
|
||||
n = u->overframes;
|
||||
u->overframes = 0;
|
||||
} else if ((packet = get_opus_packet()) > 0) {
|
||||
} else if ((packet = get_audio_packet()) > 0) {
|
||||
if (frames < MAX_OPUS_FRAMES) {
|
||||
// don't have enough contiguous space, use the overflow buffer
|
||||
n = OP(&gu, decode, u->decoder, u->packet.packet, u->packet.bytes, (opus_int16*) u->overbuf, MAX_OPUS_FRAMES, 0);
|
||||
@@ -286,7 +300,7 @@ static decode_state opus_decompress(void) {
|
||||
* outputbuf and streambuf for maybe a long time while we process it all, so don't do that */
|
||||
n = OP(&gu, decode, u->decoder, u->packet.packet, u->packet.bytes, (opus_int16*) write_buf, frames, 0);
|
||||
}
|
||||
} else if (!packet && !OG(&go, page_eos, &u->page)) {
|
||||
} else if (!packet) {
|
||||
UNLOCK_O_direct;
|
||||
return DECODE_RUNNING;
|
||||
}
|
||||
@@ -342,7 +356,7 @@ static decode_state opus_decompress(void) {
|
||||
|
||||
} else {
|
||||
|
||||
LOG_INFO("opus decode error: %d", n);
|
||||
LOG_INFO("decode error: %d", n);
|
||||
UNLOCK_O_direct;
|
||||
return DECODE_COMPLETE;
|
||||
}
|
||||
@@ -357,7 +371,7 @@ static void opus_open(u8_t size, u8_t rate, u8_t chan, u8_t endianness) {
|
||||
|
||||
if (!u->overbuf) u->overbuf = malloc(MAX_OPUS_FRAMES * BYTES_PER_FRAME);
|
||||
|
||||
u->status = OGG_SYNC;
|
||||
u->status = OGG_ID_HEADER;
|
||||
u->overframes = 0;
|
||||
|
||||
OG(&go, stream_clear, &u->state);
|
||||
|
||||
@@ -443,7 +443,7 @@ void output_close_common(void) {
|
||||
}
|
||||
|
||||
void output_flush(void) {
|
||||
LOG_INFO("flush output buffer");
|
||||
LOG_INFO("flush output buffer (full)");
|
||||
buf_flush(outputbuf);
|
||||
LOCK;
|
||||
output.fade = FADE_INACTIVE;
|
||||
@@ -457,3 +457,15 @@ void output_flush(void) {
|
||||
output.frames_played = 0;
|
||||
UNLOCK;
|
||||
}
|
||||
|
||||
bool output_flush_streaming(void) {
|
||||
LOG_INFO("flush output buffer (streaming)");
|
||||
LOCK;
|
||||
bool flushed = output.track_start != NULL;
|
||||
if (output.track_start) {
|
||||
outputbuf->writep = output.track_start;
|
||||
output.track_start = NULL;
|
||||
}
|
||||
UNLOCK;
|
||||
return flushed;
|
||||
}
|
||||
|
||||
@@ -305,8 +305,18 @@ static void process_strm(u8_t *pkt, int len) {
|
||||
sendSTAT("STMt", strm->replay_gain); // STMt replay_gain is no longer used to track latency, but support it
|
||||
break;
|
||||
case 'f':
|
||||
{
|
||||
decode_flush(false);
|
||||
bool flushed = false;
|
||||
if (!output.external) flushed |= output_flush_streaming();
|
||||
// we can have fully finished the current streaming, that's still a flush
|
||||
if (stream_disconnect() || flushed) sendSTAT("STMf", 0);
|
||||
buf_flush(streambuf);
|
||||
output.stop_time = gettime_ms();
|
||||
break;
|
||||
}
|
||||
case 'q':
|
||||
decode_flush(strm->command == 'q');
|
||||
decode_flush(true);
|
||||
if (!output.external) output_flush();
|
||||
status.frames_played = 0;
|
||||
if (stream_disconnect() && strm->command == 'f') sendSTAT("STMf", 0);
|
||||
@@ -383,7 +393,7 @@ static void process_strm(u8_t *pkt, int len) {
|
||||
stream_file(header, header_len, strm->threshold * 1024);
|
||||
autostart -= 2;
|
||||
} else {
|
||||
stream_sock(ip, port, header, header_len, strm->threshold * 1024, autostart >= 2);
|
||||
stream_sock(ip, port, strm->format, header, header_len, strm->threshold * 1024, autostart >= 2);
|
||||
}
|
||||
sendSTAT("STMc", 0);
|
||||
sentSTMu = sentSTMo = sentSTMl = false;
|
||||
|
||||
@@ -580,12 +580,26 @@ struct streamstate {
|
||||
u32_t meta_next;
|
||||
u32_t meta_left;
|
||||
bool meta_send;
|
||||
struct {
|
||||
enum { STREAM_OGG_OFF, STREAM_OGG_SYNC, STREAM_OGG_HEADER, STREAM_OGG_SEGMENTS, STREAM_OGG_PAGE } state;
|
||||
u32_t want, miss, match;
|
||||
u8_t* data, segments[255];
|
||||
#pragma pack(push, 1)
|
||||
struct {
|
||||
char pattern[4];
|
||||
u8_t version, type;
|
||||
u64_t granule;
|
||||
u32_t serial, page, checksum;
|
||||
u8_t count;
|
||||
} header;
|
||||
} ogg;
|
||||
#pragma pack(pop)
|
||||
};
|
||||
|
||||
void stream_init(log_level level, unsigned stream_buf_size);
|
||||
void stream_close(void);
|
||||
void stream_file(const char *header, size_t header_len, unsigned threshold);
|
||||
void stream_sock(u32_t ip, u16_t port, const char *header, size_t header_len, unsigned threshold, bool cont_wait);
|
||||
void stream_sock(u32_t ip, u16_t port, char codec, const char *header, size_t header_len, unsigned threshold, bool cont_wait);
|
||||
bool stream_disconnect(void);
|
||||
|
||||
// decode.c
|
||||
@@ -727,6 +741,7 @@ struct outputstate {
|
||||
void output_init_common(log_level level, const char *device, unsigned output_buf_size, unsigned rates[], unsigned idle);
|
||||
void output_close_common(void);
|
||||
void output_flush(void);
|
||||
bool output_flush_streaming(void);
|
||||
// _* called with mutex locked
|
||||
frames_t _output_frames(frames_t avail);
|
||||
void _checkfade(bool);
|
||||
|
||||
@@ -59,7 +59,7 @@ is enough and much faster than a mutex
|
||||
static bool polling;
|
||||
static sockfd fd;
|
||||
|
||||
struct streamstate stream;
|
||||
struct EXT_RAM_ATTR streamstate stream;
|
||||
|
||||
#if USE_SSL
|
||||
static SSL_CTX *SSLctx;
|
||||
@@ -148,6 +148,8 @@ static bool running = true;
|
||||
static void _disconnect(stream_state state, disconnect_code disconnect) {
|
||||
stream.state = state;
|
||||
stream.disconnect = disconnect;
|
||||
if (stream.ogg.state == STREAM_OGG_PAGE && stream.ogg.data) free(stream.ogg.data);
|
||||
stream.ogg.data = NULL;
|
||||
#if USE_SSL
|
||||
if (ssl) {
|
||||
SSL_shutdown(ssl);
|
||||
@@ -160,6 +162,121 @@ static void _disconnect(stream_state state, disconnect_code disconnect) {
|
||||
wake_controller();
|
||||
}
|
||||
|
||||
static u32_t memfind(const u8_t* haystack, u32_t n, const char* needle, u32_t len, u32_t *offset) {
|
||||
int i;
|
||||
for (i = 0; i < n && *offset != len; i++) *offset = (haystack[i] == needle[*offset]) ? *offset + 1 : 0;
|
||||
return i;
|
||||
}
|
||||
|
||||
static void stream_ogg(size_t n) {
|
||||
if (stream.ogg.state == STREAM_OGG_OFF) return;
|
||||
u8_t* p = streambuf->writep;
|
||||
|
||||
while (n) {
|
||||
size_t consumed = min(stream.ogg.miss, n);
|
||||
|
||||
// copy as many bytes as possible and come back later if we do'nt have enough
|
||||
if (stream.ogg.data) {
|
||||
memcpy(stream.ogg.data + stream.ogg.want - stream.ogg.miss, p, consumed);
|
||||
stream.ogg.miss -= consumed;
|
||||
if (stream.ogg.miss) return;
|
||||
}
|
||||
|
||||
// we have what we want, let's parse
|
||||
switch (stream.ogg.state) {
|
||||
case STREAM_OGG_SYNC: {
|
||||
stream.ogg.miss -= consumed;
|
||||
if (consumed) break;
|
||||
|
||||
// we have to memorize position in case any of last 3 bytes match...
|
||||
int pos = memfind(p, n, "OggS", 4, &stream.ogg.match);
|
||||
if (stream.ogg.match == 4) {
|
||||
consumed = pos - stream.ogg.match;
|
||||
stream.ogg.state = STREAM_OGG_HEADER;
|
||||
stream.ogg.miss = stream.ogg.want = sizeof(stream.ogg.header);
|
||||
stream.ogg.data = (u8_t*) &stream.ogg.header;
|
||||
stream.ogg.match = 0;
|
||||
} else {
|
||||
LOG_INFO("OggS not at expected position");
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case STREAM_OGG_HEADER:
|
||||
if (!memcmp(stream.ogg.header.pattern, "OggS", 4)) {
|
||||
stream.ogg.miss = stream.ogg.want = stream.ogg.header.count;
|
||||
stream.ogg.data = stream.ogg.segments;
|
||||
stream.ogg.state = STREAM_OGG_SEGMENTS;
|
||||
} else {
|
||||
stream.ogg.state = STREAM_OGG_SYNC;
|
||||
stream.ogg.data = NULL;
|
||||
}
|
||||
break;
|
||||
case STREAM_OGG_SEGMENTS:
|
||||
// calculate size of page using lacing values
|
||||
for (int i = 0; i < stream.ogg.want; i++) stream.ogg.miss += stream.ogg.data[i];
|
||||
stream.ogg.want = stream.ogg.miss;
|
||||
|
||||
if (stream.ogg.header.granule == 0) {
|
||||
// granule 0 means a new stream, so let's look into it
|
||||
stream.ogg.state = STREAM_OGG_PAGE;
|
||||
stream.ogg.data = malloc(stream.ogg.want);
|
||||
} else {
|
||||
// otherwise, jump over data
|
||||
stream.ogg.state = STREAM_OGG_SYNC;
|
||||
stream.ogg.data = NULL;
|
||||
}
|
||||
break;
|
||||
case STREAM_OGG_PAGE: {
|
||||
u32_t offset = 0;
|
||||
|
||||
// try to find one of valid Ogg pattern (vorbis, opus)
|
||||
for (char** tag = (char*[]) { "\x3vorbis", "OpusTags", NULL }; *tag; tag++, offset = 0) {
|
||||
u32_t pos = memfind(stream.ogg.data, stream.ogg.want, *tag, strlen(*tag), &offset);
|
||||
if (offset != strlen(*tag)) continue;
|
||||
|
||||
// u32:len,char[]:vendorId, u32:N, N x (u32:len,char[]:comment)
|
||||
char* p = (char*) stream.ogg.data + pos;
|
||||
p += *p + 4;
|
||||
u32_t count = *p;
|
||||
p += 4;
|
||||
|
||||
// LMS metadata format for Ogg is "Ogg", N x (u16:len,char[]:comment)
|
||||
memcpy(stream.header, "Ogg", 3);
|
||||
stream.header_len = 3;
|
||||
|
||||
for (u32_t len; count--; p += len) {
|
||||
len = *p;
|
||||
p += 4;
|
||||
|
||||
// only report what we use and don't overflow (network byte order)
|
||||
if (!strncasecmp(p, "TITLE=", 6) || !strncasecmp(p, "ARTIST=", 7) || !strncasecmp(p, "ALBUM=", 6)) {
|
||||
if (stream.header_len + len > MAX_HEADER) break;
|
||||
stream.header[stream.header_len++] = len >> 8;
|
||||
stream.header[stream.header_len++] = len;
|
||||
memcpy(stream.header + stream.header_len, p, len);
|
||||
stream.header_len += len;
|
||||
}
|
||||
}
|
||||
|
||||
stream.meta_send = true;
|
||||
wake_controller();
|
||||
LOG_INFO("Ogg metadata length: %u", stream.header_len - 3);
|
||||
break;
|
||||
}
|
||||
free(stream.ogg.data);
|
||||
stream.ogg.state = STREAM_OGG_SYNC;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
p += consumed;
|
||||
n -= consumed;
|
||||
}
|
||||
}
|
||||
|
||||
static void *stream_thread() {
|
||||
|
||||
while (running) {
|
||||
@@ -343,6 +460,7 @@ static void *stream_thread() {
|
||||
}
|
||||
|
||||
if (n > 0) {
|
||||
stream_ogg(n);
|
||||
_buf_inc_writep(streambuf, n);
|
||||
stream.bytes += n;
|
||||
if (stream.meta_interval) {
|
||||
@@ -485,7 +603,7 @@ void stream_file(const char *header, size_t header_len, unsigned threshold) {
|
||||
UNLOCK;
|
||||
}
|
||||
|
||||
void stream_sock(u32_t ip, u16_t port, const char *header, size_t header_len, unsigned threshold, bool cont_wait) {
|
||||
void stream_sock(u32_t ip, u16_t port, char codec, const char *header, size_t header_len, unsigned threshold, bool cont_wait) {
|
||||
struct sockaddr_in addr;
|
||||
|
||||
#if EMBEDDED
|
||||
@@ -584,6 +702,9 @@ void stream_sock(u32_t ip, u16_t port, const char *header, size_t header_len, un
|
||||
stream.sent_headers = false;
|
||||
stream.bytes = 0;
|
||||
stream.threshold = threshold;
|
||||
|
||||
stream.ogg.miss = stream.ogg.match = 0;
|
||||
stream.ogg.state = (codec == 'o' || codec == 'p') ? STREAM_OGG_SYNC : STREAM_OGG_OFF;
|
||||
|
||||
UNLOCK;
|
||||
}
|
||||
@@ -604,6 +725,8 @@ bool stream_disconnect(void) {
|
||||
disc = true;
|
||||
}
|
||||
stream.state = STOPPED;
|
||||
if (stream.ogg.state == STREAM_OGG_PAGE && stream.ogg.data) free(stream.ogg.data);
|
||||
stream.ogg.data = NULL;
|
||||
UNLOCK;
|
||||
return disc;
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ static inline int32_t clip15(int32_t x) {
|
||||
|
||||
struct vorbis {
|
||||
bool opened;
|
||||
enum { OGG_SYNC, OGG_ID_HEADER, OGG_COMMENT_HEADER, OGG_SETUP_HEADER } status;
|
||||
enum { OGG_ID_HEADER, OGG_COMMENT_HEADER, OGG_SETUP_HEADER } status;
|
||||
struct {
|
||||
ogg_stream_state state;
|
||||
ogg_packet packet;
|
||||
@@ -138,48 +138,16 @@ extern struct processstate process;
|
||||
#define OG(h, fn, ...) (h)->ogg_ ## fn(__VA_ARGS__)
|
||||
#endif
|
||||
|
||||
static int get_ogg_packet(void) {
|
||||
static int get_audio_packet(void) {
|
||||
int status, packet = -1;
|
||||
|
||||
LOCK_S;
|
||||
size_t bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf));
|
||||
|
||||
while (!(status = OG(&go, stream_packetout, &v->state, &v->packet)) && bytes) {
|
||||
|
||||
// if sync_pageout (or sync_pageseek) is not called first, sync buffers build ups
|
||||
while (!(status = OG(&go, sync_pageout, &v->sync, &v->page)) && bytes) {
|
||||
size_t consumed = min(bytes, 4096);
|
||||
char* buffer = OG(&gv, sync_buffer, &v->sync, consumed);
|
||||
memcpy(buffer, streambuf->readp, consumed);
|
||||
OG(&gv, sync_wrote, &v->sync, consumed);
|
||||
|
||||
_buf_inc_readp(streambuf, consumed);
|
||||
bytes -= consumed;
|
||||
}
|
||||
|
||||
// if we have a new page, put it in
|
||||
if (status) OG(&go, stream_pagein, &v->state, &v->page);
|
||||
}
|
||||
|
||||
// only return a negative value when true end of streaming is reached
|
||||
if (status > 0) packet = status;
|
||||
else if (stream.state > DISCONNECT || _buf_used(streambuf)) packet = 0;
|
||||
|
||||
UNLOCK_S;
|
||||
return packet;
|
||||
}
|
||||
|
||||
static int read_vorbis_header(void) {
|
||||
int status = 0;
|
||||
bool fetch = true;
|
||||
|
||||
LOCK_S;
|
||||
|
||||
size_t bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf));
|
||||
|
||||
while (bytes && !status) {
|
||||
// first fetch a page if we need one
|
||||
if (fetch) {
|
||||
|
||||
// if sync_pageout (or sync_pageseek) is not called here, sync buffers build up
|
||||
while (!(status = OG(&go, sync_pageout, &v->sync, &v->page)) && bytes) {
|
||||
size_t consumed = min(bytes, 4096);
|
||||
char* buffer = OG(&go, sync_buffer, &v->sync, consumed);
|
||||
memcpy(buffer, streambuf->readp, consumed);
|
||||
@@ -187,81 +155,122 @@ static int read_vorbis_header(void) {
|
||||
|
||||
_buf_inc_readp(streambuf, consumed);
|
||||
bytes -= consumed;
|
||||
|
||||
if (!OG(&go, sync_pageseek, &v->sync, &v->page)) continue;
|
||||
}
|
||||
|
||||
switch (v->status) {
|
||||
case OGG_SYNC:
|
||||
v->status = OGG_ID_HEADER;
|
||||
OG(&go, stream_reset_serialno, &v->state, OG(&go, page_serialno, &v->page));
|
||||
fetch = false;
|
||||
break;
|
||||
case OGG_ID_HEADER:
|
||||
status = OG(&go, stream_pagein, &v->state, &v->page);
|
||||
if (!OG(&go, stream_packetout, &v->state, &v->packet)) break;
|
||||
|
||||
// if we have a new page, put it in and reset serialno at BoS
|
||||
if (status) {
|
||||
OG(&go, stream_pagein, &v->state, &v->page);
|
||||
if (OG(&go, page_bos, &v->page)) OG(&go, stream_reset_serialno, &v->state, OG(&go, page_serialno, &v->page));
|
||||
}
|
||||
}
|
||||
|
||||
/* odd packets are not audio and should be discarded. With no packet, we
|
||||
* return a negative value when there is really nothing more to proceed */
|
||||
if (status > 0 && (v->packet.packet[0] & 0x01) == 0) packet = status;
|
||||
else if (stream.state > DISCONNECT || _buf_used(streambuf)) packet = 0;
|
||||
|
||||
UNLOCK_S;
|
||||
return packet;
|
||||
}
|
||||
|
||||
static int read_vorbis_header(void) {
|
||||
int done = 0;
|
||||
bool fetch = true;
|
||||
|
||||
LOCK_S;
|
||||
size_t bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf));
|
||||
|
||||
while (bytes && !done) {
|
||||
int status;
|
||||
|
||||
// get aligned to a page and ready to bring it in
|
||||
do {
|
||||
size_t consumed = min(bytes, 4096);
|
||||
|
||||
char* buffer = OG(&go, sync_buffer, &v->sync, consumed);
|
||||
memcpy(buffer, streambuf->readp, consumed);
|
||||
OG(&go, sync_wrote, &v->sync, consumed);
|
||||
|
||||
_buf_inc_readp(streambuf, consumed);
|
||||
bytes -= consumed;
|
||||
|
||||
status = fetch ? OG(&go, sync_pageout, &v->sync, &v->page) :
|
||||
OG(&go, sync_pageseek, &v->sync, &v->page);
|
||||
} while (bytes && status <= 0);
|
||||
|
||||
// nothing has been found and we have no more bytes, come back later
|
||||
if (status <= 0) break;
|
||||
|
||||
// always set stream serialno if we have a new one
|
||||
if (OG(&go, page_bos, &v->page)) OG(&go, stream_reset_serialno, &v->state, OG(&go, page_serialno, &v->page));
|
||||
|
||||
// bring new page in if we want it (otherwise we're just skipping)
|
||||
if (fetch) OG(&go, stream_pagein, &v->state, &v->page);
|
||||
|
||||
// not a switch...case b/c we might have multiple packets in a page in vorbis
|
||||
if (v->status == OGG_ID_HEADER) {
|
||||
// we need the id packet, get more pages if we don't
|
||||
if (!OG(&go, stream_packetout, &v->state, &v->packet)) continue;
|
||||
|
||||
OV(&gv, info_init, &v->info);
|
||||
status = OV(&gv, synthesis_headerin, &v->info, &v->comment, &v->packet);
|
||||
|
||||
if (status) {
|
||||
LOG_ERROR("vorbis id header packet error %d", status);
|
||||
status = -1;
|
||||
LOG_ERROR("id header packet error %d", status);
|
||||
done = -1;
|
||||
} else {
|
||||
v->channels = v->info.channels;
|
||||
v->rate = v->info.rate;
|
||||
v->status = OGG_COMMENT_HEADER;
|
||||
|
||||
// only fetch if no other packet already in (they should not)
|
||||
fetch = OG(&go, page_packets, &v->page) <= 1;
|
||||
if (!fetch) LOG_INFO("id packet should terminate page");
|
||||
fetch = false;
|
||||
LOG_INFO("id acquired");
|
||||
// we should only have one packet, so get next pages
|
||||
if (OG(&go, page_packets, &v->page) == 1) continue;
|
||||
}
|
||||
break;
|
||||
case OGG_SETUP_HEADER:
|
||||
// header packets don't align with pages on Vorbis (contrary to Opus)
|
||||
if (fetch) OG(&go, stream_pagein, &v->state, &v->page);
|
||||
}
|
||||
|
||||
if (v->status == OGG_COMMENT_HEADER) {
|
||||
// don't consume VorbisComment which could be a huge packet, just skip it
|
||||
int packets = OG(&go, page_packets, &v->page);
|
||||
if (!packets) continue;
|
||||
|
||||
// we have a "fake" comment packet that is just has the last page...
|
||||
v->status = OGG_SETUP_HEADER;
|
||||
OG(&go, stream_pagein, &v->state, &v->page);
|
||||
OG(&go, stream_packetout, &v->state, &v->packet);
|
||||
|
||||
OV(&gv, comment_init, &v->comment);
|
||||
v->comment.vendor = "N/A";
|
||||
fetch = true;
|
||||
LOG_INFO("comment skipped successfully");
|
||||
|
||||
// because of lack of page alignment, we might have the setup page already fully in
|
||||
if (packets == 1) continue;
|
||||
}
|
||||
|
||||
if (v->status == OGG_SETUP_HEADER) {
|
||||
// we need the setup packet, get more pages if we don't
|
||||
if (OG(&go, stream_packetout, &v->state, &v->packet) <= 0) continue;
|
||||
|
||||
// finally build a codec if we have the packet
|
||||
status = OG(&go, stream_packetout, &v->state, &v->packet);
|
||||
if (status && ((status = OV(&gv, synthesis_headerin, &v->info, &v->comment, &v->packet)) ||
|
||||
(status = OV(&gv, synthesis_init, &v->decoder, &v->info)))) {
|
||||
LOG_ERROR("vorbis setup header packet error %d", status);
|
||||
if (OV(&gv, synthesis_headerin, &v->info, &v->comment, &v->packet) ||
|
||||
OV(&gv, synthesis_init, &v->decoder, &v->info)) {
|
||||
LOG_ERROR("setup header packet error %d", status);
|
||||
// no need to free comment, it's fake
|
||||
OV(&gv, info_clear, &v->info);
|
||||
status = -1;
|
||||
done = -1;
|
||||
} else {
|
||||
OV(&gv, block_init, &v->decoder, &v->block);
|
||||
v->opened = true;
|
||||
LOG_INFO("codec up and running (rate: %d, channels:%d)", v->rate, v->channels);
|
||||
status = 1;
|
||||
LOG_INFO("codec up and running");
|
||||
done = 1;
|
||||
}
|
||||
//@FIXME: can we have audio on that page as well?
|
||||
break;
|
||||
case OGG_COMMENT_HEADER: {
|
||||
// don't consume VorbisComment, just skip it
|
||||
int packets = OG(&go, page_packets, &v->page);
|
||||
if (packets) {
|
||||
v->status = OGG_SETUP_HEADER;
|
||||
OG(&go, stream_pagein, &v->state, &v->page);
|
||||
OG(&go, stream_packetout, &v->state, &v->packet);
|
||||
|
||||
OV(&gv, comment_init, &v->comment);
|
||||
v->comment.vendor = "N/A";
|
||||
|
||||
// because of lack of page alignment, we might have the setup page already fully in
|
||||
if (packets > 1) fetch = false;
|
||||
LOG_INFO("comment skipped succesfully");
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
UNLOCK_S;
|
||||
return status;
|
||||
return done;
|
||||
}
|
||||
|
||||
inline int pcm_out(vorbis_dsp_state* decoder, void*** pcm) {
|
||||
@@ -317,12 +326,12 @@ static decode_state vorbis_decode(void) {
|
||||
if (v->overflow) {
|
||||
n = pcm_out(&v->decoder, &pcm);
|
||||
v->overflow = n - min(n, frames);
|
||||
} else if ((packet = get_ogg_packet()) > 0) {
|
||||
} else if ((packet = get_audio_packet()) > 0) {
|
||||
n = OV(&gv, synthesis, &v->block, &v->packet);
|
||||
if (n == 0) n = OV(&gv, synthesis_blockin, &v->decoder, &v->block);
|
||||
if (n == 0) n = pcm_out(&v->decoder, &pcm);
|
||||
v->overflow = n - min(n, frames);
|
||||
} else if (!packet && !OG(&go, page_eos, &v->page)) {
|
||||
} else if (!packet) {
|
||||
UNLOCK_O_direct;
|
||||
return DECODE_RUNNING;
|
||||
}
|
||||
@@ -410,7 +419,7 @@ static void vorbis_open(u8_t size, u8_t rate, u8_t chan, u8_t endianness) {
|
||||
}
|
||||
|
||||
v->opened = false;
|
||||
v->status = OGG_SYNC;
|
||||
v->status = OGG_ID_HEADER;
|
||||
v->overflow = 0;
|
||||
|
||||
OG(&go, stream_clear, &v->state);
|
||||
|
||||
File diff suppressed because one or more lines are too long
BIN
components/wifi-manager/webapp/dist/index.html.gz
vendored
BIN
components/wifi-manager/webapp/dist/index.html.gz
vendored
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
BIN
components/wifi-manager/webapp/dist/js/index.105fd5.bundle.js.gz
vendored
Normal file
BIN
components/wifi-manager/webapp/dist/js/index.105fd5.bundle.js.gz
vendored
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -71,6 +71,10 @@ declare function getStatus(): {};
|
||||
declare function getStatus(): {};
|
||||
declare function getStatus(): {};
|
||||
declare function getStatus(): {};
|
||||
declare function getStatus(): {};
|
||||
declare function getStatus(): {};
|
||||
declare function getRadioButton(entry: any): string;
|
||||
declare function getRadioButton(entry: any): string;
|
||||
declare function getRadioButton(entry: any): string;
|
||||
declare function getRadioButton(entry: any): string;
|
||||
declare function getRadioButton(entry: any): string;
|
||||
@@ -217,6 +221,8 @@ declare function pushStatus(): void;
|
||||
declare function pushStatus(): void;
|
||||
declare function pushStatus(): void;
|
||||
declare function pushStatus(): void;
|
||||
declare function pushStatus(): void;
|
||||
declare function pushStatus(): void;
|
||||
declare let sd: {};
|
||||
declare let rf: boolean;
|
||||
declare function refreshStatus(): void;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
target_add_binary_data( __idf_wifi-manager webapp/dist/css/index.4bbe29a78a667faa2b6f.css.gz BINARY)
|
||||
target_add_binary_data( __idf_wifi-manager webapp/dist/favicon-32x32.png BINARY)
|
||||
target_add_binary_data( __idf_wifi-manager webapp/dist/index.html.gz BINARY)
|
||||
target_add_binary_data( __idf_wifi-manager webapp/dist/js/index.0ba488.bundle.js.gz BINARY)
|
||||
target_add_binary_data( __idf_wifi-manager webapp/dist/js/node_vendors.0ba488.bundle.js.gz BINARY)
|
||||
target_add_binary_data( __idf_wifi-manager webapp/dist/js/index.105fd5.bundle.js.gz BINARY)
|
||||
target_add_binary_data( __idf_wifi-manager webapp/dist/js/node_vendors.105fd5.bundle.js.gz BINARY)
|
||||
|
||||
@@ -6,29 +6,29 @@ extern const uint8_t _favicon_32x32_png_start[] asm("_binary_favicon_32x32_png_s
|
||||
extern const uint8_t _favicon_32x32_png_end[] asm("_binary_favicon_32x32_png_end");
|
||||
extern const uint8_t _index_html_gz_start[] asm("_binary_index_html_gz_start");
|
||||
extern const uint8_t _index_html_gz_end[] asm("_binary_index_html_gz_end");
|
||||
extern const uint8_t _index_0ba488_bundle_js_gz_start[] asm("_binary_index_0ba488_bundle_js_gz_start");
|
||||
extern const uint8_t _index_0ba488_bundle_js_gz_end[] asm("_binary_index_0ba488_bundle_js_gz_end");
|
||||
extern const uint8_t _node_vendors_0ba488_bundle_js_gz_start[] asm("_binary_node_vendors_0ba488_bundle_js_gz_start");
|
||||
extern const uint8_t _node_vendors_0ba488_bundle_js_gz_end[] asm("_binary_node_vendors_0ba488_bundle_js_gz_end");
|
||||
extern const uint8_t _index_105fd5_bundle_js_gz_start[] asm("_binary_index_105fd5_bundle_js_gz_start");
|
||||
extern const uint8_t _index_105fd5_bundle_js_gz_end[] asm("_binary_index_105fd5_bundle_js_gz_end");
|
||||
extern const uint8_t _node_vendors_105fd5_bundle_js_gz_start[] asm("_binary_node_vendors_105fd5_bundle_js_gz_start");
|
||||
extern const uint8_t _node_vendors_105fd5_bundle_js_gz_end[] asm("_binary_node_vendors_105fd5_bundle_js_gz_end");
|
||||
const char * resource_lookups[] = {
|
||||
"/css/index.4bbe29a78a667faa2b6f.css.gz",
|
||||
"/favicon-32x32.png",
|
||||
"/index.html.gz",
|
||||
"/js/index.0ba488.bundle.js.gz",
|
||||
"/js/node_vendors.0ba488.bundle.js.gz",
|
||||
"/js/index.105fd5.bundle.js.gz",
|
||||
"/js/node_vendors.105fd5.bundle.js.gz",
|
||||
""
|
||||
};
|
||||
const uint8_t * resource_map_start[] = {
|
||||
_index_4bbe29a78a667faa2b6f_css_gz_start,
|
||||
_favicon_32x32_png_start,
|
||||
_index_html_gz_start,
|
||||
_index_0ba488_bundle_js_gz_start,
|
||||
_node_vendors_0ba488_bundle_js_gz_start
|
||||
_index_105fd5_bundle_js_gz_start,
|
||||
_node_vendors_105fd5_bundle_js_gz_start
|
||||
};
|
||||
const uint8_t * resource_map_end[] = {
|
||||
_index_4bbe29a78a667faa2b6f_css_gz_end,
|
||||
_favicon_32x32_png_end,
|
||||
_index_html_gz_end,
|
||||
_index_0ba488_bundle_js_gz_end,
|
||||
_node_vendors_0ba488_bundle_js_gz_end
|
||||
_index_105fd5_bundle_js_gz_end,
|
||||
_node_vendors_105fd5_bundle_js_gz_end
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/***********************************
|
||||
webpack_headers
|
||||
dist/css/index.4bbe29a78a667faa2b6f.css.gz,dist/favicon-32x32.png,dist/index.html.gz,dist/js/index.0ba488.bundle.js.gz,dist/js/node_vendors.0ba488.bundle.js.gz
|
||||
dist/css/index.4bbe29a78a667faa2b6f.css.gz,dist/favicon-32x32.png,dist/index.html.gz,dist/js/index.105fd5.bundle.js.gz,dist/js/node_vendors.105fd5.bundle.js.gz
|
||||
***********************************/
|
||||
#pragma once
|
||||
#include <inttypes.h>
|
||||
|
||||
BIN
server_certs/DigiCertGlobalRootCA.crt.67
Normal file
BIN
server_certs/DigiCertGlobalRootCA.crt.67
Normal file
Binary file not shown.
BIN
server_certs/DigiCertGlobalRootCA.crt.68
Normal file
BIN
server_certs/DigiCertGlobalRootCA.crt.68
Normal file
Binary file not shown.
BIN
server_certs/DigiCertGlobalRootCA.crt.69
Normal file
BIN
server_certs/DigiCertGlobalRootCA.crt.69
Normal file
Binary file not shown.
BIN
server_certs/DigiCertGlobalRootCA.crt.70
Normal file
BIN
server_certs/DigiCertGlobalRootCA.crt.70
Normal file
Binary file not shown.
BIN
server_certs/DigiCertGlobalRootCA.crt.71
Normal file
BIN
server_certs/DigiCertGlobalRootCA.crt.71
Normal file
Binary file not shown.
BIN
server_certs/r2m01.cer.39
Normal file
BIN
server_certs/r2m01.cer.39
Normal file
Binary file not shown.
BIN
server_certs/r2m01.cer.40
Normal file
BIN
server_certs/r2m01.cer.40
Normal file
Binary file not shown.
BIN
server_certs/r2m01.cer.41
Normal file
BIN
server_certs/r2m01.cer.41
Normal file
Binary file not shown.
BIN
server_certs/r2m01.cer.42
Normal file
BIN
server_certs/r2m01.cer.42
Normal file
Binary file not shown.
BIN
server_certs/r2m01.cer.43
Normal file
BIN
server_certs/r2m01.cer.43
Normal file
Binary file not shown.
Reference in New Issue
Block a user