| 
					
				 | 
			
			
				@@ -43,14 +43,16 @@ static bool enable_bt_sink; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 static bool enable_airplay; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 #define RAOP_OUTPUT_SIZE 	(RAOP_SAMPLE_RATE * 2 * 2 * 2 * 1.2) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-#define SYNC_NB				5 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+#define SYNC_WIN_RUN	32 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+#define SYNC_WIN_CHECK	8 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+#define SYNC_WIN_START	2 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 static raop_event_t	raop_state; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-static struct { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-	bool enabled, start; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-	s32_t error[SYNC_NB]; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-	u32_t idx, len; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+static EXT_RAM_ATTR struct { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	bool enabled; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	int sum, count, win, errors[SYNC_WIN_RUN]; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	u32_t len; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 	u32_t start_time, playtime; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 } raop_sync; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -60,6 +62,7 @@ static struct { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 static void sink_data_handler(const uint8_t *data, uint32_t len) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     size_t bytes, space; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	int wait = 5; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 		 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 	// would be better to lock output, but really, it does not matter 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 	if (!output.external) { 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -92,8 +95,12 @@ static void sink_data_handler(const uint8_t *data, uint32_t len) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 		UNLOCK_O; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 		 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 		// allow i2s to empty the buffer if needed 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-		if (len && !space) usleep(50000); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		if (len && !space && wait--) usleep(20000); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 	}	 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	if (!wait) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		LOG_WARN("Waited too long, dropping frames"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 /**************************************************************************************** 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -191,51 +198,59 @@ static bool raop_sink_cmd_handler(raop_event_t event, va_list args) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 	switch (event) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 		case RAOP_TIMING: { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 			u32_t ms, now = gettime_ms(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-			s32_t sync_nb, error = 0; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-			 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			int error; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+									 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 			if (!raop_sync.enabled || output.state < OUTPUT_RUNNING || output.frames_played_dmp < output.device_frames) break; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 			 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 			// first must make sure we started on time 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-			if (raop_sync.start) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			if (raop_sync.win == SYNC_WIN_START) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 				// how many ms have we really played 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 				ms = now - output.updated + ((u64_t) (output.frames_played_dmp - output.device_frames) * 1000) / RAOP_SAMPLE_RATE; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-				raop_sync.error[raop_sync.idx] = ms - (now - raop_sync.start_time);  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-				sync_nb = 2; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-				LOG_INFO("backend played %u, desired %u, (delta:%d)", ms, now - raop_sync.start_time, raop_sync.error[raop_sync.idx]); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				error = ms - (now - raop_sync.start_time); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				LOG_INFO("backend played %u, desired %u, (delta:%d)", ms, now - raop_sync.start_time, ms - (now - raop_sync.start_time)); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 			} else {	 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 				// in how many ms will the most recent block play  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 				ms = ((u64_t) ((_buf_used(outputbuf) - raop_sync.len) / BYTES_PER_FRAME + output.device_frames + output.frames_in_process) * 1000) / RAOP_SAMPLE_RATE - (now - output.updated); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-				raop_sync.error[raop_sync.idx] = (raop_sync.playtime - now) - ms; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-				if (abs(raop_sync.error[raop_sync.idx]) > 1000) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-					LOG_INFO("erroneous sync point %d", raop_sync.error[raop_sync.idx]); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-					raop_sync.error[raop_sync.idx] = raop_sync.error[raop_sync.idx] > 0 ? 1000 : -1000; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				error = (raop_sync.playtime - now) - ms; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				// make sure impact of erroneous point is limited, but taken into account  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				if (abs(error) > 1000) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+					LOG_INFO("limiting erroneous sync point %d", error); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+					error = (error > 0) ? SYNC_WIN_RUN : -SYNC_WIN_RUN; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 				} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-				sync_nb = SYNC_NB; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-				LOG_DEBUG("head local:%u, remote:%u (delta:%d)", ms, raop_sync.playtime - now, raop_sync.error[raop_sync.idx]); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+								 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				LOG_DEBUG("head local:%u, remote:%u (delta:%d)", ms, raop_sync.playtime - now, error); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 				LOG_DEBUG("obuf:%u, sync_len:%u, devframes:%u, inproc:%u", _buf_used(outputbuf), raop_sync.len, output.device_frames, output.frames_in_process); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 			}	 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 			 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-			// calculate the average error 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-			for (int i = 0; i < sync_nb; i++) error += raop_sync.error[i]; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-			error /= sync_nb; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-			raop_sync.idx = (raop_sync.idx + 1) % sync_nb; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			// calculate sum, error and update sliding window 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			raop_sync.errors[raop_sync.count++ % raop_sync.win] = error; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			raop_sync.sum += error; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			error = raop_sync.sum / min(raop_sync.count, raop_sync.win); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 			 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-			// need at least nb_sync measures done to exit quick mode 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-			if (raop_sync.start && !raop_sync.idx && abs(error) < 10) raop_sync.start = false; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			// move to normal mode if possible 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			if (raop_sync.win == SYNC_WIN_START && raop_sync.count >= SYNC_WIN_START && abs(error) < 10) raop_sync.win = SYNC_WIN_RUN; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			// wait till e have enough data or there is a strong deviation 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			if ((raop_sync.count >= raop_sync.win && abs(error) > 10) || (raop_sync.count >= SYNC_WIN_CHECK && abs(error) > 100)) {  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 			 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-			// correct if needed 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-			if (error < -10) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-				output.skip_frames = (abs(error) * RAOP_SAMPLE_RATE) / 1000; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-				output.state = OUTPUT_SKIP_FRAMES;					 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-				memset(raop_sync.error, 0, sizeof(raop_sync.error)); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-				LOG_INFO("skipping %u frames", output.skip_frames); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-			} else if (error > 10) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-				output.pause_frames = (abs(error) * RAOP_SAMPLE_RATE) / 1000; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-				output.state = OUTPUT_PAUSE_FRAMES; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-				memset(raop_sync.error, 0, sizeof(raop_sync.error)); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-				LOG_INFO("pausing for %u frames", output.pause_frames); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-			} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-				 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				// correct if needed 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				if (error < 0) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+					output.skip_frames = -(error * RAOP_SAMPLE_RATE) / 1000; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+					output.state = OUTPUT_SKIP_FRAMES;					 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+					LOG_INFO("skipping %u frames", output.skip_frames); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				} else { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+					output.pause_frames = (error * RAOP_SAMPLE_RATE) / 1000; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+					output.state = OUTPUT_PAUSE_FRAMES; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+					LOG_INFO("pausing for %u frames", output.pause_frames); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				// reset sliding window		 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				raop_sync.sum = raop_sync.count = 0; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+				memset(raop_sync.errors, 0, sizeof(raop_sync.errors)); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+											 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			}	 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 			break; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 		} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 		case RAOP_SETUP: 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -250,9 +265,9 @@ static bool raop_sink_cmd_handler(raop_event_t event, va_list args) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 		case RAOP_STREAM: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 			LOG_INFO("Stream", NULL); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 			raop_state = event; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-			memset(raop_sync.error, 0, sizeof(raop_sync.error)); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-			raop_sync.idx = 0; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-			raop_sync.start = true;		 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			raop_sync.win = SYNC_WIN_START; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			raop_sync.sum = raop_sync.count = 0 ; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+			memset(raop_sync.errors, 0, sizeof(raop_sync.errors)); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 			raop_sync.enabled = !strcasestr(output.device, "BT"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 			output.next_sample_rate = output.current_sample_rate = RAOP_SAMPLE_RATE; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 			break; 
			 |