Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable aptX (HD) decoding support using standalone codec extracted from ffmpeg. #253

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ Dependencies:
- [mp3lame](http://lame.sourceforge.net/) (when MP3 support is enabled with `--enable-mp3lame`)
- [mpg123](https://www.mpg123.org/) (when MPEG support is enabled with `--enable-mpg123`)
- [fdk-aac](https://github.com/mstorsjo/fdk-aac) (when AAC support is enabled with `--enable-aac`)
- [openaptx](https://github.com/Arkq/openaptx) (when apt-X support is enabled with `--enable-aptx`)
- [openaptx](https://github.com/Arkq/openaptx) (when apt-X encoding support is enabled with `--enable-aptx-source`)
- [ffaptx](https://github.com/t123yh/ffaptx) (when apt-X decoding support is enabled with `--enable-aptx-sink`)
- [libldac](https://github.com/EHfive/ldacBT) (when LDAC support is enabled with `--enable-ldac`)

Dependencies for client applications (e.g. `bluealsa-aplay`):
Expand Down
22 changes: 15 additions & 7 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,20 @@ AM_COND_IF([ENABLE_AAC], [
AC_DEFINE([ENABLE_AAC], [1], [Define to 1 if AAC is enabled.])
])

AC_ARG_ENABLE([aptx],
[AS_HELP_STRING([--enable-aptx], [enable apt-X encoding support])])
AM_CONDITIONAL([ENABLE_APTX], [test "x$enable_aptx" = "xyes"])
AM_COND_IF([ENABLE_APTX], [
PKG_CHECK_MODULES([APTX], [openaptx >= 1.0.0])
AC_DEFINE([ENABLE_APTX], [1], [Define to 1 if apt-X is enabled.])
AC_ARG_ENABLE([aptx-source],
[AS_HELP_STRING([--enable-aptx-source], [enable apt-X encoding support powered by openaptx])])
AM_CONDITIONAL([ENABLE_APTX_SOURCE], [test "x$enable_aptx_source" = "xyes"])
AM_COND_IF([ENABLE_APTX_SOURCE], [
PKG_CHECK_MODULES([OPENAPTX], [openaptx >= 1.0.0])
AC_DEFINE([ENABLE_APTX_SOURCE], [1], [Define to 1 if apt-X source is enabled.])
])

AC_ARG_ENABLE([aptx-sink],
[AS_HELP_STRING([--enable-aptx-sink], [enable apt-X decoding support powered by ffaptx])])
AM_CONDITIONAL([ENABLE_APTX_SINK], [test "x$enable_aptx_sink" = "xyes"])
AM_COND_IF([ENABLE_APTX_SINK], [
PKG_CHECK_MODULES([FFAPTX], [ffaptx >= 0.0.1])
AC_DEFINE([ENABLE_APTX_SINK], [1], [Define to 1 if apt-X sink is enabled.])
])

AC_ARG_ENABLE([ldac],
Expand Down Expand Up @@ -200,7 +208,7 @@ AM_COND_IF([ALSA_1_1_2], [
])

# warn user that aptX support is not an open-source feature
AM_COND_IF([ENABLE_APTX], [
AM_COND_IF([ENABLE_APTX_SOURCE], [
AC_MSG_WARN([ *** aptX encoder support ***])
AC_MSG_WARN([You have enabled support for aptX encoding. Note, that])
AC_MSG_WARN([by default bluez-alsa will use openaptx library, which])
Expand Down
6 changes: 4 additions & 2 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ endif

AM_CFLAGS = \
@AAC_CFLAGS@ \
@APTX_CFLAGS@ \
@OPENAPTX_CFLAGS@ \
@FFAPTX_CFLAGS@ \
@BLUEZ_CFLAGS@ \
@GIO2_CFLAGS@ \
@GLIB2_CFLAGS@ \
Expand All @@ -50,7 +51,8 @@ AM_CFLAGS = \

LDADD = \
@AAC_LIBS@ \
@APTX_LIBS@ \
@FFAPTX_LIBS@ \
@OPENAPTX_LIBS@ \
@BLUEZ_LIBS@ \
@GIO2_LIBS@ \
@GLIB2_LIBS@ \
Expand Down
24 changes: 20 additions & 4 deletions src/ba-transport.c
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,6 @@ unsigned int ba_transport_get_channels(const struct ba_transport *t) {
}
break;
#endif
#if ENABLE_APTX
case A2DP_CODEC_VENDOR_APTX:
switch (((a2dp_aptx_t *)t->a2dp.cconfig)->channel_mode) {
case APTX_CHANNEL_MODE_MONO:
Expand All @@ -387,7 +386,14 @@ unsigned int ba_transport_get_channels(const struct ba_transport *t) {
return 2;
}
break;
#endif
case A2DP_CODEC_VENDOR_APTX_HD:
switch (((a2dp_aptx_t *)t->a2dp.cconfig)->channel_mode) {
case APTX_CHANNEL_MODE_MONO:
return 1;
case APTX_CHANNEL_MODE_STEREO:
return 2;
}
break;
#if ENABLE_LDAC
case A2DP_CODEC_VENDOR_LDAC:
switch (((a2dp_ldac_t *)t->a2dp.cconfig)->channel_mode) {
Expand Down Expand Up @@ -472,7 +478,6 @@ unsigned int ba_transport_get_sampling(const struct ba_transport *t) {
}
break;
#endif
#if ENABLE_APTX
case A2DP_CODEC_VENDOR_APTX:
switch (((a2dp_aptx_t *)t->a2dp.cconfig)->frequency) {
case APTX_SAMPLING_FREQ_16000:
Expand All @@ -485,7 +490,18 @@ unsigned int ba_transport_get_sampling(const struct ba_transport *t) {
return 48000;
}
break;
#endif
case A2DP_CODEC_VENDOR_APTX_HD:
switch (((a2dp_aptx_hd_t *)t->a2dp.cconfig)->aptx.frequency) {
case APTX_SAMPLING_FREQ_16000:
return 16000;
case APTX_SAMPLING_FREQ_32000:
return 32000;
case APTX_SAMPLING_FREQ_44100:
return 44100;
case APTX_SAMPLING_FREQ_48000:
return 48000;
}
break;
#if ENABLE_LDAC
case A2DP_CODEC_VENDOR_LDAC:
switch (((a2dp_ldac_t *)t->a2dp.cconfig)->frequency) {
Expand Down
6 changes: 6 additions & 0 deletions src/bluealsa.h
Original file line number Diff line number Diff line change
Expand Up @@ -111,4 +111,10 @@ extern struct ba_config config;

int bluealsa_config_init(void);

#if ENABLE_APTX_SOURCE || ENABLE_APTX_SINK
# define ENABLE_APTX 1
#else
# define ENABLE_APTX 0
#endif

#endif
31 changes: 30 additions & 1 deletion src/bluez-a2dp.c
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,20 @@ static const struct bluez_a2dp_sampling_freq a2dp_aptx_samplings[] = {
{ 48000, APTX_SAMPLING_FREQ_48000 },
};

static const a2dp_aptx_hd_t a2dp_aptxhd = {
.aptx = {
.info = A2DP_SET_VENDOR_ID_CODEC_ID(APTX_HD_VENDOR_ID, APTX_HD_CODEC_ID),
.channel_mode =
APTX_CHANNEL_MODE_STEREO,
.frequency =
APTX_SAMPLING_FREQ_16000 |
APTX_SAMPLING_FREQ_32000 |
APTX_SAMPLING_FREQ_44100 |
APTX_SAMPLING_FREQ_48000,
},
.rfa = 0
};

static const a2dp_ldac_t a2dp_ldac = {
.info = A2DP_SET_VENDOR_ID_CODEC_ID(LDAC_VENDOR_ID, LDAC_CODEC_ID),
.channel_mode =
Expand Down Expand Up @@ -341,6 +355,17 @@ static const struct bluez_a2dp_codec a2dp_codec_sink_aptx = {
.samplings_size = ARRAYSIZE(a2dp_aptx_samplings),
};

static const struct bluez_a2dp_codec a2dp_codec_sink_aptxhd = {
.dir = BLUEZ_A2DP_SINK,
.id = A2DP_CODEC_VENDOR_APTX_HD,
.cfg = &a2dp_aptxhd,
.cfg_size = sizeof(a2dp_aptxhd),
.channels = a2dp_aptx_channels,
.channels_size = ARRAYSIZE(a2dp_aptx_channels),
.samplings = a2dp_aptx_samplings,
.samplings_size = ARRAYSIZE(a2dp_aptx_samplings),
};

static const struct bluez_a2dp_codec a2dp_codec_source_ldac = {
.dir = BLUEZ_A2DP_SOURCE,
.id = A2DP_CODEC_VENDOR_LDAC,
Expand All @@ -367,7 +392,7 @@ static const struct bluez_a2dp_codec *a2dp_codecs[] = {
#if ENABLE_LDAC
&a2dp_codec_source_ldac,
#endif
#if ENABLE_APTX
#if ENABLE_APTX_SOURCE
&a2dp_codec_source_aptx,
#endif
#if ENABLE_AAC
Expand All @@ -381,6 +406,10 @@ static const struct bluez_a2dp_codec *a2dp_codecs[] = {
# if ENABLE_MP3LAME || ENABLE_MPG123
&a2dp_codec_sink_mpeg,
# endif
#endif
#if ENABLE_APTX_SINK
&a2dp_codec_sink_aptx,
&a2dp_codec_sink_aptxhd,
#endif
&a2dp_codec_source_sbc,
&a2dp_codec_sink_sbc,
Expand Down
7 changes: 7 additions & 0 deletions src/bluez.c
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,13 @@ static void bluez_endpoint_set_configuration(GDBusMethodInvocation *inv, void *u
cap_freq = cap->frequency;
break;
}

case A2DP_CODEC_VENDOR_APTX_HD: {
a2dp_aptx_hd_t *cap = capabilities;
cap_chm = cap->aptx.channel_mode;
cap_freq = cap->aptx.frequency;
break;
}
#endif

#if ENABLE_LDAC
Expand Down
140 changes: 137 additions & 3 deletions src/io.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,14 @@
# define AACENCODER_LIB_VERSION LIB_VERSION( \
AACENCODER_LIB_VL0, AACENCODER_LIB_VL1, AACENCODER_LIB_VL2)
#endif
#if ENABLE_APTX
#if ENABLE_APTX_SOURCE
# include <openaptx.h>
#endif

#if ENABLE_APTX_SINK
# include <ffaptx.h>
#endif

#if ENABLE_MP3LAME
# include <lame/lame.h>
#endif
Expand Down Expand Up @@ -1615,7 +1620,7 @@ static void *io_thread_a2dp_source_aac(void *arg) {
}
#endif

#if ENABLE_APTX
#if ENABLE_APTX_SOURCE
static void *io_thread_a2dp_source_aptx(void *arg) {
struct ba_transport *t = (struct ba_transport *)arg;

Expand Down Expand Up @@ -2407,6 +2412,125 @@ static void *io_thread_sco(void *arg) {
return NULL;
}

#if ENABLE_APTX_SINK
static void *io_thread_a2dp_sink_aptx_aptxhd(bool hd, void* arg) {
struct ba_transport *t = (struct ba_transport *)arg;

pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
pthread_cleanup_push(PTHREAD_CLEANUP(ba_transport_pthread_cleanup), t);

const unsigned int channels = ba_transport_get_channels(t);
if (channels != NB_CHANNELS)
{
error("Unsupported aptX channel number: %d", channels);
goto fail_ch;
}

struct io_thread_data io = {
.fds[0] = { t->sig_fd[0], POLLIN, 0 },
.fds[1] = { t->bt_fd, POLLIN, 0 },
.t_locked = !ba_transport_pthread_cleanup_lock(t),
};

ffb_uint8_t bt = { 0 };
ffb_int16_t pcm = { 0 };

pthread_cleanup_push(PTHREAD_CLEANUP(ffb_uint8_free), &bt);
pthread_cleanup_push(PTHREAD_CLEANUP(ffb_int16_free), &pcm);

AptXContext aptx;
ff_aptx_init(&aptx, hd);

int32_t max_pcm_buf = NB_CHANNELS * 4 * t->mtu_read / aptx.block_size;
if (ffb_init(&bt, t->mtu_read) == NULL || ffb_init(&pcm, max_pcm_buf) == NULL) {
error("Couldn't create data buffer: %s", strerror(ENOMEM));
goto fail_ffb;
}

for (;;) {
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);

ssize_t len;

if (poll(io.fds, ARRAYSIZE(io.fds), -1) == -1) {
if (errno == EINTR)
continue;
error("Transport poll error: %s", strerror(errno));
goto fail;
}

if (io.fds[0].revents & POLLIN) {
ba_transport_recv_signal(t);
continue;
}

if ((len = read(io.fds[1].fd, bt.tail, ffb_len_in(&bt))) == -1) {
debug("BT read error: %s", strerror(errno));
continue;
}

pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);

size_t payload_len;
const uint8_t* buf;
if (!hd)
{
buf = bt.data;
payload_len = len;
}
else
{
// TODO: figure out what we can do with RTP header. `pulseaudio-modules-bt` seems to be ignoring this header.
const size_t rtp_header_size = 12;
buf = bt.data + rtp_header_size;
payload_len = len - rtp_header_size;
}

size_t nb_samples = NB_CHANNELS * 4 * payload_len / aptx.block_size;
const uint8_t *bufend = buf + payload_len;

while (nb_samples > 0)
{
int32_t sample_cnt = MIN(max_pcm_buf, nb_samples);
nb_samples -= sample_cnt;

for (int opos = 0; buf < bufend && opos < sample_cnt; opos += 8)
{
int32_t samples[NB_CHANNELS][4];
ff_aptx_decode_samples(&aptx, buf, samples);
for (int s = 0; s < 4; s++)
for (int ch = 0; ch < NB_CHANNELS; ch++)
{
pcm.data[opos + s * NB_CHANNELS + ch] = samples[ch][s] >> 8;
}
buf += aptx.block_size;
}

io_thread_scale_pcm(t, pcm.data, sample_cnt, NB_CHANNELS);
if (io_thread_write_pcm(&t->a2dp.pcm, pcm.data, sample_cnt) == -1)
error("FIFO write error: %s", strerror(errno));
}
}

fail:
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
fail_ffb:
pthread_cleanup_pop(1);
pthread_cleanup_pop(1);
fail_ch:
pthread_cleanup_pop(1);
return NULL;
}

static void *io_thread_a2dp_sink_aptx(void* arg) {
io_thread_a2dp_sink_aptx_aptxhd(false, arg);
}

static void *io_thread_a2dp_sink_aptxhd(void* arg) {
io_thread_a2dp_sink_aptx_aptxhd(true, arg);
}
#endif /* ENALBE_APTX_SINK */

/**
* Dump incoming BT data to a file. */
static void *io_thread_a2dp_sink_dump(void *arg) {
Expand Down Expand Up @@ -2522,7 +2646,7 @@ int io_thread_create(struct ba_transport *t) {
name = "ba-io-aac";
break;
#endif
#if ENABLE_APTX
#if ENABLE_APTX_SOURCE
case A2DP_CODEC_VENDOR_APTX:
routine = io_thread_a2dp_source_aptx;
name = "ba-io-aptx";
Expand Down Expand Up @@ -2558,6 +2682,16 @@ int io_thread_create(struct ba_transport *t) {
routine = io_thread_a2dp_sink_aac;
name = "ba-io-aac";
break;
#endif
#if ENABLE_APTX_SINK
case A2DP_CODEC_VENDOR_APTX:
routine = io_thread_a2dp_sink_aptx;
name = "ba-io-aptx";
break;
case A2DP_CODEC_VENDOR_APTX_HD:
routine = io_thread_a2dp_sink_aptxhd;
name = "ba-io-aptxhd";
break;
#endif
default:
warn("Codec not supported: %u", t->type.codec);
Expand Down
Loading