FFT
在前面例程我们介绍了FFT(傅里叶变换)和音频的录制与播放实验,本次实验结合二者,通过将时域采集到的音频数据通过 FFT 为频域。
例程功能:获取耳机麦克风的音频数据作为时域数据输入 FFT 模块进行 FFT 得到频域数据后,计算频域数据各个频率点的幅值并在 LCD 上进行直观的图像显示。

代码展示
python
import time
import os
import urandom
import sys
import array
import gc
from media.media import * #导入media模块,用于初始化vb buffer
from media.pyaudio import * #导入pyaudio模块,用于采集和播放音频
from machine import FFT
from media.display import *
import media.wave as wave #导入wav模块,用于保存和加载wav音频文件
HIST_NUM = 50
FFT_POINTS = 128
DISPLAY_MODE = "LCD"
# 根据模式设置显示宽高
if DISPLAY_MODE == "VIRT":
# 虚拟显示器模式
DISPLAY_WIDTH = ALIGN_UP(800, 16)
DISPLAY_HEIGHT = 480
hist_width = int(DISPLAY_WIDTH / HIST_NUM)
elif DISPLAY_MODE == "LCD":
# LCD寸屏幕模式
DISPLAY_WIDTH = ALIGN_UP(480, 16)
DISPLAY_HEIGHT = 800
hist_width = int(DISPLAY_HEIGHT / HIST_NUM)
elif DISPLAY_MODE == "HDMI":
# HDMI扩展板模式
DISPLAY_WIDTH = ALIGN_UP(1920, 16)
DISPLAY_HEIGHT = 1080
hist_width = int(DISPLAY_WIDTH / HIST_NUM)
else:
raise ValueError("请选择 'VIRT', 'LCD' 或 'HDMI'")
def exit_check():
try:
os.exitpoint()
except KeyboardInterrupt as e:
print("用户停止: ", e)
return True
return False
def audio_fft(filename):
# 创建用于绘制的图像
img = image.Image(DISPLAY_WIDTH, DISPLAY_HEIGHT, image.RGB565)
if DISPLAY_MODE == "VIRT":
# 使用 IDE 作为输出显示,可以设定任意分辨率
Display.init(Display.VIRT, width=DISPLAY_WIDTH, height=DISPLAY_HEIGHT, fps=30)
elif DISPLAY_MODE == "LCD":
# 使用 LCD 作为显示输出
Display.init(Display.ST7701, width=DISPLAY_WIDTH, height=DISPLAY_HEIGHT,fps=10, to_ide=True)
elif DISPLAY_MODE == "HDMI":
# 使用 HDMI 作为显示输出
Display.init(Display.LT9611, to_ide=True)
else:
raise ValueError("请选择 'VIRT', 'LCD' 或 'HDMI'")
try:
wavef = wave.open(filename, 'rb') #打开wav文件
CHUNK = int(wavef.get_framerate()/25) #设置音频chunk值
pya = PyAudio() #创建PyAudio对象
pya.initialize(CHUNK) #初始化PyAudio对象
MediaManager.init() #vb buffer初始化
#创建音频输出流,设置的音频参数均为wave中获取到的参数
stream = pya.open(format=pya.get_format_from_width(wavef.get_sampwidth()),
channels=wavef.get_channels(),
rate=wavef.get_framerate(),
output=True,frames_per_buffer=CHUNK)
stream.volume(vol=30) #设置音频输出流的音量
data = wavef.read_frames(CHUNK) #从wav文件中读取数一帧数据
#从音频输入流中获取数据写入到音频输出流中,并用柱状图显示音频数据
while True:
stream.write(data) #将帧数据写入到音频输出流中
img.clear()
# 读取音频数据,缓存在data
data = wavef.read_frames(CHUNK) #从wav文件中读取数一帧数据
# 创建一个FFT对象,运算点数为FFT_POINTS,偏移是0
fft1 = FFT(data,FFT_POINTS,0)
# 对data进行进行傅里叶变换,并返回相应的频率
res = fft1.run()
# 获取各个频率点的幅值
amp = fft1.amplitude(res)
# 将获取的音频幅值转换为,显示矩形高度
if DISPLAY_MODE == "LCD":
for hist in range(HIST_NUM):
if amp[hist] > DISPLAY_WIDTH:
hist_height = DISPLAY_WIDTH
else:
hist_height = amp[hist]
# 根据音频幅值画矩形
img.draw_rectangle(0,hist * hist_width, hist_height,
hist_width,color=(255, 255, 255), fill=True)
else:
for hist in range(HIST_NUM):
if amp[hist] > DISPLAY_HEIGHT:
hist_height = DISPLAY_HEIGHT
else:
hist_height = amp[hist]
# 根据音频幅值画矩形
img.draw_rectangle(hist * hist_width, DISPLAY_HEIGHT - hist_height, hist_width,
hist_height,color=(255, 255, 255), fill=True)
# 将绘制的图像显示
Display.show_image(img)
time.sleep(0.01)
os.exitpoint()
gc.collect()
if exit_check():
break
except BaseException as e:
print(f"异常: {e}")
# 销毁显示
finally:
stream.stop_stream() #停止音频输出流
stream.close() #关闭音频输出流
pya.terminate() #释放音频对象
Display.deinit()
os.exitpoint(os.EXITPOINT_ENABLE_SLEEP)
time.sleep_ms(100)
# 释放媒体缓冲区
MediaManager.deinit()
if __name__ == "__main__":
os.exitpoint(os.EXITPOINT_ENABLE)
print("audio sample start")
audio_fft('/sdcard/examples/Jzhou.wav') #采集音频并输出
print("audio sample done")
效果展示
点击 CanMV IDE 上的"开始(运行脚本)"按钮后, 便了看到 LCD 上显示了板载数字麦克风采集到音频数据的频谱图,如下图所示:
【学习k230 - 例程23】音频FFT柱状图