用Python-pyqt5制作一个可以打开音频文件,一边播放音频一边同时显示波形和FFT波形两个窗口的,带按键的多线程程序
时间: 2024-05-16 19:11:54 浏览: 13
以下是一个Python-pyqt5制作的可以打开音频文件,一边播放音频一边同时显示波形和FFT波形两个窗口的,带按键的多线程程序的示例代码:
```python
import sys
import os
import threading
import time
import numpy as np
import wave
from scipy import fftpack
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QFileDialog, QVBoxLayout
from PyQt5.QtGui import QPainter, QPen
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QPointF
class AudioPlayer(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("Audio Player")
self.setGeometry(100, 100, 800, 600)
self.audio_file_path = None
self.audio_data = None
self.audio_data_size = None
self.audio_frames_per_second = None
self.audio_num_channels = None
self.audio_sample_width = None
self.audio_duration = None
self.play_button = QPushButton("Play")
self.play_button.clicked.connect(self.play_audio)
self.open_button = QPushButton("Open")
self.open_button.clicked.connect(self.open_audio_file)
self.fft_button = QPushButton("FFT")
self.fft_button.clicked.connect(self.show_fft)
self.waveform = Waveform()
self.fft_waveform = Waveform()
self.layout = QVBoxLayout()
self.layout.addWidget(self.play_button)
self.layout.addWidget(self.open_button)
self.layout.addWidget(self.fft_button)
self.layout.addWidget(self.waveform)
self.layout.addWidget(self.fft_waveform)
self.setLayout(self.layout)
self.player_thread = None
self.player_thread_exit_flag = False
self.fft_thread = None
self.fft_thread_exit_flag = False
def open_audio_file(self):
self.audio_file_path, _ = QFileDialog.getOpenFileName(self, "Open Audio File", os.getenv("HOME"), "Audio Files (*.wav)")
if self.audio_file_path:
self.read_audio_file()
self.waveform.set_data(self.audio_data, self.audio_frames_per_second, self.audio_num_channels)
self.fft_waveform.set_data(self.audio_data, self.audio_frames_per_second, self.audio_num_channels)
def read_audio_file(self):
with wave.open(self.audio_file_path, "rb") as wf:
self.audio_data_size = wf.getnframes()
self.audio_frames_per_second = wf.getframerate()
self.audio_num_channels = wf.getnchannels()
self.audio_sample_width = wf.getsampwidth()
self.audio_duration = self.audio_data_size / self.audio_frames_per_second
self.audio_data = np.frombuffer(wf.readframes(self.audio_data_size), dtype=np.int16)
def play_audio(self):
if not self.audio_file_path:
return
if self.player_thread and self.player_thread.is_alive():
self.player_thread_exit_flag = True
self.player_thread.join()
self.player_thread_exit_flag = False
self.play_button.setText("Play")
return
self.player_thread = threading.Thread(target=self.play_audio_thread)
self.player_thread.start()
self.play_button.setText("Stop")
def play_audio_thread(self):
audio = self.audio_data.copy()
p = pyaudio.PyAudio()
stream = p.open(format=p.get_format_from_width(self.audio_sample_width),
channels=self.audio_num_channels,
rate=self.audio_frames_per_second,
output=True)
chunk_size = 1024
chunk_index = 0
while chunk_index * chunk_size < len(audio) and not self.player_thread_exit_flag:
start_index = chunk_index * chunk_size
end_index = start_index + chunk_size
chunk = audio[start_index:end_index].tobytes()
stream.write(chunk)
chunk_index += 1
stream.stop_stream()
stream.close()
p.terminate()
self.play_button.setText("Play")
def show_fft(self):
if not self.audio_file_path:
return
if self.fft_thread and self.fft_thread.is_alive():
self.fft_thread_exit_flag = True
self.fft_thread.join()
self.fft_thread_exit_flag = False
self.fft_button.setText("FFT")
return
self.fft_thread = threading.Thread(target=self.show_fft_thread)
self.fft_thread.start()
self.fft_button.setText("Stop")
def show_fft_thread(self):
audio = self.audio_data.copy()
fft_size = 1024
fft_sample_rate = self.audio_frames_per_second // fft_size
fft_num_samples = len(audio) // fft_size * fft_size
audio = audio[:fft_num_samples].reshape(-1, fft_size)
fft_data = fftpack.fft(audio, axis=1)
fft_data = np.abs(fft_data[:, :fft_size//2])
self.fft_waveform.set_data(fft_data, fft_sample_rate, 1)
while not self.fft_thread_exit_flag:
time.sleep(0.1)
self.fft_button.setText("FFT")
class Waveform(QWidget):
def __init__(self):
super().__init__()
self.data = None
self.sample_rate = None
self.num_channels = None
def set_data(self, data, sample_rate, num_channels):
self.data = data
self.sample_rate = sample_rate
self.num_channels = num_channels
self.update()
def paintEvent(self, event):
if not self.data:
return
painter = QPainter(self)
painter.setRenderHint(QPainter.Antialiasing)
pen = QPen(Qt.green)
pen.setWidth(2)
painter.setPen(pen)
width = self.width()
height = self.height()
data_size = len(self.data)
step_size = data_size // width
scale_factor = height / (2 ** 16)
for channel in range(self.num_channels):
points = []
for i in range(width):
start_index = i * step_size
end_index = start_index + step_size
data_slice = self.data[start_index:end_index, channel]
mean_value = np.mean(data_slice)
y = height / 2 - mean_value * scale_factor
points.append(QPointF(i, y))
painter.drawPolyline(points)
if __name__ == "__main__":
app = QApplication(sys.argv)
audio_player = AudioPlayer()
audio_player.show()
sys.exit(app.exec_())
```
需要注意的几点:
1. 这个程序使用了多线程,一个用于播放音频,一个用于显示FFT波形。
2. 程序中使用了PyAudio和wave库来读取和播放音频。
3. Waveform类用于显示音频波形,FFT波形类似,只是把数据转换为FFT之后再绘制。