PyAudio 输入溢出

Posted

技术标签:

【中文标题】PyAudio 输入溢出【英文标题】:PyAudio Input overflowed 【发布时间】:2012-05-30 19:48:33 【问题描述】:

我正在尝试在 python 中制作实时绘图声音。我需要从我的麦克风中获取块。

使用PyAudio,尝试使用

import pyaudio
import wave
import sys

chunk = 1024
FORMAT = pyaudio.paInt16
CHANNELS = 1
RATE = 44100
RECORD_SECONDS = 5
WAVE_OUTPUT_FILENAME = "output.wav"

p = pyaudio.PyAudio()

stream = p.open(format = FORMAT,
                channels = CHANNELS,
                rate = RATE,
                input = True,
                frames_per_buffer = chunk)

print "* recording"
all = []
for i in range(0, RATE / chunk * RECORD_SECONDS):
    data = stream.read(chunk)
    all.append(data)
print "* done recording"

stream.close()
p.terminate()

之后,我收到以下错误:

* recording
Traceback (most recent call last):
  File "gg.py", line 23, in <module>
    data = stream.read(chunk)
  File "/usr/lib64/python2.7/site-packages/pyaudio.py", line 564, in read
    return pa.read_stream(self._stream, num_frames)
IOError: [Errno Input overflowed] -9981

我无法理解这个缓冲区。我想使用阻塞 IO 模式,所以如果块不可用,我想等待这些块。但是当我创建除段或睡眠(0.1)之外的尝试时,我会听到咔嗒声,所以这不是我想要的。

请为我的问题提出最佳解决方案?

【问题讨论】:

也许你的块太小了。也许它在缓冲区中获取的数据多于您提取的数据,因为块大小足够小,Python 代码跟不上。 嗨。只是想知道这个问题是否有任何更新?我间歇性地收到[Errno Input overflowed] -9981 错误。我检查了p.is_format_supported 是否适用于我使用的格式。 【参考方案1】:

我在运行您的代码时遇到了同样的错误。我查看了我的默认音频设备的默认采样率,我的 macbook 的内置麦克风,它是 48000Hz 而不是 44100Hz。

p.get_device_info_by_index(0)['defaultSampleRate']
Out[12]: 48000.0

当我将 RATE 更改为此值时,它起作用了。

【讨论】:

我遇到了同样的错误,您的解决方案(最高 48000)有效。但我已经运行了代码: if p.is_format_supported(44100.0, # Sample rate input_device=devinfo["index"], input_channels=devinfo['maxInputChannels'], input_format=pyaudio.paInt16): print 'Yay!' ......它奏效了!所以我很困惑问题是什么。有什么见解吗? 尝试升级portaudio,这为我解决了一些速率问题。我使用“brew install portaudio --HEAD”。 这对我有用,我没有意识到声卡的默认采样率为 48khz,谢谢! 谢谢,这也是我的问题。我没想到这会成为预算硬件的问题,但也许 48k 正在成为事实上的标准?【参考方案2】:
FORMAT = pyaudio.paInt16

确保设置正确的格式,我的内置麦克风设置为 24 位(请参阅 Audio-Midi-Setup 应用程序)。

【讨论】:

【参考方案3】:

似乎很多人都遇到了这个问题。我对其进行了一些研究,我认为这意味着在上一次调用 stream.read() 和当前调用之间,流中的数据丢失了(即缓冲区填满的速度比你清除它的速度快)。

来自Pa_ReadStream() 的文档(stream.read() 最终调用的 PortAudio 函数):

@return On success PaNoError will be returned, or PaInputOverflowed if
input data was discarded by PortAudio after the previous call and
before this call.

PaInputOverflowed 然后在 pyaudio 包装器中生成 IOError)。

如果您可以不捕获每一帧,那么您可以忽略此错误。如果拥有每一帧对您来说绝对至关重要,那么您需要找到一种方法来提高应用程序的优先级。我对 Python 不够熟悉,不知道用 Python 的方式来执行此操作,但值得尝试一个简单的 nice 命令,或将调度策略更改为 SCHED_DEADLINE。

编辑:

现在的一个问题是,当抛出 IOError 时,您会丢失该调用中收集的所有帧。要忽略溢出并只返回我们拥有的内容,您可以应用下面的补丁,这将导致 stream.read() 忽略来自 PortAudio 的输出欠载和输入溢出错误(但如果发生不同的错误仍然会抛出一些东西)。更好的方法是根据您的需要自定义此行为(投掷/不投掷)。

diff --git a/src/_portaudiomodule.c b/src/_portaudiomodule.c
index a8f053d..0878e74 100644
--- a/src/_portaudiomodule.c
+++ b/src/_portaudiomodule.c
@@ -2484,15 +2484,15 @@ pa_read_stream(PyObject *self, PyObject *args)
      else 
       /* clean up */
       _cleanup_Stream_object(streamObject);
+
+      /* free the string buffer */
+      Py_XDECREF(rv);
+
+      PyErr_SetObject(PyExc_IOError,
+                       Py_BuildValue("(s,i)",
+                                     Pa_GetErrorText(err), err));
+      return NULL;
     
-
-    /* free the string buffer */
-    Py_XDECREF(rv);
-
-    PyErr_SetObject(PyExc_IOError,
-                   Py_BuildValue("(s,i)",
-                                 Pa_GetErrorText(err), err));
-    return NULL;
   

   return rv;

【讨论】:

【参考方案4】:

我在 OS X 10.10 上工作,在尝试从 SYBA USB 卡(C 媒体芯片组)中的麦克风获取音频时遇到同样的错误,并使用 fft 等实时处理它:

IOError: [Errno Input overflowed] -9981

当使用回调模式而不是阻塞模式时,溢出已完全解决,如 libbkmz 所写。(https://www.python.org/dev/peps/pep-0263/)

基于此,部分工作代码如下所示:

"""
Creating the audio stream from our mic
"""
rate=48000
self.chunk=2**12
width = 2

p = pyaudio.PyAudio()

# callback function to stream audio, another thread.
def callback(in_data,frame_count, time_info, status):
    self.audio = numpy.fromstring(in_data,dtype=numpy.int16)
    return (self.audio, pyaudio.paContinue)

#create a pyaudio object
self.inStream = p.open(format = p.get_format_from_width(width, unsigned=False),
                       channels=1,
                       rate=rate,
                       input=True,
                       frames_per_buffer=self.chunk,
                       stream_callback = callback)

"""
Setting up the array that will handle the timeseries of audio data from our input
"""
self.audio = numpy.empty((self.buffersize),dtype="int16")

    self.inStream.start_stream()

while True:
  try:
    self.ANY_FUNCTION() #any function to run parallel to the audio thread, running forever, until ctrl+C is pressed. 

  except KeyboardInterrupt:

    self.inStream.stop_stream()
    self.inStream.close()
    p.terminate()
    print("* Killed Process")
    quit()

这段代码将创建一个回调函数,然后创建一个流对象,启动它,然后在任何函数中循环。一个单独的线程流式传输音频,并且当主循环停止时该流关闭。 self.audio 用于任何功能。如果没有终止,我也遇到了线程永远运行的问题。

由于 Pyaudio 在单独的线程中运行此流,这使得音频流稳定,阻塞模式可能已经饱和,具体取决于脚本中其余进程的速度或时间。

请注意,块大小为 2^12,但较小的块也可以。我考虑并尝试了其他一些参数,以确保它们都有意义:

块大小变大或变小(无效) 缓冲区中字的位数和格式,在本例中为有符号 16 位。 变量的有符号性(尝试无符号并得到饱和模式) 麦克风输入的性质,系统默认选择,增益等。

希望对某人有用!

【讨论】:

【参考方案5】:

我在非常慢的树莓派上遇到了同样的问题,但我能够通过使用更快的 array 模块来存储数据来解决它(在大多数情况下)。

import array
import pyaudio 

FORMAT = pyaudio.paInt16
CHANNELS = 1
INPUT_CHANNEL=2
RATE = 48000
CHUNK = 512

p = pyaudio.PyAudio()
stream = p.open(format=FORMAT,
                channels=CHANNELS,
                rate=RATE,
                input=INPUT_CHANNEL,
                frames_per_buffer =CHUNK)

print("* recording")


try:
    data = array.array('h')
    for i in range(0, int(RATE / CHUNK * RECORD_SECONDS)):
        data.fromstring(stream.read(CHUNK))
finally:
    stream.stop_stream()
    stream.close()
    p.terminate()

print("* done recording")

data 的内容之后是二进制的。 但是您可以使用numpy.array(data, dtype='i') 来获取一个numpy 整数数组。

【讨论】:

【参考方案6】:

我的other answer 在大多数情况下都解决了这个问题。但是,有时错误仍然会发生。

这就是我放弃 pyaudio 并改用 pyalsaaudio 的原因。我的 Raspy 现在可以流畅地录制任何声音。

import alsaaudio   
import numpy as np
import array

# constants
CHANNELS    = 1
INFORMAT    = alsaaudio.PCM_FORMAT_FLOAT_LE
RATE        = 44100
FRAMESIZE   = 1024

# set up audio input
recorder=alsaaudio.PCM(type=alsaaudio.PCM_CAPTURE)
recorder.setchannels(CHANNELS)
recorder.setrate(RATE)
recorder.setformat(INFORMAT)
recorder.setperiodsize(FRAMESIZE)


buffer = array.array('f')
while <some condition>:
    buffer.fromstring(recorder.read()[1])

data = np.array(buffer, dtype='f')

【讨论】:

真的很有帮助,谢谢!我使用常规列表而不是array.array,它更简单但对我来说效果很好,所以主要的变化是pyaudio =>pyalsaaudio。另外,我的麦克风需要PCM_FORMAT_S16_LE【参考方案7】:

pyaudio.Stream.read() 有一个关键字参数exception_on_overflow,将其设置为 False。

对于您的示例代码,如下所示:

import pyaudio
import wave
import sys

chunk = 1024
FORMAT = pyaudio.paInt16
CHANNELS = 1
RATE = 44100
RECORD_SECONDS = 5
WAVE_OUTPUT_FILENAME = "output.wav"

p = pyaudio.PyAudio()

stream = p.open(format = FORMAT,
                channels = CHANNELS,
                rate = RATE,
                input = True,
                frames_per_buffer = chunk)

print "* recording"
all = []
for i in range(0, RATE / chunk * RECORD_SECONDS):
    data = stream.read(chunk, exception_on_overflow = False)
    all.append(data)
print "* done recording"

stream.close()
p.terminate()

有关详细信息,请参阅PyAudio documentation。

【讨论】:

我得到:TypeError: read() got an unexpected keyword argument 'exception_on_overflow'【参考方案8】:

这对我有帮助:https://***.com/a/46787874/5047984

我使用多处理来并行写入文件以录制音频。这是我的代码:

recordAudioSamples.py

import pyaudio
import wave
import datetime
import signal
import ftplib
import sys
import os

# configuration for assos_listen
import config

# run the audio capture and send sound sample processes
# in parallel
from multiprocessing import Process

# CONFIG
CHUNK = config.chunkSize
FORMAT = pyaudio.paInt16
CHANNELS = 1
RATE = config.samplingRate
RECORD_SECONDS = config.sampleLength

# HELPER FUNCTIONS

# write to ftp
def uploadFile(filename):

    print("start uploading file: " + filename)
    # connect to container
    ftp = ftplib.FTP(config.ftp_server_ip, config.username, config.password)

    # write file
    ftp.storbinary('STOR '+filename, open(filename, 'rb'))
    # close connection
    ftp.quit()
    print("finished uploading: " +filename)

# write to sd-card
def storeFile(filename,frames):

    print("start writing file: " + filename)
    wf = wave.open(filename, 'wb')
    wf.setnchannels(CHANNELS)
    wf.setsampwidth(p.get_sample_size(FORMAT))
    wf.setframerate(RATE)
    wf.writeframes(b''.join(frames))
    wf.close()
    print(filename + " written")

# abort the sampling process
def signal_handler(signal, frame):
    print('You pressed Ctrl+C!')

    # close stream and pyAudio
    stream.stop_stream()
    stream.close()
    p.terminate()

    sys.exit(0)

# MAIN FUNCTION
def recordAudio(p, stream):

    sampleNumber = 0
    while (True):
        print("*  recording")
        sampleNumber = sampleNumber +1

        frames = []
        startDateTimeStr = datetime.datetime.now().strftime("%Y_%m_%d_%I_%M_%S_%f")
        for i in range(0, int(RATE / CHUNK * RECORD_SECONDS)):
            data = stream.read(CHUNK)
            frames.append(data)

        fileName =  str(config.sensorID) + "_" + startDateTimeStr + ".wav"

        # create a store process to write the file in parallel
        storeProcess = Process(target=storeFile, args=(fileName,frames))
        storeProcess.start()

        if (config.upload == True):
            # since waiting for the upload to finish will take some time
            # and we do not want to have gaps in our sample
            # we start the upload process in parallel
            print("start uploading...")
            uploadProcess = Process(target=uploadFile, args=(fileName,))
            uploadProcess.start()

# ENTRYPOINT FROM CONSOLE
if __name__ == '__main__':

    p = pyaudio.PyAudio()
    stream = p.open(format=FORMAT,
                    channels=CHANNELS,
                    rate=RATE,
                    input=True,
                    frames_per_buffer=CHUNK)

    # directory to write and read files from
    os.chdir(config.storagePath)

    # abort by pressing C
    signal.signal(signal.SIGINT, signal_handler)
    print('\n\n--------------------------\npress Ctrl+C to stop the recording')

    # start recording
    recordAudio(p, stream)

config.py

### configuration file for assos_listen
# upload
upload = False

# config for this sensor
sensorID = "al_01"

# sampling rate & chunk size
chunkSize = 8192
samplingRate = 44100 # 44100 needed for Aves sampling
# choices=[4000, 8000, 16000, 32000, 44100] :: default 16000

# sample length in seconds
sampleLength = 10

# configuration for assos_store container
ftp_server_ip = "192.168.0.157"
username = "sensor"
password = "sensor"

# storage on assos_listen device
storagePath = "/home/pi/assos_listen_pi/storage/"

【讨论】:

为什么不使用线程?阻塞 I/O 释放 GIL,有效利用多核而没有多处理的复杂性。【参考方案9】:

这对我很有帮助:

input_ = stream.read(chunk, exception_on_overflow=False)
exception_on_overflow = False

【讨论】:

以上是关于PyAudio 输入溢出的主要内容,如果未能解决你的问题,请参考以下文章

PyAudio IOError:没有可用的默认输入设备

如何使用 PyAudio 选择特定的输入设备

pyaudio 录制多个频道

将多通道 PyAudio 转换为 NumPy 数组

如何选择使用哪个设备录制(Python PyAudio)

PyAudio 回调函数只调用一次