09.12.2019, 12:33, "Andrey Semashev" <andrey.semas...@gmail.com>: > I have another piece of feedback to provide. Sometimes I experience > audio dropouts. Sometimes in both left and right headphones, sometimes > just one. In particular, I noticed this happen when I have a > Bluetooth-connected DualShock 4 gamepad connected and playing games, but > it also happens without it, although less often. > > I assume this is caused by Bluetooth bandwidth limitation. Note that EOZ > Air are "truly wireless" (i.e. the two headphones connect wirelessly), > and I have multiple WiFi networks available (one access point in the > same room as the Bluetooth transmitter, a few others behind walls). I > expect 2.4 GHz radio to be rather crowded. The gamepad and the > headphones are in the same room as the Bluetooth transmitter, in clear > direct visibility, so it can't get better than that. > > I can see Pali's patches offer reduce_encoder_bitrate API that is > supposed to mitigate this problem, but both SBC XQ profiles don't allow > bitrate reduction. I think, SBC XQ desperately needs to support bitrate > reduction, as the codec with the highest bitrate out of all.
Yes, and my patch was providing 100% adaptative SBC XQ, with native bitpool reduction, as you suggest. But something more complex and yet more "static"' has been preferred. Users might > actually have reduced experience due to audio dropouts compared to the > previously supported SBC HQ. > > On 2019-12-07 21:09, Andrey Semashev wrote: >> FWIW, I tested the patch[1] from your github repository with PulseAudio >> 13.0 on Ubuntu 19.10 with my EOZ Air. It works, and the sound quality is >> subjectively better compared to the stock PA 13.0. The noise level that >> can be heard on silent audio sections seems to be lower. >> >> However, I can still hear compression artefacts on quiet or nearly >> silent audio sections, which sound like high-pitch squeaking sounds. It >> can often be heard on various music fade out sections. I don't know if >> this is inherrent from SBC codec itself or is a deficiency of libsbc >> implementation, or the headphones. These artefacts are my main complaint >> about SBC. >> >> My EOZ Air also supports AAC, and the audio quality is better still with >> it (I tested with pulseaudio-modules-bt[2] and my Android phone). There >> are no squeaky artefacts with AAC. >> >> Which brings me to my question to PA developers. Would an addition of >> support for AAC be possible? >> >> pulseaudio-modules-bt uses libfdk-aac to implement AAC, and if possible, >> I would prefer that library as it provides the best quality available on >> Linux. However, if licensing is an issue, there is also a built-in >> implementation in ffmpeg. >> >> [1]: Patch obtained with `git diff >> >> 200618b32f0964a479d69c9b6e5073e6931c370a..0853d7465729c1fb6c25745c05dc749a1b9a8d6f` >> >> [2]: https://github.com/EHfive/pulseaudio-modules-bt >> >> On 2019-09-20 12:45, Hyperion wrote: >>> HI, >>> >>> I have reworked my mod so that it can safely achieve bitpool 94 in >>> both dual channel and joint stereo, as long as the device supports it >>> (only my high end Harman Kardon support 94 in joint stereo). >>> Devices supporting only bitpool 53 are kept safe (bitpool is always >>> negociated, never forced : this doesn't use fixed caps). >>> >>> Anyway most devices support at least bitpool 47 in dual channel mode, >>> and BT (version 3 and up) bandwidth is much higher : so this patch >>> deprecates both APTX and LDAC. >>> >>> The Git is up to date : https://github.com/JPGuillemin/pulseaudio >>> >>> This patch is likely safe to be applied on the official master cause >>> it doesn't need Bluez multi-codec API, and doesn't introduce any >>> patent issue (mainly from CSR, openaptx being underground reverse >>> ingeniering). >>> >>> All the best >>> JP >>> >>> 19.09.2019, 13:07, "Hyperion" <h1p8r...@yandex.com>: >>>> The Git branch with my mods : >>>> https://github.com/JPGuillemin/pulseaudio/tree/SBC-XQ >>>> >>>> I'm sure it needs improvement, but I need testers ;) for now it works >>>> as expected on all my devices... >>>> >>>> JP >>>> >>>> 19.09.2019, 11:21, "Hyperion" <h1p8r...@yandex.com>: >>>>> Yes, thanks, you're right, I'm going to do it as soon as I get >>>>> enough testing feedback. >>>>> >>>>> Talking about tests : I'm going to switch back to Pali's default >>>>> values for announced bitpool capabilities, cause some (rare) devices >>>>> produces a few distorsion with dual channel 47 bitpool. >>>>> >>>>> So back to dual 38 / 76 , which should be ok on any device. >>>>> >>>>> New patch : >>>>> >>>>> http://download.zenwalk.org/x86_64/testing/pulseaudio-13.0-SBC-XQ_V3.patch >>>>> >>>>> Thanks for testing :) >>>>> >>>>> jp >>>>> >>>>> 19.09.2019, 10:27, "Andrey Semashev" <andrey.semas...@gmail.com>: >>>>>> I'm not the maintainer, but it might be better to create a merge >>>>>> request >>>>>> in GitLab project: >>>>>> >>>>>> https://gitlab.freedesktop.org/pulseaudio/pulseaudio >>>>>> >>>>>> and post any additional information there so it doesn't get lost. >>>>>> >>>>>> On 2019-09-18 14:02, Hyperion wrote: >>>>>>> Patch V2 with added DUAL_CHANNEL as preferred mode. >>>>>>> >>>>>>> Works without any issue on more than 10 stereo and mono >>>>>>> devices that I have here. >>>>>>> >>>>>>> >>>>>>> http://download.zenwalk.org/x86_64/testing/pulseaudio-13.0-SBC-XQ_V2.patch >>>>>>> >>>>>>> /*** >>>>>>> This file is part of PulseAudio. >>>>>>> >>>>>>> Copyright 2018-2019 Pali Rohár <pali.ro...@gmail.com> >>>>>>> >>>>>>> PulseAudio is free software; you can redistribute it and/or >>>>>>> modify >>>>>>> it under the terms of the GNU Lesser General Public License as >>>>>>> published by the Free Software Foundation; either version >>>>>>> 2.1 of the >>>>>>> License, or (at your option) any later version. >>>>>>> >>>>>>> PulseAudio is distributed in the hope that it will be >>>>>>> useful, but >>>>>>> WITHOUT ANY WARRANTY; without even the implied warranty of >>>>>>> MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See >>>>>>> the GNU >>>>>>> General Public License for more details. >>>>>>> >>>>>>> You should have received a copy of the GNU Lesser General >>>>>>> Public >>>>>>> License along with PulseAudio; if not, see >>>>>>> <http://www.gnu.org/licenses/>. >>>>>>> ***/ >>>>>>> >>>>>>> #ifdef HAVE_CONFIG_H >>>>>>> #include <config.h> >>>>>>> #endif >>>>>>> >>>>>>> #include <pulsecore/core-util.h> >>>>>>> #include <pulsecore/log.h> >>>>>>> #include <pulsecore/macro.h> >>>>>>> #include <pulsecore/once.h> >>>>>>> #include <pulse/sample.h> >>>>>>> #include <pulse/xmalloc.h> >>>>>>> >>>>>>> #include <arpa/inet.h> >>>>>>> >>>>>>> #include <sbc/sbc.h> >>>>>>> >>>>>>> #include "a2dp-codecs.h" >>>>>>> #include "a2dp-codec-api.h" >>>>>>> #include "rtp.h" >>>>>>> >>>>>>> #define SBC_BITPOOL_DEC_LIMIT 32 >>>>>>> #define SBC_BITPOOL_DEC_STEP 5 >>>>>>> >>>>>>> struct sbc_info { >>>>>>> sbc_t sbc; /* Codec data */ >>>>>>> size_t codesize, frame_length; /* SBC Codesize, >>>>>>> frame_length. We simply cache those values here */ >>>>>>> uint16_t seq_num; /* Cumulative packet sequence */ >>>>>>> uint8_t frequency; >>>>>>> uint8_t blocks; >>>>>>> uint8_t subbands; >>>>>>> uint8_t mode; >>>>>>> uint8_t allocation; >>>>>>> uint8_t initial_bitpool; >>>>>>> uint8_t min_bitpool; >>>>>>> uint8_t max_bitpool; >>>>>>> }; >>>>>>> >>>>>>> static bool can_accept_capabilities(const uint8_t >>>>>>> *capabilities_buffer, uint8_t capabilities_size, bool for_encoding) { >>>>>>> const a2dp_sbc_t *capabilities = (const a2dp_sbc_t *) >>>>>>> capabilities_buffer; >>>>>>> >>>>>>> if (capabilities_size != sizeof(*capabilities)) >>>>>>> return false; >>>>>>> >>>>>>> if (!(capabilities->frequency & (SBC_SAMPLING_FREQ_16000 >>>>>>> | SBC_SAMPLING_FREQ_32000 | SBC_SAMPLING_FREQ_44100 | >>>>>>> SBC_SAMPLING_FREQ_48000))) >>>>>>> return false; >>>>>>> >>>>>>> if (!(capabilities->channel_mode & (SBC_CHANNEL_MODE_MONO >>>>>>> | SBC_CHANNEL_MODE_DUAL_CHANNEL | SBC_CHANNEL_MODE_STEREO | >>>>>>> SBC_CHANNEL_MODE_JOINT_STEREO))) >>>>>>> return false; >>>>>>> >>>>>>> if (!(capabilities->allocation_method & >>>>>>> (SBC_ALLOCATION_SNR | SBC_ALLOCATION_LOUDNESS))) >>>>>>> return false; >>>>>>> >>>>>>> if (!(capabilities->subbands & (SBC_SUBBANDS_4 | >>>>>>> SBC_SUBBANDS_8))) >>>>>>> return false; >>>>>>> >>>>>>> if (!(capabilities->block_length & (SBC_BLOCK_LENGTH_4 | >>>>>>> SBC_BLOCK_LENGTH_8 | SBC_BLOCK_LENGTH_12 | SBC_BLOCK_LENGTH_16))) >>>>>>> return false; >>>>>>> >>>>>>> return true; >>>>>>> } >>>>>>> >>>>>>> static const char *choose_remote_endpoint(const pa_hashmap >>>>>>> *capabilities_hashmap, const pa_sample_spec *default_sample_spec, >>>>>>> bool for_encoding) { >>>>>>> const pa_a2dp_codec_capabilities *a2dp_capabilities; >>>>>>> const char *key; >>>>>>> void *state; >>>>>>> >>>>>>> /* There is no preference, just choose random valid entry */ >>>>>>> PA_HASHMAP_FOREACH_KV(key, a2dp_capabilities, >>>>>>> capabilities_hashmap, state) { >>>>>>> if >>>>>>> (can_accept_capabilities(a2dp_capabilities->buffer, >>>>>>> a2dp_capabilities->size, for_encoding)) >>>>>>> return key; >>>>>>> } >>>>>>> >>>>>>> return NULL; >>>>>>> } >>>>>>> >>>>>>> static uint8_t fill_capabilities(uint8_t >>>>>>> capabilities_buffer[MAX_A2DP_CAPS_SIZE]) { >>>>>>> a2dp_sbc_t *capabilities = (a2dp_sbc_t *) >>>>>>> capabilities_buffer; >>>>>>> >>>>>>> pa_zero(*capabilities); >>>>>>> >>>>>>> capabilities->channel_mode = SBC_CHANNEL_MODE_MONO | >>>>>>> SBC_CHANNEL_MODE_DUAL_CHANNEL | SBC_CHANNEL_MODE_STEREO | >>>>>>> SBC_CHANNEL_MODE_JOINT_STEREO; >>>>>>> capabilities->frequency = SBC_SAMPLING_FREQ_16000 | >>>>>>> SBC_SAMPLING_FREQ_32000 | SBC_SAMPLING_FREQ_44100 | >>>>>>> SBC_SAMPLING_FREQ_48000; >>>>>>> capabilities->allocation_method = SBC_ALLOCATION_SNR | >>>>>>> SBC_ALLOCATION_LOUDNESS; >>>>>>> capabilities->subbands = SBC_SUBBANDS_4 | SBC_SUBBANDS_8; >>>>>>> capabilities->block_length = SBC_BLOCK_LENGTH_4 | >>>>>>> SBC_BLOCK_LENGTH_8 | SBC_BLOCK_LENGTH_12 | SBC_BLOCK_LENGTH_16; >>>>>>> capabilities->min_bitpool = SBC_MIN_BITPOOL; >>>>>>> capabilities->max_bitpool = >>>>>>> SBC_BITPOOL_HQ_JOINT_STEREO_44100; >>>>>>> >>>>>>> return sizeof(*capabilities); >>>>>>> } >>>>>>> >>>>>>> static bool is_configuration_valid(const uint8_t >>>>>>> *config_buffer, uint8_t config_size) { >>>>>>> const a2dp_sbc_t *config = (const a2dp_sbc_t *) >>>>>>> config_buffer; >>>>>>> >>>>>>> if (config_size != sizeof(*config)) { >>>>>>> pa_log_error("Invalid size of config buffer"); >>>>>>> return false; >>>>>>> } >>>>>>> >>>>>>> if (config->frequency != SBC_SAMPLING_FREQ_16000 && >>>>>>> config->frequency != SBC_SAMPLING_FREQ_32000 && >>>>>>> config->frequency != SBC_SAMPLING_FREQ_44100 && >>>>>>> config->frequency != SBC_SAMPLING_FREQ_48000) { >>>>>>> pa_log_error("Invalid sampling frequency in >>>>>>> configuration"); >>>>>>> return false; >>>>>>> } >>>>>>> >>>>>>> if (config->channel_mode != SBC_CHANNEL_MODE_MONO && >>>>>>> config->channel_mode != SBC_CHANNEL_MODE_DUAL_CHANNEL && >>>>>>> config->channel_mode != SBC_CHANNEL_MODE_STEREO && >>>>>>> config->channel_mode != SBC_CHANNEL_MODE_JOINT_STEREO) { >>>>>>> pa_log_error("Invalid channel mode in configuration"); >>>>>>> return false; >>>>>>> } >>>>>>> >>>>>>> if (config->allocation_method != SBC_ALLOCATION_SNR && >>>>>>> config->allocation_method != SBC_ALLOCATION_LOUDNESS) { >>>>>>> pa_log_error("Invalid allocation method in >>>>>>> configuration"); >>>>>>> return false; >>>>>>> } >>>>>>> >>>>>>> if (config->subbands != SBC_SUBBANDS_4 && >>>>>>> config->subbands != SBC_SUBBANDS_8) { >>>>>>> pa_log_error("Invalid SBC subbands in configuration"); >>>>>>> return false; >>>>>>> } >>>>>>> >>>>>>> if (config->block_length != SBC_BLOCK_LENGTH_4 && >>>>>>> config->block_length != SBC_BLOCK_LENGTH_8 && >>>>>>> config->block_length != SBC_BLOCK_LENGTH_12 && >>>>>>> config->block_length != SBC_BLOCK_LENGTH_16) { >>>>>>> pa_log_error("Invalid block length in configuration"); >>>>>>> return false; >>>>>>> } >>>>>>> >>>>>>> if (config->min_bitpool > config->max_bitpool) { >>>>>>> pa_log_error("Invalid bitpool in configuration"); >>>>>>> return false; >>>>>>> } >>>>>>> >>>>>>> return true; >>>>>>> } >>>>>>> >>>>>>> static uint8_t default_bitpool(uint8_t freq, uint8_t mode) { >>>>>>> /* These bitpool values were chosen based on the A2DP >>>>>>> spec recommendation */ >>>>>>> switch (freq) { >>>>>>> case SBC_SAMPLING_FREQ_48000: >>>>>>> switch (mode) { >>>>>>> case SBC_CHANNEL_MODE_MONO: >>>>>>> case SBC_CHANNEL_MODE_DUAL_CHANNEL: >>>>>>> return SBC_BITPOOL_HQ_MONO_48000; >>>>>>> >>>>>>> case SBC_CHANNEL_MODE_STEREO: >>>>>>> case SBC_CHANNEL_MODE_JOINT_STEREO: >>>>>>> return SBC_BITPOOL_HQ_JOINT_STEREO_48000; >>>>>>> } >>>>>>> break; >>>>>>> case SBC_SAMPLING_FREQ_44100: >>>>>>> switch (mode) { >>>>>>> case SBC_CHANNEL_MODE_MONO: >>>>>>> case SBC_CHANNEL_MODE_DUAL_CHANNEL: >>>>>>> return SBC_BITPOOL_HQ_MONO_44100; >>>>>>> >>>>>>> case SBC_CHANNEL_MODE_STEREO: >>>>>>> case SBC_CHANNEL_MODE_JOINT_STEREO: >>>>>>> return SBC_BITPOOL_HQ_JOINT_STEREO_44100; >>>>>>> } >>>>>>> break; >>>>>>> case SBC_SAMPLING_FREQ_16000: >>>>>>> case SBC_SAMPLING_FREQ_32000: >>>>>>> switch (mode) { >>>>>>> case SBC_CHANNEL_MODE_MONO: >>>>>>> case SBC_CHANNEL_MODE_DUAL_CHANNEL: >>>>>>> case SBC_CHANNEL_MODE_STEREO: >>>>>>> case SBC_CHANNEL_MODE_JOINT_STEREO: >>>>>>> return SBC_BITPOOL_HQ_JOINT_STEREO_44100; >>>>>>> } >>>>>>> break; >>>>>>> } >>>>>>> >>>>>>> pa_assert_not_reached(); >>>>>>> } >>>>>>> >>>>>>> static uint8_t fill_preferred_configuration(const >>>>>>> pa_sample_spec *default_sample_spec, const uint8_t >>>>>>> *capabilities_buffer, uint8_t capabilities_size, uint8_t >>>>>>> config_buffer[MAX_A2DP_CAPS_SIZE]) { >>>>>>> a2dp_sbc_t *config = (a2dp_sbc_t *) config_buffer; >>>>>>> const a2dp_sbc_t *capabilities = (const a2dp_sbc_t *) >>>>>>> capabilities_buffer; >>>>>>> int i; >>>>>>> >>>>>>> static const struct { >>>>>>> uint32_t rate; >>>>>>> uint8_t cap; >>>>>>> } freq_table[] = { >>>>>>> { 16000U, SBC_SAMPLING_FREQ_16000 }, >>>>>>> { 32000U, SBC_SAMPLING_FREQ_32000 }, >>>>>>> { 44100U, SBC_SAMPLING_FREQ_44100 }, >>>>>>> { 48000U, SBC_SAMPLING_FREQ_48000 } >>>>>>> }; >>>>>>> >>>>>>> if (capabilities_size != sizeof(*capabilities)) { >>>>>>> pa_log_error("Invalid size of capabilities buffer"); >>>>>>> return 0; >>>>>>> } >>>>>>> >>>>>>> pa_zero(*config); >>>>>>> >>>>>>> /* Find the lowest freq that is at least as high as the >>>>>>> requested sampling rate */ >>>>>>> for (i = 0; (unsigned) i < PA_ELEMENTSOF(freq_table); i++) >>>>>>> if (freq_table[i].rate >= default_sample_spec->rate >>>>>>> && (capabilities->frequency & freq_table[i].cap)) { >>>>>>> config->frequency = freq_table[i].cap; >>>>>>> break; >>>>>>> } >>>>>>> >>>>>>> if ((unsigned) i == PA_ELEMENTSOF(freq_table)) { >>>>>>> for (--i; i >= 0; i--) { >>>>>>> if (capabilities->frequency & freq_table[i].cap) { >>>>>>> config->frequency = freq_table[i].cap; >>>>>>> break; >>>>>>> } >>>>>>> } >>>>>>> >>>>>>> if (i < 0) { >>>>>>> pa_log_error("Not suitable sample rate"); >>>>>>> return 0; >>>>>>> } >>>>>>> } >>>>>>> >>>>>>> pa_assert((unsigned) i < PA_ELEMENTSOF(freq_table)); >>>>>>> >>>>>>> if (default_sample_spec->channels <= 1) { >>>>>>> if (capabilities->channel_mode & SBC_CHANNEL_MODE_MONO) >>>>>>> config->channel_mode = SBC_CHANNEL_MODE_MONO; >>>>>>> else if (capabilities->channel_mode & >>>>>>> SBC_CHANNEL_MODE_JOINT_STEREO) >>>>>>> config->channel_mode = >>>>>>> SBC_CHANNEL_MODE_JOINT_STEREO; >>>>>>> else if (capabilities->channel_mode & >>>>>>> SBC_CHANNEL_MODE_STEREO) >>>>>>> config->channel_mode = SBC_CHANNEL_MODE_STEREO; >>>>>>> else if (capabilities->channel_mode & >>>>>>> SBC_CHANNEL_MODE_DUAL_CHANNEL) >>>>>>> config->channel_mode = >>>>>>> SBC_CHANNEL_MODE_DUAL_CHANNEL; >>>>>>> else { >>>>>>> pa_log_error("No supported channel modes"); >>>>>>> return 0; >>>>>>> } >>>>>>> } else { >>>>>>> if (capabilities->channel_mode & >>>>>>> SBC_CHANNEL_MODE_DUAL_CHANNEL) >>>>>>> config->channel_mode = >>>>>>> SBC_CHANNEL_MODE_DUAL_CHANNEL; >>>>>>> else if (capabilities->channel_mode & >>>>>>> SBC_CHANNEL_MODE_STEREO) >>>>>>> config->channel_mode = SBC_CHANNEL_MODE_STEREO; >>>>>>> else if (capabilities->channel_mode & >>>>>>> SBC_CHANNEL_MODE_JOINT_STEREO) >>>>>>> config->channel_mode = >>>>>>> SBC_CHANNEL_MODE_JOINT_STEREO; >>>>>>> else if (capabilities->channel_mode & >>>>>>> SBC_CHANNEL_MODE_MONO) >>>>>>> config->channel_mode = SBC_CHANNEL_MODE_MONO; >>>>>>> else { >>>>>>> pa_log_error("No supported channel modes"); >>>>>>> return 0; >>>>>>> } >>>>>>> } >>>>>>> >>>>>>> if (capabilities->block_length & SBC_BLOCK_LENGTH_16) >>>>>>> config->block_length = SBC_BLOCK_LENGTH_16; >>>>>>> else if (capabilities->block_length & SBC_BLOCK_LENGTH_12) >>>>>>> config->block_length = SBC_BLOCK_LENGTH_12; >>>>>>> else if (capabilities->block_length & SBC_BLOCK_LENGTH_8) >>>>>>> config->block_length = SBC_BLOCK_LENGTH_8; >>>>>>> else if (capabilities->block_length & SBC_BLOCK_LENGTH_4) >>>>>>> config->block_length = SBC_BLOCK_LENGTH_4; >>>>>>> else { >>>>>>> pa_log_error("No supported block lengths"); >>>>>>> return 0; >>>>>>> } >>>>>>> >>>>>>> if (capabilities->subbands & SBC_SUBBANDS_8) >>>>>>> config->subbands = SBC_SUBBANDS_8; >>>>>>> else if (capabilities->subbands & SBC_SUBBANDS_4) >>>>>>> config->subbands = SBC_SUBBANDS_4; >>>>>>> else { >>>>>>> pa_log_error("No supported subbands"); >>>>>>> return 0; >>>>>>> } >>>>>>> >>>>>>> if (capabilities->allocation_method & >>>>>>> SBC_ALLOCATION_LOUDNESS) >>>>>>> config->allocation_method = SBC_ALLOCATION_LOUDNESS; >>>>>>> else if (capabilities->allocation_method & >>>>>>> SBC_ALLOCATION_SNR) >>>>>>> config->allocation_method = SBC_ALLOCATION_SNR; >>>>>>> else { >>>>>>> pa_log_error("No supported allocation method"); >>>>>>> return 0; >>>>>>> } >>>>>>> >>>>>>> config->min_bitpool = (uint8_t) PA_MAX(SBC_MIN_BITPOOL, >>>>>>> capabilities->min_bitpool); >>>>>>> config->max_bitpool = (uint8_t) >>>>>>> PA_MIN(default_bitpool(config->frequency, config->channel_mode), >>>>>>> capabilities->max_bitpool); >>>>>>> >>>>>>> if (config->min_bitpool > config->max_bitpool) { >>>>>>> pa_log_error("No supported bitpool"); >>>>>>> return 0; >>>>>>> } >>>>>>> >>>>>>> return sizeof(*config); >>>>>>> } >>>>>>> >>>>>>> static void set_params(struct sbc_info *sbc_info) { >>>>>>> sbc_info->sbc.frequency = sbc_info->frequency; >>>>>>> sbc_info->sbc.blocks = sbc_info->blocks; >>>>>>> sbc_info->sbc.subbands = sbc_info->subbands; >>>>>>> sbc_info->sbc.mode = sbc_info->mode; >>>>>>> sbc_info->sbc.allocation = sbc_info->allocation; >>>>>>> sbc_info->sbc.bitpool = sbc_info->initial_bitpool; >>>>>>> sbc_info->sbc.endian = SBC_LE; >>>>>>> >>>>>>> sbc_info->codesize = sbc_get_codesize(&sbc_info->sbc); >>>>>>> sbc_info->frame_length = >>>>>>> sbc_get_frame_length(&sbc_info->sbc); >>>>>>> } >>>>>>> >>>>>>> static void *init(bool for_encoding, bool for_backchannel, >>>>>>> const uint8_t *config_buffer, uint8_t config_size, pa_sample_spec >>>>>>> *sample_spec) { >>>>>>> struct sbc_info *sbc_info; >>>>>>> const a2dp_sbc_t *config = (const a2dp_sbc_t *) >>>>>>> config_buffer; >>>>>>> int ret; >>>>>>> >>>>>>> pa_assert(config_size == sizeof(*config)); >>>>>>> pa_assert(!for_backchannel); >>>>>>> >>>>>>> sbc_info = pa_xnew0(struct sbc_info, 1); >>>>>>> >>>>>>> ret = sbc_init(&sbc_info->sbc, 0); >>>>>>> if (ret != 0) { >>>>>>> pa_xfree(sbc_info); >>>>>>> pa_log_error("SBC initialization failed: %d", ret); >>>>>>> return NULL; >>>>>>> } >>>>>>> >>>>>>> sample_spec->format = PA_SAMPLE_S16LE; >>>>>>> >>>>>>> switch (config->frequency) { >>>>>>> case SBC_SAMPLING_FREQ_48000: >>>>>>> sbc_info->frequency = SBC_FREQ_48000; >>>>>>> sample_spec->rate = 48000U; >>>>>>> break; >>>>>>> case SBC_SAMPLING_FREQ_44100: >>>>>>> sbc_info->frequency = SBC_FREQ_44100; >>>>>>> sample_spec->rate = 44100U; >>>>>>> break; >>>>>>> case SBC_SAMPLING_FREQ_32000: >>>>>>> sbc_info->frequency = SBC_FREQ_32000; >>>>>>> sample_spec->rate = 32000U; >>>>>>> break; >>>>>>> case SBC_SAMPLING_FREQ_16000: >>>>>>> sbc_info->frequency = SBC_FREQ_16000; >>>>>>> sample_spec->rate = 16000U; >>>>>>> break; >>>>>>> default: >>>>>>> pa_assert_not_reached(); >>>>>>> } >>>>>>> >>>>>>> switch (config->channel_mode) { >>>>>>> case SBC_CHANNEL_MODE_DUAL_CHANNEL: >>>>>>> sbc_info->mode = SBC_MODE_DUAL_CHANNEL; >>>>>>> sample_spec->channels = 2; >>>>>>> break; >>>>>>> case SBC_CHANNEL_MODE_STEREO: >>>>>>> sbc_info->mode = SBC_MODE_STEREO; >>>>>>> sample_spec->channels = 2; >>>>>>> break; >>>>>>> case SBC_CHANNEL_MODE_JOINT_STEREO: >>>>>>> sbc_info->mode = SBC_MODE_JOINT_STEREO; >>>>>>> sample_spec->channels = 2; >>>>>>> break; >>>>>>> case SBC_CHANNEL_MODE_MONO: >>>>>>> sbc_info->mode = SBC_MODE_MONO; >>>>>>> sample_spec->channels = 1; >>>>>>> break; >>>>>>> default: >>>>>>> pa_assert_not_reached(); >>>>>>> } >>>>>>> >>>>>>> switch (config->allocation_method) { >>>>>>> case SBC_ALLOCATION_LOUDNESS: >>>>>>> sbc_info->allocation = SBC_AM_LOUDNESS; >>>>>>> break; >>>>>>> case SBC_ALLOCATION_SNR: >>>>>>> sbc_info->allocation = SBC_AM_SNR; >>>>>>> break; >>>>>>> default: >>>>>>> pa_assert_not_reached(); >>>>>>> } >>>>>>> >>>>>>> switch (config->subbands) { >>>>>>> case SBC_SUBBANDS_4: >>>>>>> sbc_info->subbands = SBC_SB_4; >>>>>>> break; >>>>>>> case SBC_SUBBANDS_8: >>>>>>> sbc_info->subbands = SBC_SB_8; >>>>>>> break; >>>>>>> default: >>>>>>> pa_assert_not_reached(); >>>>>>> } >>>>>>> >>>>>>> switch (config->block_length) { >>>>>>> case SBC_BLOCK_LENGTH_4: >>>>>>> sbc_info->blocks = SBC_BLK_4; >>>>>>> break; >>>>>>> case SBC_BLOCK_LENGTH_8: >>>>>>> sbc_info->blocks = SBC_BLK_8; >>>>>>> break; >>>>>>> case SBC_BLOCK_LENGTH_12: >>>>>>> sbc_info->blocks = SBC_BLK_12; >>>>>>> break; >>>>>>> case SBC_BLOCK_LENGTH_16: >>>>>>> sbc_info->blocks = SBC_BLK_16; >>>>>>> break; >>>>>>> default: >>>>>>> pa_assert_not_reached(); >>>>>>> } >>>>>>> >>>>>>> sbc_info->min_bitpool = config->min_bitpool; >>>>>>> sbc_info->max_bitpool = config->max_bitpool; >>>>>>> >>>>>>> /* Set minimum bitpool for source to get the maximum >>>>>>> possible block_size >>>>>>> * in get_block_size() function. This block_size is >>>>>>> length of buffer used >>>>>>> * for decoded audio data and so is inversely >>>>>>> proportional to frame length >>>>>>> * which depends on bitpool value. Bitpool is controlled >>>>>>> by other side from >>>>>>> * range [min_bitpool, max_bitpool]. */ >>>>>>> sbc_info->initial_bitpool = for_encoding ? >>>>>>> sbc_info->max_bitpool : sbc_info->min_bitpool; >>>>>>> >>>>>>> set_params(sbc_info); >>>>>>> >>>>>>> pa_log_info("SBC parameters: allocation=%s, subbands=%u, >>>>>>> blocks=%u, mode=%s bitpool=%u codesize=%u frame_length=%u", >>>>>>> sbc_info->sbc.allocation ? "SNR" : >>>>>>> "Loudness", sbc_info->sbc.subbands ? 8 : 4, >>>>>>> (sbc_info->sbc.blocks+1)*4, >>>>>>> sbc_info->sbc.mode == SBC_MODE_MONO ? "Mono" : >>>>>>> sbc_info->sbc.mode == SBC_MODE_DUAL_CHANNEL ? >>>>>>> "DualChannel" : >>>>>>> sbc_info->sbc.mode == SBC_MODE_STEREO ? >>>>>>> "Stereo" : "JointStereo", >>>>>>> sbc_info->sbc.bitpool, >>>>>>> (unsigned)sbc_info->codesize, (unsigned)sbc_info->frame_length); >>>>>>> >>>>>>> return sbc_info; >>>>>>> } >>>>>>> >>>>>>> static void deinit(void *codec_info) { >>>>>>> struct sbc_info *sbc_info = (struct sbc_info *) codec_info; >>>>>>> >>>>>>> sbc_finish(&sbc_info->sbc); >>>>>>> pa_xfree(sbc_info); >>>>>>> } >>>>>>> >>>>>>> static void set_bitpool(struct sbc_info *sbc_info, uint8_t >>>>>>> bitpool) { >>>>>>> if (bitpool > sbc_info->max_bitpool) >>>>>>> bitpool = sbc_info->max_bitpool; >>>>>>> else if (bitpool < sbc_info->min_bitpool) >>>>>>> bitpool = sbc_info->min_bitpool; >>>>>>> >>>>>>> sbc_info->sbc.bitpool = bitpool; >>>>>>> >>>>>>> sbc_info->codesize = sbc_get_codesize(&sbc_info->sbc); >>>>>>> sbc_info->frame_length = >>>>>>> sbc_get_frame_length(&sbc_info->sbc); >>>>>>> >>>>>>> pa_log_debug("Bitpool has changed to %u", >>>>>>> sbc_info->sbc.bitpool); >>>>>>> } >>>>>>> >>>>>>> static int reset(void *codec_info) { >>>>>>> struct sbc_info *sbc_info = (struct sbc_info *) codec_info; >>>>>>> int ret; >>>>>>> >>>>>>> ret = sbc_reinit(&sbc_info->sbc, 0); >>>>>>> if (ret != 0) { >>>>>>> pa_log_error("SBC reinitialization failed: %d", ret); >>>>>>> return -1; >>>>>>> } >>>>>>> >>>>>>> /* sbc_reinit() sets also default parameters, so reset >>>>>>> them back */ >>>>>>> set_params(sbc_info); >>>>>>> >>>>>>> sbc_info->seq_num = 0; >>>>>>> return 0; >>>>>>> } >>>>>>> >>>>>>> static size_t get_block_size(void *codec_info, size_t link_mtu) { >>>>>>> struct sbc_info *sbc_info = (struct sbc_info *) codec_info; >>>>>>> size_t rtp_size = sizeof(struct rtp_header) + >>>>>>> sizeof(struct rtp_sbc_payload); >>>>>>> size_t frame_count = (link_mtu - rtp_size) / >>>>>>> sbc_info->frame_length; >>>>>>> >>>>>>> /* frame_count is only 4 bit number */ >>>>>>> if (frame_count > 15) >>>>>>> frame_count = 15; >>>>>>> >>>>>>> return frame_count * sbc_info->codesize; >>>>>>> } >>>>>>> >>>>>>> static size_t reduce_encoder_bitrate(void *codec_info, size_t >>>>>>> write_link_mtu) { >>>>>>> struct sbc_info *sbc_info = (struct sbc_info *) codec_info; >>>>>>> uint8_t bitpool; >>>>>>> >>>>>>> /* Check if bitpool is already at its limit */ >>>>>>> if (sbc_info->sbc.bitpool <= SBC_BITPOOL_DEC_LIMIT) >>>>>>> return 0; >>>>>>> >>>>>>> bitpool = sbc_info->sbc.bitpool - SBC_BITPOOL_DEC_STEP; >>>>>>> >>>>>>> if (bitpool < SBC_BITPOOL_DEC_LIMIT) >>>>>>> bitpool = SBC_BITPOOL_DEC_LIMIT; >>>>>>> >>>>>>> if (sbc_info->sbc.bitpool == bitpool) >>>>>>> return 0; >>>>>>> >>>>>>> set_bitpool(sbc_info, bitpool); >>>>>>> return get_block_size(codec_info, write_link_mtu); >>>>>>> } >>>>>>> >>>>>>> static size_t encode_buffer(void *codec_info, uint32_t >>>>>>> timestamp, const uint8_t *input_buffer, size_t input_size, uint8_t >>>>>>> *output_buffer, size_t output_size, size_t *processed) { >>>>>>> struct sbc_info *sbc_info = (struct sbc_info *) codec_info; >>>>>>> struct rtp_header *header; >>>>>>> struct rtp_sbc_payload *payload; >>>>>>> uint8_t *d; >>>>>>> const uint8_t *p; >>>>>>> size_t to_write, to_encode; >>>>>>> uint8_t frame_count; >>>>>>> >>>>>>> header = (struct rtp_header*) output_buffer; >>>>>>> payload = (struct rtp_sbc_payload*) (output_buffer + >>>>>>> sizeof(*header)); >>>>>>> >>>>>>> frame_count = 0; >>>>>>> >>>>>>> p = input_buffer; >>>>>>> to_encode = input_size; >>>>>>> >>>>>>> d = output_buffer + sizeof(*header) + sizeof(*payload); >>>>>>> to_write = output_size - sizeof(*header) - sizeof(*payload); >>>>>>> >>>>>>> /* frame_count is only 4 bit number */ >>>>>>> while (PA_LIKELY(to_encode > 0 && to_write > 0 && >>>>>>> frame_count < 15)) { >>>>>>> ssize_t written; >>>>>>> ssize_t encoded; >>>>>>> >>>>>>> encoded = sbc_encode(&sbc_info->sbc, >>>>>>> p, to_encode, >>>>>>> d, to_write, >>>>>>> &written); >>>>>>> >>>>>>> if (PA_UNLIKELY(encoded <= 0)) { >>>>>>> pa_log_error("SBC encoding error (%li)", (long) >>>>>>> encoded); >>>>>>> break; >>>>>>> } >>>>>>> >>>>>>> if (PA_UNLIKELY(written < 0)) { >>>>>>> pa_log_error("SBC encoding error (%li)", (long) >>>>>>> written); >>>>>>> break; >>>>>>> } >>>>>>> >>>>>>> pa_assert_fp((size_t) encoded <= to_encode); >>>>>>> pa_assert_fp((size_t) encoded == sbc_info->codesize); >>>>>>> >>>>>>> pa_assert_fp((size_t) written <= to_write); >>>>>>> pa_assert_fp((size_t) written == >>>>>>> sbc_info->frame_length); >>>>>>> >>>>>>> p += encoded; >>>>>>> to_encode -= encoded; >>>>>>> >>>>>>> d += written; >>>>>>> to_write -= written; >>>>>>> >>>>>>> frame_count++; >>>>>>> } >>>>>>> >>>>>>> PA_ONCE_BEGIN { >>>>>>> pa_log_debug("Using SBC codec implementation: %s", >>>>>>> pa_strnull(sbc_get_implementation_info(&sbc_info->sbc))); >>>>>>> } PA_ONCE_END; >>>>>>> >>>>>>> if (PA_UNLIKELY(frame_count == 0)) { >>>>>>> *processed = 0; >>>>>>> return 0; >>>>>>> } >>>>>>> >>>>>>> /* write it to the fifo */ >>>>>>> pa_memzero(output_buffer, sizeof(*header) + >>>>>>> sizeof(*payload)); >>>>>>> header->v = 2; >>>>>>> >>>>>>> /* A2DP spec: "A payload type in the RTP dynamic range >>>>>>> shall be chosen". >>>>>>> * RFC3551 defines the dynamic range to span from 96 to >>>>>>> 127, and 96 appears >>>>>>> * to be the most common choice in A2DP implementations. */ >>>>>>> header->pt = 96; >>>>>>> >>>>>>> header->sequence_number = htons(sbc_info->seq_num++); >>>>>>> header->timestamp = htonl(timestamp); >>>>>>> header->ssrc = htonl(1); >>>>>>> payload->frame_count = frame_count; >>>>>>> >>>>>>> *processed = p - input_buffer; >>>>>>> return d - output_buffer; >>>>>>> } >>>>>>> >>>>>>> static size_t decode_buffer(void *codec_info, const uint8_t >>>>>>> *input_buffer, size_t input_size, uint8_t *output_buffer, size_t >>>>>>> output_size, size_t *processed) { >>>>>>> struct sbc_info *sbc_info = (struct sbc_info *) codec_info; >>>>>>> >>>>>>> struct rtp_header *header; >>>>>>> struct rtp_sbc_payload *payload; >>>>>>> const uint8_t *p; >>>>>>> uint8_t *d; >>>>>>> size_t to_write, to_decode; >>>>>>> uint8_t frame_count; >>>>>>> >>>>>>> header = (struct rtp_header *) input_buffer; >>>>>>> payload = (struct rtp_sbc_payload*) (input_buffer + >>>>>>> sizeof(*header)); >>>>>>> >>>>>>> frame_count = payload->frame_count; >>>>>>> >>>>>>> /* TODO: Add support for decoding fragmented SBC frames */ >>>>>>> if (payload->is_fragmented) { >>>>>>> pa_log_error("Unsupported fragmented SBC frame"); >>>>>>> *processed = 0; >>>>>>> return 0; >>>>>>> } >>>>>>> >>>>>>> p = input_buffer + sizeof(*header) + sizeof(*payload); >>>>>>> to_decode = input_size - sizeof(*header) - sizeof(*payload); >>>>>>> >>>>>>> d = output_buffer; >>>>>>> to_write = output_size; >>>>>>> >>>>>>> while (PA_LIKELY(to_decode > 0 && to_write > 0 && >>>>>>> frame_count > 0)) { >>>>>>> size_t written; >>>>>>> ssize_t decoded; >>>>>>> >>>>>>> decoded = sbc_decode(&sbc_info->sbc, >>>>>>> p, to_decode, >>>>>>> d, to_write, >>>>>>> &written); >>>>>>> >>>>>>> if (PA_UNLIKELY(decoded <= 0)) { >>>>>>> pa_log_error("SBC decoding error (%li)", (long) >>>>>>> decoded); >>>>>>> break; >>>>>>> } >>>>>>> >>>>>>> /* Reset frame length, it can be changed due to >>>>>>> bitpool change */ >>>>>>> sbc_info->frame_length = >>>>>>> sbc_get_frame_length(&sbc_info->sbc); >>>>>>> >>>>>>> pa_assert_fp((size_t) decoded <= to_decode); >>>>>>> pa_assert_fp((size_t) decoded == >>>>>>> sbc_info->frame_length); >>>>>>> >>>>>>> pa_assert_fp((size_t) written <= to_write); >>>>>>> pa_assert_fp((size_t) written == sbc_info->codesize); >>>>>>> >>>>>>> p += decoded; >>>>>>> to_decode -= decoded; >>>>>>> >>>>>>> d += written; >>>>>>> to_write -= written; >>>>>>> >>>>>>> frame_count--; >>>>>>> } >>>>>>> >>>>>>> *processed = p - input_buffer; >>>>>>> return d - output_buffer; >>>>>>> } >>>>>>> >>>>>>> const pa_a2dp_codec pa_a2dp_codec_sbc = { >>>>>>> .name = "sbc", >>>>>>> .description = "SBC", >>>>>>> .id = { A2DP_CODEC_SBC, 0, 0 }, >>>>>>> .support_backchannel = false, >>>>>>> .can_accept_capabilities = can_accept_capabilities, >>>>>>> .choose_remote_endpoint = choose_remote_endpoint, >>>>>>> .fill_capabilities = fill_capabilities, >>>>>>> .is_configuration_valid = is_configuration_valid, >>>>>>> .fill_preferred_configuration = >>>>>>> fill_preferred_configuration, >>>>>>> .init = init, >>>>>>> .deinit = deinit, >>>>>>> .reset = reset, >>>>>>> .get_read_block_size = get_block_size, >>>>>>> .get_write_block_size = get_block_size, >>>>>>> .reduce_encoder_bitrate = reduce_encoder_bitrate, >>>>>>> .encode_buffer = encode_buffer, >>>>>>> .decode_buffer = decode_buffer, >>>>>>> }; >>>>>>> >>>>>>> 17.09.2019, 09:34, "Hyperion" <h1p8r...@yandex.com>: >>>>>>>> btw here's the updated V12-12 patch in Pali's latest rel95 >>>>>>>> patchset that applies to PA 13.0. >>>>>>>> >>>>>>>> >>>>>>>> http://download.zenwalk.org/x86_64/testing/v12-12-13-zen-bluetooth-Implement-A2DP-codec-switching-and-backchannel-support-.patch >>>>>>>> >>>>>>>> JP >>>>>>>> >>>>>>>> 16.09.2019, 09:32, "Hyperion" <h1p8r...@yandex.com>: >>>>>>>>> Hi, >>>>>>>>> >>>>>>>>> Following what has already be done in some Android >>>>>>>>> derivatives, here's a simple patch that extend SBP bitpool >>>>>>>>> negociation to XQ quality. >>>>>>>>> >>>>>>>>> According to : >>>>>>>>> --> >>>>>>>>> >>>>>>>>> http://soundexpert.org/articles/-/blogs/audio-quality-of-sbc-xq-bluetooth-audio-codec >>>>>>>>> >>>>>>>>> --> https://lineageos.org/engineering/Bluetooth-SBC-XQ/ , >>>>>>>>> --> and confirmed by my own experimentation on more than >>>>>>>>> 10 different devices, >>>>>>>>> >>>>>>>>> here are the features of the (very simple and non >>>>>>>>> intrusive) patch : >>>>>>>>> - allow to use bitpool 76 on devices that support it, aka >>>>>>>>> SBC XQ >>>>>>>>> - harmless for devices limited to bitpool 53 >>>>>>>>> - deprecates the need for APTX & APTX HD support, which >>>>>>>>> are not better than SBC XQ, are not Open Source, and less >>>>>>>>> supported by devices >>>>>>>>> >>>>>>>>> This patch will be superseded by multi-profiles Pali Rohar >>>>>>>>> A2DP stack implementation, when it's ready for production. >>>>>>>>> Thanks to Pali for the help testing many codec parameters. >>>>>>>>> >>>>>>>>> Let me know if I have to clone the git to push my patch, >>>>>>>>> or if a regular PA dev could do it. >>>>>>>>> >>>>>>>>> All the best >>>>>>>>> JP >>>>>>>>> >>>>>>>>> diff -rNaud >>>>>>>>> pulseaudio-13.0/src/modules/bluetooth/a2dp-codec-sbc.c >>>>>>>>> pulseaudio-13.0-new/src/modules/bluetooth/a2dp-codec-sbc.c >>>>>>>>> --- pulseaudio-13.0/src/modules/bluetooth/a2dp-codec-sbc.c >>>>>>>>> 2019-09-13 15:20:03.000000000 +0200 >>>>>>>>> +++ >>>>>>>>> pulseaudio-13.0-new/src/modules/bluetooth/a2dp-codec-sbc.c >>>>>>>>> 2019-09-16 08:57:50.363122019 +0200 >>>>>>>>> @@ -290,10 +290,10 @@ >>>>>>>>> return 0; >>>>>>>>> } >>>>>>>>> >>>>>>>>> - if (capabilities->allocation_method & >>>>>>>>> SBC_ALLOCATION_LOUDNESS) >>>>>>>>> - config->allocation_method = SBC_ALLOCATION_LOUDNESS; >>>>>>>>> - else if (capabilities->allocation_method & >>>>>>>>> SBC_ALLOCATION_SNR) >>>>>>>>> + if (capabilities->allocation_method & SBC_ALLOCATION_SNR) >>>>>>>>> config->allocation_method = SBC_ALLOCATION_SNR; >>>>>>>>> + else if (capabilities->allocation_method & >>>>>>>>> SBC_ALLOCATION_LOUDNESS) >>>>>>>>> + config->allocation_method = SBC_ALLOCATION_LOUDNESS; >>>>>>>>> else { >>>>>>>>> pa_log_error("No supported allocation method"); >>>>>>>>> return 0; >>>>>>>>> diff -rNaud >>>>>>>>> pulseaudio-13.0/src/modules/bluetooth/a2dp-codecs.h >>>>>>>>> pulseaudio-13.0-new/src/modules/bluetooth/a2dp-codecs.h >>>>>>>>> --- pulseaudio-13.0/src/modules/bluetooth/a2dp-codecs.h >>>>>>>>> 2019-09-13 15:20:03.000000000 +0200 >>>>>>>>> +++ >>>>>>>>> pulseaudio-13.0-new/src/modules/bluetooth/a2dp-codecs.h >>>>>>>>> 2019-09-16 08:44:20.382086305 +0200 >>>>>>>>> @@ -61,14 +61,11 @@ >>>>>>>>> * Allocation method = Loudness >>>>>>>>> * Subbands = 8 >>>>>>>>> */ >>>>>>>>> -#define SBC_BITPOOL_MQ_MONO_44100 19 >>>>>>>>> -#define SBC_BITPOOL_MQ_MONO_48000 18 >>>>>>>>> -#define SBC_BITPOOL_MQ_JOINT_STEREO_44100 35 >>>>>>>>> -#define SBC_BITPOOL_MQ_JOINT_STEREO_48000 33 >>>>>>>>> -#define SBC_BITPOOL_HQ_MONO_44100 31 >>>>>>>>> -#define SBC_BITPOOL_HQ_MONO_48000 29 >>>>>>>>> -#define SBC_BITPOOL_HQ_JOINT_STEREO_44100 53 >>>>>>>>> -#define SBC_BITPOOL_HQ_JOINT_STEREO_48000 51 >>>>>>>>> + >>>>>>>>> +#define SBC_BITPOOL_HQ_MONO_44100 38 >>>>>>>>> +#define SBC_BITPOOL_HQ_MONO_48000 38 >>>>>>>>> +#define SBC_BITPOOL_HQ_JOINT_STEREO_44100 76 >>>>>>>>> +#define SBC_BITPOOL_HQ_JOINT_STEREO_48000 76 >>>>>>>>> >>>>>>>>> #define MPEG_CHANNEL_MODE_MONO (1 << 3) >>>>>>>>> #define MPEG_CHANNEL_MODE_DUAL_CHANNEL (1 << 2) >>>>>>>>> >>>>>>>>> _______________________________________________ >>>>>>>>> pulseaudio-discuss mailing list >>>>>>>>> pulseaudio-discuss@lists.freedesktop.org >>>>>>>>> >>>>>>>>> https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss >>>>>>> _______________________________________________ >>>>>>> pulseaudio-discuss mailing list >>>>>>> pulseaudio-discuss@lists.freedesktop.org >>>>>>> https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss >>>>>> >>>>>> _______________________________________________ >>>>>> pulseaudio-discuss mailing list >>>>>> pulseaudio-discuss@lists.freedesktop.org >>>>>> https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss >>>>> >>>>> _______________________________________________ >>>>> pulseaudio-discuss mailing list >>>>> pulseaudio-discuss@lists.freedesktop.org >>>>> https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss >>>> >>>> _______________________________________________ >>>> pulseaudio-discuss mailing list >>>> pulseaudio-discuss@lists.freedesktop.org >>>> https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss >>> _______________________________________________ >>> pulseaudio-discuss mailing list >>> pulseaudio-discuss@lists.freedesktop.org >>> https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss > > _______________________________________________ > pulseaudio-discuss mailing list > pulseaudio-discuss@lists.freedesktop.org > https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss _______________________________________________ pulseaudio-discuss mailing list pulseaudio-discuss@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss