win10下禁止自动更新,Window Update禁用无效后续方法

win10禁用自动更新,现在需要禁用两个服务,分别是Windows Update和Windows Update Medic Service。为啥呢。

Windows Update 是启用检测、下载和安装 Windows 和其他程序的更新。单个禁用它没有效果因为win10鸡贼地加了Windows Update Medic Service服务,是启用对Windows更新组件的修复和保护。

 

禁用步骤:

1.按键盘的window键 + R键 打开运行窗口. (window键是键盘上

 

2.在运行窗口中输入 → services.msc(如下图所示)

3.进入服务窗口,找到开头讲的两个服务

 

 

4.先把Windows Update 禁用,

       4.1双击(或者右键--属性),如图禁用

    4.2 然后在恢复那里选择--- 无操作 ,如图,记得应用和确定

 

 

5. 禁用完Windows Update,再禁用Windows Update Medic Service,但是需要用命令来禁用,按上面操作会显示 ---“拒绝访问”

    在Cortana上输入cmd,然后右键以管理人员运行,一定要是管理人员运行。

 

   运行代码如下:

   

REG add "HKLM\SYSTEM\CurrentControlSet\Services\WaaSMedicSvc" /v "Start" /t REG_DWORD /d "4" /f

    结果如下:

 

6.执行命令的方法除了用cmd外,可以用Windows PowerShell

  在  开始菜单栏---右击-----Windows PowerShell(管理员)。  界面如下:

最后运行命令就行啦

 

7. 命令执行成功后 Windows Update Medic Service 显示  -- 禁用

上面只是把它禁用了,但是还会恢复,需要禁用它的恢复。

---------------------------------------------------------------------------------------------分割线

过段时间发现还是没啥用,Windows Update Medic Service服务还有恢复啥的,就又来一种方法,彻底给它错误参数。

服务中禁止“Windows Update Medic Service”服务并阻止其恢复,但系统提示“拒绝访问”,解决方法见下:
1. 运行“regedit”(win键+R键输入regedit,或者小娜搜索regedit ),打开注册表编辑器,定位到 “HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\WaaSMedicSvc”,右侧找到“Start”键,右键点击“修改”,将数值改为“4”;


2.再找到“FailureActions”键,右键点击“修改”,修改该键的二进制数据,将“0010”、“0018”行的左起第5个数值由原来的“01”改为“00”,修改完成保存关闭;


3. “服务“中找到Windows Update Medic Service服务,切换到“恢复”页签,可以看到该服务已被禁止,且三次失败后的动作皆为“无操作”;




至此,烦人的Windows Update Medic Service服务被彻底禁用并阻止其恢复,win10 1809版已彻底关闭自动更新。

 

若后面还是出现参考贴吧:http://tieba.baidu.com/p/5848532152

import os import subprocess import shutil import time import tkinter as tk from tkinter import filedialog, ttk, scrolledtext, messagebox, PhotoImage import pandas as pd import win32com.client as win32 from bs4 import BeautifulSoup import threading import tempfile import queue import traceback class DiffProcessorApp: def __init__(self, root): self.root = root root.title("高级文件夹比较工具") root.geometry("1000x700") root.configure(bg="#f5f5f5") # 创建现代风格主题 self.style = ttk.Style() self.style.theme_use('clam') # 自定义主题颜色 self.style.configure('TButton', font=('Segoe UI', 10, 'bold'), borderwidth=1, foreground="#333", background="#4CAF50", bordercolor="#388E3C", relief="flat", padding=8, anchor="center") self.style.map('TButton', background=[('active', '#388E3C'), ('disabled', '#BDBDBD')], foreground=[('disabled', '#9E9E9E')]) self.style.configure('TLabel', font=('Segoe UI', 9), background="#f5f5f5") self.style.configure('TLabelframe', font=('Segoe UI', 10, 'bold'), background="#f5f5f5", relief="flat", borderwidth=2) self.style.configure('TLabelframe.Label', font=('Segoe UI', 10, 'bold'), background="#f5f5f5", foreground="#2E7D32") self.style.configure('Treeview', font=('Segoe UI', 9), rowheight=25) self.style.configure('Treeview.Heading', font=('Segoe UI', 9, 'bold')) # 创建主框架 main_frame = ttk.Frame(root, padding="15") main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) # 标题区域 header_frame = ttk.Frame(main_frame) header_frame.pack(fill=tk.X, pady=(0, 15)) # 添加标题图标 try: icon = PhotoImage(file="folder_icon.png") self.icon_label = ttk.Label(header_frame, image=icon) self.icon_label.image = icon self.icon_label.pack(side=tk.LEFT, padx=(0, 10)) except: self.icon_label = ttk.Label(header_frame, text="📁", font=("Arial", 24)) self.icon_label.pack(side=tk.LEFT, padx=(0, 10)) title_label = ttk.Label(header_frame, text="高级文件夹比较工具", font=("Segoe UI", 18, "bold"), foreground="#2E7D32") title_label.pack(side=tk.LEFT) # 文件选择区域 file_frame = ttk.LabelFrame(main_frame, text="文件夹选择", padding="12") file_frame.pack(fill=tk.X, pady=5) # 文件夹选择 self.old_folder_entry, self.new_folder_entry = self.create_folder_selector(file_frame, "原始文件夹:") self.new_folder_entry = self.create_folder_selector(file_frame, "修改后文件夹:")[0] # 比较选项区域 options_frame = ttk.LabelFrame(main_frame, text="比较选项", padding="12") options_frame.pack(fill=tk.X, pady=5) # 递归比较选项 self.recursive_var = tk.BooleanVar(value=True) recursive_check = ttk.Checkbutton(options_frame, text="递归比较子文件夹", variable=self.recursive_var) recursive_check.grid(row=0, column=0, padx=10, pady=5, sticky=tk.W) # 文件过滤 filter_frame = ttk.Frame(options_frame) filter_frame.grid(row=0, column=1, padx=10, pady=5, sticky=tk.W) ttk.Label(filter_frame, text="文件过滤:").pack(side=tk.LEFT, padx=(0, 5)) self.filter_var = tk.StringVar(value="*.*") filter_entry = ttk.Entry(filter_frame, textvariable=self.filter_var, width=15) filter_entry.pack(side=tk.LEFT) # 目标Excel选择 excel_frame = ttk.LabelFrame(main_frame, text="输出设置", padding="12") excel_frame.pack(fill=tk.X, pady=5) ttk.Label(excel_frame, text="目标Excel文件:").grid(row=0, column=0, sticky=tk.W, padx=5, pady=5) self.excel_file_entry = ttk.Entry(excel_frame, width=60) self.excel_file_entry.grid(row=0, column=1, padx=5, pady=5) ttk.Button(excel_frame, text="浏览...", command=lambda: self.select_file(self.excel_file_entry, [("Excel文件", "*.xlsx *.xlsm")])).grid(row=0, column=2, padx=5, pady=5) # 执行按钮区域 button_frame = ttk.Frame(main_frame) button_frame.pack(fill=tk.X, pady=10) self.run_button = ttk.Button(button_frame, text="执行比较", command=self.start_processing, width=20, style='TButton') self.run_button.pack(side=tk.LEFT) # 停止按钮 self.stop_button = ttk.Button(button_frame, text="停止", command=self.stop_processing, width=10, state=tk.DISABLED) self.stop_button.pack(side=tk.LEFT, padx=10) # 进度条 self.progress = ttk.Progressbar(main_frame, orient=tk.HORIZONTAL, length=700, mode='determinate') self.progress.pack(fill=tk.X, pady=5) # 状态信息 status_frame = ttk.Frame(main_frame) status_frame.pack(fill=tk.X, pady=5) self.status_var = tk.StringVar(value="准备就绪") status_label = ttk.Label(status_frame, textvariable=self.status_var, font=("Segoe UI", 9), foreground="#2E7D32") status_label.pack(side=tk.LEFT) # 日志和预览区域 notebook = ttk.Notebook(main_frame) notebook.pack(fill=tk.BOTH, expand=True, pady=5) # 文件夹结构标签 tree_frame = ttk.Frame(notebook, padding="5") notebook.add(tree_frame, text="文件夹结构") # 创建树形视图 self.tree = ttk.Treeview(tree_frame, columns=("Status"), show="tree") self.tree.heading("#0", text="文件夹结构", anchor=tk.W) self.tree.heading("Status", text="状态", anchor=tk.W) self.tree.column("#0", width=400) self.tree.column("Status", width=100) vsb = ttk.Scrollbar(tree_frame, orient="vertical", command=self.tree.yview) hsb = ttk.Scrollbar(tree_frame, orient="horizontal", command=self.tree.xview) self.tree.configure(yscrollcommand=vsb.set, xscrollcommand=hsb.set) self.tree.grid(row=0, column=0, sticky="nsew") vsb.grid(row=0, column=1, sticky="ns") hsb.grid(row=1, column=0, sticky="ew") # 日志标签 log_frame = ttk.Frame(notebook, padding="5") notebook.add(log_frame, text="执行日志") self.log_text = scrolledtext.ScrolledText(log_frame, height=10, wrap=tk.WORD, font=("Consolas", 9)) self.log_text.pack(fill=tk.BOTH, expand=True) self.log_text.config(state=tk.DISABLED) # 设置网格权重 tree_frame.grid_rowconfigure(0, weight=1) tree_frame.grid_columnconfigure(0, weight=1) # 线程控制 self.processing = False self.queue = queue.Queue() # 启动队列处理 self.root.after(100, self.process_queue) def create_folder_selector(self, parent, label_text): """创建文件夹选择器组件""" frame = ttk.Frame(parent) frame.pack(fill=tk.X, pady=5) ttk.Label(frame, text=label_text).grid(row=0, column=0, sticky=tk.W, padx=5, pady=5) entry = ttk.Entry(frame, width=70) entry.grid(row=0, column=1, padx=5, pady=5) button = ttk.Button(frame, text="浏览文件夹...", command=lambda: self.select_folder(entry)) button.grid(row=0, column=2, padx=5, pady=5) return entry, button def select_folder(self, entry): """选择文件夹""" foldername = filedialog.askdirectory() if foldername: entry.delete(0, tk.END) entry.insert(0, foldername) # 自动填充文件夹结构 self.populate_folder_tree(foldername) def select_file(self, entry, filetypes=None): """选择文件""" if filetypes is None: filetypes = [("所有文件", "*.*")] filename = filedialog.askopenfilename(filetypes=filetypes) if filename: entry.delete(0, tk.END) entry.insert(0, filename) def populate_folder_tree(self, path): """填充文件夹结构树""" self.tree.delete(*self.tree.get_children()) if not os.path.isdir(path): return # 添加根节点 root_node = self.tree.insert("", "end", text=os.path.basename(path), values=("文件夹",), open=True) self.add_tree_nodes(root_node, path) def add_tree_nodes(self, parent, path): """递归添加树节点""" try: for item in os.listdir(path): item_path = os.path.join(path, item) if os.path.isdir(item_path): node = self.tree.insert(parent, "end", text=item, values=("文件夹",)) self.add_tree_nodes(node, item_path) else: self.tree.insert(parent, "end", text=item, values=("文件",)) except PermissionError: self.log_message(f"权限错误: 无法访问 {path}") def log_message(self, message): """记录日志消息""" self.queue.put(("log", message)) def update_progress(self, value): """更新进度条""" self.queue.put(("progress", value)) def update_status(self, message): """更新状态信息""" self.queue.put(("status", message)) def process_queue(self): """处理线程队列中的消息""" try: while not self.queue.empty(): msg_type, data = self.queue.get_nowait() if msg_type == "log": self.log_text.config(state=tk.NORMAL) self.log_text.insert(tk.END, data + "\n") self.log_text.see(tk.END) self.log_text.config(state=tk.DISABLED) elif msg_type == "progress": self.progress['value'] = data elif msg_type == "status": self.status_var.set(data) except queue.Empty: pass self.root.after(100, self.process_queue) def run_winmerge(self, path1, path2, output_html): """调用WinMerge生成HTML差异文件""" winmerge_path = r"E:\App\WinMerge\WinMerge2.16.12.0\WinMergeU.exe" # 修复TypeError: 确保所有参数都是字符串 winmerge_cmd = [ str(winmerge_path), '/u', '/dl', 'Base', '/dr', 'Modified', '/or', str(output_html), str(path1), str(path2) ] # 添加递归选项 if self.recursive_var.get(): winmerge_cmd.insert(1, '/r') self.log_message("正在调用WinMerge生成差异报告...") try: result = subprocess.run(winmerge_cmd, capture_output=True, text=True, timeout=120) if result.returncode == 0: self.log_message(f"HTML差异报告生成完成: {output_html}") return True else: error_msg = f"WinMerge执行失败: {result.stderr}" # 修复TypeError: 使用f-string避免字符串连接问题 self.log_message(error_msg) return False except subprocess.TimeoutExpired: self.log_message("WinMerge执行超时,请检查输入文件大小") return False except Exception as e: # 修复TypeError: 使用f-string记录异常 self.log_message(f"WinMerge执行错误: {str(e)}") return False def parse_html_diff(self, html_path): """解析HTML差异文件""" self.log_message("正在解析HTML差异文件...") try: with open(html_path, 'r', encoding='utf-8') as f: content = f.read() soup = BeautifulSoup(content, 'html.parser') diff_table = soup.find('table', {'class': 'diff'}) if not diff_table: self.log_message("错误: 未找到差异表格") return None # 提取表格数据 diff_data = [] for row in diff_table.find_all('tr')[1:]: # 跳过表头 cols = row.find_all('td') if len(cols) >= 3: # 修复TypeError: 确保所有值都是字符串 diff_type = str(cols[0].get_text(strip=True)) content_left = str(cols[1].get_text(strip=True)) content_right = str(cols[2].get_text(strip=True)) diff_data.append([diff_type, content_left, content_right]) # 修复TypeError: 使用f-string记录结果 self.log_message(f"成功解析 {len(diff_data)} 行差异数据") return diff_data except Exception as e: # 修复TypeError: 使用f-string记录异常 error_msg = f"解析HTML失败: {str(e)}\n{traceback.format_exc()}" self.log_message(error_msg) return None def write_to_excel(self, excel_path, diff_data): """将差异数据写入Excel""" self.log_message("正在写入Excel文件...") try: # 使用win32com打开Excel excel = win32.gencache.EnsureDispatch('Excel.Application') excel.Visible = True workbook = excel.Workbooks.Open(os.path.abspath(excel_path)) sheet = workbook.Sheets("一覧") # 从第6行开始写入数据 start_row = 6 for i, row_data in enumerate(diff_data): for j, value in enumerate(row_data[:6]): # 确保值是字符串类型 sheet.Cells(start_row + i, j + 1).Value = str(value) # 保存Excel workbook.Save() self.log_message(f"数据已写入Excel第{start_row}行开始") # 触发"作成"按钮 self.log_message("正在触发'作成'按钮...") try: # 查找按钮并点击 button = sheet.Buttons("作成") button.OnAction = "作成按钮的处理" button.Click() self.log_message("已触发'作成'按钮") # 等待处理完成 self.update_status("处理中...请等待") # 简单等待机制 for _ in range(30): # 最多等待30秒 if not self.processing: break if excel.CalculationState == 0: # 0 = xlDone break time.sleep(1) self.log_message("处理中...") self.log_message("处理完成") self.update_status("处理完成") except Exception as e: # 修复TypeError: 使用f-string记录异常 self.log_message(f"按钮操作失败: {str(e)}. 请手动点击'作成'按钮") # 关闭Excel workbook.Close() excel.Quit() return True except Exception as e: # 修复TypeError: 使用f-string记录异常 self.log_message(f"Excel操作失败: {str(e)}\n{traceback.format_exc()}") return False def start_processing(self): """启动处理线程 - 修复无响应问题""" if self.processing: self.log_message("警告: 处理正在进行中") return # 获取路径 old_path = self.old_folder_entry.get() new_path = self.new_folder_entry.get() excel_file = self.excel_file_entry.get() # 详细路径验证 validation_errors = [] if not old_path: validation_errors.append("原始文件夹路径为空") elif not os.path.isdir(old_path): validation_errors.append(f"原始文件夹路径无效: {old_path}") if not new_path: validation_errors.append("新文件夹路径为空") elif not os.path.isdir(new_path): validation_errors.append(f"新文件夹路径无效: {new_path}") if not excel_file: validation_errors.append("Excel文件路径为空") elif not excel_file.lower().endswith(('.xlsx', '.xlsm')): validation_errors.append("Excel文件必须是.xlsx或.xlsm格式") if validation_errors: self.log_message("错误: " + "; ".join(validation_errors)) messagebox.showerror("输入错误", "\n".join(validation_errors)) return # 检查WinMerge安装 winmerge_path = r"E:\App\WinMerge\WinMerge2.16.12.0\WinMergeU.exe" if not os.path.exists(winmerge_path): self.log_message(f"错误: WinMerge未安装在默认位置 {winmerge_path}") messagebox.showwarning("WinMerge未安装", "请确保WinMerge已安装或更新路径配置") return # 禁用执行按钮,启用停止按钮 self.run_button.config(state=tk.DISABLED) self.stop_button.config(state=tk.NORMAL) self.processing = True # 启动处理线程 thread = threading.Thread(target=self.process_folders, args=(old_path, new_path, excel_file)) thread.daemon = True thread.start() self.log_message("处理线程已启动") def process_folders(self, old_path, new_path, excel_file): """处理文件夹比较的线程函数 - 增强异常处理""" output_html = None try: # 步骤1: 生成HTML差异文件 self.update_status("生成HTML差异文件...") self.update_progress(20) # 使用临时文件存储HTML报告 with tempfile.NamedTemporaryFile(suffix=".html", delete=False) as temp_file: output_html = temp_file.name if not self.run_winmerge(old_path, new_path, output_html): self.update_status("WinMerge执行失败") return # 步骤2: 将HTML文件与Excel放在同一目录 self.update_status("准备文件...") self.update_progress(40) excel_dir = os.path.dirname(excel_file) if excel_dir: target_html = os.path.join(excel_dir, "diff_report.html") try: shutil.copy(output_html, target_html) self.log_message(f"已将HTML文件复制到: {target_html}") except Exception as e: self.log_message(f"文件复制失败: {str(e)}") return # 步骤3: 解析HTML差异文件 self.update_status("解析差异数据...") self.update_progress(60) diff_data = self.parse_html_diff(output_html) if not diff_data: self.update_status("HTML解析失败") return # 步骤4: 写入Excel并触发按钮 self.update_status("写入Excel并触发处理...") self.update_progress(80) if not self.write_to_excel(excel_file, diff_data): self.update_status("Excel操作失败") return # 完成 self.update_progress(100) self.update_status("处理完成!") self.log_message("文件夹比较流程执行完毕") messagebox.showinfo("完成", "文件夹比较处理成功完成") except Exception as e: error_msg = f"执行过程中发生错误: {str(e)}\n{traceback.format_exc()}" self.log_message(error_msg) self.update_status("执行失败") messagebox.showerror("错误", f"处理失败: {str(e)}") finally: # 重新启用执行按钮 if self.processing: self.stop_processing() # 清理临时文件 if output_html and os.path.exists(output_html): try: os.remove(output_html) except: pass def run_winmerge(self, path1, path2, output_html): """调用WinMerge生成HTML差异文件 - 增强错误处理""" winmerge_path = r"E:\App\WinMerge\WinMerge2.16.12.0\WinMergeU.exe" # 验证WinMerge可执行文件 if not os.path.exists(winmerge_path): self.log_message(f"错误: WinMerge路径不存在 {winmerge_path}") return False winmerge_cmd = [ winmerge_path, '/u', '/dl', 'Base', '/dr', 'Modified', '/or', output_html, path1, path2 ] # 添加递归选项 if self.recursive_var.get(): winmerge_cmd.insert(1, '/r') self.log_message(f"执行WinMerge命令: {' '.join(winmerge_cmd)}") try: result = subprocess.run( winmerge_cmd, capture_output=True, text=True, timeout=120, creationflags=subprocess.CREATE_NO_WINDOW # 避免控制台窗口闪烁 ) if result.returncode == 0: self.log_message(f"HTML差异报告生成完成: {output_html}") return True else: error_msg = f"WinMerge执行失败(退出码{result.returncode}): {result.stderr}" self.log_message(error_msg) return False except subprocess.TimeoutExpired: self.log_message("WinMerge执行超时(120秒),请检查输入文件大小") return False except Exception as e: self.log_message(f"WinMerge执行错误: {str(e)}") return False def write_to_excel(self, excel_path, diff_data): """将差异数据写入Excel - 增强健壮性""" self.log_message("正在写入Excel文件...") excel = None workbook = None try: # 验证Excel文件存在 if not os.path.exists(excel_path): self.log_message(f"错误: Excel文件不存在 {excel_path}") return False # 使用win32com打开Excel excel = win32.gencache.EnsureDispatch('Excel.Application') excel.Visible = True excel.DisplayAlerts = False # 禁用警告提示 # 尝试打开工作簿 try: workbook = excel.Workbooks.Open(os.path.abspath(excel_path)) except Exception as e: self.log_message(f"打开Excel文件失败: {str(e)}") return False # 检查工作表是否存在 sheet_names = [sheet.Name for sheet in workbook.Sheets] if "一覧" not in sheet_names: self.log_message("错误: Excel文件中缺少'一覧'工作表") return False sheet = workbook.Sheets("一覧") # 从第6行开始写入数据 start_row = 6 for i, row_data in enumerate(diff_data): for j, value in enumerate(row_data[:6]): # 确保值是字符串类型 sheet.Cells(start_row + i, j + 1).Value = str(value) # 保存Excel workbook.Save() self.log_message(f"数据已写入Excel第{start_row}行开始") # 触发"作成"按钮 self.log_message("正在触发'作成'按钮...") try: # 查找按钮并点击 button = sheet.Buttons("作成") button.OnAction = "作成按钮的处理" button.Click() self.log_message("已触发'作成'按钮") # 等待处理完成 self.update_status("处理中...请等待") wait_time = 0 max_wait = 60 # 最大等待60秒 while self.processing and wait_time < max_wait: if excel.CalculationState == 0: # 0 = xlDone break time.sleep(1) wait_time += 1 self.log_message(f"处理中...({wait_time}秒)") if wait_time >= max_wait: self.log_message("警告: 处理超时") else: self.log_message("处理完成") return True except Exception as e: self.log_message(f"按钮操作失败: {str(e)}. 请手动点击'作成'按钮") return False except Exception as e: self.log_message(f"Excel操作失败: {str(e)}\n{traceback.format_exc()}") return False finally: # 确保正确关闭Excel try: if workbook: workbook.Close(SaveChanges=False) if excel: excel.Quit() except Exception as e: self.log_message(f"关闭Excel时出错: {str(e)}") def stop_processing(self): """停止处理""" self.processing = False self.stop_button.config(state=tk.DISABLED) self.run_button.config(state=tk.NORMAL) self.update_status("操作已停止") def process_folders(self, old_path, new_path, excel_file): """处理文件夹比较的线程函数""" try: # 步骤1: 生成HTML差异文件 self.update_status("生成HTML差异文件...") self.update_progress(20) # 使用临时文件存储HTML报告 with tempfile.NamedTemporaryFile(suffix=".html", delete=False) as temp_file: output_html = temp_file.name if not self.run_winmerge(old_path, new_path, output_html): return # 步骤2: 将HTML文件与Excel放在同一目录 self.update_status("准备文件...") self.update_progress(40) excel_dir = os.path.dirname(excel_file) if excel_dir: target_html = os.path.join(excel_dir, "diff_report.html") shutil.copy(output_html, target_html) self.log_message(f"已将HTML文件复制到: {target_html}") # 步骤3: 解析HTML差异文件 self.update_status("解析差异数据...") self.update_progress(60) diff_data = self.parse_html_diff(output_html) if not diff_data: return # 步骤4: 写入Excel并触发按钮 self.update_status("写入Excel并触发处理...") self.update_progress(80) self.write_to_excel(excel_file, diff_data) # 完成 self.update_progress(100) self.update_status("处理完成!") self.log_message("文件夹比较流程执行完毕") except Exception as e: # 修复TypeError: 使用f-string记录异常 error_msg = f"执行过程中发生错误: {str(e)}\n{traceback.format_exc()}" self.log_message(error_msg) self.update_status("执行失败") finally: # 重新启用执行按钮 if self.processing: self.stop_processing() # 清理临时文件 if os.path.exists(output_html): try: os.remove(output_html) except: pass if __name__ == "__main__": root = tk.Tk() app = DiffProcessorApp(root) root.mainloop() 这里启动winmerge成功,并且成功生成了报告,但是进程还是显示卡在了生成报告那里,不继续往下执行了,是不是生成报告之后,winmerge有个弹窗显示生成成功了,我点击确定了,这个动作导致的?请解决这个问题
最新发布
07-11
function timbre_transfer_2 % 创建主界面 fig = figure('Name', '高级音色转换系统 v3.2', 'Position', [50, 50, 1200, 900], ... 'NumberTitle', 'off', 'MenuBar', 'none', 'Resize', 'on', ... 'CloseRequestFcn', @close_gui, 'Color', [0.94, 0.94, 0.94]); % 全局变量 fs = 44100; % 默认采样率 source_audio = []; % 源音频(提供音色) target_audio = []; % 目标音频(提供内容) converted_audio = []; % 转换后的音频 processing = false; % 处理状态标志 conversion_complete = false; % 转换完成标志 % STFT参数 stft_params.win_len = 2048; % 窗长 stft_params.overlap = 1536; % 重叠点数 (75%) stft_params.nfft = 2048; % FFT点数 stft_params.window = hamming(stft_params.win_len, 'periodic'); % 汉明窗 stft_params.lifter_order = 30; % 包络阶数 stft_params.phase_iter = 5; % 相位迭代次数 stft_params.fs = fs; % 采样率参数 stft_params.hop_size = stft_params.win_len - stft_params.overlap; % 跳跃长度 % 计算合成窗 (确保完美重建) stft_params.win_synthesis = stft_params.window / sum(stft_params.window.^2) * stft_params.hop_size; % === 创建控件 === % 顶部控制面板 control_panel = uipanel('Title', '音频控制', 'Position', [0.02, 0.92, 0.96, 0.07], ... 'BackgroundColor', [0.9, 0.95, 1]); uicontrol('Parent', control_panel, 'Style', 'pushbutton', 'String', '导入源音频(音色)',... 'Position', [20, 10, 150, 30], 'Callback', @load_source, ... 'FontSize', 10, 'FontWeight', 'bold', 'BackgroundColor', [0.7, 0.9, 1]); uicontrol('Parent', control_panel, 'Style', 'pushbutton', 'String', '导入目标音频(内容)',... 'Position', [190, 10, 150, 30], 'Callback', @load_target, ... 'FontSize', 10, 'FontWeight', 'bold', 'BackgroundColor', [0.7, 0.9, 1]); uicontrol('Parent', control_panel, 'Style', 'pushbutton', 'String', '执行音色转换',... 'Position', [360, 10, 150, 30], 'Callback', @transfer_timbre, ... 'FontSize', 10, 'FontWeight', 'bold', 'BackgroundColor', [0.8, 1, 0.8]); uicontrol('Parent', control_panel, 'Style', 'pushbutton', 'String', '播放目标音频',... 'Position', [530, 10, 120, 30], 'Callback', {@play_target_audio}, ... 'FontSize', 10, 'BackgroundColor', [1, 0.95, 0.8]); uicontrol('Parent', control_panel, 'Style', 'pushbutton', 'String', '播放转换音频',... 'Position', [670, 10, 120, 30], 'Callback', {@play_converted_audio}, ... 'FontSize', 10, 'BackgroundColor', [1, 0.95, 0.8]); uicontrol('Parent', control_panel, 'Style', 'pushbutton', 'String', '保存转换音频',... 'Position', [810, 10, 120, 30], 'Callback', @save_audio, ... 'FontSize', 10, 'BackgroundColor', [0.9, 1, 0.8]); uicontrol('Parent', control_panel, 'Style', 'pushbutton', 'String', '停止播放',... 'Position', [950, 10, 120, 30], 'Callback', @stop_audio, ... 'FontSize', 10, 'BackgroundColor', [1, 0.8, 0.8]); % 参数控制面板 param_panel = uipanel('Title', 'STFT参数设置', 'Position', [0.02, 0.82, 0.96, 0.09], ... 'BackgroundColor', [0.95, 0.97, 1], 'FontWeight', 'bold'); uicontrol('Parent', param_panel, 'Style', 'text', 'String', '窗长:',... 'Position', [20, 40, 50, 20], 'HorizontalAlignment', 'left',... 'BackgroundColor', [0.95, 0.97, 1], 'FontWeight', 'bold'); win_len_edit = uicontrol('Parent', param_panel, 'Style', 'edit',... 'String', num2str(stft_params.win_len),... 'Position', [80, 40, 80, 25], 'Callback', @update_params, ... 'BackgroundColor', [1, 1, 1]); uicontrol('Parent', param_panel, 'Style', 'text', 'String', '重叠率(%):',... 'Position', [180, 40, 70, 20], 'HorizontalAlignment', 'left',... 'BackgroundColor', [0.95, 0.97, 1], 'FontWeight', 'bold'); overlap_edit = uicontrol('Parent', param_panel, 'Style', 'edit',... 'String', '75',... 'Position', [260, 40, 80, 25], 'Callback', @update_params, ... 'BackgroundColor', [1, 1, 1]); uicontrol('Parent', param_panel, 'Style', 'text', 'String', 'FFT点数:',... 'Position', [360, 40, 60, 20], 'HorizontalAlignment', 'left',... 'BackgroundColor', [0.95, 0.97, 1], 'FontWeight', 'bold'); nfft_edit = uicontrol('Parent', param_panel, 'Style', 'edit',... 'String', num2str(stft_params.nfft),... 'Position', [430, 40, 80, 25], 'Callback', @update_params, ... 'BackgroundColor', [1, 1, 1]); uicontrol('Parent', param_panel, 'Style', 'text', 'String', '包络阶数:',... 'Position', [530, 40, 60, 20], 'HorizontalAlignment', 'left',... 'BackgroundColor', [0.95, 0.97, 1], 'FontWeight', 'bold'); lifter_edit = uicontrol('Parent', param_panel, 'Style', 'edit',... 'String', num2str(stft_params.lifter_order),... 'Position', [600, 40, 80, 25], 'Callback', @update_params, ... 'BackgroundColor', [1, 1, 1]); uicontrol('Parent', param_panel, 'Style', 'text', 'String', '相位迭代:',... 'Position', [700, 40, 60, 20], 'HorizontalAlignment', 'left',... 'BackgroundColor', [0.95, 0.97, 1], 'FontWeight', 'bold'); iter_edit = uicontrol('Parent', param_panel, 'Style', 'edit',... 'String', num2str(stft_params.phase_iter),... 'Position', [770, 40, 80, 25], 'Callback', @update_params, ... 'BackgroundColor', [1, 1, 1]); % 波形显示区域 - 使用选项卡 tabgp = uitabgroup(fig, 'Position', [0.02, 0.02, 0.96, 0.35]); tab1 = uitab(tabgp, 'Title', '目标音频'); tab2 = uitab(tabgp, 'Title', '转换后音频'); tab3 = uitab(tabgp, 'Title', '源音频'); ax1 = axes('Parent', tab1, 'Position', [0.07, 0.15, 0.9, 0.75]); title(ax1, '目标音频波形'); xlabel(ax1, '时间 (s)'); ylabel(ax1, '幅度'); grid(ax1, 'on'); ax2 = axes('Parent', tab2, 'Position', [0.07, 0.15, 0.9, 0.75]); title(ax2, '转换后音频波形'); xlabel(ax2, '时间 (s)'); ylabel(ax2, '幅度'); grid(ax2, 'on'); ax3 = axes('Parent', tab3, 'Position', [0.07, 0.15, 0.9, 0.75]); title(ax3, '源音频波形'); xlabel(ax3, '时间 (s)'); ylabel(ax3, '幅度'); grid(ax3, 'on'); % 频谱显示区域(只保留三个频谱图) spec_panel = uipanel('Title', '频谱分析', 'Position', [0.02, 0.38, 0.96, 0.43], ... 'BackgroundColor', [0.98, 0.98, 0.98], 'FontWeight', 'bold'); % 增大频谱图尺寸(垂直方向) ax4 = axes('Parent', spec_panel, 'Position', [0.03, 0.1, 0.3, 0.8]); % 高度增加到80% title(ax4, '源音频频谱'); ax5 = axes('Parent', spec_panel, 'Position', [0.36, 0.1, 0.3, 0.8]); % 高度增加到80% title(ax5, '目标音频频谱'); ax6 = axes('Parent', spec_panel, 'Position', [0.69, 0.1, 0.3, 0.8]); % 高度增加到80% title(ax6, '转换后频谱'); % 状态文本 status_text = uicontrol('Style', 'text', 'Position', [50, 5, 900, 30],... 'String', '就绪', 'HorizontalAlignment', 'left',... 'FontSize', 10, 'FontWeight', 'bold', 'BackgroundColor', [1, 1, 1]); % 进度条 progress_ax = axes('Position', [0.1, 0.97, 0.8, 0.02],... 'XLim', [0, 1], 'YLim', [0, 1], 'Box', 'on', 'Color', [0.9, 0.9, 0.9]); progress_bar = patch(progress_ax, [0 0 0 0], [0 0 1 1], [0.2, 0.6, 1]); axis(progress_ax, 'off'); progress_text = uicontrol('Style', 'text', 'Position', [500, 970, 200, 20],... 'String', '', 'HorizontalAlignment', 'center',... 'FontSize', 10, 'FontWeight', 'bold', 'BackgroundColor', [1, 1, 1]); % 诊断信息面板 diag_panel = uipanel('Title', '处理日志', 'Position', [0.02, 0.02, 0.96, 0.35], ... 'BackgroundColor', [0.95, 0.95, 0.95], 'Visible', 'off'); diag_text = uicontrol('Parent', diag_panel, 'Style', 'listbox', ... 'Position', [10, 10, 1140, 250], 'String', {'系统已初始化'}, ... 'HorizontalAlignment', 'left', 'FontSize', 9, ... 'BackgroundColor', [1, 1, 1], 'Max', 100, 'Min', 0); % 添加显示/隐藏日志按钮 uicontrol('Style', 'pushbutton', 'String', '显示日志',... 'Position', [1125, 928, 120, 30], 'Callback', @toggle_log, ... 'FontSize', 9, 'BackgroundColor', [0.9, 0.95, 1]); % === 回调函数 === % 更新参数回调 function update_params(~, ~) try % 获取新参数值 new_win_len = str2double(get(win_len_edit, 'String')); overlap_percent = str2double(get(overlap_edit, 'String')); new_nfft = str2double(get(nfft_edit, 'String')); lifter_order = str2double(get(lifter_edit, 'String')); phase_iter = str2double(get(iter_edit, 'String')); % 验证参数 if isnan(new_win_len) || new_win_len <= 0 || mod(new_win_len, 1) ~= 0 error('窗长必须是正整数'); end if isnan(overlap_percent) || overlap_percent < 0 || overlap_percent > 100 error('重叠率必须是0-100之间的数字'); end if isnan(new_nfft) || new_nfft <= 0 || mod(new_nfft, 1) ~= 0 error('FFT点数必须是正整数'); end if isnan(lifter_order) || lifter_order <= 0 || mod(lifter_order, 1) ~= 0 error('包络阶数必须是正整数'); end if isnan(phase_iter) || phase_iter <= 0 || mod(phase_iter, 1) ~= 0 error('相位迭代次数必须是正整数'); end % 更新参数 stft_params.win_len = new_win_len; stft_params.overlap = round(overlap_percent/100 * new_win_len); stft_params.nfft = new_nfft; stft_params.window = hamming(new_win_len, 'periodic'); stft_params.lifter_order = lifter_order; stft_params.phase_iter = phase_iter; stft_params.hop_size = stft_params.win_len - stft_params.overlap; stft_params.win_synthesis = stft_params.window / sum(stft_params.window.^2) * stft_params.hop_size; update_diag(sprintf('参数更新: 窗长=%d, 重叠=%d(%.0f%%), FFT=%d', ... new_win_len, stft_params.overlap, overlap_percent, new_nfft)); catch e errordlg(['参数错误: ', e.message], '输入错误'); update_diag(['参数错误: ', e.message], true); end end % 更新诊断信息 function update_diag(msg, force) if nargin < 2, force = false; end if ~conversion_complete || force current = get(diag_text, 'String'); new_msg = sprintf('[%s] %s', datestr(now, 'HH:MM:SS'), msg); set(diag_text, 'String', [current; {new_msg}]); set(diag_text, 'Value', length(get(diag_text, 'String'))); end end % 切换日志显示 function toggle_log(~, ~) if strcmp(get(diag_panel, 'Visible'), 'on') set(diag_panel, 'Visible', 'off'); set(tabgp, 'Position', [0.02, 0.02, 0.96, 0.35]); else set(diag_panel, 'Visible', 'on'); set(tabgp, 'Position', [0.02, 0.38, 0.96, 0.35]); end end % 关闭GUI回调 function close_gui(~, ~) if processing choice = questdlg('处理正在进行中,确定要关闭吗?', '确认关闭', '是', '否', '否'); if strcmp(choice, '否') return; end end stop_audio(); delete(fig); end % 导入源音频 function load_source(~, ~) if processing, return; end [file, path] = uigetfile({'*.wav;*.mp3;*.ogg', '音频文件 (*.wav,*.mp3,*.ogg)'}); if isequal(file, 0), return; end try [audio, fs_in] = audioread(fullfile(path, file)); update_diag(['加载源音频: ', file, ' (', num2str(fs_in), 'Hz)']); set(status_text, 'String', ['正在处理源音频: ', file]); drawnow; % 转换为单声道并归一化 if size(audio, 2) > 1 source_audio = mean(audio, 2); update_diag('转换为单声道'); else source_audio = audio; end source_audio = source_audio / max(abs(source_audio)); update_diag('归一化完成'); % 更新采样率参数 stft_params.fs = fs; % 采样率处理 if fs == 0 fs = fs_in; elseif fs ~= fs_in update_diag(['重采样: ', num2str(fs_in), 'Hz -> ', num2str(fs), 'Hz']); source_audio = resample(source_audio, fs, fs_in); end % 显示波形和频谱 plot(ax3, (0:length(source_audio)-1)/fs, source_audio); title(ax3, ['源音频波形: ', file]); xlabel(ax3, '时间 (s)'); ylabel(ax3, '幅度'); grid(ax3, 'on'); % 显示频谱 show_spectrum(ax4, source_audio, fs, stft_params, '源音频频谱'); set(status_text, 'String', ['已加载源音频: ', file, ' (', num2str(fs/1000), 'kHz)']); update_diag(['源音频长度: ', num2str(length(source_audio)/fs), '秒']); % 重置转换完成标志 conversion_complete = false; catch e errordlg(['加载源音频失败: ', e.message], '错误'); update_diag(['错误: ', e.message], true); end end % 导入目标音频 function load_target(~, ~) if processing, return; end [file, path] = uigetfile({'*.wav;*.mp3;*.ogg', '音频文件 (*.wav,*.mp3,*.ogg)'}); if isequal(file, 0), return; end try [audio, fs_in] = audioread(fullfile(path, file)); update_diag(['加载目标音频: ', file, ' (', num2str(fs_in), 'Hz)']); set(status_text, 'String', ['正在处理目标音频: ', file]); drawnow; % 转换为单声道并归一化 if size(audio, 2) > 1 target_audio = mean(audio, 2); update_diag('转换为单声道'); else target_audio = audio; end target_audio = target_audio / max(abs(target_audio)); update_diag('归一化完成'); % 更新采样率参数 stft_params.fs = fs; % 采样率处理 if fs == 0 fs = fs_in; elseif fs ~= fs_in update_diag(['重采样: ', num2str(fs_in), 'Hz -> ', num2str(fs), 'Hz']); target_audio = resample(target_audio, fs, fs_in); end % 显示波形和频谱 plot(ax1, (0:length(target_audio)-1)/fs, target_audio); title(ax1, ['目标音频波形: ', file]); xlabel(ax1, '时间 (s)'); ylabel(ax1, '幅度'); grid(ax1, 'on'); % 显示频谱 show_spectrum(ax5, target_audio, fs, stft_params, '目标音频频谱'); set(status_text, 'String', ['已加载目标音频: ', file, ' (', num2str(fs/1000), 'kHz)']); update_diag(['目标音频长度: ', num2str(length(target_audio)/fs), '秒']); % 重置转换完成标志 conversion_complete = false; catch e errordlg(['加载目标音频失败: ', e.message], '错误'); update_diag(['错误: ', e.message], true); end end %% === 在transfer_timbre末尾添加后处理 === function transfer_timbre(~, ~) if processing, return; end if isempty(source_audio) || isempty(target_audio) errordlg('请先导入源音频和目标音频!', '错误'); return; end % 设置处理状态 processing = true; conversion_complete = false; set(status_text, 'String', '开始音色转换...'); update_diag('=== 开始音色转换 ==='); drawnow; % 统一音频长度(以目标音频长度为基准) target_length = length(target_audio); source_length = length(source_audio); if source_length < target_length % 源音频较短,重复填充 num_repeat = ceil(target_length / source_length); extended_source = repmat(source_audio, num_repeat, 1); source_audio_adj = extended_source(1:target_length); update_diag('源音频已扩展以匹配目标长度'); elseif source_length > target_length % 源音频较长,截断 source_audio_adj = source_audio(1:target_length); update_diag('源音频已截断以匹配目标长度'); else source_audio_adj = source_audio; end % 确保长度兼容 target_audio_adj = target_audio(1:min(target_length, length(source_audio_adj))); source_audio_adj = source_audio_adj(1:min(target_length, length(source_audio_adj))); try % === 瞬态检测 === update_diag('检测瞬态区域...'); transients = detect_transients(target_audio_adj, stft_params.win_len, stft_params.hop_size); % === 目标音频STFT === update_diag('对目标音频进行STFT...'); update_progress(0.1, '目标音频STFT'); [mag_target, phase_target] = optimized_stft(target_audio_adj, stft_params, @update_progress); update_diag(sprintf('目标音频STFT完成: %d帧', size(mag_target,2))); % === 源音频STFT === update_diag('对源音频进行STFT...'); update_progress(0.3, '源音频STFT'); [mag_source] = optimized_stft(source_audio_adj, stft_params, @update_progress); update_diag(sprintf('源音频STFT完成: %d帧', size(mag_source,2))); % 确保频谱矩阵大小相同 if size(mag_target, 2) ~= size(mag_source, 2) min_frames = min(size(mag_target, 2), size(mag_source, 2)); mag_target = mag_target(:, 1:min_frames); mag_source = mag_source(:, 1:min_frames); phase_target = phase_target(:, 1:min_frames); update_diag(sprintf('调整频谱帧数: %d帧', min_frames)); end % === 改进的频谱转换算法 === update_diag('应用改进的音色转换算法...'); update_progress(0.65, '频谱转换'); % 1. 计算源音频的频谱包络 mag_source_env = spectral_envelope(mag_source, stft_params.lifter_order, stft_params.nfft); % 2. 计算目标音频的频谱包络 mag_target_env = spectral_envelope(mag_target, stft_params.lifter_order, stft_params.nfft); % 3. 计算源音频的频谱细节(改进方法) mag_source_detail = spectral_detail(mag_source, mag_source_env); % 4. 应用转换:目标包络 + 源细节 mag_new = mag_target_env .* mag_source_detail; % 5. 频谱整形(增强音色特征) mag_new = spectral_shaping(mag_new, mag_source_env, mag_target_env); % % 6. 相位处理(直接使用目标相位) % phase_new = phase_target; % update_diag('使用目标音频相位'); % === 改进的相位处理 === if any(transients) update_diag('瞬态区域使用目标相位'); phase_new = phase_target; % 瞬态区域直接使用目标相位 else update_diag('非瞬态区域重建相位'); phase_new = phase_reconstruction(mag_new, phase_target, stft_params); end % === 重建音频 === update_diag('重建音频(ISTFT)...'); update_progress(0.90, 'ISTFT重建'); converted_audio = optimized_istft(mag_new, phase_new, stft_params, @update_progress); converted_audio = converted_audio / max(abs(converted_audio)); % 归一化 % === 添加后处理 === converted_audio = post_process(converted_audio, fs, source_audio, target_audio); % 确保长度匹配 if length(converted_audio) > target_length converted_audio = converted_audio(1:target_length); elseif length(converted_audio) < target_length converted_audio = [converted_audio; zeros(target_length - length(converted_audio), 1)]; end % 显示结果 plot(ax2, (0:length(converted_audio)-1)/fs, converted_audio); title(ax2, '转换后音频波形'); xlabel(ax2, '时间 (s)'); ylabel(ax2, '幅度'); grid(ax2, 'on'); % 显示转换后频谱 show_spectrum(ax6, converted_audio, fs, stft_params, '转换后频谱'); % 更新状态 update_progress(1.0, '转换完成'); set(status_text, 'String', '音色转换完成!'); update_diag('音色转换成功!', true); % 设置完成标志 conversion_complete = true; % 清理大内存变量 clear mag_target mag_source mag_new; catch e errordlg(['音色转换失败: ', e.message], '错误'); update_diag(['错误: ', e.message], true); set(progress_bar, 'FaceColor', [1, 0.3, 0.3]); set(progress_text, 'String', '处理失败'); end % 重置处理状态 processing = false; end %% === 后处理函数 === function y = post_process(x, fs, source, target) % 1. 瞬态增强 y = transient_enhancement(x, fs); % 2. 频谱均衡 if ~isempty(source) && ~isempty(target) y = spectral_eq(y, fs, source, target); end % 3. 动态范围控制 y = dynamic_range_control(y, fs); % 4. 最终归一化 y = y / max(abs(y)); end %% === 瞬态增强函数 === function y = transient_enhancement(x, fs) % 瞬态检测和增强 envelope = abs(hilbert(x)); diff_env = diff(envelope); diff_env = [diff_env(1); diff_env]; threshold = 0.1 * max(abs(diff_env)); transients = abs(diff_env) > threshold; attack_time = 0.005; decay_time = 0.05; attack_samples = round(attack_time * fs); decay_samples = round(decay_time * fs); gain_vector = ones(size(x)); transient_starts = find(diff([0; transients]) == 1); for i = 1:length(transient_starts) start_idx = transient_starts(i); end_idx = min(start_idx + attack_samples + decay_samples, length(x)); attack_phase = linspace(1, 1.8, attack_samples)'; decay_phase = linspace(1.8, 1, decay_samples)'; full_phase = [attack_phase; decay_phase]; valid_length = min(length(full_phase), end_idx - start_idx); gain_vector(start_idx:start_idx+valid_length-1) = full_phase(1:valid_length); end y = x .* gain_vector; y = y / max(abs(y)) * 0.98; limiter_threshold = 0.95; y(y > limiter_threshold) = limiter_threshold; y(y < -limiter_threshold) = -limiter_threshold; end %% === 修正后的频谱均衡函数 === function y = spectral_eq(y, fs, source, target) % 基于源和目标频谱特性的均衡 (绕过graphicEQ) % 1. 频谱分析参数 window_size = 1024; overlap = 512; nfft = 1024; % 2. 计算源音频平均频谱 [~, F, ~, Pxx_source] = spectrogram(... source, hamming(window_size), overlap, nfft, fs, 'power'); Pxx_source = mean(Pxx_source, 2); % 3. 计算目标音频平均频谱 [~, ~, ~, Pxx_target] = spectrogram(... target, hamming(window_size), overlap, nfft, fs, 'power'); Pxx_target = mean(Pxx_target, 2); % 4. 计算期望增益 (幅度比) desired_gain = sqrt(Pxx_target ./ (Pxx_source + eps)); gains_db = 20*log10(desired_gain); % 转换为dB % 5. 设计均衡滤波器组 (绕过graphicEQ) eq_filter = design_robust_eq(F, gains_db, fs); % 6. 应用均衡 y = filter(eq_filter, y); end %% 鲁棒的均衡滤波器设计函数 function Hd = design_robust_eq(F, gains_db, fs) % 方法1:尝试使用designfilt的graphiceq选项 try Hd = designfilt('graphiceq', ... 'SampleRate', fs, ... 'Frequencies', F, ... 'Gains', gains_db, ... 'Bandwidth', 1/3); % 1/3倍频程带宽 return; catch end % 方法2:手动创建级联滤波器组 try % 初始化滤波器集合 filters = {}; % 为每个频点设计峰值滤波器 for i = 1:length(F) % 跳过无效频点 if F(i) <= 0 || F(i) >= fs/2 continue; end % 设计峰值滤波器 [b, a] = peaking_filter(F(i), gains_db(i), fs); % 添加到滤波器集合 filters{end+1} = dfilt.df2(b, a); %#ok<AGROW> end % 级联所有滤波器 Hd = dfilt.cascade(filters{:}); return; catch end % 方法3:基础FIR均衡器 (最后防线) try % 创建目标频率响应 freq_vector = linspace(0, fs/2, 1024); gain_vector = interp1(F, gains_db, freq_vector, 'pchip', 'extrap'); mag_response = 10.^(gain_vector/20); % dB转幅度 % 设计FIR滤波器 Hd = designfilt('arbmagfir', ... 'FilterOrder', 256, ... 'Frequencies', freq_vector, ... 'Amplitudes', mag_response, ... 'SampleRate', fs); return; catch end % 所有方法均失败时返回直通滤波器 warning('所有均衡器设计方法均失败,返回直通滤波器'); Hd = dfilt.dffir(1); % 增益为1的FIR滤波器 end %% 峰值滤波器设计函数 function [b, a] = peaking_filter(fc, G, fs, Q) % 参数默认值 if nargin < 4 Q = 1.5; % 默认品质因数 end % 标准化频率 w0 = 2*pi*fc/fs; alpha = sin(w0)/(2*Q); % 增益线性转换 A = 10^(G/40); % 滤波器系数 b0 = 1 + alpha*A; b1 = -2*cos(w0); b2 = 1 - alpha*A; a0 = 1 + alpha/A; a1 = -2*cos(w0); a2 = 1 - alpha/A; % 归一化系数 b = [b0, b1, b2] / a0; a = [a0, a1, a2] / a0; end %% === 动态范围控制函数 === function y = dynamic_range_control(y, fs) compressor1 = compressor(... 'SampleRate', fs, ... 'Threshold', -20, ... 'Ratio', 2, ... 'KneeWidth', 6, ... 'AttackTime', 0.02, ... 'ReleaseTime', 0.1, ... 'MakeUpGainMode', 'Auto'); y = compressor1(y); end %% === 瞬态检测函数 === function transients = detect_transients(audio, win_len, hop_size) % 基于能量变化的瞬态检测 num_frames = floor((length(audio)-win_len)/hop_size) + 1; transients = false(num_frames, 1); prev_energy = 0; threshold = 0.3; % 能量变化阈值 for i = 1:num_frames start_idx = (i-1)*hop_size + 1; end_idx = start_idx + win_len - 1; frame = audio(start_idx:end_idx); frame_energy = sum(frame.^2); if i > 1 energy_diff = (frame_energy - prev_energy) / prev_energy; if energy_diff > threshold transients(i) = true; end end prev_energy = frame_energy; end end %% === 新增相位重建函数 === function phase = phase_reconstruction(mag, init_phase, params) % Griffin-Lim相位重建算法 num_iter = params.phase_iter; [num_bins, ~] = size(mag); current_phase = init_phase; for iter = 1:num_iter % 1. 创建复数频谱 S_half = mag .* exp(1i*current_phase); % 2. 重建时域信号 x_recon = optimized_istft(mag, current_phase, params, []); % 3. 重新计算STFT [~, new_phase] = optimized_stft(x_recon, params, []); % 4. 更新相位 current_phase = new_phase; end phase = current_phase; end % 替换原来的 spectral_envelope 函数 %% === 改进的频谱包络提取函数 === function env = spectral_envelope(mag, lifter_order, nfft) % 使用倒谱分析提取更准确的包络 [num_bins, num_frames] = size(mag); env = zeros(size(mag)); for i = 1:num_frames % 1. 对数幅度谱 log_mag = log(mag(:, i) + eps); % 2. 计算倒谱 cepstrum = real(ifft(log_mag, nfft)); % 3. 提升:保留低频部分(包络) cepstrum(lifter_order+1:end-lifter_order+1) = 0; % 4. 重建包络 env_frame = real(fft(cepstrum, nfft)); env(:, i) = exp(env_frame(1:num_bins)); end % 5. 时域平滑 env = movmean(env, 3, 2); end %% === 修改播放按钮回调 === function play_target_audio(~, ~) % 禁用播放按钮避免重复点击 set(handles.play_target_btn, 'Enable', 'off'); drawnow; try if isempty(target_audio) errordlg('目标音频为空!请先导入目标音频。', '播放错误'); return; end % 在后台线程中播放 parfeval(@() play_audio(target_audio, fs), 0); catch e errordlg(['播放失败: ' e.message], '播放错误'); end % 延迟后重新启用按钮 pause(1); % 防止立即重复点击 set(handles.play_target_btn, 'Enable', 'on'); end %% === 修改播放按钮回调 === function play_converted_audio(~, ~) % 禁用播放按钮避免重复点击 set(handles.play_target_btn, 'Enable', 'off'); drawnow; try if isempty(converted_audio) errordlg('目标音频为空!请先导入目标音频。', '播放错误'); return; end % 在后台线程中播放 parfeval(@() play_audio(converted_audio, fs), 0); catch e errordlg(['播放失败: ' e.message], '播放错误'); end % 延迟后重新启用按钮 pause(1); % 防止立即重复点击 set(handles.play_target_btn, 'Enable', 'on'); end % 进度更新函数 function update_progress(progress, message) if nargin >= 1 set(progress_bar, 'XData', [0, progress, progress, 0]); end if nargin >= 2 set(progress_text, 'String', message); set(status_text, 'String', message); end if nargin == 1 set(progress_text, 'String', sprintf('%.0f%%', progress*100)); end % 强制刷新界面 drawnow limitrate; end %% === 修改 play_audio 函数 === function play_audio(audio, fs) % 使用持久变量存储播放器状态 persistent player persistent is_playing % 初始化状态 if isempty(is_playing) is_playing = false; end % === 停止当前播放 === if is_playing && ~isempty(player) && isplaying(player) stop(player); is_playing = false; update_diag('停止当前播放'); end % === 增强音频验证 === if isempty(audio) || all(audio == 0) errordlg('无效的音频数据!', '播放错误'); update_diag('播放失败: 无效音频数据', true); return; end % 检查音频数据范围 if max(abs(audio)) > 1.5 audio = audio / max(abs(audio)); update_diag('音频已归一化', false); end % === 异步播放实现 === try % 确保使用列向量 if ~iscolumn(audio) audio = audio(:); end % 创建新播放器对象 player = audioplayer(audio, fs); % 设置回调函数 set(player, 'StartFcn', @play_start_callback); set(player, 'StopFcn', @play_stop_callback); set(player, 'TimerFcn', @play_progress_callback); set(player, 'TimerPeriod', 0.1); % 100ms更新一次 % 异步播放 play(player); is_playing = true; catch e % 错误处理 errordlg(['播放失败: ' e.message], '播放错误'); update_diag(['播放错误: ' e.message], true); % 尝试系统命令播放 try_system_play(audio, fs); end end %% === 播放回调函数 === function play_start_callback(obj, ~) % 播放开始回调 duration = obj.TotalSamples / obj.SampleRate; status_msg = sprintf('开始播放 (%.1f秒)', duration); set(status_text, 'String', status_msg); update_diag(status_msg, false); end function play_stop_callback(~, ~) % 播放结束回调 set(status_text, 'String', '播放完成'); update_diag('播放完成', false); end function play_progress_callback(obj, ~) % 播放进度回调 if isplaying(obj) current = obj.CurrentSample; total = obj.TotalSamples; fs = obj.SampleRate; elapsed = current / fs; total_time = total / fs; progress = current / total; set(progress_bar, 'XData', [0, progress, progress, 0]); set(progress_text, 'String', sprintf('播放进度: %.0f%%', progress*100)); status = sprintf('播放中: %.1f/%.1f秒', elapsed, total_time); set(status_text, 'String', status); end end %% === 备用播放方案 === function try_system_play(audio, fs) % 当audioplayer失败时使用系统命令播放 try % 保存临时音频文件 temp_file = [tempname '.wav']; audiowrite(temp_file, audio, fs); % 跨平台播放命令 if ispc % Windows系统 system(['start "" "' temp_file '"']); elseif ismac % macOS系统 system(['afplay "' temp_file '" &']); else % Linux系统 system(['aplay "' temp_file '" &']); end update_diag(['使用系统命令播放: ' temp_file], true); set(status_text, 'String', '使用外部播放器播放音频'); catch e errordlg(['备用播放失败: ' e.message], '播放错误'); update_diag(['备用播放失败: ' e.message], true); end end function stop_audio(~, ~) try % 获取当前所有播放器对象 allPlayers = findall(0, 'Type', 'audioplayer'); % 停止并删除所有播放器 for i = 1:length(allPlayers) if isplaying(allPlayers(i)) stop(allPlayers(i)); end delete(allPlayers(i)); end set(status_text, 'String', '播放已停止'); update_diag('音频播放已停止', true); catch e errordlg(['停止播放失败: ', e.message], '错误'); update_diag(['停止播放错误: ', e.message], true); end end % 保存音频函数 function save_audio(~, ~) if processing errordlg('处理正在进行中,请稍后保存', '错误'); return; end if isempty(converted_audio) errordlg('没有转换后的音频可保存!', '错误'); return; end [file, path] = uiputfile('*.wav', '保存转换音频'); if isequal(file, 0), return; end set(status_text, 'String', '正在保存音频...'); update_diag(['开始保存: ', file], true); try % 直接保存音频 filename = fullfile(path, file); audiowrite(filename, converted_audio, fs); set(status_text, 'String', ['已保存: ', file]); update_diag(['音频已保存: ', filename], true); catch e errordlg(['保存失败: ', e.message], '极错误'); update_diag(['保存错误: ', e.message], true); end end function show_spectrum(ax, audio, fs, params, title_str) try % 检查输入音频 if isempty(audio) || length(audio) < params.win_len error('无效音频数据: 长度=%d, 需要≥%d', length(audio), params.win_len); end % 计算STFT [~, ~, f, t] = optimized_stft(audio, params, []); % 直接使用optimized_stft的维度验证 [mag, ~, f, t] = optimized_stft(audio, params, []); spec_data = 10*log10(abs(mag) + eps); % 绘图 cla(ax); imagesc(ax, t, f, spec_data); % 坐标轴设置 set(ax, 'YDir', 'normal'); axis(ax, 'tight'); ylim(ax, [0, fs/2]); % 限制到Nyquist频率 title(ax, [title_str, sprintf(' (%.1f秒)', length(audio)/fs)]); xlabel(ax, '时间 (s)'); ylabel(ax, '频率 (Hz)'); colorbar(ax); colormap(ax, 'jet'); catch e % 错误处理 cla(ax); err_msg = sprintf('频谱错误: %s\n音频尺寸: %dx%d', e.message, size(audio,1), size(audio,2)); text(ax, 0.5, 0.5, err_msg, ... 'HorizontalAlignment', 'center', ... 'Color', 'red', ... 'FontSize', 9); title(ax, [title_str, ' (错误)']); end end end %% === 单帧相位重建函数 === function phase_frame = frame_phase_reconstruction(mag_frame, init_phase, params) % 基于MAGNA方法的快速单帧相位重建 num_iter = 3; % 减少迭代次数 nfft = params.nfft; phase_frame = init_phase; % 创建初始复数频谱 S_half = mag_frame .* exp(1i*phase_frame); % 创建完整频谱 S_full = zeros(nfft, 1); S_full(1:length(mag_frame)) = S_half; S_full(end-length(mag_frame)+2:end) = conj(S_half(end-1:-1:2)); for iter = 1:num_iter % 1. 逆FFT frame = real(ifft(S_full)); % 2. 正向FFT S_full_new = fft(frame, nfft); % 3. 保持幅度,更新相位 S_full_new = mag_frame .* exp(1i*angle(S_full_new(1:length(mag_frame)))); % 4. 重建完整频谱 S_full(1:length(mag_frame)) = S_full_new; S_full(end-length(mag_frame)+2:end) = conj(S_full_new(end-1:-1:2)); end phase_frame = angle(S_full_new); end %% === 改进的频谱细节函数 === function detail = spectral_detail(mag, env) % 更自然的细节提取 alpha = 0.5; % 细节增强因子 beta = 0.1; % 噪声抑制因子 % 基础细节计算 base_detail = mag ./ (env + eps); % 应用压缩函数避免极端值 detail = tanh(alpha * base_detail) / tanh(alpha); % 频域平滑 detail = imgaussfilt(detail, 1.5); end %% === 改进的频谱整形 === function mag_out = spectral_shaping(mag, env_source, env_target) % 更保守的频谱整形 balance_factor = 0.5; % 降低源音色特征强度 % 计算频谱比例因子(带限) freq_range = 1:min(100, size(mag,1)); % 只影响低频区域 ratio = ones(size(mag)); ratio(freq_range, :) = (env_source(freq_range, :) ./ env_target(freq_range, :)).^balance_factor; % 限制比例范围 ratio = min(max(ratio, 0.8), 1.2); % 应用比例因子 mag_out = mag .* ratio; end %% === 核心音频处理函数 === function [mag, phase, f, t] = optimized_stft(x, params, progress_callback) % 参数提取 win_len = params.win_len; hop_size = params.hop_size; nfft = params.nfft; fs = params.fs; % 输入验证 if isempty(x) || length(x) < win_len error('无效输入: 信号长度(%d) < 窗长(%d)', length(x), win_len); end % 创建窗函数 win = hann(win_len, 'periodic'); % 计算帧数 num_frames = floor((length(x) - win_len) / hop_size) + 1; % 初始化STFT矩阵 stft_matrix = zeros(nfft, num_frames); % === 关键修复: 正确的时间向量计算 === % 每帧的中心时间点 (秒) t = ((0:num_frames-1) * hop_size + win_len/2) / fs; % 进行STFT for i = 1:num_frames start_idx = (i-1) * hop_size + 1; end_idx = min(start_idx + win_len - 1, length(x)); segment = x(start_idx:end_idx); % 零填充短于窗长的段 if length(segment) < win_len segment = [segment; zeros(win_len - length(segment), 1)]; end segment = segment .* win; X = fft(segment, nfft); stft_matrix(:, i) = X; % 进度更新 if ~isempty(progress_callback) progress = i / num_frames; progress_callback(progress); end end % 取单边频谱 num_freq_bins = floor(nfft/2) + 1; stft_matrix = stft_matrix(1:num_freq_bins, :); % 计算幅度和相位 mag = abs(stft_matrix); phase = angle(stft_matrix); % 频率向量 (Hz) f = (0:num_freq_bins-1)' * (fs / nfft); % === 维度验证 === assert(size(mag, 1) == length(f), ... '频率维度不匹配: mag行数=%d, f长度=%d', size(mag,1), length(f)); assert(size(mag, 2) == length(t), ... '时间维度不匹配: mag列数=%d, t长度=%d', size(mag,2), length(t)); end function x_recon = optimized_istft(mag, phase, params, progress_callback) % 优化的逆短时傅里叶变换(ISTFT)实现 % 输入: % mag - 幅度谱 (单边谱) % phase - 相位谱 (单边谱) % params - 参数结构体 % progress_callback - 进度回调函数 % 输出: % x_recon - 重建的时域信号 % === 输入验证增强 === if isempty(mag) || isempty(phase) error('ISTFT输入为空'); end % === 参数提取 === nfft = params.nfft; win_len = params.win_len; hop_size = win_len - params.overlap; win_synth = params.win_synthesis; [num_bins, num_frames] = size(mag); % 计算信号总长度 total_samples = (num_frames - 1) * hop_size + win_len; x_recon = zeros(total_samples, 1); % 进度更新间隔 update_interval = max(1, floor(num_frames/10)); % === 重建复数频谱 === S_half = mag .* exp(1i * phase); % === 创建双边谱 === S_full = zeros(nfft, num_frames); if rem(nfft, 2) % 奇数点FFT S_full(1:num_bins, :) = S_half; S_full(num_bins+1:end, :) = conj(S_half(end:-1:2, :)); else % 偶数点FFT S_full(1:num_bins, :) = S_half; % 注意:Nyquist点处理 S_full(num_bins+1:end, :) = conj(S_half(end-1:-1:2, :)); end % === 执行逆FFT和重叠相加 === for frame_idx = 1:num_frames % 1. 逆FFT frame = real(ifft(S_full(:, frame_idx), nfft)); % 2. 应用合成窗 frame_win = frame(1:win_len) .* win_synth; % 3. 计算位置并叠加 start_idx = (frame_idx - 1) * hop_size + 1; end_idx = start_idx + win_len - 1; % 确保不越界 if end_idx > total_samples end_idx = total_samples; frame_win = frame_win(1:(end_idx - start_idx + 1)); end % 重叠相加 x_recon(start_idx:end_idx) = x_recon(start_idx:end_idx) + frame_win; % 4. 进度更新 if ~isempty(progress_callback) && mod(frame_idx, update_interval) == 0 progress_callback(frame_idx/num_frames * 0.2, ... sprintf('ISTFT重建: %d/%d', frame_idx, num_frames)); end end % === 归一化处理 === % 计算重叠因子 overlap_factor = win_len / hop_size; % 计算归一化窗口 norm_win = zeros(total_samples, 1); for i = 1:num_frames start_idx = (i - 1) * hop_size + 1; end_idx = min(start_idx + win_len - 1, total_samples); norm_win(start_idx:end_idx) = norm_win(start_idx:end_idx) + win_synth(1:(end_idx-start_idx+1)).^2; end % 避免除以零 norm_win(norm_win < eps) = eps; % 应用归一化 x_recon = x_recon ./ norm_win; end 在这个代码中,运行时总是卡在ISTFT中,每次卡的进度值不一样,请修改
06-14
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值