回到首页

找到可用的扬声器

用python库播放音频存在听不到声音的问题,使用如下方法找到正确的发声设备id

#!/usr/bin/env python3
"""Load an audio file into memory and play its contents.

NumPy and the soundfile module (https://python-soundfile.readthedocs.io/)
must be installed for this to work.

This example program loads the whole file into memory before starting
playback.
To play very long files, you should use play_long_file.py instead.

This example could simply be implemented like this::

    import sounddevice as sd
    import soundfile as sf

    data, fs = sf.read('my-file.wav')
    sd.play(data, fs)
    sd.wait()

... but in this example we show a more low-level implementation
using a callback stream.

"""
import argparse
import threading

import sounddevice as sd
import soundfile as sf


def int_or_str(text):
    """Helper function for argument parsing."""
    try:
        return int(text)
    except ValueError:
        return text


parser = argparse.ArgumentParser(add_help=False)
parser.add_argument(
    '-l', '--list-devices', action='store_true',
    help='show list of audio devices and exit')
args, remaining = parser.parse_known_args()
if args.list_devices:
    print(sd.query_devices())
    parser.exit(0)
parser = argparse.ArgumentParser(
    description=__doc__,
    formatter_class=argparse.RawDescriptionHelpFormatter,
    parents=[parser])
parser.add_argument(
    'filename', metavar='FILENAME',
    help='audio file to be played back')
parser.add_argument(
    '-d', '--device', type=int_or_str,
    help='output device (numeric ID or substring)')
args = parser.parse_args(remaining)

event = threading.Event()

print(sd.default.device)

try:
    data, fs = sf.read(args.filename, always_2d=True)

    current_frame = 0

    def callback(outdata, frames, time, status):
        global current_frame
        if status:
            print(status)
        chunksize = min(len(data) - current_frame, frames)
        outdata[:chunksize] = data[current_frame:current_frame + chunksize]
        if chunksize < frames:
            outdata[chunksize:] = 0
            raise sd.CallbackStop()
        current_frame += chunksize
    print(data.shape[1])
    stream = sd.OutputStream(
        samplerate=fs, device=args.device, channels=data.shape[1],
        callback=callback, finished_callback=event.set)
    with stream:
        event.wait()  # Wait until playback is finished
except KeyboardInterrupt:
    parser.exit('\nInterrupted by user')
except Exception as e:
    parser.exit(type(e).__name__ + ': ' + str(e))
python test.py A2_0.wav -l,获取声卡设备列表,以输出的第一个设备信息0 HDA Intel PCH: ALC887-VD Analog (hw:0,0), ALSA (2 in, 2 out)为例,有两个输入通道和两个输出通道,假如A2_0.wav是单通道文件,而输出设备的输出通道数是0,则会出现报错:PortAudioError: Error opening OutputStream: Invalid number of channels [PaErrorCode -9998]。sounddevice.default.device表示sounddevice默认选择的输出设备编号。接下来python test.py A2_0.wav -d $id脚本批量化执行该命令,找出能正常播放音频的设备id。结果是23和31(-l参数返回的结果的id)。以上代码来自Example Programs§

pyaudio库

"""PyAudio Example: Play a wave file."""

import pyaudio
import wave
import sys

CHUNK = 1024

# if len(sys.argv) < 2:
#     print("Plays a wave file.\n\nUsage: %s filename.wav" % sys.argv[0])
#     sys.exit(-1)

wf = wave.open(sys.argv[1], 'rb')

# instantiate PyAudio (1)
p = pyaudio.PyAudio()

# open stream (2)
stream = p.open(format=p.get_format_from_width(wf.getsampwidth()),
                channels=wf.getnchannels(),
                rate=wf.getframerate(),
                output_device_index=int(sys.argv[2]),
                output=True)

# read data
data = wf.readframes(CHUNK)

# play stream (3)
while len(data):
    stream.write(data)
    data = wf.readframes(CHUNK)

# stop stream (4)
stream.stop_stream()
stream.close()

# close PyAudio (5)
p.terminate()
python test.py A2_0.wav $id,批量查找能发出声音的设备id,设备id的获取方式[2]
import pyaudio

p=pyaudio.PyAudio()
for i in range(p.get_device_count()):
    device_info=p.get_device_info_by_index(i)
    print(device_info)
,结果是10,而且播放这个音频之前不能用设备10听b站音乐,听了会报OSError: [Errno -9998] Invalid number of channels的错误,需要用其他设备听一下(也不会有声音,但没报错),之后就能播放这个音频,我这台主机硬件设备有点儿特殊,造成pyaudio的这种bug.

参考链接:Python常用音频库
[2].pyaduio使用指定设备录制或播放音频

本文创建于2022.9.13/10.22,修改于2022.9.13/16.9