From 38fee2068031f01700acbfadb202f434e9dd3ce3 Mon Sep 17 00:00:00 2001 From: philippe44 Date: Mon, 11 Sep 2023 18:36:08 -0700 Subject: [PATCH] better handling of AIrPlay network issues - release --- components/raop/raop.c | 12 ++++-- components/raop/raop_sink.c | 12 ++++-- components/raop/raop_sink.h | 2 +- components/raop/rtp.c | 51 ++++++++++++++---------- components/squeezelite/decode_external.c | 2 +- 5 files changed, 47 insertions(+), 32 deletions(-) diff --git a/components/raop/raop.c b/components/raop/raop.c index 76b41932..1befdcac 100644 --- a/components/raop/raop.c +++ b/components/raop/raop.c @@ -276,6 +276,7 @@ bool raop_cmd(struct raop_ctx_s *ctx, raop_event_t event, void *param) { struct sockaddr_in addr; int sock; char *command = NULL; + bool success = false; // first notify the remote controller (if any) switch(event) { @@ -325,7 +326,7 @@ bool raop_cmd(struct raop_ctx_s *ctx, raop_event_t event, void *param) { // no command to send to remote or no remote found yet if (!command || !ctx->active_remote.port) { NFREE(command); - return false; + return success; } sock = socket(AF_INET, SOCK_STREAM, 0); @@ -348,16 +349,19 @@ bool raop_cmd(struct raop_ctx_s *ctx, raop_event_t event, void *param) { len = recv(sock, resp, 512, 0); if (len > 0) resp[len-1] = '\0'; LOG_INFO("[%p]: sending airplay remote\n%s<== received ==>\n%s", ctx, buf, resp); - + NFREE(method); NFREE(buf); kd_free(headers); - } + success = true; + } else { + LOG_INFO("[%p]: can't connect to remote for %s", ctx, command); + } free(command); closesocket(sock); - return true; + return success; } /*----------------------------------------------------------------------------*/ diff --git a/components/raop/raop_sink.c b/components/raop/raop_sink.c index f87071e7..04c9e39c 100644 --- a/components/raop/raop_sink.c +++ b/components/raop/raop_sink.c @@ -119,7 +119,12 @@ static bool cmd_handler(raop_event_t event, ...) { break; case RAOP_FLUSH: displayer_control(DISPLAYER_TIMER_PAUSE); - break; + break; + case RAOP_STALLED: + raop_abort(raop); + actrls_unset(); + displayer_control(DISPLAYER_SHUTDOWN); + break; case RAOP_STOP: actrls_unset(); displayer_control(DISPLAYER_SUSPEND); @@ -191,8 +196,7 @@ void raop_sink_init(raop_cmd_vcb_t cmd_cb, raop_data_cb_t data_cb) { */ void raop_disconnect(void) { LOG_INFO("forced disconnection"); - displayer_control(DISPLAYER_SHUTDOWN); // in case we can't communicate with AirPlay controller, abort session - if (!raop_cmd(raop, RAOP_STOP, NULL)) raop_abort(raop); - actrls_unset(); + if (!raop_cmd(raop, RAOP_STOP, NULL)) cmd_handler(RAOP_STALLED); + else displayer_control(DISPLAYER_SHUTDOWN); } diff --git a/components/raop/raop_sink.h b/components/raop/raop_sink.h index 4f6c5dea..99558fa9 100644 --- a/components/raop/raop_sink.h +++ b/components/raop/raop_sink.h @@ -14,7 +14,7 @@ #define RAOP_SAMPLE_RATE 44100 -typedef enum { RAOP_SETUP, RAOP_STREAM, RAOP_PLAY, RAOP_FLUSH, RAOP_METADATA, RAOP_ARTWORK, RAOP_PROGRESS, RAOP_PAUSE, RAOP_STOP, +typedef enum { RAOP_SETUP, RAOP_STREAM, RAOP_PLAY, RAOP_FLUSH, RAOP_METADATA, RAOP_ARTWORK, RAOP_PROGRESS, RAOP_PAUSE, RAOP_STOP, RAOP_STALLED, RAOP_VOLUME, RAOP_TIMING, RAOP_PREV, RAOP_NEXT, RAOP_REW, RAOP_FWD, RAOP_VOLUME_UP, RAOP_VOLUME_DOWN, RAOP_RESUME, RAOP_TOGGLE } raop_event_t ; diff --git a/components/raop/rtp.c b/components/raop/rtp.c index dfdf568b..5652b348 100644 --- a/components/raop/rtp.c +++ b/components/raop/rtp.c @@ -82,7 +82,7 @@ static log_level *loglevel = &raop_loglevel; #define RTP_SYNC (0x01) #define NTP_SYNC (0x02) -#define RESEND_TO 200 +#define RESEND_TO 250 enum { DATA = 0, CONTROL, TIMING }; @@ -149,6 +149,7 @@ typedef struct rtp_s { struct alac_codec_s *alac_codec; int flush_seqno; bool playing; + int stalled; raop_data_cb_t data_cb; raop_cmd_cb_t cmd_cb; } rtp_t; @@ -466,26 +467,27 @@ static void buffer_put_packet(rtp_t *ctx, seq_t seqno, unsigned rtptime, bool fi 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)) { - seq_t i; - u32_t now; - // newer than expected if (ctx->latency && seq_order(ctx->latency / ctx->frame_size, seqno - ctx->ab_write - 1)) { - // only get rtp latency-1 frames back (last one is seqno) - LOG_WARN("[%p] too many missing frames %hu seq: %hu, (W:%hu R:%hu)", ctx, seqno - ctx->ab_write - 1, seqno, ctx->ab_write, ctx->ab_read); - ctx->ab_write = seqno - ctx->latency / ctx->frame_size; - } + // this is a shitstorm, reset buffer + LOG_WARN("[%p] too many missing frames %hu seq: %hu, (W:%hu R:%hu)", ctx, seqno - ctx->ab_write - 1, seqno, ctx->ab_write, ctx->ab_read); + 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); + + // resend date is after all requests have been sent + u32_t now = gettime_ms(); + + // set expected timing of missed frames for buffer_push_packet and set last_resend date + for (seq_t i = ctx->ab_write + 1; seq_order(i, seqno); i++) { + ctx->audio_buffer[BUFIDX(i)].rtptime = rtptime - (seqno-i)*ctx->frame_size; + ctx->audio_buffer[BUFIDX(i)].last_resend = now; + } + LOG_DEBUG("[%p]: packet newer seqno:%hu rtptime:%u (W:%hu R:%hu)", ctx, seqno, rtptime, ctx->ab_write, ctx->ab_read); + } - // need to request re-send and adjust timing of gaps - rtp_request_resend(ctx, ctx->ab_write + 1, seqno-1); - for (now = gettime_ms(), i = ctx->ab_write + 1; seq_order(i, seqno); i++) { - ctx->audio_buffer[BUFIDX(i)].rtptime = rtptime - (seqno-i)*ctx->frame_size; - ctx->audio_buffer[BUFIDX(i)].last_resend = now; - } - - 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)) { @@ -524,10 +526,9 @@ static void buffer_put_packet(rtp_t *ctx, seq_t seqno, unsigned rtptime, bool fi static void buffer_push_packet(rtp_t *ctx) { abuf_t *curframe = NULL; u32_t now, playtime, hold = max((ctx->latency * 1000) / (8 * RAOP_SAMPLE_RATE), 100); - int i; // not ready to play yet - if (!ctx->playing || ctx->synchro.status != (RTP_SYNC | NTP_SYNC)) return; + if (!ctx->playing || ctx->synchro.status != (RTP_SYNC | NTP_SYNC)) return; // there is always at least one frame in the buffer do { @@ -573,10 +574,11 @@ 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); // each missing packet will be requested up to (latency_frames / 16) times - for (i = 0; seq_order(ctx->ab_read + i, ctx->ab_write); i += 16) { + for (int i = 0; seq_order(ctx->ab_read + i, ctx->ab_write); i += 16) { abuf_t *frame = ctx->audio_buffer + BUFIDX(ctx->ab_read + i); if (!frame->ready && now - frame->last_resend > RESEND_TO) { - rtp_request_resend(ctx, ctx->ab_read + i, ctx->ab_read + i); + // stop if one fails + if (!rtp_request_resend(ctx, ctx->ab_read + i, ctx->ab_read + i)) break; frame->last_resend = now; } } @@ -613,7 +615,10 @@ static void rtp_thread_func(void *arg) { FD_ZERO(&fds); for (i = 0; i < 3; i++) { FD_SET(ctx->rtp_sockets[i].sock, &fds); } - if (select(sock + 1, &fds, NULL, NULL, &timeout) <= 0) continue; + if (select(sock + 1, &fds, NULL, NULL, &timeout) <= 0) { + if (ctx->stalled++ == 30*10) ctx->cmd_cb(RAOP_STALLED); + continue; + } for (i = 0; i < 3; i++) if (FD_ISSET(ctx->rtp_sockets[i].sock, &fds)) idx = i; @@ -631,6 +636,7 @@ static void rtp_thread_func(void *arg) { } assert(plen <= MAX_PACKET); + ctx->stalled = 0; type = packet[1] & ~0x80; pktp = packet; @@ -823,6 +829,7 @@ static bool rtp_request_resend(rtp_t *ctx, seq_t first, seq_t last) { if (sizeof(req) != sendto(ctx->rtp_sockets[CONTROL].sock, req, sizeof(req), MSG_DONTWAIT, (struct sockaddr*) &ctx->rtp_host, sizeof(ctx->rtp_host))) { LOG_WARN("[%p]: SENDTO failed (%s)", ctx, strerror(errno)); + return false; } return true; diff --git a/components/squeezelite/decode_external.c b/components/squeezelite/decode_external.c index d7437526..f938130e 100644 --- a/components/squeezelite/decode_external.c +++ b/components/squeezelite/decode_external.c @@ -289,6 +289,7 @@ static bool raop_sink_cmd_handler(raop_event_t event, va_list args) raop_sync.enabled = !strcasestr(output.device, "BT"); output.next_sample_rate = output.current_sample_rate = RAOP_SAMPLE_RATE; break; + case RAOP_STALLED: case RAOP_STOP: output.external = 0; __attribute__ ((fallthrough)); @@ -492,7 +493,6 @@ void decode_restore(int external) { #if CONFIG_AIRPLAY_SINK case DECODE_RAOP: raop_disconnect(); - raop_state = RAOP_STOP; break; #endif #if CONFIG_CSPOT_SINK