Compare commits

..

15 Commits

Author SHA1 Message Date
philippe44
58840f894f Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2024-01-02 00:44:20 -08:00
philippe44
94109ebf38 fix copy typo - release 2024-01-02 00:43:32 -08:00
github-actions
fc20618fa2 Update prebuilt objects [skip actions] 2024-01-02 08:37:41 +00:00
philippe44
70720d3445 don't allocate segments - release 2024-01-02 00:35:47 -08:00
github-actions
4abe1304e8 Update prebuilt objects [skip actions] 2024-01-01 02:54:46 +00:00
philippe44
be7fb4b669 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-12-31 18:50:29 -08:00
philippe44
3554af1460 add vorbis/ogg live metadata (and fix some ogg issues) - release 2023-12-31 18:50:25 -08:00
Sébastien
02d422c1f4 update workflow dependency 2023-12-28 09:11:46 -05:00
github-actions
343b728323 Update prebuilt objects [skip actions] 2023-12-28 13:56:07 +00:00
philippe44
9679c5c104 fix flush mode 2023-12-27 19:46:16 -08:00
philippe44
5c90086bbd sync with upstream/cspot 2023-11-21 12:52:55 -08:00
philippe44
807a0e547a airplay improvements 2023-11-19 22:57:21 -08:00
philippe44
1d54331224 Merge branch 'master-v4.3' of https://github.com/sle118/squeezelite-esp32 into master-v4.3 2023-11-16 22:52:23 -08:00
philippe44
e85a3cddf1 GDS optimization 2023-11-16 22:52:18 -08:00
github-actions
d4d97d5a60 Update prebuilt objects [skip actions] 2023-11-17 05:45:59 +00:00
57 changed files with 501 additions and 286 deletions

View File

@@ -88,7 +88,7 @@ jobs:
git commit -m "Update prebuilt objects [skip actions]" git commit -m "Update prebuilt objects [skip actions]"
git push https://${{secrets.github_token}}@github.com/sle118/squeezelite-esp32.git git push https://${{secrets.github_token}}@github.com/sle118/squeezelite-esp32.git
- name: Locally store commonly built objects - name: Locally store commonly built objects
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: prebuilt_objects name: prebuilt_objects
path: | path: |
@@ -130,7 +130,7 @@ jobs:
git status 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 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 name: Restore common objects
with: with:
name: prebuilt_objects name: prebuilt_objects

View File

@@ -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 2023-11-09
- force gpio_pad_select_gpio in dac_controlset in case somebody uses UART gpio's (or other pre-programmed) - force gpio_pad_select_gpio in dac_controlset in case somebody uses UART gpio's (or other pre-programmed)

View File

@@ -88,11 +88,6 @@ static void Update( struct GDS_Device* Device ) {
#endif #endif
} }
static void DrawPixelFast4( 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 SetLayout( struct GDS_Device* Device, struct GDS_Layout *Layout ) { static void SetLayout( struct GDS_Device* Device, struct GDS_Layout *Layout ) {
if (Layout->HFlip) { if (Layout->HFlip) {
Device->WriteCommand( Device, 0x40 + 0x20 ); Device->WriteCommand( Device, 0x40 + 0x20 );
@@ -165,7 +160,6 @@ static bool Init( struct GDS_Device* Device ) {
static const struct GDS_Device SH1122 = { static const struct GDS_Device SH1122 = {
.DisplayOn = DisplayOn, .DisplayOff = DisplayOff, .SetContrast = SetContrast, .DisplayOn = DisplayOn, .DisplayOff = DisplayOff, .SetContrast = SetContrast,
.DrawPixelFast = DrawPixelFast4,
.SetLayout = SetLayout, .SetLayout = SetLayout,
.Update = Update, .Init = Init, .Update = Update, .Init = Init,
.Mode = GDS_GRAYSCALE, .Depth = 4, .Mode = GDS_GRAYSCALE, .Depth = 4,

View File

@@ -109,7 +109,7 @@ void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2,
int c = x1; int c = x1;
// for a row that is not on a boundary, no optimization possible // for a row that is not on a boundary, no optimization possible
while (r & 0x07 && r <= y2) { 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++; r++;
} }
// go fast if we have more than 8 lines to write // 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); memset(optr + Width * r + x1, _Color, x2 - x1 + 1);
r += 8; r += 8;
} else while (r <= y2) { } 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++; 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 // try to do byte processing as much as possible
for (int r = y1; r <= y2; r++) { for (int r = y1; r <= y2; r++) {
int c = x1; 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; int chunk = (x2 - c + 1) >> 1;
memset(optr + ((r * Width + c) >> 1), _Color, chunk); 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) { } 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 { } else {
for (int y = y1; y <= y2; y++) { for (int y = y1; y <= y2; y++) {
for (int x = x1; x <= x2; x++) { 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; 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 ) { bool GDS_Init( struct GDS_Device* Device ) {
if (Device->Depth > 8) Device->FramebufferSize = Device->Width * Device->Height * ((8 + Device->Depth - 1) / 8); 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); 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 // allocate FB unless explicitely asked not to
if (!(Device->Alloc & GDS_ALLOC_NONE)) { if (!(Device->Alloc & GDS_ALLOC_NONE)) {

View File

@@ -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 ) { 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 ) { 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; if (y < 0) y = 0;
else if (y >= Device->Height) y = Device->Height - 1; 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 ) { 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++ ) { for ( ; x < x1; x++ ) {
if ( IsPixelVisible( Device, x, y ) == true ) { if ( IsPixelVisible( Device, x, y ) == true ) {
DrawPixelFast( Device, x, y, Color ); Device->DrawPixelFast( Device, x, y, Color );
} }
if ( Error > 0 ) { if ( Error > 0 ) {
@@ -126,7 +126,7 @@ static inline void DrawTallLine( struct GDS_Device* Device, int x0, int y0, int
for ( ; y < y1; y++ ) { for ( ; y < y1; y++ ) {
if ( IsPixelVisible( Device, x, y ) == true ) { if ( IsPixelVisible( Device, x, y ) == true ) {
DrawPixelFast( Device, x, y, Color ); Device->DrawPixelFast( Device, x, y, Color );
} }
if ( Error > 0 ) { if ( Error > 0 ) {
@@ -348,14 +348,14 @@ void GDS_DrawBitmapCBR(struct GDS_Device* Device, uint8_t *Data, int Width, int
// don't know bitdepth, use brute-force solution // don't know bitdepth, use brute-force solution
for (int i = Width * Height, r = 0, c = 0; --i >= 0;) { for (int i = Width * Height, r = 0, c = 0; --i >= 0;) {
uint8_t Byte = *Data++; uint8_t Byte = *Data++;
DrawPixelFast( Device, c, (r << 3) + 7, (Byte & 0x01) * Color ); Byte >>= 1; Device->DrawPixelFast( Device, c, (r << 3) + 7, (Byte & 0x01) * Color ); Byte >>= 1;
DrawPixelFast( Device, c, (r << 3) + 6, (Byte & 0x01) * Color ); Byte >>= 1; Device->DrawPixelFast( Device, c, (r << 3) + 6, (Byte & 0x01) * Color ); Byte >>= 1;
DrawPixelFast( Device, c, (r << 3) + 5, (Byte & 0x01) * Color ); Byte >>= 1; Device->DrawPixelFast( Device, c, (r << 3) + 5, (Byte & 0x01) * Color ); Byte >>= 1;
DrawPixelFast( Device, c, (r << 3) + 4, (Byte & 0x01) * Color ); Byte >>= 1; Device->DrawPixelFast( Device, c, (r << 3) + 4, (Byte & 0x01) * Color ); Byte >>= 1;
DrawPixelFast( Device, c, (r << 3) + 3, (Byte & 0x01) * Color ); Byte >>= 1; Device->DrawPixelFast( Device, c, (r << 3) + 3, (Byte & 0x01) * Color ); Byte >>= 1;
DrawPixelFast( Device, c, (r << 3) + 2, (Byte & 0x01) * Color ); Byte >>= 1; Device->DrawPixelFast( Device, c, (r << 3) + 2, (Byte & 0x01) * Color ); Byte >>= 1;
DrawPixelFast( Device, c, (r << 3) + 1, (Byte & 0x01) * Color ); Byte >>= 1; Device->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) + 0, (Byte & 0x01) * Color );
if (++r == Height) { c++; r = 0; } if (++r == Height) { c++; r = 0; }
} }
/* for better understanding, here is the mundane version /* for better understanding, here is the mundane version

View File

@@ -156,69 +156,9 @@ static inline bool IsPixelVisible( struct GDS_Device* Device, int x, int y ) {
return Result; 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 ) { static inline void IRAM_ATTR DrawPixel( struct GDS_Device* Device, int x, int y, int Color ) {
if ( IsPixelVisible( Device, x, y ) == true ) { if ( IsPixelVisible( Device, x, y ) == true ) {
DrawPixelFast( Device, x, y, Color ); Device->DrawPixelFast( Device, x, y, Color );
} }
} }

View File

@@ -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); 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 c = (Attr & GDS_TEXT_CLEAR_EOL) ? X : 0; c < Device->TextWidth; c++)
for (int y = Y_min; y < Y_max; y++) 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 ); GDS_FontDrawString( Device, X, Device->Lines[N].Y, Text, GDS_COLOR_WHITE );

View File

@@ -461,7 +461,7 @@ static void buffer_put_packet(rtp_t *ctx, seq_t seqno, unsigned rtptime, bool fi
return; return;
} }
} }
if (seqno == (u16_t) (ctx->ab_write+1)) { if (seqno == (u16_t) (ctx->ab_write+1)) {
// expected packet // expected packet
abuf = ctx->audio_buffer + BUFIDX(seqno); 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); 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 // stop when we reach a ready frame or a recent pending resend
for (int i = 0; seq_order(ctx->ab_read + i, ctx->ab_write); i += 16) { if (first && (frame->ready || now - frame->last_resend <= RESEND_TO)) {
abuf_t *frame = ctx->audio_buffer + BUFIDX(ctx->ab_read + i); if (!rtp_request_resend(ctx, first, ctx->ab_read + i - 1)) break;
if (!frame->ready && now - frame->last_resend > RESEND_TO) { first = 0;
// stop if one fails i += step - 1;
if (!rtp_request_resend(ctx, ctx->ab_read + i, ctx->ab_read + i)) break; } else if (!frame->ready && now - frame->last_resend > RESEND_TO) {
frame->last_resend = now; if (!first) first = ctx->ab_read + i;
} frame->last_resend = now;
} }
}
}
/*---------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------*/

View File

@@ -62,7 +62,7 @@ static void task_stats( cJSON* top ) {
current.n = uxTaskGetSystemState( current.tasks, current.n, &current.total ); current.n = uxTaskGetSystemState( current.tasks, current.n, &current.total );
cJSON_AddNumberToObject(top,"ntasks",current.n); cJSON_AddNumberToObject(top,"ntasks",current.n);
char scratch[SCRATCH_SIZE] = { }; char scratch[SCRATCH_SIZE] = {0};
#ifdef CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS #ifdef CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS
#pragma message("Compiled with runtime 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)); heap_caps_get_minimum_free_size(MALLOC_CAP_DMA));
task_stats(top); task_stats(top);
char * top_a= cJSON_PrintUnformatted(top); char * top_a= cJSON_PrintUnformatted(top);
if(top_a){ if(top_a){
messaging_post_message(MESSAGING_INFO, MESSAGING_CLASS_STATS,top_a); messaging_post_message(MESSAGING_INFO, MESSAGING_CLASS_STATS,top_a);
FREE_AND_NULL(top_a); FREE_AND_NULL(top_a);
} }
cJSON_Delete(top); cJSON_Delete(top);
} }

View File

@@ -227,7 +227,7 @@ int main(int argc, char *argv[]) {
} else if (!strcasecmp(arg, "-i")) { } else if (!strcasecmp(arg, "-i")) {
identity = *++argv; identity = *++argv;
} else { } 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; if (!identity) identity = *argv;
else if (!type) (void) !asprintf(&type, "%s.local", *argv); else if (!type) (void) !asprintf(&type, "%s.local", *argv);
else if (!port) port = atoi(*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**)); txt = (const char**) malloc((argc + 1) * sizeof(char**));
memcpy(txt, argv, argc * sizeof(char**)); memcpy(txt, argv, argc * sizeof(char**));
txt[argc] = NULL; txt[argc] = NULL;
break;
} }
argc--; argc--;
} }
@@ -250,13 +251,14 @@ int main(int argc, char *argv[]) {
mdnsd_set_hostname(svr, hostname, host); mdnsd_set_hostname(svr, hostname, host);
svc = mdnsd_register_svc(svr, identity, type, port, NULL, txt); svc = mdnsd_register_svc(svr, identity, type, port, NULL, txt);
mdns_service_destroy(svc); // mdns_service_destroy(svc);
#ifdef _WIN32 #ifdef _WIN32
Sleep(INFINITE); Sleep(INFINITE);
#else #else
pause(); pause();
#endif #endif
mdns_service_remove(svr, svc);
mdnsd_stop(svr); mdnsd_stop(svr);
} else { } else {
printf("Can't start server"); printf("Can't start server");
@@ -264,7 +266,7 @@ int main(int argc, char *argv[]) {
} }
free(type); free(type);
free(txt); if (txt) free(txt);
#ifdef _WIN32 #ifdef _WIN32
winsock_close(); winsock_close();

View File

@@ -144,11 +144,10 @@ static int create_recv_sock(uint32_t host) {
} }
#if !defined(_WIN32) #if !defined(_WIN32)
on = sizeof(on); socklen_t len = sizeof(on);
socklen_t len; if (!getsockopt(sd, SOL_SOCKET, SO_REUSEPORT, &on, &len)) {
if (!getsockopt(sd, SOL_SOCKET, SO_REUSEPORT,(char*) &on, &len)) {
on = 1; 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); log_message(LOG_ERR, "recv setsockopt(SO_REUSEPORT): %m\n", r);
} }
} }

View File

@@ -5,6 +5,7 @@
#include <unistd.h> #include <unistd.h>
#include <cstring> #include <cstring>
#include <vector> #include <vector>
#include <mutex>
#if __has_include("avahi-client/client.h") #if __has_include("avahi-client/client.h")
#include <avahi-client/client.h> #include <avahi-client/client.h>

View File

@@ -201,21 +201,23 @@ void SpircHandler::handleFrame(std::vector<uint8_t>& data) {
break; break;
} }
case MessageType_kMessageTypeReplace: { case MessageType_kMessageTypeReplace: {
CSPOT_LOG(debug, "Got replace frame %d", playbackState->remoteTracks.size()); CSPOT_LOG(debug, "Got replace frame %d",
playbackState->remoteTracks.size());
playbackState->syncWithRemote(); playbackState->syncWithRemote();
// 1st track is the current one, but update the position // 1st track is the current one, but update the position
bool cleared = trackQueue->updateTracks( bool cleared = trackQueue->updateTracks(
playbackState->remoteFrame.state.position_ms + playbackState->remoteFrame.state.position_ms +
ctx->timeProvider->getSyncedTimestamp() - ctx->timeProvider->getSyncedTimestamp() -
playbackState->innerFrame.state.position_measured_at); playbackState->innerFrame.state.position_measured_at,
false);
this->notify(); this->notify();
// need to re-load all if streaming track is completed // need to re-load all if streaming track is completed
if (cleared) { if (cleared) {
sendEvent(EventType::FLUSH); sendEvent(EventType::FLUSH);
trackPlayer->resetState(); trackPlayer->resetState();
} }
break; break;
} }

View File

@@ -609,16 +609,16 @@ bool TrackQueue::updateTracks(uint32_t requestedPosition, bool initial) {
playableSemaphore->give(); playableSemaphore->give();
} else if (preloadedTracks[0]->loading) { } else if (preloadedTracks[0]->loading) {
// try to not re-load track if we are still loading it // try to not re-load track if we are still loading it
// remove everything except first track
preloadedTracks.erase(preloadedTracks.begin() + 1, preloadedTracks.end());
// Push a song on the preloaded queue // remove everything except first track
CSPOT_LOG(info, "Keeping current track %d", currentTracksIndex); preloadedTracks.erase(preloadedTracks.begin() + 1, preloadedTracks.end());
queueNextTrack(1);
cleared = false; // Push a song on the preloaded queue
CSPOT_LOG(info, "Keeping current track %d", currentTracksIndex);
queueNextTrack(1);
cleared = false;
} else { } else {
// Clear preloaded tracks // Clear preloaded tracks
preloadedTracks.clear(); preloadedTracks.clear();

View File

@@ -44,7 +44,7 @@
#define MAX_OPUS_FRAMES 5760 #define MAX_OPUS_FRAMES 5760
struct opus { 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_stream_state state;
ogg_packet packet; ogg_packet packet;
ogg_sync_state sync; 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; (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; int status, packet = -1;
LOCK_S; LOCK_S;
size_t bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf)); size_t bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf));
while (!(status = OG(&go, stream_packetout, &u->state, &u->packet)) && bytes) { while (!(status = OG(&go, stream_packetout, &u->state, &u->packet)) && bytes) {
// if sync_pageout (or sync_pageseek) is not called here, sync builds ups // 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) { while (!(status = OG(&go, sync_pageout, &u->sync, &u->page)) && bytes) {
size_t consumed = min(bytes, 4096); 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); memcpy(buffer, streambuf->readp, consumed);
OG(&gu, sync_wrote, &u->sync, consumed); OG(&go, sync_wrote, &u->sync, consumed);
_buf_inc_readp(streambuf, consumed); _buf_inc_readp(streambuf, consumed);
bytes -= consumed; bytes -= consumed;
} }
// if we have a new page, put it in // 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 (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));
// 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;
/* 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; UNLOCK_S;
return packet; return packet;
} }
static int read_opus_header(void) { static int read_opus_header(void) {
int status = 0; int done = 0;
bool fetch = true; bool fetch = true;
LOCK_S; LOCK_S;
size_t bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf)); size_t bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf));
while (bytes && !status) { while (bytes && !done) {
// first fetch a page if we need one int status;
if (fetch) {
// get aligned to a page and ready to bring it in
do {
size_t consumed = min(bytes, 4096); 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); memcpy(buffer, streambuf->readp, consumed);
OG(&gu, sync_wrote, &u->sync, consumed); OG(&go, sync_wrote, &u->sync, consumed);
_buf_inc_readp(streambuf, consumed); _buf_inc_readp(streambuf, consumed);
bytes -= 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) { // nothing has been found and we have no more bytes, come back later
case OGG_SYNC: if (status <= 0) break;
u->status = OGG_ID_HEADER;
OG(&gu, stream_reset_serialno, &u->state, OG(&gu, page_serialno, &u->page)); // always set stream serialno if we have a new one
fetch = false; if (OG(&go, page_bos, &u->page)) OG(&go, stream_reset_serialno, &u->state, OG(&go, page_serialno, &u->page));
break;
case OGG_ID_HEADER: // bring new page in if we want it (otherwise we're just skipping)
status = OG(&gu, stream_pagein, &u->state, &u->page); if (fetch) OG(&go, 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)) { // no need for a switch...case
LOG_ERROR("wrong opus header packet (size:%u)", u->packet.bytes); if (u->status == OGG_ID_HEADER) {
status = -100; // we need the id packet, get more pages if we don't
break; if (OG(&go, stream_packetout, &u->state, &u->packet) <= 0) continue;
}
u->status = OGG_COMMENT_HEADER; // 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->channels = u->packet.packet[9];
u->pre_skip = parse_uint16(u->packet.packet + 10); u->pre_skip = parse_uint16(u->packet.packet + 10);
u->rate = parse_uint32(u->packet.packet + 12); u->rate = parse_uint32(u->packet.packet + 12);
u->gain = parse_int16(u->packet.packet + 16); u->gain = parse_int16(u->packet.packet + 16);
u->decoder = OP(&gu, decoder_create, 48000, u->channels, &status); u->decoder = OP(&gu, decoder_create, 48000, u->channels, &status);
fetch = false;
if (!u->decoder || status != OPUS_OK) { if (!u->decoder || status != OPUS_OK) {
LOG_ERROR("can't create decoder %d (channels:%u)", status, u->channels); LOG_ERROR("can't create decoder %d (channels:%u)", status, u->channels);
} }
else {
LOG_INFO("codec up and running");
}
} }
fetch = true; } else if (u->status == OGG_COMMENT_HEADER) {
break; // don't consume VorbisComment which could be a huge packet, just skip it
case OGG_COMMENT_HEADER: if (!OG(&go, page_packets, &u->page)) continue;
// skip packets to consume VorbisComment. With opus, header packets align on pages LOG_INFO("comment skipped successfully");
status = OG(&gu, page_packets, &u->page); done = 1;
break;
default:
break;
} }
} }
UNLOCK_S; UNLOCK_S;
return status; return done;
} }
static decode_state opus_decompress(void) { 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); memcpy(write_buf, u->overbuf, u->overframes * BYTES_PER_FRAME);
n = u->overframes; n = u->overframes;
u->overframes = 0; u->overframes = 0;
} else if ((packet = get_opus_packet()) > 0) { } else if ((packet = get_audio_packet()) > 0) {
if (frames < MAX_OPUS_FRAMES) { if (frames < MAX_OPUS_FRAMES) {
// don't have enough contiguous space, use the overflow buffer // 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); 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 */ * 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); 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; UNLOCK_O_direct;
return DECODE_RUNNING; return DECODE_RUNNING;
} }
@@ -342,7 +356,7 @@ static decode_state opus_decompress(void) {
} else { } else {
LOG_INFO("opus decode error: %d", n); LOG_INFO("decode error: %d", n);
UNLOCK_O_direct; UNLOCK_O_direct;
return DECODE_COMPLETE; 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); if (!u->overbuf) u->overbuf = malloc(MAX_OPUS_FRAMES * BYTES_PER_FRAME);
u->status = OGG_SYNC; u->status = OGG_ID_HEADER;
u->overframes = 0; u->overframes = 0;
OG(&go, stream_clear, &u->state); OG(&go, stream_clear, &u->state);

View File

@@ -443,7 +443,7 @@ void output_close_common(void) {
} }
void output_flush(void) { void output_flush(void) {
LOG_INFO("flush output buffer"); LOG_INFO("flush output buffer (full)");
buf_flush(outputbuf); buf_flush(outputbuf);
LOCK; LOCK;
output.fade = FADE_INACTIVE; output.fade = FADE_INACTIVE;
@@ -457,3 +457,15 @@ void output_flush(void) {
output.frames_played = 0; output.frames_played = 0;
UNLOCK; 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;
}

View File

@@ -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 sendSTAT("STMt", strm->replay_gain); // STMt replay_gain is no longer used to track latency, but support it
break; break;
case 'f': 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': case 'q':
decode_flush(strm->command == 'q'); decode_flush(true);
if (!output.external) output_flush(); if (!output.external) output_flush();
status.frames_played = 0; status.frames_played = 0;
if (stream_disconnect() && strm->command == 'f') sendSTAT("STMf", 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); stream_file(header, header_len, strm->threshold * 1024);
autostart -= 2; autostart -= 2;
} else { } 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); sendSTAT("STMc", 0);
sentSTMu = sentSTMo = sentSTMl = false; sentSTMu = sentSTMo = sentSTMl = false;

View File

@@ -580,12 +580,26 @@ struct streamstate {
u32_t meta_next; u32_t meta_next;
u32_t meta_left; u32_t meta_left;
bool meta_send; 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_init(log_level level, unsigned stream_buf_size);
void stream_close(void); void stream_close(void);
void stream_file(const char *header, size_t header_len, unsigned threshold); 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); bool stream_disconnect(void);
// decode.c // 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_init_common(log_level level, const char *device, unsigned output_buf_size, unsigned rates[], unsigned idle);
void output_close_common(void); void output_close_common(void);
void output_flush(void); void output_flush(void);
bool output_flush_streaming(void);
// _* called with mutex locked // _* called with mutex locked
frames_t _output_frames(frames_t avail); frames_t _output_frames(frames_t avail);
void _checkfade(bool); void _checkfade(bool);

View File

@@ -59,7 +59,7 @@ is enough and much faster than a mutex
static bool polling; static bool polling;
static sockfd fd; static sockfd fd;
struct streamstate stream; struct EXT_RAM_ATTR streamstate stream;
#if USE_SSL #if USE_SSL
static SSL_CTX *SSLctx; static SSL_CTX *SSLctx;
@@ -148,6 +148,8 @@ static bool running = true;
static void _disconnect(stream_state state, disconnect_code disconnect) { static void _disconnect(stream_state state, disconnect_code disconnect) {
stream.state = state; stream.state = state;
stream.disconnect = disconnect; 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 USE_SSL
if (ssl) { if (ssl) {
SSL_shutdown(ssl); SSL_shutdown(ssl);
@@ -160,6 +162,121 @@ static void _disconnect(stream_state state, disconnect_code disconnect) {
wake_controller(); 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() { static void *stream_thread() {
while (running) { while (running) {
@@ -343,6 +460,7 @@ static void *stream_thread() {
} }
if (n > 0) { if (n > 0) {
stream_ogg(n);
_buf_inc_writep(streambuf, n); _buf_inc_writep(streambuf, n);
stream.bytes += n; stream.bytes += n;
if (stream.meta_interval) { if (stream.meta_interval) {
@@ -485,7 +603,7 @@ void stream_file(const char *header, size_t header_len, unsigned threshold) {
UNLOCK; 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; struct sockaddr_in addr;
#if EMBEDDED #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.sent_headers = false;
stream.bytes = 0; stream.bytes = 0;
stream.threshold = threshold; stream.threshold = threshold;
stream.ogg.miss = stream.ogg.match = 0;
stream.ogg.state = (codec == 'o' || codec == 'p') ? STREAM_OGG_SYNC : STREAM_OGG_OFF;
UNLOCK; UNLOCK;
} }
@@ -604,6 +725,8 @@ bool stream_disconnect(void) {
disc = true; disc = true;
} }
stream.state = STOPPED; stream.state = STOPPED;
if (stream.ogg.state == STREAM_OGG_PAGE && stream.ogg.data) free(stream.ogg.data);
stream.ogg.data = NULL;
UNLOCK; UNLOCK;
return disc; return disc;
} }

View File

@@ -50,7 +50,7 @@ static inline int32_t clip15(int32_t x) {
struct vorbis { struct vorbis {
bool opened; 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 { struct {
ogg_stream_state state; ogg_stream_state state;
ogg_packet packet; ogg_packet packet;
@@ -138,48 +138,16 @@ extern struct processstate process;
#define OG(h, fn, ...) (h)->ogg_ ## fn(__VA_ARGS__) #define OG(h, fn, ...) (h)->ogg_ ## fn(__VA_ARGS__)
#endif #endif
static int get_ogg_packet(void) { static int get_audio_packet(void) {
int status, packet = -1; int status, packet = -1;
LOCK_S; LOCK_S;
size_t bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf)); size_t bytes = min(_buf_used(streambuf), _buf_cont_read(streambuf));
while (!(status = OG(&go, stream_packetout, &v->state, &v->packet)) && bytes) { 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 // 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) { 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) {
size_t consumed = min(bytes, 4096); size_t consumed = min(bytes, 4096);
char* buffer = OG(&go, sync_buffer, &v->sync, consumed); char* buffer = OG(&go, sync_buffer, &v->sync, consumed);
memcpy(buffer, streambuf->readp, consumed); memcpy(buffer, streambuf->readp, consumed);
@@ -187,81 +155,122 @@ static int read_vorbis_header(void) {
_buf_inc_readp(streambuf, consumed); _buf_inc_readp(streambuf, consumed);
bytes -= consumed; bytes -= consumed;
if (!OG(&go, sync_pageseek, &v->sync, &v->page)) continue;
} }
switch (v->status) { // if we have a new page, put it in and reset serialno at BoS
case OGG_SYNC: if (status) {
v->status = OGG_ID_HEADER; OG(&go, stream_pagein, &v->state, &v->page);
OG(&go, stream_reset_serialno, &v->state, OG(&go, page_serialno, &v->page)); if (OG(&go, page_bos, &v->page)) 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); /* odd packets are not audio and should be discarded. With no packet, we
if (!OG(&go, stream_packetout, &v->state, &v->packet)) break; * 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); OV(&gv, info_init, &v->info);
status = OV(&gv, synthesis_headerin, &v->info, &v->comment, &v->packet); status = OV(&gv, synthesis_headerin, &v->info, &v->comment, &v->packet);
if (status) { if (status) {
LOG_ERROR("vorbis id header packet error %d", status); LOG_ERROR("id header packet error %d", status);
status = -1; done = -1;
} else { } else {
v->channels = v->info.channels; v->channels = v->info.channels;
v->rate = v->info.rate; v->rate = v->info.rate;
v->status = OGG_COMMENT_HEADER; v->status = OGG_COMMENT_HEADER;
fetch = false;
// 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");
LOG_INFO("id acquired"); 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 (v->status == OGG_COMMENT_HEADER) {
if (fetch) OG(&go, stream_pagein, &v->state, &v->page); // 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 // finally build a codec if we have the packet
status = OG(&go, stream_packetout, &v->state, &v->packet); if (OV(&gv, synthesis_headerin, &v->info, &v->comment, &v->packet) ||
if (status && ((status = OV(&gv, synthesis_headerin, &v->info, &v->comment, &v->packet)) || OV(&gv, synthesis_init, &v->decoder, &v->info)) {
(status = OV(&gv, synthesis_init, &v->decoder, &v->info)))) { LOG_ERROR("setup header packet error %d", status);
LOG_ERROR("vorbis setup header packet error %d", status);
// no need to free comment, it's fake // no need to free comment, it's fake
OV(&gv, info_clear, &v->info); OV(&gv, info_clear, &v->info);
status = -1; done = -1;
} else { } else {
OV(&gv, block_init, &v->decoder, &v->block); OV(&gv, block_init, &v->decoder, &v->block);
v->opened = true; v->opened = true;
LOG_INFO("codec up and running (rate: %d, channels:%d)", v->rate, v->channels); LOG_INFO("codec up and running");
status = 1; 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; break;
} }
} }
UNLOCK_S; UNLOCK_S;
return status; return done;
} }
inline int pcm_out(vorbis_dsp_state* decoder, void*** pcm) { inline int pcm_out(vorbis_dsp_state* decoder, void*** pcm) {
@@ -317,12 +326,12 @@ static decode_state vorbis_decode(void) {
if (v->overflow) { if (v->overflow) {
n = pcm_out(&v->decoder, &pcm); n = pcm_out(&v->decoder, &pcm);
v->overflow = n - min(n, frames); 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); n = OV(&gv, synthesis, &v->block, &v->packet);
if (n == 0) n = OV(&gv, synthesis_blockin, &v->decoder, &v->block); if (n == 0) n = OV(&gv, synthesis_blockin, &v->decoder, &v->block);
if (n == 0) n = pcm_out(&v->decoder, &pcm); if (n == 0) n = pcm_out(&v->decoder, &pcm);
v->overflow = n - min(n, frames); v->overflow = n - min(n, frames);
} else if (!packet && !OG(&go, page_eos, &v->page)) { } else if (!packet) {
UNLOCK_O_direct; UNLOCK_O_direct;
return DECODE_RUNNING; 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->opened = false;
v->status = OGG_SYNC; v->status = OGG_ID_HEADER;
v->overflow = 0; v->overflow = 0;
OG(&go, stream_clear, &v->state); OG(&go, stream_clear, &v->state);

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

View File

@@ -71,6 +71,10 @@ declare function getStatus(): {};
declare function getStatus(): {}; declare function getStatus(): {};
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; 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 function pushStatus(): void;
declare function pushStatus(): void;
declare function pushStatus(): void;
declare let sd: {}; declare let sd: {};
declare let rf: boolean; declare let rf: boolean;
declare function refreshStatus(): void; declare function refreshStatus(): void;

View File

@@ -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/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/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/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/index.105fd5.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/node_vendors.105fd5.bundle.js.gz BINARY)

View File

@@ -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 _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_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_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_105fd5_bundle_js_gz_start[] asm("_binary_index_105fd5_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 _index_105fd5_bundle_js_gz_end[] asm("_binary_index_105fd5_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_105fd5_bundle_js_gz_start[] asm("_binary_node_vendors_105fd5_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 _node_vendors_105fd5_bundle_js_gz_end[] asm("_binary_node_vendors_105fd5_bundle_js_gz_end");
const char * resource_lookups[] = { const char * resource_lookups[] = {
"/css/index.4bbe29a78a667faa2b6f.css.gz", "/css/index.4bbe29a78a667faa2b6f.css.gz",
"/favicon-32x32.png", "/favicon-32x32.png",
"/index.html.gz", "/index.html.gz",
"/js/index.0ba488.bundle.js.gz", "/js/index.105fd5.bundle.js.gz",
"/js/node_vendors.0ba488.bundle.js.gz", "/js/node_vendors.105fd5.bundle.js.gz",
"" ""
}; };
const uint8_t * resource_map_start[] = { const uint8_t * resource_map_start[] = {
_index_4bbe29a78a667faa2b6f_css_gz_start, _index_4bbe29a78a667faa2b6f_css_gz_start,
_favicon_32x32_png_start, _favicon_32x32_png_start,
_index_html_gz_start, _index_html_gz_start,
_index_0ba488_bundle_js_gz_start, _index_105fd5_bundle_js_gz_start,
_node_vendors_0ba488_bundle_js_gz_start _node_vendors_105fd5_bundle_js_gz_start
}; };
const uint8_t * resource_map_end[] = { const uint8_t * resource_map_end[] = {
_index_4bbe29a78a667faa2b6f_css_gz_end, _index_4bbe29a78a667faa2b6f_css_gz_end,
_favicon_32x32_png_end, _favicon_32x32_png_end,
_index_html_gz_end, _index_html_gz_end,
_index_0ba488_bundle_js_gz_end, _index_105fd5_bundle_js_gz_end,
_node_vendors_0ba488_bundle_js_gz_end _node_vendors_105fd5_bundle_js_gz_end
}; };

View File

@@ -1,6 +1,6 @@
/*********************************** /***********************************
webpack_headers 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 #pragma once
#include <inttypes.h> #include <inttypes.h>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
server_certs/r2m01.cer.40 Normal file

Binary file not shown.

BIN
server_certs/r2m01.cer.41 Normal file

Binary file not shown.

BIN
server_certs/r2m01.cer.42 Normal file

Binary file not shown.

BIN
server_certs/r2m01.cer.43 Normal file

Binary file not shown.