目标

我有一个外接的解码放大一体机(钰龙欧若拉)和一个外接的DAC小尾巴( FIIO KA11 ),之前在 Windows 上用的时候都是用它们各自提供的 ASIO 驱动,到了 Arch 上之后就不需要额外的驱动了。这两个外接音频设备我分别用在了两台电脑上,这两台电脑都是 Arch + Windows 的双系统,这里以 FIIO KA11 作为外接 DAC 来举例。

Arch 的音频系统有几个层级,最关键的就是底层的 ALSA 和上层的音频服务器 PipeWire 或 PipeWire-Pulse 。其中 PipeWire-Pulse 用来替代 PulseAudio 。

要做的事情有两个:

  1. 需要一个专用的音频播放器直接无采样地播放各种音质的音乐(如采样率为 44.1kHz 的 CD 音质、常见的 48kHz、Hires 中的 96kHz 384kHz ),最好这个播放器直接操作 ALSA 层输出到外置解码放大设备
  2. 需要修改 PipeWire 让其他应用程序输出的高品质音乐不被下采样

专用音频播放服务器 mpd 的安装与配置

对于第一件事,我参考了 NGA上的一篇文章 ,其中介绍了使用 mpd 做后台音频服务器,并使用 cantata 作界面客户端来播放高品质音乐。我在经过一番搜索后决定采用 mpd + ncmpcpp 的方案,下面记录了安装与配置过程。

首先安装 mpd 软件包:

1
2
3
sudo pacman -S mpd
# 这里也装一下 alsa 的一些工具包
sudo pacman -S alsa-utils alsa-tools

然后确保外置解码器已经连接到电脑,查看解码器的设备地址:

1
aplay -l

获取到如下信息片段:

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 :

1
sudo pacman -S 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 )的音频输出都会被重采样。参考链接:

研究之后发现解决方法几乎是固定的:

  1. 不要使用 Firefox 听 Bilibili 的 Hires ,使用 Chromium
  2. 设置系统默认采样率为外置 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 上的听音系统。不过说到底音乐本身才是我们更应该关注的东西。在这一点上我的确是认同“音乐第一,折腾第二”的。