[Python] 纯文本查看 复制代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
希沃平板电脑音量电平显示程序
通过调用内置麦克风获取课室学生读书音量,用电平显示
电平强度从0到100用不同的颜色动态显示
"""
import sys
import numpy as np
import threading
import time
from datetime import datetime
import json
import os
try:
import pyaudio
PYAUDIO_AVAILABLE = True
except ImportError:
PYAUDIO_AVAILABLE = False
print("提示: pyaudio 未安装,将使用模拟数据模式")
print("如需使用真实麦克风,请安装: pip install pyaudio")
print("Windows用户可能需要从以下网址下载预编译版本:")
print("https://www.lfd.uci.edu/~gohlke/pythonlibs/#pyaudio")
try:
import tkinter as tk
from tkinter import ttk, messagebox
TKINTER_AVAILABLE = True
except ImportError:
TKINTER_AVAILABLE = False
print("警告: tkinter 未安装,将使用控制台输出")
try:
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
MATPLOTLIB_AVAILABLE = True
except ImportError:
MATPLOTLIB_AVAILABLE = False
print("警告: matplotlib 未安装,将使用简单图形")
class AudioRecorder:
"""音频录制器类"""
def __init__(self, rate=44100, chunk=1024, channels=1):
"""
初始化音频录制器
Args:
rate: 采样率 (Hz)
chunk: 每次读取的帧数
channels: 声道数
"""
self.rate = rate
self.chunk = chunk
self.channels = channels
self.recording = False
self.audio_data = []
self.current_volume = 0
self.volume_history = []
self.max_history_length = 100
# 音频增益,用于放大输入信号
self.gain = 5.0 # 默认增益为5倍
# 音频流
self.stream = None
self.p = None
def start_recording(self):
"""开始录制音频"""
if not PYAUDIO_AVAILABLE:
print("错误: pyaudio 未安装,无法使用真实麦克风")
print("请安装: pip install pyaudio")
print("Windows用户可能需要从以下网址下载预编译版本:")
print("https://www.lfd.uci.edu/~gohlke/pythonlibs/#pyaudio")
return False
try:
self.p = pyaudio.PyAudio()
self.stream = self.p.open(
format=pyaudio.paInt16,
channels=self.channels,
rate=self.rate,
input=True,
frames_per_buffer=self.chunk
)
self.recording = True
self.audio_data = []
self.volume_history = []
# 启动音频处理线程
self._audio_thread = threading.Thread(target=self._process_audio)
self._audio_thread.daemon = True
self._audio_thread.start()
print(f"音频录制已开始 (采样率: {self.rate}Hz, 块大小: {self.chunk})")
return True
except Exception as e:
print(f"启动音频录制失败: {e}")
print("请检查麦克风是否连接并可用")
return False
def stop_recording(self):
"""停止录制音频"""
self.recording = False
if self.stream:
self.stream.stop_stream()
self.stream.close()
if self.p:
self.p.terminate()
print("音频录制已停止")
def _process_audio(self):
"""处理音频数据"""
while self.recording and self.stream:
try:
# 读取音频数据
data = self.stream.read(self.chunk, exception_on_overflow=False)
# 转换为numpy数组
audio_array = np.frombuffer(data, dtype=np.int16)
# 计算音量 (RMS)
if len(audio_array) > 0:
rms = np.sqrt(np.mean(audio_array.astype(np.float32) ** 2))
# 转换为0-100的电平值
# 假设最大可能值为32767 (16位有符号整数)
max_possible = 32767.0
# 应用增益放大输入信号,确保即使小声也能有足够的显示范围
volume_percent = min(100, (rms / max_possible) * 100 * self.gain)
self.current_volume = volume_percent
# 保存历史数据
self.volume_history.append(volume_percent)
if len(self.volume_history) > self.max_history_length:
self.volume_history.pop(0)
# 保存原始音频数据(可选,用于分析)
self.audio_data.append(audio_array)
except Exception as e:
if self.recording: # 仅在仍然录制时打印错误
print(f"音频处理错误: {e}")
break
def get_current_volume(self):
"""获取当前音量"""
return self.current_volume
def get_volume_history(self):
"""获取音量历史"""
return self.volume_history.copy()
def get_average_volume(self, window=10):
"""获取平均音量"""
if not self.volume_history:
return 0
recent = self.volume_history[-window:] if len(self.volume_history) >= window else self.volume_history
return sum(recent) / len(recent)
def get_volume_level(self):
"""获取音量等级 (0-10)"""
volume = self.current_volume
if volume < 10:
return 0
elif volume < 20:
return 1
elif volume < 30:
return 2
elif volume < 40:
return 3
elif volume < 50:
return 4
elif volume < 60:
return 5
elif volume < 70:
return 6
elif volume < 80:
return 7
elif volume < 90:
return 8
elif volume < 95:
return 9
else:
return 10
class VolumeMeterGUI:
"""音量电平显示GUI"""
def __init__(self, recorder):
"""
初始化GUI
Args:
recorder: AudioRecorder实例
"""
self.recorder = recorder
self.root = None
self.canvas = None
self.running = False
self.use_simulation_var = None # 将在start方法中初始化
# 颜色定义 (从绿色渐变到红色,越接近峰值越红)
self.colors = [
"#00FF00", # 亮绿色 (0-10%)
"#33FF00", # 黄绿色 (10-20%)
"#66FF00", # 黄绿色 (20-30%)
"#99FF00", # 黄绿色 (30-40%)
"#CCFF00", # 黄绿色 (40-50%)
"#FFFF00", # 黄色 (50-60%)
"#FFCC00", # 橙黄色 (60-70%)
"#FF9900", # 橙色 (70-80%)
"#FF6600", # 橙红色 (80-90%)
"#FF3300", # 红色 (90-95%)
"#FF0000", # 亮红色 (95-100%)
]
# 音量等级标签
self.level_labels = [
"极静", # 0
"很静", # 1
"安静", # 2
"较静", # 3
"正常", # 4
"适中", # 5
"较响", # 6
"响亮", # 7
"很响", # 8
"极响", # 9
"爆表", # 10
]
def start(self):
"""启动GUI - 新版本:竖条吸附在屏幕右边"""
if not TKINTER_AVAILABLE:
print("错误: tkinter 不可用,无法启动GUI")
return False
self.root = tk.Tk()
self.root.title("音量电平显示器 v1.0 - by:任我行电脑工作室")
# 设置窗口图标
try:
self.root.iconbitmap("icon.ico")
except Exception as e:
print(f"警告: 无法加载图标文件: {e}")
# 设置窗口无边框,置顶显示
self.root.overrideredirect(True) # 无边框
self.root.attributes('-topmost', True) # 置顶
# 获取屏幕尺寸
screen_width = self.root.winfo_screenwidth()
screen_height = self.root.winfo_screenheight()
# 计算窗口位置和大小
window_width = 180 # 窗口宽度增加到180,确保右侧数值能完整显示
window_height = int(screen_height * 0.8) # 屏幕高度的80%
window_x = screen_width - window_width # 最右边
window_y = (screen_height - window_height) // 2 # 垂直居中
# 设置窗口位置和大小
self.root.geometry(f"{window_width}x{window_height}+{window_x}+{window_y}")
# 设置窗口背景为黑色
self.root.configure(bg='black')
# 创建主画布
self.canvas = tk.Canvas(self.root, width=window_width, height=window_height, bg='black', highlightthickness=0)
self.canvas.pack(fill=tk.BOTH, expand=True)
# 绑定右键点击事件
self.canvas.bind("<Button-3>", self.show_settings_dialog) # 右键点击
self.canvas.bind("<Button-1>", self.toggle_recording) # 左键点击切换监测状态
# 初始化显示变量
self.volume_var = tk.StringVar(value="0.0")
self.level_var = tk.StringVar(value="极静")
self.status_var = tk.StringVar(value="就绪")
self.sensitivity_var = tk.DoubleVar(value=1.0) # 默认灵敏度改为1.0
self.use_simulation_var = tk.BooleanVar(value=False) # 音频模式(只使用真实麦克风)
# 绘制初始竖条
self.draw_vertical_meter(0)
# 启动更新循环
self.running = True
self.update_display()
# 自动开始检测电平
self.auto_start_recording()
# 启动主循环
self.root.mainloop()
return True
def toggle_recording(self, event=None):
"""切换录制状态(左键点击触发)"""
if not self.recorder.recording:
# 开始录制
if self.recorder.start_recording():
self.log("开始监测课室音量")
else:
self.log("错误: 无法启动音频监测")
else:
# 停止录制
self.recorder.stop_recording()
self.log("停止监测课室音量")
def show_settings_dialog(self, event):
"""显示设置对话框(右键点击触发)"""
# 创建设置对话框
settings_window = tk.Toplevel(self.root)
settings_window.title("设置")
settings_window.geometry("300x480")
settings_window.resizable(False, False)
# 设置对话框图标
try:
settings_window.iconbitmap("icon.ico")
except Exception as e:
print(f"警告: 无法加载对话框图标文件: {e}")
# 使对话框模态
settings_window.transient(self.root)
settings_window.grab_set()
# 居中显示
settings_window.update_idletasks()
width = settings_window.winfo_width()
height = settings_window.winfo_height()
x = (settings_window.winfo_screenwidth() // 2) - (width // 2)
y = (settings_window.winfo_screenheight() // 2) - (height // 2)
settings_window.geometry(f"{width}x{height}+{x}+{y}")
# 设置对话框内容
ttk.Label(settings_window, text="音量电平显示器设置", font=("Arial", 14, "bold")).pack(pady=10)
# 灵敏度设置
sensitivity_frame = ttk.LabelFrame(settings_window, text="灵敏度设置", padding=10)
sensitivity_frame.pack(fill="x", padx=10, pady=5)
# 创建水平布局框架
sens_row_frame = ttk.Frame(sensitivity_frame)
sens_row_frame.pack(fill="x", pady=5)
ttk.Label(sens_row_frame, text="灵敏度:").pack(side="left", padx=(0, 10))
# 灵敏度滑块
sensitivity_scale = ttk.Scale(
sens_row_frame,
from_=0.1,
to=3.0,
variable=self.sensitivity_var,
orient=tk.HORIZONTAL,
length=150
)
sensitivity_scale.pack(side="left", padx=(0, 10))
# 灵敏度值显示(在滑块旁边)
sens_value_var = tk.StringVar(value=f"{self.sensitivity_var.get():.1f}x")
sens_value_label = ttk.Label(sens_row_frame, textvariable=sens_value_var, width=6, anchor="center")
sens_value_label.pack(side="left")
def update_sens_label(val):
sens_value_var.set(f"{float(val):.1f}x")
sensitivity_scale.configure(command=update_sens_label)
# 音频模式信息
audio_frame = ttk.LabelFrame(settings_window, text="音频输入", padding=10)
audio_frame.pack(fill="x", padx=10, pady=5)
if PYAUDIO_AVAILABLE:
mode_text = "当前模式: 真实麦克风"
detail_text = "使用硬件麦克风采集真实音频"
else:
mode_text = "警告: pyaudio 未安装"
detail_text = "请安装pyaudio以使用真实麦克风"
audio_info = ttk.Label(
audio_frame,
text=f"{mode_text}\n{detail_text}",
justify="left"
)
audio_info.pack(anchor="w")
if not PYAUDIO_AVAILABLE:
install_button = ttk.Button(
audio_frame,
text="安装 pyaudio",
command=self.install_pyaudio,
width=15
)
install_button.pack(pady=(10, 0))
# 显示设置
display_frame = ttk.LabelFrame(settings_window, text="显示设置", padding=10)
display_frame.pack(fill="x", padx=10, pady=5)
# 窗口透明度
opacity_row_frame = ttk.Frame(display_frame)
opacity_row_frame.pack(fill="x", pady=5)
ttk.Label(opacity_row_frame, text="透明度:").pack(side="left", padx=(0, 10))
opacity_var = tk.DoubleVar(value=0.9)
opacity_scale = ttk.Scale(
opacity_row_frame,
from_=0.3,
to=1.0,
variable=opacity_var,
orient=tk.HORIZONTAL,
length=150
)
opacity_scale.pack(side="left", padx=(0, 10))
opacity_value_label = ttk.Label(opacity_row_frame, text=f"{opacity_var.get():.1f}", width=6, anchor="center")
opacity_value_label.pack(side="left")
def update_opacity(val):
opacity = float(val)
opacity_value_label.config(text=f"{opacity:.1f}")
self.root.attributes('-alpha', opacity)
opacity_scale.configure(command=update_opacity)
# 控制按钮
button_frame = ttk.Frame(settings_window)
button_frame.pack(fill="x", padx=10, pady=20)
# 重置按钮
reset_button = ttk.Button(
button_frame,
text="重置数据",
command=self.reset_display,
width=12
)
reset_button.pack(side="left", padx=5)
# 退出按钮
exit_button = ttk.Button(
button_frame,
text="退出程序",
command=self.exit_program,
width=12
)
exit_button.pack(side="right", padx=5)
# 关闭按钮
close_button = ttk.Button(
button_frame,
text="关闭",
command=settings_window.destroy,
width=12
)
close_button.pack(side="right", padx=5)
# 信息标签
info_frame = ttk.LabelFrame(settings_window, text="程序信息", padding=10)
info_frame.pack(fill="x", padx=10, pady=5)
info_text = """音量电平显示器 v1.0
by:任我行电脑工作室
功能说明:
• 左键点击: 开始/停止监测
• 右键点击: 打开设置
• 竖条显示实时音量
• 颜色随音量变化
作者: CodeArts代码智能体"""
ttk.Label(info_frame, text=info_text, justify="left").pack(anchor="w")
def auto_start_recording(self):
"""自动开始录制(程序启动时调用)"""
if not self.recorder.recording:
if self.recorder.start_recording():
self.log("程序启动,自动开始监测课室音量")
self.log(f"默认灵敏度: {self.sensitivity_var.get():.1f}x")
else:
self.log("警告: 自动启动监测失败")
def reset_display(self):
"""重置显示"""
self.recorder.volume_history = []
self.log("显示已重置")
def exit_program(self):
"""退出程序"""
self.running = False
if self.recorder.recording:
self.recorder.stop_recording()
if self.root:
self.root.quit()
self.root.destroy()
def install_pyaudio(self):
"""安装pyaudio依赖"""
import subprocess
import sys
import tkinter.messagebox as messagebox
try:
# 显示安装提示
messagebox.showinfo("安装pyaudio", "正在安装pyaudio,请稍候...")
# 安装pyaudio
result = subprocess.run(
[sys.executable, "-m", "pip", "install", "pyaudio"],
capture_output=True,
text=True,
check=True
)
if result.returncode == 0:
messagebox.showinfo("安装成功", "pyaudio安装成功!\n请重启程序以使用真实麦克风。")
# 退出程序,让用户重新启动
self.exit_program()
else:
messagebox.showerror("安装失败", f"pyaudio安装失败:\n{result.stderr}")
except subprocess.CalledProcessError as e:
messagebox.showerror("安装错误", f"安装过程中出错:\n{e.stderr}")
except Exception as e:
messagebox.showerror("错误", f"无法安装pyaudio:\n{str(e)}")
def update_display(self):
"""更新显示"""
if not self.running or not self.root:
return
try:
# 获取当前音量
volume = self.recorder.get_current_volume()
# 电平显示不受灵敏度调整影响,直接使用原始音量
adjusted_volume = min(100, volume)
# 获取音量等级
level = self.recorder.get_volume_level()
level = min(level, 10) # 确保不超过10
# 更新等级显示
self.level_var.set(self.level_labels[level])
# 绘制垂直音量条
self.draw_vertical_meter(adjusted_volume)
# 100ms后再次更新
self.root.after(100, self.update_display)
except Exception as e:
print(f"更新显示时出错: {e}")
self.root.after(100, self.update_display)
def draw_vertical_meter(self, volume):
"""绘制垂直音量条(吸附在屏幕右边)"""
if not self.canvas:
return
# 清空画布
self.canvas.delete("all")
# 获取画布尺寸
canvas_width = self.canvas.winfo_width() or 120
canvas_height = self.canvas.winfo_height() or 600
# 计算竖条参数
bar_width = 40 # 竖条宽度
bar_margin = 20 # 边距
bar_x = (canvas_width - bar_width) // 2 # 水平居中
# 计算竖条高度(基于音量百分比)
bar_height = int((volume / 100) * (canvas_height - 2 * bar_margin))
bar_y_bottom = canvas_height - bar_margin
bar_y_top = bar_y_bottom - bar_height
# 计算颜色索引
level = self.recorder.get_volume_level()
level = min(level, 10) # 确保不超过10
color = self.colors[level]
# 绘制背景(深灰色)
self.canvas.create_rectangle(
0, 0, canvas_width, canvas_height,
fill="#1a1a1a", outline=""
)
# 绘制竖条背景(浅灰色)
self.canvas.create_rectangle(
bar_x, bar_margin,
bar_x + bar_width, canvas_height - bar_margin,
fill="#333333", outline="#666666", width=2
)
# 绘制音量填充条
if volume > 0:
self.canvas.create_rectangle(
bar_x, bar_y_top,
bar_x + bar_width, bar_y_bottom,
fill=color, outline=color, width=2
)
# 绘制刻度线
for i in range(0, 101, 10):
y_pos = bar_margin + (100 - i) * (canvas_height - 2 * bar_margin) / 100
# 主要刻度(每20%)
if i % 20 == 0:
self.canvas.create_line(
bar_x - 10, y_pos, bar_x + bar_width + 10, y_pos,
fill="white", width=2
)
# 刻度标签
self.canvas.create_text(
bar_x + bar_width + 30, y_pos, # 从25增加到30,确保有足够空间
text=f"{i}%",
fill="white",
font=("Arial", 10, "bold"),
anchor="w"
)
# 次要刻度(每10%)
else:
self.canvas.create_line(
bar_x - 5, y_pos, bar_x + bar_width + 5, y_pos,
fill="#888888", width=1
)
# 显示当前音量数值(在竖条顶部)
if volume > 0:
self.canvas.create_text(
canvas_width // 2, bar_y_top - 20,
text=f"{volume:.1f}%",
fill="white",
font=("Arial", 14, "bold")
)
# 显示音量等级(在竖条底部)
level_text = self.level_labels[level]
self.canvas.create_text(
canvas_width // 2, canvas_height - bar_margin + 25,
text=level_text,
fill=color,
font=("Arial", 12, "bold")
)
# 显示状态(在顶部)
status_text = "监测中" if self.recorder.recording else "已停止"
status_color = "#00FF00" if self.recorder.recording else "#FF0000"
self.canvas.create_text(
canvas_width // 2, 15,
text=status_text,
fill=status_color,
font=("Arial", 10)
)
# 显示操作提示
self.canvas.create_text(
canvas_width // 2, canvas_height - 10,
text="左键: 开始/停止 | 右键: 设置",
fill="#888888",
font=("Arial", 8)
)
def log(self, message):
"""添加日志消息(简化版,只打印到控制台)"""
timestamp = datetime.now().strftime("%H:%M:%S")
print(f"[{timestamp}] {message}")
def log(self, message):
"""添加日志消息(简化版,只打印到控制台)"""
timestamp = datetime.now().strftime("%H:%M:%S")
print(f"[{timestamp}] {message}")
def check_dependencies():
"""检查依赖"""
missing = []
if not PYAUDIO_AVAILABLE:
missing.append("pyaudio (用于音频录制)")
print("提示: 安装 pyaudio: pip install pyaudio")
if not TKINTER_AVAILABLE:
missing.append("tkinter (用于GUI界面)")
print("提示: tkinter 通常随Python一起安装")
if not MATPLOTLIB_AVAILABLE:
missing.append("matplotlib (用于图表显示)")
print("提示: 安装 matplotlib: pip install matplotlib")
if missing:
print(f"缺少依赖: {', '.join(missing)}")
print("程序将在有限功能下运行")
return len(missing) == 0
def main():
"""主函数"""
print("=" * 60)
print("音量电平显示器 v1.0")
print("by:任我行电脑工作室")
print("用于监测课室学生读书音量")
print("=" * 60)
# 检查依赖
all_deps_available = check_dependencies()
if not all_deps_available:
print("\n警告: 部分依赖不可用,程序功能可能受限")
print("建议安装所有依赖以获得完整功能")
print("将在3秒后自动继续...")
import time
time.sleep(3)
# 自动继续运行
# 创建音频录制器(只使用真实麦克风模式)
recorder = AudioRecorder()
# 创建GUI
gui = VolumeMeterGUI(recorder)
print("\n启动GUI界面...")
print("使用说明:")
print("1. 点击'开始监测'按钮开始采集音频")
print("2. 调整灵敏度滑块以改变音量检测灵敏度")
print("3. 点击'重置'按钮清除历史数据")
print("4. 点击'退出'按钮结束程序")
print("\n颜色说明:")
print("绿色(0-50%): 音量较低")
print("黄色(50-60%): 音量适中")
print("橙色(60-90%): 音量较高")
print("红色(90-100%): 音量过高")
# 启动GUI
try:
gui.start()
except Exception as e:
print(f"启动GUI时出错: {e}")
print("尝试使用控制台模式...")
run_console_mode(recorder)
print("程序结束")
def run_console_mode(recorder):
"""控制台模式"""
print("\n控制台模式启动")
print("按 Ctrl+C 退出")
try:
# 开始录制
if recorder.start_recording():
print("音频监测已开始")
# 显示音量
import time
while True:
volume = recorder.get_current_volume()
level = recorder.get_volume_level()
level_text = recorder.get_volume_level()
# 创建简单的ASCII电平条
bar_length = 20
filled = int((volume / 100) * bar_length)
bar = "█" * filled + "░" * (bar_length - filled)
# 选择颜色代码
if level <= 3:
color_code = "\033[92m" # 绿色
elif level <= 6:
color_code = "\033[93m" # 黄色
elif level <= 8:
color_code = "\033[91m" # 红色
else:
color_code = "\033[91;1m" # 亮红色
# 清屏并显示
print("\033[2J\033[H") # 清屏并移动光标到左上角
print("课室音量监测 (控制台模式)")
print("=" * 40)
print(f"当前音量: {color_code}{volume:6.1f}%\033[0m")
print(f"音量等级: {color_code}{level_text}\033[0m")
print(f"电平条: {color_code}{bar}\033[0m")
print("=" * 40)
print("颜色说明:")
print("\033[92m绿色: 安静\033[0m")
print("\033[93m黄色: 适中\033[0m")
print("\033[91m红色: 响亮\033[0m")
print("\033[91;1m亮红: 过响\033[0m")
print("\n按 Ctrl+C 退出")
time.sleep(0.1) # 100ms更新一次
except KeyboardInterrupt:
print("\n\n停止监测...")
finally:
recorder.stop_recording()
print("程序结束")
if __name__ == "__main__":
main()