// SPDX-License-Identifier: Apache-2.0 /* * Copyright (C) 2014 Tieto Poland * */ #define _GNU_SOURCE #include #include #include #include #include #include "audio-msg.h" #include "hal-audio.h" #include "hal-log.h" #include "profiles/audio/a2dp-codecs.h" #define APTX_SO_NAME "libbt-aptx.so" struct aptx_data { a2dp_aptx_t aptx; void *enc; }; static const a2dp_aptx_t aptx_presets[] = { { .info = A2DP_SET_VENDOR_ID_CODEC_ID(APTX_VENDOR_ID, APTX_CODEC_ID), .frequency = APTX_SAMPLING_FREQ_44100 | APTX_SAMPLING_FREQ_48000, .channel_mode = APTX_CHANNEL_MODE_STEREO, }, { .info = A2DP_SET_VENDOR_ID_CODEC_ID(APTX_VENDOR_ID, APTX_CODEC_ID), .frequency = APTX_SAMPLING_FREQ_48000, .channel_mode = APTX_CHANNEL_MODE_STEREO, }, { .info = A2DP_SET_VENDOR_ID_CODEC_ID(APTX_VENDOR_ID, APTX_CODEC_ID), .frequency = APTX_SAMPLING_FREQ_44100, .channel_mode = APTX_CHANNEL_MODE_STEREO, }, }; static void *aptx_handle; static int aptx_btencsize; static int (*aptx_init)(void *, short); static int (*aptx_encode)(void *, void *, void *, void *); static bool aptx_load(void) { const char * (*aptx_version)(void); const char * (*aptx_build)(void); int (*aptx_sizeofenc)(void); aptx_handle = dlopen(APTX_SO_NAME, RTLD_LAZY); if (!aptx_handle) { error("APTX: failed to open library %s (%s)", APTX_SO_NAME, dlerror()); return false; } aptx_version = dlsym(aptx_handle, "aptxbtenc_version"); aptx_build = dlsym(aptx_handle, "aptxbtenc_build"); if (aptx_version && aptx_build) info("APTX: using library version %s build %s", aptx_version(), aptx_build()); else warn("APTX: cannot retrieve library version"); aptx_sizeofenc = dlsym(aptx_handle, "SizeofAptxbtenc"); aptx_init = dlsym(aptx_handle, "aptxbtenc_init"); aptx_encode = dlsym(aptx_handle, "aptxbtenc_encodestereo"); if (!aptx_sizeofenc || !aptx_init || !aptx_encode) { error("APTX: failed initialize library"); dlclose(aptx_handle); aptx_handle = NULL; return false; } aptx_btencsize = aptx_sizeofenc(); info("APTX: codec library initialized (size=%d)", aptx_btencsize); return true; } static void aptx_unload(void) { if (!aptx_handle) return; dlclose(aptx_handle); aptx_handle = NULL; } static int aptx_get_presets(struct audio_preset *preset, size_t *len) { int i; int count; size_t new_len = 0; uint8_t *ptr = (uint8_t *) preset; size_t preset_size = sizeof(*preset) + sizeof(a2dp_aptx_t); DBG(""); count = sizeof(aptx_presets) / sizeof(aptx_presets[0]); for (i = 0; i < count; i++) { preset = (struct audio_preset *) ptr; if (new_len + preset_size > *len) break; preset->len = sizeof(a2dp_aptx_t); memcpy(preset->data, &aptx_presets[i], preset->len); new_len += preset_size; ptr += preset_size; } *len = new_len; return i; } static bool aptx_codec_init(struct audio_preset *preset, uint16_t payload_len, void **codec_data) { struct aptx_data *aptx_data; DBG(""); if (preset->len != sizeof(a2dp_aptx_t)) { error("APTX: preset size mismatch"); return false; } aptx_data = malloc(sizeof(*aptx_data)); if (!aptx_data) return false; memset(aptx_data, 0, sizeof(*aptx_data)); memcpy(&aptx_data->aptx, preset->data, preset->len); aptx_data->enc = calloc(1, aptx_btencsize); if (!aptx_data->enc) { error("APTX: failed to create encoder"); free(aptx_data); return false; } /* 1 = big-endian, this is what devices are using */ aptx_init(aptx_data->enc, 1); *codec_data = aptx_data; return true; } static bool aptx_cleanup(void *codec_data) { struct aptx_data *aptx_data = (struct aptx_data *) codec_data; free(aptx_data->enc); free(codec_data); return true; } static bool aptx_get_config(void *codec_data, struct audio_input_config *config) { struct aptx_data *aptx_data = (struct aptx_data *) codec_data; config->rate = aptx_data->aptx.frequency & APTX_SAMPLING_FREQ_48000 ? 48000 : 44100; config->channels = AUDIO_CHANNEL_OUT_STEREO; config->format = AUDIO_FORMAT_PCM_16_BIT; return true; } static size_t aptx_get_buffer_size(void *codec_data) { /* TODO: return actual value */ return 0; } static size_t aptx_get_mediapacket_duration(void *codec_data) { /* TODO: return actual value */ return 0; } static ssize_t aptx_encode_mediapacket(void *codec_data, const uint8_t *buffer, size_t len, struct media_packet *mp, size_t mp_data_len, size_t *written) { struct aptx_data *aptx_data = (struct aptx_data *) codec_data; const int16_t *ptr = (const void *) buffer; size_t bytes_in = 0; size_t bytes_out = 0; while ((len - bytes_in) >= 16 && (mp_data_len - bytes_out) >= 4) { int pcm_l[4], pcm_r[4]; int i; for (i = 0; i < 4; i++) { pcm_l[i] = ptr[0]; pcm_r[i] = ptr[1]; ptr += 2; } aptx_encode(aptx_data->enc, pcm_l, pcm_r, &mp->data[bytes_out]); bytes_in += 16; bytes_out += 4; } *written = bytes_out; return bytes_in; } static bool aptx_update_qos(void *codec_data, uint8_t op) { /* * aptX has constant bitrate of 352kbps (with constant 4:1 compression * ratio) thus QoS is not possible here. */ return false; } static const struct audio_codec codec = { .type = A2DP_CODEC_VENDOR, .use_rtp = false, .load = aptx_load, .unload = aptx_unload, .get_presets = aptx_get_presets, .init = aptx_codec_init, .cleanup = aptx_cleanup, .get_config = aptx_get_config, .get_buffer_size = aptx_get_buffer_size, .get_mediapacket_duration = aptx_get_mediapacket_duration, .encode_mediapacket = aptx_encode_mediapacket, .update_qos = aptx_update_qos, }; const struct audio_codec *codec_aptx(void) { return &codec; }