Bluetooth Audio

There have been many attempts at implementing different audio profiles for BlueZ. The current approach is to implement an audio service which fits into the new D-Bus based service framework and which implements all of the profiles in a flexible enough way to be usable by everyone.

To minimize audio data copying in memory and to acheive as low latency as possible, the audio service is not responsible for transmitting the actual audio but passes the audio stream socket descriptor to an ALSA plugin which will in turn be responsible for enconding/decoding and sending/receiving of the audio data.

Some things to work on

  • System-wide audio device (mixing for multiple simultaneous access)
  • For combo sets, choose voice or hifi low in driver (based on client requirements)
  • Changing from wired to wireless audio mid stream (probably needs something like pulse)

Current status

See AudioDevices on examples of how to use the current code.

SBC codec

The SBC codec has gotten some work recently. The codec uses 32-bit fixed-point math and can be found in the  sbc/ subdirectory of bluez-utils.

There are a couple of optimizations the codec could see but it performs reasonably on 200MHz+ devices.

 bluez/utils/audio

The  audio/ subdirectory in bluez-utils contains the actual audio service and ALSA plugin implementation.

Some important files used by the audio service:

  • manager.c Implements the org.bluez.audio.Manager interface
  • headset.c Implements the org.bluez.audio.Headset interface
  • sink.c Implements the org.bluez.audio.Sink interface
  • avdtp.c Implements the AVDTP signaling protocol
  • control.c Implements the AVCTP and AVRCP layers as well as the org.bluez.audio.Control interface
  • a2dp.c Implements the local SBC Stream End Points and their callbacks
  • unix.c Contains code for communicating with the ALSA plugin
  • ipc.h Contains the protocol struct for the audio service <-> alsa communication
  • pcm_bluetooth.c Contains the actual ALSA plugin

D-Bus API

The most up-to-date version of the currently implemented audio service D-Bus API can be found in  audio/audio-api.txt (bluez-utils source tree).

The Plan

The audio profile handling is split up into two parts:

  • audio service
    • Provides D-Bus API for applications
    • Responsible for managing connections. And sending/receiving control data to/from audio devices.
  • alsa plugin
    • Receives a audio stream descriptor from the audio service
    • Communicates volume changes to the audio service
    • Responsible for encoding/decoding audio data and sending/receiving it to/from the headset.

High-level audio service D-Bus interface structure

org.bluez.audio.Manager

This is the main audio service interface accessed through the /org/bluez/audio object. Through this it is possible to add/remove remote audio devices and discover their capabilities. Each remote device gets assigned a unique object (e.g. /org/bluez/audio/device1) which implement one or more of the interfaces listed below.

org.bluez.audio.Device

This interface is implemented by all audio device objects. Through it you can e.g. get the Bluetooth address of the device.

org.bluez.audio.Headset

This interface is available for audio devices which support the headset role of the Headset Profile (HSP) and/or the Handsfree Profile (HFP).

org.bluez.audio.Gateway

This interface is available for audio devices which support the audio gateway role of HSP and/or HFP.

org.bluez.audio.Sink

This interface is available for audio devices which support the sink role of the A2DP profile.

org.bluez.audio.Source

This interface is available for audio devices which support the source role of the A2DP profile

org.bluez.audio.Control

This interface is available for audio devices which support the AVRCP profile.

File structure

common files

  • ipc.h contains the common structs etc. defining the protocol between alsa and the audio service

audio service

  • audio.conf Configure file to enable/disable profiles. PCM/HCI SCO routing setting.
  • unix.c Handling of the communication between the audio service and the alsa plugin
  • manager.c Implements the org.bluez.audio.Manager interface
  • HSP/HFP
    • headset.c Implements the org.bluez.audio.Headset interface
    • gateway.c Implements the org.bluez.audio.Gateway interface
  • A2DP
    • avdtp.c Implements the AVDTP protocol
    • a2dp.c Implements the local SBC Stream End Points
    • sink.c Implements the org.bluez.audio.Sink interface. Makes use of avdtp.c.
    • source.c Implements the org.bluez.audio.Source interface. Makes use of avdtp.c.
  • AVRCP
    • control.c Implements the AVCTP and AVRCP layers as well as the org.bluez.audio.Control interface

ALSA plugin

  • pcm_bluetooth.c
  • ctl_bluetooth.c

Profile-specific design considerations

HFP

Work is on the way to support HFP 1.5. As it is a complicated beast to tackle, there is a specific design page.

Requirements of the audio service - alsa plugin interface

Basic data structures

There's a lot of implementation detail below. The minimal changes to try out the architecture are:

  • finish the unix socket communications (data structures)
  • write a basic alsa plugin that takes no parameters

Alsa parameters

Every alsa plugin device could have one or more of these parameters so the plugin and the daemon can figure out how to process the audio:

  • preference:
    • none: prefer a2dp then sco
    • voice, sco: client needs low-latency (sco) duplex audio
    • hifi, a2dp: client needs a2dp audio
  • device: bdaddr of the preferable remote device.

asound.conf

We will have to put a bunch of entries in here that applications can be pointed at, 0 in the examples is to signify the first/only device, ie device_index=0. could also have alsa entries for 1, 2, 3

  • voice, aka voice0 (audio_preference=voice; will use sco and/or wired)
  • hifi, aka hifi0 (audio_preference=hifi; will use a2dp or wired for output, wired mic)
  • auto, aka auto0 (audio_preference=auto; autodetect profile)
  • loud, aka loud0 (alternate_speaker_device=default; will send sound, eg ringer, to any headset too)
  • any, aka any0 (audio_preference=none; always prefer a2dp, sco, then wired)

e.g.:

  • voice would be appropriate for a voip app that we know won't autodetect
  • hifi would be right for an app that *needs* stereo output
  • auto could be what apps use by default, could leave apps on this if they behave
  • any makes sense for an internet radio receiver that doesn't care what the profile is but wants to use a headset if it's active

Autodetecting client for combo a2dp/sco sets usage

When a combo set is connected, we have the problem of figuring out whether an audio client wants the sco side or the a2dp side. We had planned to watch for the open call and check to see if it was write-only or read-write. Unfortunately the opens come separately for read and write and may come in any order. In the autodetect scenario, a2dp is the default profile when they're both available.

  • if a reader request comes in first, we know we need to use either sco or wired audio
  • if writer comes first, prefer a2dp, then sco, then wired
  • try to negotiate an a2dp source or use wired mic when a reader request comes after a writer request that was connected to a2dp

More details for using combo sets

There is a best-practices document full of recommendations for how to handle a combo headset. For example, when a voice client wants to use a set that is in a2dp mode, we pause the a2dp stream, open the sco voice connection, and unpause the a2dp stream when the voice client is finished.

Control socket API

The following two sections list the requirements for what should be possible to do over the unix socket between the audio service and the alsa plugin(s).

The plugz prototype for sco has a daemon "headsetd" for managing connections. It accepts connections on two different sockets: one for pcm and one for ctl. This approach reflects a sort of influence from the way alsa does things. I think we could use just one socket for both by expanding the data structure to handle both volume controls and sco descriptor locking. The plugz data structures are ctl_packet_t in ctl_sco.c and snd_pcm_sco_headset_t for requests and msghdr for responses in pcm_sco.c.

Plugz does not have any way to handle multiple independent headsets. We should at least make a design that doesn't prevent multiple headsets. The dbus interface allows us to be specific about what headset we want to connect or which headset has connected itself. The dbus api will probably also need to pass an index for talking to that headset along with connection events. It would usually be 0 (meaning the only set connected) but it could be 1, 2, 3. We'd have to set up device entries in asound.conf for all these, eg: bluetooth (aka bluetooth0), bluetooth1, bluetooth2. These would have an index parameter set to the right value.

audio service -> alsa

  • Passing the audio stream socket descriptor
    • audio data encoding (PCM, SBC, etc.)
    • read, write or read/write
  • headset volume changes
  • rerouting event
    • headset has disconnected or stream socket preempted; close socket and use default device
    • headset has connected; should acquire and use audio stream socket

alsa -> audio service

  • Requesting and accepting the audio stream socket descriptor
    • In the read/write case inform the service if only one was picked (so the service can offer the other one to another requestor)
  • Release stream socket (or is it implicit on closing the control socket?)
  • Inform the service (and thereby the headset) of mixer volume changes

Related Documents

Comments

Comment by elmarco on Fri Feb 2 14:42:50 2007

Could be nice if we can merge/use the audio device tree in HAL instead of duplicating it here in the bt.audio daemon. This way we could rely on capabilities, enumeration, device condition emitting ("ButtonPressed")...

Comment by bmidgley on Wed Feb 21 03:58:56 2007

This does sound reasonable, but I'm not sure how using the HAL tree impacts what we're doing here. Does it change how we choose and employ the d-bus objects? Does it eliminate some of the d-bus spec here and replace it entirely with HAL queries?

Comment by anonymous on Thu Jun 7 21:21:57 2007

I know this is a purely volunteer work but.. is there any timeframe for all/any of those proposed changes? I've been looking forward to a working a2recv for many months.. anyway, good work so far, thank you!

Comment by elmarco on Tue Jun 19 12:29:27 2007

"device index = 0 for only connected device"

It would be probably better to give an id (preferably meaningful string).

ex: "device identifier" this identifier could be the object path (query in the audio service api) or an identifier announced/available somewhere (dbus and/or HAL), like the suffix of /org/bluez/audio/device0 = "device0"

It could also rely on the audio_preference thingy to select the best device for you.

Comment by jh on Thu Aug 23 13:37:05 2007

As discussed on IRC, the identifier to use for the devices should simply be the Bluetooth address of the device.

Comment by anonymous on Mon Oct 1 15:24:47 2007

I would like to request two features, one of which I see is already being addressed. I live in an apartment and it would be very helpful to be able to stream stereo sound to 2+ headphones. The other feature is a bit more complicated. My PS3 uses a bluetooth headset for voice comms but doesn't put the stereo sound over the headset. It would be nice to have a combo of a2dp for stereo sound coming from alsa as well as a gateway that mixes in the mono HSP for the mic. From what I have seen, profiles are not mutually exclusive but define minimums for compliance and are sometimes layered on top of one another.

Thank you for all the great work on bluez.

Attachments