From 87cb3ea4a1d3eab5d660c44f296262e3800d2af4 Mon Sep 17 00:00:00 2001 From: anonymix007 <48598263+anonymix007@users.noreply.github.com> Date: Thu, 23 Nov 2023 15:54:38 +0300 Subject: [PATCH] bluez5: add LHDC V3 A2DP vendor codec --- meson_options.txt | 4 + spa/include/spa/param/bluetooth/audio.h | 2 + spa/include/spa/param/bluetooth/type-info.h | 1 + spa/meson.build | 8 + spa/plugins/bluez5/a2dp-codec-caps.h | 64 +++ spa/plugins/bluez5/a2dp-codec-lhdc.c | 607 ++++++++++++++++++++ spa/plugins/bluez5/codec-loader.c | 2 + spa/plugins/bluez5/meson.build | 11 + 8 files changed, 699 insertions(+) create mode 100644 spa/plugins/bluez5/a2dp-codec-lhdc.c diff --git a/meson_options.txt b/meson_options.txt index 74ce3fa94..287a8bcaa 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -133,6 +133,10 @@ option('bluez5-codec-ldac-dec', description: 'Enable LDAC Sony open source codec decoding', type: 'feature', value: 'auto') +option('bluez5-codec-lhdc', + description: 'Enable LHDC open source codec implementation', + type: 'feature', + value: 'auto') option('bluez5-codec-aac', description: 'Enable Fraunhofer FDK AAC open source codec implementation', type: 'feature', diff --git a/spa/include/spa/param/bluetooth/audio.h b/spa/include/spa/param/bluetooth/audio.h index c95e22d6d..d9bba2b8c 100644 --- a/spa/include/spa/param/bluetooth/audio.h +++ b/spa/include/spa/param/bluetooth/audio.h @@ -25,6 +25,8 @@ enum spa_bluetooth_audio_codec { SPA_BLUETOOTH_AUDIO_CODEC_APTX, SPA_BLUETOOTH_AUDIO_CODEC_APTX_HD, SPA_BLUETOOTH_AUDIO_CODEC_LDAC, + SPA_BLUETOOTH_AUDIO_CODEC_LHDC_V3, + SPA_BLUETOOTH_AUDIO_CODEC_LHDC_V5, SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL, SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL_DUPLEX, SPA_BLUETOOTH_AUDIO_CODEC_FASTSTREAM, diff --git a/spa/include/spa/param/bluetooth/type-info.h b/spa/include/spa/param/bluetooth/type-info.h index 7d9cd3653..97f9cf9b3 100644 --- a/spa/include/spa/param/bluetooth/type-info.h +++ b/spa/include/spa/param/bluetooth/type-info.h @@ -29,6 +29,7 @@ static const struct spa_type_info spa_type_bluetooth_audio_codec[] = { { SPA_BLUETOOTH_AUDIO_CODEC_APTX, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "aptx", NULL }, { SPA_BLUETOOTH_AUDIO_CODEC_APTX_HD, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "aptx_hd", NULL }, { SPA_BLUETOOTH_AUDIO_CODEC_LDAC, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "ldac", NULL }, + { SPA_BLUETOOTH_AUDIO_CODEC_LHDC_V3, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "lhdc_v3", NULL }, { SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "aptx_ll", NULL }, { SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL_DUPLEX, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "aptx_ll_duplex", NULL }, { SPA_BLUETOOTH_AUDIO_CODEC_FASTSTREAM, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "faststream", NULL }, diff --git a/spa/meson.build b/spa/meson.build index 27e8ff049..599747879 100644 --- a/spa/meson.build +++ b/spa/meson.build @@ -99,6 +99,14 @@ if get_option('spa-plugins').allowed() endif endif summary({'LC3plus': lc3plus_dep.found()}, bool_yn: true, section: 'Bluetooth audio codecs') + lhdc_enc_dep = dependency('lhdcBT-enc', required : false) + if not lhdc_enc_dep.found() + lhdc_enc_lhdc_h_dep = cc.find_library('lhdcBT_enc', has_headers: ['lhdcBT.h'], required : false) + if lhdc_enc_lhdc_h_dep.found() + lhdc_enc_dep = declare_dependency(dependencies : [ lhdc_enc_lhdc_h_dep ]) + endif + endif + summary({'LHDC': lhdc_enc_dep.found()}, bool_yn: true, section: 'Bluetooth audio codecs') if get_option('bluez5-codec-opus').enabled() and not opus_dep.found() error('bluez5-codec-opus enabled, but opus dependency not found') endif diff --git a/spa/plugins/bluez5/a2dp-codec-caps.h b/spa/plugins/bluez5/a2dp-codec-caps.h index d775ce101..a7171cb90 100644 --- a/spa/plugins/bluez5/a2dp-codec-caps.h +++ b/spa/plugins/bluez5/a2dp-codec-caps.h @@ -192,6 +192,32 @@ #define LDAC_SAMPLING_FREQ_176400 0x02 #define LDAC_SAMPLING_FREQ_192000 0x01 +#define LHDC_V3_VENDOR_ID 0x0000053a +#define LHDC_V3_CODEC_ID 0x4c33 + +#define LHDC_V5_VENDOR_ID 0x0000053a +#define LHDC_V5_CODEC_ID 0x4c35 + +#define LHDC_CHANNEL_MODE_STEREO 0x03 + +#define LHDC_BIT_DEPTH_16 0x02 +#define LHDC_BIT_DEPTH_24 0x01 + +#define LHDC_VER3 0x01 + +#define LHDC_SAMPLING_FREQ_44100 0x08 +#define LHDC_SAMPLING_FREQ_48000 0x04 +#define LHDC_SAMPLING_FREQ_88200 0x02 +#define LHDC_SAMPLING_FREQ_96000 0x01 + +#define LHDC_MAX_BIT_RATE_400K 0x02 +#define LHDC_MAX_BIT_RATE_500K 0x01 +#define LHDC_MAX_BIT_RATE_900K 0x00 + +#define LHDC_CH_SPLIT_MODE_NONE 0x01 +#define LHDC_CH_SPLIT_MODE_TWS 0x02 +#define LHDC_CH_SPLIT_MODE_TWS_PLUS 0x04 + #define FASTSTREAM_VENDOR_ID 0x0000000a #define FASTSTREAM_CODEC_ID 0x0001 @@ -377,6 +403,44 @@ typedef struct { uint8_t source_frequency:4; } __attribute__ ((packed)) a2dp_faststream_t; +typedef struct { + a2dp_vendor_codec_t info; + uint8_t frequency:4; + uint8_t bit_depth:2; + uint8_t jas:1; + uint8_t ar:1; + uint8_t version:4; + uint8_t max_bit_rate:2; + uint8_t low_latency:1; + uint8_t llac:1; + uint8_t ch_split_mode:4; + uint8_t meta:1; + uint8_t min_bitrate:1; + uint8_t larc:1; + uint8_t lhdc_v4:1; +} __attribute__ ((packed)) a2dp_lhdc_v3_t; + +typedef struct { + a2dp_vendor_codec_t info; + uint8_t frequency:5; + uint8_t rfa1:3; + uint8_t bit_depth:3; + uint8_t rfa2:1; + uint8_t max_bit_rate:2; + uint8_t min_bit_rate:2; + uint8_t version:4; + uint8_t frame_len_5ms:1; + uint8_t rfa3:3; + uint8_t ar:1; + uint8_t jas:1; + uint8_t meta:1; + uint8_t rfa4:3; + uint8_t low_latency:1; + uint8_t reserved:1; // lossless? + uint8_t ar_on:1; + uint8_t rfa5:7; +} __attribute__ ((packed)) a2dp_lhdc_v5_t; + #elif __BYTE_ORDER == __BIG_ENDIAN typedef struct { diff --git a/spa/plugins/bluez5/a2dp-codec-lhdc.c b/spa/plugins/bluez5/a2dp-codec-lhdc.c new file mode 100644 index 000000000..dbb38f934 --- /dev/null +++ b/spa/plugins/bluez5/a2dp-codec-lhdc.c @@ -0,0 +1,607 @@ +/* Spa A2DP LHDC codec */ +/* SPDX-FileCopyrightText: Copyright © 2020 Wim Taymans */ +/* SPDX-FileCopyrightText: Copyright © 2025 anonymix007 */ +/* SPDX-License-Identifier: MIT */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "rtp.h" +#include "media-codecs.h" + +static struct spa_log *log; + +struct props_v3 { + LHDCBT_QUALITY_T eqmid; +}; + +struct rtp_lhdc_payload { + uint8_t seq_num; + uint8_t latency:2; + uint8_t frame_count:6; +}; + +static_assert(sizeof(struct rtp_lhdc_payload) == sizeof(uint16_t), "LHDC payload header must be 2 bytes"); + +struct impl_v3 { + HANDLE_LHDC_BT lhdc; + + struct rtp_header *header; + struct rtp_lhdc_payload *payload; + + int mtu; + int eqmid; + int frequency; + int bit_depth; + int codesize; + int block_size; + int frame_length; + int frame_count; + uint8_t seq_num; + int32_t buf[2][LHDCV2_BT_ENC_BLOCK_SIZE]; +}; + +static int codec_fill_caps_v3(const struct media_codec *codec, uint32_t flags, + const struct spa_dict *settings, uint8_t caps[A2DP_MAX_CAPS_SIZE]) +{ + static const a2dp_lhdc_v3_t a2dp_lhdc = { + .info.vendor_id = LHDC_V3_VENDOR_ID, + .info.codec_id = LHDC_V3_CODEC_ID, + .frequency = LHDC_SAMPLING_FREQ_44100 | LHDC_SAMPLING_FREQ_48000 | LHDC_SAMPLING_FREQ_96000, + .bit_depth = LHDC_BIT_DEPTH_16 | LHDC_BIT_DEPTH_24, + .jas = 0, + .ar = 0, + .version = LHDC_VER3, + .max_bit_rate = LHDC_MAX_BIT_RATE_900K, + .low_latency = 0, + .llac = 0, + .ch_split_mode = LHDC_CH_SPLIT_MODE_NONE, + .meta = 0, + .min_bitrate = 0, + .larc = 0, + .lhdc_v4 = 1, + }; + + memcpy(caps, &a2dp_lhdc, sizeof(a2dp_lhdc)); + return sizeof(a2dp_lhdc); +} + +static const struct media_codec_config +lhdc_frequencies_v3[] = { + { LHDC_SAMPLING_FREQ_44100, 44100, 0 }, + { LHDC_SAMPLING_FREQ_48000, 48000, 2 }, + { LHDC_SAMPLING_FREQ_96000, 96000, 1 }, +}; + +static int codec_select_config_v3(const struct media_codec *codec, uint32_t flags, + const void *caps, size_t caps_size, + const struct media_codec_audio_info *info, + const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE]) +{ + a2dp_lhdc_v3_t conf; + int i; + + if (caps_size < sizeof(conf)) + return -EINVAL; + + memcpy(&conf, caps, sizeof(conf)); + + if (codec->vendor.vendor_id != conf.info.vendor_id || + codec->vendor.codec_id != conf.info.codec_id) + return -ENOTSUP; + + if ((i = media_codec_select_config(lhdc_frequencies_v3, + SPA_N_ELEMENTS(lhdc_frequencies_v3), + conf.frequency, + info ? info->rate : A2DP_CODEC_DEFAULT_RATE + )) < 0) + return -ENOTSUP; + conf.frequency = lhdc_frequencies_v3[i].config; + + conf.low_latency = 0; + conf.llac = 0; + conf.lhdc_v4 = 1; + conf.bit_depth = LHDC_BIT_DEPTH_24; + + memcpy(config, &conf, sizeof(conf)); + + return sizeof(conf); +} + +static int codec_enum_config_v3(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) +{ + a2dp_lhdc_v3_t conf; + struct spa_pod_frame f[2]; + struct spa_pod_choice *choice; + uint32_t i = 0; + uint32_t position[SPA_AUDIO_MAX_CHANNELS]; + + if (caps_size < sizeof(conf)) + return -EINVAL; + + memcpy(&conf, caps, sizeof(conf)); + + 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_S32), + 0); + spa_pod_builder_prop(b, SPA_FORMAT_AUDIO_rate, 0); + + spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_None, 0); + choice = (struct spa_pod_choice*)spa_pod_builder_frame(b, &f[1]); + i = 0; + if (conf.frequency & LHDC_SAMPLING_FREQ_48000) { + if (i++ == 0) + spa_pod_builder_int(b, 48000); + spa_pod_builder_int(b, 48000); + } + if (conf.frequency & LHDC_SAMPLING_FREQ_44100) { + if (i++ == 0) + spa_pod_builder_int(b, 44100); + spa_pod_builder_int(b, 44100); + } + if (conf.frequency & LHDC_SAMPLING_FREQ_96000) { + if (i++ == 0) + spa_pod_builder_int(b, 96000); + spa_pod_builder_int(b, 96000); + } + if (i > 1) + choice->body.type = SPA_CHOICE_Enum; + spa_pod_builder_pop(b, &f[1]); + + if (i == 0) + return -EINVAL; + + + position[0] = SPA_AUDIO_CHANNEL_FL; + position[1] = SPA_AUDIO_CHANNEL_FR; + spa_pod_builder_add(b, + SPA_FORMAT_AUDIO_channels, SPA_POD_Int(2), + SPA_FORMAT_AUDIO_position, SPA_POD_Array(sizeof(uint32_t), + SPA_TYPE_Id, 2, position), + 0); + *param = spa_pod_builder_pop(b, &f[0]); + return *param == NULL ? -EIO : 1; +} + +static int codec_enum_config_v5(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) +{ + a2dp_lhdc_v5_t conf; + struct spa_pod_frame f[2]; + struct spa_pod_choice *choice; + uint32_t i = 0; + uint32_t position[SPA_AUDIO_MAX_CHANNELS]; + + if (caps_size < sizeof(conf)) + return -EINVAL; + + memcpy(&conf, caps, sizeof(conf)); + + 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_S32), + 0); + spa_pod_builder_prop(b, SPA_FORMAT_AUDIO_rate, 0); + + spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_None, 0); + choice = (struct spa_pod_choice*)spa_pod_builder_frame(b, &f[1]); + i = 0; + if (conf.frequency & LHDC_SAMPLING_FREQ_48000) { + if (i++ == 0) + spa_pod_builder_int(b, 48000); + spa_pod_builder_int(b, 48000); + } + if (conf.frequency & LHDC_SAMPLING_FREQ_44100) { + if (i++ == 0) + spa_pod_builder_int(b, 44100); + spa_pod_builder_int(b, 44100); + } + if (conf.frequency & LHDC_SAMPLING_FREQ_96000) { + if (i++ == 0) + spa_pod_builder_int(b, 96000); + spa_pod_builder_int(b, 96000); + } + if (i > 1) + choice->body.type = SPA_CHOICE_Enum; + spa_pod_builder_pop(b, &f[1]); + + if (i == 0) + return -EINVAL; + + + position[0] = SPA_AUDIO_CHANNEL_FL; + position[1] = SPA_AUDIO_CHANNEL_FR; + spa_pod_builder_add(b, + SPA_FORMAT_AUDIO_channels, SPA_POD_Int(2), + SPA_FORMAT_AUDIO_position, SPA_POD_Array(sizeof(uint32_t), + SPA_TYPE_Id, 2, position), + 0); + *param = spa_pod_builder_pop(b, &f[0]); + return *param == NULL ? -EIO : 1; +} + +static int codec_get_block_size_v3(void *data) +{ + struct impl_v3 *this = data; + return this->codesize; +} + +static const struct { const char *name; int v; } eqmids_v3[] = { + { "low0", .v = LHDCBT_QUALITY_LOW0 }, + { "low1", .v = LHDCBT_QUALITY_LOW1 }, + { "low2", .v = LHDCBT_QUALITY_LOW2 }, + { "low3", .v = LHDCBT_QUALITY_LOW3 }, + { "low4", .v = LHDCBT_QUALITY_LOW4 }, + { "low", .v = LHDCBT_QUALITY_LOW }, + { "mid", .v = LHDCBT_QUALITY_MID }, + { "high", .v = LHDCBT_QUALITY_HIGH }, + { "auto", .v = LHDCBT_QUALITY_AUTO }, + { 0 }, +}; + +static int string_to_eqmid_v3(const char * eqmid) +{ + for (size_t i = 0; eqmids_v3[i].name; i++) { + if (spa_streq(eqmids_v3[i].name, eqmid)) + return eqmids_v3[i].v; + } + return LHDCBT_QUALITY_AUTO; +} + +static void *codec_init_props_v3(const struct media_codec *codec, uint32_t flags, const struct spa_dict *settings) +{ + struct props_v3 *p = calloc(1, sizeof(struct props_v3)); + const char *str; + + if (p == NULL) + return NULL; + + if (settings == NULL || (str = spa_dict_lookup(settings, "bluez5.a2dp.lhdc_v3.quality")) == NULL) + str = "auto"; + + p->eqmid = string_to_eqmid_v3(str); + return p; +} + +static void codec_clear_props_v3(void *props) +{ + free(props); +} + +static int codec_enum_props_v3(void *props, const struct spa_dict *settings, uint32_t id, uint32_t idx, + struct spa_pod_builder *b, struct spa_pod **param) +{ + struct props_v3 *p = props; + struct spa_pod_frame f[2]; + switch (id) { + case SPA_PARAM_PropInfo: + { + switch (idx) { + case 0: + spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_PropInfo, id); + spa_pod_builder_prop(b, SPA_PROP_INFO_id, 0); + spa_pod_builder_id(b, SPA_PROP_quality); + spa_pod_builder_prop(b, SPA_PROP_INFO_description, 0); + spa_pod_builder_string(b, "LHDC quality"); + + spa_pod_builder_prop(b, SPA_PROP_INFO_type, 0); + spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_Enum, 0); + spa_pod_builder_int(b, p->eqmid); + for (size_t i = 0; eqmids_v3[i].name; i++) { + spa_pod_builder_int(b, eqmids_v3[i].v); + } + spa_pod_builder_pop(b, &f[1]); + + spa_pod_builder_prop(b, SPA_PROP_INFO_labels, 0); + spa_pod_builder_push_struct(b, &f[1]); + for (size_t i = 0; eqmids_v3[i].name; i++) { + spa_pod_builder_int(b, eqmids_v3[i].v); + spa_pod_builder_string(b, eqmids_v3[i].name); + } + spa_pod_builder_pop(b, &f[1]); + + *param = spa_pod_builder_pop(b, &f[0]); + break; + default: + return 0; + } + break; + } + case SPA_PARAM_Props: + { + switch (idx) { + case 0: + *param = spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_Props, id, + SPA_PROP_quality, SPA_POD_Int(p->eqmid)); + break; + default: + return 0; + } + break; + } + default: + return -ENOENT; + } + return 1; +} + +static int codec_set_props_v3(void *props, const struct spa_pod *param) +{ + struct props_v3 *p = props; + const LHDCBT_QUALITY_T prev_eqmid = p->eqmid; + if (param == NULL) { + p->eqmid = LHDCBT_QUALITY_AUTO; + } else { + spa_pod_parse_object(param, + SPA_TYPE_OBJECT_Props, NULL, + SPA_PROP_quality, SPA_POD_OPT_Int(&p->eqmid)); + if (p->eqmid > LHDCBT_QUALITY_AUTO || p->eqmid < LHDCBT_QUALITY_LOW0) + p->eqmid = prev_eqmid; + } + + return prev_eqmid != p->eqmid; +} + +static LHDC_VERSION_SETUP get_version_v3(const a2dp_lhdc_v3_t *configuration) { + if (configuration->llac) { + return LLAC; + } else if (configuration->lhdc_v4) { + return LHDC_V4; + } else { + return LHDC_V3; + } +} + +static int get_encoder_interval_v3(const a2dp_lhdc_v3_t *configuration) { + if (configuration->low_latency) { + return 10; + } else { + return 20; + } +} + +static int get_bit_depth_v3(const a2dp_lhdc_v3_t *configuration) { + if (configuration->bit_depth == LHDC_BIT_DEPTH_16) { + return 16; + } else { + return 24; + } +} + +static LHDCBT_QUALITY_T get_max_bitrate_v3(const a2dp_lhdc_v3_t *configuration) { + if (configuration->max_bit_rate == LHDC_MAX_BIT_RATE_400K) { + return LHDCBT_QUALITY_LOW; + } else if (configuration->max_bit_rate == LHDC_MAX_BIT_RATE_500K) { + return LHDCBT_QUALITY_MID; + } else { + return LHDCBT_QUALITY_HIGH; + } +} + +static void *codec_init_v3(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_v3 *this; + a2dp_lhdc_v3_t *conf = config; + int res; + struct props_v3 *p = props; + + this = calloc(1, sizeof(struct impl_v3)); + if (this == NULL) + goto error_errno; + + this->lhdc = lhdcBT_get_handle(get_version_v3(conf)); + if (this->lhdc == NULL) + goto error_errno; + + if (p == NULL) { + this->eqmid = LHDCBT_QUALITY_AUTO; + } else { + this->eqmid = p->eqmid; + } + + this->mtu = mtu; + this->frequency = info->info.raw.rate; + this->bit_depth = get_bit_depth_v3(conf); + + lhdcBT_set_hasMinBitrateLimit(this->lhdc, conf->min_bitrate); + lhdcBT_set_max_bitrate(this->lhdc, get_max_bitrate_v3(conf)); + + res = lhdcBT_init_encoder(this->lhdc, + this->frequency, + this->bit_depth, + this->eqmid, + conf->ch_split_mode > LHDC_CH_SPLIT_MODE_NONE, + 0, + this->mtu, + get_encoder_interval_v3(conf)); + if (res < 0) + goto error; + + this->block_size = lhdcBT_get_block_Size(this->lhdc); + this->codesize = info->info.raw.channels * lhdcBT_get_block_Size(this->lhdc); + + switch (info->info.raw.format) { + case SPA_AUDIO_FORMAT_S32: + this->codesize *= 4; + break; + default: + res = -EINVAL; + goto error; + } + + return this; + +error_errno: + res = -errno; +error: + if (this && this->lhdc) + lhdcBT_free_handle(this->lhdc); + free(this); + errno = -res; + return NULL; +} + +static void codec_deinit_v3(void *data) +{ + struct impl_v3 *this = data; + if (this->lhdc) + lhdcBT_free_handle(this->lhdc); + free(this); +} + +static int codec_update_props_v3(void *data, void *props) +{ + struct impl_v3 *this = data; + struct props_v3 *p = props; + int res; + + if (p == NULL) + return 0; + + this->eqmid = p->eqmid; + + if ((res = lhdcBT_set_bitrate(this->lhdc, this->eqmid)) < 0) + goto error; + return 0; +error: + return res; +} + +static int codec_abr_process_v3(void *data, size_t unsent) +{ + struct impl_v3 *this = data; + return this->eqmid == LHDCBT_QUALITY_AUTO ? lhdcBT_adjust_bitrate(this->lhdc, unsent / this->mtu) : -ENOTSUP; +} + +static int codec_start_encode_v3(void *data, + void *dst, size_t dst_size, uint16_t seqnum, uint32_t timestamp) +{ + struct impl_v3 *this = data; + + this->header = (struct rtp_header *)dst; + this->payload = SPA_PTROFF(dst, sizeof(struct rtp_header), struct rtp_lhdc_payload); + memset(this->header, 0, sizeof(struct rtp_header)+sizeof(struct rtp_lhdc_payload)); + + this->payload->frame_count = 0; + this->header->v = 2; + this->header->pt = 96; + this->header->sequence_number = htons(seqnum); + this->header->timestamp = htonl(timestamp); + this->header->ssrc = htonl(1); + return sizeof(struct rtp_header) + sizeof(struct rtp_lhdc_payload); +} + +static void deinterleave_32_c2(int32_t * SPA_RESTRICT * SPA_RESTRICT dst, const int32_t * SPA_RESTRICT src, size_t n_samples) +{ + /* We'll trust the compiler to optimize this */ + const size_t n_channels = 2; + size_t i, j; + for (j = 0; j < n_samples; ++j) + for (i = 0; i < n_channels; ++i) + dst[i][j] = *src++; +} + +static int codec_encode_v3(void *data, + const void *src, size_t src_size, + void *dst, size_t dst_size, + size_t *dst_out, int *need_flush) +{ + struct impl_v3 *this = data; + int res, src_used; + uint32_t dst_used, frame_num = 0; + int32_t *inputs[2] = { this->buf[0], inputs[1] = this->buf[1] }; + + src_used = this->codesize; + dst_used = dst_size; + + deinterleave_32_c2(inputs, src, this->block_size); + + res = lhdcBT_encode_stereo(this->lhdc, inputs[0], inputs[1], dst, &dst_used, &frame_num); + if (SPA_UNLIKELY(res < 0)) + return -EINVAL; + + *dst_out = dst_used; + + this->payload->frame_count += frame_num; + + *need_flush = (this->payload->frame_count > 0) ? NEED_FLUSH_ALL : NEED_FLUSH_NO; + + if (this->payload->frame_count > 0) + this->payload->seq_num = this->seq_num++; + + return src_used; +} + +static int codec_reduce_bitpool(void *data) +{ + return -ENOTSUP; +} + +static int codec_increase_bitpool(void *data) +{ + return -ENOTSUP; +} + +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 a2dp_codec_lhdc_v3 = { + .id = SPA_BLUETOOTH_AUDIO_CODEC_LHDC_V3, + .codec_id = A2DP_CODEC_VENDOR, + .vendor = { .vendor_id = LHDC_V3_VENDOR_ID, + .codec_id = LHDC_V3_CODEC_ID }, + .name = "lhdc_v3", + .description = "LHDC V3", + .fill_caps = codec_fill_caps_v3, + .select_config = codec_select_config_v3, + .enum_config = codec_enum_config_v3, + .init_props = codec_init_props_v3, + .enum_props = codec_enum_props_v3, + .set_props = codec_set_props_v3, + .clear_props = codec_clear_props_v3, + .init = codec_init_v3, + .deinit = codec_deinit_v3, + .update_props = codec_update_props_v3, + .get_block_size = codec_get_block_size_v3, + .abr_process = codec_abr_process_v3, + .start_encode = codec_start_encode_v3, + .encode = codec_encode_v3, + .reduce_bitpool = codec_reduce_bitpool, + .increase_bitpool = codec_increase_bitpool +}; + +MEDIA_CODEC_EXPORT_DEF( + "lhdc", + &a2dp_codec_lhdc_v3 +); diff --git a/spa/plugins/bluez5/codec-loader.c b/spa/plugins/bluez5/codec-loader.c index 6fd1d0430..18539c825 100644 --- a/spa/plugins/bluez5/codec-loader.c +++ b/spa/plugins/bluez5/codec-loader.c @@ -32,6 +32,7 @@ static int codec_order(const struct media_codec *c) { static const enum spa_bluetooth_audio_codec order[] = { SPA_BLUETOOTH_AUDIO_CODEC_LC3, + SPA_BLUETOOTH_AUDIO_CODEC_LHDC_V3, SPA_BLUETOOTH_AUDIO_CODEC_LDAC, SPA_BLUETOOTH_AUDIO_CODEC_APTX_HD, SPA_BLUETOOTH_AUDIO_CODEC_APTX, @@ -169,6 +170,7 @@ const struct media_codec * const *load_media_codecs(struct spa_plugin_loader *lo MEDIA_CODEC_FACTORY_LIB("aptx"), MEDIA_CODEC_FACTORY_LIB("faststream"), MEDIA_CODEC_FACTORY_LIB("ldac"), + MEDIA_CODEC_FACTORY_LIB("lhdc"), MEDIA_CODEC_FACTORY_LIB("sbc"), MEDIA_CODEC_FACTORY_LIB("lc3plus"), MEDIA_CODEC_FACTORY_LIB("opus"), diff --git a/spa/plugins/bluez5/meson.build b/spa/plugins/bluez5/meson.build index 154dc9fe9..9d0ead93b 100644 --- a/spa/plugins/bluez5/meson.build +++ b/spa/plugins/bluez5/meson.build @@ -138,6 +138,17 @@ if ldac_dep.found() install_dir : spa_plugindir / 'bluez5') endif +if lhdc_enc_dep.found() + lhdc_enc_args = codec_args + bluez_codec_ldac = shared_library('spa-codec-bluez5-lhdc', + [ 'a2dp-codec-lhdc.c', 'media-codecs.c' ], + include_directories : [ configinc ], + c_args : lhdc_enc_args, + dependencies : [ spa_dep, lhdc_enc_dep ], + install : true, + install_dir : spa_plugindir / 'bluez5') +endif + if get_option('bluez5-codec-lc3plus').allowed() and lc3plus_dep.found() bluez_codec_lc3plus = shared_library('spa-codec-bluez5-lc3plus', [ 'a2dp-codec-lc3plus.c', 'media-codecs.c' ],