我试图从一个包含 1 个单词的语音记录的 .wav 文件中估计基频。
我试图做的是用 audioInputStream 读取文件。格式为 PCM_SIGNED 44100.0 Hz,16 位,立体声,4 字节/帧,little-endian。
因此,我制作了一个新缓冲区以仅包含一个通道。这段代码实现了:
double [] audioRight = new double[audioBytes.length/2];
for(int i = 0, k = 0; i <= audioBytes.length-1; i+=4, k+=2){
audioRight[k]=audioBytes[i];
audioRight[k+1]=audioBytes[i+1];
}
然后将数据移动到大小为两倍的 fftBuffer 中,然后应用 DFT。使用的库是 JTransform。使用的函数称为 realForwardFull。
DoubleFFT_1D fftDo= new DoubleFFT_1D(audioLeft.length);
double[] fftBuffer = new double [audioLeft.length*2];
for (int i = 0; i < audioLeft.length; i++){
fftBuffer[i] = audioLeft[i];
}
fftDo.realForwardFull(fftBuffer);
这给出了一个复数列表,我用它来计算每个复数的幅度/幅度,以制作功率谱。
用于获得幅度的公式 Amplitude=sqrt(IM IM+RE RE)。
这提供了一个幅度数组,我将谐波求和方法应用于这些幅度。谐波总和是指给出最高总和的指数 + 3 个谐波是表示基频的指数。
double top_sum = 0;
double first_index = 0;
double sum = 0;
double f_0 = 0;
double FR = audioInputStream.getFormat().getSampleRate()/2/ampBuffer.length;
for (int i = 50; i <= ampBuffer.length/4-1; i++){
sum = ampBuffer[i]+ampBuffer[i*2]+ampBuffer[i*3]+ampBuffer[i*4];
if (top_sum < sum){
top_sum=sum;
first_index = i;
然而,该索引需要映射回正确的频率域。据我了解,应该通过说 (index / fttBuffer.length)*sampleRate 来完成。
这提供了对基频的估计。
然而,结果并不“正确”。我有几个不同的 .wav 文件要测试,其中大多数文件的结果都超出了预期范围。对于相同的女性声音,三个不同的词给出的结果分别为 40、13 和 360。所有三个结果都预计在 250 到 350 的范围内。
我认为造成这种情况的一些问题是幅度缓冲区值。绘制时,该图未显示任何代表谐波的清晰峰。
这是图表的图像:
我知道这是很多信息,但我相信更多信息可以更容易理解所做的事情。
回顾:我不确定的是幅度数据。这些价值观有意义吗?他们绘制正确吗?在搜索谐波并找到基频之前,我是否需要对数据进行处理?
我考虑过应用某种窗口,因为我怀疑泄漏可能是为什么该图确实具有彼此不谐波的峰值。
任何帮助或建议将不胜感激。在此先感谢您的帮助!
编辑:作为对建议的尝试:
ByteBuffer buf = ByteBuffer.wrap(audioBytes);
buf.order(ByteOrder.LITTLE_ENDIAN);
double[] audio = new double[audioBytes.length/2];
for(int i = 0; i < audioBytes.length/2; i++) {
short s = buf.getShort();
double mono = (double) s;
double mono_norm = mono / 32768.0;
audio[i]=mono_norm;
}
现在应该将一个通道的 pcm 数据保存在数组 audio[] 中。