用Python实现实时信号处理 - 如何连续捕获音频?

时间:2016-01-05 19:30:57

标签: python multithreading audio signal-processing alsa

我计划实施类似DSP的" Python中的信号处理器。它应该通过ALSA捕获音频的小片段,处理它们,然后通过ALSA播放它们。

为了开始,我编写了以下(非常简单的)代码。

import alsaaudio

inp = alsaaudio.PCM(alsaaudio.PCM_CAPTURE, alsaaudio.PCM_NORMAL)
inp.setchannels(1)
inp.setrate(96000)
inp.setformat(alsaaudio.PCM_FORMAT_U32_LE)
inp.setperiodsize(1920)

outp = alsaaudio.PCM(alsaaudio.PCM_PLAYBACK, alsaaudio.PCM_NORMAL)
outp.setchannels(1)
outp.setrate(96000)
outp.setformat(alsaaudio.PCM_FORMAT_U32_LE)
outp.setperiodsize(1920)

while True:
    l, data = inp.read()
    # TODO: Perform some processing.
    outp.write(data)

问题是,音频"口吃"而且不是无间隙的。我尝试使用PCM模式,将其设置为PCM_ASYNC或PCM_NONBLOCK,但问题仍然存在。我认为问题在于样品"之间"随后两次调用" inp.read()"迷路了。

有没有办法捕捉音频"持续"在Python中(最好不需要太多"特定" /"非标准"库)?我希望始终能够抓住信号并在背景中#34;进入某个缓冲区,我可以从中读取一些瞬时状态",即使在我执行读取操作的时间内,音频也会被进一步捕获到缓冲区中。我怎样才能做到这一点?

即使我使用专用的进程/线程来捕获音频,这个进程/线程总是至少必须(1)从源读取音频,(2)然后将它放入某个缓冲区(从中& #34;信号处理"进程/线程然后读取)。因此,这两个操作仍将按时间顺序进行,因此样本将丢失。我该如何避免这种情况?

非常感谢您的建议!

编辑2:现在我正在运行。

import alsaaudio
from multiprocessing import Process, Queue
import numpy as np
import struct

"""
A class implementing buffered audio I/O.
"""
class Audio:

    """
    Initialize the audio buffer.
    """
    def __init__(self):
        #self.__rate = 96000
        self.__rate = 8000
        self.__stride = 4
        self.__pre_post = 4
        self.__read_queue = Queue()
        self.__write_queue = Queue()

    """
    Reads audio from an ALSA audio device into the read queue.
    Supposed to run in its own process.
    """
    def __read(self):
        inp = alsaaudio.PCM(alsaaudio.PCM_CAPTURE, alsaaudio.PCM_NORMAL)
        inp.setchannels(1)
        inp.setrate(self.__rate)
        inp.setformat(alsaaudio.PCM_FORMAT_U32_BE)
        inp.setperiodsize(self.__rate / 50)

        while True:
            _, data = inp.read()
            self.__read_queue.put(data)

    """
    Writes audio to an ALSA audio device from the write queue.
    Supposed to run in its own process.
    """
    def __write(self):
        outp = alsaaudio.PCM(alsaaudio.PCM_PLAYBACK, alsaaudio.PCM_NORMAL)
        outp.setchannels(1)
        outp.setrate(self.__rate)
        outp.setformat(alsaaudio.PCM_FORMAT_U32_BE)
        outp.setperiodsize(self.__rate / 50)

        while True:
            data = self.__write_queue.get()
            outp.write(data)

    """
    Pre-post data into the output buffer to avoid buffer underrun.
    """
    def __pre_post_data(self):
        zeros = np.zeros(self.__rate / 50, dtype = np.uint32)

        for i in range(0, self.__pre_post):
            self.__write_queue.put(zeros)

    """
    Runs the read and write processes.
    """
    def run(self):
        self.__pre_post_data()
        read_process = Process(target = self.__read)
        write_process = Process(target = self.__write)
        read_process.start()
        write_process.start()

    """
    Reads audio samples from the queue captured from the reading thread.
    """
    def read(self):
        return self.__read_queue.get()

    """
    Writes audio samples to the queue to be played by the writing thread.
    """
    def write(self, data):
        self.__write_queue.put(data)

    """
    Pseudonymize the audio samples from a binary string into an array of integers.
    """
    def pseudonymize(self, s):
        return struct.unpack(">" + ("I" * (len(s) / self.__stride)), s)

    """
    Depseudonymize the audio samples from an array of integers into a binary string.
    """
    def depseudonymize(self, a):
        s = ""

        for elem in a:
            s += struct.pack(">I", elem)

        return s

    """
    Normalize the audio samples from an array of integers into an array of floats with unity level.
    """
    def normalize(self, data, max_val):
        data = np.array(data)
        bias = int(0.5 * max_val)
        fac = 1.0 / (0.5 * max_val)
        data = fac * (data - bias)
        return data

    """
    Denormalize the data from an array of floats with unity level into an array of integers.
    """
    def denormalize(self, data, max_val):
        bias = int(0.5 * max_val)
        fac = 0.5 * max_val
        data = np.array(data)
        data = (fac * data).astype(np.int64) + bias
        return data

debug = True
audio = Audio()
audio.run()

while True:
    data = audio.read()
    pdata = audio.pseudonymize(data)

    if debug:
        print "[PRE-PSEUDONYMIZED] Min: " + str(np.min(pdata)) + ", Max: " + str(np.max(pdata))

    ndata = audio.normalize(pdata, 0xffffffff)

    if debug:
        print "[PRE-NORMALIZED] Min: " + str(np.min(ndata)) + ", Max: " + str(np.max(ndata))
        print "[PRE-NORMALIZED] Level: " + str(int(10.0 * np.log10(np.max(np.absolute(ndata)))))

    #ndata += 0.01 # When I comment in this line, it wreaks complete havoc!

    if debug:
        print "[POST-NORMALIZED] Level: " + str(int(10.0 * np.log10(np.max(np.absolute(ndata)))))
        print "[POST-NORMALIZED] Min: " + str(np.min(ndata)) + ", Max: " + str(np.max(ndata))

    pdata = audio.denormalize(ndata, 0xffffffff)

    if debug:
        print "[POST-PSEUDONYMIZED] Min: " + str(np.min(pdata)) + ", Max: " + str(np.max(pdata))
        print ""

    data = audio.depseudonymize(pdata)
    audio.write(data)

然而,当我甚至对音频数据进行最轻微的修改(例如注释那行)时,输出会产生很多噪音和极端失真。好像我没有正确处理PCM数据。奇怪的是,"水平仪"等的输出似乎都有意义。但是,当我稍微偏移它时,输出完全失真(但是连续)。

编辑3 :我刚刚发现我的算法(此处未包含)在将其应用于wave文件时有效。所以问题实际上似乎归结为ALSA API。

编辑4 :我终于找到了问题。他们是以下。

第1天 - ALSA悄悄地"倒退"请求PCM_FORMAT_U32_LE到PCM_FORMAT_U8_LE,因此假设每个样本宽度为4个字节,我错误地解释了数据。它在我请求PCM_FORMAT_S32_LE时有效。

第二 - ALSA输出似乎期望字节的周期大小,即使它们在规范中明确指出它在中是预期的。因此,如果使用32位采样深度,则必须将周期大小设置为输出的四倍。

第三 - 即使在Python中(其中有一个"全局解释器锁"),与Thread相比,进程也很慢。通过更改为线程,您可以大大减少延迟,因为I / O线程基本上不做任何计算密集的事情。

3 个答案:

答案 0 :(得分:2)

当你

  1. 读取一大块数据,
  2. 写一个数据块,
  3. 然后等待读取第二块数据,
  4. 如果第二个块不短于第一个块,则输出设备的缓冲区将变为空。

    在开始实际处理之前,您应该用静音填充输出设备的缓冲区。然后,输入或输出处理中的小延迟都无关紧要。

答案 1 :(得分:2)

你可以手动完成所有操作,正如@CL在他/她answer中推荐的那样,但我建议你只使用 而是GNU Radio

这是一个框架,负责完成所有“将小块样本输入和输出算法”;它可以很好地扩展,你可以用Python或C ++编写信号处理。

事实上,它附带了一个音频源和一个音频接收器,可直接与ALSA通信,只需提供/接收连续样本。我建议通过GNU Radio的Guided Tutorials阅读;他们准确地解释了为音频应用程序进行信号处理的必要条件。

一个非常小的流程图看起来像:

Flow graph

您可以将高通滤波器替换为您自己的信号处理模块,或者使用现有模块的任意组合。

有一些有用的东西,比如文件和wav文件接收器和信号源,滤波器,重采样器,放大器(ok,乘法器),......

答案 2 :(得分:0)

我终于找到了问题。他们是以下。

第1天 - ALSA悄悄地"倒退"请求PCM_FORMAT_U32_LE到PCM_FORMAT_U8_LE,因此假设每个样本宽度为4个字节,我错误地解释了数据。它在我请求PCM_FORMAT_S32_LE时有效。

第二 - ALSA输出似乎期望以字节为单位的周期大小,即使它们明确表明它在规范中的帧中是预期的。因此,如果使用32位采样深度,则必须将周期大小设置为输出的四倍。

第三 - 即使在Python中(其中有一个"全局解释器锁"),与Thread相比,进程也很慢。通过更改为线程,您可以大大减少延迟,因为I / O线程基本上不做任何计算密集的事情。

音频现在无间隙且不失真,但延迟太高了。