我正在尝试开发一款从乐器中提取音频的Android应用程序。我正在使用快速傅立叶变换方法和Jtransforms。以下是我目前所拥有的:
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new readFrequencies().execute();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
private class readFrequencies extends AsyncTask<Void,Integer,Integer> {
@Override
protected Integer doInBackground(Void... arg0) {
AudioRecord recorder = null;
int bufferSize = 0;
boolean recording = true;
int rate = 8000;
short audioFormat = AudioFormat.ENCODING_PCM_16BIT;
short channelConfig = AudioFormat.CHANNEL_IN_MONO;
try {
bufferSize = AudioRecord.getMinBufferSize(rate,channelConfig, audioFormat);
recorder = new AudioRecord(AudioSource.DEFAULT, rate,
channelConfig, audioFormat, bufferSize);
if (recorder.getState() == AudioRecord.STATE_INITIALIZED) {
/*
* Android 4.1.2
*
int recorder_id = recorder.getAudioSessionId();
if (NoiseSuppressor.isAvailable()) NoiseSuppressor.create(recorder_id);
*/
}
else {
Toast.makeText(getApplicationContext(),
"Error en la inicialización", Toast.LENGTH_SHORT).show();
}
} catch (Exception e) {}
short[] audioData = new short[bufferSize];
if (recorder != null) {
while (recording) {
if (recorder.getRecordingState() == AudioRecord.RECORDSTATE_STOPPED) {
recorder.startRecording();
}
else {
int numshorts = recorder.read(audioData,0,audioData.length);
if ((numshorts != AudioRecord.ERROR_INVALID_OPERATION) &&
(numshorts != AudioRecord.ERROR_BAD_VALUE)) {
// Hann
double[] preRealData = new double[bufferSize];
double PI = 3.14159265359;
for (int i = 0; i < bufferSize; i++) {
double multiplier = 0.5 * (1 - Math.cos(2*PI*i/(bufferSize-1)));
preRealData[i] = multiplier * audioData[i];
}
DoubleFFT_1D fft = new DoubleFFT_1D(bufferSize);
double[] realData = new double[bufferSize * 2];
for (int i=0;i<bufferSize;i++) {
realData[2*i] = preRealData[i];
realData[2*i+1] = 0;
}
fft.complexForward(realData);
double magnitude[] = new double[bufferSize / 2];
for (int i = 0; i < magnitude.length; i++) {
double R = realData[2 * i];
double I = realData[2 * i + 1];
magnitude[i] = Math.sqrt(I*I + R*R);
}
int maxIndex = 0;
double max = magnitude[0];
for(int i = 1; i < magnitude.length; i++) {
if (magnitude[i] > max) {
max = magnitude[i];
maxIndex = i;
}
}
int frequency = rate * maxIndex / bufferSize;
publishProgress(frequency);
}
else {
if (numshorts == AudioRecord.ERROR_BAD_VALUE) {
Toast.makeText(getApplicationContext(),
"ERROR_BAD_VALUE", Toast.LENGTH_SHORT).show();
}
else {
Toast.makeText(getApplicationContext(),
"ERROR_INVALID_OPERATION", Toast.LENGTH_SHORT).show();
}
return -1;
}
}
}
if (recorder.getState() == AudioRecord.RECORDSTATE_RECORDING)
recorder.stop(); //stop the recorder before ending the thread
recorder.release();
recorder=null;
}
return 0;
}
protected void onProgressUpdate(Integer... f) {
TextView texto = (TextView) findViewById(R.id.texto);
texto.setText(String.valueOf(f[0]));
}
protected void onPostExecute(Integer f) {
TextView texto = (TextView) findViewById(R.id.texto);
int frecuencias = f.intValue();
texto.setText(String.valueOf(frecuencias));
}
}
}
有了这个代码,我可以从产生纯信号的频率发生器中获得准确的频率。然而,当我用仪器尝试同样的方法时,我会获得随机频率。我知道,当涉及到真实的仪器时,产生的信号包含谐波,这可能会影响最终结果,但我不知道在这种情况下如何获得真实的频率。有人能帮我吗?
我使用过TarsosDSP,也尝试过自相关方法,但我一直没能得到我想要的。
提前谢谢。
频谱中最强大的频率分量与人类听众感知到的音高之间存在巨大差异。
这篇学术论文可能是对解决音调检测的一些问题的方法的决定性综述,但没有解决使用真实信号需要处理的感知问题。至少你需要考虑到在音符的开头和结尾跳八度的弦乐器和管柱乐器,以及缺少基本功的情况。这将是安卓系统上的一次特殊抽奖,因为设备上的麦克风在任何方面都不一致。
在频谱中搜索峰值频率并不比使用自相关更好,并且经常与真实信号错误。
FFT方法(更恰当地说,使用STFT和根据相位差的频率估计)可以起作用,但只需要对频谱进行大量后处理。一些商业彩色调谐器应用程序使用基于FFT的方法(我可以证明这已经写了一个)。
你可能想看看Sonic Visualiser的一些功能提取插件是如何解决这个问题的。
我还建议回顾一下之前在这里提出的大量问题——通常是想要构建吉他调谐器的提问者。
值得一提的是,我相信乐器的电子调谐器(如吉他调谐器等)不会这样做。他们不是像你那样做FFT,而是简单地测量波的周期(即零交叉之间的时间),然后根据周期计算频率。