diff --git a/src/misc.c b/src/misc.c index 298cb7aa..b6ceef75 100644 --- a/src/misc.c +++ b/src/misc.c @@ -751,6 +751,57 @@ murmur_hash64(const void *key, int len, uint32_t seed) return h; } +int +clock_gettime_with_res(clockid_t clock_id, struct timespec *tp, struct timespec *res) +{ + int r_val = -1; + if(res && tp) + { + r_val = clock_gettime(clock_id, tp); + /* this will only work for sub-second resolutions. */ + if(r_val == 0 && res->tv_nsec > 1) + { + tp->tv_nsec = (tp->tv_nsec/res->tv_nsec)*res->tv_nsec; + } + } + return r_val; +} + +struct timespec +timespec_add(struct timespec time1, struct timespec time2) +{ + struct timespec result; + + result.tv_sec = time1.tv_sec + time2.tv_sec; + result.tv_nsec = time1.tv_nsec + time2.tv_nsec; + if (result.tv_nsec >= 1000000000L) + { + result.tv_sec++; + result.tv_nsec -= 1000000000L; + } + return result; +} + +int +timespec_cmp(struct timespec time1, struct timespec time2) +{ + /* Less than. */ + if (time1.tv_sec < time2.tv_sec) + return -1; + /* Greater than. */ + else if (time1.tv_sec > time2.tv_sec) + return 1; + /* Less than. */ + else if (time1.tv_nsec < time2.tv_nsec) + return -1; + /* Greater than. */ + else if (time1.tv_nsec > time2.tv_nsec) + return 1; + /* Equal. */ + else + return 0; +} + #else # error Platform not supported #endif diff --git a/src/misc.h b/src/misc.h index 56ecb3ff..4a1f2658 100644 --- a/src/misc.h +++ b/src/misc.h @@ -3,7 +3,7 @@ #define __MISC_H__ #include - +#include struct onekeyval { char *name; @@ -72,4 +72,15 @@ b64_encode(uint8_t *in, size_t len); uint64_t murmur_hash64(const void *key, int len, uint32_t seed); +/* timer functions */ +int +clock_gettime_with_res(clockid_t clock_id, struct timespec *tp, struct timespec *res); + +struct timespec +timespec_add(struct timespec time1, struct timespec time2); + +int +timespec_cmp(struct timespec time1, struct timespec time2); + + #endif /* !__MISC_H__ */ diff --git a/src/player.c b/src/player.c index c4061a60..40d5d6e2 100644 --- a/src/player.c +++ b/src/player.c @@ -64,6 +64,10 @@ # define MIN(a, b) ((a < b) ? a : b) #endif +#ifndef MAX +#define MAX(a, b) ((a > b) ? a : b) +#endif + enum player_sync_source { PLAYER_SYNC_CLOCK, @@ -150,6 +154,10 @@ static int pb_timer_fd; static struct event pb_timer_ev; #if defined(__linux__) static struct timespec pb_timer_last; +static struct timespec packet_timer_last; +static uint64_t MINIMUM_STREAM_PERIOD; +static struct timespec packet_time = { 0, AIRTUNES_V2_STREAM_PERIOD }; +static struct timespec timer_res; #endif /* Sync source */ @@ -358,7 +366,7 @@ player_get_current_pos_clock(uint64_t *pos, struct timespec *ts, int commit) uint64_t delta; int ret; - ret = clock_gettime(CLOCK_MONOTONIC, ts); + ret = clock_gettime_with_res(CLOCK_MONOTONIC, ts, &timer_res); if (ret < 0) { DPRINTF(E_LOG, L_PLAYER, "Couldn't get clock: %s\n", strerror(errno)); @@ -402,7 +410,7 @@ player_get_current_pos_laudio(uint64_t *pos, struct timespec *ts, int commit) *pos = laudio_get_pos(); - ret = clock_gettime(CLOCK_MONOTONIC, ts); + ret = clock_gettime_with_res(CLOCK_MONOTONIC, ts, &timer_res); if (ret < 0) { DPRINTF(E_LOG, L_PLAYER, "Couldn't get clock: %s\n", strerror(errno)); @@ -1397,17 +1405,35 @@ player_playback_cb(int fd, short what, void *arg) struct itimerspec next; uint64_t ticks; int ret; + uint32_t packet_send_count = 0; + struct timespec next_tick; + struct timespec stream_period = { 0, MINIMUM_STREAM_PERIOD }; /* Acknowledge timer */ read(fd, &ticks, sizeof(ticks)); - playback_write(); + /* Decide how many packets to send */ + next_tick = timespec_add(pb_timer_last, stream_period); + do + { + playback_write(); + packet_timer_last = timespec_add(packet_timer_last, packet_time); + packet_send_count++; + /* not possible to have more than 126 audio packets per second */ + if(packet_send_count > 126) + { + DPRINTF(E_LOG, L_PLAYER, "Timing error detected during playback! Aborting.\n"); + playback_abort(); + return; + } + } + while(timespec_cmp(packet_timer_last, next_tick) < 0 ); /* Make sure playback is still running */ if (player_state == PLAY_STOPPED) return; - pb_timer_last.tv_nsec += AIRTUNES_V2_STREAM_PERIOD; + pb_timer_last.tv_nsec += MINIMUM_STREAM_PERIOD; if (pb_timer_last.tv_nsec >= 1000000000) { pb_timer_last.tv_sec++; @@ -1831,7 +1857,7 @@ device_activate_cb(struct raop_device *dev, struct raop_session *rs, enum raop_s if ((player_state == PLAY_PLAYING) && (raop_sessions == 1)) { - ret = clock_gettime(CLOCK_MONOTONIC, &ts); + ret = clock_gettime_with_res(CLOCK_MONOTONIC, &ts, &timer_res); if (ret < 0) { DPRINTF(E_LOG, L_PLAYER, "Could not get current time: %s\n", strerror(errno)); @@ -2157,7 +2183,7 @@ playback_start_bh(struct player_command *cmd) } } - ret = clock_gettime(CLOCK_MONOTONIC, &pb_pos_stamp); + ret = clock_gettime_with_res(CLOCK_MONOTONIC, &pb_pos_stamp, &timer_res); if (ret < 0) { DPRINTF(E_LOG, L_PLAYER, "Couldn't get current clock: %s\n", strerror(errno)); @@ -2168,6 +2194,13 @@ playback_start_bh(struct player_command *cmd) memset(&pb_timer_ev, 0, sizeof(struct event)); #if defined(__linux__) + /* + * initialize the packet timer to the same relative time that we have + * for the playback timer. + */ + packet_timer_last.tv_sec = pb_pos_stamp.tv_sec; + packet_timer_last.tv_nsec = pb_pos_stamp.tv_nsec; + pb_timer_last.tv_sec = pb_pos_stamp.tv_sec; pb_timer_last.tv_nsec = pb_pos_stamp.tv_nsec; @@ -4021,6 +4054,18 @@ player_init(void) update_handler = NULL; + /* + * Determine if the resolution of the system timer is > or < the size + * of an audio packet. NOTE: this assumes the system clock resolution + * is less than one second. + */ + if(clock_getres(CLOCK_MONOTONIC, &timer_res) < 0) + { + DPRINTF(E_LOG, L_PLAYER, "Could not get the system timer resolution.\n"); + return -1; + } + MINIMUM_STREAM_PERIOD = MAX(timer_res.tv_nsec, AIRTUNES_V2_STREAM_PERIOD); + /* Random RTP time start */ gcry_randomize(&rnd, sizeof(rnd), GCRY_STRONG_RANDOM); last_rtptime = ((uint64_t)1 << 32) | rnd; diff --git a/src/player.h b/src/player.h index 29d39824..f52a61c3 100644 --- a/src/player.h +++ b/src/player.h @@ -6,7 +6,8 @@ #if defined(__linux__) /* AirTunes v2 packet interval in ns */ -# define AIRTUNES_V2_STREAM_PERIOD 7980000 +/* (352 samples/packet * 1e9 ns/s) / 44100 samples/s = 7981859 ns/packet */ +#define AIRTUNES_V2_STREAM_PERIOD 7981859 #elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) /* AirTunes v2 packet interval in ms */ # define AIRTUNES_V2_STREAM_PERIOD 8