【PyQt5】嵌套多线程数据交互实现

使用场景:

PyQt5 +Python开发的GUI小工具,使用QThread实现前后端数据交互,同时,后端使用2个多线程进行逻辑编写,在线程1里面启动线程2,且线程2需要线程1给传递数据。

python 复制代码
import sys
import time
import threading
import pyaudio
import wave
from PyQt5.QtCore import QThread, pyqtSignal, QMutex, QTimer  
from PyQt5.QtWidgets import (QApplication, QMainWindow, QPushButton, 
                             QTextBrowser, QVBoxLayout, QWidget, QLabel, 
                             QSpinBox, QHBoxLayout)

class AudioRecorderThread(QThread):
    """
    线程2:音频录制线程
    """
    update_signal = pyqtSignal(str)  # 用于发送更新信息的信号
    recording_finished = pyqtSignal(str)  # 录制完成信号
    audio_wav_name=pyqtSignal(str)  # 传递录制的音频文件名
    
    def __init__(self, filename="output.wav", parent=None):
        super().__init__(parent)
        self.filename = filename
        self._is_recording = False
        self._mutex = QMutex()
        self.frames = []
        self.audio = pyaudio.PyAudio()
        
        # 音频参数
        self.format = pyaudio.paInt16
        self.channels = 1
        self.rate = 44100
        self.chunk = 1024

    def run(self):
        """开始录制音频"""
        self._is_recording = True
        self.frames = []
        
        try:
            # 打开音频流
            stream = self.audio.open(
                format=self.format,
                channels=self.channels,
                rate=self.rate,
                input=True,
                frames_per_buffer=self.chunk
            )
            
            self.update_signal.emit("音频录制已开始...")
            
            # 录制循环
            while self.is_recording():
                data = stream.read(self.chunk)
                self.frames.append(data)
                
            # 停止录制
            stream.stop_stream()
            stream.close()
            
            # 保存录音文件
            self.save_audio()
            self.update_signal.emit("音频录制已停止")
            self.recording_finished.emit(self.filename)
            
        except Exception as e:
            self.update_signal.emit(f"录制错误: {str(e)}")

    def is_recording(self):
        """检查是否正在录制(线程安全)"""
        with QMutexLocker(self._mutex):
            return self._is_recording

    def stop_recording(self):
        """停止录制(线程安全)"""
        with QMutexLocker(self._mutex):
            self._is_recording = False

    def save_audio(self):
        """保存录制的音频到文件"""
        try:
            wf = wave.open(self.filename, 'wb')
            wf.setnchannels(self.channels)
            wf.setsampwidth(self.audio.get_sample_size(self.format))
            wf.setframerate(self.rate)
            wf.writeframes(b''.join(self.frames))
            wf.close()
            self.audio_wav_name.emit(self.filename)
            self.update_signal.emit(f"音频已保存到: {self.filename}")
        except Exception as e:
            self.update_signal.emit(f"保存音频错误: {str(e)}")

    def __del__(self):
        """清理资源"""
        self.audio.terminate()


class ControlThread(QThread):
    """
    线程1:控制线程,管理音频录制线程
    """
    update_signal = pyqtSignal(str)  # 用于发送更新信息的信号
    
    def __init__(self, max_duration=10, parent=None):
        super().__init__(parent)
        self._isPause=False #是否暂停
        self.is_runnnin=True #标志位表示线程是否需要退出
        self.finished=False
        self.cond=QWaitCondition()
        self.mutex=QMutex()
        self.max_duration = max_duration  # 最大录制时长(秒)
        self.record_wav_file=None #音频文件的绝对路径
        self.recorder = AudioRecorderThread()
        # 将录制线程的信号连接到控制线程的信号
        self.recorder.update_signal.connect(self.update_signal.emit)
        self.recorder.recording_finished.connect(self.on_recording_finished)
        self.recorder.audio_wav_name.connect(self.get_audio_wav_name)
        self.recording_start_time = 0
    def check_mutex(self):
        self.mutex.lock()
        while self._isPuse:
            self.cond.wait(self.mutex)
        self.mutex.unlock()
    def stop_recording(self):
        """停止录制线程"""
        if self.recorder.is_recording():
            self.recorder.stop_recording()
            # 等待录制线程结束
            self.recorder.wait()

    def on_recording_finished(self, filename):
        """录制完成时的处理"""
        self.update_signal.emit(f"录制完成,文件已保存: {filename}")
        
	def get_audio_wav_name(self,wav_name):
		self.record_wav_file=wav_name
		
    def run(self):
        """启动控制逻辑"""
        self.update_signal.emit("控制线程启动")
        while not self.finished:
            # 如果是暂停状态,则阻塞等待
        	self.check_mutex()
        	#todo: 可填写其他操作步骤
	        # 启动音频录制线程
	        self.recorder.start()
	        self.recording_start_time = time.time()
	        
	        # 检查是否达到停止条件
	        while self.recorder.isRunning():
	            elapsed = time.time() - self.recording_start_time
	            self.update_signal.emit(f"已录制: {elapsed:.1f}秒")
	            
	            # 检查是否达到最大时长条件
	            if elapsed >= self.max_duration:
	                self.update_signal.emit(f"已达到最大录制时长({self.max_duration}秒),停止录制...")
	                self.stop_recording()
	                break
	                
	            # 休眠一段时间再检查
	            self.msleep(500)  # 每500毫秒检查一次
	        #todo: 可填写其他操作步骤    
	        self.update_signal.emit("控制线程结束")

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.control_thread = None
        self.init_ui()

    def init_ui(self):
        self.setWindowTitle('PyQt5 音频录制工具')
        self.setGeometry(100, 100, 600, 400)

        layout = QVBoxLayout()

        # 状态显示
        self.text_browser = QTextBrowser(self)
        layout.addWidget(self.text_browser)

        # 录制时长设置
        duration_layout = QHBoxLayout()
        duration_layout.addWidget(QLabel("录制时长(秒):"))
        self.duration_spinbox = QSpinBox()
        self.duration_spinbox.setRange(1, 3600)  # 1秒到1小时
        self.duration_spinbox.setValue(10)  # 默认10秒
        duration_layout.addWidget(self.duration_spinbox)
        duration_layout.addStretch()
        layout.addLayout(duration_layout)

        # 按钮布局
        button_layout = QHBoxLayout()
        
        self.start_button = QPushButton('开始录制', self)
        self.start_button.clicked.connect(self.start_recording)
        button_layout.addWidget(self.start_button)

        self.stop_button = QPushButton('停止录制', self)
        self.stop_button.clicked.connect(self.stop_recording)
        self.stop_button.setEnabled(False)
        button_layout.addWidget(self.stop_button)

        layout.addLayout(button_layout)

        central_widget = QWidget()
        central_widget.setLayout(layout)
        self.setCentralWidget(central_widget)

    def start_recording(self):
        """启动录制"""
        if self.control_thread is None or not self.control_thread.isRunning():
            max_duration = self.duration_spinbox.value()
            self.control_thread = ControlThread(max_duration)
            self.control_thread.update_signal.connect(self.update_text_browser)
            self.control_thread.start()
            
            self.start_button.setEnabled(False)
            self.stop_button.setEnabled(True)
            self.text_browser.append("已启动录制线程...")

    def stop_recording(self):
        """停止录制"""
        if self.control_thread and self.control_thread.isRunning():
            self.text_browser.append("正在停止录制...")
            self.control_thread.stop_recording()
            # 等待控制线程结束
            self.control_thread.wait()
            
            self.start_button.setEnabled(True)
            self.stop_button.setEnabled(False)

    def update_text_browser(self, message):
        """更新状态显示"""
        self.text_browser.append(message)

    def closeEvent(self, event):
        """确保窗口关闭时所有线程已停止"""
        if self.control_thread and self.control_thread.isRunning():
            self.control_thread.stop_recording()
            if not self.control_thread.wait(2000):  # 等待最多2秒
                self.control_thread.terminate()  # 强制终止
                self.control_thread.wait()
        event.accept()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    
    # 检查pyaudio是否可用
    try:
        p = pyaudio.PyAudio()
        p.terminate()
    except Exception as e:
        print(f"PyAudio初始化错误: {e}")
        print("请确保已安装PyAudio和PortAudio")
        sys.exit(1)
    
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())
相关推荐
牛油果子哥q2 分钟前
C++序列式容器深度精讲:vector/list/deque底层实现、扩容原理、迭代器失效、性能对比、工程选型避坑
开发语言·c++·list
渡我白衣15 分钟前
Acceptor模块的设计与实现
java·linux·服务器·开发语言·网络·c++·人工智能
沫璃染墨28 分钟前
《从零入门Linux系统篇(二十八):文件篇·一——Linux为什么“万物皆文件”:从C语言文件流到系统调用》
linux·运维·服务器·开发语言·c++·文件
luteres2 小时前
Mybatis+SSM整合笔记
java·开发语言·mybatis
郝学胜-神的一滴2 小时前
《C++11 工程级应用01:告别冗长类型,开启简洁高效编码新时代》深度解读
开发语言·c++·算法·软件开发·系统设计
其实防守也摸鱼2 小时前
推荐一个自动化教育SRC漏洞挖掘系统--AutoHunter
运维·开发语言·人工智能·学习·安全·web安全·自动化
覆东流9 小时前
7.Java数组
java·开发语言·后端
码行山野赴时序归途9 小时前
三道经典数组题:从暴力到最优的算法思维
c语言·开发语言·数据结构·算法·leetcode
lzhdim9 小时前
提高 SQL 语句执行速度的方法
java·开发语言·数据库·sql·oracle
JavaPub-rodert9 小时前
王仕宇在 Go Context 如何解决协程泄漏与超时控制
开发语言·后端·golang·iphone·javapub·王仕宇