生成的正弦波听起来嗡嗡声或“方波”而不是正弦

问题描述

我之前问过一个类似的问题,但我让问题变得比它必须的更复杂。我正在生成一个 100 hz 的正弦波,然后我使用 simpleaudio 进行播放。
注意:当我将 wave 编码为 .wav 文件时遇到了这个问题。听起来与简单的音频完全一样。将频道从 2 更改为 1 也会改变声音,但不能解决此问题。

要安装简单的音频:

sudo apt-get install -y python3-dev libasound2-dev
python -m pip install simpleaudio

独立代码

import numpy as np
import simpleaudio as sa
import matplotlib.pyplot as plt

def generate_sine_tone(numsamples,sample_time,frequency):
    t = np.arange(numsamples) * sample_time # Time vector
    signal = 8388605*np.sin(2*np.pi * frequency*t)
    return signal

if __name__ == "__main__":
    duration = 1
    samprate = 44100 # Sampling rate
    numsamples = samprate*duration# Sample count
    st = 1.0 / samprate # Sample time
    t = np.arange(numsamples) * st # Time vecto

    nchannels = 2
    sampwidth = 3

    signal = generate_sine_tone(numsamples,st,100)
    signal2 = np.asarray([ int(x) for x in signal ])

    play_obj = sa.play_buffer(signal2,nchannels,sampwidth,samprate)
    print(signal2)
    plt.figure(0)
    plt.plot(signal2)
    plt.show()

在命令行中运行此命令将生成 1 秒或 44100 个样本的正弦波图,即 100 个正弦波周期。它还会将声音播放到您的扬声器中,因此请在运行前将系统声音调低一点。

enter image description here

我关于这个问题的其他帖子:Trying to generate a sine wave '.wav' file in Python. Comes out as a square wave
https://music.stackexchange.com/questions/110688/generated-sine-wave-in-python-comes-out-buzzy-or-square-ey

预期声音:https://www.youtube.com/watch?v=eDk1bOX-P3w&t=4s
接收到的声音(大约):https://www.youtube.com/watch?v=F7DnVBJ9R34

这个问题让我很恼火,如果能提供任何帮助,我将不胜感激。

解决方法

这里有两个问题。

较小的问题是您正在创建单个阵列并像立体声一样播放它。您需要设置 nchannels = 1(或通过创建一个包含两列的数组来复制所有值)。

另一个问题是尝试创建 24 位样本。很少有人拥有足够好的设备和足够好的耳朵来分辨 24 位和 16 位音频之间的区别。使用 2 的样本宽度使事情变得容易得多。如果您愿意,您可以生成 24 位样本并将它们标准化为 16 位以进行播放:signal *= 32767 / np.max(np.abs(signal))

此代码有效

import numpy as np
import simpleaudio as sa

def generate_sine_tone(numsamples,sample_time,frequency):
    t = np.arange(numsamples) * sample_time # Time vector
    signal = 32767*np.sin(2*np.pi * frequency*t)
    return signal

duration = 1
samprate = 44100 # Sampling rate  
numsamples = samprate*duration# Sample count
st = 1.0 / samprate # Sample time

nchannels = 1
sampwidth = 2

signal = generate_sine_tone(numsamples,st,100)
signal2 = signal.astype(np.int16)
#signal2 = np.asarray([ int(x) for x in signal ])

play_obj = sa.play_buffer(signal2,nchannels,sampwidth,samprate)
play_obj.wait_done()
,

simpleaudio.play_buffer() 函数不会转换您的数据。它只需要确切的内存缓冲区(即它从您提供的对象中获取的缓冲区)并将其解释为您声称它包含的内容。在您的程序中,您对缓冲区包含的内容(2 * 3 字节项目)的描述并不是它实际包含的内容(1 * 8 字节项目)。不幸的是,在您的示例程序中,这不会导致错误,因为您给它的缓冲区大小恰好是 6 的精确倍数,即您声称内存缓冲区项目具有的字节大小。如果再试一次,numsamples = 44101,就会报错,因为 44101 * 8 不能被 6 整除:

ValueError: Buffer size (in bytes) is not a multiple of bytes-per-sample and the number of channels.

试试 print(signal2.itemsize) 显示的内容。这不是您声称在调用 simpleaudio.play_buffer() 时使用的 3 * 2。如果以下仍然正确,即使您尝试过,也无法从 Numpy 获取 24 位缓冲区:NumPy: 3-byte,6-byte types (aka uint24,uint48)

也许这就是本教程告诉您仅对 Numpy 缓冲区使用 16 位数据类型的原因,请参阅 https://github.com/hamiltron/py-simple-audio/blob/master/docs/tutorial.rst

Numpy 数组可用于存储音频,但有一些关键的 要求。如果它们要存储立体声音频,则该阵列必须具有 两列,因为每一列包含一个音频数据通道。他们 还必须有一个带符号的 16 位整数 dtype 和样本幅度 因此,值必须在 -32768 到 32767 的范围内。

这些“缓冲区”是什么?它们是 Python 对象在彼此和以例如编写的库之间传递低级原始字节数据的一种方式。 C. 看这个:https://docs.python.org/3/c-api/buffer.html 或这个:https://jakevdp.github.io/blog/2014/05/05/introduction-to-the-python-buffer-protocol/

如果你想从你的音频数据创建 24 位缓冲区,那么你必须使用其他一些库或低级逐字节黑客来创建内存缓冲区,因为 Numpy 不会这样做你。但是您也许可以使用 dtype=numpy.float32 来获得每个通道具有 4 字节样本的 32 位浮点数。 Simpleaudio 从样本大小中检测到这一点,例如 Alsa:

https://github.com/hamiltron/py-simple-audio/blob/master/c_src/simpleaudio_alsa.c

    /* set that format appropriately */
    if (bytes_per_chan == 1) {
        sample_format = SND_PCM_FORMAT_U8;
    } else if (bytes_per_chan == 2) {
        sample_format = SND_PCM_FORMAT_S16_LE;
    } else if (bytes_per_chan == 3) {
        sample_format = SND_PCM_FORMAT_S24_3LE;
    } else if (bytes_per_chan == 4) {
        sample_format = SND_PCM_FORMAT_FLOAT_LE;
    } else {
        ALSA_EXCEPTION("Unsupported Sample Format.","",err_msg_buf);
        return NULL;
    }

这有点像使用车辆的重量来确定它是汽车、摩托车还是自行车。它有效,但只询问车辆的重量而完全不询问其类型可能会让人感到奇怪。

所以。要修复您的程序,请使用 dtypeasarray() 参数将您的数据转换为您想要的缓冲区格式,并在 play_buffer() 中声明正确的格式。也许从正弦生成中删除比例因子 8388605,将其替换为您真正想要的任何内容,并将其放置在格式规范附近的某个位置。