Fix a bug causing problems in CYGWIN 64, fix a bug causing excessive resend requests, fix two possible lock problems, tidy up some backend code, quieten some debug messages, start using pthread_cancel, bring level with 3.2d55.
This commit is contained in:
+1
-1
@@ -121,7 +121,7 @@ static void deinit(void) {
|
||||
static void start(__attribute__((unused)) int sample_rate,
|
||||
__attribute__((unused)) int sample_format) {}
|
||||
|
||||
static void play(short buf[], int samples) { ao_play(dev, (char *)buf, samples * 4); }
|
||||
static void play(void *buf, int samples) { ao_play(dev, buf, samples * 4); }
|
||||
|
||||
static void stop(void) {}
|
||||
|
||||
|
||||
+1
-1
@@ -48,7 +48,7 @@ static void start(int sample_rate, __attribute__((unused)) int sample_format) {
|
||||
debug(1, "dummy audio output started at Fs=%d Hz\n", sample_rate);
|
||||
}
|
||||
|
||||
static void play(__attribute__((unused)) short buf[], __attribute__((unused)) int samples) {}
|
||||
static void play(__attribute__((unused)) void *buf, __attribute__((unused)) int samples) {}
|
||||
|
||||
static void stop(void) { debug(1, "dummy audio stopped\n"); }
|
||||
|
||||
|
||||
+5
-6
@@ -198,22 +198,21 @@ static void start(__attribute__((unused)) int sample_rate,
|
||||
pa_threaded_mainloop_unlock(mainloop);
|
||||
}
|
||||
|
||||
static void play(short buf[], int samples) {
|
||||
static void play(void *buf, int samples) {
|
||||
// debug(1,"pa_play of %d samples.",samples);
|
||||
char *bbuf = (char *)buf;
|
||||
// copy the samples into the queue
|
||||
size_t bytes_to_transfer = samples * 2 * 2;
|
||||
size_t space_to_end_of_buffer = audio_umb - audio_eoq;
|
||||
if (space_to_end_of_buffer >= bytes_to_transfer) {
|
||||
memcpy(audio_eoq, bbuf, bytes_to_transfer);
|
||||
memcpy(audio_eoq, buf, bytes_to_transfer);
|
||||
audio_occupancy += bytes_to_transfer;
|
||||
pthread_mutex_lock(&buffer_mutex);
|
||||
audio_eoq += bytes_to_transfer;
|
||||
pthread_mutex_unlock(&buffer_mutex);
|
||||
} else {
|
||||
memcpy(audio_eoq, bbuf, space_to_end_of_buffer);
|
||||
bbuf += space_to_end_of_buffer;
|
||||
memcpy(audio_lmb, bbuf, bytes_to_transfer - space_to_end_of_buffer);
|
||||
memcpy(audio_eoq, buf, space_to_end_of_buffer);
|
||||
buf += space_to_end_of_buffer;
|
||||
memcpy(audio_lmb, buf, bytes_to_transfer - space_to_end_of_buffer);
|
||||
pthread_mutex_lock(&buffer_mutex);
|
||||
audio_occupancy += bytes_to_transfer;
|
||||
pthread_mutex_unlock(&buffer_mutex);
|
||||
|
||||
+2
-2
@@ -33,7 +33,7 @@ static int init(int, char **);
|
||||
static void onmove_cb(void *, int);
|
||||
static void deinit(void);
|
||||
static void start(int, int);
|
||||
static void play(short[], int);
|
||||
static void play(void *, int);
|
||||
static void stop(void);
|
||||
static void onmove_cb(void *, int);
|
||||
static int delay(long *);
|
||||
@@ -222,7 +222,7 @@ static void start(__attribute__((unused)) int sample_rate,
|
||||
pthread_mutex_unlock(&sndio_mutex);
|
||||
}
|
||||
|
||||
static void play(short buf[], int frames) {
|
||||
static void play(void *buf, int frames) {
|
||||
if (frames > 0) {
|
||||
pthread_mutex_lock(&sndio_mutex);
|
||||
written += sio_write(hdl, buf, frames * framesize);
|
||||
|
||||
+2
-2
@@ -167,7 +167,7 @@ static void start(int sample_rate, int sample_format) {
|
||||
debug(1, "libsoundio output started\n");
|
||||
}
|
||||
|
||||
static void play(short buf[], int samples) {
|
||||
static void play(void *buf, int samples) {
|
||||
// int err;
|
||||
int free_bytes = soundio_ring_buffer_free_count(ring_buffer);
|
||||
int written_bytes = 0;
|
||||
@@ -181,7 +181,7 @@ static void play(short buf[], int samples) {
|
||||
free_bytes);
|
||||
|
||||
if (write_bytes) {
|
||||
memcpy(write_ptr, (char *)buf, write_bytes);
|
||||
memcpy(write_ptr, buf, write_bytes);
|
||||
written_bytes += write_bytes;
|
||||
soundio_ring_buffer_advance_write_ptr(ring_buffer, write_bytes);
|
||||
debug(3, "[<<---] Written to buffer : %d\n", written_bytes);
|
||||
|
||||
+1
-1
@@ -43,7 +43,7 @@ static void start(__attribute__((unused)) int sample_rate,
|
||||
fd = STDOUT_FILENO;
|
||||
}
|
||||
|
||||
static void play(short buf[], int samples) {
|
||||
static void play(void *buf, int samples) {
|
||||
char errorstring[1024];
|
||||
int warned = 0;
|
||||
int rc = write(fd, buf, samples * 4);
|
||||
|
||||
@@ -106,14 +106,14 @@ void mdns_unregister(void) {
|
||||
|
||||
void *mdns_dacp_monitor(char *dacp_id) {
|
||||
void *reply = NULL;
|
||||
if ((dacp_id!=NULL) && (*dacp_id!='\0')) {
|
||||
if ((config.mdns) && (config.mdns->mdns_dacp_monitor)) {
|
||||
reply = config.mdns->mdns_dacp_monitor(dacp_id);
|
||||
if (reply == NULL) {
|
||||
debug(1, "Error starting a DACP monitor.");
|
||||
}
|
||||
} else
|
||||
debug(1, "Can't start a DACP monitor -- none registered.");
|
||||
if ((dacp_id != NULL) && (*dacp_id != '\0')) {
|
||||
if ((config.mdns) && (config.mdns->mdns_dacp_monitor)) {
|
||||
reply = config.mdns->mdns_dacp_monitor(dacp_id);
|
||||
if (reply == NULL) {
|
||||
debug(1, "Error starting a DACP monitor.");
|
||||
}
|
||||
} else
|
||||
debug(3, "Can't start a DACP monitor -- none registered.");
|
||||
}
|
||||
return reply;
|
||||
}
|
||||
@@ -122,7 +122,7 @@ void mdns_dacp_dont_monitor(void *userdata) {
|
||||
if ((config.mdns) && (config.mdns->mdns_dacp_dont_monitor)) {
|
||||
config.mdns->mdns_dacp_dont_monitor(userdata);
|
||||
} else
|
||||
debug(1, "Can't stop a DACP monitor -- none registered.");
|
||||
debug(3, "Can't stop a DACP monitor -- none registered.");
|
||||
}
|
||||
void mdns_ls_backends(void) {
|
||||
mdns_backend **b = NULL;
|
||||
|
||||
+10
-2
@@ -364,9 +364,17 @@ static int avahi_register(char *srvname, int srvport) {
|
||||
|
||||
static void avahi_unregister(void) {
|
||||
debug(1, "avahi: avahi_unregister.");
|
||||
if (tpoll)
|
||||
if (tpoll) {
|
||||
avahi_threaded_poll_stop(tpoll);
|
||||
tpoll = NULL;
|
||||
|
||||
if (client) {
|
||||
avahi_client_free(client);
|
||||
client = NULL;
|
||||
};
|
||||
|
||||
avahi_threaded_poll_free(tpoll);
|
||||
tpoll = NULL;
|
||||
}
|
||||
|
||||
if (service_name)
|
||||
free(service_name);
|
||||
|
||||
@@ -106,6 +106,8 @@
|
||||
// static abuf_t audio_buffer[BUFFER_FRAMES];
|
||||
#define BUFIDX(seqno) ((seq_t)(seqno) % BUFFER_FRAMES)
|
||||
|
||||
void do_flush(int64_t timestamp, rtsp_conn_info *conn);
|
||||
|
||||
// make timestamps and seqnos definitely monotonic
|
||||
|
||||
// add an epoch to the timestamp. The monotonic timestamp guaranteed to start between 2^32 and 2^33
|
||||
@@ -464,147 +466,176 @@ static void free_audio_buffers(rtsp_conn_info *conn) {
|
||||
|
||||
void player_put_packet(seq_t seqno, uint32_t actual_timestamp, int64_t timestamp, uint8_t *data,
|
||||
int len, rtsp_conn_info *conn) {
|
||||
if (pthread_rwlock_tryrdlock(&conn->player_thread_lock) == 0) {
|
||||
if (conn->player_thread != NULL) {
|
||||
|
||||
// all timestamps are done at the output rate
|
||||
// the "actual_timestamp" is the one that comes in the packet, and is carried over for debugging
|
||||
// and checking only.
|
||||
// all timestamps are done at the output rate
|
||||
// the "actual_timestamp" is the one that comes in the packet, and is carried over for
|
||||
// debugging
|
||||
// and checking only.
|
||||
|
||||
int64_t ltimestamp = timestamp * conn->output_sample_ratio;
|
||||
int64_t ltimestamp = timestamp * conn->output_sample_ratio;
|
||||
|
||||
// ignore a request to flush that has been made before the first packet...
|
||||
if (conn->packet_count == 0) {
|
||||
pthread_mutex_lock(&conn->flush_mutex);
|
||||
conn->flush_requested = 0;
|
||||
conn->flush_rtp_timestamp = 0;
|
||||
pthread_mutex_unlock(&conn->flush_mutex);
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&conn->ab_mutex);
|
||||
conn->packet_count++;
|
||||
conn->time_of_last_audio_packet = get_absolute_time_in_fp();
|
||||
if (conn->connection_state_to_output) { // if we are supposed to be processing these packets
|
||||
|
||||
// if (flush_rtp_timestamp != 0)
|
||||
// debug(1,"Flush_rtp_timestamp is %u",flush_rtp_timestamp);
|
||||
|
||||
if ((conn->flush_rtp_timestamp != 0) && (ltimestamp <= conn->flush_rtp_timestamp)) {
|
||||
debug(3,
|
||||
"Dropping flushed packet in player_put_packet, seqno %u, timestamp %lld, flushing to "
|
||||
"timestamp: %lld.",
|
||||
seqno, ltimestamp, conn->flush_rtp_timestamp);
|
||||
} else {
|
||||
if ((conn->flush_rtp_timestamp != 0x0) &&
|
||||
(ltimestamp > conn->flush_rtp_timestamp)) // if we have gone past the flush boundary time
|
||||
conn->flush_rtp_timestamp = 0x0;
|
||||
|
||||
abuf_t *abuf = 0;
|
||||
|
||||
if (!conn->ab_synced) {
|
||||
debug(3, "syncing to seqno %u.", seqno);
|
||||
conn->ab_write = seqno;
|
||||
conn->ab_read = seqno;
|
||||
conn->ab_synced = 1;
|
||||
// ignore a request to flush that has been made before the first packet...
|
||||
if (conn->packet_count == 0) {
|
||||
pthread_mutex_lock(&conn->flush_mutex);
|
||||
conn->flush_requested = 0;
|
||||
conn->flush_rtp_timestamp = 0;
|
||||
pthread_mutex_unlock(&conn->flush_mutex);
|
||||
}
|
||||
|
||||
// here, we should check for missing frames
|
||||
if (!conn->ab_buffering) {
|
||||
int j;
|
||||
for (j = 1; j <= 8; j++) {
|
||||
// check j times, after a short period of has elapsed, assuming 352 frames per packet
|
||||
int back_step = (((250 * 44100) / 352) / 1000) * (j); // approx 250 ms intervals
|
||||
int k;
|
||||
for (k = -2; k <= 2; k++) {
|
||||
if (back_step <
|
||||
seq_diff(conn->ab_read, conn->ab_write,
|
||||
conn->ab_read)) { // if it's within the range of frames in use...
|
||||
int item_to_check = (conn->ab_write - back_step) & 0xffff;
|
||||
seq_t next = item_to_check;
|
||||
abuf_t *check_buf = conn->audio_buffer + BUFIDX(next);
|
||||
if ((!check_buf->ready) &&
|
||||
(check_buf->resend_level <
|
||||
j)) { // prevent multiple requests from the same level of lookback
|
||||
check_buf->resend_level = j;
|
||||
if (config.disable_resend_requests == 0) {
|
||||
rtp_request_resend(next, 1, conn);
|
||||
if (j >= 6)
|
||||
debug(2, "Resend request level #%d for packet %u in range %u to %u.", j, next,
|
||||
conn->ab_read, conn->ab_write);
|
||||
conn->resend_requests++;
|
||||
pthread_mutex_lock(&conn->ab_mutex);
|
||||
conn->packet_count++;
|
||||
conn->time_of_last_audio_packet = get_absolute_time_in_fp();
|
||||
if (conn->connection_state_to_output) { // if we are supposed to be processing these packets
|
||||
|
||||
// if (flush_rtp_timestamp != 0)
|
||||
// debug(1,"Flush_rtp_timestamp is %u",flush_rtp_timestamp);
|
||||
|
||||
if ((conn->flush_rtp_timestamp != 0) && (ltimestamp <= conn->flush_rtp_timestamp)) {
|
||||
debug(
|
||||
3,
|
||||
"Dropping flushed packet in player_put_packet, seqno %u, timestamp %lld, flushing to "
|
||||
"timestamp: %lld.",
|
||||
seqno, ltimestamp, conn->flush_rtp_timestamp);
|
||||
} else {
|
||||
if ((conn->flush_rtp_timestamp != 0x0) &&
|
||||
(ltimestamp >
|
||||
conn->flush_rtp_timestamp)) // if we have gone past the flush boundary time
|
||||
conn->flush_rtp_timestamp = 0x0;
|
||||
|
||||
abuf_t *abuf = 0;
|
||||
|
||||
if (!conn->ab_synced) {
|
||||
debug(3, "syncing to seqno %u.", seqno);
|
||||
conn->ab_write = seqno;
|
||||
conn->ab_read = seqno;
|
||||
conn->ab_synced = 1;
|
||||
}
|
||||
|
||||
// here, we should check for missing frames
|
||||
int resend_interval = (((250 * 44100) / 352) / 1000); // approximately 250 ms intervals
|
||||
const int number_of_resend_attempts = 8;
|
||||
int latency_based_resend_interval =
|
||||
(conn->latency) / (number_of_resend_attempts * conn->max_frames_per_packet);
|
||||
if (latency_based_resend_interval > resend_interval)
|
||||
resend_interval = latency_based_resend_interval;
|
||||
|
||||
if (conn->resend_interval != resend_interval) {
|
||||
debug(2, "Resend interval for latency of %" PRId64 " frames is %d frames.",
|
||||
conn->latency, resend_interval);
|
||||
conn->resend_interval = resend_interval;
|
||||
}
|
||||
if (!conn->ab_buffering) {
|
||||
int j;
|
||||
for (j = 1; j <= number_of_resend_attempts; j++) {
|
||||
// check j times, after a short period of has elapsed, assuming 352 frames per packet
|
||||
|
||||
int back_step = resend_interval * j;
|
||||
|
||||
int32_t sd = seq_diff(conn->ab_read, conn->ab_write, conn->ab_read);
|
||||
int k;
|
||||
for (k = -2; k <= 2; k++) {
|
||||
if ((back_step + k) < sd) { // if it's within the range of frames in use...
|
||||
int item_to_check = (conn->ab_write - (back_step + k)) & 0xffff;
|
||||
seq_t next = item_to_check;
|
||||
abuf_t *check_buf = conn->audio_buffer + BUFIDX(next);
|
||||
if ((!check_buf->ready) &&
|
||||
(check_buf->resend_level <
|
||||
j)) { // prevent multiple requests from the same level of lookback
|
||||
check_buf->resend_level = j;
|
||||
if (config.disable_resend_requests == 0) {
|
||||
rtp_request_resend(next, 1, conn);
|
||||
if ((back_step + k + resend_interval) >= sd)
|
||||
debug(2, "Last-ditch (#%d) resend request for packet %u in range %u to %u. "
|
||||
"Looking back %d packets.",
|
||||
j, next, conn->ab_read, conn->ab_write, back_step + k);
|
||||
conn->resend_requests++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (conn->ab_write == seqno) { // expected packet
|
||||
abuf = conn->audio_buffer + BUFIDX(seqno);
|
||||
conn->ab_write = SUCCESSOR(seqno);
|
||||
} else if (seq_order(conn->ab_write, seqno, conn->ab_read)) { // newer than expected
|
||||
// if (ORDINATE(seqno)>(BUFFER_FRAMES*7)/8)
|
||||
// debug(1,"An interval of %u frames has opened, with ab_read: %u, ab_write: %u and
|
||||
// seqno:
|
||||
// %u.",seq_diff(ab_read,seqno),ab_read,ab_write,seqno);
|
||||
int32_t gap = seq_diff(conn->ab_write, seqno, conn->ab_read);
|
||||
if (gap <= 0)
|
||||
debug(1, "Unexpected gap size: %d.", gap);
|
||||
int i;
|
||||
for (i = 0; i < gap; i++) {
|
||||
abuf = conn->audio_buffer + BUFIDX(seq_sum(conn->ab_write, i));
|
||||
abuf->ready = 0; // to be sure, to be sure
|
||||
abuf->resend_level = 0;
|
||||
abuf->timestamp = 0;
|
||||
abuf->given_timestamp = 0;
|
||||
abuf->sequence_number = 0;
|
||||
}
|
||||
// debug(1,"N %d s %u.",seq_diff(ab_write,PREDECESSOR(seqno))+1,ab_write);
|
||||
abuf = conn->audio_buffer + BUFIDX(seqno);
|
||||
// rtp_request_resend(ab_write, gap);
|
||||
// resend_requests++;
|
||||
conn->ab_write = SUCCESSOR(seqno);
|
||||
} else if (seq_order(conn->ab_read, seqno, conn->ab_read)) { // late but not yet played
|
||||
conn->late_packets++;
|
||||
abuf = conn->audio_buffer + BUFIDX(seqno);
|
||||
/*
|
||||
if (abuf->ready)
|
||||
debug(1,"Late apparently duplicate packet received that is %d packets
|
||||
late.",seq_diff(seqno, conn->ab_write, conn->ab_read));
|
||||
else
|
||||
debug(1,"Late packet received that is %d packets late.",seq_diff(seqno,
|
||||
conn->ab_write, conn->ab_read));
|
||||
*/
|
||||
} else { // too late.
|
||||
|
||||
// debug(1,"Too late packet received that is %d packets late.",seq_diff(seqno,
|
||||
// conn->ab_write, conn->ab_read));
|
||||
conn->too_late_packets++;
|
||||
}
|
||||
// pthread_mutex_unlock(&ab_mutex);
|
||||
|
||||
if (abuf) {
|
||||
int datalen = conn->max_frames_per_packet;
|
||||
if (alac_decode(abuf->data, &datalen, data, len, conn) == 0) {
|
||||
abuf->ready = 1;
|
||||
abuf->length = datalen;
|
||||
abuf->timestamp = ltimestamp;
|
||||
abuf->given_timestamp = actual_timestamp;
|
||||
abuf->sequence_number = seqno;
|
||||
} else {
|
||||
debug(1, "Bad audio packet detected and discarded.");
|
||||
abuf->ready = 0;
|
||||
abuf->resend_level = 0;
|
||||
abuf->timestamp = 0;
|
||||
abuf->given_timestamp = 0;
|
||||
abuf->sequence_number = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// pthread_mutex_lock(&ab_mutex);
|
||||
}
|
||||
int rc = pthread_cond_signal(&conn->flowcontrol);
|
||||
if (rc)
|
||||
debug(1, "Error signalling flowcontrol.");
|
||||
}
|
||||
|
||||
if (conn->ab_write == seqno) { // expected packet
|
||||
abuf = conn->audio_buffer + BUFIDX(seqno);
|
||||
conn->ab_write = SUCCESSOR(seqno);
|
||||
} else if (seq_order(conn->ab_write, seqno, conn->ab_read)) { // newer than expected
|
||||
// if (ORDINATE(seqno)>(BUFFER_FRAMES*7)/8)
|
||||
// debug(1,"An interval of %u frames has opened, with ab_read: %u, ab_write: %u and seqno:
|
||||
// %u.",seq_diff(ab_read,seqno),ab_read,ab_write,seqno);
|
||||
int32_t gap = seq_diff(conn->ab_write, seqno, conn->ab_read);
|
||||
if (gap <= 0)
|
||||
debug(1, "Unexpected gap size: %d.", gap);
|
||||
int i;
|
||||
for (i = 0; i < gap; i++) {
|
||||
abuf = conn->audio_buffer + BUFIDX(seq_sum(conn->ab_write, i));
|
||||
abuf->ready = 0; // to be sure, to be sure
|
||||
abuf->resend_level = 0;
|
||||
abuf->timestamp = 0;
|
||||
abuf->given_timestamp = 0;
|
||||
abuf->sequence_number = 0;
|
||||
}
|
||||
// debug(1,"N %d s %u.",seq_diff(ab_write,PREDECESSOR(seqno))+1,ab_write);
|
||||
abuf = conn->audio_buffer + BUFIDX(seqno);
|
||||
// rtp_request_resend(ab_write, gap);
|
||||
// resend_requests++;
|
||||
conn->ab_write = SUCCESSOR(seqno);
|
||||
} else if (seq_order(conn->ab_read, seqno, conn->ab_read)) { // late but not yet played
|
||||
conn->late_packets++;
|
||||
abuf = conn->audio_buffer + BUFIDX(seqno);
|
||||
/*
|
||||
if (abuf->ready)
|
||||
debug(1,"Late apparently duplicate packet received that is %d packets
|
||||
late.",seq_diff(seqno, conn->ab_write, conn->ab_read));
|
||||
else
|
||||
debug(1,"Late packet received that is %d packets late.",seq_diff(seqno,
|
||||
conn->ab_write, conn->ab_read));
|
||||
*/
|
||||
} else { // too late.
|
||||
|
||||
// debug(1,"Too late packet received that is %d packets late.",seq_diff(seqno,
|
||||
// conn->ab_write, conn->ab_read));
|
||||
conn->too_late_packets++;
|
||||
}
|
||||
// pthread_mutex_unlock(&ab_mutex);
|
||||
|
||||
if (abuf) {
|
||||
int datalen = conn->max_frames_per_packet;
|
||||
if (alac_decode(abuf->data, &datalen, data, len, conn) == 0) {
|
||||
abuf->ready = 1;
|
||||
abuf->length = datalen;
|
||||
abuf->timestamp = ltimestamp;
|
||||
abuf->given_timestamp = actual_timestamp;
|
||||
abuf->sequence_number = seqno;
|
||||
} else {
|
||||
debug(1, "Bad audio packet detected and discarded.");
|
||||
abuf->ready = 0;
|
||||
abuf->resend_level = 0;
|
||||
abuf->timestamp = 0;
|
||||
abuf->given_timestamp = 0;
|
||||
abuf->sequence_number = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// pthread_mutex_lock(&ab_mutex);
|
||||
pthread_mutex_unlock(&conn->ab_mutex);
|
||||
} else {
|
||||
debug(
|
||||
1,
|
||||
"No player thread while adding a player_put_packet calle was made -- packet discarded.");
|
||||
}
|
||||
int rc = pthread_cond_signal(&conn->flowcontrol);
|
||||
if (rc)
|
||||
debug(1, "Error signalling flowcontrol.");
|
||||
pthread_rwlock_unlock(&conn->player_thread_lock);
|
||||
} else {
|
||||
debug(1, "player_put_packet discarded packet %d because the player thread was locked.", seqno);
|
||||
}
|
||||
pthread_mutex_unlock(&conn->ab_mutex);
|
||||
}
|
||||
|
||||
int32_t rand_in_range(int32_t exclusive_range_limit) {
|
||||
@@ -1200,15 +1231,13 @@ static abuf_t *buffer_get_frame(rtsp_conn_info *conn) {
|
||||
} while (wait);
|
||||
|
||||
if (conn->player_thread_please_stop) {
|
||||
debug(3, "buffer_get_frame exiting due to thread stop request.");
|
||||
pthread_mutex_unlock(&conn->ab_mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// seq_t read = conn->ab_read;
|
||||
|
||||
// check if t+8, t+16, t+32, t+64, t+128, ... (buffer_start_fill / 2)
|
||||
// packets have arrived... last-chance resend
|
||||
|
||||
if (!curframe->ready) {
|
||||
// debug(1, "Supplying a silent frame for frame %u", read);
|
||||
conn->missing_packets++;
|
||||
@@ -1395,26 +1424,12 @@ typedef struct stats { // statistics for running averages
|
||||
} stats_t;
|
||||
|
||||
static void *player_thread_func(void *arg) {
|
||||
// note, the thread will be started up with the player_thread_lock locked. You must release it
|
||||
// quickly.
|
||||
|
||||
rtsp_conn_info *conn = (rtsp_conn_info *)arg;
|
||||
|
||||
conn->please_stop = 0;
|
||||
conn->packet_count = 0;
|
||||
conn->previous_random_number = 0;
|
||||
conn->input_bytes_per_frame = 4;
|
||||
conn->player_thread_please_stop = 0;
|
||||
conn->decoder_in_use = 0;
|
||||
conn->ab_buffering = 1;
|
||||
conn->ab_synced = 0;
|
||||
conn->first_packet_timestamp = 0;
|
||||
conn->flush_requested = 0;
|
||||
conn->fix_volume = 0x10000;
|
||||
|
||||
if (conn->latency == 0) {
|
||||
debug(3, "No latency has (yet) been specified. Setting 88,200 (2 seconds) frames "
|
||||
"as a default.");
|
||||
conn->latency = 88200;
|
||||
}
|
||||
pthread_rwlock_wrlock(&conn->player_thread_lock);
|
||||
|
||||
int rc = pthread_mutex_init(&conn->ab_mutex, NULL);
|
||||
if (rc)
|
||||
@@ -1437,6 +1452,28 @@ static void *player_thread_func(void *arg) {
|
||||
#endif
|
||||
if (rc)
|
||||
debug(1, "Error initialising flowcontrol condition variable.");
|
||||
|
||||
pthread_rwlock_unlock(&conn->player_thread_lock);
|
||||
|
||||
// it's safe now
|
||||
|
||||
conn->packet_count = 0;
|
||||
conn->previous_random_number = 0;
|
||||
conn->input_bytes_per_frame = 4;
|
||||
conn->player_thread_please_stop = 0;
|
||||
conn->decoder_in_use = 0;
|
||||
conn->ab_buffering = 1;
|
||||
conn->ab_synced = 0;
|
||||
conn->first_packet_timestamp = 0;
|
||||
conn->flush_requested = 0;
|
||||
conn->fix_volume = 0x10000;
|
||||
|
||||
if (conn->latency == 0) {
|
||||
debug(3, "No latency has (yet) been specified. Setting 88,200 (2 seconds) frames "
|
||||
"as a default.");
|
||||
conn->latency = 88200;
|
||||
}
|
||||
|
||||
config.output->start(config.output_rate, config.output_format);
|
||||
|
||||
init_decoder((int32_t *)&conn->stream.fmtp,
|
||||
@@ -1725,8 +1762,12 @@ static void *player_thread_func(void *arg) {
|
||||
free(silence);
|
||||
}
|
||||
} else if (frames_to_drop) {
|
||||
if (frames_to_drop > 3 * config.output_rate)
|
||||
warn("Very large number of frames to drop: %" PRId64 ".", frames_to_drop);
|
||||
if (frames_to_drop > 3 * config.output_rate) {
|
||||
warn("Shome mhistake shurely: very large number of frames to drop: %" PRId64
|
||||
" -- setting it to %" PRId64 ".",
|
||||
frames_to_drop, 3 * config.output_rate);
|
||||
frames_to_drop = 3 * config.output_rate;
|
||||
}
|
||||
debug(3, "%" PRId64 " frames to drop.", frames_to_drop);
|
||||
frames_to_drop -= inframe->length;
|
||||
if (frames_to_drop < 0)
|
||||
@@ -2271,6 +2312,8 @@ static void *player_thread_func(void *arg) {
|
||||
}
|
||||
}
|
||||
|
||||
debug(3, "Connection %d: player thread main loop exit.", conn->connection_number);
|
||||
|
||||
if (config.statistics_requested) {
|
||||
int rawSeconds = (int)difftime(time(NULL), playstart);
|
||||
int elapsedHours = rawSeconds / 3600;
|
||||
@@ -2286,25 +2329,26 @@ static void *player_thread_func(void *arg) {
|
||||
mdns_dacp_dont_monitor(conn->dapo_private_storage);
|
||||
#endif
|
||||
|
||||
debug(3, "Requesting output device to stop.");
|
||||
|
||||
if (config.output->stop)
|
||||
config.output->stop();
|
||||
|
||||
debug(2, "Shut down audio, control and timing threads");
|
||||
conn->please_stop = 1;
|
||||
pthread_kill(rtp_audio_thread, SIGUSR1);
|
||||
pthread_kill(rtp_control_thread, SIGUSR1);
|
||||
pthread_kill(rtp_timing_thread, SIGUSR1);
|
||||
debug(2, "Cancelling timing, control and audio threads");
|
||||
pthread_cancel(rtp_timing_thread);
|
||||
pthread_cancel(rtp_control_thread);
|
||||
pthread_cancel(rtp_audio_thread);
|
||||
debug(2, "Joining terminated threads.");
|
||||
pthread_join(rtp_timing_thread, NULL);
|
||||
debug(2, "timing thread joined");
|
||||
pthread_join(rtp_audio_thread, NULL);
|
||||
debug(2, "audio thread joined");
|
||||
debug(2, "Timing thread terminated.");
|
||||
pthread_join(rtp_control_thread, NULL);
|
||||
debug(2, "control thread joined");
|
||||
debug(2, "Control thread terminated.");
|
||||
pthread_join(rtp_audio_thread, NULL);
|
||||
debug(2, "Audio thread terminated.");
|
||||
clear_reference_timestamp(conn);
|
||||
conn->rtp_running = 0;
|
||||
|
||||
// usleep(100000); // allow this time to (?) allow the alsa subsystem to finish cleaning up after
|
||||
// itself. 50 ms seems too short
|
||||
debug(2, "Freeing audio buffers and decoders.");
|
||||
|
||||
free_audio_buffers(conn);
|
||||
terminate_decoders(conn);
|
||||
@@ -2322,7 +2366,7 @@ static void *player_thread_func(void *arg) {
|
||||
if (rc)
|
||||
debug(1, "Error destroying vol_mutex variable.");
|
||||
|
||||
debug(2, "Player thread exit on RTSP conversation thread %d.", conn->connection_number);
|
||||
debug(2, "Connection %d: player thread terminated.", conn->connection_number);
|
||||
if (conn->dacp_id) {
|
||||
free(conn->dacp_id);
|
||||
conn->dacp_id = NULL;
|
||||
@@ -2333,7 +2377,7 @@ static void *player_thread_func(void *arg) {
|
||||
free(tbuf);
|
||||
if (sbuf)
|
||||
free(sbuf);
|
||||
return 0;
|
||||
pthread_exit(NULL);
|
||||
}
|
||||
|
||||
// takes the volume as specified by the airplay protocol
|
||||
@@ -2571,7 +2615,7 @@ void player_volume(double airplay_volume, rtsp_conn_info *conn) {
|
||||
player_volume_without_notification(airplay_volume, conn);
|
||||
}
|
||||
|
||||
void player_flush(int64_t timestamp, rtsp_conn_info *conn) {
|
||||
void do_flush(int64_t timestamp, rtsp_conn_info *conn) {
|
||||
debug(3, "Flush requested up to %u. It seems as if 0 is special.", timestamp);
|
||||
pthread_mutex_lock(&conn->flush_mutex);
|
||||
conn->flush_requested = 1;
|
||||
@@ -2588,10 +2632,24 @@ void player_flush(int64_t timestamp, rtsp_conn_info *conn) {
|
||||
send_ssnc_metadata('pfls', NULL, 0, 1);
|
||||
}
|
||||
#endif
|
||||
debug(3, "Flush request made.");
|
||||
}
|
||||
|
||||
void player_flush(int64_t timestamp, rtsp_conn_info *conn) {
|
||||
debug(3, "player_flush");
|
||||
if (pthread_rwlock_tryrdlock(&conn->player_thread_lock) == 0) {
|
||||
if (conn->player_thread != NULL)
|
||||
do_flush(timestamp, conn);
|
||||
else
|
||||
debug(1, "Flush requested when player thread is gone.");
|
||||
pthread_rwlock_unlock(&conn->player_thread_lock);
|
||||
} else {
|
||||
debug(3, "Can't acquire a read lock for a flush -- ignored.");
|
||||
}
|
||||
}
|
||||
|
||||
int player_play(rtsp_conn_info *conn) {
|
||||
// need to use conn in place of streram below. Need to put the stream as a parameter to he
|
||||
// need to use conn in place of stream below. Need to put the stream as a parameter to he
|
||||
if (conn->player_thread != NULL)
|
||||
die("Trying to create a second player thread for this RTSP session");
|
||||
if (config.buffer_start_fill > BUFFER_FRAMES)
|
||||
@@ -2612,25 +2670,33 @@ int player_play(rtsp_conn_info *conn) {
|
||||
int rc = pthread_attr_setstacksize(&tattr, size);
|
||||
if (rc)
|
||||
debug(1, "Error setting stack size for player_thread: %s", strerror(errno));
|
||||
|
||||
// hack alert -- the player thread itself releases the player_thread_lock rwlock as soon as it's
|
||||
// finished initialising.
|
||||
pthread_create(pt, &tattr, player_thread_func, (void *)conn);
|
||||
pthread_attr_destroy(&tattr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void player_stop(rtsp_conn_info *conn) {
|
||||
int player_stop(rtsp_conn_info *conn) {
|
||||
pthread_rwlock_wrlock(&conn->player_thread_lock);
|
||||
if (conn->player_thread) {
|
||||
conn->player_thread_please_stop = 1;
|
||||
pthread_cond_signal(&conn->flowcontrol); // tell it to give up
|
||||
pthread_kill(*conn->player_thread, SIGUSR1);
|
||||
pthread_join(*conn->player_thread, NULL);
|
||||
free(conn->player_thread);
|
||||
conn->player_thread = NULL;
|
||||
pthread_rwlock_unlock(&conn->player_thread_lock);
|
||||
#ifdef CONFIG_METADATA
|
||||
debug(2, "pend");
|
||||
send_ssnc_metadata('pend', NULL, 0, 1);
|
||||
#endif
|
||||
command_stop();
|
||||
free(conn->player_thread);
|
||||
conn->player_thread = NULL;
|
||||
return 0;
|
||||
} else {
|
||||
pthread_rwlock_unlock(&conn->player_thread_lock);
|
||||
debug(3, "player thread of RTSP conversation %d is already deleted.", conn->connection_number);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,17 @@ typedef struct audio_buffer_entry { // decoded audio packets
|
||||
} abuf_t;
|
||||
|
||||
// default buffer size
|
||||
// needs to be a power of 2 because of the way BUFIDX(seqno) works
|
||||
// This eeds to be a power of 2 because of the way BUFIDX(seqno) works.
|
||||
// 512 is the minimum for normal operation -- it gives 512*352/44100 or just over 4 seconds of
|
||||
// buffers.
|
||||
// For at least 10 seconds, you need to go to 2048.
|
||||
// Resend requests will be spaced out evenly in the latency period, subject to a minimum interval of
|
||||
// about 0.25 seconds.
|
||||
// Each buffer occupies 352*4 bytes plus about, say, 64 bytes of overhead in various places, say
|
||||
// rougly 1,500 bytes per buffer.
|
||||
// Thus, 2048 buffers will occupy about 3 megabytes -- no big deal in a normal machine but maybe a
|
||||
// problem in an embedded device.
|
||||
|
||||
#define BUFFER_FRAMES 1024
|
||||
|
||||
typedef struct {
|
||||
@@ -57,6 +67,7 @@ typedef struct {
|
||||
|
||||
typedef struct {
|
||||
int connection_number; // for debug ID purposes, nothing else...
|
||||
int resend_interval; // this is really just for debugging
|
||||
int AirPlayVersion; // zero if not an AirPlay session. Used to help calculate latency
|
||||
int64_t latency; // the actual latency used for this play session
|
||||
int64_t minimum_latency; // set if an a=min-latency: line appears in the ANNOUNCE message; zero
|
||||
@@ -70,10 +81,11 @@ typedef struct {
|
||||
SOCKADDR remote, local;
|
||||
int stop;
|
||||
int running;
|
||||
pthread_t thread;
|
||||
pthread_t thread, timer_requester;
|
||||
|
||||
// pthread_t *ptp;
|
||||
pthread_t *player_thread;
|
||||
pthread_rwlock_t player_thread_lock; // used to control access by "outsiders"
|
||||
|
||||
abuf_t audio_buffer[BUFFER_FRAMES];
|
||||
int max_frames_per_packet, input_num_channels, input_bit_depth, input_rate;
|
||||
@@ -81,7 +93,6 @@ typedef struct {
|
||||
int max_frame_size_change;
|
||||
int64_t previous_random_number;
|
||||
alac_file *decoder_info;
|
||||
uint32_t please_stop;
|
||||
uint64_t packet_count;
|
||||
int connection_state_to_output;
|
||||
int player_thread_please_stop;
|
||||
@@ -168,7 +179,6 @@ typedef struct {
|
||||
|
||||
uint64_t local_to_remote_time_difference; // used to switch between local and remote clocks
|
||||
|
||||
int timing_sender_stop; // for asking the timing-sending thread to stop
|
||||
int last_stuff_request;
|
||||
|
||||
int64_t play_segment_reference_frame;
|
||||
@@ -191,7 +201,7 @@ typedef struct {
|
||||
} rtsp_conn_info;
|
||||
|
||||
int player_play(rtsp_conn_info *conn);
|
||||
void player_stop(rtsp_conn_info *conn);
|
||||
int player_stop(rtsp_conn_info *conn);
|
||||
|
||||
void player_volume(double f, rtsp_conn_info *conn);
|
||||
void player_volume_without_notification(double f, rtsp_conn_info *conn);
|
||||
|
||||
@@ -48,8 +48,6 @@
|
||||
uint64_t local_to_remote_time_jitters;
|
||||
uint64_t local_to_remote_time_jitters_count;
|
||||
|
||||
void memory_barrier();
|
||||
|
||||
void rtp_initialise(rtsp_conn_info *conn) {
|
||||
conn->rtp_time_of_last_resend_request_error_fp = 0;
|
||||
conn->rtp_running = 0;
|
||||
@@ -67,10 +65,15 @@ void rtp_terminate(rtsp_conn_info *conn) {
|
||||
debug(1, "Error destroying reference_time_mutex variable.");
|
||||
}
|
||||
|
||||
void *rtp_audio_receiver(void *arg) {
|
||||
debug(3, "Audio receiver -- Server RTP thread starting.");
|
||||
void rtp_audio_receiver_cleanup_handler(void *arg) {
|
||||
debug(3, "Audio Receiver Cleanup.");
|
||||
rtsp_conn_info *conn = (rtsp_conn_info *)arg;
|
||||
close(conn->audio_socket);
|
||||
debug(3, "Audio Receiver Cleanup Successful.");
|
||||
}
|
||||
|
||||
// we inherit the signal mask (SIGUSR1)
|
||||
void *rtp_audio_receiver(void *arg) {
|
||||
pthread_cleanup_push(rtp_audio_receiver_cleanup_handler, arg);
|
||||
rtsp_conn_info *conn = (rtsp_conn_info *)arg;
|
||||
|
||||
int32_t last_seqno = -1;
|
||||
@@ -87,17 +90,7 @@ void *rtp_audio_receiver(void *arg) {
|
||||
float stat_M2 = 0.0;
|
||||
|
||||
ssize_t nread;
|
||||
while (conn->please_stop == 0) {
|
||||
fd_set readfds;
|
||||
FD_ZERO(&readfds);
|
||||
FD_SET(conn->audio_socket, &readfds);
|
||||
do {
|
||||
memory_barrier();
|
||||
} while (conn->please_stop == 0 &&
|
||||
pselect(conn->audio_socket + 1, &readfds, NULL, NULL, NULL, &pselect_sigset) <= 0);
|
||||
if (conn->please_stop != 0) {
|
||||
break;
|
||||
}
|
||||
while (1) {
|
||||
nread = recv(conn->audio_socket, packet, sizeof(packet), 0);
|
||||
|
||||
uint64_t local_time_now_fp = get_absolute_time_in_fp();
|
||||
@@ -181,17 +174,26 @@ void *rtp_audio_receiver(void *arg) {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
debug(3, "Audio receiver -- Server RTP thread interrupted. terminating.");
|
||||
close(conn->audio_socket);
|
||||
*/
|
||||
|
||||
return NULL;
|
||||
debug(1, "Audio receiver thread \"normal\" exit -- this can't happen. Hah!");
|
||||
pthread_cleanup_pop(0); // don't execute anything here.
|
||||
debug(2, "Audio receiver thread exit.");
|
||||
pthread_exit(NULL);
|
||||
}
|
||||
|
||||
void rtp_control_handler_cleanup_handler(void *arg) {
|
||||
debug(3, "Control Receiver Cleanup.");
|
||||
rtsp_conn_info *conn = (rtsp_conn_info *)arg;
|
||||
close(conn->control_socket);
|
||||
debug(3, "Control Receiver Cleanup Successful.");
|
||||
}
|
||||
|
||||
void *rtp_control_receiver(void *arg) {
|
||||
// we inherit the signal mask (SIGUSR1)
|
||||
|
||||
debug(3, "Control receiver -- Server RTP thread starting.");
|
||||
|
||||
pthread_cleanup_push(rtp_control_handler_cleanup_handler, arg);
|
||||
rtsp_conn_info *conn = (rtsp_conn_info *)arg;
|
||||
|
||||
conn->reference_timestamp = 0; // nothing valid received yet
|
||||
@@ -200,18 +202,7 @@ void *rtp_control_receiver(void *arg) {
|
||||
uint64_t remote_time_of_sync;
|
||||
int64_t sync_rtp_timestamp;
|
||||
ssize_t nread;
|
||||
while (conn->please_stop == 0) {
|
||||
fd_set readfds;
|
||||
FD_ZERO(&readfds);
|
||||
FD_SET(conn->control_socket, &readfds);
|
||||
do {
|
||||
memory_barrier();
|
||||
} while (conn->please_stop == 0 &&
|
||||
pselect(conn->control_socket + 1, &readfds, NULL, NULL, NULL, &pselect_sigset) <= 0);
|
||||
if (conn->please_stop != 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
while (1) {
|
||||
nread = recv(conn->control_socket, packet, sizeof(packet), 0);
|
||||
// local_time_now = get_absolute_time_in_fp();
|
||||
// clock_gettime(CLOCK_MONOTONIC,&tn);
|
||||
@@ -225,47 +216,47 @@ void *rtp_control_receiver(void *arg) {
|
||||
ssize_t plen = nread;
|
||||
if (packet[1] == 0xd4) { // sync data
|
||||
/*
|
||||
// the following stanza is for debugging only -- normally commented out.
|
||||
{
|
||||
char obf[4096];
|
||||
char *obfp = obf;
|
||||
int obfc;
|
||||
for (obfc = 0; obfc < plen; obfc++) {
|
||||
snprintf(obfp, 3, "%02X", packet[obfc]);
|
||||
obfp += 2;
|
||||
};
|
||||
*obfp = 0;
|
||||
// the following stanza is for debugging only -- normally commented out.
|
||||
{
|
||||
char obf[4096];
|
||||
char *obfp = obf;
|
||||
int obfc;
|
||||
for (obfc = 0; obfc < plen; obfc++) {
|
||||
snprintf(obfp, 3, "%02X", packet[obfc]);
|
||||
obfp += 2;
|
||||
};
|
||||
*obfp = 0;
|
||||
|
||||
|
||||
// get raw timestamp information
|
||||
// I think that a good way to understand these timestamps is that
|
||||
// (1) the rtlt below is the timestamp of the frame that should be playing at the
|
||||
client-time specified in the packet if there was no delay
|
||||
// and (2) that the rt below is the timestamp of the frame that should be playing
|
||||
at
|
||||
the client-time specified in the packet on this device taking account of the delay
|
||||
// Thus, (3) the latency can be calculated by subtracting the second from the
|
||||
first.
|
||||
// There must be more to it -- there something missing.
|
||||
// get raw timestamp information
|
||||
// I think that a good way to understand these timestamps is that
|
||||
// (1) the rtlt below is the timestamp of the frame that should be playing at the
|
||||
// client-time specified in the packet if there was no delay
|
||||
// and (2) that the rt below is the timestamp of the frame that should be playing
|
||||
// at the client-time specified in the packet on this device taking account of
|
||||
// the delay
|
||||
// Thus, (3) the latency can be calculated by subtracting the second from the
|
||||
// first.
|
||||
// There must be more to it -- there something missing.
|
||||
|
||||
// In addition, it seems that if the value of the short represented by the second
|
||||
pair
|
||||
of bytes in the packe is 7
|
||||
// then an extra time lag is expected to be added, presumably by the AirPort
|
||||
Express.
|
||||
Best guess is that this delay is 11,025 frames.
|
||||
// In addition, it seems that if the value of the short represented by the second
|
||||
// pair of bytes in the packe is 7
|
||||
// then an extra time lag is expected to be added, presumably by
|
||||
// the AirPort Express.
|
||||
|
||||
// uint32_t rtlt = nctohl(&packet[4]); // raw timestamp less latency
|
||||
// uint32_t rt = nctohl(&packet[16]); // raw timestamp
|
||||
// Best guess is that this delay is 11,025 frames.
|
||||
|
||||
// uint32_t fl = nctohs(&packet[2]); //
|
||||
// uint32_t rtlt = nctohl(&packet[4]); // raw timestamp less latency
|
||||
// uint32_t rt = nctohl(&packet[16]); // raw timestamp
|
||||
|
||||
// debug(1,"Sync Packet of %d bytes received: \"%s\", flags: %d, timestamps %u and
|
||||
%u,
|
||||
giving a latency of %d frames.",plen,obf,fl,rt,rtlt,rt-rtlt);
|
||||
// debug(1,"Monotonic timestamps are: %" PRId64 " and %" PRId64 "
|
||||
respectively.",monotonic_timestamp(rt, conn),monotonic_timestamp(rtlt, conn));
|
||||
}
|
||||
// uint32_t fl = nctohs(&packet[2]); //
|
||||
|
||||
// debug(1,"Sync Packet of %d bytes received: \"%s\", flags: %d, timestamps %u and
|
||||
%u,
|
||||
giving a latency of %d frames.",plen,obf,fl,rt,rtlt,rt-rtlt);
|
||||
// debug(1,"Monotonic timestamps are: %" PRId64 " and %" PRId64 "
|
||||
respectively.",monotonic_timestamp(rt, conn),monotonic_timestamp(rtlt, conn));
|
||||
}
|
||||
*/
|
||||
if (conn->local_to_remote_time_difference) { // need a time packet to be interchanged
|
||||
// first...
|
||||
@@ -406,15 +397,13 @@ void *rtp_control_receiver(void *arg) {
|
||||
debug(1, "Control Receiver -- error receiving a packet.");
|
||||
}
|
||||
}
|
||||
|
||||
debug(3, "Control RTP thread interrupted. terminating.");
|
||||
close(conn->control_socket);
|
||||
|
||||
return NULL;
|
||||
debug(1, "Control RTP thread \"normal\" exit -- this can't happen. Hah!");
|
||||
pthread_cleanup_pop(0); // don't execute anything here.
|
||||
debug(2, "Control RTP thread exit.");
|
||||
pthread_exit(NULL);
|
||||
}
|
||||
|
||||
void *rtp_timing_sender(void *arg) {
|
||||
debug(3, "Timing sender thread starting.");
|
||||
rtsp_conn_info *conn = (rtsp_conn_info *)arg;
|
||||
struct timing_request {
|
||||
char leader;
|
||||
@@ -434,9 +423,7 @@ void *rtp_timing_sender(void *arg) {
|
||||
req.seqno = htons(7);
|
||||
|
||||
conn->time_ping_count = 0;
|
||||
|
||||
// we inherit the signal mask (SIGUSR1)
|
||||
while (conn->timing_sender_stop == 0) {
|
||||
while (1) {
|
||||
// debug(1,"Send a timing request");
|
||||
|
||||
if (!conn->rtp_running)
|
||||
@@ -456,63 +443,45 @@ void *rtp_timing_sender(void *arg) {
|
||||
msgsize = sizeof(struct sockaddr_in6);
|
||||
}
|
||||
#endif
|
||||
fd_set writefds;
|
||||
FD_ZERO(&writefds);
|
||||
FD_SET(conn->timing_socket, &writefds);
|
||||
do {
|
||||
memory_barrier();
|
||||
} while (conn->timing_sender_stop == 0 &&
|
||||
pselect(conn->timing_socket + 1, NULL, &writefds, NULL, NULL, &pselect_sigset) <= 0);
|
||||
if (conn->timing_sender_stop != 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
if ((config.diagnostic_drop_packet_fraction == 0.0) ||
|
||||
(drand48() > config.diagnostic_drop_packet_fraction)) {
|
||||
|
||||
if (sendto(conn->timing_socket, &req, sizeof(req), 0,
|
||||
(struct sockaddr *)&conn->rtp_client_timing_socket, msgsize) == -1) {
|
||||
char em[1024];
|
||||
strerror_r(errno, em, sizeof(em));
|
||||
debug(1, "Error %d using send-to to the timing socket: \"%s\".", errno, em);
|
||||
}
|
||||
|
||||
} else {
|
||||
debug(3, "Timing Sender Thread -- dropping outgoing packet to simulate bad network.");
|
||||
}
|
||||
|
||||
request_number++;
|
||||
|
||||
// this is to deal with the possibility of missing a timing_sender_stop signal.
|
||||
// if the signal came in just before the usleep, then it wouldn't cause the sleep to end.
|
||||
// so, we will wait a maximum time of the wait_interval
|
||||
|
||||
int wait_time;
|
||||
int wait_interval = 20000; // 20 milliseconds
|
||||
|
||||
if (request_number <= 4)
|
||||
wait_time = 500000;
|
||||
usleep(500000); // these are thread cancellation points
|
||||
else
|
||||
wait_time = 3000000;
|
||||
while ((wait_time > 0) && (conn->timing_sender_stop == 0)) {
|
||||
usleep(wait_interval);
|
||||
wait_time -= wait_interval;
|
||||
}
|
||||
usleep(3000000);
|
||||
}
|
||||
debug(3, "rtp_timing_sender thread interrupted. terminating.");
|
||||
return NULL;
|
||||
debug(3, "rtp_timing_sender thread interrupted. This should never happen.");
|
||||
pthread_exit(NULL);
|
||||
}
|
||||
|
||||
void rtp_timing_receiver_cleanup_handler(void *arg) {
|
||||
debug(3, "Timing Receiver Cleanup.");
|
||||
rtsp_conn_info *conn = (rtsp_conn_info *)arg;
|
||||
pthread_cancel(conn->timer_requester);
|
||||
pthread_join(conn->timer_requester, NULL);
|
||||
close(conn->timing_socket);
|
||||
debug(3, "Timing Receiver Cleanup Successful.");
|
||||
}
|
||||
|
||||
void *rtp_timing_receiver(void *arg) {
|
||||
debug(3, "Timing receiver -- Server RTP thread starting.");
|
||||
// we inherit the signal mask (SIGUSR1)
|
||||
pthread_cleanup_push(rtp_timing_receiver_cleanup_handler, arg);
|
||||
rtsp_conn_info *conn = (rtsp_conn_info *)arg;
|
||||
|
||||
uint8_t packet[2048];
|
||||
ssize_t nread;
|
||||
conn->timing_sender_stop = 0;
|
||||
pthread_t timer_requester;
|
||||
pthread_create(&timer_requester, NULL, &rtp_timing_sender, arg);
|
||||
pthread_create(&conn->timer_requester, NULL, &rtp_timing_sender, arg);
|
||||
// struct timespec att;
|
||||
uint64_t distant_receive_time, distant_transmit_time, arrival_time, return_time;
|
||||
local_to_remote_time_jitters = 0;
|
||||
@@ -523,17 +492,7 @@ void *rtp_timing_receiver(void *arg) {
|
||||
uint64_t first_local_to_remote_time_difference = 0;
|
||||
// uint64_t first_local_to_remote_time_difference_time;
|
||||
// uint64_t l2rtd = 0;
|
||||
while (conn->please_stop == 0) {
|
||||
fd_set readfds;
|
||||
FD_ZERO(&readfds);
|
||||
FD_SET(conn->timing_socket, &readfds);
|
||||
do {
|
||||
memory_barrier();
|
||||
} while (conn->please_stop == 0 &&
|
||||
pselect(conn->timing_socket + 1, &readfds, NULL, NULL, NULL, &pselect_sigset) <= 0);
|
||||
if (conn->please_stop != 0) {
|
||||
break;
|
||||
}
|
||||
while (1) {
|
||||
nread = recv(conn->timing_socket, packet, sizeof(packet), 0);
|
||||
|
||||
if (nread >= 0) {
|
||||
@@ -562,9 +521,9 @@ void *rtp_timing_receiver(void *arg) {
|
||||
|
||||
return_time = arrival_time - conn->departure_time;
|
||||
|
||||
uint64_t rtus = (return_time * 1000000) >> 32;
|
||||
// uint64_t rtus = (return_time * 1000000) >> 32;
|
||||
|
||||
if (rtus < 300000) {
|
||||
if (((return_time * 1000000) >> 32) < 300000) {
|
||||
|
||||
// debug(2,"Synchronisation ping return time is %f milliseconds.",(rtus*1.0)/1000);
|
||||
|
||||
@@ -727,7 +686,7 @@ void *rtp_timing_receiver(void *arg) {
|
||||
//(return_time*1000000)>>32);
|
||||
} else {
|
||||
debug(2, "Time ping turnaround time: %lld us -- it looks like a timing ping was lost.",
|
||||
rtus);
|
||||
(return_time * 1000000) >> 32);
|
||||
}
|
||||
} else {
|
||||
debug(1, "Timing port -- Unknown RTP packet of type 0x%02X length %d.", packet[1], nread);
|
||||
@@ -740,17 +699,10 @@ void *rtp_timing_receiver(void *arg) {
|
||||
}
|
||||
}
|
||||
|
||||
debug(3, "Timing thread interrupted. terminating.");
|
||||
conn->timing_sender_stop = 1;
|
||||
void *retval;
|
||||
pthread_kill(timer_requester, SIGUSR1);
|
||||
debug(3, "Wait for timer requester to exit.");
|
||||
pthread_join(timer_requester, &retval);
|
||||
debug(3, "Closed and terminated timer requester thread.");
|
||||
debug(3, "Timing RTP thread terminated.");
|
||||
close(conn->timing_socket);
|
||||
|
||||
return NULL;
|
||||
debug(1, "Timing Receiver RTP thread \"normal\" exit -- this can't happen. Hah!");
|
||||
pthread_cleanup_pop(0); // don't execute anything here.
|
||||
debug(2, "Timing Receiver RTP thread exit.");
|
||||
pthread_exit(NULL);
|
||||
}
|
||||
|
||||
static uint16_t bind_port(int ip_family, const char *self_ip_address, uint32_t scope_id,
|
||||
@@ -808,8 +760,6 @@ static uint16_t bind_port(int ip_family, const char *self_ip_address, uint32_t s
|
||||
struct sockaddr_in *sa = (struct sockaddr_in *)&local;
|
||||
sport = ntohs(sa->sin_port);
|
||||
}
|
||||
fcntl(local_socket, F_SETFL, O_NONBLOCK);
|
||||
|
||||
*sock = local_socket;
|
||||
return sport;
|
||||
}
|
||||
@@ -862,8 +812,8 @@ void rtp_setup(SOCKADDR *local, SOCKADDR *remote, uint16_t cport, uint16_t tport
|
||||
inet_ntop(conn->connection_ip_family, self_addr, conn->self_ip_string,
|
||||
sizeof(conn->self_ip_string));
|
||||
|
||||
debug(2, "SETUP connection from %s to self at %s on RTSP conversation thread %d.",
|
||||
conn->client_ip_string, conn->self_ip_string, conn->connection_number);
|
||||
debug(2, "Connection %d: SETUP -- Connection from %s to self at %s.",
|
||||
conn->connection_number,conn->client_ip_string, conn->self_ip_string);
|
||||
|
||||
// set up a the record of the remote's control socket
|
||||
struct addrinfo hints;
|
||||
@@ -978,7 +928,7 @@ void rtp_request_resend(seq_t first, uint32_t count, rtsp_conn_info *conn) {
|
||||
}
|
||||
#endif
|
||||
uint64_t time_of_sending_fp = get_absolute_time_in_fp();
|
||||
uint64_t resend_error_backoff_time = (uint64_t)1 << (32 - 1); // half a second
|
||||
uint64_t resend_error_backoff_time = (uint64_t)1 << (32 - 4); // one sixteenth of a second
|
||||
if ((conn->rtp_time_of_last_resend_request_error_fp == 0) ||
|
||||
((time_of_sending_fp - conn->rtp_time_of_last_resend_request_error_fp) >
|
||||
resend_error_backoff_time)) {
|
||||
@@ -988,16 +938,19 @@ void rtp_request_resend(seq_t first, uint32_t count, rtsp_conn_info *conn) {
|
||||
(struct sockaddr *)&conn->rtp_client_control_socket, msgsize) == -1) {
|
||||
char em[1024];
|
||||
strerror_r(errno, em, sizeof(em));
|
||||
debug(1,
|
||||
"Error %d using send-to to an audio socket: \"%s\". Backing off for 0.5 seconds.",
|
||||
debug(1, "Error %d using send-to to an audio socket: \"%s\". Backing off for 1/16th of a "
|
||||
"second.",
|
||||
errno, em);
|
||||
conn->rtp_time_of_last_resend_request_error_fp = time_of_sending_fp;
|
||||
} else {
|
||||
conn->rtp_time_of_last_resend_request_error_fp = 0;
|
||||
}
|
||||
|
||||
} else {
|
||||
debug(3, "Dropping resend request packet to simulate a bad network. Backing off for 0.5 "
|
||||
"seconds.");
|
||||
debug(
|
||||
3,
|
||||
"Dropping resend request packet to simulate a bad network. Backing off for 1/16th of a "
|
||||
"second.");
|
||||
conn->rtp_time_of_last_resend_request_error_fp = time_of_sending_fp;
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -697,7 +697,7 @@ static void handle_record(rtsp_conn_info *conn, rtsp_message *req, rtsp_message
|
||||
p = strchr(p, '=');
|
||||
if (p) {
|
||||
rtptime = uatoi(p + 1); // unsigned integer -- up to 2^32-1
|
||||
rtptime--;
|
||||
// rtptime--;
|
||||
// debug(1,"RTSP Flush Requested by handle_record: %u.",rtptime);
|
||||
player_flush(rtptime, conn);
|
||||
}
|
||||
@@ -762,12 +762,12 @@ static void handle_flush(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *
|
||||
}
|
||||
|
||||
static void handle_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *resp) {
|
||||
debug(2, "Connection %d: SETUP", conn->connection_number);
|
||||
debug(3, "Connection %d: SETUP", conn->connection_number);
|
||||
uint16_t cport, tport;
|
||||
|
||||
char *ar = msg_get_header(req, "Active-Remote");
|
||||
if (ar) {
|
||||
debug(2, "Active-Remote string seen: \"%s\".", ar);
|
||||
debug(2, "Connection %d: SETUP -- Active-Remote string seen: \"%s\".", conn->connection_number, ar);
|
||||
// get the active remote
|
||||
char *p;
|
||||
conn->dacp_active_remote = strtoul(ar, &p, 10);
|
||||
@@ -775,13 +775,13 @@ static void handle_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *
|
||||
send_metadata('ssnc', 'acre', ar, strlen(ar), req, 1);
|
||||
#endif
|
||||
} else {
|
||||
debug(2, "Note: no Active-Remote information the SETUP Record.");
|
||||
debug(2, "Connection %d: SETUP -- Note: no Active-Remote information the SETUP Record.",conn->connection_number);
|
||||
conn->dacp_active_remote = 0;
|
||||
}
|
||||
|
||||
ar = msg_get_header(req, "DACP-ID");
|
||||
if (ar) {
|
||||
debug(2, "DACP-ID string seen: \"%s\".", ar);
|
||||
debug(2, "Connection %d: SETUP -- DACP-ID string seen: \"%s\".",conn->connection_number, ar);
|
||||
if (conn->dacp_id) // this is in case SETUP was previously called
|
||||
free(conn->dacp_id);
|
||||
conn->dacp_id = strdup(ar);
|
||||
@@ -789,7 +789,7 @@ static void handle_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *
|
||||
send_metadata('ssnc', 'daid', ar, strlen(ar), req, 1);
|
||||
#endif
|
||||
} else {
|
||||
debug(2, "Note: no DACP-ID string information in the SETUP Record.");
|
||||
debug(2, "Connection %d: SETUP doesn't include DACP-ID string information.",conn->connection_number);
|
||||
if (conn->dacp_id) // this is in case SETUP was previously called
|
||||
free(conn->dacp_id);
|
||||
conn->dacp_id = NULL;
|
||||
@@ -797,14 +797,14 @@ static void handle_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *
|
||||
|
||||
char *hdr = msg_get_header(req, "Transport");
|
||||
if (!hdr) {
|
||||
debug(1, "SETUP doesn't contain a Transport header.");
|
||||
debug(1, "Connection %d: SETUP doesn't contain a Transport header.",conn->connection_number);
|
||||
goto error;
|
||||
}
|
||||
|
||||
char *p;
|
||||
p = strstr(hdr, "control_port=");
|
||||
if (!p) {
|
||||
debug(1, "SETUP doesn't specify a control_port.");
|
||||
debug(1, "Connection %d: SETUP doesn't specify a control_port.",conn->connection_number);
|
||||
goto error;
|
||||
}
|
||||
p = strchr(p, '=') + 1;
|
||||
@@ -812,7 +812,7 @@ static void handle_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *
|
||||
|
||||
p = strstr(hdr, "timing_port=");
|
||||
if (!p) {
|
||||
debug(1, "SETUP doesn't specify a timing_port.");
|
||||
debug(1, "Connection %d: SETUP doesn't specify a timing_port.",conn->connection_number);
|
||||
goto error;
|
||||
}
|
||||
p = strchr(p, '=') + 1;
|
||||
@@ -820,19 +820,19 @@ static void handle_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *
|
||||
|
||||
if (conn->rtp_running) {
|
||||
if ((conn->remote_control_port != cport) || (conn->remote_timing_port != tport)) {
|
||||
warn("Duplicate SETUP message with different control (old %u, new %u) or timing (old %u, new "
|
||||
"%u) ports! This is probably fatal!",
|
||||
warn("Connection %d: Duplicate SETUP message with different control (old %u, new %u) or timing (old %u, new "
|
||||
"%u) ports! This is probably fatal!",conn->connection_number,
|
||||
conn->remote_control_port, cport, conn->remote_timing_port, tport);
|
||||
} else {
|
||||
warn("Duplicate SETUP message with the same control (%u) and timing (%u) ports. This is "
|
||||
"probably not fatal.",
|
||||
warn("Connection %d: Duplicate SETUP message with the same control (%u) and timing (%u) ports. This is "
|
||||
"probably not fatal.",conn->connection_number,
|
||||
conn->remote_control_port, conn->remote_timing_port);
|
||||
}
|
||||
} else {
|
||||
rtp_setup(&conn->local, &conn->remote, cport, tport, conn);
|
||||
}
|
||||
if (conn->local_audio_port == 0) {
|
||||
debug(1, "SETUP seems to specify a null audio port.");
|
||||
debug(1, "Connection %d: SETUP seems to specify a null audio port.",conn->connection_number);
|
||||
goto error;
|
||||
}
|
||||
|
||||
@@ -851,7 +851,7 @@ static void handle_setup(rtsp_conn_info *conn, rtsp_message *req, rtsp_message *
|
||||
return;
|
||||
|
||||
error:
|
||||
warn("Error in setup request -- unlocking play lock on RTSP conversation thread %d.",
|
||||
warn("Connection %d: SETUP -- Error in setup request -- unlocking play lock.",
|
||||
conn->connection_number);
|
||||
playing_conn = NULL;
|
||||
pthread_mutex_unlock(&play_lock);
|
||||
@@ -1861,6 +1861,12 @@ authenticate:
|
||||
static void *rtsp_conversation_thread_func(void *pconn) {
|
||||
rtsp_conn_info *conn = pconn;
|
||||
|
||||
// create the player thread lock.
|
||||
int rwli = pthread_rwlock_init(&conn->player_thread_lock, NULL);
|
||||
if (rwli != 0)
|
||||
die("Error %d initialising player_thread_lock for conversation thread %d.", rwli,
|
||||
conn->connection_number);
|
||||
|
||||
rtp_initialise(conn);
|
||||
|
||||
rtsp_message *req, *resp;
|
||||
@@ -1943,6 +1949,7 @@ static void *rtsp_conversation_thread_func(void *pconn) {
|
||||
if (tstop) {
|
||||
debug(3, "Synchronously terminate playing thread of RTSP conversation thread %d.",
|
||||
conn->connection_number);
|
||||
|
||||
if (conn->player_thread)
|
||||
debug(1, "RTSP Channel unexpectedly closed or a serious error occured -- closing the "
|
||||
"player thread.");
|
||||
@@ -1965,10 +1972,16 @@ static void *rtsp_conversation_thread_func(void *pconn) {
|
||||
playing_conn = NULL;
|
||||
pthread_mutex_unlock(&play_lock);
|
||||
}
|
||||
debug(2, "RTSP conversation thread %d terminated.", conn->connection_number);
|
||||
// please_shutdown = 0;
|
||||
debug(2, "Connection %d: RTSP thread terminated.", conn->connection_number);
|
||||
conn->running = 0;
|
||||
return NULL;
|
||||
|
||||
// release the player_thread_lock
|
||||
int rwld = pthread_rwlock_destroy(&conn->player_thread_lock);
|
||||
if (rwld)
|
||||
debug(1, "Error %d destroying player_thread_lock for conversation thread %d.", rwld,
|
||||
conn->connection_number);
|
||||
|
||||
pthread_exit(NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -2088,8 +2101,8 @@ void rtsp_listen_loop(void) {
|
||||
|
||||
int acceptfd;
|
||||
struct timeval tv;
|
||||
while (1) {
|
||||
tv.tv_sec = 300;
|
||||
do {
|
||||
tv.tv_sec = 60;
|
||||
tv.tv_usec = 0;
|
||||
|
||||
for (i = 0; i < nsock; i++)
|
||||
@@ -2182,7 +2195,13 @@ void rtsp_listen_loop(void) {
|
||||
conn->running = 1; // this must happen before the thread is tracked
|
||||
track_thread(conn);
|
||||
}
|
||||
}
|
||||
perror("select");
|
||||
die("fell out of the RTSP select loop");
|
||||
} while (1);
|
||||
|
||||
mdns_unregister();
|
||||
|
||||
if (sockfd)
|
||||
free(sockfd);
|
||||
|
||||
// perror("select");
|
||||
// die("fell out of the RTSP select loop");
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ general =
|
||||
// as "org.gnome.ShairportSync" on the whichever bus you specify here: "system" (default) or "session".
|
||||
};
|
||||
|
||||
// Advanced parameters for controlling how a Shairport Sync runs
|
||||
// Advanced parameters for controlling how Shairport Sync runs a play session
|
||||
sessioncontrol =
|
||||
{
|
||||
// run_this_before_play_begins = "/full/path/to/application and args"; // make sure the application has executable permission. If it's a script, include the shebang (#!/bin/...) on the first line
|
||||
|
||||
+32
-17
@@ -274,6 +274,8 @@ int parse_options(int argc, char **argv) {
|
||||
optind = j;
|
||||
|
||||
optCon = poptGetContext(NULL, optind, (const char **)argv, optionsTable, 0);
|
||||
if (optCon == NULL)
|
||||
die("Can not get a secondary popt context.");
|
||||
poptSetOtherOptionHelp(optCon, "[OPTIONS]* ");
|
||||
|
||||
/* Now do options processing just to get a debug level */
|
||||
@@ -318,6 +320,8 @@ int parse_options(int argc, char **argv) {
|
||||
die("%s: %s", poptBadOption(optCon, POPT_BADOPTION_NOALIAS), poptStrerror(c));
|
||||
}
|
||||
|
||||
poptFreeContext(optCon);
|
||||
|
||||
if ((daemonisewith) && (daemonisewithout))
|
||||
die("Select either daemonize_with_pid_file or daemonize_without_pid_file -- you have selected "
|
||||
"both!");
|
||||
@@ -888,6 +892,8 @@ int parse_options(int argc, char **argv) {
|
||||
optind = j;
|
||||
|
||||
optCon = poptGetContext(NULL, optind, (const char **)argv, optionsTable, 0);
|
||||
if (optCon == NULL)
|
||||
die("Can not get a popt context.");
|
||||
poptSetOtherOptionHelp(optCon, "[OPTIONS]* ");
|
||||
|
||||
/* Now do options processing, get portname */
|
||||
@@ -935,6 +941,7 @@ int parse_options(int argc, char **argv) {
|
||||
die("%s: %s", poptBadOption(optCon, POPT_BADOPTION_NOALIAS), poptStrerror(c));
|
||||
}
|
||||
|
||||
poptFreeContext(optCon);
|
||||
#ifdef CONFIG_METADATA
|
||||
if ((config.metadata_enabled == 1) && (config.metadata_pipename == NULL))
|
||||
config.metadata_pipename = strdup("/tmp/shairport-sync-metadata");
|
||||
@@ -956,6 +963,10 @@ int parse_options(int argc, char **argv) {
|
||||
char hostname[100];
|
||||
gethostname(hostname, 100);
|
||||
char *i1 = str_replace(raw_service_name, "%h", hostname);
|
||||
if (raw_service_name) {
|
||||
free(raw_service_name);
|
||||
raw_service_name = NULL;
|
||||
}
|
||||
if ((hostname[0] >= 'a') && (hostname[0] <= 'z'))
|
||||
hostname[0] = hostname[0] - 0x20; // convert a lowercase first letter into a capital letter
|
||||
char *i2 = str_replace(i1, "%H", hostname);
|
||||
@@ -1041,13 +1052,15 @@ void shairport_startup_complete(void) {
|
||||
}
|
||||
}
|
||||
|
||||
const char *pid_file_proc(void) {
|
||||
|
||||
char fn[8192];
|
||||
snprintf(fn, sizeof(fn), "%s/%s.pid", config.computed_piddir,
|
||||
daemon_pid_file_ident ? daemon_pid_file_ident : "unknown");
|
||||
// debug(1,"fn \"%s\".",fn);
|
||||
return strdup(fn);
|
||||
const char *pid_file_proc(char *fn, size_t max_length) {
|
||||
if (fn) {
|
||||
snprintf(fn, max_length, "%s/%s.pid", config.computed_piddir,
|
||||
daemon_pid_file_ident ? daemon_pid_file_ident : "unknown");
|
||||
// debug(1,"fn \"%s\".",fn);
|
||||
} else {
|
||||
debug(1, "the sise of the buffer for the PID file path is zero.");
|
||||
}
|
||||
return fn;
|
||||
}
|
||||
|
||||
void exit_function() {
|
||||
@@ -1168,11 +1181,6 @@ int main(int argc, char **argv) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Point to a function to help locate where the PID file will go
|
||||
// We always use this function because the default location
|
||||
// is unsatisfactory. By default we want to use /var/run/shairport-sync/.
|
||||
daemon_pid_file_proc = pid_file_proc;
|
||||
|
||||
/* Set indentification string for the daemon for both syslog and PID file */
|
||||
daemon_pid_file_ident = daemon_log_ident = daemon_ident_from_argv0(argv[0]);
|
||||
|
||||
@@ -1350,10 +1358,16 @@ int main(int argc, char **argv) {
|
||||
// audio_backend_latency_offset instead.
|
||||
|
||||
if (config.userSuppliedLatency) {
|
||||
inform("The default latency setting is deprecated, as Shairport Sync can now get the correct "
|
||||
"latency from the source.");
|
||||
inform("Please remove this setting and use the relevant audio_backend_latency_offset setting, "
|
||||
"if necessary, to compensate for delays elsewhere.");
|
||||
inform("The fixed latency setting is deprecated, as Shairport Sync gets the correct "
|
||||
"latency automatically from the source.");
|
||||
inform("Use the audio_backend_latency_offset_in_seconds setting "
|
||||
"instead to compensate for timing issues.");
|
||||
if ((config.userSuppliedLatency != 0) &&
|
||||
((config.userSuppliedLatency < 4410) ||
|
||||
(config.userSuppliedLatency > BUFFER_FRAMES * 352 - 22050)))
|
||||
die("An out-of-range fixed latency has been specified. It must be between 4410 and %d (at "
|
||||
"44100 frames per second).",
|
||||
BUFFER_FRAMES * 352 - 22050);
|
||||
}
|
||||
|
||||
/* print out version */
|
||||
@@ -1366,10 +1380,11 @@ int main(int argc, char **argv) {
|
||||
debug(1, "Can't print the version information!");
|
||||
}
|
||||
|
||||
char pid_file_path_string[4096] = "\0";
|
||||
/* Print out options */
|
||||
debug(1, "statistics_requester status is %d.", config.statistics_requested);
|
||||
debug(1, "daemon status is %d.", config.daemonise);
|
||||
debug(1, "deamon pid file is \"%s\".", pid_file_proc());
|
||||
debug(1, "deamon pid file is \"%s\".", pid_file_proc(pid_file_path_string, 4096));
|
||||
debug(1, "rtsp listening port is %d.", config.port);
|
||||
debug(1, "udp base port is %d.", config.udp_port_base);
|
||||
debug(1, "udp port range is %d.", config.udp_port_range);
|
||||
|
||||
Reference in New Issue
Block a user