问题
I am creating a tuner for Android (similar to a guitar tuner) and I am wondering how to allow the tuner to run continuously (for a couple minutes or so). I don't want it to be a service that runs in the background, just while the user is in my app.
I have successfully used the AudioRecord
class and am obtaining data that seems correct. I am in the process of filtering this data and finding the fundamental frequency of the input signal, but need help figuring out how to allow my tuner to run continuously.
This is what my code looks like so far:
import android.app.Activity;
import android.graphics.Color;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.AudioTrack;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import com.dustin.tuner2.FFT;
import com.dustin.tuner2.Complex;
public class Tuner2 extends Activity implements OnClickListener {
Button btnTune;
TextView fft;
TextView freq;
TextView results;
MediaRecorder recorder;
AudioRecord tuner;
boolean startTuning = true;
int audioSource = MediaRecorder.AudioSource.MIC;
int sampleRateInHz = AudioTrack.getNativeOutputSampleRate(AudioManager.STREAM_SYSTEM);
int channelConfig = AudioFormat.CHANNEL_CONFIGURATION_MONO;
int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
int bufferSizeInBytes;
int samples;
short[] audioBuffer;
short[] audioData;
double[] temp;
String fileName;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
btnTune = (Button)findViewById(R.id.btnTune);
freq = (TextView)findViewById(R.id.freq);
btnTune.setOnClickListener(this);
bufferSizeInBytes = 4096;
//bufferSizeInBytes = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);
results = (TextView)findViewById(R.id.results);
fft = (TextView)findViewById(R.id.fft);
}
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
if (v == btnTune)
{
onTune(startTuning);
if (startTuning) {
((Button)v).setText("Stop Tuning");
}
else {
((Button)v).setText("Start Tuninig");
}
startTuning = !startTuning;
}
}
//------------------------------------------------------------>
private void onTune(boolean start) {
if(start) {
startTuning();
} else {
Toast.makeText(getApplicationContext(), "Tuning Stopped", Toast.LENGTH_SHORT).show();
tuner.stop();
}
}
private void startTuning()
{
tuner = new AudioRecord(audioSource, sampleRateInHz, channelConfig, audioFormat, bufferSizeInBytes);
audioData = new short[bufferSizeInBytes];
trigger();
}
public void trigger(){
acquire();
computeFFT();
display();
}
public void acquire(){
try {
tuner.startRecording();
samples = tuner.read(audioData, 0, bufferSizeInBytes);
}
catch (Throwable t){
}
}
public void computeFFT(){
//Conversion from short to double
double[] micBufferData = new double[bufferSizeInBytes];//size may need to change
final int bytesPerSample = 2; // As it is 16bit PCM
final double amplification = 100.0; // choose a number as you like
for (int index = 0, floatIndex = 0; index < bufferSizeInBytes - bytesPerSample + 1; index += bytesPerSample, floatIndex++) {
double sample = 0;
for (int b = 0; b < bytesPerSample; b++) {
int v = audioData[index + b];
if (b < bytesPerSample - 1 || bytesPerSample == 1) {
v &= 0xFF;
}
sample += v << (b * 8);
}
double sample32 = amplification * (sample / 32768.0);
micBufferData[floatIndex] = sample32;
}
//Create Complex array for use in FFT
Complex[] fftTempArray = new Complex[bufferSizeInBytes];
for (int i=0; i<bufferSizeInBytes; i++)
{
fftTempArray[i] = new Complex(micBufferData[i], 0);
}
//Obtain array of FFT data
final Complex[] fftArray = FFT.fft(fftTempArray);
final Complex[] fftInverse = FFT.ifft(fftTempArray);
//Create an array of magnitude of fftArray
double[] magnitude = new double[fftArray.length];
for (int i=0; i<fftArray.length; i++){
magnitude[i]= fftArray[i].abs();
}
fft.setTextColor(Color.GREEN);
fft.setText("fftArray is "+ fftArray[500] +" and fftTempArray is "+fftTempArray[500] + " and fftInverse is "+fftInverse[500]+" and audioData is "+audioData[500]+ " and magnitude is "+ magnitude[1] + ", "+magnitude[500]+", "+magnitude[1000]+" You rock dude!");
for(int i = 2; i < samples; i++){
fft.append(" " + magnitude[i] + " Hz");
}
}
public void display(){
results.setTextColor(Color.BLUE);
results.setText(audioData[1]+"");
for(int i = 2; i < samples; i++){
results.append(" " + audioData[i]);
}
results.invalidate();
//fft.setTextColor(Color.GREEN);
//fft.setText("Buffer size is "+bufferSizeInBytes);
//fft.setText(fftArray[1]+" Hz");
//for(int i = 2; i < samples; i++){
//fft.append(" " + fftArray[i] + " Hz");
//}
//fft.invalidate();
}
Do I need to change something concerning the button and what it does when pressed? Would it just involve the buffer size? How often I compute the FFT?
回答1:
Unless I am misunderstanding, you could just use a while loop that checks a boolean variable. When the user clicks the stop button set that variable to false.
while (tuning) {
trigger();
}
you should also probably introduce a delay between these calls. It would also be wise to run this code on a thread other than the UI thread. See http://developer.android.com/resources/articles/painless-threading.html
A simple example of what I mean would be to do
new Thread(new Runnable() {
@Override
public void run() {
while (tuning) {
trigger();
try {
Thread.sleep(SLEEP_TIME_MS);
} catch (InterruptedException e) {
// handle exception
}
}
}
}).start();
but then you have to worry about updating the UI as you cannot do that from this Thread. The best option is to use AsyncTask
http://developer.android.com/reference/android/os/AsyncTask.html
来源:https://stackoverflow.com/questions/8325656/how-to-create-tuner-that-runs-continuously