Skip to content

Commit

Permalink
Expose PCM channel map and per-channel volume
Browse files Browse the repository at this point in the history
This commit changes the PCM Volume property type, and by doing that it
breaks compatibility with old BlueALSA clients.
  • Loading branch information
arkq committed Sep 3, 2024
1 parent c4b6cf9 commit 0d488c7
Show file tree
Hide file tree
Showing 34 changed files with 659 additions and 409 deletions.
2 changes: 2 additions & 0 deletions .github/spellcheck-wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ utils

# Others
AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ
aas
ABCD
ABCDEFGHIJKLMNOPQRSTUVWXYZ
aloader
Expand Down Expand Up @@ -249,6 +250,7 @@ SIGIO
SIGPIPE
SIGSEGV
SIGTERM
SL
SNR
spandsp
SRA
Expand Down
1 change: 1 addition & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ unreleased
- renamed bluealsa to bluealsad (no backward compatibility)
- renamed bluealsa.conf to org.bluealsa.conf (no backward compatibility)
- renamed bluealsa-cli to bluealsactl (no backward compatibility)
- channel map and volume control for surround sound (5.1, 7.1) audio

bluez-alsa v4.3.1 (2024-08-30)
==============================
Expand Down
7 changes: 3 additions & 4 deletions doc/bluealsactl.1.rst
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ info *PCM_PATH*
'PropertyName: Value'. Values are presented in human-readable format - for
example the Volume property is printed as:

``Volume: L: 127 R: 127``
``Volume: 127 127``

The list of available A2DP codecs requires BlueZ SEP support
(BlueZ >= 5.52)
Expand Down Expand Up @@ -122,7 +122,7 @@ codec [-c NUM] [-s NUM] [--force] *PCM_PATH* [*CODEC*\ [:*CONFIG*]]

Selecting the HFP codec when using oFono is not supported.

volume *PCM_PATH* [*VOLUME* [*VOLUME*]]
volume *PCM_PATH* [*VOLUME* [*VOLUME*]...]
Get or set the volume value of the given PCM.

If *VOLUME* is given, set the loudness component of the volume property of
Expand All @@ -131,11 +131,10 @@ volume *PCM_PATH* [*VOLUME* [*VOLUME*]]
If only one value *VOLUME* is given it is applied to all channels.
For stereo (2-channel) PCMs the first value *VOLUME* is applied to channel
1 (Left), and the second value *VOLUME* is applied to channel 2 (Right).
For mono (1-channel) PCMs the second value *VOLUME* is ignored.

Valid A2DP values for *VOLUME* are 0-127, valid HFP/HSP values are 0-15.

mute *PCM_PATH* [*STATE* [*STATE*]]
mute *PCM_PATH* [*STATE* [*STATE*]...]
Get or set the mute switch of the given PCM.

If *STATE* argument(s) are given, set mute component of the volume property
Expand Down
14 changes: 9 additions & 5 deletions doc/org.bluealsa.PCM1.7.rst
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ array{string, dict} GetCodecs()
List of supported number of audio channels.
:array{uint32} SupportedSampling:
List of supported sampling frequency.
:array{array{string}} ChannelMaps:
List of supported channel maps.

void SelectCodec(string codec, dict props)
Select PCM codec. This call shall be made before PCM stream opening for
Expand Down Expand Up @@ -145,6 +147,9 @@ uint16 Format [readonly]
byte Channels [readonly]
Number of audio channels.

array{string} ChannelMap [readonly]
Channel map for selected codec.

uint32 Sampling [readonly]
Sampling frequency.

Expand All @@ -170,11 +175,10 @@ boolean SoftVolume [readwrite]
internally or will delegate this task to BlueALSA PCM client or connected
Bluetooth device respectively for PCM sink or PCM source.

uint16 Volume [readwrite]
This property holds volume (loudness) value and mute information for
channel 1 (left) and 2 (right). Data for channel 1 is stored in the upper
byte, channel 2 is stored in the lower byte. The highest bit of both bytes
determines whether channel is muted.
array{byte} Volume [readwrite]
This property holds volume (loudness) for all channels. The highest bit
of each byte determines whether channel is muted. The order of channels
is defined by the ChannelMap property.

Possible values:
::
Expand Down
78 changes: 52 additions & 26 deletions src/a2dp-aac.c
Original file line number Diff line number Diff line change
Expand Up @@ -39,27 +39,50 @@
#include "shared/log.h"
#include "shared/rt.h"

static const enum ba_transport_pcm_channel a2dp_aac_channel_map_mono[] = {
BA_TRANSPORT_PCM_CHANNEL_MONO,
};

static const enum ba_transport_pcm_channel a2dp_aac_channel_map_stereo[] = {
BA_TRANSPORT_PCM_CHANNEL_FL, BA_TRANSPORT_PCM_CHANNEL_FR,
};

static const enum ba_transport_pcm_channel a2dp_aac_channel_map_5_1[] = {
BA_TRANSPORT_PCM_CHANNEL_FC,
BA_TRANSPORT_PCM_CHANNEL_FL, BA_TRANSPORT_PCM_CHANNEL_FR,
BA_TRANSPORT_PCM_CHANNEL_RL, BA_TRANSPORT_PCM_CHANNEL_RR,
BA_TRANSPORT_PCM_CHANNEL_LFE,
};

static const enum ba_transport_pcm_channel a2dp_aac_channel_map_7_1[] = {
BA_TRANSPORT_PCM_CHANNEL_FC,
BA_TRANSPORT_PCM_CHANNEL_FL, BA_TRANSPORT_PCM_CHANNEL_FR,
BA_TRANSPORT_PCM_CHANNEL_SL, BA_TRANSPORT_PCM_CHANNEL_SR,
BA_TRANSPORT_PCM_CHANNEL_RL, BA_TRANSPORT_PCM_CHANNEL_RR,
BA_TRANSPORT_PCM_CHANNEL_LFE,
};

static const struct a2dp_bit_mapping a2dp_aac_channels[] = {
{ AAC_CHANNEL_MODE_MONO, 1 },
{ AAC_CHANNEL_MODE_STEREO, 2 },
{ AAC_CHANNEL_MODE_5_1, 6 },
{ AAC_CHANNEL_MODE_7_1, 8 },
{ AAC_CHANNEL_MODE_MONO, .ch = { 1, a2dp_aac_channel_map_mono } },
{ AAC_CHANNEL_MODE_STEREO, .ch = { 2, a2dp_aac_channel_map_stereo } },
{ AAC_CHANNEL_MODE_5_1, .ch = { 6, a2dp_aac_channel_map_5_1 } },
{ AAC_CHANNEL_MODE_7_1, .ch = { 8, a2dp_aac_channel_map_7_1 } },
{ 0 }
};

static const struct a2dp_bit_mapping a2dp_aac_samplings[] = {
{ AAC_SAMPLING_FREQ_8000, 8000 },
{ AAC_SAMPLING_FREQ_11025, 11025 },
{ AAC_SAMPLING_FREQ_12000, 12000 },
{ AAC_SAMPLING_FREQ_16000, 16000 },
{ AAC_SAMPLING_FREQ_22050, 22050 },
{ AAC_SAMPLING_FREQ_24000, 24000 },
{ AAC_SAMPLING_FREQ_32000, 32000 },
{ AAC_SAMPLING_FREQ_44100, 44100 },
{ AAC_SAMPLING_FREQ_48000, 48000 },
{ AAC_SAMPLING_FREQ_64000, 64000 },
{ AAC_SAMPLING_FREQ_88200, 88200 },
{ AAC_SAMPLING_FREQ_96000, 96000 },
{ AAC_SAMPLING_FREQ_8000, { 8000 } },
{ AAC_SAMPLING_FREQ_11025, { 11025 } },
{ AAC_SAMPLING_FREQ_12000, { 12000 } },
{ AAC_SAMPLING_FREQ_16000, { 16000 } },
{ AAC_SAMPLING_FREQ_22050, { 22050 } },
{ AAC_SAMPLING_FREQ_24000, { 24000 } },
{ AAC_SAMPLING_FREQ_32000, { 32000 } },
{ AAC_SAMPLING_FREQ_44100, { 44100 } },
{ AAC_SAMPLING_FREQ_48000, { 48000 } },
{ AAC_SAMPLING_FREQ_64000, { 64000 } },
{ AAC_SAMPLING_FREQ_88200, { 88200 } },
{ AAC_SAMPLING_FREQ_96000, { 96000 } },
{ 0 }
};

Expand Down Expand Up @@ -669,12 +692,12 @@ static int a2dp_aac_configuration_check(
}

const uint16_t conf_sampling_freq = A2DP_AAC_GET_SAMPLING_FREQ(conf_v);
if (a2dp_bit_mapping_lookup(a2dp_aac_samplings, conf_sampling_freq) == 0) {
if (a2dp_bit_mapping_lookup(a2dp_aac_samplings, conf_sampling_freq) == -1) {
debug("AAC: Invalid sampling frequency: %#x", A2DP_AAC_GET_SAMPLING_FREQ(*conf));
return A2DP_CHECK_ERR_SAMPLING;
}

if (a2dp_bit_mapping_lookup(a2dp_aac_channels, conf_v.channel_mode) == 0) {
if (a2dp_bit_mapping_lookup(a2dp_aac_channels, conf_v.channel_mode) == -1) {
debug("AAC: Invalid channel mode: %#x", conf->channel_mode);
return A2DP_CHECK_ERR_CHANNEL_MODE;
}
Expand All @@ -684,19 +707,22 @@ static int a2dp_aac_configuration_check(

static int a2dp_aac_transport_init(struct ba_transport *t) {

unsigned int channels;
if ((channels = a2dp_bit_mapping_lookup(a2dp_aac_channels,
t->a2dp.configuration.aac.channel_mode)) == 0)
ssize_t channels_i;
if ((channels_i = a2dp_bit_mapping_lookup(a2dp_aac_channels,
t->a2dp.configuration.aac.channel_mode)) == -1)
return -1;

unsigned int sampling;
if ((sampling = a2dp_bit_mapping_lookup(a2dp_aac_samplings,
A2DP_AAC_GET_SAMPLING_FREQ(t->a2dp.configuration.aac))) == 0)
ssize_t sampling_i;
if ((sampling_i = a2dp_bit_mapping_lookup(a2dp_aac_samplings,
A2DP_AAC_GET_SAMPLING_FREQ(t->a2dp.configuration.aac))) == -1)
return -1;

t->a2dp.pcm.format = BA_TRANSPORT_PCM_FORMAT_S16_2LE;
t->a2dp.pcm.channels = channels;
t->a2dp.pcm.sampling = sampling;
t->a2dp.pcm.channels = a2dp_aac_channels[channels_i].value;
t->a2dp.pcm.sampling = a2dp_aac_samplings[sampling_i].value;

memcpy(t->a2dp.pcm.channel_map, a2dp_aac_channels[channels_i].ch.map,
t->a2dp.pcm.channels * sizeof(*t->a2dp.pcm.channel_map));

return 0;
}
Expand Down
43 changes: 27 additions & 16 deletions src/a2dp-aptx-hd.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,25 @@
#include "shared/log.h"
#include "shared/rt.h"

static const enum ba_transport_pcm_channel a2dp_aptx_channel_map_mono[] = {
BA_TRANSPORT_PCM_CHANNEL_MONO,
};

static const enum ba_transport_pcm_channel a2dp_aptx_channel_map_stereo[] = {
BA_TRANSPORT_PCM_CHANNEL_FL, BA_TRANSPORT_PCM_CHANNEL_FR,
};

static const struct a2dp_bit_mapping a2dp_aptx_channels[] = {
{ APTX_CHANNEL_MODE_MONO, 1 },
{ APTX_CHANNEL_MODE_STEREO, 2 },
{ APTX_CHANNEL_MODE_MONO, .ch = { 1, a2dp_aptx_channel_map_mono } },
{ APTX_CHANNEL_MODE_STEREO, .ch = { 2, a2dp_aptx_channel_map_stereo } },
{ 0 }
};

static const struct a2dp_bit_mapping a2dp_aptx_samplings[] = {
{ APTX_SAMPLING_FREQ_16000, 16000 },
{ APTX_SAMPLING_FREQ_32000, 32000 },
{ APTX_SAMPLING_FREQ_44100, 44100 },
{ APTX_SAMPLING_FREQ_48000, 48000 },
{ APTX_SAMPLING_FREQ_16000, { 16000 } },
{ APTX_SAMPLING_FREQ_32000, { 32000 } },
{ APTX_SAMPLING_FREQ_44100, { 44100 } },
{ APTX_SAMPLING_FREQ_48000, { 48000 } },
{ 0 }
};

Expand Down Expand Up @@ -379,12 +387,12 @@ static int a2dp_aptx_hd_configuration_check(
/* Validate configuration against BlueALSA capabilities. */
a2dp_aptx_hd_caps_intersect(&conf_v, &sep->config.capabilities);

if (a2dp_bit_mapping_lookup(a2dp_aptx_samplings, conf_v.aptx.sampling_freq) == 0) {
if (a2dp_bit_mapping_lookup(a2dp_aptx_samplings, conf_v.aptx.sampling_freq) == -1) {
debug("apt-X HD: Invalid sampling frequency: %#x", conf->aptx.sampling_freq);
return A2DP_CHECK_ERR_SAMPLING;
}

if (a2dp_bit_mapping_lookup(a2dp_aptx_channels, conf_v.aptx.channel_mode) == 0) {
if (a2dp_bit_mapping_lookup(a2dp_aptx_channels, conf_v.aptx.channel_mode) == -1) {
debug("apt-X HD: Invalid channel mode: %#x", conf->aptx.channel_mode);
return A2DP_CHECK_ERR_CHANNEL_MODE;
}
Expand All @@ -394,19 +402,22 @@ static int a2dp_aptx_hd_configuration_check(

static int a2dp_aptx_hd_transport_init(struct ba_transport *t) {

unsigned int channels;
if ((channels = a2dp_bit_mapping_lookup(a2dp_aptx_channels,
t->a2dp.configuration.aptx_hd.aptx.channel_mode)) == 0)
ssize_t channels_i;
if ((channels_i = a2dp_bit_mapping_lookup(a2dp_aptx_channels,
t->a2dp.configuration.aptx_hd.aptx.channel_mode)) == -1)
return -1;

unsigned int sampling;
if ((sampling = a2dp_bit_mapping_lookup(a2dp_aptx_samplings,
t->a2dp.configuration.aptx_hd.aptx.sampling_freq)) == 0)
ssize_t sampling_i;
if ((sampling_i = a2dp_bit_mapping_lookup(a2dp_aptx_samplings,
t->a2dp.configuration.aptx_hd.aptx.sampling_freq)) == -1)
return -1;

t->a2dp.pcm.format = BA_TRANSPORT_PCM_FORMAT_S24_4LE;
t->a2dp.pcm.channels = channels;
t->a2dp.pcm.sampling = sampling;
t->a2dp.pcm.channels = a2dp_aptx_channels[channels_i].value;
t->a2dp.pcm.sampling = a2dp_aptx_samplings[sampling_i].value;

memcpy(t->a2dp.pcm.channel_map, a2dp_aptx_channels[channels_i].ch.map,
t->a2dp.pcm.channels * sizeof(*t->a2dp.pcm.channel_map));

return 0;
}
Expand Down
43 changes: 27 additions & 16 deletions src/a2dp-aptx.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,25 @@
#include "shared/log.h"
#include "shared/rt.h"

static const enum ba_transport_pcm_channel a2dp_aptx_channel_map_mono[] = {
BA_TRANSPORT_PCM_CHANNEL_MONO,
};

static const enum ba_transport_pcm_channel a2dp_aptx_channel_map_stereo[] = {
BA_TRANSPORT_PCM_CHANNEL_FL, BA_TRANSPORT_PCM_CHANNEL_FR,
};

static const struct a2dp_bit_mapping a2dp_aptx_channels[] = {
{ APTX_CHANNEL_MODE_MONO, 1 },
{ APTX_CHANNEL_MODE_STEREO, 2 },
{ APTX_CHANNEL_MODE_MONO, .ch = { 1, a2dp_aptx_channel_map_mono } },
{ APTX_CHANNEL_MODE_STEREO, .ch = { 2, a2dp_aptx_channel_map_stereo } },
{ 0 }
};

static const struct a2dp_bit_mapping a2dp_aptx_samplings[] = {
{ APTX_SAMPLING_FREQ_16000, 16000 },
{ APTX_SAMPLING_FREQ_32000, 32000 },
{ APTX_SAMPLING_FREQ_44100, 44100 },
{ APTX_SAMPLING_FREQ_48000, 48000 },
{ APTX_SAMPLING_FREQ_16000, { 16000 } },
{ APTX_SAMPLING_FREQ_32000, { 32000 } },
{ APTX_SAMPLING_FREQ_44100, { 44100 } },
{ APTX_SAMPLING_FREQ_48000, { 48000 } },
{ 0 }
};

Expand Down Expand Up @@ -341,12 +349,12 @@ static int a2dp_aptx_configuration_check(
/* Validate configuration against BlueALSA capabilities. */
a2dp_aptx_caps_intersect(&conf_v, &sep->config.capabilities);

if (a2dp_bit_mapping_lookup(a2dp_aptx_samplings, conf_v.sampling_freq) == 0) {
if (a2dp_bit_mapping_lookup(a2dp_aptx_samplings, conf_v.sampling_freq) == -1) {
debug("apt-X: Invalid sampling frequency: %#x", conf->sampling_freq);
return A2DP_CHECK_ERR_SAMPLING;
}

if (a2dp_bit_mapping_lookup(a2dp_aptx_channels, conf_v.channel_mode) == 0) {
if (a2dp_bit_mapping_lookup(a2dp_aptx_channels, conf_v.channel_mode) == -1) {
debug("apt-X: Invalid channel mode: %#x", conf->channel_mode);
return A2DP_CHECK_ERR_CHANNEL_MODE;
}
Expand All @@ -356,19 +364,22 @@ static int a2dp_aptx_configuration_check(

static int a2dp_aptx_transport_init(struct ba_transport *t) {

unsigned int channels;
if ((channels = a2dp_bit_mapping_lookup(a2dp_aptx_channels,
t->a2dp.configuration.aptx.channel_mode)) == 0)
ssize_t channels_i;
if ((channels_i = a2dp_bit_mapping_lookup(a2dp_aptx_channels,
t->a2dp.configuration.aptx.channel_mode)) == -1)
return -1;

unsigned int sampling;
if ((sampling = a2dp_bit_mapping_lookup(a2dp_aptx_samplings,
t->a2dp.configuration.aptx.sampling_freq)) == 0)
ssize_t sampling_i;
if ((sampling_i = a2dp_bit_mapping_lookup(a2dp_aptx_samplings,
t->a2dp.configuration.aptx.sampling_freq)) == -1)
return -1;

t->a2dp.pcm.format = BA_TRANSPORT_PCM_FORMAT_S16_2LE;
t->a2dp.pcm.channels = channels;
t->a2dp.pcm.sampling = sampling;
t->a2dp.pcm.channels = a2dp_aptx_channels[channels_i].value;
t->a2dp.pcm.sampling = a2dp_aptx_samplings[sampling_i].value;

memcpy(t->a2dp.pcm.channel_map, a2dp_aptx_channels[channels_i].ch.map,
t->a2dp.pcm.channels * sizeof(*t->a2dp.pcm.channel_map));

return 0;
}
Expand Down
Loading

0 comments on commit 0d488c7

Please sign in to comment.