importwarningsimporttkinterastkfromtkinterimportttk,messagebox,filedialogimportsoundcardasscimportsoundfileassfimportnumpyasnpimportthreadingimporttime# 屏蔽无关警告warnings.filterwarnings(ignore,categorysc.SoundcardRuntimeWarning)classAudioRecorderApp:def__init__(self,root):self.rootroot self.root.title(多功能录音机 - 系统内录麦克风)self.root.geometry(500x400)self.root.resizable(False,False)# 核心变量self.is_recordingFalse# 是否正在录音self.is_pausedFalse# 是否暂停self.recorded_data[]# 录制的数据self.record_threadNone# 录音线程self.samplerate44100# 采样率self.selected_deviceNone# 选中的录音设备self.record_typetk.StringVar(valuesystem)# 录音类型system(内录)/mic(麦克风)# 初始化UIself._init_ui()# 加载设备列表self._load_devices()def_init_ui(self):# 1. 录音类型选择type_framettk.LabelFrame(self.root,text录音类型)type_frame.pack(padx20,pady10,fillx)ttk.Radiobutton(type_frame,text系统内录,variableself.record_type,valuesystem,commandself._on_record_type_change).pack(sideleft,padx20,pady5)ttk.Radiobutton(type_frame,text麦克风录音,variableself.record_type,valuemic,commandself._on_record_type_change).pack(sideleft,padx20,pady5)# 2. 设备选择device_framettk.LabelFrame(self.root,text录音设备)device_frame.pack(padx20,pady10,fillx)ttk.Label(device_frame,text选择设备).pack(sideleft,padx10,pady5)self.device_comboboxttk.Combobox(device_frame,statereadonly,width30)self.device_combobox.pack(sideleft,padx10,pady5)self.device_combobox.bind(ComboboxSelected,self._on_device_selected)# 3. 录制时长设置time_framettk.LabelFrame(self.root,text录制设置)time_frame.pack(padx20,pady10,fillx)ttk.Label(time_frame,text录制时长(秒)).pack(sideleft,padx10,pady5)self.time_entryttk.Entry(time_frame,width10)self.time_entry.insert(0,5)# 默认5秒self.time_entry.pack(sideleft,padx5,pady5)ttk.Label(time_frame,text0为无限时长手动停止).pack(sideleft,padx5,pady5)# 4. 状态显示status_framettk.LabelFrame(self.root,text录制状态)status_frame.pack(padx20,pady10,fillx)self.status_labelttk.Label(status_frame,text未开始录制,foregroundgray)self.status_label.pack(padx10,pady10)# 5. 控制按钮btn_framettk.Frame(self.root)btn_frame.pack(padx20,pady20,fillx)self.start_btnttk.Button(btn_frame,text开始录制,commandself.start_recording)self.start_btn.pack(sideleft,padx10,pady5,fillx,expandTrue)self.pause_btnttk.Button(btn_frame,text暂停,commandself.pause_recording,statedisabled)self.pause_btn.pack(sideleft,padx10,pady5,fillx,expandTrue)self.stop_btnttk.Button(btn_frame,text停止录制,commandself.stop_recording,statedisabled)self.stop_btn.pack(sideleft,padx10,pady5,fillx,expandTrue)self.save_btnttk.Button(btn_frame,text保存录音,commandself.save_recording,statedisabled)self.save_btn.pack(sideleft,padx10,pady5,fillx,expandTrue)def_load_devices(self):加载录音设备列表根据录音类型ifself.record_type.get()system:# 系统内录加载扬声器设备devicessc.all_speakers()else:# 麦克风录音加载麦克风设备devicessc.all_microphones()# 提取设备名device_names[dev.namefordevindevices]self.device_combobox[values]device_namesifdevice_names:self.device_combobox.current(0)self.selected_devicedevices[0]def_on_record_type_change(self):切换录音类型时重新加载设备列表self._load_devices()self.status_label.config(text切换录音类型请重新选择设备,foregroundblue)def_on_device_selected(self,event):选择设备后更新选中的设备selected_idxself.device_combobox.current()ifself.record_type.get()system:self.selected_devicesc.all_speakers()[selected_idx]else:self.selected_devicesc.all_microphones()[selected_idx]self.status_label.config(textf已选中设备{self.selected_device.name},foregroundgreen)def_record_task(self):后台录音线程任务try:# 获取录制时长try:record_secondsint(self.time_entry.get())exceptValueError:messagebox.showerror(错误,录制时长请输入数字)self.is_recordingFalsereturn# 创建录音器ifself.record_type.get()system:# 系统内录开启loopbackmicsc.get_microphone(idstr(self.selected_device.name),include_loopbackTrue)else:# 麦克风录音普通麦克风micsc.get_microphone(idstr(self.selected_device.name))withmic.recorder(samplerateself.samplerate,blocksize1024)asrecorder:self.recorded_data[]total_framesself.samplerate*record_secondsifrecord_seconds0elsefloat(inf)recorded_frames0self.status_label.config(text正在录制...,foregroundred)whileself.is_recordingandrecorded_framestotal_frames:ifnotself.is_paused:# 每次录制1024帧chunkrecorder.record(numframes1024)self.recorded_data.append(chunk)recorded_frameslen(chunk)# 更新录制时长显示elapsedrecorded_frames/self.samplerate self.status_label.config(textf正在录制... 已录制{elapsed:.1f}秒,foregroundred)else:time.sleep(0.1)# 暂停时休眠# 录制结束self.is_recordingFalseself.root.after(0,lambda:self.status_label.config(textf录制完成总时长{recorded_frames/self.samplerate:.1f}秒,foregroundgreen))self.root.after(0,lambda:self._update_btn_state(after_stopTrue))exceptExceptionase:self.root.after(0,lambda:messagebox.showerror(录制错误,f录音失败{str(e)}))self.root.after(0,lambda:self.status_label.config(text录制失败,foregroundred))self.is_recordingFalseself.root.after(0,lambda:self._update_btn_state(after_stopTrue))defstart_recording(self):开始录制ifnotself.selected_device:messagebox.showwarning(警告,请先选择录音设备)returnifself.is_recordingandnotself.is_paused:messagebox.showinfo(提示,已经在录制中了)returnifself.is_paused:# 恢复暂停self.is_pausedFalseself.status_label.config(text恢复录制...,foregroundred)self.pause_btn.config(text暂停)return# 开始新的录制self.is_recordingTrueself.is_pausedFalseself._update_btn_state(after_startTrue)# 启动后台录音线程self.record_threadthreading.Thread(targetself._record_task,daemonTrue)self.record_thread.start()defpause_recording(self):暂停/恢复录制ifnotself.is_recording:returnself.is_pausednotself.is_pausedifself.is_paused:self.status_label.config(text录制已暂停,foregroundorange)self.pause_btn.config(text恢复)else:self.status_label.config(text恢复录制...,foregroundred)self.pause_btn.config(text暂停)defstop_recording(self):停止录制ifself.is_recording:self.is_recordingFalseself.status_label.config(text正在停止录制...,foregroundorange)# 等待线程结束ifself.record_thread:self.record_thread.join(timeout2)self._update_btn_state(after_stopTrue)self.status_label.config(text录制已停止,foregroundgreen)defsave_recording(self):保存录制的音频ifnotself.recorded_data:messagebox.showwarning(警告,没有可保存的录音数据)return# 选择保存路径file_pathfiledialog.asksaveasfilename(defaultextension.wav,filetypes[(WAV音频文件,*.wav),(所有文件,*.*)],title保存录音)ifnotfile_path:returntry:# 合并录制的数据datanp.concatenate(self.recorded_data,axis0)# 立体声转单声道兼容更多播放器datadata.mean(axis1)# 保存为WAV文件sf.write(file_path,data,self.samplerate)messagebox.showinfo(成功,f录音已保存到\n{file_path})exceptExceptionase:messagebox.showerror(保存失败,f保存出错{str(e)})def_update_btn_state(self,after_startFalse,after_stopFalse):更新按钮状态ifafter_start:self.start_btn.config(statenormal)# 开始按钮仍可用用于恢复暂停self.pause_btn.config(statenormal)self.stop_btn.config(statenormal)self.save_btn.config(statedisabled)elifafter_stop:self.start_btn.config(statenormal)self.pause_btn.config(statedisabled)self.stop_btn.config(statedisabled)self.save_btn.config(statenormal)else:# 默认状态self.start_btn.config(statenormal)self.pause_btn.config(statedisabled)self.stop_btn.config(statedisabled)self.save_btn.config(statedisabled)if__name____main__:roottk.Tk()appAudioRecorderApp(root)root.mainloop()