有关使用PyAudio和PyQtGraph进行实时音频信号处理的问题

时间:2017-10-03 08:46:59

标签: python signal-processing fft pyaudio pyqtgraph

我需要用Python进行一些实时音频信号处理,即通过成帧,加窗和计算FFT来分析频域中的信号,然后根据分析结果应用一些滤波器。我正在使用PyAudio进行音频采集,使用PyQtGraph进行波形和FFT可视化,如thisthis code中所述。

现在我的代码只检测具有最高值的N功率谱区,并通过在FFT图上绘制垂直线来突出显示它们。这是看起来像:

import pyaudio
import numpy as np
from scipy.signal import argrelextrema
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore


##Some settings

FORMAT = pyaudio.paFloat32
CHANNELS = 1
FS = 44100
CHUNK = 256
NFFT = 2048
OVERLAP = 0.5
PLOTSIZE = 32*CHUNK
N = 4

freq_range = np.linspace(10, FS/2, NFFT//2 + 1)
df = FS/NFFT
HOP = NFFT*(1-OVERLAP)


##Some preliminary functions

def db_spectrum(data) : #computes positive frequency power spectrum
    fft_input = data*np.hanning(NFFT)
    spectrum = abs(np.fft.rfft(fft_input))/NFFT
    spectrum[1:-1] *= 2
    return 20*np.log10(spectrum)

def highest_peaks(spectrum) : #finds peaks (local maxima) and picks the N highest ones
    peak_indices = argrelextrema(spectrum, np.greater)[0]
    peak_values = spectrum[peak_indices]
    highest_peak_indices = np.argpartition(peak_values, -N)[-N:]
    return peak_indices[(highest_peak_indices)]

def detection_plot(peaks) : #formats data for vertical line plotting
    x = []
    y = []
    for peak in peaks :
        x.append(peak*df)
        x.append(peak*df)
        y.append(-200)
        y.append(0)
    return x, y


##Main class containing loop and UI

class SpectrumAnalyzer(pg.GraphicsWindow) :

    def __init__(self) :
        super().__init__() 
        self.initUI()
        self.initTimer()
        self.initData()
        self.pa = pyaudio.PyAudio()
        self.stream = self.pa.open(format = FORMAT,
                                   channels = CHANNELS,
                                   rate = FS,
                                   input = True,
                                   output = True,
                                   frames_per_buffer = CHUNK)

    def initUI(self) :
        self.setWindowTitle("Microphone Audio Data")
        audio_plot = self.addPlot(title="Waveform")
        audio_plot.showGrid(True, True)
        audio_plot.addLegend()
        audio_plot.setYRange(-1,1)
        self.time_curve = audio_plot.plot()
        self.nextRow()
        fft_plot = self.addPlot(title="FFT") 
        fft_plot.showGrid(True, True)
        fft_plot.addLegend()
        fft_plot.setLogMode(True, False)
        fft_plot.setYRange(-140,0) #may be adjusted depending on your input
        self.fft_curve = fft_plot.plot(pen='y')
        self.detection = fft_plot.plot(pen='r')

    def initTimer(self) :
        self.timer = QtCore.QTimer()
        self.timer.timeout.connect(self.update)
        self.timer.start(0)

    def initData(self) :
        self.waveform_data = np.zeros(PLOTSIZE)
        self.fft_data = np.zeros(NFFT)
        self.fft_counter = 0

    def closeEvent(self, event) :
        self.timer.stop()
        self.stream.stop_stream()
        self.stream.close()
        self.pa.terminate()

    def update(self) :

        raw_data = self.stream.read(CHUNK)
        self.stream.write(raw_data, CHUNK)
        self.fft_counter += CHUNK

        sample_data = np.fromstring(raw_data, dtype=np.float32)
        self.waveform_data = np.concatenate([self.waveform_data, sample_data])  #update plot data
        self.waveform_data = self.waveform_data[CHUNK:]                         #
        self.time_curve.setData(self.waveform_data)

        self.fft_data = np.concatenate([self.fft_data, sample_data])    #update fft input
        self.fft_data = self.fft_data[CHUNK:]                           #
        if self.fft_counter == HOP :
            spectrum = db_spectrum(self.fft_data)
            peaks = highest_peaks(spectrum)
            x, y = detection_plot(peaks)
            self.detection.setData(x, y, connect = 'pairs')
            self.fft_curve.setData(freq_range, spectrum)
            self.fft_counter = 0

if __name__ == '__main__':
    spec = SpectrumAnalyzer()

代码工作正常但我还有一些问题:

  • 我了解通过以timer.start()作为参数调用0,我们会尽快再次调用update方法。我的脚本如何知道只有在收到下一个音频块时才需要调用update方法?
  • 在上面链接的代码中,为了在关闭PyQtGraph窗口时停止计时器和流,不修改closeEvent方法。过去常见的事情是,即使关闭窗口后,也会调用update方法并录制我的音频。那是正常的行为吗?
  • 我读过在使用PyQt GUI时,我应该首先调用QtGui.QApplication实例并调用exec方法。为什么这样,为什么我的代码工作,即使我没有这样做?
  • 将来我需要实施比仅检测N最高峰要求更高的分析。鉴于我的代码的实际结构,如果我在update方法中添加这样的分析,我理解CPU必须在接收下一个音频块之前计算所有内容,同时它可以等待下一个FFT输入数据做好准备。跳跃大小大于块大小,这将使CPU有更多时间来计算所有内容。我怎样才能做到这一点?多线程?

0 个答案:

没有答案