如何录制音频并在 Android (java) 中定位特定频率
How do I record audio and locate a certain frequency in Android (java)
我知道有很多关于此的问题,但其中 none 给出了明确的答案。以下是我用来尝试完成这项工作的代码。
package com.nonexistent.rs.sometestthing;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.AudioTrack;
import android.media.MediaRecorder;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
public static int calculate(int sampleRate, short [] audioData){
int numSamples = audioData.length;
int numCrossing = 0;
for (int p = 0; p < numSamples-1; p++)
{
if ((audioData[p] > 0 && audioData[p + 1] <= 0) ||
(audioData[p] < 0 && audioData[p + 1] >= 0))
{
numCrossing++;
}
}
float numSecondsRecorded = (float)numSamples/(float)sampleRate;
float numCycles = numCrossing/2;
float frequency = numCycles/numSecondsRecorded;
return (int)frequency;
}
public void getpitch(View v){
int channel_config = AudioFormat.CHANNEL_IN_MONO;
int format = AudioFormat.ENCODING_PCM_16BIT;
int sampleSize = 8000;
int bufferSize = AudioRecord.getMinBufferSize(sampleSize, channel_config, format);
AudioRecord audioInput = new AudioRecord(MediaRecorder.AudioSource.MIC, sampleSize, channel_config, format, bufferSize);
TextView txtview = (TextView)findViewById(R.id.text);
short[] audioBuffer = new short[bufferSize];
audioInput.startRecording();
audioInput.read(audioBuffer, 0, bufferSize);
//recorder.startRecording();
//recorder.read(audioBuffer, 0, bufferSize);
txtview.setText(""+calculate(8000,audioBuffer));
}
}
这就是我的所有代码,而且大部分都有效。问题是在我单击按钮后 textview 显示 0。 (你可以看到函数 getpitch() 响应按钮上的点击。)此外, calculate() 函数来自另一个类似的问题,但它不起作用。
我相当确定它确实开始记录,并且确实将它写入数组。问题是,我不知道如何分析该数组以获得频率,或者更确切地说,找到某个频率。
有人知道怎么做吗?请尽量不要给出非常复杂的答案,因为我对此很陌生。
你有几个潜在的问题点:
你选择的sampleRate
(=sampleSize
in getpitch)是8000,在构造函数的文档中(getMinBufferSize
的文档参考)你有:
Class constructor. Though some invalid parameters will result in an IllegalArgumentException exception, other errors do not. Thus you should call getState() immediately after construction to confirm that the object is usable.
sampleRateInHz - int: the sample rate expressed in Hertz. 44100Hz is currently the only rate that is guaranteed to work on all devices, but other rates such as 22050, 16000, and 11025 may work on some devices.
这对我来说提出了一个问题,如果这个结构甚至给了你一个工作 AudioRecord
。您应该在构建之后添加提到的检查以查看是否属于这种情况。
-- 检查后(请参阅此答案的底部)- AudioRecord 的状态确实有问题,即使它只是在某些设备上(它在我的设备上崩溃了,我知道它没有在你的)--
与接受额外 readMode
参数的非常相似的方法不同,此处您无法指定是否要阻止该方法。此外,该文档(此时)不包含有关此方法在这个意义上的行为的信息 - 它是否阻止并等待录制完成或 returns 立即使用尽可能多的音频(看在另一个 read(short[], int, int, int) method). So if the behaviour is the latter one here it is possible that calling the method one line after calling startRecording()
doesn't actually allow it any time whatsoever to record and it then just returns no data at all. To check that - capture the returned value from the read(short[], int, int)
call. This value will tell you whether any data was read. Log it and check. Also note the possible error codes you might get there (again - documentation).
的文档中
我不确定您在 calculate
方法中使用的代码是否正确。此实现是计算正弦波频率的一种非常直接的方法,这意味着如果您记录纯 note/pitch/frequency,它将为您提供频率。对于实际录制的音频,它很可能有很多频率的叠加(意味着它们加起来就是你录制的复杂波形)。当你说 "locate a certain frequency" 时,我不确定你想要从真实记录的波中得到什么,但我会向你指出傅立叶 decomposition/analysis。抱歉 - 您希望它保持简单,但我认为这是正确的方向。如果您确实录制了一个音符(可能用于调吉他),您可能会对当前代码感到满意,但我会检查此实现是否对您的目的来说过于简单。
我想自己检查一下,所以我打开了一个包含您的代码的新项目(并添加了 onCreate
以将 xml 附加到 java)。我收到错误:
Caused by: java.lang.IllegalStateException: startRecording() called on an uninitialized AudioRecord.
at android.media.AudioRecord.startRecording(AudioRecord.java:894)
at com.trysoq_audiorecord.MainActivity.getpitch(MainActivity.java:58)
在构造 audioInput
之后为 audioInput.getState()
添加日志后,我得到 0,根据文档,这意味着未初始化。所以我认为我的 #1 建议至少在这里有一个问题。
使用下面的代码
public class MainActivity extends Activity {
MediaRecorder recorder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
}
public void init(){
recorder=new MediaRecorder();
recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
recorder.setOutputFormat(MediaRecorder.OutputFormat.AMR_NB);
recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
recorder.setOutputFile("/storage/sdcard0/android_730_new.amr");
recorder.setMaxDuration(10000);
recorder.setOnInfoListener(new OnInfoListener() {
@Override
public void onInfo(MediaRecorder mr, int what, int extra) {
if(what==recorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED){
Toast.makeText(getApplicationContext(), "10 seconds recording completed....", 2000).show();
recorder.stop();
}
}
});
try{
recorder.prepare();
}catch (Exception e) {
// TODO: handle exception
}
}
public void start(View v){
recorder.start();
}
public void stop(View v){
recorder.stop();
}
}
我最终通过将缓冲区大小增加到 176000 字节并将该数字乘以我想要记录的秒数来解决此问题。我还添加了一个 stop() 和一个 release() 调用。频率测量部分非常不准确(略低于我测试频率的 1/2),但它可能可以校准。
我知道有很多关于此的问题,但其中 none 给出了明确的答案。以下是我用来尝试完成这项工作的代码。
package com.nonexistent.rs.sometestthing;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.AudioTrack;
import android.media.MediaRecorder;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
public static int calculate(int sampleRate, short [] audioData){
int numSamples = audioData.length;
int numCrossing = 0;
for (int p = 0; p < numSamples-1; p++)
{
if ((audioData[p] > 0 && audioData[p + 1] <= 0) ||
(audioData[p] < 0 && audioData[p + 1] >= 0))
{
numCrossing++;
}
}
float numSecondsRecorded = (float)numSamples/(float)sampleRate;
float numCycles = numCrossing/2;
float frequency = numCycles/numSecondsRecorded;
return (int)frequency;
}
public void getpitch(View v){
int channel_config = AudioFormat.CHANNEL_IN_MONO;
int format = AudioFormat.ENCODING_PCM_16BIT;
int sampleSize = 8000;
int bufferSize = AudioRecord.getMinBufferSize(sampleSize, channel_config, format);
AudioRecord audioInput = new AudioRecord(MediaRecorder.AudioSource.MIC, sampleSize, channel_config, format, bufferSize);
TextView txtview = (TextView)findViewById(R.id.text);
short[] audioBuffer = new short[bufferSize];
audioInput.startRecording();
audioInput.read(audioBuffer, 0, bufferSize);
//recorder.startRecording();
//recorder.read(audioBuffer, 0, bufferSize);
txtview.setText(""+calculate(8000,audioBuffer));
}
}
这就是我的所有代码,而且大部分都有效。问题是在我单击按钮后 textview 显示 0。 (你可以看到函数 getpitch() 响应按钮上的点击。)此外, calculate() 函数来自另一个类似的问题,但它不起作用。
我相当确定它确实开始记录,并且确实将它写入数组。问题是,我不知道如何分析该数组以获得频率,或者更确切地说,找到某个频率。
有人知道怎么做吗?请尽量不要给出非常复杂的答案,因为我对此很陌生。
你有几个潜在的问题点:
你选择的
sampleRate
(=sampleSize
in getpitch)是8000,在构造函数的文档中(getMinBufferSize
的文档参考)你有:Class constructor. Though some invalid parameters will result in an IllegalArgumentException exception, other errors do not. Thus you should call getState() immediately after construction to confirm that the object is usable.
sampleRateInHz - int: the sample rate expressed in Hertz. 44100Hz is currently the only rate that is guaranteed to work on all devices, but other rates such as 22050, 16000, and 11025 may work on some devices.
这对我来说提出了一个问题,如果这个结构甚至给了你一个工作
AudioRecord
。您应该在构建之后添加提到的检查以查看是否属于这种情况。
-- 检查后(请参阅此答案的底部)- AudioRecord 的状态确实有问题,即使它只是在某些设备上(它在我的设备上崩溃了,我知道它没有在你的)--
与接受额外
readMode
参数的非常相似的方法不同,此处您无法指定是否要阻止该方法。此外,该文档(此时)不包含有关此方法在这个意义上的行为的信息 - 它是否阻止并等待录制完成或 returns 立即使用尽可能多的音频(看在另一个 read(short[], int, int, int) method). So if the behaviour is the latter one here it is possible that calling the method one line after callingstartRecording()
doesn't actually allow it any time whatsoever to record and it then just returns no data at all. To check that - capture the returned value from theread(short[], int, int)
call. This value will tell you whether any data was read. Log it and check. Also note the possible error codes you might get there (again - documentation). 的文档中
我不确定您在
calculate
方法中使用的代码是否正确。此实现是计算正弦波频率的一种非常直接的方法,这意味着如果您记录纯 note/pitch/frequency,它将为您提供频率。对于实际录制的音频,它很可能有很多频率的叠加(意味着它们加起来就是你录制的复杂波形)。当你说 "locate a certain frequency" 时,我不确定你想要从真实记录的波中得到什么,但我会向你指出傅立叶 decomposition/analysis。抱歉 - 您希望它保持简单,但我认为这是正确的方向。如果您确实录制了一个音符(可能用于调吉他),您可能会对当前代码感到满意,但我会检查此实现是否对您的目的来说过于简单。
我想自己检查一下,所以我打开了一个包含您的代码的新项目(并添加了 onCreate
以将 xml 附加到 java)。我收到错误:
Caused by: java.lang.IllegalStateException: startRecording() called on an uninitialized AudioRecord.
at android.media.AudioRecord.startRecording(AudioRecord.java:894)
at com.trysoq_audiorecord.MainActivity.getpitch(MainActivity.java:58)
在构造 audioInput
之后为 audioInput.getState()
添加日志后,我得到 0,根据文档,这意味着未初始化。所以我认为我的 #1 建议至少在这里有一个问题。
使用下面的代码
public class MainActivity extends Activity {
MediaRecorder recorder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
}
public void init(){
recorder=new MediaRecorder();
recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
recorder.setOutputFormat(MediaRecorder.OutputFormat.AMR_NB);
recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
recorder.setOutputFile("/storage/sdcard0/android_730_new.amr");
recorder.setMaxDuration(10000);
recorder.setOnInfoListener(new OnInfoListener() {
@Override
public void onInfo(MediaRecorder mr, int what, int extra) {
if(what==recorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED){
Toast.makeText(getApplicationContext(), "10 seconds recording completed....", 2000).show();
recorder.stop();
}
}
});
try{
recorder.prepare();
}catch (Exception e) {
// TODO: handle exception
}
}
public void start(View v){
recorder.start();
}
public void stop(View v){
recorder.stop();
}
}
我最终通过将缓冲区大小增加到 176000 字节并将该数字乘以我想要记录的秒数来解决此问题。我还添加了一个 stop() 和一个 release() 调用。频率测量部分非常不准确(略低于我测试频率的 1/2),但它可能可以校准。