LWN:PipeWire,新一代的Linux audio/video bus
关注了就能看到更多这么棒的文章哦~
PipeWire: The Linux audio/video bus
March 2, 2021
This article was contributed by Ahmed S. Darwish
DeepL assisted translation
https://lwn.net/Articles/847412/
十多年来,PulseAudio 一直凭借其主流的音频混音(audio mixing)和路由守护进程(routing daemon)以及其音频 API 来服务于 Linux 桌面。遗憾的是,PulseAudio 的内部架构并不适合日益增长的沙盒应用场景(sandboxed-applications use case),尽管人们一直在努力改善。PipeWire 部分来源于这些尝试,作为一个新的 daemon(守护进程),将在即将到来的 Fedora 34 版本中取代 PulseAudio。这个改变值得介绍一下。
过去,Fedora 8 在 2007 年底切换到 PulseAudio 的过程并不顺利。Linux 的长期用户仍然记得,当时人们对这个 daemon 的印象就是会导致系统音频无法工作。在经历了那么一个坎坷的开端之后,PulseAudio 最终成为了 Linux 声音服务器(sound-server)斗争中的赢家。它提供了一个原生的客户端音频(native client audio) API,同时也能支持那些正在使用当时常见的音频 API 的应用程序–包括 raw Linux ALSA sound API,这种 API 通常只允许有一个应用程序访问声卡。PulseAudio 会将多个应用程序的音频流混合起来(mix),作为音频管理的中心节点,提供了细粒度的设置功能,支持无缝路由(seamless routing)到蓝牙、USB 或 HDMI。它对自己在 Linux Desktop 中的定位是相当于 Windows Vista 的用户模式音频引擎(user-mode audio engine)和 macOS CoreAudio daemon。
Cracks at PulseAudio
到了 2015 年,PulseAudio 事实上仍是 Linux 的缺省音频守护进程,但危机也开始产生了。逐渐转向沙盒化的桌面应用,对其原始设计来说是致命的。因为通过 PulseAudio,一个应用程序就可以窥探到其他应用程序的音频、对麦克风进行无中介的(unmediated)访问、或者加载 server modules 来干扰其他应用程序。人们试图对 PulseAudio 进行修复,也就是修改 access-control layer 和 per-client memfd-backed transport。这都是必要改动,但仍然还不足以隔离各个客户端的音频。
大约在那个时候,PulseAudio 的核心开发人员之一 David Henningson 退出了此项目。他对于 PulseAudio 守护进程对沙盒应用场景的不适应、以及它对音频路由的决策的策略以及混合音频的机制感到沮丧。在他发言的末尾,他提出,这些问题是否应该让我们承担创建一个新的、必需的 Linux audio daemon 的阵痛了:
在软件中没有什么是不可能的,但如果要对 PulseAudio 的架构设计进行大改动,从而用正确的方式来支持所有这些要求(而不是 "在上面再加一个 layer"[…])将是非常困难的,所以我的判断是,从头开始写新的东西会更容易。
而且我确实认为可以写出一些从 PulseAudio、JACK 和 AudioFlinger 中吸取了精华的东西,从而得到一个既适用于移动设备又适用于桌面设备的东西;并且适用于专业音频(pro-audio)、游戏、低功率音乐播放(low-power music playback)等。[…]我认为,作为一个开源社区,开发出这样的声音服务器会更有价值。
PulseVideo to Pinos
与此同时,GStreamer 的联合创始人 Wim Taymans 被安排开发一个 Linux service,用来协调网络浏览器对摄像设备的访问权。最初,他把这个项目称为 PulseVideo。这个名字背后的想法很简单:类似于 PulseAudio (为了协调对 ALSA 声音设备的访问),PulseVideo 则是为了协调和复用 Video4Linux2 摄像头设备节点。
不久之后,Taymans 发现了 William Manley 创建的一个名称类似的 PulseVideo 原型程序[视频],并帮助其将其中必须的 GStreamer 功能加了进来。为了避免与 PulseAudio 的名称冲突,并且由于它的功能已经扩展过,不再是针对 camera access 的了,所以 Taymans 后来将项目改名为 Pinos(来自于他在西班牙所居住的城市)
Pinos 是建立在 GStreamer pipeline 之上的,使用了早期对 Manley 的原型程序增加的一些基础设施代码。使用了 D-Bus 加上传递文件描述符来用于进程间通信。在 GStreamer 2015 会议上,Taymans 向与会者描述了 Pinos 架构,并进行了多个应用程序同时访问系统 camera 输入数据的演示。
由于其灵活的、基于 pipeline 的、文件描述符传递的架构,Pinos 还可以支持反向的应用,也就是多媒体广播:应用程序可以通过传递 memfd 或 dma-buf 文件描述符来 "upload" 多媒体流(media stream)。然后,多媒体流可以被进一步处理并分发到其他应用程序和系统多媒体 sink 设备,如 ALSA 声音设备。
虽然只是顺便提了一下,不过这个双向发送的能力,以及跨应用程序发送 stream 的能力使得 Pinos 可以作为一个通用的音频/视频总线(generic audio/video bus),用来在相互隔离的、可能是 sandbox 的用户进程之间有效地传递多媒体内容。因此,只要适当扩展一下的话,Pinos 就可能替代 PulseAudio 了。Taymans 被与会者明确地问到了这个问题[视频,31:35],他回答道:"取代 PulseAudio 不是一件容易的事,它不在目前的考虑之中[…],但[Pinos]可以有广泛用法,所以它以后可以做更多的事情"。
随着前文所讨论的 PulseAudio 的不足之处越来越多,这里说的"以后可以做更多" 就很快变成有需要的了。
PipeWire
到了 2016 年,Taymans 开始重新思考 Pinos 的基础设计,将它的目标扩展为标准的 Linux 音频/视频守护进程。这包括需要用到 "大量小 buffer" 的低延迟音频用例(low-latency audio use case),这些以前通常是由 JACK 支持的。这里就有两个主要问题需要解决了。
首先,核心守护进程(core daemon)和客户端库(client libraries)对 GStreamer 元素和流水线(elements and pipelines)的依赖性变成了一个麻烦。GStreamer 中有大量的底层逻辑来确保实现中的灵活性。处理 GStreamer pipeline 的过程,这都是在 Pinos 的实时线程的上下文中完成的,那么这些底层逻辑就可能会引入一些隐含的内存分配(implicit memory allocation)、线程创建和等待 lock 的行为。众所周知,这些都是会对硬实时代码(hard realtime code)所必须具有的可预测性而产生负面影响的。
为了在满足硬实时要求的同时,也实现 GStreamer pipeline 的部分灵活性,Taymans 创建了一个更简单的多媒体流水线框架,并将其称为 SPA——简单插件 API(Simple Plugin API)[PDF]。该框架的设计中为了确保能在实时线程(如 Pinos media processing threads)中可靠地运行,设置了一个永远不应该超过的时间预算(time budget)。SPA 不加快任何内存分配,相应的,内存分配等就变成了 SPA 框架应用程序(framework application)的唯一责任。
每个 node 都有一组定义明确的状态。其中一个状态就是用来配置这个 node 的所有 port、format 和 buffer 的,这是由 main thread(non-realtime)来完成的;还有一个状态用来供 host 分配这个 node 在完成配置之后所需的所有 buffer;另外还有一个状态,就是实际在实时线程中完成处理工作。在 streaming 的过程中,如果任何一个 media pipeline node 的状态发生了变化(比如发生了某个 event),就可以通知到实时线程(realtime threads),从而将控制权切换回 main thread 来进行重新配置(reconfiguration)。
其次,不再使用 D-Bus 作为 IPC protocol。取而代之的是一个受 Wayland 启发,通过 Unix 域套接字实现的原生完全异步协议(native fully asynchronous protocol)——但是不会使用 XML serialization。Taymans 需要的是一个简单且能保证硬实时的协议。
当集成了 SPA framework、开发了 native IPC protocol 之后,这个项目已经超出了它最初的目的,它从一个用来共享 camera access 的 D-Bus daemon 变成了一个完全实时的音频/视频总线(full realtime-capable audio/video bus)。因此,它被重新命名为 PipeWire,这个名字反映了它作为基于 pipeline 的多媒体共享和处理引擎的新地位。
Lessons learned
从一开始,PipeWire 的开发者就利用了一套来自当前音频守护进程的经验,包括了 JACK、PulseAudio 和 Chromium OS 音频服务器(CRAS)。与 PulseAudio 的有意将 Linux 音频环境分为消费级与专业实时音频不同,PipeWire 从一开始就被设计成可以同时处理两种音频。为了避免 PulseAudio sandbox 的限制,它天生就关注了安全性:每个 PipeWire node(其中包含若干个 SPA nodes)里面都有一个 per-client permission bitfield(每个客户端都有相应的 bit 来配置权限)。这种具有安全意识的设计就可以简单而安全地与 Flatpak portals 集成起来了。沙箱应用权限接口(sandboxed-application permissions interface)现在也升级成为了一个 freedesktop XDG 标准。
同 CRAS 和 PulseAudio 一样(但与 JACK 不同),PipeWire 使用了基于定时器的音频调度(time-based audio scheduling)。一个可以在运行时重新动态配置的定时器被用来安排唤醒(scheduling wake-ups)来填写 audio buffer(音频缓冲区),而不是依赖固定间隔时间的声卡中断。除了省电的好处外,这样一来 audio daemon 就可以提供动态延迟(dynamic latency)了:对于需要省电的场景和消费级音频(如音乐播放),可以设置较高延迟;对于延迟敏感的工作场景(如专业音频,即 professional audio),可以设置低延迟。
与 CRAS 类似、但与 PulseAudio 不同的是,PipeWire 不是基于 audio-buffer rewinding(音频缓冲区重写)机制的。当基于定时器的音频调度在使用非常大的 buffer 时(就像 PulseAudio 一样),就需要能支持重写声卡中的 buffer,以便对意料之外的事件(如新加入的音频流或者音频流的音量发生了变化)能够提供低延迟响应(low-latency response)。这是必须撤销(revoke)已经发送到音频设备的那个大 buffer,并提交一个新的 buffer。这导致代码复杂性显著增加,也会有更多的corner case需要处理。PipeWire 和 CRAS 都将 latency/buffering 的最大值设置得很小,从而完全没有必要进行 buffer rewinding 了。
与 JACK 一样,PipeWire 选择了外部会话管理器(external-session-manager)。专业音频用户通常会在会话管理程序(如 Catia 或 QjackCtl)中构建自己的音频流水线(audio pipeline),然后让音频守护进程来根据最终设置来执行。这样做的好处是将 policy(也就是如何构建媒体流水线)与 mechanism(音频守护进程如何执行流水线)两者分开了。在 GUADEC 2018 上,开发者明确请求 Taymans[视频,23:15]让 GNOME 以及其他可能的外部守护进程来控制 audio stack 中的相关部分。一些系统集成方已经遇到了问题,因为 PulseAudio 的音频路由策略决策是深深藏在其内部模块代码中的。这也是 Henningson 的退出邮件中提到的痛点之一。
最后,顺应过去十年出现的多个有影响力的系统守护进程的趋势,PipeWire 大量使用了 Linux 内核专用的 API。这包括 memfd、eventfd、timerfd、signalfd、epoll 和 dma-buf——所有这些都将 "文件描述符,file-descriptor" 作为系统中 event 和 shared buffer 的主要标识符。PipeWire 可以 import dma-buf 文件描述符,这是其能高效实现 Wayland 抓屏和录屏的关键。对于 4K 和 8K 这些大屏幕,CPU 不需要碰那些非常大的 GPU buffer。GNOME mutter(或其他类似的应用程序) 会传递一个 dma-buf 描述符供 PipeWire 的 SPA pipeline,用来进行进一步的处理和采集。
Adoption
native PipeWire API 从这个项目的 0.3 版本就被定为 stable 了。通过 PipeWire ALSA 插件就可以支持原有的 raw ALSA 应用程序。如果同时安装了 native PipeWire 和 PipeWire JACK library,则通过重新实现的 JACK 客户端函数库和 pw-jack 工具就能支持使用了 JACK 的应用程序。使用了 PulseAudio 的应用程序则通过 pipewire-pulse 守护进程来支持,该守护进程会监听 PulseAudio 自己的 socket 并转为 native communication protocol。这样,那些使用了自带的 native PulseAudio client libraries 的容器化桌面应用程序仍可以得到支持。WebRTC 是所有主流浏览器使用的通信框架(代码),支持了 native PipeWire support for Wayland screen sharing, 并通过 Flatpak portal 进行协调(mediate)。
下图显示了在 Arch Linux 系统上使用 pw-dot 工具 生成的 PipeWire 多媒体流水线,经过了一些美化。图中可以看到组合了 PipeWire-native 和 PulseAudio-native 的应用程序。
左边,GNOME Cheese 和用 gst-launch-1.0 创建的 GStreamer pipeline instance 都在并行访问同一个摄像头录制的数据。中间,Firefox 正在使用 WebRTC 和 Flatpak portal 共享屏幕内容(用于 Jitsi 会议)。右边的 Spotify 音乐播放器(是一个 PulseAudio 应用程序)正在播放音频,这些音频被路由传送到系统默认的 ALSA sink,并且 GNOME Settings(是另一个 PulseAudio 应用程序)正在实时监控该 sink 的左/右声道状态。
在各大 Linux 发行版中,Fedora 从 Fedora 27 版本开始就提供了 PipeWire 守护程序(仅用于 Wayland 抓屏)。Debian 提供了 PipeWire 包,但不支持替代 PulseAudio 或 JACK。Arch Linux 在其仓库中提供了 PipeWire,并且如果用户需要的话,可以使用官方提供的其他包来帮助实现替换 PulseAudio 和 JACK。同样,Gentoo 也为替换这两个守护进程提供了大量的文档。即将发布的 Fedora 34 版本将是第一个默认用 PipeWire 完全替换(开箱即用) PulseAudio 的 Linux 发行版。
总的来说,这是 Linux 多媒体领域的一个关键时刻。虽然开源是一个关于技术的故事,但它同时也是一个关于努力创造技术的人的故事。值得指出的是,PulseAudio 和 JACK 的开发者都有一个共识,那就是 PipeWire 和它的作者在做正确的事情。即将发布的 Fedora 34 应该就是一个试金石,关乎 PipeWire 今后 Linux 各大发行版中能否得到采用。
全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。
欢迎分享、转载及基于现有协议再创作~
长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~
更多推荐
所有评论(0)