Linux蓝牙之BlueALSA(轻量蓝牙音频后端)构建配置与使用
Linux蓝牙之BlueALSA(轻量蓝牙音频后端)构建配置与使用
前言
前面有简单介绍 BlueALSA(轻量蓝牙音频后端):
- https://notes.z-dd.online/2026/09/21/Linux%E8%93%9D%E7%89%99%E4%B9%8BBlueALSA%E7%AE%80%E4%BB%8B/
- https://mp.weixin.qq.com/s/n0aO8k0z9P4jjROWDo3Mgg
目前没有现成的安装包,所以需要自己构建,今天记录下 BlueALSA 构建配置与使用,以下都已通过我的 AI Agent 团队验证测试。
目标环境:Linux Desktop,aarch64,kernel 5.4.18,BlueZ 5.62
交付版本:bluez-alsa v5.0.0 + sbc 2.2 + fdk-aac 2.0.2 + ldacBT 2.0.2.3 + libopenaptx 0.2.0 + liblc3 1.1.1
安装前缀:/usr/local(不动系统 /usr,BlueZ 与 PulseAudio 不受影响)
构建与安装:一切皆可选
BlueALSA 的构建系统充分体现了其“最小化”的设计目标。根据 INSTALL.md,它的许多特性都是可选的,只有在配置构建时显式请求才会被包含,因此 configure 选项非常多。
核心依赖
构建 BlueALSA 本身需要以下基础库:
- alsa-lib >= 1.0.27
- bluez >= 5.51
- glib >= 2.58.2(需要 GIO 支持)
- sbc >= 1.5
安装依赖
sudo apt-get install -y \
git pkg-config autoconf automake libtool \
libasound2-dev libglib2.0-dev libdbus-1-dev libbluetooth-dev libbsd-dev \
libsbc-dev libspandsp-dev libreadline-dev libsystemd-dev libudev-dev \
meson ninja-build cmake g++
sudo dpkg --configure -a # 清掉半配置状态
| 包 | 用途 |
|---|---|
libasound2-dev |
ALSA 插件与 PCM 接口(必需) |
libglib2.0-dev / libdbus-1-dev |
GLib 事件循环与 D-Bus 服务(必需) |
libbluetooth-dev |
BlueZ 头文件,hci.h / sdp_lib.h(必需) |
libsbc-dev |
SBC 编解码(必需,但版本不够,见 §2) |
libbsd-dev |
strlcpy 等 BSD 扩展(必需) |
libspandsp-dev |
mSBC 解码的丢包隐藏(PLC,codec-msbc.c 里的 plc_init/plc_free;启用 --enable-msbc 时必需) |
libreadline-dev |
bluealsactl 交互补全 |
libsystemd-dev |
sd_notify 就绪通知(启用 --enable-systemd 时必需) |
meson ninja-build |
构建 liblc3 |
cmake g++ |
构建 fdk-aac / ldacBT(fdk-aac 是 C++ 工程) |
验证解析到的版本(v5.0.0 的下限):
pkg-config --modversion alsa glib-2.0 dbus-1 bluez # 1.2.2 / 2.64.6 / 1.12.16 / 5.62
编译与构建
编译 sbc 2.2
bluez-alsa v5.0.0 要求 sbc ≥ 1.5,发行版源里只有 1.4,缺 sbc_reinit_a2dp 等符号。
从 kernel.org 取 sbc 2.2,装到 /usr/local 与系统 1.4 隔离:
git clone --depth 1 git://git.kernel.org/pub/scm/bluetooth/sbc.git
cd sbc && git checkout 2.2
./configure --prefix=/usr/local --disable-tools --disable-tester
make -j"$(nproc)"
sudo make install
sudo ldconfig
**不要装到
--prefix=/usr**:那样会覆盖/usr/include/sbc/sbc.h与/usr/lib/libsbc.so,
但ld.so缓存里 multiarch 目录的 1.4 仍优先,链接期报undefined reference to 'sbc_reinit_a2dp'。装/usr/local后系统 1.4 原样保留,
BlueZ / PulseAudio 继续用它,互不干扰。
验证:
PKG_CONFIG_PATH=/usr/local/lib/pkgconfig pkg-config --modversion sbc # 2.2
grep -c sbc_reinit_a2dp /usr/local/include/sbc/sbc.h # 1
编译四个 codec 库(可选,用于 AAC / LDAC / aptX / LC3)
全部装到 /usr/local。只需要 SBC + mSBC 的话可以跳过本节。
| 库 | 版本 | 构建系统 | 命令要点 |
|---|---|---|---|
| fdk-aac | 2.0.2 | cmake | cmake -DCMAKE_INSTALL_PREFIX=/usr/local -DCMAKE_BUILD_TYPE=Release .. |
| ldacBT | 2.0.2.3 | cmake | 先把 libldac 源码填入 ldacBT-2.0.2.3/libldac/;产物仅 libldacBT_enc/abr |
| libopenaptx | 0.2.0 | make | make -j8 && sudo make install |
| liblc3 | 1.1.1 | meson | meson setup build --prefix=/usr/local -Dtools=true && ninja -C build |
-Dtools**默认是false**(见meson_options.txt),必须显式写-Dtools=true
才会编出elc3/dlc3这两个 LC3 编解码自检工具(§3 末尾的 roundtrip 验证靠它们)。ldacBT 的 CMakeLists 只声明了
LDAC_SOFT_FLOAT一个 option,没有LDAC_DECODE;
传-DLDAC_DECODE=ON只会变成一个无人读取的UNINITIALIZED缓存项,静默无效。
解码方向不存在的原因见下方说明。liblc3 源码 tag 是
1.1.1,但 meson 工程版本号为1.1.0,故lc3.pc报lc3 1.1.0。
每个库装完执行 sudo ldconfig。验证:
export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig:/usr/local/lib/aarch64-linux-gnu/pkgconfig
for m in fdk-aac ldacBT-enc ldacBT-abr libopenaptx lc3; do
printf '%-14s %s\n' "$m" "$(pkg-config --modversion $m)"
done
# fdk-aac 2.0.2 / ldacBT-enc 2.0.2.3 / ldacBT-abr 2.0.2.3 / libopenaptx 0.2.0 / lc3 1.1.0
LDAC 只有编码方向:
ldacBT-dec(解码库)Sony 未公开源码,开源 libldac 只含
encoder + ABR。因此 A2DP sink 收 LDAC 无解码,LDAC 只能用于 source(发送)方向。
configure 会自动检测:找不到 dec 就不定义HAVE_LDAC_DECODE,不注册 LDAC sink 端点。
编译安装 bluez-alsa v5.0.0
v5.0.0 的 tarball 不含 configure 脚本,必须先 autoreconf:
tar xzf bluez-alsa-5.0.0.tar.gz && cd bluez-alsa-5.0.0
autoreconf --install --force
export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig:/usr/local/lib/aarch64-linux-gnu/pkgconfig:/usr/lib/pkgconfig
export LDFLAGS="-L/usr/local/lib -Wl,-rpath,/usr/local/lib"
pkg-config --modversion sbc # 必须打印 2.2,否则回到 §2
./configure \
--prefix=/usr/local \
--sysconfdir=/etc \
--enable-msbc \
--enable-rfcomm \
--enable-a2dpconf \
--enable-hcitop \
--enable-ofono \
--enable-systemd \
--enable-aac \
--enable-ldac \
--enable-aptx \
--enable-aptx-hd \
--enable-lc3-swb \
--with-libopenaptx \
--with-systemdsystemunitdir=/lib/systemd/system \
--with-systemdbluealsadargs="-S -p a2dp-source -p a2dp-sink -p hfp-ag" \
--disable-test
make -j"$(nproc)"
sudo make install
sudo ldconfig
| 选项 | 作用 |
|---|---|
--enable-msbc |
HFP 宽带语音(依赖 spandsp) |
--enable-rfcomm |
编出 bluealsa-rfcomm,HFP 的 AT 命令通道 |
--enable-a2dpconf / --enable-hcitop |
编出两个独立工具 a2dpconf(A2DP 编解码参数调优)与 hcitop(HCI 流量 top),源码在 utils/a2dpconf.c、utils/hcitop.c。不是 bluealsactl 的子命令 |
--enable-ofono |
支持 ofono 后端(本机未装 ofono,实际走 native HFP-AG) |
--enable-systemd |
sd_notify + 生成 bluealsa.service |
--enable-aac/ldac/aptx/aptx-hd/lc3-swb |
对应 codec;缺库则该 codec 静默不编入 |
--with-libopenaptx |
aptX 用开源实现(替代 Qualcomm 闭源库) |
--with-systemdbluealsadargs |
写进 unit 的默认启动参数 |
产物:
/usr/local/bin/{bluealsad,bluealsactl,bluealsa-aplay,bluealsa-rfcomm,a2dpconf,hcitop}
/usr/lib/aarch64-linux-gnu/alsa-lib/libasound_module_{pcm,ctl}_bluealsa.so
/usr/local/share/alsa/alsa.conf.d/20-bluealsa.conf
/usr/share/dbus-1/system.d/org.bluealsa.conf
/lib/systemd/system/{bluealsa,bluealsa-aplay}.service
a2dpconf/hcitop由--enable-a2dpconf/--enable-hcitop产出,同装/usr/local/bin,
卸载时一并删。ALSA 插件不在
/usr/local而在系统 multiarch 目录:configure.ac:390的--with-alsaplugindir未传参时默认展开为$(pkg-config --variable=libdir alsa)/alsa-lib
=/usr/lib/aarch64-linux-gnu/alsa-lib(实测吻合)。这是刻意行为——ALSA 只在该目录找插件,
装到/usr/local反而加载不到。而20-bluealsa.conf走--prefix,落在/usr/local/share/alsa/alsa.conf.d/,故 §5.1 必须建软链到/etc/alsa/conf.d/才会被读取
(/etc/alsa/conf.d/里发行版自带的几项也都是指向/usr/share/alsa/alsa.conf.d/的软链)。
配置:以 ALSA 为中心
基础配置:ALSA 插件注册
最简单的使用方式是,在 ALSA 配置文件(如 ~/.asoundrc 或 /etc/asound.conf)中定义一个 PCM 设备。
例如,使用预定义的 bluealsa PCM,它是一个 plug 类型设备,会自动处理音频格式转换:
pcm.bluealsa {
type bluealsa
DEV=01:23:45:67:89:AB # 目标设备MAC地址
PROFILE=a2dp # 配置文件:a2dp 或 sco
CODEC=aac # 可选,强制使用特定编解码器
}
该 PCM 支持的关键参数包括:
- DEV:蓝牙设备地址。默认值
00:00:00:00:00:00表示选择最近连接该配置文件的设备。 - PROFILE:
a2dp(高质量音频)或sco(用于 HFP/HSP,支持麦克风)。默认是a2dp。 - CODEC:指定编解码器,会覆盖自动协商的结果。默认
unchanged表示使用现有设置。 - VOL:初始音量。
简单操作可以使用复用自带的配置:
sudo mkdir -p /etc/alsa/conf.d
sudo ln -sf /usr/local/share/alsa/alsa.conf.d/20-bluealsa.conf /etc/alsa/conf.d/20-bluealsa.conf
之后,应用程序即可通过 bluealsa 这个 PCM 设备名来播放或录制音频,比如aplay -L | grep bluealsa 能看到该设备,-D bluealsa:... 可直接用。
启动守护进程
通常,bluealsad 由 systemd 等系统服务管理。你也可以手动启动它,并指定要启用的配置文件,例如:bluealsa -p a2dp -p hfp。
开启全部 codec:
--with-systemdbluealsadargs 写入的默认参数不含 --all-codecs,
v5.0.0 里非强制 codec 需要显式开启才会注册端点。用 dropin 覆盖:
sudo systemctl daemon-reload
sudo systemctl edit bluealsa # 写入下面两行
[Service]
ExecStart=
ExecStart=/usr/local/bin/bluealsad -S --all-codecs -p a2dp-source -p a2dp-sink -p hfp-ag
ExecStart=空行是必需的——先清空再赋值,否则 systemd 报重复定义。
sudo systemctl enable --now bluealsa
systemctl is-active bluealsa # active
编解码器协商与限制
当设备连接时,BlueALSA 会自动协商可用的最佳编解码器。CODEC 参数允许你强制指定,但需注意:
- 如果指定的编解码器不可用,插件会发出警告并回退到默认值。
- 对于 HFP,BlueALSA 不支持从 HFP-HF 节点更改编解码器,只有 HFP-AG 节点可以更改。如果搭配 oFono 使用,该参数会失效。
- 对于 A2DP,可以在编解码器名称后附加一个十六进制配置字符串来精确控制,例如
CODEC=SBC:FC450240可以限制 SBC 只使用单声道或双声道模式。
PulseAudio冲突问题:释放 HFP UUID(关键,否则 HFP 注册失败)
PulseAudio 的蓝牙模块会与 bluealsad 抢注同一个 HFP UUID,症状是UUID already registered in BlueZ / Couldn't register hands-free profile。
注释掉 /etc/pulse/default.pa 里的蓝牙发现模块:
sudo sed -i 's/^load-module module-bluetooth-discover/#load-module module-bluetooth-discover/' /etc/pulse/default.pa
grep -n 'module-bluetooth-discover' /etc/pulse/default.pa # 行首应有 #
发行版里这行通常形如 load-module module-bluetooth-discover enable_msbc=false,sed 的前缀匹配会保留后面的参数,不受影响。
module-bluez5-discover不需要单独注释——/etc/pulse/default.pa里没有它的独立行,
它是被module-bluetooth-discover内部加载的,注释前者即可一并停掉。
机器上可能有两个 PulseAudio 实例(用户级 + 由 ToDesk 之类服务拉起的 root 级),
二者都读同一份/etc/pulse/default.pa,所以改这一处即可覆盖。
但已经在跑的 root 实例不会自动重载配置,需要让它重启(或直接 kill,
拉起它的服务会按新配置重拉)才真正释放 UUID。
使用
实用工具示例
- **
bluealsa-aplay**:此命令可将来自蓝牙麦克风的音频流实时播放到本地声卡上。最简单的用法是直接运行bluealsa-aplay,它会捕获所有已连接蓝牙设备的音频并播放到默认的 ALSA 设备。 - **
bluealsactl**:用于通过 D-Bus 与bluealsad守护进程交互,可用于查询状态、管理连接等。
连接设备
bluetoothctl
scan on
pair <MAC> # 首次
trust <MAC> # 建议,便于自动回连
connect <MAC>
quit
查看状态与 PCM
bluealsactl status # 服务、适配器、各 profile 支持的 codec
bluealsactl list-pcms # 当前已连接设备对应的 PCM 路径
status 实测输出:
Service: org.bluealsa
Version: v5.0.0
Adapters: hci0
Profiles:
A2DP-source : SBC AAC aptX aptX-HD LDAC
A2DP-sink : SBC AAC aptX aptX-HD
HFP-AG : CVSD mSBC LC3-SWB
播放与录音
# A2DP 播放(source 方向)
aplay -D bluealsa:DEV=<MAC>,PROFILE=a2dp test.wav
# HFP 录音(AG 方向,麦克风;mSBC 不可用时先降级:bluealsactl codec <PCM> CVSD)
arecord -D bluealsa:DEV=<MAC>,PROFILE=sco -f S16_LE -r 8000 -c 1 rec.wav
PROFILE只接受三个值(src/asound/bluealsa-pcm.c: str2profile):a2dp/asha/sco,写hfp无效。方向(source/sink)不用指定——
插件按 capture/playback 自动选择对应 PCM。
运行时调参
PCM 的 D-Bus 对象路径由 src/ba-transport-pcm.c:99 拼接,形如:
/org/bluealsa/<hci>/dev_<MAC>/<profile段>/<source|sink>
例如 /org/bluealsa/hci0/dev_C4_60_0A_B8_DD_D8/a2dpsrc/sink。
profile 段取值(ba-transport-pcm.c:45-70 的 transport_get_dbus_path_type(),注意是缩写):
| profile | 路径段 |
|---|---|
| A2DP source / sink | a2dpsrc / **a2dpsnk**(不是 a2dpsink) |
| ASHA source / sink | ashasrc / ashasnk |
| HFP AG / HF | hfpag / hfphf |
| HSP AG / HS | hspag / hsphs |
最后一段是 PCM 的方向,指 PCM 对应用户侧的 capture 还是 playback,不是蓝牙空口方向,也与 profile 名字面不对应(ba-transport-pcm.h:21-26:MODE_SOURCE = capturing,MODE_SINK = playing;分配处 ba-transport.c:442-446、652-658):
- A2DP source profile(本机发流给耳机):主通道 PCM 是给 aplay 用的→
a2dpsrc/sink(playback=MODE_SINK);back-channel(FastStream 双向时的收流)是a2dpsrc/source - HFP-AG:扬声器(耳机出声,本机 playback)→
hfpag/sink;麦克风(耳机收音,本机 capture)→hfpag/source——所以arecord麦克风录音操作的是hfpag/source - 同一 profile 的两条 PCM:音量各自独立,但 codec 是共享的(见下方说明)
音量 vs codec 的作用域不同(容易误判,已对源码核实):
- 音量是 PCM 级:
struct ba_transport_pcm自带volume[8]数组(ba-transport-pcm.h:144),
所以hfpag/sink(扬声器)与hfpag/source(麦克风)的音量分别可调,bluealsactl volume各管各的。- codec 是 transport 级,不是 PCM 级:属性 getter
ba_variant_new_pcm_codec(pcm)第一行就取const struct ba_transport *t = pcm->t,再调ba_transport_get_codec(t)(bluealsa-dbus.c:281-284、ba-transport.c:1077);SelectCodec方法同样落到ba_transport_select_codec_sco(t, codec_id)/ba_transport_select_codec_a2dp(t, ...),且共用一把t->codec_select_client_mtx
(bluealsa-dbus.c:770lock /881走 a2dp /899走 sco /915unlock,四行实测吻合;bluealsa_pcm_select_codec()入口在:755)。- 结构体层面更直接:
struct ba_transport_pcm的全部字段里没有任何 codec 身份字段
(只有codec_delay_dms/reported_codec_delay_dms这类延迟量),而struct ba_transport独占uint32_t codec_id与保护它的codec_id_mtx
(ba-transport.h:106-108)+codec_select_client_mtx(:111)。
即 codec 只有一份、挂在 transport 上,PCM 结构体里根本没有可各自保存的位置。- 变更会广播给两条 PCM:codec 一改,代码显式对两条都发
UPDATE_CODEC通知——sco.c:242-252对pcm_spk与pcm_mic各调一次bluealsa_dbus_pcm_update(... | BA_DBUS_PCM_UPDATE_CODEC | ...);ba-rfcomm.c:1242-1246(SLC 建链时已选定 codec)
同样两条都发。这解释了为什么改一次 codec,bluealsactl info两边一起变。同一 profile 的两条 PCM 是同一个 transport 的成员(A2DP:
pcm与pcm_bc,ba-transport.h:162;HFP:pcm_spk与pcm_mic,:209-210),
因此对任一条 PCM 设 codec 会同时影响另一条——bluealsactl codec <PCM> CVSD之后,
扬声器和麦克风两条路径一起降到 CVSD,不是只降你指定的那条。
实践中先用 list-pcms 取到确切路径再操作:
| 命令 | 作用 |
|---|---|
bluealsactl list-services |
列出所有 BlueALSA D-Bus 服务名(多实例/-B 后缀时用) |
bluealsactl info <PCM> |
查看 codec / 采样率 / 声道 / 延迟 / 音量 |
bluealsactl codec <PCM> [CODEC] |
查询或切换 codec(如降级到 CVSD) |
bluealsactl volume <PCM> [L] [R] |
查询或设置音量 |
bluealsactl mute <PCM> |
静音开关 |
bluealsactl client-delay <PCM> |
客户端缓冲延迟 |
bluealsactl soft-volume <PCM> |
软音量控制 |
bluealsactl monitor |
实时打印 D-Bus 信号(调试用) |
bluealsactl open <PCM> |
通过 stdin/stdout 传原始 PCM |
已知限制
mSBC / LC3-SWB 需要内核补丁
HFP 的 mSBC 与 LC3-SWB 在本机上无法出数据,dmesg 报:
Bluetooth: hci0: Device does not support ALT setting 6
Bluetooth: hci0: urb submission failed (90) # 90 = EMSGSIZE
根因是内核 btusb 无 alt6 时 new_alts 留在 0,切到零带宽端点。
不是硬件限制,需要打上上游的修复补丁。
codec 限制
- AAC / aptX / LDAC 这些 codec 都需要设备支持。
- LDAC 仅 source 方向,sink 方向缺 Sony 未开放的解码库。
卸载
sudo systemctl disable --now bluealsa
sudo rm -f /etc/alsa/conf.d/20-bluealsa.conf
sudo rm -f /etc/systemd/system/bluealsa.service.d/all-codecs.conf
sudo rm -f /lib/systemd/system/bluealsa{,-aplay}.service
sudo rm -f /usr/share/dbus-1/system.d/org.bluealsa.conf
sudo rm -f /usr/local/bin/bluealsa{d,ctl,-aplay,-rfcomm} /usr/local/bin/{a2dpconf,hcitop}
sudo rm -f /usr/lib/aarch64-linux-gnu/alsa-lib/libasound_module_{pcm,ctl}_bluealsa.*
sudo rm -f /usr/local/lib/libsbc.so* # 系统 1.4 在 /lib/aarch64-linux-gnu/,不受影响
sudo rm -rf /usr/local/include/sbc /usr/local/lib/pkgconfig/sbc.pc
# 若按 §3 装过四个 codec 库,一并卸掉(注意 liblc3 在 multiarch 目录,与其余三个不同)
sudo rm -f /usr/local/lib/libfdk-aac.* /usr/local/lib/pkgconfig/fdk-aac.pc
sudo rm -f /usr/local/lib/libopenaptx.* /usr/local/lib/pkgconfig/libopenaptx.pc
sudo rm -f /usr/local/lib/libldacBT_* /usr/local/lib/pkgconfig/ldacBT-*.pc
sudo rm -f /usr/local/lib/aarch64-linux-gnu/liblc3.so* \
/usr/local/lib/aarch64-linux-gnu/pkgconfig/lc3.pc \
/usr/local/bin/{elc3,dlc3,openaptxenc,openaptxdec}
sudo systemctl daemon-reload && sudo ldconfig
恢复 PulseAudio 蓝牙:去掉 /etc/pulse/default.pa 中那行的 # 前缀后重启 PA。





