mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-10-29 05:40:27 -04:00
bluez5: add LHDC V5 A2DP decoder
This commit is contained in:
parent
a9ba34da23
commit
f2b3b63f21
6 changed files with 318 additions and 20 deletions
|
|
@ -30,6 +30,7 @@ static const struct spa_type_info spa_type_bluetooth_audio_codec[] = {
|
||||||
{ SPA_BLUETOOTH_AUDIO_CODEC_APTX_HD, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "aptx_hd", 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_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_LHDC_V3, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "lhdc_v3", NULL },
|
||||||
|
{ SPA_BLUETOOTH_AUDIO_CODEC_LHDC_V5, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "lhdc_v5", 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, 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_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 },
|
{ SPA_BLUETOOTH_AUDIO_CODEC_FASTSTREAM, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "faststream", NULL },
|
||||||
|
|
|
||||||
|
|
@ -117,6 +117,24 @@ if get_option('spa-plugins').allowed()
|
||||||
endif
|
endif
|
||||||
summary({'LHDC V3 Decoder': lhdc_enc_dep.found()}, bool_yn: true, section: 'Bluetooth audio codecs')
|
summary({'LHDC V3 Decoder': lhdc_enc_dep.found()}, bool_yn: true, section: 'Bluetooth audio codecs')
|
||||||
|
|
||||||
|
lhdc_v5_enc_dep = dependency('lhdcv5BT-enc', required : false)
|
||||||
|
if not lhdc_v5_enc_dep.found()
|
||||||
|
lhdc_v5_enc_lhdc_h_dep = cc.find_library('lhdcv5BT_enc', has_headers: ['lhdcv5BT.h'], required : false)
|
||||||
|
if lhdc_v5_enc_lhdc_h_dep.found()
|
||||||
|
lhdc_v5_enc_dep = declare_dependency(dependencies : [ lhdc_v5_enc_lhdc_h_dep ])
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
summary({'LHDC V5 Encoder': lhdc_v5_enc_dep.found()}, bool_yn: true, section: 'Bluetooth audio codecs')
|
||||||
|
|
||||||
|
lhdc_v5_dec_dep = dependency('lhdcv5BT-dec', required : false)
|
||||||
|
if not lhdc_v5_dec_dep.found()
|
||||||
|
lhdc_v5_dec_lhdc_h_dep = cc.find_library('lhdcv5BT_dec', has_headers: ['lhdcv5BT_dec.h'], required : false)
|
||||||
|
if lhdc_v5_dec_lhdc_h_dep.found()
|
||||||
|
lhdc_v5_dec_dep = declare_dependency(dependencies : [ lhdc_v5_dec_lhdc_h_dep ])
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
summary({'LHDC V5 Decoder': lhdc_v5_enc_dep.found()}, bool_yn: true, section: 'Bluetooth audio codecs')
|
||||||
|
|
||||||
if get_option('bluez5-codec-opus').enabled() and not opus_dep.found()
|
if get_option('bluez5-codec-opus').enabled() and not opus_dep.found()
|
||||||
error('bluez5-codec-opus enabled, but opus dependency not found')
|
error('bluez5-codec-opus enabled, but opus dependency not found')
|
||||||
endif
|
endif
|
||||||
|
|
|
||||||
|
|
@ -218,6 +218,27 @@
|
||||||
#define LHDC_CH_SPLIT_MODE_TWS 0x02
|
#define LHDC_CH_SPLIT_MODE_TWS 0x02
|
||||||
#define LHDC_CH_SPLIT_MODE_TWS_PLUS 0x04
|
#define LHDC_CH_SPLIT_MODE_TWS_PLUS 0x04
|
||||||
|
|
||||||
|
#define LHDCV5_SAMPLING_FREQ_44100 (1 << 5)
|
||||||
|
#define LHDCV5_SAMPLING_FREQ_48000 (1 << 4)
|
||||||
|
#define LHDCV5_SAMPLING_FREQ_96000 (1 << 2)
|
||||||
|
#define LHDCV5_SAMPLING_FREQ_192000 (1 << 0)
|
||||||
|
|
||||||
|
#define LHDCV5_BIT_DEPTH_16 (1 << 2)
|
||||||
|
#define LHDCV5_BIT_DEPTH_24 (1 << 1)
|
||||||
|
#define LHDCV5_BIT_DEPTH_32 (1 << 0)
|
||||||
|
|
||||||
|
#define LHDCV5_MAX_BITRATE_900K (3)
|
||||||
|
#define LHDCV5_MAX_BITRATE_500K (2)
|
||||||
|
#define LHDCV5_MAX_BITRATE_400K (1)
|
||||||
|
#define LHDCV5_MAX_BITRATE_1000K (0)
|
||||||
|
|
||||||
|
#define LHDCV5_MIN_BITRATE_400K (3)
|
||||||
|
#define LHDCV5_MIN_BITRATE_256K (2)
|
||||||
|
#define LHDCV5_MIN_BITRATE_160K (1)
|
||||||
|
#define LHDCV5_MIN_BITRATE_64K (0)
|
||||||
|
|
||||||
|
#define LHDCV5_VER_1 (1 << 0)
|
||||||
|
|
||||||
#define FASTSTREAM_VENDOR_ID 0x0000000a
|
#define FASTSTREAM_VENDOR_ID 0x0000000a
|
||||||
#define FASTSTREAM_CODEC_ID 0x0001
|
#define FASTSTREAM_CODEC_ID 0x0001
|
||||||
|
|
||||||
|
|
@ -422,23 +443,25 @@ typedef struct {
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
a2dp_vendor_codec_t info;
|
a2dp_vendor_codec_t info;
|
||||||
uint8_t frequency:5;
|
uint8_t sampling_freq:6;
|
||||||
uint8_t rfa1:3;
|
uint8_t rfa1:2;
|
||||||
uint8_t bit_depth:3;
|
uint8_t bit_depth:3;
|
||||||
uint8_t rfa2:1;
|
uint8_t rfa2:1;
|
||||||
uint8_t max_bit_rate:2;
|
uint8_t max_bitrate:2;
|
||||||
uint8_t min_bit_rate:2;
|
uint8_t min_bitrate:2;
|
||||||
uint8_t version:4;
|
uint8_t version:4;
|
||||||
uint8_t frame_len_5ms:1;
|
uint8_t frame_len_5ms:1;
|
||||||
uint8_t rfa3:3;
|
uint8_t rfa3:3;
|
||||||
uint8_t ar:1;
|
uint8_t ar:1;
|
||||||
uint8_t jas:1;
|
uint8_t jas:1;
|
||||||
uint8_t meta:1;
|
uint8_t meta:1;
|
||||||
uint8_t rfa4:3;
|
uint8_t rfa4:1;
|
||||||
|
uint8_t lossless_96k:1;
|
||||||
|
uint8_t lossless_24b:1;
|
||||||
uint8_t low_latency:1;
|
uint8_t low_latency:1;
|
||||||
uint8_t reserved:1; // lossless?
|
uint8_t lossless_48k:1;
|
||||||
uint8_t ar_on:1;
|
|
||||||
uint8_t rfa5:7;
|
uint8_t rfa5:7;
|
||||||
|
uint8_t lossless_raw_48k:1;
|
||||||
} __attribute__ ((packed)) a2dp_lhdc_v5_t;
|
} __attribute__ ((packed)) a2dp_lhdc_v5_t;
|
||||||
|
|
||||||
#elif __BYTE_ORDER == __BIG_ENDIAN
|
#elif __BYTE_ORDER == __BIG_ENDIAN
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@
|
||||||
#include <lhdcBT.h>
|
#include <lhdcBT.h>
|
||||||
#include <lhdcBT_dec.h>
|
#include <lhdcBT_dec.h>
|
||||||
|
|
||||||
|
#include <lhdcv5BT_dec.h>
|
||||||
|
|
||||||
#include "rtp.h"
|
#include "rtp.h"
|
||||||
#include "media-codecs.h"
|
#include "media-codecs.h"
|
||||||
|
|
||||||
|
|
@ -50,12 +52,25 @@ struct impl_v3 {
|
||||||
int bit_depth;
|
int bit_depth;
|
||||||
int codesize;
|
int codesize;
|
||||||
int block_size;
|
int block_size;
|
||||||
int frame_length;
|
|
||||||
int frame_count;
|
|
||||||
uint8_t seq_num;
|
uint8_t seq_num;
|
||||||
int32_t buf[2][LHDCV2_BT_ENC_BLOCK_SIZE];
|
int32_t buf[2][LHDCV2_BT_ENC_BLOCK_SIZE];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct impl_v5 {
|
||||||
|
HANDLE_LHDCV5_BT dec;
|
||||||
|
|
||||||
|
struct rtp_header *header;
|
||||||
|
struct rtp_lhdc_payload *payload;
|
||||||
|
|
||||||
|
int mtu;
|
||||||
|
int eqmid;
|
||||||
|
int frequency;
|
||||||
|
int bit_depth;
|
||||||
|
int frame_samples;
|
||||||
|
uint8_t seq_num;
|
||||||
|
int32_t buf[2][LHDCV5_MAX_SAMPLE_FRAME];
|
||||||
|
};
|
||||||
|
|
||||||
static int codec_fill_caps_v3(const struct media_codec *codec, uint32_t flags,
|
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])
|
const struct spa_dict *settings, uint8_t caps[A2DP_MAX_CAPS_SIZE])
|
||||||
{
|
{
|
||||||
|
|
@ -81,6 +96,32 @@ static int codec_fill_caps_v3(const struct media_codec *codec, uint32_t flags,
|
||||||
return sizeof(a2dp_lhdc);
|
return sizeof(a2dp_lhdc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int codec_fill_caps_v5(const struct media_codec *codec, uint32_t flags,
|
||||||
|
const struct spa_dict *settings, uint8_t caps[A2DP_MAX_CAPS_SIZE])
|
||||||
|
{
|
||||||
|
static const a2dp_lhdc_v5_t a2dp_lhdc = {
|
||||||
|
.info.vendor_id = LHDC_V5_VENDOR_ID,
|
||||||
|
.info.codec_id = LHDC_V5_CODEC_ID,
|
||||||
|
.sampling_freq = LHDCV5_SAMPLING_FREQ_44100 | LHDCV5_SAMPLING_FREQ_48000 | LHDCV5_SAMPLING_FREQ_96000,
|
||||||
|
.bit_depth = LHDCV5_BIT_DEPTH_16 | LHDCV5_BIT_DEPTH_24,
|
||||||
|
.max_bitrate = LHDCV5_MAX_BITRATE_1000K,
|
||||||
|
.min_bitrate = LHDCV5_MIN_BITRATE_64K,
|
||||||
|
.version = LHDCV5_VER_1,
|
||||||
|
.frame_len_5ms = 1,
|
||||||
|
.ar = 0,
|
||||||
|
.jas = 0,
|
||||||
|
.meta = 0,
|
||||||
|
.lossless_96k = 0,
|
||||||
|
.lossless_24b = 0,
|
||||||
|
.low_latency = 0,
|
||||||
|
.lossless_48k = 0,
|
||||||
|
.lossless_raw_48k = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
memcpy(caps, &a2dp_lhdc, sizeof(a2dp_lhdc));
|
||||||
|
return sizeof(a2dp_lhdc);
|
||||||
|
}
|
||||||
|
|
||||||
static const struct media_codec_config
|
static const struct media_codec_config
|
||||||
lhdc_frequencies_v3[] = {
|
lhdc_frequencies_v3[] = {
|
||||||
{ LHDC_SAMPLING_FREQ_44100, 44100, 0 },
|
{ LHDC_SAMPLING_FREQ_44100, 44100, 0 },
|
||||||
|
|
@ -88,6 +129,13 @@ lhdc_frequencies_v3[] = {
|
||||||
{ LHDC_SAMPLING_FREQ_96000, 96000, 1 },
|
{ LHDC_SAMPLING_FREQ_96000, 96000, 1 },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static const struct media_codec_config
|
||||||
|
lhdc_frequencies_v5[] = {
|
||||||
|
{ LHDCV5_SAMPLING_FREQ_44100, 44100, 0 },
|
||||||
|
{ LHDCV5_SAMPLING_FREQ_48000, 48000, 2 },
|
||||||
|
{ LHDCV5_SAMPLING_FREQ_96000, 96000, 1 },
|
||||||
|
};
|
||||||
|
|
||||||
static int codec_select_config_v3(const struct media_codec *codec, uint32_t flags,
|
static int codec_select_config_v3(const struct media_codec *codec, uint32_t flags,
|
||||||
const void *caps, size_t caps_size,
|
const void *caps, size_t caps_size,
|
||||||
const struct media_codec_audio_info *info,
|
const struct media_codec_audio_info *info,
|
||||||
|
|
@ -123,6 +171,38 @@ static int codec_select_config_v3(const struct media_codec *codec, uint32_t flag
|
||||||
return sizeof(conf);
|
return sizeof(conf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int codec_select_config_v5(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_v5_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_v5,
|
||||||
|
SPA_N_ELEMENTS(lhdc_frequencies_v5),
|
||||||
|
conf.sampling_freq,
|
||||||
|
info ? info->rate : A2DP_CODEC_DEFAULT_RATE
|
||||||
|
)) < 0)
|
||||||
|
return -ENOTSUP;
|
||||||
|
conf.sampling_freq = lhdc_frequencies_v5[i].config;
|
||||||
|
|
||||||
|
conf.bit_depth = LHDCV5_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,
|
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,
|
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_builder *b, struct spa_pod **param)
|
||||||
|
|
@ -174,7 +254,6 @@ static int codec_enum_config_v3(const struct media_codec *codec, uint32_t flags,
|
||||||
if (i == 0)
|
if (i == 0)
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
||||||
|
|
||||||
position[0] = SPA_AUDIO_CHANNEL_FL;
|
position[0] = SPA_AUDIO_CHANNEL_FL;
|
||||||
position[1] = SPA_AUDIO_CHANNEL_FR;
|
position[1] = SPA_AUDIO_CHANNEL_FR;
|
||||||
spa_pod_builder_add(b,
|
spa_pod_builder_add(b,
|
||||||
|
|
@ -215,17 +294,17 @@ static int codec_enum_config_v5(const struct media_codec *codec, uint32_t flags,
|
||||||
spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_None, 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]);
|
choice = (struct spa_pod_choice*)spa_pod_builder_frame(b, &f[1]);
|
||||||
i = 0;
|
i = 0;
|
||||||
if (conf.frequency & LHDC_SAMPLING_FREQ_48000) {
|
if (conf.sampling_freq & LHDCV5_SAMPLING_FREQ_48000) {
|
||||||
if (i++ == 0)
|
if (i++ == 0)
|
||||||
spa_pod_builder_int(b, 48000);
|
spa_pod_builder_int(b, 48000);
|
||||||
spa_pod_builder_int(b, 48000);
|
spa_pod_builder_int(b, 48000);
|
||||||
}
|
}
|
||||||
if (conf.frequency & LHDC_SAMPLING_FREQ_44100) {
|
if (conf.sampling_freq & LHDCV5_SAMPLING_FREQ_44100) {
|
||||||
if (i++ == 0)
|
if (i++ == 0)
|
||||||
spa_pod_builder_int(b, 44100);
|
spa_pod_builder_int(b, 44100);
|
||||||
spa_pod_builder_int(b, 44100);
|
spa_pod_builder_int(b, 44100);
|
||||||
}
|
}
|
||||||
if (conf.frequency & LHDC_SAMPLING_FREQ_96000) {
|
if (conf.sampling_freq & LHDCV5_SAMPLING_FREQ_96000) {
|
||||||
if (i++ == 0)
|
if (i++ == 0)
|
||||||
spa_pod_builder_int(b, 96000);
|
spa_pod_builder_int(b, 96000);
|
||||||
spa_pod_builder_int(b, 96000);
|
spa_pod_builder_int(b, 96000);
|
||||||
|
|
@ -237,7 +316,6 @@ static int codec_enum_config_v5(const struct media_codec *codec, uint32_t flags,
|
||||||
if (i == 0)
|
if (i == 0)
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
||||||
|
|
||||||
position[0] = SPA_AUDIO_CHANNEL_FL;
|
position[0] = SPA_AUDIO_CHANNEL_FL;
|
||||||
position[1] = SPA_AUDIO_CHANNEL_FR;
|
position[1] = SPA_AUDIO_CHANNEL_FR;
|
||||||
spa_pod_builder_add(b,
|
spa_pod_builder_add(b,
|
||||||
|
|
@ -255,6 +333,12 @@ static int codec_get_block_size_v3(void *data)
|
||||||
return this->codesize;
|
return this->codesize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int codec_get_block_size_v5(void *data)
|
||||||
|
{
|
||||||
|
struct impl_v5 *this = data;
|
||||||
|
return this->frame_samples * 4 * 2;
|
||||||
|
}
|
||||||
|
|
||||||
static const struct { const char *name; int v; } eqmids_v3[] = {
|
static const struct { const char *name; int v; } eqmids_v3[] = {
|
||||||
{ "low0", .v = LHDCBT_QUALITY_LOW0 },
|
{ "low0", .v = LHDCBT_QUALITY_LOW0 },
|
||||||
{ "low1", .v = LHDCBT_QUALITY_LOW1 },
|
{ "low1", .v = LHDCBT_QUALITY_LOW1 },
|
||||||
|
|
@ -292,9 +376,13 @@ static void *codec_init_props_v3(const struct media_codec *codec, uint32_t flags
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void *codec_init_props_v5(const struct media_codec *codec, uint32_t flags, const struct spa_dict *settings)
|
||||||
|
{
|
||||||
|
return malloc(1);
|
||||||
|
}
|
||||||
|
|
||||||
static void codec_clear_props_v3(void *props)
|
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,
|
static int codec_enum_props_v3(void *props, const struct spa_dict *settings, uint32_t id, uint32_t idx,
|
||||||
|
|
@ -355,6 +443,11 @@ static int codec_enum_props_v3(void *props, const struct spa_dict *settings, uin
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int codec_enum_props_v5(void *props, const struct spa_dict *settings, uint32_t id, uint32_t idx,
|
||||||
|
struct spa_pod_builder *b, struct spa_pod **param) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int codec_set_props_v3(void *props, const struct spa_pod *param)
|
static int codec_set_props_v3(void *props, const struct spa_pod *param)
|
||||||
{
|
{
|
||||||
struct props_v3 *p = props;
|
struct props_v3 *p = props;
|
||||||
|
|
@ -372,6 +465,11 @@ static int codec_set_props_v3(void *props, const struct spa_pod *param)
|
||||||
return prev_eqmid != p->eqmid;
|
return prev_eqmid != p->eqmid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int codec_set_props_v5(void *props, const struct spa_pod *param)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static LHDC_VERSION_SETUP get_version_v3(const a2dp_lhdc_v3_t *configuration) {
|
static LHDC_VERSION_SETUP get_version_v3(const a2dp_lhdc_v3_t *configuration) {
|
||||||
if (configuration->llac) {
|
if (configuration->llac) {
|
||||||
return LLAC;
|
return LLAC;
|
||||||
|
|
@ -398,6 +496,14 @@ static int get_bit_depth_v3(const a2dp_lhdc_v3_t *configuration) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int get_bit_depth_v5(const a2dp_lhdc_v5_t *configuration) {
|
||||||
|
if (configuration->bit_depth == LHDCV5_BIT_DEPTH_16) {
|
||||||
|
return 16;
|
||||||
|
} else {
|
||||||
|
return 24;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static LHDCBT_QUALITY_T get_max_bitrate_v3(const a2dp_lhdc_v3_t *configuration) {
|
static LHDCBT_QUALITY_T get_max_bitrate_v3(const a2dp_lhdc_v3_t *configuration) {
|
||||||
if (configuration->max_bit_rate == LHDC_MAX_BIT_RATE_400K) {
|
if (configuration->max_bit_rate == LHDC_MAX_BIT_RATE_400K) {
|
||||||
return LHDCBT_QUALITY_LOW;
|
return LHDCBT_QUALITY_LOW;
|
||||||
|
|
@ -496,6 +602,47 @@ error:
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void *codec_init_v5(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_v5 *this;
|
||||||
|
a2dp_lhdc_v5_t *conf = config;
|
||||||
|
int res;
|
||||||
|
|
||||||
|
this = calloc(1, sizeof(struct impl_v5));
|
||||||
|
if (this == NULL)
|
||||||
|
goto error_errno;
|
||||||
|
|
||||||
|
this->mtu = mtu;
|
||||||
|
this->frequency = info->info.raw.rate;
|
||||||
|
this->bit_depth = get_bit_depth_v5(conf);
|
||||||
|
|
||||||
|
tLHDCV5_DEC_CONFIG dec_config = {
|
||||||
|
.version = VERSION_5,
|
||||||
|
.sample_rate = this->frequency,
|
||||||
|
.bits_depth = this->bit_depth,
|
||||||
|
.bit_rate = LHDCV5BT_BIT_RATE_1000K,
|
||||||
|
.lossless_enable = 0,
|
||||||
|
.lossless_raw_enable = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
if ((res = lhdcv5BT_dec_init_decoder(&this->dec, &dec_config)) < 0)
|
||||||
|
goto error;
|
||||||
|
|
||||||
|
this->frame_samples = (50 * (this->frequency == 44100 ? 48000 : this->frequency)) / 10000;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
error_errno:
|
||||||
|
res = -errno;
|
||||||
|
error:
|
||||||
|
if (this && this->dec)
|
||||||
|
lhdcv5BT_dec_deinit_decoder(this->dec);
|
||||||
|
free(this);
|
||||||
|
errno = -res;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
static void codec_deinit_v3(void *data)
|
static void codec_deinit_v3(void *data)
|
||||||
{
|
{
|
||||||
struct impl_v3 *this = data;
|
struct impl_v3 *this = data;
|
||||||
|
|
@ -506,6 +653,14 @@ static void codec_deinit_v3(void *data)
|
||||||
free(this);
|
free(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void codec_deinit_v5(void *data)
|
||||||
|
{
|
||||||
|
struct impl_v5 *this = data;
|
||||||
|
if (this->dec)
|
||||||
|
lhdcv5BT_dec_deinit_decoder(this->dec);
|
||||||
|
free(this);
|
||||||
|
}
|
||||||
|
|
||||||
static int codec_update_props_v3(void *data, void *props)
|
static int codec_update_props_v3(void *data, void *props)
|
||||||
{
|
{
|
||||||
struct impl_v3 *this = data;
|
struct impl_v3 *this = data;
|
||||||
|
|
@ -524,12 +679,23 @@ error:
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int codec_update_props_v5(void *data, void *props)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int codec_abr_process_v3(void *data, size_t unsent)
|
static int codec_abr_process_v3(void *data, size_t unsent)
|
||||||
{
|
{
|
||||||
struct impl_v3 *this = data;
|
struct impl_v3 *this = data;
|
||||||
return this->eqmid == LHDCBT_QUALITY_AUTO ? lhdcBT_adjust_bitrate(this->lhdc, unsent / this->mtu) : -ENOTSUP;
|
return this->eqmid == LHDCBT_QUALITY_AUTO ? lhdcBT_adjust_bitrate(this->lhdc, unsent / this->mtu) : -ENOTSUP;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int codec_abr_process_v5(void *data, size_t unsent)
|
||||||
|
{
|
||||||
|
return -ENOTSUP;
|
||||||
|
}
|
||||||
|
|
||||||
static int codec_start_encode_v3(void *data,
|
static int codec_start_encode_v3(void *data,
|
||||||
void *dst, size_t dst_size, uint16_t seqnum, uint32_t timestamp)
|
void *dst, size_t dst_size, uint16_t seqnum, uint32_t timestamp)
|
||||||
{
|
{
|
||||||
|
|
@ -548,7 +714,11 @@ static int codec_start_encode_v3(void *data,
|
||||||
return sizeof(struct rtp_header) + sizeof(struct rtp_lhdc_payload);
|
return sizeof(struct rtp_header) + sizeof(struct rtp_lhdc_payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int codec_start_encode_v5(void *data,
|
||||||
|
void *dst, size_t dst_size, uint16_t seqnum, uint32_t timestamp)
|
||||||
|
{
|
||||||
|
return -ENOTSUP;
|
||||||
|
}
|
||||||
|
|
||||||
static void deinterleave_32_c2(int32_t * SPA_RESTRICT * SPA_RESTRICT dst, const int32_t * SPA_RESTRICT src, size_t n_samples)
|
static void deinterleave_32_c2(int32_t * SPA_RESTRICT * SPA_RESTRICT dst, const int32_t * SPA_RESTRICT src, size_t n_samples)
|
||||||
{
|
{
|
||||||
|
|
@ -590,6 +760,13 @@ static int codec_encode_v3(void *data,
|
||||||
|
|
||||||
return src_used;
|
return src_used;
|
||||||
}
|
}
|
||||||
|
static int codec_encode_v5(void *data,
|
||||||
|
const void *src, size_t src_size,
|
||||||
|
void *dst, size_t dst_size,
|
||||||
|
size_t *dst_out, int *need_flush)
|
||||||
|
{
|
||||||
|
return -ENOTSUP;
|
||||||
|
}
|
||||||
|
|
||||||
static int codec_start_decode(void *data,
|
static int codec_start_decode(void *data,
|
||||||
const void *src, size_t src_size, uint16_t *seqnum, uint32_t *timestamp)
|
const void *src, size_t src_size, uint16_t *seqnum, uint32_t *timestamp)
|
||||||
|
|
@ -607,7 +784,7 @@ static int codec_start_decode(void *data,
|
||||||
return header_size;
|
return header_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char *dec_errors[] = {
|
static const char *dec_errors_v3[] = {
|
||||||
[-LHDCBT_DEC_FUNC_SUCCEED] = "OK",
|
[-LHDCBT_DEC_FUNC_SUCCEED] = "OK",
|
||||||
[-LHDCBT_DEC_FUNC_FAIL] = "General error",
|
[-LHDCBT_DEC_FUNC_FAIL] = "General error",
|
||||||
[-LHDCBT_DEC_FUNC_INPUT_NOT_ENOUGH] = "Not enough input data",
|
[-LHDCBT_DEC_FUNC_INPUT_NOT_ENOUGH] = "Not enough input data",
|
||||||
|
|
@ -643,7 +820,56 @@ static int codec_decode_v3(void *data,
|
||||||
return consumed;
|
return consumed;
|
||||||
|
|
||||||
error:
|
error:
|
||||||
spa_log_error(log, "lhdcBT_dec_decode: %s (%d)!", dec_errors[-err], err);
|
spa_log_error(log, "lhdcBT_dec_decode: %s (%d)!", dec_errors_v3[-err], err);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *dec_errors_v5[] = {
|
||||||
|
[-LHDCV5BT_DEC_API_SUCCEED] = "OK",
|
||||||
|
[-LHDCV5BT_DEC_API_FAIL] = "General error",
|
||||||
|
[-LHDCV5BT_DEC_API_INVALID_INPUT] = "Invalid input",
|
||||||
|
[-LHDCV5BT_DEC_API_INVALID_OUTPUT] = "Invalid output",
|
||||||
|
[-LHDCV5BT_DEC_API_INVALID_SEQ_NO] = "Invalid sequence number",
|
||||||
|
[-LHDCV5BT_DEC_API_INIT_DECODER_FAIL] = "Decoder initialization error",
|
||||||
|
[-LHDCV5BT_DEC_API_CHANNEL_SETUP_FAIL] = "Channel setup error",
|
||||||
|
[-LHDCV5BT_DEC_API_FRAME_INFO_FAIL] = "Failed to fetch frame info",
|
||||||
|
[-LHDCV5BT_DEC_API_INPUT_NOT_ENOUGH] = "Not enough input data",
|
||||||
|
[-LHDCV5BT_DEC_API_OUTPUT_NOT_ENOUGH] = "Invalid sequence number",
|
||||||
|
[-LHDCV5BT_DEC_API_DECODE_FAIL] = "Decode error",
|
||||||
|
[-LHDCV5BT_DEC_API_ALLOC_MEM_FAIL] = "Out of memory",
|
||||||
|
};
|
||||||
|
|
||||||
|
static int codec_decode_v5(void *data,
|
||||||
|
const void *src, size_t src_size,
|
||||||
|
void *dst, size_t dst_size,
|
||||||
|
size_t *dst_out)
|
||||||
|
{
|
||||||
|
struct impl_v5 *this = data;
|
||||||
|
|
||||||
|
uint32_t decoded = dst_size;
|
||||||
|
uint32_t consumed = 0;
|
||||||
|
|
||||||
|
int err = 0;
|
||||||
|
|
||||||
|
if ((err = lhdcv5BT_dec_check_frame_data_enough(this->dec, src, src_size, &consumed)) < 0)
|
||||||
|
goto error;
|
||||||
|
|
||||||
|
consumed += sizeof(struct rtp_lhdc_payload);
|
||||||
|
|
||||||
|
if ((err = lhdcv5BT_dec_decode(this->dec, src, consumed, dst, &decoded, 24)) < 0)
|
||||||
|
goto error;
|
||||||
|
|
||||||
|
int32_t *samples = dst;
|
||||||
|
for (size_t i = 0; i < decoded / 4; i++)
|
||||||
|
samples[i] *= (1 << 8);
|
||||||
|
|
||||||
|
if (dst_out)
|
||||||
|
*dst_out = decoded;
|
||||||
|
|
||||||
|
return consumed;
|
||||||
|
|
||||||
|
error:
|
||||||
|
spa_log_error(log, "lhdcv5BT_dec_decode: %s (%d)!", dec_errors_v5[-err], err);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -691,7 +917,36 @@ const struct media_codec a2dp_codec_lhdc_v3 = {
|
||||||
.set_log = codec_set_log,
|
.set_log = codec_set_log,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const struct media_codec a2dp_codec_lhdc_v5 = {
|
||||||
|
.id = SPA_BLUETOOTH_AUDIO_CODEC_LHDC_V5,
|
||||||
|
.codec_id = A2DP_CODEC_VENDOR,
|
||||||
|
.vendor = { .vendor_id = LHDC_V5_VENDOR_ID,
|
||||||
|
.codec_id = LHDC_V5_CODEC_ID },
|
||||||
|
.name = "lhdc_v5",
|
||||||
|
.description = "LHDC V5",
|
||||||
|
.fill_caps = codec_fill_caps_v5,
|
||||||
|
.select_config = codec_select_config_v5,
|
||||||
|
.enum_config = codec_enum_config_v5,
|
||||||
|
.init_props = codec_init_props_v5,
|
||||||
|
.enum_props = codec_enum_props_v5,
|
||||||
|
.set_props = codec_set_props_v5,
|
||||||
|
.clear_props = codec_clear_props_v3,
|
||||||
|
.init = codec_init_v5,
|
||||||
|
.deinit = codec_deinit_v5,
|
||||||
|
.update_props = codec_update_props_v5,
|
||||||
|
.get_block_size = codec_get_block_size_v5,
|
||||||
|
.abr_process = codec_abr_process_v5,
|
||||||
|
.start_encode = codec_start_encode_v5,
|
||||||
|
.encode = codec_encode_v5,
|
||||||
|
.start_decode = codec_start_decode,
|
||||||
|
.decode = codec_decode_v5,
|
||||||
|
.reduce_bitpool = codec_reduce_bitpool,
|
||||||
|
.increase_bitpool = codec_increase_bitpool,
|
||||||
|
.set_log = codec_set_log,
|
||||||
|
};
|
||||||
|
|
||||||
MEDIA_CODEC_EXPORT_DEF(
|
MEDIA_CODEC_EXPORT_DEF(
|
||||||
"lhdc",
|
"lhdc",
|
||||||
&a2dp_codec_lhdc_v3
|
&a2dp_codec_lhdc_v3,
|
||||||
|
&a2dp_codec_lhdc_v5
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ static int codec_order(const struct media_codec *c)
|
||||||
{
|
{
|
||||||
static const enum spa_bluetooth_audio_codec order[] = {
|
static const enum spa_bluetooth_audio_codec order[] = {
|
||||||
SPA_BLUETOOTH_AUDIO_CODEC_LC3,
|
SPA_BLUETOOTH_AUDIO_CODEC_LC3,
|
||||||
|
SPA_BLUETOOTH_AUDIO_CODEC_LHDC_V5,
|
||||||
SPA_BLUETOOTH_AUDIO_CODEC_LHDC_V3,
|
SPA_BLUETOOTH_AUDIO_CODEC_LHDC_V3,
|
||||||
SPA_BLUETOOTH_AUDIO_CODEC_LDAC,
|
SPA_BLUETOOTH_AUDIO_CODEC_LDAC,
|
||||||
SPA_BLUETOOTH_AUDIO_CODEC_APTX_HD,
|
SPA_BLUETOOTH_AUDIO_CODEC_APTX_HD,
|
||||||
|
|
|
||||||
|
|
@ -144,7 +144,7 @@ if lhdc_enc_dep.found()
|
||||||
[ 'a2dp-codec-lhdc.c', 'media-codecs.c' ],
|
[ 'a2dp-codec-lhdc.c', 'media-codecs.c' ],
|
||||||
include_directories : [ configinc ],
|
include_directories : [ configinc ],
|
||||||
c_args : lhdc_args,
|
c_args : lhdc_args,
|
||||||
dependencies : [ spa_dep, lhdc_enc_dep, lhdc_dec_dep ],
|
dependencies : [ spa_dep, lhdc_enc_dep, lhdc_dec_dep, lhdc_v5_enc_dep, lhdc_v5_dec_dep ],
|
||||||
install : true,
|
install : true,
|
||||||
install_dir : spa_plugindir / 'bluez5')
|
install_dir : spa_plugindir / 'bluez5')
|
||||||
endif
|
endif
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue