[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 "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
// ahead) of the player position, before compensation is attempted
#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.
// Instead we allocate our own buffer, and when it is time to play we write as
// much as we can to alsa's buffer.
as->prebuf_len = (start_pos - pos) / AIRTUNES_V2_PACKET_SAMPLES + 1;
if (as->prebuf_len > (3 * 44100 - offset) / AIRTUNES_V2_PACKET_SAMPLES)
as->prebuf_len = (start_pos - pos) / ALSA_SAMPLES_PER_PACKET + 1;
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);
return;
}
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)
{
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->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
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 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;
if (prebuffering || *avail < AIRTUNES_V2_PACKET_SAMPLES)
if (prebuffering || *avail < ALSA_SAMPLES_PER_PACKET)
return 0; // No actual writing
// 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)
npackets = as->prebuf_head - as->prebuf_tail;
else
npackets = as->prebuf_len - as->prebuf_tail;
nsamp = npackets * AIRTUNES_V2_PACKET_SAMPLES;
nsamp = npackets * ALSA_SAMPLES_PER_PACKET;
while (nsamp > *avail)
{
npackets -= 1;
nsamp -= AIRTUNES_V2_PACKET_SAMPLES;
nsamp -= ALSA_SAMPLES_PER_PACKET;
}
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
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);
// 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);
prebuf_empty = (as->prebuf_head == as->prebuf_tail);
as->pos += AIRTUNES_V2_PACKET_SAMPLES;
as->pos += ALSA_SAMPLES_PER_PACKET;
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
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;
}
@ -799,7 +802,7 @@ playback_pos_get(uint64_t *pos, uint64_t next_pkt)
// Make pos the rtptime of the packet containing cur_pos
*pos = next_pkt;
while (*pos > cur_pos)
*pos -= AIRTUNES_V2_PACKET_SAMPLES;
*pos -= ALSA_SAMPLES_PER_PACKET;
}
/* ------------------ INTERFACE FUNCTIONS CALLED BY OUTPUTS.C --------------- */

View File

@ -39,13 +39,13 @@
#include "player.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
{
/* pcm data */
uint8_t samples[1408]; // STOB(AIRTUNES_V2_PACKET_SAMPLES)
uint8_t samples[FIFO_PACKET_SIZE];
/* RTP-time of the first sample*/
uint64_t rtptime;
@ -453,7 +453,7 @@ static void
fifo_write(uint8_t *buf, uint64_t rtptime)
{
struct fifo_session *fifo_session = sessions;
size_t length = STOB(AIRTUNES_V2_PACKET_SAMPLES);
size_t length = FIFO_PACKET_SIZE;
ssize_t bytes;
struct fifo_packet *packet;
uint64_t cur_pos;

View File

@ -38,6 +38,8 @@
#include "outputs.h"
#include "commands.h"
// From Airplay
#define PULSE_SAMPLES_PER_PACKET 352
#define PULSE_MAX_DEVICES 64
#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;
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.prebuf = (uint32_t)-1;
ps->attr.minreq = (uint32_t)-1;
@ -760,7 +762,7 @@ pulse_write(uint8_t *buf, uint64_t rtptime)
if (!sessions)
return;
length = STOB(AIRTUNES_V2_PACKET_SAMPLES);
length = STOB(PULSE_SAMPLES_PER_PACKET, 16, 2);
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)
#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
// 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.
// (value is in milliseconds and should be low enough to avoid output underrun)
#define PLAYER_READ_BEHIND_MAX 1500
// 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
// by extension playback_cb(). We will try to catch up, but if the delay
@ -124,6 +134,9 @@
// (value is in milliseconds)
#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 {
int volume;
uint64_t spk_id;
@ -172,6 +185,19 @@ union player_arg
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;
static int player_exit;
@ -199,15 +225,15 @@ static int pb_timer_fd;
timer_t pb_timer;
#endif
static struct event *pb_timer_ev;
static struct timespec pb_timer_last;
static struct timespec packet_timer_last;
//static struct timespec pb_timer_last;
//static struct timespec packet_timer_last;
// How often the playback timer triggers playback_cb()
static struct timespec tick_interval;
// Timer resolution
static struct timespec timer_res;
// 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)
static int pb_read_deficit;
@ -242,8 +268,8 @@ static uint32_t cur_plid;
static uint32_t cur_plversion;
// Player buffer (holds one packet)
static uint8_t pb_buffer[STOB(AIRTUNES_V2_PACKET_SAMPLES)];
static size_t pb_buffer_offset;
//static uint8_t pb_buffer[STOB(AIRTUNES_V2_PACKET_SAMPLES)];
//static size_t pb_buffer_offset;
// Play history
static struct player_history *history;
@ -443,7 +469,7 @@ metadata_trigger(int startup)
struct input_metadata metadata;
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)
return;
@ -652,8 +678,8 @@ source_pause(uint64_t pos)
// TODO what if ret < 0?
// 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->output_start = last_rtptime + AIRTUNES_V2_PACKET_SAMPLES;
cur_streaming->stream_start = TEMP_NEXT_RTPTIME - ((uint64_t)ret * 44100) / 1000;
cur_streaming->output_start = TEMP_NEXT_RTPTIME;
cur_streaming->end = 0;
return 0;
@ -676,8 +702,8 @@ source_seek(int seek_ms)
return -1;
// 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->output_start = last_rtptime + AIRTUNES_V2_PACKET_SAMPLES;
cur_streaming->stream_start = TEMP_NEXT_RTPTIME - ((uint64_t)ret * 44100) / 1000;
cur_streaming->output_start = TEMP_NEXT_RTPTIME;
return ret;
}
@ -945,7 +971,7 @@ source_switch(int nbytes)
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()))
{
@ -960,7 +986,7 @@ source_switch(int nbytes)
if (ret < 0)
{
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;
}
@ -978,6 +1004,32 @@ source_switch(int nbytes)
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 -------------- */
@ -985,6 +1037,7 @@ source_switch(int nbytes)
static int
source_read(uint8_t *buf, int len)
{
struct media_quality quality;
int nbytes;
uint32_t item_id;
int ret;
@ -1019,6 +1072,11 @@ source_read(uint8_t *buf, int len)
{
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
// 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;
}
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
playback_cb(int fd, short what, void *arg)
{
struct timespec next_tick;
uint64_t overrun;
int got;
int nsamples;
int i;
int ret;
// 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
// 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.
next_tick = timespec_add(pb_timer_last, tick_interval);
for (; overrun > 0; overrun--)
next_tick = timespec_add(next_tick, tick_interval);
do
for (i = 1 + overrun + pb_read_deficit; i > 0; i--)
{
playback_write();
packet_timer_last = timespec_add(packet_timer_last, packet_time);
source_check();
// 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 (player_state == PLAY_STOPPED)
return;
pb_timer_last = next_tick;
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();
}
}
@ -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);
if (ret < 0)
{
DPRINTF(E_LOG, L_PLAYER, "Could not get current time: %s\n", strerror(errno));
// 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);
DPRINTF(E_LOG, L_PLAYER, "Could not get current time: %s\n", strerror(errno));
else
outputs_playback_start2(&ts);
}
outputs_status_cb(session, device_streaming_cb);
@ -1775,7 +1807,7 @@ playback_abort(void)
static 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();
@ -1825,7 +1857,7 @@ get_status(void *arg, int *retval)
status->id = cur_streaming->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->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
// 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();
@ -1935,35 +1967,29 @@ playback_stop(void *arg, int *retval)
static enum command_state
playback_start_bh(void *arg, int *retval)
{
struct timespec ts;
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
// for the playback timer.
packet_timer_last.tv_sec = pb_pos_stamp.tv_sec;
packet_timer_last.tv_nsec = pb_pos_stamp.tv_nsec;
// 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;
// pb_timer_last.tv_sec = pb_pos_stamp.tv_sec;
// pb_timer_last.tv_nsec = pb_pos_stamp.tv_nsec;
pb_buffer_offset = 0;
// pb_buffer_offset = 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();
if (ret < 0)
goto out_fail;
// Everything OK, start outputs
outputs_playback_start(last_rtptime + AIRTUNES_V2_PACKET_SAMPLES, &pb_pos_stamp);
outputs_playback_start2(&ts);
status_update(PLAY_PLAYING);
@ -1998,7 +2024,7 @@ playback_start_item(void *arg, int *retval)
}
// 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)
{
@ -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)
{
playback_abort();
@ -2066,7 +2092,7 @@ playback_start_item(void *arg, int *retval)
{
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)
{
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;
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)
{
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();
ret = source_open(ps, last_rtptime + AIRTUNES_V2_PACKET_SAMPLES, 0);
ret = source_open(ps, TEMP_NEXT_RTPTIME, 0);
if (ret < 0)
{
source_free(ps);
@ -2275,7 +2301,7 @@ playback_next_bh(void *arg, int *retval)
source_stop();
ret = source_open(ps, last_rtptime + AIRTUNES_V2_PACKET_SAMPLES, 0);
ret = source_open(ps, TEMP_NEXT_RTPTIME, 0);
if (ret < 0)
{
source_free(ps);
@ -2388,7 +2414,7 @@ playback_pause(void *arg, int *retval)
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();
@ -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);
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)
{
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;
}
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
player_get_status(struct player_status *status)
{
@ -3444,32 +3485,20 @@ player(void *arg)
int
player_init(void)
{
struct media_quality default_quality = { 44100, 16, 2 };
uint64_t interval;
uint32_t rnd;
int ret;
player_exit = 0;
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");
dev_list = NULL;
master_volume = -1;
output_sessions = 0;
cur_playing = NULL;
cur_streaming = NULL;
cur_plid = 0;
cur_plversion = 0;
player_state = PLAY_STOPPED;
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
// 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);
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
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;
pb_write_deficit_max = (PLAYER_WRITE_BEHIND_MAX * 1000000 / interval);
@ -3532,6 +3561,8 @@ player_init(void)
goto evnew_fail;
}
session_init(&pb_session, &default_quality);
cmdbase = commands_base_new(evbase_player, NULL);
ret = outputs_init();
@ -3568,6 +3599,7 @@ player_init(void)
outputs_deinit();
outputs_fail:
commands_base_free(cmdbase);
session_deinit(&pb_session);
evnew_fail:
event_base_free(evbase_player);
evbase_fail:
@ -3600,6 +3632,8 @@ player_deinit(void)
player_exit = 1;
commands_base_destroy(cmdbase);
session_deinit(&pb_session);
ret = pthread_join(tid_player, NULL);
if (ret != 0)
{

View File

@ -7,15 +7,6 @@
#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
#define MAX_HISTORY_COUNT 20
@ -85,6 +76,9 @@ struct player_history
int
player_get_current_pos(uint64_t *pos, struct timespec *ts, int commit);
int
player_get_time(struct timespec *ts);
int
player_get_status(struct player_status *status);