从麦克风生成频谱图

时间:2017-04-11 18:02:51

标签: python numpy audio matplotlib scipy

下面我的代码将从麦克风输入,如果音频块的平均值超过某个阈值,它将产生音频块的频谱图(长度为30 ms)。以下是在正常会话过程中生成的频谱图的样子:

enter image description here

从我所看到的情况来看,这看起来并不像我希望光谱图看起来像音频及其环境。我期待更像下面的内容(转换为保留空间):

enter image description here

我正在录制的麦克风是我Macbook上的默认麦克风,有关出错的任何建议吗?

record.py:

import pyaudio
import struct
import math
import numpy as np
from scipy import signal
import matplotlib.pyplot as plt


THRESHOLD = 40 # dB
RATE = 44100
INPUT_BLOCK_TIME = 0.03 # 30 ms
INPUT_FRAMES_PER_BLOCK = int(RATE * INPUT_BLOCK_TIME)

def get_rms(block):
    return np.sqrt(np.mean(np.square(block)))

class AudioHandler(object):
    def __init__(self):
        self.pa = pyaudio.PyAudio()
        self.stream = self.open_mic_stream()
        self.threshold = THRESHOLD
        self.plot_counter = 0

    def stop(self):
        self.stream.close()

    def find_input_device(self):
        device_index = None
        for i in range( self.pa.get_device_count() ):
            devinfo = self.pa.get_device_info_by_index(i)
            print('Device %{}: %{}'.format(i, devinfo['name']))

            for keyword in ['mic','input']:
                if keyword in devinfo['name'].lower():
                    print('Found an input: device {} - {}'.format(i, devinfo['name']))
                    device_index = i
                    return device_index

        if device_index == None:
            print('No preferred input found; using default input device.')

        return device_index

    def open_mic_stream( self ):
        device_index = self.find_input_device()

        stream = self.pa.open(  format = pyaudio.paInt16,
                                channels = 1,
                                rate = RATE,
                                input = True,
                                input_device_index = device_index,
                                frames_per_buffer = INPUT_FRAMES_PER_BLOCK)

        return stream

    def processBlock(self, snd_block):
        f, t, Sxx = signal.spectrogram(snd_block, RATE)
        plt.pcolormesh(t, f, Sxx)
        plt.ylabel('Frequency [Hz]')
        plt.xlabel('Time [sec]')
        plt.savefig('data/spec{}.png'.format(self.plot_counter), bbox_inches='tight')
        self.plot_counter += 1

    def listen(self):
        try:
            raw_block = self.stream.read(INPUT_FRAMES_PER_BLOCK, exception_on_overflow = False)
            count = len(raw_block) / 2
            format = '%dh' % (count)
            snd_block = np.array(struct.unpack(format, raw_block))
        except Exception as e:
            print('Error recording: {}'.format(e))
            return

        amplitude = get_rms(snd_block)
        if amplitude > self.threshold:
            self.processBlock(snd_block)
        else:
            pass

if __name__ == '__main__':
    audio = AudioHandler()
    for i in range(0,100):
        audio.listen()

根据评论进行修改:

如果我们将速率限制为16000 Hz并使用色度图的对数刻度,则这是在麦克风附近点击的输出:

enter image description here

对我来说,这看起来仍然有点奇怪,但似乎也是朝着正确方向迈出的一步。

使用Sox并与我的程序生成的频谱图进行比较:

4 个答案:

答案 0 :(得分:15)

首先,观察您的代码将最多100个谱图(如果多次调用processBlock)相互叠加,您只能看到最后一个。你可能想解决这个问题。此外,我假设您知道为什么要使用30ms录音。就个人而言,我无法想到一个实用的应用程序,其中笔记本电脑麦克风记录的30ms可以提供有趣的见解。它取决于你录制的内容以及你如何触发录制,但这个问题与实际问题相关。

否则代码完美无缺。只需对processBlock函数进行一些小的更改,运用一些背景知识,就可以获得信息和美学光谱图。

因此,让我们谈谈实际的频谱图。我将SoX输出作为参考。 colorbar注释表明它是dBFS 1 ,这是一个对数度量(dB是Decibel的缩写)。因此,让我们首先将频谱图转换为dB:

    f, t, Sxx = signal.spectrogram(snd_block, RATE)   
    dBS = 10 * np.log10(Sxx)  # convert to dB
    plt.pcolormesh(t, f, dBS)

enter image description here

这改善了色标。现在我们看到之前隐藏的较高频段的噪声。接下来,让我们解决时间问题。频谱图将信号分成段(默认长度为256)并计算每个段的频谱。这意味着我们具有出色的频率分辨率但是时间分辨率非常差,因为只有少数这样的段适合信号窗口(大约1300个样本长)。在时间和频率分辨率之间总是需要权衡。这与uncertainty principle有关。因此,让我们通过将信号分成更短的段来交换一些频率分辨率以获得时间分辨率:

f, t, Sxx = signal.spectrogram(snd_block, RATE, nperseg=64)

enter image description here

大!现在我们在两个轴上都获得了相对平衡的分辨率 - 但等等!为什么结果如此像素化?!实际上,这是短30ms时间窗口中的所有信息。只有很多方法可以在两个维度上分布1300个样本。但是,我们可以作弊并使用更高的FFT分辨率和重叠段。这使得结果更平滑,但它没有提供额外的信息:

f, t, Sxx = signal.spectrogram(snd_block, RATE, nperseg=64, nfft=256, noverlap=60)

enter image description here

看到漂亮的光谱干涉图案。 (这些模式取决于所使用的窗口函数,但请不要在此处详细说明。请参阅频谱图函数的window参数来使用这些参数。)结果看起来不错,但实际上不包含比上一张图像更多的信息。

为了使结果更加SoX-lixe观察到SoX谱图在时间轴上相当模糊。您可以通过使用原始的低时间分辨率(长段)获得此效果,但让它们重叠以获得平滑度:

f, t, Sxx = signal.spectrogram(snd_block, RATE, noverlap=250)

enter image description here

我个人更喜欢第三种解决方案,但您需要找到自己喜欢的时间/频率权衡。

最后,让我们使用更像SoX的色彩映射:

plt.pcolormesh(t, f, dBS, cmap='inferno')

enter image description here

以下一行的简短评论:

THRESHOLD = 40 # dB

将阈值与输入信号的RMS进行比较,输入信号的RMS 以dB为单位测量,但原始幅度单位。

1 显然FS是满量程的缩写。 dBFS表示dB度量相对于最大范围。 0 dB是当前表示中可能的最大信号,因此实际值必须<= 0 dB。

答案 1 :(得分:6)

更新让我的答案更清晰,并希望通过@kazemakase赞美优秀的解释,我发现了三件我希望会有所帮助的事情:

  1. 使用LogNorm:

    plt.pcolormesh(t, f, Sxx, cmap='RdBu', norm=LogNorm(vmin=Sxx.min(), vmax=Sxx.max()))
    
  2. 使用numpy的fromstring方法

  3. 结果显示RMS计算不适用于此方法,因为数据受约束长度数据类型且溢出变为负数:即507 * 507 = -5095。

    1. 使用colorbar(),当你看到比例

      时,eveything会变得更容易
      plt.colorbar()
      
    2. 原始答案:

      我在代码中播放10kHz频率得到了不错的结果,只有几处改动:

      • 导入LogNorm

        from matplotlib.colors import LogNorm
        
      • 在网格中使用LogNorm

        plt.pcolormesh(t, f, Sxx, cmap='RdBu', norm=LogNorm(vmin=Sxx.min(), vmax=Sxx.max()))
        

      这给了我: LogNorm Scale pcolormesh

      你可能还需要在savefig之后调用plt.close(),我认为读取的流需要一些工作,因为后面的图像会掉落声音的第一个四分之一。

      我还推荐plt.colorbar(),以便您可以使用

      查看最终结果

      更新:看到有人花时间进行投票

      下面是我的代码,用于光谱图的工作版本。 它捕获五秒钟的音频并将它们写入spec文件和音频文件,以便您进行比较。 Theres stilla很多要改进并且几乎没有优化:我确定它的丢弃块是因为有时间写音频和spec文件。更好的方法是使用非阻塞回调,稍后我可能会这样做

      与原始代码的主要区别在于为numpy获取正确格式数据的更改:

      np.fromstring(raw_block,dtype=np.int16)
      

      而不是

      struct.unpack(format, raw_block)
      

      当我尝试使用以下方法将音频写入文件时,这显然是一个主要问题:

      scipy.io.wavfile.write('data/audio{}.wav'.format(self.plot_counter),RATE,snd_block)
      

      听到很多音乐,鼓很明显:

      some music

      代码:

      import pyaudio
      import struct
      import math
      import numpy as np
      from scipy import signal
      import matplotlib.pyplot as plt
      from matplotlib.colors import LogNorm
      import time
      from scipy.io.wavfile import write
      
      THRESHOLD = 0 # dB
      RATE = 44100
      INPUT_BLOCK_TIME = 1 # 30 ms
      INPUT_FRAMES_PER_BLOCK = int(RATE * INPUT_BLOCK_TIME)
      INPUT_FRAMES_PER_BLOCK_BUFFER = int(RATE * INPUT_BLOCK_TIME)
      
      def get_rms(block):
          return np.sqrt(np.mean(np.square(block)))
      
      class AudioHandler(object):
          def __init__(self):
              self.pa = pyaudio.PyAudio()
              self.stream = self.open_mic_stream()
              self.threshold = THRESHOLD
              self.plot_counter = 0
      
          def stop(self):
              self.stream.close()
      
          def find_input_device(self):
              device_index = None
              for i in range( self.pa.get_device_count() ):
                  devinfo = self.pa.get_device_info_by_index(i)
                  print('Device %{}: %{}'.format(i, devinfo['name']))
      
                  for keyword in ['mic','input']:
                      if keyword in devinfo['name'].lower():
                          print('Found an input: device {} - {}'.format(i, devinfo['name']))
                          device_index = i
                          return device_index
      
              if device_index == None:
                  print('No preferred input found; using default input device.')
      
              return device_index
      
          def open_mic_stream( self ):
              device_index = self.find_input_device()
      
              stream = self.pa.open(  format = self.pa.get_format_from_width(2,False),
                                      channels = 1,
                                      rate = RATE,
                                      input = True,
                                      input_device_index = device_index)
      
              stream.start_stream()
              return stream
      
          def processBlock(self, snd_block):
              f, t, Sxx = signal.spectrogram(snd_block, RATE)
              zmin = Sxx.min()
              zmax = Sxx.max()
              plt.pcolormesh(t, f, Sxx, cmap='RdBu', norm=LogNorm(vmin=zmin, vmax=zmax))
              plt.ylabel('Frequency [Hz]')
              plt.xlabel('Time [sec]')
              plt.axis([t.min(), t.max(), f.min(), f.max()])
              plt.colorbar()
              plt.savefig('data/spec{}.png'.format(self.plot_counter), bbox_inches='tight')
              plt.close()
              write('data/audio{}.wav'.format(self.plot_counter),RATE,snd_block)
              self.plot_counter += 1
      
          def listen(self):
              try:
                  print "start", self.stream.is_active(), self.stream.is_stopped()
                  #raw_block = self.stream.read(INPUT_FRAMES_PER_BLOCK, exception_on_overflow = False)
      
                  total = 0
                  t_snd_block = []
                  while total < INPUT_FRAMES_PER_BLOCK:
                      while self.stream.get_read_available() <= 0:
                        print 'waiting'
                        time.sleep(0.01)
                      while self.stream.get_read_available() > 0 and total < INPUT_FRAMES_PER_BLOCK:
                          raw_block = self.stream.read(self.stream.get_read_available(), exception_on_overflow = False)
                          count = len(raw_block) / 2
                          total = total + count
                          print "done", total,count
                          format = '%dh' % (count)
                          t_snd_block.append(np.fromstring(raw_block,dtype=np.int16))
                  snd_block = np.hstack(t_snd_block)
              except Exception as e:
                  print('Error recording: {}'.format(e))
                  return
      
              self.processBlock(snd_block)
      
      if __name__ == '__main__':
          audio = AudioHandler()
          for i in range(0,5):
              audio.listen()
      

答案 2 :(得分:5)

我认为问题在于您正在尝试进行30ms音频块的频谱图,这个频谱很短,您可以将信号视为静止。
谱图实际上是STFT,您也可以在Scipy documentation

中找到它
  

scipy.signal.spectrogram (x,fs = 1.0,window =('tukey',0.25),nperseg = None,noverlap = None,nfft = None,detrend ='constant', return_onesided = True,scaling ='density',axis = -1,mode ='psd')

     

使用连续的傅里叶变换计算频谱图。

     

频谱图可用作一种可视化非平稳信号频率内容随时间变化的方法。

在第一个图中,您有四个切片,这是您的信号块连续四次fft的结果,有一些窗口和重叠。第二个数字有一个独特的切片,但它取决于您使用的谱图参数 关键是你想用这个信号做什么。算法的目的是什么?

答案 3 :(得分:-2)

我不确定直接使用Python是声音处理的最佳方式,最精确的是使用FFT ... [在我看来使用cython似乎是使用python进行声音处理的义务]

您是否评估了绑定任何外部FFT方法的可能性(例如,使用fftw)并继续使用python将作业分派给外部方法&amp;更新图片结果?

您可能会在python here中找到一些相对优化FFT的信息,也可以查看scipy FFT实现。