欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android Studio 开发实践——简易版音游APP(三)

程序员文章站 2022-04-28 18:53:47
...

音频采样

将音频转为数据流格式。

wav格式音频采样代码实现参考链接:https://www.jb51.net/article/68440.htm

wav格式音频格式理解参考链接:https://blog.csdn.net/ininw/article/details/70195934

mp3格式音频采样代码实现参考链接:https://blog.csdn.net/xyz_fly/article/details/7663036

mp3格式音频格式理解参考链接:https://blog.csdn.net/fulinwsuafcie/article/details/8972346

 

节奏点识别

傅里叶变换理解参考链接:https://zhuanlan.zhihu.com/p/19763358

FFT算法实现参考链接:https://blog.csdn.net/lwz45698752/article/details/81540855

节奏点识别代码实现参考链接(这一系列):https://blog.csdn.net/highmiao_19/article/details/99298462

 

大致原理就是我们音频采样得到的是时域上的信号,通过FFT将其转化成频域上的信号,即频谱,代表着频率,声音的音调就由频率区分,然后通过识别哪些点上频率有突然的变化,就可能是一个节奏的终点另一个节奏的起点,这就是我们要得到的时间点(放着滑块的点),也可以通过筛选人声或各种乐器的特定频率区段来得到更细致的节奏信息。

得到节奏点的大致思路是,得到频谱信息后,将音频信息分为一个个样本窗口,window为窗口大小,每个样本窗口里的 '第k次频谱实部值与第k-1次的差值' 的所有正差值的和为该窗口的光谱通量,window_size是该窗口周围的左右窗口数,取这些窗口的平均光谱通量值为本窗口的阈值,multiplier是阈值的加权权重,然后所有大于加权阈值的频谱对应时间点即为所求。(就是取一段时间的平均频率,大于这个频率的就是频率发生突变的节奏点,各种参数可以自行调整)

package com.example.free.Classes;

import java.util.ArrayList;
import java.util.List;

public class HandleData {

    //这是自定义或测试时设置的几个窗口大小和阈值的加权值
    public static final int WINDOW_256=256;
    public static final int WINDOW_1024=1024;
    public static final int WINDOW_2048=2048;
    public static final int WINDOW_SIZE_10=10;
    public static final int WINDOW_SIZE_20=20;
    public static final int WINDOW_SIZE_30=30;
    public static final double MULTIPLIER_1=1f;
    public static final double MULTIPLIER_1_5=1.5f;
    public static final double MULTIPLIER_2=2f;
    public static final double MULTIPLIER_2_5=2.5f;
    public static final double MULTIPLIER_3=3f;//
    public static final double MULTIPLIER_3_5=3.5f;
    public static final double MULTIPLIER_4=4f;

    public static void handleData(int[] data,ArrayList<Integer> allTime,int sampling,long musicTime,int _window,int window_size,double multiplier){

        final int window=_window;//样本窗口越大,,可能超过阈值的越多
        final int THRESHOLD_WINDOW_SIZE=window_size;//阈值左右各取多少个样本窗口的光谱通量!!!!越慢的歌要的越少,size越大,块数越多
        final double MULTIPLIER=multiplier;//阈值的加权值!!!!越慢的歌要的越高(快的歌太高,短时间变得快,块数越少)



        double[] dataFFT;//频谱
        List<Double> spectralFlux = new ArrayList<>();//每个样本窗口的光谱通量
        List<Double> threshold = new ArrayList<>( );//每个样本窗口的阈值


        int len = data.length;

        int m=len/window;//样本窗口数

        dataFFT=new double[len];
        double[] dataFFTVariation=new double[len-m];


        FFT fft=new FFT(window);//本地类

        double[] re=new double[window];//实部
        double[] im=new double[window];//虚部,为0

        double max=re[0];
        double min=re[0];

        for(int k=0;k<m;k++){
            //处理每个样本窗口
            for(int i=0;i<window;i++){
                re[i]=data[k*window+i];
                im[i]=0;
            }
            fft.fft(re, im);//每个样本窗口离散傅里叶变换后的fft,赋值到了re实部和im虚部里面

            double flux=0;//flux是样本窗口里本次采样频谱与上个频谱之差,所有频谱的差的正值和为该样本窗口的光谱通量
            for(int i=0;i<window;i++){
                dataFFT[k*window+i]=re[i];//把实部值添加到频谱数组里面

                if(i>0){
                    dataFFTVariation[k*window+i-1-k]=dataFFT[k*window+i]-dataFFT[k*window+i-1];
                    double value=dataFFTVariation[k*window+i-1-k];
                    flux+=value<0?0:value;
                }
            }

            spectralFlux.add( flux );
        }


        //阈值
        int delete=10;
        for(int i=0;i<spectralFlux.size()-delete;i++){//去除最后结尾异变的几个值
            int start = Math.max( 0, i - THRESHOLD_WINDOW_SIZE );//防止开头、结尾溢出
            int end = Math.min( spectralFlux.size()-delete - 1, i + THRESHOLD_WINDOW_SIZE );
            double mean = 0;
            for( int j = start; j <= end; j++ ) {
                mean += spectralFlux.get(j);
            }
            mean /= (end - start);
            threshold.add( mean * MULTIPLIER );//每个样本窗口与前后THRESHOLD_WINDOW_SIZE 10个样本窗口光谱通量的均值为该样本窗口的阈值
            if(mean<spectralFlux.get(i)){
                int time=(int)(i*window/(len*1.0)*musicTime);
                if((allTime.size()>0&&(time-allTime.get(allTime.size()-1))>100)||allTime.size()<1)
                    allTime.add(time);//添加进该时间节点
            }
        }
        for(int i=0;i<5;i++)
            allTime.remove(0);//去除整个音频开始结束无声音但有噪音的一段时间的时间点

    }
}