CD 从抓轨到搭建流媒体服务器 —— 以《月临寐乡》为例

2022-07-19

v0.0.1

偶然进了 Static World 的群并入坑了 月临寐乡 ,梦开始了。作为幻想乡的萌新,也算是有了自己喜欢的社团。但是更细节的东西,狐狐脑子一下子塞不下那么多东西,只能慢慢探索了惹。CD 从抓轨到搭建流媒体服务器 —— 以《月临寐乡》为例

所以,关于音频格式、元数据、 alsa 、 ffmpeg(待续) 的部分应该有好多错误(拍飞),大佬们多批评。

缘起

之前在 幻想遊園郷Memories of a Town 的时候并没有注意过 tag 的问题,而是用 K3B 抓轨后将纯粹的 wav 算了 md5sum 就扔进曲库了。但是这次显然要做精致一点。

CD 从抓轨到搭建流媒体服务器 —— 以《月临寐乡》为例

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 来解决。、

CD 从抓轨到搭建流媒体服务器 —— 以《月临寐乡》为例

CD 从抓轨到搭建流媒体服务器 —— 以《月临寐乡》为例

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 ,其帮助页面介绍了如何使用该插件。

CD 从抓轨到搭建流媒体服务器 —— 以《月临寐乡》为例

以 Windows10 为例,首先下载 THBWiki.src ,将其放到 %appdata%Mp3tagdatasources 目录下。启动 Mp3tag ,在“Tag Sources”下拉框下就可以找到 THBWiki 的选项。

CD 从抓轨到搭建流媒体服务器 —— 以《月临寐乡》为例

注意此时图中显示了歌曲元数据是因为我之前有添加过。

CD 从抓轨到搭建流媒体服务器 —— 以《月临寐乡》为例

在搜索框中搜索后,将会返回搜索结果,检查后点击 OK 即可。

CD 从抓轨到搭建流媒体服务器 —— 以《月临寐乡》为例

所有信息将被加入并保存,仔细看应该可以看出元数据已经变掉了。

Audacity

Audacity 是 Linux 下一个著名的音频编辑软件,将曲目导入,在导出的时候就可以编辑元数据。或者在 Edit->Metadata 编辑,但是不能插入专辑封面。同样是支持 Linux 和 Windows ,但是不是很推荐这个软件啦。首先它本身不是一个专门编辑元数据的软件,其次它的元数据编辑功能完全可以被 Kid3 代替,甚至生成的 RIFF INFO 在 Windows 的表现还不如 Kid3 。

EasyTAG

EasyTAG 支持的格式也非常广泛,就是不支持 wav (*_*)。

puddletag

puddletag 也是不支持 wav ,别的格式可以考虑用一下啦。

foobar2000

foobar2000 是 windows only,很多人用,但是我试了试感觉巨难用。

流媒体服务器

这里选择的是 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> 

通常在一台服务器上,我们会同时开多个服务,但是 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 restrictionsknown https restrictions 。 还可以参考这篇笔记,有趣的是这篇笔记在 icecast 2.5.0 下成功用 nginx 代理并给出了配置,不知道我没有成功是否和 nginx 的版本也相关。最后我还是摆了,让 Icecast2 直接监听在了 0.0.0.0:2011

从官方下载源码包,这里放上链接: tarballzip 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-inputspacmd 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=Loopbackplughw:CARD=Loopback 都有 DEV=0DEV=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

发表评论

相关文章