浏览代码

See CHANGELOG - release

philippe44 1 年之前
父节点
当前提交
ccc7b86369

+ 6 - 0
CHANGELOG

@@ -1,3 +1,9 @@
+2024-01-16
+ - catch-up with cspot latest
+ - refactor airplay flush/first packet
+ - new libFLAC that supports multi-stream OggFlac
+ - fix output threshold
+ 
 2024-01-10
  - add OggFlac to stream metadata
  - fix OggFlac deadlock in flac callback when not enough data in streambuf

二进制
components/codecs/lib/libFLAC.a


+ 63 - 47
components/raop/rtp.c

@@ -126,11 +126,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 +142,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 +226,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 +334,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();
-
-	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);
+{  
+    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;
+
+    // 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 +361,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,26 +434,50 @@ 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 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->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);
+	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 {
-			pthread_mutex_unlock(&ctx->ab_mutex);
-			return;
+            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);         
+	}   
+
 	if (seqno == (u16_t) (ctx->ab_write+1)) {
 		// expected packet
 		abuf = ctx->audio_buffer + BUFIDX(seqno);
@@ -475,7 +491,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();
@@ -528,7 +544,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 {

+ 8 - 0
components/spotify/cspot/bell/external/civetweb/civetweb.c

@@ -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);

+ 8 - 0
components/spotify/cspot/bell/main/io/BellHTTPServer.cpp

@@ -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>();

+ 3 - 0
components/spotify/cspot/bell/main/io/include/BellHTTPServer.h

@@ -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);
 };

+ 18 - 10
components/spotify/cspot/bell/main/platform/linux/MDNSService.cpp

@@ -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;
 }

+ 14 - 5
components/spotify/cspot/bell/main/platform/win32/MDNSService.cpp

@@ -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;
 }

+ 6 - 8
components/spotify/cspot/bell/main/utilities/include/BellTask.h

@@ -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;
   }
 };

+ 1 - 1
components/spotify/cspot/include/TrackQueue.h

@@ -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);

+ 3 - 0
components/spotify/cspot/protobuf/metadata.proto

@@ -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;

+ 4 - 0
components/spotify/cspot/src/TrackQueue.cpp

@@ -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;
 }
 

+ 1 - 1
components/squeezelite/output.c

@@ -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();