From 0d488c7b671b0156f20a244a63ce1ab5e345afc7 Mon Sep 17 00:00:00 2001 From: Arkadiusz Bokowy Date: Wed, 28 Aug 2024 21:20:38 +0200 Subject: [PATCH] Expose PCM channel map and per-channel volume This commit changes the PCM Volume property type, and by doing that it breaks compatibility with old BlueALSA clients. --- .github/spellcheck-wordlist.txt | 2 + NEWS | 1 + doc/bluealsactl.1.rst | 7 ++- doc/org.bluealsa.PCM1.7.rst | 14 ++++-- src/a2dp-aac.c | 78 ++++++++++++++++++++----------- src/a2dp-aptx-hd.c | 43 ++++++++++------- src/a2dp-aptx.c | 43 ++++++++++------- src/a2dp-faststream.c | 31 ++++++------ src/a2dp-lc3plus.c | 39 ++++++++++------ src/a2dp-ldac.c | 49 +++++++++++-------- src/a2dp-mpeg.c | 51 ++++++++++++-------- src/a2dp-opus.c | 39 ++++++++++------ src/a2dp-sbc.c | 47 ++++++++++++------- src/a2dp.c | 10 ++-- src/a2dp.h | 23 +++++++-- src/asound/bluealsa-ctl.c | 26 +++++------ src/asound/bluealsa-pcm.c | 25 +++++----- src/ba-transport-pcm.c | 62 +++++++++++++++--------- src/ba-transport-pcm.h | 23 +++++++-- src/bluealsa-dbus.c | 83 ++++++++++++++++++++++++--------- src/bluealsa-dbus.h | 19 ++++---- src/bluealsa-iface.xml | 63 +++++++++++++------------ src/bluealsactl/cmd-mute.c | 32 ++++++------- src/bluealsactl/cmd-volume.c | 50 +++++++++----------- src/bluealsactl/main.c | 25 ++++++---- src/bluez.c | 8 ++-- src/io.c | 10 ++-- src/sco.c | 2 + src/shared/dbus-client-pcm.c | 63 +++++++++++++++++++------ src/shared/dbus-client-pcm.h | 22 ++++----- src/storage.c | 38 ++++++++++----- test/test-ba.c | 8 +++- test/test-utils-ctl.c | 10 ++-- utils/aplay/aplay.c | 22 ++++----- 34 files changed, 659 insertions(+), 409 deletions(-) diff --git a/.github/spellcheck-wordlist.txt b/.github/spellcheck-wordlist.txt index 98783a73c..f9d690922 100644 --- a/.github/spellcheck-wordlist.txt +++ b/.github/spellcheck-wordlist.txt @@ -124,6 +124,7 @@ utils # Others AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ +aas ABCD ABCDEFGHIJKLMNOPQRSTUVWXYZ aloader @@ -249,6 +250,7 @@ SIGIO SIGPIPE SIGSEGV SIGTERM +SL SNR spandsp SRA diff --git a/NEWS b/NEWS index 305a9232e..a735f7efe 100644 --- a/NEWS +++ b/NEWS @@ -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) ============================== diff --git a/doc/bluealsactl.1.rst b/doc/bluealsactl.1.rst index 385f6462e..073034ccc 100644 --- a/doc/bluealsactl.1.rst +++ b/doc/bluealsactl.1.rst @@ -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) @@ -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 @@ -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 diff --git a/doc/org.bluealsa.PCM1.7.rst b/doc/org.bluealsa.PCM1.7.rst index 2e845e229..f8f1b8ac5 100644 --- a/doc/org.bluealsa.PCM1.7.rst +++ b/doc/org.bluealsa.PCM1.7.rst @@ -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 @@ -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. @@ -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: :: diff --git a/src/a2dp-aac.c b/src/a2dp-aac.c index 56f8aaf95..adb990dc0 100644 --- a/src/a2dp-aac.c +++ b/src/a2dp-aac.c @@ -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 } }; @@ -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; } @@ -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; } diff --git a/src/a2dp-aptx-hd.c b/src/a2dp-aptx-hd.c index 8646c9384..2193adb03 100644 --- a/src/a2dp-aptx-hd.c +++ b/src/a2dp-aptx-hd.c @@ -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 } }; @@ -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; } @@ -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; } diff --git a/src/a2dp-aptx.c b/src/a2dp-aptx.c index 4949b1a24..d7fbc0c62 100644 --- a/src/a2dp-aptx.c +++ b/src/a2dp-aptx.c @@ -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 } }; @@ -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; } @@ -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; } diff --git a/src/a2dp-faststream.c b/src/a2dp-faststream.c index ca3741cb4..5d2666df8 100644 --- a/src/a2dp-faststream.c +++ b/src/a2dp-faststream.c @@ -33,13 +33,13 @@ #include "shared/rt.h" static const struct a2dp_bit_mapping a2dp_faststream_samplings_music[] = { - { FASTSTREAM_SAMPLING_FREQ_MUSIC_44100, 44100 }, - { FASTSTREAM_SAMPLING_FREQ_MUSIC_48000, 48000 }, + { FASTSTREAM_SAMPLING_FREQ_MUSIC_44100, { 44100 } }, + { FASTSTREAM_SAMPLING_FREQ_MUSIC_48000, { 48000 } }, { 0 } }; static const struct a2dp_bit_mapping a2dp_faststream_samplings_voice[] = { - { FASTSTREAM_SAMPLING_FREQ_VOICE_16000, 16000 }, + { FASTSTREAM_SAMPLING_FREQ_VOICE_16000, { 16000 } }, { 0 } }; @@ -374,13 +374,13 @@ static int a2dp_faststream_configuration_check( } if (conf_v.direction & FASTSTREAM_DIRECTION_VOICE && - a2dp_bit_mapping_lookup(a2dp_faststream_samplings_voice, conf_v.sampling_freq_voice) == 0) { + a2dp_bit_mapping_lookup(a2dp_faststream_samplings_voice, conf_v.sampling_freq_voice) == -1) { debug("FastStream: Invalid voice sampling frequency: %#x", conf->sampling_freq_voice); return A2DP_CHECK_ERR_SAMPLING_VOICE; } if (conf_v.direction & FASTSTREAM_DIRECTION_MUSIC && - a2dp_bit_mapping_lookup(a2dp_faststream_samplings_music, conf_v.sampling_freq_music) == 0) { + a2dp_bit_mapping_lookup(a2dp_faststream_samplings_music, conf_v.sampling_freq_music) == -1) { debug("FastStream: Invalid music sampling frequency: %#x", conf->sampling_freq_music); return A2DP_CHECK_ERR_SAMPLING_MUSIC; } @@ -392,27 +392,32 @@ static int a2dp_faststream_transport_init(struct ba_transport *t) { if (t->a2dp.configuration.faststream.direction & FASTSTREAM_DIRECTION_MUSIC) { - unsigned int sampling; - if ((sampling = a2dp_bit_mapping_lookup(a2dp_faststream_samplings_music, - t->a2dp.configuration.faststream.sampling_freq_music)) == 0) + ssize_t sampling_i; + if ((sampling_i = a2dp_bit_mapping_lookup(a2dp_faststream_samplings_music, + t->a2dp.configuration.faststream.sampling_freq_music)) == -1) return -1; t->a2dp.pcm.format = BA_TRANSPORT_PCM_FORMAT_S16_2LE; - t->a2dp.pcm.sampling = sampling; t->a2dp.pcm.channels = 2; + t->a2dp.pcm.sampling = a2dp_faststream_samplings_music[sampling_i].value; + + t->a2dp.pcm.channel_map[0] = BA_TRANSPORT_PCM_CHANNEL_FL; + t->a2dp.pcm.channel_map[1] = BA_TRANSPORT_PCM_CHANNEL_FR; } if (t->a2dp.configuration.faststream.direction & FASTSTREAM_DIRECTION_VOICE) { - unsigned int sampling; - if ((sampling = a2dp_bit_mapping_lookup(a2dp_faststream_samplings_voice, - t->a2dp.configuration.faststream.sampling_freq_voice)) == 0) + ssize_t sampling_i; + if ((sampling_i = a2dp_bit_mapping_lookup(a2dp_faststream_samplings_voice, + t->a2dp.configuration.faststream.sampling_freq_voice)) == -1) return -1; t->a2dp.pcm_bc.format = BA_TRANSPORT_PCM_FORMAT_S16_2LE; - t->a2dp.pcm_bc.sampling = sampling; t->a2dp.pcm_bc.channels = 1; + t->a2dp.pcm_bc.sampling = a2dp_faststream_samplings_voice[sampling_i].value; + + t->a2dp.pcm_bc.channel_map[0] = BA_TRANSPORT_PCM_CHANNEL_MONO; } diff --git a/src/a2dp-lc3plus.c b/src/a2dp-lc3plus.c index b56a69769..2a7edecdd 100644 --- a/src/a2dp-lc3plus.c +++ b/src/a2dp-lc3plus.c @@ -39,15 +39,23 @@ #include "shared/log.h" #include "shared/rt.h" +static const enum ba_transport_pcm_channel a2dp_lc3plus_channel_map_mono[] = { + BA_TRANSPORT_PCM_CHANNEL_MONO, +}; + +static const enum ba_transport_pcm_channel a2dp_lc3plus_channel_map_stereo[] = { + BA_TRANSPORT_PCM_CHANNEL_FL, BA_TRANSPORT_PCM_CHANNEL_FR, +}; + static const struct a2dp_bit_mapping a2dp_lc3plus_channels[] = { - { LC3PLUS_CHANNEL_MODE_MONO, 1 }, - { LC3PLUS_CHANNEL_MODE_STEREO, 2 }, + { LC3PLUS_CHANNEL_MODE_MONO, .ch = { 1, a2dp_lc3plus_channel_map_mono } }, + { LC3PLUS_CHANNEL_MODE_STEREO, .ch = { 2, a2dp_lc3plus_channel_map_stereo } }, { 0 } }; static const struct a2dp_bit_mapping a2dp_lc3plus_samplings[] = { - { LC3PLUS_SAMPLING_FREQ_48000, 48000 }, - { LC3PLUS_SAMPLING_FREQ_96000, 96000 }, + { LC3PLUS_SAMPLING_FREQ_48000, { 48000 } }, + { LC3PLUS_SAMPLING_FREQ_96000, { 96000 } }, { 0 } }; @@ -669,13 +677,13 @@ static int a2dp_lc3plus_configuration_check( return A2DP_CHECK_ERR_FRAME_DURATION; } - if (a2dp_bit_mapping_lookup(a2dp_lc3plus_channels, conf_v.channel_mode) == 0) { + if (a2dp_bit_mapping_lookup(a2dp_lc3plus_channels, conf_v.channel_mode) == -1) { debug("LC3plus: Invalid channel mode: %#x", conf->channel_mode); return A2DP_CHECK_ERR_CHANNEL_MODE; } uint16_t conf_sampling_freq = A2DP_LC3PLUS_GET_SAMPLING_FREQ(conf_v); - if (a2dp_bit_mapping_lookup(a2dp_lc3plus_samplings, conf_sampling_freq) == 0) { + if (a2dp_bit_mapping_lookup(a2dp_lc3plus_samplings, conf_sampling_freq) == -1) { debug("LC3plus: Invalid sampling frequency: %#x", A2DP_LC3PLUS_GET_SAMPLING_FREQ(*conf)); return A2DP_CHECK_ERR_SAMPLING; } @@ -685,19 +693,22 @@ static int a2dp_lc3plus_configuration_check( static int a2dp_lc3plus_transport_init(struct ba_transport *t) { - unsigned int channels; - if ((channels = a2dp_bit_mapping_lookup(a2dp_lc3plus_channels, - t->a2dp.configuration.lc3plus.channel_mode)) == 0) + ssize_t channels_i; + if ((channels_i = a2dp_bit_mapping_lookup(a2dp_lc3plus_channels, + t->a2dp.configuration.lc3plus.channel_mode)) == -1) return -1; - unsigned int sampling; - if ((sampling = a2dp_bit_mapping_lookup(a2dp_lc3plus_samplings, - A2DP_LC3PLUS_GET_SAMPLING_FREQ(t->a2dp.configuration.lc3plus))) == 0) + ssize_t sampling_i; + if ((sampling_i = a2dp_bit_mapping_lookup(a2dp_lc3plus_samplings, + A2DP_LC3PLUS_GET_SAMPLING_FREQ(t->a2dp.configuration.lc3plus))) == -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_lc3plus_channels[channels_i].value; + t->a2dp.pcm.sampling = a2dp_lc3plus_samplings[sampling_i].value; + + memcpy(t->a2dp.pcm.channel_map, a2dp_lc3plus_channels[channels_i].ch.map, + t->a2dp.pcm.channels * sizeof(*t->a2dp.pcm.channel_map)); return 0; } diff --git a/src/a2dp-ldac.c b/src/a2dp-ldac.c index 23834793d..47729f6c7 100644 --- a/src/a2dp-ldac.c +++ b/src/a2dp-ldac.c @@ -36,20 +36,28 @@ #include "shared/log.h" #include "shared/rt.h" +static const enum ba_transport_pcm_channel a2dp_ldac_channel_map_mono[] = { + BA_TRANSPORT_PCM_CHANNEL_MONO, +}; + +static const enum ba_transport_pcm_channel a2dp_ldac_channel_map_stereo[] = { + BA_TRANSPORT_PCM_CHANNEL_FL, BA_TRANSPORT_PCM_CHANNEL_FR, +}; + static const struct a2dp_bit_mapping a2dp_ldac_channels[] = { - { LDAC_CHANNEL_MODE_MONO, 1 }, - { LDAC_CHANNEL_MODE_DUAL, 2 }, - { LDAC_CHANNEL_MODE_STEREO, 2 }, + { LDAC_CHANNEL_MODE_MONO, .ch = { 1, a2dp_ldac_channel_map_mono } }, + { LDAC_CHANNEL_MODE_DUAL, .ch = { 2, a2dp_ldac_channel_map_stereo } }, + { LDAC_CHANNEL_MODE_STEREO, .ch = { 2, a2dp_ldac_channel_map_stereo } }, { 0 } }; static const struct a2dp_bit_mapping a2dp_ldac_samplings[] = { - { LDAC_SAMPLING_FREQ_44100, 44100 }, - { LDAC_SAMPLING_FREQ_48000, 48000 }, - { LDAC_SAMPLING_FREQ_88200, 88200 }, - { LDAC_SAMPLING_FREQ_96000, 96000 }, - { LDAC_SAMPLING_FREQ_176400, 176400 }, - { LDAC_SAMPLING_FREQ_192000, 192000 }, + { LDAC_SAMPLING_FREQ_44100, { 44100 } }, + { LDAC_SAMPLING_FREQ_48000, { 48000 } }, + { LDAC_SAMPLING_FREQ_88200, { 88200 } }, + { LDAC_SAMPLING_FREQ_96000, { 96000 } }, + { LDAC_SAMPLING_FREQ_176400, { 176400 } }, + { LDAC_SAMPLING_FREQ_192000, { 192000 } }, { 0 } }; @@ -435,12 +443,12 @@ static int a2dp_ldac_configuration_check( /* Validate configuration against BlueALSA capabilities. */ a2dp_ldac_caps_intersect(&conf_v, &sep->config.capabilities); - if (a2dp_bit_mapping_lookup(a2dp_ldac_samplings, conf_v.sampling_freq) == 0) { + if (a2dp_bit_mapping_lookup(a2dp_ldac_samplings, conf_v.sampling_freq) == -1) { debug("LDAC: Invalid sampling frequency: %#x", conf->sampling_freq); return A2DP_CHECK_ERR_SAMPLING; } - if (a2dp_bit_mapping_lookup(a2dp_ldac_channels, conf_v.channel_mode) == 0) { + if (a2dp_bit_mapping_lookup(a2dp_ldac_channels, conf_v.channel_mode) == -1) { debug("LDAC: Invalid channel mode: %#x", conf->channel_mode); return A2DP_CHECK_ERR_CHANNEL_MODE; } @@ -450,21 +458,24 @@ static int a2dp_ldac_configuration_check( static int a2dp_ldac_transport_init(struct ba_transport *t) { - unsigned int channels; - if ((channels = a2dp_bit_mapping_lookup(a2dp_ldac_channels, - t->a2dp.configuration.ldac.channel_mode)) == 0) + ssize_t channels_i; + if ((channels_i = a2dp_bit_mapping_lookup(a2dp_ldac_channels, + t->a2dp.configuration.ldac.channel_mode)) == -1) return -1; - unsigned int sampling; - if ((sampling = a2dp_bit_mapping_lookup(a2dp_ldac_samplings, - t->a2dp.configuration.ldac.sampling_freq)) == 0) + ssize_t sampling_i; + if ((sampling_i = a2dp_bit_mapping_lookup(a2dp_ldac_samplings, + t->a2dp.configuration.ldac.sampling_freq)) == -1) return -1; /* LDAC library internally for encoding uses 31-bit integers or * floats, so the best choice for PCM sample is signed 32-bit. */ t->a2dp.pcm.format = BA_TRANSPORT_PCM_FORMAT_S32_4LE; - t->a2dp.pcm.channels = channels; - t->a2dp.pcm.sampling = sampling; + t->a2dp.pcm.channels = a2dp_ldac_channels[channels_i].value; + t->a2dp.pcm.sampling = a2dp_ldac_samplings[sampling_i].value; + + memcpy(t->a2dp.pcm.channel_map, a2dp_ldac_channels[channels_i].ch.map, + t->a2dp.pcm.channels * sizeof(*t->a2dp.pcm.channel_map)); return 0; } diff --git a/src/a2dp-mpeg.c b/src/a2dp-mpeg.c index fb309089c..89b22d04a 100644 --- a/src/a2dp-mpeg.c +++ b/src/a2dp-mpeg.c @@ -44,21 +44,29 @@ #include "shared/log.h" #include "shared/rt.h" +static const enum ba_transport_pcm_channel a2dp_mpeg_channel_map_mono[] = { + BA_TRANSPORT_PCM_CHANNEL_MONO, +}; + +static const enum ba_transport_pcm_channel a2dp_mpeg_channel_map_stereo[] = { + BA_TRANSPORT_PCM_CHANNEL_FL, BA_TRANSPORT_PCM_CHANNEL_FR, +}; + static const struct a2dp_bit_mapping a2dp_mpeg_channels[] = { - { MPEG_CHANNEL_MODE_MONO, 1 }, - { MPEG_CHANNEL_MODE_DUAL_CHANNEL, 2 }, - { MPEG_CHANNEL_MODE_STEREO, 2 }, - { MPEG_CHANNEL_MODE_JOINT_STEREO, 2 }, + { MPEG_CHANNEL_MODE_MONO, .ch = { 1, a2dp_mpeg_channel_map_mono } }, + { MPEG_CHANNEL_MODE_DUAL_CHANNEL, .ch = { 2, a2dp_mpeg_channel_map_stereo } }, + { MPEG_CHANNEL_MODE_STEREO, .ch = { 2, a2dp_mpeg_channel_map_stereo } }, + { MPEG_CHANNEL_MODE_JOINT_STEREO, .ch = { 2, a2dp_mpeg_channel_map_stereo } }, { 0 } }; static const struct a2dp_bit_mapping a2dp_mpeg_samplings[] = { - { MPEG_SAMPLING_FREQ_16000, 16000 }, - { MPEG_SAMPLING_FREQ_22050, 22050 }, - { MPEG_SAMPLING_FREQ_24000, 24000 }, - { MPEG_SAMPLING_FREQ_32000, 32000 }, - { MPEG_SAMPLING_FREQ_44100, 44100 }, - { MPEG_SAMPLING_FREQ_48000, 48000 }, + { MPEG_SAMPLING_FREQ_16000, { 16000 } }, + { MPEG_SAMPLING_FREQ_22050, { 22050 } }, + { MPEG_SAMPLING_FREQ_24000, { 24000 } }, + { MPEG_SAMPLING_FREQ_32000, { 32000 } }, + { MPEG_SAMPLING_FREQ_44100, { 44100 } }, + { MPEG_SAMPLING_FREQ_48000, { 48000 } }, { 0 } }; @@ -595,12 +603,12 @@ static int a2dp_mpeg_configuration_check( return A2DP_CHECK_ERR_MPEG_LAYER; } - if (a2dp_bit_mapping_lookup(a2dp_mpeg_channels, conf_v.channel_mode) == 0) { + if (a2dp_bit_mapping_lookup(a2dp_mpeg_channels, conf_v.channel_mode) == -1) { debug("MPEG: Invalid channel mode: %#x", conf->channel_mode); return A2DP_CHECK_ERR_CHANNEL_MODE; } - if (a2dp_bit_mapping_lookup(a2dp_mpeg_samplings, conf_v.sampling_freq) == 0) { + if (a2dp_bit_mapping_lookup(a2dp_mpeg_samplings, conf_v.sampling_freq) == -1) { debug("MPEG: Invalid sampling frequency: %#x", conf->sampling_freq); return A2DP_CHECK_ERR_SAMPLING; } @@ -610,19 +618,22 @@ static int a2dp_mpeg_configuration_check( static int a2dp_mpeg_transport_init(struct ba_transport *t) { - unsigned int channels; - if ((channels = a2dp_bit_mapping_lookup(a2dp_mpeg_channels, - t->a2dp.configuration.mpeg.channel_mode)) == 0) + ssize_t channels_i; + if ((channels_i = a2dp_bit_mapping_lookup(a2dp_mpeg_channels, + t->a2dp.configuration.mpeg.channel_mode)) == -1) return -1; - unsigned int sampling; - if ((sampling = a2dp_bit_mapping_lookup(a2dp_mpeg_samplings, - t->a2dp.configuration.mpeg.sampling_freq)) == 0) + ssize_t sampling_i; + if ((sampling_i = a2dp_bit_mapping_lookup(a2dp_mpeg_samplings, + t->a2dp.configuration.mpeg.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_mpeg_channels[channels_i].value; + t->a2dp.pcm.sampling = a2dp_mpeg_samplings[sampling_i].value; + + memcpy(t->a2dp.pcm.channel_map, a2dp_mpeg_channels[channels_i].ch.map, + t->a2dp.pcm.channels * sizeof(*t->a2dp.pcm.channel_map)); return 0; } diff --git a/src/a2dp-opus.c b/src/a2dp-opus.c index 51d72be01..d7a9a48ba 100644 --- a/src/a2dp-opus.c +++ b/src/a2dp-opus.c @@ -36,15 +36,23 @@ #include "shared/log.h" #include "shared/rt.h" +static const enum ba_transport_pcm_channel a2dp_opus_channel_map_mono[] = { + BA_TRANSPORT_PCM_CHANNEL_MONO, +}; + +static const enum ba_transport_pcm_channel a2dp_opus_channel_map_stereo[] = { + BA_TRANSPORT_PCM_CHANNEL_FL, BA_TRANSPORT_PCM_CHANNEL_FR, +}; + static const struct a2dp_bit_mapping a2dp_opus_channels[] = { - { OPUS_CHANNEL_MODE_MONO, 1 }, - { OPUS_CHANNEL_MODE_DUAL, 2 }, - { OPUS_CHANNEL_MODE_STEREO, 2 }, + { OPUS_CHANNEL_MODE_MONO, .ch = { 1, a2dp_opus_channel_map_mono } }, + { OPUS_CHANNEL_MODE_DUAL, .ch = { 2, a2dp_opus_channel_map_stereo } }, + { OPUS_CHANNEL_MODE_STEREO, .ch = { 2, a2dp_opus_channel_map_stereo } }, { 0 } }; static const struct a2dp_bit_mapping a2dp_opus_samplings[] = { - { OPUS_SAMPLING_FREQ_48000, 48000 }, + { OPUS_SAMPLING_FREQ_48000, { 48000 } }, { 0 } }; @@ -400,7 +408,7 @@ static int a2dp_opus_configuration_check( /* Validate configuration against BlueALSA capabilities. */ a2dp_opus_caps_intersect(&conf_v, &sep->config.capabilities); - if (a2dp_bit_mapping_lookup(a2dp_opus_samplings, conf_v.sampling_freq) == 0) { + if (a2dp_bit_mapping_lookup(a2dp_opus_samplings, conf_v.sampling_freq) == -1) { debug("Opus: Invalid sampling frequency: %#x", conf->sampling_freq); return A2DP_CHECK_ERR_SAMPLING; } @@ -414,7 +422,7 @@ static int a2dp_opus_configuration_check( return A2DP_CHECK_ERR_FRAME_DURATION; } - if (a2dp_bit_mapping_lookup(a2dp_opus_channels, conf_v.channel_mode) == 0) { + if (a2dp_bit_mapping_lookup(a2dp_opus_channels, conf_v.channel_mode) == -1) { debug("Opus: Invalid channel mode: %#x", conf->channel_mode); return A2DP_CHECK_ERR_CHANNEL_MODE; } @@ -424,19 +432,22 @@ static int a2dp_opus_configuration_check( static int a2dp_opus_transport_init(struct ba_transport *t) { - unsigned int channels; - if ((channels = a2dp_bit_mapping_lookup(a2dp_opus_channels, - t->a2dp.configuration.opus.channel_mode)) == 0) + ssize_t channels_i; + if ((channels_i = a2dp_bit_mapping_lookup(a2dp_opus_channels, + t->a2dp.configuration.opus.channel_mode)) == -1) return -1; - unsigned int sampling; - if ((sampling = a2dp_bit_mapping_lookup(a2dp_opus_samplings, - t->a2dp.configuration.opus.sampling_freq)) == 0) + ssize_t sampling_i; + if ((sampling_i = a2dp_bit_mapping_lookup(a2dp_opus_samplings, + t->a2dp.configuration.opus.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_opus_channels[channels_i].value; + t->a2dp.pcm.sampling = a2dp_opus_samplings[sampling_i].value; + + memcpy(t->a2dp.pcm.channel_map, a2dp_opus_channels[channels_i].ch.map, + t->a2dp.pcm.channels * sizeof(*t->a2dp.pcm.channel_map)); return 0; } diff --git a/src/a2dp-sbc.c b/src/a2dp-sbc.c index 6880ef396..8dedf2e95 100644 --- a/src/a2dp-sbc.c +++ b/src/a2dp-sbc.c @@ -38,19 +38,27 @@ #include "shared/log.h" #include "shared/rt.h" +static const enum ba_transport_pcm_channel a2dp_sbc_channel_map_mono[] = { + BA_TRANSPORT_PCM_CHANNEL_MONO, +}; + +static const enum ba_transport_pcm_channel a2dp_sbc_channel_map_stereo[] = { + BA_TRANSPORT_PCM_CHANNEL_FL, BA_TRANSPORT_PCM_CHANNEL_FR, +}; + static const struct a2dp_bit_mapping a2dp_sbc_channels[] = { - { SBC_CHANNEL_MODE_MONO, 1 }, - { SBC_CHANNEL_MODE_DUAL_CHANNEL, 2 }, - { SBC_CHANNEL_MODE_STEREO, 2 }, - { SBC_CHANNEL_MODE_JOINT_STEREO, 2 }, + { SBC_CHANNEL_MODE_MONO, .ch = { 1, a2dp_sbc_channel_map_mono } }, + { SBC_CHANNEL_MODE_DUAL_CHANNEL, .ch = { 2, a2dp_sbc_channel_map_stereo } }, + { SBC_CHANNEL_MODE_STEREO, .ch = { 2, a2dp_sbc_channel_map_stereo } }, + { SBC_CHANNEL_MODE_JOINT_STEREO, .ch = { 2, a2dp_sbc_channel_map_stereo } }, { 0 }, }; static const struct a2dp_bit_mapping a2dp_sbc_samplings[] = { - { SBC_SAMPLING_FREQ_16000, 16000 }, - { SBC_SAMPLING_FREQ_32000, 32000 }, - { SBC_SAMPLING_FREQ_44100, 44100 }, - { SBC_SAMPLING_FREQ_48000, 48000 }, + { SBC_SAMPLING_FREQ_16000, { 16000 } }, + { SBC_SAMPLING_FREQ_32000, { 32000 } }, + { SBC_SAMPLING_FREQ_44100, { 44100 } }, + { SBC_SAMPLING_FREQ_48000, { 48000 } }, { 0 }, }; @@ -488,12 +496,12 @@ static int a2dp_sbc_configuration_check( /* Validate configuration against BlueALSA capabilities. */ a2dp_sbc_caps_intersect(&conf_v, &sep->config.capabilities); - if (a2dp_bit_mapping_lookup(a2dp_sbc_samplings, conf_v.sampling_freq) == 0) { + if (a2dp_bit_mapping_lookup(a2dp_sbc_samplings, conf_v.sampling_freq) == -1) { debug("SBC: Invalid sampling frequency: %#x", conf->sampling_freq); return A2DP_CHECK_ERR_SAMPLING; } - if (a2dp_bit_mapping_lookup(a2dp_sbc_channels, conf_v.channel_mode) == 0) { + if (a2dp_bit_mapping_lookup(a2dp_sbc_channels, conf_v.channel_mode) == -1) { debug("SBC: Invalid channel mode: %#x", conf->channel_mode); return A2DP_CHECK_ERR_CHANNEL_MODE; } @@ -541,19 +549,22 @@ static int a2dp_sbc_configuration_check( static int a2dp_sbc_transport_init(struct ba_transport *t) { - unsigned int channels; - if ((channels = a2dp_bit_mapping_lookup(a2dp_sbc_channels, - t->a2dp.configuration.sbc.channel_mode)) == 0) + ssize_t channels_i; + if ((channels_i = a2dp_bit_mapping_lookup(a2dp_sbc_channels, + t->a2dp.configuration.sbc.channel_mode)) == -1) return -1; - unsigned int sampling; - if ((sampling = a2dp_bit_mapping_lookup(a2dp_sbc_samplings, - t->a2dp.configuration.sbc.sampling_freq)) == 0) + ssize_t sampling_i; + if ((sampling_i = a2dp_bit_mapping_lookup(a2dp_sbc_samplings, + t->a2dp.configuration.sbc.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_sbc_channels[channels_i].value; + t->a2dp.pcm.sampling = a2dp_sbc_samplings[sampling_i].value; + + memcpy(t->a2dp.pcm.channel_map, a2dp_sbc_channels[channels_i].ch.map, + t->a2dp.pcm.channels * sizeof(*t->a2dp.pcm.channel_map)); return 0; } diff --git a/src/a2dp.c b/src/a2dp.c index 1257a65c1..f6d45117b 100644 --- a/src/a2dp.c +++ b/src/a2dp.c @@ -120,15 +120,15 @@ int a2dp_bit_mapping_foreach( * * @param mappings Zero-terminated array of A2DP mappings. * @param bit_value A2DP codec bit-value to be looked up. - * @return On success this function returns the associated value. Otherwise, - * 0 is returned. */ -unsigned int a2dp_bit_mapping_lookup( + * @return This function returns the index of the mapping, or -1 if mapping + * for the given bit-value does not exist. */ +ssize_t a2dp_bit_mapping_lookup( const struct a2dp_bit_mapping *mappings, uint32_t bit_value) { for (size_t i = 0; mappings[i].bit_value != 0; i++) if (mappings[i].bit_value == bit_value) - return mappings[i].value; - return 0; + return i; + return -1; } /** diff --git a/src/a2dp.h b/src/a2dp.h index 0a31c6a3d..7a7ba0e89 100644 --- a/src/a2dp.h +++ b/src/a2dp.h @@ -16,10 +16,13 @@ # include #endif +#include #include #include #include +#include +#include "ba-transport-pcm.h" #include "shared/a2dp-codecs.h" /** @@ -31,9 +34,22 @@ * possible value for the given bit-field. */ struct a2dp_bit_mapping { uint32_t bit_value; - unsigned int value; + union { + /* Single value mapping. */ + unsigned int value; + /* Channel mode mapping with channel count and channel map. */ + struct { + unsigned int channels; + const enum ba_transport_pcm_channel *map; + } ch; + }; }; +static_assert( + offsetof(struct a2dp_bit_mapping, value) == + offsetof(struct a2dp_bit_mapping, ch.channels), + "Invalid a2dp_bit_mapping structure layout"); + /** * Callback function for iterating over A2DP bit-field. */ typedef int (*a2dp_bit_mapping_foreach_func)( @@ -53,7 +69,7 @@ int a2dp_bit_mapping_foreach( a2dp_bit_mapping_foreach_func func, void *userdata); -unsigned int a2dp_bit_mapping_lookup( +ssize_t a2dp_bit_mapping_lookup( const struct a2dp_bit_mapping *mappings, uint32_t bit_value); @@ -143,9 +159,6 @@ struct a2dp_sep_config { }; -/* XXX: avoid circular dependency */ -struct ba_transport; - /** * A2DP Stream End-Point. */ struct a2dp_sep { diff --git a/src/asound/bluealsa-ctl.c b/src/asound/bluealsa-ctl.c index 7a7b622a5..9987003cb 100644 --- a/src/asound/bluealsa-ctl.c +++ b/src/asound/bluealsa-ctl.c @@ -1030,14 +1030,12 @@ static int bluealsa_read_integer(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, long value[0] = active ? elem->dev->battery_level : 0; break; case CTL_ELEM_TYPE_SWITCH: - value[0] = active ? !pcm->volume.ch1_muted : 0; - if (pcm->channels == 2) - value[1] = active ? !pcm->volume.ch2_muted : 0; + for (size_t i = 0; i < pcm->channels; i++) + value[i] = active ? !pcm->volume[i].muted : 0; break; case CTL_ELEM_TYPE_VOLUME: - value[0] = active ? pcm->volume.ch1_volume : 0; - if (pcm->channels == 2) - value[1] = active ? pcm->volume.ch2_volume : 0; + for (size_t i = 0; i < pcm->channels; i++) + value[i] = active ? pcm->volume[i].volume : 0; break; case CTL_ELEM_TYPE_CODEC: case CTL_ELEM_TYPE_VOLUME_MODE: @@ -1056,7 +1054,9 @@ static int bluealsa_write_integer(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, lon struct ctl_elem *elem = &ctl->elem_list[key]; struct ba_pcm *pcm = elem->pcm; - uint16_t old = pcm->volume.raw; + + uint8_t old[ARRAYSIZE(pcm->volume)]; + memcpy(old, pcm->volume, sizeof(old)); if (!elem->active) { /* Ignore the write request because the associated PCM profile has been @@ -1072,14 +1072,12 @@ static int bluealsa_write_integer(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, lon /* this element should be read-only */ return -EINVAL; case CTL_ELEM_TYPE_SWITCH: - pcm->volume.ch1_muted = !value[0]; - if (pcm->channels == 2) - pcm->volume.ch2_muted = !value[1]; + for (size_t i = 0; i < pcm->channels; i++) + pcm->volume[i].muted = !value[i]; break; case CTL_ELEM_TYPE_VOLUME: - pcm->volume.ch1_volume = value[0]; - if (pcm->channels == 2) - pcm->volume.ch2_volume = value[1]; + for (size_t i = 0; i < pcm->channels; i++) + pcm->volume[i].volume = value[i]; break; case CTL_ELEM_TYPE_CODEC: case CTL_ELEM_TYPE_VOLUME_MODE: @@ -1088,7 +1086,7 @@ static int bluealsa_write_integer(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key, lon } /* check whether update is required */ - if (pcm->volume.raw == old) + if (memcmp(pcm->volume, old, sizeof(old)) == 0) return 0; if (!ba_dbus_pcm_update(&ctl->dbus_ctx, pcm, BLUEALSA_PCM_VOLUME, NULL)) diff --git a/src/asound/bluealsa-pcm.c b/src/asound/bluealsa-pcm.c index 79b97a67f..67a14b6bb 100644 --- a/src/asound/bluealsa-pcm.c +++ b/src/asound/bluealsa-pcm.c @@ -1336,9 +1336,7 @@ static int bluealsa_set_hw_constraint(struct bluealsa_pcm *pcm) { n = 0; for (size_t i = 0; i < ARRAYSIZE(codec->channels) && codec->channels[i] != 0; i++) - /* TODO: Remove this when BlueALSA will support channels mapping. */ - if (codec->channels[i] <= 2) - list[n++] = codec->channels[i]; + list[n++] = codec->channels[i]; if ((err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_CHANNELS, n, list)) < 0) return err; @@ -1353,21 +1351,26 @@ static int bluealsa_set_hw_constraint(struct bluealsa_pcm *pcm) { static bool bluealsa_update_pcm_volume(struct bluealsa_pcm *pcm, int volume, int mute, DBusError *err) { - uint16_t old = pcm->ba_pcm.volume.raw; + + uint8_t old[ARRAYSIZE(pcm->ba_pcm.volume)]; + memcpy(old, pcm->ba_pcm.volume, sizeof(old)); + if (volume >= 0) { const int v = BA_PCM_VOLUME_MAX(&pcm->ba_pcm) * volume / 100; - pcm->ba_pcm.volume.ch1_volume = v; - if (pcm->ba_pcm.channels == 2) - pcm->ba_pcm.volume.ch2_volume = v; + for (size_t i = 0; i < pcm->ba_pcm.channels; i++) + pcm->ba_pcm.volume[i].volume = v; } + if (mute >= 0) { const bool v = !!mute; - pcm->ba_pcm.volume.ch1_muted = v; - if (pcm->ba_pcm.channels == 2) - pcm->ba_pcm.volume.ch2_muted = v; + for (size_t i = 0; i < pcm->ba_pcm.channels; i++) + pcm->ba_pcm.volume[i].muted = v; } - if (pcm->ba_pcm.volume.raw == old) + + /* check whether update is required */ + if (memcmp(pcm->ba_pcm.volume, old, sizeof(old)) == 0) return true; + return ba_dbus_pcm_update(&pcm->dbus_ctx, &pcm->ba_pcm, BLUEALSA_PCM_VOLUME, err); } diff --git a/src/ba-transport-pcm.c b/src/ba-transport-pcm.c index bc0fcd8d6..aaf2694e1 100644 --- a/src/ba-transport-pcm.c +++ b/src/ba-transport-pcm.c @@ -77,10 +77,10 @@ int transport_pcm_init( pcm->pipe[0] = -1; pcm->pipe[1] = -1; - pcm->volume[0].level = config.volume_init_level; - pcm->volume[1].level = config.volume_init_level; - ba_transport_pcm_volume_set(&pcm->volume[0], NULL, NULL, NULL); - ba_transport_pcm_volume_set(&pcm->volume[1], NULL, NULL, NULL); + for (size_t i = 0; i < ARRAYSIZE(pcm->volume); i++) { + pcm->volume[i].level = config.volume_init_level; + ba_transport_pcm_volume_set(&pcm->volume[i], NULL, NULL, NULL); + } pthread_mutex_init(&pcm->mutex, NULL); pthread_mutex_init(&pcm->state_mtx, NULL); @@ -649,23 +649,15 @@ int ba_transport_pcm_volume_update(struct ba_transport_pcm *pcm) { if (t->profile & BA_TRANSPORT_PROFILE_MASK_A2DP) { /* A2DP specification defines volume property as a single value - volume - * for only one channel. For stereo audio we will use an average of left - * and right PCM channels. */ - - unsigned int volume; - switch (pcm->channels) { - case 1: - volume = ba_transport_pcm_volume_level_to_range( - pcm->volume[0].level, BLUEZ_A2DP_VOLUME_MAX); - break; - case 2: - volume = ba_transport_pcm_volume_level_to_range( - (pcm->volume[0].level + pcm->volume[1].level) / 2, - BLUEZ_A2DP_VOLUME_MAX); - break; - default: - g_assert_not_reached(); - } + * for only one channel. For multi-channel audio, we will use calculated + * average volume level. */ + + int level_sum = 0; + for (size_t i = 0; i < pcm->channels; i++) + level_sum += pcm->volume[i].level; + + unsigned int volume = ba_transport_pcm_volume_level_to_range( + level_sum / pcm->channels, BLUEZ_A2DP_VOLUME_MAX); /* skip update if nothing has changed */ if (volume != t->a2dp.volume) { @@ -740,3 +732,31 @@ void ba_transport_pcm_delay_adjustment_set( GINT_TO_POINTER(codec_id), GINT_TO_POINTER(adjustment)); pthread_mutex_unlock(&pcm->delay_adjustments_mtx); } + +const char *ba_transport_pcm_channel_to_string( + enum ba_transport_pcm_channel channel) { + switch (channel) { + case BA_TRANSPORT_PCM_CHANNEL_MONO: + return "MONO"; + case BA_TRANSPORT_PCM_CHANNEL_FL: + return "FL"; + case BA_TRANSPORT_PCM_CHANNEL_FR: + return "FR"; + case BA_TRANSPORT_PCM_CHANNEL_FC: + return "FC"; + case BA_TRANSPORT_PCM_CHANNEL_RL: + return "RL"; + case BA_TRANSPORT_PCM_CHANNEL_RR: + return "RR"; + case BA_TRANSPORT_PCM_CHANNEL_SL: + return "SL"; + case BA_TRANSPORT_PCM_CHANNEL_SR: + return "SR"; + case BA_TRANSPORT_PCM_CHANNEL_LFE: + return "LFE"; + default: + error("Unsupported channel type: %#x", channel); + g_assert_not_reached(); + return NULL; + } +} diff --git a/src/ba-transport-pcm.h b/src/ba-transport-pcm.h index 260a600e3..e6fe79719 100644 --- a/src/ba-transport-pcm.h +++ b/src/ba-transport-pcm.h @@ -54,6 +54,18 @@ enum ba_transport_pcm_state { #define BA_TRANSPORT_PCM_FORMAT_S24_4LE BA_TRANSPORT_PCM_FORMAT(1, 24, 4, 0) #define BA_TRANSPORT_PCM_FORMAT_S32_4LE BA_TRANSPORT_PCM_FORMAT(1, 32, 4, 0) +enum ba_transport_pcm_channel { + BA_TRANSPORT_PCM_CHANNEL_MONO, /* mono */ + BA_TRANSPORT_PCM_CHANNEL_FL, /* front left */ + BA_TRANSPORT_PCM_CHANNEL_FR, /* front right */ + BA_TRANSPORT_PCM_CHANNEL_FC, /* front center */ + BA_TRANSPORT_PCM_CHANNEL_RL, /* rear left */ + BA_TRANSPORT_PCM_CHANNEL_RR, /* rear right */ + BA_TRANSPORT_PCM_CHANNEL_SL, /* side left */ + BA_TRANSPORT_PCM_CHANNEL_SR, /* side right */ + BA_TRANSPORT_PCM_CHANNEL_LFE, /* low frequency effects */ +}; + struct ba_transport_pcm_volume { /* volume level change in "dB * 100" */ int level; @@ -126,9 +138,11 @@ struct ba_transport_pcm { /* internal software volume control */ bool soft_volume; - /* Volume configuration for channel left [0] and right [1]. In case of - * a monophonic sound, only the left [0] channel shall be used. */ - struct ba_transport_pcm_volume volume[2]; + /* channel map for current PCM configuration */ + enum ba_transport_pcm_channel channel_map[8]; + + /* per-channel volume */ + struct ba_transport_pcm_volume volume[8]; /* new PCM client mutex */ pthread_mutex_t client_mtx; @@ -246,4 +260,7 @@ void ba_transport_pcm_delay_adjustment_set( uint32_t codec_id, int16_t adjustment); +const char *ba_transport_pcm_channel_to_string( + enum ba_transport_pcm_channel channel); + #endif diff --git a/src/bluealsa-dbus.c b/src/bluealsa-dbus.c index 873cceb8e..d66d4f03e 100644 --- a/src/bluealsa-dbus.c +++ b/src/bluealsa-dbus.c @@ -245,6 +245,17 @@ static GVariant *ba_variant_new_pcm_channels(const struct ba_transport_pcm *pcm) return g_variant_new_byte(pcm->channels); } +static GVariant *ba_variant_new_pcm_channel_map(const struct ba_transport_pcm *pcm) { + + const char *strv[ARRAYSIZE(pcm->channel_map)]; + const size_t n = pcm->channels; + + for (size_t i = 0; i < n; i++) + strv[i] = ba_transport_pcm_channel_to_string(pcm->channel_map[i]); + + return g_variant_new_strv(strv, n); +} + static GVariant *ba_variant_new_pcm_sampling(const struct ba_transport_pcm *pcm) { return g_variant_new_uint32(pcm->sampling); } @@ -286,13 +297,17 @@ static uint8_t ba_volume_pack_dbus_volume(bool muted, int value) { } static GVariant *ba_variant_new_pcm_volume(const struct ba_transport_pcm *pcm) { + const bool is_sco = pcm->t->profile & BA_TRANSPORT_PROFILE_MASK_SCO; const int max = is_sco ? HFP_VOLUME_GAIN_MAX : BLUEZ_A2DP_VOLUME_MAX; - uint8_t ch1 = ba_volume_pack_dbus_volume(pcm->volume[0].scale == 0, - ba_transport_pcm_volume_level_to_range(pcm->volume[0].level, max)); - uint8_t ch2 = ba_volume_pack_dbus_volume(pcm->volume[1].scale == 0, - ba_transport_pcm_volume_level_to_range(pcm->volume[1].level, max)); - return g_variant_new_uint16((ch1 << 8) | (pcm->channels == 1 ? 0 : ch2)); + const size_t n = pcm->channels; + + uint8_t volume[ARRAYSIZE(pcm->volume)]; + for (size_t i = 0; i < n; i++) + volume[i] = ba_volume_pack_dbus_volume(pcm->volume[i].scale == 0, + ba_transport_pcm_volume_level_to_range(pcm->volume[i].level, max)); + + return g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, volume, n, sizeof(*volume)); } static int ba_populate_channels(struct a2dp_bit_mapping mapping, void *userdata) { @@ -305,6 +320,16 @@ static int ba_populate_sampling(struct a2dp_bit_mapping mapping, void *userdata) return 0; } +static int ba_populate_channel_map(struct a2dp_bit_mapping mapping, void *userdata) { + + const char *strv[16]; + for (size_t i = 0; i < mapping.ch.channels; i++) + strv[i] = ba_transport_pcm_channel_to_string(mapping.ch.map[i]); + + g_variant_builder_add_value(userdata, g_variant_new_strv(strv, mapping.ch.channels)); + return 0; +} + /** * Populate dict variant builder with remote SEP properties. */ static void ba_variant_populate_remote_sep(GVariantBuilder *props, @@ -327,6 +352,10 @@ static void ba_variant_populate_remote_sep(GVariantBuilder *props, sep->caps_helpers->foreach_sampling_freq(&caps, stream, ba_populate_sampling, &builder); g_variant_builder_add(props, "{sv}", "SupportedSampling", g_variant_builder_end(&builder)); + g_variant_builder_init(&builder, G_VARIANT_TYPE("aas")); + sep->caps_helpers->foreach_channel_mode(&caps, stream, ba_populate_channel_map, &builder); + g_variant_builder_add(props, "{sv}", "ChannelMaps", g_variant_builder_end(&builder)); + } static GVariant *bluealsa_manager_get_property(const char *property, @@ -937,6 +966,8 @@ static GVariant *bluealsa_pcm_get_property(const char *property, return ba_variant_new_pcm_format(pcm); if (strcmp(property, "Channels") == 0) return ba_variant_new_pcm_channels(pcm); + if (strcmp(property, "ChannelMap") == 0) + return ba_variant_new_pcm_channel_map(pcm); if (strcmp(property, "Sampling") == 0) return ba_variant_new_pcm_sampling(pcm); if (strcmp(property, "Codec") == 0) { @@ -970,14 +1001,13 @@ static GVariant *bluealsa_pcm_get_property(const char *property, static bool bluealsa_pcm_set_property(const char *property, GVariant *value, GError **error, void *userdata) { - (void)error; struct ba_transport_pcm *pcm = userdata; if (strcmp(property, "SoftVolume") == 0) { pcm->soft_volume = g_variant_get_boolean(value); bluealsa_dbus_pcm_update(pcm, BA_DBUS_PCM_UPDATE_SOFT_VOLUME); - return TRUE; + return true; } if (strcmp(property, "Volume") == 0) { @@ -985,30 +1015,37 @@ static bool bluealsa_pcm_set_property(const char *property, GVariant *value, const bool is_sco = pcm->t->profile & BA_TRANSPORT_PROFILE_MASK_SCO; const int max = is_sco ? HFP_VOLUME_GAIN_MAX : BLUEZ_A2DP_VOLUME_MAX; - uint16_t packed = g_variant_get_uint16(value); - uint8_t ch1 = packed >> 8; - uint8_t ch2 = packed & 0xFF; + size_t channels = 0; + const uint8_t *volume = g_variant_get_fixed_array(value, &channels, sizeof(uint8_t)); - int ch1_level = ba_transport_pcm_volume_range_to_level(ch1 & 0x7F, max); - bool ch1_muted = !!(ch1 & 0x80); - int ch2_level = ba_transport_pcm_volume_range_to_level(ch2 & 0x7F, max); - bool ch2_muted = !!(ch2 & 0x80); + if (channels != pcm->channels) { + *error = g_error_new(G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, + "Invalid number of channels: %zu != %u", channels, pcm->channels); + return false; + } pthread_mutex_lock(&pcm->mutex); - ba_transport_pcm_volume_set(&pcm->volume[0], &ch1_level, &ch1_muted, NULL); - ba_transport_pcm_volume_set(&pcm->volume[1], &ch2_level, &ch2_muted, NULL); - pthread_mutex_unlock(&pcm->mutex); - debug("Setting volume: %u [%.2f dB] %c%c %u [%.2f dB]", - ch1 & 0x7F, 0.01 * ch1_level, ch1_muted ? 'x' : '<', - ch2_muted ? 'x' : '>', ch2 & 0x7F, 0.01 * ch2_level); + for (size_t i = 0; i < channels; i++) { + + const bool muted = !!(volume[i] & 0x80); + const int level = ba_transport_pcm_volume_range_to_level(volume[i] & 0x7F, max); + + debug("Setting volume [ch=%zu]: %u [%.2f dB] [%c]", + i, volume[i] & 0x7F, 0.01 * level, muted ? 'x' : ' '); + + ba_transport_pcm_volume_set(&pcm->volume[i], &level, &muted, NULL); + + } + + pthread_mutex_unlock(&pcm->mutex); ba_transport_pcm_volume_update(pcm); - return TRUE; + return true; } g_assert_not_reached(); - return FALSE; + return false; } /** @@ -1075,6 +1112,8 @@ void bluealsa_dbus_pcm_update(struct ba_transport_pcm *pcm, unsigned int mask) { g_variant_builder_add(&props, "{sv}", "Format", ba_variant_new_pcm_format(pcm)); if (mask & BA_DBUS_PCM_UPDATE_CHANNELS) g_variant_builder_add(&props, "{sv}", "Channels", ba_variant_new_pcm_channels(pcm)); + if (mask & BA_DBUS_PCM_UPDATE_CHANNEL_MAP) + g_variant_builder_add(&props, "{sv}", "ChannelMap", ba_variant_new_pcm_channel_map(pcm)); if (mask & BA_DBUS_PCM_UPDATE_SAMPLING) g_variant_builder_add(&props, "{sv}", "Sampling", ba_variant_new_pcm_sampling(pcm)); if (mask & BA_DBUS_PCM_UPDATE_CODEC) diff --git a/src/bluealsa-dbus.h b/src/bluealsa-dbus.h index 74ad3763c..d81cc0f9d 100644 --- a/src/bluealsa-dbus.h +++ b/src/bluealsa-dbus.h @@ -1,6 +1,6 @@ /* * BlueALSA - bluealsa-dbus.h - * Copyright (c) 2016-2022 Arkadiusz Bokowy + * Copyright (c) 2016-2024 Arkadiusz Bokowy * * This file is a part of bluez-alsa. * @@ -24,14 +24,15 @@ #define BA_DBUS_PCM_UPDATE_FORMAT (1 << 0) #define BA_DBUS_PCM_UPDATE_CHANNELS (1 << 1) -#define BA_DBUS_PCM_UPDATE_SAMPLING (1 << 2) -#define BA_DBUS_PCM_UPDATE_CODEC (1 << 3) -#define BA_DBUS_PCM_UPDATE_CODEC_CONFIG (1 << 4) -#define BA_DBUS_PCM_UPDATE_DELAY (1 << 5) -#define BA_DBUS_PCM_UPDATE_DELAY_ADJUSTMENT (1 << 6) -#define BA_DBUS_PCM_UPDATE_SOFT_VOLUME (1 << 7) -#define BA_DBUS_PCM_UPDATE_VOLUME (1 << 8) -#define BA_DBUS_PCM_UPDATE_RUNNING (1 << 9) +#define BA_DBUS_PCM_UPDATE_CHANNEL_MAP (1 << 2) +#define BA_DBUS_PCM_UPDATE_SAMPLING (1 << 3) +#define BA_DBUS_PCM_UPDATE_CODEC (1 << 4) +#define BA_DBUS_PCM_UPDATE_CODEC_CONFIG (1 << 5) +#define BA_DBUS_PCM_UPDATE_DELAY (1 << 6) +#define BA_DBUS_PCM_UPDATE_DELAY_ADJUSTMENT (1 << 7) +#define BA_DBUS_PCM_UPDATE_SOFT_VOLUME (1 << 8) +#define BA_DBUS_PCM_UPDATE_VOLUME (1 << 9) +#define BA_DBUS_PCM_UPDATE_RUNNING (1 << 10) #define BA_DBUS_RFCOMM_UPDATE_FEATURES (1 << 0) #define BA_DBUS_RFCOMM_UPDATE_BATTERY (1 << 1) diff --git a/src/bluealsa-iface.xml b/src/bluealsa-iface.xml index aeef7f2c5..5f29da915 100644 --- a/src/bluealsa-iface.xml +++ b/src/bluealsa-iface.xml @@ -1,5 +1,5 @@