[pulseaudio-commits] [Git][pulseaudio/pulseaudio][master] 19 commits: bluetooth: unify encoder code paths

PulseAudio Marge Bot gitlab at gitlab.freedesktop.org
Mon Apr 5 15:52:49 UTC 2021



PulseAudio Marge Bot pushed to branch master at PulseAudio / pulseaudio


Commits:
3902cee4 by Igor V. Kovalenko at 2021-04-05T15:43:32+00:00
bluetooth: unify encoder code paths

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/507>

- - - - -
913e7767 by Igor V. Kovalenko at 2021-04-05T15:43:32+00:00
bluetooth: unify decoder code paths

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/507>

- - - - -
a7b21fb5 by Igor V. Kovalenko at 2021-04-05T15:43:32+00:00
bluetooth: add CVSD codec implementation

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/507>

- - - - -
f22cfa8f by James Bottomley at 2021-04-05T15:43:32+00:00
bluetooth: add support for mSBC codec

Adding processing support for the mSBC codec is somewhat problematic,
because, although it is a SBC codec, the a2dp handling can't simply be
reused because the codec is used on an eSCO link with transparent
data, meaning the transmission unit has to be 48 bytes (fragmenting
the codec packets) and reassembly and boundary detection is required
to be done by the implementation.  Therefore we have to implement
separate render and push routines for msbc that do this fragmentation.

Fragmentation is done by emulating circular buffers.  The receive
(push) buffer is easy, since the mSBC packet size is 60, simply have a
buffer of this size in the sbc_info area where the fragments are
reassembled.  Once we have a full 60 bytes, decode and restart from
zero.  The send (render) buffer is more problematic, since the
transmit must be done from contiguous memory.  This means that the
buffer must be the lowest common multiple of the transmission unit and
the packet size.  This value is 240 since 240/48 == 5 and 240/60 == 4.
So the buffer pointers are reset at 240 which is a whole number of
both rendered packets and eSCO transmission units.

Signed-off-by: James Bottomley <James.Bottomley at HansenPartnership.com>
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/507>

- - - - -
4444ecad by James Bottomley at 2021-04-05T15:43:32+00:00
bluetooth: add wideband audio codec negotiation to HFP

The HFP protocol supports the ability to negotiate codecs if that is
supported by both AG and HF.  This patch adds advertising of codec
negotiation support and the ability to negotiate a codec change.  The
only currently supported extra codec (as of HF 1.7.1) is mSBC.  mSBC
requires that the transmission be done over an eSCO link with
Transparent Data.  The linux kernel ensures the former, but we have to
manually set the socket to transparent data.

Signed-off-by: James Bottomley <James.Bottomley at HansenPartnership.com>
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/507>

- - - - -
62776cc8 by Igor V. Kovalenko at 2021-04-05T15:43:32+00:00
bluetooth: apply write MTU detection based on read packet size

HFP Audio Connection SCO configuration is negotiated symmetrically in both
directions, and USB HCI SCO packet framing is also symmetric in both directions.
This means that packet size will be the same for reads and writes over HFP SCO
socket.

HFP profile specification states that valid speech data shall exist on the
Synchronous Connection in both directions after the Audio Connection is
established.

This guarantees that an incoming packet will arrive shortly after SCO connection
is established. Use it's size to fix write MTU in case kernel value is wrong.

Discussion here https://lore.kernel.org/patchwork/patch/1303411/

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/507>

- - - - -
6c0c9cf8 by Igor V. Kovalenko at 2021-04-05T15:43:32+00:00
bluetooth: use helper to set multiple transport object attributes

For mSBC to work correctly the following must be set correctly
- codec object
- transport write method
- transport setsockopt method

Use helper method to set all three simultaneously.
Static configuration structure may be cleaner solution.

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/507>

- - - - -
7d191b64 by Igor V. Kovalenko at 2021-04-05T15:43:32+00:00
bluetooth: mSBC: ignore all-zero packets

This is a workaround for hardware/driver which inserts all-zero packets in what
otherwise looks like a valid mSBC stream.

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/507>

- - - - -
976fc1d0 by Igor V. Kovalenko at 2021-04-05T15:43:32+00:00
bluetooth: mSBC: ignore empty encoded frame

If input block size is shorter than SBC frame codesize, encoder will return 0.
Log this and skip whole input block.

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/507>

- - - - -
030dc8b9 by Igor V. Kovalenko at 2021-04-05T15:43:32+00:00
bluetooth: prepare to redo transport writeout scheduling

Bluetooth SCO is synchronous stream, make our writes more uniformly paced.
To do this, first separate writing to socket from rendering a frame.

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/507>

- - - - -
f0d32e94 by Igor V. Kovalenko at 2021-04-05T15:43:32+00:00
bluetooth: add mSBC to backend-ofono

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/507>

- - - - -
ddad63a2 by Igor V. Kovalenko at 2021-04-05T15:43:32+00:00
bluetooth: show negotiated HFP codec

While codec switching for HFP is not implemented, show current codec via
messaging api.

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/507>

- - - - -
436813d8 by Igor V. Kovalenko at 2021-04-05T15:43:32+00:00
bluetooth: remember negotiated HFP codec in native backend

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/507>

- - - - -
436a98a5 by Igor V. Kovalenko at 2021-04-05T15:43:32+00:00
bluetooth: produce silence on mSBC decoding error

We are supposed to conceal packet loss. This is not trivial but we can at least
produce silence instead of breaking on mSBC decoding error.

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/507>

- - - - -
9de895fe by Igor V. Kovalenko at 2021-04-05T15:43:32+00:00
bluetooth: add more call indicators

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/507>

- - - - -
c7c9ca22 by Igor V. Kovalenko at 2021-04-05T15:43:32+00:00
bluetooth: set initial packet size to 60

Raise initial MTU size to fix frame size when hci can do 60 byte frames.

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/507>

- - - - -
310e2877 by Igor V. Kovalenko at 2021-04-05T15:43:32+00:00
bluetooth: split BT codec from A2DP SEP configuration api

Common API for all bluetooth codecs is now pa_bt_codec.
API to negotiate and configure A2DP SEP over Bluez is now pa_a2dp_endpoint_conf.

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/507>

- - - - -
6a929407 by Igor V. Kovalenko at 2021-04-05T15:43:32+00:00
bluetooth: add modarg to allow disabling mSBC codec

Add module-bluetooth-discover argument enable_msbc, default is true (enabled)

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/507>

- - - - -
1a194c99 by Igor V. Kovalenko at 2021-04-05T15:43:32+00:00
bluetooth: mSBC: log lost input audio packets at debug level

Part-of: <https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/507>

- - - - -


18 changed files:

- meson.build
- src/modules/bluetooth/a2dp-codec-api.h
- src/modules/bluetooth/a2dp-codec-aptx-gst.c
- src/modules/bluetooth/a2dp-codec-ldac-gst.c
- src/modules/bluetooth/a2dp-codec-sbc.c
- src/modules/bluetooth/a2dp-codec-util.c
- src/modules/bluetooth/a2dp-codec-util.h
- src/modules/bluetooth/backend-native.c
- src/modules/bluetooth/backend-ofono.c
- src/modules/bluetooth/bluez5-util.c
- src/modules/bluetooth/bluez5-util.h
- + src/modules/bluetooth/bt-codec-api.h
- + src/modules/bluetooth/bt-codec-cvsd.c
- + src/modules/bluetooth/bt-codec-msbc.c
- + src/modules/bluetooth/bt-codec-msbc.h
- src/modules/bluetooth/meson.build
- src/modules/bluetooth/module-bluez5-device.c
- src/modules/bluetooth/module-bluez5-discover.c


Changes:

=====================================
meson.build
=====================================
@@ -696,6 +696,7 @@ if avahi_dep.found()
 endif
 
 sbc_dep = dependency('sbc', version : '>= 1.0', required : false)
+
 if get_option('bluez5')
   assert(dbus_dep.found(), 'BlueZ requires D-Bus support')
   assert(sbc_dep.found(), 'BlueZ requires SBC support')


=====================================
src/modules/bluetooth/a2dp-codec-api.h
=====================================
@@ -22,6 +22,8 @@
 
 #include <pulsecore/core.h>
 
+#include "bt-codec-api.h"
+
 #define MAX_A2DP_CAPS_SIZE 254
 #define DEFAULT_OUTPUT_RATE_REFRESH_INTERVAL_MS 500
 
@@ -36,13 +38,7 @@ typedef struct pa_a2dp_codec_id {
     uint16_t vendor_codec_id;
 } pa_a2dp_codec_id;
 
-typedef struct pa_a2dp_codec {
-    /* Unique name of the codec, lowercase and without whitespaces, used for
-     * constructing identifier, D-Bus paths, ... */
-    const char *name;
-    /* Human readable codec description */
-    const char *description;
-
+typedef struct pa_a2dp_endpoint_conf {
     /* A2DP codec id */
     pa_a2dp_codec_id id;
 
@@ -67,41 +63,8 @@ typedef struct pa_a2dp_codec {
     /* Fill preferred codec configuration, returns size of filled buffer or 0 on failure */
     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]);
 
-    /* Initialize codec, returns codec info data and set sample_spec,
-     * for_encoding is true when codec_info is used for encoding,
-     * for_backchannel is true when codec_info is used for backchannel */
-    void *(*init)(bool for_encoding, bool for_backchannel, const uint8_t *config_buffer, uint8_t config_size, pa_sample_spec *sample_spec, pa_core *core);
-    /* Deinitialize and release codec info data in codec_info */
-    void (*deinit)(void *codec_info);
-    /* Reset internal state of codec info data in codec_info, returns
-     * a negative value on failure */
-    int (*reset)(void *codec_info);
-
-    /* Get read block size for codec, it is minimal size of buffer
-     * needed to decode read_link_mtu bytes of encoded data */
-    size_t (*get_read_block_size)(void *codec_info, size_t read_link_mtu);
-    /* Get write block size for codec, it is maximal size of buffer
-     * which can produce at most write_link_mtu bytes of encoded data */
-    size_t (*get_write_block_size)(void *codec_info, size_t write_link_mtu);
-
-    /* Reduce encoder bitrate for codec, returns new write block size or zero
-     * if not changed, called when socket is not accepting encoded data fast
-     * enough */
-    size_t (*reduce_encoder_bitrate)(void *codec_info, size_t write_link_mtu);
-
-    /* Increase encoder bitrate for codec, returns new write block size or zero
-     * if not changed, called periodically when socket is keeping up with
-     * encoded data */
-    size_t (*increase_encoder_bitrate)(void *codec_info, size_t write_link_mtu);
-
-    /* Encode input_buffer of input_size to output_buffer of output_size,
-     * returns size of filled ouput_buffer and set processed to size of
-     * processed input_buffer */
-    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);
-    /* Decode input_buffer of input_size to output_buffer of output_size,
-     * returns size of filled ouput_buffer and set processed to size of
-     * processed input_buffer */
-    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);
-} pa_a2dp_codec;
+    /* Bluetooth codec */
+    pa_bt_codec bt_codec;
+} pa_a2dp_endpoint_conf;
 
 #endif


=====================================
src/modules/bluetooth/a2dp-codec-aptx-gst.c
=====================================
@@ -459,6 +459,13 @@ static size_t get_block_size(void *codec_info, size_t link_mtu) {
     return frame_count * 4 * 6;
 }
 
+static size_t get_encoded_block_size(void *codec_info, size_t input_size) {
+    /* input size should be aligned to codec input block size */
+    pa_assert_fp(input_size % (4 * 6) == 0);
+
+    return (input_size / (4 * 6)) * 4;
+}
+
 static size_t get_block_size_hd(void *codec_info, size_t link_mtu) {
     /* 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 */
     size_t rtp_size = sizeof(struct rtp_header);
@@ -467,6 +474,15 @@ static size_t get_block_size_hd(void *codec_info, size_t link_mtu) {
     return frame_count * 6 * 4;
 }
 
+static size_t get_encoded_block_size_hd(void *codec_info, size_t input_size) {
+    size_t rtp_size = sizeof(struct rtp_header);
+
+    /* input size should be aligned to codec input block size */
+    pa_assert_fp(input_size % (4 * 6) == 0);
+
+    return (input_size / (4 * 6)) * 6 + rtp_size;
+}
+
 static size_t reduce_encoder_bitrate(void *codec_info, size_t write_link_mtu) {
     return 0;
 }
@@ -538,9 +554,7 @@ static size_t decode_buffer_hd(void *codec_info, const uint8_t *input_buffer, si
     return written;
 }
 
-const pa_a2dp_codec pa_a2dp_codec_aptx = {
-    .name = "aptx",
-    .description = "aptX",
+const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_aptx = {
     .id = { A2DP_CODEC_VENDOR, APTX_VENDOR_ID, APTX_CODEC_ID },
     .support_backchannel = false,
     .can_be_supported = can_be_supported,
@@ -549,19 +563,22 @@ const pa_a2dp_codec pa_a2dp_codec_aptx = {
     .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,
+    .bt_codec = {
+        .name = "aptx",
+        .description = "aptX",
+        .init = init,
+        .deinit = deinit,
+        .reset = reset,
+        .get_read_block_size = get_block_size,
+        .get_write_block_size = get_block_size,
+        .get_encoded_block_size = get_encoded_block_size,
+        .reduce_encoder_bitrate = reduce_encoder_bitrate,
+        .encode_buffer = encode_buffer,
+        .decode_buffer = decode_buffer,
+    },
 };
 
-const pa_a2dp_codec pa_a2dp_codec_aptx_hd = {
-    .name = "aptx_hd",
-    .description = "aptX HD",
+const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_aptx_hd = {
     .id = { A2DP_CODEC_VENDOR, APTX_HD_VENDOR_ID, APTX_HD_CODEC_ID },
     .support_backchannel = false,
     .can_be_supported = can_be_supported,
@@ -570,12 +587,17 @@ const pa_a2dp_codec pa_a2dp_codec_aptx_hd = {
     .fill_capabilities = fill_capabilities_hd,
     .is_configuration_valid = is_configuration_valid_hd,
     .fill_preferred_configuration = fill_preferred_configuration_hd,
-    .init = init_hd,
-    .deinit = deinit,
-    .reset = reset_hd,
-    .get_read_block_size = get_block_size_hd,
-    .get_write_block_size = get_block_size_hd,
-    .reduce_encoder_bitrate = reduce_encoder_bitrate,
-    .encode_buffer = encode_buffer_hd,
-    .decode_buffer = decode_buffer_hd,
+    .bt_codec = {
+        .name = "aptx_hd",
+        .description = "aptX HD",
+        .init = init_hd,
+        .deinit = deinit,
+        .reset = reset_hd,
+        .get_read_block_size = get_block_size_hd,
+        .get_write_block_size = get_block_size_hd,
+        .get_encoded_block_size = get_encoded_block_size_hd,
+        .reduce_encoder_bitrate = reduce_encoder_bitrate,
+        .encode_buffer = encode_buffer_hd,
+        .decode_buffer = decode_buffer_hd,
+    },
 };


=====================================
src/modules/bluetooth/a2dp-codec-ldac-gst.c
=====================================
@@ -405,6 +405,11 @@ static size_t get_block_size(void *codec_info, size_t link_mtu) {
     return get_ldac_num_samples(codec_info) * get_ldac_num_frames(codec_info, info->codec_type) * pa_frame_size(info->ss);
 }
 
+static size_t get_encoded_block_size(void *codec_info, size_t input_size) {
+    /* encoded block size is not exactly known, report input_size */
+    return input_size;
+}
+
 static size_t reduce_encoder_bitrate(void *codec_info, size_t write_link_mtu) {
     return 0;
 }
@@ -419,9 +424,7 @@ static size_t encode_buffer(void *codec_info, uint32_t timestamp, const uint8_t
     return written;
 }
 
-const pa_a2dp_codec pa_a2dp_codec_ldac_eqmid_hq = {
-    .name = "ldac_hq",
-    .description = "LDAC (High Quality)",
+const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_ldac_eqmid_hq = {
     .id = { A2DP_CODEC_VENDOR, LDAC_VENDOR_ID, LDAC_CODEC_ID },
     .support_backchannel = false,
     .can_be_supported = can_be_supported,
@@ -430,18 +433,21 @@ const pa_a2dp_codec pa_a2dp_codec_ldac_eqmid_hq = {
     .fill_capabilities = fill_capabilities,
     .is_configuration_valid = is_configuration_valid,
     .fill_preferred_configuration = fill_preferred_configuration,
-    .init = init_hq,
-    .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,
+    .bt_codec = {
+        .name = "ldac_hq",
+        .description = "LDAC (High Quality)",
+        .init = init_hq,
+        .deinit = deinit,
+        .reset = reset,
+        .get_read_block_size = get_block_size,
+        .get_write_block_size = get_block_size,
+        .get_encoded_block_size = get_encoded_block_size,
+        .reduce_encoder_bitrate = reduce_encoder_bitrate,
+        .encode_buffer = encode_buffer,
+    },
 };
 
-const pa_a2dp_codec pa_a2dp_codec_ldac_eqmid_sq = {
-    .name = "ldac_sq",
-    .description = "LDAC (Standard Quality)",
+const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_ldac_eqmid_sq = {
     .id = { A2DP_CODEC_VENDOR, LDAC_VENDOR_ID, LDAC_CODEC_ID },
     .support_backchannel = false,
     .can_be_supported = can_be_supported,
@@ -450,18 +456,21 @@ const pa_a2dp_codec pa_a2dp_codec_ldac_eqmid_sq = {
     .fill_capabilities = fill_capabilities,
     .is_configuration_valid = is_configuration_valid,
     .fill_preferred_configuration = fill_preferred_configuration,
-    .init = init_sq,
-    .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,
+    .bt_codec = {
+        .name = "ldac_sq",
+        .description = "LDAC (Standard Quality)",
+        .init = init_sq,
+        .deinit = deinit,
+        .reset = reset,
+        .get_read_block_size = get_block_size,
+        .get_write_block_size = get_block_size,
+        .get_encoded_block_size = get_encoded_block_size,
+        .reduce_encoder_bitrate = reduce_encoder_bitrate,
+        .encode_buffer = encode_buffer,
+    },
 };
 
-const pa_a2dp_codec pa_a2dp_codec_ldac_eqmid_mq = {
-    .name = "ldac_mq",
-    .description = "LDAC (Mobile Quality)",
+const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_ldac_eqmid_mq = {
     .id = { A2DP_CODEC_VENDOR, LDAC_VENDOR_ID, LDAC_CODEC_ID },
     .support_backchannel = false,
     .can_be_supported = can_be_supported,
@@ -470,11 +479,16 @@ const pa_a2dp_codec pa_a2dp_codec_ldac_eqmid_mq = {
     .fill_capabilities = fill_capabilities,
     .is_configuration_valid = is_configuration_valid,
     .fill_preferred_configuration = fill_preferred_configuration,
-    .init = init_mq,
-    .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,
+    .bt_codec = {
+        .name = "ldac_mq",
+        .description = "LDAC (Mobile Quality)",
+        .init = init_mq,
+        .deinit = deinit,
+        .reset = reset,
+        .get_read_block_size = get_block_size,
+        .get_write_block_size = get_block_size,
+        .get_encoded_block_size = get_encoded_block_size,
+        .reduce_encoder_bitrate = reduce_encoder_bitrate,
+        .encode_buffer = encode_buffer,
+    },
 };


=====================================
src/modules/bluetooth/a2dp-codec-sbc.c
=====================================
@@ -714,6 +714,16 @@ static size_t get_block_size(void *codec_info, size_t link_mtu) {
     return frame_count * sbc_info->codesize;
 }
 
+static size_t get_encoded_block_size(void *codec_info, size_t input_size) {
+    struct sbc_info *sbc_info = (struct sbc_info *) codec_info;
+    size_t rtp_size = sizeof(struct rtp_header) + sizeof(struct rtp_sbc_payload);
+
+    /* input size should be aligned to codec input block size */
+    pa_assert_fp(input_size % sbc_info->codesize == 0);
+
+    return (input_size / sbc_info->codesize) * sbc_info->frame_length + rtp_size;
+}
+
 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;
@@ -886,9 +896,7 @@ static size_t decode_buffer(void *codec_info, const uint8_t *input_buffer, size_
     return d - output_buffer;
 }
 
-const pa_a2dp_codec pa_a2dp_codec_sbc = {
-    .name = "sbc",
-    .description = "SBC",
+const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_sbc = {
     .id = { A2DP_CODEC_SBC, 0, 0 },
     .support_backchannel = false,
     .can_be_supported = can_be_supported,
@@ -897,15 +905,20 @@ const pa_a2dp_codec pa_a2dp_codec_sbc = {
     .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,
-    .increase_encoder_bitrate = increase_encoder_bitrate,
-    .encode_buffer = encode_buffer,
-    .decode_buffer = decode_buffer,
+    .bt_codec = {
+        .name = "sbc",
+        .description = "SBC",
+        .init = init,
+        .deinit = deinit,
+        .reset = reset,
+        .get_read_block_size = get_block_size,
+        .get_write_block_size = get_block_size,
+        .get_encoded_block_size = get_encoded_block_size,
+        .reduce_encoder_bitrate = reduce_encoder_bitrate,
+        .increase_encoder_bitrate = increase_encoder_bitrate,
+        .encode_buffer = encode_buffer,
+        .decode_buffer = decode_buffer,
+    },
 };
 
 /* There are multiple definitions of SBC XQ, but in all cases this is
@@ -921,9 +934,7 @@ const pa_a2dp_codec pa_a2dp_codec_sbc = {
  * we can gain from increased bitrate.
  */
 
-const pa_a2dp_codec pa_a2dp_codec_sbc_xq_453 = {
-    .name = "sbc_xq_453",
-    .description = "SBC XQ 453kbps",
+const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_sbc_xq_453 = {
     .id = { A2DP_CODEC_SBC, 0, 0 },
     .support_backchannel = false,
     .can_be_supported = can_be_supported,
@@ -932,20 +943,23 @@ const pa_a2dp_codec pa_a2dp_codec_sbc_xq_453 = {
     .fill_capabilities = fill_capabilities_xq,
     .is_configuration_valid = is_configuration_valid,
     .fill_preferred_configuration = fill_preferred_configuration_xq_453kbps,
-    .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,
-    .increase_encoder_bitrate = increase_encoder_bitrate,
-    .encode_buffer = encode_buffer,
-    .decode_buffer = decode_buffer,
+    .bt_codec = {
+        .name = "sbc_xq_453",
+        .description = "SBC XQ 453kbps",
+        .init = init,
+        .deinit = deinit,
+        .reset = reset,
+        .get_read_block_size = get_block_size,
+        .get_write_block_size = get_block_size,
+        .get_encoded_block_size = get_encoded_block_size,
+        .reduce_encoder_bitrate = reduce_encoder_bitrate,
+        .increase_encoder_bitrate = increase_encoder_bitrate,
+        .encode_buffer = encode_buffer,
+        .decode_buffer = decode_buffer,
+    },
 };
 
-const pa_a2dp_codec pa_a2dp_codec_sbc_xq_512 = {
-    .name = "sbc_xq_512",
-    .description = "SBC XQ 512kbps",
+const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_sbc_xq_512 = {
     .id = { A2DP_CODEC_SBC, 0, 0 },
     .support_backchannel = false,
     .can_be_supported = can_be_supported,
@@ -954,20 +968,23 @@ const pa_a2dp_codec pa_a2dp_codec_sbc_xq_512 = {
     .fill_capabilities = fill_capabilities_xq,
     .is_configuration_valid = is_configuration_valid,
     .fill_preferred_configuration = fill_preferred_configuration_xq_512kbps,
-    .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,
-    .increase_encoder_bitrate = increase_encoder_bitrate,
-    .encode_buffer = encode_buffer,
-    .decode_buffer = decode_buffer,
+    .bt_codec = {
+        .name = "sbc_xq_512",
+        .description = "SBC XQ 512kbps",
+        .init = init,
+        .deinit = deinit,
+        .reset = reset,
+        .get_read_block_size = get_block_size,
+        .get_write_block_size = get_block_size,
+        .get_encoded_block_size = get_encoded_block_size,
+        .reduce_encoder_bitrate = reduce_encoder_bitrate,
+        .increase_encoder_bitrate = increase_encoder_bitrate,
+        .encode_buffer = encode_buffer,
+        .decode_buffer = decode_buffer,
+    },
 };
 
-const pa_a2dp_codec pa_a2dp_codec_sbc_xq_552 = {
-    .name = "sbc_xq_552",
-    .description = "SBC XQ 552kbps",
+const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_sbc_xq_552 = {
     .id = { A2DP_CODEC_SBC, 0, 0 },
     .support_backchannel = false,
     .can_be_supported = can_be_supported,
@@ -976,13 +993,18 @@ const pa_a2dp_codec pa_a2dp_codec_sbc_xq_552 = {
     .fill_capabilities = fill_capabilities_xq,
     .is_configuration_valid = is_configuration_valid,
     .fill_preferred_configuration = fill_preferred_configuration_xq_552kbps,
-    .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,
-    .increase_encoder_bitrate = increase_encoder_bitrate,
-    .encode_buffer = encode_buffer,
-    .decode_buffer = decode_buffer,
+    .bt_codec = {
+        .name = "sbc_xq_552",
+        .description = "SBC XQ 552kbps",
+        .init = init,
+        .deinit = deinit,
+        .reset = reset,
+        .get_read_block_size = get_block_size,
+        .get_write_block_size = get_block_size,
+        .get_encoded_block_size = get_encoded_block_size,
+        .reduce_encoder_bitrate = reduce_encoder_bitrate,
+        .increase_encoder_bitrate = increase_encoder_bitrate,
+        .encode_buffer = encode_buffer,
+        .decode_buffer = decode_buffer,
+    },
 };


=====================================
src/modules/bluetooth/a2dp-codec-util.c
=====================================
@@ -29,54 +29,84 @@
 
 #include "a2dp-codec-util.h"
 
-extern const pa_a2dp_codec pa_a2dp_codec_sbc;
-extern const pa_a2dp_codec pa_a2dp_codec_sbc_xq_453;
-extern const pa_a2dp_codec pa_a2dp_codec_sbc_xq_512;
-extern const pa_a2dp_codec pa_a2dp_codec_sbc_xq_552;
+extern const pa_bt_codec pa_bt_codec_msbc;
+extern const pa_bt_codec pa_bt_codec_cvsd;
+
+/* List of HSP/HFP codecs.
+ */
+static const pa_bt_codec *pa_hf_codecs[] = {
+    &pa_bt_codec_cvsd,
+    &pa_bt_codec_msbc,
+};
+
+extern const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_sbc;
+extern const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_sbc_xq_453;
+extern const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_sbc_xq_512;
+extern const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_sbc_xq_552;
 #ifdef HAVE_GSTAPTX
-extern const pa_a2dp_codec pa_a2dp_codec_aptx;
-extern const pa_a2dp_codec pa_a2dp_codec_aptx_hd;
+extern const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_aptx;
+extern const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_aptx_hd;
 #endif
 #ifdef HAVE_GSTLDAC
-extern const pa_a2dp_codec pa_a2dp_codec_ldac_eqmid_hq;
-extern const pa_a2dp_codec pa_a2dp_codec_ldac_eqmid_sq;
-extern const pa_a2dp_codec pa_a2dp_codec_ldac_eqmid_mq;
+extern const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_ldac_eqmid_hq;
+extern const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_ldac_eqmid_sq;
+extern const pa_a2dp_endpoint_conf pa_a2dp_endpoint_conf_ldac_eqmid_mq;
 #endif
 
 /* This is list of supported codecs. Their order is important.
  * Codec with lower index has higher priority. */
-static const pa_a2dp_codec *pa_a2dp_codecs[] = {
+static const pa_a2dp_endpoint_conf *pa_a2dp_endpoint_configurations[] = {
 #ifdef HAVE_GSTLDAC
-    &pa_a2dp_codec_ldac_eqmid_hq,
-    &pa_a2dp_codec_ldac_eqmid_sq,
-    &pa_a2dp_codec_ldac_eqmid_mq,
+    &pa_a2dp_endpoint_conf_ldac_eqmid_hq,
+    &pa_a2dp_endpoint_conf_ldac_eqmid_sq,
+    &pa_a2dp_endpoint_conf_ldac_eqmid_mq,
 #endif
 #ifdef HAVE_GSTAPTX
-    &pa_a2dp_codec_aptx_hd,
-    &pa_a2dp_codec_aptx,
+    &pa_a2dp_endpoint_conf_aptx_hd,
+    &pa_a2dp_endpoint_conf_aptx,
 #endif
-    &pa_a2dp_codec_sbc,
-    &pa_a2dp_codec_sbc_xq_453,
-    &pa_a2dp_codec_sbc_xq_512,
-    &pa_a2dp_codec_sbc_xq_552,
+    &pa_a2dp_endpoint_conf_sbc,
+    &pa_a2dp_endpoint_conf_sbc_xq_453,
+    &pa_a2dp_endpoint_conf_sbc_xq_512,
+    &pa_a2dp_endpoint_conf_sbc_xq_552,
 };
 
-unsigned int pa_bluetooth_a2dp_codec_count(void) {
-    return PA_ELEMENTSOF(pa_a2dp_codecs);
+unsigned int pa_bluetooth_a2dp_endpoint_conf_count(void) {
+    return PA_ELEMENTSOF(pa_a2dp_endpoint_configurations);
+}
+
+const pa_a2dp_endpoint_conf *pa_bluetooth_a2dp_endpoint_conf_iter(unsigned int i) {
+    pa_assert(i < pa_bluetooth_a2dp_endpoint_conf_count());
+    return pa_a2dp_endpoint_configurations[i];
 }
 
-const pa_a2dp_codec *pa_bluetooth_a2dp_codec_iter(unsigned int i) {
-    pa_assert(i < pa_bluetooth_a2dp_codec_count());
-    return pa_a2dp_codecs[i];
+unsigned int pa_bluetooth_hf_codec_count(void) {
+    return PA_ELEMENTSOF(pa_hf_codecs);
+}
+
+const pa_bt_codec *pa_bluetooth_hf_codec_iter(unsigned int i) {
+    pa_assert(i < pa_bluetooth_hf_codec_count());
+    return pa_hf_codecs[i];
+}
+
+const pa_bt_codec *pa_bluetooth_get_hf_codec(const char *name) {
+    unsigned int i;
+
+    for (i = 0; i < PA_ELEMENTSOF(pa_hf_codecs); ++i) {
+        if (pa_streq(pa_hf_codecs[i]->name, name))
+            return pa_hf_codecs[i];
+    }
+
+    return NULL;
 }
 
-const pa_a2dp_codec *pa_bluetooth_get_a2dp_codec(const char *name) {
+const pa_a2dp_endpoint_conf *pa_bluetooth_get_a2dp_endpoint_conf(const char *name) {
     unsigned int i;
-    unsigned int count = pa_bluetooth_a2dp_codec_count();
+    unsigned int count = pa_bluetooth_a2dp_endpoint_conf_count();
 
     for (i = 0; i < count; i++) {
-        if (pa_streq(pa_a2dp_codecs[i]->name, name))
-            return pa_a2dp_codecs[i];
+        if (pa_streq(pa_a2dp_endpoint_configurations[i]->bt_codec.name, name))
+            return pa_a2dp_endpoint_configurations[i];
     }
 
     return NULL;
@@ -97,13 +127,13 @@ void pa_bluetooth_a2dp_codec_gst_init(void) {
 
 bool pa_bluetooth_a2dp_codec_is_available(const pa_a2dp_codec_id *id, bool is_a2dp_sink) {
     unsigned int i;
-    unsigned int count = pa_bluetooth_a2dp_codec_count();
-    const pa_a2dp_codec *a2dp_codec;
+    unsigned int count = pa_bluetooth_a2dp_endpoint_conf_count();
+    const pa_a2dp_endpoint_conf *conf;
 
     for (i = 0; i < count; i++) {
-        a2dp_codec = pa_bluetooth_a2dp_codec_iter(i);
-        if (memcmp(id, &a2dp_codec->id, sizeof(pa_a2dp_codec_id)) == 0
-                && a2dp_codec->can_be_supported(is_a2dp_sink))
+        conf = pa_bluetooth_a2dp_endpoint_conf_iter(i);
+        if (memcmp(id, &conf->id, sizeof(pa_a2dp_codec_id)) == 0
+                && conf->can_be_supported(is_a2dp_sink))
             return true;
     }
 


=====================================
src/modules/bluetooth/a2dp-codec-util.h
=====================================
@@ -23,13 +23,13 @@
 #include "a2dp-codec-api.h"
 
 /* Get number of supported A2DP codecs */
-unsigned int pa_bluetooth_a2dp_codec_count(void);
+unsigned int pa_bluetooth_a2dp_endpoint_conf_count(void);
 
 /* Get i-th codec. Codec with higher number has higher priority */
-const pa_a2dp_codec *pa_bluetooth_a2dp_codec_iter(unsigned int i);
+const pa_a2dp_endpoint_conf *pa_bluetooth_a2dp_endpoint_conf_iter(unsigned int i);
 
 /* Get codec by name */
-const pa_a2dp_codec *pa_bluetooth_get_a2dp_codec(const char *name);
+const pa_a2dp_endpoint_conf *pa_bluetooth_get_a2dp_endpoint_conf(const char *name);
 
 /* Check if the given codec can be supported in A2DP_SINK or A2DP_SOURCE */
 bool pa_bluetooth_a2dp_codec_is_available(const pa_a2dp_codec_id *id, bool is_a2dp_sink);
@@ -37,4 +37,13 @@ bool pa_bluetooth_a2dp_codec_is_available(const pa_a2dp_codec_id *id, bool is_a2
 /* Initialise GStreamer */
 void pa_bluetooth_a2dp_codec_gst_init(void);
 
+/* Get number of supported HSP/HFP codecs */
+unsigned int pa_bluetooth_hf_codec_count(void);
+
+/* Get i-th codec. Codec with higher number has higher priority */
+const pa_bt_codec *pa_bluetooth_hf_codec_iter(unsigned int i);
+
+/* Get HSP/HFP codec by name */
+const pa_bt_codec *pa_bluetooth_get_hf_codec(const char *name);
+
 #endif


=====================================
src/modules/bluetooth/backend-native.c
=====================================
@@ -35,6 +35,7 @@
 #include <bluetooth/sco.h>
 
 #include "bluez5-util.h"
+#include "bt-codec-msbc.h"
 
 #define HSP_MAX_GAIN 15
 
@@ -59,6 +60,9 @@ struct transport_data {
 struct hfp_config {
     uint32_t capabilities;
     int state;
+    bool support_codec_negotiation;
+    bool support_msbc;
+    int selected_codec;
 };
 
 /*
@@ -91,7 +95,7 @@ enum hfp_ag_features {
 /* gateway features we support, which is as little as we can get away with */
 static uint32_t hfp_features =
     /* HFP 1.6 requires this */
-    (1 << HFP_AG_ESTATUS );
+    (1 << HFP_AG_ESTATUS ) | (1 << HFP_AG_CODECS);
 
 #define HSP_AG_PROFILE "/Profile/HSPAGProfile"
 #define HFP_AG_PROFILE "/Profile/HFPAGProfile"
@@ -219,6 +223,20 @@ static void rfcomm_write_response(int fd, const char *fmt, ...)
     va_end(ap);
 }
 
+static int sco_setsockopt_enable_bt_voice(pa_bluetooth_transport *t, int fd) {
+    /* the mSBC codec requires a special transparent eSCO connection */
+    struct bt_voice voice;
+
+    memset(&voice, 0, sizeof(voice));
+    voice.setting = BT_VOICE_TRANSPARENT;
+    if (setsockopt(fd, SOL_BLUETOOTH, BT_VOICE, &voice, sizeof(voice)) < 0) {
+        pa_log_error("sockopt(): %s", pa_cstrerror(errno));
+        return -1;
+    }
+    pa_log_info("Enabled BT_VOICE_TRANSPARENT connection for mSBC");
+    return 0;
+}
+
 static int sco_do_connect(pa_bluetooth_transport *t) {
     pa_bluetooth_device *d = t->device;
     struct sockaddr_sco addr;
@@ -254,6 +272,9 @@ static int sco_do_connect(pa_bluetooth_transport *t) {
         goto fail_close;
     }
 
+    if (t->setsockopt && t->setsockopt(t, sock) < 0)
+        goto fail_close;
+
     memset(&addr, 0, len);
     addr.sco_family = AF_BLUETOOTH;
     bacpy(&addr.sco_bdaddr, &dst);
@@ -305,8 +326,8 @@ static int sco_acquire_cb(pa_bluetooth_transport *t, bool optional, size_t *imtu
     if (sock < 0)
         goto fail;
 
-    if (imtu) *imtu = 48;
-    if (omtu) *omtu = 48;
+    if (imtu) *imtu = 60;
+    if (omtu) *omtu = 60;
 
     if (t->device->autodetect_mtu) {
         struct sco_options sco_opt;
@@ -323,6 +344,11 @@ static int sco_acquire_cb(pa_bluetooth_transport *t, bool optional, size_t *imtu
         }
     }
 
+    /* read/decode machinery only works if we get at most one MSBC encoded packet at a time
+     * when it is fixed to process stream of packets, lift this assertion */
+    pa_assert(*imtu <= MSBC_PACKET_SIZE);
+    pa_assert(*omtu <= MSBC_PACKET_SIZE);
+
     return sock;
 
 fail:
@@ -334,6 +360,61 @@ static void sco_release_cb(pa_bluetooth_transport *t) {
     /* device will close the SCO socket for us */
 }
 
+static ssize_t sco_transport_write(pa_bluetooth_transport *t, int fd, const void* buffer, size_t size, size_t write_mtu) {
+    ssize_t l = 0;
+    size_t written = 0;
+    size_t write_size;
+
+    pa_assert(t);
+
+    /* since SCO setup is symmetric, fix write MTU to be size of last read packet */
+    if (t->last_read_size)
+        write_mtu = PA_MIN(t->last_read_size, write_mtu);
+
+    /* if encoder buffer has less data than required to make complete packet */
+    if (size < write_mtu)
+        return 0;
+
+    /* write out MTU sized chunks only */
+    while (written < size) {
+        write_size = PA_MIN(size - written, write_mtu);
+        if (write_size < write_mtu)
+            break;
+        l = pa_write(fd, buffer + written, write_size, &t->stream_write_type);
+        if (l < 0)
+            break;
+        written += l;
+    }
+
+    if (l < 0) {
+        if (errno == EAGAIN) {
+            /* Hmm, apparently the socket was not writable, give up for now */
+            pa_log_debug("Got EAGAIN on write() after POLLOUT, probably there is a temporary connection loss.");
+            /* Drain write buffer */
+            written = size;
+        } else if (errno == EINVAL && t->last_read_size == 0) {
+            /* Likely write_link_mtu is still wrong, retry after next successful read */
+            pa_log_debug("got write EINVAL, next successful read should fix MTU");
+            /* Drain write buffer */
+            written = size;
+        } else {
+            pa_log_error("Failed to write data to socket: %s", pa_cstrerror(errno));
+            /* Report error from write call */
+            return -1;
+        }
+    }
+
+    /* if too much data left discard it all */
+    if (size - written >= write_mtu) {
+        pa_log_warn("Wrote memory block to socket only partially! %lu written, discarding pending write size %lu larger than write_mtu %lu",
+                    written, size, write_mtu);
+        /* Drain write buffer */
+        written = size;
+    }
+
+    return written;
+}
+
 static void sco_io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event_flags_t events, void *userdata) {
     pa_bluetooth_transport *t = userdata;
 
@@ -480,6 +561,11 @@ static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf
 {
     struct hfp_config *c = t->config;
     int val;
+    char str[5];
+
+    /* first-time initialize selected codec to CVSD */
+    if (c->selected_codec == 0)
+        c->selected_codec = 1;
 
     /* stateful negotiation */
     if (c->state == 0 && sscanf(buf, "AT+BRSF=%d", &val) == 1) {
@@ -488,29 +574,87 @@ static bool hfp_rfcomm_handle(int fd, pa_bluetooth_transport *t, const char *buf
         rfcomm_write_response(fd, "+BRSF: %d", hfp_features);
         c->state = 1;
 
+        return true;
+    } else if (sscanf(buf, "AT+BAC=%3s", str) == 1) {
+        if (strncmp(str, "1,2", 3) == 0)
+            c->support_msbc = true;
+        else
+            c->support_msbc = false;
+
+        c->support_codec_negotiation = true;
+
+        if (c->state == 1) {
+            /* initial list of codecs supported by HF */
+        } else {
+            /* HF sent updated list of codecs */
+        }
+
+        /* no state change */
+
         return true;
     } else if (c->state == 1 && pa_startswith(buf, "AT+CIND=?")) {
-          /* we declare minimal no indicators */
+        /* we declare minimal no indicators */
         rfcomm_write_response(fd, "+CIND: "
                      /* many indicators can be supported, only call and
                       * callheld are mandatory, so that's all we repy */
+                     "(\"service\",(0-1)),"
                      "(\"call\",(0-1)),"
+                     "(\"callsetup\",(0-3)),"
                      "(\"callheld\",(0-2))");
         c->state = 2;
+
         return true;
     } else if (c->state == 2 && pa_startswith(buf, "AT+CIND?")) {
-        rfcomm_write_response(fd, "+CIND: 0,0");
+        rfcomm_write_response(fd, "+CIND: 0,0,0,0");
         c->state = 3;
+
         return true;
     } else if ((c->state == 2 || c->state == 3) && pa_startswith(buf, "AT+CMER=")) {
         rfcomm_write_response(fd, "OK");
-        c->state = 4;
-        transport_put(t);
+
+        if (c->support_codec_negotiation) {
+            if (c->support_msbc && pa_bluetooth_discovery_get_enable_msbc(t->device->discovery)) {
+                rfcomm_write_response(fd, "+BCS:2");
+                c->state = 4;
+            } else {
+                rfcomm_write_response(fd, "+BCS:1");
+                c->state = 4;
+            }
+        } else {
+            c->state = 5;
+            pa_bluetooth_transport_reconfigure(t, pa_bluetooth_get_hf_codec("CVSD"), sco_transport_write, NULL);
+            transport_put(t);
+        }
+
         return false;
+    } else if (sscanf(buf, "AT+BCS=%d", &val)) {
+        if (val == 1) {
+            pa_bluetooth_transport_reconfigure(t, pa_bluetooth_get_hf_codec("CVSD"), sco_transport_write, NULL);
+        } else if (val == 2 && pa_bluetooth_discovery_get_enable_msbc(t->device->discovery)) {
+            pa_bluetooth_transport_reconfigure(t, pa_bluetooth_get_hf_codec("mSBC"), sco_transport_write, sco_setsockopt_enable_bt_voice);
+        } else {
+            pa_assert_fp(val != 1 && val != 2);
+            rfcomm_write_response(fd, "ERROR");
+            return false;
+        }
+
+        c->selected_codec = val;
+
+        if (c->state == 4) {
+            c->state = 5;
+            pa_log_info("HFP negotiated codec %s", t->bt_codec->name);
+            transport_put(t);
+        }
+
+        return true;
+    } if (c->state == 4) {
+        /* the ack for the codec setting may take a while. we need
+         * to reply OK to everything else until then */
+        return true;
     }
 
     /* if we get here, negotiation should be complete */
-    if (c->state != 4) {
+    if (c->state != 5) {
         pa_log_error("HFP negotiation failed in state %d with inbound %s\n",
                      c->state, buf);
         rfcomm_write_response(fd, "ERROR");
@@ -751,6 +895,8 @@ static DBusMessage *profile_new_connection(DBusConnection *conn, DBusMessage *m,
         t->set_source_volume = set_source_volume;
     }
 
+    pa_bluetooth_transport_reconfigure(t, pa_bluetooth_get_hf_codec("CVSD"), sco_transport_write, NULL);
+
     trd = pa_xnew0(struct transport_data, 1);
     trd->rfcomm_fd = fd;
     trd->mainloop = b->core->mainloop;


=====================================
src/modules/bluetooth/backend-ofono.c
=====================================
@@ -83,6 +83,61 @@ struct pa_bluetooth_backend {
     PA_LLIST_HEAD(pa_dbus_pending, pending);
 };
 
+static ssize_t sco_transport_write(pa_bluetooth_transport *t, int fd, const void* buffer, size_t size, size_t write_mtu) {
+    ssize_t l = 0;
+    size_t written = 0;
+    size_t write_size;
+
+    pa_assert(t);
+
+    /* since SCO setup is symmetric, fix write MTU to be size of last read packet */
+    if (t->last_read_size)
+        write_mtu = PA_MIN(t->last_read_size, write_mtu);
+
+    /* if encoder buffer has less data than required to make complete packet */
+    if (size < write_mtu)
+        return 0;
+
+    /* write out MTU sized chunks only */
+    while (written < size) {
+        write_size = PA_MIN(size - written, write_mtu);
+        if (write_size < write_mtu)
+            break;
+        l = pa_write(fd, buffer + written, write_size, &t->stream_write_type);
+        if (l < 0)
+            break;
+        written += l;
+    }
+
+    if (l < 0) {
+        if (errno == EAGAIN) {
+            /* Hmm, apparently the socket was not writable, give up for now */
+            pa_log_debug("Got EAGAIN on write() after POLLOUT, probably there is a temporary connection loss.");
+            /* Drain write buffer */
+            written = size;
+        } else if (errno == EINVAL && t->last_read_size == 0) {
+            /* Likely write_link_mtu is still wrong, retry after next successful read */
+            pa_log_debug("got write EINVAL, next successful read should fix MTU");
+            /* Drain write buffer */
+            written = size;
+        } else {
+            pa_log_error("Failed to write data to socket: %s", pa_cstrerror(errno));
+            /* Report error from write call */
+            return -1;
+        }
+    }
+
+    /* if too much data left discard it all */
+    if (size - written >= write_mtu) {
+        pa_log_warn("Wrote memory block to socket only partially! %lu written, discarding pending write size %lu larger than write_mtu %lu",
+                    written, size, write_mtu);
+        /* Drain write buffer */
+        written = size;
+    }
+
+    return written;
+}
+
 static pa_dbus_pending* hf_dbus_send_and_add_to_pending(pa_bluetooth_backend *backend, DBusMessage *m,
                                                     DBusPendingCallNotifyFunction func, void *call_data) {
     pa_dbus_pending *p;
@@ -165,14 +220,21 @@ static int card_acquire(struct hf_audio_card *card) {
                                       DBUS_TYPE_BYTE, &codec,
                                       DBUS_TYPE_INVALID) == true)) {
         dbus_message_unref(r);
-        if (codec != HFP_AUDIO_CODEC_CVSD) {
+
+        if (codec == HFP_AUDIO_CODEC_CVSD) {
+            pa_bluetooth_transport_reconfigure(card->transport, pa_bluetooth_get_hf_codec("CVSD"), sco_transport_write, NULL);
+        } else if (codec == HFP_AUDIO_CODEC_MSBC) {
+            /* oFono is expected to set up socket BT_VOICE_TRANSPARENT option */
+            pa_bluetooth_transport_reconfigure(card->transport, pa_bluetooth_get_hf_codec("mSBC"), sco_transport_write, NULL);
+        } else {
+            pa_assert_fp(codec != HFP_AUDIO_CODEC_CVSD && codec != HFP_AUDIO_CODEC_MSBC);
             pa_log_error("Invalid codec: %u", codec);
             /* shutdown to make sure connection is dropped immediately */
             shutdown(fd, SHUT_RDWR);
             close(fd);
             return -1;
         }
-        card->transport->codec = codec;
+
         card->fd = fd;
         return 0;
     }
@@ -267,11 +329,14 @@ static int hf_audio_agent_transport_acquire(pa_bluetooth_transport *t, bool opti
      * the Bluetooth adapter and (for adapters in the USB bus) the MxPS
      * value from the Isoc USB endpoint in use by btusb and should be
      * made available to userspace by the Bluetooth kernel subsystem.
-     * Meanwhile the empiric value 48 will be used. */
+     *
+     * Set initial MTU to max size which is reported to be working (60 bytes)
+     * See also pa_bluetooth_transport::last_read_size handling.
+     */
     if (imtu)
-        *imtu = 48;
+        *imtu = 60;
     if (omtu)
-        *omtu = 48;
+        *omtu = 60;
 
     err = socket_accept(card->fd);
     if (err < 0) {
@@ -355,6 +420,7 @@ static void hf_audio_agent_card_found(pa_bluetooth_backend *backend, const char
     card->transport->acquire = hf_audio_agent_transport_acquire;
     card->transport->release = hf_audio_agent_transport_release;
     card->transport->userdata = card;
+    pa_bluetooth_transport_reconfigure(card->transport, pa_bluetooth_get_hf_codec("CVSD"), sco_transport_write, NULL);
 
     pa_bluetooth_transport_put(card->transport);
     pa_hashmap_put(backend->cards, card->path, card);
@@ -482,6 +548,8 @@ static void hf_audio_agent_register(pa_bluetooth_backend *hf) {
     pa_assert_se(m = dbus_message_new_method_call(OFONO_SERVICE, "/", HF_AUDIO_MANAGER_INTERFACE, "Register"));
 
     codecs[ncodecs++] = HFP_AUDIO_CODEC_CVSD;
+    if (pa_bluetooth_discovery_get_enable_msbc(hf->discovery))
+        codecs[ncodecs++] = HFP_AUDIO_CODEC_MSBC;
 
     pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &pcodecs, ncodecs,
                                           DBUS_TYPE_INVALID));
@@ -627,7 +695,7 @@ static DBusMessage *hf_audio_agent_new_connection(DBusConnection *c, DBusMessage
 
     card = pa_hashmap_get(backend->cards, path);
 
-    if (!card || codec != HFP_AUDIO_CODEC_CVSD || card->fd >= 0) {
+    if (!card || (codec != HFP_AUDIO_CODEC_CVSD && codec != HFP_AUDIO_CODEC_MSBC) || card->fd >= 0) {
         pa_log_warn("New audio connection invalid arguments (path=%s fd=%d, codec=%d)", path, fd, codec);
         pa_assert_se(r = dbus_message_new_error(m, "org.ofono.Error.InvalidArguments", "Invalid arguments in method call"));
         shutdown(fd, SHUT_RDWR);
@@ -639,7 +707,12 @@ static DBusMessage *hf_audio_agent_new_connection(DBusConnection *c, DBusMessage
 
     card->connecting = false;
     card->fd = fd;
-    card->transport->codec = codec;
+    if (codec == HFP_AUDIO_CODEC_CVSD) {
+        pa_bluetooth_transport_reconfigure(card->transport, pa_bluetooth_get_hf_codec("CVSD"), sco_transport_write, NULL);
+    } else if (codec == HFP_AUDIO_CODEC_MSBC) {
+        /* oFono is expected to set up socket BT_VOICE_TRANSPARENT option */
+        pa_bluetooth_transport_reconfigure(card->transport, pa_bluetooth_get_hf_codec("mSBC"), sco_transport_write, NULL);
+    }
 
     pa_bluetooth_transport_set_state(card->transport, PA_BLUETOOTH_TRANSPORT_STATE_PLAYING);
 


=====================================
src/modules/bluetooth/bluez5-util.c
=====================================
@@ -22,11 +22,14 @@
 #include <config.h>
 #endif
 
+#include <errno.h>
+
 #include <pulse/rtclock.h>
 #include <pulse/timeval.h>
 #include <pulse/xmalloc.h>
 
 #include <pulsecore/core.h>
+#include <pulsecore/core-error.h>
 #include <pulsecore/core-util.h>
 #include <pulsecore/dbus-shared.h>
 #include <pulsecore/log.h>
@@ -115,6 +118,7 @@ struct pa_bluetooth_discovery {
     pa_bluetooth_backend *ofono_backend, *native_backend;
     PA_LLIST_HEAD(pa_dbus_pending, pending);
     bool enable_native_hfp_hf;
+    bool enable_msbc;
 };
 
 static pa_dbus_pending* send_and_add_to_pending(pa_bluetooth_discovery *y, DBusMessage *m,
@@ -181,6 +185,22 @@ pa_bluetooth_transport *pa_bluetooth_transport_new(pa_bluetooth_device *d, const
     return t;
 }
 
+void pa_bluetooth_transport_reconfigure(pa_bluetooth_transport *t, const pa_bt_codec *bt_codec,
+                                        pa_bluetooth_transport_write_cb write_cb, pa_bluetooth_transport_setsockopt_cb setsockopt_cb) {
+    pa_assert(t);
+
+    t->bt_codec = bt_codec;
+
+    t->write = write_cb;
+    t->setsockopt = setsockopt_cb;
+
+    /* reset stream write type hint */
+    t->stream_write_type = 0;
+
+    /* reset SCO MTU adjustment hint */
+    t->last_read_size = 0;
+}
+
 static const char *transport_state_to_string(pa_bluetooth_transport_state_t state) {
     switch(state) {
         case PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED:
@@ -346,7 +366,7 @@ static void pa_bluetooth_device_switch_codec_reply(DBusPendingCall *pending, voi
 }
 
 bool pa_bluetooth_device_switch_codec(pa_bluetooth_device *device, pa_bluetooth_profile_t profile,
-        pa_hashmap *capabilities_hashmap, const pa_a2dp_codec *a2dp_codec,
+        pa_hashmap *capabilities_hashmap, const pa_a2dp_endpoint_conf *endpoint_conf,
         void (*codec_switch_cb)(bool, pa_bluetooth_profile_t profile, void *), void *userdata) {
     DBusMessageIter iter, dict;
     DBusMessage *m;
@@ -361,7 +381,7 @@ bool pa_bluetooth_device_switch_codec(pa_bluetooth_device *device, pa_bluetooth_
 
     pa_assert(device);
     pa_assert(capabilities_hashmap);
-    pa_assert(a2dp_codec);
+    pa_assert(endpoint_conf);
 
     if (device->codec_switching_in_progress) {
         pa_log_error("Codec switching operation already in progress");
@@ -372,19 +392,19 @@ bool pa_bluetooth_device_switch_codec(pa_bluetooth_device *device, pa_bluetooth_
 
     all_endpoints = NULL;
     all_endpoints = pa_hashmap_get(is_a2dp_sink ? device->a2dp_sink_endpoints : device->a2dp_source_endpoints,
-            &a2dp_codec->id);
+            &endpoint_conf->id);
     pa_assert(all_endpoints);
 
-    pa_assert_se(endpoint = a2dp_codec->choose_remote_endpoint(capabilities_hashmap, &device->discovery->core->default_sample_spec, is_a2dp_sink));
+    pa_assert_se(endpoint = endpoint_conf->choose_remote_endpoint(capabilities_hashmap, &device->discovery->core->default_sample_spec, is_a2dp_sink));
     pa_assert_se(capabilities = pa_hashmap_get(all_endpoints, endpoint));
 
-    config_size = a2dp_codec->fill_preferred_configuration(&device->discovery->core->default_sample_spec,
+    config_size = endpoint_conf->fill_preferred_configuration(&device->discovery->core->default_sample_spec,
             capabilities->buffer, capabilities->size, config);
     if (config_size == 0)
         return false;
 
     pa_endpoint = pa_sprintf_malloc("%s/%s", is_a2dp_sink ? A2DP_SOURCE_ENDPOINT : A2DP_SINK_ENDPOINT,
-            a2dp_codec->name);
+            endpoint_conf->bt_codec.name);
 
     pa_assert_se(m = dbus_message_new_method_call(BLUEZ_SERVICE, endpoint,
                 BLUEZ_MEDIA_ENDPOINT_INTERFACE, "SetConfiguration"));
@@ -582,6 +602,45 @@ static void bluez5_transport_release_cb(pa_bluetooth_transport *t) {
         pa_log_info("Transport %s released", t->path);
 }
 
+static ssize_t a2dp_transport_write(pa_bluetooth_transport *t, int fd, const void* buffer, size_t size, size_t write_mtu) {
+    ssize_t l = 0;
+    size_t written = 0;
+    size_t write_size;
+
+    pa_assert(t);
+
+    while (written < size) {
+        write_size = PA_MIN(size - written, write_mtu);
+        l = pa_write(fd, buffer + written, write_size, &t->stream_write_type);
+        if (l < 0)
+            break;
+        written += l;
+    }
+
+    if (l < 0) {
+        if (errno == EAGAIN) {
+            /* Hmm, apparently the socket was not writable, give up for now */
+            pa_log_debug("Got EAGAIN on write() after POLLOUT, probably there is a temporary connection loss.");
+            /* Drain write buffer */
+            written = size;
+        } else {
+            pa_log_error("Failed to write data to socket: %s", pa_cstrerror(errno));
+            /* Report error from write call */
+            return -1;
+        }
+    }
+
+    /* if too much data left discard it all */
+    if (size - written >= write_mtu) {
+        pa_log_warn("Wrote memory block to socket only partially! %lu written, discarding pending write size %lu larger than write_mtu %lu",
+                    written, size, write_mtu);
+        /* Drain write buffer */
+        written = size;
+    }
+
+    return written;
+}
+
 bool pa_bluetooth_device_any_transport_connected(const pa_bluetooth_device *d) {
     unsigned i;
 
@@ -757,6 +816,14 @@ bool pa_bluetooth_discovery_get_enable_native_hfp_hf(pa_bluetooth_discovery *y)
     return y->enable_native_hfp_hf;
 }
 
+bool pa_bluetooth_discovery_get_enable_msbc(pa_bluetooth_discovery *y)
+{
+    pa_assert(y);
+    pa_assert(PA_REFCNT_VALUE(y) > 0);
+
+    return y->enable_msbc;
+}
+
 pa_bluetooth_device* pa_bluetooth_discovery_get_device_by_address(pa_bluetooth_discovery *y, const char *remote, const char *local) {
     pa_bluetooth_device *d;
     void *state = NULL;
@@ -1111,7 +1178,7 @@ finish:
     pa_xfree(endpoint);
 }
 
-static void register_legacy_sbc_endpoint(pa_bluetooth_discovery *y, const pa_a2dp_codec *a2dp_codec, const char *path, const char *endpoint, const char *uuid) {
+static void register_legacy_sbc_endpoint(pa_bluetooth_discovery *y, const pa_a2dp_endpoint_conf *endpoint_conf, const char *path, const char *endpoint, const char *uuid) {
     DBusMessage *m;
     DBusMessageIter i, d;
     uint8_t capabilities[MAX_A2DP_CAPS_SIZE];
@@ -1120,8 +1187,8 @@ static void register_legacy_sbc_endpoint(pa_bluetooth_discovery *y, const pa_a2d
 
     pa_log_debug("Registering %s on adapter %s", endpoint, path);
 
-    codec_id = a2dp_codec->id.codec_id;
-    capabilities_size = a2dp_codec->fill_capabilities(capabilities);
+    codec_id = endpoint_conf->id.codec_id;
+    capabilities_size = endpoint_conf->fill_capabilities(capabilities);
     pa_assert(capabilities_size != 0);
 
     pa_assert_se(m = dbus_message_new_method_call(BLUEZ_SERVICE, path, BLUEZ_MEDIA_INTERFACE, "RegisterEndpoint"));
@@ -1187,12 +1254,12 @@ finish:
 
     if (fallback) {
         /* If bluez does not support RegisterApplication, fallback to old legacy API with just one SBC codec */
-        const pa_a2dp_codec *a2dp_codec_sbc;
-        a2dp_codec_sbc = pa_bluetooth_get_a2dp_codec("sbc");
-        pa_assert(a2dp_codec_sbc);
-        register_legacy_sbc_endpoint(y, a2dp_codec_sbc, path, A2DP_SINK_ENDPOINT "/sbc",
+        const pa_a2dp_endpoint_conf *endpoint_conf;
+        endpoint_conf = pa_bluetooth_get_a2dp_endpoint_conf("sbc");
+        pa_assert(endpoint_conf);
+        register_legacy_sbc_endpoint(y, endpoint_conf, path, A2DP_SINK_ENDPOINT "/sbc",
                 PA_BLUETOOTH_UUID_A2DP_SINK);
-        register_legacy_sbc_endpoint(y, a2dp_codec_sbc, path, A2DP_SOURCE_ENDPOINT "/sbc",
+        register_legacy_sbc_endpoint(y, endpoint_conf, path, A2DP_SOURCE_ENDPOINT "/sbc",
                 PA_BLUETOOTH_UUID_A2DP_SOURCE);
         pa_log_warn("Only SBC codec is available for A2DP profiles");
     }
@@ -1766,7 +1833,7 @@ bool pa_bluetooth_profile_should_attenuate_volume(pa_bluetooth_profile_t peer_pr
     pa_assert_not_reached();
 }
 
-static const pa_a2dp_codec *a2dp_endpoint_to_a2dp_codec(const char *endpoint) {
+static const pa_a2dp_endpoint_conf *a2dp_sep_to_a2dp_endpoint_conf(const char *endpoint) {
     const char *codec_name;
 
     if (pa_startswith(endpoint, A2DP_SINK_ENDPOINT "/"))
@@ -1776,14 +1843,14 @@ static const pa_a2dp_codec *a2dp_endpoint_to_a2dp_codec(const char *endpoint) {
     else
         return NULL;
 
-    return pa_bluetooth_get_a2dp_codec(codec_name);
+    return pa_bluetooth_get_a2dp_endpoint_conf(codec_name);
 }
 
 static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage *m, void *userdata) {
     pa_bluetooth_discovery *y = userdata;
     pa_bluetooth_device *d;
     pa_bluetooth_transport *t;
-    const pa_a2dp_codec *a2dp_codec = NULL;
+    const pa_a2dp_endpoint_conf *endpoint_conf = NULL;
     const char *sender, *path, *endpoint_path, *dev_path = NULL, *uuid = NULL;
     const uint8_t *config = NULL;
     int size = 0;
@@ -1867,17 +1934,17 @@ static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage
 
             dbus_message_iter_get_fixed_array(&array, &config, &size);
 
-            a2dp_codec = a2dp_endpoint_to_a2dp_codec(endpoint_path);
-            pa_assert(a2dp_codec);
+            endpoint_conf = a2dp_sep_to_a2dp_endpoint_conf(endpoint_path);
+            pa_assert(endpoint_conf);
 
-            if (!a2dp_codec->is_configuration_valid(config, size))
+            if (!endpoint_conf->is_configuration_valid(config, size))
                 goto fail;
         }
 
         dbus_message_iter_next(&props);
     }
 
-    if (!a2dp_codec)
+    if (!endpoint_conf)
         goto fail2;
 
     if ((d = pa_hashmap_get(y->devices, dev_path))) {
@@ -1903,13 +1970,13 @@ static DBusMessage *endpoint_set_configuration(DBusConnection *conn, DBusMessage
     dbus_message_unref(r);
 
     t = pa_bluetooth_transport_new(d, sender, path, p, config, size);
-    t->a2dp_codec = a2dp_codec;
     t->acquire = bluez5_transport_acquire_cb;
     t->release = bluez5_transport_release_cb;
+    pa_bluetooth_transport_reconfigure(t, &endpoint_conf->bt_codec, a2dp_transport_write, NULL);
     pa_bluetooth_transport_put(t);
 
     pa_log_debug("Transport %s available for profile %s", t->path, pa_bluetooth_profile_to_string(t->profile));
-    pa_log_info("Selected codec: %s", a2dp_codec->name);
+    pa_log_info("Selected codec: %s", endpoint_conf->bt_codec.name);
 
     return NULL;
 
@@ -1926,7 +1993,7 @@ static DBusMessage *endpoint_select_configuration(DBusConnection *conn, DBusMess
     const char *endpoint_path;
     uint8_t *cap;
     int size;
-    const pa_a2dp_codec *a2dp_codec;
+    const pa_a2dp_endpoint_conf *endpoint_conf;
     uint8_t config[MAX_A2DP_CAPS_SIZE];
     uint8_t *config_ptr = config;
     size_t config_size;
@@ -1943,10 +2010,10 @@ static DBusMessage *endpoint_select_configuration(DBusConnection *conn, DBusMess
         goto fail;
     }
 
-    a2dp_codec = a2dp_endpoint_to_a2dp_codec(endpoint_path);
-    pa_assert(a2dp_codec);
+    endpoint_conf = a2dp_sep_to_a2dp_endpoint_conf(endpoint_path);
+    pa_assert(endpoint_conf);
 
-    config_size = a2dp_codec->fill_preferred_configuration(&y->core->default_sample_spec, cap, size, config);
+    config_size = endpoint_conf->fill_preferred_configuration(&y->core->default_sample_spec, cap, size, config);
     if (config_size == 0)
         goto fail;
 
@@ -2028,7 +2095,7 @@ static DBusHandlerResult endpoint_handler(DBusConnection *c, DBusMessage *m, voi
 
     pa_log_debug("dbus: path=%s, interface=%s, member=%s", path, interface, member);
 
-    if (!a2dp_endpoint_to_a2dp_codec(path))
+    if (!a2dp_sep_to_a2dp_endpoint_conf(path))
         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
 
     if (dbus_message_is_method_call(m, DBUS_INTERFACE_INTROSPECTABLE, "Introspect")) {
@@ -2154,30 +2221,30 @@ static DBusHandlerResult object_manager_handler(DBusConnection *c, DBusMessage *
                                          DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
                                          &array);
 
-        for (i = 0; i < pa_bluetooth_a2dp_codec_count(); i++) {
-            const pa_a2dp_codec *a2dp_codec;
+        for (i = 0; i < pa_bluetooth_a2dp_endpoint_conf_count(); i++) {
+            const pa_a2dp_endpoint_conf *endpoint_conf;
             uint8_t capabilities[MAX_A2DP_CAPS_SIZE];
             uint8_t capabilities_size;
             uint8_t codec_id;
             char *endpoint;
 
-            a2dp_codec = pa_bluetooth_a2dp_codec_iter(i);
+            endpoint_conf = pa_bluetooth_a2dp_endpoint_conf_iter(i);
 
-            codec_id = a2dp_codec->id.codec_id;
+            codec_id = endpoint_conf->id.codec_id;
 
-            if (a2dp_codec->can_be_supported(false)) {
-                capabilities_size = a2dp_codec->fill_capabilities(capabilities);
+            if (endpoint_conf->can_be_supported(false)) {
+                capabilities_size = endpoint_conf->fill_capabilities(capabilities);
                 pa_assert(capabilities_size != 0);
-                endpoint = pa_sprintf_malloc("%s/%s", A2DP_SINK_ENDPOINT, a2dp_codec->name);
+                endpoint = pa_sprintf_malloc("%s/%s", A2DP_SINK_ENDPOINT, endpoint_conf->bt_codec.name);
                 append_a2dp_object(&array, endpoint, PA_BLUETOOTH_UUID_A2DP_SINK, codec_id,
                         capabilities, capabilities_size);
                 pa_xfree(endpoint);
             }
 
-            if (a2dp_codec->can_be_supported(true)) {
-                capabilities_size = a2dp_codec->fill_capabilities(capabilities);
+            if (endpoint_conf->can_be_supported(true)) {
+                capabilities_size = endpoint_conf->fill_capabilities(capabilities);
                 pa_assert(capabilities_size != 0);
-                endpoint = pa_sprintf_malloc("%s/%s", A2DP_SOURCE_ENDPOINT, a2dp_codec->name);
+                endpoint = pa_sprintf_malloc("%s/%s", A2DP_SOURCE_ENDPOINT, endpoint_conf->bt_codec.name);
                 append_a2dp_object(&array, endpoint, PA_BLUETOOTH_UUID_A2DP_SOURCE, codec_id,
                         capabilities, capabilities_size);
                 pa_xfree(endpoint);
@@ -2210,12 +2277,12 @@ static void object_manager_done(pa_bluetooth_discovery *y) {
             A2DP_OBJECT_MANAGER_PATH);
 }
 
-pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c, int headset_backend, bool enable_native_hfp_hf) {
+pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c, int headset_backend, bool enable_native_hfp_hf, bool enable_msbc) {
     pa_bluetooth_discovery *y;
     DBusError err;
     DBusConnection *conn;
     unsigned i, count;
-    const pa_a2dp_codec *a2dp_codec;
+    const pa_a2dp_endpoint_conf *endpoint_conf;
     char *endpoint;
 
     pa_bluetooth_a2dp_codec_gst_init();
@@ -2224,6 +2291,7 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c, int headset_backe
     y->core = c;
     y->headset_backend = headset_backend;
     y->enable_native_hfp_hf = enable_native_hfp_hf;
+    y->enable_msbc = enable_msbc;
     y->adapters = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL,
                                       (pa_free_cb_t) adapter_free);
     y->devices = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL,
@@ -2274,17 +2342,17 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *c, int headset_backe
 
     object_manager_init(y);
 
-    count = pa_bluetooth_a2dp_codec_count();
+    count = pa_bluetooth_a2dp_endpoint_conf_count();
     for (i = 0; i < count; i++) {
-        a2dp_codec = pa_bluetooth_a2dp_codec_iter(i);
-        if (a2dp_codec->can_be_supported(false)) {
-            endpoint = pa_sprintf_malloc("%s/%s", A2DP_SINK_ENDPOINT, a2dp_codec->name);
+        endpoint_conf = pa_bluetooth_a2dp_endpoint_conf_iter(i);
+        if (endpoint_conf->can_be_supported(false)) {
+            endpoint = pa_sprintf_malloc("%s/%s", A2DP_SINK_ENDPOINT, endpoint_conf->bt_codec.name);
             endpoint_init(y, endpoint);
             pa_xfree(endpoint);
         }
 
-        if (a2dp_codec->can_be_supported(true)) {
-            endpoint = pa_sprintf_malloc("%s/%s", A2DP_SOURCE_ENDPOINT, a2dp_codec->name);
+        if (endpoint_conf->can_be_supported(true)) {
+            endpoint = pa_sprintf_malloc("%s/%s", A2DP_SOURCE_ENDPOINT, endpoint_conf->bt_codec.name);
             endpoint_init(y, endpoint);
             pa_xfree(endpoint);
         }
@@ -2312,7 +2380,7 @@ pa_bluetooth_discovery* pa_bluetooth_discovery_ref(pa_bluetooth_discovery *y) {
 
 void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) {
     unsigned i, count;
-    const pa_a2dp_codec *a2dp_codec;
+    const pa_a2dp_endpoint_conf *endpoint_conf;
     char *endpoint;
 
     pa_assert(y);
@@ -2364,18 +2432,18 @@ void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) {
 
         object_manager_done(y);
 
-        count = pa_bluetooth_a2dp_codec_count();
+        count = pa_bluetooth_a2dp_endpoint_conf_count();
         for (i = 0; i < count; i++) {
-            a2dp_codec = pa_bluetooth_a2dp_codec_iter(i);
+            endpoint_conf = pa_bluetooth_a2dp_endpoint_conf_iter(i);
 
-            if (a2dp_codec->can_be_supported(false)) {
-                endpoint = pa_sprintf_malloc("%s/%s", A2DP_SINK_ENDPOINT, a2dp_codec->name);
+            if (endpoint_conf->can_be_supported(false)) {
+                endpoint = pa_sprintf_malloc("%s/%s", A2DP_SINK_ENDPOINT, endpoint_conf->bt_codec.name);
                 endpoint_done(y, endpoint);
                 pa_xfree(endpoint);
             }
 
-            if (a2dp_codec->can_be_supported(true)) {
-                endpoint = pa_sprintf_malloc("%s/%s", A2DP_SOURCE_ENDPOINT, a2dp_codec->name);
+            if (endpoint_conf->can_be_supported(true)) {
+                endpoint = pa_sprintf_malloc("%s/%s", A2DP_SOURCE_ENDPOINT, endpoint_conf->bt_codec.name);
                 endpoint_done(y, endpoint);
                 pa_xfree(endpoint);
             }


=====================================
src/modules/bluetooth/bluez5-util.h
=====================================
@@ -88,6 +88,8 @@ typedef int (*pa_bluetooth_transport_acquire_cb)(pa_bluetooth_transport *t, bool
 typedef void (*pa_bluetooth_transport_release_cb)(pa_bluetooth_transport *t);
 typedef void (*pa_bluetooth_transport_destroy_cb)(pa_bluetooth_transport *t);
 typedef pa_volume_t (*pa_bluetooth_transport_set_volume_cb)(pa_bluetooth_transport *t, pa_volume_t volume);
+typedef ssize_t (*pa_bluetooth_transport_write_cb)(pa_bluetooth_transport *t, int fd, const void* buffer, size_t size, size_t write_mtu);
+typedef int (*pa_bluetooth_transport_setsockopt_cb)(pa_bluetooth_transport *t, int fd);
 
 struct pa_bluetooth_transport {
     pa_bluetooth_device *device;
@@ -96,11 +98,12 @@ struct pa_bluetooth_transport {
     char *path;
     pa_bluetooth_profile_t profile;
 
-    uint8_t codec;
     void *config;
     size_t config_size;
 
-    const pa_a2dp_codec *a2dp_codec;
+    const pa_bt_codec *bt_codec;
+    int stream_write_type;
+    size_t last_read_size;
 
     pa_volume_t source_volume;
     pa_volume_t sink_volume;
@@ -109,6 +112,8 @@ struct pa_bluetooth_transport {
 
     pa_bluetooth_transport_acquire_cb acquire;
     pa_bluetooth_transport_release_cb release;
+    pa_bluetooth_transport_write_cb write;
+    pa_bluetooth_transport_setsockopt_cb setsockopt;
     pa_bluetooth_transport_destroy_cb destroy;
     pa_bluetooth_transport_set_volume_cb set_sink_volume;
     pa_bluetooth_transport_set_volume_cb set_source_volume;
@@ -177,13 +182,16 @@ static inline void pa_bluetooth_native_backend_enable_shared_profiles(pa_bluetoo
 pa_bluetooth_transport *pa_bluetooth_transport_new(pa_bluetooth_device *d, const char *owner, const char *path,
                                                    pa_bluetooth_profile_t p, const uint8_t *config, size_t size);
 
+void pa_bluetooth_transport_reconfigure(pa_bluetooth_transport *t, const pa_bt_codec *bt_codec,
+                                        pa_bluetooth_transport_write_cb write_cb, pa_bluetooth_transport_setsockopt_cb setsockopt_cb);
+
 void pa_bluetooth_transport_set_state(pa_bluetooth_transport *t, pa_bluetooth_transport_state_t state);
 void pa_bluetooth_transport_put(pa_bluetooth_transport *t);
 void pa_bluetooth_transport_unlink(pa_bluetooth_transport *t);
 void pa_bluetooth_transport_free(pa_bluetooth_transport *t);
 
 bool pa_bluetooth_device_any_transport_connected(const pa_bluetooth_device *d);
-bool pa_bluetooth_device_switch_codec(pa_bluetooth_device *device, pa_bluetooth_profile_t profile, pa_hashmap *capabilities_hashmap, const pa_a2dp_codec *a2dp_codec, void (*codec_switch_cb)(bool, pa_bluetooth_profile_t profile, void *), void *userdata);
+bool pa_bluetooth_device_switch_codec(pa_bluetooth_device *device, pa_bluetooth_profile_t profile, pa_hashmap *capabilities_hashmap, const pa_a2dp_endpoint_conf *endpoint_conf, void (*codec_switch_cb)(bool, pa_bluetooth_profile_t profile, void *), void *userdata);
 
 pa_bluetooth_device* pa_bluetooth_discovery_get_device_by_path(pa_bluetooth_discovery *y, const char *path);
 pa_bluetooth_device* pa_bluetooth_discovery_get_device_by_address(pa_bluetooth_discovery *y, const char *remote, const char *local);
@@ -201,9 +209,10 @@ static inline bool pa_bluetooth_uuid_is_hsp_hs(const char *uuid) {
 #define HEADSET_BACKEND_NATIVE 1
 #define HEADSET_BACKEND_AUTO 2
 
-pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *core, int headset_backend, bool default_profile_hfp);
+pa_bluetooth_discovery* pa_bluetooth_discovery_get(pa_core *core, int headset_backend, bool default_profile_hfp, bool enable_msbc);
 pa_bluetooth_discovery* pa_bluetooth_discovery_ref(pa_bluetooth_discovery *y);
 void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y);
 void pa_bluetooth_discovery_set_ofono_running(pa_bluetooth_discovery *y, bool is_running);
 bool pa_bluetooth_discovery_get_enable_native_hfp_hf(pa_bluetooth_discovery *y);
+bool pa_bluetooth_discovery_get_enable_msbc(pa_bluetooth_discovery *y);
 #endif


=====================================
src/modules/bluetooth/bt-codec-api.h
=====================================
@@ -0,0 +1,67 @@
+#pragma once
+
+/***
+  This file is part of PulseAudio.
+
+  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/>.
+***/
+
+#include <pulsecore/core.h>
+
+typedef struct pa_bt_codec {
+    /* Unique name of the codec, lowercase and without whitespaces, used for
+     * constructing identifier, D-Bus paths, ... */
+    const char *name;
+    /* Human readable codec description */
+    const char *description;
+
+    /* Initialize codec, returns codec info data and set sample_spec,
+     * for_encoding is true when codec_info is used for encoding,
+     * for_backchannel is true when codec_info is used for backchannel */
+    void *(*init)(bool for_encoding, bool for_backchannel, const uint8_t *config_buffer, uint8_t config_size, pa_sample_spec *sample_spec, pa_core *core);
+    /* Deinitialize and release codec info data in codec_info */
+    void (*deinit)(void *codec_info);
+    /* Reset internal state of codec info data in codec_info, returns
+     * a negative value on failure */
+    int (*reset)(void *codec_info);
+
+    /* Get read block size for codec, it is minimal size of buffer
+     * needed to decode read_link_mtu bytes of encoded data */
+    size_t (*get_read_block_size)(void *codec_info, size_t read_link_mtu);
+    /* Get write block size for codec, it is maximal size of buffer
+     * which can produce at most write_link_mtu bytes of encoded data */
+    size_t (*get_write_block_size)(void *codec_info, size_t write_link_mtu);
+    /* Get encoded block size for codec to hold one encoded frame.
+     * Note HFP mSBC codec encoded block may not fit into one MTU and is sent out in chunks. */
+    size_t (*get_encoded_block_size)(void *codec_info, size_t input_size);
+
+    /* Reduce encoder bitrate for codec, returns new write block size or zero
+     * if not changed, called when socket is not accepting encoded data fast
+     * enough */
+    size_t (*reduce_encoder_bitrate)(void *codec_info, size_t write_link_mtu);
+
+    /* Increase encoder bitrate for codec, returns new write block size or zero
+     * if not changed, called periodically when socket is keeping up with
+     * encoded data */
+    size_t (*increase_encoder_bitrate)(void *codec_info, size_t write_link_mtu);
+
+    /* Encode input_buffer of input_size to output_buffer of output_size,
+     * returns size of filled ouput_buffer and set processed to size of
+     * processed input_buffer */
+    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);
+    /* Decode input_buffer of input_size to output_buffer of output_size,
+     * returns size of filled ouput_buffer and set processed to size of
+     * processed input_buffer */
+    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);
+} pa_bt_codec;


=====================================
src/modules/bluetooth/bt-codec-cvsd.c
=====================================
@@ -0,0 +1,123 @@
+/***
+  This file is part of PulseAudio.
+
+  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.h>
+#include "bt-codec-api.h"
+
+typedef struct codec_info {
+    pa_sample_spec sample_spec;
+} codec_info_t;
+
+static void *init(bool for_encoding, bool for_backchannel, const uint8_t *config_buffer, uint8_t config_size, pa_sample_spec *sample_spec, pa_core *core) {
+    codec_info_t *info;
+
+    info = pa_xnew0(codec_info_t, 1);
+
+    info->sample_spec.format = PA_SAMPLE_S16LE;
+    info->sample_spec.channels = 1;
+    info->sample_spec.rate = 8000;
+
+    *sample_spec = info->sample_spec;
+
+    return info;
+}
+
+static void deinit(void *codec_info) {
+    pa_xfree(codec_info);
+}
+
+static int reset(void *codec_info) {
+    return 0;
+}
+
+static size_t get_block_size(void *codec_info, size_t link_mtu) {
+    codec_info_t *info = (codec_info_t *) codec_info;
+    size_t block_size = link_mtu;
+
+    if (!pa_frame_aligned(block_size, &info->sample_spec)) {
+        pa_log_debug("Got invalid block size: %lu, rounding down", block_size);
+        block_size = pa_frame_align(block_size, &info->sample_spec);
+    }
+
+    return block_size;
+}
+
+static size_t get_encoded_block_size(void *codec_info, size_t input_size) {
+    codec_info_t *info = (codec_info_t *) codec_info;
+
+    /* input size should be aligned to sample spec */
+    pa_assert_fp(pa_frame_aligned(input_size, &info->sample_spec));
+
+    return input_size;
+}
+
+static size_t reduce_encoder_bitrate(void *codec_info, size_t write_link_mtu) {
+    return 0;
+}
+
+static size_t increase_encoder_bitrate(void *codec_info, size_t write_link_mtu) {
+    return 0;
+}
+
+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) {
+    pa_assert(input_size <= output_size);
+
+    memcpy(output_buffer, input_buffer, input_size);
+    *processed = input_size;
+
+    return input_size;
+}
+
+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) {
+    codec_info_t *info = (codec_info_t *) codec_info;
+
+    *processed = input_size;
+
+    /* In some rare occasions, we might receive packets of a very strange
+     * size. This could potentially be possible if the SCO packet was
+     * received partially over-the-air, or more probably due to hardware
+     * issues in our Bluetooth adapter. In these cases, in order to avoid
+     * an assertion failure due to unaligned data, just discard the whole
+     * packet */
+    if (!pa_frame_aligned(input_size, &info->sample_spec)) {
+        pa_log_warn("SCO packet received of unaligned size: %zu", input_size);
+        return 0;
+    }
+
+    memcpy(output_buffer, input_buffer, input_size);
+
+    return input_size;
+}
+
+/* dummy passthrough codec used with HSP/HFP CVSD */
+const pa_bt_codec pa_bt_codec_cvsd = {
+    .name = "CVSD",
+    .description = "CVSD",
+    .init = init,
+    .deinit = deinit,
+    .reset = reset,
+    .get_read_block_size = get_block_size,
+    .get_write_block_size = get_block_size,
+    .get_encoded_block_size = get_encoded_block_size,
+    .reduce_encoder_bitrate = reduce_encoder_bitrate,
+    .increase_encoder_bitrate = increase_encoder_bitrate,
+    .encode_buffer = encode_buffer,
+    .decode_buffer = decode_buffer,
+};


=====================================
src/modules/bluetooth/bt-codec-msbc.c
=====================================
@@ -0,0 +1,317 @@
+/***
+  This file is part of PulseAudio.
+
+  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.h>
+#include "bt-codec-api.h"
+
+#include "bt-codec-msbc.h"
+#include <sbc/sbc.h>
+
+typedef struct sbc_info {
+    sbc_t sbc;                           /* Codec data */
+    size_t codesize, frame_length;       /* SBC Codesize, frame_length. We simply cache those values here */
+    uint8_t msbc_seq:2;                  /* mSBC packet sequence number, 2 bits only */
+
+    uint16_t msbc_push_offset;
+    uint8_t input_buffer[MSBC_PACKET_SIZE];                        /* Codec transfer buffer */
+
+    pa_sample_spec sample_spec;
+} sbc_info_t;
+
+static void *init(bool for_encoding, bool for_backchannel, const uint8_t *config_buffer, uint8_t config_size, pa_sample_spec *sample_spec, pa_core *core) {
+    struct sbc_info *info;
+    int ret;
+
+    info = pa_xnew0(struct sbc_info, 1);
+
+    ret = sbc_init_msbc(&info->sbc, 0);
+    if (ret != 0) {
+        pa_xfree(info);
+        pa_log_error("mSBC initialization failed: %d", ret);
+        return NULL;
+    }
+
+    info->sbc.endian = SBC_LE;
+
+    info->codesize = sbc_get_codesize(&info->sbc);
+    info->frame_length = sbc_get_frame_length(&info->sbc);
+    pa_log_info("mSBC codesize=%d, frame_length=%d",
+                (int)info->codesize,
+                (int)info->frame_length);
+
+    info->sample_spec.format = PA_SAMPLE_S16LE;
+    info->sample_spec.channels = 1;
+    info->sample_spec.rate = 16000;
+
+    pa_assert(pa_frame_aligned(info->codesize, &info->sample_spec));
+
+    *sample_spec = info->sample_spec;
+
+    return 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 int reset(void *codec_info) {
+    struct sbc_info *sbc_info = (struct sbc_info *) codec_info;
+    int ret;
+
+    /* SBC library release 1.5 has a bug in sbc_reinit_msbc:
+     * it forgets to restore priv->msbc flag after clearing priv content.
+     * This causes decoder assertion on first call since codesize would be
+     * different from expected for mSBC configuration.
+     *
+     * Do not use sbc_reinit_msbc until it is fixed.
+     */
+
+    sbc_finish(&sbc_info->sbc);
+    ret = sbc_init_msbc(&sbc_info->sbc, 0);
+    if (ret != 0) {
+        pa_xfree(sbc_info);
+        pa_log_error("mSBC initialization failed: %d", ret);
+        return -1;
+    }
+
+    sbc_info->sbc.endian = SBC_LE;
+
+    sbc_info->msbc_seq = 0;
+    sbc_info->msbc_push_offset = 0;
+
+    return 0;
+}
+
+static size_t get_read_block_size(void *codec_info, size_t link_mtu) {
+    struct sbc_info *info = (struct sbc_info *) codec_info;
+    size_t block_size = info->codesize;
+
+    /* this never happens as sbc_info->codesize is always frame-aligned */
+    if (!pa_frame_aligned(block_size, &info->sample_spec)) {
+        pa_log_debug("Got invalid block size: %lu, rounding down", block_size);
+        block_size = pa_frame_align(block_size, &info->sample_spec);
+    }
+
+    return block_size;
+}
+
+static size_t get_write_block_size(void *codec_info, size_t link_mtu) {
+    struct sbc_info *info = (struct sbc_info *) codec_info;
+    return info->codesize;
+}
+
+static size_t get_encoded_block_size(void *codec_info, size_t input_size) {
+    struct sbc_info *info = (struct sbc_info *) codec_info;
+    size_t encoded_size = MSBC_PACKET_SIZE;
+
+    /* input size should be aligned to write block size */
+    pa_assert_fp(input_size % info->codesize == 0);
+
+    return encoded_size * (input_size / info->codesize);
+}
+
+static size_t reduce_encoder_bitrate(void *codec_info, size_t write_link_mtu) {
+    return 0;
+}
+
+static size_t increase_encoder_bitrate(void *codec_info, size_t write_link_mtu) {
+    return 0;
+}
+
+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 msbc_frame *frame;
+    uint8_t seq;
+    ssize_t encoded;
+    ssize_t written;
+
+    pa_assert(input_size == sbc_info->codesize);
+
+    /* must be room to render packet */
+    pa_assert(output_size >= MSBC_PACKET_SIZE);
+
+    frame = (struct msbc_frame *)output_buffer;
+    seq = sbc_info->msbc_seq++;
+    frame->hdr.id0 = MSBC_H2_ID0;
+    frame->hdr.id1.s.id1 = MSBC_H2_ID1;
+    if (seq & 0x02)
+        frame->hdr.id1.s.sn1 = 3;
+    else
+        frame->hdr.id1.s.sn1 = 0;
+    if (seq & 0x01)
+        frame->hdr.id1.s.sn0 = 3;
+    else
+        frame->hdr.id1.s.sn0 = 0;
+
+    encoded = sbc_encode(&sbc_info->sbc,
+                         input_buffer, input_size,
+                         frame->payload, MSBC_FRAME_SIZE,
+                         &written);
+
+    frame->padding = 0x00;
+
+    if (PA_UNLIKELY(encoded <= 0)) {
+        pa_log_error("SBC encoding error (%li) for input size %lu, SBC codesize %lu",
+                    (long) encoded, input_size, sbc_get_codesize(&sbc_info->sbc));
+
+        if (encoded < 0) {
+            *processed = 0;
+            return -1;
+        } else {
+            *processed = input_size;
+            return 0;
+        }
+    }
+
+    pa_assert_fp((size_t) encoded == sbc_info->codesize);
+    pa_assert_fp((size_t) written == sbc_info->frame_length);
+
+    *processed = encoded;
+
+    return MSBC_PACKET_SIZE;
+}
+
+static inline bool is_all_zero(const uint8_t *ptr, size_t len) {
+    size_t i;
+
+    for (i = 0; i < len; ++i)
+        if (ptr[i] != 0)
+            return false;
+
+    return true;
+}
+
+/*
+ * We build a msbc frame up in the sbc_info buffer until we have a whole one
+ */
+static struct msbc_frame *msbc_find_frame(struct sbc_info *si, ssize_t *len,
+                                          const uint8_t *buf, int *pseq)
+{
+    int i;
+    uint8_t *p = si->input_buffer;
+
+    /* skip input if it has all zero bytes
+     * this could happen with older kernels inserting all-zero blocks
+     * inside otherwise valid mSBC stream */
+    if (*len > 0 && is_all_zero(buf, *len))
+        *len = 0;
+
+    for (i = 0; i < *len; i++) {
+        union msbc_h2_id1 id1;
+
+        if (si->msbc_push_offset == 0) {
+            if (buf[i] != MSBC_H2_ID0)
+                continue;
+        } else if (si->msbc_push_offset == 1) {
+            id1.b = buf[i];
+
+            if (id1.s.id1 != MSBC_H2_ID1)
+                goto error;
+            if (id1.s.sn0 != 3 && id1.s.sn0 != 0)
+                goto error;
+            if (id1.s.sn1 != 3 && id1.s.sn1 != 0)
+                goto error;
+        } else if (si->msbc_push_offset == 2) {
+            if (buf[i] != MSBC_SYNC_BYTE)
+                goto error;
+        }
+        p[si->msbc_push_offset++] = buf[i];
+
+        if (si->msbc_push_offset == MSBC_PACKET_SIZE) {
+            id1.b = p[1];
+            *pseq = (id1.s.sn0 & 0x1) | (id1.s.sn1 & 0x2);
+            si->msbc_push_offset = 0;
+            *len = *len - i;
+            return (struct msbc_frame *)p;
+        }
+        continue;
+
+        error:
+        si->msbc_push_offset = 0;
+    }
+    *len = 0;
+    return NULL;
+}
+
+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;
+    ssize_t remaining;
+    ssize_t decoded;
+    size_t written = 0;
+    struct msbc_frame *frame;
+    int seq;
+
+    remaining = input_size;
+    frame = msbc_find_frame(sbc_info, &remaining, input_buffer, &seq);
+
+    /* only process when we have a full frame */
+    if (!frame) {
+        *processed = input_size - remaining;
+        return 0;
+    }
+
+    uint8_t lost_packets = (4 + seq - sbc_info->msbc_seq++) % 4;
+
+    if (lost_packets) {
+        pa_log_debug("Lost %d input audio packet(s)", lost_packets);
+        sbc_info->msbc_seq = seq + 1;
+    }
+
+    decoded = sbc_decode(&sbc_info->sbc, frame->payload, MSBC_FRAME_SIZE, output_buffer, output_size, &written);
+
+    /* now we've consumed the sbc_info buffer, start a new one with
+     * the partial frame we have */
+    if (remaining > 0)
+        msbc_find_frame(sbc_info, &remaining, input_buffer + input_size - remaining, &seq);
+
+    pa_assert_fp(remaining == 0);
+
+    if (PA_UNLIKELY(decoded <= 0)) {
+        pa_log_error("mSBC decoding error (%li)", (long) decoded);
+        pa_silence_memory(output_buffer, sbc_info->codesize, &sbc_info->sample_spec);
+        decoded = sbc_info->frame_length;
+        written = sbc_info->codesize;
+    }
+
+    pa_assert_fp((size_t)decoded == sbc_info->frame_length);
+    pa_assert_fp((size_t)written == sbc_info->codesize);
+
+    *processed = input_size - remaining;
+    return written;
+}
+
+/* Modified SBC codec for HFP Wideband Speech*/
+const pa_bt_codec pa_bt_codec_msbc = {
+    .name = "mSBC",
+    .description = "mSBC",
+    .init = init,
+    .deinit = deinit,
+    .reset = reset,
+    .get_read_block_size = get_read_block_size,
+    .get_write_block_size = get_write_block_size,
+    .get_encoded_block_size = get_encoded_block_size,
+    .reduce_encoder_bitrate = reduce_encoder_bitrate,
+    .increase_encoder_bitrate = increase_encoder_bitrate,
+    .encode_buffer = encode_buffer,
+    .decode_buffer = decode_buffer,
+};


=====================================
src/modules/bluetooth/bt-codec-msbc.h
=====================================
@@ -0,0 +1,35 @@
+#pragma once
+
+/*
+ * Parameters for use with mSBC over eSCO link
+ */
+
+#define MSBC_H2_ID0	0x01
+#define MSBC_H2_ID1	0x08
+#define MSBC_FRAME_SIZE	57
+
+#define MSBC_SYNC_BYTE	0xad
+
+struct msbc_h2_id1_s {
+    uint8_t id1:4;
+    uint8_t sn0:2;
+    uint8_t sn1:2;
+} __attribute__ ((packed));
+
+union msbc_h2_id1 {
+    struct msbc_h2_id1_s s;
+    uint8_t b;
+};
+
+struct msbc_h2_header {
+    uint8_t id0;
+    union msbc_h2_id1 id1;
+} __attribute__ ((packed));
+
+struct msbc_frame {
+    struct msbc_h2_header hdr;
+    uint8_t payload[MSBC_FRAME_SIZE];
+    uint8_t padding;		/* must be zero */
+} __attribute__ ((packed));
+
+#define MSBC_PACKET_SIZE	sizeof(struct msbc_frame)


=====================================
src/modules/bluetooth/meson.build
=====================================
@@ -2,6 +2,8 @@ libbluez5_util_sources = [
   'a2dp-codec-sbc.c',
   'a2dp-codec-util.c',
   'bluez5-util.c',
+  'bt-codec-cvsd.c',
+  'bt-codec-msbc.c',
 ]
 
 libbluez5_util_headers = [


=====================================
src/modules/bluetooth/module-bluez5-device.c
=====================================
@@ -128,7 +128,6 @@ struct userdata {
     bluetooth_msg *msg;
 
     int stream_fd;
-    int stream_write_type;
     size_t read_link_mtu;
     size_t write_link_mtu;
     size_t read_block_size;
@@ -139,12 +138,13 @@ struct userdata {
     pa_smoother *read_smoother;
     pa_memchunk write_memchunk;
 
-    const pa_a2dp_codec *a2dp_codec;
+    const pa_bt_codec *bt_codec;
 
     void *encoder_info;
     pa_sample_spec encoder_sample_spec;
     void *encoder_buffer;                        /* Codec transfer buffer */
     size_t encoder_buffer_size;                  /* Size of the buffer */
+    size_t encoder_buffer_used;                  /* Used space in the buffer */
 
     void *decoder_info;
     pa_sample_spec decoder_sample_spec;
@@ -255,265 +255,88 @@ static void connect_ports(struct userdata *u, void *new_data, pa_direction_t dir
     }
 }
 
-/* Run from IO thread */
-static int sco_process_render(struct userdata *u) {
-    ssize_t l;
-    pa_memchunk memchunk;
-    int saved_errno;
-
+static bool bt_prepare_encoder_buffer(struct userdata *u)
+{
+    size_t encoded_size, reserved_size;
     pa_assert(u);
-    pa_assert(u->profile == PA_BLUETOOTH_PROFILE_HSP_HS ||
-              u->profile == PA_BLUETOOTH_PROFILE_HSP_AG ||
-              u->profile == PA_BLUETOOTH_PROFILE_HFP_HF ||
-              u->profile == PA_BLUETOOTH_PROFILE_HFP_AG);
-    pa_assert(u->sink);
-
-    pa_sink_render_full(u->sink, u->write_block_size, &memchunk);
-
-    pa_assert(memchunk.length == u->write_block_size);
-
-    for (;;) {
-        const void *p;
-
-        /* Now write that data to the socket. The socket is of type
-         * SEQPACKET, and we generated the data of the MTU size, so this
-         * should just work. */
-
-        p = (const uint8_t *) pa_memblock_acquire_chunk(&memchunk);
-        l = pa_write(u->stream_fd, p, memchunk.length, &u->stream_write_type);
-        pa_memblock_release(memchunk.memblock);
-
-        pa_assert(l != 0);
-
-        if (l > 0)
-            break;
+    pa_assert(u->bt_codec);
+
+    /* If socket write MTU is less than encoded frame size, there could be
+     * up to one write MTU of data left in encoder buffer from previous round.
+     *
+     * Reserve space for 2 encoded frames to cover that.
+     *
+     * Note for A2DP codecs it is expected that size of encoded frame is less
+     * than write link MTU. Therefore each encoded frame is sent out completely
+     * and there is no used space in encoder buffer before next encoder call.
+     */
+    if (u->bt_codec->get_encoded_block_size)
+        encoded_size = u->bt_codec->get_encoded_block_size(u->encoder_info, u->write_block_size);
+    else
+        encoded_size = u->write_block_size;
 
-        saved_errno = errno;
+    reserved_size = 2 * encoded_size;
 
-        pa_memblock_unref(memchunk.memblock);
+    if (u->encoder_buffer_size < reserved_size) {
+        u->encoder_buffer = pa_xrealloc(u->encoder_buffer, reserved_size);
+        u->encoder_buffer_size = reserved_size;
 
-        if (saved_errno == EAGAIN) {
-            /* Hmm, apparently the socket was not writable, give up for now.
-             * Because the data was already rendered, let's discard the block. */
-            pa_log_debug("Got EAGAIN on write() after POLLOUT, probably there is a temporary connection loss.");
-            return 1;
+        if (u->encoder_buffer_used > reserved_size) {
+            u->encoder_buffer_used = 0;
         }
-
-        pa_log_error("Failed to write data to SCO socket: %s", pa_cstrerror(saved_errno));
-        return -1;
-    }
-
-    pa_assert((size_t) l <= memchunk.length);
-
-    if ((size_t) l != memchunk.length) {
-        pa_log_error("Wrote memory block to socket only partially! %llu written, wanted to write %llu.",
-                    (unsigned long long) l,
-                    (unsigned long long) memchunk.length);
-
-        pa_memblock_unref(memchunk.memblock);
-        return -1;
     }
 
-    u->write_index += (uint64_t) memchunk.length;
-    pa_memblock_unref(memchunk.memblock);
+    /* Report if there is still not enough space for new block */
+    if (u->encoder_buffer_size < u->encoder_buffer_used + encoded_size)
+        return false;
 
-    return 1;
+    return true;
 }
 
 /* Run from IO thread */
-static int sco_process_push(struct userdata *u) {
-    ssize_t l;
-    pa_memchunk memchunk;
-    struct cmsghdr *cm;
-    struct msghdr m;
-    bool found_tstamp = false;
-    pa_usec_t tstamp = 0;
+static int bt_write_buffer(struct userdata *u) {
+    ssize_t written = 0;
 
     pa_assert(u);
-    pa_assert(u->profile == PA_BLUETOOTH_PROFILE_HSP_HS ||
-              u->profile == PA_BLUETOOTH_PROFILE_HSP_AG ||
-              u->profile == PA_BLUETOOTH_PROFILE_HFP_HF ||
-              u->profile == PA_BLUETOOTH_PROFILE_HFP_AG);
-    pa_assert(u->source);
-    pa_assert(u->read_smoother);
-
-    memchunk.memblock = pa_memblock_new(u->core->mempool, u->read_block_size);
-    memchunk.index = memchunk.length = 0;
-
-    for (;;) {
-        void *p;
-        uint8_t aux[1024];
-        struct iovec iov;
-
-        pa_zero(m);
-        pa_zero(aux);
-        pa_zero(iov);
-
-        m.msg_iov = &iov;
-        m.msg_iovlen = 1;
-        m.msg_control = aux;
-        m.msg_controllen = sizeof(aux);
-
-        p = pa_memblock_acquire(memchunk.memblock);
-        iov.iov_base = p;
-        iov.iov_len = pa_memblock_get_length(memchunk.memblock);
-        l = recvmsg(u->stream_fd, &m, 0);
-        pa_memblock_release(memchunk.memblock);
-
-        if (l > 0)
-            break;
-
-        if (l < 0 && errno == EINTR)
-            /* Retry right away if we got interrupted */
-            continue;
+    pa_assert(u->transport);
+    pa_assert(u->bt_codec);
 
-        pa_memblock_unref(memchunk.memblock);
+    written = u->transport->write(u->transport, u->stream_fd, u->encoder_buffer, u->encoder_buffer_used, u->write_link_mtu);
 
-        if (l < 0 && errno == EAGAIN)
-            /* Hmm, apparently the socket was not readable, give up for now. */
-            return 0;
+    if (written > 0) {
+        /* calculate remainder */
+        u->encoder_buffer_used -= written;
 
-        pa_log_error("Failed to read data from SCO socket: %s", l < 0 ? pa_cstrerror(errno) : "EOF");
-        return -1;
-    }
+        /* move any remainder back to start of u->encoder_buffer */
+        if (u->encoder_buffer_used)
+            memmove(u->encoder_buffer, u->encoder_buffer + written, u->encoder_buffer_used);
 
-    pa_assert((size_t) l <= pa_memblock_get_length(memchunk.memblock));
-
-    /* In some rare occasions, we might receive packets of a very strange
-     * size. This could potentially be possible if the SCO packet was
-     * received partially over-the-air, or more probably due to hardware
-     * issues in our Bluetooth adapter. In these cases, in order to avoid
-     * an assertion failure due to unaligned data, just discard the whole
-     * packet */
-    if (!pa_frame_aligned(l, &u->decoder_sample_spec)) {
-        pa_log_warn("SCO packet received of unaligned size: %zu", l);
-        pa_memblock_unref(memchunk.memblock);
+        return 1;
+    } else if (written == 0) {
+        /* Not enough data in encoder buffer */
+        return 0;
+    } else {
+        /* Reset encoder sequence number and buffer positions */
+        u->bt_codec->reset(u->encoder_info);
+        u->encoder_buffer_used = 0;
         return -1;
     }
-
-    memchunk.length = (size_t) l;
-    u->read_index += (uint64_t) l;
-
-    for (cm = CMSG_FIRSTHDR(&m); cm; cm = CMSG_NXTHDR(&m, cm))
-        if (cm->cmsg_level == SOL_SOCKET && cm->cmsg_type == SO_TIMESTAMP) {
-            struct timeval *tv = (struct timeval*) CMSG_DATA(cm);
-            pa_rtclock_from_wallclock(tv);
-            tstamp = pa_timeval_load(tv);
-            found_tstamp = true;
-            break;
-        }
-
-    if (!found_tstamp) {
-        PA_ONCE_BEGIN {
-            pa_log_warn("Couldn't find SO_TIMESTAMP data in auxiliary recvmsg() data!");
-        } PA_ONCE_END;
-        tstamp = pa_rtclock_now();
-    }
-
-    pa_smoother_put(u->read_smoother, tstamp, pa_bytes_to_usec(u->read_index, &u->decoder_sample_spec));
-    pa_smoother_resume(u->read_smoother, tstamp, true);
-
-    pa_source_post(u->source, &memchunk);
-    pa_memblock_unref(memchunk.memblock);
-
-    return l;
 }
 
 /* Run from IO thread */
-static void a2dp_prepare_encoder_buffer(struct userdata *u) {
-    pa_assert(u);
-
-    if (u->encoder_buffer_size < u->write_link_mtu) {
-        pa_xfree(u->encoder_buffer);
-        u->encoder_buffer = pa_xmalloc(u->write_link_mtu);
-    }
-
-    /* Encoder buffer cannot be larger then link MTU, otherwise
-     * encode method would produce larger packets then link MTU */
-    u->encoder_buffer_size = u->write_link_mtu;
-}
-
-/* Run from IO thread */
-static void a2dp_prepare_decoder_buffer(struct userdata *u) {
-    pa_assert(u);
-
-    if (u->decoder_buffer_size < u->read_link_mtu) {
-        pa_xfree(u->decoder_buffer);
-        u->decoder_buffer = pa_xmalloc(u->read_link_mtu);
-    }
-
-    /* Decoder buffer cannot be larger then link MTU, otherwise
-     * decode method would produce larger output then read_block_size */
-    u->decoder_buffer_size = u->read_link_mtu;
-}
-
-/* Run from IO thread */
-static int a2dp_write_buffer(struct userdata *u, size_t nbytes) {
-    int ret = 0;
-
-    /* Encoder function of A2DP codec may provide empty buffer, in this case do
-     * not post any empty buffer via A2DP socket. It may be because of codec
-     * internal state, e.g. encoder is waiting for more samples so it can
-     * provide encoded data. */
-    if (PA_UNLIKELY(!nbytes)) {
-        u->write_index += (uint64_t) u->write_memchunk.length;
-        pa_memblock_unref(u->write_memchunk.memblock);
-        pa_memchunk_reset(&u->write_memchunk);
-        return 0;
-    }
-
-    for (;;) {
-        ssize_t l;
-
-        l = pa_write(u->stream_fd, u->encoder_buffer, nbytes, &u->stream_write_type);
-
-        pa_assert(l != 0);
-
-        if (l < 0) {
-
-            if (errno == EAGAIN) {
-                /* Hmm, apparently the socket was not writable, give up for now */
-                pa_log_debug("Got EAGAIN on write() after POLLOUT, probably there is a temporary connection loss.");
-                break;
-            }
-
-            pa_log_error("Failed to write data to socket: %s", pa_cstrerror(errno));
-            ret = -1;
-            break;
-        }
-
-        pa_assert((size_t) l <= nbytes);
-
-        if ((size_t) l != nbytes) {
-            pa_log_warn("Wrote memory block to socket only partially! %llu written, wanted to write %llu.",
-                        (unsigned long long) l,
-                        (unsigned long long) nbytes);
-            ret = -1;
-            break;
-        }
-
-        u->write_index += (uint64_t) u->write_memchunk.length;
-        pa_memblock_unref(u->write_memchunk.memblock);
-        pa_memchunk_reset(&u->write_memchunk);
-
-        ret = 1;
-
-        break;
-    }
-
-    return ret;
-}
+static int bt_process_render(struct userdata *u) {
+    int ret;
 
-/* Run from IO thread */
-static int a2dp_process_render(struct userdata *u) {
     const uint8_t *ptr;
     size_t processed;
     size_t length;
 
     pa_assert(u);
-    pa_assert(u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK);
     pa_assert(u->sink);
-    pa_assert(u->a2dp_codec);
+    pa_assert(u->bt_codec);
+
+    if (!bt_prepare_encoder_buffer(u))
+        return false;
 
     /* First, render some data */
     if (!u->write_memchunk.memblock)
@@ -521,12 +344,12 @@ static int a2dp_process_render(struct userdata *u) {
 
     pa_assert(u->write_memchunk.length == u->write_block_size);
 
-    a2dp_prepare_encoder_buffer(u);
-
-    /* Try to create a packet of the full MTU */
     ptr = (const uint8_t *) pa_memblock_acquire_chunk(&u->write_memchunk);
 
-    length = u->a2dp_codec->encode_buffer(u->encoder_info, u->write_index / pa_frame_size(&u->encoder_sample_spec), ptr, u->write_memchunk.length, u->encoder_buffer, u->encoder_buffer_size, &processed);
+    length = u->bt_codec->encode_buffer(u->encoder_info, u->write_index / pa_frame_size(&u->encoder_sample_spec),
+            ptr, u->write_memchunk.length,
+            u->encoder_buffer + u->encoder_buffer_used, u->encoder_buffer_size - u->encoder_buffer_used,
+            &processed);
 
     pa_memblock_release(u->write_memchunk.memblock);
 
@@ -535,35 +358,48 @@ static int a2dp_process_render(struct userdata *u) {
         return -1;
     }
 
-    return a2dp_write_buffer(u, length);
-}
+    /* Encoder function of BT codec may provide empty buffer, in this case do
+     * not post any empty buffer via BT socket. It may be because of codec
+     * internal state, e.g. encoder is waiting for more samples so it can
+     * provide encoded data. */
 
-/* Run from IO thread */
-static int a2dp_process_push(struct userdata *u) {
-    int ret = 0;
-    pa_memchunk memchunk;
+    if (PA_LIKELY(length)) {
+        u->encoder_buffer_used += length;
+        ret = 1;
+    } else
+        ret = 0;
+
+    u->write_index += (uint64_t) u->write_memchunk.length;
+    pa_memblock_unref(u->write_memchunk.memblock);
+    pa_memchunk_reset(&u->write_memchunk);
+
+    return ret;
+}
 
+static void bt_prepare_decoder_buffer(struct userdata *u) {
     pa_assert(u);
-    pa_assert(u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE);
-    pa_assert(u->source);
-    pa_assert(u->read_smoother);
-    pa_assert(u->a2dp_codec);
 
-    memchunk.memblock = pa_memblock_new(u->core->mempool, u->read_block_size);
-    memchunk.index = memchunk.length = 0;
+    if (u->decoder_buffer_size < u->read_link_mtu) {
+        pa_xfree(u->decoder_buffer);
+        u->decoder_buffer = pa_xmalloc(u->read_link_mtu);
+    }
 
-    a2dp_prepare_decoder_buffer(u);
+    /* Decoder buffer cannot be larger then link MTU, otherwise
+     * decode method would produce larger output then read_block_size */
+    u->decoder_buffer_size = u->read_link_mtu;
+}
 
+/* Run from IO thread */
+static ssize_t bt_transport_read(pa_bluetooth_transport *t, int fd, void *buffer, size_t size, pa_usec_t *p_timestamp) {
+    ssize_t received = 0;
+
+    pa_assert(t);
     for (;;) {
         uint8_t aux[1024];
         struct iovec iov;
         struct cmsghdr *cm;
         struct msghdr m;
         bool found_tstamp = false;
-        pa_usec_t tstamp;
-        uint8_t *ptr;
-        ssize_t l;
-        size_t processed;
 
         pa_zero(m);
         pa_zero(aux);
@@ -574,77 +410,113 @@ static int a2dp_process_push(struct userdata *u) {
         m.msg_control = aux;
         m.msg_controllen = sizeof(aux);
 
-        iov.iov_base = u->decoder_buffer;
-        iov.iov_len = u->decoder_buffer_size;
+        iov.iov_base = buffer;
+        iov.iov_len = size;
 
-        l = recvmsg(u->stream_fd, &m, 0);
+        received = recvmsg(fd, &m, 0);
 
-        if (l <= 0) {
+        if (received <= 0) {
 
-            if (l < 0 && errno == EINTR)
+            if (received < 0 && errno == EINTR)
                 /* Retry right away if we got interrupted */
                 continue;
 
-            else if (l < 0 && errno == EAGAIN)
+            else if (received < 0 && errno == EAGAIN)
                 /* Hmm, apparently the socket was not readable, give up for now. */
-                break;
+                return 0;
 
-            pa_log_error("Failed to read data from socket: %s", l < 0 ? pa_cstrerror(errno) : "EOF");
-            ret = -1;
-            break;
+            pa_log_error("Failed to read data from socket: %s", received < 0 ? pa_cstrerror(errno) : "EOF");
+            return -1;
         }
 
-        pa_assert((size_t) l <= u->decoder_buffer_size);
+        pa_assert((size_t) received <= size);
 
-        /* TODO: get timestamp from rtp */
+        /* allow write side to find out size of last read packet */
+        t->last_read_size = received;
 
-        for (cm = CMSG_FIRSTHDR(&m); cm; cm = CMSG_NXTHDR(&m, cm)) {
-            if (cm->cmsg_level == SOL_SOCKET && cm->cmsg_type == SO_TIMESTAMP) {
-                struct timeval *tv = (struct timeval*) CMSG_DATA(cm);
-                pa_rtclock_from_wallclock(tv);
-                tstamp = pa_timeval_load(tv);
-                found_tstamp = true;
-                break;
+        if (p_timestamp) {
+            /* TODO: get timestamp from rtp */
+
+            for (cm = CMSG_FIRSTHDR(&m); cm; cm = CMSG_NXTHDR(&m, cm)) {
+                if (cm->cmsg_level == SOL_SOCKET && cm->cmsg_type == SO_TIMESTAMP) {
+                    struct timeval *tv = (struct timeval*) CMSG_DATA(cm);
+                    pa_rtclock_from_wallclock(tv);
+                    *p_timestamp = pa_timeval_load(tv);
+                    found_tstamp = true;
+                    break;
+                }
             }
-        }
 
-        if (!found_tstamp) {
-            PA_ONCE_BEGIN {
-                pa_log_warn("Couldn't find SO_TIMESTAMP data in auxiliary recvmsg() data!");
-            } PA_ONCE_END;
-            tstamp = pa_rtclock_now();
+            if (!found_tstamp) {
+                PA_ONCE_BEGIN {
+                    pa_log_warn("Couldn't find SO_TIMESTAMP data in auxiliary recvmsg() data!");
+                } PA_ONCE_END;
+                *p_timestamp = pa_rtclock_now();
+            }
         }
 
-        ptr = pa_memblock_acquire(memchunk.memblock);
-        memchunk.length = pa_memblock_get_length(memchunk.memblock);
+        break;
+    }
 
-        memchunk.length = u->a2dp_codec->decode_buffer(u->decoder_info, u->decoder_buffer, l, ptr, memchunk.length, &processed);
+    return received;
+}
 
-        pa_memblock_release(memchunk.memblock);
+/* Run from IO thread */
+/* Read incoming data, decode it and post result (if any) to source output.
+ * Returns number of bytes posted to source output. */
+static int bt_process_push(struct userdata *u) {
+    pa_usec_t tstamp;
+    uint8_t *ptr;
+    ssize_t received;
+    size_t processed = 0;
 
-        if (processed != (size_t) l) {
-            pa_log_error("Decoding error");
-            ret = -1;
-            break;
-        }
+    pa_assert(u);
+    pa_assert(u->source);
+    pa_assert(u->read_smoother);
+    pa_assert(u->bt_codec);
+    pa_assert(u->transport);
 
-        u->read_index += (uint64_t) memchunk.length;
-        pa_smoother_put(u->read_smoother, tstamp, pa_bytes_to_usec(u->read_index, &u->decoder_sample_spec));
-        pa_smoother_resume(u->read_smoother, tstamp, true);
+    bt_prepare_decoder_buffer(u);
 
-        /* Decoding of A2DP codec data may result in empty buffer, in this case
-         * do not post empty audio samples. It may happen due to algorithmic
-         * delay of audio codec. */
-        if (PA_LIKELY(memchunk.length))
-            pa_source_post(u->source, &memchunk);
+    received = bt_transport_read(u->transport, u->stream_fd, u->decoder_buffer, u->decoder_buffer_size, &tstamp);
 
-        ret = l;
-        break;
+    if (received <= 0) {
+        return received;
+    }
+
+    pa_memchunk memchunk;
+
+    memchunk.memblock = pa_memblock_new(u->core->mempool, u->read_block_size);
+    memchunk.index = memchunk.length = 0;
+
+    ptr = pa_memblock_acquire(memchunk.memblock);
+    memchunk.length = pa_memblock_get_length(memchunk.memblock);
+
+    memchunk.length = u->bt_codec->decode_buffer(u->decoder_info, u->decoder_buffer, received, ptr, memchunk.length, &processed);
+
+    pa_memblock_release(memchunk.memblock);
+
+    if (processed != (size_t) received) {
+        pa_log_error("Decoding error");
+        return -1;
     }
 
+    u->read_index += (uint64_t) memchunk.length;
+    pa_smoother_put(u->read_smoother, tstamp, pa_bytes_to_usec(u->read_index, &u->decoder_sample_spec));
+    pa_smoother_resume(u->read_smoother, tstamp, true);
+
+    /* Decoding of data may result in empty buffer, in this case
+     * do not post empty audio samples. It may happen due to algorithmic
+     * delay of audio codec. */
+    if (PA_LIKELY(memchunk.length))
+        pa_source_post(u->source, &memchunk);
+
+    /* report decoded size */
+    received = memchunk.length;
+
     pa_memblock_unref(memchunk.memblock);
 
-    return ret;
+    return received;
 }
 
 static void update_sink_buffer_size(struct userdata *u) {
@@ -772,28 +644,23 @@ static void handle_sink_block_size_change(struct userdata *u) {
 
 /* Run from I/O thread */
 static void transport_config_mtu(struct userdata *u) {
-    if (u->profile == PA_BLUETOOTH_PROFILE_HSP_HS
-        || u->profile == PA_BLUETOOTH_PROFILE_HSP_AG
-        || u->profile == PA_BLUETOOTH_PROFILE_HFP_HF
-        || u->profile == PA_BLUETOOTH_PROFILE_HFP_AG) {
-        u->read_block_size = u->read_link_mtu;
-        u->write_block_size = u->write_link_mtu;
+    pa_assert(u->bt_codec);
 
-        if (!pa_frame_aligned(u->read_block_size, &u->source->sample_spec)) {
-            pa_log_debug("Got invalid read MTU: %lu, rounding down", u->read_block_size);
-            u->read_block_size = pa_frame_align(u->read_block_size, &u->source->sample_spec);
-        }
+    if (u->encoder_info) {
+        u->write_block_size = u->bt_codec->get_write_block_size(u->encoder_info, u->write_link_mtu);
 
         if (!pa_frame_aligned(u->write_block_size, &u->sink->sample_spec)) {
             pa_log_debug("Got invalid write MTU: %lu, rounding down", u->write_block_size);
             u->write_block_size = pa_frame_align(u->write_block_size, &u->sink->sample_spec);
         }
-    } else {
-        pa_assert(u->a2dp_codec);
-        if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK) {
-            u->write_block_size = u->a2dp_codec->get_write_block_size(u->encoder_info, u->write_link_mtu);
-        } else {
-            u->read_block_size = u->a2dp_codec->get_read_block_size(u->decoder_info, u->read_link_mtu);
+    }
+
+    if (u->decoder_info) {
+        u->read_block_size = u->bt_codec->get_read_block_size(u->decoder_info, u->read_link_mtu);
+
+        if (!pa_frame_aligned(u->read_block_size, &u->source->sample_spec)) {
+            pa_log_debug("Got invalid read MTU: %lu, rounding down", u->read_block_size);
+            u->read_block_size = pa_frame_align(u->read_block_size, &u->source->sample_spec);
         }
     }
 
@@ -820,13 +687,15 @@ static int setup_stream(struct userdata *u) {
 
     pa_log_info("Transport %s resuming", u->transport->path);
 
-    if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK) {
-        pa_assert(u->a2dp_codec);
-        if (u->a2dp_codec->reset(u->encoder_info) < 0)
+    pa_assert(u->bt_codec);
+
+    if (u->encoder_info) {
+        if (u->bt_codec->reset(u->encoder_info) < 0)
             return -1;
-    } else if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE) {
-        pa_assert(u->a2dp_codec);
-        if (u->a2dp_codec->reset(u->decoder_info) < 0)
+    }
+
+    if (u->decoder_info) {
+        if (u->bt_codec->reset(u->decoder_info) < 0)
             return -1;
     }
 
@@ -1078,8 +947,8 @@ static int add_source(struct userdata *u) {
     data.name = pa_sprintf_malloc("bluez_source.%s.%s", u->device->address, pa_bluetooth_profile_to_string(u->profile));
     data.namereg_fail = false;
     pa_proplist_sets(data.proplist, "bluetooth.protocol", pa_bluetooth_profile_to_string(u->profile));
-    if (u->a2dp_codec)
-        pa_proplist_sets(data.proplist, PA_PROP_BLUETOOTH_CODEC, u->a2dp_codec->name);
+    if (u->bt_codec)
+        pa_proplist_sets(data.proplist, PA_PROP_BLUETOOTH_CODEC, u->bt_codec->name);
     pa_source_new_data_set_sample_spec(&data, &u->decoder_sample_spec);
     if (u->profile == PA_BLUETOOTH_PROFILE_HSP_HS
         || u->profile == PA_BLUETOOTH_PROFILE_HFP_HF)
@@ -1294,8 +1163,8 @@ static int add_sink(struct userdata *u) {
     data.name = pa_sprintf_malloc("bluez_sink.%s.%s", u->device->address, pa_bluetooth_profile_to_string(u->profile));
     data.namereg_fail = false;
     pa_proplist_sets(data.proplist, "bluetooth.protocol", pa_bluetooth_profile_to_string(u->profile));
-    if (u->a2dp_codec)
-        pa_proplist_sets(data.proplist, PA_PROP_BLUETOOTH_CODEC, u->a2dp_codec->name);
+    if (u->bt_codec)
+        pa_proplist_sets(data.proplist, PA_PROP_BLUETOOTH_CODEC, u->bt_codec->name);
     pa_sink_new_data_set_sample_spec(&data, &u->encoder_sample_spec);
     if (u->profile == PA_BLUETOOTH_PROFILE_HSP_HS
         || u->profile == PA_BLUETOOTH_PROFILE_HFP_HF)
@@ -1343,42 +1212,54 @@ static int add_sink(struct userdata *u) {
 }
 
 /* Run from main thread */
-static int transport_config(struct userdata *u) {
-    if (u->profile == PA_BLUETOOTH_PROFILE_HSP_HS
-        || u->profile == PA_BLUETOOTH_PROFILE_HSP_AG
-        || u->profile == PA_BLUETOOTH_PROFILE_HFP_HF
-        || u->profile == PA_BLUETOOTH_PROFILE_HFP_AG) {
-        u->encoder_sample_spec.format = PA_SAMPLE_S16LE;
-        u->encoder_sample_spec.channels = 1;
-        u->encoder_sample_spec.rate = 8000;
-        u->decoder_sample_spec.format = PA_SAMPLE_S16LE;
-        u->decoder_sample_spec.channels = 1;
-        u->decoder_sample_spec.rate = 8000;
-        return 0;
-    } else {
-        bool is_a2dp_sink = u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK;
-        void *info;
+static pa_direction_t get_profile_direction(pa_bluetooth_profile_t p) {
+    static const pa_direction_t profile_direction[] = {
+        [PA_BLUETOOTH_PROFILE_A2DP_SINK] = PA_DIRECTION_OUTPUT,
+        [PA_BLUETOOTH_PROFILE_A2DP_SOURCE] = PA_DIRECTION_INPUT,
+        [PA_BLUETOOTH_PROFILE_HSP_HS] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT,
+        [PA_BLUETOOTH_PROFILE_HSP_AG] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT,
+        [PA_BLUETOOTH_PROFILE_HFP_HF] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT,
+        [PA_BLUETOOTH_PROFILE_HFP_AG] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT,
+        [PA_BLUETOOTH_PROFILE_OFF] = 0
+    };
 
-        pa_assert(u->transport);
+    return profile_direction[p];
+}
 
-        pa_assert(!u->a2dp_codec);
-        pa_assert(!u->encoder_info);
-        pa_assert(!u->decoder_info);
+/* Run from main thread */
+static int transport_config(struct userdata *u) {
+    pa_assert(u);
+    pa_assert(u->transport);
+    pa_assert(!u->bt_codec);
+    pa_assert(!u->encoder_info);
+    pa_assert(!u->decoder_info);
 
-        u->a2dp_codec = u->transport->a2dp_codec;
-        pa_assert(u->a2dp_codec);
+    u->bt_codec = u->transport->bt_codec;
+    pa_assert(u->bt_codec);
 
-        info = u->a2dp_codec->init(is_a2dp_sink, false, u->transport->config, u->transport->config_size, is_a2dp_sink ? &u->encoder_sample_spec : &u->decoder_sample_spec, u->core);
-        if (is_a2dp_sink)
-            u->encoder_info = info;
-        else
-            u->decoder_info = info;
+    /* reset encoder buffer contents */
+    u->encoder_buffer_used = 0;
 
-        if (!info)
+    if (get_profile_direction(u->profile) & PA_DIRECTION_OUTPUT) {
+        u->encoder_info = u->bt_codec->init(true, false, u->transport->config, u->transport->config_size, &u->encoder_sample_spec, u->core);
+
+        if (!u->encoder_info)
             return -1;
+    }
 
-        return 0;
+    if (get_profile_direction(u->profile) & PA_DIRECTION_INPUT) {
+        u->decoder_info = u->bt_codec->init(false, false, u->transport->config, u->transport->config_size, &u->decoder_sample_spec, u->core);
+
+        if (!u->decoder_info) {
+            if (u->encoder_info) {
+                u->bt_codec->deinit(u->encoder_info);
+                u->encoder_info = NULL;
+            }
+            return -1;
+        }
     }
+
+    return 0;
 }
 
 /* Run from main thread */
@@ -1411,21 +1292,6 @@ static int setup_transport(struct userdata *u) {
     return transport_config(u);
 }
 
-/* Run from main thread */
-static pa_direction_t get_profile_direction(pa_bluetooth_profile_t p) {
-    static const pa_direction_t profile_direction[] = {
-        [PA_BLUETOOTH_PROFILE_A2DP_SINK] = PA_DIRECTION_OUTPUT,
-        [PA_BLUETOOTH_PROFILE_A2DP_SOURCE] = PA_DIRECTION_INPUT,
-        [PA_BLUETOOTH_PROFILE_HSP_HS] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT,
-        [PA_BLUETOOTH_PROFILE_HSP_AG] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT,
-        [PA_BLUETOOTH_PROFILE_HFP_HF] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT,
-        [PA_BLUETOOTH_PROFILE_HFP_AG] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT,
-        [PA_BLUETOOTH_PROFILE_OFF] = 0
-    };
-
-    return profile_direction[p];
-}
-
 /* Run from main thread */
 static int init_profile(struct userdata *u) {
     int r = 0;
@@ -1451,23 +1317,19 @@ static int init_profile(struct userdata *u) {
     return r;
 }
 
-static int write_block(struct userdata *u) {
-    int n_written;
+static int bt_render_block(struct userdata *u) {
+    int n_rendered;
 
     if (u->write_index <= 0)
         u->started_at = pa_rtclock_now();
 
-    if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK) {
-        if ((n_written = a2dp_process_render(u)) < 0)
-            return -1;
-    } else {
-        if ((n_written = sco_process_render(u)) < 0)
-            return -1;
-    }
+    n_rendered = bt_process_render(u);
 
-    return n_written;
-}
+    if (n_rendered < 0)
+        n_rendered = -1;
 
+    return n_rendered;
+}
 
 /* I/O thread function */
 static void thread_func(void *userdata) {
@@ -1534,16 +1396,17 @@ static void thread_func(void *userdata) {
                 if (pollfd->revents & POLLIN) {
                     int n_read;
 
-                    if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE)
-                        n_read = a2dp_process_push(u);
-                    else
-                        n_read = sco_process_push(u);
+                    n_read = bt_process_push(u);
 
                     if (n_read < 0)
                         goto fail;
 
                     if (have_sink && n_read > 0) {
-                        /* We just read something, so we are supposed to write something, too */
+                        /* We just read something, so we are supposed to write something, too
+                         *
+                         * If source and sink sample specifications are not equal,
+                         * expected write size needs to be adjusted accordingly.
+                         */
                         bytes_to_write += n_read;
                         blocks_to_write += bytes_to_write / u->write_block_size;
                         bytes_to_write = bytes_to_write % u->write_block_size;
@@ -1566,28 +1429,39 @@ static void thread_func(void *userdata) {
                  * for the sink */
                 if (have_source) {
 
-                    if (writable && blocks_to_write > 0) {
+                    /* If the stream is writable, send some data if necessary */
+                    if (writable) {
                         int result;
 
-                        if ((result = write_block(u)) < 0)
+                        if (blocks_to_write > 0) {
+                            result = bt_render_block(u);
+                            if (result < 0)
+                                goto fail;
+                            blocks_to_write -= result;
+                        }
+
+                        result = bt_write_buffer(u);
+
+                        if (result < 0)
                             goto fail;
 
-                        blocks_to_write -= result;
-
-                        /* writable controls whether we set POLLOUT when polling - we set it to
-                         * false to enable POLLOUT. If there are more blocks to write, we want to
-                         * be woken up immediately when the socket becomes writable. If there
-                         * aren't currently any more blocks to write, then we'll have to wait
-                         * until we've received more data, so in that case we only want to set
-                         * POLLIN. Note that when we are woken up the next time, POLLOUT won't be
-                         * set in revents even if the socket has meanwhile become writable, which
-                         * may seem bad, but in that case we'll set POLLOUT in the subsequent
-                         * poll, and the poll will return immediately, so our writes won't be
-                         * delayed. */
-                        if (blocks_to_write > 0)
+                        if (result)
                             writable = false;
                     }
 
+                    /* writable controls whether we set POLLOUT when polling - we set it to
+                     * false to enable POLLOUT. If there are more blocks to write, we want to
+                     * be woken up immediately when the socket becomes writable. If there
+                     * aren't currently any more blocks to write, then we'll have to wait
+                     * until we've received more data, so in that case we only want to set
+                     * POLLIN. Note that when we are woken up the next time, POLLOUT won't be
+                     * set in revents even if the socket has meanwhile become writable, which
+                     * may seem bad, but in that case we'll set POLLOUT in the subsequent
+                     * poll, and the poll will return immediately, so our writes won't be
+                     * delayed. */
+                    if (blocks_to_write > 0)
+                        writable = false;
+
                 /* There is no source, we have to use the system clock for timing */
                 } else {
                     bool have_written = false;
@@ -1634,8 +1508,8 @@ static void thread_func(void *userdata) {
                                 skip_bytes -= bytes_to_render;
                             }
 
-                            if (u->write_index > 0 && u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK) {
-                                size_t new_write_block_size = u->a2dp_codec->reduce_encoder_bitrate(u->encoder_info, u->write_link_mtu);
+                            if (u->write_index > 0 && (get_profile_direction(u->profile) & PA_DIRECTION_OUTPUT)) {
+                                size_t new_write_block_size = u->bt_codec->reduce_encoder_bitrate(u->encoder_info, u->write_link_mtu);
                                 if (new_write_block_size) {
                                     u->write_block_size = new_write_block_size;
                                     handle_sink_block_size_change(u);
@@ -1648,16 +1522,25 @@ static void thread_func(void *userdata) {
                     }
 
                     /* If the stream is writable, send some data if necessary */
-                    if (writable && blocks_to_write > 0) {
+                    if (writable) {
                         int result;
 
-                        if ((result = write_block(u)) < 0)
+                        if (blocks_to_write > 0) {
+                            int result = bt_render_block(u);
+                            if (result < 0)
+                                goto fail;
+                            blocks_to_write -= result;
+                        }
+
+                        result = bt_write_buffer(u);
+
+                        if (result < 0)
                             goto fail;
 
-                        blocks_to_write -= result;
-                        writable = false;
-                        if (result)
+                        if (result) {
+                            writable = false;
                             have_written = true;
+                        }
                     }
 
                     /* If nothing was written during this iteration, either the stream
@@ -1674,11 +1557,11 @@ static void thread_func(void *userdata) {
                             sleep_for = time_passed < next_write_at ? next_write_at - time_passed : 0;
                             /* pa_log("Sleeping for %lu; time passed %lu, next write at %lu", (unsigned long) sleep_for, (unsigned long) time_passed, (unsigned long)next_write_at); */
 
-                            if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK && u->write_memchunk.memblock == NULL) {
-                                /* write_block() is keeping up with input, try increasing bitrate */
-                                if (u->a2dp_codec->increase_encoder_bitrate
+                            if ((get_profile_direction(u->profile) & PA_DIRECTION_OUTPUT) && u->write_memchunk.memblock == NULL) {
+                                /* bt_write_buffer() is keeping up with input, try increasing bitrate */
+                                if (u->bt_codec->increase_encoder_bitrate
                                     && pa_timeval_age(&tv_last_output_rate_change) >= u->device->output_rate_refresh_interval_ms * PA_USEC_PER_MSEC) {
-                                    size_t new_write_block_size = u->a2dp_codec->increase_encoder_bitrate(u->encoder_info, u->write_link_mtu);
+                                    size_t new_write_block_size = u->bt_codec->increase_encoder_bitrate(u->encoder_info, u->write_link_mtu);
                                     if (new_write_block_size) {
                                         u->write_block_size = new_write_block_size;
                                         handle_sink_block_size_change(u);
@@ -1779,8 +1662,8 @@ static int start_thread(struct userdata *u) {
     }
 
     if (u->sink || u->source)
-        if (u->a2dp_codec)
-            pa_proplist_sets(u->card->proplist, PA_PROP_BLUETOOTH_CODEC, u->a2dp_codec->name);
+        if (u->bt_codec)
+            pa_proplist_sets(u->card->proplist, PA_PROP_BLUETOOTH_CODEC, u->bt_codec->name);
 
     return 0;
 }
@@ -1845,19 +1728,34 @@ static void stop_thread(struct userdata *u) {
         u->read_smoother = NULL;
     }
 
-    if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK || u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE) {
+    if (u->bt_codec) {
         if (u->encoder_info) {
-            u->a2dp_codec->deinit(u->encoder_info);
+            u->bt_codec->deinit(u->encoder_info);
             u->encoder_info = NULL;
         }
 
         if (u->decoder_info) {
-            u->a2dp_codec->deinit(u->decoder_info);
+            u->bt_codec->deinit(u->decoder_info);
             u->decoder_info = NULL;
         }
 
-        u->a2dp_codec = NULL;
+        u->bt_codec = NULL;
     }
+
+    if (u->encoder_buffer) {
+        pa_xfree(u->encoder_buffer);
+        u->encoder_buffer = NULL;
+    }
+
+    u->encoder_buffer_size = 0;
+    u->encoder_buffer_used = 0;
+
+    if (u->decoder_buffer) {
+        pa_xfree(u->decoder_buffer);
+        u->decoder_buffer = NULL;
+    }
+
+    u->decoder_buffer_size = 0;
 }
 
 /* Run from main thread */
@@ -2446,7 +2344,7 @@ static void switch_codec_cb_handler(bool success, pa_bluetooth_profile_t profile
         }
 
     pa_log_info("Codec successfully switched to %s with profile: %s",
-            u->a2dp_codec->name, pa_bluetooth_profile_to_string(u->profile));
+            u->bt_codec->name, pa_bluetooth_profile_to_string(u->profile));
 
     return;
 
@@ -2463,31 +2361,49 @@ static char *list_codecs(struct userdata *u) {
     bool is_a2dp_sink;
     void *state;
 
-    is_a2dp_sink = u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK;
-
-    a2dp_endpoints = is_a2dp_sink ? u->device->a2dp_sink_endpoints : u->device->a2dp_source_endpoints;
-
     encoder = pa_json_encoder_new();
 
     pa_json_encoder_begin_element_array(encoder);
 
-    PA_HASHMAP_FOREACH_KV(key, a2dp_capabilities, a2dp_endpoints, state) {
-        for (i = 0; i < pa_bluetooth_a2dp_codec_count(); i++) {
-            const pa_a2dp_codec *a2dp_codec;
+    if (u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK || u->profile == PA_BLUETOOTH_PROFILE_A2DP_SOURCE) {
+        is_a2dp_sink = u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK;
+
+        a2dp_endpoints = is_a2dp_sink ? u->device->a2dp_sink_endpoints : u->device->a2dp_source_endpoints;
+
+        PA_HASHMAP_FOREACH_KV(key, a2dp_capabilities, a2dp_endpoints, state) {
+            for (i = 0; i < pa_bluetooth_a2dp_endpoint_conf_count(); i++) {
+                const pa_a2dp_endpoint_conf *endpoint_conf;
 
-            a2dp_codec = pa_bluetooth_a2dp_codec_iter(i);
+                endpoint_conf = pa_bluetooth_a2dp_endpoint_conf_iter(i);
 
-            if (memcmp(key, &a2dp_codec->id, sizeof(pa_a2dp_codec_id)) == 0) {
-                if (a2dp_codec->can_be_supported(is_a2dp_sink)) {
-                    pa_json_encoder_begin_element_object(encoder);
+                if (memcmp(key, &endpoint_conf->id, sizeof(pa_a2dp_codec_id)) == 0) {
+                    if (endpoint_conf->can_be_supported(is_a2dp_sink)) {
+                        pa_json_encoder_begin_element_object(encoder);
 
-                    pa_json_encoder_add_member_string(encoder, "name", a2dp_codec->name);
-                    pa_json_encoder_add_member_string(encoder, "description", a2dp_codec->description);
+                        pa_json_encoder_add_member_string(encoder, "name", endpoint_conf->bt_codec.name);
+                        pa_json_encoder_add_member_string(encoder, "description", endpoint_conf->bt_codec.description);
 
-                    pa_json_encoder_end_object(encoder);
+                        pa_json_encoder_end_object(encoder);
+                    }
                 }
             }
         }
+    } else {
+        /* find out active codec selection from device profile */
+        for (i = 0; i < pa_bluetooth_hf_codec_count(); i++) {
+            const pa_bt_codec *hf_codec;
+
+            hf_codec = pa_bluetooth_hf_codec_iter(i);
+
+            if (true) {
+                pa_json_encoder_begin_element_object(encoder);
+
+                pa_json_encoder_add_member_string(encoder, "name", hf_codec->name);
+                pa_json_encoder_add_member_string(encoder, "description", hf_codec->description);
+
+                pa_json_encoder_end_object(encoder);
+            }
+        }
     }
 
     pa_json_encoder_end_array(encoder);
@@ -2499,7 +2415,7 @@ static int bluez5_device_message_handler(const char *object_path, const char *me
     char *message_handler_path;
     pa_hashmap *capabilities_hashmap;
     pa_bluetooth_profile_t profile;
-    const pa_a2dp_codec *codec;
+    const pa_a2dp_endpoint_conf *endpoint_conf;
     const char *codec_name;
     struct userdata *u;
     bool is_a2dp_sink;
@@ -2530,13 +2446,15 @@ static int bluez5_device_message_handler(const char *object_path, const char *me
     if (u->profile == PA_BLUETOOTH_PROFILE_OFF) {
         pa_log_info("Bluetooth profile is off. Message cannot be handled.");
         return -PA_ERR_INVALID;
-    } else if (u->profile != PA_BLUETOOTH_PROFILE_A2DP_SINK &&
-            u->profile != PA_BLUETOOTH_PROFILE_A2DP_SOURCE) {
-        pa_log_info("Listing or switching codecs only allowed for A2DP sink or source");
-        return -PA_ERR_INVALID;
     }
 
     if (pa_streq(message, "switch-codec")) {
+        if (u->profile != PA_BLUETOOTH_PROFILE_A2DP_SINK &&
+            u->profile != PA_BLUETOOTH_PROFILE_A2DP_SOURCE) {
+            pa_log_info("Switching codecs only allowed for A2DP sink or source");
+            return -PA_ERR_INVALID;
+        }
+
         if (!parameters) {
             pa_log_info("Codec switching operation requires codec name string parameter");
             return -PA_ERR_INVALID;
@@ -2549,20 +2467,20 @@ static int bluez5_device_message_handler(const char *object_path, const char *me
 
         codec_name = pa_json_object_get_string(parameters);
 
-        if (u->a2dp_codec && pa_streq(codec_name, u->a2dp_codec->name)) {
+        if (u->bt_codec && pa_streq(codec_name, u->bt_codec->name)) {
             pa_log_info("Requested codec is currently selected codec");
             return -PA_ERR_INVALID;
         }
 
-        codec = pa_bluetooth_get_a2dp_codec(codec_name);
-        if (codec == NULL) {
+        endpoint_conf = pa_bluetooth_get_a2dp_endpoint_conf(codec_name);
+        if (endpoint_conf == NULL) {
             pa_log_info("Invalid codec %s specified for switching", codec_name);
             return -PA_ERR_INVALID;
         }
 
         is_a2dp_sink = u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK;
 
-        if (!codec->can_be_supported(is_a2dp_sink)) {
+        if (!endpoint_conf->can_be_supported(is_a2dp_sink)) {
             pa_log_info("Codec not found on system");
             return -PA_ERR_NOTSUPPORTED;
         }
@@ -2578,14 +2496,14 @@ static int bluez5_device_message_handler(const char *object_path, const char *me
             return -PA_ERR_INVALID;
         }
 
-        capabilities_hashmap = pa_hashmap_get(is_a2dp_sink ? u->device->a2dp_sink_endpoints : u->device->a2dp_source_endpoints, &codec->id);
+        capabilities_hashmap = pa_hashmap_get(is_a2dp_sink ? u->device->a2dp_sink_endpoints : u->device->a2dp_source_endpoints, &endpoint_conf->id);
         if (!capabilities_hashmap) {
             pa_log_info("No remote endpoint found for %s codec. Codec not supported by remote endpoint.",
-                    codec->name);
+                    endpoint_conf->bt_codec.name);
             return -PA_ERR_INVALID;
         }
 
-        pa_log_info("Initiating codec switching process to %s", codec->name);
+        pa_log_info("Initiating codec switching process to %s", endpoint_conf->bt_codec.name);
 
         /*
          * The current profile needs to be saved before we stop the thread and
@@ -2596,7 +2514,7 @@ static int bluez5_device_message_handler(const char *object_path, const char *me
 
         stop_thread(u);
 
-        if (!pa_bluetooth_device_switch_codec(u->device, profile, capabilities_hashmap, codec, switch_codec_cb_handler, userdata)
+        if (!pa_bluetooth_device_switch_codec(u->device, profile, capabilities_hashmap, endpoint_conf, switch_codec_cb_handler, userdata)
                 && !u->device->codec_switching_in_progress)
             goto profile_off;
 
@@ -2608,8 +2526,10 @@ static int bluez5_device_message_handler(const char *object_path, const char *me
         pa_json_encoder *encoder;
         encoder = pa_json_encoder_new();
 
-        if (u->a2dp_codec)
-            pa_json_encoder_add_element_string(encoder, u->a2dp_codec->name);
+        if (u->bt_codec)
+            pa_json_encoder_add_element_string(encoder, u->bt_codec->name);
+        else
+            pa_json_encoder_add_element_null(encoder);
 
         *response = pa_json_encoder_to_string_free(encoder);
 


=====================================
src/modules/bluetooth/module-bluez5-discover.c
=====================================
@@ -37,6 +37,7 @@ PA_MODULE_LOAD_ONCE(true);
 PA_MODULE_USAGE(
     "headset=ofono|native|auto"
     "autodetect_mtu=<boolean>"
+    "enable_msbc=<boolean, enable mSBC support in native and oFono backends, default is true>"
     "output_rate_refresh_interval_ms=<interval between attempts to improve output rate in milliseconds>"
     "enable_native_hfp_hf=<boolean, enable HFP support in native backend>"
 );
@@ -44,6 +45,7 @@ PA_MODULE_USAGE(
 static const char* const valid_modargs[] = {
     "headset",
     "autodetect_mtu",
+    "enable_msbc",
     "output_rate_refresh_interval_ms",
     "enable_native_hfp_hf",
     NULL
@@ -111,6 +113,7 @@ int pa__init(pa_module *m) {
     const char *headset_str;
     int headset_backend;
     bool autodetect_mtu;
+    bool enable_msbc;
     uint32_t output_rate_refresh_interval_ms;
     bool enable_native_hfp_hf = true;
 
@@ -140,6 +143,10 @@ int pa__init(pa_module *m) {
     if (pa_modargs_get_value_boolean(ma, "autodetect_mtu", &autodetect_mtu) < 0) {
         pa_log("Invalid boolean value for autodetect_mtu parameter");
     }
+    enable_msbc = true;
+    if (pa_modargs_get_value_boolean(ma, "enable_msbc", &enable_msbc) < 0) {
+        pa_log("Invalid boolean value for enable_msbc parameter");
+    }
     if (pa_modargs_get_value_boolean(ma, "enable_native_hfp_hf", &enable_native_hfp_hf) < 0) {
         pa_log("enable_native_hfp_hf must be true or false");
         goto fail;
@@ -158,7 +165,7 @@ int pa__init(pa_module *m) {
     u->output_rate_refresh_interval_ms = output_rate_refresh_interval_ms;
     u->loaded_device_paths = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
 
-    if (!(u->discovery = pa_bluetooth_discovery_get(u->core, headset_backend, enable_native_hfp_hf)))
+    if (!(u->discovery = pa_bluetooth_discovery_get(u->core, headset_backend, enable_native_hfp_hf, enable_msbc)))
         goto fail;
 
     u->device_connection_changed_slot =



View it on GitLab: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/compare/8a87af380a89bfcc9d29a915828ddc43456569a5...1a194c99181f504f8da31c29f1e928ba7a7f0165

-- 
View it on GitLab: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/compare/8a87af380a89bfcc9d29a915828ddc43456569a5...1a194c99181f504f8da31c29f1e928ba7a7f0165
You're receiving this email because of your account on gitlab.freedesktop.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.freedesktop.org/archives/pulseaudio-commits/attachments/20210405/57bfe46b/attachment-0001.htm>


More information about the pulseaudio-commits mailing list