* [PATCH BlueZ 1/2] media: Add Mute property to MediaTransport1
@ 2026-06-09 18:11 Simon Mikuda
2026-06-09 18:11 ` [PATCH BlueZ 2/2] client/player: Add transport.mute command Simon Mikuda
2026-06-09 19:30 ` [BlueZ,1/2] media: Add Mute property to MediaTransport1 bluez.test.bot
0 siblings, 2 replies; 3+ messages in thread
From: Simon Mikuda @ 2026-06-09 18:11 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Simon Mikuda
Boolean, optional, readwrite. Only present for LE Audio (BAP) unicast
transports backed by VCS. A2DP has no mute concept in AVRCP.
bt_vcp_set_mute() writes VCS Control Point Mute/Unmute for a client
session, or updates Volume State for a server session.
bt_vcp_get_mute() returns the cached value updated by Volume State
notifications.
---
doc/org.bluez.MediaTransport.rst | 6 +++
profiles/audio/transport.c | 75 ++++++++++++++++++++++++++++
profiles/audio/vcp.c | 20 ++++++++
profiles/audio/vcp.h | 12 +++++
src/shared/vcp.c | 86 ++++++++++++++++++++++++++++++++
src/shared/vcp.h | 3 ++
6 files changed, 202 insertions(+)
diff --git a/doc/org.bluez.MediaTransport.rst b/doc/org.bluez.MediaTransport.rst
index e50ec3f20..33d468325 100644
--- a/doc/org.bluez.MediaTransport.rst
+++ b/doc/org.bluez.MediaTransport.rst
@@ -160,6 +160,12 @@ Examples:
:bluetoothctl: > transport.volume <transport> [value]
+boolean Mute [readwrite, optional]
+``````````````````````````````````
+
+Indicates mute state of the transport, only present for LE Audio (BAP)
+transports backed by VCS.
+
object Endpoint [readonly, optional, experimental]
``````````````````````````````````````````````````
diff --git a/profiles/audio/transport.c b/profiles/audio/transport.c
index 4b9d26c5e..6b7dd9360 100644
--- a/profiles/audio/transport.c
+++ b/profiles/audio/transport.c
@@ -1191,6 +1191,64 @@ static void set_volume(const GDBusPropertyTable *property,
g_dbus_pending_property_success(id);
}
+static int transport_bap_get_mute(struct media_transport *transport);
+static int transport_bap_set_mute(struct media_transport *transport,
+ bool mute);
+
+static gboolean mute_exists(const GDBusPropertyTable *property, void *data)
+{
+ struct media_transport *transport = data;
+
+ return transport_bap_get_mute(transport) >= 0;
+}
+
+static gboolean get_mute(const GDBusPropertyTable *property,
+ DBusMessageIter *iter, void *data)
+{
+ struct media_transport *transport = data;
+ dbus_bool_t mute;
+ int ret;
+
+ ret = transport_bap_get_mute(transport);
+ if (ret < 0)
+ return FALSE;
+
+ mute = ret;
+
+ dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &mute);
+
+ return TRUE;
+}
+
+static void set_mute(const GDBusPropertyTable *property,
+ DBusMessageIter *iter, GDBusPendingPropertySet id,
+ void *data)
+{
+ struct media_transport *transport = data;
+ dbus_bool_t mute;
+ int err;
+
+ if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_BOOLEAN) {
+ g_dbus_pending_property_error(id,
+ ERROR_INTERFACE ".InvalidArguments",
+ "Expected BOOLEAN");
+ return;
+ }
+
+ dbus_message_iter_get_basic(iter, &mute);
+
+ err = transport_bap_set_mute(transport, mute);
+ if (err) {
+ error("Unable to set mute: %s (%d)", strerror(-err), err);
+ g_dbus_pending_property_error(id, ERROR_INTERFACE ".Failed",
+ "Internal error %s (%d)",
+ strerror(-err), err);
+ return;
+ }
+
+ g_dbus_pending_property_success(id);
+}
+
static gboolean endpoint_exists(const GDBusPropertyTable *property, void *data)
{
struct media_transport *transport = data;
@@ -1544,6 +1602,7 @@ static const GDBusPropertyTable transport_bap_uc_properties[] = {
{ "Metadata", "ay", get_metadata, set_metadata },
{ "Links", "ao", get_links, NULL, links_exists },
{ "Volume", "q", get_volume, set_volume, volume_exists },
+ { "Mute", "b", get_mute, set_mute, mute_exists },
{ }
};
@@ -2428,6 +2487,22 @@ static int transport_bap_set_volume(struct media_transport *transport,
return -ENOTSUP; /* TODO: MICP */
}
+static int transport_bap_get_mute(struct media_transport *transport)
+{
+ if (transport_bap_is_playback(transport))
+ return bt_audio_vcp_get_mute(transport->device);
+ else
+ return -ENOTSUP; /* TODO: MICP */
+}
+
+static int transport_bap_set_mute(struct media_transport *transport, bool mute)
+{
+ if (transport_bap_is_playback(transport))
+ return bt_audio_vcp_set_mute(transport->device, mute);
+ else
+ return -ENOTSUP; /* TODO: MICP */
+}
+
static void transport_bap_destroy(void *data)
{
struct bap_transport *bap = data;
diff --git a/profiles/audio/vcp.c b/profiles/audio/vcp.c
index 00ee2b64b..7adac4dd5 100644
--- a/profiles/audio/vcp.c
+++ b/profiles/audio/vcp.c
@@ -185,6 +185,26 @@ int bt_audio_vcp_set_volume(struct btd_device *device, uint8_t volume)
return -ENODEV;
}
+int bt_audio_vcp_get_mute(struct btd_device *device)
+{
+ struct vcp_data *data = queue_find(sessions, match_device, device);
+
+ if (data)
+ return bt_vcp_get_mute(data->vcp);
+
+ return -ENODEV;
+}
+
+int bt_audio_vcp_set_mute(struct btd_device *device, bool mute)
+{
+ struct vcp_data *data = queue_find(sessions, match_device, device);
+
+ if (data)
+ return bt_vcp_set_mute(data->vcp, mute) ? 0 : -EIO;
+
+ return -ENODEV;
+}
+
static void vcp_remote_client_detached(struct bt_vcp *vcp, void *user_data)
{
struct vcp_data *data;
diff --git a/profiles/audio/vcp.h b/profiles/audio/vcp.h
index b538cebf0..35ffc09f8 100644
--- a/profiles/audio/vcp.h
+++ b/profiles/audio/vcp.h
@@ -12,6 +12,8 @@
int bt_audio_vcp_get_volume(struct btd_device *device);
int bt_audio_vcp_set_volume(struct btd_device *device, uint8_t volume);
+int bt_audio_vcp_get_mute(struct btd_device *device);
+int bt_audio_vcp_set_mute(struct btd_device *device, bool mute);
#else
@@ -26,4 +28,14 @@ static inline int bt_audio_vcp_set_volume(struct btd_device *device,
return -ENODEV;
}
+static inline int bt_audio_vcp_get_mute(struct btd_device *device)
+{
+ return -ENODEV;
+}
+
+static inline int bt_audio_vcp_set_mute(struct btd_device *device, bool mute)
+{
+ return -ENODEV;
+}
+
#endif
diff --git a/src/shared/vcp.c b/src/shared/vcp.c
index c7f74956e..49eff8d02 100644
--- a/src/shared/vcp.c
+++ b/src/shared/vcp.c
@@ -216,6 +216,7 @@ struct bt_vcp {
uint8_t volume;
uint8_t volume_counter;
+ uint8_t mute;
struct bt_vcp_client_op pending_op;
@@ -861,6 +862,9 @@ static uint8_t vcs_mute(struct bt_vcs *vcs, struct bt_vcp *vcp,
vstate->mute = 0x01;
vstate->counter = -~vstate->counter; /*Increment Change Counter*/
+ gatt_db_attribute_notify(vdb->vcs->vs, (void *)vstate,
+ sizeof(struct vol_state),
+ bt_vcp_get_att(vcp));
return 0;
}
@@ -2076,6 +2080,7 @@ static void vcp_vstate_notify(struct bt_vcp *vcp, uint16_t value_handle,
vcp->volume = vstate->vol_set;
vcp->volume_counter = vstate->counter;
+ vcp->mute = vstate->mute;
if (vcp->volume_changed)
vcp->volume_changed(vcp, vcp->volume);
@@ -2207,6 +2212,86 @@ uint8_t bt_vcp_get_volume(struct bt_vcp *vcp)
return vcp->volume;
}
+static void vcp_mute_cp_sent(bool success, uint8_t err, void *user_data)
+{
+ struct bt_vcp *vcp = user_data;
+
+ if (!success)
+ DBG(vcp, "setting mute failed: error 0x%x", err);
+}
+
+static bool vcp_set_mute_client(struct bt_vcp *vcp, uint8_t mute)
+{
+ struct bt_vcs_param req;
+ uint16_t value_handle;
+ struct bt_vcs *vcs = vcp_get_vcs(vcp);
+
+ if (!vcs || !vcs->vol_cp) {
+ DBG(vcp, "error: vol_cp characteristic not available");
+ return false;
+ }
+
+ if (!gatt_db_attribute_get_char_data(vcs->vol_cp, NULL, &value_handle,
+ NULL, NULL, NULL)) {
+ DBG(vcp, "error: vol_cp characteristic not available");
+ return false;
+ }
+
+ if (vcp->mute == mute)
+ return true;
+
+ req.op = mute ? BT_VCS_MUTE : BT_VCS_UNMUTE;
+ req.change_counter = vcp->volume_counter;
+
+ if (!bt_gatt_client_write_value(vcp->client, value_handle, (void *)&req,
+ sizeof(req), vcp_mute_cp_sent, vcp,
+ NULL)) {
+ DBG(vcp, "error writing mute");
+ return false;
+ }
+
+ return true;
+}
+
+static bool vcp_set_mute_server(struct bt_vcp *vcp, uint8_t mute)
+{
+ struct bt_vcp_db *vdb = vcp_get_vdb(vcp);
+ struct vol_state *vstate;
+
+ vcp->mute = mute;
+
+ if (!vdb) {
+ DBG(vcp, "error: VDB not available");
+ return false;
+ }
+
+ vstate = vdb_get_vstate(vdb);
+ if (!vstate) {
+ DBG(vcp, "error: VSTATE not available");
+ return false;
+ }
+
+ vstate->mute = mute;
+ vstate->counter = -~vstate->counter; /*Increment Change Counter*/
+
+ gatt_db_attribute_notify(vdb->vcs->vs, (void *) vstate,
+ sizeof(struct vol_state), bt_vcp_get_att(vcp));
+ return true;
+}
+
+bool bt_vcp_set_mute(struct bt_vcp *vcp, uint8_t mute)
+{
+ if (vcp->client)
+ return vcp_set_mute_client(vcp, mute);
+ else
+ return vcp_set_mute_server(vcp, mute);
+}
+
+uint8_t bt_vcp_get_mute(struct bt_vcp *vcp)
+{
+ return vcp->mute;
+}
+
static void vcp_voffset_state_notify(struct bt_vcp *vcp, uint16_t value_handle,
const uint8_t *value, uint16_t length,
void *user_data)
@@ -2320,6 +2405,7 @@ static void read_vol_state(struct bt_vcp *vcp, bool success, uint8_t att_ecode,
vcp->volume = vs->vol_set;
vcp->volume_counter = vs->counter;
+ vcp->mute = vs->mute;
}
static void read_vol_offset_state(struct bt_vcp *vcp, bool success,
diff --git a/src/shared/vcp.h b/src/shared/vcp.h
index e031beafd..807d2bdbe 100644
--- a/src/shared/vcp.h
+++ b/src/shared/vcp.h
@@ -42,6 +42,9 @@ void bt_vcp_detach(struct bt_vcp *vcp);
uint8_t bt_vcp_get_volume(struct bt_vcp *vcp);
bool bt_vcp_set_volume(struct bt_vcp *vcp, uint8_t volume);
+uint8_t bt_vcp_get_mute(struct bt_vcp *vcp);
+bool bt_vcp_set_mute(struct bt_vcp *vcp, uint8_t mute);
+
bool bt_vcp_set_debug(struct bt_vcp *vcp, bt_vcp_debug_func_t cb,
void *user_data, bt_vcp_destroy_func_t destroy);
--
2.43.0
^ permalink raw reply related [flat|nested] 3+ messages in thread
* [PATCH BlueZ 2/2] client/player: Add transport.mute command
2026-06-09 18:11 [PATCH BlueZ 1/2] media: Add Mute property to MediaTransport1 Simon Mikuda
@ 2026-06-09 18:11 ` Simon Mikuda
2026-06-09 19:30 ` [BlueZ,1/2] media: Add Mute property to MediaTransport1 bluez.test.bot
1 sibling, 0 replies; 3+ messages in thread
From: Simon Mikuda @ 2026-06-09 18:11 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Simon Mikuda
Mirrors transport.volume. Accepts on/off, yes/no, 1/0.
---
client/player.c | 51 +++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 51 insertions(+)
diff --git a/client/player.c b/client/player.c
index c5e4beef0..2c3008221 100644
--- a/client/player.c
+++ b/client/player.c
@@ -6070,6 +6070,54 @@ static void cmd_volume_transport(int argc, char *argv[])
}
}
+static void mute_callback(const DBusError *error, void *user_data)
+{
+ if (dbus_error_is_set(error)) {
+ bt_shell_printf("Failed to set Mute: %s\n", error->name);
+ return bt_shell_noninteractive_quit(EXIT_FAILURE);
+ }
+
+ bt_shell_printf("Changing Mute succeeded\n");
+
+ return bt_shell_noninteractive_quit(EXIT_SUCCESS);
+}
+
+static void cmd_mute_transport(int argc, char *argv[])
+{
+ GDBusProxy *proxy;
+ dbus_bool_t mute;
+
+ proxy = g_dbus_proxy_lookup(transports, NULL, argv[1],
+ BLUEZ_MEDIA_TRANSPORT_INTERFACE);
+ if (!proxy) {
+ bt_shell_printf("Transport %s not found\n", argv[1]);
+ return bt_shell_noninteractive_quit(EXIT_FAILURE);
+ }
+
+ if (argc == 2) {
+ print_property(proxy, "Mute");
+ return bt_shell_noninteractive_quit(EXIT_FAILURE);
+ }
+
+ if (!strcasecmp(argv[2], "on") || !strcasecmp(argv[2], "yes") ||
+ !strcmp(argv[2], "1"))
+ mute = TRUE;
+ else if (!strcasecmp(argv[2], "off") || !strcasecmp(argv[2], "no") ||
+ !strcmp(argv[2], "0"))
+ mute = FALSE;
+ else {
+ bt_shell_printf("Invalid argument: %s\n", argv[2]);
+ return bt_shell_noninteractive_quit(EXIT_FAILURE);
+ }
+
+ if (!g_dbus_proxy_set_property_basic(proxy, "Mute", DBUS_TYPE_BOOLEAN,
+ &mute, mute_callback,
+ NULL, NULL)) {
+ bt_shell_printf("Failed to set mute\n");
+ return bt_shell_noninteractive_quit(EXIT_FAILURE);
+ }
+}
+
static void set_metadata_cb(const DBusError *error, void *user_data)
{
if (dbus_error_is_set(error)) {
@@ -6188,6 +6236,9 @@ static const struct bt_shell_menu transport_menu = {
{ "volume", "<transport> [value]", cmd_volume_transport,
"Get/Set transport volume",
transport_generator },
+ { "mute", "<transport> [on/off]", cmd_mute_transport,
+ "Get/Set transport mute",
+ transport_generator },
{ "select", "<transport> [transport1...]", cmd_select_transport,
"Select Transport",
transport_generator },
--
2.43.0
^ permalink raw reply related [flat|nested] 3+ messages in thread
* RE: [BlueZ,1/2] media: Add Mute property to MediaTransport1
2026-06-09 18:11 [PATCH BlueZ 1/2] media: Add Mute property to MediaTransport1 Simon Mikuda
2026-06-09 18:11 ` [PATCH BlueZ 2/2] client/player: Add transport.mute command Simon Mikuda
@ 2026-06-09 19:30 ` bluez.test.bot
1 sibling, 0 replies; 3+ messages in thread
From: bluez.test.bot @ 2026-06-09 19:30 UTC (permalink / raw)
To: linux-bluetooth, simon.mikuda
[-- Attachment #1: Type: text/plain, Size: 989 bytes --]
This is automated email and please do not reply to this email!
Dear submitter,
Thank you for submitting the patches to the linux bluetooth mailing list.
This is a CI test results with your patch series:
PW Link:https://patchwork.kernel.org/project/bluetooth/list/?series=1108770
---Test result---
Test Summary:
CheckPatch PASS 1.16 seconds
GitLint PASS 0.64 seconds
BuildEll PASS 20.17 seconds
BluezMake PASS 613.44 seconds
MakeCheck PASS 19.16 seconds
MakeDistcheck PASS 235.40 seconds
CheckValgrind PASS 276.40 seconds
CheckSmatch PASS 326.72 seconds
bluezmakeextell PASS 165.83 seconds
IncrementalBuild PASS 625.23 seconds
ScanBuild PASS 947.04 seconds
https://github.com/bluez/bluez/pull/2204
---
Regards,
Linux Bluetooth
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-06-09 19:30 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-09 18:11 [PATCH BlueZ 1/2] media: Add Mute property to MediaTransport1 Simon Mikuda
2026-06-09 18:11 ` [PATCH BlueZ 2/2] client/player: Add transport.mute command Simon Mikuda
2026-06-09 19:30 ` [BlueZ,1/2] media: Add Mute property to MediaTransport1 bluez.test.bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox