Chleo
Chleo

Reputation: 3

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

I want to make a program with a recording and a stop button, and a label on top to show if it is still recording or done recording. I took the basic recording-code-structure from another question, there probably could be a mistake in how I rewrote it.

Now to my problem: the widow is opening and it looks like how I want it to look, but as soon as I click anything aftrer the 'record' button the program hangs itself (with the label changed to 'recording...'). One time I also waited about 10 minutes to see if I wasn't just too impatient the times before (that was not the problem) it goes until Windows says 'Python is not responding' because I clicked the exit button / too many times on the window.

before clicking -> recording, stop button clicked

working with: Python 3.10.1 & VSCode 1.63.2

Any help will really be appreciated!

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

Upvotes: 0

Views: 831

Answers (1)

Domarm
Domarm

Reputation: 2550

The problem in Your solution was caused by the fact that You were recording audio within the same Thread as Your GUI.
Then when Your code get to the recording part:

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

GUI got stuck and nothing could help, because GUI couldn't process anything else.

PyQt provides very nice mechanism to handle events called signals and slots and I would recommend You to read more about it. It's not an easy topic, especially in the beginning, so be patient...

I modified Your earlier code and make it work as You intended to:

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

The difference is now that we use QThread which will run Your audio recording in separate Thread and enables Your GUI stay active.
This QThread has also two signals attached sig_started and sig_stopped.
To notify our Window that recording is ongoing or stopped we connect those signals to slots recording_started and recording_stopped.

This should now properly record Your file and store it into file.

Upvotes: 2

Related Questions