mirror of
https://github.com/sle118/squeezelite-esp32.git
synced 2025-12-16 00:17:03 +03:00
Compare commits
22 Commits
SqueezeAmp
...
I2S-4MFlas
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cb47ec855b | ||
|
|
787a5d9a6e | ||
|
|
7b9deb795c | ||
|
|
4b1f8a8d4b | ||
|
|
4f8661100b | ||
|
|
6b2eb1b3c0 | ||
|
|
f3593fa2f4 | ||
|
|
ccc7b86369 | ||
|
|
9e3c6dcf30 | ||
|
|
7be81887a6 | ||
|
|
29f71fc677 | ||
|
|
a14a6edc1b | ||
|
|
35bf0a3c10 | ||
|
|
55a8658f3a | ||
|
|
b0d9e1668c | ||
|
|
922367baf5 | ||
|
|
01320db007 | ||
|
|
f677695fc7 | ||
|
|
7902af2bf0 | ||
|
|
5faecb08f6 | ||
|
|
8e3d91020f | ||
|
|
4f54a47c83 |
23
CHANGELOG
23
CHANGELOG
@@ -1,3 +1,24 @@
|
||||
2024-01-27
|
||||
- complete libflac fix and add chaining enablement
|
||||
- fixed stream Ogg demux issue with unknown granule
|
||||
|
||||
2024-01-19
|
||||
- fixed libflac with OggFlac
|
||||
- AirPlay missed frame logging
|
||||
|
||||
2024-01-16
|
||||
- catch-up with cspot latest
|
||||
- refactor airplay flush/first packet
|
||||
- new libFLAC that supports multi-stream OggFlac
|
||||
- fix output threshold
|
||||
- log missed frames
|
||||
|
||||
2024-01-10
|
||||
- add OggFlac to stream metadata
|
||||
- fix OggFlac deadlock in flac callback when not enough data in streambuf
|
||||
- fix no displayer due to threadshold too high (use 500ms instead)
|
||||
- reset outputbuf when cspot starts
|
||||
|
||||
2024-01-01
|
||||
- ogg stream are parsed to foward metadata to LMS
|
||||
- fix some ogg parsing on multi-stream containers
|
||||
@@ -14,7 +35,7 @@
|
||||
- force gpio_pad_select_gpio in dac_controlset in case somebody uses UART gpio's (or other pre-programmed)
|
||||
|
||||
2023-11-08
|
||||
- execute dac_controlset even whne there is no i2s (for gpio)
|
||||
- execute dac_controlset even when there is no i2s (for gpio)
|
||||
|
||||
2023-11-07
|
||||
- led-vu gain + misc fixes
|
||||
|
||||
@@ -637,7 +637,11 @@ You can install IDF manually on Linux or Windows (using the Subsystem for Linux)
|
||||
**Use the esp-idf 4.3.5 https://github.com/espressif/esp-idf/tree/release/v4.3.5 ** or the 4.4.5 (and above version) if you want to build for esp32-s3
|
||||
|
||||
## Building SqueezeESP32
|
||||
When initially cloning the repo, make sure you do it recursively. For example: `git clone --recursive https://github.com/sle118/squeezelite-esp32.git`
|
||||
When initially cloning the repo, make sure you do it recursively. For example: `git clone --recursive https://github.com/sle118/squeezelite-esp32.git`. You also should install cspot additional components for protobuf use.
|
||||
```
|
||||
$ sudo pip3 install protobuf grpcio-tools
|
||||
```
|
||||
NB: I need to check on a fresh installation, but you might also require "protoc". You should do that within the esp32 local Python environment.
|
||||
|
||||
Don't forget to choose one of the config files in build_scripts/ and rename it sdkconfig.defaults or sdkconfig as many important WiFi/BT options are set there. **The codecs libraries will not be rebuilt by these scripts (it's a tedious process - see below)**
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* libFLAC - Free Lossless Audio Codec library
|
||||
* Copyright (C) 2000-2009 Josh Coalson
|
||||
* Copyright (C) 2011-2022 Xiph.Org Foundation
|
||||
* Copyright (C) 2011-2023 Xiph.Org Foundation
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
@@ -782,6 +782,25 @@ FLAC_API void FLAC__stream_decoder_delete(FLAC__StreamDecoder *decoder);
|
||||
*/
|
||||
FLAC_API FLAC__bool FLAC__stream_decoder_set_ogg_serial_number(FLAC__StreamDecoder *decoder, long serial_number);
|
||||
|
||||
/** Set the "allow Ogg chaining" flag. If set, the Ogg decoder will
|
||||
* prepare to receive a new stream once the last Ogg page arrives for
|
||||
* the stream encapsulating the FLAC audio data. This can be used to
|
||||
* support chained Ogg FLAC streams; a new \c STREAMINFO signals the
|
||||
* beginning of a new stream.
|
||||
*
|
||||
* \note
|
||||
* This function has no effect with native FLAC decoding.
|
||||
*
|
||||
* \default \c false
|
||||
* \param decoder A decoder instance to set.
|
||||
* \param allow Whether to allow chained streams.
|
||||
* \assert
|
||||
* \code decoder != NULL \endcode
|
||||
* \retval FLAC__bool
|
||||
* \c false if the decoder is already initialized, else \c true.
|
||||
*/
|
||||
FLAC_API FLAC__bool FLAC__stream_decoder_set_ogg_chaining(FLAC__StreamDecoder* decoder, FLAC__bool value);
|
||||
|
||||
/** Set the "MD5 signature checking" flag. If \c true, the decoder will
|
||||
* compute the MD5 signature of the unencoded audio data while decoding
|
||||
* and compare it to the signature from the STREAMINFO block, if it
|
||||
@@ -906,6 +925,17 @@ FLAC_API FLAC__StreamDecoderState FLAC__stream_decoder_get_state(const FLAC__Str
|
||||
*/
|
||||
FLAC_API const char *FLAC__stream_decoder_get_resolved_state_string(const FLAC__StreamDecoder *decoder);
|
||||
|
||||
/** Get the "allow Ogg chaining" flag as described in
|
||||
* \code FLAC__stream_decoder_set_ogg_chaining \endcode.
|
||||
*
|
||||
* \param decoder A decoder instance to query.
|
||||
* \assert
|
||||
* \code decoder != NULL \endcode
|
||||
* \retval FLAC__bool
|
||||
* See above.
|
||||
*/
|
||||
FLAC_API FLAC__bool FLAC__stream_decoder_get_ogg_chaining(const FLAC__StreamDecoder* decoder);
|
||||
|
||||
/** Get the "MD5 signature checking" flag.
|
||||
* This is the value of the setting, not whether or not the decoder is
|
||||
* currently checking the MD5 (remember, it can be turned off automatically
|
||||
|
||||
Binary file not shown.
@@ -96,6 +96,7 @@ typedef struct __attribute__((__packed__)) audio_buffer_entry { // decoded aud
|
||||
u16_t len;
|
||||
u8_t ready;
|
||||
u8_t allocated;
|
||||
u8_t missed;
|
||||
} abuf_t;
|
||||
|
||||
typedef struct rtp_s {
|
||||
@@ -126,11 +127,6 @@ typedef struct rtp_s {
|
||||
u32_t rtp, time;
|
||||
u8_t status;
|
||||
} synchro;
|
||||
struct {
|
||||
u32_t time;
|
||||
seq_t seqno;
|
||||
u32_t rtptime;
|
||||
} record;
|
||||
int latency; // rtp hold depth in samples
|
||||
u32_t resent_req, resent_rec; // total resent + recovered frames
|
||||
u32_t silent_frames; // total silence frames
|
||||
@@ -147,8 +143,8 @@ typedef struct rtp_s {
|
||||
#endif
|
||||
|
||||
struct alac_codec_s *alac_codec;
|
||||
int flush_seqno;
|
||||
bool playing;
|
||||
int first_seqno;
|
||||
enum { RTP_WAIT, RTP_STREAM, RTP_PLAY } state;
|
||||
int stalled;
|
||||
raop_data_cb_t data_cb;
|
||||
raop_cmd_cb_t cmd_cb;
|
||||
@@ -231,7 +227,7 @@ rtp_resp_t rtp_init(struct in_addr host, int latency, char *aeskey, char *aesiv,
|
||||
ctx->rtp_host.sin_family = AF_INET;
|
||||
ctx->rtp_host.sin_addr.s_addr = INADDR_ANY;
|
||||
pthread_mutex_init(&ctx->ab_mutex, 0);
|
||||
ctx->flush_seqno = -1;
|
||||
ctx->first_seqno = -1;
|
||||
ctx->latency = latency;
|
||||
ctx->ab_read = ctx->ab_write;
|
||||
|
||||
@@ -339,24 +335,23 @@ void rtp_end(rtp_t *ctx)
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
bool rtp_flush(rtp_t *ctx, unsigned short seqno, unsigned int rtptime, bool exit_locked)
|
||||
{
|
||||
bool rc = true;
|
||||
u32_t now = gettime_ms();
|
||||
{
|
||||
pthread_mutex_lock(&ctx->ab_mutex);
|
||||
|
||||
// always store flush seqno as we only want stricly above it, even when equal to RECORD
|
||||
ctx->first_seqno = seqno;
|
||||
bool flushed = false;
|
||||
|
||||
if (now < ctx->record.time + 250 || (ctx->record.seqno == seqno && ctx->record.rtptime == rtptime)) {
|
||||
rc = false;
|
||||
LOG_ERROR("[%p]: FLUSH ignored as same as RECORD (%hu - %u)", ctx, seqno, rtptime);
|
||||
} else {
|
||||
pthread_mutex_lock(&ctx->ab_mutex);
|
||||
buffer_reset(ctx->audio_buffer);
|
||||
ctx->playing = false;
|
||||
ctx->flush_seqno = seqno;
|
||||
if (!exit_locked) pthread_mutex_unlock(&ctx->ab_mutex);
|
||||
// no need to stop playing if recent or equal to record - but first_seqno is needed
|
||||
if (ctx->state == RTP_PLAY) {
|
||||
buffer_reset(ctx->audio_buffer);
|
||||
ctx->state = RTP_WAIT;
|
||||
flushed = true;
|
||||
LOG_INFO("[%p]: FLUSH packets below %hu - %u", ctx, seqno, rtptime);
|
||||
}
|
||||
|
||||
LOG_INFO("[%p]: flush %hu %u", ctx, seqno, rtptime);
|
||||
|
||||
return rc;
|
||||
|
||||
if (!exit_locked || !flushed) pthread_mutex_unlock(&ctx->ab_mutex);
|
||||
return flushed;
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
@@ -367,11 +362,9 @@ void rtp_flush_release(rtp_t *ctx) {
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
void rtp_record(rtp_t *ctx, unsigned short seqno, unsigned rtptime) {
|
||||
ctx->record.seqno = seqno;
|
||||
ctx->record.rtptime = rtptime;
|
||||
ctx->record.time = gettime_ms();
|
||||
|
||||
LOG_INFO("[%p]: record %hu %u", ctx, seqno, rtptime);
|
||||
ctx->first_seqno = (seqno || rtptime) ? seqno : -1;
|
||||
ctx->state = RTP_WAIT;
|
||||
LOG_INFO("[%p]: record %hu - %u", ctx, seqno, rtptime);
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
@@ -442,29 +435,54 @@ static void alac_decode(rtp_t *ctx, s16_t *dest, char *buf, int len, u16_t *outs
|
||||
/*---------------------------------------------------------------------------*/
|
||||
static void buffer_put_packet(rtp_t *ctx, seq_t seqno, unsigned rtptime, bool first, char *data, int len) {
|
||||
abuf_t *abuf = NULL;
|
||||
u32_t playtime;
|
||||
|
||||
pthread_mutex_lock(&ctx->ab_mutex);
|
||||
|
||||
if (!ctx->playing) {
|
||||
if ((ctx->flush_seqno == -1 || seq_order(ctx->flush_seqno, seqno)) &&
|
||||
(ctx->synchro.status & RTP_SYNC) && (ctx->synchro.status & NTP_SYNC)) {
|
||||
ctx->ab_write = seqno-1;
|
||||
ctx->ab_read = seqno;
|
||||
ctx->flush_seqno = -1;
|
||||
ctx->playing = true;
|
||||
ctx->resent_req = ctx->resent_rec = ctx->silent_frames = ctx->discarded = 0;
|
||||
playtime = ctx->synchro.time + ((rtptime - ctx->synchro.rtp) * 10) / (RAOP_SAMPLE_RATE / 100);
|
||||
ctx->cmd_cb(RAOP_PLAY, playtime);
|
||||
} else {
|
||||
pthread_mutex_unlock(&ctx->ab_mutex);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* if we have received a RECORD with a seqno, then this is the first allowed rtp sequence number
|
||||
* and we are in RTP_WAIT state. If seqno was 0, then we are waiting for a flush that will tell
|
||||
* us what should be our first allowed packet but we must accept everything, wait and clean when
|
||||
* we the it arrives. This means that first packet moves us to RTP_STREAM state where we accept
|
||||
* frames but wait for the FLUSH. If this was a FLUSH while playing, then we are also in RTP_WAIT
|
||||
* state but we do have an allowed seqno and we should not accept any frame before we have it */
|
||||
|
||||
// if we have a pending first seqno and we are below, always ignore it
|
||||
if (ctx->first_seqno != -1 && seq_order(seqno, ctx->first_seqno)) {
|
||||
pthread_mutex_unlock(&ctx->ab_mutex);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ctx->state == RTP_WAIT) {
|
||||
ctx->ab_write = seqno - 1;
|
||||
ctx->ab_read = ctx->ab_write + 1;
|
||||
ctx->resent_req = ctx->resent_rec = ctx->silent_frames = ctx->discarded = 0;
|
||||
if (ctx->first_seqno != -1) {
|
||||
LOG_INFO("[%p]: 1st accepted packet:%d, now playing", ctx, seqno);
|
||||
ctx->state = RTP_PLAY;
|
||||
ctx->first_seqno = -1;
|
||||
u32_t playtime = ctx->synchro.time + ((rtptime - ctx->synchro.rtp) * 10) / (RAOP_SAMPLE_RATE / 100);
|
||||
ctx->cmd_cb(RAOP_PLAY, playtime);
|
||||
} else {
|
||||
ctx->state = RTP_STREAM;
|
||||
LOG_INFO("[%p]: 1st accepted packet:%hu, waiting for FLUSH", ctx, seqno);
|
||||
}
|
||||
} else if (ctx->state == RTP_STREAM && ctx->first_seqno != -1 && seq_order(ctx->first_seqno, seqno + 1)) {
|
||||
// now we're talking, but first discard all packets with a seqno below first_seqno AND not ready
|
||||
while (seq_order(ctx->ab_read, ctx->first_seqno) ||
|
||||
!ctx->audio_buffer[BUFIDX(ctx->ab_read)].ready) {
|
||||
ctx->audio_buffer[BUFIDX(ctx->ab_read)].ready = false;
|
||||
ctx->ab_read++;
|
||||
}
|
||||
LOG_INFO("[%p]: done waiting for FLUSH with packet:%d, now playing starting:%hu", ctx, seqno, ctx->ab_read);
|
||||
ctx->state = RTP_PLAY;
|
||||
ctx->first_seqno = -1;
|
||||
u32_t playtime = ctx->synchro.time + ((rtptime - ctx->synchro.rtp) * 10) / (RAOP_SAMPLE_RATE / 100);
|
||||
ctx->cmd_cb(RAOP_PLAY, playtime);
|
||||
}
|
||||
|
||||
abuf = ctx->audio_buffer + BUFIDX(seqno);
|
||||
|
||||
if (seqno == (u16_t) (ctx->ab_write+1)) {
|
||||
// expected packet
|
||||
abuf = ctx->audio_buffer + BUFIDX(seqno);
|
||||
ctx->ab_write = seqno;
|
||||
LOG_SDEBUG("packet expected seqno:%hu rtptime:%u (W:%hu R:%hu)", seqno, rtptime, ctx->ab_write, ctx->ab_read);
|
||||
} else if (seq_order(ctx->ab_write, seqno)) {
|
||||
@@ -475,7 +493,7 @@ static void buffer_put_packet(rtp_t *ctx, seq_t seqno, unsigned rtptime, bool fi
|
||||
ctx->ab_read = seqno;
|
||||
} else {
|
||||
// request re-send missed frames and evaluate resent date as a whole *after*
|
||||
rtp_request_resend(ctx, ctx->ab_write + 1, seqno-1);
|
||||
if (ctx->state == RTP_PLAY) rtp_request_resend(ctx, ctx->ab_write + 1, seqno-1);
|
||||
|
||||
// resend date is after all requests have been sent
|
||||
u32_t now = gettime_ms();
|
||||
@@ -488,16 +506,15 @@ static void buffer_put_packet(rtp_t *ctx, seq_t seqno, unsigned rtptime, bool fi
|
||||
LOG_DEBUG("[%p]: packet newer seqno:%hu rtptime:%u (W:%hu R:%hu)", ctx, seqno, rtptime, ctx->ab_write, ctx->ab_read);
|
||||
}
|
||||
|
||||
abuf = ctx->audio_buffer + BUFIDX(seqno);
|
||||
ctx->ab_write = seqno;
|
||||
} else if (seq_order(ctx->ab_read, seqno + 1)) {
|
||||
// recovered packet, not yet sent
|
||||
abuf = ctx->audio_buffer + BUFIDX(seqno);
|
||||
ctx->resent_rec++;
|
||||
LOG_DEBUG("[%p]: packet recovered seqno:%hu rtptime:%u (W:%hu R:%hu)", ctx, seqno, rtptime, ctx->ab_write, ctx->ab_read);
|
||||
} else {
|
||||
// too late
|
||||
LOG_DEBUG("[%p]: packet too late seqno:%hu rtptime:%u (W:%hu R:%hu)", ctx, seqno, rtptime, ctx->ab_write, ctx->ab_read);
|
||||
// too late
|
||||
if (abuf->missed) LOG_INFO("[%p]: packet too late seqno:%hu rtptime:%u (W:%hu R:%hu)", ctx, seqno, rtptime, ctx->ab_write, ctx->ab_read);
|
||||
abuf = NULL;
|
||||
}
|
||||
|
||||
if (ctx->in_frames++ > 1000) {
|
||||
@@ -508,6 +525,7 @@ static void buffer_put_packet(rtp_t *ctx, seq_t seqno, unsigned rtptime, bool fi
|
||||
if (abuf) {
|
||||
alac_decode(ctx, abuf->data, data, len, &abuf->len);
|
||||
abuf->ready = 1;
|
||||
abuf->missed = 0;
|
||||
// this is the local rtptime when this frame is expected to play
|
||||
abuf->rtptime = rtptime;
|
||||
buffer_push_packet(ctx);
|
||||
@@ -528,7 +546,7 @@ static void buffer_push_packet(rtp_t *ctx) {
|
||||
u32_t now, playtime, hold = max((ctx->latency * 1000) / (8 * RAOP_SAMPLE_RATE), 100);
|
||||
|
||||
// not ready to play yet
|
||||
if (!ctx->playing || ctx->synchro.status != (RTP_SYNC | NTP_SYNC)) return;
|
||||
if (ctx->state != RTP_PLAY || ctx->synchro.status != (RTP_SYNC | NTP_SYNC)) return;
|
||||
|
||||
// there is always at least one frame in the buffer
|
||||
do {
|
||||
@@ -551,6 +569,7 @@ static void buffer_push_packet(rtp_t *ctx) {
|
||||
LOG_DEBUG("[%p]: created zero frame (W:%hu R:%hu)", ctx, ctx->ab_write, ctx->ab_read);
|
||||
ctx->data_cb(silence_frame, ctx->frame_size * 4, playtime);
|
||||
ctx->silent_frames++;
|
||||
curframe->missed = 1;
|
||||
}
|
||||
} else if (curframe->ready) {
|
||||
ctx->data_cb((const u8_t*) curframe->data, curframe->len, playtime);
|
||||
|
||||
@@ -22482,9 +22482,13 @@ mg_init_library(unsigned features)
|
||||
file_mutex_init =
|
||||
pthread_mutex_init(&global_log_file_lock, &pthread_mutex_attr);
|
||||
if (file_mutex_init == 0) {
|
||||
#ifdef WINSOCK_START
|
||||
/* Start WinSock */
|
||||
WSADATA data;
|
||||
failed = wsa = WSAStartup(MAKEWORD(2, 2), &data);
|
||||
#else
|
||||
failed = wsa = 0;
|
||||
#endif
|
||||
}
|
||||
#else
|
||||
mutexattr_init = pthread_mutexattr_init(&pthread_mutex_attr);
|
||||
@@ -22498,7 +22502,9 @@ mg_init_library(unsigned features)
|
||||
if (failed) {
|
||||
#if defined(_WIN32)
|
||||
if (wsa == 0) {
|
||||
#ifdef WINSOCK_START
|
||||
(void)WSACleanup();
|
||||
#endif
|
||||
}
|
||||
if (file_mutex_init == 0) {
|
||||
(void)pthread_mutex_destroy(&global_log_file_lock);
|
||||
@@ -22598,7 +22604,9 @@ mg_exit_library(void)
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32)
|
||||
#ifdef WINSOCK_START
|
||||
(void)WSACleanup();
|
||||
#endif
|
||||
(void)pthread_mutex_destroy(&global_log_file_lock);
|
||||
#else
|
||||
(void)pthread_mutexattr_destroy(&pthread_mutex_attr);
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
|
||||
using namespace bell;
|
||||
|
||||
std::mutex BellHTTPServer::initMutex;
|
||||
|
||||
class WebSocketHandler : public CivetWebSocketHandler {
|
||||
public:
|
||||
BellHTTPServer::WSDataHandler dataHandler;
|
||||
@@ -187,6 +189,7 @@ bool BellHTTPServer::handlePost(CivetServer* server,
|
||||
}
|
||||
|
||||
BellHTTPServer::BellHTTPServer(int serverPort) {
|
||||
std::lock_guard lock(initMutex);
|
||||
mg_init_library(0);
|
||||
BELL_LOG(info, "HttpServer", "Server listening on port %d", serverPort);
|
||||
this->serverPort = serverPort;
|
||||
@@ -197,6 +200,11 @@ BellHTTPServer::BellHTTPServer(int serverPort) {
|
||||
server = std::make_unique<CivetServer>(civetWebOptions);
|
||||
}
|
||||
|
||||
BellHTTPServer::~BellHTTPServer() {
|
||||
std::lock_guard lock(initMutex);
|
||||
mg_exit_library();
|
||||
}
|
||||
|
||||
std::unique_ptr<BellHTTPServer::HTTPResponse> BellHTTPServer::makeJsonResponse(
|
||||
const std::string& json, int status) {
|
||||
auto response = std::make_unique<BellHTTPServer::HTTPResponse>();
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace bell {
|
||||
class BellHTTPServer : public CivetHandler {
|
||||
public:
|
||||
BellHTTPServer(int serverPort);
|
||||
~BellHTTPServer();
|
||||
|
||||
enum class WSState { CONNECTED, READY, CLOSED };
|
||||
|
||||
@@ -100,6 +101,8 @@ class BellHTTPServer : public CivetHandler {
|
||||
std::mutex responseMutex;
|
||||
HTTPHandler notFoundHandler;
|
||||
|
||||
static std::mutex initMutex;
|
||||
|
||||
bool handleGet(CivetServer* server, struct mg_connection* conn);
|
||||
bool handlePost(CivetServer* server, struct mg_connection* conn);
|
||||
};
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
|
||||
#if __has_include("avahi-client/client.h")
|
||||
#include <avahi-client/client.h>
|
||||
@@ -41,8 +42,9 @@ class implMDNSService : public MDNSService {
|
||||
#endif
|
||||
static struct mdnsd* mdnsServer;
|
||||
static in_addr_t host;
|
||||
static std::atomic<size_t> instances;
|
||||
|
||||
implMDNSService(struct mdns_service* service) : service(service){};
|
||||
implMDNSService(struct mdns_service* service) : service(service){ instances++; };
|
||||
#ifndef BELL_DISABLE_AVAHI
|
||||
implMDNSService(AvahiEntryGroup* avahiGroup) : avahiGroup(avahiGroup){};
|
||||
#endif
|
||||
@@ -51,6 +53,7 @@ class implMDNSService : public MDNSService {
|
||||
|
||||
struct mdnsd* implMDNSService::mdnsServer = NULL;
|
||||
in_addr_t implMDNSService::host = INADDR_ANY;
|
||||
std::atomic<size_t> implMDNSService::instances = 0;
|
||||
static std::mutex registerMutex;
|
||||
#ifndef BELL_DISABLE_AVAHI
|
||||
AvahiClient* implMDNSService::avahiClient = NULL;
|
||||
@@ -66,11 +69,21 @@ void implMDNSService::unregisterService() {
|
||||
#ifndef BELL_DISABLE_AVAHI
|
||||
if (avahiGroup) {
|
||||
avahi_entry_group_free(avahiGroup);
|
||||
if (!--instances && implMDNSService::avahiClient) {
|
||||
avahi_client_free(implMDNSService::avahiClient);
|
||||
avahi_simple_poll_free(implMDNSService::avahiPoll);
|
||||
implMDNSService::avahiClient = nullptr;
|
||||
implMDNSService::avahiPoll = nullptr;
|
||||
}
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
mdns_service_remove(implMDNSService::mdnsServer, service);
|
||||
}
|
||||
if (!--instances && implMDNSService::mdnsServer) {
|
||||
mdnsd_stop(implMDNSService::mdnsServer);
|
||||
implMDNSService::mdnsServer = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<MDNSService> MDNSService::registerService(
|
||||
@@ -180,19 +193,14 @@ std::unique_ptr<MDNSService> MDNSService::registerService(
|
||||
std::string type(serviceType + "." + serviceProto + ".local");
|
||||
|
||||
BELL_LOG(info, "MDNS", "using built-in mDNS for %s", serviceName.c_str());
|
||||
struct mdns_service* mdnsService =
|
||||
auto service =
|
||||
mdnsd_register_svc(implMDNSService::mdnsServer, serviceName.c_str(),
|
||||
type.c_str(), servicePort, NULL, txt.data());
|
||||
if (mdnsService) {
|
||||
auto service =
|
||||
mdnsd_register_svc(implMDNSService::mdnsServer, serviceName.c_str(),
|
||||
type.c_str(), servicePort, NULL, txt.data());
|
||||
|
||||
return std::make_unique<implMDNSService>(service);
|
||||
}
|
||||
if (service) return std::make_unique<implMDNSService>(service);
|
||||
}
|
||||
|
||||
BELL_LOG(error, "MDNS", "cannot start any mDNS listener for %s",
|
||||
serviceName.c_str());
|
||||
return NULL;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -19,13 +19,12 @@ using namespace bell;
|
||||
class implMDNSService : public MDNSService {
|
||||
private:
|
||||
struct mdns_service* service;
|
||||
void unregisterService(void) {
|
||||
mdns_service_remove(implMDNSService::mdnsServer, service);
|
||||
};
|
||||
void unregisterService(void);
|
||||
|
||||
public:
|
||||
static struct mdnsd* mdnsServer;
|
||||
implMDNSService(struct mdns_service* service) : service(service){};
|
||||
static std::atomic<size_t> instances;
|
||||
implMDNSService(struct mdns_service* service) : service(service) { instances++; };
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -33,8 +32,18 @@ class implMDNSService : public MDNSService {
|
||||
**/
|
||||
|
||||
struct mdnsd* implMDNSService::mdnsServer = NULL;
|
||||
std::atomic<size_t> implMDNSService::instances = 0;
|
||||
|
||||
static std::mutex registerMutex;
|
||||
|
||||
void implMDNSService::unregisterService() {
|
||||
mdns_service_remove(implMDNSService::mdnsServer, service);
|
||||
if (!--instances && implMDNSService::mdnsServer) {
|
||||
mdnsd_stop(implMDNSService::mdnsServer);
|
||||
implMDNSService::mdnsServer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<MDNSService> MDNSService::registerService(
|
||||
const std::string& serviceName, const std::string& serviceType,
|
||||
const std::string& serviceProto, const std::string& serviceHost,
|
||||
@@ -94,5 +103,5 @@ std::unique_ptr<MDNSService> MDNSService::registerService(
|
||||
mdnsd_register_svc(implMDNSService::mdnsServer, serviceName.c_str(),
|
||||
type.c_str(), servicePort, NULL, txt.data());
|
||||
|
||||
return std::make_unique<implMDNSService>(service);
|
||||
return service ? std::make_unique<implMDNSService>(service) : nullptr;
|
||||
}
|
||||
|
||||
@@ -72,7 +72,11 @@ class Task {
|
||||
(LPTHREAD_START_ROUTINE)taskEntryFunc, this, 0, NULL);
|
||||
return thread != NULL;
|
||||
#else
|
||||
return (pthread_create(&thread, NULL, taskEntryFunc, this) == 0);
|
||||
if (!pthread_create(&thread, NULL, taskEntryFunc, this)) {
|
||||
pthread_detach(thread);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -108,13 +112,7 @@ class Task {
|
||||
#endif
|
||||
|
||||
static void* taskEntryFunc(void* This) {
|
||||
Task* self = (Task*)This;
|
||||
self->runTask();
|
||||
#if _WIN32
|
||||
WaitForSingleObject(self->thread, INFINITE);
|
||||
#else
|
||||
pthread_join(self->thread, NULL);
|
||||
#endif
|
||||
((Task*)This)->runTask();
|
||||
return NULL;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -24,7 +24,7 @@ class CDNAudioFile;
|
||||
// Used in got track info event
|
||||
struct TrackInfo {
|
||||
std::string name, album, artist, imageUrl, trackId;
|
||||
uint32_t duration;
|
||||
uint32_t duration, number, discNumber;
|
||||
|
||||
void loadPbTrack(Track* pbTrack, const std::vector<uint8_t>& gid);
|
||||
void loadPbEpisode(Episode* pbEpisode, const std::vector<uint8_t>& gid);
|
||||
|
||||
@@ -32,6 +32,8 @@ message Artist {
|
||||
message Track {
|
||||
optional bytes gid = 1;
|
||||
optional string name = 2;
|
||||
optional sint32 number = 5;
|
||||
optional sint32 disc_number = 6;
|
||||
optional sint32 duration = 0x7;
|
||||
optional Album album = 0x3;
|
||||
repeated Artist artist = 0x4;
|
||||
@@ -44,6 +46,7 @@ message Episode {
|
||||
optional bytes gid = 1;
|
||||
optional string name = 2;
|
||||
optional sint32 duration = 7;
|
||||
optional sint32 number = 65;
|
||||
repeated AudioFile file = 12;
|
||||
repeated Restriction restriction = 0x4B;
|
||||
optional ImageGroup covers = 0x44;
|
||||
|
||||
@@ -97,6 +97,8 @@ void TrackInfo::loadPbTrack(Track* pbTrack, const std::vector<uint8_t>& gid) {
|
||||
}
|
||||
}
|
||||
|
||||
number = pbTrack->has_number ? pbTrack->number : 0;
|
||||
discNumber = pbTrack->has_disc_number ? pbTrack->disc_number : 0;
|
||||
duration = pbTrack->duration;
|
||||
}
|
||||
|
||||
@@ -113,6 +115,8 @@ void TrackInfo::loadPbEpisode(Episode* pbEpisode,
|
||||
imageUrl = "https://i.scdn.co/image/" + bytesToHexString(imageId);
|
||||
}
|
||||
|
||||
number = pbEpisode->has_number ? pbEpisode->number : 0;
|
||||
discNumber = 0;
|
||||
duration = pbEpisode->duration;
|
||||
}
|
||||
|
||||
|
||||
@@ -288,6 +288,7 @@ static bool raop_sink_cmd_handler(raop_event_t event, va_list args)
|
||||
output.frames_played = 0;
|
||||
output.external = DECODE_RAOP;
|
||||
output.state = OUTPUT_STOPPED;
|
||||
|
||||
if (decode.state != DECODE_STOPPED) decode.state = DECODE_ERROR;
|
||||
LOG_INFO("resizing buffer %u", outputbuf->size);
|
||||
break;
|
||||
@@ -377,6 +378,7 @@ static bool cspot_cmd_handler(cspot_event_t cmd, va_list args)
|
||||
output.state = OUTPUT_STOPPED;
|
||||
sink_state = SINK_ABORT;
|
||||
_buf_flush(outputbuf);
|
||||
_buf_limit(outputbuf, 0);
|
||||
if (decode.state != DECODE_STOPPED) decode.state = DECODE_ERROR;
|
||||
LOG_INFO("CSpot start track");
|
||||
break;
|
||||
|
||||
@@ -70,6 +70,7 @@ struct flac {
|
||||
);
|
||||
FLAC__bool (* FLAC__stream_decoder_process_single)(FLAC__StreamDecoder *decoder);
|
||||
FLAC__StreamDecoderState (* FLAC__stream_decoder_get_state)(const FLAC__StreamDecoder *decoder);
|
||||
FLAC__bool (*FLAC__stream_decoder_set_ogg_chaining)(FLAC__StreamDecoder* decoder, FLAC__bool allow);
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -121,6 +122,9 @@ static FLAC__StreamDecoderReadStatus read_cb(const FLAC__StreamDecoder *decoder,
|
||||
_buf_inc_readp(streambuf, bytes);
|
||||
UNLOCK_S;
|
||||
|
||||
// give some time for stream to acquire data otherwise flac will hammer us
|
||||
if (!end && !bytes) usleep(100 * 1000);
|
||||
|
||||
*want = bytes;
|
||||
|
||||
return end ? FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM : FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;
|
||||
@@ -137,8 +141,8 @@ static FLAC__StreamDecoderWriteStatus write_cb(const FLAC__StreamDecoder *decode
|
||||
FLAC__int32 *rptr = (FLAC__int32 *)buffer[channels > 1 ? 1 : 0];
|
||||
|
||||
if (decode.new_stream) {
|
||||
LOG_INFO("setting track_start");
|
||||
LOCK_O;
|
||||
LOG_INFO("setting track_start");
|
||||
output.track_start = outputbuf->writep;
|
||||
decode.new_stream = false;
|
||||
|
||||
@@ -253,6 +257,7 @@ static void flac_open(u8_t sample_size, u8_t sample_rate, u8_t channels, u8_t en
|
||||
|
||||
if ( f->container == 'o' ) {
|
||||
LOG_INFO("ogg/flac container - using init_ogg_stream");
|
||||
FLAC(f, stream_decoder_set_ogg_chaining, f->decoder, true);
|
||||
FLAC(f, stream_decoder_init_ogg_stream, f->decoder, &read_cb, NULL, NULL, NULL, NULL, &write_cb, NULL, &error_cb, NULL);
|
||||
} else {
|
||||
FLAC(f, stream_decoder_init_stream, f->decoder, &read_cb, NULL, NULL, NULL, NULL, &write_cb, NULL, &error_cb, NULL);
|
||||
@@ -295,6 +300,7 @@ static bool load_flac() {
|
||||
f->FLAC__stream_decoder_init_ogg_stream = dlsym(handle, "FLAC__stream_decoder_init_ogg_stream");
|
||||
f->FLAC__stream_decoder_process_single = dlsym(handle, "FLAC__stream_decoder_process_single");
|
||||
f->FLAC__stream_decoder_get_state = dlsym(handle, "FLAC__stream_decoder_get_state");
|
||||
f->FLAC__stream_decoder_set_ogg_chaining = dlsym(handle, "FLAC__stream_decoder_set_ogg_chaining");
|
||||
|
||||
if ((err = dlerror()) != NULL) {
|
||||
LOG_INFO("dlerror: %s", err);
|
||||
|
||||
@@ -194,7 +194,7 @@ static int read_opus_header(void) {
|
||||
// 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
|
||||
// always set stream serialno if we have a new one (no multiplexed streams)
|
||||
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)
|
||||
|
||||
@@ -60,7 +60,7 @@ frames_t _output_frames(frames_t avail) {
|
||||
silence = false;
|
||||
|
||||
// start when threshold met
|
||||
if (output.state == OUTPUT_BUFFER && (frames * BYTES_PER_FRAME) > output.threshold * output.next_sample_rate / 10 && frames > output.start_frames) {
|
||||
if (output.state == OUTPUT_BUFFER && frames > output.threshold * output.next_sample_rate / 10 && frames > output.start_frames) {
|
||||
output.state = OUTPUT_RUNNING;
|
||||
LOG_INFO("start buffer frames: %u", frames);
|
||||
wake_controller();
|
||||
|
||||
@@ -139,8 +139,11 @@ static int _write_frames(frames_t out_frames, bool silence, s32_t gainL, s32_t g
|
||||
u8_t *buf = silencebuf;
|
||||
memcpy(btout + oframes * BYTES_PER_FRAME, buf, out_frames * BYTES_PER_FRAME);
|
||||
}
|
||||
|
||||
output_visu_export(btout + oframes * BYTES_PER_FRAME, out_frames, output.current_sample_rate, silence, (gainL + gainR) / 2);
|
||||
|
||||
// don't update visu if we don't have enough data in buffer (500 ms)
|
||||
if (silence || _buf_used(outputbuf) > BYTES_PER_FRAME * output.current_sample_rate / 2) {
|
||||
output_visu_export(btout + oframes * BYTES_PER_FRAME, out_frames, output.current_sample_rate, silence, (gainL + gainR) / 2);
|
||||
}
|
||||
|
||||
oframes += out_frames;
|
||||
|
||||
|
||||
@@ -439,7 +439,7 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
|
||||
static DRAM_ATTR StaticTask_t xTaskBuffer __attribute__ ((aligned (4)));
|
||||
static EXT_RAM_ATTR StackType_t xStack[OUTPUT_THREAD_STACK_SIZE] __attribute__ ((aligned (4)));
|
||||
output_i2s_task = xTaskCreateStaticPinnedToCore( (TaskFunction_t) output_thread_i2s, "output_i2s", OUTPUT_THREAD_STACK_SIZE,
|
||||
NULL, CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT + 1, xStack, &xTaskBuffer, 0 );
|
||||
NULL, CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT + 10, xStack, &xTaskBuffer, 0 );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -485,8 +485,8 @@ static int _i2s_write_frames(frames_t out_frames, bool silence, s32_t gainL, s32
|
||||
memcpy(obuf + oframes * BYTES_PER_FRAME, silencebuf, out_frames * BYTES_PER_FRAME);
|
||||
}
|
||||
|
||||
// don't update visu if we don't have enough data in buffer
|
||||
if (silence || output.external || _buf_used(outputbuf) > outputbuf->size >> 2 ) {
|
||||
// don't update visu if we don't have enough data in buffer (500 ms)
|
||||
if (silence || _buf_used(outputbuf) > BYTES_PER_FRAME * output.current_sample_rate / 2) {
|
||||
output_visu_export(obuf + oframes * BYTES_PER_FRAME, out_frames, output.current_sample_rate, silence, (gainL + gainR) / 2);
|
||||
}
|
||||
|
||||
|
||||
@@ -393,7 +393,9 @@ static void process_strm(u8_t *pkt, int len) {
|
||||
stream_file(header, header_len, strm->threshold * 1024);
|
||||
autostart -= 2;
|
||||
} else {
|
||||
stream_sock(ip, port, strm->format, header, header_len, strm->threshold * 1024, autostart >= 2);
|
||||
stream_sock(ip, port, strm->flags & 0x20,
|
||||
strm->format == 'o' || strm->format == 'u' || (strm->format == 'f' && strm->pcm_sample_size == 'o'),
|
||||
header, header_len, strm->threshold * 1024, autostart >= 2);
|
||||
}
|
||||
sendSTAT("STMc", 0);
|
||||
sentSTMu = sentSTMo = sentSTMl = false;
|
||||
@@ -412,8 +414,8 @@ static void process_strm(u8_t *pkt, int len) {
|
||||
output.fade_secs = strm->transition_period;
|
||||
output.invert = (strm->flags & 0x03) == 0x03;
|
||||
output.channels = (strm->flags & 0x0c) >> 2;
|
||||
UNLOCK_O;
|
||||
LOG_DEBUG("set fade: %u, channels: %u, invert: %u", output.fade_mode, output.channels, output.invert);
|
||||
UNLOCK_O;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -580,26 +580,12 @@ 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, char codec, const char *header, size_t header_len, unsigned threshold, bool cont_wait);
|
||||
void stream_sock(u32_t ip, u16_t port, bool use_ssl, bool use_ogg, const char *header, size_t header_len, unsigned threshold, bool cont_wait);
|
||||
bool stream_disconnect(void);
|
||||
|
||||
// decode.c
|
||||
|
||||
@@ -61,6 +61,23 @@ static sockfd fd;
|
||||
|
||||
struct EXT_RAM_ATTR streamstate stream;
|
||||
|
||||
static EXT_RAM_ATTR struct {
|
||||
bool flac;
|
||||
u64_t serial;
|
||||
enum { OGG_OFF, OGG_SYNC, OGG_HEADER, OGG_SEGMENTS, OGG_PAGE } state;
|
||||
size_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;
|
||||
#pragma pack(pop)
|
||||
} ogg;
|
||||
|
||||
#if USE_SSL
|
||||
static SSL_CTX *SSLctx;
|
||||
SSL *ssl;
|
||||
@@ -113,7 +130,6 @@ static int _poll(SSL *ssl, struct pollfd *pollinfo, int timeout) {
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
static bool send_header(void) {
|
||||
char *ptr = stream.header;
|
||||
int len = stream.header_len;
|
||||
@@ -148,8 +164,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 (ogg.state == OGG_PAGE && ogg.data) free(ogg.data);
|
||||
ogg.data = NULL;
|
||||
#if USE_SSL
|
||||
if (ssl) {
|
||||
SSL_shutdown(ssl);
|
||||
@@ -162,81 +178,96 @@ 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;
|
||||
static size_t memfind(const u8_t* haystack, size_t n, const char* needle, size_t len, size_t* offset) {
|
||||
size_t i;
|
||||
for (i = 0; i < n && *offset != len; i++) *offset = (haystack[i] == needle[*offset]) ? *offset + 1 : 0;
|
||||
return i;
|
||||
}
|
||||
|
||||
/* https://xiph.org/ogg/doc/framing.html
|
||||
* https://xiph.org/flac/ogg_mapping.html
|
||||
* https://xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-610004.2 */
|
||||
|
||||
static void stream_ogg(size_t n) {
|
||||
if (stream.ogg.state == STREAM_OGG_OFF) return;
|
||||
if (ogg.state == OGG_OFF) return;
|
||||
u8_t* p = streambuf->writep;
|
||||
|
||||
while (n) {
|
||||
size_t consumed = min(stream.ogg.miss, n);
|
||||
size_t consumed = min(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;
|
||||
if (ogg.data) {
|
||||
memcpy(ogg.data + ogg.want - ogg.miss, p, consumed);
|
||||
ogg.miss -= consumed;
|
||||
if (ogg.miss) return;
|
||||
}
|
||||
|
||||
// we have what we want, let's parse
|
||||
switch (stream.ogg.state) {
|
||||
case STREAM_OGG_SYNC: {
|
||||
stream.ogg.miss -= consumed;
|
||||
switch (ogg.state) {
|
||||
case OGG_SYNC: {
|
||||
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;
|
||||
size_t pos = memfind(p, n, "OggS", 4, &ogg.match);
|
||||
if (ogg.match == 4) {
|
||||
consumed = pos - ogg.match;
|
||||
ogg.state = OGG_HEADER;
|
||||
ogg.miss = ogg.want = sizeof(ogg.header);
|
||||
ogg.data = (u8_t*) &ogg.header;
|
||||
ogg.match = 0;
|
||||
} else {
|
||||
LOG_INFO("OggS not at expected position");
|
||||
if (!ogg.match) LOG_INFO("OggS not at expected position %zu/%zu", pos, n);
|
||||
LOG_INFO("OggS not at expected position %zu/%zu", pos, n);
|
||||
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;
|
||||
case OGG_HEADER:
|
||||
if (!memcmp(ogg.header.pattern, "OggS", 4)) {
|
||||
ogg.miss = ogg.want = ogg.header.count;
|
||||
ogg.data = ogg.segments;
|
||||
ogg.state = OGG_SEGMENTS;
|
||||
} else {
|
||||
stream.ogg.state = STREAM_OGG_SYNC;
|
||||
stream.ogg.data = NULL;
|
||||
ogg.state = OGG_SYNC;
|
||||
ogg.data = NULL;
|
||||
}
|
||||
break;
|
||||
case STREAM_OGG_SEGMENTS:
|
||||
case 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;
|
||||
for (size_t i = 0; i < ogg.want; i++) ogg.miss += ogg.data[i];
|
||||
ogg.want = ogg.miss;
|
||||
|
||||
// acquire serial number when we are looking for headers and hit a bos
|
||||
if (ogg.serial == ULLONG_MAX && (ogg.header.type & 0x02)) ogg.serial = ogg.header.serial;
|
||||
|
||||
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 {
|
||||
// we have overshot and missed header, reset serial number to restart search (O and -1 are le/be)
|
||||
if (ogg.header.serial == ogg.serial && ogg.header.granule && ogg.header.granule != -1) ogg.serial = ULLONG_MAX;
|
||||
|
||||
// not our serial (the above protected us from granule > 0)
|
||||
if (ogg.header.serial != ogg.serial) {
|
||||
// otherwise, jump over data
|
||||
stream.ogg.state = STREAM_OGG_SYNC;
|
||||
stream.ogg.data = NULL;
|
||||
ogg.state = OGG_SYNC;
|
||||
ogg.data = NULL;
|
||||
} else {
|
||||
ogg.state = OGG_PAGE;
|
||||
ogg.data = malloc(ogg.want);
|
||||
}
|
||||
break;
|
||||
case STREAM_OGG_PAGE: {
|
||||
u32_t offset = 0;
|
||||
case OGG_PAGE: {
|
||||
char** tag = (char* []){ "\x3vorbis", "OpusTags", NULL };
|
||||
size_t ofs = 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;
|
||||
|
||||
/* with OggFlac, we need the next page (packet) - VorbisComment is wrapped into a FLAC_METADATA
|
||||
* and except with vorbis, comment packet starts a new page but even in vorbis, it won't span
|
||||
* accross multiple pages */
|
||||
if (ogg.flac) ofs = 4;
|
||||
else if (!memcmp(ogg.data, "\x7f""FLAC", 5)) ogg.flac = true;
|
||||
else for (size_t n = 0; *tag; tag++, ofs = 0) if ((ofs = memfind(ogg.data, ogg.want, *tag, strlen(*tag), &n)) && n == strlen(*tag)) break;
|
||||
|
||||
if (ofs) {
|
||||
// u32:len,char[]:vendorId, u32:N, N x (u32:len,char[]:comment)
|
||||
char* p = (char*) stream.ogg.data + pos;
|
||||
char* p = (char*) ogg.data + ofs;
|
||||
p += *p + 4;
|
||||
u32_t count = *p;
|
||||
p += 4;
|
||||
@@ -256,16 +287,19 @@ static void stream_ogg(size_t n) {
|
||||
stream.header[stream.header_len++] = len;
|
||||
memcpy(stream.header + stream.header_len, p, len);
|
||||
stream.header_len += len;
|
||||
LOG_INFO("metadata: %.*s", len, p);
|
||||
}
|
||||
}
|
||||
|
||||
ogg.flac = false;
|
||||
ogg.serial = ULLONG_MAX;
|
||||
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;
|
||||
free(ogg.data);
|
||||
ogg.data = NULL;
|
||||
ogg.state = OGG_SYNC;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -603,7 +637,7 @@ void stream_file(const char *header, size_t header_len, unsigned threshold) {
|
||||
UNLOCK;
|
||||
}
|
||||
|
||||
void stream_sock(u32_t ip, u16_t port, char codec, const char *header, size_t header_len, unsigned threshold, bool cont_wait) {
|
||||
void stream_sock(u32_t ip, u16_t port, bool use_ssl, bool use_ogg, const char *header, size_t header_len, unsigned threshold, bool cont_wait) {
|
||||
struct sockaddr_in addr;
|
||||
|
||||
#if EMBEDDED
|
||||
@@ -703,8 +737,10 @@ void stream_sock(u32_t ip, u16_t port, char codec, const char *header, size_t he
|
||||
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;
|
||||
ogg.miss = ogg.match = 0;
|
||||
ogg.state = use_ogg ? OGG_SYNC : OGG_OFF;
|
||||
ogg.flac = false;
|
||||
ogg.serial = ULLONG_MAX;
|
||||
|
||||
UNLOCK;
|
||||
}
|
||||
@@ -725,8 +761,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;
|
||||
if (ogg.state == OGG_PAGE && ogg.data) free(ogg.data);
|
||||
ogg.data = NULL;
|
||||
UNLOCK;
|
||||
return disc;
|
||||
}
|
||||
|
||||
@@ -201,7 +201,7 @@ static int read_vorbis_header(void) {
|
||||
// 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
|
||||
// always set stream serialno if we have a new one (no multiplexed streams)
|
||||
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)
|
||||
|
||||
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.4ae048.bundle.js.gz
vendored
Normal file
BIN
components/wifi-manager/webapp/dist/js/index.4ae048.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
@@ -73,6 +73,12 @@ 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;
|
||||
@@ -223,6 +229,9 @@ 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 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.105fd5.bundle.js.gz BINARY)
|
||||
target_add_binary_data( __idf_wifi-manager webapp/dist/js/node_vendors.105fd5.bundle.js.gz BINARY)
|
||||
target_add_binary_data( __idf_wifi-manager webapp/dist/js/index.4ae048.bundle.js.gz BINARY)
|
||||
target_add_binary_data( __idf_wifi-manager webapp/dist/js/node_vendors.4ae048.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_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");
|
||||
extern const uint8_t _index_4ae048_bundle_js_gz_start[] asm("_binary_index_4ae048_bundle_js_gz_start");
|
||||
extern const uint8_t _index_4ae048_bundle_js_gz_end[] asm("_binary_index_4ae048_bundle_js_gz_end");
|
||||
extern const uint8_t _node_vendors_4ae048_bundle_js_gz_start[] asm("_binary_node_vendors_4ae048_bundle_js_gz_start");
|
||||
extern const uint8_t _node_vendors_4ae048_bundle_js_gz_end[] asm("_binary_node_vendors_4ae048_bundle_js_gz_end");
|
||||
const char * resource_lookups[] = {
|
||||
"/css/index.4bbe29a78a667faa2b6f.css.gz",
|
||||
"/favicon-32x32.png",
|
||||
"/index.html.gz",
|
||||
"/js/index.105fd5.bundle.js.gz",
|
||||
"/js/node_vendors.105fd5.bundle.js.gz",
|
||||
"/js/index.4ae048.bundle.js.gz",
|
||||
"/js/node_vendors.4ae048.bundle.js.gz",
|
||||
""
|
||||
};
|
||||
const uint8_t * resource_map_start[] = {
|
||||
_index_4bbe29a78a667faa2b6f_css_gz_start,
|
||||
_favicon_32x32_png_start,
|
||||
_index_html_gz_start,
|
||||
_index_105fd5_bundle_js_gz_start,
|
||||
_node_vendors_105fd5_bundle_js_gz_start
|
||||
_index_4ae048_bundle_js_gz_start,
|
||||
_node_vendors_4ae048_bundle_js_gz_start
|
||||
};
|
||||
const uint8_t * resource_map_end[] = {
|
||||
_index_4bbe29a78a667faa2b6f_css_gz_end,
|
||||
_favicon_32x32_png_end,
|
||||
_index_html_gz_end,
|
||||
_index_105fd5_bundle_js_gz_end,
|
||||
_node_vendors_105fd5_bundle_js_gz_end
|
||||
_index_4ae048_bundle_js_gz_end,
|
||||
_node_vendors_4ae048_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.105fd5.bundle.js.gz,dist/js/node_vendors.105fd5.bundle.js.gz
|
||||
dist/css/index.4bbe29a78a667faa2b6f.css.gz,dist/favicon-32x32.png,dist/index.html.gz,dist/js/index.4ae048.bundle.js.gz,dist/js/node_vendors.4ae048.bundle.js.gz
|
||||
***********************************/
|
||||
#pragma once
|
||||
#include <inttypes.h>
|
||||
|
||||
BIN
server_certs/DigiCertGlobalRootCA.crt.72
Normal file
BIN
server_certs/DigiCertGlobalRootCA.crt.72
Normal file
Binary file not shown.
BIN
server_certs/DigiCertGlobalRootCA.crt.73
Normal file
BIN
server_certs/DigiCertGlobalRootCA.crt.73
Normal file
Binary file not shown.
BIN
server_certs/DigiCertGlobalRootCA.crt.74
Normal file
BIN
server_certs/DigiCertGlobalRootCA.crt.74
Normal file
Binary file not shown.
BIN
server_certs/DigiCertGlobalRootCA.crt.75
Normal file
BIN
server_certs/DigiCertGlobalRootCA.crt.75
Normal file
Binary file not shown.
BIN
server_certs/DigiCertGlobalRootCA.crt.76
Normal file
BIN
server_certs/DigiCertGlobalRootCA.crt.76
Normal file
Binary file not shown.
BIN
server_certs/DigiCertGlobalRootCA.crt.77
Normal file
BIN
server_certs/DigiCertGlobalRootCA.crt.77
Normal file
Binary file not shown.
BIN
server_certs/DigiCertGlobalRootCA.crt.78
Normal file
BIN
server_certs/DigiCertGlobalRootCA.crt.78
Normal file
Binary file not shown.
BIN
server_certs/r2m01.cer.44
Normal file
BIN
server_certs/r2m01.cer.44
Normal file
Binary file not shown.
BIN
server_certs/r2m01.cer.45
Normal file
BIN
server_certs/r2m01.cer.45
Normal file
Binary file not shown.
BIN
server_certs/r2m01.cer.46
Normal file
BIN
server_certs/r2m01.cer.46
Normal file
Binary file not shown.
BIN
server_certs/r2m01.cer.47
Normal file
BIN
server_certs/r2m01.cer.47
Normal file
Binary file not shown.
BIN
server_certs/r2m01.cer.48
Normal file
BIN
server_certs/r2m01.cer.48
Normal file
Binary file not shown.
BIN
server_certs/r2m01.cer.49
Normal file
BIN
server_certs/r2m01.cer.49
Normal file
Binary file not shown.
BIN
server_certs/r2m01.cer.50
Normal file
BIN
server_certs/r2m01.cer.50
Normal file
Binary file not shown.
Reference in New Issue
Block a user