arkq/bluez-alsa

Routing audio to bluealsa from hw:0,1

Opened this issue · 31 comments

Hi,
I have a issue with bluealsa while routing audio from hw. And my requirement is to redirect the audio from hw:0,1 to bluealsa. From that moment, Audio should not come through hw:0,1. Its like bluealsa needs to act as virtual plugin at runtime.
bluealsa version : 4.1.1
conf file : /bluez-alsa/src/asound/20-bluealsa.conf.in
OS : Linux username 5.4.210 #2 SMP PREEMPT Thu Feb 29 20:24:03 IST 2024 aarch64 GNU/Linux
BlueZ version : 5.72

For example,
--> If other music sources writes audio on hw:0,1, then if BT got connected it should redirect audio to bluealsa plugin without disturbing (closing pcm and reopen snd_pcm_open() with bluealsa plugin) other music services whoever writing audio.
--> alsaloop -C hw:0,1 -P bluealsa (or) arecord -D hw:0,1 -r 48000 -c 2 -f S16_LE | aplay -D bluealsa
I have tried above steps, I could hear audio mixed with noise. What is the issue here ?

I am new to Alsa and PCM . Any help will be appreciated. Thanks

arkq commented

If other music sources writes audio on hw:0,1, then if BT got connected it should redirect audio to bluealsa plugin without disturbing (closing pcm and reopen snd_pcm_open() with bluealsa plugin) other music services whoever writing audio.

This concept is not new, but I'm afraid that ALSA still does not support such use case out of the box (I mean there is no plugin available which would allow such use case). I'm not sure how difficult would be to write such plugin for ALSA... It does not seems to be complicated (but of course devils is in the details). It could be hosted in the bluez-alsa repo, since such feature is mostly related to dynamically appearing/disappearing PCMs like Bluetooth and USB cards.

My idea is as such:
PCM plugin which collects all available output PCMs and redirects output to one of these based on some heuristic. Also, there should be possible configuration which could whitelist/blacklist PCMs from the set. And finally, CTL plugin which could change the mode of operation from auto-selected PCM to user defined PCM (or change heuristic mode).

I assume Android does this at the system level but I don't even know if/how Android relates to ALSA. Otherwise, it is done by applications sniffing for udev/dbus signals.

arkq commented

Otherwise, it is done by applications sniffing for udev/dbus signals.

Yes, that's one way of doing it. @borine has created a script which does that and dynamically changes ALSA configuration: https://github.com/borine/bluealsa-autoconfig However, it does not allow to dynamically switch between PCMs.

I think, that from the user point of view, the best way would be to create a dedicated software plugin which can be used as a default PCM on the system. From the user point of view this plugin could not fail at all. In case there are no available PCMs, it could redirect audio to null. And in case there are some output PCMs, it could open such (in the background) and if it will succeed, then it can send samples to it (e.g. via plug plugin to handle format conversion automatically). In case PCM disappears/something wrong happens, we can open another PCM from the set of available PCMs and redirect samples to it. In the worst case we can go back to null. I think that such plugin could simplify a lot from the user point of view. The question is, how complicated such plugin would be :D

EDIT: It's quite possible that such new plugin would work best in tandem with https://github.com/borine/bluealsa-autoconfig

The dynamic switching would be the difficult part. And I suppose you would have to deal with the situation where BT audio is coming in via ba-aplay and perhaps the user wants to send it out via BT to another device? When one or both go down, what order do they reconnect in?

arkq commented

When one or both go down, what order do they reconnect in?

I was thinking about a bit simpler setup :D In my mind the only part that we need to take care about is the output - not the input. In case when the input goes down, it's up to the application what to do. However, in case of output going down, we can handle it for the application - as if it would talk with the audio server. The application sees PCM which can be opened and written to, what happens then it's up to the plugin (audio server). Problematic might be switching between PCMs with different latencies - application might not liked the idea of audio going out of sync rapidly.

it's up to the application what to do

What if there's no application? In OSMC we just pass BT audio from ba-aplay to the default sink in ALSA (or one defined by a user in alsa conf). Not pretty, as we have to wait for Kodi to release the sink and some phones won't let you adjust the volume. Just saying. You are the experts.

pass BT audio from ba-aplay to the default sink in ALSA

so ba-aplay is the application !!

some phones won't let you adjust the volume.

I guess that is android phones. Bluez AVRCP implementation appears to be incompatible with Android. There is a Bluez patch linked in a recent BlueALSA issue to fix that (I don't have a note of it right now). The problem may be gone with Android 14, but so far we have had no confirmation one way or another, and I do not have any Android devices to test with.

we have to wait for Kodi to release the sink

Yes, perhaps one day BlueALSA will get multi-client support, but for now only one client per stream is permitted.

PCMs with different latencies

Yes, indeed I think an option to manage latency by holding a large buffer (and therefore always incurring a large latency) would be necessary. Not a problem for applications such as kodi, but not acceptable for voice applications, I think.

so ba-aplay is the application !!

Do'oh!

I thought the volume thing was mainly on iPhones. My ancient Android phone works fine.

Hello,
If I write audio into hw:0,1 and i want to capture after BT connected. So, I thought of using "alsaloop -C hw:0,1 -P bluealsa"
Here, I can hear audio on both local hw and BT speaker. But audio out from BT speaker is noisy. Why ?
I have tried with "arecord" also, Same noisy output.
I need to loopback audio from hw:0,1 to bluealsa

Could you confirm, Is it proper method to that ? Or Do we have any other mechanism to do that ?

It really depends on what hw:0,1 is. For most soundcards, the playback stream for a device is on a different physical port to the capture stream of the same device. So for example playback to hw:0,1 might go to the local speakers whereas capture from hw:0,1 might come from the local microphone. So most likely alsaloop is sending sound from your microphone to the bluetooth device, and so this sound includes feedback from the local speakers as well as ambient noise.

Thanks for the explanation.Is it any other way remove noise or loopback method to achieve this requirement ?

is it any other way ... to achieve this requirement ?

Well, the simple answer is to use an audio server like pipewire or pulseaudio. If you are unable to use an audio server then you really have to learn a lot more about the basics of linux low-level audio streaming. This is not the right place to look for such detailed training.

I found something here. For this requirement we can create Virtual PCM with subdevices using "sudo modprobe snd-aloop" and it will give you New card (card 1),devices (0,1) and 8 subdevices (0 to 7) for individual devices. Then, All music services need to open and write into Virtual pcm , say example (hw:1,0,4) instead of direct (hw:0,1) and we will get loopback on (hw:1,1,4), Here we can use "alsaloop" to capture from hw:1,1,4 and playback to required pcm . We can kill it and restart it dynamically based on the output selection. No need to disturb the audio writing music sources. Ref link : https://sysplay.in/blog/linux/2019/06/playing-with-alsa-loopback-devices/

Sure, there are some notes on using the snd-aloop Loopback device with BlueALSA in the wiki here:
https://github.com/arkq/bluez-alsa/wiki/Using-BlueALSA-with-dmix

But, if I was building a system with the specific requirement to automatically switch a running stream from sound card to bluetooth, my first choice would still be Pipewire.

@borine Could you please explain me why pipewire will be your choice than bluealsa ? Does pulseaudio/pipewire better than bluealsa ? bluealsa will be plugin in this case, How pulseaudio and pipewire will be behaving in this case ?

Kindly suggest me any blog/tutorial to study about it. Thanks

why pipewire will be your choice than bluealsa

Because the main issue discussed here is moving a running audio stream, uninterrupted, from one output device to another. This is outside the scope of ALSA. There is no ALSA tool for this. Whichever way you choose to combine different ALSA plugins and utilities you will always need to write some new code to actually achieve the change from one device to another. And to do that successfully such that it works well with many different applications is not trivial.

BlueALSA sits behind the ALSA interface, so merely allow you to use a Bluetooth device as you would a sound card device.

What you need is something that sits in front of the ALSA interface, between your applications and ALSA, which can provide the required additional functionality, before passing the resulting stream to ALSA. That is the role of an audio service, such as Pipewire.

Now Pipewire also has its own Bluetooth audio solution, and Bluez does not permit two different services to both provide A2DP service. So when using Pipewire the best advice is to not use BlueALSA at all. It may be possible to use BlueALSA through Pipewire's ALSA backend (see for example #681) but I do not know of anyone who has managed to do it successfully yet, and I would not recommend it.

I think it is a shame that there are no Linux audio servers that are designed to be able to use ALSA for all audio I/O, and not just for sound cards, but that is the reality at this time. So Pipewire is not "better" than BlueALSA, it is just a different tool designed for a different purpose.

So by all means try out your alsaloop/snd-aloop solution, and if successful please do document it to share it with others, I think it would be very useful to many systems. I'm just thinking that you are commiting yourself to a lot of work to solve a problem that has already been solved by Pipewire (and by Pulseaudio)

@borine Thanks for the detailed explanation. Do we have any audio player in pulseaudio like bluealsa-aplay ?

Do we have any audio player in pulseaudio like bluealsa-aplay ?

I guess the nearest equivalent is the pulse module-loopback module or the pipewire libpipewire-module-loopback module. See the pulseaudio/pipewire/wireplumber docs and seek help from pulseaudio/pipewire/wireplumber forums

arkq commented

I've created a very simple PoC ALSA plugin for dynamic PCM switching: https://github.com/arkq/alsa-plugin-dswitch It "works" with aplay and bluealsa, but in order to be useable a much more work would be required.

The concept is very simple. The plugin exposes common PCM interface which can be opened by any application. Then, when the PCM is started by the application, a real PCM is opened and all samples are forwarded to it. In case when the write returns ENODEV, plugin tries to open another real PCM according to the priority list. Also, there is a background thread which checks for appearing PCMs with higher priority (for the purpose of the PoC it's done with a busy loop), and in such case the current PCM is closed and new one is opened. Idea is very simple, but in order to be robust some buffering will have to be added. Also, this plugin does not support file descriptor polling right now, so it will not work with anything else than aplay.

@borine @graham8 what do you think about such concept?

I'm not sure whether I'd be able to "finish" it to be useable, but maybe someone can develop it beyond simple PoC. The best idea would be to add such plugin to alsa-plugins repo, but I'm not sure whether such plugin can be generic enough to fit upstreaming criteria. The problem is that all the "backends" need to be behind plug plugin, or the exposed common PCM needs to be an intersection of all the backends (which in case of bluealsa is impossible, because Bluetooth PCM configuration is not known up front).

I've created a very simple PoC ALSA plugin for dynamic PCM switching

That's a very interesting idea. I have some ideas on how to make the devices selectable from the ALSA config, and also on how to get rid of the "busy loop" for device detection. I'll raise PRs on the dswitch repo when I have some code ready, and suggest we use the issue list there also for general discussion.

@borine, I have one doubt. In our system, All the other music source using alsa-lib like they open and write audio on snd_pcm() APIs, In this case , If i use pulseaudio for bluetooth, How can i achieve BT as audio source ?

In bluealsa, As a plugin other music services can write into it. It will go to bluealsa daemon and send to bluetooth speaker. Here, How will be in pulseaudio with bluetooth ? Other music service needs to integrate with pulseaudio instead of ALSA api ?

Sorry for this kind of basic questions.

I did not mean to suggest that you use pulseaudio and bluealsa together. What I mean is that if you want live streams to be switched from one device to another without stopping, and without the music applications doing that themselves, then you need an audio server such as Pipewire or Pulseaudio. That is true even if you are not using Bluetooth. Suppose you want to redirect a running stream from an on-board sound card to a usb device when the usb one is plugged in. How would you do that with only ALSA? I think you cannot. So you need your applications to send their audio to an audio server which can do that task. Then the audio server uses its own bluetooth implementation and Bluealsa is not needed.

Hi @borine, @arkq
Currently I am using "gst-launch-1.0" to stream audio, Here , the alsasink device. I use "bluealsa" plugin to do playback on connected BT speaker.
gst-launch-1.0 alsasrc device=hw:2,1,4 ! alsasink device=bluealsa sync=false &, This is how i start gst player to capture from virtual PCM and do playback on connected BT speakers.

Here, If i check with BT speakers and neckband ,then the audio playback is fine, there is no glitch at all.
But if I check with bluetooth ear buds, then i will face audio glitch and breaks. While this issue comes, I could see below prints continuously from gstreamer,

"WARNING: from element /GstPipeline:pipeline0/GstAlsaSrc:alsasrc0: Can't record audio fast enough
dropped 10584 samples. this is most likely because downstream can't keep up and is consuming samples too slowly."

Even if i play using alsaloop also, I am getting this audio breaks issue with earbuds. I am not sure why this happens only with bluetooth earbuds?

bluealsa version : 4.1.1
conf file : /bluez-alsa/src/asound/20-bluealsa.conf.in
OS : Linux username 5.4.210 #2 SMP PREEMPT Thu Feb 29 20:24:03 IST 2024 aarch64 GNU/Linux
BlueZ version : 5.72

I am not sure why this happens only with bluetooth earbuds

This may be related to the used codec and codec configuration. Please can you give the output of the following command when each of the bluetooth devices are connected so that we can compare the selected configurations for "BT speakers", "neckband" and "bluetooth ear buds":

bluealsa-aplay --list-devices

**** List of PLAYBACK Bluetooth Devices ****
hci0: xx:xx:xx:xx:xx:xx [neckband], audio-headset
A2DP (SBC): S16_LE 2 channels 48000 Hz

**** List of PLAYBACK Bluetooth Devices ****
hci0: xx:xx:xx:xx:xx:xx [earbuds], audio-headset
A2DP (AAC): S16_LE 2 channels 48000 Hz

**** List of PLAYBACK Bluetooth Devices ****
hci0: xx:xx:xx:xx:xx:xx [speaker], audio-card
A2DP (SBC): S16_LE 2 channels 48000 Hz

I have verified with Many earbuds, But i am getting the audio breaks issue with some of them only. Let me know, If we need to change any configuration on codec,

defaults.bluealsa.codec "SBC",
In my bluealsa.conf, I have configured default codec as SBC only, But still earbuds taking AAC , They choose this codec as best during negotiation, is it ?

defaults.bluealsa.codec "SBC"

Hmm, so something unexpected here. The config default codec is usually "unchanged" which allows the device and the bluealsa service to negotiate the best available codec. If you set this default to "SBC" then I would expect all devices to use SBC, I am surprised that the earbuds are using AAC.

I have verified with Many earbuds, But i am getting the audio breaks issue with some of them only.

Please can you verify, do all the non-working earbuds use AAC and all the working ones use SBC ?

In any case, using a different codec should not in itself make the device consume samples more slowly. I was expecting to see the earbuds using a different rate (but all your devices are using 48000).

I can think of two reasons why the bluetooth device may run more slowly than the hw capture device:

  • the "bluealsa" pcm is of type "plug" so uses libasound's internal resampler to convert rates, which can cause this kind of problem.
  • A bluetooth device can be slow to start because it needs to exchange A2DP and AVDTP profile messages in order to initialize the stream. This causes issues for some applications, though for most it does not. For example alsaloop has no problem using bluealsa as sink (playback) device, but can have fatal problems using bluealsa as source (capture). I have no experience with gst-launch-1.0 to know whether this is affected.

A possible fix for the first problem is to tell the application explicitly what sample rate to run the sink at (i.e. 48000). That way we can be sure that the libasound resampler is not being used by ALSA plug.

For the second problem you may find that increasing buffer sizes and/or latency may help. It depends entirely on how the application adjusts for clock drift between the devices.

Please can you verify, do all the non-working earbuds use AAC and all the working ones use SBC ?

Working earbuds also using AAC codec only.

In any case, using a different codec should not in itself make the device consume samples more slowly. I was expecting to see the earbuds using a different rate (but all your devices are using 48000).

Is it expected that earbuds choose 48K always ? or Can we change it forcefully to 44.1k?

the "bluealsa" pcm is of type "plug" so uses libasound's internal resampler to convert rates, which can cause this kind of problem.

If this is a case, How come other BT speakers,Neckbands, and some earbuds too working without audio glitch ?

A possible fix for the first problem is to tell the application explicitly what sample rate to run the sink at (i.e. 48000). That way we can be sure that the libasound resampler is not being used by ALSA plug.

If i tell the application (i.e alsaloop) with rate 48000, it will do resampling inside the application, right ?, this resampling also will be done by libasound, Is it ?. Will it make issue again ?

For the second problem you may find that increasing buffer sizes and/or latency may help. It depends entirely on how the application adjusts for clock drift between the devices.

I have increased the latency and checked with alsaloop application,even after i was getting the same issue.4

One more issue, One of my earbuds(JBL) using only one codec, I have connected and checked through phone, it was showing SBC codec only. If i connect and doing playback (as BT source with bluealsa plugin) with my device and tried "scan on" in bluetoothctl, at that time i was getting audio glitch issue. Normally, it works fine.

Is it expected that earbuds choose 48K always ? or Can we change it forcefully to 44.1k?

You can tell bluealsa to use 44.1 whenever possible (see the bluealsa manual page), but I do not recommend it as 48k generally gives better results

If this is a case, How come other BT speakers,Neckbands, and some earbuds too working without audio glitch ?

If they are all using 2-channel s16_le at 48000 frames per second then most likely the ALSA rate plugin is not the cause of this problem.

If i tell the application (i.e alsaloop) with rate 48000, it will do resampling inside the application, right ?, this resampling also will be done by libasound, Is it ?. Will it make issue again ?

It depends on the application, you will have to check each application you wish to use. For alsaloop both capture and playback devices will be set to 48000, so no rate conversion is done (assuming the capture device can be set to run at 48000).

checked with alsaloop application

there are some notes on using alsaloop with BlueALSA in the wiki here: https://github.com/arkq/bluez-alsa/wiki/Using-BlueALSA-with-dmix#alsaloop Other than that I can not offer any suggestions on why these devices are having problems.

doing playback ... and ... "scan on"

Many users have reported such issues, but this is not a BlueALSA problem - you will experience the same with any bluez audio solution. My only advice is "do not do that".

Thanks for the detailed responses @borine , Let me cover all possible test cases and come back with the results.

Hi @borine ,
I have reverted back from default SBC to unchanged, Now the audio breaks solved with malfunctioning earbuds. Its working as expected with AAC,rate=48000 configuration. the problem because of it was using SBC, rate = 48000. I'm not sure why with SBC,

So, I can go with codec "unchanged" right ?

I remember, i have tested with Marshall speaker as Sink device. At that time, audio was coming out with very low playback speed. For that reason only, i have set this codec as SBC by default, then it was fine. So i left it
Now only, I got the reason that Marshall speaker takes 96000 rate with AAC and 48000 rate with SBC

hci0: xx:xx:xx:xx:xx:xx [marshall], audio-card
A2DP (AAC): S16_LE 2 channels 96000 Hz

How can I avoid this problem by blocking of selecting 96000 rate ?

I have enabled "bluealsa --a2dp-force-audio-cd" force 44.1k , After that, I am not getting low speed playback issue with Marshall too.
hci0: xx:xx:xx:xx:xx:xx [marshall], audio-card
A2DP (AAC): S16_LE 2 channels 44100 Hz

Is it good idea to avoid that low speed playback issue ?

One more issue, One of my earbuds(JBL) using only one codec, I have connected and checked through phone, it was showing SBC codec only. If i connect and doing playback (as BT source with bluealsa plugin) with my device and tried "scan on" in bluetoothctl, at that time i was getting audio glitch issue. Normally, it works fine.

This issue, I am not seeing with other speakers,neckbands and earbuds but only with JBL earbuds. Could you please give some details to debug this issue ?