Not sure it's expired worlwide. In case it is, it's good news. Fine for me.<br /><br />10:28, 7 octobre 2019, "Pali Rohár" <pali.rohar@gmail.com>:<br /><blockquote class="b4fd5cf2ec92bc68cb898700bb81355fwmi-quote"><p>If you mean EP<span class="177d5a4333ac019606de889e143743a1wmi-callto">0398973</span>B1 then it is already expired. I'm not aware of<br />other patents covering aptX. So I think libopenaptx should be safe here.<br /><br />But I'm not lawyer and do not understand jurisdiction across whole<br />world, but I guess it is same problem as with expired patents for MP3.<br /><br />Why should be prohibited to distribute clean room LGPL written code as<br />binary?<br /><br />On Monday 07 October 2019 10:11:39 Hyperion wrote:<br /></p><blockquote class="b4fd5cf2ec92bc68cb898700bb81355fwmi-quote"> both libraries are comming from the same reverse-engineering of a PROPRIETARY PATENTED codec, and BOTH are subject to lawsuit in case it is widely distributed as binary. <br /><br /> JP<br /><br /> 07.10.2019, 10:06, "Pali Rohár" <<a href="mailto:pali.rohar@gmail.com">pali.rohar@gmail.com</a>>:<br /> > But that is something different. Look at commit message where is link to<br /> > correct library for building.<br /> ><br /> > On Monday 07 October 2019 10:05:52 Hyperion wrote:<br /> >>  Sorry, I should have provided the link to the source GIT <a href="https://github.com/Arkq/openaptx">https://github.com/Arkq/openaptx</a><br /> >><br /> >>  JP<br /> >><br /> >>  07.10.2019, 09:51, "Pali Rohár" <<a href="mailto:pali.rohar@gmail.com">pali.rohar@gmail.com</a>>:<br /> >>  > On Monday 07 October 2019 09:47:21 Hyperion wrote:<br /> >>  >>  Quotig OpenAPTX Github README :<br /> >>  >>  "This project is for research purposes only. Without a proper license private and commercial usage might be a case of a patent infringement. If you are looking for a library, which can be installed and used legally (commercial, private and educational usage), go to the Qualcomm® aptX™ homepage and contact Qualcomm customer service."<br /> >>  ><br /> >>  > Sorry, but I have not found any such quotes in README file.<br /> >>  ><br /> >>  >>  So APTX support MUST only be provided for researching purposes, and so it should remain an external patch, out of the main PA tree.<br /> >>  >><br /> >>  >>  All the best,<br /> >>  >><br /> >>  >>  The source code itself is licensed under the terms of the MIT license. However, compression algorithms are patented and licensed under the terms of a proprietary license. Hence, compilation and redistribution in a binary format is forbidden!<br /> >>  ><br /> >>  > Library is fully licensed under LGPLv2.1+, not under MIT. And there are<br /> >>  > no proprietary parts. Maybe you are referring to different library?<br /> >>  ><br /> >>  >>  06.10.2019, 19:59, "Pali Rohár" <<a href="mailto:pali.rohar@gmail.com">pali.rohar@gmail.com</a>>:<br /> >>  >>  > This patch provides support for aptX and aptX HD codecs in bluetooth A2DP<br /> >>  >>  > profile. It uses open source LGPLv2.1+ licensed libopenaptx library which<br /> >>  >>  > can be found at <a href="https://github.com/pali/libopenaptx">https://github.com/pali/libopenaptx</a>.<br /> >>  >>  ><br /> >>  >>  > aptX for s24 stereo samples provides fixed 6:1 compression ratio and<br /> >>  >>  > bitrate 352.8 kbit/s, aptX HD provides fixed 4:1 compression ratio and<br /> >>  >>  > bitrate 529.2 kbit/s.<br /> >>  >>  ><br /> >>  >>  > According to soundexpert research, aptX codec used in bluetooth A2DP is no<br /> >>  >>  > better than SBC High Quality settings. And you cannot hear difference<br /> >>  >>  > between aptX and SBC High Quality, aptX is just a copper-less overpriced<br /> >>  >>  > audio cable.<br /> >>  >>  ><br /> >>  >>  > aptX HD is high-bitrate version of aptX. It has clearly noticeable increase<br /> >>  >>  > in sound quality (not dramatic though taking into account the increase in<br /> >>  >>  > bitrate).<br /> >>  >>  ><br /> >>  >>  > <a href="http://soundexpert.org/news/-/blogs/audio-quality-of-bluetooth-aptx">http://soundexpert.org/news/-/blogs/audio-quality-of-bluetooth-aptx</a><br /> >>  >>  > ---<br /> >>  >>  >  configure.ac | 36 +++<br /> >>  >>  >  src/Makefile.am | 6 +<br /> >>  >>  >  src/modules/bluetooth/a2dp-codec-aptx.c | 479 ++++++++++++++++++++++++++++++++<br /> >>  >>  >  src/modules/bluetooth/a2dp-codec-util.c | 8 +<br /> >>  >>  >  4 files changed, 529 insertions(+)<br /> >>  >>  >  create mode 100644 src/modules/bluetooth/a2dp-codec-aptx.c<br /> >>  >>  ><br /> >>  >>  > diff --git a/configure.ac b/configure.ac<br /> >>  >>  > index <span class="177d5a4333ac019606de889e143743a1wmi-callto">8278353</span>d4..26c625a<span class="177d5a4333ac019606de889e143743a1wmi-callto">59 100644</span><br /> >>  >>  > --- a/configure.ac<br /> >>  >>  > +++ b/configure.ac<br /> >>  >>  > @@ -1118,6 +1118,40 @@ AC_SUBST(HAVE_BLUEZ_5_NATIVE_HEADSET)<br /> >>  >>  >  AM_CONDITIONAL([HAVE_BLUEZ_5_NATIVE_HEADSET], [test "x$HAVE_BLUEZ_5_NATIVE_HEADSET" = x1])<br /> >>  >>  >  AS_IF([test "x$HAVE_BLUEZ_5_NATIVE_HEADSET" = "x1"], AC_DEFINE([HAVE_BLUEZ_5_NATIVE_HEADSET], 1, [Bluez 5 native headset backend enabled]))<br /> >>  >>  ><br /> >>  >>  > +#### Bluetooth A2DP aptX codec (optional) ###<br /> >>  >>  > +<br /> >>  >>  > +AC_ARG_ENABLE([aptx],<br /> >>  >>  > + AS_HELP_STRING([--disable-aptx],[Disable optional bluetooth A2DP aptX and aptX HD codecs support (via libopenaptx)]))<br /> >>  >>  > +AC_ARG_VAR([OPENAPTX_CPPFLAGS], [C preprocessor flags for openaptx])<br /> >>  >>  > +AC_ARG_VAR([OPENAPTX_LDFLAGS], [linker flags for openaptx])<br /> >>  >>  > +<br /> >>  >>  > +CPPFLAGS_SAVE="$CPPFLAGS"<br /> >>  >>  > +LDFLAGS_SAVE="$LDFLAGS"<br /> >>  >>  > +LIBS_SAVE="$LIBS"<br /> >>  >>  > +<br /> >>  >>  > +CPPFLAGS="$CPPFLAGS $OPENAPTX_CPPFLAGS"<br /> >>  >>  > +LDFLAGS="$LDFLAGS $OPENAPTX_LDFLAGS"<br /> >>  >>  > +<br /> >>  >>  > +AS_IF([test "x$HAVE_BLUEZ_5" = "x1" && test "x$enable_aptx" != "xno"],<br /> >>  >>  > + [AC_CHECK_HEADER([openaptx.h],<br /> >>  >>  > + [AC_SEARCH_LIBS([aptx_init], [openaptx],<br /> >>  >>  > + [HAVE_OPENAPTX=1; AS_IF([test "x$ac_cv_search_aptx_init" != "xnone required"], [OPENAPTX_LDFLAGS="$OPENAPTX_LDFLAGS $ac_cv_search_aptx_init"])],<br /> >>  >>  > + [HAVE_OPENAPTX=0])],<br /> >>  >>  > + [HAVE_OPENAPTX=0])])<br /> >>  >>  > +<br /> >>  >>  > +CPPFLAGS="$CPPFLAGS_SAVE"<br /> >>  >>  > +LDFLAGS="$LDFLAGS_SAVE"<br /> >>  >>  > +LIBS="$LIBS_SAVE"<br /> >>  >>  > +<br /> >>  >>  > +AS_IF([test "x$HAVE_BLUEZ_5" = "x1" && test "x$enable_aptx" = "xyes" && test "x$HAVE_OPENAPTX" = "x0"],<br /> >>  >>  > + [AC_MSG_ERROR([*** libopenaptx from <a href="https://github.com/pali/libopenaptx">https://github.com/pali/libopenaptx</a> not found])])<br /> >>  >>  > +<br /> >>  >>  > +AC_SUBST(OPENAPTX_CPPFLAGS)<br /> >>  >>  > +AC_SUBST(OPENAPTX_LDFLAGS)<br /> >>  >>  > +AC_SUBST(HAVE_OPENAPTX)<br /> >>  >>  > +AM_CONDITIONAL([HAVE_OPENAPTX], [test "x$HAVE_OPENAPTX" = "x1"])<br /> >>  >>  > +AS_IF([test "x$HAVE_OPENAPTX" = "x1"], AC_DEFINE([HAVE_OPENAPTX], 1, [Have openaptx codec library]))<br /> >>  >>  > +<br /> >>  >>  >  #### UDEV support (optional) ####<br /> >>  >>  ><br /> >>  >>  >  AC_ARG_ENABLE([udev],<br /> >>  >>  > @@ -1603,6 +1637,7 @@ AS_IF([test "x$HAVE_SYSTEMD_JOURNAL" = "x1"], ENABLE_SYSTEMD_JOURNAL=yes, ENABLE<br /> >>  >>  >  AS_IF([test "x$HAVE_BLUEZ_5" = "x1"], ENABLE_BLUEZ_5=yes, ENABLE_BLUEZ_5=no)<br /> >>  >>  >  AS_IF([test "x$HAVE_BLUEZ_5_OFONO_HEADSET" = "x1"], ENABLE_BLUEZ_5_OFONO_HEADSET=yes, ENABLE_BLUEZ_5_OFONO_HEADSET=no)<br /> >>  >>  >  AS_IF([test "x$HAVE_BLUEZ_5_NATIVE_HEADSET" = "x1"], ENABLE_BLUEZ_5_NATIVE_HEADSET=yes, ENABLE_BLUEZ_5_NATIVE_HEADSET=no)<br /> >>  >>  > +AS_IF([test "x$HAVE_OPENAPTX" = "x1"], ENABLE_APTX=yes, ENABLE_APTX=no)<br /> >>  >>  >  AS_IF([test "x$HAVE_HAL_COMPAT" = "x1"], ENABLE_HAL_COMPAT=yes, ENABLE_HAL_COMPAT=no)<br /> >>  >>  >  AS_IF([test "x$HAVE_TCPWRAP" = "x1"], ENABLE_TCPWRAP=yes, ENABLE_TCPWRAP=no)<br /> >>  >>  >  AS_IF([test "x$HAVE_LIBSAMPLERATE" = "x1"], ENABLE_LIBSAMPLERATE="yes (DEPRECATED)", ENABLE_LIBSAMPLERATE=no)<br /> >>  >>  > @@ -1661,6 +1696,7 @@ echo "<br /> >>  >>  >        Enable BlueZ 5: ${ENABLE_BLUEZ_5}<br /> >>  >>  >          Enable ofono headsets: ${ENABLE_BLUEZ_5_OFONO_HEADSET}<br /> >>  >>  >          Enable native headsets: ${ENABLE_BLUEZ_5_NATIVE_HEADSET}<br /> >>  >>  > + Enable aptX+aptXHD codecs: ${ENABLE_APTX}<br /> >>  >>  >      Enable udev: ${ENABLE_UDEV}<br /> >>  >>  >        Enable HAL->udev compat: ${ENABLE_HAL_COMPAT}<br /> >>  >>  >      Enable systemd<br /> >>  >>  > diff --git a/src/Makefile.am b/src/Makefile.am<br /> >>  >>  > index b84c778cc..e317b<span class="177d5a4333ac019606de889e143743a1wmi-callto">7972 100644</span><br /> >>  >>  > --- a/src/Makefile.am<br /> >>  >>  > +++ b/src/Makefile.am<br /> >>  >>  > @@ -2164,6 +2164,12 @@ libbluez5_util_la_SOURCES += modules/bluetooth/a2dp-codec-sbc.c<br /> >>  >>  >  libbluez5_util_la_LIBADD += $(SBC_LIBS)<br /> >>  >>  >  libbluez5_util_la_CFLAGS += $(SBC_CFLAGS)<br /> >>  >>  ><br /> >>  >>  > +if HAVE_OPENAPTX<br /> >>  >>  > +libbluez5_util_la_SOURCES += modules/bluetooth/a2dp-codec-aptx.c<br /> >>  >>  > +libbluez5_util_la_CPPFLAGS += $(OPENAPTX_CPPFLAGS)<br /> >>  >>  > +libbluez5_util_la_LDFLAGS += $(OPENAPTX_LDFLAGS)<br /> >>  >>  > +endif<br /> >>  >>  > +<br /> >>  >>  >  module_bluez5_discover_la_SOURCES = modules/bluetooth/module-bluez5-discover.c<br /> >>  >>  >  module_bluez5_discover_la_LDFLAGS = $(MODULE_LDFLAGS)<br /> >>  >>  >  module_bluez5_discover_la_LIBADD = $(MODULE_LIBADD) $(DBUS_LIBS) libbluez5-util.la<br /> >>  >>  > diff --git a/src/modules/bluetooth/a2dp-codec-aptx.c b/src/modules/bluetooth/a2dp-codec-aptx.c<br /> >>  >>  > new file mode 100644<br /> >>  >>  > index <span class="177d5a4333ac019606de889e143743a1wmi-callto">000000000</span>..2bd9e7652<br /> >>  >>  > --- /dev/null<br /> >>  >>  > +++ b/src/modules/bluetooth/a2dp-codec-aptx.c<br /> >>  >>  > @@ -0,0 +1,479 @@<br /> >>  >>  > +/***<br /> >>  >>  > + This file is part of PulseAudio.<br /> >>  >>  > +<br /> >>  >>  > + Copyright <span class="177d5a4333ac019606de889e143743a1wmi-callto">2018-2019</span> Pali Rohár <<a href="mailto:pali.rohar@gmail.com">pali.rohar@gmail.com</a>><br /> >>  >>  > +<br /> >>  >>  > + PulseAudio is free software; you can redistribute it and/or modify<br /> >>  >>  > + it under the terms of the GNU Lesser General Public License as<br /> >>  >>  > + published by the Free Software Foundation; either version 2.1 of the<br /> >>  >>  > + License, or (at your option) any later version.<br /> >>  >>  > +<br /> >>  >>  > + PulseAudio is distributed in the hope that it will be useful, but<br /> >>  >>  > + WITHOUT ANY WARRANTY; without even the implied warranty of<br /> >>  >>  > + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU<br /> >>  >>  > + General Public License for more details.<br /> >>  >>  > +<br /> >>  >>  > + You should have received a copy of the GNU Lesser General Public<br /> >>  >>  > + License along with PulseAudio; if not, see <<a href="http://www.gnu.org/licenses/">http://www.gnu.org/licenses/</a>>.<br /> >>  >>  > +***/<br /> >>  >>  > +<br /> >>  >>  > +#ifdef HAVE_CONFIG_H<br /> >>  >>  > +#include <config.h><br /> >>  >>  > +#endif<br /> >>  >>  > +<br /> >>  >>  > +#include <pulsecore/log.h><br /> >>  >>  > +#include <pulsecore/macro.h><br /> >>  >>  > +#include <pulsecore/once.h><br /> >>  >>  > +#include <pulse/sample.h><br /> >>  >>  > +<br /> >>  >>  > +#include <arpa/inet.h><br /> >>  >>  > +<br /> >>  >>  > +#include <openaptx.h><br /> >>  >>  > +<br /> >>  >>  > +#include "a2dp-codecs.h"<br /> >>  >>  > +#include "a2dp-codec-api.h"<br /> >>  >>  > +#include "rtp.h"<br /> >>  >>  > +<br /> >>  >>  > +struct aptx_hd_info {<!-- --><br /> >>  >>  > + struct aptx_context *aptx_c;<br /> >>  >>  > + uint16_t seq_num;<br /> >>  >>  > +};<br /> >>  >>  > +<br /> >>  >>  > +static bool can_accept_capabilities_common(const a2dp_aptx_t *capabilities, uint32_t vendor_id, uint16_t codec_id) {<!-- --><br /> >>  >>  > + if (A2DP_GET_VENDOR_ID(capabilities->info) != vendor_id || A2DP_GET_CODEC_ID(capabilities->info) != codec_id)<br /> >>  >>  > + return false;<br /> >>  >>  > +<br /> >>  >>  > + if (!(capabilities->frequency & (APTX_SAMPLING_FREQ_16000 | APTX_SAMPLING_FREQ_32000 |<br /> >>  >>  > + APTX_SAMPLING_FREQ_44100 | APTX_SAMPLING_FREQ_48000)))<br /> >>  >>  > + return false;<br /> >>  >>  > +<br /> >>  >>  > + if (!(capabilities->channel_mode & APTX_CHANNEL_MODE_STEREO))<br /> >>  >>  > + return false;<br /> >>  >>  > +<br /> >>  >>  > + return true;<br /> >>  >>  > +}<br /> >>  >>  > +<br /> >>  >>  > +static bool can_accept_capabilities(const uint8_t *capabilities_buffer, uint8_t capabilities_size, bool for_encoding) {<!-- --><br /> >>  >>  > + const a2dp_aptx_t *capabilities = (const a2dp_aptx_t *) capabilities_buffer;<br /> >>  >>  > +<br /> >>  >>  > + if (capabilities_size != sizeof(*capabilities))<br /> >>  >>  > + return false;<br /> >>  >>  > +<br /> >>  >>  > + return can_accept_capabilities_common(capabilities, APTX_VENDOR_ID, APTX_CODEC_ID);<br /> >>  >>  > +}<br /> >>  >>  > +<br /> >>  >>  > +static bool can_accept_capabilities_hd(const uint8_t *capabilities_buffer, uint8_t capabilities_size, bool for_encoding) {<!-- --><br /> >>  >>  > + const a2dp_aptx_hd_t *capabilities = (const a2dp_aptx_hd_t *) capabilities_buffer;<br /> >>  >>  > +<br /> >>  >>  > + if (capabilities_size != sizeof(*capabilities))<br /> >>  >>  > + return false;<br /> >>  >>  > +<br /> >>  >>  > + return can_accept_capabilities_common(&capabilities->aptx, APTX_HD_VENDOR_ID, APTX_HD_CODEC_ID);<br /> >>  >>  > +}<br /> >>  >>  > +<br /> >>  >>  > +static const char *choose_remote_endpoint_common(bool (*can_accept_capabilities_func)(const uint8_t *, uint8_t, bool), const pa_hashmap *capabilities_hashmap, bool for_encoding) {<!-- --><br /> >>  >>  > + const pa_a2dp_codec_capabilities *a2dp_capabilities;<br /> >>  >>  > + const char *key;<br /> >>  >>  > + void *state;<br /> >>  >>  > +<br /> >>  >>  > + /* There is no preference, just choose random valid entry */<br /> >>  >>  > + PA_HASHMAP_FOREACH_KV(key, a2dp_capabilities, capabilities_hashmap, state) {<!-- --><br /> >>  >>  > + if (can_accept_capabilities_func(a2dp_capabilities->buffer, a2dp_capabilities->size, for_encoding))<br /> >>  >>  > + return key;<br /> >>  >>  > + }<br /> >>  >>  > +<br /> >>  >>  > + return NULL;<br /> >>  >>  > +}<br /> >>  >>  > +<br /> >>  >>  > +static const char *choose_remote_endpoint(const pa_hashmap *capabilities_hashmap, const pa_sample_spec *default_sample_spec, bool for_encoding) {<!-- --><br /> >>  >>  > + return choose_remote_endpoint_common(can_accept_capabilities, capabilities_hashmap, for_encoding);<br /> >>  >>  > +}<br /> >>  >>  > +<br /> >>  >>  > +static const char *choose_remote_endpoint_hd(const pa_hashmap *capabilities_hashmap, const pa_sample_spec *default_sample_spec, bool for_encoding) {<!-- --><br /> >>  >>  > + return choose_remote_endpoint_common(can_accept_capabilities_hd, capabilities_hashmap, for_encoding);<br /> >>  >>  > +}<br /> >>  >>  > +<br /> >>  >>  > +static void fill_capabilities_common(a2dp_aptx_t *capabilities, uint32_t vendor_id, uint16_t codec_id) {<!-- --><br /> >>  >>  > + capabilities->info = A2DP_SET_VENDOR_ID_CODEC_ID(vendor_id, codec_id);<br /> >>  >>  > + capabilities->channel_mode = APTX_CHANNEL_MODE_STEREO;<br /> >>  >>  > + capabilities->frequency = APTX_SAMPLING_FREQ_16000 | APTX_SAMPLING_FREQ_32000 |<br /> >>  >>  > + APTX_SAMPLING_FREQ_44100 | APTX_SAMPLING_FREQ_48000;<br /> >>  >>  > +}<br /> >>  >>  > +<br /> >>  >>  > +static uint8_t fill_capabilities(uint8_t capabilities_buffer[MAX_A2DP_CAPS_SIZE]) {<!-- --><br /> >>  >>  > + a2dp_aptx_t *capabilities = (a2dp_aptx_t *) capabilities_buffer;<br /> >>  >>  > +<br /> >>  >>  > + pa_zero(*capabilities);<br /> >>  >>  > + fill_capabilities_common(capabilities, APTX_VENDOR_ID, APTX_CODEC_ID);<br /> >>  >>  > + return sizeof(*capabilities);<br /> >>  >>  > +}<br /> >>  >>  > +<br /> >>  >>  > +static uint8_t fill_capabilities_hd(uint8_t capabilities_buffer[MAX_A2DP_CAPS_SIZE]) {<!-- --><br /> >>  >>  > + a2dp_aptx_hd_t *capabilities = (a2dp_aptx_hd_t *) capabilities_buffer;<br /> >>  >>  > +<br /> >>  >>  > + pa_zero(*capabilities);<br /> >>  >>  > + fill_capabilities_common(&capabilities->aptx, APTX_HD_VENDOR_ID, APTX_HD_CODEC_ID);<br /> >>  >>  > + return sizeof(*capabilities);<br /> >>  >>  > +}<br /> >>  >>  > +<br /> >>  >>  > +static bool is_configuration_valid_common(const a2dp_aptx_t *config, uint32_t vendor_id, uint16_t codec_id) {<!-- --><br /> >>  >>  > + if (A2DP_GET_VENDOR_ID(config->info) != vendor_id || A2DP_GET_CODEC_ID(config->info) != codec_id) {<!-- --><br /> >>  >>  > + pa_log_error("Invalid vendor codec information in configuration");<br /> >>  >>  > + return false;<br /> >>  >>  > + }<br /> >>  >>  > +<br /> >>  >>  > + if (config->frequency != APTX_SAMPLING_FREQ_16000 && config->frequency != APTX_SAMPLING_FREQ_32000 &&<br /> >>  >>  > + config->frequency != APTX_SAMPLING_FREQ_44100 && config->frequency != APTX_SAMPLING_FREQ_48000) {<!-- --><br /> >>  >>  > + pa_log_error("Invalid sampling frequency in configuration");<br /> >>  >>  > + return false;<br /> >>  >>  > + }<br /> >>  >>  > +<br /> >>  >>  > + if (config->channel_mode != APTX_CHANNEL_MODE_STEREO) {<!-- --><br /> >>  >>  > + pa_log_error("Invalid channel mode in configuration");<br /> >>  >>  > + return false;<br /> >>  >>  > + }<br /> >>  >>  > +<br /> >>  >>  > + return true;<br /> >>  >>  > +}<br /> >>  >>  > +<br /> >>  >>  > +static bool is_configuration_valid(const uint8_t *config_buffer, uint8_t config_size) {<!-- --><br /> >>  >>  > + const a2dp_aptx_t *config = (const a2dp_aptx_t *) config_buffer;<br /> >>  >>  > +<br /> >>  >>  > + if (config_size != sizeof(*config)) {<!-- --><br /> >>  >>  > + pa_log_error("Invalid size of config buffer");<br /> >>  >>  > + return false;<br /> >>  >>  > + }<br /> >>  >>  > +<br /> >>  >>  > + return is_configuration_valid_common(config, APTX_VENDOR_ID, APTX_CODEC_ID);<br /> >>  >>  > +}<br /> >>  >>  > +<br /> >>  >>  > +static bool is_configuration_valid_hd(const uint8_t *config_buffer, uint8_t config_size) {<!-- --><br /> >>  >>  > + const a2dp_aptx_hd_t *config = (const a2dp_aptx_hd_t *) config_buffer;<br /> >>  >>  > +<br /> >>  >>  > + if (config_size != sizeof(*config)) {<!-- --><br /> >>  >>  > + pa_log_error("Invalid size of config buffer");<br /> >>  >>  > + return false;<br /> >>  >>  > + }<br /> >>  >>  > +<br /> >>  >>  > + return is_configuration_valid_common(&config->aptx, APTX_HD_VENDOR_ID, APTX_HD_CODEC_ID);<br /> >>  >>  > +}<br /> >>  >>  > +<br /> >>  >>  > +static bool fill_preferred_configuration_common(const pa_sample_spec *default_sample_spec, const a2dp_aptx_t *capabilities, a2dp_aptx_t *config, uint32_t vendor_id, uint16_t codec_id) {<!-- --><br /> >>  >>  > + int i;<br /> >>  >>  > +<br /> >>  >>  > + static const struct {<!-- --><br /> >>  >>  > + uint32_t rate;<br /> >>  >>  > + uint8_t cap;<br /> >>  >>  > + } freq_table[] = {<!-- --><br /> >>  >>  > + { 16000U, APTX_SAMPLING_FREQ_16000 },<br /> >>  >>  > + { 32000U, APTX_SAMPLING_FREQ_32000 },<br /> >>  >>  > + { 44100U, APTX_SAMPLING_FREQ_44100 },<br /> >>  >>  > + { 48000U, APTX_SAMPLING_FREQ_48000 }<br /> >>  >>  > + };<br /> >>  >>  > +<br /> >>  >>  > + if (A2DP_GET_VENDOR_ID(capabilities->info) != vendor_id || A2DP_GET_CODEC_ID(capabilities->info) != codec_id) {<!-- --><br /> >>  >>  > + pa_log_error("No supported vendor codec information");<br /> >>  >>  > + return false;<br /> >>  >>  > + }<br /> >>  >>  > +<br /> >>  >>  > + config->info = A2DP_SET_VENDOR_ID_CODEC_ID(vendor_id, codec_id);<br /> >>  >>  > +<br /> >>  >>  > + if (!(capabilities->channel_mode & APTX_CHANNEL_MODE_STEREO)) {<!-- --><br /> >>  >>  > + pa_log_error("No supported channel modes");<br /> >>  >>  > + return false;<br /> >>  >>  > + }<br /> >>  >>  > +<br /> >>  >>  > + config->channel_mode = APTX_CHANNEL_MODE_STEREO;<br /> >>  >>  > +<br /> >>  >>  > + /* Find the lowest freq that is at least as high as the requested sampling rate */<br /> >>  >>  > + for (i = 0; (unsigned) i < PA_ELEMENTSOF(freq_table); i++) {<!-- --><br /> >>  >>  > + if (freq_table[i].rate >= default_sample_spec->rate && (capabilities->frequency & freq_table[i].cap)) {<!-- --><br /> >>  >>  > + config->frequency = freq_table[i].cap;<br /> >>  >>  > + break;<br /> >>  >>  > + }<br /> >>  >>  > + }<br /> >>  >>  > +<br /> >>  >>  > + if ((unsigned) i == PA_ELEMENTSOF(freq_table)) {<!-- --><br /> >>  >>  > + for (--i; i >= 0; i--) {<!-- --><br /> >>  >>  > + if (capabilities->frequency & freq_table[i].cap) {<!-- --><br /> >>  >>  > + config->frequency = freq_table[i].cap;<br /> >>  >>  > + break;<br /> >>  >>  > + }<br /> >>  >>  > + }<br /> >>  >>  > +<br /> >>  >>  > + if (i < 0) {<!-- --><br /> >>  >>  > + pa_log_error("Not suitable sample rate");<br /> >>  >>  > + return false;<br /> >>  >>  > + }<br /> >>  >>  > + }<br /> >>  >>  > +<br /> >>  >>  > + return true;<br /> >>  >>  > +}<br /> >>  >>  > +<br /> >>  >>  > +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]) {<!-- --><br /> >>  >>  > + a2dp_aptx_t *config = (a2dp_aptx_t *) config_buffer;<br /> >>  >>  > + const a2dp_aptx_t *capabilities = (const a2dp_aptx_t *) capabilities_buffer;<br /> >>  >>  > +<br /> >>  >>  > + if (capabilities_size != sizeof(*capabilities)) {<!-- --><br /> >>  >>  > + pa_log_error("Invalid size of capabilities buffer");<br /> >>  >>  > + return 0;<br /> >>  >>  > + }<br /> >>  >>  > +<br /> >>  >>  > + pa_zero(*config);<br /> >>  >>  > +<br /> >>  >>  > + if (!fill_preferred_configuration_common(default_sample_spec, capabilities, config, APTX_VENDOR_ID, APTX_CODEC_ID))<br /> >>  >>  > + return 0;<br /> >>  >>  > +<br /> >>  >>  > + return sizeof(*config);<br /> >>  >>  > +}<br /> >>  >>  > +<br /> >>  >>  > +static uint8_t fill_preferred_configuration_hd(const pa_sample_spec *default_sample_spec, const uint8_t *capabilities_buffer, uint8_t capabilities_size, uint8_t config_buffer[MAX_A2DP_CAPS_SIZE]) {<!-- --><br /> >>  >>  > + a2dp_aptx_hd_t *config = (a2dp_aptx_hd_t *) config_buffer;<br /> >>  >>  > + const a2dp_aptx_hd_t *capabilities = (const a2dp_aptx_hd_t *) capabilities_buffer;<br /> >>  >>  > +<br /> >>  >>  > + if (capabilities_size != sizeof(*capabilities)) {<!-- --><br /> >>  >>  > + pa_log_error("Invalid size of capabilities buffer");<br /> >>  >>  > + return 0;<br /> >>  >>  > + }<br /> >>  >>  > +<br /> >>  >>  > + pa_zero(*config);<br /> >>  >>  > +<br /> >>  >>  > + if (!fill_preferred_configuration_common(default_sample_spec, &capabilities->aptx, &config->aptx, APTX_HD_VENDOR_ID, APTX_HD_CODEC_ID))<br /> >>  >>  > + return 0;<br /> >>  >>  > +<br /> >>  >>  > + return sizeof(*config);<br /> >>  >>  > +}<br /> >>  >>  > +<br /> >>  >>  > +static void *init_common(const a2dp_aptx_t *config, pa_sample_spec *sample_spec, int hd) {<!-- --><br /> >>  >>  > + struct aptx_context *aptx_c;<br /> >>  >>  > +<br /> >>  >>  > + aptx_c = aptx_init(hd);<br /> >>  >>  > + if (!aptx_c) {<!-- --><br /> >>  >>  > + pa_log_error("libopenaptx initialization failed");<br /> >>  >>  > + return NULL;<br /> >>  >>  > + }<br /> >>  >>  > +<br /> >>  >>  > + sample_spec->format = PA_SAMPLE_S24LE;<br /> >>  >>  > +<br /> >>  >>  > + switch (config->frequency) {<!-- --><br /> >>  >>  > + case APTX_SAMPLING_FREQ_16000:<br /> >>  >>  > + sample_spec->rate = 16000U;<br /> >>  >>  > + break;<br /> >>  >>  > + case APTX_SAMPLING_FREQ_32000:<br /> >>  >>  > + sample_spec->rate = 32000U;<br /> >>  >>  > + break;<br /> >>  >>  > + case APTX_SAMPLING_FREQ_44100:<br /> >>  >>  > + sample_spec->rate = 44100U;<br /> >>  >>  > + break;<br /> >>  >>  > + case APTX_SAMPLING_FREQ_48000:<br /> >>  >>  > + sample_spec->rate = 48000U;<br /> >>  >>  > + break;<br /> >>  >>  > + default:<br /> >>  >>  > + pa_assert_not_reached();<br /> >>  >>  > + }<br /> >>  >>  > +<br /> >>  >>  > + switch (config->channel_mode) {<!-- --><br /> >>  >>  > + case APTX_CHANNEL_MODE_STEREO:<br /> >>  >>  > + sample_spec->channels = 2;<br /> >>  >>  > + break;<br /> >>  >>  > + default:<br /> >>  >>  > + pa_assert_not_reached();<br /> >>  >>  > + }<br /> >>  >>  > +<br /> >>  >>  > + PA_ONCE_BEGIN {<!-- --><br /> >>  >>  > +#if OPENAPTX_MAJOR == 0 && OPENAPTX_MINOR == 0 && OPENAPTX_PATCH == 0<br /> >>  >>  > + /* libopenaptx version 0.0.0 does not export version global variables */<br /> >>  >>  > + const int aptx_major = OPENAPTX_MAJOR;<br /> >>  >>  > + const int aptx_minor = OPENAPTX_MINOR;<br /> >>  >>  > + const int aptx_patch = OPENAPTX_PATCH;<br /> >>  >>  > +#endif<br /> >>  >>  > + pa_log_debug("Using aptX codec implementation: libopenaptx %d.%d.%d from <a href="https://github.com/pali/libopenaptx">https://github.com/pali/libopenaptx</a>", aptx_major, aptx_minor, aptx_patch);<br /> >>  >>  > + } PA_ONCE_END;<br /> >>  >>  > +<br /> >>  >>  > + return aptx_c;<br /> >>  >>  > +}<br /> >>  >>  > +<br /> >>  >>  > +static void *init(bool for_encoding, bool for_backchannel, const uint8_t *config_buffer, uint8_t config_size, pa_sample_spec *sample_spec) {<!-- --><br /> >>  >>  > + const a2dp_aptx_t *config = (const a2dp_aptx_t *) config_buffer;<br /> >>  >>  > +<br /> >>  >>  > + pa_assert(config_size == sizeof(*config));<br /> >>  >>  > + pa_assert(!for_backchannel);<br /> >>  >>  > +<br /> >>  >>  > + return init_common(config, sample_spec, 0);<br /> >>  >>  > +}<br /> >>  >>  > +<br /> >>  >>  > +static void *init_hd(bool for_encoding, bool for_backchannel, const uint8_t *config_buffer, uint8_t config_size, pa_sample_spec *sample_spec) {<!-- --><br /> >>  >>  > + struct aptx_hd_info *aptx_hd_info;<br /> >>  >>  > + const a2dp_aptx_hd_t *config = (const a2dp_aptx_hd_t *) config_buffer;<br /> >>  >>  > +<br /> >>  >>  > + pa_assert(config_size == sizeof(*config));<br /> >>  >>  > + pa_assert(!for_backchannel);<br /> >>  >>  > +<br /> >>  >>  > + aptx_hd_info = pa_xnew0(struct aptx_hd_info, 1);<br /> >>  >>  > +<br /> >>  >>  > + aptx_hd_info->aptx_c = init_common(&config->aptx, sample_spec, 1);<br /> >>  >>  > + if (!aptx_hd_info->aptx_c) {<!-- --><br /> >>  >>  > + pa_xfree(aptx_hd_info);<br /> >>  >>  > + return NULL;<br /> >>  >>  > + }<br /> >>  >>  > +<br /> >>  >>  > + return aptx_hd_info;<br /> >>  >>  > +}<br /> >>  >>  > +<br /> >>  >>  > +static void deinit(void *codec_info) {<!-- --><br /> >>  >>  > + struct aptx_context *aptx_c = (struct aptx_context *) codec_info;<br /> >>  >>  > +<br /> >>  >>  > + aptx_finish(aptx_c);<br /> >>  >>  > +}<br /> >>  >>  > +<br /> >>  >>  > +static void deinit_hd(void *codec_info) {<!-- --><br /> >>  >>  > + struct aptx_hd_info *aptx_hd_info = (struct aptx_hd_info *) codec_info;<br /> >>  >>  > +<br /> >>  >>  > + deinit(aptx_hd_info->aptx_c);<br /> >>  >>  > + pa_xfree(aptx_hd_info);<br /> >>  >>  > +}<br /> >>  >>  > +<br /> >>  >>  > +static int reset(void *codec_info) {<!-- --><br /> >>  >>  > + struct aptx_context *aptx_c = (struct aptx_context *) codec_info;<br /> >>  >>  > +<br /> >>  >>  > + aptx_reset(aptx_c);<br /> >>  >>  > + return 0;<br /> >>  >>  > +}<br /> >>  >>  > +<br /> >>  >>  > +static int reset_hd(void *codec_info) {<!-- --><br /> >>  >>  > + struct aptx_hd_info *aptx_hd_info = (struct aptx_hd_info *) codec_info;<br /> >>  >>  > +<br /> >>  >>  > + reset(aptx_hd_info->aptx_c);<br /> >>  >>  > + aptx_hd_info->seq_num = 0;<br /> >>  >>  > + return 0;<br /> >>  >>  > +}<br /> >>  >>  > +<br /> >>  >>  > +static size_t get_block_size(void *codec_info, size_t link_mtu) {<!-- --><br /> >>  >>  > + /* aptX compression ratio is 6:1 and we need to process one aptX frame (4 bytes) at once */<br /> >>  >>  > + size_t frame_count = (link_mtu / 4);<br /> >>  >>  > +<br /> >>  >>  > + return frame_count * 4 * 6;<br /> >>  >>  > +}<br /> >>  >>  > +<br /> >>  >>  > +static size_t get_block_size_hd(void *codec_info, size_t link_mtu) {<!-- --><br /> >>  >>  > + /* aptX HD compression ratio is 4:1 and we need to process one aptX HD frame (6 bytes) at once, plus aptX HD frames are encapsulated in RTP */<br /> >>  >>  > + size_t rtp_size = sizeof(struct rtp_header);<br /> >>  >>  > + size_t frame_count = (link_mtu - rtp_size) / 6;<br /> >>  >>  > +<br /> >>  >>  > + return frame_count * 6 * 4;<br /> >>  >>  > +}<br /> >>  >>  > +<br /> >>  >>  > +static size_t reduce_encoder_bitrate(void *codec_info, size_t write_link_mtu) {<!-- --><br /> >>  >>  > + return 0;<br /> >>  >>  > +}<br /> >>  >>  > +<br /> >>  >>  > +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) {<!-- --><br /> >>  >>  > + struct aptx_context *aptx_c = (struct aptx_context *) codec_info;<br /> >>  >>  > + size_t written;<br /> >>  >>  > +<br /> >>  >>  > + *processed = aptx_encode(aptx_c, input_buffer, input_size, output_buffer, output_size, &written);<br /> >>  >>  > + if (PA_UNLIKELY(*processed == 0 || *processed != input_size))<br /> >>  >>  > + pa_log_error("aptX encoding error");<br /> >>  >>  > +<br /> >>  >>  > + return written;<br /> >>  >>  > +}<br /> >>  >>  > +<br /> >>  >>  > +static size_t encode_buffer_hd(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) {<!-- --><br /> >>  >>  > + struct aptx_hd_info *aptx_hd_info = (struct aptx_hd_info *) codec_info;<br /> >>  >>  > + struct rtp_header *header;<br /> >>  >>  > + size_t written;<br /> >>  >>  > +<br /> >>  >>  > + if (PA_UNLIKELY(output_size < sizeof(*header))) {<!-- --><br /> >>  >>  > + *processed = 0;<br /> >>  >>  > + return 0;<br /> >>  >>  > + }<br /> >>  >>  > +<br /> >>  >>  > + written = encode_buffer(aptx_hd_info->aptx_c, timestamp, input_buffer, input_size, output_buffer + sizeof(*header), output_size - sizeof(*header), processed);<br /> >>  >>  > +<br /> >>  >>  > + if (PA_LIKELY(written > 0)) {<!-- --><br /> >>  >>  > + header = (struct rtp_header *) output_buffer;<br /> >>  >>  > + pa_zero(*header);<br /> >>  >>  > + header->v = 2;<br /> >>  >>  > + header->pt = 96;<br /> >>  >>  > + header->sequence_number = htons(aptx_hd_info->seq_num++);<br /> >>  >>  > + header->timestamp = htonl(timestamp);<br /> >>  >>  > + header->ssrc = htonl(1);<br /> >>  >>  > + written += sizeof(*header);<br /> >>  >>  > + }<br /> >>  >>  > +<br /> >>  >>  > + return written;<br /> >>  >>  > +}<br /> >>  >>  > +<br /> >>  >>  > +static size_t decode_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) {<!-- --><br /> >>  >>  > + struct aptx_context *aptx_c = (struct aptx_context *) codec_info;<br /> >>  >>  > + size_t written;<br /> >>  >>  > +<br /> >>  >>  > + *processed = aptx_decode(aptx_c, input_buffer, input_size, output_buffer, output_size, &written);<br /> >>  >>  > +<br /> >>  >>  > + /* Due to aptX latency, aptx_decode starts filling output buffer after 90 input samples.<br /> >>  >>  > + * If input buffer contains less than 90 samples, aptx_decode returns zero (=no output)<br /> >>  >>  > + * but set *processed to non zero as input samples were processed. So do not check for<br /> >>  >>  > + * return value of aptx_decode, zero is valid. Decoding error is indicating by fact that<br /> >>  >>  > + * not all input samples were processed. */<br /> >>  >>  > + if (PA_UNLIKELY(*processed != input_size))<br /> >>  >>  > + pa_log_error("aptX decoding error");<br /> >>  >>  > +<br /> >>  >>  > + return written;<br /> >>  >>  > +}<br /> >>  >>  > +<br /> >>  >>  > +static size_t decode_buffer_hd(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) {<!-- --><br /> >>  >>  > + struct aptx_hd_info *aptx_hd_info = (struct aptx_hd_info *) codec_info;<br /> >>  >>  > + struct rtp_header *header;<br /> >>  >>  > + size_t written;<br /> >>  >>  > +<br /> >>  >>  > + if (PA_UNLIKELY(input_size < sizeof(*header))) {<!-- --><br /> >>  >>  > + *processed = 0;<br /> >>  >>  > + return 0;<br /> >>  >>  > + }<br /> >>  >>  > +<br /> >>  >>  > + header = (struct rtp_header *) input_buffer;<br /> >>  >>  > + written = decode_buffer(aptx_hd_info->aptx_c, timestamp, input_buffer + sizeof(*header), input_size - sizeof(*header), output_buffer, output_size, processed);<br /> >>  >>  > + *timestamp = ntohl(header->timestamp);<br /> >>  >>  > + *processed += sizeof(*header);<br /> >>  >>  > + return written;<br /> >>  >>  > +}<br /> >>  >>  > +<br /> >>  >>  > +const pa_a2dp_codec pa_a2dp_codec_aptx = {<!-- --><br /> >>  >>  > + .name = "aptx",<br /> >>  >>  > + .description = "aptX",<br /> >>  >>  > + .id = { A2DP_CODEC_VENDOR, APTX_VENDOR_ID, APTX_CODEC_ID },<br /> >>  >>  > + .support_backchannel = false,<br /> >>  >>  > + .can_accept_capabilities = can_accept_capabilities,<br /> >>  >>  > + .choose_remote_endpoint = choose_remote_endpoint,<br /> >>  >>  > + .fill_capabilities = fill_capabilities,<br /> >>  >>  > + .is_configuration_valid = is_configuration_valid,<br /> >>  >>  > + .fill_preferred_configuration = fill_preferred_configuration,<br /> >>  >>  > + .init = init,<br /> >>  >>  > + .deinit = deinit,<br /> >>  >>  > + .reset = reset,<br /> >>  >>  > + .get_read_block_size = get_block_size,<br /> >>  >>  > + .get_write_block_size = get_block_size,<br /> >>  >>  > + .reduce_encoder_bitrate = reduce_encoder_bitrate,<br /> >>  >>  > + .encode_buffer = encode_buffer,<br /> >>  >>  > + .decode_buffer = decode_buffer,<br /> >>  >>  > +};<br /> >>  >>  > +<br /> >>  >>  > +const pa_a2dp_codec pa_a2dp_codec_aptx_hd = {<!-- --><br /> >>  >>  > + .name = "aptx_hd",<br /> >>  >>  > + .description = "aptX HD",<br /> >>  >>  > + .id = { A2DP_CODEC_VENDOR, APTX_HD_VENDOR_ID, APTX_HD_CODEC_ID },<br /> >>  >>  > + .support_backchannel = false,<br /> >>  >>  > + .can_accept_capabilities = can_accept_capabilities_hd,<br /> >>  >>  > + .choose_remote_endpoint = choose_remote_endpoint_hd,<br /> >>  >>  > + .fill_capabilities = fill_capabilities_hd,<br /> >>  >>  > + .is_configuration_valid = is_configuration_valid_hd,<br /> >>  >>  > + .fill_preferred_configuration = fill_preferred_configuration_hd,<br /> >>  >>  > + .init = init_hd,<br /> >>  >>  > + .deinit = deinit_hd,<br /> >>  >>  > + .reset = reset_hd,<br /> >>  >>  > + .get_read_block_size = get_block_size_hd,<br /> >>  >>  > + .get_write_block_size = get_block_size_hd,<br /> >>  >>  > + .reduce_encoder_bitrate = reduce_encoder_bitrate,<br /> >>  >>  > + .encode_buffer = encode_buffer_hd,<br /> >>  >>  > + .decode_buffer = decode_buffer_hd,<br /> >>  >>  > +};<br /> >>  >>  > diff --git a/src/modules/bluetooth/a2dp-codec-util.c b/src/modules/bluetooth/a2dp-codec-util.c<br /> >>  >>  > index 94d01e7bd..363fcc<span class="177d5a4333ac019606de889e143743a1wmi-callto">387 100644</span><br /> >>  >>  > --- a/src/modules/bluetooth/a2dp-codec-util.c<br /> >>  >>  > +++ b/src/modules/bluetooth/a2dp-codec-util.c<br /> >>  >>  > @@ -27,11 +27,19 @@<br /> >>  >>  >  #include "a2dp-codec-util.h"<br /> >>  >>  ><br /> >>  >>  >  extern const pa_a2dp_codec pa_a2dp_codec_sbc;<br /> >>  >>  > +#ifdef HAVE_OPENAPTX<br /> >>  >>  > +extern const pa_a2dp_codec pa_a2dp_codec_aptx;<br /> >>  >>  > +extern const pa_a2dp_codec pa_a2dp_codec_aptx_hd;<br /> >>  >>  > +#endif<br /> >>  >>  ><br /> >>  >>  >  /* This is list of supported codecs. Their order is important.<br /> >>  >>  >   * Codec with higher index has higher priority. */<br /> >>  >>  >  const pa_a2dp_codec *pa_a2dp_codecs[] = {<!-- --><br /> >>  >>  >      &pa_a2dp_codec_sbc,<br /> >>  >>  > +#ifdef HAVE_OPENAPTX<br /> >>  >>  > + &pa_a2dp_codec_aptx,<br /> >>  >>  > + &pa_a2dp_codec_aptx_hd,<br /> >>  >>  > +#endif<br /> >>  >>  >  };<br /> >>  >>  ><br /> >>  >>  >  unsigned int pa_bluetooth_a2dp_codec_count(void) {<!-- --><br /> >>  >>  > --<br /> >>  >>  > 2.11.0<br /> >>  >>  ><br /> >>  >>  > _______________________________________________<br /> >>  >>  > pulseaudio-discuss mailing list<br /> >>  >>  > <a href="mailto:pulseaudio-discuss@lists.freedesktop.org">pulseaudio-discuss@lists.freedesktop.org</a><br /> >>  >>  > <a href="https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss">https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss</a><br /> >>  >>  _______________________________________________<br /> >>  >>  pulseaudio-discuss mailing list<br /> >>  >>  <a href="mailto:pulseaudio-discuss@lists.freedesktop.org">pulseaudio-discuss@lists.freedesktop.org</a><br /> >>  >>  <a href="https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss">https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss</a><br /> >>  ><br /> >>  > --<br /> >>  > Pali Rohár<br /> >>  > <a href="mailto:pali.rohar@gmail.com">pali.rohar@gmail.com</a><br /> ><br /> > --<br /> > Pali Rohár<br /> > <a href="mailto:pali.rohar@gmail.com">pali.rohar@gmail.com</a><br /></blockquote><p><br /></p><span class="c18e9d485856a85513717a5a5b59d3fewmi-sign">-- <br />Pali Rohár<br /><a href="mailto:pali.rohar@gmail.com">pali.rohar@gmail.com</a><br /></span></blockquote><br /><br />-- <br />Sent from Yandex.Mail for mobile