Skip to content

Commit

Permalink
Persistent storage for PCM volume and mute state
Browse files Browse the repository at this point in the history
  • Loading branch information
arkq committed Sep 2, 2022
1 parent 8015dbb commit a0a83a3
Show file tree
Hide file tree
Showing 14 changed files with 413 additions and 5 deletions.
5 changes: 3 additions & 2 deletions NEWS
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
unreleased
==========

Optional non-dynamic operation mode for ALSA control plug-in
Optional extended controls for ALSA control plug-in
- persistent storage for PCM volume and mute state
- optional non-dynamic operation mode for ALSA control plug-in
- optional extended controls for ALSA control plug-in

bluez-alsa v4.0.0 (2022-06-03)
==============================
Expand Down
16 changes: 15 additions & 1 deletion configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -299,14 +299,28 @@ AC_ARG_WITH([alsaplugindir],
[alsaplugindir=$($PKG_CONFIG --variable=libdir alsa)/alsa-lib])
AC_SUBST([ALSA_PLUGIN_DIR], [$alsaplugindir])

# If no --localstatedir and --prefix options were given, use the /var as a
# default value for the local state directory.
if test "$localstatedir" = "\${prefix}/var"; then
test "x$prefix" = xNONE && AC_SUBST([localstatedir], [/var])
fi

AC_SUBST([localstoragedir], [$localstatedir/lib])
if test "$localstatedir" = "\${prefix}/var"; then
AC_SUBST([localstoragedir], [$prefix/var/lib])
fi

# Unfortunately, for ALSA >= 1.1.7 the directory for add-on configuration files
# is hard-coded as /etc/alsa/conf.d (unless the distribution has patched the
# source codes). So, we will use that value as a default, unless (for backwards
# compatibility) the user overrides it with --prefix or --sysconfdir option.
if test "$sysconfdir" = "\${prefix}/etc"; then
test "x$prefix" = xNONE && sysconfdir=/etc
test "x$prefix" = xNONE && AC_SUBST([sysconfdir], [/etc])
fi

AC_DEFINE_UNQUOTED(BLUEALSA_STORAGE_DIR, "${localstoragedir}/bluealsa",
[Directory for the storage files.])

AC_ARG_WITH([alsaconfdir],
AS_HELP_STRING([--with-alsaconfdir=DIR], [path to ALSA add-on configuration files]),
[alsaconfdir="$withval"],
Expand Down
4 changes: 4 additions & 0 deletions misc/systemd/bluealsa.service.in
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,9 @@ SystemCallFilter=@system-service
SystemCallFilter=~@resources @privileged
UMask=0077

# Setup state directory for persistent storage
ReadWritePaths=/var/lib/bluealsa
StateDirectory=bluealsa

[Install]
WantedBy=bluetooth.target
1 change: 1 addition & 0 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ bluealsa_SOURCES = \
io.c \
rtp.c \
sco.c \
storage.c \
utils.c \
main.c

Expand Down
9 changes: 8 additions & 1 deletion src/ba-device.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* BlueALSA - ba-device.c
* Copyright (c) 2016-2019 Arkadiusz Bokowy
* Copyright (c) 2016-2022 Arkadiusz Bokowy
*
* This file is a part of bluez-alsa.
*
Expand All @@ -17,6 +17,7 @@
#include "ba-transport.h"
#include "bluealsa-config.h"
#include "hci.h"
#include "storage.h"
#include "shared/log.h"

struct ba_device *ba_device_new(
Expand Down Expand Up @@ -49,6 +50,9 @@ struct ba_device *ba_device_new(
g_hash_table_insert(adapter->devices, &d->addr, d);
pthread_mutex_unlock(&adapter->devices_mutex);

/* load data from persistent storage */
storage_device_load(d);

return d;
}

Expand Down Expand Up @@ -132,6 +136,9 @@ void ba_device_unref(struct ba_device *d) {
if (ref_count > 0)
return;

/* save persistent storage */
storage_device_save(d);

debug("Freeing device: %s", batostr_(&d->addr));
g_assert_cmpint(ref_count, ==, 0);

Expand Down
16 changes: 16 additions & 0 deletions src/ba-transport.c
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
#include "hci.h"
#include "hfp.h"
#include "sco.h"
#include "storage.h"
#include "utils.h"
#include "shared/a2dp-codecs.h"
#include "shared/defs.h"
Expand Down Expand Up @@ -683,6 +684,9 @@ struct ba_transport *ba_transport_new_a2dp(

ba_transport_set_codec(t, type.codec);

storage_pcm_data_sync(&t->a2dp.pcm);
storage_pcm_data_sync(&t->a2dp.pcm_bc);

if (t->a2dp.pcm.channels > 0)
bluealsa_dbus_pcm_register(&t->a2dp.pcm);
if (t->a2dp.pcm_bc.channels > 0)
Expand Down Expand Up @@ -791,6 +795,9 @@ struct ba_transport *ba_transport_new_sco(

ba_transport_set_codec(t, type.codec);

storage_pcm_data_sync(&t->sco.spk_pcm);
storage_pcm_data_sync(&t->sco.mic_pcm);

bluealsa_dbus_pcm_register(&t->sco.spk_pcm);
bluealsa_dbus_pcm_register(&t->sco.mic_pcm);

Expand Down Expand Up @@ -882,6 +889,15 @@ void ba_transport_unref(struct ba_transport *t) {
if (ref_count > 0)
return;

if (t->type.profile & BA_TRANSPORT_PROFILE_MASK_A2DP) {
storage_pcm_data_update(&t->a2dp.pcm);
storage_pcm_data_update(&t->a2dp.pcm_bc);
}
else if (t->type.profile & BA_TRANSPORT_PROFILE_MASK_SCO) {
storage_pcm_data_update(&t->sco.spk_pcm);
storage_pcm_data_update(&t->sco.mic_pcm);
}

debug("Freeing transport: %s", ba_transport_type_to_string(t->type));
g_assert_cmpint(ref_count, ==, 0);

Expand Down
3 changes: 3 additions & 0 deletions src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
#if ENABLE_OFONO
# include "ofono.h"
#endif
#include "storage.h"
#if ENABLE_UPOWER
# include "upower.h"
#endif
Expand Down Expand Up @@ -567,6 +568,8 @@ int main(int argc, char **argv) {

a2dp_codecs_init();

storage_init(BLUEALSA_STORAGE_DIR);

/* In order to receive EPIPE while writing to the pipe whose reading end
* is closed, the SIGPIPE signal has to be handled. For more information
* see the io_thread_write_pcm() function. */
Expand Down
234 changes: 234 additions & 0 deletions src/storage.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
/*
* BlueALSA - storage.c
* Copyright (c) 2016-2022 Arkadiusz Bokowy
*
* This file is a part of bluez-alsa.
*
* This project is licensed under the terms of the MIT license.
*
*/

#include "storage.h"

#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>

#include <bluetooth/bluetooth.h>

#include <glib.h>

#include "utils.h"
#include "shared/log.h"

#define BA_STORAGE_KEY_SOFT_VOLUME "SoftVolume"
#define BA_STORAGE_KEY_VOLUME "Volume"
#define BA_STORAGE_KEY_MUTE "Mute"

struct storage {
/* remote BT device address */
bdaddr_t addr;
/* associated storage file */
GKeyFile *keyfile;
};

static char storage_root_dir[128];
static GHashTable *storage_map = NULL;

static struct storage *storage_lookup(const bdaddr_t *addr) {
return g_hash_table_lookup(storage_map, addr);
}

static struct storage *storage_new(const bdaddr_t *addr) {

struct storage *st;
/* return existing storage if it exists */
if ((st = storage_lookup(addr)) != NULL)
return st;

if ((st = malloc(sizeof(*st))) == NULL)
return NULL;

bacpy(&st->addr, addr);
st->keyfile = g_key_file_new();

/* Insert a new storage into the map. Please, note that the key is a pointer
* to memory stored in the value structure. This is fine as long as the value
* is not replaced during key insertion. In such case the old (dangling) key
* pointer might/will be used to access the new value! */
g_hash_table_insert(storage_map, &st->addr, st);

return st;
}

static void storage_free(struct storage *st) {
if (st == NULL)
return;
g_key_file_free(st->keyfile);
free(st);
}

/**
* Initialize BlueALSA persistent storage.
*
* @param root The root directory for the persistent storage.
* @return On success this function returns 0. Otherwise -1 is returned. */
int storage_init(const char *root) {

strncpy(storage_root_dir, root, sizeof(storage_root_dir) - 1);
if (mkdir(storage_root_dir, S_IRWXU) == -1 && errno != EEXIST)
warn("Couldn't create storage directory: %s", strerror(errno));

if (storage_map == NULL)
storage_map = g_hash_table_new_full(g_bdaddr_hash, g_bdaddr_equal,
NULL, (GDestroyNotify)storage_free);

return 0;
}

/**
* Load persistent storage file for the given BT device. */
int storage_device_load(const struct ba_device *d) {

char addrstr[18];
char path[sizeof(storage_root_dir) + sizeof(addrstr)];
ba2str(&d->addr, addrstr);
snprintf(path, sizeof(path), "%s/%s", storage_root_dir, addrstr);

debug("Loading storage: %s", path);

struct storage *st;
if ((st = storage_new(&d->addr)) == NULL)
return -1;

GError *err = NULL;
if (!g_key_file_load_from_file(st->keyfile, path, G_KEY_FILE_NONE, &err)) {
if (err->code != G_FILE_ERROR_NOENT)
warn("Couldn't load storage: %s", err->message);
g_error_free(err);
return -1;
}

return 0;
}

/**
* Save persistent storage file for the given BT device. */
int storage_device_save(const struct ba_device *d) {

char addrstr[18];
char path[sizeof(storage_root_dir) + sizeof(addrstr)];
ba2str(&d->addr, addrstr);
snprintf(path, sizeof(path), "%s/%s", storage_root_dir, addrstr);

struct storage *st;
if ((st = storage_lookup(&d->addr)) == NULL)
return -1;

debug("Saving storage: %s", path);

GError *err = NULL;
if (!g_key_file_save_to_file(st->keyfile, path, &err)) {
error("Couldn't save storage: %s", err->message);
g_error_free(err);
return -1;
}

/* remove the storage from the map */
g_hash_table_remove(storage_map, &d->addr);

return 0;
}

/**
* Synchronize PCM with persistent storage.
*
* @param pcm The PCM structure to synchronize.
* @return This function returns 1 or 0 respectively if the storage data was
* synchronized or not. On error -1 is returned. */
int storage_pcm_data_sync(struct ba_transport_pcm *pcm) {

const struct ba_transport *t = pcm->t;
const struct ba_device *d = t->d;
int rv = 0;

struct storage *st;
if ((st = storage_lookup(&d->addr)) == NULL)
goto final;

GKeyFile *keyfile = st->keyfile;
const char *group = pcm->ba_dbus_path;

if (!g_key_file_has_group(keyfile, group))
goto final;

if (g_key_file_has_key(keyfile, group, BA_STORAGE_KEY_SOFT_VOLUME, NULL)) {
pcm->soft_volume = g_key_file_get_boolean(keyfile, group,
BA_STORAGE_KEY_SOFT_VOLUME, NULL);
rv = 1;
}

if (g_key_file_has_key(keyfile, group, BA_STORAGE_KEY_VOLUME, NULL)) {
int *list;
gsize len = 0;
if ((list = g_key_file_get_integer_list(keyfile, group,
BA_STORAGE_KEY_VOLUME, &len, NULL)) != NULL &&
len == 2) {
ba_transport_pcm_volume_set(&pcm->volume[0], &list[0], NULL, NULL);
ba_transport_pcm_volume_set(&pcm->volume[1], &list[1], NULL, NULL);
}
g_free(list);
rv = 1;
}

if (g_key_file_has_key(keyfile, group, BA_STORAGE_KEY_MUTE, NULL)) {
gboolean *list;
gsize len = 0;
if ((list = g_key_file_get_boolean_list(keyfile, group,
BA_STORAGE_KEY_MUTE, &len, NULL)) != NULL &&
len == 2) {
const bool mute[2] = { list[0], list[1] };
ba_transport_pcm_volume_set(&pcm->volume[0], NULL, &mute[0], NULL);
ba_transport_pcm_volume_set(&pcm->volume[1], NULL, &mute[1], NULL);
}
g_free(list);
rv = 1;
}

final:
return rv;
}

/**
* Update persistent storage with PCM data.
*
* @param pcm The PCM structure for which to update the storage.
* @return On success this function returns 0. Otherwise -1 is returned. */
int storage_pcm_data_update(const struct ba_transport_pcm *pcm) {

const struct ba_transport *t = pcm->t;
const struct ba_device *d = t->d;

struct storage *st;
if ((st = storage_lookup(&d->addr)) == NULL)
if ((st = storage_new(&d->addr)) == NULL)
return -1;

GKeyFile *keyfile = st->keyfile;
const char *group = pcm->ba_dbus_path;

g_key_file_set_boolean(keyfile, group, BA_STORAGE_KEY_SOFT_VOLUME,
pcm->soft_volume);

int volume[2] = { pcm->volume[0].level, pcm->volume[1].level };
g_key_file_set_integer_list(keyfile, group, BA_STORAGE_KEY_VOLUME, volume, 2);

gboolean mute[2] = { pcm->volume[0].soft_mute, pcm->volume[1].soft_mute };
g_key_file_set_boolean_list(keyfile, group, BA_STORAGE_KEY_MUTE, mute, 2);

return 0;
}
Loading

0 comments on commit a0a83a3

Please sign in to comment.