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?
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
Related
At first I should appologize if code below looks horrible and not formatted correctly. I don't know much about programming, so I took pieces of programs from different sources and just put them together.
In app I'd like to display frequency (using zero crossing method) of sound from phone mic.
When I running app it shows frequency once but doesn't updating it.
1) Can you please help me to find solution for displaying frequency values repeatedly in the same textbox (or in more appropriate widget on the screen)?
*2) Is it possible to make this app without buttons. I mean is that app shows frequency right after it starts?*SOLVED
Thank You!
package alar.alar.com.frequencyFreq;
import android.app.Activity;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.widget.TextView;
public class MainActivity extends Activity {
private static final int RECORDER_SAMPLERATE = 8000;
private static final int RECORDER_CHANNELS = AudioFormat.CHANNEL_IN_MONO;
private static final int RECORDER_AUDIO_ENCODING = AudioFormat.ENCODING_PCM_16BIT;
public AudioRecord recorder = null;
int numCrossing, p;
short audioData[];
public int frequency;
public boolean recording;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
int bufferSize = AudioRecord.getMinBufferSize(RECORDER_SAMPLERATE,
RECORDER_CHANNELS, RECORDER_AUDIO_ENCODING);
recorder = new AudioRecord(MediaRecorder.AudioSource.MIC,
RECORDER_SAMPLERATE, RECORDER_CHANNELS, RECORDER_AUDIO_ENCODING, bufferSize);
recorder.startRecording();
recording = true;
audioData = new short[bufferSize];
recorder.read(audioData, 0, bufferSize);
numCrossing = 0;
for (p = 0; p < bufferSize - 1; p++) {
if ((audioData[p] > 0 && audioData[p + 1] <= 0) ||
(audioData[p] < 0 && audioData[p + 1] >= 0)) {
numCrossing++;
}
}
frequency = (8000 / bufferSize) * (numCrossing / 2);
TextView textView = (TextView) this.findViewById(R.id.textView);
textView.setText(String.valueOf(frequency));
}//onCreate
}//activity
try this
private Timer timer;
private TimerTask timerTask;
public void onPause(){ super.onPause();
timer.cancel();
} public void onResume(){
super.onResume();
try {
timer = new Timer(); timerTask = new TimerTask() {
#Override
public void run() {
//your method }
};
timer.schedule(timerTask, 1000, 1000); } catch (IllegalStateException e){ android.util.Log.i("Damn", "resume error"); } }
I'm sampling audio on android, and sending some RGB values based on this to an arduino device using Bluetooth.
There's a long delay (several seconds) between the audio samples being sent, and the arduino reacting. I'm assuming this is caused by android being much faster than the arduino, and some sort of flow control going on where the transmitting bytes are getting backed up into a buffer on the phone. The code to connect bluetooth is:
mmSocket = mmDevice.createRfcommSocketToServiceRecord(uuid);
mmSocket.connect();
mmOutputStream = mmSocket.getOutputStream();
and then to send data:
mmOutputStream.write(0xFF);
mmOutputStream.write(outputFreq);
mmOutputStream.write(outputMagnitude);
I don't mind losing data, as I only need the most recent values to be sent.
What is the best way to achive this? I'm new to Android programming would like this to work quite soon so simpler solutions are better! I've though about some sort of stack, and a seperate thread that runs on a timer, and skims the top of the stack and sends just those values, but it sounds quite complex as i don't know anything about thread programming.
Is there a way instead to configure the outputstream that it simply discards data that hasn't been sent in time?
Here is the full code if it's any help:
package com.example.fft1;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import com.androidplot.series.XYSeries;
import com.androidplot.xy.BoundaryMode;
import com.androidplot.xy.LineAndPointFormatter;
import com.androidplot.xy.XYPlot;
import com.androidplot.xy.SimpleXYSeries;
import edu.emory.mathcs.jtransforms.fft.DoubleFFT_1D;
import android.graphics.Color;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.os.AsyncTask;
import android.os.Bundle;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.Intent;
import android.util.Log;
import android.view.Menu;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends Activity {
XYPlot plot;
SimpleXYSeries plotSeries;
AudioRecord audioRecord;
RecordAudio recordTask;
int frequency = 44100;
int channelConfiguration = AudioFormat.CHANNEL_IN_MONO;
int audioEncoding = AudioFormat.ENCODING_PCM_16BIT;
BluetoothAdapter mBluetoothAdapter;
BluetoothSocket mmSocket;
BluetoothDevice mmDevice;
OutputStream mmOutputStream;
InputStream mmInputStream;
int counter;
#SuppressWarnings("deprecation")
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// initialize our XYPlot reference:
plot = (XYPlot) findViewById(R.id.mySimpleXYPlot);
plot.setRangeBoundaries(-1000000, 1000000, BoundaryMode.FIXED);
Number[] seriesData = {1,2,3,4,5,6};
// Turn the above arrays into XYSeries':
plotSeries = new SimpleXYSeries(
Arrays.asList(seriesData), // SimpleXYSeries takes a List so turn our array into a List
SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, // Y_VALS_ONLY means use the element index as the x value
"Series1"); // Set the display title of the series
// Create a formatter to use for drawing a series using LineAndPointRenderer:
#SuppressWarnings("deprecation")
LineAndPointFormatter series1Format = new LineAndPointFormatter(
Color.rgb(0, 200, 0), // line color
Color.rgb(0, 100, 0), // point color
null); // fill color (none)
// add a new series' to the xyplot:
plot.addSeries(plotSeries, series1Format);
// reduce the number of range labels
plot.setTicksPerRangeLabel(3);
// by default, AndroidPlot displays developer guides to aid in laying out your plot.
// To get rid of them call disableAllMarkup():
plot.disableAllMarkup();
Button startBtn = (Button)findViewById(R.id.startButton);
int bufferSize = AudioRecord.getMinBufferSize(frequency, channelConfiguration, audioEncoding);
audioRecord = new AudioRecord(
MediaRecorder.AudioSource.DEFAULT,
frequency,
channelConfiguration,
audioEncoding,
bufferSize
);
startBtn.setOnClickListener(new startBtnClick());
Button connectBtn = (Button)findViewById(R.id.connectBtn);
connectBtn.setOnClickListener(new connectBtnClick());
}
class startBtnClick implements Button.OnClickListener {
#Override
public void onClick(View view) {
Button button = (Button) view;
if (button.getText().toString().equals("Start")) {
button.setText("Stop");
recordTask = new RecordAudio();
recordTask.execute();
} else {
button.setText("Start");
recordTask.cancel(false);
}
}
}
class connectBtnClick implements Button.OnClickListener {
#Override
public void onClick(View view) {
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if(!mBluetoothAdapter.isEnabled()) {
Intent enableBluetooth = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBluetooth, 0);
}
Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
if(pairedDevices.size() > 0) {
for(BluetoothDevice device : pairedDevices) {
Log.v("BT2", "Device: " + device.getName());
if(device.getName().equals("linvor")) {
mmDevice = device;
break;
}
}
}
UUID uuid = UUID.fromString("00001101-0000-1000-8000-00805f9b34fb"); //Standard SerialPortService ID
try {
mmSocket = mmDevice.createRfcommSocketToServiceRecord(uuid);
mmSocket.connect();
mmOutputStream = mmSocket.getOutputStream();
mmInputStream = mmSocket.getInputStream();
for (int i = 0; i < 255; i++) {
mmOutputStream.write(0xFF);
mmOutputStream.write(i);
mmOutputStream.write(255);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//beginListenForData();
}
private class RecordAudio extends AsyncTask<Void, Integer[], Void> {
#Override
protected Void doInBackground(Void... params) {
int blockSize = 128;
short[] buffer = new short[blockSize];
double[] bufferD = new double[blockSize];
audioRecord.startRecording();
// Here's the Fast Fourier Transform from JTransforms
DoubleFFT_1D fft = new DoubleFFT_1D(buffer.length);
while (!isCancelled()) {
counter = (counter + 1) % 1000;
//Log.v("FFT1", String.valueOf(counter));
int sumEnergy = 0;
logTime("start");
// Read audio to 'samples' array and convert it to double[]
audioRecord.read(buffer, 0, buffer.length);
logTime("after reading");
for (int i = 0; i < buffer.length; i++) {
bufferD[i]=buffer[i];
}
fft.realForward(bufferD);
logTime("after fft");
Integer[] spectrum = new Integer[blockSize/2];
for (int k = 0; k < blockSize / 2; k++) {
spectrum[k] = new Integer((int) Math.sqrt( (bufferD[2*k] * bufferD[2*k]) + (bufferD[2*k+1] * bufferD[2*k+1]) ));
}
int averageMagnitude = 0;
int middleFreqBin = 0;
for (int i = 0; i < spectrum.length; i++) {
averageMagnitude += spectrum[i];
}
averageMagnitude /= spectrum.length;
int halfMagnitudeSum = 0;
for (int i = 0; i < spectrum.length / 2; i++) {
halfMagnitudeSum += spectrum[i] * i;
}
halfMagnitudeSum /= 2;
int runningTotal = 0;
for (int i = 0; i < spectrum.length; i++) {
runningTotal += spectrum[i] * i;
if (runningTotal > halfMagnitudeSum) {
middleFreqBin = i;
break;
}
}
int outputMagnitude = map(averageMagnitude, 0, 50000, 0, 254);
int outputFreq = map(middleFreqBin, 0, spectrum.length, 0, 254);
if (outputMagnitude > 254) outputMagnitude = 254;
try {
//Log.v("FFT1", "OutputFreq: " + outputFreq + ", outputMagnitude: " + outputMagnitude);
mmOutputStream.write(0xFF);
mmOutputStream.write(outputFreq);
mmOutputStream.write(outputMagnitude);
Thread.sleep(10);
} catch (Exception e) {
// TODO Auto-generated catch block
Log.v("FFT1","Not connected");
}
logTime("after bluetooth");
publishProgress(spectrum);
}
return null;
}
protected void onCancelled() {
audioRecord.stop();
}
protected void onProgressUpdate(Integer[]... args) {
Integer[] spectrum = args[0];
plotSeries.setModel(Arrays.asList(spectrum), SimpleXYSeries.ArrayFormat.Y_VALS_ONLY);
plot.redraw();
}
int map(int x, int in_min, int in_max, int out_min, int out_max) {
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
}
#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;
}
public void logTime(String text) {
if (counter < 5) {
String time = String.valueOf(new java.util.Date().getTime());
Log.v("FFT1", text + ": " + time.substring(time.length()-4, time.length()));
}
}
}
The OutputStream.write(); lines returned immediately, but the amount of delay increased over time. Clearly data was getting backed up somewhere in the bluetooth stack, which was why I put in the Thread.sleep(10), to try and slow things down.
That caused other problems around blocking though, and I replaced it with a couple of lines that check when the last write() was before sending any new data. If it was less than a configured time (call it timeDelay), then it skips the new write(). Manually tuning the value of timeDelay then enabled me to avoid flooding the bluetooth stack. For in
I have helped from the book Pro Android media...
Here is the code:
public class MicMeter extends Activity implements OnClickListener {
RecordAudio recordTask;
int blocksize = 256;
int frequency = 8000;
int channelConfig = AudioFormat.CHANNEL_CONFIGURATION_MONO;
int audioEncoding = AudioFormat.ENCODING_PCM_16BIT;
TextView txt;
Button start;
boolean started = false;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mic_meter);
start = (Button)findViewById(R.id.button1);
txt = (TextView)findViewById(R.id.textView1);
start.setOnClickListener(this);
}
private class RecordAudio extends AsyncTask <Void,double[],Void>{
#Override
protected Void doInBackground(Void... params) {
try{
int bufferSize = AudioRecord.getMinBufferSize(frequency,channelConfig,audioEncoding);
AudioRecord audioRecord = new AudioRecord( MediaRecorder.AudioSource.MIC, frequency, channelConfig, audioEncoding, bufferSize);
short[] buffer = new short[blocksize];
double[] meter = new double[blocksize];
audioRecord.startRecording();
while(started){
int bufferReadResult = audioRecord.read(buffer, 0, blocksize);
for (int i = 0; i < blocksize && i < bufferReadResult; i++) {
meter[i] = (double) buffer[i] / 32768.0; // signed 16 bit
}
publishProgress(meter);
}
audioRecord.stop();
}catch (Throwable t) {
Log.e("AudioRecord","RecordingFail");
}
return null;
}
#Override
protected void onProgressUpdate(double[]... meter) {
for(int i = 0 ; i < meter[0].length ; i++){
double[] helper = meter[i];
txt.setText(Double.toString(helper));
}
}
}
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
if(started){
recordTask.cancel(true);
}else{
started = true;
recordTask = new RecordAudio();
recordTask.execute();
}
}
}
while i press the button.
It shows 255.0 and then it doesn't response...
Are there any way to fix it??
Are there any beter version about this?
thank
for(int i = 0 ; i < meter[0].length ; i++){
double helper = i;
txt.setText(Double.toString(helper));
setText overwrites the old value. So only the last call will show. The last call sets it to helper, which will always be meter[0].length. Since that's a fixed number, it won't change.
I have a problem reading audiodata from microphone in android. My app must change lightness of a logo picture depending on volume of a sound, which I read from microphone. I use AudioRecord class to get audiodata. Depending on sample rate and buffer size, every 2nd/4th/8th read operation goes with latency of 100-200 ms. So my app can't react on volume changes properly. I tried to reduce buffer size, and to execute data processing in other thread (using AudioRecord.setPositionNotificationPeriod() and callback) but those not make much sense - latency just becomes a little shorter. Here is my code:
package com.mikesoft.Clown;
import android.app.Activity;
import android.media.AudioFormat;
import android.media.AudioRecord.OnRecordPositionUpdateListener;
import android.media.AudioRecord;
import android.media.MediaRecorder.AudioSource;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import java.lang.Math;
public class TestActivity extends Activity implements OnClickListener, OnRecordPositionUpdateListener {
private static final String LOG_TAG = TestActivity.class.getSimpleName();
//arrays for selecting usable audioformat
private static int[] mSampleRates = new int[] {8000, 11025, 22050, 44100};
private static short[] audioFormat = new short[] {AudioFormat.ENCODING_PCM_8BIT, AudioFormat.ENCODING_PCM_16BIT};
private static short[] channelConfig = new short[] {AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.CHANNEL_CONFIGURATION_STEREO};
private short[][] buffers;
private int ix;
private AudioRecord recorder = null;
private int bufferSize = 0;
private AudioThread recordingThread = null;
private boolean isRecording = false;
/** The OpenGL View */
private LogoView glSurface;
//markers
private short[] buffer;
private long read_time;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_test);
findViewById(R.id.btnStart).setOnClickListener(this);
findViewById(R.id.btnStop).setOnClickListener(this);
mVolumeText = (TextView)findViewById(R.id.volume);
setRecordMode();
glSurface = (LogoView)findViewById(R.id.glSurface);
}
private void setRecordMode() {
((Button) findViewById(R.id.btnStop)).setClickable(isRecording);
((Button) findViewById(R.id.btnStart)).setClickable(!isRecording);
}
public void startRecording() {
if (null == recorder) {
recorder = findAudioRecord();
recorder.setRecordPositionUpdateListener(this);
recorder.startRecording();
isRecording = true;
recordingThread = new AudioThread();
}
Log.d(LOG_TAG, "Audio recording started");
}
//thread for executing audio recorder
private class AudioThread extends Thread {
private AudioThread() {
start();
}
#Override
public void run() {
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);
recordAudio();
}
}
private void recordAudio() {
ix = 0;
int read = 0;
recorder.setPositionNotificationPeriod(bufferSize);
while (isRecording) {
//long t1 = System.currentTimeMillis();
buffer = buffers[ix++ % buffers.length];
read = recorder.read(buffer, 0, buffer.length);
//time after reading
//read_time = System.currentTimeMillis();
//Log.d(LOG_TAG, "read bytes: " + read);
//Log.d(LOG_TAG, "read_time=" + (read_time - t1));
//int vol = 0;
//for (int i=0;i<buffer.length;i++)
// vol = Math.max(vol, Math.abs(buffer[i]));
//float norm = (float)vol/(float)Short.MAX_VALUE;
//glSurface.setLightness(norm);
}
}
//initialize AudioRecord with suitable format
public AudioRecord findAudioRecord() {
//for (int rate : mSampleRates) {
int rate = 22050;
for (short format : audioFormat) {
for (short channel : channelConfig) {
try {
Log.d(LOG_TAG, "Attempting rate " + rate + "Hz, bits: " + audioFormat + ", channel: "
+ channelConfig);
bufferSize = AudioRecord.getMinBufferSize(rate, channel, format);
if (bufferSize != AudioRecord.ERROR_BAD_VALUE) {
// check if we can instantiate and have a success
AudioRecord recorder = new AudioRecord(AudioSource.MIC, rate, channel, format, bufferSize);
//bufferSize/=8;
if (recorder.getState() == AudioRecord.STATE_INITIALIZED) {
buffers = new short[256][bufferSize];
return recorder;
}
}
} catch (Exception e) {
Log.e(LOG_TAG, rate + "Exception, keep trying.",e);
}
}
}
//}
return null;
}
public void stopRecording() {
if (null != recorder) {
isRecording = false;
recorder.stop();
recorder.release();
recorder = null;
recordingThread = null;
}
Log.d(LOG_TAG, "Audio recording stopped");
}
#Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btnStart:
startRecording();
setRecordMode();
break;
case R.id.btnStop:
stopRecording();
setRecordMode();
break;
default:
break;
}
}
#Override
public void onMarkerReached(AudioRecord arg0) {
// TODO Auto-generated method stub
}
#Override
public void onPeriodicNotification(AudioRecord arg0) {
float norm;
short[] buf = buffers[ix % buffers.length];
int vol = 0;
for (int i = 0; i < buf.length; i++)
vol = Math.max(vol, Math.abs(buf[i]));
norm = (float) vol / (float) Short.MAX_VALUE;
glSurface.setLightness(norm);
}
}
I tried devices&emulators with Android 1.6-3.2 but it's the same thing everywhere. Is there a solution for my problem in Java? Or I should use NDK approaches?
Will appreciate any help.
I made a little signal processing app. It processes audio signal (morse code) on certain frequency with Goerztel algorithm. Application saves temporary file to the filesystem and after recording is finished, starts to detect signals. Now I got the result with bunch of magnitudes.
I don't really know what to read from those magnitudes. How can I decode the morse code from those magnitudes? How can I read them? Tried to find references, but nowhere is explained what is the result and how to read it.
EDIT:
My morse code application is made with Delphi and uses Windows Beep function to send signals with certain frequency. I'm using 1200 Hz for signals. Also pauses between signals and words and morse beeps are like wikipedia described. All is accurate.
Goertzel.java:
public class Goertzel {
private float samplingRate;
private float targetFrequency;
private int n;
private double coeff, Q1, Q2;
private double sine, cosine;
public Goertzel(float samplingRate, float targetFrequency, int inN) {
this.samplingRate = samplingRate;
this.targetFrequency = targetFrequency;
n = inN;
sine = Math.sin(2 * Math.PI * (targetFrequency / samplingRate));
cosine = Math.cos(2 * Math.PI * (targetFrequency / samplingRate));
coeff = 2 * cosine;
}
public void resetGoertzel() {
Q1 = 0;
Q2 = 0;
}
public void initGoertzel() {
int k;
float floatN;
double omega;
floatN = (float) n;
k = (int) (0.5 + ((floatN * targetFrequency) / samplingRate));
omega = (2.0 * Math.PI * k) / floatN;
sine = Math.sin(omega);
cosine = Math.cos(omega);
coeff = 2.0 * cosine;
resetGoertzel();
}
public void processSample(double sample) {
double Q0;
Q0 = coeff * Q1 - Q2 + sample;
Q2 = Q1;
Q1 = Q0;
}
public double[] getRealImag(double[] parts) {
parts[0] = (Q1 - Q2 * cosine);
parts[1] = (Q2 * sine);
return parts;
}
public double getMagnitudeSquared() {
return (Q1 * Q1 + Q2 * Q2 - Q1 * Q2 * coeff);
}
}
SoundCompareActivity.java
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import android.app.Activity;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
public class SoundCompareActivity extends Activity {
private static final int RECORDER_SAMPLE_RATE = 8000; // at least 2 times
// higher than sound
// frequency,
private static final int RECORDER_CHANNELS = AudioFormat.CHANNEL_CONFIGURATION_MONO;
private static final int RECORDER_AUDIO_ENCODING = AudioFormat.ENCODING_PCM_16BIT;
private AudioRecord recorder = null;
private int bufferSize = 0;
private Thread recordingThread = null;
private boolean isRecording = false;
private Button startRecBtn;
private Button stopRecBtn;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
startRecBtn = (Button) findViewById(R.id.button1);
stopRecBtn = (Button) findViewById(R.id.button2);
startRecBtn.setEnabled(true);
stopRecBtn.setEnabled(false);
bufferSize = AudioRecord.getMinBufferSize(RECORDER_SAMPLE_RATE,
RECORDER_CHANNELS, RECORDER_AUDIO_ENCODING);
startRecBtn.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
Log.d("SOUNDCOMPARE", "Start Recording");
startRecBtn.setEnabled(false);
stopRecBtn.setEnabled(true);
stopRecBtn.requestFocus();
startRecording();
}
});
stopRecBtn.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
Log.d("SOUNDCOMPARE", "Stop recording");
startRecBtn.setEnabled(true);
stopRecBtn.setEnabled(false);
startRecBtn.requestFocus();
stopRecording();
}
});
}
private void startRecording() {
recorder = new AudioRecord(MediaRecorder.AudioSource.MIC,
RECORDER_SAMPLE_RATE, RECORDER_CHANNELS,
RECORDER_AUDIO_ENCODING, bufferSize);
recorder.startRecording();
isRecording = true;
recordingThread = new Thread(new Runnable() {
#Override
public void run() {
writeAudioDataToTempFile();
}
}, "AudioRecorder Thread");
recordingThread.start();
}
private String getTempFilename() {
File file = new File(getFilesDir(), "tempaudio");
if (!file.exists()) {
file.mkdirs();
}
File tempFile = new File(getFilesDir(), "signal.raw");
if (tempFile.exists())
tempFile.delete();
return (file.getAbsolutePath() + "/" + "signal.raw");
}
private void writeAudioDataToTempFile() {
byte data[] = new byte[bufferSize];
String filename = getTempFilename();
FileOutputStream os = null;
try {
os = new FileOutputStream(filename);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
int read = 0;
if (os != null) {
while (isRecording) {
read = recorder.read(data, 0, bufferSize);
if (read != AudioRecord.ERROR_INVALID_OPERATION) {
try {
os.write(data);
} catch (IOException e) {
e.printStackTrace();
}
}
}
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void deleteTempFile() {
File file = new File(getTempFilename());
file.delete();
}
private void stopRecording() {
if (recorder != null) {
isRecording = false;
recorder.stop();
recorder.release();
recorder = null;
recordingThread = null;
}
new MorseDecoder().execute(new File(getTempFilename()));
}
}
MorseDecoder.java:
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.ShortBuffer;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.os.AsyncTask;
import android.util.Log;
public class MorseDecoder extends AsyncTask<File, Void, Void> {
private FileInputStream is = null;
#Override
protected Void doInBackground(File... files) {
int index;
//double magnitudeSquared;
double magnitude;
int bufferSize = AudioRecord.getMinBufferSize(8000,
AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT);
Goertzel g = new Goertzel(8000, 1200, bufferSize);
g.initGoertzel();
for (int i = 0; i < files.length; i++) {
byte[] data = new byte[bufferSize];
try {
is = new FileInputStream(files[i]);
while(is.read(data) != -1) {
ShortBuffer sbuf = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer();
short[] audioShorts = new short[sbuf.capacity()];
sbuf.get(audioShorts);
float[] audioFloats = new float[audioShorts.length];
for (int j = 0; j < audioShorts.length; j++) {
audioFloats[j] = ((float)audioShorts[j]) / 0x8000;
}
for (index = 0; index < audioFloats.length; index++) {
g.processSample(data[index]);
}
magnitude = Math.sqrt(g.getMagnitudeSquared());
Log.d("SoundCompare", "Relative magnitude = " + magnitude);
g.resetGoertzel();
}
is.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
}
EDIT2:
Notices some bugs in processing samples. Changed code in while loop.
while(is.read(data) != -1) {
ShortBuffer sbuf = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer();
short[] audioShorts = new short[sbuf.capacity()];
sbuf.get(audioShorts);
float[] audioFloats = new float[audioShorts.length];
for (int j = 0; j < audioShorts.length; j++) {
audioFloats[j] = ((float)audioShorts[j]) / 0x8000;
}
for (index = 0; index < audioFloats.length; index++) {
g.processSample(audioFloats[index]);
magnitude = Math.sqrt(g.getMagnitudeSquared());
Log.d("SoundCompare", "Relative magnitude = " + magnitude);
}
//magnitude = Math.sqrt(g.getMagnitudeSquared());
//Log.d("SoundCompare", "Relative magnitude = " + magnitude);
g.resetGoertzel();
}
Regards,
evilone
The output of your Goertzel filter will increase when a tone within its passband is present, and then decrease when the tone is removed. In order to detect pulses of a tone, e.g. morse code, you need some kind of threshold detector on the output of the filter which will just give a boolean value for "tone present" / "tone not present" on a sample-by-sample basis. Try plotting the output values and it should be obvious once you see it in graphical form.
Plot the signal magnitudes on a graph versus time (some CW decoding apps for the PC do this in real-time). Now figure out what the graph for each Morse code symbol should look like. Then study some pattern matching algorithms. If there is enough noise present, you may want to try some statistical pattern matching methods.
Here's the Wikipedia link for proper Morse Code timing.