You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用pyaudiowpatch多段录制扬声器音频时遇OSError -9999错误

问题:pyaudiowpatch二次录制扬声器音频报错OSError -9999

使用pyaudiowpatch录制扬声器输出音频,首次录制完成后,再次启动录制线程时触发如下错误:

Exception in thread Thread-3 (record_audio):
Traceback (most recent call last):
  File "C:\Program Files\Python311\Lib\threading.py", line 1038, in _bootstrap_inner
    self.run()
  File "C:\Program Files\Python311\Lib\threading.py", line 975, in run
    self._target(*self._args, **self._kwargs)
  File "c:\Users\...\gui.py", line 24, in record_audio
    with open_audio_stream(default_speakers, wave_file) as stream:
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "c:\Users\...\gui.py", line 67, in open_audio_stream
    stream = audio.open(format=pyaudio.paInt16,
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Program Files\Python311\Lib\site-packages\pyaudiowpatch\__init__.py", line 801, in open
    stream = Stream(self, *args, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Program Files\Python311\Lib\site-packages\pyaudiowpatch\__init__.py", line 467, in __init__
    pa.start_stream(self._stream)
OSError: [Errno -9999] Unanticipated host error

场景说明:用GUI两个按钮控制录制,点击录制按钮创建线程,点击停止按钮终止线程,但第二次点击录制按钮时程序崩溃。相关代码如下:

import pyaudiowpatch as pyaudio
import time
import wave
import threading

def record_audio():
    # Get the default speakers
    default_speakers = get_default_speakers()

    # Open the wave file
    wave_file = open_wave_file(default_speakers)

    # Set up the audio stream and callback
    with open_audio_stream(default_speakers, wave_file) as stream:
        # Print a message to indicate recording has started
        print_recording_message(default_speakers)

        # Continuously write audio frames to the wave file until the stop event is triggered
        while not stop_recording:
            time.sleep(0.1)

    # Close the wave file
    wave_file.close()

recording_thread = threading.Thread(target=record_audio)

def get_default_speakers():
    with pyaudio.PyAudio() as audio:
        try:
            host_api_info = audio.get_host_api_info_by_type(pyaudio.paWASAPI)
        except OSError:
            print_error_and_exit("WASAPI is not available on the system. Exiting...")

        default_speakers = audio.get_device_info_by_index(host_api_info["defaultOutputDevice"])

        if not default_speakers["isLoopbackDevice"]:
            for loopback in audio.get_loopback_device_info_generator():
                if default_speakers["name"] in loopback["name"]:
                    default_speakers = loopback
                    break
            else:
                print_error_and_exit("Default loopback output device not found. Exiting...")

        return default_speakers


def open_wave_file(default_speakers):
    wave_file = wave.open(filename+".wav", 'wb')
    wave_file.setnchannels(default_speakers["maxInputChannels"])
    wave_file.setsampwidth(pyaudio.PyAudio().get_sample_size(pyaudio.paInt16))
    wave_file.setframerate(int(default_speakers["defaultSampleRate"]))
    return wave_file


def open_audio_stream(default_speakers, wave_file):
    audio = pyaudio.PyAudio()
    stream = audio.open(format=pyaudio.paInt16,
                        channels=default_speakers["maxInputChannels"],
                        rate=int(default_speakers["defaultSampleRate"]),
                        frames_per_buffer=audio.get_sample_size(pyaudio.paInt16),
                        input=True,
                        input_device_index=default_speakers["index"],
                        stream_callback=lambda in_data, frame_count, time_info, status: callback(in_data, frame_count, time_info, status, wave_file))
    return stream


def print_recording_message(default_speakers):
    print(f"Recording from: ({default_speakers['index']}){default_speakers['name']}")


def print_error_and_exit(error_message):
    print(error_message)
    exit()


def callback(in_data, frame_count, time_info, status, wave_file):
    wave_file.writeframes(in_data)
    return (in_data, pyaudio.paContinue)
解决方案

问题根源

  1. PyAudio实例资源泄漏:open_audio_stream每次创建新的pyaudio.PyAudio()实例,但未正确关闭,导致音频设备资源被占用,二次启动时无法获取资源。
  2. 线程实例重复使用:全局recording_thread只能启动一次,二次启动必须创建新的线程实例。
  3. 线程同步不可靠:用普通布尔变量stop_recording控制线程停止,线程间同步存在竞态条件,可能导致资源未正确释放。
  4. Stream上下文管理不完整:open_audio_stream仅返回Stream,但未管理PyAudio实例的生命周期,导致实例无法正常销毁。

修复步骤及代码

1. 替换线程同步变量

用threading.Event()替代布尔变量,实现可靠的线程停止控制。

2. 每次录制创建新线程

删除全局预创建的recording_thread,点击录制时动态生成新线程。

3. 完整管理PyAudio和Stream资源

修改open_audio_stream,确保PyAudio实例和Stream都被正确关闭;或者用上下文管理器包装两者。

4. 修复Wave文件操作中的PyAudio实例泄漏

open_wave_file中每次创建新的PyAudio实例获取采样宽度,改为复用实例或缓存值。

修复后的完整代码:

import pyaudiowpatch as pyaudio
import time
import wave
import threading

# 线程停止信号,初始为停止状态
stop_recording = threading.Event()
stop_recording.set()
recording_thread = None
filename = "recording"  # 补充缺失的全局变量

def record_audio():
    default_speakers = get_default_speakers()
    wave_file = None
    audio_instance = None
    stream = None

    try:
        wave_file = open_wave_file(default_speakers)
        audio_instance, stream = open_audio_stream(default_speakers, wave_file)
        
        print_recording_message(default_speakers)
        stream.start_stream()
        
        # 等待停止信号
        while not stop_recording.is_set():
            time.sleep(0.1)
            
    finally:
        # 确保资源全部释放
        if stream is not None and stream.is_active():
            stream.stop_stream()
            stream.close()
        if audio_instance is not None:
            audio_instance.terminate()
        if wave_file is not None:
            wave_file.close()

def get_default_speakers():
    with pyaudio.PyAudio() as audio:
        try:
            host_api_info = audio.get_host_api_info_by_type(pyaudio.paWASAPI)
        except OSError:
            print_error_and_exit("WASAPI is not available on the system. Exiting...")

        default_speakers = audio.get_device_info_by_index(host_api_info["defaultOutputDevice"])

        if not default_speakers["isLoopbackDevice"]:
            for loopback in audio.get_loopback_device_info_generator():
                if default_speakers["name"] in loopback["name"]:
                    default_speakers = loopback
                    break
            else:
                print_error_and_exit("Default loopback output device not found. Exiting...")

        return default_speakers

def open_wave_file(default_speakers):
    # 复用PyAudio实例避免重复创建
    with pyaudio.PyAudio() as audio:
        sample_width = audio.get_sample_size(pyaudio.paInt16)
    
    wave_file = wave.open(f"{filename}.wav", 'wb')
    wave_file.setnchannels(default_speakers["maxInputChannels"])
    wave_file.setsampwidth(sample_width)
    wave_file.setframerate(int(default_speakers["defaultSampleRate"]))
    return wave_file

def open_audio_stream(default_speakers, wave_file):
    audio = pyaudio.PyAudio()
    stream = audio.open(
        format=pyaudio.paInt16,
        channels=default_speakers["maxInputChannels"],
        rate=int(default_speakers["defaultSampleRate"]),
        frames_per_buffer=audio.get_sample_size(pyaudio.paInt16),
        input=True,
        input_device_index=default_speakers["index"],
        stream_callback=lambda in_data, frame_count, time_info, status: callback(in_data, frame_count, time_info, status, wave_file)
    )
    return audio, stream

def print_recording_message(default_speakers):
    print(f"Recording from: ({default_speakers['index']}){default_speakers['name']}")

def print_error_and_exit(error_message):
    print(error_message)
    exit()

def callback(in_data, frame_count, time_info, status, wave_file):
    wave_file.writeframes(in_data)
    return (in_data, pyaudio.paContinue)

# GUI按钮回调示例
def start_recording():
    global recording_thread
    if stop_recording.is_set() and (recording_thread is None or not recording_thread.is_alive()):
        stop_recording.clear()
        recording_thread = threading.Thread(target=record_audio)
        recording_thread.start()

def stop_recording_func():
    if not stop_recording.is_set():
        stop_recording.set()
        if recording_thread is not None:
            recording_thread.join()

关键修改说明

  • 线程信号:stop_recording改为threading.Event(),通过set()/clear()控制停止,避免竞态条件。
  • 资源释放:在record_audio中用try/finally确保所有资源(Stream、PyAudio实例、Wave文件)无论是否报错都会被正确关闭。
  • 线程创建:每次点击录制时新建线程实例,避免复用已终止的线程。
  • PyAudio实例管理:open_audio_stream返回PyAudio实例和Stream,确保在finally块中终止实例;open_wave_file用上下文管理器创建PyAudio实例获取采样宽度,避免泄漏。

内容的提问来源于stack exchange,提问作者Juan R Ayala-Olivares

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 07:47:47