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

news2025/4/17 21:22:23

1. 背景介绍

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

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

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

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 问题代码

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.")

报错:

./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 实例

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()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2330968.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

QAM 信号的距离以及能量归一化

QAM星座图平均功率能量_星座图功率计算-CSDN博客 正交幅度调制(QAM) - Vinson88 - 博客园 不同阶QAM调制星座图中,符号能量的归一化计算原理_qpsk的星座图归一化-CSDN博客 https://zhuanlan.zhihu.com/p/690157236

Reactive编程框架与工具

文章目录 6.2 后端 Reactive 框架6.2.1 Spring WebFlux核心架构核心组件实际应用高级特性性能优化适用场景与限制 6.2.2 Akka(Actor模型)Actor模型基础基本用法高级特性响应式特性实现性能优化实际应用场景优势与挑战 6.2.3 Vert.x(事件驱动&…

Python爬虫第7节-requests库的高级用法

目录 前言 一、文件上传 二、Cookies 三、会话维持 四、SSL证书验证 五、代理设置 六、超时设置 七、身份认证 八、Prepared Request 前言 上一节,我们认识了requests库的基本用法,像发起GET、POST请求,以及了解Response对象是什么。…

Maven的安装配置-项目管理工具

各位看官,大家早安午安晚安呀~~~ 如果您觉得这篇文章对您有帮助的话 欢迎您一键三连,小编尽全力做到更好 欢迎您分享给更多人哦 今天我们来学习:Maven的安装配置-项目管理工具 目录 1.什么是Maven?Maven用来干什么的&#xff1f…

智能 SQL 优化工具 PawSQL 月度更新 | 2025年3月

📌 更新速览 本月更新包含 21项功能增强 和 9项问题修复,重点提升SQL解析精度与优化建议覆盖率。 一、SQL解析能力扩展 ✨ 新增SQL语法解析支持 SELECT...INTO TABLE 语法解析(3/26) ALTER INDEX RENAME/VISIBLE 语句解析&#…

Ubuntu虚拟机编译安装部分OpenCV模块方法实现——保姆级教程

Ubuntu虚拟机的安装过程可以查看另一篇文章:VMware安装Ubuntu虚拟机实现COpenCV代码在虚拟机下运行教程-CSDN博客 目前我们已经下载好了OpenCV,这里以OpenCV4.5.2为例。 在内存要求尽可能小的情况下,可以尝试只编译安装代码中使用到的OpenC…

spring mvc @ResponseBody 注解转换为 JSON 的原理与实现详解

ResponseBody 注解转换为 JSON 的原理与实现详解 1. 核心作用 ResponseBody 是 Spring MVC 的一个注解,用于将方法返回的对象直接序列化为 HTTP 响应体(如 JSON 或 XML),而不是通过视图解析器渲染为视图(如 HTML&…

skynet.rawcall使用详解及应用场景

目录 核心特性函数原型使用场景场景 1:高性能二进制传输(如文件转发)场景 2:自定义序列化协议(如 Protocol Buffers)场景 3:跨服务共享内存(避免拷贝) 配套接收方实现与 …

使用SpringSecurity下,发生重定向异常

使用SpringSecurity下,发生空转异常 环境信息: Spring Boot 3.4.4 , jdk 17 , springSecurity 6.4.4 问题背景: 没有自定义controller ,改写了login 页面,并且进行了成功后的跳转处理&#xf…

Elasticsearch | ES索引模板、索引和索引别名的创建与管理

关注:CodingTechWork 引言 在使用 Elasticsearch (ES) 和 Kibana 构建数据存储和分析系统时,索引模板、索引和索引别名的管理是关键步骤。本文将详细介绍如何通过 RESTful API 和 Kibana Dev Tools 创建索引模板、索引以及索引别名,并提供具…

力扣hot100_回溯(2)_python版本

一、39. 组合总和(中等) 代码: class Solution:def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:ans []path []def dfs(i: int, left: int) -> None:if left 0:# 找到一个合法组合ans.append(pa…

LPDDR4内存颗粒命名规则全解析:三星、镁光、海力士、南亚、长鑫等厂商型号解码与选型指南

由于之前DDR的系列选型文章有很好的反馈,所以补充LPDDR4低功耗内存的选型和命名规则,总结了目前市面上常用的内存,供硬件工程师及数码爱好者参考。 在智能手机、平板电脑和低功耗设备中,LPDDR4 SDRAM凭借其高带宽、低功耗特性成为…

【杂谈】Godot4.4导出到Android平台(正式导出)

学博而后可约,事历而后知要。 目录 一、准备二、Gradle构建三、配置Java SDK四、配置Android SDK五、配置密钥 一、准备 本文在前文【杂谈】Godot4.4导出到安卓平台(调试导出)的基础上,进行正式导出。调试导出并不是真正的编译导…

基于AI设计开发出来的业务系统是什么样的?没有菜单?没有表格?

基于AI设计开发出的业务系统仍然会包含菜单、表格等传统UI元素,但AI技术会显著改变它们的实现方式和交互逻辑。以下是具体分析: 一、传统元素的持续存在 功能刚需性 • 菜单承担着系统导航的核心功能,表格则是结构化数据展示的基础载体。根…

数字足迹管理(DFM):你的网络隐身指南

数字足迹管理(DFM):你的网络隐身指南 ‌你可能不知道,你的姓名、电话、住址正在网上被“明码标价”‌ ——而这一切,可能只是因为你点过外卖、寄过快递,甚至注册过一个网站。 一、什么是数字足迹管理&#…

如何避免“过度承诺”导致的验收失败

如何避免“过度承诺”导致的验收失败?关键在于: 评估可行性、设置合理目标、高频沟通反馈、阶段性验收、做好风险管理。其中设置合理目标至关重要,很多团队往往在项目初期为迎合客户或领导而报出“最理想方案”,忽略了资源、技术及…

紧跟数字人热潮:123 数字人分身克隆系统源码部署与风口洞察

在当今数字化浪潮中,数字人技术无疑已成为最具活力与潜力的领域之一,正以迅猛之势席卷多个行业,重塑着人们的交互方式与商业运作模式。C 站作为技术交流的前沿阵地,汇聚了众多关注前沿科技的开发者与技术爱好者,今天来…

QT控件 修改QtTreePropertyBrowser自定义属性编辑器源码,添加第一列标题勾选,按钮,右键菜单事件等功能

头阵子遇到一个需要修改QtTreePropertyBrowser控件的需求,QT开发做这么久了,这个控件倒是第一次用,费了点时间研究,在这里做个简单的总结。 QtTreePropertyBrowser控件 是 Qt 解决方案 (Qt Solutions) 中的一个组件,用…

开源模型应用落地-模型上下文协议(MCP)-从数据孤岛到万物互联(一)

一、前言 当开发者还在为每个AI工具编写臃肿的API适配器时,一场关于「连接」的技术革命已悄然降临。模型上下文协议(MCP)正在用一套全新的交互语法,重新定义人工智能与物理世界的对话方式。MCP协议如同为AI系统装上了“万能接口”…

【区块链安全 | 第三十八篇】合约审计之获取私有数据(二)

文章目录 前言漏洞代码代码审计攻击步骤修复建议审计思路 前言 在【区块链安全 | 第三十七篇】合约审计之获取私有数据(一)中,介绍了私有数据、访问私有数据实例、Solidity 中的数据存储方式等知识,本文通过分析具体合约代码进行…