:wc: relfile 00-WikiIndex :parser: no-camelcase ---2 Bluez A2DP HOWTO %abs= This article explains how to create A2DP Sink and Source with bluez 4.x Audio and Media DBus API. It provides the high-level overview missing from bluez' documentation, such as the relation between events (DBus' signals), when they happen, and which bluez "API" definitions are really API calls and which ones are __callbacks__. I don't go into detail of every methods and events (what parameters are available, what are their types, etc) as this has been sufficiently explained bluez documentation, available in their [[http://www.kernel.org/pub/linux/bluetooth/bluez-4.101.tar.xz] source tarball] or online [[+_blank http://git.kernel.org/cgit/bluetooth/bluez.git/tree/doc?id=67ef3ac83752e3705fad55eb25586e1d21e44c2e] here]. Here, I will only summarise the missing critical information that is necessary for you to build your own application that acts as an A2DP Sink or Source. %= ---3 Bluez Events for A2DP This diagram summarises the events and sequences when a remote bluetooth device starts an A2DP protocol connection to start audio streaming. (m# images/bluez4-a2dp-overview.png #) First, the caveat: the %:blue: **"bluetooth device" <--> "bluez"** %:: part of that diagram must be taken with a grain of salt, as it is not accurate. If you want to know the details you need to consult [[+_blank https://www.bluetooth.org/en-us/specification/adopted-specifications] A2DP and GAVPD specifications]. I show it there so that you can see the big picture of what is happening. There are 3 levels of events that happen during the lifetime of the your app. I have marked these as A, B, and C. **Level A** are the highest level events, these are startup/shutdown events and activities. **Level B** are events and actions that you must do when a remote bluetooth device is connected or disconnected. **Level C** are the actions you must do to carry out the actual audio streaming. ---4 "Level A" events/actions (A1 and A2) %blackbox= These are the events/actions you must do/handle when your application is starting up or shutting down. They only need to done once. %= **A1. Application Startup.** Upon starting up, you need to tell bluez that your app will handle A2DP Sink or Sources for it. You do it by calling %:green: ''org.bluez.Media.RegisterEvent'' %:: with the appropriate parameters, mainly __UUID__, __Codec__, and __Capabilities__. Bluez documentation doesn't make it clear, but you %em- cannot just plug arbitrary made-up values %- here. __"UUID"__ must be one of the pre-defined __"Service Class identifiers"__ (from [[+_blank https://www.bluetooth.org/en-us/specification/assigned-numbers-overview/service-discovery] here]), you want either __AudioSource__ or __AudioSink__ UUID. __"Codec"__ must be one of the available supported codecs from [[+_blank https://www.bluetooth.org/en-us/specification/adopted-specifications] A2DP specification], and the __"Capabilities"__ must be filled with the particular codec's parameters that you want to support. If the registration is successful, you'll get an empty reply otherwise you'll get an error. **A2. Application Termination.** Assuming you have successfully registered, bluez will notify you that your registration has been cancelled. This usually only happens the the bluetooth daemon itself is about to shutdown. Bluez does it by calling %:blue: ''org.bluez.MediaEndpoint.Release'' %:: method, which you must implement and handle (don't you wish now that bluez documentation differentiates between %:green:real "API"%:: calls and %:blue:"callback"%:: interfaces, like this one? ). At this stage you don't need to de-register or do any other cleanup with bluez, you just need to clean-up your own resources. Reply with a blank message, and after that you are free to terminate your app. ---4 "Level B" events/actions (B1 and B2) .blackbox There are the events/actions that happen / you must do when remote bluetooth devices get connected. They can happen multiple times within the lifetime of your app (ie between Level A events), for the same devices (in pairs), and for different devices (may be overlapping). .. **B1. Device Connection Events** happen when a remote bluetooth device is connected. Assuming that your registration is successful, bluez will call your app again when an A2DP device is trying to connect to the computer. It does it using %:blue: ''org.bluez.MediaEndpoint.SelectConfiguration'' %::. You will need to implement this method and interface and handle the call. Through this call, bluez will pass you some __"Capabilities"__ codec parameters from the other end. You are supposed to compare this with your own capabilities and choose the best match that provide the highest quality audio. Your reply to bluez will contain the this chosen configuration. If everything is all right, bluez will then call your app again, using %:blue: ''org.bluez.MediaEndpoint.SetConfiguration'' %::. The parameter to this call should contain exactly the same codec parameters you gave back earlier in your reply to "SelectConfiguration". Among other things, the most important thing you must do here is this: you must record the **"transport path"** given as parameter of this call. It is a unique object path that you need to pass along to %:green: ''org.bluez.MediaTransport.Acquire'' %:: to get the file descriptor you need to use for the actual streaming. If you don't keep that path, you can't find it again. All being good, you reply with empty message. **B2. Device Disconnection Events** happen the remote bluetooth device is disconnected. Bluez will call you on %:blue: ''org.bluez.MediaEndpoint.ClearConfiguration'' %:: method. You are supposed to clear any of your resources you keep for that particular bluetooth device connection (ie, that particular "transport path"). Reply with a blank message. ---4 "Level C" events/actions (C1 and C2) .blackbox These are the events/actions that happen / you must do to do the actual audio streaming. It can happen multiple times within "Level B" events for the same remote device, usually in pairs. .. **C1. Start streaming event**. To detect this event, you must listen to %:blue: ''org.bluez.AudioSource.PropertyChanged'' %:: signal and keep track of its "State" property. The "start streaming" event happens when the state changes from %:red: "connected" %:: to %:red: "playing" %::. (There are a few other events too, which may be interesting for other purposes but not for us). When this happens, you need to call %:green: ''org.bluez.MediaTransport.Acquire'' %::. Bluez will give you a file descriptor that you can read from, as well as its read MTU (maximum transfer unit) - which is how big each packet would be. From here onwards, you can read this descriptor to obtain the A2DP packet, decode it, and output it. The Read MTU helps to determine how big a buffer you need to allocate. Note that the read isn't always successful, you must allow for error conditions such as __EAGAIN__ because your CPU will be much faster at reading than what bluetooth (and the remote device) can send. **C2. Stop streaming event**. Like "start streaming event", you can't decide this from ''org.bluez.AudioSource.PropertyChanged'' signal alone. You need to detect the transition, which is %:red: "playing" %:: to %:red: "connected" %::. When this happens, you need to call %:green: ''org.bluez.MediaTransport.Release'' %:: to release the transport back to bluez. In my tests, this is not strictly necessary but it is polite to do so. It is also good for you to detect this event so that you can can tell our "streaming" function to stop its work and rest for a while. That's it! Easy peasy eh? ---3 How about A2DP Source? The explanation above describes the sequence of events and methods to call when the computer is acting as A2DP sink (or "Source", in bluez' parlance). What about A2DP Source (the computer to send audio data to bluetooth speakers)? As it turns out, the sequence of events (and methods to call) is exactly the same with very minor change: # Instead of %:blue: ''AudioSource.PropertyChange'' %::, you need to listen to %:red: ''AudioSink.PropertyChange'' %::. # The transition you need to detect is a bit different - instead of %:blue: "connected" -> "playing" %::(and vice versa), you listen to %:red: "disconnected" -> "connected" %:: (and vice versa). # You __write__ to the descriptor with encoded data instead of reading from it. . ---3 The example code I've created a working example that implements all of the above. I use a thread for doing the actual streaming (reading/writing to the file descriptors). - I create the thread when I received B1 event (SetConfiguration) but it is suspended until I receive C1 event. - When I receive C1 event, I call MediaTransport.Acquire to get the descriptor, assign it to the thread, and then set the thread loose. - When I receive C1 event, I suspend the thread again. - When I reiceve B2 event, I terminate the thread and clean up its resources. . The code implements both Sink and Source. As you can see, the difference in handling is minimal. **Note**: The code is only an example. A lot of the error handling have been left out on purpose: it focuses neither on performance nor robustness, but more on the working (and hopefully correct) way of handling A2DP under bluez. That being said, I find that the Sink is good enough, while the Source is a bit unsatisfactory. There is a README inside the tarball that shows how you can setup ALSA asoundrc for use with the A2DP Source so that it can act as a poor man's ALSA PCM plugin. As usual, the code is released under GNU GPL Version 3 or later unless the bits that I took from PulseAudion and bluez itself (SBC stuff, SBC setup stuff, and actual A2DP packet encoding/decoding) - they are licensed as per their original licenses. You can get the code from [[./files/a2dp-alsa-2016-01-02.tar.bz2] here]. ---3 Bluez 5 and beyond **Question**: Bluez 4.x is already obsolete by now. What do I have to do to get this example to work with bluez 5? **Answer**: A lot of work. I have not investigated bluez 5 version of this fully as I'm quite satisfied with bluez 4 for now. But from what I have gathered, the sequence of events is identical. Sure the DBus interfaces change their names (bluez 5 add "1" to the interface names, e.g. "org.bluez.MediaEndpoint" becomes "org.bluez.MediaEndpoint1"); and the signals change their skins too (AudioSource/AudioSink are gone, replaced by generic ''org.freedesktop.Properties.PropertyChanged'', and you can probably decide whether to start/stop streaming directly from the state instead of having to watch the transitions), but the underlying events are still the same. Originally posted here: - [[../../../blog/?viewDetailed=00031] Bluez must be one of the best kept secrets in Linux]. - [[../../../blog/?viewDetailed=00032] Bluez A2DP AudioSink for ALSA].