单击录音按钮后,PyQt6 和 pyaudio 的音频录音停止工作

Audio recording with PyQt6 and pyaudio stops working after clicking recording button

我想制作一个带有录制和停止按钮的程序,并在顶部显示一个标签,以显示它是仍在录制还是已完成录制。 我从another question中获取了基本的录音代码结构,我重写它的方式可能有错误。

现在我的问题是:寡妇正在打开它看起来像我想要的样子,但是只要我点击 'record' 按钮后的任何东西,程序就会自行挂起 (标签更改为 'recording...')。有一次我也等了大约 10 分钟,看看我是不是太不耐烦了 (那不是问题) 直到 Windows 说 'Python is not responding' 因为我在 window.

上点击了退出按钮/太多次

before clicking -> recording, stop button clicked

正在使用:Python 3.10.1 & VSCode 1.63.2

任何帮助将不胜感激!

from PyQt6.QtWidgets import *
from PyQt6.QtGui import *
import pyaudio as pa
import wave
import sys


class Window(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Rec Audio")
        self.stoped = False

        vbox = QVBoxLayout()

        self.labelRec = QLabel('')
        self.labelRec.setFixedSize(130, 15)

        hbox = QHBoxLayout()
        self.recbtn = QPushButton('▶ record')
        self.recbtn.setFixedSize(90, 30)
        self.recbtn.clicked.connect(self.recAudio)

        self.stopbtn = QPushButton('▪')
        self.stopbtn.setFixedSize(40, 30)
        self.stopbtn.clicked.connect(self.stopRec)

        hbox.addWidget(self.recbtn)
        hbox.addWidget(self.stopbtn)

        vbox.addWidget(self.labelRec)
        vbox.addLayout(hbox)

        self.setLayout(vbox)

    def recAudio(self):
        audio = pa.PyAudio()
        frames = []
        stream = audio.open(format=pa.paInt16, channels=1, rate=44100, input=True, frames_per_buffer=1024)
        self.stoped = False
        self.labelRec.setText('◉ recording...')
        self.repaint()

        while self.stoped == False:
            data = stream.read(1024)
            frames.append(data)

        stream.close()
    
        self.labelRec.setText('recording stopped')

        wf = wave.open('test_recording.wav', 'wb')
        wf.setnchannels(1)
        wf.setsampwidth(audio.get_sample_size(pa.paInt16))
        wf.setframerate(44100)
        wf.writeframes(b''.join(frames))
        wf.close()

    def stopRec(self):
        self.stoped = True

app = QApplication([])
win = Window()
win.show()
sys.exit(app.exec())

您的解决方案中的问题是由于您在与 GUI 相同的线程中录制音频造成的。
然后当你的代码到达录音部分时:

while self.stoped == False:
    data = stream.read(1024)
    frames.append(data)

GUI 卡住了,没有任何帮助,因为 GUI 无法处理其他任何东西。

PyQt 提供了非常好的机制来处理称为信号和槽的事件,我建议您阅读更多相关内容。这不是一个容易的话题,尤其是在开始的时候,所以请耐心等待...

我修改了您之前的代码并使其按照您的预期运行:

import sys
import wave

import pyaudio as pa
from PyQt6.QtCore import QThread, pyqtSignal, pyqtSlot
from PyQt6.QtWidgets import *


class RecordingThread(QThread):
    stopped = False
    sig_started = pyqtSignal()
    sig_stopped = pyqtSignal()

    def __init__(self, target_file):
        self.target_file = target_file
        super().__init__()

    def run(self) -> None:
        audio = pa.PyAudio()
        frames = []
        stream = audio.open(format=pa.paInt16, channels=1, rate=44100, input=True, frames_per_buffer=1024)
        self.stopped = False
        self.sig_started.emit()

        while not self.stopped:
            data = stream.read(1024)
            frames.append(data)

        stream.close()

        self.sig_stopped.emit()

        wf = wave.open(self.target_file, 'wb')
        wf.setnchannels(1)
        wf.setsampwidth(audio.get_sample_size(pa.paInt16))
        wf.setframerate(44100)
        wf.writeframes(b''.join(frames))
        wf.close()

    @pyqtSlot()
    def stop(self):
        self.stopped = True


class Window(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Rec Audio")

        # Create recording thread and attach slots to its signals
        self.recording_thread = RecordingThread(target_file='test_recording.wav')
        self.recording_thread.sig_started.connect(self.recording_started)
        self.recording_thread.sig_stopped.connect(self.recording_stopped)

        vbox = QVBoxLayout()

        self.labelRec = QLabel('')
        self.labelRec.setFixedSize(130, 15)

        hbox = QHBoxLayout()
        self.recbtn = QPushButton('▶ record')
        self.recbtn.setFixedSize(90, 30)
        # Connect signal "recbtn.clicked" to the slot "recording_thread.start" of our QThread
        # Never connect directly to the run, always to start!
        self.recbtn.clicked.connect(self.recording_thread.start)

        self.stopbtn = QPushButton('▪')
        self.stopbtn.setDisabled(True)
        self.stopbtn.setFixedSize(40, 30)
        # Connect signal "stopbtn.clicked" to the slot "recording_thread.stop" of our QThread
        self.stopbtn.clicked.connect(self.recording_thread.stop)

        hbox.addWidget(self.recbtn)
        hbox.addWidget(self.stopbtn)

        vbox.addWidget(self.labelRec)
        vbox.addLayout(hbox)

        self.setLayout(vbox)

    @pyqtSlot()
    def recording_started(self):
        """This slot is called when recording starts"""
        self.labelRec.setText('◉ recording...')
        self.stopbtn.setDisabled(False)
        self.recbtn.setDisabled(True)

    @pyqtSlot()
    def recording_stopped(self):
        """This slot is called when recording stops"""
        self.labelRec.setText('recording stopped')
        self.recbtn.setDisabled(False)
        self.stopbtn.setDisabled(True)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    win = Window()
    win.show()
    app.exec()

不同之处在于我们现在使用 QThread,它将 运行 您的音频记录在单独的线程中并使您的 GUI 保持活动状态。
这个 QThread 还附加了两个信号 sig_startedsig_stopped.
为了通知我们的 Window 记录正在进行或停止,我们将这些信号连接到插槽 recording_startedrecording_stopped.

现在应该可以正确记录您的文件并将其存储到文件中。