Skip to content

Instantly share code, notes, and snippets.

@mill1000
Last active April 7, 2024 20:32
Show Gist options
  • Save mill1000/74c7473ee3b4a5b13f6325e9994ff84c to your computer and use it in GitHub Desktop.
Save mill1000/74c7473ee3b4a5b13f6325e9994ff84c to your computer and use it in GitHub Desktop.
Headless A2DP Audio Streaming on Raspbian Stretch

About

This gist will show how to setup Raspbian Stretch as a headless Bluetooth A2DP audio sink. This will allow your phone, laptop or other Bluetooth device to play audio wirelessly through a Rasperry Pi.

Motivation

A quick search will turn up a plethora of tutorials on setting up A2DP on the Raspberry Pi. However, I felt this gist was necessary because this solution is:

  • Automatic & Headless - Once setup, the system is entirely automatic. No user iteration is required to pair, connect or start playback. Therefore the Raspberry Pi can be run headless.
  • Simple - This solution has few dependencies, readily available packages and minimal configuration.
  • Up to date - As of December 2017. Written for Raspbian Stretch & Bluez 5.43

Prerequisites

  • Raspbian Stretch - I used the Lite version as this is a headless setup. See the official guide if you need help.
  • Bluez-alsa - Available in the Raspbian package repo. This software allows us to stream A2DP audio over Bluetooth without PulseAudio.
  • Raspberry Pi with Bluetooth - The Raspberry Pi 3 has integrated Bluetooth, however there is a known bug when the WiFi is used simultaneously. Cheap USB Bluetooth dongles work equally well.

Disabling Integrated Bluetooth

If you are using a separate USB Bluetooth dongle, disable the integrated Bluetooth to prevent conflicts.

To disable the integrated Bluetooth add the following

# Disable onboard Bluetooth
dtoverlay=pi3-disable-bt

to /boot/config.txt and execute the following command

sudo systemctl disable hciuart.service

Initial Setup

First make sure the system is up to date using the following commands.

sudo apt-get update
sudo apt-get upgrade

Then reboot the Pi to ensure the latest kernel is loaded.

Now install the required packages.

sudo apt-get install bluealsa python-dbus

Make Bluetooth Discoverable

Normally a Bluetooth device is only discoverable for a limited amount of time. Since this is a headless setup we want the device to always be discoverable.

  1. Set the DiscoverableTimeout in /etc/bluetooth/main.conf to 0
# How long to stay in discoverable mode before going back to non-discoverable
# The value is in seconds. Default is 180, i.e. 3 minutes.
# 0 = disable timer, i.e. stay discoverable forever
DiscoverableTimeout = 0
  1. Enable discovery on the Bluetooth controller
sudo bluetoothctl
power on
discoverable on
exit

Install The A2DP Bluetooth Agent

A Bluetooth agent is a piece of software that handles pairing and authorization of Bluetooth devices. The following agent allows the Raspberry Pi to automatically pair and accept A2DP connections from Bluetooth devices. All other Bluetooth services are rejected.

Copy the included file a2dp-agent to /usr/local/bin and make the file executable with

sudo chmod +x /usr/local/bin/a2dp-agent

Testing The Agent

Before continuing, verify that the agent is functional. The Raspberry Pi should be discoverable, pairable and recognized as an audio device.

Note: At this point the device will not output any audio. This step is only to verify the Bluetooth is discoverable and bindable.

  1. Manually run the agent by executing
sudo /usr/local/bin/a2dp-agent
  1. Attempt to pair and connect with the Raspberry Pi using your phone or computer.
  2. The agent should output the accepted and rejected Bluetooth UUIDs
A2DP Agent Registered
AuthorizeService (/org/bluez/hci0/dev_94_01_C2_47_01_AA, 0000111E-0000-1000-8000-00805F9B34FB)
Rejecting non-A2DP Service
AuthorizeService (/org/bluez/hci0/dev_94_01_C2_47_01_AA, 0000110d-0000-1000-8000-00805f9b34fb)
Authorized A2DP Service
AuthorizeService (/org/bluez/hci0/dev_94_01_C2_47_01_AA, 0000111E-0000-1000-8000-00805F9B34FB)
Rejecting non-A2DP Service

If the Raspberry Pi is not recognized as a audio device, ensure that the bluealsa package was installed as part of the Initial Setup

Install The A2DP Bluetooth Agent As A Service

To make the A2DP Bluetooth Agent run on boot copy the included file bt-agent-a2dp.service to /etc/systemd/system. Now run the following command to enable the A2DP Agent service

sudo systemctl enable bt-agent-a2dp.service

Thanks to @matthijskooijman for fixing up some issues in the Bluetooth Agent service.

Bluetooth devices should now be able to discover, pair and connect to the Raspberry Pi without any user intervention.

Testing Audio Playback

Now that Bluetooth devices can pair and connect with the Raspberry Pi we can test the audio playback.

The tool bluealsa-aplay is used to forward audio from the Bluetooth device to the ALSA output device (sound card).

Execute the following command to accept A2DP audio from any connected Bluetooth device.

bluealsa-aplay -vv 00:00:00:00:00:00

Play a song on the Bluetooth device and the Raspberry Pi should output audio on either the headphone jack or the HDMI port. See this guide for configuring the audio output device of the Raspberry Pi.

Install The Audio Playback As A Service

To make the audio playback run on boot copy the included file a2dp-playback.service to /etc/systemd/system. Now run the following command to enable A2DP Playback service

sudo systemctl enable a2dp-playback.service

Reboot and enjoy!

Low Volume Output

If you are experiencing low volume output, run alsamixer and increase the volume of the Pi's soundcard.

#!/usr/bin/python
from __future__ import absolute_import, print_function, unicode_literals
import sys
import dbus
import dbus.service
import dbus.mainloop.glib
try:
from gi.repository import GObject
except ImportError:
import gobject as GObject
AGENT_INTERFACE = "org.bluez.Agent1"
AGENT_PATH = "/test/agent"
class Rejected(dbus.DBusException):
_dbus_error_name = "org.bluez.Error.Rejected"
class Agent(dbus.service.Object):
exit_on_release = True
def set_exit_on_release(self, exit_on_release):
self.exit_on_release = exit_on_release
@dbus.service.method(AGENT_INTERFACE,
in_signature="", out_signature="")
def Release(self):
print("Release")
if self.exit_on_release:
mainloop.quit()
@dbus.service.method(AGENT_INTERFACE,
in_signature="os", out_signature="")
def AuthorizeService(self, device, uuid):
print("AuthorizeService (%s, %s)" % (device, uuid))
if uuid == "0000110d-0000-1000-8000-00805f9b34fb":
print("Authorized A2DP Service")
return
print("Rejecting non-A2DP Service")
raise Rejected("Connection rejected")
@dbus.service.method(AGENT_INTERFACE,
in_signature="o", out_signature="s")
def RequestPinCode(self, device):
print("RequestPinCode (%s)" % (device))
return "0000"
@dbus.service.method(AGENT_INTERFACE,
in_signature="o", out_signature="u")
def RequestPasskey(self, device):
print("RequestPasskey (%s)" % (device))
return dbus.UInt32("password")
@dbus.service.method(AGENT_INTERFACE,
in_signature="ouq", out_signature="")
def DisplayPasskey(self, device, passkey, entered):
print("DisplayPasskey (%s, %06u entered %u)" %
(device, passkey, entered))
@dbus.service.method(AGENT_INTERFACE,
in_signature="os", out_signature="")
def DisplayPinCode(self, device, pincode):
print("DisplayPinCode (%s, %s)" % (device, pincode))
@dbus.service.method(AGENT_INTERFACE,
in_signature="ou", out_signature="")
def RequestConfirmation(self, device, passkey):
print("RequestConfirmation (%s, %06d)" % (device, passkey))
return
@dbus.service.method(AGENT_INTERFACE,
in_signature="o", out_signature="")
def RequestAuthorization(self, device):
print("RequestAuthorization (%s)" % (device))
raise Rejected("Pairing rejected")
@dbus.service.method(AGENT_INTERFACE,
in_signature="", out_signature="")
def Cancel(self):
print("Cancel")
if __name__ == '__main__':
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
bus = dbus.SystemBus()
agent = Agent(bus, AGENT_PATH)
obj = bus.get_object("org.bluez", "/org/bluez");
manager = dbus.Interface(obj, "org.bluez.AgentManager1")
manager.RegisterAgent(AGENT_PATH, "NoInputNoOutput")
print("A2DP Agent Registered")
manager.RequestDefaultAgent(AGENT_PATH)
mainloop = GObject.MainLoop()
mainloop.run()
[Unit]
Description=A2DP Playback
After=bluealsa.service syslog.service
Requires=bluealsa.service
[Service]
ExecStartPre=/bin/sleep 3
ExecStart=/usr/bin/bluealsa-aplay --profile-a2dp 00:00:00:00:00:00
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=A2DP-Playback
User=pi
[Install]
WantedBy=multi-user.target
[Unit]
Description=A2DP Bluetooth Agent
After=bluetooth.service
Wants=bluetooth.service
[Service]
ExecStartPre=/bin/sh -c "echo discoverable on | bluetoothctl"
ExecStart=/usr/bin/python -u /usr/local/bin/a2dp-agent
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=A2DP-Agent
[Install]
WantedBy=bluetooth.service
@alanmilinovic
Copy link

I just tested that this one works on Buster:
https://github.com/nicokaiser/rpi-audio-receiver

True, it is working more stable but still stuttering a lot, every few seconds. Not usable at all.

@Nibbos
Copy link

Nibbos commented Nov 18, 2019

Like many, I am experiencing stutter and sound drop-out with this project (Pi 3B). I have read the entire thread but am uncertain which of the many suggestions to this I should be employing. I have a bluetooth USB dongle from an old Pi B that I could use with my Pi 3B, but am uncertain how to disable WiFi on the Pi 3B and how I would re-enable when not using the BT streaming functionality.
A summary or update on best fix would be most appreciated.
Many thanks in advance.

@mill1000
Copy link
Author

@Nibbos, I mentioned that you may experience issues using the on-board Bluetooth. There is an issue tracking it here (raspberrypi/linux#1402). If you have not already, try using the external dongle.

I don't actually use this setup anymore as Bluetooth audio quality is OK as best and codec support is spotty.

@risinek
Copy link

risinek commented Mar 11, 2020

Thanks! Works like a charm with the little change in bluealsa service config as mentioned in comments already - ExecStart=/usr/bin/bluealsa -p a2dp-sink and the sleep time set to 15 seconds in playback service.

For the delay issue - just use bluealsa-aplay --pcm-buffer-time=135000 --pcm-period-time=33750 --profile-a2dp 00:00:00:00:00:00 in the playback service config

@fredrike
Copy link

Just joined to say thank you! I've been struggling with this for quite some time.

As I've tested so many different solutions I had a lot of legacy laying around, had to run a sudo apt-get purge bluetooth pi-bluetooth bluealsa blueman bluez before sudo apt-get install pi-bluetooth bluealsa. Also had to modify /etc/bluetooth/main.conf with Class = 0x24043C (derrived from http://bluetooth-pentest.narod.ru/software/bluetooth_class_of_device-service_generator.html) to be discoverable by my LG tv. The sync is acceptable with ExecStart=/usr/bin/bluealsa-aplay --pcm-buffer-time=170000 --pcm-period-time=10000 --profile-a2dp 00:00:00:00:00:00 in /etc/systemd/system/a2dp-playback.service.

@marmite-sandwich
Copy link

This is a great project and gist, but I am failing at the same point as @Phil171, @davthomaspilot and @d-koppenhagen. I tried the solution suggested by @hashtagchris, but no joy.

However, when I ran
sudo /usr/local/bin/a2dp-agent
I just got
A2DP Agent Registered
I didn't get any AuthorizeService records.

In fact, I can't even get a2dp-agent to terminate at this point - tried exit, end, bye, quit, q. The only thing which worked was ^c, which doesn't feel like it was the intended way to quit. Any suggestions welcome. There seem to be a lot of different fixes to the original gist, but they seem to mainly relate to issues encountered after this first test.

My Android phone asked if I wanted to pair, I said yes and after crashing out of the silent agent, I did a bluetoothctl and the mobile was back as a paired device. But the agent is not recognising the phone as an a2dp device (it does support a2dp). The phone is now paired but will not connect.

Cheers,
Marmite

@mill1000
Copy link
Author

mill1000 commented Apr 17, 2020

@marmite-sandwich The a2dp-agent script does not accept any inputs so Ctrl-C is the best way to kill it.

I just tried to follow my steps using a fresh install of the last Raspbian Stretch image. It appears something has changed in blue-alsa or the Raspiban package at least. It does not default to a A2DP profile.

As stated by @risinek and @Antannah try adding -p a2dp-sink to the bluealsa.service file located here /lib/systemd/system/bluealsa.service. Or use the service file from here and configure the options as directed.

@JeanDeho
Copy link

JeanDeho commented Apr 30, 2020

Hi, it is running again - in stretch with following additional steps, since changes in the blue-alsa or Raspbian package.

No bluetooth audio profile -> I added this actions, now it works as expected:

(1.) set class in bluetooth-config
$> sudo nano /etc/bluetooth/main.conf
...
Class = 0x41C
...

(2.) adding -p a2dp-sink to the bluealsa.service file located here /lib/systemd/system/bluealsa.service
$> sudo nano /lib/systemd/system/bluealsa.service
ExecStart=/usr/bin/bluealsa -p a2dp-sink

(3.) in addition (I read this on a busters install. guide)
$> sudo nano /lib/systemd/system/bluetooth.service
ExecStart=/usr/lib/bluetooth/bluetoothd --plugin=a2dp

(4.) don't know, if this is necessary, nevertheless I did this after changes on *.service files in (2.) and (3.):
$> sudo systemctl daemon-reload
$> sudo reboot

THEN this gist works as expected, according to the original upper example:
$> sudo /usr/local/bin/a2dp-agent

Remarks:
Maybe not every step is necessary.
Works like a charme with Samsung Note 9 (smartphone reboot = no problem = Raspberry Pi reboot).
Works not 100% with Motorola Play 4, after a reboot of Raspberry Pi, bluetooth pairing must be removed on smartphone.

@Andre-85
Copy link

Andre-85 commented Jun 17, 2020

Thank you very much for your guide!

For running it on Ubuntu 20.04 I ported the a2dp-agent script to python3. The dependencies changed to python3-dbus, python3-gi and python3.

#!/usr/bin/python3

import sys
import dbus
import dbus.service
import dbus.mainloop.glib
from gi.repository import GLib

AGENT_INTERFACE = "org.bluez.Agent1"
AGENT_PATH = "/test/agent"
  
class Rejected(dbus.DBusException):
    _dbus_error_name = "org.bluez.Error.Rejected"

class Agent(dbus.service.Object):
    exit_on_release = True

    def set_exit_on_release(self, exit_on_release):
        self.exit_on_release = exit_on_release

    @dbus.service.method(AGENT_INTERFACE, in_signature="", out_signature="")
    def Release(self):
        print("Release")
        if self.exit_on_release:
            mainloop.quit()

    @dbus.service.method(AGENT_INTERFACE, in_signature="os", out_signature="")
    def AuthorizeService(self, device, uuid):
        print("AuthorizeService ({0:s}, {1:s})".format(device, uuid))
        if uuid == "0000110d-0000-1000-8000-00805f9b34fb":
            print("Authorized A2DP Service")
            return
        print("Rejecting non-A2DP Service")
        raise Rejected("Connection rejected")

    @dbus.service.method(AGENT_INTERFACE, in_signature="o", out_signature="s")
    def RequestPinCode(self, device):
        print("RequestPinCode ({0:s})".format(device))
        return "0000"

    @dbus.service.method(AGENT_INTERFACE, in_signature="o", out_signature="u")
    def RequestPasskey(self, device):
        print("RequestPasskey ({0:s})".format(device))
        return dbus.UInt32("password")

    @dbus.service.method(AGENT_INTERFACE, in_signature="ouq", out_signature="")
    def DisplayPasskey(self, device, passkey, entered):
        print("DisplayPasskey ({0:s}, {1:06d} entered {2:d})".format(device, passkey, entered))

    @dbus.service.method(AGENT_INTERFACE, in_signature="os", out_signature="")
    def DisplayPinCode(self, device, pincode):
        print("DisplayPinCode ({0:s}, {1:s})".format(device, pincode))

    @dbus.service.method(AGENT_INTERFACE, in_signature="ou", out_signature="")
    def RequestConfirmation(self, device, passkey):
        print("RequestConfirmation ({0:s}, {1:06d})".format(device, passkey))
        return

    @dbus.service.method(AGENT_INTERFACE, in_signature="o", out_signature="")
    def RequestAuthorization(self, device):
        print("RequestAuthorization ({0:s})".format(device))
        raise Rejected("Pairing rejected")

    @dbus.service.method(AGENT_INTERFACE, in_signature="", out_signature="")
    def Cancel(self):
        print("Cancel")

if __name__ == '__main__':
    dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)

    bus = dbus.SystemBus()

    agent = Agent(bus, AGENT_PATH)

    obj = bus.get_object("org.bluez", "/org/bluez");
    manager = dbus.Interface(obj, "org.bluez.AgentManager1")
    manager.RegisterAgent(AGENT_PATH, "NoInputNoOutput")

    print("A2DP Agent Registered")

    manager.RequestDefaultAgent(AGENT_PATH)

    mainloop = GLib.MainLoop()
    mainloop.run()

@mill1000
Copy link
Author

@chrisdo023 It is possible to use bluez-alsa to send audio data. Look at it's readme here: https://github.com/Arkq/bluez-alsa#configuration--usage

@bobthechemist
Copy link

The instructions worked as provided for me with a RPi Zero W (running Stretch updated as of day of this post) equipped with an Adafruit speaker bonnet so long as the device sending audio was iOS (did not test with Android). When another RPi (3B also on Stretch) was sending audio, it could find the device but would not populate the audio menu to select as an audio device unless I added @JeanDeho's item #1 (add the class line to bluetooth config). When discovering bluetooth devices through the RPi GUI, this changed the yellow question mark icon in the bluetooth menu to an audio icon and the audio menu was now populated with my speakers.

I can now send audio via command line aplay -D bluealsa:HCI=hci0,DEV=<BTADDRESS>,PROFILE=a2dp test.wav however audio from youtube videos playing in chromium do not have sound.

As others have mentioned, I also needed to add the sink parameter (#2 in @JeanDeho's comment) however #3 resulted in the bluetooth service not working. I have not been able to debug further.

@spmp
Copy link

spmp commented Feb 3, 2021

Thanks for this example, it helped me a lot. In the end my solution is basically simple-agent with the addition of your service filtering/rejection. I haven't figured out how to tell the client there is no 'phone call' mode tho 8(

One of the problems with this (and other) solutions is that they require additional steps to enable pairing and discovery, but we can do this all with DBus in the application with the addition of:

# Depends on physical setup, maybe you will need to do 'find_device' or similar.
DEVICE_PATH = '/org/bluez/hci0'
...
    obj = bus.get_object(BUS_NAME, "/org/bluez");

    # Set Discoverable and Pairable to always on
    print("Setting device to 'discoverable' and 'pairable'...")
    prop = dbus.Interface(bus.get_object("org.bluez", DEVICE_PATH), "org.freedesktop.DBus.Properties")
    prop.Set("org.bluez.Adapter1", "DiscoverableTimeout", dbus.UInt32(0))
    prop.Set("org.bluez.Adapter1", "PairableTimeout", dbus.UInt32(0))
    prop.Set("org.bluez.Adapter1", "Discoverable", dbus.Boolean(True))
    prop.Set("org.bluez.Adapter1", "Pairable", dbus.Boolean(True))

How can I share what I have done? In this gist?
Chur

@mrobinson513
Copy link

Thanks for this example, it helped me a lot. In the end my solution is basically simple-agent with the addition of your service filtering/rejection. I haven't figured out how to tell the client there is no 'phone call' mode tho 8(

One of the problems with this (and other) solutions is that they require additional steps to enable pairing and discovery, but we can do this all with DBus in the application with the addition of:

# Depends on physical setup, maybe you will need to do 'find_device' or similar.
DEVICE_PATH = '/org/bluez/hci0'
...
    obj = bus.get_object(BUS_NAME, "/org/bluez");

    # Set Discoverable and Pairable to always on
    print("Setting device to 'discoverable' and 'pairable'...")
    prop = dbus.Interface(bus.get_object("org.bluez", DEVICE_PATH), "org.freedesktop.DBus.Properties")
    prop.Set("org.bluez.Adapter1", "DiscoverableTimeout", dbus.UInt32(0))
    prop.Set("org.bluez.Adapter1", "PairableTimeout", dbus.UInt32(0))
    prop.Set("org.bluez.Adapter1", "Discoverable", dbus.Boolean(True))
    prop.Set("org.bluez.Adapter1", "Pairable", dbus.Boolean(True))

How can I share what I have done? In this gist?
Chur

If you have some working update I think many would benefit from a net new gist. This one has gotten a little long in the tooth. Might do one myself if I can get all these "this trick worked for me" bits consolidated.

@mill1000
Copy link
Author

@mrobinson513, @spmp if either of you do whip up a newer Gist I will gladly add a link to it at the top of this one.

@ryepup
Copy link

ryepup commented Apr 7, 2021

I ran into a lot of the same issues as the folks above using pi os lite (2021-03-04-raspios-buster-armhf-lite), and the many of the fixes above worked for me.

One new issue I didn't represented in this thread; I was unable to pair from my android phone. less /var/log/syslog showed the a2dp-agent was rejecting me, but I was never asked to enter any codes.

I was able to get around this by changing the python code to accept any auth attempts:

 	@dbus.service.method(AGENT_INTERFACE,
 					in_signature="o", out_signature="")
 	def RequestAuthorization(self, device):
- 		print("RequestAuthorization (%s)" % (device))
- 		raise Rejected("Pairing rejected")
+		print("Authorizing (%s)" % (device))
+		return

There's probably a better way to do it 🤷‍♂️

@calanor
Copy link

calanor commented Apr 25, 2021

Thanks! Works very well in my rapsberry pi 4 and buster.
The only thing is that when I restart the raspberry pi the device don't connects automatically. Looking at the info with bluetoothctl it looks like it is paired but not connected. I have had to make a little script that connect the paired devices when the pi boot.
The other direction works perfectly. If the mobile device is restarted or losses the coverage with the pi running, it is automatically paired and connected.

@calanor
Copy link

calanor commented May 19, 2021

I have tested it with a bluetooth v4 dongle an work well. Today I have tested with a v5 dongle but it does not work. The difference is that instead of asking for a code like "0000" on the mobile device, it sends a random code that must be accepted in bluetoothctl with messages like:

[NEW] Device 74: 23: 44: 56: XX: XX Xiaomi
Request confirmation
[agent] Confirm passkey 268699 (yes / no): yes
[CHG] Device 74: 23: 44: 56: XX: XX Connected: no
[DEL] Device 74: 23: 44: 56: XX: XX Xiaomi

In your script no appears any message.

I returned to my V4 dongle.

@aloobhai
Copy link

aloobhai commented May 23, 2021

Tested this and it works on my rpi4 except it won't connect to my Ubuntu20.04 LTS laptop and my android smart TV.
In short only works for android phones. It is discoverable from my laptop however not as an audio device, so I'm guessing
it tried to connect to the pi regularly and gets rejected. It attempts pairing and disconnects within a second without any error message.
With my android TV it's just not discoverable at all( I've connected other bluetooth speakers to it without any problems)

Edit: Tv is now discoverable after changing 'Class = 0x000414' in '/etc/bluetooth/main.conf'. Someone here in the comment used Class = 0x24043C but that did not work for me. I'm using the built in bluetooth on my android smart tv(Motorola).

@Harry4Offies
Copy link

I was struggling with the setup of connection with sspmode 1. So, pairing without a pin code. With the agent provided I could not get a connection.
btmon showed:

> HCI Event: Simple Pairing Complete (0x36) plen 7           #33 [hci0] 32.309975
        Status: Authentication Failure (0x05)
        Address: 12:34:45:8B:93:FA (Samsung Electronics Co.,Ltd)

I am new in linux, bluetooth and python. So, it was a puzzle, because the code looked very obvious. But then a saw that the RequestAuthorization handler contained raise Rejected("Pairing rejected").
Once I removed that line, it worked like a charm.

@spmp
Copy link

spmp commented Sep 16, 2021

After upgrading to Ubuntu 21.04 what I have done is not working at all. No idea what changed. And... like all good projects I have forgotten how to debug and test. Damn 8( This was so good 8(

@spmp
Copy link

spmp commented Sep 16, 2021

@eichblatt
Copy link

Hi, I am trying something similar: to send audio from a headless RPi to a bluetooth speaker. I can do it via the GUI, but I just can't find the magic commands to make it work headlessly. Have you had any experience with this?

@mill1000
Copy link
Author

@eichblatt Sorry Steve, I won't be of much help. I haven't used Bluetooth in a while and my knowledge of it is now all 4 years old. Have you taken a look at this section of the bluez-alsa docs? https://github.com/Arkq/bluez-alsa#configuration--usage

@eichblatt
Copy link

Thank you! I think my problem might be pulse audio, more than bluetooth.

@spmp
Copy link

spmp commented Jan 18, 2022

Good day @eichblatt ,
I have put my current version in a repo: https://github.com/spmp/promiscuous-bluetooth-audio-sinc
I tested this today on a Ubuntu 21.04 install and it worked like a charm. There are various fixes and no doubt issues....
Maybe we should move this thread over to the issues section of that repo.
@mill1000 I hope you feel properly attributed.

@dumtux
Copy link

dumtux commented Feb 19, 2022

I've tried many ways to accomplish this on Raspberry CM4, but non was successful.
Bluealsa, Bluez, auto paring, fixed PIN code, ... so many tries and fails!

Finally this worked out of the box - https://github.com/spmp/promiscuous-bluetooth-audio-sinc/blob/main/a2dp-agent
Thank you, @spmp

@spmp
Copy link

spmp commented Feb 20, 2022

Glad it worked as expected @hotteshen 8)
Please highlight any issues and let me know if the documentation - largely taken from this gist - is adequate.

@calanor
Copy link

calanor commented Aug 5, 2022

Hello,
with the new version of Raspberry pi OS Bullseye the bluealsa and python-dbus is obsolete. I installed the bluealsa package using this guide.
I don't know how install python-dbus.

Any upgrade of this guide for new Bullseye version?

@DominikStarke
Copy link

@calanor This guide largely still works.

The required dbus package can be installed using pip install dbus-python. After installation @spmp's a2dp agent should work.
For bluealsa it's probably best to compile it yourself. I followed the official instructions and it works even with AAC, aptX and LDAC if enabled.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment