[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:
parent
cdd0aa884b
commit
fcc91ecd86
|
@ -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 --------------- */
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
284
src/player.c
284
src/player.c
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
12
src/player.h
12
src/player.h
|
@ -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);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue