Flet音频可视化工具音频可播放但频谱无显示问题排查
问题
使用Flet框架开发音频可视化工具,音频可正常播放,但频谱可视化效果无法显示,终端无报错。已确认以下内容:
- 音频数据加载和归一化正常
update_visualization方法已被正确传入音频块数据- pygame音频播放功能正常
代码实现
import flet as ft import numpy as np from pydub import AudioSegment from pydub.utils import make_chunks import matplotlib.pyplot as plt import io import threading import pygame import queue # Audio settings sample_rate = 44100 chunk_size = 1024 class AudioVisualizer(ft.Container): def __init__(self, file_path: str) -> None: super().__init__() self.file_path = file_path self.image = ft.Image(src=io.BytesIO(), width=800, height=400) self.page = None self.audio_data = self.load_audio() self.audio_queue = queue.Queue() self.start_visualization() def load_audio(self): audio = AudioSegment.from_mp3(self.file_path) audio = audio.set_frame_rate(sample_rate) audio_data = np.array(audio.get_array_of_samples()) audio_data = audio_data / np.max(np.abs(audio_data)) # Normalize return audio_data def update_visualization(self, audio_data): try: plt.figure(figsize=(8, 4)) plt.plot(audio_data) plt.ylim(-1, 1) plt.gca().set_facecolor('white') buf = io.BytesIO() plt.savefig(buf, format='png', facecolor='white') # Set figure background to white buf.seek(0) self.image.src = buf if self.page: self.page.update() # Update the Flet image control else: print("Page reference is not set.") plt.close() except Exception as e: print(f"Error in update_visualization: {e}") def start_visualization(self): try: self.visualization_thread = threading.Thread(target=self.visualize_audio, daemon=True) self.visualization_thread.start() print("Visualization thread started.") self.play_audio() except Exception as e: print(f"Error in start_visualization: {e}") def visualize_audio(self): chunks = make_chunks(self.audio_data, chunk_size) for chunk in chunks: self.audio_queue.put(chunk) self.audio_queue.put(None) # Signal to stop the update loop def play_audio(self): try: pygame.mixer.init() pygame.mixer.music.load(self.file_path) pygame.mixer.music.play() print("Audio playback started.") except Exception as e: print(f"Error in play_audio: {e}") def update_loop(self): try: while True: audio_data = self.audio_queue.get() if audio_data is None: break self.update_visualization(audio_data) except Exception as e: print(f"Error in update_loop: {e}") def main(page: ft.Page) -> None: try: audio_file_path = "C:\Users\Administrator\Desktop\your_music_file.mp3" visualizer = AudioVisualizer(audio_file_path) visualizer.page = page # Add controls to the page page.add( ft.Container( content=visualizer.image, alignment=ft.alignment.center, expand=True, ) ) page.update() print("Page updated with visualizer.") # Start the update loop in the main thread visualizer.update_loop() except Exception as e: print(f"Error in main: {e}") ft.app(target=main)
分析与修复方案
核心问题1:Flet Image控件不支持直接传入BytesIO对象
Flet的Image.src仅支持文件路径、网络URL或base64编码的字符串,直接传入BytesIO对象无法被识别渲染。
核心问题2:可视化与音频播放不同步
当前代码会瞬间将所有音频块塞入队列并处理完,而音频还在播放,导致可视化提前结束;同时main线程被update_loop阻塞,干扰Flet的UI事件循环。
核心问题3:Windows路径转义错误
代码中的路径"C:\Users\Administrator\Desktop\your_music_file.mp3"存在转义问题,需改为双反斜杠或原始字符串格式。
修复后的完整代码
import flet as ft import numpy as np from pydub import AudioSegment from pydub.utils import make_chunks import matplotlib.pyplot as plt import io import threading import pygame import queue import base64 import time # Audio settings sample_rate = 44100 chunk_size = 1024 # 音频块播放时长(秒),用于同步可视化 chunk_duration = chunk_size / sample_rate class AudioVisualizer(ft.Container): def __init__(self, file_path: str) -> None: super().__init__() self.file_path = file_path self.image = ft.Image(width=800, height=400) self.page = None self.audio_data = self.load_audio() self.audio_queue = queue.Queue() self.is_playing = False def load_audio(self): audio = AudioSegment.from_mp3(self.file_path) audio = audio.set_frame_rate(sample_rate) audio_data = np.array(audio.get_array_of_samples()) audio_data = audio_data / np.max(np.abs(audio_data)) # Normalize return audio_data def update_visualization(self, audio_data): try: plt.figure(figsize=(8, 4)) # 替换为频谱图(原代码是波形图,FFT转换后得到频谱) fft_data = np.fft.fft(audio_data) freq = np.fft.fftfreq(len(audio_data), 1/sample_rate) plt.plot(freq[:len(freq)//2], np.abs(fft_data)[:len(freq)//2]) plt.ylim(0, 50) plt.gca().set_facecolor('white') plt.xlabel("Frequency (Hz)") plt.ylabel("Amplitude") buf = io.BytesIO() plt.savefig(buf, format='png', facecolor='white', bbox_inches='tight') buf.seek(0) # 将图片转为base64编码字符串,适配Flet Image控件 img_base64 = base64.b64encode(buf.getvalue()).decode('utf-8') self.image.src_base64 = img_base64 if self.page: self.page.update() plt.close() except Exception as e: print(f"Error in update_visualization: {e}") def start_visualization(self): try: self.visualization_thread = threading.Thread(target=self.visualize_audio, daemon=True) self.visualization_thread.start() print("Visualization thread started.") self.play_audio() except Exception as e: print(f"Error in start_visualization: {e}") def visualize_audio(self): chunks = make_chunks(self.audio_data, chunk_size) self.is_playing = True for chunk in chunks: if not self.is_playing: break self.audio_queue.put(chunk) # 等待对应音频块播放时长,保持可视化与音频同步 time.sleep(chunk_duration) self.audio_queue.put(None) def play_audio(self): try: pygame.mixer.init(frequency=sample_rate) pygame.mixer.music.load(self.file_path) pygame.mixer.music.play() print("Audio playback started.") # 将update_loop放入子线程,避免阻塞Flet主线程 threading.Thread(target=self.update_loop, daemon=True).start() except Exception as e: print(f"Error in play_audio: {e}") def update_loop(self): try: while True: audio_data = self.audio_queue.get() if audio_data is None or not self.is_playing: break self.update_visualization(audio_data) except Exception as e: print(f"Error in update_loop: {e}") def main(page: ft.Page) -> None: try: # 修复路径转义问题,使用原始字符串 audio_file_path = r"C:\Users\Administrator\Desktop\your_music_file.mp3" visualizer = AudioVisualizer(audio_file_path) visualizer.page = page page.add( ft.Container( content=visualizer.image, alignment=ft.alignment.center, expand=True, ) ) page.update() print("Page updated with visualizer.") # 启动可视化流程 visualizer.start_visualization() except Exception as e: print(f"Error in main: {e}") ft.app(target=main)
关键修复点说明
- Image源适配:将
BytesIO中的图片数据转为base64编码字符串,赋值给image.src_base64,这是Flet支持的本地图片渲染方式。 - 同步逻辑优化:在可视化线程中,每个音频块入队后等待对应播放时长,确保可视化与音频进度一致;同时将
update_loop放到子线程,避免阻塞Flet的UI事件循环。 - 路径修复:使用原始字符串处理Windows路径,避免转义字符导致的路径错误。
- 频谱图实现:原代码绘制的是波形图,修复后通过FFT转换实现了真正的频谱可视化。
内容的提问来源于stack exchange,提问作者PythonX
相关产品推荐
相关产品推荐

