CD 从抓轨到搭建流媒体服务器 —— 以《月临寐乡》为例
2022-07-19
v0.0.2
偶然进了 Static World 的群并入坑了 月临寐乡 ,梦开始了。作为幻想乡的萌新,也算是有了自己喜欢的社团。但是更细节的东西,狐狐脑子一下子塞不下那么多东西,只能慢慢探索了惹。
所以,关于音频格式、元数据、 alsa 、 ffmpeg(待续) 的部分应该有好多错误(拍飞),大佬们多批评。
缘起
之前在 幻想遊園郷 和 Memories of a Town 的时候并没有注意过 tag 的问题,而是用 K3B 抓轨后将纯粹的 wav 算了 md5sum 就扔进曲库了。但是这次显然要做精致一点。
wav 格式的音频文件支持元数据吗?答案是不支持。但是这并不意味着不可以嵌入 ID3 tag 。既然能嵌入 ID3 tag 那么就一定可以嵌入专辑封面,但是无语的地方在于 Windows 并不能识别 wav 里的 ID3 tag,如果你加入了 ID3 tag ,虽然 VLC 、 mpv 、 mplayer 都可以认到,但是 Windows 资源管理器、 Groove 、 Windows Media Player 甚至 Audacious 都是认不到的,它们只会去读 wav 的一个 RIFF INFO,而且 Windows 资源管理器、 Groove 、 Windows Media Player 会在 UTF-16/UTF-8 乱码。
当然 RIFF INFO 就没有专辑封面了,所以在 Windows 默认的几个播放器以及资源管理器都将无法显示专辑封面。如果你并不 care Windows 的操作,那么你可以在 Linux 下欢快地享受 ID3 tag 的强大。
当然另一个解决方法是将其转换为 flac 等其他无损格式,这就是另一个话题了。
抓轨
有一台光驱就能抓。
我在 Debian 下使用了 K3B ,默认设置即可,得到 wav 格式的文件。
如果在 Windows 下也可以使用群友推荐的 EAC 即 Exact Audio Copy ,支持从 freedb 拉取元数据,也支持 AccurateRip ,就不用自己敲元数据了。当然对于比较早拿到新碟的来说,远程数据库大概率也是没有。
导入元数据
Kid3 和 Mp3tag 是我尝试下来最好用的两个软件,分别也是 Linux 和 Windows 下比较好的解决方案。这里列举了几个常见的可行方法,供大家一一尝试。
Kid3
Kid3 作为 K 家的软件,其功能强大自不必说。支持 Linux 和 Windows ,可以批量编辑,操作非常灵活,我主要就用它。 ID3 相关功能方面,支持 ID3 tag 的编辑并且可以在 ID3v1.1 、 ID3v2.3 和 ID3v2.4 之间一键转换。
在 File->Open Folder 就可以导入整个文件夹的曲目,并且全选曲目再在编辑框中编辑就可以批量编辑,也可以批量转换 tag 版本。
在编辑区, Tag2 部分就是 ID3v2.x 的编辑区域,在这里也可以插入专辑封面,而且看起来并没有图片大小限制;Tag3 部分是 RIFF INFO 的编辑区域,是的,它支持 RIFF INFO ,可惜是 UTF-16 编码,在 Windows 直接乱码,只能回到 Windows 来解决。、
Mp3tag
Windows only (不要跟我说可以 wine)。
Mp3tag 是一个在 Windows 下常用的 tag 编辑器,默认会添加 ID3 tag 和 RIFF INFO 且没有编码问题,可以被 Windows 资源管理器和 Groove 、 Windows Media Player 正常识别。但是添加的专辑封面由于在 ID3 tag 中,依然无法被识别。
还记得 Kid3 编辑的 RIFF INFO 会有编码问题吗,一个简便但是奇怪的方法就是在 Kid3 中编辑好元数据和封面,然后再到 Mp3tag 打开,重新保存,这样 Mp3tag 会将元数据重新写入成可以被 Windows 识别的编码。
Mp3tag 默认支持从 MusicBrainz 和 freedb 检索元数据,另外 THBWiki 提供了一个 API 来检索东方相关专辑和曲目和获取资料,并提供了一个 Mp3tag 插件来自动填入 ID3 tag ,其帮助页面介绍了如何使用该插件。
以 Windows10 为例,首先下载 THBWiki.src ,将其放到 %appdata%\Mp3tag\data\sources
目录下。启动 Mp3tag ,在“Tag Sources”下拉框下就可以找到 THBWiki 的选项。
注意此时图中显示了歌曲元数据是因为我之前有添加过。
在搜索框中搜索后,将会返回搜索结果,检查后点击 OK 即可。
所有信息将被加入并保存,仔细看应该可以看出元数据已经变掉了。
Audacity
Audacity 是 Linux 下一个著名的音频编辑软件,将曲目导入,在导出的时候就可以编辑元数据。或者在 Edit->Metadata 编辑,但是不能插入专辑封面。同样是支持 Linux 和 Windows ,但是不是很推荐这个软件啦。首先它本身不是一个专门编辑元数据的软件,其次它的元数据编辑功能完全可以被 Kid3 代替,甚至生成的 RIFF INFO 在 Windows 的表现还不如 Kid3 。
EasyTAG
EasyTAG 支持的格式也非常广泛,就是不支持 wav (*_*)。
puddletag
puddletag 也是不支持 wav ,别的格式可以考虑用一下啦。
foobar2000
foobar2000 是 windows only,很多人用,但是我试了试感觉巨难用。
MusicBrainz Picard
Picard 是 MusicBrainz 官方出的,支持 Linux 、 Windows 和 MacOS 的打 tag 工具,但是对于 RIFF INFO 似乎依旧是中文乱码,挺怪的。
Thtagger
Thtagger…… 笑死,是我自己写的,仅仅是为了解决在 Linux 环境下为 wav 添加不中文乱码的 RIFF INFO 而几天捏的轮子,而且这部分还是从 Picard 参考的,其实就是把字符编码改成 cp936 。能用,支持从 THBWiki 查找元数据,写得很糟就是了。
流媒体服务器
这里选择的是 icecast2 ,这是一个比较流行的流媒体服务器软件。最新的 Release 是 Release 2.5.0-beta3 ,但是遗憾的是它依然是 beta ;最近的稳定版是 2018 年释出的 2.4.4 。
icecast 2.4.4
我的服务链接,基于 2.4.4 版本。
如果你在比较新的 Debian 或者 Ubuntu 上安装,都将会安装上 2.4.4 的版本:
$ sudo apt-get install icecast2
打包者为我们做好了大部分配置工作。在 Debian11 上,会自动添加 icecast 用户组和 icecast 用户,这是由于 icecast2 是默认由 icecast 用户启动的。自启动 demon 放在是 /etc/init.d/icecast2
,配置文件是 /etc/icecast2/icecast.xml
,可以发现 /etc/icecast2/icecast.xml
的所有者也是 icecast 。
icecast 2.4.4 的配置比较简单,可以参考官网的2.4.1文档以及 FAQ 。
对于简单的配置:
<location>
和<admin>
只用于 Web 端显示,设置即可<source-password>
用于推流时使用;<relay-password>
用于中继,但是由于只有一台服务器所以用不到;<admin-user>
和<admin-password>
用于 Web 页面的管理员登录- 默认监听
0.0.0.0:8000
,如果需要更改则在配置中的<listen-socket>
指定<port>
和<bind-address>
,<listen-socket>
可以有多个 - 使用 systemctl 重启
systemctl restart icecast2.service
,或在非 systemd 的系统上sudo service icecast2 restart
如果出现了 UTF-8 乱码,可以参考 <mount>
的配置:
<mount type="normal">
<mount-name>/sw1</mount-name>
<charset>UTF8</charset>
</mount>
另外如果希望你的流媒体服务可以在 icacast2 的列表中被搜索到,可以选择加入如下的配置:
<!-- Uncomment this if you want directory listings -->
<directory>
<yp-url-timeout>15</yp-url-timeout>
<yp-url>http://dir.xiph.org/cgi-bin/yp-cgi</yp-url>
</directory>
默认端口是 8000 ,如果希望直接监听在 80 ,可以为 icecast2 添加相关权限: sudo setcap cap_net_bind_service=+eip /usr/bin/icecast2
。
通常在一台服务器上,我们会同时开多个服务,但是 80 端口只有一个,这时候就可以使用 nginx 来作为 proxy 根据规则转发请求,同时配置简单的限流措施。尽管有人给出了一个非常全的 nginx 配置 ,然而给 icecast2.4.x 套一个 nginx 后,尽管网页可以打开,远程推流将无法正常进行(当然直接在服务器上不过 nginx 推是可以的),在很多主流播放器(比如 VLC)上收听也会断断续续。
所以我只能用 stream 来实现:
stream {
limit_conn_zone $binary_remote_addr zone=sperip:2M;
upstream icecast {
server 127.0.0.1:50110;
}
server {
listen 2011;
listen [::]:2011;
limit_conn sperip 5;
proxy_ssl off;
proxy_pass icecast;
}
}
糟糕的是,如果这样写, icecast2 生成的 XSFP 和在 dir.xiph.org 上的端口都将显示成 50110 而不是 2011 ,意义不是很大。
所以我只能尝试更新的 2.5.0-beta3 。
icecast 2.5.0-beta3
实践证明在 Debian11 下 2.5.0-beta3 在 nginx 代理的情况下,推流会频繁掉线,所以如果没啥兴趣没必要看这部分了。 Icecast2 官方给出了两个关于 nginx 代理的页面,分别为 known reverse proxy restrictions 和 known https restrictions 。 还可以参考这篇笔记,有趣的是这篇笔记在 icecast 2.5.0 下成功用 nginx 代理并给出了配置,不知道我没有成功是否和 nginx 的版本也相关。最后我还是摆了,让 Icecast2 直接监听在了 0.0.0.0:2011
。
从官方下载源码包,这里放上链接: tarball 和 zip ball,编译三部曲如下:
$ ./configure
$ make
$ sudo make install
但是我并没有这么干,因为第一步就没过(bushi)。
其实 debian 打过这个包,把它的编译脚本拿来抄作业就好了,找到 2.4.4 的 tarball ,这里给出北外源的链接,在 debian/control
就可以看到编译依赖。或者把 debian
目录放到新的 2.5.0-beta3 的源码目录下,直接打 debian 包不香吗。
打包前记得修改包版本,修改 changelog 即可:
$ dch -s
icecast2 (2.5.0-beta3) unstable; urgency=high
* 信息自己写,这只是个示例
-- weilinfox <weilinfox@inuyasha.love> Sun, 17 Jul 2022 16:21:13 +0000
没想到的是,测试没有过。首先 icecast2 在 2.5.0-beta2 引入了测试,测试放在了 tests/
目录下,可以切换过去并 make check-TESTS
运行测试;其次, icecast2 不允许使用 root 用户运行,所以偷懒用 root 打包会直接测试失败;再次,测试需要依赖 ffmpeg ,如果你的环境没有,需要单独安装;最后,即使你都注意到了,其中有 4 个测试是无法通过的,虽然看起来对功能影响不大。
测试记录如下:
FAIL: admin.test 5 - buildm3u-user
FAIL: admin.test 6 - buildm3u-fakeuser
FAIL: admin.test 46 - mount-sourceauth
FAIL: admin.test 57 - on-connect-test-sourceauth
============================================================================
Testsuite summary for Icecast 2.4.99.3
============================================================================
# TOTAL: 61
# PASS: 57
# SKIP: 0
# XFAIL: 0
# FAIL: 4
# XPASS: 0
# ERROR: 0
============================================================================
See tests/test-suite.log
Please report to icecast@xiph.org
============================================================================
确认了问题不大后,我将 admin.test 的测试取消了。在 tests/Makefile.am
和 中可以看到下面的三行:
TESTS = \
startup.test \
admin.test
直接改成:
TESTS = \
startup.test
再 dpkg-buildpackage
打包就可以了。
打包命令简单写在下面:
$ mk-build-deps
$ sudo apt-get install ./icecast2-build-deps_2.5.0-beta3_all.deb
$ dpkg-buildpackage -b -uc -us
在上级目录可以找到 debian 包 icecast2_2.5.0-beta3_amd64.deb
。
注意 2.5.0-beta3 并不能直接使用 2.4.4 的配置文件,配置理论上应该参考官网的 2.5.0 文档,但是它指向的似乎……还是 2.4.1 的文档啊,甚至 tarball 中的 doc 也是老的文档。事实上有些选项已经不适用了,所以只能把 conf/icecast.xml.in
或者打完 debian 包后生成的 /etc/icecast2/icecast.xml
文件做为模板,重新写配置,大部分配置还是一样的,已经改变的地方只能从注释中找寻蛛丝马迹。
推流
DarkIce
DarkIce 是一个音频推流工具,它从声卡或其他音频设备采集声音,然后编码并推送,支持 IceCast 1.3.x 和 2.x 。最新 Release 1.4 。
由于 Debian 和 Ubuntu 源中均为 1.3 版本,不兼容 2.5.0-beta3 的协议,如果使用了 2.5.0-beta3 的服务器就需要自行编译 1.4 版本。
$ sudo apt-get install darkice
默认配置文件在 /etc/darkice.cfg
,这个文件通常需要自己创建,可以查看帮助文档:
$ man darkice
$ man darkice.cfg
这里给出一个配置文件的示例:
[general]
duration = 0
bufferSecs = 10
reconnect = yes
[input]
device = default
sampleRate = 44100
bitsPerSample = 16
channel = 2
[icecast2-0]
format = mp3
bitrateMode = vbr
#bitrate = 1411
quality = 0.8
server = sw.inuyasha.love
port = <port>
password = <your password>
mountPoint = <mount point>
sampleRate = 44100
channel = 2
name = 白玉製作所 channel 1
description = 白玉製作所 Audio Streaming Channel 1
url = http://sw.inuyasha.love:2011/<mount point>
genre =
public = yes
localDumpFile = /tmp/live_sw.mp3
fileAddDate = no
#fileDateFormat =
#lowpass =
#highpass =
bitrateMode = vbr
的好处在于,可以根据数据本身的情况动态调整码率,在保证质量的前提下节约了带宽。
最主要的坑就在于 [input]
下的 device
,这个设备可以是 OSS DSP , ALSA 设备, PalseAudio 设备,或者 Jack 设备。这里的 default 是默认的 ALSA 设备,如果测试不能使用就需要根据具体情况修改,后面将会提到。
编译 DarkIce 1.4 的过程和前面编译 Icecast2 类似,首先下载 DarkIce Relase 1.4,然后下载 debian 打包 1.3 时使用的脚本,这里同样给出北外源的链接。同样,将 debian
目录移动到 darkice 源码目录。
修改包版本,修改 changelog 即可:
$ dch -s
darkice (1.4) experimental; urgency=high
* Compiled with C++11 standard
* 信息自己写,这只是个示例
-- weilinfox <weilinfox@inuyasha.love> Mon, 18 Jul 2022 21:32:10 +0800
打包命令:
$ mk-build-deps
$ sudo apt-get install ./darkice-build-deps_1.4_amd64.deb
$ dpkg-buildpackage -b -uc -us
DarkIce1.4 在构建时可能出现一个常见的编译错误,我在 g++11.2 复现如下:
In file included from Connector.h:39,
from Connector.cpp:33:
Referable.h:102:57: error: ISO C++17 does not allow dynamic exception specifications
102 | ~Referable ( void ) throw ( Exception )
| ^~~~~
Referable.h:121:57: error: ISO C++17 does not allow dynamic exception specifications
121 | increaseReferenceCount ( void ) throw ( Exception )
| ^~~~~
Referable.h:139:57: error: ISO C++17 does not allow dynamic exception specifications
139 | decreaseReferenceCount ( void ) throw ( Exception )
| ^~~~~
Referable.h: In destructor ‘virtual Referable::~Referable()’:
Referable.h:105:17: warning: ‘throw’ will always call ‘terminate’ [-Wterminate]
105 | throw Exception( __FILE__, __LINE__,
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
106 | "reference count positive in destructor",
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
107 | referenceCount);
| ~~~~~~~~~~~~~~~
Referable.h:105:17: note: in C++11 destructors default to ‘noexcept’
In file included from Connector.h:40,
from Connector.cpp:33:
Ref.h: At global scope:
Ref.h:114:49: error: ISO C++17 does not allow dynamic exception specifications
114 | Ref ( const Ref<T> & other ) throw ( Exception )
| ^~~~~
Ref.h:127:49: error: ISO C++17 does not allow dynamic exception specifications
127 | Ref ( T * obj ) throw ( Exception )
| ^~~~~
Ref.h:139:49: error: ISO C++17 does not allow dynamic exception specifications
139 | ~Ref ( void ) throw ( Exception )
| ^~~~~
Ref.h:150:49: error: ISO C++17 does not allow dynamic exception specifications
150 | operator->() const throw ( Exception )
| ^~~~~
Ref.h:167:49: error: ISO C++17 does not allow dynamic exception specifications
167 | operator= ( Ref<T> other ) throw ( Exception )
| ^~~~~
Ref.h:181:49: error: ISO C++17 does not allow dynamic exception specifications
181 | operator= ( T* obj ) throw ( Exception )
| ^~~~~
Ref.h:195:49: error: ISO C++17 does not allow dynamic exception specifications
195 | set ( T * newobj ) throw ( Exception )
|
如果出现了相同的错误,可以将版本切为 C++11 ,可以在 debian/rules
的开头添加一行:
DEB_CXXFLAGS_MAINT_APPEND := -std=c++11
重新构建即可。
构建成功后可以对 1.3 平滑升级,不需要更改任何配置。
ALSA (The Advanced Linux Sound Architecture)
要不是这个专我大概这辈子都不会去碰这个东西
为啥会扯到 ALSA 呢,还记得 DarkIce 要采集吗,推流的时候需要把播放器播放的重新采集编码,所以这里使用 Loopback 虚拟声卡设备。
曾经试过 Loopback + palseaudio ,但是 palseaudio 不太稳定。关于 palseaudio 可以使用 pacmd list-sink-inputs
和 pacmd list-source-outputs
查看输入和输出的源,实测 mplayer 会随机从 snd_aloop 设备掉到默认声卡设备。最终直接采用 ALSA Loopback sound card 。
需要安装 alsa 工具:
$ sudo apt-get install alsa-utils
使用 aplay -L
查看现有设备:
$ aplay -L
null
Discard all samples (playback) or generate zero samples (capture)
default
Playback/recording through the PulseAudio sound server
lavrate
Rate Converter Plugin Using Libav/FFmpeg Library
samplerate
Rate Converter Plugin Using Samplerate Library
speexrate
Rate Converter Plugin Using Speex Resampler
jack
JACK Audio Connection Kit
oss
Open Sound System
pulse
PulseAudio Sound Server
upmix
Plugin for channel upmix (4,6,8)
vdownmix
Plugin for channel downmix (stereo) with a simple spacialization
hw:CARD=RK809,DEV=0
Analog RK809, fe410000.i2s-rk817-hifi rk817-hifi-0
Direct hardware device without any conversions
plughw:CARD=RK809,DEV=0
Analog RK809, fe410000.i2s-rk817-hifi rk817-hifi-0
Hardware device with all software conversions
sysdefault:CARD=RK809
Analog RK809, fe410000.i2s-rk817-hifi rk817-hifi-0
Default Audio Device
dmix:CARD=RK809,DEV=0
Analog RK809, fe410000.i2s-rk817-hifi rk817-hifi-0
Direct sample mixing device
usbstream:CARD=RK809
Analog RK809
USB Stream Output
如果只显示为 null ,也就是没有声卡设备,那么需要排查自己的用户是否在 audio
用户组。如果 sudo aplay -L
可以看到声卡设备,则你的用户大概率不在 audio
用户组,把自己的用户加入该组后重新登录:
$ usermod -a -G audio <your_username>
$ exit
如果你的用户确实在 audio
用户组,且 sudo aplay -L
也没有声卡设备,那你可以创建虚拟声卡,需要载入相关内核模块:
$ sudo modprobe snd-dummy
$ sudo aplay -L
null
Discard all samples (playback) or generate zero samples (capture)
hw:CARD=Dummy,DEV=0
Dummy, Dummy PCM
Direct hardware device without any conversions
plughw:CARD=Dummy,DEV=0
Dummy, Dummy PCM
Hardware device with all software conversions
default:CARD=Dummy
Dummy, Dummy PCM
Default Audio Device
sysdefault:CARD=Dummy
Dummy, Dummy PCM
Default Audio Device
dmix:CARD=Dummy,DEV=0
Dummy, Dummy PCM
Direct sample mixing device
如上显示则虚拟声卡设备正常,可以将这一行加入 /etc/modules
,使系统启动时自动载入该内核模块:
$ echo snd-dummy | sudo tee -a /etc/modules
检查声卡设备正常后,就可以使用 Loopback 设备,载入相关的内核模块:
$ sudo modprobe snd-aloop
$ aplay -L
# 应当多出来下面的设备
hw:CARD=Loopback,DEV=0
Loopback, Loopback PCM
Direct hardware device without any conversions
hw:CARD=Loopback,DEV=1
Loopback, Loopback PCM
Direct hardware device without any conversions
plughw:CARD=Loopback,DEV=0
Loopback, Loopback PCM
Hardware device with all software conversions
plughw:CARD=Loopback,DEV=1
Loopback, Loopback PCM
Hardware device with all software conversions
sysdefault:CARD=Loopback
Loopback, Loopback PCM
Default Audio Device
front:CARD=Loopback,DEV=0
Loopback, Loopback PCM
Front output / input
surround21:CARD=Loopback,DEV=0
Loopback, Loopback PCM
2.1 Surround output to Front and Subwoofer speakers
surround40:CARD=Loopback,DEV=0
Loopback, Loopback PCM
4.0 Surround output to Front and Rear speakers
surround41:CARD=Loopback,DEV=0
Loopback, Loopback PCM
4.1 Surround output to Front, Rear and Subwoofer speakers
# 这里省略后面的输出
如果可以看到 Loopback 设备则成功,可以将该内核模块加入 /etc/modules
,使系统启动时自动载入该内核模块:
$ echo snd-aloop | sudo tee -a /etc/modules
观察 Loopback 声卡的设备信息可以看到, hw:CARD=Loopback
和 plughw:CARD=Loopback
都有 DEV=0
和 DEV=1
两个设备,实测它们的行为就像管道一样,一端输入一端采集即可。
以 hw:CARD=Loopback
为例, DarkIce 的配置改为 device = hw:CARD=Loopback,DEV=1
, aplay 的播放命令为 aplay -D hw:CARD=Loopback,DEV=0 xxxx.wav
。
这里给出我的定时推流脚本以及 DarkIce 的 [input]
部分配置:
#!/bin/bash
# 开始时间 18:30
START_TIME="1830"
# 结束时间 21:00
# 每一轮播完才会检查结束时间
END_TIME="2100"
start=0
killall darkice
while true; do
time_now=$(date +%H%M)
if [ "${time_now}" -lt "${START_TIME}" ] || [ "${time_now}" -ge "${END_TIME}" ]; then
[ "${start}" != "0" ] && killall darkice && echo 'Stop broadcast now.'
start=0; sleep 5s; continue
fi
if [ "${start}" == "0" ] && [ "${time_now}" -ge "${START_TIME}" ]; then
start=1
darkice -c /etc/darkice.cfg &
echo Start broadcast now
# 开播前放两次 攻撃戦
for i in $(seq 2); do
aplay -D hw:CARD=Loopback,DEV=0 /home/hachi/Music/North\ Korean\ Archives\ -\ 攻撃戦だ.wav
done
sleep 5s
fi
aplay -D hw:CARD=Loopback,DEV=0 /home/hachi/Music/SW/月溯莲台/*.wav
sleep 5s
aplay -D hw:CARD=Loopback,DEV=0 /home/hachi/Music/SW/月临寐乡/*.wav
sleep 5s
done
或者简单粗暴一点:
#!/bin/bash
while 1; do
aplay -D hw:CARD=Loopback,DEV=0 /home/hachi/Music/SW/月溯莲台/*.wav
aplay -D hw:CARD=Loopback,DEV=0 /home/hachi/Music/SW/月临寐乡/*.wav
done
# /etc/darkice.cfg
# 只给出 input 部分作为播放脚本的参考
# hw:CARD=Loopback,1 和 hw:CARD=Loopback,DEV=1 含义一致
[input]
device = default
device = hw:CARD=Loopback,1
sampleRate = 44100
bitsPerSample = 16
channel = 2
by SDUST weilinfox