Python库用于播放固定频率声音。

46

我家里有蚊虫问题。这通常与程序员社区无关;但是,我看到一些设备声称通过播放17KHz的音调来驱赶这些令人讨厌的生物。我想使用我的笔记本电脑来实现这个功能。

一个方法是创建一个只有一个固定频率音调的MP3文件(可以使用Audacity轻松完成),用Python库打开它并重复播放。

第二种方法是使用计算机内置扬声器播放声音。我正在寻找类似于QBasic Sound的东西:

SOUND 17000, 100

有没有适用于Python的相关库?


16
小心使用 MP3,因为它通过去除人类听觉范围内不太可听的频率来进行压缩,而听力阈值通常在20kHz左右,接近你的17kHz。因此,当将你的固定频率音调转换为 MP3 时,可能会播放不同的一组频率,或者削弱你想要的那个频率。作为人类的你可能察觉不出区别,但蚊子却能... - Jaime
1
非常感谢。如果选择这种解决方案,我将使用一个短(约3秒钟)的WAV文件。 - Adam Matan
14
那么,对蚊子有效吗? - naumcho
如何使用Python播放固定频率声音 - jfs
是的,请告诉我它是否有效:)我曾经测试过其中一种手持设备,将其靠近飞在我手臂附近的蚊子。它没有开始叮咬,但也似乎没有做出任何反应,只是停留在皮肤附近。 - X10D
6个回答

29

PyAudiere是一个简单的跨平台解决方案,用于解决以下问题:

>>> import audiere
>>> d = audiere.open_device()
>>> t = d.create_tone(17000) # 17 KHz
>>> t.play() # non-blocking call
>>> import time
>>> time.sleep(5)
>>> t.stop()

pyaudiere.org网站已经消失。可以通过wayback machine获取该网站和Python 2的二进制安装程序(debian,windows),例如,这里有源代码 pyaudiere-0.2.tar.gz

为了在Linux、Windows、OSX上同时支持Python 2和3,可以使用pyaudio模块代替:

#!/usr/bin/env python
"""Play a fixed frequency sound."""
from __future__ import division
import math

from pyaudio import PyAudio # sudo apt-get install python{,3}-pyaudio

try:
    from itertools import izip
except ImportError: # Python 3
    izip = zip
    xrange = range

def sine_tone(frequency, duration, volume=1, sample_rate=22050):
    n_samples = int(sample_rate * duration)
    restframes = n_samples % sample_rate

    p = PyAudio()
    stream = p.open(format=p.get_format_from_width(1), # 8bit
                    channels=1, # mono
                    rate=sample_rate,
                    output=True)
    s = lambda t: volume * math.sin(2 * math.pi * frequency * t / sample_rate)
    samples = (int(s(t) * 0x7f + 0x80) for t in xrange(n_samples))
    for buf in izip(*[samples]*sample_rate): # write several samples at a time
        stream.write(bytes(bytearray(buf)))

    # fill remainder of frameset with silence
    stream.write(b'\x80' * restframes)

    stream.stop_stream()
    stream.close()
    p.terminate()

例子:

sine_tone(
    # see http://www.phy.mtu.edu/~suits/notefreqs.html
    frequency=440.00, # Hz, waves per second A4
    duration=3.21, # seconds to play sound
    volume=.01, # 0..1 how loud it is
    # see http://en.wikipedia.org/wiki/Bit_rate#Audio
    sample_rate=22050 # number of samples per second
)

这是一个修改后(支持Python 3)的这个AskUbuntu答案


很好。你能谈一下稳定性问题吗?最新版本是0.2。 - Adam Matan
@Udi Pasmon:PyAudiere是相应C++库http://audiere.sourceforge.net/的简单包装器。 - jfs
5
时长短于1秒的样本无法使用花哨的示例编写代码。您可以用以下代码替换原来的代码:samples = (int(s(t) * 0x7f + 0x80) for t in range(n_samples)) stream.write(bytes(bytearray(samples))) 这将允许您一次写入所有样本而不是分批写入。请注意,这两段代码的意思相同,只是后者更简洁易懂。 - Foxichu
我的一位朋友与我分享了这个链接来获取和安装pyaudio:https://people.csail.mit.edu/hubert/pyaudio/ - natterstefan
如果您使用此解决方案,则会将奇怪的错误消息发送到 STDERR。我该如何摆脱它们?请参见 https://stackoverflow.com/questions/50162431/how-to-remove-pyaudio-errors 以获取详细信息! - Regis May

28

Python自带模块winsound,不需要安装任何外部库,它应该可以满足你的需求(但也仅此而已)。

 import winsound
 winsound.Beep(17000, 100)

这非常简单易行,但只适用于Windows。

但是:
针对这个问题的完整答案应该指出,尽管这种方法会产生声音,但它并不能驱赶蚊子。已经有人测试过了:请参见这里这里


顺便说一下,虽然这会产生声音,但我真的怀疑它能否阻止蚊子,事实上,我怀疑它们甚至听不到。问题在于,大多数昆虫不像我们一样使用鼓膜听觉,而是使用感觉毛听觉。但感觉毛只对空气速度敏感,而不是压力,当你离扬声器远时,几乎全部是压力,速度很小。也就是说,除非他们站在您的扬声器旁边,否则他们听不到它。 - tom10
因此,扬声器必须非常强大,可能不是普通的PC扬声器。 幸运的是,我仍然是科学系的学生 - 我将询问昆虫学家并在此处发布答案。 - Adam Matan
人类的耳朵是压力探测器,而蚊子的耳朵则探测声场的粒子速度分量,这仅限于声源在声学近场的直接附近。蚊子的耳朵对声学远场中的压力波动不敏感。http://www.ncbi.nlm.nih.gov/pmc/articles/PMC1636503/ - tom10
我该如何在Django中注册winsound?https://stackoverflow.com/questions/59047909/modulenotfounderror-at-admin-no-module-named-winsound - Tms91

5

我将代码放在这里,因为它有助于程序员更清楚地了解代码的工作原理。解释已经写在代码里:

#!/usr/bin/env python3
import pyaudio
import struct
import math

FORMAT = pyaudio.paInt16
CHANNELS = 2
RATE = 44100

p = pyaudio.PyAudio()


def data_for_freq(frequency: float, time: float = None):
    """get frames for a fixed frequency for a specified time or
    number of frames, if frame_count is specified, the specified
    time is ignored"""
    frame_count = int(RATE * time)

    remainder_frames = frame_count % RATE
    wavedata = []

    for i in range(frame_count):
        a = RATE / frequency  # number of frames per wave
        b = i / a
        # explanation for b
        # considering one wave, what part of the wave should this be
        # if we graph the sine wave in a
        # displacement vs i graph for the particle
        # where 0 is the beginning of the sine wave and
        # 1 the end of the sine wave
        # which part is "i" is denoted by b
        # for clarity you might use
        # though this is redundant since math.sin is a looping function
        # b = b - int(b)

        c = b * (2 * math.pi)
        # explanation for c
        # now we map b to between 0 and 2*math.PI
        # since 0 - 2*PI, 2*PI - 4*PI, ...
        # are the repeating domains of the sin wave (so the decimal values will
        # also be mapped accordingly,
        # and the integral values will be multiplied
        # by 2*PI and since sin(n*2*PI) is zero where n is an integer)
        d = math.sin(c) * 32767
        e = int(d)
        wavedata.append(e)

    for i in range(remainder_frames):
        wavedata.append(0)

    number_of_bytes = str(len(wavedata))  
    wavedata = struct.pack(number_of_bytes + 'h', *wavedata)

    return wavedata


def play(frequency: float, time: float):
    """
    play a frequency for a fixed time!
    """
    frames = data_for_freq(frequency, time)
    stream = p.open(format=FORMAT, channels=CHANNELS, rate=RATE, output=True)
    stream.write(frames)
    stream.stop_stream()
    stream.close()


if __name__ == "__main__":
    play(400, 1)

我用我的FFT分析仪和示波器测试了这段精彩的代码,实际上它输出的频率是所要求频率的两倍。这行代码:c = b * (2 * math.pi),应该改为:c = b * (1.0 * math.pi),以输出正确的频率。我还想补充一点,当使用USB声卡时,谐波小于-95 dBc,因此这段代码生成了非常好的16位质量正弦波。 :-) - Steve Hageman

1

我简化了jfs关于Python3.6+的回答,并进行了一些小改进:

import math
from pyaudio import PyAudio, paUInt8

def generate_sine_wave(frequency, duration, volume=0.2, sample_rate=22050):
    ''' Generate a tone at the given frequency.

        Limited to unsigned 8-bit samples at a given sample_rate.
        The sample rate should be at least double the frequency.
    '''
    if sample_rate < (frequency * 2):
        print('Warning: sample_rate must be at least double the frequency '
              f'to accurately represent it:\n    sample_rate {sample_rate}'
              f' ≯ {frequency*2} (frequency {frequency}*2)')

    num_samples = int(sample_rate * duration)
    rest_frames = num_samples % sample_rate

    pa = PyAudio()
    stream = pa.open(
        format=paUInt8,
        channels=1,  # mono
        rate=sample_rate,
        output=True,
    )

    # make samples
    s = lambda i: volume * math.sin(2 * math.pi * frequency * i / sample_rate)
    samples = (int(s(i) * 0x7F + 0x80) for i in range(num_samples))

    # write several samples at a time
    for buf in zip( *([samples] * sample_rate) ):
        stream.write(bytes(buf))

    # fill remainder of frameset with silence
    stream.write(b'\x80' * rest_frames)

    stream.stop_stream()
    stream.close()
    pa.terminate()

generate_sine_wave(
    # see http://www.phy.mtu.edu/~suits/notefreqs.html
    frequency=523.25,   # Hz, waves per second C6
    duration=1.2,       # seconds to play sound
    volume=0.25,        # 0..1 how loud it is
    sample_rate=22050,  # number of samples per second: 11025, 22050, 44100
)

0
你可以使用SDL(Simple Direct Media Library)的Python绑定

0
这个解决方案使用了pyaudio库,并播放方波声音。
from pyaudio import PyAudio
import time
p = PyAudio() # create PyAudio oobject
stream = p.open(format=p.get_format_from_width(1), # open stream
                    channels=1,
                    rate=41900,
                    output=True)
def square_wave(freq, duration, volume, framerate) :
    total=int(round(framerate*(duration/1000))) # calculate length of audio in samples
    i=0 # played samples counter
    sample_threshold=int(round(framerate*(0.5/freq))) # how much frames to do silence/sound (the frequence delay in samples)
    samples_last=0 # played samples counter (resets each sample_threshold)
    value=int(round(volume*255)) # the value to yield
    while i<=total : # play until the sound ends
        yield value # yield the value
        samples_last+=1 # count played sample
        if samples_last>=sample_threshold : # if played samples reach the threshold...
            samples_last=0 # reset counter
            value=0 if value!=0 else int(round(volume*255)) # switch value to volume or back to 0
        i+=1 # total counter increment
def playtone(freq, duration, volume) :
    framerate=41900
    volume/=100 # recalculate volume from percentage format to 0-1
    data=tuple(square_wave(freq, duration, volume, framerate)) # get the data of square_wave as tuple
    stream.write(bytes(bytearray(data))) # play it (TODO: split in chunks to instant ^C abort)
#playtone(1000, 500, 100) # 1000hz sound for 500 ms (1 second = 1000 ms) with full (100%) volume

@sanitizedUser 这段代码声明了square_wave函数,用于生成声波并播放,以及playtone函数,用于播放该声波。要播放固定频率的声音,按照要求调用playtone函数,传入要播放的频率、播放时长(以毫秒为单位)和音量(以百分比表示)。 - mark0392

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接