πŸŽ™οΈ Microphone Control Service (MICS)

πŸŽ™οΈ Microphone Control Service (MICS)

BLE GATT Service β€” How Bluetooth Controls Your Mic Mute Button

1
Characteristic
3
Mute States
3
Topologies
BLE
GATT Based

What is MICS?

Ever pressed the mute button on your wireless headset and wondered β€” how does your phone know the mic is muted? The answer (in modern BLE audio devices) is the Microphone Control Service (MICS).

MICS is a BLE GATT service defined by the Bluetooth SIG. It gives any connected BLE client the ability to:

  • πŸ“– Read the current microphone mute state
  • ✏️ Write a new mute state (mute or unmute)
  • πŸ”” Subscribe to notifications when the state changes

Think of it as a smart, encrypted mute button exposed over Bluetooth β€” any trusted connected device can see and control it in real time.

πŸ”‘ Key Concepts in This Post

MICS GATT Service Mute Characteristic AICS BLE Notifications ATT Error Codes Encryption Required BlueZ D-Bus EATT SDP Interop CCCD

🌍 Real-World Analogy β€” The Hotel DND Sign

The three mute states map perfectly to a hotel “Do Not Disturb” sign:

State Value Analogy Who Can Change It?
βœ… Not Muted 0x00 DND sign is off β€” guests can knock Client or Server
πŸ”‡ Muted 0x01 DND sign is on β€” no entry Client or Server
πŸ”’ Disabled 0x02 Door is bolted shut from inside β€” nobody external can change it Server ONLY

The Disabled state represents a physical hardware privacy switch (like a laptop privacy shutter). The device locks the mic mute at hardware level β€” no BLE command can override it.

πŸ“ Service Topologies β€” Three Ways to Use MICS

MICS can work alone or alongside the Audio Input Control Service (AICS). MICS handles global device-wide mute. AICS handles per-microphone gain (volume) and per-input mute. There are three standard configurations:

Topology 1 β€” MICS Only (Simplest Setup)

No AICS included. Just a global mute switch. Best for headsets or speakers that only need ON/OFF mute with no individual gain control.

🎀
Microphone In

πŸŽ›οΈ
Microphone
Control Service
(MICS)

πŸ”Š
Microphone Out
Use case: Basic wireless earbuds, conference speakers β€” just need a simple mute toggle with no per-mic volume control.

Topology 2 β€” MICS + Single AICS (Global + Per-Input Control)

One AICS is included inside MICS. MICS = device-wide mute. AICS = individual input gain (volume) and mute for that one microphone. Together they give you fine-grained control.

🎀
Mic In

🎚️
Audio Input
Control
(AICS)

πŸŽ›οΈ
Microphone
Control
(MICS)

πŸ”Š
Mic Out
Use case: Smart headsets or hearing aids β€” global mute via MICS, plus volume/gain adjustment per input via AICS.

Topology 3 β€” MICS + Multiple AICS (Multi-Mic Device)

Multiple AICS instances, one per microphone. Each mic gets its own independent gain and mute. MICS still provides the single device-wide master mute. Perfect for TWS earbuds (left + right mic) or conference room speakerphones.

🎀 Mic 1
🎚️ AICS #1

🎀 Mic 2
🎚️ AICS #2

πŸŽ›οΈ
MICS
Global Mute

πŸ”Š
Audio Out
Use case: TWS earbuds with left + right mics, conference speakerphones, or any device with multiple independently controllable microphone inputs.

πŸ”§ The Mute Characteristic β€” The Only Characteristic in MICS

Characteristic Overview

MICS has exactly one characteristic: the Mute characteristic. It’s a single byte that tells the current mute state of the device’s audio.

Property Value What It Means
Characteristic Name Mute The only characteristic in MICS
UUID 0x2BC3 Assigned by Bluetooth SIG
Requirement Mandatory (M) Every MICS server must have it
Properties Read, Write, Notify Can read state, write new state, subscribe for changes
Security πŸ”’ Encryption Required BLE link must be encrypted (bonded) first
Data Size 1 byte (uint8) Transmitted little-endian (LSO first)
⚠️ Security note: You cannot just connect and read/write the mute state. The BLE connection must be encrypted (i.e., the devices must be paired/bonded). This prevents unauthorized apps from muting your microphone without permission.

πŸ“Š All Mute Characteristic Values Explained
Hex Value State Meaning Client Can Write?
0x00 βœ… Not Muted Microphone is active, audio is flowing normally βœ… Yes (to mute)
0x01 πŸ”‡ Muted Microphone is silenced β€” no audio output βœ… Yes (to unmute)
0x02 πŸ”’ Disabled Mute is hardware-locked (e.g., physical privacy switch). Mic is muted and stays muted. ❌ No β€” server only
0x03–0xFF β›” RFU Reserved for future use β€” not valid today ❌ No β€” ATT error returned

πŸ”„ Mute State Machine β€” Who Can Change What

The mute state transitions follow strict rules. The most important: only the device itself (server) can enter or exit the Disabled state.

βœ…
NOT MUTED
0x00

Client writes
0x01
Client writes
0x00

πŸ”‡
MUTED
0x01

Server sets
0x02
Server clears
Disabled

πŸ”’
DISABLED
0x02
πŸ“Œ Key Rules to Remember:

  • Client can freely toggle between Not Muted ↔ Muted
  • Only the server (device hardware) can set or clear the Disabled state
  • If client tries to write anything when state is Disabled β†’ ATT error 0x80 (Mute Disabled)
  • If client tries to write value 0x02 or any RFU value β†’ ATT error 0x13 (Value Not Allowed)

❌ ATT Error Codes in MICS

When a client does something invalid, the server returns an ATT error response. MICS defines one custom application-level error:

Error Name Error Code Trigger Condition
Mute Disabled 0x80 Client writes any value while current state is Disabled
Value Not Allowed 0x13 Client writes 0x02 (Disabled) or any RFU value (0x03–0xFF) β€” standard ATT error

Both errors tell the client: “you can’t do that.” The difference is the reason β€” one is about the current locked state, the other is about writing an invalid value.

πŸ“‹ GATT Sub-Procedure Requirements

To implement MICS, your BLE server and client code must support these four GATT operations:

GATT Sub-Procedure Required? Purpose in MICS
Write Characteristic Values Mandatory Client sends mute/unmute command
Notifications Mandatory Server alerts clients when mute state changes
Read Characteristic Descriptors Mandatory Client reads the CCCD to check notification status
Write Characteristic Descriptors Mandatory Client writes CCCD to enable/disable notifications
πŸ’‘ Reliability of Notifications: Over a standard (Unenhanced) ATT bearer, BLE notifications are not guaranteed to arrive β€” they can be dropped. For reliable delivery, use an Enhanced ATT (EATT) bearer which adds L2CAP credit-based flow control. A higher-layer profile (like Telephony Bearer Profile) can mandate EATT for MICS.

πŸ”” Notification Flow β€” How the Client Stays in Sync

Once the client writes 0x0001 to the CCCD (Client Characteristic Configuration Descriptor), it subscribes to mute change notifications. Every time the mute state changes β€” whether by the client, server, or a hardware switch β€” all subscribed clients get notified.

πŸ“± BLE Client (Phone / Laptop) Action 🎧 BLE Server (Headset / Mic Device)
Writes CCCD = 0x0001 β†’β†’β†’ Saves notification preference for this client
Reads Mute characteristic β†’β†’β†’ Returns current value: 0x00 (Not Muted)
Writes Mute = 0x01 (Mute!) β†’β†’β†’ Updates Mute value to 0x01, triggers notifications
← Receives Notification: Mute = 0x01 ←←← Sends notification to all subscribed clients
Writes Mute = 0x00 when Disabled β†’ ❌ ATT Error 0x80 β†’ βœ— Returns ATT Error Response: Mute Disabled (0x80)

πŸ’» BlueZ Implementation β€” C Code Examples

1. Defining MICS Constants and Data Structure

Start by defining the UUIDs and mute state constants. In BlueZ, GATT services are registered through D-Bus using the GATT Manager API.

/* ===== mics.h β€” MICS Constants ===== */

/* MICS Service UUID (Bluetooth SIG Assigned Numbers) */
#define MICS_UUID           "0000184d-0000-1000-8000-00805f9b34fb"

/* Mute Characteristic UUID */
#define MUTE_CHAR_UUID      "00002bc3-0000-1000-8000-00805f9b34fb"

/* Mute characteristic values */
#define MUTE_NOT_MUTED      0x00   /* Mic active, audio flowing */
#define MUTE_MUTED          0x01   /* Mic silenced by software  */
#define MUTE_DISABLED       0x02   /* Mute locked by hardware   */

/* ATT Application Error Code β€” defined by MICS spec */
#define ATT_ERR_MUTE_DISABLED    0x80
/* Standard ATT error (BT Core Spec) */
#define ATT_ERR_VALUE_NOT_ALLOWED 0x13

/* MICS service state structure */
struct mics_service {
    uint8_t  mute_state;           /* Current mute value        */
    bool     notify_enabled;       /* Client subscribed?         */
};

/* Initialize with default Not Muted state */
static struct mics_service *mics_init(void)
{
    struct mics_service *svc = g_new0(struct mics_service, 1);
    svc->mute_state    = MUTE_NOT_MUTED;
    svc->notify_enabled = false;
    return svc;
}
2. Discovering MICS via bluetoothctl

After pairing with a BLE device that exposes MICS, use BlueZ’s interactive tool to explore the service. The UUID 0x184D identifies MICS; the Mute characteristic UUID is 0x2BC3.

# Step 1: Power on and scan
bluetoothctl power on
bluetoothctl scan on

# Step 2: Connect to your MICS device
bluetoothctl connect AA:BB:CC:DD:EE:FF

# Step 3: List all GATT attributes (services + characteristics)
bluetoothctl list-attributes AA:BB:CC:DD:EE:FF

# Look for output like:
# Service    /org/bluez/hci0/dev_AA_BB_CC_DD_EE_FF/service000c
#            0000184d-0000-1000-8000-00805f9b34fb (MICS)
# Characteristic /org/.../service000c/char000d
#            00002bc3-0000-1000-8000-00805f9b34fb (Mute)
#            Flags: read write notify

# Step 4: Select the Mute characteristic
bluetoothctl select-attribute /org/bluez/hci0/dev_AA_BB_CC_DD_EE_FF/service000c/char000d

# Step 5: Read the current mute state
bluetoothctl read-attribute

# Example responses:
# Attribute Value (1):  00          -> Not Muted
# Attribute Value (1):  01          -> Muted
# Attribute Value (1):  02          -> Disabled (hardware lock)
3. Writing Mute State via BlueZ D-Bus API (C)

To programmatically mute or unmute over BLE in C, call WriteValue on the Mute characteristic proxy. The link must be encrypted (bonded) or the ATT operation will be rejected.

#include <glib.h>
#include <gio/gio.h>

/*
 * mics_write_mute() β€” send mute or unmute command to MICS server
 *
 * @char_proxy : D-Bus proxy for the Mute characteristic
 * @mute_val   : MUTE_NOT_MUTED (0x00) or MUTE_MUTED (0x01)
 *
 * Returns 0 on success, negative errno on failure.
 */
static int mics_write_mute(GDBusProxy *char_proxy, uint8_t mute_val)
{
    GVariant      *params;
    GVariantBuilder opts_builder;
    GError        *error = NULL;

    /* Validate: client can only write 0x00 or 0x01 */
    if (mute_val != MUTE_NOT_MUTED && mute_val != MUTE_MUTED) {
        g_printerr("[MICS] Invalid value 0x%02x β€” only 0x00/0x01 allowed\n",
                   mute_val);
        return -EINVAL;
    }

    /* Build WriteValue call: (ay, a{sv}) */
    g_variant_builder_init(&opts_builder, G_VARIANT_TYPE_VARDICT);

    params = g_variant_new(
        "(ay@a{sv})",
        g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE,
                                  &mute_val, 1, sizeof(uint8_t)),
        g_variant_builder_end(&opts_builder));

    g_dbus_proxy_call_sync(char_proxy, "WriteValue", params,
                           G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error);

    if (error) {
        /*
         * If the server is in Disabled state it returns
         * application error 0x80 (Mute Disabled).
         */
        g_printerr("[MICS] WriteValue failed: %s\n", error->message);
        g_error_free(error);
        return -EIO;
    }

    g_print("[MICS] Mute set to: %s\n",
            mute_val == MUTE_MUTED ? "MUTED πŸ”‡" : "NOT MUTED βœ…");
    return 0;
}
4. Enabling Notifications and Handling Mute Changes

Subscribe to the Mute characteristic via StartNotify. Connect a GObject signal to handle incoming notifications β€” the callback fires whenever the mute state changes on the server side.

/*
 * mics_notify_cb() β€” called whenever Mute characteristic value changes
 * Triggered by: BLE notification from MICS server
 */
static void mics_notify_cb(GDBusProxy *proxy,
                            GVariant   *changed_props,
                            GStrv       invalidated,
                            gpointer    user_data)
{
    GVariant       *val;
    const uint8_t  *data;
    gsize           len;

    val = g_variant_lookup_value(changed_props,
                                 "Value",
                                 G_VARIANT_TYPE_BYTESTRING);
    if (!val)
        return;

    data = g_variant_get_fixed_array(val, &len, sizeof(uint8_t));
    if (len < 1) {
        g_variant_unref(val);
        return;
    }

    /* Decode and print the new mute state */
    switch (data[0]) {
    case MUTE_NOT_MUTED:
        g_print("[MICS Notify] 🎀 Microphone: NOT MUTED (0x00)\n");
        break;
    case MUTE_MUTED:
        g_print("[MICS Notify] πŸ”‡ Microphone: MUTED (0x01)\n");
        break;
    case MUTE_DISABLED:
        g_print("[MICS Notify] πŸ”’ Mute control: DISABLED by hardware (0x02)\n");
        break;
    default:
        g_printerr("[MICS Notify] ⚠️  Unknown value: 0x%02x\n", data[0]);
    }

    g_variant_unref(val);
}

/* Enable notifications for the Mute characteristic */
static int mics_enable_notify(GDBusProxy *char_proxy)
{
    GError *error = NULL;

    /* Wire up the PropertiesChanged signal BEFORE calling StartNotify */
    g_signal_connect(char_proxy, "g-properties-changed",
                     G_CALLBACK(mics_notify_cb), NULL);

    /* Tell BlueZ to subscribe (writes CCCD = 0x0001 under the hood) */
    g_dbus_proxy_call_sync(char_proxy, "StartNotify", NULL,
                           G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error);
    if (error) {
        g_printerr("[MICS] StartNotify failed: %s\n", error->message);
        g_error_free(error);
        return -EIO;
    }

    g_print("[MICS] βœ… Subscribed to Mute notifications\n");
    return 0;
}
5. Server Side β€” Handling Write Requests with Proper ATT Errors

On the peripheral (server) side, when a remote client sends a Write request to the Mute characteristic, you must validate it and return the correct ATT error when needed. This is the critical guard logic the spec mandates.

/*
 * mics_server_write_handler()
 * Called by BlueZ GATT server when client writes Mute characteristic.
 *
 * Returns NULL on success, or a D-Bus error message on failure.
 */
static DBusMessage *mics_server_write_handler(DBusConnection *conn,
                                               DBusMessage    *msg,
                                               void           *user_data)
{
    struct mics_service *svc = user_data;
    DBusMessageIter     iter, array_iter;
    uint8_t             new_val = 0;

    /* Parse the incoming byte value */
    dbus_message_iter_init(msg, &iter);
    if (dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_ARRAY) {
        dbus_message_iter_recurse(&iter, &array_iter);
        dbus_message_iter_get_basic(&array_iter, &new_val);
    }

    /*
     * Rule 1 (MICS spec Β§3.1.1):
     * Client must NOT write Disabled (0x02) or any RFU value.
     * Return standard ATT error: Value Not Allowed (0x13)
     */
    if (new_val >= MUTE_DISABLED) {
        g_print("[MICS Server] ❌ Rejected write 0x%02x β€” Value Not Allowed\n",
                new_val);
        return g_dbus_create_error(msg,
                   "org.bluez.Error.NotPermitted",
                   "ATT Error 0x13: Value Not Allowed");
    }

    /*
     * Rule 2 (MICS spec Β§3.1.1):
     * If current state is Disabled, reject ALL client writes.
     * Return application error: Mute Disabled (0x80)
     */
    if (svc->mute_state == MUTE_DISABLED) {
        g_print("[MICS Server] πŸ”’ Rejected write β€” Mute is hardware-disabled\n");
        return g_dbus_create_error(msg,
                   "org.bluez.Error.NotPermitted",
                   "ATT App Error 0x80: Mute Disabled");
    }

    /* βœ… Valid write β€” apply new mute state */
    svc->mute_state = new_val;
    g_print("[MICS Server] βœ… Mute state updated to: 0x%02x (%s)\n",
            new_val, new_val == MUTE_MUTED ? "MUTED" : "NOT MUTED");

    /* Notify all subscribed clients */
    mics_send_notification(conn, svc, new_val);

    return dbus_message_new_method_return(msg);
}

/*
 * mics_hardware_disable()
 * Called locally when a physical privacy switch is engaged.
 * ONLY the server can call this β€” clients cannot trigger it.
 */
static void mics_hardware_disable(struct mics_service *svc,
                                   DBusConnection      *conn)
{
    svc->mute_state = MUTE_DISABLED;
    g_print("[MICS Server] πŸ”’ Hardware privacy switch engaged β€” Disabled\n");

    /* Notify all connected clients about the state change */
    mics_send_notification(conn, svc, MUTE_DISABLED);
}

πŸ“‘ SDP Interoperability β€” When MICS Runs over BR/EDR

Although MICS is a GATT/BLE service, it can also run over Bluetooth Classic (BR/EDR). When it does, the device must register an SDP record so Classic Bluetooth clients can discover it.

SDP Record Item Value Status
Service Class UUID Β«Microphone Control ServiceΒ» Mandatory
Protocol: L2CAP β†’ ATT PSM = ATT Mandatory
Enhanced ATT (EATT) PSM = EATT Conditional (C.1)
BrowseGroupList PublicBrowseRoot Mandatory

C.1 β€” EATT condition: The Additional Protocol Descriptor List (with PSM = EATT) is mandatory if Enhanced Attribute Protocol is supported, and excluded otherwise.

πŸ’‘ What is EATT?
Enhanced ATT (EATT) uses L2CAP’s Credit-Based Flow Control mode for sending ATT PDUs. Unlike the basic ATT bearer, EATT guarantees ordered, reliable delivery of notifications β€” making it ideal for time-sensitive mute state changes in audio applications.

πŸ“ Quick Summary β€” Everything in One Place
MICS = global mic mute over BLE Only 1 MICS instance per device Single characteristic: Mute 3 states: 0x00, 0x01, 0x02 Encryption required Client writes 0x00 or 0x01 only Error 0x80 = Mute Disabled Error 0x13 = Value Not Allowed AICS = per-input gain + mute 3 topologies supported EATT for reliable notifications SDP record for BR/EDR
Aspect Detail
MICS Service UUID 0x184D
Mute Char UUID 0x2BC3
Max Instances on a Device 1 (only one MICS per device)
Byte Order Little Endian β€” LSO (Least Significant Octet) first
Bluetooth Core Spec v4.0 or later (any version with GATT)
Spec Version v1.0 β€” Adopted by Bluetooth SIG (February 2021)
Prepared By Generic Audio Working Group

πŸš€ Keep Exploring BLE Audio Services!

MICS is part of the Bluetooth LE Audio profile family. The next logical step is AICS (Audio Input Control Service) β€” which adds per-input gain control and individual mute per microphone. Head back to EmbeddedPathashala for more free Bluetooth deep-dives!

🏠 EmbeddedPathashala πŸ“‘ More BLE Blogs

Leave a Reply

Your email address will not be published. Required fields are marked *