• micropython 可视化音频 频谱解析(应该是全网首家)(预告,还没研究完成)


    本人是音乐爱好者,从小就特别喜欢那个随着音乐跳动的方框效果,就是这个:
    在这里插入图片描述
    arduino上一大把
    对,我忍你很久了,我就想用mpy做,全网没有,行我自己研究。
    果然兴趣是最好的老师,我之前有篇博客专门讲音频,有兴趣的可以回顾一下。

    提到可视化频谱,必然绕不开fft,大学学过这玩意,当时一心玩,老师讲的一个字都么听进去,网上教程简略扫了一下, 大该就是把时域转频域的工具,我大mpy居然没有fft函数,奶奶的,先放着。
    音频信息如何收集?第一种傻瓜式的ADC,模拟转数字,原始粗暴,第二种,I2S库,我之前博客有讲过,数据是PCM编码。
    然后又去学PCM编码,一学豁然开朗,舒服,以代码为例:

    audio_in = I2S(0,
                   sck=bck_pin, ws=ws_pin, sd=sdin_pin,
                   mode=I2S.RX,
                   bits=16,
                   format=I2S.MONO,
                   rate=8000,
                   ibuf=64000)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    这里初始化了一个I2S对象,应用题来了,已知:bits=16 rate=8000 buf=64000 单声道,请问可以录几秒?之前的博客实践告诉我,4秒,为啥是4秒呢?能懂这个PCM编码已经懂了一大半了,基本原理还是得自己啃,我不直接喂你,我尽量人话解释一下,bits=16:16位采样,也就是有65536中大小的数据,存一个数据需要2bytes,rate=8000hz,一秒存8000个数据,单声道,不用双声道,简化了,然后就算吧,2bytes80004=64000bytes,芜湖,刚好对上,这不就很明了了么,就是逐个bytes存的呀,至于具体咋编的码,我还要看下。

    micropython的I2S有几种模式的,阻塞,非阻塞,即时,翻文档:

    在这里插入图片描述
    这一看阻塞就没戏,试试剩下的两个喽。
    0905更新:
    目前非阻塞的可以正常用了,存完树蕨后吗,取出后出发回调函数继续存数据。

    资料太少,完整学FFT又太费精力,奋斗一天脑子快炸了。
    找了一个半成品的mpy项目,只给固件不给源码,最关键的两步还都打成c语言方法了,废了,可算终于能跑了,就是效果不太好。
    白嫖到了一个mpy的fft算法,效率位置,与numpy库的fft.fft比了一下,结果相同的。

    总体项目思路:
    fft必学必用,一句话就是时域转化成频域的方法,各种波皆能分解成N个正弦波之和。
    取音频部分,有两个方法,一是直接ADC麦,一个线,精度差噪声高,貌似需要用dma能达到较高的采样,官方mpy无此方法,居然在4年前的一个什么第三方固件里有这个方法,官方是真懒,可能觉得用得少,就一直没加。二是I2S协议麦克,精度高,三根线,往上说的用I2S采集ADC是个特么什么方法还是没搞懂,至少MPY不行,哎学会加C的功能块多么重要。
    有个什么定理 大致就是说采样频率必需比解析频率高两倍才能进行fft分析
    采样频率直接交给i2了,设置多少都行,就是体积爆炸,还有关键一点就是buf的编码翻了半天没有,只能凭感觉蒙,我认为是2bytes 小端的有符号编码,根据实际大小猜的,完整的python to_bytes有个signed=True的参数,mpy没有,草,手动转好了。
    最最最核心的fft变换到现在还是一脸懵,不过我肯定算法是输入数字列表,输出虚数列表,进行128长度的fft,单片机就很卡了,推荐64以下,不过这个fft的长度和采样率什么关系还是没搞懂,我采样了10000个数据,随便找连续的64个分析就行????
    分析完,取模,也会是abs代表各频率大小,那么多少分频又是咋算的,还有归一化怎么弄,我每个采样点大小是0-32768,这个咋算呢。

    9月5晚更新,通过用电脑端完整python的库,验证了我数据计算的想法全是对的,初步有点效果,目前就是杂音大,识别的频率不太shoul受控制,其他的基本都ok了。
    此项目学习的东西:
    1.mpy fft (仅代码实现和概念,原理公式压根没学)
    2.wav pcm的编码方式

    差的:具体采样控制 频谱识别控制

    9.6更新:
    哎,就怕专研啊,今天是研究fft的第三天,终于可以说看懂皮毛辣
    涉及的点多知识杂,网上的教程都是一块一块的,经过三天的灌输,脑子中的理念终于连城串了,不容易,现在我不实际上代码都敢肯定自己掌握的没错,这次真的懂了。
    1.采样率的选择,必须大于实际频率的2倍,常用音乐基本在3k以下,虽然说音乐是20k,也就是说那些超高频其实用的不多,为了让频谱更直观,这里直接采样率拉到6400,或者12800,至于为啥,是为了方便整除呀,不能整除的话,涉及到一个什么词来的,反正不好,影响效果。
    2.fft采样点数,或者叫分频数,这个概念是给我添麻烦最多的,一会儿叫这个一会儿叫那个,什么鬼,现在我脑子里就认定他是fft数,就完了,一般取2的倍数,也是方便运算,esp32实测128比较慢,不流畅,所以我只能用64了,用64表示fft运算数组长度为64,然后频率也是分成64份呀,这个非常非常关键,6400hz/64 也就是说横轴每个单位代表100hz,然后由于什么对称,所以我是0-3200hz 间隔100hz的结果,也就是选64个点,由于对称,只能用32个点。
    3 幅度计算,这个也头大好久,fft变换后,对每个复数取模,就完事儿啦,有个教程取对数是为了计算db,给我搞蒙了好久,还有个概念是直流分量,就是第一个数不能用,肯定很大,用来表示基础的,画图是直接删去,后面的要归一化,归一化就是除以fft数的一半

    糙,就这么些东西搞了整整三天终于把代码层弄通啦,写代码我有强迫症,不放源码的,不懂具体实现的一概视为没掌握,老想搞懂。肯定还是有很多细节需要打磨的,这个有精力再弄吧。
    最终上代码留念,有兴趣的可以看看,很乱,懒得整理了:

    import array, time, gc
    from math import *
    from cmath import *
    from machine import Pin, SoftI2C,I2S
    from ssd1306 import SSD1306_I2C
    
    i2c = SoftI2C(sda=Pin(13), scl=Pin(14))
    oled = SSD1306_I2C(128, 64, i2c, addr=0x3c)
    oled.show()  
    
    def FFT(P):   #mpy比较精简的fft,需要math,cmath
        n = len(P)
        if n == 1: return P
    
        ye = FFT(P[0::2])
        yo = FFT(P[1::2])
    
        y = [0]*n
        w = exp(-1j*2*pi/n)
        n2 = n//2
        for j in range(n//2):
            wj = w ** j
            yow = [a*wj for a in yo]
    
            y[j] = ye[j] + yow[j]
            y[j+n2] = ye[j] - yow[j]
    
        return y
    
    bck_pin = Pin(23)
    ws_pin = Pin(5)
    sdin_pin = Pin(18)
    audio_in = I2S(0,
                   sck=bck_pin, ws=ws_pin, sd=sdin_pin,
                   mode=I2S.RX,
                   bits=16,
                   format=I2S.MONO,
                   rate=6400,
                   ibuf=6400)
    
    mic_samples = bytearray(64)
    
    top, downstep = [0.]*32, 1 # 用于存放最上面的点
    mark = 4
    
    def i2s_callback(t):
        cyd=64   #多少个采样点
        start = time.ticks_ms()
        oled.fill(0)
        global num_read
        num_read = audio_in.readinto(mic_samples)
        fdlist=[]
        #print(int.from_bytes(mic_samples[0:2],'little'))
        for i in range(0,cyd):
            twobits=mic_samples[2*i:2*i+2]
            #print(twobits)
            wa=int.from_bytes(twobits,'little')
            if wa>32000:
                wa=wa-65536
            fdlist.append(wa)
        #print(fdlist[0],fdlist[1])
        y=FFT(fdlist)[0:32]
        x = [abs(yy)/32 for yy in y]   #归一化
        ruler = 64 / (max(x[1:32]) + 0.001)
        ruler = mark if ruler > mark else ruler
        #print(x)
        for i in range(0,30):
            top[i] = top[i] - downstep / ruler if top[i] > x[i + 2] else x[i + 2]
            if top[i] < 0: top[i] = 0
            oled.fill_rect((i+1)*4, 63 - int(x[i+2] * ruler), 3, int(x[i+2] * ruler) + 1 , 1)
            #oled.fill_rect((i+2)*4, 63 - int(top[i] * ruler), 2, 2, 1)
        gc.collect()
        end = time.ticks_ms()
        fps = 1000/(end - start);
        #oled.text("fps=" + str(round(fps,1)), 60, 0)
        #print(fps)
        oled.show()
            
    
    audio_in.irq(i2s_callback)
    
    num_read = audio_in.readinto(mic_samples)
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
  • 相关阅读:
    flink cdc原理与使用
    将神经网络粒子化的内在合理性
    决策树与随机森林
    Nginx map 实现时间格式转换
    react antd 一些问题和要注意的地方
    【Ubuntu·系统·的Linux环境变量配置方法最全】
    Spring-bean实例化三种方式
    华为数通方向HCIP-DataCom H12-831题库(多选题:241-259)
    2022 LGR 非专业级别软件能力认证第一轮 (SCP-S) 提高级 C++语言模拟试题
    RPC是什么?RPC与HTTP的关系
  • 原文地址:https://blog.csdn.net/jd3096/article/details/126684057