我在使用 Python 的 sounddevice 模块时遇到了未知的麻烦。 下面的代码输出一个 GUI,您可以在其中输出用户定义频率的正弦波。此外,还可以使用用户定义的频率和幅度/量的正弦波来调制其幅度。 按下“播放”按钮后,我收到输出溢出并且音频立即开始滞后。 如果我删除代码中的幅度调制选项并且听到平滑的正弦信号,则不会出现此问题。
我知道这段代码可能会使用太多的 CPU 时间来保证音频信号的流畅。
有人在类似的项目中遇到过类似的问题吗?或者有人对声音设备模块足够熟悉来帮助我吗?
代码的很大一部分是从 Github 上的这个示例复制粘贴的。
我绝不是一个经验丰富的程序员。非常感谢任何帮助!
import argparse
import sys
import tkinter as tk
import sounddevice as sd
import numpy as np
# the following function and parsing part is copy-paste from the link above
# and with a few modifications of the remaining code wouldn't be crucial I guess.
# I left it there because I wanted to spare myself the modifications.
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(
'frequency', nargs='?', metavar='FREQUENCY', type=float, default=500,
help='frequency in Hz (default: %(default)s)')
parser.add_argument(
'-d', '--device', type=int_or_str,
help='output device (numeric ID or substring)')
parser.add_argument(
'-a', '--amplitude', type=float, default=0.2,
help='amplitude (default: %(default)s)')
args = parser.parse_args(remaining)
start_idx = 0
def play():
samplerate = sd.query_devices(args.device, 'output')['default_samplerate']
# the callback function is basically copy-paste from the link abouve
# except I added the modulating sine wave
def callback(outdata, frames, time, status):
if status:
print(status, file=sys.stderr)
global start_idx
t = (start_idx + np.arange(frames)) / samplerate
t = t.reshape(-1, 1)
am_amount = float(am_amt_1.get())
a_modulator = np.sin(2*np.pi*float(am_freq_1.get())*t)*am_amount+1-am_amount
carrier = np.sin(2 * np.pi * float(freq_1.get()) * t)
outdata[:] = carrier*a_modulator
start_idx += frames
with sd.OutputStream(device=args.device, channels=1, callback=callback,
samplerate=samplerate):
input() #don't really know what this is doing but there's no audio at all without it
# setting up the GUI
main = tk.Tk()
main.title('Test')
main.geometry('10000x10000')
# entries for frequency in hz and am amount
freq_1 = tk.Entry(main)
freq_1.grid(row = 0, column = 1)
am_freq_1 = tk.Entry(main)
am_freq_1.grid(row=1,column=1)
am_amt_1 = tk.Entry(main)
am_amt_1.grid(row=2,column=1)
# labels
f1 = tk.Label(main,text='Frequency')
f1.grid(row=0,column=0)
amf_1 = tk.Label(main,text='AM Frequency')
amf_1.grid(row=1,column=0)
amamt_1 = tk.Label(main,text='AM Amount')
amamt_1.grid(row=2,column=0)
# play button executing the above play function
pl = tk.Button(main, text='Play',command = play)
pl.grid(row=3,column=1)
main.mainloop()
我使用笔记本电脑的内置输出设备和 M-Audio Fast Track Pro 尝试了该程序。
为了简洁起见,我还对其余部分进行了一些重构。您需要在
input()
中添加
with
的原因是设备会立即关闭;我在这里用 sleep 代替了它,所以声音播放 2 秒然后退出。如果您的声音设备名称中不包含
Speakers
,您需要在
device=...
行中进行更改。
import sys
import time
import tkinter as tk
import numpy as np
import sounddevice as sd
def play(*, device, freq: float, am_freq: float, am_amount: float):
samplerate = sd.query_devices(device, "output")["default_samplerate"]
start_idx = 0
def callback(outdata, frames, time, status):
nonlocal start_idx
if status:
print(status, file=sys.stderr)
t = (start_idx + np.arange(frames)) / samplerate
t = t.reshape(-1, 1)
a_modulator = (
np.sin(2 * np.pi * am_freq * t) * am_amount + 1 - am_amount
)
carrier = np.sin(2 * np.pi * freq * t)
outdata[:] = carrier * a_modulator
start_idx += frames
with sd.OutputStream(
device=device,
channels=1,
callback=callback,
samplerate=samplerate,
):
time.sleep(2) # Play for a while
def make_box(parent, label, value, y):
box = tk.Entry(parent)
box.insert(0, value)
label = tk.Label(parent, text=label)
label.grid(row=y, column=0)
box.grid(row=y, column=1)
return (label, box)
def main():
root = tk.Tk()
root.title("Test")
freq_label, freq_box = make_box(root, "Frequency", "500", 0)
am_freq_label, am_freq_box = make_box(root, "AM Frequency", "25", 1)
am_amt_label, am_amt_box = make_box(root, "AM Amount", "0.5", 2)
pl = tk.Button(
root,
text="Play",
command=(
lambda: play(
device="Speakers",
freq=float(freq_box.get()),
am_freq=float(am_freq_box.get() or 0),
am_amount=float(am_amt_box.get() or 0),
)
),
)
pl.grid(row=3, column=1)
root.mainloop()
if __name__ == "__main__":
main()