bluez5: support packet loss concealment in codecs

LC3 and Opus have built-in support for packet loss concealment.

Add codec interface for that, and implement for LC3.

Extend media_codec interface so that packets not aligned with socket
reads can be handled, as in HFP. This is required for correct sequence
number counting, and for being able to run codec PLC *before* decoding
the next correctly received packet.
This commit is contained in:
Pauli Virtanen 2025-06-13 20:53:49 +03:00 committed by Wim Taymans
parent a2ede93479
commit d0680a2b3d
5 changed files with 76 additions and 5 deletions

View file

@ -1310,6 +1310,24 @@ static SPA_UNUSED int codec_decode(void *data,
return consumed;
}
static int codec_produce_plc(void *data, void *dst, size_t dst_size)
{
struct impl *this = data;
int ich, res;
if (dst_size < this->codesize)
return -EINVAL;
for (ich = 0; ich < this->channels; ich++) {
uint8_t *out = (uint8_t *)dst + (ich * 4);
res = lc3_decode(this->dec[ich], NULL, 0, LC3_PCM_FORMAT_S24, out, this->channels);
if (SPA_UNLIKELY(res < 0))
return -EINVAL;
}
return this->codesize;
}
static int codec_reduce_bitpool(void *data)
{
return -ENOTSUP;
@ -1424,6 +1442,7 @@ const struct media_codec bap_codec_lc3 = {
.encode = codec_encode,
.start_decode = codec_start_decode,
.decode = codec_decode,
.produce_plc = codec_produce_plc,
.reduce_bitpool = codec_reduce_bitpool,
.increase_bitpool = codec_increase_bitpool,
.set_log = codec_set_log,

View file

@ -213,6 +213,21 @@ static int codec_decode(void *data,
return H2_PACKET_SIZE - 2;
}
static int codec_produce_plc(void *data, void *dst, size_t dst_size)
{
struct impl *this = data;
int res;
if (dst_size < LC3_A127_BLOCK_SIZE)
return -EINVAL;
res = lc3_decode(this->dec, NULL, 0, LC3_PCM_FORMAT_FLOAT, dst, 1);
if (res != 1)
return -EINVAL;
return LC3_A127_BLOCK_SIZE;
}
static void codec_set_log(struct spa_log *global_log)
{
log = global_log;
@ -233,6 +248,7 @@ static const struct media_codec hfp_codec_a127 = {
.set_log = codec_set_log,
.start_decode = codec_start_decode,
.decode = codec_decode,
.produce_plc = codec_produce_plc,
.name = "lc3_a127",
.description = "LC3-24kHz",
};

View file

@ -195,6 +195,8 @@ static int codec_decode(void *data,
int res;
*dst_out = 0;
if (dst_size < LC3_SWB_BLOCK_SIZE)
return -EINVAL;
if (!this->data)
this->data = h2_reader_read(&this->h2, src, src_size, &consumed, &this->avail);
@ -205,14 +207,30 @@ static int codec_decode(void *data,
this->data = NULL;
if (res) {
h2_reader_init(&this->h2, true);
return -EINVAL;
/* fail decoding silently, so remainder of packet is processed */
spa_log_debug(log, "decoding failed: %d", res);
return consumed;
}
*dst_out = LC3_SWB_BLOCK_SIZE;
return consumed;
}
static int codec_produce_plc(void *data, void *dst, size_t dst_size)
{
struct impl *this = data;
int res;
if (dst_size < LC3_SWB_BLOCK_SIZE)
return -EINVAL;
res = lc3_decode(this->dec, NULL, 0, LC3_PCM_FORMAT_FLOAT, dst, 1);
if (res != 1)
return -EINVAL;
return LC3_SWB_BLOCK_SIZE;
}
static void codec_set_log(struct spa_log *global_log)
{
log = global_log;
@ -233,8 +251,10 @@ const struct media_codec hfp_codec_msbc = {
.set_log = codec_set_log,
.start_decode = codec_start_decode,
.decode = codec_decode,
.produce_plc = codec_produce_plc,
.name = "lc3_swb",
.description = "LC3-SWB",
.stream_pkt = true,
};
MEDIA_CODEC_EXPORT_DEF(

View file

@ -193,8 +193,9 @@ static int codec_decode(void *data,
this->data = NULL;
if (res < 0) {
h2_reader_init(&this->h2, true);
return res;
/* fail decoding silently, so remainder of packet is processed */
spa_log_debug(log_, "decoding failed: %d", res);
return consumed;
}
return consumed;
@ -222,6 +223,7 @@ const struct media_codec hfp_codec_msbc = {
.decode = codec_decode,
.name = "msbc",
.description = "MSBC",
.stream_pkt = true,
};
MEDIA_CODEC_EXPORT_DEF(

View file

@ -26,7 +26,7 @@
#define SPA_TYPE_INTERFACE_Bluez5CodecMedia SPA_TYPE_INFO_INTERFACE_BASE "Bluez5:Codec:Media:Private"
#define SPA_VERSION_BLUEZ5_CODEC_MEDIA 14
#define SPA_VERSION_BLUEZ5_CODEC_MEDIA 15
struct spa_bluez5_codec_a2dp {
struct spa_interface iface;
@ -88,6 +88,10 @@ struct media_codec {
const struct media_codec *duplex_codec; /**< Codec for non-standard A2DP duplex channel */
const bool stream_pkt; /**< If true, socket data may contain multiple packets.
* After successful decode, start_decode() should be
* called again to parse the remaining data. */
int (*get_bis_config)(const struct media_codec *codec, uint8_t *caps,
uint8_t *caps_size, struct spa_dict *settings,
struct bap_codec_qos *qos);
@ -202,6 +206,16 @@ struct media_codec {
void *dst, size_t dst_size,
size_t *dst_out);
/**
* Generate audio data corresponding to one lost packet, using codec internal
* packet loss concealment.
*
* NULL if not available.
*
* \return number of bytes produced, or < 0 for error
*/
int (*produce_plc) (void *data, void *dst, size_t dst_size);
int (*reduce_bitpool) (void *data);
int (*increase_bitpool) (void *data);