mirror of
https://github.com/owntone/owntone-server.git
synced 2024-12-29 00:23:23 -05:00
Merge pull request #1663 from owntone/esp32
[raop] Make compressed ALAC default, but with a config option
This commit is contained in:
commit
67de2303f9
@ -315,6 +315,10 @@ audio {
|
|||||||
# OwnTone behind a firewall)
|
# OwnTone behind a firewall)
|
||||||
# control_port = 0
|
# control_port = 0
|
||||||
# timing_port = 0
|
# timing_port = 0
|
||||||
|
|
||||||
|
# Switch Airplay 1 streams to uncompressed ALAC (as opposed to regular,
|
||||||
|
# compressed ALAC). Reduces CPU use at the cost of network bandwidth.
|
||||||
|
# uncompressed_alac = false
|
||||||
#}
|
#}
|
||||||
|
|
||||||
# AirPlay per device settings
|
# AirPlay per device settings
|
||||||
|
@ -156,6 +156,7 @@ static cfg_opt_t sec_airplay_shared[] =
|
|||||||
{
|
{
|
||||||
CFG_INT("control_port", 0, CFGF_NONE),
|
CFG_INT("control_port", 0, CFGF_NONE),
|
||||||
CFG_INT("timing_port", 0, CFGF_NONE),
|
CFG_INT("timing_port", 0, CFGF_NONE),
|
||||||
|
CFG_BOOL("uncompressed_alac", cfg_false, CFGF_NONE),
|
||||||
CFG_END()
|
CFG_END()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -68,6 +68,7 @@
|
|||||||
#include "artwork.h"
|
#include "artwork.h"
|
||||||
#include "dmap_common.h"
|
#include "dmap_common.h"
|
||||||
#include "rtp_common.h"
|
#include "rtp_common.h"
|
||||||
|
#include "transcode.h"
|
||||||
#include "outputs.h"
|
#include "outputs.h"
|
||||||
#include "pair_ap/pair.h"
|
#include "pair_ap/pair.h"
|
||||||
|
|
||||||
@ -154,18 +155,24 @@ struct raop_extra
|
|||||||
|
|
||||||
struct raop_master_session
|
struct raop_master_session
|
||||||
{
|
{
|
||||||
struct evbuffer *evbuf;
|
struct evbuffer *input_buffer;
|
||||||
int evbuf_samples;
|
int input_buffer_samples;
|
||||||
|
|
||||||
struct rtp_session *rtp_session;
|
struct rtp_session *rtp_session;
|
||||||
|
|
||||||
struct rtcp_timestamp cur_stamp;
|
struct rtcp_timestamp cur_stamp;
|
||||||
|
|
||||||
|
// ALAC encoder and buffer for encoded data
|
||||||
|
struct encode_ctx *encode_ctx;
|
||||||
|
struct evbuffer *encoded_buffer;
|
||||||
|
|
||||||
uint8_t *rawbuf;
|
uint8_t *rawbuf;
|
||||||
size_t rawbuf_size;
|
size_t rawbuf_size;
|
||||||
int samples_per_packet;
|
int samples_per_packet;
|
||||||
bool encrypt;
|
bool encrypt;
|
||||||
|
|
||||||
|
struct media_quality quality;
|
||||||
|
|
||||||
// Number of samples that we tell the output to buffer (this will mean that
|
// Number of samples that we tell the output to buffer (this will mean that
|
||||||
// the position that we send in the sync packages are offset by this amount
|
// the position that we send in the sync packages are offset by this amount
|
||||||
// compared to the rtptimes of the corresponding RTP packages we are sending)
|
// compared to the rtptimes of the corresponding RTP packages we are sending)
|
||||||
@ -332,6 +339,9 @@ static struct timeval keep_alive_tv = { RAOP_KEEP_ALIVE_INTERVAL, 0 };
|
|||||||
static struct raop_master_session *raop_master_sessions;
|
static struct raop_master_session *raop_master_sessions;
|
||||||
static struct raop_session *raop_sessions;
|
static struct raop_session *raop_sessions;
|
||||||
|
|
||||||
|
/* Don't encode ALAC with ffmpeg */
|
||||||
|
static bool raop_uncompressed_alac;
|
||||||
|
|
||||||
// Forwards
|
// Forwards
|
||||||
static int
|
static int
|
||||||
raop_device_start(struct output_device *rd, int callback_id);
|
raop_device_start(struct output_device *rd, int callback_id);
|
||||||
@ -390,7 +400,7 @@ alac_write_bits(uint8_t **p, uint8_t val, int blen, int *bpos)
|
|||||||
|
|
||||||
/* Raw data must be little endian */
|
/* Raw data must be little endian */
|
||||||
static void
|
static void
|
||||||
alac_encode(uint8_t *dst, uint8_t *raw, int len)
|
alac_encode_uncompressed(uint8_t *dst, uint8_t *raw, int len)
|
||||||
{
|
{
|
||||||
uint8_t *maxraw;
|
uint8_t *maxraw;
|
||||||
int bpos;
|
int bpos;
|
||||||
@ -419,6 +429,59 @@ alac_encode(uint8_t *dst, uint8_t *raw, int len)
|
|||||||
alac_write_bits(&dst, 7, 3, &bpos); /* end tag */
|
alac_write_bits(&dst, 7, 3, &bpos); /* end tag */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
alac_encode_no_xcode(struct evbuffer *evbuf, uint8_t *rawbuf, size_t rawbuf_size)
|
||||||
|
{
|
||||||
|
#define BODY_LEN STOB(RAOP_SAMPLES_PER_PACKET, RAOP_QUALITY_BITS_PER_SAMPLE_DEFAULT, RAOP_QUALITY_CHANNELS_DEFAULT)
|
||||||
|
// the "+ 1" above is space for the end tag (3 bits)
|
||||||
|
uint8_t dst[ALAC_HEADER_LEN + BODY_LEN + 1];
|
||||||
|
int len = ALAC_HEADER_LEN + rawbuf_size + 1;
|
||||||
|
|
||||||
|
if (len > sizeof(dst))
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_RAOP, "Bug! Invalid input to ALAC encoder, destination buffer would overflow\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
alac_encode_uncompressed(dst, rawbuf, rawbuf_size);
|
||||||
|
evbuffer_add(evbuf, dst, len);
|
||||||
|
return len;
|
||||||
|
#undef BODY_LEN
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
alac_encode_xcode(struct evbuffer *evbuf, struct encode_ctx *encode_ctx, uint8_t *rawbuf, size_t rawbuf_size, int nsamples, struct media_quality *quality)
|
||||||
|
{
|
||||||
|
transcode_frame *frame;
|
||||||
|
int len;
|
||||||
|
|
||||||
|
frame = transcode_frame_new(rawbuf, rawbuf_size, nsamples, quality);
|
||||||
|
if (!frame)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_RAOP, "Could not convert raw PCM to frame (bufsize=%zu)\n", rawbuf_size);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
len = transcode_encode(evbuf, encode_ctx, frame, 0);
|
||||||
|
transcode_frame_free(frame);
|
||||||
|
if (len < 0)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_RAOP, "Could not ALAC encode frame\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
alac_encode(struct evbuffer *evbuf, struct encode_ctx *encode_ctx, uint8_t *rawbuf, size_t rawbuf_size, int nsamples, struct media_quality *quality)
|
||||||
|
{
|
||||||
|
if (raop_uncompressed_alac)
|
||||||
|
return alac_encode_no_xcode(evbuf, rawbuf, rawbuf_size);
|
||||||
|
else
|
||||||
|
return alac_encode_xcode(evbuf, encode_ctx, rawbuf, rawbuf_size, nsamples, quality);
|
||||||
|
}
|
||||||
|
|
||||||
/* AirTunes v2 time synchronization helpers */
|
/* AirTunes v2 time synchronization helpers */
|
||||||
static inline void
|
static inline void
|
||||||
timespec_to_ntp(struct timespec *ts, struct ntp_stamp *ns)
|
timespec_to_ntp(struct timespec *ts, struct ntp_stamp *ns)
|
||||||
@ -1737,51 +1800,6 @@ raop_status(struct raop_session *rs)
|
|||||||
rs->callback_id = -1;
|
rs->callback_id = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct raop_master_session *
|
|
||||||
master_session_make(struct media_quality *quality, bool encrypt)
|
|
||||||
{
|
|
||||||
struct raop_master_session *rms;
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
// First check if we already have a suitable session
|
|
||||||
for (rms = raop_master_sessions; rms; rms = rms->next)
|
|
||||||
{
|
|
||||||
if (encrypt == rms->encrypt && quality_is_equal(quality, &rms->rtp_session->quality))
|
|
||||||
return rms;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Let's create a master session
|
|
||||||
ret = outputs_quality_subscribe(quality);
|
|
||||||
if (ret < 0)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_RAOP, "Could not subscribe to required audio quality (%d/%d/%d)\n", quality->sample_rate, quality->bits_per_sample, quality->channels);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
CHECK_NULL(L_RAOP, rms = calloc(1, sizeof(struct raop_master_session)));
|
|
||||||
|
|
||||||
rms->rtp_session = rtp_session_new(quality, RAOP_PACKET_BUFFER_SIZE, 0);
|
|
||||||
if (!rms->rtp_session)
|
|
||||||
{
|
|
||||||
outputs_quality_unsubscribe(quality);
|
|
||||||
free(rms);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
rms->encrypt = encrypt;
|
|
||||||
rms->samples_per_packet = RAOP_SAMPLES_PER_PACKET;
|
|
||||||
rms->rawbuf_size = STOB(rms->samples_per_packet, quality->bits_per_sample, quality->channels);
|
|
||||||
rms->output_buffer_samples = OUTPUTS_BUFFER_DURATION * quality->sample_rate;
|
|
||||||
|
|
||||||
CHECK_NULL(L_RAOP, rms->rawbuf = malloc(rms->rawbuf_size));
|
|
||||||
CHECK_NULL(L_RAOP, rms->evbuf = evbuffer_new());
|
|
||||||
|
|
||||||
rms->next = raop_master_sessions;
|
|
||||||
raop_master_sessions = rms;
|
|
||||||
|
|
||||||
return rms;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
master_session_free(struct raop_master_session *rms)
|
master_session_free(struct raop_master_session *rms)
|
||||||
{
|
{
|
||||||
@ -1790,7 +1808,14 @@ master_session_free(struct raop_master_session *rms)
|
|||||||
|
|
||||||
outputs_quality_unsubscribe(&rms->rtp_session->quality);
|
outputs_quality_unsubscribe(&rms->rtp_session->quality);
|
||||||
rtp_session_free(rms->rtp_session);
|
rtp_session_free(rms->rtp_session);
|
||||||
evbuffer_free(rms->evbuf);
|
|
||||||
|
transcode_encode_cleanup(&rms->encode_ctx);
|
||||||
|
|
||||||
|
if (rms->input_buffer)
|
||||||
|
evbuffer_free(rms->input_buffer);
|
||||||
|
if (rms->encoded_buffer)
|
||||||
|
evbuffer_free(rms->encoded_buffer);
|
||||||
|
|
||||||
free(rms->rawbuf);
|
free(rms->rawbuf);
|
||||||
free(rms);
|
free(rms);
|
||||||
}
|
}
|
||||||
@ -1824,6 +1849,73 @@ master_session_cleanup(struct raop_master_session *rms)
|
|||||||
master_session_free(rms);
|
master_session_free(rms);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static struct raop_master_session *
|
||||||
|
master_session_make(struct media_quality *quality, bool encrypt)
|
||||||
|
{
|
||||||
|
struct raop_master_session *rms;
|
||||||
|
struct decode_ctx *decode_ctx;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
// First check if we already have a suitable session
|
||||||
|
for (rms = raop_master_sessions; rms; rms = rms->next)
|
||||||
|
{
|
||||||
|
if (encrypt == rms->encrypt && quality_is_equal(quality, &rms->rtp_session->quality))
|
||||||
|
return rms;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let's create a master session
|
||||||
|
ret = outputs_quality_subscribe(quality);
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_RAOP, "Could not subscribe to required audio quality (%d/%d/%d)\n", quality->sample_rate, quality->bits_per_sample, quality->channels);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK_NULL(L_RAOP, rms = calloc(1, sizeof(struct raop_master_session)));
|
||||||
|
|
||||||
|
rms->rtp_session = rtp_session_new(quality, RAOP_PACKET_BUFFER_SIZE, 0);
|
||||||
|
if (!rms->rtp_session)
|
||||||
|
{
|
||||||
|
outputs_quality_unsubscribe(quality);
|
||||||
|
free(rms);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
decode_ctx = transcode_decode_setup_raw(XCODE_PCM16, quality);
|
||||||
|
if (!decode_ctx)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_RAOP, "Could not create decoding context\n");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
rms->encode_ctx = transcode_encode_setup(XCODE_ALAC, quality, decode_ctx, NULL, 0, 0);
|
||||||
|
transcode_decode_cleanup(&decode_ctx);
|
||||||
|
if (!rms->encode_ctx)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_RAOP, "Will not be able to stream AirPlay 2, ffmpeg has no ALAC encoder\n");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
rms->encrypt = encrypt;
|
||||||
|
rms->quality = *quality;
|
||||||
|
rms->samples_per_packet = RAOP_SAMPLES_PER_PACKET;
|
||||||
|
rms->rawbuf_size = STOB(rms->samples_per_packet, quality->bits_per_sample, quality->channels);
|
||||||
|
rms->output_buffer_samples = OUTPUTS_BUFFER_DURATION * quality->sample_rate;
|
||||||
|
|
||||||
|
CHECK_NULL(L_RAOP, rms->rawbuf = malloc(rms->rawbuf_size));
|
||||||
|
CHECK_NULL(L_RAOP, rms->input_buffer = evbuffer_new());
|
||||||
|
CHECK_NULL(L_RAOP, rms->encoded_buffer = evbuffer_new());
|
||||||
|
|
||||||
|
rms->next = raop_master_sessions;
|
||||||
|
raop_master_sessions = rms;
|
||||||
|
|
||||||
|
return rms;
|
||||||
|
|
||||||
|
error:
|
||||||
|
master_session_free(rms);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
session_free(struct raop_session *rs)
|
session_free(struct raop_session *rs)
|
||||||
{
|
{
|
||||||
@ -2710,16 +2802,11 @@ raop_keep_alive_timer_cb(int fd, short what, void *arg)
|
|||||||
/* -------------------- Creation and sending of RTP packets ---------------- */
|
/* -------------------- Creation and sending of RTP packets ---------------- */
|
||||||
|
|
||||||
static int
|
static int
|
||||||
packet_prepare(struct rtp_packet *pkt, uint8_t *rawbuf, size_t rawbuf_size, bool encrypt)
|
packet_encrypt(struct rtp_packet *pkt)
|
||||||
{
|
{
|
||||||
char ebuf[64];
|
char ebuf[64];
|
||||||
gpg_error_t gc_err;
|
gpg_error_t gc_err;
|
||||||
|
|
||||||
alac_encode(pkt->payload, rawbuf, rawbuf_size);
|
|
||||||
|
|
||||||
if (!encrypt)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
// Reset cipher
|
// Reset cipher
|
||||||
gc_err = gcry_cipher_reset(raop_aes_ctx);
|
gc_err = gcry_cipher_reset(raop_aes_ctx);
|
||||||
if (gc_err != GPG_ERR_NO_ERROR)
|
if (gc_err != GPG_ERR_NO_ERROR)
|
||||||
@ -2846,14 +2933,25 @@ packets_send(struct raop_master_session *rms)
|
|||||||
{
|
{
|
||||||
struct rtp_packet *pkt;
|
struct rtp_packet *pkt;
|
||||||
struct raop_session *rs;
|
struct raop_session *rs;
|
||||||
|
int len;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
pkt = rtp_packet_next(rms->rtp_session, ALAC_HEADER_LEN + rms->rawbuf_size + 1, rms->samples_per_packet, RAOP_RTP_PAYLOADTYPE, 0);
|
len = alac_encode(rms->encoded_buffer, rms->encode_ctx, rms->rawbuf, rms->rawbuf_size, rms->samples_per_packet, &rms->quality);
|
||||||
/* the "+ 1" above is space for the end tag (3 bits) */
|
if (len < 0)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
ret = packet_prepare(pkt, rms->rawbuf, rms->rawbuf_size, rms->encrypt);
|
pkt = rtp_packet_next(rms->rtp_session, len, rms->samples_per_packet, RAOP_RTP_PAYLOADTYPE, 0);
|
||||||
|
|
||||||
|
evbuffer_remove(rms->encoded_buffer, pkt->payload, pkt->payload_len);
|
||||||
|
|
||||||
|
if (rms->encrypt)
|
||||||
|
{
|
||||||
|
ret = packet_encrypt(pkt);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
return -1;
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
for (rs = raop_sessions; rs; rs = rs->next)
|
for (rs = raop_sessions; rs; rs = rs->next)
|
||||||
{
|
{
|
||||||
@ -2906,15 +3004,15 @@ timestamp_set(struct raop_master_session *rms, struct timespec ts)
|
|||||||
// -> we should be playing rtptime X + 600
|
// -> we should be playing rtptime X + 600
|
||||||
//
|
//
|
||||||
// So how do we measure samples received from player? We know that from the
|
// So how do we measure samples received from player? We know that from the
|
||||||
// pos, which says how much has been sent to the device, and from rms->evbuf,
|
// pos, which says how much has been sent to the device, and from rms->input_buffer,
|
||||||
// which is the unsent stuff being buffered:
|
// which is the unsent stuff being buffered:
|
||||||
// - received = (pos - X) + rms->evbuf_samples
|
// - received = (pos - X) + rms->input_buffer_samples
|
||||||
//
|
//
|
||||||
// This means the rtptime is computed as:
|
// This means the rtptime is computed as:
|
||||||
// - rtptime = X + received - rms->output_buffer_samples
|
// - rtptime = X + received - rms->output_buffer_samples
|
||||||
// -> rtptime = X + (pos - X) + rms->evbuf_samples - rms->out_buffer_samples
|
// -> rtptime = X + (pos - X) + rms->input_buffer_samples - rms->out_buffer_samples
|
||||||
// -> rtptime = pos + rms->evbuf_samples - rms->output_buffer_samples
|
// -> rtptime = pos + rms->input_buffer_samples - rms->output_buffer_samples
|
||||||
rms->cur_stamp.pos = rms->rtp_session->pos + rms->evbuf_samples - rms->output_buffer_samples;
|
rms->cur_stamp.pos = rms->rtp_session->pos + rms->input_buffer_samples - rms->output_buffer_samples;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -4448,14 +4546,14 @@ raop_write(struct output_buffer *obuf)
|
|||||||
packets_sync_send(rms);
|
packets_sync_send(rms);
|
||||||
|
|
||||||
// TODO avoid this copy
|
// TODO avoid this copy
|
||||||
evbuffer_add(rms->evbuf, obuf->data[i].buffer, obuf->data[i].bufsize);
|
evbuffer_add(rms->input_buffer, obuf->data[i].buffer, obuf->data[i].bufsize);
|
||||||
rms->evbuf_samples += obuf->data[i].samples;
|
rms->input_buffer_samples += obuf->data[i].samples;
|
||||||
|
|
||||||
// Send as many packets as we have data for (one packet requires rawbuf_size bytes)
|
// Send as many packets as we have data for (one packet requires rawbuf_size bytes)
|
||||||
while (evbuffer_get_length(rms->evbuf) >= rms->rawbuf_size)
|
while (evbuffer_get_length(rms->input_buffer) >= rms->rawbuf_size)
|
||||||
{
|
{
|
||||||
evbuffer_remove(rms->evbuf, rms->rawbuf, rms->rawbuf_size);
|
evbuffer_remove(rms->input_buffer, rms->rawbuf, rms->rawbuf_size);
|
||||||
rms->evbuf_samples -= rms->samples_per_packet;
|
rms->input_buffer_samples -= rms->samples_per_packet;
|
||||||
|
|
||||||
packets_send(rms);
|
packets_send(rms);
|
||||||
}
|
}
|
||||||
@ -4558,6 +4656,8 @@ raop_init(void)
|
|||||||
goto out_stop_timing;
|
goto out_stop_timing;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
raop_uncompressed_alac = cfg_getbool(cfg_getsec(cfg, "airplay_shared"), "uncompressed_alac");
|
||||||
|
|
||||||
ret = mdns_browse("_raop._tcp", raop_device_cb, MDNS_CONNECTION_TEST);
|
ret = mdns_browse("_raop._tcp", raop_device_cb, MDNS_CONNECTION_TEST);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user