用Python-pyqt5制作一个可以打开音频文件,一边播放音频一边同时显示波形和FFT波形两个窗口的,带按键的多线程程序
时间: 2024-05-14 07:12:45 浏览: 15
这是一个基本的Python-pyqt5多线程程序,它可以打开音频文件,一边播放音频一边同时显示波形和FFT波形两个窗口,并带有按键操作。
我们首先需要导入必要的模块和库:
```python
import sys
import os
import wave
import struct
import numpy as np
import matplotlib.pyplot as plt
import threading
import time
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import Qt, QThread, pyqtSignal
from PyQt5.QtWidgets import QApplication, QMainWindow, QFileDialog, QMessageBox
```
接下来,我们需要定义一个Thread类,用于处理音频文件的读取和波形绘制:
```python
class WorkerThread(QThread):
plot_signal = pyqtSignal(np.ndarray)
fft_signal = pyqtSignal(np.ndarray)
status_signal = pyqtSignal(str)
def __init__(self, filename):
super().__init__()
self.filename = filename
def run(self):
wf = wave.open(self.filename, 'rb')
# 获取音频文件的参数
channels = wf.getnchannels()
sample_width = wf.getsampwidth()
framerate = wf.getframerate()
nframes = wf.getnframes()
# 读取音频数据
data = wf.readframes(nframes)
# 将数据转换为numpy数组
data = np.frombuffer(data, dtype='<i2')
# 如果是双声道,则转换为左右声道分别存储
if channels == 2:
left = data[::2]
right = data[1::2]
else:
left = data
right = None
# 计算时间轴
time_axis = np.arange(0, nframes) / framerate
# 发送绘制波形的信号
self.plot_signal.emit(left)
# 计算FFT
fft_size = 2 ** int(np.ceil(np.log2(nframes)))
freq_axis = np.fft.fftfreq(fft_size, 1 / framerate)[:fft_size // 2]
fft_data = np.fft.fft(left, fft_size)[:fft_size // 2]
fft_data = np.abs(fft_data)
# 发送绘制FFT波形的信号
self.fft_signal.emit(fft_data)
# 发送状态信号
self.status_signal.emit('Ready')
```
我们定义了一个WorkerThread类,它继承自QThread类,并具有三个信号:plot_signal,fft_signal和status_signal。plot_signal和fft_signal将分别在波形和FFT波形窗口中绘制波形和FFT波形,而status_signal将用于在状态栏中显示应用程序的状态。
在run方法中,我们首先打开音频文件并获取其参数。接下来,我们读取音频数据并将其转换为numpy数组。如果音频文件是双声道,则将其转换为左右声道分别存储。然后,我们计算时间轴并发送plot_signal信号以绘制波形。
接下来,我们计算FFT,并发送fft_signal信号以绘制FFT波形。最后,我们发送status_signal信号以显示“Ready”状态。
下一步是创建主窗口并添加必要的控件。我们添加了一个菜单栏、一个状态栏和两个工具栏。菜单栏包含“文件”菜单,状态栏用于显示应用程序的状态,如“Ready”和“Processing”,工具栏包含打开和播放按钮。
```python
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
# 设置窗口标题和大小
self.setWindowTitle('Audio Player')
self.setGeometry(100, 100, 800, 600)
# 添加菜单栏
menu_bar = self.menuBar()
file_menu = menu_bar.addMenu('文件')
# 添加状态栏
status_bar = self.statusBar()
status_bar.showMessage('Ready')
# 添加工具栏
toolbar = self.addToolBar('工具栏')
# 添加打开按钮
open_button = QtWidgets.QAction(QtGui.QIcon('open.png'), '打开', self)
open_button.setShortcut('Ctrl+O')
open_button.triggered.connect(self.open_file)
toolbar.addAction(open_button)
# 添加播放按钮
play_button = QtWidgets.QAction(QtGui.QIcon('play.png'), '播放', self)
play_button.setShortcut('Ctrl+P')
play_button.triggered.connect(self.play_file)
toolbar.addAction(play_button)
# 添加停止按钮
stop_button = QtWidgets.QAction(QtGui.QIcon('stop.png'), '停止', self)
stop_button.setShortcut('Ctrl+S')
stop_button.triggered.connect(self.stop_file)
toolbar.addAction(stop_button)
# 添加分割线
toolbar.addSeparator()
# 添加退出按钮
exit_button = QtWidgets.QAction(QtGui.QIcon('exit.png'), '退出', self)
exit_button.setShortcut('Ctrl+Q')
exit_button.triggered.connect(self.close)
toolbar.addAction(exit_button)
# 添加波形窗口
self.plot_widget = PlotWidget(self)
self.setCentralWidget(self.plot_widget)
# 添加FFT波形窗口
self.fft_plot_widget = PlotWidget(self)
self.fft_plot_widget.setMaximumHeight(100)
self.fft_plot_widget.setMinimumHeight(100)
self.addToolBar(Qt.BottomToolBarArea, self.fft_plot_widget)
```
我们创建了一个MainWindow类,并在其中添加了一个菜单栏、一个状态栏和两个工具栏。我们还添加了一个PlotWidget类,用于绘制波形和FFT波形。波形窗口被设置为中心窗口,而FFT波形窗口被设置为底部工具栏。
下一步是实现打开和播放音频文件的方法:
```python
def open_file(self):
# 打开文件对话框
filename, _ = QFileDialog.getOpenFileName(self, '打开文件', '.', 'WAV files (*.wav)')
# 如果选择了文件,则开始处理
if filename:
self.statusBar().showMessage('Processing...')
self.thread = WorkerThread(filename)
self.thread.plot_signal.connect(self.plot_widget.plot)
self.thread.fft_signal.connect(self.fft_plot_widget.plot)
self.thread.status_signal.connect(self.statusBar().showMessage)
self.thread.start()
def play_file(self):
if hasattr(self, 'thread'):
self.thread.status_signal.emit('Playing...')
```
我们使用QFileDialog打开文件对话框,让用户选择要打开的文件。如果用户选择了文件,则创建一个WorkerThread对象并连接plot_signal、fft_signal和status_signal信号以更新波形和状态。
当用户点击播放按钮时,我们发送一个“Playing”状态信号。这只是一个演示,实际上我们需要使用第三方库来播放音频文件。
最后,我们实现了停止方法:
```python
def stop_file(self):
if hasattr(self, 'thread'):
self.thread.terminate()
self.statusBar().showMessage('Ready')
```
当用户点击停止按钮时,我们终止WorkerThread线程并将状态栏设置为“Ready”。
完整的代码如下: