A2DP validates Bluetooth media audio between the AGL device and a remote phone or headset. On AGL, BlueZ owns the Bluetooth profile lifecycle, while PipeWire and WirePlumber normally register the local media endpoints and route the audio stream after the Bluetooth device connects.
Before running this test, complete the Profile GAP flow so the adapter is powered on and the remote device is paired and trusted.
For D-Bus examples, the default adapter path is assumed to be /org/bluez/hci0. Replace hci0
and the sample device address with the values from your target.
Check audio endpoint registration#
A2DP needs local media endpoints before BlueZ can create an audio transport. On an AGL image, these endpoints should be registered by PipeWire and WirePlumber.
Check BlueZ logs:
journalctl -u bluetooth --no-pager | grep -i endpoint
Sample output:
bluetoothd[621]: Endpoint registered: sender=:1.42 path=/MediaEndpoint/A2DPSink/sbc
bluetoothd[621]: Endpoint registered: sender=:1.42 path=/MediaEndpoint/A2DPSource/sbc
Using D-Bus:
busctl tree org.bluez
Look for media endpoint paths such as /MediaEndpoint/A2DPSink/sbc, /MediaEndpoint/A2DPSource/sbc, or endpoints below
/org/bluez/hci0.
Connect an A2DP device#
Start an interactive bluetoothctl session:
bluetoothctl
Connect to the paired remote device:
connect <bt_address>
Example:
connect F8:7D:76:9D:9B:6B
Sample output:
Attempting to connect to F8:7D:76:9D:9B:6B
Connection successful
Using D-Bus:
busctl call org.bluez /org/bluez/hci0/dev_F8_7D_76_9D_9B_6B org.bluez.Device1 Connect
Connect the A2DP profile#
If the device is already connected but the audio profile is not active, connect the A2DP profile explicitly.
A2DP UUIDs:
0000110a-0000-1000-8000-00805f9b34fbis Audio Source.0000110b-0000-1000-8000-00805f9b34fbis Audio Sink.
For an IVI target receiving music from a phone, the phone is normally the A2DP source and the AGL target is the A2DP sink.
Using D-Bus:
busctl call org.bluez /org/bluez/hci0/dev_F8_7D_76_9D_9B_6B org.bluez.Device1 ConnectProfile s 0000110a-0000-1000-8000-00805f9b34fb
For a headset or speaker receiving audio from AGL, use the Audio Sink UUID:
busctl call org.bluez /org/bluez/hci0/dev_20_19_D8_36_90_40 org.bluez.Device1 ConnectProfile s 0000110b-0000-1000-8000-00805f9b34fb
Verify device information#
Use bluetoothctl to confirm that the remote device is connected and exposes the expected audio UUIDs.
info <bt_address>
Example:
info F8:7D:76:9D:9B:6B
Sample output:
Device F8:7D:76:9D:9B:6B (public)
Name: Phone
Paired: yes
Trusted: yes
Connected: yes
UUID: Audio Source (0000110a-0000-1000-8000-00805f9b34fb)
UUID: A/V Remote Control Target (0000110c-0000-1000-8000-00805f9b34fb)
Using D-Bus:
busctl get-property org.bluez /org/bluez/hci0/dev_F8_7D_76_9D_9B_6B org.bluez.Device1 Connected
busctl get-property org.bluez /org/bluez/hci0/dev_F8_7D_76_9D_9B_6B org.bluez.Device1 UUIDs
Verify media transport#
After the remote device starts media playback, BlueZ should expose a media transport object.
Using D-Bus:
busctl call org.bluez / org.freedesktop.DBus.ObjectManager GetManagedObjects
Look for an object path similar to:
/org/bluez/hci0/dev_F8_7D_76_9D_9B_6B/sep1/fd0
Then inspect the transport:
busctl introspect org.bluez /org/bluez/hci0/dev_F8_7D_76_9D_9B_6B/sep1/fd0 org.bluez.MediaTransport1
Common transport properties:
busctl get-property org.bluez /org/bluez/hci0/dev_F8_7D_76_9D_9B_6B/sep1/fd0 org.bluez.MediaTransport1 State
busctl get-property org.bluez /org/bluez/hci0/dev_F8_7D_76_9D_9B_6B/sep1/fd0 org.bluez.MediaTransport1 UUID
busctl get-property org.bluez /org/bluez/hci0/dev_F8_7D_76_9D_9B_6B/sep1/fd0 org.bluez.MediaTransport1 Codec
busctl get-property org.bluez /org/bluez/hci0/dev_F8_7D_76_9D_9B_6B/sep1/fd0 org.bluez.MediaTransport1 Volume
Sample output:
s "active"
s "0000110a-0000-1000-8000-00805f9b34fb"
y 0
q 96
Observe PipeWire routing#
BlueZ confirms the Bluetooth profile state, but AGL audio success also depends on PipeWire receiving and routing the stream.
Check nodes:
wpctl status
Sample output:
Audio
├─ Devices:
│ 45. Phone A2DP Sink
├─ Sinks:
│ * 52. Phone A2DP Sink
Check active PipeWire metadata:
pw-cli ls Node | grep -i bluez
Disconnect the A2DP device#
To disconnect the remote Bluetooth device, run:
disconnect <bt_address>
Example:
disconnect F8:7D:76:9D:9B:6B
Sample output:
Attempting to disconnect from F8:7D:76:9D:9B:6B
Successfully disconnected
Using D-Bus:
busctl call org.bluez /org/bluez/hci0/dev_F8_7D_76_9D_9B_6B org.bluez.Device1 Disconnect
To disconnect only the A2DP source profile:
busctl call org.bluez /org/bluez/hci0/dev_F8_7D_76_9D_9B_6B org.bluez.Device1 DisconnectProfile s 0000110a-0000-1000-8000-00805f9b34fb
BlueZ Proxy XML#
Use this proxy XML as a compact reference for the BlueZ D-Bus interfaces used by the A2DP commands above.
<node>
<interface name="org.bluez.Device1">
<method name="Connect"/>
<method name="Disconnect"/>
<method name="ConnectProfile">
<arg name="uuid" type="s" direction="in"/>
</method>
<method name="DisconnectProfile">
<arg name="uuid" type="s" direction="in"/>
</method>
<property name="Address" type="s" access="read"/>
<property name="Name" type="s" access="read"/>
<property name="Alias" type="s" access="readwrite"/>
<property name="Paired" type="b" access="read"/>
<property name="Trusted" type="b" access="readwrite"/>
<property name="Connected" type="b" access="read"/>
<property name="UUIDs" type="as" access="read"/>
<property name="Adapter" type="o" access="read"/>
<property name="ServicesResolved" type="b" access="read"/>
</interface>
<interface name="org.bluez.Media1">
<method name="RegisterEndpoint">
<arg name="endpoint" type="o" direction="in"/>
<arg name="properties" type="a{sv}" direction="in"/>
</method>
<method name="UnregisterEndpoint">
<arg name="endpoint" type="o" direction="in"/>
</method>
<method name="RegisterPlayer">
<arg name="player" type="o" direction="in"/>
<arg name="properties" type="a{sv}" direction="in"/>
</method>
<method name="UnregisterPlayer">
<arg name="player" type="o" direction="in"/>
</method>
<property name="SupportedUUIDs" type="as" access="read"/>
<property name="SupportedFeatures" type="as" access="read"/>
</interface>
<interface name="org.bluez.MediaEndpoint1">
<method name="SetConfiguration">
<arg name="transport" type="o" direction="in"/>
<arg name="properties" type="a{sv}" direction="in"/>
</method>
<method name="SelectConfiguration">
<arg name="capabilities" type="ay" direction="in"/>
<arg name="configuration" type="ay" direction="out"/>
</method>
<method name="ClearConfiguration">
<arg name="transport" type="o" direction="in"/>
</method>
<method name="Release"/>
</interface>
<interface name="org.bluez.MediaTransport1">
<method name="Acquire">
<arg name="fd" type="h" direction="out"/>
<arg name="read_mtu" type="q" direction="out"/>
<arg name="write_mtu" type="q" direction="out"/>
</method>
<method name="TryAcquire">
<arg name="fd" type="h" direction="out"/>
<arg name="read_mtu" type="q" direction="out"/>
<arg name="write_mtu" type="q" direction="out"/>
</method>
<method name="Release"/>
<property name="Device" type="o" access="read"/>
<property name="UUID" type="s" access="read"/>
<property name="Codec" type="y" access="read"/>
<property name="Configuration" type="ay" access="read"/>
<property name="State" type="s" access="read"/>
<property name="Delay" type="q" access="read"/>
<property name="Volume" type="q" access="readwrite"/>
</interface>
<interface name="org.freedesktop.DBus.ObjectManager">
<method name="GetManagedObjects">
<arg name="objects" type="a{oa{sa{sv}}}" direction="out"/>
</method>
<signal name="InterfacesAdded">
<arg name="object" type="o"/>
<arg name="interfaces" type="a{sa{sv}}"/>
</signal>
<signal name="InterfacesRemoved">
<arg name="object" type="o"/>
<arg name="interfaces" type="as"/>
</signal>
</interface>
<interface name="org.freedesktop.DBus.Properties">
<method name="Get">
<arg name="interface" type="s" direction="in"/>
<arg name="name" type="s" direction="in"/>
<arg name="value" type="v" direction="out"/>
</method>
<method name="Set">
<arg name="interface" type="s" direction="in"/>
<arg name="name" type="s" direction="in"/>
<arg name="value" type="v" direction="in"/>
</method>
<method name="GetAll">
<arg name="interface" type="s" direction="in"/>
<arg name="properties" type="a{sv}" direction="out"/>
</method>
<signal name="PropertiesChanged">
<arg name="interface" type="s"/>
<arg name="changed" type="a{sv}"/>
<arg name="invalidated" type="as"/>
</signal>
</interface>
</node>
References#
- BlueZ Device API: Device API
- BlueZ Media API: Media API