Introducing Bluetooth telephony support in PipeWire
PipeWire 1.4 is almost out of the door! One of the highlights of the new release — which I personally worked on and presented also recently on my FOSDEM talk — is Bluetooth telephony support.
This consists of a new D-Bus API, very similar - and mostly compatible - to the one of oFono, which is exposed by PipeWire’s BlueZ plugin when a mobile phone is paired with the Linux system that runs PipeWire. This API allows applications to make phone calls, accept or reject incoming calls, put calls on hold or join them in three-way calling setups. Practically, everything that the Bluetooth Hands-Free Profile (HFP) protocol allows you to do with a mobile phone device.
While there is no user interface for it (yet), it is straightforward to use it from the command line with tools such as busctl
or qdbus
. Here’s a short tutorial on how to use it:
- To begin with, make sure you are running PipeWire 1.3.82 or later, with the bluez plugin loaded (in WirePlumber).
- Pair a mobile phone with your Linux system. You may use your desktop’s UI for that, or
bluetoothctl
on the command line. Now, explore the API. The main object is
/org/pipewire/Telephony
on theorg.pipewire.Telephony
service:> busctl --user introspect org.pipewire.Telephony /org/pipewire/Telephony NAME TYPE SIGNATURE RESULT/VALUE FLAGS org.freedesktop.DBus.Introspectable interface - - - .Introspect method - s - org.freedesktop.DBus.ObjectManager interface - - - .GetManagedObjects method - a{oa{sa{sv}}} - .InterfacesAdded signal oa{sa{sv}} - - .InterfacesRemoved signal oas - - org.ofono.Manager interface - - - .GetModems method - a{oa{sv}} - .ModemAdded signal oa{sv} - - .ModemRemoved signal o - -
You can list the available connected phones by calling
GetManagedObjects
(prefered) orGetModems
from the oFono compatibility interface:> busctl --user call org.pipewire.Telephony /org/pipewire/Telephony org.freedesktop.DBus.ObjectManager GetManagedObjects a{oa{sa{sv}}} 1 "/org/pipewire/Telephony/ag1" 2 "org.pipewire.Telephony.AudioGateway1" 1 "Address" s "XX:XX:XX:XX:XX:XX" "org.pipewire.Telephony.AudioGatewayTransport1" 3 "Codec" y 0 "State" s "idle" "RejectSCO" b false
And based on this output, you can now explore the object that represents the connected phone:
> busctl --user introspect org.pipewire.Telephony /org/pipewire/Telephony/ag1 NAME TYPE SIGNATURE RESULT/VALUE FLAGS org.freedesktop.DBus.Introspectable interface - - - .Introspect method - s - org.freedesktop.DBus.ObjectManager interface - - - .GetManagedObjects method - a{oa{sa{sv}}} - .InterfacesAdded signal oa{sa{sv}} - - .InterfacesRemoved signal oas - - org.freedesktop.DBus.Properties interface - - - .Get method ss v - .GetAll method s a{sv} - .Set method ssv - - .PropertiesChanged signal sa{sv}as - - org.ofono.VoiceCallManager interface - - - .CreateMultiparty method - - - .Dial method s - - .GetCalls method - a{oa{sv}} - .HangupAll method - - - .HoldAndAnswer method - - - .ReleaseAndAnswer method - - - .ReleaseAndSwap method - - - .SendTones method s - - .SwapCalls method - - - .CallAdded signal oa{sv} - - .CallRemoved signal o - - org.pipewire.Telephony.AudioGateway1 interface - - - .CreateMultiparty method - - - .Dial method s - - .HangupAll method - - - .HoldAndAnswer method - - - .ReleaseAndAnswer method - - - .ReleaseAndSwap method - - - .SendTones method s - - .SwapCalls method - - - .Address property s "XX:XX:XX:XX:XX:XX" const org.pipewire.Telephony.AudioGatewayTransport1 interface - - - .Activate method - - - .Codec property y 0 emits-change
Now you can start making phone calls (replace
+30XXXXXXXXXX
with an actual phone number):❯ busctl --user call org.pipewire.Telephony /org/pipewire/Telephony/ag1 org.pipewire.Telephony.AudioGateway1 Dial s +30XXXXXXXXXX o "/org/pipewire/Telephony/ag1/call1"
Notice that the above command returns a “call” object. When a phone call is in progress, a “call” object is always available to represent it. Let’s explore it:
❯ busctl --user introspect org.pipewire.Telephony /org/pipewire/Telephony/ag1/call1 NAME TYPE SIGNATURE RESULT/VALUE FLAGS org.freedesktop.DBus.Introspectable interface - - - .Introspect method - s - org.freedesktop.DBus.Properties interface - - - .Get method ss v - .GetAll method s a{sv} - .Set method ssv - - .PropertiesChanged signal sa{sv}as - - org.ofono.VoiceCall interface - - - .Answer method - - - .GetProperties method - a{sv} - .Hangup method - - - .PropertyChanged signal sv - - org.pipewire.Telephony.Call1 interface - - - .Answer method - - - .Hangup method - - - .IncomingLine property s "" emits-change .LineIdentification property s "+30XXXXXXXXXX" emits-change .Multiparty property b false emits-change .Name property s "" emits-change .State property s "alerting" emits-change
Finally, you may hang up the call like this:
❯ busctl --user call org.pipewire.Telephony /org/pipewire/Telephony/ag1/call1 org.pipewire.Telephony.Call1 Hangup
And that’s it!
You can read more about how to use the API in the README.Telephony file in the PipeWire repository.
Have fun calling! 📞