/*
|
* BlueALSA - utils.c
|
* Copyright (c) 2016-2018 Arkadiusz Bokowy
|
*
|
* This file is a part of bluez-alsa.
|
*
|
* This project is licensed under the terms of the MIT license.
|
*
|
*/
|
|
#include "utils.h"
|
|
#include <errno.h>
|
#include <stdlib.h>
|
#include <string.h>
|
|
#include <bluetooth/bluetooth.h>
|
#include <bluetooth/hci_lib.h>
|
#include <bluetooth/sco.h>
|
|
#if ENABLE_LDAC
|
# include "ldacBT.h"
|
#endif
|
|
#include "a2dp-codecs.h"
|
#include "bluez.h"
|
#include "shared/log.h"
|
|
|
/**
|
* Calculate the optimum bitpool for given parameters.
|
*
|
* @param freq Sampling frequency.
|
* @param model Channel mode.
|
* @return Coded SBC bitpool. */
|
int a2dp_sbc_default_bitpool(int freq, int mode) {
|
switch (freq) {
|
case SBC_SAMPLING_FREQ_16000:
|
case SBC_SAMPLING_FREQ_32000:
|
return 53;
|
case SBC_SAMPLING_FREQ_44100:
|
switch (mode) {
|
case SBC_CHANNEL_MODE_MONO:
|
case SBC_CHANNEL_MODE_DUAL_CHANNEL:
|
return 31;
|
case SBC_CHANNEL_MODE_STEREO:
|
case SBC_CHANNEL_MODE_JOINT_STEREO:
|
return 53;
|
default:
|
warn("Invalid channel mode: %u", mode);
|
return 53;
|
}
|
case SBC_SAMPLING_FREQ_48000:
|
switch (mode) {
|
case SBC_CHANNEL_MODE_MONO:
|
case SBC_CHANNEL_MODE_DUAL_CHANNEL:
|
return 29;
|
case SBC_CHANNEL_MODE_STEREO:
|
case SBC_CHANNEL_MODE_JOINT_STEREO:
|
return 51;
|
default:
|
warn("Invalid channel mode: %u", mode);
|
return 51;
|
}
|
default:
|
warn("Invalid sampling freq: %u", freq);
|
return 53;
|
}
|
}
|
|
/**
|
* Get the list of all available HCI controllers.
|
*
|
* @param di The address to the device info structure pointer, where the list
|
* of all available devices will be stored. Allocated memory should be freed
|
* with the free().
|
* @param num The address, where the number of initialized device structures
|
* will be stored.
|
* @return On success this function returns 0. Otherwise, -1 is returned and
|
* errno is set to indicate the error. */
|
int hci_devlist(struct hci_dev_info **di, int *num) {
|
|
int i;
|
|
if ((*di = malloc(HCI_MAX_DEV * sizeof(**di))) == NULL)
|
return -1;
|
|
for (i = *num = 0; i < HCI_MAX_DEV; i++)
|
if (hci_devinfo(i, &(*di)[*num]) == 0)
|
(*num)++;
|
|
return 0;
|
}
|
|
/**
|
* Open SCO link for given Bluetooth device.
|
*
|
* @param di The address to the HCI device info structure for which the SCO
|
* link should be established.
|
* @param ba Pointer to the Bluetooth address structure for a target device.
|
* @param transparent Use transparent mode for voice transmission.
|
* @return On success this function returns socket file descriptor. Otherwise,
|
* -1 is returned and errno is set to indicate the error. */
|
int hci_open_sco(const struct hci_dev_info *di, const bdaddr_t *ba, bool transparent) {
|
|
struct sockaddr_sco addr_hci = {
|
.sco_family = AF_BLUETOOTH,
|
.sco_bdaddr = di->bdaddr,
|
};
|
struct sockaddr_sco addr_dev = {
|
.sco_family = AF_BLUETOOTH,
|
.sco_bdaddr = *ba,
|
};
|
int dd, err;
|
|
if ((dd = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO)) == -1)
|
return -1;
|
if (bind(dd, (struct sockaddr *)&addr_hci, sizeof(addr_hci)) == -1)
|
goto fail;
|
|
if (transparent) {
|
struct bt_voice voice = {
|
.setting = BT_VOICE_TRANSPARENT,
|
};
|
if (setsockopt(dd, SOL_BLUETOOTH, BT_VOICE, &voice, sizeof(voice)) == -1)
|
goto fail;
|
}
|
|
if (connect(dd, (struct sockaddr *)&addr_dev, sizeof(addr_dev)) == -1)
|
goto fail;
|
|
return dd;
|
|
fail:
|
err = errno;
|
close(dd);
|
errno = err;
|
return -1;
|
}
|
|
int hci_submit_cmd_wait(uint16_t ogf, uint16_t ocf, uint8_t *params,
|
uint8_t plen)
|
{
|
int fd;
|
uint16_t index = 0;
|
uint8_t status;
|
int ret;
|
struct hci_request rq;
|
|
fd = hci_open_dev(index);
|
if (fd < 0) {
|
error("Couldn't open device: %s(%d)\n", strerror(errno), errno);
|
return -1;
|
}
|
|
memset(&rq, 0, sizeof(rq));
|
rq.ogf = ogf;
|
rq.ocf = ocf;
|
rq.cparam = params;
|
rq.clen = plen;
|
rq.rparam = &status;
|
rq.rlen = 1;
|
|
ret = hci_send_req(fd, &rq, 1000);
|
if (status || ret < 0) {
|
error("Can't send cmd for hci%d: %s (%d)\n", index,
|
strerror(errno), errno);
|
hci_close_dev(fd);
|
return -1;
|
}
|
|
hci_close_dev(fd);
|
|
return 0;
|
}
|
|
/**
|
* Get BlueZ D-Bus object path for given profile and codec.
|
*
|
* @param profile Bluetooth profile.
|
* @param codec Bluetooth profile codec.
|
* @return This function returns BlueZ D-Bus object path. */
|
const char *g_dbus_get_profile_object_path(enum bluetooth_profile profile, uint16_t codec) {
|
switch (profile) {
|
case BLUETOOTH_PROFILE_A2DP_SOURCE:
|
switch (codec) {
|
case A2DP_CODEC_SBC:
|
return "/A2DP/SBC/Source";
|
#if ENABLE_MPEG
|
case A2DP_CODEC_MPEG12:
|
return "/A2DP/MPEG12/Source";
|
#endif
|
#if ENABLE_AAC
|
case A2DP_CODEC_MPEG24:
|
return "/A2DP/MPEG24/Source";
|
#endif
|
#if ENABLE_APTX
|
case A2DP_CODEC_VENDOR_APTX:
|
return "/A2DP/APTX/Source";
|
#endif
|
#if ENABLE_LDAC
|
case A2DP_CODEC_VENDOR_LDAC:
|
return "/A2DP/LDAC/Source";
|
#endif
|
default:
|
warn("Unsupported A2DP codec: %#x", codec);
|
return "/A2DP/Source";
|
}
|
case BLUETOOTH_PROFILE_A2DP_SINK:
|
switch (codec) {
|
case A2DP_CODEC_SBC:
|
return "/A2DP/SBC/Sink";
|
#if ENABLE_MPEG
|
case A2DP_CODEC_MPEG12:
|
return "/A2DP/MPEG12/Sink";
|
#endif
|
#if ENABLE_AAC
|
case A2DP_CODEC_MPEG24:
|
return "/A2DP/MPEG24/Sink";
|
#endif
|
#if ENABLE_APTX
|
case A2DP_CODEC_VENDOR_APTX:
|
return "/A2DP/APTX/Sink";
|
#endif
|
#if ENABLE_LDAC
|
case A2DP_CODEC_VENDOR_LDAC:
|
return "/A2DP/LDAC/Sink";
|
#endif
|
default:
|
warn("Unsupported A2DP codec: %#x", codec);
|
return "/A2DP/Sink";
|
}
|
case BLUETOOTH_PROFILE_HSP_HS:
|
return "/HSP/Headset";
|
case BLUETOOTH_PROFILE_HSP_AG:
|
return "/HSP/AudioGateway";
|
case BLUETOOTH_PROFILE_HFP_HF:
|
return "/HFP/HandsFree";
|
case BLUETOOTH_PROFILE_HFP_AG:
|
return "/HFP/AudioGateway";
|
case BLUETOOTH_PROFILE_NULL:
|
break;
|
}
|
return "/";
|
}
|
|
/**
|
* Convert BlueZ D-Bus object path into a Bluetooth profile.
|
*
|
* @param path BlueZ D-Bus object path.
|
* @return On success this function returns Bluetooth profile. If object
|
* path cannot be recognize, NULL profile is returned. */
|
enum bluetooth_profile g_dbus_object_path_to_profile(const char *path) {
|
if (strncmp(path, "/A2DP", 5) == 0) {
|
if (strstr(path + 5, "/Source") != NULL)
|
return BLUETOOTH_PROFILE_A2DP_SOURCE;
|
if (strstr(path + 5, "/Sink") != NULL)
|
return BLUETOOTH_PROFILE_A2DP_SINK;
|
}
|
if (strncmp(path, "/HSP", 4) == 0) {
|
if (strcmp(path + 4, "/Headset") == 0)
|
return BLUETOOTH_PROFILE_HSP_HS;
|
if (strcmp(path + 4, "/AudioGateway") == 0)
|
return BLUETOOTH_PROFILE_HSP_AG;
|
}
|
if (strncmp(path, "/HFP", 4) == 0) {
|
if (strcmp(path + 4, "/HandsFree") == 0)
|
return BLUETOOTH_PROFILE_HFP_HF;
|
if (strcmp(path + 4, "/AudioGateway") == 0)
|
return BLUETOOTH_PROFILE_HFP_AG;
|
}
|
return BLUETOOTH_PROFILE_NULL;
|
}
|
|
/**
|
* Convert BlueZ D-Bus device path into a bdaddr_t structure.
|
*
|
* @param path BlueZ D-Bus device path.
|
* @param addr Address where the parsed address will be stored.
|
* @return On success this function returns 0. Otherwise, -1 is returned. */
|
int g_dbus_device_path_to_bdaddr(const char *path, bdaddr_t *addr) {
|
|
char *tmp, *p;
|
int ret;
|
|
if ((path = strrchr(path, '/')) == NULL)
|
return -1;
|
if ((path = strstr(path, "dev_")) == NULL)
|
return -1;
|
if ((tmp = strdup(path + 4)) == NULL)
|
return -1;
|
|
for (p = tmp; *p != '\0'; p++)
|
if (*p == '_')
|
*p = ':';
|
|
ret = str2ba(tmp, addr);
|
|
free(tmp);
|
return ret;
|
}
|
|
/**
|
* Get a property of a given D-Bus interface.
|
*
|
* @param conn D-Bus connection handler.
|
* @param name Valid D-Bus name or NULL.
|
* @param path Valid D-Bus object path.
|
* @param interface Interface with the given property.
|
* @param property The property name.
|
* @return On success this function returns variant containing property value.
|
* Otherwise, NULL is returned. */
|
GVariant *g_dbus_get_property(GDBusConnection *conn, const char *name,
|
const char *path, const char *interface, const char *property) {
|
|
GDBusMessage *msg = NULL, *rep = NULL;
|
GVariant *value = NULL;
|
GError *err = NULL;
|
|
msg = g_dbus_message_new_method_call(name, path, "org.freedesktop.DBus.Properties", "Get");
|
g_dbus_message_set_body(msg, g_variant_new("(ss)", interface, property));
|
|
if ((rep = g_dbus_connection_send_message_with_reply_sync(conn, msg,
|
G_DBUS_SEND_MESSAGE_FLAGS_NONE, -1, NULL, NULL, &err)) == NULL)
|
goto fail;
|
|
if (g_dbus_message_get_message_type(rep) == G_DBUS_MESSAGE_TYPE_ERROR) {
|
g_dbus_message_to_gerror(rep, &err);
|
goto fail;
|
}
|
|
g_variant_get(g_dbus_message_get_body(rep), "(v)", &value);
|
|
fail:
|
|
if (msg != NULL)
|
g_object_unref(msg);
|
if (rep != NULL)
|
g_object_unref(rep);
|
if (err != NULL) {
|
warn("Couldn't get property: %s", err->message);
|
g_error_free(err);
|
}
|
|
return value;
|
}
|
|
/**
|
* Set a property of a given D-Bus interface.
|
*
|
* @param conn D-Bus connection handler.
|
* @param name Valid D-Bus name or NULL.
|
* @param path Valid D-Bus object path.
|
* @param interface Interface with the given property.
|
* @param property The property name.
|
* @param value Variant containing property value.
|
* @return On success this function returns TRUE. Otherwise, FALSE. */
|
gboolean g_dbus_set_property(GDBusConnection *conn, const char *name,
|
const char *path, const char *interface, const char *property,
|
const GVariant *value) {
|
|
GDBusMessage *msg = NULL, *rep = NULL;
|
GError *err = NULL;
|
|
msg = g_dbus_message_new_method_call(name, path, "org.freedesktop.DBus.Properties", "Set");
|
g_dbus_message_set_body(msg, g_variant_new("(ssv)", interface, property, value));
|
|
if ((rep = g_dbus_connection_send_message_with_reply_sync(conn, msg,
|
G_DBUS_SEND_MESSAGE_FLAGS_NONE, -1, NULL, NULL, &err)) == NULL)
|
goto fail;
|
|
if (g_dbus_message_get_message_type(rep) == G_DBUS_MESSAGE_TYPE_ERROR) {
|
g_dbus_message_to_gerror(rep, &err);
|
goto fail;
|
}
|
|
fail:
|
|
if (msg != NULL)
|
g_object_unref(msg);
|
if (rep != NULL)
|
g_object_unref(rep);
|
if (err != NULL) {
|
warn("Couldn't set property: %s", err->message);
|
g_error_free(err);
|
return FALSE;
|
}
|
|
return TRUE;
|
}
|
|
/**
|
* Convert Bluetooth profile into a human-readable string.
|
*
|
* @param profile Bluetooth profile.
|
* @return Human-readable string. */
|
const char *bluetooth_profile_to_string(enum bluetooth_profile profile) {
|
switch (profile) {
|
case BLUETOOTH_PROFILE_NULL:
|
return "N/A";
|
case BLUETOOTH_PROFILE_A2DP_SOURCE:
|
return "A2DP Source";
|
case BLUETOOTH_PROFILE_A2DP_SINK:
|
return "A2DP Sink";
|
case BLUETOOTH_PROFILE_HSP_HS:
|
return "HSP Headset";
|
case BLUETOOTH_PROFILE_HSP_AG:
|
return "HSP Audio Gateway";
|
case BLUETOOTH_PROFILE_HFP_HF:
|
return "HFP Hands-Free";
|
case BLUETOOTH_PROFILE_HFP_AG:
|
return "HFP Audio Gateway";
|
}
|
return "N/A";
|
}
|
|
/**
|
* Convert Bluetooth A2DP codec into a human-readable string.
|
*
|
* @param codec Bluetooth A2DP audio codec.
|
* @return Human-readable string. */
|
const char *bluetooth_a2dp_codec_to_string(uint16_t codec) {
|
switch (codec) {
|
case A2DP_CODEC_SBC:
|
return "SBC";
|
#if ENABLE_MPEG
|
case A2DP_CODEC_MPEG12:
|
return "MPEG";
|
#endif
|
#if ENABLE_AAC
|
case A2DP_CODEC_MPEG24:
|
return "AAC";
|
#endif
|
#if ENABLE_APTX
|
case A2DP_CODEC_VENDOR_APTX:
|
return "APT-X";
|
#endif
|
#if ENABLE_APTX_HD
|
case A2DP_CODEC_VENDOR_APTX_HD:
|
return "APT-X HD";
|
#endif
|
#if ENABLE_LDAC
|
case A2DP_CODEC_VENDOR_LDAC:
|
return "LDAC";
|
#endif
|
}
|
return "N/A";
|
}
|
|
/**
|
* Convert Bluetooth address into a human-readable string.
|
*
|
* This function returns statically allocated buffer. It is not by any means
|
* thread safe. This function should be used for debugging purposes only.
|
*
|
* For debugging purposes, one could use the batostr() function provided by
|
* the bluez library. However, this function converts the Bluetooth address
|
* to the string with a incorrect (reversed) bytes order...
|
*
|
* @param ba Pointer to the Bluetooth address structure.
|
* @return On success this function returns statically allocated buffer with
|
* human-readable Bluetooth address. On error, it returns NULL. */
|
const char *batostr_(const bdaddr_t *ba) {
|
static char addr[18];
|
if (ba2str(ba, addr) > 0)
|
return addr;
|
return NULL;
|
}
|
|
/**
|
* Scale PCM signal stored in the buffer.
|
*
|
* Neutral value for scaling factor is 1.0. It is possible to increase
|
* signal gain by using scaling factor values greater than 1, however
|
* clipping will most certainly occur.
|
*
|
* @param buffer Address to the buffer where the PCM signal is stored.
|
* @param size The number of samples in the buffer.
|
* @param channels The number of channels in the buffer.
|
* @param ch1_scale The scaling factor for 1st channel.
|
* @param ch1_scale The scaling factor for 2nd channel. */
|
void snd_pcm_scale_s16le(int16_t *buffer, size_t size, int channels,
|
double ch1_scale, double ch2_scale) {
|
switch (channels) {
|
case 1:
|
if (ch1_scale != 1.0)
|
while (size--)
|
buffer[size] = buffer[size] * ch1_scale;
|
break;
|
case 2:
|
if (ch1_scale != 1.0 || ch2_scale != 1.0)
|
while (size--) {
|
double scale = size % 2 == 0 ? ch1_scale : ch2_scale;
|
buffer[size] = buffer[size] * scale;
|
}
|
break;
|
}
|
}
|
|
#if ENABLE_AAC
|
/**
|
* Get string representation of the FDK-AAC decoder error code.
|
*
|
* @param error FDK-AAC decoder error code.
|
* @return Human-readable string. */
|
const char *aacdec_strerror(AAC_DECODER_ERROR err) {
|
switch (err) {
|
case AAC_DEC_OK:
|
return "Success";
|
case AAC_DEC_OUT_OF_MEMORY:
|
return "Out of memory";
|
case AAC_DEC_TRANSPORT_SYNC_ERROR:
|
return "Transport sync error";
|
case AAC_DEC_NOT_ENOUGH_BITS:
|
return "Not enough bits";
|
case AAC_DEC_INVALID_HANDLE:
|
return "Invalid handle";
|
case AAC_DEC_UNSUPPORTED_AOT:
|
return "Unsupported AOT";
|
case AAC_DEC_UNSUPPORTED_FORMAT:
|
return "Unsupported format";
|
case AAC_DEC_UNSUPPORTED_ER_FORMAT:
|
return "Unsupported ER format";
|
case AAC_DEC_UNSUPPORTED_EPCONFIG:
|
return "Unsupported EP format";
|
case AAC_DEC_UNSUPPORTED_MULTILAYER:
|
return "Unsupported multilayer";
|
case AAC_DEC_UNSUPPORTED_CHANNELCONFIG:
|
return "Unsupported channels";
|
case AAC_DEC_UNSUPPORTED_SAMPLINGRATE:
|
return "Unsupported sample rate";
|
case AAC_DEC_INVALID_SBR_CONFIG:
|
return "Unsupported SBR";
|
case AAC_DEC_SET_PARAM_FAIL:
|
return "Unsupported parameter";
|
case AAC_DEC_NEED_TO_RESTART:
|
return "Restart required";
|
case AAC_DEC_TRANSPORT_ERROR:
|
return "Transport error";
|
case AAC_DEC_PARSE_ERROR:
|
return "Parse error";
|
case AAC_DEC_UNSUPPORTED_EXTENSION_PAYLOAD:
|
return "Unsupported extension payload";
|
case AAC_DEC_DECODE_FRAME_ERROR:
|
return "Bitstream corrupted";
|
case AAC_DEC_CRC_ERROR:
|
return "CRC mismatch";
|
case AAC_DEC_INVALID_CODE_BOOK:
|
return "Invalid codebook";
|
case AAC_DEC_UNSUPPORTED_PREDICTION:
|
return "Unsupported prediction";
|
case AAC_DEC_UNSUPPORTED_CCE:
|
return "Unsupported CCE";
|
case AAC_DEC_UNSUPPORTED_LFE:
|
return "Unsupported LFE";
|
case AAC_DEC_UNSUPPORTED_GAIN_CONTROL_DATA:
|
return "Unsupported gain control data";
|
case AAC_DEC_UNSUPPORTED_SBA:
|
return "Unsupported SBA";
|
case AAC_DEC_TNS_READ_ERROR:
|
return "TNS read error";
|
case AAC_DEC_RVLC_ERROR:
|
return "RVLC decode error";
|
case AAC_DEC_ANC_DATA_ERROR:
|
return "Ancillary data error";
|
case AAC_DEC_TOO_SMALL_ANC_BUFFER:
|
return "Too small ancillary buffer";
|
case AAC_DEC_TOO_MANY_ANC_ELEMENTS:
|
return "Too many ancillary elements";
|
default:
|
debug("Unknown error code: %#x", err);
|
return "Unknown error";
|
}
|
}
|
#endif
|
|
#if ENABLE_AAC
|
/**
|
* Get string representation of the FDK-AAC encoder error code.
|
*
|
* @param error FDK-AAC encoder error code.
|
* @return Human-readable string. */
|
const char *aacenc_strerror(AACENC_ERROR err) {
|
switch (err) {
|
case AACENC_OK:
|
return "Success";
|
case AACENC_INVALID_HANDLE:
|
return "Invalid handle";
|
case AACENC_MEMORY_ERROR:
|
return "Out of memory";
|
case AACENC_UNSUPPORTED_PARAMETER:
|
return "Unsupported parameter";
|
case AACENC_INVALID_CONFIG:
|
return "Invalid config";
|
case AACENC_INIT_ERROR:
|
return "Initialization error";
|
case AACENC_INIT_AAC_ERROR:
|
return "AAC library initialization error";
|
case AACENC_INIT_SBR_ERROR:
|
return "SBR library initialization error";
|
case AACENC_INIT_TP_ERROR:
|
return "Transport library initialization error";
|
case AACENC_INIT_META_ERROR:
|
return "Metadata library initialization error";
|
case AACENC_ENCODE_ERROR:
|
return "Encoding error";
|
case AACENC_ENCODE_EOF:
|
return "End of file";
|
default:
|
debug("Unknown error code: %#x", err);
|
return "Unknown error";
|
}
|
}
|
#endif
|
|
#if ENABLE_LDAC
|
/**
|
* Get string representation of the LDAC error code.
|
*
|
* @param error LDAC error code.
|
* @return Human-readable string. */
|
const char *ldacBT_strerror(int err) {
|
switch (LDACBT_API_ERR(err)) {
|
case LDACBT_ERR_NONE:
|
return "Success";
|
case LDACBT_ERR_ASSERT_SAMPLING_FREQ:
|
case LDACBT_ERR_ASSERT_SUP_SAMPLING_FREQ:
|
case LDACBT_ERR_CHECK_SAMPLING_FREQ:
|
return "Invalid sample rate";
|
case LDACBT_ERR_ASSERT_CHANNEL_CONFIG:
|
case LDACBT_ERR_CHECK_CHANNEL_CONFIG:
|
return "Invalid channel config";
|
case LDACBT_ERR_ASSERT_FRAME_LENGTH:
|
case LDACBT_ERR_ASSERT_SUP_FRAME_LENGTH:
|
case LDACBT_ERR_ASSERT_FRAME_STATUS:
|
return "Invalid frame status";
|
case LDACBT_ERR_ASSERT_NSHIFT:
|
return "Invalid N-shift";
|
case LDACBT_ERR_ASSERT_CHANNEL_MODE:
|
return "Invalid channel mode";
|
case LDACBT_ERR_ALTER_EQMID_LIMITED:
|
return "EQMID limited";
|
case LDACBT_ERR_HANDLE_NOT_INIT:
|
return "Invalid handle";
|
case LDACBT_ERR_ILL_EQMID:
|
return "Unsupported EQMID";
|
case LDACBT_ERR_ILL_SAMPLING_FREQ:
|
return "Unsupported sample rate";
|
case LDACBT_ERR_ILL_NUM_CHANNEL:
|
return "Unsupported channels";
|
case LDACBT_ERR_ILL_MTU_SIZE:
|
return "Unsupported MTU";
|
default:
|
debug("Unknown error code: %#x", err);
|
return "Unknown error";
|
}
|
}
|
#endif
|