PulseAudio 是一个 POSIX 操作系统的音频服务器系统,它是我们的音频应用程序访问系统音频设备的代理。它是所有相关的现代 Linux 发行版的组成部分,并被多个供应商用在了各种各样的移动设备中。它在应用程序和硬件设备间传递音频数据时,可以对音频数据执行一些高级操作。比如,把音频数据传给不同的机器,修改样本格式或通道数,或者混音多路音频到一路输入/输出,这些用 PulseAudio 实现都很简单。PulseAudio 主页。
很多学习了编程语言,操作系统,计算机网络和标准库及操作系统 API 的同学都会有一些疑问:自己好像已经学习了不少知识了,但这些知识能用来做些什么,基于这些知识怎么才能实现一个有一定价值的应用,应用程序究竟是如何访问硬件设备的,如音频设备、视频设备等。
通过了解 PulseAudio 系统的设计和实现,可以让我们看到许许多多 POSIX 系统,特别是 Linux 系统平台下,开发一个实用的软件,所需要了解的许许多多跟系统相关的点,如基于 udev 的设备发现和设备状态变化监听,各种各样的进程间通信方式,如基于 IPv4 和 IPv6 的 TCP,Unix 域 socket,D-Bus 等,访问音频设备的 alsa 架构等。
这里简单看一下 PulseAudio 的设计和实现。
PulseAudio 的代码下载及编译
编译出调试版代码,并在调试器中把代码运行起来,可以为我们研究一个软件项目提供极大的便利。我们在 Ubuntu 20.04 上编译 PulseAudio 并把它跑起来。
我们可以通过 Git 下载代码,当前 PulseAudio 最新发布的版本为 14.2,我们切换到这个分支:
$ git clone https://github.com/pulseaudio/pulseaudio.git
$ git checkout -t origin/stable-14.x
PulseAudio 是一个通过 meson + ninja 来构建的工程,更多关于 meson 构建系统的信息,可以参考 meson 主页。在开始构建 PulseAudio 前,需要先下载 meson:
$ sudo apt install meson
还需要下载 PulseAudio 本身及测试用例编译的依赖库:
$ sudo apt-get install libltdl-dev libsndfile1-dev cmake libsbc-dev check libbluetooth-dev
编译 PulseAudio:
$ cd pulseaudio
$ mkdir build
$ cd build/
$ meson ..
$ ninja -C .
. . . . . . . . .
Database: tdb
Legacy Database Entry Support: true
module-stream-restore:
Clear old devices: false
Running from build tree: true
System User: pulse
System Group: pulse
Access Group: pulse-access
meson.build:899: WARNING:
You do not have speex support enabled. It is strongly recommended
that you enable speex support if your platform supports it as it is
the primary method used for audio resampling and is thus a critical
part of PulseAudio on that platform.
Build targets in project: 167
Found ninja-1.8.2 at ~/bin/depot_tools/ninja
PulseAudio 默认已经运行在 Ubuntu 20.04 中了。为了能够运行我们编译出来的 PulseAudio 服务,需要先暂停系统中已经存在的 pulseaudio 服务:
systemctl --user stop pulseaudio.socket
systemctl --user stop pulseaudio.service
注意,执行这些命令不需要超级用户权限 sudo。
调试完成之后,还可以通过如下命令重新启动系统的 pulseaudio 服务:
systemctl --user start pulseaudio.socket
systemctl --user start pulseaudio.service
通过如下命令,可以将 pulseaudio 服务运行起来:
~/data/opensource/pulseaudio$ build/src/daemon/pulseaudio -n -F build/src/daemon/default.pa -p $(pwd)/build/src/modules/
W: [pulseaudio] caps.c: Normally all extra capabilities would be dropped now, but that's impossible because PulseAudio was built without capabilities support.
N: [pulseaudio] daemon-conf.c: Detected that we are run from the build tree, fixing search path.
N: [pulseaudio] alsa-util.c: Disabling timer-based scheduling because running inside a VM.
E: [alsa-sink-ES1371/1] alsa-sink.c: ALSA 提醒我们在该设备中写入新数据,但实际上没有什么可以写入的!
E: [alsa-sink-ES1371/1] alsa-sink.c: 这很可能是 ALSA 驱动程序 'snd_ens1371' 中的一个 bug。请向 ALSA 开发人员报告这个问题。
E: [alsa-sink-ES1371/1] alsa-sink.c: 我们因 POLLOUT 被设置而唤醒 -- 但结果是 snd_pcm_avail() 返回 0 或者另一个小于最小可用值的数值。
N: [pulseaudio] alsa-util.c: Disabling timer-based scheduling because running inside a VM.
E: [pulseaudio] stdin-util.c: Unable to read or parse data from client.
E: [pulseaudio] module.c: Failed to load module "module-gsettings" (argument: ""): initialization failed.
PulseAudio 的设计和实现
pulse audio 工程有许多测试用例,可以用于帮助我们了解这个工程设计与实现。其中许多测试用例是单元测试,主要用于测试 pulse audio 基础组件的 API,但也有一些测试用例会跑完整的录制和播放流程,可以让我们听到通过 pulse audio 播放出来的音频数据,这些测试用例有 lo-latency-test (pulseaudio/src/tests/lo-latency-test.c----pulseaudio/build/src/tests/lo-latency-test) 和 sync-playback (pulseaudio/src/tests/sync-playback.c----pulseaudio/build/src/tests/sync-playback) 等。
这里我们通过观察 sync-playback 和 pulseaudio 服务的交互来了解 PulseAudio 的实现。
从进程角度来看,pulse audio 是客户端/服务器架构的。pulse audio 提供一个常驻系统的服务进程,它接收音频相关的操作请求命令,管理音频设备,实现与音频设备间的数据交互等。各种接入 pulse audio 库,通过 pulse audio API 访问音频设备的应用,是 pulse audio 系统服务的客户端,它们通过 pulse audio 系统服务向音频播放设备写入数据将声音播放出来,通过 pulse audio 系统服务读取录制设备以获得采集的音频数据。
pulse audio 系统服务和它的客户端可以通过多种方式实现进程间通信。对于命令的传递,可以通过 UNIX 域 socket,IPv4 socket,IP v6 socket,Linux 系统的 D-Bus 等,对于大块的音频 PCM 数据,则通过共享内存来传递。
这里看一下 pulse audio 数据传输的基本过程。
- 发生在 pulse audio 系统服务进程中的 socket 监听
#0 pa_socket_server_new_unix ()
at ../src/pulsecore/socket-server.c:175
#1 module_native_protocol_unix_LTX_pa__init () at ../src/modules/module-protocol-stub.c:330
#2 pa_module_load ()
at ../src/pulsecore/module.c:191
#3 pa_cli_command_load ()
at ../src/pulsecore/cli-command.c:437
#4 pa_cli_command_execute_line_stateful () at ../src/pulsecore/cli-command.c:2141
#5 pa_cli_command_execute_file_stream ()
at ../src/pulsecore/cli-command.c:2181
#6 pa_cli_command_execute_file ()
at ../src/pulsecore/cli-command.c:2212
#7 pa_cli_command_execute_line_stateful () at ../src/pulsecore/cli-command.c:2106
#8 pa_cli_command_execute ()
at ../src/pulsecore/cli-command.c:2238
#9 main () at ../src/daemon/main.c:1112
pulseaudio 系统服务在进程启动时,会执行传入的配置文件 build/src/daemon/default.pa 中的命令。如配置文件中的 load-module module-native-protocol-unix 行会使得 pulseaudio 系统服务去加载 module-native-protocol-unix 模块,在这个模块中会起动监听路径为 /run/user/1000/pulse/native 的 Unix 域 socket,详细的执行路径如上面的堆栈信息,并设置连接建立等事件发生时的回调 socket_server_on_connection_cb() 等。Unix 域 socket 由模块 module-native-protocol-unix 管理。
- 客户端建立连接
#0 pa_socket_client_new_unix ()
at ../src/pulsecore/socket-client.c:227
#1 pa_socket_client_new_string () at ../src/pulsecore/socket-client.c:459
#2 try_next_connection () at ../src/pulse/context.c:884
#3 pa_context_connect () at ../src/pulse/context.c:1046
#4 sync_playback_test () at ../src/tests/sync-playback.c:165
#5 main () at ../src/tests/sync-playback.c:184
客户端应用程序,在调用 pa_context_connect() 接口时,会去连接 pulseaudio 系统服务监听的 Unix 域 socket。
- pulseaudio 系统服务将命令处理程序和 IO 通道关联
pulseaudio 系统服务在收到客户端的连接时,socket-server 在其回调中,根据新连接的文件描述符创建 pa_iochannel,并执行回调 socket_server_on_connection_cb(),这个回调通过 pa_native_protocol_connect() 函数为连接创建所需的资源,如 pa_client,pa_native_connection 和流 pa_pstream 等,为流 pa_pstream 设置各种事件的回调,并将命令处理程序关联到流上:
#0 pa_native_protocol_connect () at ../src/pulsecore/protocol-native.c:5194
#1 socket_server_on_connection_cb ()
at ../src/modules/module-protocol-stub.c:202
#2 callback ()
at ../src/pulsecore/socket-server.c:143
#3 dispatch_pollfds () at ../src/pulse/mainloop.c:655
#4 pa_mainloop_dispatch () at ../src/pulse/mainloop.c:896
#5 pa_mainloop_iterate () at ../src/pulse/mainloop.c:927
#6 pa_mainloop_run () at ../src/pulse/mainloop.c:942
#7 main () at ../src/daemon/main.c:1170
pa_native_protocol_connect() 函数的部分代码如下:
c->pstream = pa_pstream_new(p->core->mainloop, io, p->core->mempool);
pa_pstream_set_receive_packet_callback(c->pstream, pstream_packet_callback, c);
pa_pstream_set_receive_memblock_callback(c->pstream, pstream_memblock_callback, c);
pa_pstream_set_die_callback(c->pstream, pstream_die_callback, c);
pa_pstream_set_drain_callback(c->pstream, pstream_drain_callback, c);
pa_pstream_set_revoke_callback(c->pstream, pstream_revoke_callback, c);
pa_pstream_set_release_callback(c->pstream, pstream_release_callback, c);
c->pdispatch = pa_pdispatch_new(p->core->mainloop, true, command_table, PA_COMMAND_MAX);
c->record_streams = pa_idxset_new(NULL, NULL);
c->output_streams = pa_idxset_new(NULL, NULL);
- 客户端发送命令请求 pulseaudio 系统服务创建资源及 pulseaudio 系统服务处理命令

本文介绍了PulseAudio的设计和实现细节,包括其客户端/服务器架构、进程间通信机制、音频数据传输流程以及与ALSA的交互过程。

2680

被折叠的 条评论
为什么被折叠?



