Как записать аудио и найти определенную частоту в Android (Java)
Я знаю, что есть много вопросов по этому поводу, но ни один из них не дает четкого ответа. Ниже приведен код, который я использую, чтобы попытаться сделать это.
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));
}
}
Вот и весь мой код, и он в основном работает. Проблема в том, что текстовое представление отображает 0 после нажатия кнопки. (Вы можете видеть, что функция getpitch() реагирует на нажатие кнопки.) Кроме того, функция Calculate() относится к другому аналогичному вопросу, хотя и не работает.
Я уверен, что он начинает запись и записывает ее в массив. Проблема в том, что я не знаю, как анализировать этот массив, чтобы получить частоту, или, скорее, найти определенную частоту.
Кто-нибудь знает как? Пожалуйста, постарайтесь не давать чрезвычайно сложных ответов, так как я новичок в этом.
3 ответа
Я решил исправить это, увеличив размер буфера до 176000 байт и умножив это число на количество секунд, которое я хотел записать. Я также добавил вызов stop() и release(). Часть измерения частоты крайне неточна (чуть менее половины частоты, с которой я тестировал), но ее, вероятно, можно откалибровать.
У вас есть несколько потенциально проблемных моментов:
sampleRate
знак равноsampleSize
в getpitch) вы выбрали 8000. В документации конструктора (который документацияgetMinBufferSize
относится к) у вас есть:Конструктор класса. Хотя некоторые неверные параметры приведут к исключению IllegalArgumentException, другие ошибки этого не делают. Таким образом, вы должны вызвать getState() сразу после создания, чтобы подтвердить, что объект можно использовать.
sampleRateInHz - int: частота дискретизации, выраженная в герцах. 44100 Гц в настоящее время является единственной частотой, которая гарантированно работает на всех устройствах, но другие частоты, такие как 22050, 16000 и 11025, могут работать на некоторых устройствах.
что для меня поднимает вопрос, если конструкция даже дала вам работу
AudioRecord
, Вы должны добавить упомянутую проверку сразу после создания, чтобы увидеть, так ли это.
- После проверки (см. Нижнюю часть этого ответа) - у вас действительно есть проблема с состоянием AudioRecord, даже если он только на некоторых устройствах (у меня произошел сбой, и я понимаю, что не у вас) -
В отличие от очень похожего метода, который принимает дополнительный
readMode
Параметр, здесь вы не можете указать, хотите ли вы, чтобы метод блокировал или нет. Кроме того, документация не содержит (на данный момент) информацию о поведении этого метода в этом смысле - блокирует ли он и ждет ли завершения записи или сразу же возвращается с максимально возможным количеством звука (см. Документацию другой метод read(short[], int, int, int)). Так что, если поведение здесь последнее, возможно, что вызов метода через одну строку после вызоваstartRecording()
на самом деле не позволяет ему записывать время, а затем просто не возвращает никаких данных. Чтобы проверить это - запишите возвращенное значение изread(short[], int, int)
вызов. Это значение скажет вам, были ли прочитаны какие-либо данные. Войдите и проверьте. Также обратите внимание на возможные коды ошибок, которые вы можете получить (опять же - документация).Я не уверен, что вы используете правильный код для того, что вы хотите в
calculate
метод. Эта реализация является очень простым способом вычисления частоты синусоидальной волны, то есть она даст вам частоту, если вы запишете чистую ноту / высоту / частоту. Для фактически записанного аудио, которое, скорее всего, имеет суперпозицию множества частот (то есть они составляют сложную волну, которую вы записываете). Я не уверен, что вы хотите от реалистично записанной волны, подобной этой, когда вы говорите "найдите определенную частоту", но я бы указал вам на разложение / анализ Фурье. Извините - вы хотели, чтобы все было просто, но я думаю, что это правильное направление. Вы можете быть довольны своим текущим кодом, если вы действительно записываете одну ноту (для настройки гитары, может быть), но я бы проверил, что эта реализация не слишком упрощена для ваших целей.
Я хотел проверить это сам, поэтому я открыл новый проект с вашим кодом (и добавил onCreate
прикрепить XML к Java). Я получил ошибку:
Вызывается: java.lang.IllegalStateException: startRecording() вызывается для неинициализированной AudioRecord.
на android.media.AudioRecord.startRecording(AudioRecord.java:894)
на com.trysoq_audiorecord.MainActivity.getpitch(MainActivity.java:58)
После добавления журнала для audioInput.getState()
сразу после строительства audioInput
Я получил 0, что в соответствии с документацией означает UNINITIALIZED. Так что я думаю, что мое предложение № 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();
}
}