mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-10-29 05:40:27 -04:00
bluez5: backend-native: set HFP/HSP volume when connecting
The HFP/HSP HW volume on the device is not necessarily what we think it is. Sync the device volume to our value at connect time.
This commit is contained in:
parent
276c4bc554
commit
d82b3d6479
1 changed files with 119 additions and 9 deletions
|
|
@ -29,6 +29,7 @@
|
|||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <poll.h>
|
||||
|
||||
#include <bluetooth/bluetooth.h>
|
||||
#include <bluetooth/sco.h>
|
||||
|
|
@ -73,6 +74,7 @@ struct impl {
|
|||
struct spa_log *log;
|
||||
struct spa_loop *main_loop;
|
||||
struct spa_system *main_system;
|
||||
struct spa_loop_utils *loop_utils;
|
||||
struct spa_dbus *dbus;
|
||||
DBusConnection *conn;
|
||||
|
||||
|
|
@ -127,6 +129,7 @@ struct rfcomm {
|
|||
struct spa_hook transport_listener;
|
||||
enum spa_bt_profile profile;
|
||||
struct spa_source timer;
|
||||
struct spa_source *volume_sync_timer;
|
||||
char* path;
|
||||
bool has_volume;
|
||||
struct rfcomm_volume volumes[SPA_BT_VOLUME_ID_TERM];
|
||||
|
|
@ -225,6 +228,8 @@ finish:
|
|||
|
||||
static int codec_switch_stop_timer(struct rfcomm *rfcomm);
|
||||
|
||||
static void volume_sync_stop_timer(struct rfcomm *rfcomm);
|
||||
|
||||
static void rfcomm_free(struct rfcomm *rfcomm)
|
||||
{
|
||||
codec_switch_stop_timer(rfcomm);
|
||||
|
|
@ -247,6 +252,8 @@ static void rfcomm_free(struct rfcomm *rfcomm)
|
|||
close (rfcomm->source.fd);
|
||||
rfcomm->source.fd = -1;
|
||||
}
|
||||
if (rfcomm->volume_sync_timer)
|
||||
spa_loop_utils_destroy_source(rfcomm->backend->loop_utils, rfcomm->volume_sync_timer);
|
||||
free(rfcomm);
|
||||
}
|
||||
|
||||
|
|
@ -809,6 +816,7 @@ static bool rfcomm_hfp_ag(struct rfcomm *rfcomm, char* buf)
|
|||
rfcomm->hfp_ag_switching_codec = false;
|
||||
rfcomm->hfp_ag_initial_codec_setup = HFP_AG_INITIAL_CODEC_SETUP_NONE;
|
||||
codec_switch_stop_timer(rfcomm);
|
||||
volume_sync_stop_timer(rfcomm);
|
||||
|
||||
if (selected_codec != HFP_AUDIO_CODEC_CVSD && selected_codec != HFP_AUDIO_CODEC_MSBC) {
|
||||
spa_log_warn(backend->log, "unsupported codec negotiation: %d", selected_codec);
|
||||
|
|
@ -1203,6 +1211,18 @@ fail_close:
|
|||
return -1;
|
||||
}
|
||||
|
||||
static int rfcomm_ag_sync_volume(struct rfcomm *rfcomm, bool later);
|
||||
|
||||
static void wait_for_socket(int fd)
|
||||
{
|
||||
struct pollfd fds[1];
|
||||
const int timeout_ms = 500;
|
||||
|
||||
fds[0].fd = fd;
|
||||
fds[0].events = POLLIN | POLLERR | POLLHUP;
|
||||
poll(fds, 1, timeout_ms);
|
||||
}
|
||||
|
||||
static int sco_acquire_cb(void *data, bool optional)
|
||||
{
|
||||
struct spa_bt_transport *t = data;
|
||||
|
|
@ -1225,6 +1245,25 @@ static int sco_acquire_cb(void *data, bool optional)
|
|||
rfcomm_hfp_ag_set_cind(td->rfcomm, true);
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Send RFCOMM volume after connection is ready, and also after
|
||||
* a timeout.
|
||||
*
|
||||
* Some headsets adjust their HFP volume when in A2DP mode
|
||||
* without reporting via RFCOMM to us, so the volume level can
|
||||
* be out of sync, and we can't know what it is. Moreover, they may
|
||||
* take the first +VGS command after connection only partially
|
||||
* into account, and need a long enough timeout.
|
||||
*
|
||||
* E.g. with Sennheiser HD-250BT, the first +VGS changes the
|
||||
* actual volume, but does not update the level in the hardware
|
||||
* volume buttons, which is updated by an +VGS event only after
|
||||
* sufficient time is elapsed from the connection.
|
||||
*/
|
||||
wait_for_socket(sock);
|
||||
rfcomm_ag_sync_volume(td->rfcomm, false);
|
||||
rfcomm_ag_sync_volume(td->rfcomm, true);
|
||||
|
||||
t->fd = sock;
|
||||
|
||||
/* Fallback value */
|
||||
|
|
@ -1471,10 +1510,8 @@ fail_close:
|
|||
return -1;
|
||||
}
|
||||
|
||||
static int sco_set_volume_cb(void *data, int id, float volume)
|
||||
static int rfcomm_ag_set_volume(struct spa_bt_transport *t, int id)
|
||||
{
|
||||
struct spa_bt_transport *t = data;
|
||||
struct spa_bt_transport_volume *t_volume = &t->volumes[id];
|
||||
struct transport_data *td = t->user_data;
|
||||
struct rfcomm *rfcomm = td->rfcomm;
|
||||
const char *format;
|
||||
|
|
@ -1485,12 +1522,7 @@ static int sco_set_volume_cb(void *data, int id, float volume)
|
|||
|| !(rfcomm->has_volume && rfcomm->volumes[id].active))
|
||||
return -ENOTSUP;
|
||||
|
||||
value = spa_bt_volume_linear_to_hw(volume, t_volume->hw_volume_max);
|
||||
t_volume->volume = volume;
|
||||
|
||||
if (rfcomm->volumes[id].hw_volume == value)
|
||||
return 0;
|
||||
rfcomm->volumes[id].hw_volume = value;
|
||||
value = rfcomm->volumes[id].hw_volume;
|
||||
|
||||
if (id == SPA_BT_VOLUME_ID_RX)
|
||||
if (rfcomm->profile & SPA_BT_PROFILE_HFP_HF)
|
||||
|
|
@ -1511,6 +1543,29 @@ static int sco_set_volume_cb(void *data, int id, float volume)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int sco_set_volume_cb(void *data, int id, float volume)
|
||||
{
|
||||
struct spa_bt_transport *t = data;
|
||||
struct spa_bt_transport_volume *t_volume = &t->volumes[id];
|
||||
struct transport_data *td = t->user_data;
|
||||
struct rfcomm *rfcomm = td->rfcomm;
|
||||
int value;
|
||||
|
||||
if (!rfcomm_volume_enabled(rfcomm)
|
||||
|| !(rfcomm->profile & SPA_BT_PROFILE_HEADSET_HEAD_UNIT)
|
||||
|| !(rfcomm->has_volume && rfcomm->volumes[id].active))
|
||||
return -ENOTSUP;
|
||||
|
||||
value = spa_bt_volume_linear_to_hw(volume, t_volume->hw_volume_max);
|
||||
t_volume->volume = volume;
|
||||
|
||||
if (rfcomm->volumes[id].hw_volume == value)
|
||||
return 0;
|
||||
rfcomm->volumes[id].hw_volume = value;
|
||||
|
||||
return rfcomm_ag_set_volume(t, id);
|
||||
}
|
||||
|
||||
static const struct spa_bt_transport_implementation sco_transport_impl = {
|
||||
SPA_VERSION_BT_TRANSPORT_IMPLEMENTATION,
|
||||
.acquire = sco_acquire_cb,
|
||||
|
|
@ -1570,6 +1625,60 @@ static int codec_switch_stop_timer(struct rfcomm *rfcomm)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void volume_sync_stop_timer(struct rfcomm *rfcomm)
|
||||
{
|
||||
if (rfcomm->volume_sync_timer)
|
||||
spa_loop_utils_update_timer(rfcomm->backend->loop_utils, rfcomm->volume_sync_timer,
|
||||
NULL, NULL, false);
|
||||
}
|
||||
|
||||
static void volume_sync_timer_event(void *data, uint64_t expirations)
|
||||
{
|
||||
struct rfcomm *rfcomm = data;
|
||||
|
||||
volume_sync_stop_timer(rfcomm);
|
||||
|
||||
if (rfcomm->transport) {
|
||||
rfcomm_ag_set_volume(rfcomm->transport, SPA_BT_VOLUME_ID_TX);
|
||||
rfcomm_ag_set_volume(rfcomm->transport, SPA_BT_VOLUME_ID_RX);
|
||||
}
|
||||
}
|
||||
|
||||
static int volume_sync_start_timer(struct rfcomm *rfcomm)
|
||||
{
|
||||
struct timespec ts;
|
||||
const uint64_t timeout = 1500 * SPA_NSEC_PER_MSEC;
|
||||
|
||||
if (rfcomm->volume_sync_timer == NULL)
|
||||
rfcomm->volume_sync_timer = spa_loop_utils_add_timer(rfcomm->backend->loop_utils,
|
||||
volume_sync_timer_event, rfcomm);
|
||||
|
||||
if (rfcomm->volume_sync_timer == NULL)
|
||||
return -EIO;
|
||||
|
||||
ts.tv_sec = timeout / SPA_NSEC_PER_SEC;
|
||||
ts.tv_nsec = timeout % SPA_NSEC_PER_SEC;
|
||||
spa_loop_utils_update_timer(rfcomm->backend->loop_utils, rfcomm->volume_sync_timer,
|
||||
&ts, NULL, false);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rfcomm_ag_sync_volume(struct rfcomm *rfcomm, bool later)
|
||||
{
|
||||
if (rfcomm->transport == NULL)
|
||||
return -ENOENT;
|
||||
|
||||
if (!later) {
|
||||
rfcomm_ag_set_volume(rfcomm->transport, SPA_BT_VOLUME_ID_TX);
|
||||
rfcomm_ag_set_volume(rfcomm->transport, SPA_BT_VOLUME_ID_RX);
|
||||
} else {
|
||||
volume_sync_start_timer(rfcomm);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void codec_switch_timer_event(struct spa_source *source)
|
||||
{
|
||||
struct rfcomm *rfcomm = source->data;
|
||||
|
|
@ -2222,6 +2331,7 @@ struct spa_bt_backend *backend_native_new(struct spa_bt_monitor *monitor,
|
|||
backend->dbus = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DBus);
|
||||
backend->main_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Loop);
|
||||
backend->main_system = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_System);
|
||||
backend->loop_utils = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_LoopUtils);
|
||||
backend->conn = dbus_connection;
|
||||
backend->sco.fd = -1;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue