音頻採樣
將音頻轉爲數據流格式。
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);//去除整個音頻開始結束無聲音但有噪音的一段時間的時間點
}
}