diff --git a/NEWS b/NEWS index dbff0ca32..61f060a81 100644 --- a/NEWS +++ b/NEWS @@ -4,7 +4,7 @@ unreleased - renamed bluealsa to bluealsad (no backward compatibility) - renamed bluealsa.conf to org.bluealsa.conf (no backward compatibility) - renamed bluealsa-cli to bluealsactl (no backward compatibility) -- optional support for A2DP Sink and Source with LHDC v3 codec +- optional support for A2DP Sink and Source with LHDC v2 and v3 codec - channel map and volume control for surround sound (5.1, 7.1) audio - native A2DP volume control by default (dropped --a2dp-volume option) - fix configuration for Android 13 A2DP Opus codec diff --git a/src/a2dp-lhdc.c b/src/a2dp-lhdc.c index 1fd5792ad..81baef13b 100644 --- a/src/a2dp-lhdc.c +++ b/src/a2dp-lhdc.c @@ -49,7 +49,13 @@ static const struct a2dp_bit_mapping a2dp_lhdc_rates[] = { { 0 }, }; -static void a2dp_lhdc_caps_intersect( +static void a2dp_lhdc_v2_caps_intersect( + void *capabilities, + const void *mask) { + a2dp_caps_bitwise_intersect(capabilities, mask, sizeof(a2dp_lhdc_v2_t)); +} + +static void a2dp_lhdc_v3_caps_intersect( void *capabilities, const void *mask) { a2dp_caps_bitwise_intersect(capabilities, mask, sizeof(a2dp_lhdc_v3_t)); @@ -68,7 +74,18 @@ static int a2dp_lhdc_caps_foreach_channel_mode( return -1; } -static int a2dp_lhdc_caps_foreach_sample_rate( +static int a2dp_lhdc_v2_caps_foreach_sample_rate( + const void *capabilities, + enum a2dp_stream stream, + a2dp_bit_mapping_foreach_func func, + void *userdata) { + const a2dp_lhdc_v2_t *caps = capabilities; + if (stream == A2DP_MAIN) + return a2dp_bit_mapping_foreach(a2dp_lhdc_rates, caps->sampling_freq, func, userdata); + return -1; +} + +static int a2dp_lhdc_v3_caps_foreach_sample_rate( const void *capabilities, enum a2dp_stream stream, a2dp_bit_mapping_foreach_func func, @@ -88,7 +105,17 @@ static void a2dp_lhdc_caps_select_channel_mode( (void)channels; } -static void a2dp_lhdc_caps_select_sample_rate( +static void a2dp_lhdc_v2_caps_select_sample_rate( + void *capabilities, + enum a2dp_stream stream, + unsigned int rate) { + a2dp_lhdc_v2_t *caps = capabilities; + if (stream == A2DP_MAIN) + caps->sampling_freq = a2dp_bit_mapping_lookup_value(a2dp_lhdc_rates, + caps->sampling_freq, rate); +} + +static void a2dp_lhdc_v3_caps_select_sample_rate( void *capabilities, enum a2dp_stream stream, unsigned int rate) { @@ -98,44 +125,55 @@ static void a2dp_lhdc_caps_select_sample_rate( caps->sampling_freq, rate); } -static struct a2dp_caps_helpers a2dp_lhdc_caps_helpers = { - .intersect = a2dp_lhdc_caps_intersect, +static struct a2dp_caps_helpers a2dp_lhdc_v2_caps_helpers = { + .intersect = a2dp_lhdc_v2_caps_intersect, + .has_stream = a2dp_caps_has_main_stream_only, + .foreach_channel_mode = a2dp_lhdc_caps_foreach_channel_mode, + .foreach_sample_rate = a2dp_lhdc_v2_caps_foreach_sample_rate, + .select_channel_mode = a2dp_lhdc_caps_select_channel_mode, + .select_sample_rate = a2dp_lhdc_v2_caps_select_sample_rate, +}; + +static struct a2dp_caps_helpers a2dp_lhdc_v3_caps_helpers = { + .intersect = a2dp_lhdc_v3_caps_intersect, .has_stream = a2dp_caps_has_main_stream_only, .foreach_channel_mode = a2dp_lhdc_caps_foreach_channel_mode, - .foreach_sample_rate = a2dp_lhdc_caps_foreach_sample_rate, + .foreach_sample_rate = a2dp_lhdc_v3_caps_foreach_sample_rate, .select_channel_mode = a2dp_lhdc_caps_select_channel_mode, - .select_sample_rate = a2dp_lhdc_caps_select_sample_rate, + .select_sample_rate = a2dp_lhdc_v3_caps_select_sample_rate, }; -static LHDC_VERSION_SETUP get_version(const a2dp_lhdc_v3_t *configuration) { - if (configuration->llac) - return LLAC; - if (configuration->lhdc_v4) - return LHDC_V4; - return LHDC_V3; +static LHDC_VERSION_SETUP get_lhdc_enc_version(const void *configuration) { + switch (((a2dp_vendor_info_t *)configuration)->codec_id) { + case LHDC_V2_CODEC_ID: + return LHDC_V2; + case LHDC_V3_CODEC_ID: { + const a2dp_lhdc_v3_t *v3 = configuration; + if (v3->llac) + return LLAC; + if (v3->lhdc_v4) + return LHDC_V4; + return LHDC_V3; + } break; + default: + return 0; + } } -static lhdc_ver_t get_decoder_version(const a2dp_lhdc_v3_t *configuration) { +static lhdc_ver_t get_lhdc_dec_version(const void *configuration) { static const lhdc_ver_t versions[] = { + [LHDC_V2] = VERSION_2, [LHDC_V3] = VERSION_3, [LHDC_V4] = VERSION_4, [LLAC] = VERSION_LLAC, }; - return versions[get_version(configuration)]; -} - -static int get_interval(const a2dp_lhdc_v3_t *configuration) { - return configuration->low_latency ? 10 : 20; -} - -static int get_bit_depth(const a2dp_lhdc_v3_t *configuration) { - return configuration->bit_depth == LHDC_BIT_DEPTH_16 ? 16 : 24; + return versions[get_lhdc_enc_version(configuration)]; } -static LHDCBT_QUALITY_T get_max_bitrate(const a2dp_lhdc_v3_t *configuration) { - switch (configuration->max_bitrate) { +static LHDCBT_QUALITY_T get_lhdc_max_bitrate(uint8_t config_max_bitrate) { + switch (config_max_bitrate) { case LHDC_MAX_BITRATE_400K: return LHDCBT_QUALITY_LOW; case LHDC_MAX_BITRATE_500K: @@ -154,26 +192,44 @@ void *a2dp_lhdc_enc_thread(struct ba_transport_pcm *t_pcm) { struct ba_transport *t = t_pcm->t; struct io_poll io = { .timeout = -1 }; - const a2dp_lhdc_v3_t *configuration = &t->a2dp.configuration.lhdc_v3; - const unsigned int bit_depth = get_bit_depth(configuration); + const uint32_t codec_id = ba_transport_get_codec(t); const unsigned int channels = t_pcm->channels; const unsigned int rate = t_pcm->rate; HANDLE_LHDC_BT handle; - if ((handle = lhdcBT_get_handle(get_version(configuration))) == NULL) { + if ((handle = lhdcBT_get_handle(get_lhdc_enc_version(&t->a2dp.configuration))) == NULL) { error("Couldn't get LHDC handle: %s", strerror(errno)); goto fail_open_lhdc; } pthread_cleanup_push(PTHREAD_CLEANUP(lhdcBT_free_handle), handle); - lhdcBT_set_hasMinBitrateLimit(handle, configuration->min_bitrate); - lhdcBT_set_max_bitrate(handle, get_max_bitrate(configuration)); + int lhdc_max_bitrate_index = 0; + int lhdc_bit_depth = 0; + int lhdc_dual_channel = 0; + int lhdc_interval = 0; + + switch (codec_id) { + case A2DP_CODEC_VENDOR_ID(LHDC_V2_VENDOR_ID, LHDC_V2_CODEC_ID): + lhdc_max_bitrate_index = get_lhdc_max_bitrate(t->a2dp.configuration.lhdc_v2.max_bitrate); + lhdc_bit_depth = t->a2dp.configuration.lhdc_v2.bit_depth == LHDC_BIT_DEPTH_16 ? 16 : 24; + lhdc_dual_channel = t->a2dp.configuration.lhdc_v2.ch_split_mode > LHDC_CH_SPLIT_MODE_NONE; + lhdc_interval = t->a2dp.configuration.lhdc_v2.low_latency ? 10 : 20; + break; + case A2DP_CODEC_VENDOR_ID(LHDC_V3_VENDOR_ID, LHDC_V3_CODEC_ID): + lhdcBT_set_hasMinBitrateLimit(handle, t->a2dp.configuration.lhdc_v3.min_bitrate); + lhdc_max_bitrate_index = get_lhdc_max_bitrate(t->a2dp.configuration.lhdc_v3.max_bitrate); + lhdc_bit_depth = t->a2dp.configuration.lhdc_v3.bit_depth == LHDC_BIT_DEPTH_16 ? 16 : 24; + lhdc_dual_channel = t->a2dp.configuration.lhdc_v3.ch_split_mode > LHDC_CH_SPLIT_MODE_NONE; + lhdc_interval = t->a2dp.configuration.lhdc_v3.low_latency ? 10 : 20; + break; + } + + lhdcBT_set_max_bitrate(handle, lhdc_max_bitrate_index); - if (lhdcBT_init_encoder(handle, rate, bit_depth, config.lhdc_eqmid, - configuration->ch_split_mode > LHDC_CH_SPLIT_MODE_NONE, 0, - t->mtu_write - RTP_HEADER_LEN - sizeof(rtp_lhdc_media_header_t), - get_interval(configuration)) == -1) { + if (lhdcBT_init_encoder(handle, rate, lhdc_bit_depth, config.lhdc_eqmid, lhdc_dual_channel, + 0, t->mtu_write - RTP_HEADER_LEN - sizeof(rtp_lhdc_media_header_t), + lhdc_interval) == -1) { error("Couldn't initialize LHDC encoder"); goto fail_init; } @@ -198,9 +254,9 @@ void *a2dp_lhdc_enc_thread(struct ba_transport_pcm *t_pcm) { goto fail_ffb; } - const unsigned int ldac_delay_frames = 1024; + const unsigned int lhdc_delay_frames = 1024; /* Get the total delay introduced by the codec. */ - t_pcm->codec_delay_dms = ldac_delay_frames * 10000 / rate; + t_pcm->codec_delay_dms = lhdc_delay_frames * 10000 / rate; rtp_header_t *rtp_header; rtp_lhdc_media_header_t *rtp_lhdc_media_header; @@ -241,15 +297,31 @@ void *a2dp_lhdc_enc_thread(struct ba_transport_pcm *t_pcm) { /* anchor for RTP payload */ bt.tail = rtp_payload; - int32_t *pcm_ch_buffers[2] = { pcm_ch1, pcm_ch2 }; - audio_deinterleave_s24_4le(pcm_ch_buffers, input, channels, lhdc_ch_samples); - uint32_t encoded; uint32_t frames; + int rv; + + if (codec_id == A2DP_CODEC_VENDOR_ID(LHDC_V2_VENDOR_ID, LHDC_V2_CODEC_ID)) { + + if ((rv = lhdcBT_encode(handle, input, bt.tail)) < 0) { + error("LHDC encoding error: %d", rv); + break; + } + + encoded = rv; + frames = 1; + + } + else { + + int32_t *pcm_ch_buffers[2] = { pcm_ch1, pcm_ch2 }; + audio_deinterleave_s24_4le(pcm_ch_buffers, input, channels, lhdc_ch_samples); + + if ((rv = lhdcBT_encode_stereo(handle, pcm_ch1, pcm_ch2, bt.tail, &encoded, &frames)) < 0) { + error("LHDC encoding error: %d", rv); + break; + } - if (lhdcBT_encode_stereo(handle, pcm_ch1, pcm_ch2, bt.tail, &encoded, &frames) < 0) { - error("LHDC encoding error"); - break; } input += lhdc_pcm_samples; @@ -260,9 +332,9 @@ void *a2dp_lhdc_enc_thread(struct ba_transport_pcm *t_pcm) { rtp_state_new_frame(&rtp, rtp_header); - rtp_lhdc_media_header->seq_number = seq_num++; rtp_lhdc_media_header->latency = 0; rtp_lhdc_media_header->frame_count = frames; + rtp_lhdc_media_header->seq_number = seq_num++; /* Try to get the number of bytes queued in the * socket output buffer. */ @@ -331,17 +403,22 @@ void *a2dp_lhdc_dec_thread(struct ba_transport_pcm *t_pcm) { struct ba_transport *t = t_pcm->t; struct io_poll io = { .timeout = -1 }; - const a2dp_lhdc_v3_t *configuration = &t->a2dp.configuration.lhdc_v3; const size_t sample_size = BA_TRANSPORT_PCM_FORMAT_BYTES(t_pcm->format); const unsigned int channels = t_pcm->channels; const unsigned int rate = t_pcm->rate; - const unsigned int bit_depth = get_bit_depth(configuration); - tLHDCV3_DEC_CONFIG dec_config = { - .version = get_decoder_version(configuration), - .sample_rate = rate, - .bits_depth = bit_depth, - }; + tLHDCV3_DEC_CONFIG dec_config = { .sample_rate = rate }; + + switch (ba_transport_get_codec(t)) { + case A2DP_CODEC_VENDOR_ID(LHDC_V2_VENDOR_ID, LHDC_V2_CODEC_ID): + dec_config.version = VERSION_2; + dec_config.bits_depth = t->a2dp.configuration.lhdc_v2.bit_depth == LHDC_BIT_DEPTH_16 ? 16 : 24; + break; + case A2DP_CODEC_VENDOR_ID(LHDC_V3_VENDOR_ID, LHDC_V3_CODEC_ID): + dec_config.version = get_lhdc_dec_version(&t->a2dp.configuration.lhdc_v3); + dec_config.bits_depth = t->a2dp.configuration.lhdc_v3.bit_depth == LHDC_BIT_DEPTH_16 ? 16 : 24; + break; + } if (lhdcBT_dec_init_decoder(&dec_config) < 0) { error("Couldn't initialise LHDC decoder: %s", strerror(errno)); @@ -392,8 +469,12 @@ void *a2dp_lhdc_dec_thread(struct ba_transport_pcm *t_pcm) { const uint8_t *rtp_payload = (uint8_t *)rtp_lhdc_media_header; size_t rtp_payload_len = len - (rtp_payload - (uint8_t *)bt.data); + int rv; uint32_t decoded = ffb_blen_in(&pcm); - lhdcBT_dec_decode(rtp_payload, rtp_payload_len, pcm.data, &decoded, 24); + if ((rv = lhdcBT_dec_decode(rtp_payload, rtp_payload_len, pcm.data, &decoded, 24)) != 0) { + error("LHDC decoding error: %s", lhdcBT_dec_strerror(rv)); + continue; + } const size_t samples = decoded / sample_size; @@ -421,7 +502,38 @@ void *a2dp_lhdc_dec_thread(struct ba_transport_pcm *t_pcm) { return NULL; } -static int a2dp_lhdc_configuration_select( +static int a2dp_lhdc_v2_configuration_select( + const struct a2dp_sep *sep, + void *capabilities) { + + a2dp_lhdc_v2_t *caps = capabilities; + const a2dp_lhdc_v2_t saved = *caps; + + /* Narrow capabilities to values supported by BlueALSA. */ + a2dp_lhdc_v2_caps_intersect(caps, &sep->config.capabilities); + + if (caps->bit_depth & LHDC_BIT_DEPTH_24) + caps->bit_depth = LHDC_BIT_DEPTH_24; + else if (caps->bit_depth & LHDC_BIT_DEPTH_16) + caps->bit_depth = LHDC_BIT_DEPTH_16; + else { + error("LHDC: No supported bit depths: %#x", saved.bit_depth); + return errno = ENOTSUP, -1; + } + + unsigned int sampling_freq = 0; + if (a2dp_lhdc_v2_caps_foreach_sample_rate(caps, A2DP_MAIN, + a2dp_bit_mapping_foreach_get_best_sample_rate, &sampling_freq) != -1) + caps->sampling_freq = sampling_freq; + else { + error("LHDC: No supported sample rates: %#x", saved.sampling_freq); + return errno = ENOTSUP, -1; + } + + return 0; +} + +static int a2dp_lhdc_v3_configuration_select( const struct a2dp_sep *sep, void *capabilities) { @@ -431,7 +543,7 @@ static int a2dp_lhdc_configuration_select( const a2dp_lhdc_v3_t saved = *caps; /* Narrow capabilities to values supported by BlueALSA. */ - a2dp_lhdc_caps_intersect(caps, &sep->config.capabilities); + a2dp_lhdc_v3_caps_intersect(caps, &sep->config.capabilities); if (caps->bit_depth & LHDC_BIT_DEPTH_24) caps->bit_depth = LHDC_BIT_DEPTH_24; @@ -443,7 +555,7 @@ static int a2dp_lhdc_configuration_select( } unsigned int sampling_freq = 0; - if (a2dp_lhdc_caps_foreach_sample_rate(caps, A2DP_MAIN, + if (a2dp_lhdc_v3_caps_foreach_sample_rate(caps, A2DP_MAIN, a2dp_bit_mapping_foreach_get_best_sample_rate, &sampling_freq) != -1) caps->sampling_freq = sampling_freq; else { @@ -454,7 +566,25 @@ static int a2dp_lhdc_configuration_select( return 0; } -static int a2dp_lhdc_configuration_check( +static int a2dp_lhdc_v2_configuration_check( + const struct a2dp_sep *sep, + const void *configuration) { + + const a2dp_lhdc_v2_t *conf = configuration; + a2dp_lhdc_v2_t conf_v = *conf; + + /* Validate configuration against BlueALSA capabilities. */ + a2dp_lhdc_v2_caps_intersect(&conf_v, &sep->config.capabilities); + + if (a2dp_bit_mapping_lookup(a2dp_lhdc_rates, conf_v.sampling_freq) == -1) { + debug("LHDC: Invalid sample rate: %#x", conf->sampling_freq); + return A2DP_CHECK_ERR_RATE; + } + + return A2DP_CHECK_OK; +} + +static int a2dp_lhdc_v3_configuration_check( const struct a2dp_sep *sep, const void *configuration) { @@ -462,7 +592,7 @@ static int a2dp_lhdc_configuration_check( a2dp_lhdc_v3_t conf_v = *conf; /* Validate configuration against BlueALSA capabilities. */ - a2dp_lhdc_caps_intersect(&conf_v, &sep->config.capabilities); + a2dp_lhdc_v3_caps_intersect(&conf_v, &sep->config.capabilities); if (a2dp_bit_mapping_lookup(a2dp_lhdc_rates, conf_v.sampling_freq) == -1) { debug("LHDC: Invalid sample rate: %#x", conf->sampling_freq); @@ -475,9 +605,20 @@ static int a2dp_lhdc_configuration_check( static int a2dp_lhdc_transport_init(struct ba_transport *t) { ssize_t rate_i; - if ((rate_i = a2dp_bit_mapping_lookup(a2dp_lhdc_rates, - t->a2dp.configuration.lhdc_v3.sampling_freq)) == -1) + switch (t->codec_id) { + case A2DP_CODEC_VENDOR_ID(LHDC_V2_VENDOR_ID, LHDC_V2_CODEC_ID): + if ((rate_i = a2dp_bit_mapping_lookup(a2dp_lhdc_rates, + t->a2dp.configuration.lhdc_v2.sampling_freq)) == -1) + return -1; + break; + case A2DP_CODEC_VENDOR_ID(LHDC_V3_VENDOR_ID, LHDC_V3_CODEC_ID): + if ((rate_i = a2dp_bit_mapping_lookup(a2dp_lhdc_rates, + t->a2dp.configuration.lhdc_v3.sampling_freq)) == -1) + return -1; + break; + default: return -1; + } /* LHDC library uses 32-bit signed integers for the encoder API and * 24-bit signed integers for the decoder API. So, the best common @@ -494,7 +635,14 @@ static int a2dp_lhdc_transport_init(struct ba_transport *t) { static int a2dp_lhdc_source_init(struct a2dp_sep *sep) { if (config.a2dp.force_44100) - sep->config.capabilities.lhdc_v3.sampling_freq = LHDC_SAMPLING_FREQ_44100; + switch (sep->config.codec_id) { + case A2DP_CODEC_VENDOR_ID(LHDC_V2_VENDOR_ID, LHDC_V2_CODEC_ID): + sep->config.capabilities.lhdc_v2.sampling_freq = LHDC_SAMPLING_FREQ_44100; + break; + case A2DP_CODEC_VENDOR_ID(LHDC_V3_VENDOR_ID, LHDC_V3_CODEC_ID): + sep->config.capabilities.lhdc_v3.sampling_freq = LHDC_SAMPLING_FREQ_44100; + break; + } return 0; } @@ -502,7 +650,34 @@ static int a2dp_lhdc_source_transport_start(struct ba_transport *t) { return ba_transport_pcm_start(&t->a2dp.pcm, a2dp_lhdc_enc_thread, "ba-a2dp-lhdc"); } -struct a2dp_sep a2dp_lhdc_source = { +struct a2dp_sep a2dp_lhdc_v2_source = { + .name = "A2DP Source (LHDC v2)", + .config = { + .type = A2DP_SOURCE, + .codec_id = A2DP_CODEC_VENDOR_ID(LHDC_V2_VENDOR_ID, LHDC_V2_CODEC_ID), + .caps_size = sizeof(a2dp_lhdc_v2_t), + .capabilities.lhdc_v2 = { + .info = A2DP_VENDOR_INFO_INIT(LHDC_V2_VENDOR_ID, LHDC_V2_CODEC_ID), + .sampling_freq = + LHDC_SAMPLING_FREQ_44100 | + LHDC_SAMPLING_FREQ_48000 | + LHDC_SAMPLING_FREQ_96000, + .bit_depth = + LHDC_BIT_DEPTH_16 | + LHDC_BIT_DEPTH_24, + .max_bitrate = LHDC_MAX_BITRATE_900K, + .ch_split_mode = LHDC_CH_SPLIT_MODE_NONE, + }, + }, + .init = a2dp_lhdc_source_init, + .configuration_select = a2dp_lhdc_v2_configuration_select, + .configuration_check = a2dp_lhdc_v2_configuration_check, + .transport_init = a2dp_lhdc_transport_init, + .transport_start = a2dp_lhdc_source_transport_start, + .caps_helpers = &a2dp_lhdc_v2_caps_helpers, +}; + +struct a2dp_sep a2dp_lhdc_v3_source = { .name = "A2DP Source (LHDC v3)", .config = { .type = A2DP_SOURCE, @@ -526,18 +701,44 @@ struct a2dp_sep a2dp_lhdc_source = { }, }, .init = a2dp_lhdc_source_init, - .configuration_select = a2dp_lhdc_configuration_select, - .configuration_check = a2dp_lhdc_configuration_check, + .configuration_select = a2dp_lhdc_v3_configuration_select, + .configuration_check = a2dp_lhdc_v3_configuration_check, .transport_init = a2dp_lhdc_transport_init, .transport_start = a2dp_lhdc_source_transport_start, - .caps_helpers = &a2dp_lhdc_caps_helpers, + .caps_helpers = &a2dp_lhdc_v3_caps_helpers, }; static int a2dp_lhdc_sink_transport_start(struct ba_transport *t) { return ba_transport_pcm_start(&t->a2dp.pcm, a2dp_lhdc_dec_thread, "ba-a2dp-lhdc"); } -struct a2dp_sep a2dp_lhdc_sink = { +struct a2dp_sep a2dp_lhdc_v2_sink = { + .name = "A2DP Sink (LHDC v2)", + .config = { + .type = A2DP_SINK, + .codec_id = A2DP_CODEC_VENDOR_ID(LHDC_V2_VENDOR_ID, LHDC_V2_CODEC_ID), + .caps_size = sizeof(a2dp_lhdc_v2_t), + .capabilities.lhdc_v2 = { + .info = A2DP_VENDOR_INFO_INIT(LHDC_V2_VENDOR_ID, LHDC_V2_CODEC_ID), + .sampling_freq = + LHDC_SAMPLING_FREQ_44100 | + LHDC_SAMPLING_FREQ_48000 | + LHDC_SAMPLING_FREQ_96000, + .bit_depth = + LHDC_BIT_DEPTH_16 | + LHDC_BIT_DEPTH_24, + .max_bitrate = LHDC_MAX_BITRATE_900K, + .ch_split_mode = LHDC_CH_SPLIT_MODE_NONE, + }, + }, + .configuration_select = a2dp_lhdc_v2_configuration_select, + .configuration_check = a2dp_lhdc_v2_configuration_check, + .transport_init = a2dp_lhdc_transport_init, + .transport_start = a2dp_lhdc_sink_transport_start, + .caps_helpers = &a2dp_lhdc_v2_caps_helpers, +}; + +struct a2dp_sep a2dp_lhdc_v3_sink = { .name = "A2DP Sink (LHDC v3)", .config = { .type = A2DP_SINK, @@ -560,9 +761,9 @@ struct a2dp_sep a2dp_lhdc_sink = { .ch_split_mode = LHDC_CH_SPLIT_MODE_NONE, }, }, - .configuration_select = a2dp_lhdc_configuration_select, - .configuration_check = a2dp_lhdc_configuration_check, + .configuration_select = a2dp_lhdc_v3_configuration_select, + .configuration_check = a2dp_lhdc_v3_configuration_check, .transport_init = a2dp_lhdc_transport_init, .transport_start = a2dp_lhdc_sink_transport_start, - .caps_helpers = &a2dp_lhdc_caps_helpers, + .caps_helpers = &a2dp_lhdc_v3_caps_helpers, }; diff --git a/src/a2dp-lhdc.h b/src/a2dp-lhdc.h index ed7ef117f..8e91ceb1a 100644 --- a/src/a2dp-lhdc.h +++ b/src/a2dp-lhdc.h @@ -1,6 +1,6 @@ /* - * BlueALSA - a2dp-ldac.h - * Copyright (c) 2016-2021 Arkadiusz Bokowy + * BlueALSA - a2dp-lhdc.h + * Copyright (c) 2016-2024 Arkadiusz Bokowy * Copyright (c) 2023 anonymix007 * * This file is a part of bluez-alsa. @@ -19,7 +19,9 @@ #include "a2dp.h" -extern struct a2dp_sep a2dp_lhdc_sink; -extern struct a2dp_sep a2dp_lhdc_source; +extern struct a2dp_sep a2dp_lhdc_v2_source; +extern struct a2dp_sep a2dp_lhdc_v3_source; +extern struct a2dp_sep a2dp_lhdc_v2_sink; +extern struct a2dp_sep a2dp_lhdc_v3_sink; #endif diff --git a/src/a2dp.c b/src/a2dp.c index 372479ab8..ef9d41290 100644 --- a/src/a2dp.c +++ b/src/a2dp.c @@ -190,8 +190,10 @@ struct a2dp_sep * const a2dp_seps[] = { &a2dp_lc3plus_sink, #endif #if ENABLE_LHDC - &a2dp_lhdc_source, - &a2dp_lhdc_sink, + &a2dp_lhdc_v3_source, + &a2dp_lhdc_v3_sink, + &a2dp_lhdc_v2_source, + &a2dp_lhdc_v2_sink, #endif #if ENABLE_LDAC &a2dp_ldac_source, diff --git a/src/rtp.h b/src/rtp.h index 34e7e83ac..cd24ca6bd 100644 --- a/src/rtp.h +++ b/src/rtp.h @@ -76,14 +76,14 @@ typedef struct rtp_mpeg_audio_header { /** * LHDC media payload header. */ typedef struct rtp_lhdc_media_header { - uint8_t seq_number; -#if __BYTE_ORDER == __LITTLE_ENDIAN - uint8_t latency:2; +#if __BYTE_ORDER == __BIG_ENDIAN uint8_t frame_count:6; + uint8_t latency:2; #else - uint8_t frame_count:6; uint8_t latency:2; + uint8_t frame_count:6; #endif + uint8_t seq_number; } __attribute__ ((packed)) rtp_lhdc_media_header_t; void *rtp_a2dp_init(void *s, rtp_header_t **hdr, void **phdr, size_t phdr_size); diff --git a/src/utils.c b/src/utils.c index 0b1f771f0..2c13e90c9 100644 --- a/src/utils.c +++ b/src/utils.c @@ -26,6 +26,10 @@ # include "ldacBT.h" #endif +#if ENABLE_LHDC +# include +#endif + #include "shared/defs.h" #include "shared/log.h" @@ -433,3 +437,22 @@ const char *ldacBT_strerror(int err) { return "Unknown error"; } #endif + +#if ENABLE_LHDC +const char *lhdcBT_dec_strerror(int err) { + switch (err) { + case LHDCBT_DEC_FUNC_SUCCEED: + return "Success"; + case LHDCBT_DEC_FUNC_FAIL: + return "Decode failed"; + case LHDCBT_DEC_FUNC_INPUT_NOT_ENOUGH: + return "Too small input buffer"; + case LHDCBT_DEC_FUNC_OUTPUT_NOT_ENOUGH: + return "Output buffer too small"; + case LHDCBT_DEC_FUNC_INVALID_SEQ_NO: + return "Invalid sequence number"; + } + debug("Unknown error code: %#x", err); + return "Unknown error"; +} +#endif diff --git a/src/utils.h b/src/utils.h index df0d5b940..e90995249 100644 --- a/src/utils.h +++ b/src/utils.h @@ -57,4 +57,8 @@ const char *lc3plus_strerror(LC3PLUS_Error err); const char *ldacBT_strerror(int err); #endif +#if ENABLE_LHDC +const char *lhdcBT_dec_strerror(int err); +#endif + #endif diff --git a/test/test-io.c b/test/test-io.c index 31ebc968f..65384a7b8 100644 --- a/test/test-io.c +++ b/test/test-io.c @@ -87,7 +87,7 @@ #if ENABLE_OFONO # include "ofono.h" #endif -#if ENABLE_LC3PLUS || ENABLE_LDAC_IO_TEST || ENABLE_LHDC +#if ENABLE_LC3PLUS || ENABLE_LDAC_IO_TEST # include "rtp.h" #endif #include "storage.h" @@ -221,9 +221,16 @@ static const a2dp_ldac_t config_ldac_48000_stereo = { }; __attribute__ ((unused)) -static const a2dp_lhdc_v3_t config_lhdc_44100_stereo = { +static const a2dp_lhdc_v2_t config_lhdc_v2_48000_stereo = { + .info = A2DP_VENDOR_INFO_INIT(LHDC_V2_VENDOR_ID, LHDC_V2_CODEC_ID), + .sampling_freq = LHDC_SAMPLING_FREQ_48000, + .bit_depth = LHDC_BIT_DEPTH_16, +}; + +__attribute__ ((unused)) +static const a2dp_lhdc_v3_t config_lhdc_v3_48000_stereo = { .info = A2DP_VENDOR_INFO_INIT(LHDC_V3_VENDOR_ID, LHDC_V3_CODEC_ID), - .sampling_freq = LHDC_SAMPLING_FREQ_44100, + .sampling_freq = LHDC_SAMPLING_FREQ_48000, .bit_depth = LHDC_BIT_DEPTH_24, }; @@ -463,7 +470,7 @@ static void pcm_write_frames(struct ba_transport_pcm *pcm, size_t frames) { struct bt_data { struct bt_data *next; - uint8_t data[1024]; + uint8_t data[2048]; size_t len; }; @@ -555,7 +562,7 @@ static void *test_io_thread_dump_bt(struct ba_transport_pcm *t_pcm) { struct ba_transport *t = t_pcm->t; struct pollfd pfds[] = {{ t_pcm->fd_bt, POLLIN, 0 }}; struct bt_dump *btd = NULL; - uint8_t buffer[1024]; + uint8_t buffer[sizeof(bt_data.data)]; ssize_t len; if (dump_data) { @@ -1322,28 +1329,55 @@ CK_START_TEST(test_a2dp_ldac) { #endif #if ENABLE_LHDC -CK_START_TEST(test_a2dp_lhdc) { +CK_START_TEST(test_a2dp_lhdc_v2) { + + struct ba_transport *t1 = test_transport_new_a2dp(device1, + BA_TRANSPORT_PROFILE_A2DP_SOURCE, "/path/lhdc", &a2dp_lhdc_v2_source, + &config_lhdc_v2_48000_stereo); + struct ba_transport *t2 = test_transport_new_a2dp(device2, + BA_TRANSPORT_PROFILE_A2DP_SINK, "/path/lhdc", &a2dp_lhdc_v2_sink, + &config_lhdc_v2_48000_stereo); + + struct ba_transport_pcm *t1_pcm = &t1->a2dp.pcm; + struct ba_transport_pcm *t2_pcm = &t2->a2dp.pcm; + + if (aging_duration) { + t1->mtu_read = t1->mtu_write = t2->mtu_read = t2->mtu_write = 2048; + test_io(t1_pcm, t2_pcm, a2dp_lhdc_enc_thread, a2dp_lhdc_dec_thread, 4 * 1024); + } + else { + t1->mtu_read = t1->mtu_write = t2->mtu_read = t2->mtu_write = 2048; + test_io(t1_pcm, t2_pcm, a2dp_lhdc_enc_thread, test_io_thread_dump_bt, 2 * 1024); + test_io(t1_pcm, t2_pcm, test_io_thread_dump_pcm, a2dp_lhdc_dec_thread, 2 * 1024); + } + + ba_transport_destroy(t1); + ba_transport_destroy(t2); + +} CK_END_TEST +#endif + +#if ENABLE_LHDC +CK_START_TEST(test_a2dp_lhdc_v3) { config.lhdc_eqmid = LHDCBT_QUALITY_HIGH; struct ba_transport *t1 = test_transport_new_a2dp(device1, - BA_TRANSPORT_PROFILE_A2DP_SOURCE, "/path/lhdc", &a2dp_lhdc_source, - &config_lhdc_44100_stereo); + BA_TRANSPORT_PROFILE_A2DP_SOURCE, "/path/lhdc", &a2dp_lhdc_v3_source, + &config_lhdc_v3_48000_stereo); struct ba_transport *t2 = test_transport_new_a2dp(device2, - BA_TRANSPORT_PROFILE_A2DP_SINK, "/path/lhdc", &a2dp_lhdc_sink, - &config_lhdc_44100_stereo); + BA_TRANSPORT_PROFILE_A2DP_SINK, "/path/lhdc", &a2dp_lhdc_v3_sink, + &config_lhdc_v3_48000_stereo); struct ba_transport_pcm *t1_pcm = &t1->a2dp.pcm; struct ba_transport_pcm *t2_pcm = &t2->a2dp.pcm; if (aging_duration) { - t1->mtu_read = t1->mtu_write = t2->mtu_read = t2->mtu_write = - RTP_HEADER_LEN + sizeof(rtp_media_header_t) + 990; + t1->mtu_read = t1->mtu_write = t2->mtu_read = t2->mtu_write = 960; test_io(t1_pcm, t2_pcm, a2dp_lhdc_enc_thread, a2dp_lhdc_dec_thread, 4 * 1024); } else { - t1->mtu_read = t1->mtu_write = t2->mtu_read = t2->mtu_write = - RTP_HEADER_LEN + sizeof(rtp_media_header_t) + 990; + t1->mtu_read = t1->mtu_write = t2->mtu_read = t2->mtu_write = 960; test_io(t1_pcm, t2_pcm, a2dp_lhdc_enc_thread, test_io_thread_dump_bt, 2 * 1024); test_io(t1_pcm, t2_pcm, test_io_thread_dump_pcm, a2dp_lhdc_dec_thread, 2 * 1024); } @@ -1494,7 +1528,8 @@ int main(int argc, char *argv[]) { { a2dp_codecs_codec_id_to_string(A2DP_CODEC_VENDOR_ID(LDAC_VENDOR_ID, LDAC_CODEC_ID)), test_a2dp_ldac }, #endif #if ENABLE_LHDC - { a2dp_codecs_codec_id_to_string(A2DP_CODEC_VENDOR_ID(LHDC_V3_VENDOR_ID, LHDC_V3_CODEC_ID)), test_a2dp_lhdc }, + { a2dp_codecs_codec_id_to_string(A2DP_CODEC_VENDOR_ID(LHDC_V2_VENDOR_ID, LHDC_V2_CODEC_ID)), test_a2dp_lhdc_v2 }, + { a2dp_codecs_codec_id_to_string(A2DP_CODEC_VENDOR_ID(LHDC_V3_VENDOR_ID, LHDC_V3_CODEC_ID)), test_a2dp_lhdc_v3 }, #endif #if ENABLE_OPUS { a2dp_codecs_codec_id_to_string(A2DP_CODEC_VENDOR_ID(OPUS_VENDOR_ID, OPUS_CODEC_ID)), test_a2dp_opus }, diff --git a/utils/a2dpconf.c b/utils/a2dpconf.c index 1a80deeca..c078d9faa 100644 --- a/utils/a2dpconf.c +++ b/utils/a2dpconf.c @@ -503,7 +503,7 @@ static void dump_lhdc_v3(const void *blob, size_t size) { " llac:1 = %s\n" " low-latency:1 = %s\n" " max-bitrate:2 = %d\n" - " version:4 = %u\n" + " version:4 =%s\n" " lhdc-v4:1 = %s\n" " larc:1 = %s\n" " min-bitrate:1 = %s\n" @@ -521,7 +521,7 @@ static void dump_lhdc_v3(const void *blob, size_t size) { lhdc->llac ? "true" : "false", lhdc->low_latency ? "true" : "false", lhdc_get_max_bitrate(lhdc->max_bitrate), - lhdc->version, + lhdc->version & LHDC_VER3 ? " v3" : "", lhdc->lhdc_v4 ? "true" : "false", lhdc->larc ? "true" : "false", lhdc->min_bitrate ? "true" : "false",