解决Linux版网易云音乐无法播放无损格式(Flac)音乐的问题

请注意,本文编写于 191 天前,最后修改于 101 天前,其中某些信息可能已经过时。

前言

之前, 我基本都在PC上听音乐, 日常的需求基本可以满足了. 要听HiFi?自己抓CD或逛逛各分享站, 再用PC或手机OTG接个DAC加声放. 所以网易云音乐会员也没怎么开.

之后,为了舒适我日常的交通行程,购入了 Sony WH-1000XM2 蓝牙降噪耳机, 编码格式支持SBC,AAC,aptX,aptX HD,LDAC, 真是吓人; 而 Sony 也在 Android 8.0 开放了自己 ldac编码器 的代码, 厂商可以很容易的加入支持, 各知名ROM也直接将LDAC支持编译了进去; 我渐渐转向使用Android手机听音乐, 于是也开了网易音乐会员听无损(我知道有些是fake).

总之, 于是就发现了Linux版网易云音乐无法播放无损格式的问题. 在另一方面, 由于LDAC编码器开放了源代码, 并且aptX/aptX HD编码支持也在FFmpeg 4.0后加入其中, 在2018年4月的时候我就在构想能不能把LDAC编码的支持加入到Linux中?

在同年8月, 我终于将LDAC支持加入了pulseaudio的蓝牙模块中, 之后我陆续添加了aptX,aptX HD,AAC支持, 于是我又开始用PC+蓝牙LDAC听音乐了; 但网易云音乐Linux版无法播放无损的问题困扰了我几个月, 我自己也尝试摸索了一下, 但最后只发现问题与vlc播放flac有关.

更新 2018-05-27

最近更新的网易云音乐1.2.1把所有依赖都直接打包进了包里,所以它的/usr/bin里的netease-cloud-music是一个bash脚本,也使用了LD_LI­BRARY_­PATH来加载自带的依赖,但是有个问题,请看netease-cloud-music.bash:

#!/bin/sh
HERE="$(dirname "$(readlink -f "${0}")")"
export LD_LIBRARY_PATH="${HERE}"/libs
export QT_PLUGIN_PATH="${HERE}"/plugins 
export QT_QPA_PLATFORM_PLUGIN_PATH="${HERE}"/plugins/platforms
exec "${HERE}"/netease-cloud-music $@

可以看到这里把 LD_LIBRARY_PATH 覆盖了,这样的话我们声明的LD_LIBRARY_PATH就无效了,于是无法播放flac的问题再次出现.解决方法是改成如下

...
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:"${HERE}"/libs
...

分析

直到前几天我通过Google网易云音乐播放flac时的报错信息[00007f9b30003c50] prefetch stream error: unimplemented query (264) in control发现了下面的文章

https://blog.duama.top/2019/03/03/解决网易云音乐Linux版无法播放无损/

我也通过一番抓包并浏览VLC源码渐渐了解了原因; 原来, Linux版网易云音乐通过调用后端api获取音频文件URL, 然后就直接交给VLC了, VLC优先通过HTTP Header中的Content-Type获取文件类型(mime-type), 再根据mime-type选择demuxer; 问题就出在这里, 当音频为flac时, 上面提到的音频文件URL获取的HTTP Header中的Content-Typeaudio/mpeg, 这是给mp3用的, flac文件正确的mime-type应该是audio/flac; 重现一下网易云音乐播放无损音乐的流程, 网易云音乐获取FLAC音频文件URL->VLC通过Content-Type获取文件类型为audio/mpeg->VLC选择mpeg解码器解码flac文件->VLC解码失败->网易云音乐播放失败.

知道了原因, 我们就可以开始解决它了, 上面提到的文章说是用Privoxy更改Content-Type(HTTP劫持), 然后设置http_proxy环境变量; 但不知道为什么, 我通过这种方式并不能截取到音频文件URL的HTTP流; 此外, 我个人也不希望就为了一个桌面应用在后台始终运行Privoxy; 于是我尝试寻找其他解决方法.

解决方案

在vlc源代码modules目录下运行grep -r "Content-Type", 发现了VLC获取"Content-Type"的函数调用

access/http/resource.c:    const char *type = vlc_http_msg_get_header(res->response, "Content-Type");

进入代码, 发现上面res的结构体有一个path的变量, 那么问题就简单了, 只要判断res->path后缀为flac, 那么就把*type变量值改为"audio/flac"就行了; 我在gist上创建了PATCH和可用的PKGBUILD文件(VLC v3);或点击此获取PATCH;

这里有两个解决方法

  1. 应用PATCH后编译安装, 全局生效(云音乐v1.2.1失效)
  2. 应用PATCH编译后设置 LD_LIBRARY_PATH=<编译后VLC的lib目录>

应用PATCH, 编译

安装flac mpg123 libmpeg2 lua libmad libpulse alsa-libjack 的devel包及其他必要依赖, 下载vlc v3源码1和ncm.patch

$ cd vlc
$ patch -p1 < <到patch文件的路径>
patching file modules/access/http/resource.c
$ ./configure  \
      --prefix=/usr  \
      --disable-rpath \
      --enable-mpg123 \
      --enable-flac \
      --enable-libmpeg2 \
      --disable-avcodec \
      --disable-swscale \
      --disable-a52
...
$ make -j$(nproc)
# 安装到用户目录,不覆盖系统vlc
$ make DESTDIR=$HOME/.local/share/vlc-patching install

如果你不想编译的话,可以下载我的预编译包,适用用于当前Arch Linux, 其他Linux分布版可能会有库依赖缺失的问题

设置LD_LIBRARY_PATH运行网易云音乐

$ env LD_LIBRARY_PATH=$HOME/.local/share/vlc-patching/usr/lib netease-cloud-music

如果用的是我的预编译包, 上面命令中的$HOME/.local/share/vlc-patching/改成解压后的目录路径

也可以把网易云音乐desktop文件的Exec=netease-cloud-music %U如上更改

添加新评论

已有 8 条评论

➜ vlc-patching env LD_LIBRARY_PATH=$HOME/.local/share/vlc-patching/lib netease-cloud-music
[0705/110646.699992:ERROR:sandbox_linux.cc(344)] InitializeSandbox() called with multiple threads in process gpu-process.
Received signal 4 ILL_ILLOPN 7f23db1d31b0

0 0x7f23d2b69526 1 0x7f23d2b69909 2 0x7f23d8c247e0 3 0x7f23db1d31b0 libvlc_new4 0x56054798070d 5 0x56054794b1db 6 0x5605478ed442 7 0x5605478ebf14 8 0x7f23d8c10ee3 __libc_start_main9 0x5605478ecefa

r8: 000056054a9ad9d0 r9: 0000000000000000 r10: 000056054a9ad1d0 r11: 00007f23d8da6b00
r12: 000056054a9ad9d0 r13: 0000000000000000 r14: 000056054a936e10 r15: 00007fff6d429b20
di: 00007f23db1e7000 si: 0000000000000000 bp: 00007fff6d4298c0 bx: 0000000000000000
dx: 0000000000000010 ax: 0000000000000002 cx: 0000000000000003 sp: 00007fff6d429880
ip: 00007f23db1d31b0 efl: 0000000000010202 cgf: 002b000000000033 erf: 0000000000000000
trp: 0000000000000006 msk: 0000000000000000 cr2: 0000000000000000
[end of stack trace]
Calling _exit(1). Core file will not be generated.

求解

IceDream IceDream 回复 @IceDream
likes dislikes

我的是 manjaro

1.你是不是用的Wayland? 我只在wayland上遇到过不能执行的问题
如果是的话用Xorg

2.如果不是的话你是自己编译安装的吗? 还是用我的预编译包, 如果是后者的话推荐你手动编译

make[4]: 离开目录“/home/icedream/Downloads/vlc/src”
make[3]: 离开目录“/home/icedream/Downloads/vlc/src”
make[2]: 离开目录“/home/icedream/Downloads/vlc/src”
Making install in modules
make[2]: 进入目录“/home/icedream/Downloads/vlc/modules”
make install-recursive
make[3]: 进入目录“/home/icedream/Downloads/vlc/modules”
Making install in .
make[4]: 进入目录“/home/icedream/Downloads/vlc/modules”
CC access/libsftp_plugin_la-sftp.lo
access/sftp.c: 在函数‘Open’中:
access/sftp.c:310:14: 错误:‘LIBSSH2_HOSTKEY_TYPE_ECDSA_256’ undeclared (first use in this function); did you mean ‘LIBSSH2_HOSTKEY_TYPE_DSS’?
310 | case LIBSSH2_HOSTKEY_TYPE_ECDSA_256:

| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | LIBSSH2_HOSTKEY_TYPE_DSS

access/sftp.c:310:14: 附注:每个未声明的标识符在其出现的函数内只报告一次
access/sftp.c:311:42: 错误:‘LIBSSH2_KNOWNHOST_KEY_ECDSA_256’ undeclared (first use in this function); did you mean ‘LIBSSH2_KNOWNHOST_KEY_RSA1’?
311 | knownhost_fingerprint_algo = LIBSSH2_KNOWNHOST_KEY_ECDSA_256;

| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | LIBSSH2_KNOWNHOST_KEY_RSA1

access/sftp.c:314:14: 错误:‘LIBSSH2_HOSTKEY_TYPE_ECDSA_384’ undeclared (first use in this function); did you mean ‘LIBSSH2_HOSTKEY_TYPE_DSS’?
314 | case LIBSSH2_HOSTKEY_TYPE_ECDSA_384:

| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | LIBSSH2_HOSTKEY_TYPE_DSS

access/sftp.c:315:42: 错误:‘LIBSSH2_KNOWNHOST_KEY_ECDSA_384’ undeclared (first use in this function); did you mean ‘LIBSSH2_KNOWNHOST_KEY_RSA1’?
315 | knownhost_fingerprint_algo = LIBSSH2_KNOWNHOST_KEY_ECDSA_384;

| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | LIBSSH2_KNOWNHOST_KEY_RSA1

access/sftp.c:318:14: 错误:‘LIBSSH2_HOSTKEY_TYPE_ECDSA_521’ undeclared (first use in this function); did you mean ‘LIBSSH2_HOSTKEY_TYPE_DSS’?
318 | case LIBSSH2_HOSTKEY_TYPE_ECDSA_521:

| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | LIBSSH2_HOSTKEY_TYPE_DSS

access/sftp.c:319:42: 错误:‘LIBSSH2_KNOWNHOST_KEY_ECDSA_521’ undeclared (first use in this function); did you mean ‘LIBSSH2_KNOWNHOST_KEY_RSA1’?
319 | knownhost_fingerprint_algo = LIBSSH2_KNOWNHOST_KEY_ECDSA_521;

| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | LIBSSH2_KNOWNHOST_KEY_RSA1

make[4]: * [Makefile:22924:access/libsftp_plugin_la-sftp.lo] 错误 1
make[4]: 离开目录“/home/icedream/Downloads/vlc/modules”
make[3]: * [Makefile:27483:install-recursive] 错误 1
make[3]: 离开目录“/home/icedream/Downloads/vlc/modules”
make[2]: * [Makefile:27899:install] 错误 2
make[2]: 离开目录“/home/icedream/Downloads/vlc/modules”
make[1]: * [Makefile:1539:install-recursive] 错误 1
make[1]: 离开目录“/home/icedream/Downloads/vlc”
make: * [Makefile:2008:install] 错误 2

你好,我自行编译报错了,我不懂c 麻烦您帮忙看看怎么解决

你是评论了两次? 还是网站bug?

把 https://git.archlinux.org/svntogit/packages.git/tree/trunk/PKGBUILD?h=packages/vlc#n23 列出来的依赖和base-devel组里的包装了再编译

bug吧,我只评论了一次

预编译包的链接有新的吗

更新到了vlc 3.0.7.1 https://cloud.eh5.me/index.php/s/WR3pbdnRia63DmC