如何在多线程中安全地使用 PyAudio

1. 背景介绍

在多线程环境下使用 PyAudio 可能会导致段错误(Segmentation Fault)或其他不可预期的行为。这是因为 PyAudio 在多线程环境下可能会出现资源冲突或线程安全问题。

PyAudio 是一个用于音频输入输出的 Python 库,它依赖于 PortAudio 库。在多线程环境下,如果多个线程同时创建和销毁 PyAudio 实例,可能会导致资源冲突,从而引发段错误。

在多线程环境下,尽量避免在每个线程中创建和销毁 PyAudio 实例。相反,应该在主线程中创建一个共享的 PyAudio 实例,并在子线程中使用它。

python 复制代码
import pyaudio
import threading

# 在主线程中创建共享的 PyAudio 实例
p = pyaudio.PyAudio()

def audio_thread_function():
    # 在子线程中使用共享的 PyAudio 实例
    stream = p.open(format=pyaudio.paInt16,
                    channels=1,
                    rate=44100,
                    input=True,
                    frames_per_buffer=1024)
    # 音频处理逻辑
    stream.stop_stream()
    stream.close()

# 创建并启动多个音频线程
threads = []
for _ in range(4):
    thread = threading.Thread(target=audio_thread_function)
    thread.start()
    threads.append(thread)

# 等待所有线程完成
for thread in threads:
    thread.join()

# 在主线程中释放 PyAudio 资源
p.terminate()

2. 实际问题和解决方案

2.1 问题代码

python 复制代码
import time
import pyaudio
import numpy as np
import librosa
import soundfile as sf
import threading
import os

# 查找设备索引
def find_device_index(device_name, is_input=True):
    p = pyaudio.PyAudio()
    device_count = p.get_device_count()

    for i in range(device_count):
        device_info = p.get_device_info_by_index(i)
        if device_name in device_info['name']:
            if is_input and device_info['maxInputChannels'] > 0:
                p.terminate()
                return i
            elif not is_input and device_info['maxOutputChannels'] > 0:
                p.terminate()
                return i

    p.terminate()
    raise ValueError(f"Device '{device_name}' not found or not a {'input' if is_input else 'output'} device.")

# 播放音频文件
def play_audio(file_path, device_index, start_event):
    start_event.wait()  # 等待事件被设置
    audio_data, sr = librosa.load(file_path, sr=None)

    # 创建 PyAudio 实例
    p = pyaudio.PyAudio()

    # 打开流
    stream = p.open(format=pyaudio.paFloat32,
                    channels=1,
                    rate=sr,
                    output=True,
                    output_device_index=device_index)

    # 播放音频
    stream.write(audio_data.astype(np.float32).tobytes())

    # 关闭流
    stream.stop_stream()
    stream.close()
    p.terminate()

# 录音
def record_audio(device_index, output_file, start_event, stop_event):
    start_event.wait()  # 等待事件被设置
    p = pyaudio.PyAudio()

    # 打开输入流
    stream = p.open(format=pyaudio.paFloat32,
                    channels=1,
                    rate=44100,
                    input=True,
                    input_device_index=device_index,
                    frames_per_buffer=1024)

    print(f"Recording to {output_file}...")
    frames = []

    while not stop_event.is_set():  # 检查停止事件
        data = stream.read(1024)
        frames.append(data)

    print("Recording finished.")

    # 关闭流
    stream.stop_stream()
    stream.close()
    p.terminate()

    # 保存录音
    audio_data = b''.join(frames)
    audio_array = np.frombuffer(audio_data, dtype=np.float32)
    sf.write(output_file, audio_array, 44100)

# 主程序
if __name__ == "__main__":
    # 根据设备名称查找设备索引
    CUBE_4NANO_DYNA_INDEX = find_device_index("Cube 4Nano Dyna", is_input=False)
    SOUNDMATRIX_A10_OUTPUT_INDEX = find_device_index("SoundMatrix A10", is_input=False)
    SOUNDMATRIX_A10_INPUT_INDEX = find_device_index("SoundMatrix A10", is_input=True)

    # 获取音频文件列表
    audio_file1_folder = './Soundplay'
    audio_file2_folder = './A10play'
    audio_file1_list = [f for f in os.listdir(audio_file1_folder) if f.endswith('.wav')]
    audio_file2_list = [f for f in os.listdir(audio_file2_folder) if f.endswith('.wav')]

    # 创建事件对象
    start_event = threading.Event()
    stop_event = threading.Event()

    # 遍历每一对组合
    for audio_file1 in audio_file1_list:
        for audio_file2 in audio_file2_list:
            file_path1 = os.path.join(audio_file1_folder, audio_file1)
            file_path2 = os.path.join(audio_file2_folder, audio_file2)
            print(file_path1, file_path2)

            # 创建线程
            play_thread1 = threading.Thread(target=play_audio, args=(file_path1, CUBE_4NANO_DYNA_INDEX, start_event))
            play_thread2 = threading.Thread(target=play_audio,
                                            args=(file_path2, SOUNDMATRIX_A10_OUTPUT_INDEX, start_event))
            output_file_name = f"{os.path.splitext(audio_file1)[0]}_{os.path.splitext(audio_file2)[0]}_soundmatrix.wav"
            output_file_path = os.path.join('./A10rec', output_file_name)
            record_thread = threading.Thread(target=record_audio, args=(
                SOUNDMATRIX_A10_INPUT_INDEX, output_file_path, start_event, stop_event))

            # 启动录音和播放线程
            record_thread.start()  # 启动录音线程
            play_thread1.start()  # 播放第一个音频
            play_thread2.start()  # 播放第二个音频

            # 设置事件,开始播放和录音
            start_event.set()

            # 等待播放线程完成
            play_thread1.join()
            play_thread2.join()

            # 设置停止事件,结束录音
            stop_event.set()
            record_thread.join()

            # 重置事件以便下次使用
            start_event.clear()
            stop_event.clear()

    print("All tasks completed.")

报错:

python 复制代码
./Soundplay/cafeteria_SNR0_副本3.wav ./A10play/SER0.wav
Recording to ./A10rec/cafeteria_SNR0_副本3_SER0_soundmatrix.wav...
Recording finished.
./Soundplay/cafeteria_SNR0_副本2.wav ./A10play/SER0.wav

Process finished with exit code 139 (interrupted by signal 11:SIGSEGV)

2.2 解决方案:共享 PyAudio 实例

python 复制代码
import time
import pyaudio
import numpy as np
import librosa
import soundfile as sf
import threading
import os

# 查找设备索引
def find_device_index(device_name, is_input=True, p=None):
    if p is None:
        p = pyaudio.PyAudio()
        need_terminate = True
    else:
        need_terminate = False

    device_count = p.get_device_count()

    for i in range(device_count):
        device_info = p.get_device_info_by_index(i)
        if device_name in device_info['name']:
            if is_input and device_info['maxInputChannels'] > 0:
                if need_terminate:
                    p.terminate()
                return i
            elif not is_input and device_info['maxOutputChannels'] > 0:
                if need_terminate:
                    p.terminate()
                return i

    if need_terminate:
        p.terminate()
    raise ValueError(f"Device '{device_name}' not found or not a {'input' if is_input else 'output'} device.")

# 播放音频文件
def play_audio(file_path, device_index, start_event, p):
    start_event.wait()  # 等待事件被设置
    audio_data, sr = librosa.load(file_path, sr=None)

    # 打开流
    stream = p.open(format=pyaudio.paFloat32,
                    channels=1,
                    rate=sr,
                    output=True,
                    output_device_index=device_index)

    # 播放音频
    stream.write(audio_data.astype(np.float32).tobytes())

    # 关闭流
    stream.stop_stream()
    stream.close()

# 录音
def record_audio(device_index, output_file, start_event, stop_event, p):
    start_event.wait()  # 等待事件被设置
    buffer_size = 2048

    # 打开输入流
    stream = p.open(format=pyaudio.paFloat32,
                    channels=1,
                    rate=44100,
                    input=True,
                    input_device_index=device_index,
                    frames_per_buffer=buffer_size)

    print(f"Recording to {output_file}...")
    frames = []

    while not stop_event.is_set():  # 检查停止事件
        data = stream.read(buffer_size)
        frames.append(data)

    print("Recording finished.")

    # 关闭流
    stream.stop_stream()
    stream.close()

    # 保存录音
    audio_data = b''.join(frames)
    audio_array = np.frombuffer(audio_data, dtype=np.float32)
    sf.write(output_file, audio_array, 44100)

# 主程序
if __name__ == "__main__":
    # 创建共享的 PyAudio 实例
    p = pyaudio.PyAudio()

    # 根据设备名称查找设备索引
    CUBE_4NANO_DYNA_INDEX = find_device_index("Cube 4Nano Dyna", is_input=False, p=p)
    SOUNDMATRIX_A10_OUTPUT_INDEX = find_device_index("SoundMatrix A10", is_input=False, p=p)
    SOUNDMATRIX_A10_INPUT_INDEX = find_device_index("SoundMatrix A10", is_input=True, p=p)

    # 获取音频文件列表
    audio_file1_folder = './Soundplay'
    audio_file2_folder = './A10play'
    audio_file1_list = [f for f in os.listdir(audio_file1_folder) if f.endswith('.wav')]
    audio_file2_list = [f for f in os.listdir(audio_file2_folder) if f.endswith('.wav')]

    # 创建事件对象
    start_event = threading.Event()
    stop_event = threading.Event()

    # 遍历每一对组合
    for audio_file1 in audio_file1_list:
        for audio_file2 in audio_file2_list:
            file_path1 = os.path.join(audio_file1_folder, audio_file1)
            file_path2 = os.path.join(audio_file2_folder, audio_file2)
            print(file_path1, file_path2)

            # 创建线程
            play_thread1 = threading.Thread(target=play_audio, args=(file_path1, CUBE_4NANO_DYNA_INDEX, start_event, p))
            play_thread2 = threading.Thread(target=play_audio,
                                            args=(file_path2, SOUNDMATRIX_A10_OUTPUT_INDEX, start_event, p))
            output_file_name = f"{os.path.splitext(audio_file1)[0]}_{os.path.splitext(audio_file2)[0]}_soundmatrix.wav"
            output_file_path = os.path.join('./A10rec', output_file_name)
            record_thread = threading.Thread(target=record_audio, args=(
                SOUNDMATRIX_A10_INPUT_INDEX, output_file_path, start_event, stop_event, p))

            # 启动录音和播放线程
            record_thread.start()  # 启动录音线程
            play_thread1.start()  # 播放第一个音频
            play_thread2.start()  # 播放第二个音频

            # 设置事件,开始播放和录音
            start_event.set()

            # 等待播放线程完成
            play_thread1.join()
            play_thread2.join()

            # 设置停止事件,结束录音
            stop_event.set()
            record_thread.join()

            # 重置事件以便下次使用
            start_event.clear()
            stop_event.clear()

            time.sleep(3)

    # 释放 PyAudio 资源
    p.terminate()
相关推荐
台风护盾1 天前
技术解析|音频裁剪,开头吃掉半秒是怎么回事?过零检测与淡入的三个细节
音频处理·ai工具·人声分离
台风护盾2 天前
技术解析|视频去掉背景杂音,人声闷是怎么回事?噪声压制与自然度平衡的三个要点
音频处理·ai工具·人声分离
台风护盾3 天前
录音转文字怎么弄?云音雀保姆级图文教程:从上传到导出 TXT/DOCX/SRT 全流程
音频处理·ai工具·录音转文字·文字转音频·文字转换
台风护盾9 天前
视频怎么自动翻译成中文字幕?AI 视频翻译的三道坎和一条捷径
人工智能·机器翻译·音频处理·ai工具·视频翻译
AI老司机哇10 天前
技术解析|同一个音频,音频格式转换怎么选?四类方案的成本与精度对照
音频处理·人声分离·伴奏提取
台风护盾12 天前
技术解析|音频裁剪分割片段,怎么不切错?静音检测与阈值设定的四个参数
音频处理·ai工具·人声分离
台风护盾13 天前
音频处理基础|视频提取音频mp3,音质会变差吗?容器、音轨映射与时间戳的三个关键
音频处理·ai工具·人声分离
台风护盾16 天前
视频转文字实战:从视频提取音频到带时间戳字幕稿,聊聊ASR语音识别的门道
语音识别·音频处理·视频转文字·多媒体技术·字幕制作
放飞自我的Coder2 个月前
【audioop/audioop-lts详细用法】
音频处理·audioop
鲲穹AI超级员工2 个月前
电脑端各类音频编辑处理程序基础信息完整整理记录
电脑·音频处理