Linux使用ALSA作为音频驱动。作为Agent的音频输出,我计划在WSL(Ubuntu 22.04.5 LTS)上播放音频。

起因

WSL默认没有安装alsa工具包,需要通过如下命令安装:

sudo apt install alsa-utils

安装成功后,尝试执行如下命令打印音频设备:

bluebonnet27@bluebonnet27:~$ aplay -l
aplay: device_list:274: no soundcards found...

怎么会没有音频设备呢?

为WSL2添加音频驱动

WSL2使用的是微软定制的Linux内核,为了保持轻量级,默认不包含许多硬件驱动,包括音频驱动。
网上使用socket共享的方式适用于旧版的WSL,新版的WSLg使用了更简单的方式映射音频设备。

执行wsl --version查询wsl版本:

WSL 版本: 2.6.1.0
内核版本: 6.6.87.2-1
WSLg 版本: 1.0.66
MSRDC 版本: 1.2.6353
Direct3D 版本: 1.611.1-81528511
DXCore 版本: 10.0.26100.1-240331-1435.ge-release
Windows: 10.0.26100.6584

执行以下命令,以配置PulseAudio使用WSLg的音频服务:

# 创建或编辑PulseAudio配置文件
mkdir -p ~/.config/pulse
echo "default-server = unix:/mnt/wslg/PulseServer" > ~/.config/pulse/client.conf

此时/mnt/wslg/下会多出一个链接:

total 56
srwxrwxrwx  1 bluebonnet27 bluebonnet27     0 Sep 27 18:47 PulseAudioRDPSink
srwxrwxrwx  1 bluebonnet27 bluebonnet27     0 Sep 27 18:47 PulseAudioRDPSource
srwxrwxrwx  1 bluebonnet27 bluebonnet27     0 Sep 27 18:47 PulseServer
drwxr-xr-x 19 root         root          4096 Sep 27 18:46 distro
drwxr-xr-x 65 root         root          4096 Sep 13  2024 doc
-rw-------  1 bluebonnet27 bluebonnet27  1371 Sep 27 18:47 pulseaudio.log
drwxr-xr-x  3 root         root            60 Sep 27 18:46 run
drwxrwxrwx  3 bluebonnet27 bluebonnet27   100 Sep 27 18:47 runtime-dir
-r--r--r--  1 root         root           893 Sep 27 18:47 stderr.log
-rw-r--r--  1 root         root           329 Sep 13  2024 versions.txt
-rw-rw-rw-  1 bluebonnet27 bluebonnet27 32091 Sep 27 19:45 weston.log
-rw-rw-rw-  1 bluebonnet27 bluebonnet27  1679 Sep 27 18:47 wlog.log

接下来,默认的ALSA 直接访问硬件设备(通过 /dev/snd/ 设备节点),而 WSLg 中默认不创建这些节点(因为音频是通过 PulseAudio 桥接到 Windows,而非直接暴露硬件),所以会显示 “无设备”。因此,需要添加全局配置:

sudo tee /etc/asound.conf <<EOF
pcm.!default {
    type pulse
}
ctl.!default {
    type pulse
}
pcm.pulse {
    type pulse
}
ctl.pulse {
    type pulse
}
EOF

最后,需要安装一个语音相关的库:

sudo apt install libasound2-plugins

重启WSL,此时使用aplay -L就能打印设备了:

null
    Discard all samples (playback) or generate zero samples (capture)
samplerate
    Rate Converter Plugin Using Samplerate Library
speexrate
    Rate Converter Plugin Using Speex Resampler
jack
    JACK Audio Connection Kit
oss
    Open Sound System
pulse
    PulseAudio Sound Server
upmix
    Plugin for channel upmix (4,6,8)
vdownmix
    Plugin for channel downmix (stereo) with a simple spacialization
default

ubuntu有默认的测试音频,可以播放试试:

aplay /usr/share/sounds/alsa/Front_Center.wav

或者使用alsamixer打开声音面板:
alsamixer
因为是pulseaudio转的音频设备,因此只有默认这一种设备。

使用pyaudio播放

安装pyaudio之前,需要安装如下依赖:

sudo apt install python3-pyaudio portaudio19-dev libpulse-dev

然后安装即可:

pip install pyaudio

如下python代码可以打印当前pyaudio能读取到的音频设备,我们只是确认有即可,由于之前的原因,这里应该只有一个设备。

import pyaudio

p = pyaudio.PyAudio()
for i in range(p.get_device_count()):
    dev = p.get_device_info_by_index(i)
    print(f"设备 {i}: {dev}")
p.terminate()

打印信息如下(略去ALSA的打印):

设备 0: {'index': 0, 'structVersion': 2, 'name': 'pulse', 'hostApi': 0, 'maxInputChannels': 32, 'maxOutputChannels': 32, 'defaultLowInputLatency': 0.008684807256235827, 'defaultLowOutputLatency': 0.008684807256235827, 'defaultHighInputLatency': 0.034807256235827665, 'defaultHighOutputLatency': 0.034807256235827665, 'defaultSampleRate': 44100.0}
设备 1: {'index': 1, 'structVersion': 2, 'name': 'default', 'hostApi': 0, 'maxInputChannels': 32, 'maxOutputChannels': 32, 'defaultLowInputLatency': 0.008684807256235827, 'defaultLowOutputLatency': 0.008684807256235827, 'defaultHighInputLatency': 0.034807256235827665, 'defaultHighOutputLatency': 0.034807256235827665, 'defaultSampleRate': 44100.0}

接下来就可以编写代码,使用pyaudio播放音频了。我这里给一个示例代码:

import pyaudio
import wave
import sys

def list_audio_devices():
    """列出所有可用的音频输出设备"""
    p = pyaudio.PyAudio()
    print("可用音频输出设备:")
    for i in range(p.get_device_count()):
        device_info = p.get_device_info_by_index(i)
        if device_info.get('max_output_channels', 0) > 0:
            default = "(默认设备)" if device_info['default_output'] else ""
            print(f"  设备 {i}: {device_info['name']} {default}")
    p.terminate()

def play_wav(file_path, device_index=None):
    """
    播放WAV文件
    :param file_path: WAV文件路径
    :param device_index: 输出设备索引,None表示使用默认设备
    """
    # 打开WAV文件
    try:
        wf = wave.open(file_path, 'rb')
    except FileNotFoundError:
        print(f"错误:找不到文件 {file_path}")
        return

    # 初始化PyAudio
    p = pyaudio.PyAudio()

    # 打开音频流
    stream = p.open(
        format=p.get_format_from_width(wf.getsampwidth()),
        channels=wf.getnchannels(),
        rate=wf.getframerate(),
        output=True,
        output_device_index=device_index  # 指定输出设备,None则使用默认
    )

    # 读取并播放音频数据
    print(f"开始播放 {file_path}...")
    chunk = 1024
    data = wf.readframes(chunk)
    while data:
        stream.write(data)
        data = wf.readframes(chunk)

    # 清理资源
    print("播放结束")
    stream.stop_stream()
    stream.close()
    wf.close()
    p.terminate()

if __name__ == "__main__":
    # 默认播放的WAV文件路径
    wav_file = "/usr/share/sounds/alsa/Front_Center.wav"
    
    # 列出可用设备
    list_audio_devices()
    
    # 询问用户是否使用默认设备
    use_default = input("\n是否使用默认输出设备?(y/n): ").strip().lower()
    if use_default == 'n':
        try:
            device_idx = int(input("请输入要使用的设备索引:").strip())
            play_wav(wav_file, device_idx)
        except ValueError:
            print("无效的设备索引,使用默认设备播放")
            play_wav(wav_file)
    else:
        play_wav(wav_file)

Logo

更多推荐