Pyaudio回调模式下如何处理in_data?

Posted

技术标签:

【中文标题】Pyaudio回调模式下如何处理in_data?【英文标题】:How to handle in_data in Pyaudio callback mode? 【发布时间】:2014-04-01 19:13:45 【问题描述】:

我正在做一个关于 Python 信号处理的项目。到目前为止,我在非阻塞模式上取得了一些成功,但它给输出带来了相当大的延迟和剪辑。

我想使用 Pyaudio 和 Scipy.Signal 实现一个简单的实时音频过滤器,但是在 pyaudio 示例中提供的回调函数中,当我想读取 in_data 时,我无法处理它。尝试以各种方式对其进行转换,但均未成功。

这是我想要实现的代码(从麦克风读取数据,过滤器,并尽快输出):

import pyaudio
import time
import numpy as np
import scipy.signal as signal
WIDTH = 2
CHANNELS = 2
RATE = 44100

p = pyaudio.PyAudio()
b,a=signal.iirdesign(0.03,0.07,5,40)
fulldata = np.array([])

def callback(in_data, frame_count, time_info, status):
    data=signal.lfilter(b,a,in_data)
    return (data, pyaudio.paContinue)

stream = p.open(format=pyaudio.paFloat32,
                channels=CHANNELS,
                rate=RATE,
                output=True,
                input=True,
                stream_callback=callback)

stream.start_stream()

while stream.is_active():
    time.sleep(5)
    stream.stop_stream()
stream.close()

p.terminate()

这样做的正确方法是什么?

【问题讨论】:

【参考方案1】:

同时找到了我的问题的答案,回调如下:

def callback(in_data, frame_count, time_info, flag):
    global b,a,fulldata #global variables for filter coefficients and array
    audio_data = np.fromstring(in_data, dtype=np.float32)
    #do whatever with data, in my case I want to hear my data filtered in realtime
    audio_data = signal.filtfilt(b,a,audio_data,padlen=200).astype(np.float32).tostring()
    fulldata = np.append(fulldata,audio_data) #saves filtered data in an array
    return (audio_data, pyaudio.paContinue)

【讨论】:

你的脚本是CHANNELS = 2。这如何处理立体声输入? 它读取交错的数据,这意味着从零开始的每隔一个元素将(假设)向左(可能是向右),并且从第一个开始的每隔一个元素将是另一个通道。我的这个版本的代码并没有真正处理这个问题,我会提供一个 sn-p 以防你需要它: def callback(in_data, frame_count, time_info, flag): global data, recording,ch1,ch2 data = np.fromstring(in_data, dtype=np.float32) ch1=data[0::2] ch2=data[1::2] return (in_data,recording) 这些数组的长度会减半,所以如果你想玩他们回来你需要加倍他们。 那么,您在函数定义回调中处理捕获的音频数据,您是如何协调过滤和数据采集造成的延迟的?【参考方案2】:

我在尝试使用 PyAudio 回调模式时遇到了类似的问题,但我的要求是:

使用立体声输出(2 通道)。 实时处理。 使用任意脉冲响应处理输入信号,该脉冲响应可能会在处理过程中发生变化。

经过几次尝试,我成功了,下面是我的代码片段(基于找到的 PyAudio 示例here):

import pyaudio
import scipy.signal as ss
import numpy as np
import librosa   



track1_data, track1_rate = librosa.load('path/to/wav/track1', sr=44.1e3, dtype=np.float64)
track2_data, track2_rate = librosa.load('path/to/wav/track2', sr=44.1e3, dtype=np.float64)
track3_data, track3_rate = librosa.load('path/to/wav/track3', sr=44.1e3, dtype=np.float64)

# instantiate PyAudio (1)
p = pyaudio.PyAudio()
count = 0
IR_left = first_IR_left # Replace for actual IR
IR_right = first_IR_right # Replace for actual IR

# define callback (2)
def callback(in_data, frame_count, time_info, status):
    global count

    track1_frame = track1_data[frame_count*count : frame_count*(count+1)]
    track2_frame = track2_data[frame_count*count : frame_count*(count+1)]
    track3_frame = track3_data[frame_count*count : frame_count*(count+1)]

    track1_left = ss.fftconvolve(track1_frame, IR_left)
    track1_right = ss.fftconvolve(track1_frame, IR_right)
    track2_left = ss.fftconvolve(track2_frame, IR_left)
    track2_right = ss.fftconvolve(track2_frame, IR_right)
    track3_left = ss.fftconvolve(track3_frame, IR_left)
    track3_right = ss.fftconvolve(track3_frame, IR_right)

    track_left = 1/3 * track1_left + 1/3 * track2_left + 1/3 * track3_left
    track_right = 1/3 * track1_right + 1/3 * track2_right + 1/3 * track3_right

    ret_data = np.empty((track_left.size + track_right.size), dtype=track1_left.dtype)
    ret_data[1::2] = br_left
    ret_data[0::2] = br_right
    ret_data = ret_data.astype(np.float32).tostring()
    count += 1
    return (ret_data, pyaudio.paContinue)

# open stream using callback (3)
stream = p.open(format=pyaudio.paFloat32,
                channels=2,
                rate=int(track1_rate),
                output=True,
                stream_callback=callback,
                frames_per_buffer=2**16)

# start the stream (4)
stream.start_stream()

# wait for stream to finish (5)
while_count = 0
while stream.is_active():
    while_count += 1
    if while_count % 3 == 0:
        IR_left = first_IR_left # Replace for actual IR
        IR_right = first_IR_right # Replace for actual IR
    elif while_count % 3 == 1:
        IR_left = second_IR_left # Replace for actual IR
        IR_right = second_IR_right # Replace for actual IR
    elif while_count % 3 == 2:
        IR_left = third_IR_left # Replace for actual IR
        IR_right = third_IR_right # Replace for actual IR

    time.sleep(10)

# stop stream (6)
stream.stop_stream()
stream.close()

# close PyAudio (7)
p.terminate()

以下是对上述代码的一些重要反思:

使用 librosa 而不是 wave 可以让我使用 numpy 数组进行处理,这比来自 wave.readframes 的数据块要好得多。 您在p.open(format= 中设置的数据类型必须与ret_data 字节的格式相匹配。 PyAudio 最多可以使用float32ret_data 中的偶数索引字节到右侧耳机,奇数索引字节到左侧耳机。

澄清一下,这段代码将三个轨道的混合发送到立体声输出音频,并且每 10 秒它会改变脉冲响应,从而应用滤波器。 我用它来测试我正在开发的一个 3d 音频应用程序,以及头部相关脉冲响应 (HRIR) 的脉冲响应,它每 10 秒改变一次声音的位置。


编辑: 该代码有一个问题:输出具有与帧大小相对应的频率的噪声(帧大小越小频率越高)。我通过手动重叠和添加帧来解决这个问题。基本上,ss.oaconvolve 返回一个大小为track_frame.size + IR.size - 1 的数组,所以我将该数组分成第一个track_frame.size 元素(然后用于ret_data),然后是我保存的最后一个IR.size - 1 元素.然后将这些保存的元素添加到下一帧的第一个 IR.size - 1 元素中。第一帧加零。

【讨论】:

是否可以访问完整代码?我会觉得它很有用 当然! Here 是我使用它的 GitHub 存储库的链接。由于项目最终采用了不同的方式,因此有点杂乱无章,但是在该文件夹中,您会找到一个进行处理的convolutioner.py 文件,以及一个我使用Convolutioner 使用HRIR 作为脉冲响应来空间化音频的测试文件. Farrall 这似乎是一个非常有趣的工作。我可以在某处加你/写你吗?我正在写我的硕士论文,我认为你的代码对我非常有用(如果我可以使用它,显然有适当的引用) 是的,没问题,你可以通过LinkedIn联系我。 顺便说一句,我尝试运行此代码(删除所有不必要的处理,如 fft ecc ...)只是为了重现一个简单的输入音频文件,但它似乎不起作用。回调函数只被调用一次,然后程序停止。我不明白有什么问题

以上是关于Pyaudio回调模式下如何处理in_data?的主要内容,如果未能解决你的问题,请参考以下文章

云环境下如何处理 Whisper 的预分配?

将多通道 PyAudio 转换为 NumPy 数组

intel vt-x处于禁用状态下如何处理

当click_action被发送到应用程序的先前版本尚未添加意图过滤器的情况下如何处理?

Latex 下如何处理非首行的缩进对齐?

如何处理片段和活动中的后压