Skip to content

Commit

Permalink
Merge pull request #1663 from owntone/esp32
Browse files Browse the repository at this point in the history
[raop] Make compressed ALAC default, but with a config option
  • Loading branch information
ejurgensen authored Oct 7, 2023
2 parents c34acb1 + d266c8a commit 67de230
Show file tree
Hide file tree
Showing 3 changed files with 170 additions and 65 deletions.
4 changes: 4 additions & 0 deletions owntone.conf.in
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,10 @@ audio {
# OwnTone behind a firewall)
# control_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
Expand Down
1 change: 1 addition & 0 deletions src/conffile.c
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ static cfg_opt_t sec_airplay_shared[] =
{
CFG_INT("control_port", 0, CFGF_NONE),
CFG_INT("timing_port", 0, CFGF_NONE),
CFG_BOOL("uncompressed_alac", cfg_false, CFGF_NONE),
CFG_END()
};

Expand Down
230 changes: 165 additions & 65 deletions src/outputs/raop.c
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
#include "artwork.h"
#include "dmap_common.h"
#include "rtp_common.h"
#include "transcode.h"
#include "outputs.h"
#include "pair_ap/pair.h"

Expand Down Expand Up @@ -154,18 +155,24 @@ struct raop_extra

struct raop_master_session
{
struct evbuffer *evbuf;
int evbuf_samples;
struct evbuffer *input_buffer;
int input_buffer_samples;

struct rtp_session *rtp_session;

struct rtcp_timestamp cur_stamp;

// ALAC encoder and buffer for encoded data
struct encode_ctx *encode_ctx;
struct evbuffer *encoded_buffer;

uint8_t *rawbuf;
size_t rawbuf_size;
int samples_per_packet;
bool encrypt;

struct media_quality quality;

// 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
// compared to the rtptimes of the corresponding RTP packages we are sending)
Expand Down Expand Up @@ -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_session *raop_sessions;

/* Don't encode ALAC with ffmpeg */
static bool raop_uncompressed_alac;

// Forwards
static int
raop_device_start(struct output_device *rd, int callback_id);
Expand Down Expand Up @@ -390,7 +400,7 @@ alac_write_bits(uint8_t **p, uint8_t val, int blen, int *bpos)

/* Raw data must be little endian */
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;
int bpos;
Expand Down Expand Up @@ -419,6 +429,59 @@ alac_encode(uint8_t *dst, uint8_t *raw, int len)
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 */
static inline void
timespec_to_ntp(struct timespec *ts, struct ntp_stamp *ns)
Expand Down Expand Up @@ -1737,10 +1800,60 @@ raop_status(struct raop_session *rs)
rs->callback_id = -1;
}

static void
master_session_free(struct raop_master_session *rms)
{
if (!rms)
return;

outputs_quality_unsubscribe(&rms->rtp_session->quality);
rtp_session_free(rms->rtp_session);

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);
}

static void
master_session_cleanup(struct raop_master_session *rms)
{
struct raop_master_session *s;
struct raop_session *rs;

// First check if any other session is using the master session
for (rs = raop_sessions; rs; rs=rs->next)
{
if (rs->master_session == rms)
return;
}

if (rms == raop_master_sessions)
raop_master_sessions = raop_master_sessions->next;
else
{
for (s = raop_master_sessions; s && (s->next != rms); s = s->next)
; /* EMPTY */

if (!s)
DPRINTF(E_WARN, L_RAOP, "WARNING: struct raop_master_session not found in list; BUG!\n");
else
s->next = rms->next;
}

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
Expand Down Expand Up @@ -1768,60 +1881,39 @@ master_session_make(struct media_quality *quality, bool encrypt)
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->evbuf = evbuffer_new());
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;
}

static void
master_session_free(struct raop_master_session *rms)
{
if (!rms)
return;

outputs_quality_unsubscribe(&rms->rtp_session->quality);
rtp_session_free(rms->rtp_session);
evbuffer_free(rms->evbuf);
free(rms->rawbuf);
free(rms);
}

static void
master_session_cleanup(struct raop_master_session *rms)
{
struct raop_master_session *s;
struct raop_session *rs;

// First check if any other session is using the master session
for (rs = raop_sessions; rs; rs=rs->next)
{
if (rs->master_session == rms)
return;
}

if (rms == raop_master_sessions)
raop_master_sessions = raop_master_sessions->next;
else
{
for (s = raop_master_sessions; s && (s->next != rms); s = s->next)
; /* EMPTY */

if (!s)
DPRINTF(E_WARN, L_RAOP, "WARNING: struct raop_master_session not found in list; BUG!\n");
else
s->next = rms->next;
}

error:
master_session_free(rms);
return NULL;
}

static void
Expand Down Expand Up @@ -2710,16 +2802,11 @@ raop_keep_alive_timer_cb(int fd, short what, void *arg)
/* -------------------- Creation and sending of RTP packets ---------------- */

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];
gpg_error_t gc_err;

alac_encode(pkt->payload, rawbuf, rawbuf_size);

if (!encrypt)
return 0;

// Reset cipher
gc_err = gcry_cipher_reset(raop_aes_ctx);
if (gc_err != GPG_ERR_NO_ERROR)
Expand Down Expand Up @@ -2846,14 +2933,25 @@ packets_send(struct raop_master_session *rms)
{
struct rtp_packet *pkt;
struct raop_session *rs;
int len;
int ret;

pkt = rtp_packet_next(rms->rtp_session, ALAC_HEADER_LEN + rms->rawbuf_size + 1, rms->samples_per_packet, RAOP_RTP_PAYLOADTYPE, 0);
/* the "+ 1" above is space for the end tag (3 bits) */
len = alac_encode(rms->encoded_buffer, rms->encode_ctx, rms->rawbuf, rms->rawbuf_size, rms->samples_per_packet, &rms->quality);
if (len < 0)
{
return -1;
}

ret = packet_prepare(pkt, rms->rawbuf, rms->rawbuf_size, rms->encrypt);
if (ret < 0)
return -1;
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)
return -1;
}

for (rs = raop_sessions; rs; rs = rs->next)
{
Expand Down Expand Up @@ -2906,15 +3004,15 @@ timestamp_set(struct raop_master_session *rms, struct timespec ts)
// -> we should be playing rtptime X + 600
//
// 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:
// - received = (pos - X) + rms->evbuf_samples
// - received = (pos - X) + rms->input_buffer_samples
//
// This means the rtptime is computed as:
// - rtptime = X + received - rms->output_buffer_samples
// -> rtptime = X + (pos - X) + rms->evbuf_samples - rms->out_buffer_samples
// -> rtptime = pos + rms->evbuf_samples - rms->output_buffer_samples
rms->cur_stamp.pos = rms->rtp_session->pos + rms->evbuf_samples - rms->output_buffer_samples;
// -> rtptime = X + (pos - X) + rms->input_buffer_samples - rms->out_buffer_samples
// -> rtptime = pos + rms->input_buffer_samples - rms->output_buffer_samples
rms->cur_stamp.pos = rms->rtp_session->pos + rms->input_buffer_samples - rms->output_buffer_samples;
}

static void
Expand Down Expand Up @@ -4448,14 +4546,14 @@ raop_write(struct output_buffer *obuf)
packets_sync_send(rms);

// TODO avoid this copy
evbuffer_add(rms->evbuf, obuf->data[i].buffer, obuf->data[i].bufsize);
rms->evbuf_samples += obuf->data[i].samples;
evbuffer_add(rms->input_buffer, obuf->data[i].buffer, obuf->data[i].bufsize);
rms->input_buffer_samples += obuf->data[i].samples;

// 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);
rms->evbuf_samples -= rms->samples_per_packet;
evbuffer_remove(rms->input_buffer, rms->rawbuf, rms->rawbuf_size);
rms->input_buffer_samples -= rms->samples_per_packet;

packets_send(rms);
}
Expand Down Expand Up @@ -4558,6 +4656,8 @@ raop_init(void)
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);
if (ret < 0)
{
Expand Down

0 comments on commit 67de230

Please sign in to comment.