使用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)
解决方案
问题根源
- PyAudio实例资源泄漏:
open_audio_stream每次创建新的pyaudio.PyAudio()实例,但未正确关闭,导致音频设备资源被占用,二次启动时无法获取资源。 - 线程实例重复使用:全局
recording_thread只能启动一次,二次启动必须创建新的线程实例。 - 线程同步不可靠:用普通布尔变量
stop_recording控制线程停止,线程间同步存在竞态条件,可能导致资源未正确释放。 - 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
相关产品推荐
相关产品推荐

