You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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)

关键修复点说明

  1. Image源适配:将BytesIO中的图片数据转为base64编码字符串,赋值给image.src_base64,这是Flet支持的本地图片渲染方式。
  2. 同步逻辑优化:在可视化线程中,每个音频块入队后等待对应播放时长,确保可视化与音频进度一致;同时将update_loop放到子线程,避免阻塞Flet的UI事件循环。
  3. 路径修复:使用原始字符串处理Windows路径,避免转义字符导致的路径错误。
  4. 频谱图实现:原代码绘制的是波形图,修复后通过FFT转换实现了真正的频谱可视化。

内容的提问来源于stack exchange,提问作者PythonX

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 11:44:56