diff --git a/spa/plugins/bluez5/codec-loader.c b/spa/plugins/bluez5/codec-loader.c index ba2c1aa5e..1557d1f7c 100644 --- a/spa/plugins/bluez5/codec-loader.c +++ b/spa/plugins/bluez5/codec-loader.c @@ -52,6 +52,9 @@ static int codec_order(const struct media_codec *c) SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_PRO, SPA_BLUETOOTH_AUDIO_CODEC_AAC_ELD, SPA_BLUETOOTH_AUDIO_CODEC_G722, + SPA_BLUETOOTH_AUDIO_CODEC_LC3_SWB, + SPA_BLUETOOTH_AUDIO_CODEC_MSBC, + SPA_BLUETOOTH_AUDIO_CODEC_CVSD, }; size_t i; for (i = 0; i < SPA_N_ELEMENTS(order); ++i) @@ -124,6 +127,7 @@ static int load_media_codecs_from(struct impl *impl, const char *factory_name, c case MEDIA_CODEC_A2DP: case MEDIA_CODEC_BAP: case MEDIA_CODEC_ASHA: + case MEDIA_CODEC_HFP: break; default: spa_log_warn(impl->log, "codec plugin %s: unknown codec %s kind %d", @@ -171,7 +175,6 @@ fail: const struct media_codec * const *load_media_codecs(struct spa_plugin_loader *loader, struct spa_log *log) { struct impl *impl; - bool has_sbc; size_t i; const struct { const char *factory; const char *lib; } plugins[] = { #define MEDIA_CODEC_FACTORY_LIB(basename) \ @@ -185,7 +188,10 @@ const struct media_codec * const *load_media_codecs(struct spa_plugin_loader *lo MEDIA_CODEC_FACTORY_LIB("opus"), MEDIA_CODEC_FACTORY_LIB("opus-g"), MEDIA_CODEC_FACTORY_LIB("lc3"), - MEDIA_CODEC_FACTORY_LIB("g722") + MEDIA_CODEC_FACTORY_LIB("g722"), + MEDIA_CODEC_FACTORY_LIB("hfp-cvsd"), + MEDIA_CODEC_FACTORY_LIB("hfp-msbc"), + MEDIA_CODEC_FACTORY_LIB("hfp-lc3-swb"), #undef MEDIA_CODEC_FACTORY_LIB }; @@ -201,13 +207,17 @@ const struct media_codec * const *load_media_codecs(struct spa_plugin_loader *lo for (i = 0; i < SPA_N_ELEMENTS(plugins); ++i) load_media_codecs_from(impl, plugins[i].factory, plugins[i].lib); - has_sbc = false; - for (i = 0; i < impl->n_codecs; ++i) - if (impl->codecs[i]->id == SPA_BLUETOOTH_AUDIO_CODEC_SBC) - has_sbc = true; + bool has_sbc = false, has_cvsd = false; + for (i = 0; i < impl->n_codecs; ++i) { + has_sbc |= impl->codecs[i]->id == SPA_BLUETOOTH_AUDIO_CODEC_SBC; + has_cvsd |= impl->codecs[i]->id == SPA_BLUETOOTH_AUDIO_CODEC_CVSD; + } - if (!has_sbc) { - spa_log_error(impl->log, "failed to load A2DP SBC codec from plugins"); + if (!has_sbc || !has_cvsd) { + if (!has_sbc) + spa_log_error(impl->log, "failed to load A2DP SBC codec from plugins"); + if (!has_cvsd) + spa_log_error(impl->log, "failed to load HFP CVSD codec from plugins"); free_media_codecs(impl->codecs); errno = ENOENT; return NULL; diff --git a/spa/plugins/bluez5/hfp-codec-cvsd.c b/spa/plugins/bluez5/hfp-codec-cvsd.c new file mode 100644 index 000000000..2b776b540 --- /dev/null +++ b/spa/plugins/bluez5/hfp-codec-cvsd.c @@ -0,0 +1,199 @@ +/* Spa HFP CVSD Codec */ +/* SPDX-FileCopyrightText: Copyright © 2019 Collabora Ltd. */ +/* SPDX-FileCopyrightText: Copyright © 2025 Pauli Virtanen */ +/* SPDX-License-Identifier: MIT */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "media-codecs.h" +#include "hfp-h2.h" + +static struct spa_log *log; + +struct impl { + size_t block_size; + uint16_t seq; +}; + +static int codec_enum_config(const struct media_codec *codec, uint32_t flags, + const void *caps, size_t caps_size, uint32_t id, uint32_t idx, + struct spa_pod_builder *b, struct spa_pod **param) +{ + struct spa_pod_frame f[1]; + const uint32_t position[SPA_AUDIO_MAX_CHANNELS] = { SPA_AUDIO_CHANNEL_MONO }; + const int channels = 1; + + spa_assert(caps == NULL && caps_size == 0); + + if (idx > 0) + return 0; + + spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, id); + spa_pod_builder_add(b, + SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), + SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_S16_LE), + SPA_FORMAT_AUDIO_rate, SPA_POD_CHOICE_ENUM_Int(1, 8000), + SPA_FORMAT_AUDIO_channels, SPA_POD_Int(channels), + SPA_FORMAT_AUDIO_position, SPA_POD_Array(sizeof(uint32_t), + SPA_TYPE_Id, channels, position), + 0); + + *param = spa_pod_builder_pop(b, &f[0]); + return *param == NULL ? -EIO : 1; +} + +static int codec_validate_config(const struct media_codec *codec, uint32_t flags, + const void *caps, size_t caps_size, + struct spa_audio_info *info) +{ + spa_assert(caps == NULL && caps_size == 0); + + spa_zero(*info); + info->media_type = SPA_MEDIA_TYPE_audio; + info->media_subtype = SPA_MEDIA_SUBTYPE_raw; + info->info.raw.format = SPA_AUDIO_FORMAT_S16_LE; + info->info.raw.rate = 8000; + info->info.raw.channels = 1; + info->info.raw.position[0] = SPA_AUDIO_CHANNEL_MONO; + return 0; +} + +static void *codec_init(const struct media_codec *codec, uint32_t flags, + void *config, size_t config_len, const struct spa_audio_info *info, + void *props, size_t mtu) +{ + struct impl *this = NULL; + + spa_assert(config == NULL && config_len == 0 && props == NULL); + + if (mtu < 2) + return NULL; + + this = calloc(1, sizeof(struct impl)); + if (!this) + return NULL; + + this->block_size = 2 * (mtu/2); + return this; +} + +static void codec_deinit(void *data) +{ + free(data); +} + +static int codec_get_block_size(void *data) +{ + struct impl *this = data; + + return this->block_size; +} + +static int codec_start_encode (void *data, void *dst, size_t dst_size, + uint16_t seqnum, uint32_t timestamp) +{ + return 0; +} + +static int codec_encode(void *data, + const void *src, size_t src_size, + void *dst, size_t dst_size, + size_t *dst_out, int *need_flush) +{ + struct impl *this = data; + + if (src_size < this->block_size) + return -EINVAL; + if (dst_size < this->block_size) + return -EINVAL; + + spa_memmove(dst, src, this->block_size); + *dst_out = this->block_size; + *need_flush = NEED_FLUSH_ALL; + return this->block_size; +} + +static int codec_start_decode (void *data, + const void *src, size_t src_size, uint16_t *seqnum, uint32_t *timestamp) +{ + struct impl *this = data; + + if (src_size != 48 && is_zero_packet(src, src_size)) { + /* Adapter is returning non-standard CVSD stream. For example + * Intel 8087:0029 at Firmware revision 0.0 build 191 week 21 2021 + * on kernel 5.13.19 produces such data. + */ + return -EINVAL; + } + + if (src_size % 2 != 0) { + /* Unaligned data: reception or adapter problem. + * Consider the whole packet lost and report. + */ + return -EINVAL; + } + + if (seqnum) + *seqnum = this->seq; + + if (timestamp) + *timestamp = 0; + + this->seq++; + return 0; +} + +static int codec_decode(void *data, + const void *src, size_t src_size, + void *dst, size_t dst_size, + size_t *dst_out) +{ + struct impl *this = data; + int dummy; + + return codec_encode(this, src, src_size, dst, dst_size, dst_out, &dummy); +} + +static void codec_set_log(struct spa_log *global_log) +{ + log = global_log; + spa_log_topic_init(log, &codec_plugin_log_topic); +} + +const struct media_codec hfp_codec_cvsd = { + .id = SPA_BLUETOOTH_AUDIO_CODEC_CVSD, + .kind = MEDIA_CODEC_HFP, + .codec_id = 0x01, + .enum_config = codec_enum_config, + .validate_config = codec_validate_config, + .init = codec_init, + .deinit = codec_deinit, + .get_block_size = codec_get_block_size, + .start_encode = codec_start_encode, + .encode = codec_encode, + .set_log = codec_set_log, + .start_decode = codec_start_decode, + .decode = codec_decode, + .name = "cvsd", + .description = "CVSD", +}; + +MEDIA_CODEC_EXPORT_DEF( + "hfp-cvsd", + &hfp_codec_cvsd +); diff --git a/spa/plugins/bluez5/hfp-codec-lc3-swb.c b/spa/plugins/bluez5/hfp-codec-lc3-swb.c new file mode 100644 index 000000000..487c1dfa5 --- /dev/null +++ b/spa/plugins/bluez5/hfp-codec-lc3-swb.c @@ -0,0 +1,243 @@ +/* Spa HFP LC3-SWB Codec */ +/* SPDX-FileCopyrightText: Copyright © 2025 Pauli Virtanen */ +/* SPDX-License-Identifier: MIT */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "media-codecs.h" +#include "hfp-h2.h" + +#include + +#define LC3_SWB_BLOCK_SIZE 960 + +static struct spa_log *log; + +struct impl { + lc3_encoder_t enc; + lc3_decoder_t dec; + struct h2_reader h2; + uint16_t seq; + + void *data; + size_t avail; +}; + +static int codec_enum_config(const struct media_codec *codec, uint32_t flags, + const void *caps, size_t caps_size, uint32_t id, uint32_t idx, + struct spa_pod_builder *b, struct spa_pod **param) +{ + struct spa_pod_frame f[1]; + const uint32_t position[SPA_AUDIO_MAX_CHANNELS] = { SPA_AUDIO_CHANNEL_MONO }; + const int channels = 1; + + spa_assert(caps == NULL && caps_size == 0); + + if (idx > 0) + return 0; + + spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, id); + spa_pod_builder_add(b, + SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), + SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_F32), + SPA_FORMAT_AUDIO_rate, SPA_POD_CHOICE_ENUM_Int(1, 32000), + SPA_FORMAT_AUDIO_channels, SPA_POD_Int(channels), + SPA_FORMAT_AUDIO_position, SPA_POD_Array(sizeof(uint32_t), + SPA_TYPE_Id, channels, position), + 0); + + *param = spa_pod_builder_pop(b, &f[0]); + return *param == NULL ? -EIO : 1; +} + +static int codec_validate_config(const struct media_codec *codec, uint32_t flags, + const void *caps, size_t caps_size, + struct spa_audio_info *info) +{ + spa_assert(caps == NULL && caps_size == 0); + + spa_zero(*info); + info->media_type = SPA_MEDIA_TYPE_audio; + info->media_subtype = SPA_MEDIA_SUBTYPE_raw; + info->info.raw.format = SPA_AUDIO_FORMAT_F32; + info->info.raw.rate = 32000; + info->info.raw.channels = 1; + info->info.raw.position[0] = SPA_AUDIO_CHANNEL_MONO; + return 0; +} + +static void *codec_init(const struct media_codec *codec, uint32_t flags, + void *config, size_t config_len, const struct spa_audio_info *info, + void *props, size_t mtu) +{ + struct impl *this = NULL; + + spa_assert(config == NULL && config_len == 0 && props == NULL); + + this = calloc(1, sizeof(*this)); + if (!this) + goto fail; + + this->enc = lc3_setup_encoder(7500, 32000, 0, + calloc(1, lc3_encoder_size(7500, 32000))); + if (!this->enc) + goto fail; + + this->dec = lc3_setup_decoder(7500, 32000, 0, + calloc(1, lc3_decoder_size(7500, 32000))); + if (!this->dec) + goto fail; + + spa_assert(lc3_frame_samples(7500, 32000) * sizeof(float) == LC3_SWB_BLOCK_SIZE); + + h2_reader_init(&this->h2, false); + + return spa_steal_ptr(this); + +fail: + if (this) { + free(this->enc); + free(this->dec); + free(this); + } + return NULL; +} + +static void codec_deinit(void *data) +{ + struct impl *this = data; + + free(this->enc); + free(this->dec); + free(this); +} + +static int codec_get_block_size(void *data) +{ + return LC3_SWB_BLOCK_SIZE; +} + +static int codec_start_encode (void *data, void *dst, size_t dst_size, + uint16_t seqnum, uint32_t timestamp) +{ + struct impl *this = data; + + this->seq = seqnum; + return 0; +} + +static int codec_encode(void *data, + const void *src, size_t src_size, + void *dst, size_t dst_size, + size_t *dst_out, int *need_flush) +{ + struct impl *this = data; + int res; + + if (src_size < LC3_SWB_BLOCK_SIZE) + return -EINVAL; + if (dst_size < H2_PACKET_SIZE) + return -EINVAL; + + h2_write(dst, this->seq); + + res = lc3_encode(this->enc, LC3_PCM_FORMAT_FLOAT, src, 1, + H2_PACKET_SIZE - 2, SPA_PTROFF(dst, 2, void)); + if (res != 0) + return -EINVAL; + + *dst_out = H2_PACKET_SIZE; + *need_flush = NEED_FLUSH_ALL; + return LC3_SWB_BLOCK_SIZE; +} + +static int codec_start_decode (void *data, + const void *src, size_t src_size, uint16_t *seqnum, uint32_t *timestamp) +{ + struct impl *this = data; + size_t consumed; + + if (is_zero_packet(src, src_size)) + return -EINVAL; + + if (!this->data) + this->data = h2_reader_read(&this->h2, src, src_size, &consumed, &this->avail); + + if (seqnum) + *seqnum = this->h2.seq; + if (timestamp) + *timestamp = 0; + return consumed; +} + +static int codec_decode(void *data, + const void *src, size_t src_size, + void *dst, size_t dst_size, + size_t *dst_out) +{ + struct impl *this = data; + size_t consumed = 0; + int res; + + *dst_out = 0; + + if (!this->data) + this->data = h2_reader_read(&this->h2, src, src_size, &consumed, &this->avail); + if (!this->data) + return consumed; + + res = lc3_decode(this->dec, this->data, this->avail, LC3_PCM_FORMAT_FLOAT, dst, 1); + this->data = NULL; + + if (res) { + h2_reader_init(&this->h2, true); + return -EINVAL; + } + + *dst_out = LC3_SWB_BLOCK_SIZE; + return consumed; +} + +static void codec_set_log(struct spa_log *global_log) +{ + log = global_log; + spa_log_topic_init(log, &codec_plugin_log_topic); +} + +const struct media_codec hfp_codec_msbc = { + .id = SPA_BLUETOOTH_AUDIO_CODEC_LC3_SWB, + .kind = MEDIA_CODEC_HFP, + .codec_id = 0x03, + .enum_config = codec_enum_config, + .validate_config = codec_validate_config, + .init = codec_init, + .deinit = codec_deinit, + .get_block_size = codec_get_block_size, + .start_encode = codec_start_encode, + .encode = codec_encode, + .set_log = codec_set_log, + .start_decode = codec_start_decode, + .decode = codec_decode, + .name = "lc3_swb", + .description = "LC3-SWB", +}; + +MEDIA_CODEC_EXPORT_DEF( + "hfp-lc3-swb", + &hfp_codec_msbc +); diff --git a/spa/plugins/bluez5/hfp-codec-msbc.c b/spa/plugins/bluez5/hfp-codec-msbc.c new file mode 100644 index 000000000..3a9112b6d --- /dev/null +++ b/spa/plugins/bluez5/hfp-codec-msbc.c @@ -0,0 +1,230 @@ +/* Spa HFP MSBC Codec */ +/* SPDX-FileCopyrightText: Copyright © 2019 Collabora Ltd. */ +/* SPDX-FileCopyrightText: Copyright © 2025 Pauli Virtanen */ +/* SPDX-License-Identifier: MIT */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "media-codecs.h" +#include "hfp-h2.h" + +#define MSBC_BLOCK_SIZE 240 + +static struct spa_log *log; + +struct impl { + sbc_t msbc; + struct h2_reader h2; + uint16_t seq; + + void *data; + size_t avail; +}; + +static int codec_enum_config(const struct media_codec *codec, uint32_t flags, + const void *caps, size_t caps_size, uint32_t id, uint32_t idx, + struct spa_pod_builder *b, struct spa_pod **param) +{ + struct spa_pod_frame f[1]; + const uint32_t position[SPA_AUDIO_MAX_CHANNELS] = { SPA_AUDIO_CHANNEL_MONO }; + const int channels = 1; + + spa_assert(caps == NULL && caps_size == 0); + + if (idx > 0) + return 0; + + spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, id); + spa_pod_builder_add(b, + SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), + SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_S16), + SPA_FORMAT_AUDIO_rate, SPA_POD_CHOICE_ENUM_Int(1, 16000), + SPA_FORMAT_AUDIO_channels, SPA_POD_Int(channels), + SPA_FORMAT_AUDIO_position, SPA_POD_Array(sizeof(uint32_t), + SPA_TYPE_Id, channels, position), + 0); + + *param = spa_pod_builder_pop(b, &f[0]); + return *param == NULL ? -EIO : 1; +} + +static int codec_validate_config(const struct media_codec *codec, uint32_t flags, + const void *caps, size_t caps_size, + struct spa_audio_info *info) +{ + spa_assert(caps == NULL && caps_size == 0); + + spa_zero(*info); + info->media_type = SPA_MEDIA_TYPE_audio; + info->media_subtype = SPA_MEDIA_SUBTYPE_raw; + info->info.raw.format = SPA_AUDIO_FORMAT_S16_LE; + info->info.raw.rate = 16000; + info->info.raw.channels = 1; + info->info.raw.position[0] = SPA_AUDIO_CHANNEL_MONO; + return 0; +} + +static void *codec_init(const struct media_codec *codec, uint32_t flags, + void *config, size_t config_len, const struct spa_audio_info *info, + void *props, size_t mtu) +{ + spa_autofree struct impl *this = NULL; + int res; + + spa_assert(config == NULL && config_len == 0 && props == NULL); + + this = calloc(1, sizeof(*this)); + if (!this) + return NULL; + + res = sbc_init_msbc(&this->msbc, 0); + if (res < 0) + return NULL; + + /* Libsbc expects audio samples by default in host endianness, mSBC requires little endian */ + this->msbc.endian = SBC_LE; + + h2_reader_init(&this->h2, true); + + return spa_steal_ptr(this); +} + +static void codec_deinit(void *data) +{ + struct impl *this = data; + + sbc_finish(&this->msbc); + free(this); +} + +static int codec_get_block_size(void *data) +{ + return MSBC_BLOCK_SIZE; +} + +static int codec_start_encode (void *data, void *dst, size_t dst_size, + uint16_t seqnum, uint32_t timestamp) +{ + struct impl *this = data; + + this->seq = seqnum; + return 0; +} + +static int codec_encode(void *data, + const void *src, size_t src_size, + void *dst, size_t dst_size, + size_t *dst_out, int *need_flush) +{ + struct impl *this = data; + ssize_t written = 0; + int res; + + if (src_size < MSBC_BLOCK_SIZE) + return -EINVAL; + if (dst_size < H2_PACKET_SIZE) + return -EINVAL; + + h2_write(dst, this->seq); + + res = sbc_encode(&this->msbc, src, src_size, SPA_PTROFF(dst, 2, void), H2_PACKET_SIZE - 3, &written); + if (res < 0) + return -EINVAL; + + *dst_out = H2_PACKET_SIZE; + *need_flush = NEED_FLUSH_ALL; + return res; +} + +static int codec_start_decode (void *data, + const void *src, size_t src_size, uint16_t *seqnum, uint32_t *timestamp) +{ + struct impl *this = data; + size_t consumed; + + if (is_zero_packet(src, src_size)) + return -EINVAL; + + if (!this->data) + this->data = h2_reader_read(&this->h2, src, src_size, &consumed, &this->avail); + + if (seqnum) + *seqnum = this->h2.seq; + if (timestamp) + *timestamp = 0; + return consumed; +} + +static int codec_decode(void *data, + const void *src, size_t src_size, + void *dst, size_t dst_size, + size_t *dst_out) +{ + struct impl *this = data; + size_t consumed = 0; + int res; + + *dst_out = 0; + + if (!this->data) + this->data = h2_reader_read(&this->h2, src, src_size, &consumed, &this->avail); + if (!this->data) + return consumed; + + res = sbc_decode(&this->msbc, this->data, this->avail, dst, dst_size, dst_out); + this->data = NULL; + + if (res < 0) { + h2_reader_init(&this->h2, true); + return res; + } + + return consumed; +} + +static void codec_set_log(struct spa_log *global_log) +{ + log = global_log; + spa_log_topic_init(log, &codec_plugin_log_topic); +} + +const struct media_codec hfp_codec_msbc = { + .id = SPA_BLUETOOTH_AUDIO_CODEC_MSBC, + .kind = MEDIA_CODEC_HFP, + .codec_id = 0x02, + .enum_config = codec_enum_config, + .validate_config = codec_validate_config, + .init = codec_init, + .deinit = codec_deinit, + .get_block_size = codec_get_block_size, + .start_encode = codec_start_encode, + .encode = codec_encode, + .set_log = codec_set_log, + .start_decode = codec_start_decode, + .decode = codec_decode, + .name = "msbc", + .description = "MSBC", +}; + +MEDIA_CODEC_EXPORT_DEF( + "hfp-msbc", + &hfp_codec_msbc +); diff --git a/spa/plugins/bluez5/hfp-h2.h b/spa/plugins/bluez5/hfp-h2.h new file mode 100644 index 000000000..064d5249d --- /dev/null +++ b/spa/plugins/bluez5/hfp-h2.h @@ -0,0 +1,128 @@ +/* Spa HFP Codecs */ +/* SPDX-FileCopyrightText: Copyright © 2025 Pauli Virtanen */ +/* SPDX-License-Identifier: MIT */ + +#ifndef SPA_BLUEZ5_HFP_H2_H +#define SPA_BLUEZ5_HFP_H2_H + +#define H2_PACKET_SIZE 60 + +struct h2_reader { + uint8_t buf[H2_PACKET_SIZE]; + uint8_t pos; + bool msbc; + uint16_t seq; + bool started; +}; + +struct h2_writer { + uint8_t seq; +}; + +static inline void h2_reader_init(struct h2_reader *this, bool msbc) +{ + this->pos = 0; + this->msbc = msbc; + this->seq = 0; + this->started = false; +} + +static inline void h2_reader_append_byte(struct h2_reader *this, uint8_t byte) +{ + /* Parse H2 sync header */ + if (this->pos == 0) { + if (byte != 0x01) { + this->pos = 0; + return; + } + } else if (this->pos == 1) { + if (!((byte & 0x0F) == 0x08 && + ((byte >> 4) & 1) == ((byte >> 5) & 1) && + ((byte >> 6) & 1) == ((byte >> 7) & 1))) { + this->pos = 0; + return; + } + } else if (this->msbc) { + /* Beginning of MSBC frame: SYNCWORD + 2 nul bytes */ + if (this->pos == 2) { + if (byte != 0xAD) { + this->pos = 0; + return; + } + } + else if (this->pos == 3) { + if (byte != 0x00) { + this->pos = 0; + return; + } + } + else if (this->pos == 4) { + if (byte != 0x00) { + this->pos = 0; + return; + } + } + } + + if (this->pos >= H2_PACKET_SIZE) { + /* Packet completed. Reset. */ + this->pos = 0; + h2_reader_append_byte(this, byte); + return; + } + + this->buf[this->pos] = byte; + ++this->pos; +} + +static inline void *h2_reader_read(struct h2_reader *this, const uint8_t *src, size_t src_size, size_t *consumed, size_t *avail) +{ + int seq; + size_t i; + + for (i = 0; i < src_size && this->pos < H2_PACKET_SIZE; ++i) + h2_reader_append_byte(this, src[i]); + + *consumed = i; + *avail = 0; + + if (this->pos < H2_PACKET_SIZE) + return NULL; + + this->pos = 0; + + seq = ((this->buf[1] >> 4) & 1) | ((this->buf[1] >> 6) & 2); + if (!this->started) { + this->seq = seq; + this->started = true; + } + + this->seq++; + while (seq != this->seq % 4) + this->seq++; + + *avail = H2_PACKET_SIZE - 2; + return &this->buf[2]; +} + +static inline void h2_write(uint8_t *buf, uint8_t seq) +{ + static const uint8_t sntable[4] = { 0x08, 0x38, 0xc8, 0xf8 }; + + buf[0] = 0x01; + buf[1] = sntable[seq % 4]; + buf[59] = 0; +} + +static inline bool is_zero_packet(const uint8_t *data, size_t size) +{ + size_t i; + + for (i = 0; i < size; ++i) + if (data[i]) + return false; + + return true; +} + +#endif diff --git a/spa/plugins/bluez5/media-codecs.c b/spa/plugins/bluez5/media-codecs.c index 00a0bbf08..bd24d08f8 100644 --- a/spa/plugins/bluez5/media-codecs.c +++ b/spa/plugins/bluez5/media-codecs.c @@ -81,6 +81,9 @@ bool media_codec_check_caps(const struct media_codec *codec, unsigned int codec_ uint8_t config[A2DP_MAX_CAPS_SIZE]; int res; + if (codec->kind == MEDIA_CODEC_HFP) + return true; + if (codec_id != codec->codec_id) return false; diff --git a/spa/plugins/bluez5/meson.build b/spa/plugins/bluez5/meson.build index 154dc9fe9..13c46ad87 100644 --- a/spa/plugins/bluez5/meson.build +++ b/spa/plugins/bluez5/meson.build @@ -100,6 +100,22 @@ bluez_codec_faststream = shared_library('spa-codec-bluez5-faststream', install : true, install_dir : spa_plugindir / 'bluez5') +bluez_codec_hfp_cvsd = shared_library('spa-codec-bluez5-hfp-cvsd', + [ 'hfp-codec-cvsd.c', 'media-codecs.c' ], + include_directories : [ configinc ], + c_args : codec_args, + dependencies : [ spa_dep ], + install : true, + install_dir : spa_plugindir / 'bluez5') + +bluez_codec_hfp_msbc = shared_library('spa-codec-bluez5-hfp-msbc', + [ 'hfp-codec-msbc.c', 'media-codecs.c' ], + include_directories : [ configinc ], + c_args : codec_args, + dependencies : [ spa_dep, sbc_dep ], + install : true, + install_dir : spa_plugindir / 'bluez5') + if fdk_aac_dep.found() bluez_codec_aac = shared_library('spa-codec-bluez5-aac', [ 'a2dp-codec-aac.c', 'media-codecs.c' ], @@ -175,6 +191,14 @@ if get_option('bluez5-codec-lc3').allowed() and lc3_dep.found() dependencies : [ spa_dep, lc3_dep, mathlib ], install : true, install_dir : spa_plugindir / 'bluez5') + + bluez_codec_hfp_lc3_swb = shared_library('spa-codec-bluez5-hfp-lc3-swb', + [ 'hfp-codec-lc3-swb.c', 'media-codecs.c' ], + include_directories : [ configinc ], + c_args : codec_args, + dependencies : [ spa_dep, lc3_dep, mathlib ], + install : true, + install_dir : spa_plugindir / 'bluez5') endif if get_option('bluez5-codec-g722').allowed()