[player/outputs] Handle multiple quality levels + use rtp_common (WIP!)

* Untie Airtunes stuff further from player and non-Airplay outputs
* Change raop.c to use rtp_common.c (step 1)
* Change heartbeat of player to 100 ticks/sec, since we have untied from
  Airtunes 352 samples per packet (which equals 126 ticks/sec at 44100)

Still a lot to be done in the player, since the rtptime's in it don't
are probably broken.
This commit is contained in:
ejurgensen 2019-02-08 20:39:11 +01:00
parent cdd0aa884b
commit fcc91ecd86
6 changed files with 579 additions and 585 deletions

View File

@ -38,7 +38,10 @@
#include "player.h" #include "player.h"
#include "outputs.h" #include "outputs.h"
#define PACKET_SIZE STOB(AIRTUNES_V2_PACKET_SAMPLES) // Same as Airplay - maybe not optimal?
#define ALSA_SAMPLES_PER_PACKET 352
#define ALSA_PACKET_SIZE STOB(ALSA_SAMPLES_PER_PACKET, 16, 2)
// The maximum number of samples that the output is allowed to get behind (or // The maximum number of samples that the output is allowed to get behind (or
// ahead) of the player position, before compensation is attempted // ahead) of the player position, before compensation is attempted
#define ALSA_MAX_LATENCY 352 #define ALSA_MAX_LATENCY 352
@ -573,15 +576,15 @@ playback_start(struct alsa_session *as, uint64_t pos, uint64_t start_pos)
// buffering, because my sound card's start_threshold is not to be counted on. // buffering, because my sound card's start_threshold is not to be counted on.
// Instead we allocate our own buffer, and when it is time to play we write as // Instead we allocate our own buffer, and when it is time to play we write as
// much as we can to alsa's buffer. // much as we can to alsa's buffer.
as->prebuf_len = (start_pos - pos) / AIRTUNES_V2_PACKET_SAMPLES + 1; as->prebuf_len = (start_pos - pos) / ALSA_SAMPLES_PER_PACKET + 1;
if (as->prebuf_len > (3 * 44100 - offset) / AIRTUNES_V2_PACKET_SAMPLES) if (as->prebuf_len > (3 * 44100 - offset) / ALSA_SAMPLES_PER_PACKET)
{ {
DPRINTF(E_LOG, L_LAUDIO, "Sanity check of prebuf_len (%" PRIu32 " packets) failed\n", as->prebuf_len); DPRINTF(E_LOG, L_LAUDIO, "Sanity check of prebuf_len (%" PRIu32 " packets) failed\n", as->prebuf_len);
return; return;
} }
DPRINTF(E_DBG, L_LAUDIO, "Will prebuffer %d packets\n", as->prebuf_len); DPRINTF(E_DBG, L_LAUDIO, "Will prebuffer %d packets\n", as->prebuf_len);
as->prebuf = malloc(as->prebuf_len * PACKET_SIZE); as->prebuf = malloc(as->prebuf_len * ALSA_PACKET_SIZE);
if (!as->prebuf) if (!as->prebuf)
{ {
DPRINTF(E_LOG, L_LAUDIO, "Out of memory for audio buffer (requested %" PRIu32 " packets)\n", as->prebuf_len); DPRINTF(E_LOG, L_LAUDIO, "Out of memory for audio buffer (requested %" PRIu32 " packets)\n", as->prebuf_len);
@ -589,7 +592,7 @@ playback_start(struct alsa_session *as, uint64_t pos, uint64_t start_pos)
} }
as->pos = pos; as->pos = pos;
as->start_pos = start_pos - AIRTUNES_V2_PACKET_SAMPLES; as->start_pos = start_pos - ALSA_SAMPLES_PER_PACKET;
// Dump PCM config data for E_DBG logging // Dump PCM config data for E_DBG logging
ret = snd_output_buffer_open(&output); ret = snd_output_buffer_open(&output);
@ -620,32 +623,32 @@ buffer_write(struct alsa_session *as, uint8_t *buf, snd_pcm_sframes_t *avail, in
snd_pcm_sframes_t nsamp; snd_pcm_sframes_t nsamp;
snd_pcm_sframes_t ret; snd_pcm_sframes_t ret;
nsamp = AIRTUNES_V2_PACKET_SAMPLES; nsamp = ALSA_SAMPLES_PER_PACKET;
if (as->prebuf && (prebuffering || !prebuf_empty || *avail < AIRTUNES_V2_PACKET_SAMPLES)) if (as->prebuf && (prebuffering || !prebuf_empty || *avail < ALSA_SAMPLES_PER_PACKET))
{ {
pkt = &as->prebuf[as->prebuf_head * PACKET_SIZE]; pkt = &as->prebuf[as->prebuf_head * ALSA_PACKET_SIZE];
memcpy(pkt, buf, PACKET_SIZE); memcpy(pkt, buf, ALSA_PACKET_SIZE);
as->prebuf_head = (as->prebuf_head + 1) % as->prebuf_len; as->prebuf_head = (as->prebuf_head + 1) % as->prebuf_len;
if (prebuffering || *avail < AIRTUNES_V2_PACKET_SAMPLES) if (prebuffering || *avail < ALSA_SAMPLES_PER_PACKET)
return 0; // No actual writing return 0; // No actual writing
// We will now set buf so that we will transfer as much as possible to ALSA // We will now set buf so that we will transfer as much as possible to ALSA
buf = &as->prebuf[as->prebuf_tail * PACKET_SIZE]; buf = &as->prebuf[as->prebuf_tail * ALSA_PACKET_SIZE];
if (as->prebuf_head > as->prebuf_tail) if (as->prebuf_head > as->prebuf_tail)
npackets = as->prebuf_head - as->prebuf_tail; npackets = as->prebuf_head - as->prebuf_tail;
else else
npackets = as->prebuf_len - as->prebuf_tail; npackets = as->prebuf_len - as->prebuf_tail;
nsamp = npackets * AIRTUNES_V2_PACKET_SAMPLES; nsamp = npackets * ALSA_SAMPLES_PER_PACKET;
while (nsamp > *avail) while (nsamp > *avail)
{ {
npackets -= 1; npackets -= 1;
nsamp -= AIRTUNES_V2_PACKET_SAMPLES; nsamp -= ALSA_SAMPLES_PER_PACKET;
} }
as->prebuf_tail = (as->prebuf_tail + npackets) % as->prebuf_len; as->prebuf_tail = (as->prebuf_tail + npackets) % as->prebuf_len;
@ -685,7 +688,7 @@ sync_check(struct alsa_session *as, uint64_t rtptime, snd_pcm_sframes_t delay, i
else else
npackets = 0; npackets = 0;
pb_pos = rtptime - delay - AIRTUNES_V2_PACKET_SAMPLES * npackets; pb_pos = rtptime - delay - ALSA_SAMPLES_PER_PACKET * npackets;
latency = cur_pos - (pb_pos - offset); latency = cur_pos - (pb_pos - offset);
// If the latency is low or very different from our last measurement, we reset the sync_counter // If the latency is low or very different from our last measurement, we reset the sync_counter
@ -727,7 +730,7 @@ playback_write(struct alsa_session *as, uint8_t *buf, uint64_t rtptime)
prebuffering = (as->pos < as->start_pos); prebuffering = (as->pos < as->start_pos);
prebuf_empty = (as->prebuf_head == as->prebuf_tail); prebuf_empty = (as->prebuf_head == as->prebuf_tail);
as->pos += AIRTUNES_V2_PACKET_SAMPLES; as->pos += ALSA_SAMPLES_PER_PACKET;
if (prebuffering) if (prebuffering)
{ {
@ -771,7 +774,7 @@ playback_write(struct alsa_session *as, uint8_t *buf, uint64_t rtptime)
} }
// Fill the prebuf with audio before restarting, so we don't underrun again // Fill the prebuf with audio before restarting, so we don't underrun again
as->start_pos = as->pos + AIRTUNES_V2_PACKET_SAMPLES * (as->prebuf_len - 1); as->start_pos = as->pos + ALSA_SAMPLES_PER_PACKET * (as->prebuf_len - 1);
return; return;
} }
@ -799,7 +802,7 @@ playback_pos_get(uint64_t *pos, uint64_t next_pkt)
// Make pos the rtptime of the packet containing cur_pos // Make pos the rtptime of the packet containing cur_pos
*pos = next_pkt; *pos = next_pkt;
while (*pos > cur_pos) while (*pos > cur_pos)
*pos -= AIRTUNES_V2_PACKET_SAMPLES; *pos -= ALSA_SAMPLES_PER_PACKET;
} }
/* ------------------ INTERFACE FUNCTIONS CALLED BY OUTPUTS.C --------------- */ /* ------------------ INTERFACE FUNCTIONS CALLED BY OUTPUTS.C --------------- */

View File

@ -39,13 +39,13 @@
#include "player.h" #include "player.h"
#include "outputs.h" #include "outputs.h"
#define FIFO_BUFFER_SIZE 65536 /* pipe capacity on Linux >= 2.6.11 */ #define FIFO_BUFFER_SIZE 65536 // pipe capacity on Linux >= 2.6.11
#define FIFO_PACKET_SIZE 1408 // 352 samples/packet * 16 bit/sample * 2 channels
struct fifo_packet struct fifo_packet
{ {
/* pcm data */ /* pcm data */
uint8_t samples[1408]; // STOB(AIRTUNES_V2_PACKET_SAMPLES) uint8_t samples[FIFO_PACKET_SIZE];
/* RTP-time of the first sample*/ /* RTP-time of the first sample*/
uint64_t rtptime; uint64_t rtptime;
@ -453,7 +453,7 @@ static void
fifo_write(uint8_t *buf, uint64_t rtptime) fifo_write(uint8_t *buf, uint64_t rtptime)
{ {
struct fifo_session *fifo_session = sessions; struct fifo_session *fifo_session = sessions;
size_t length = STOB(AIRTUNES_V2_PACKET_SAMPLES); size_t length = FIFO_PACKET_SIZE;
ssize_t bytes; ssize_t bytes;
struct fifo_packet *packet; struct fifo_packet *packet;
uint64_t cur_pos; uint64_t cur_pos;

View File

@ -38,6 +38,8 @@
#include "outputs.h" #include "outputs.h"
#include "commands.h" #include "commands.h"
// From Airplay
#define PULSE_SAMPLES_PER_PACKET 352
#define PULSE_MAX_DEVICES 64 #define PULSE_MAX_DEVICES 64
#define PULSE_LOG_MAX 10 #define PULSE_LOG_MAX 10
@ -602,7 +604,7 @@ stream_open(struct pulse_session *ps, pa_stream_notify_cb_t cb)
flags = PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE; flags = PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE;
ps->attr.tlength = STOB(2 * ss.rate + AIRTUNES_V2_PACKET_SAMPLES - offset); // 2 second latency ps->attr.tlength = STOB(2 * ss.rate + PULSE_SAMPLES_PER_PACKET - offset, 16, 2); // 2 second latency
ps->attr.maxlength = 2 * ps->attr.tlength; ps->attr.maxlength = 2 * ps->attr.tlength;
ps->attr.prebuf = (uint32_t)-1; ps->attr.prebuf = (uint32_t)-1;
ps->attr.minreq = (uint32_t)-1; ps->attr.minreq = (uint32_t)-1;
@ -760,7 +762,7 @@ pulse_write(uint8_t *buf, uint64_t rtptime)
if (!sessions) if (!sessions)
return; return;
length = STOB(AIRTUNES_V2_PACKET_SAMPLES); length = STOB(PULSE_SAMPLES_PER_PACKET, 16, 2);
pa_threaded_mainloop_lock(pulse.mainloop); pa_threaded_mainloop_lock(pulse.mainloop);

File diff suppressed because it is too large Load Diff

View File

@ -111,12 +111,22 @@
// Default volume (must be from 0 - 100) // Default volume (must be from 0 - 100)
#define PLAYER_DEFAULT_VOLUME 50 #define PLAYER_DEFAULT_VOLUME 50
// For every tick_interval, we will read a packet from the input buffer and
// The interval between each tick of the playback clock in ms. This means that
// we read 10 ms frames from the input and pass to the output, so the clock
// ticks 100 times a second. We use this value because most common sample rates
// are divisible by 100, and because it keeps delay low.
// TODO sample rates of 22050 might cause underruns, since we would be reading
// only 100 x 220 = 22000 samples each second.
#define PLAYER_TICK_INTERVAL 10
// For every tick_interval, we will read a frame from the input buffer and
// write it to the outputs. If the input is empty, we will try to catch up next // write it to the outputs. If the input is empty, we will try to catch up next
// tick. However, at some point we will owe the outputs so much data that we // tick. However, at some point we will owe the outputs so much data that we
// have to suspend playback and wait for the input to get its act together. // have to suspend playback and wait for the input to get its act together.
// (value is in milliseconds and should be low enough to avoid output underrun) // (value is in milliseconds and should be low enough to avoid output underrun)
#define PLAYER_READ_BEHIND_MAX 1500 #define PLAYER_READ_BEHIND_MAX 1500
// Generally, an output must not block (for long) when outputs_write() is // Generally, an output must not block (for long) when outputs_write() is
// called. If an output does that anyway, the next tick event will be late, and // called. If an output does that anyway, the next tick event will be late, and
// by extension playback_cb(). We will try to catch up, but if the delay // by extension playback_cb(). We will try to catch up, but if the delay
@ -124,6 +134,9 @@
// (value is in milliseconds) // (value is in milliseconds)
#define PLAYER_WRITE_BEHIND_MAX 1500 #define PLAYER_WRITE_BEHIND_MAX 1500
// TODO fix me
#define TEMP_NEXT_RTPTIME (last_rtptime + pb_session.samples_written + pb_session.samples_per_read)
struct volume_param { struct volume_param {
int volume; int volume;
uint64_t spk_id; uint64_t spk_id;
@ -172,6 +185,19 @@ union player_arg
int intval; int intval;
}; };
struct player_session
{
uint8_t *buffer;
size_t bufsize;
uint32_t samples_written;
int samples_per_read;
struct media_quality quality;
};
static struct player_session pb_session;
struct event_base *evbase_player; struct event_base *evbase_player;
static int player_exit; static int player_exit;
@ -199,15 +225,15 @@ static int pb_timer_fd;
timer_t pb_timer; timer_t pb_timer;
#endif #endif
static struct event *pb_timer_ev; static struct event *pb_timer_ev;
static struct timespec pb_timer_last; //static struct timespec pb_timer_last;
static struct timespec packet_timer_last; //static struct timespec packet_timer_last;
// How often the playback timer triggers playback_cb() // How often the playback timer triggers playback_cb()
static struct timespec tick_interval; static struct timespec tick_interval;
// Timer resolution // Timer resolution
static struct timespec timer_res; static struct timespec timer_res;
// Time between two packets // Time between two packets
static struct timespec packet_time = { 0, AIRTUNES_V2_STREAM_PERIOD }; //static struct timespec packet_time = { 0, AIRTUNES_V2_STREAM_PERIOD };
// How many writes we owe the output (when the input is underrunning) // How many writes we owe the output (when the input is underrunning)
static int pb_read_deficit; static int pb_read_deficit;
@ -242,8 +268,8 @@ static uint32_t cur_plid;
static uint32_t cur_plversion; static uint32_t cur_plversion;
// Player buffer (holds one packet) // Player buffer (holds one packet)
static uint8_t pb_buffer[STOB(AIRTUNES_V2_PACKET_SAMPLES)]; //static uint8_t pb_buffer[STOB(AIRTUNES_V2_PACKET_SAMPLES)];
static size_t pb_buffer_offset; //static size_t pb_buffer_offset;
// Play history // Play history
static struct player_history *history; static struct player_history *history;
@ -443,7 +469,7 @@ metadata_trigger(int startup)
struct input_metadata metadata; struct input_metadata metadata;
int ret; int ret;
ret = input_metadata_get(&metadata, cur_streaming, startup, last_rtptime + AIRTUNES_V2_PACKET_SAMPLES); ret = input_metadata_get(&metadata, cur_streaming, startup, TEMP_NEXT_RTPTIME);
if (ret < 0) if (ret < 0)
return; return;
@ -652,8 +678,8 @@ source_pause(uint64_t pos)
// TODO what if ret < 0? // TODO what if ret < 0?
// Adjust start_pos to take into account the pause and seek back // Adjust start_pos to take into account the pause and seek back
cur_streaming->stream_start = last_rtptime + AIRTUNES_V2_PACKET_SAMPLES - ((uint64_t)ret * 44100) / 1000; cur_streaming->stream_start = TEMP_NEXT_RTPTIME - ((uint64_t)ret * 44100) / 1000;
cur_streaming->output_start = last_rtptime + AIRTUNES_V2_PACKET_SAMPLES; cur_streaming->output_start = TEMP_NEXT_RTPTIME;
cur_streaming->end = 0; cur_streaming->end = 0;
return 0; return 0;
@ -676,8 +702,8 @@ source_seek(int seek_ms)
return -1; return -1;
// Adjust start_pos to take into account the pause and seek back // Adjust start_pos to take into account the pause and seek back
cur_streaming->stream_start = last_rtptime + AIRTUNES_V2_PACKET_SAMPLES - ((uint64_t)ret * 44100) / 1000; cur_streaming->stream_start = TEMP_NEXT_RTPTIME - ((uint64_t)ret * 44100) / 1000;
cur_streaming->output_start = last_rtptime + AIRTUNES_V2_PACKET_SAMPLES; cur_streaming->output_start = TEMP_NEXT_RTPTIME;
return ret; return ret;
} }
@ -945,7 +971,7 @@ source_switch(int nbytes)
DPRINTF(E_DBG, L_PLAYER, "Switching track\n"); DPRINTF(E_DBG, L_PLAYER, "Switching track\n");
source_close(last_rtptime + AIRTUNES_V2_PACKET_SAMPLES + BTOS(nbytes) - 1); source_close(TEMP_NEXT_RTPTIME + BTOS(nbytes, pb_session.quality.bits_per_sample, 2) - 1);
while ((ps = source_next())) while ((ps = source_next()))
{ {
@ -960,7 +986,7 @@ source_switch(int nbytes)
if (ret < 0) if (ret < 0)
{ {
db_queue_delete_byitemid(ps->item_id); db_queue_delete_byitemid(ps->item_id);
source_close(last_rtptime + AIRTUNES_V2_PACKET_SAMPLES + BTOS(nbytes) - 1); source_close(TEMP_NEXT_RTPTIME + BTOS(nbytes, pb_session.quality.bits_per_sample, 2) - 1);
continue; continue;
} }
@ -978,6 +1004,32 @@ source_switch(int nbytes)
return 0; return 0;
} }
static void
session_init(struct player_session *session, struct media_quality *quality)
{
session->samples_written = 0;
session->quality = *quality;
session->samples_per_read = (quality->sample_rate / 1000) * (tick_interval.tv_nsec / 1000000);
session->bufsize = STOB(session->samples_per_read, quality->bits_per_sample, quality->channels);
DPRINTF(E_DBG, L_PLAYER, "New session values (q=%d/%d/%d, spr=%d, bufsize=%zu)\n",
quality->sample_rate, quality->bits_per_sample, quality->channels,
session->samples_per_read, session->bufsize);
if (session->buffer)
session->buffer = realloc(session->buffer, session->bufsize);
else
session->buffer = malloc(session->bufsize);
CHECK_NULL(L_PLAYER, session->buffer);
}
static void
session_deinit(struct player_session *session)
{
free(session->buffer);
memset(session, 0, sizeof(struct player_session));
}
/* ----------------- Main read, write and playback timer event -------------- */ /* ----------------- Main read, write and playback timer event -------------- */
@ -985,6 +1037,7 @@ source_switch(int nbytes)
static int static int
source_read(uint8_t *buf, int len) source_read(uint8_t *buf, int len)
{ {
struct media_quality quality;
int nbytes; int nbytes;
uint32_t item_id; uint32_t item_id;
int ret; int ret;
@ -1019,6 +1072,11 @@ source_read(uint8_t *buf, int len)
{ {
metadata_trigger(0); metadata_trigger(0);
} }
else if (flags & INPUT_FLAG_QUALITY)
{
input_quality_get(&quality);
session_init(&pb_session, &quality);
}
// We pad the output buffer with silence if we don't have enough data for a // We pad the output buffer with silence if we don't have enough data for a
// full packet and there is no more data coming up (no more tracks in queue) // full packet and there is no more data coming up (no more tracks in queue)
@ -1031,56 +1089,13 @@ source_read(uint8_t *buf, int len)
return nbytes; return nbytes;
} }
static void
playback_write(void)
{
int want;
int got;
source_check();
// Make sure playback is still running after source_check()
if (player_state == PLAY_STOPPED)
return;
pb_read_deficit++;
while (pb_read_deficit)
{
want = sizeof(pb_buffer) - pb_buffer_offset;
got = source_read(pb_buffer + pb_buffer_offset, want);
if (got == want)
{
pb_read_deficit--;
last_rtptime += AIRTUNES_V2_PACKET_SAMPLES;
outputs_write(pb_buffer, last_rtptime);
pb_buffer_offset = 0;
}
else if (got < 0)
{
DPRINTF(E_LOG, L_PLAYER, "Error reading from source, aborting playback\n");
playback_abort();
return;
}
else if (pb_read_deficit > pb_read_deficit_max)
{
DPRINTF(E_LOG, L_PLAYER, "Source is not providing sufficient data, temporarily suspending playback (deficit=%d)\n", pb_read_deficit);
playback_suspend();
return;
}
else
{
DPRINTF(E_SPAM, L_PLAYER, "Partial read (offset=%zu, deficit=%d)\n", pb_buffer_offset, pb_read_deficit);
pb_buffer_offset += got;
return;
}
}
}
static void static void
playback_cb(int fd, short what, void *arg) playback_cb(int fd, short what, void *arg)
{ {
struct timespec next_tick;
uint64_t overrun; uint64_t overrun;
int got;
int nsamples;
int i;
int ret; int ret;
// Check if we missed any timer expirations // Check if we missed any timer expirations
@ -1125,22 +1140,45 @@ playback_cb(int fd, short what, void *arg)
// If there was an overrun, we will try to read/write a corresponding number // If there was an overrun, we will try to read/write a corresponding number
// of times so we catch up. The read from the input is non-blocking, so it // of times so we catch up. The read from the input is non-blocking, so it
// should not bring us further behind, even if there is no data. // should not bring us further behind, even if there is no data.
next_tick = timespec_add(pb_timer_last, tick_interval); for (i = 1 + overrun + pb_read_deficit; i > 0; i--)
for (; overrun > 0; overrun--)
next_tick = timespec_add(next_tick, tick_interval);
do
{ {
playback_write(); source_check();
packet_timer_last = timespec_add(packet_timer_last, packet_time);
// Make sure playback is still running after source_check()
if (player_state != PLAY_PLAYING)
return;
got = source_read(pb_session.buffer, pb_session.bufsize);
if (got < 0)
{
DPRINTF(E_LOG, L_PLAYER, "Error reading from source, aborting playback\n");
playback_abort();
break;
}
else if (got == 0)
{
pb_read_deficit++;
break;
}
nsamples = BTOS(got, pb_session.quality.bits_per_sample, pb_session.quality.channels);
outputs_write2(pb_session.buffer, pb_session.bufsize, &pb_session.quality, nsamples);
pb_session.samples_written += nsamples;
if (got < pb_session.bufsize)
{
DPRINTF(E_DBG, L_PLAYER, "Incomplete read, wanted %zu, got %d\n", pb_session.bufsize, got);
pb_read_deficit++;
}
else if (pb_read_deficit > 0)
pb_read_deficit--;
} }
while ((timespec_cmp(packet_timer_last, next_tick) < 0) && (player_state == PLAY_PLAYING));
// Make sure playback is still running if (pb_read_deficit > pb_read_deficit_max)
if (player_state == PLAY_STOPPED) {
return; DPRINTF(E_LOG, L_PLAYER, "Source is not providing sufficient data, temporarily suspending playback (deficit=%d)\n", pb_read_deficit);
playback_suspend();
pb_timer_last = next_tick; }
} }
@ -1576,15 +1614,9 @@ device_activate_cb(struct output_device *device, struct output_session *session,
{ {
ret = clock_gettime_with_res(CLOCK_MONOTONIC, &ts, &timer_res); ret = clock_gettime_with_res(CLOCK_MONOTONIC, &ts, &timer_res);
if (ret < 0) if (ret < 0)
{ DPRINTF(E_LOG, L_PLAYER, "Could not get current time: %s\n", strerror(errno));
DPRINTF(E_LOG, L_PLAYER, "Could not get current time: %s\n", strerror(errno)); else
outputs_playback_start2(&ts);
// Fallback to nearest timer expiration time
ts.tv_sec = pb_timer_last.tv_sec;
ts.tv_nsec = pb_timer_last.tv_nsec;
}
outputs_playback_start(last_rtptime + AIRTUNES_V2_PACKET_SAMPLES, &ts);
} }
outputs_status_cb(session, device_streaming_cb); outputs_status_cb(session, device_streaming_cb);
@ -1775,7 +1807,7 @@ playback_abort(void)
static void static void
playback_suspend(void) playback_suspend(void)
{ {
player_flush_pending = outputs_flush(device_command_cb, last_rtptime + AIRTUNES_V2_PACKET_SAMPLES); player_flush_pending = outputs_flush(device_command_cb, TEMP_NEXT_RTPTIME);
playback_timer_stop(); playback_timer_stop();
@ -1825,7 +1857,7 @@ get_status(void *arg, int *retval)
status->id = cur_streaming->id; status->id = cur_streaming->id;
status->item_id = cur_streaming->item_id; status->item_id = cur_streaming->item_id;
pos = last_rtptime + AIRTUNES_V2_PACKET_SAMPLES - cur_streaming->stream_start; pos = TEMP_NEXT_RTPTIME - cur_streaming->stream_start;
status->pos_ms = (pos * 1000) / 44100; status->pos_ms = (pos * 1000) / 44100;
status->len_ms = cur_streaming->len_ms; status->len_ms = cur_streaming->len_ms;
@ -1909,7 +1941,7 @@ playback_stop(void *arg, int *retval)
// We may be restarting very soon, so we don't bring the devices to a full // We may be restarting very soon, so we don't bring the devices to a full
// stop just yet; this saves time when restarting, which is nicer for the user // stop just yet; this saves time when restarting, which is nicer for the user
*retval = outputs_flush(device_command_cb, last_rtptime + AIRTUNES_V2_PACKET_SAMPLES); *retval = outputs_flush(device_command_cb, TEMP_NEXT_RTPTIME);
playback_timer_stop(); playback_timer_stop();
@ -1935,35 +1967,29 @@ playback_stop(void *arg, int *retval)
static enum command_state static enum command_state
playback_start_bh(void *arg, int *retval) playback_start_bh(void *arg, int *retval)
{ {
struct timespec ts;
int ret; int ret;
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));
goto out_fail;
}
playback_timer_stop();
// initialize the packet timer to the same relative time that we have // initialize the packet timer to the same relative time that we have
// for the playback timer. // for the playback timer.
packet_timer_last.tv_sec = pb_pos_stamp.tv_sec; // packet_timer_last.tv_sec = pb_pos_stamp.tv_sec;
packet_timer_last.tv_nsec = pb_pos_stamp.tv_nsec; // packet_timer_last.tv_nsec = pb_pos_stamp.tv_nsec;
pb_timer_last.tv_sec = pb_pos_stamp.tv_sec; // pb_timer_last.tv_sec = pb_pos_stamp.tv_sec;
pb_timer_last.tv_nsec = pb_pos_stamp.tv_nsec; // pb_timer_last.tv_nsec = pb_pos_stamp.tv_nsec;
pb_buffer_offset = 0; // pb_buffer_offset = 0;
pb_read_deficit = 0; pb_read_deficit = 0;
ret = clock_gettime_with_res(CLOCK_MONOTONIC, &ts, &timer_res);
if (ret < 0)
goto out_fail;
ret = playback_timer_start(); ret = playback_timer_start();
if (ret < 0) if (ret < 0)
goto out_fail; goto out_fail;
// Everything OK, start outputs outputs_playback_start2(&ts);
outputs_playback_start(last_rtptime + AIRTUNES_V2_PACKET_SAMPLES, &pb_pos_stamp);
status_update(PLAY_PLAYING); status_update(PLAY_PLAYING);
@ -1998,7 +2024,7 @@ playback_start_item(void *arg, int *retval)
} }
// Update global playback position // Update global playback position
pb_pos = last_rtptime + AIRTUNES_V2_PACKET_SAMPLES - 88200; pb_pos = TEMP_NEXT_RTPTIME - 88200;
if (player_state == PLAY_STOPPED && !queue_item) if (player_state == PLAY_STOPPED && !queue_item)
{ {
@ -2039,7 +2065,7 @@ playback_start_item(void *arg, int *retval)
} }
} }
ret = source_open(ps, last_rtptime + AIRTUNES_V2_PACKET_SAMPLES, seek_ms); ret = source_open(ps, TEMP_NEXT_RTPTIME, seek_ms);
if (ret < 0) if (ret < 0)
{ {
playback_abort(); playback_abort();
@ -2066,7 +2092,7 @@ playback_start_item(void *arg, int *retval)
{ {
if (device->selected && !device->session) if (device->selected && !device->session)
{ {
ret = outputs_device_start(device, device_restart_cb, last_rtptime + AIRTUNES_V2_PACKET_SAMPLES); ret = outputs_device_start(device, device_restart_cb, TEMP_NEXT_RTPTIME);
if (ret < 0) if (ret < 0)
{ {
DPRINTF(E_LOG, L_PLAYER, "Could not start selected %s device '%s'\n", device->type_name, device->name); DPRINTF(E_LOG, L_PLAYER, "Could not start selected %s device '%s'\n", device->type_name, device->name);
@ -2086,7 +2112,7 @@ playback_start_item(void *arg, int *retval)
continue; continue;
speaker_select_output(device); speaker_select_output(device);
ret = outputs_device_start(device, device_restart_cb, last_rtptime + AIRTUNES_V2_PACKET_SAMPLES); ret = outputs_device_start(device, device_restart_cb, TEMP_NEXT_RTPTIME);
if (ret < 0) if (ret < 0)
{ {
DPRINTF(E_DBG, L_PLAYER, "Could not autoselect %s device '%s'\n", device->type_name, device->name); DPRINTF(E_DBG, L_PLAYER, "Could not autoselect %s device '%s'\n", device->type_name, device->name);
@ -2202,7 +2228,7 @@ playback_prev_bh(void *arg, int *retval)
source_stop(); source_stop();
ret = source_open(ps, last_rtptime + AIRTUNES_V2_PACKET_SAMPLES, 0); ret = source_open(ps, TEMP_NEXT_RTPTIME, 0);
if (ret < 0) if (ret < 0)
{ {
source_free(ps); source_free(ps);
@ -2275,7 +2301,7 @@ playback_next_bh(void *arg, int *retval)
source_stop(); source_stop();
ret = source_open(ps, last_rtptime + AIRTUNES_V2_PACKET_SAMPLES, 0); ret = source_open(ps, TEMP_NEXT_RTPTIME, 0);
if (ret < 0) if (ret < 0)
{ {
source_free(ps); source_free(ps);
@ -2388,7 +2414,7 @@ playback_pause(void *arg, int *retval)
return COMMAND_END; return COMMAND_END;
} }
*retval = outputs_flush(device_command_cb, last_rtptime + AIRTUNES_V2_PACKET_SAMPLES); *retval = outputs_flush(device_command_cb, TEMP_NEXT_RTPTIME);
playback_timer_stop(); playback_timer_stop();
@ -2498,7 +2524,7 @@ speaker_activate(struct output_device *device)
{ {
DPRINTF(E_DBG, L_PLAYER, "Activating %s device '%s'\n", device->type_name, device->name); DPRINTF(E_DBG, L_PLAYER, "Activating %s device '%s'\n", device->type_name, device->name);
ret = outputs_device_start(device, device_activate_cb, last_rtptime + AIRTUNES_V2_PACKET_SAMPLES); ret = outputs_device_start(device, device_activate_cb, TEMP_NEXT_RTPTIME);
if (ret < 0) if (ret < 0)
{ {
DPRINTF(E_LOG, L_PLAYER, "Could not start %s device '%s'\n", device->type_name, device->name); DPRINTF(E_LOG, L_PLAYER, "Could not start %s device '%s'\n", device->type_name, device->name);
@ -3007,6 +3033,21 @@ player_get_current_pos(uint64_t *pos, struct timespec *ts, int commit)
return 0; return 0;
} }
int
player_get_time(struct timespec *ts)
{
int ret;
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));
return -1;
}
return 0;
}
int int
player_get_status(struct player_status *status) player_get_status(struct player_status *status)
{ {
@ -3444,32 +3485,20 @@ player(void *arg)
int int
player_init(void) player_init(void)
{ {
struct media_quality default_quality = { 44100, 16, 2 };
uint64_t interval; uint64_t interval;
uint32_t rnd; uint32_t rnd;
int ret; int ret;
player_exit = 0;
speaker_autoselect = cfg_getbool(cfg_getsec(cfg, "general"), "speaker_autoselect"); speaker_autoselect = cfg_getbool(cfg_getsec(cfg, "general"), "speaker_autoselect");
clear_queue_on_stop_disabled = cfg_getbool(cfg_getsec(cfg, "mpd"), "clear_queue_on_stop_disable"); clear_queue_on_stop_disabled = cfg_getbool(cfg_getsec(cfg, "mpd"), "clear_queue_on_stop_disable");
dev_list = NULL;
master_volume = -1; master_volume = -1;
output_sessions = 0;
cur_playing = NULL;
cur_streaming = NULL;
cur_plid = 0;
cur_plversion = 0;
player_state = PLAY_STOPPED; player_state = PLAY_STOPPED;
repeat = REPEAT_OFF; repeat = REPEAT_OFF;
shuffle = 0;
consume = 0;
history = (struct player_history *)calloc(1, sizeof(struct player_history)); history = calloc(1, sizeof(struct player_history));
// Determine if the resolution of the system timer is > or < the size // Determine if the resolution of the system timer is > or < the size
// of an audio packet. NOTE: this assumes the system clock resolution // of an audio packet. NOTE: this assumes the system clock resolution
@ -3485,11 +3514,11 @@ player_init(void)
{ {
DPRINTF(E_INFO, L_PLAYER, "High resolution clock not enabled on this system (res is %ld)\n", timer_res.tv_nsec); DPRINTF(E_INFO, L_PLAYER, "High resolution clock not enabled on this system (res is %ld)\n", timer_res.tv_nsec);
timer_res.tv_nsec = 2 * AIRTUNES_V2_STREAM_PERIOD; timer_res.tv_nsec = 10 * PLAYER_TICK_INTERVAL * 1000000;
} }
// Set the tick interval for the playback timer // Set the tick interval for the playback timer
interval = MAX(timer_res.tv_nsec, AIRTUNES_V2_STREAM_PERIOD); interval = MAX(timer_res.tv_nsec, PLAYER_TICK_INTERVAL * 1000000);
tick_interval.tv_nsec = interval; tick_interval.tv_nsec = interval;
pb_write_deficit_max = (PLAYER_WRITE_BEHIND_MAX * 1000000 / interval); pb_write_deficit_max = (PLAYER_WRITE_BEHIND_MAX * 1000000 / interval);
@ -3532,6 +3561,8 @@ player_init(void)
goto evnew_fail; goto evnew_fail;
} }
session_init(&pb_session, &default_quality);
cmdbase = commands_base_new(evbase_player, NULL); cmdbase = commands_base_new(evbase_player, NULL);
ret = outputs_init(); ret = outputs_init();
@ -3568,6 +3599,7 @@ player_init(void)
outputs_deinit(); outputs_deinit();
outputs_fail: outputs_fail:
commands_base_free(cmdbase); commands_base_free(cmdbase);
session_deinit(&pb_session);
evnew_fail: evnew_fail:
event_base_free(evbase_player); event_base_free(evbase_player);
evbase_fail: evbase_fail:
@ -3600,6 +3632,8 @@ player_deinit(void)
player_exit = 1; player_exit = 1;
commands_base_destroy(cmdbase); commands_base_destroy(cmdbase);
session_deinit(&pb_session);
ret = pthread_join(tid_player, NULL); ret = pthread_join(tid_player, NULL);
if (ret != 0) if (ret != 0)
{ {

View File

@ -7,15 +7,6 @@
#include "db.h" #include "db.h"
// AirTunes v2 packet interval in ns */
// (352 samples/packet * 1e9 ns/s) / 44100 samples/s = 7981859 ns/packet
#define AIRTUNES_V2_STREAM_PERIOD 7981859
// AirTunes v2 number of samples per packet
// Probably using this value because 44100/352 and 48000/352 has good 32 byte
// alignment, which improves performance of some encoders
#define AIRTUNES_V2_PACKET_SAMPLES 352
// Maximum number of previously played songs that are remembered // Maximum number of previously played songs that are remembered
#define MAX_HISTORY_COUNT 20 #define MAX_HISTORY_COUNT 20
@ -85,6 +76,9 @@ struct player_history
int int
player_get_current_pos(uint64_t *pos, struct timespec *ts, int commit); player_get_current_pos(uint64_t *pos, struct timespec *ts, int commit);
int
player_get_time(struct timespec *ts);
int int
player_get_status(struct player_status *status); player_get_status(struct player_status *status);