目标
我有一个外接的解码放大一体机(钰龙欧若拉)和一个外接的DAC小尾巴( FIIO KA11 ),之前在 Windows 上用的时候都是用它们各自提供的 ASIO 驱动,到了 Arch 上之后就不需要额外的驱动了。这两个外接音频设备我分别用在了两台电脑上,这两台电脑都是 Arch + Windows 的双系统,这里以 FIIO KA11 作为外接 DAC 来举例。
Arch 的音频系统有几个层级,最关键的就是底层的 ALSA 和上层的音频服务器
PipeWire 或 PipeWire-Pulse 。其中 PipeWire-Pulse 用来替代 PulseAudio 。
要做的事情有两个:
- 需要一个专用的音频播放器直接无采样地播放各种音质的音乐(如采样率为 44.1kHz 的 CD 音质、常见的 48kHz、Hires 中的 96kHz
384kHz ),最好这个播放器直接操作 ALSA 层输出到外置解码放大设备
- 需要修改 PipeWire 让其他应用程序输出的高品质音乐不被下采样
专用音频播放服务器 mpd 的安装与配置
对于第一件事,我参考了 NGA上的一篇文章 ,其中介绍了使用 mpd 做后台音频服务器,并使用 cantata 作界面客户端来播放高品质音乐。我在经过一番搜索后决定采用 mpd + ncmpcpp 的方案,下面记录了安装与配置过程。
首先安装 mpd 软件包:
1
2
3
| sudo pacman -S mpd
# 这里也装一下 alsa 的一些工具包
sudo pacman -S alsa-utils alsa-tools
|
然后确保外置解码器已经连接到电脑,查看解码器的设备地址:
获取到如下信息片段:
1
2
3
| card 1: KA11 [FIIO KA11], device 0: USB Audio [USB Audio]
Subdevices: 1/1
Subdevice #0: subdevice #0
|
从这里获取设备地址 hw:1,0 ,前一个0是虚拟声卡的编号,后一个是设备编号。有了这个地址之后就可以去配置 mpd 。
这里参考 Arch Wiki 里的 mpd 相关内容 配置。直接创建一个配置文件:
1
2
3
4
| mkdir -p ~/.config/mpd
cp /usr/share/doc/mpd/mpdconf.example ~/.config/mpd/mpd.conf
# 这个文件编辑
vim ~/.config/mpd/mpd.conf
|
我主要做了以下的修改:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| # 设置音乐库目录,我这里设置为挂载的 2TB 机械硬盘
music_directory "/mnt/data/Music"
# 设置播放列表存放目录
playlist_directory "~/.config/mpd/playlists"
# 设置数据库文件存放位置
db_file "~/.config/mpd/database"
# 最关键的输出配置
audio_output {
enable "yes"
type "alsa"
name "FIIO KA11" # 设备名,可以自定义
device "hw:1,0" # 这是上面查找到的设备地址
mixer_type "hardware"
auto_resample "no" # 关闭重采样
auto_channels "no"
auto_format "no"
}
|
修改完之后保存,一定别忘了创建上面写的文件目录:
1
| mkdir -p ~/.config/mpd/playlists
|
最后启动 mpd 服务:
1
2
| systemctl --user start mpd
# 或者设置开始自启 systemctl --user enable --now mpd
|
至此,音频服务器就配置完成了。
音乐播放客户端 ncmpcpp 的安装与配置
安装 ncmpcpp :
然后参考 Arch Wiki 里的 相关条目 进行配置,下面是整个配置的过程。
1
2
3
| # 把配置模板文件拷贝到用户配置目录
mkdir -p ~/.config/ncmpcpp
cp /usr/share/doc/ncmpcpp/config ~/.config/ncmpcpp/config
|
然后修改里面的几项关键配置:
1
2
3
4
5
6
7
8
9
|
mpd_host = localhost
mpd_port = 6600
visualizer_data_source = "/tmp/mpd.fifo"
visualizer_output_name = "my_fifo"
visualizer_in_stereo = "yes"
visualizer_type = "spectrum"
visualizer_look = "+|"
|
先别着急启动,为了让 ncmpcpp 在播放时可以进行波形的可视化,我们需要在 mpd 中加一个输出:
1
| vim ~/.config/mpd/mpd.conf
|
在前一个 audio_output 项后面添加一个输出项:
1
2
3
4
5
6
| audio_output {
type "fifo"
name "my_fifo"
path "/tmp/mpd.fifo"
format "44100:16:2" # 注意这个值必须是 44100:16:2
}
|
保存后重启 mpd 服务:
1
| systemctl --user restart mpd
|
然后直接在终端输入 ncmpcpp
就可以享用无损音乐啦!
效果图如下,我播放的是马友友的《生命之歌》这张专辑,音频质量为
24bit/96kHz 。图中正在播放的是第一首古诺的圣母颂。
这是波形显示界面(按数字8进入):
播放的时候查看当前的音频输出:
1
2
3
4
5
6
7
8
| # 输入 cat /proc/asound/card1/pcm0p/sub0/hw_params 直接查看声卡输出
access: RW_INTERLEAVED
format: S24_3LE
subformat: STD
channels: 2
rate: 96000 (96000/1)
period_size: 12000
buffer_size: 48000
|
发现输出确实是 24bit/96kHz ,说明没有经过重采样,达到了预期的结果。
再通过查看 FIIO KA11 上的硬件指示灯,发现指示灯为黄色,证明 DAC 芯片的确接收到了 96kHz 的音频流。
至此,已经完成了从音频服务到播放器客户端的所有安装和配置。但仔细思考后就会发现其实还有一件事情没做。那就是 支持 USB DAC 设备的热拔插 。因为如果这个设备是随 USB 输入电脑的,那么它的地址就会变化,我们之前通过
aplay 获取的地址就失效了。
支持 USB DAC 设备的热拔插
Linux 上一般通过 udev 来响应的 USB 热拔插。
在 /usr/local/bin
下创建 python 脚本:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
| # /usr/local/bin/usb-dac.py
import subprocess
import re
import os
result = subprocess.run(["aplay", "-l"], capture_output=True, text=True)
output = result.stdout
error = result.stderr
# 获取卡号
# 你需要修改这个正则表达式来匹配你的 DAC 设备
pattern = r"card (\d+): KA11"
match = re.search(pattern, output)
if match is None:
exit(-1)
card_number = match.group(1)
home = "/home/aozora" # 使用 mpd 的用户目录,按你的用户名修改
mpd_config_file = os.path.join(home, ".config", "mpd", "mpd.conf")
def modify_device_in_file(file_path, device):
with open(file_path, 'r') as file:
file_content = file.read()
# 用于搜索 mpd.conf 里需要替换的段
# 这里正则编写的逻辑是 name 为 FIIO KA11 的下一行
# 同样你需要修改这个正则表达式来匹配你的设备名
pattern = re.compile(r'(name\s+"FIIO KA11".*?)(device\s+"hw:(\d),0")', re.S)
match = pattern.search(file_content)
snippet = match.group(0)
# 在小段里先替换到 hw 后的卡号
snippet = re.sub(r'hw:(\d),0', 'hw:{},0'.format(device), snippet)
# 用正则替换掉整个片段
new_content = pattern.sub(snippet, file_content)
# 写回
with open(file_path, 'w') as file:
file.write(new_content)
modify_device_in_file(mpd_config_file, card_number)
|
然后在 /etc/udev/rules.d
下创建一个新的规则文件,比如叫 usb-dac.rules 。内容如下:
1
2
| ACTION=="add", ATTRS{idProduct}=="0081", ATTRS{idVendor}=="2972", \
RUN+="/usr/bin/python /usr/local/bin/usb-dac.py"
|
其中 idProdcut 和 idVendor 需要你自己获取。获取方法为使用 lsusb
命令。这个命令会输出一个列表,找到你的 DAC 设备,中间会有类似 “ID 2972:0081”
字样。其中冒号前面的就是 idVendor ,冒号后面的就是 idProduct 。
保存完规则后重新 udev :
1
2
| sudo udevadm control --reload-rules
sudo udevadm trigger
|
然后你可以先故意将 mpd.conf 里的 hw:1,0 改成一个错误的值,再拔插你的
DAC 设备测试热拔插是否运行正常。
修改 PipeWire 的设置改善普通应用音质
上面的专用音频系统只能用于播放本地的高品质音频文件,那么如何让普通程序的高品质音频输出也能保持其采样率呢?其中的典型需求就是 Bilibili
的 Hires 输出以及 Tidal-Hifi 的 Max 输出。
在 Firefox 浏览器里 B站干脆没有 Hires 选项,而在 Chromium 中才会出现,疑似 Firefox 并不支持大于 48kHz 的音频播放,参考链接:
而 Chromium 会把所有音频都重采样到系统默认采样率,这就意味着所有基于 Chromium 的应用(包括 Electron )的音频输出都会被重采样。参考链接:
研究之后发现解决方法几乎是固定的:
- 不要使用 Firefox 听 Bilibili 的 Hires ,使用 Chromium
- 设置系统默认采样率为外置 DAC 支持的最大采样率
虽然这样会造成上采样(upsampling),但为了让那些原本采样率就高的音频流质量不下降,只能在这方面做点妥协了。
方法为直接修改 PipeWire 的配置:
1
2
3
4
| # 创建用户配置目录
mkdir -p ~/.config/pipewire/pipewire.conf.d
# 编写配置文件
vim ~/.config/pipewire/pipewire.conf.d/custom.conf
|
在里面输入:
1
2
3
4
| context.properties = {
default.clock.rate = 384000
default.clock.allowed-rates = [ 44100, 48000, 96000, 192000, 384000 ]
}
|
其中最重要的是 default.clock.rate
,因为 Chromium 根据这个进行重采样。这个值应该设置成 DAC 硬件支持的最大采样率。而 default.clock.allowed-rates
是硬件 DAC 支持的采样率列表。
修改完成后重启 pipewire 服务:
1
| systemctl --user restart pipewire pipewire-pulse
|
然后就可以听这些高质量的音频流了。可以用 pw-top 检查输出音频的采样率。
写在后面
这里详细描述了怎么从零开始构建 Arch Linux 上的听音系统。不过说到底音乐本身才是我们更应该关注的东西。在这一点上我的确是认同“音乐第一,折腾第二”的。