前言

为了舒适我日常的交通行程,购入了 Sony WH-1000XM2 蓝牙降噪耳机, 编码格式支持SBC,AAC,aptX,aptX HD,LDAC, 真是吓人; 而 Sony 也在 Android 8.0 开放了自己 ldac编码器 的代码, 厂商可以很容易的加入支持, 各知名ROM也直接将LDAC支持编译了进去.

https://android.googlesource.com/platform/external/libldac

EHfive/ldacBT
A dispatcher for AOSP libldac . Contribute to EHfive/ldacBT development by creating an account on GitHub.
AOSP libldac 的支持pkg-config的分发器

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

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

EHfive/pulseaudio-modules-bt
Adds Sony LDAC, aptX, aptX HD, AAC codecs (A2DP Audio) support to PulseAudio on Linux - EHfive/pulseaudio-modules-bt

更新 2019-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文件

展开查看(Gist Embed)

这里有两个解决方法

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

应用PATCH, 编译

安装flac mpg123 libmpeg2 lua libmad libpulse alsa-libjack 的devel包及其他必要依赖, 下载vlc v3源码和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如上更改

[Desktop Entry]
Categories=AudioVideo;Player;
Comment=网易云音乐
Icon=netease-cloud-music
Exec=env LD_LIBRARY_PATH=$HOME/.local/share/vlc-patching/usr/lib XDG_CURRENT_DESKTOP=DDE netease-cloud-music %U

然后。。。,就没有然后了,直接点击网易云音乐进入即可。