pulseaudio 是一个声音服务器。
文档参考:PulseAudio
模块文档:PA Module
名词解释:
- sink 是声音输出的目的地;
- source 是声音的输入来源;
- 可以用
pacmd list-sinks/list-sources
查看本地的 sinks/sources; -
pacmd/pactl
是命令行工具,可以查看信息或者在 PA 启动后在命令行更新配置,具体可用man pacmd
查看用法; -
pacat
是 PA 提供的一些工具,可以录音、播放声音等,可通过man pacat
查看,像parec
等是pacat
的软链接;
parec 是 pacat 的软链接,但为什么
parec
等效于pacat -r
呢?
我怀疑是因为pacat
命令内判断了执行脚本的名称,根据名称自动加了参数。
Pulseaudio 的配置与运行
pa 在启动时,会首先查询 ~/.config/pulse
找到配置文件,如果没找到会从 /etc/pulse
中查询系统级配置。
default.pa
是启动脚本,用于配置模块,在守护进程初始化完成后被解析。自己变更配置应该在 ~/.config/pulse
中创建 default.pa
,并且应该首行执行 .include /etc/pulse/default.pa
导入系统级配置,并在下方使用配置行覆盖原配置,切忌直接变更系统级配置。
如我本地的配置文件 ~/.config/pulse/default.pa
:
.include /etc/pulse/default.pa
load-module module-null-sink sink_name=null_sink
load-module module-null-sink sink_name=null_sink2
load-module module-pipe-source channels=2 file=/tmp/fifo.in source_name=fifo_source
set-default-source fifo_source
set-default-sink null_sink
如上配置,我加载了三个模块:
-
null_sink
是个module-null-sink
,向这里播放声音会直接消失,它有自己的速率控制; -
null_sink2
也是个module-null-sink
,pa 可以加载多次相同的模块,但设置不同的命名; -
fifo_source
是个module-pipe-source
模块,它创建了一个 pipe 文件(可通过参数file=/tmp/fifo.in
指定),作为声音
配置中设置了默认的 source/sink。
注:
- pipe 文件是个管道文件,可以通过 linux 的管道命令操作写入和读出,如上若
cat audio_file > /tmp/fifo.in
就可以向模拟的麦克风中输入声音,也可通过cat /tmp/fifo.in > audio_file
重定向声音到文件,但同时 pipe 文件写入和读出都是有 buffer 的,如果写满了但此时没读的操作就不会再写了,读的时候为空了也就读不到了。
重定向喇叭输出的声音到文件
如配置信息中 set-default-sink null_sink
默认的 sink 是 null_sink
,每启动一个 sink,会有对应的 *.monitor
出现,作为 source。因此可以使用 parec -d null_sink2.monitor audio_file.pcm
命令将声音录制到本地文件中。
播放声音到麦克风做声音输入
由于每一个 sink 会自动有一个 *.monitor 的 source,是用于 parec 录音用的。
使用命令 pacmd set-default-source null_sink2.monitor
将 null_sink2.monitor
指定为默认 source,然后使用 paplay -d null_sink2 ./home.wav
播放声音到 null_sink2
中,此时声音会自动从 null_sink2.monitor
中出来作为输入(到浏览器的麦克风)。
使用中发现命令 pacmd set-default-source null_sink2.monitor
必须要现在浏览器开启后才能设置,并且不能写在配置文件中。
弃用但保留的信息
读流的方法
最初我在配置文件中用 module-pipe-sink
做 sink:
load-module module-pipe-sink channels=2 rate=44100 format=s16le file=/tmp/fifo.out sink_name=fifo_sink
在读声音的时候使用 cat /tmp/fifo.out > audio_file
读到的 audio_file 很大。原因是要读 fifo.out 需要进行速率控制,即每 20ms 读取一次,一次读取 X bytes。
这是我写的一个比较好的读流的脚本:
import os
import time
stream_out_file = '/tmp/fifo.out'
output_file = 'py_t.wav'
if not output_file.endswith('.wav'):
output_file = output_file + '.wav'
# 经计算 16bit 2ch 44.1kHz 下,每秒读取数据 176400B(精确计算)
# 所以,200ms 有数据 35280B,20ms 有 3528B
# 系统 buf 大小 64k,为 65536B,约 18.57ms
stream = os.open(stream_out_file, os.O_RDONLY)
with open(output_file, 'wb') as wav_fd:
start = time.time_ns() # start time
_next = time.time_ns() + 20 * 1000*1000 # next time is after 20ms
while True:
if time.time_ns() > _next: # 每 20ms 读取一次
_next += 20 * 1000*1000 # next time
s = os.read(stream, 3528)
print("read length", len(s))
wav_fd.write(s)
else: # 睡 2ms
time.sleep(0.002)
写流的方法
向 fifo.in 中写流也一样,如下我之前写的一个比较流畅的脚本:
import time
stream_in_file = '/tmp/fifo.in'
def play_file(sound_file):
with open(stream_in_file, 'wb') as w_fd, \
open(sound_file, 'rb') as r_fd:
while True:
data = r_fd.read(441*8) # 数值可能与码率有关
w_fd.write(data)
w_fd.flush()
time.sleep(0.02)