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.
Related
In my android application I want to change the pitch of an audio file and save it to sd card.
I am using sonic ndk link for changing pitch of my audio file but I am unable to save the changed audio file.
Is there any way to save this changed pitch audio file? Thanks in advance
Below is the code that I am using for my audio modification and save.
public class SonicTest extends Activity implements View.OnClickListener {
private Button saveBtn;
private int bufferSize=0;
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
saveBtn = findViewById(R.id.button1);
saveBtn.setOnClickListener(this);
}
public void play(View view)
{
new Thread(new Runnable()
{
public void run()
{
final EditText speedEdit = findViewById(R.id.speed);
final EditText pitchEdit = findViewById(R.id.pitch);
final EditText rateEdit = findViewById(R.id.rate);
float speed = Float.parseFloat(speedEdit.getText().toString());
float pitch = Float.parseFloat(pitchEdit.getText().toString());
float rate = Float.parseFloat(rateEdit.getText().toString());
AndroidAudioDevice device = new AndroidAudioDevice(22050, 1);
Sonic sonic = new Sonic(22050, 1);
byte samples[] = new byte[4096];
byte modifiedSamples[] = new byte[2048];
InputStream soundFile = getResources().openRawResource(R.raw.talking);
// InputStream soundFile = getResources().openRawResource(R.raw.sample);
int bytesRead;
if(soundFile != null) {
sonic.setSpeed(speed);
sonic.setPitch(pitch);
sonic.setRate(rate);
do {
try {
bytesRead = soundFile.read(samples, 0, samples.length);
} catch (IOException e) {
e.printStackTrace();
return;
}
if(bytesRead > 0) {
sonic.putBytes(samples, bytesRead);
} else {
sonic.flush();
}
int available = sonic.availableBytes();
if(available > 0) {
if(modifiedSamples.length < available) {
modifiedSamples = new byte[available*2];
}
sonic.receiveBytes(modifiedSamples, available);
device.writeSamples(modifiedSamples, available);
}
} while(bytesRead > 0);
device.flush();
}
}
} ).start();
}
#Override
public void onClick(View v) {
LinkedList<byte[]>linkedList = AndroidAudioDevice.list;
for (int i=0; i<linkedList.size(); i++){
bufferSize = bufferSize+linkedList.get(i).length;
}
byte[]buffer = new byte[bufferSize];
int k=0;
for (int i=0 ; i< linkedList.size(); i++){
for(int j=0; j<linkedList.get(i).length; j++){
buffer[k]=linkedList.get(i)[j];
k++;
}
Log.v("Buffer array length",""+k);
}
File file = new File(Environment.getExternalStorageDirectory(),"demo.mp3");
FileOutputStream fos = null;
try {
fos = new FileOutputStream(file);
fos.write(buffer);
fos.flush();
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
package org.vinuxproject.sonic;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.os.Environment;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.LinkedList;
public class AndroidAudioDevice
{
AudioTrack track;
static int bufferLength;
public static LinkedList<byte[]>list= new LinkedList<>();
private int findFormatFromChannels(int numChannels)
{
switch(numChannels) {
case 1: return AudioFormat.CHANNEL_OUT_MONO;
case 2: return AudioFormat.CHANNEL_OUT_STEREO;
default: return -1; // Error
}
}
public AndroidAudioDevice(int sampleRate, int numChannels)
{
int format = findFormatFromChannels(numChannels);
int minSize = AudioTrack.getMinBufferSize(sampleRate, format, AudioFormat.ENCODING_PCM_16BIT);
track = new AudioTrack( AudioManager.STREAM_MUSIC, sampleRate,
format, AudioFormat.ENCODING_PCM_16BIT,
minSize*4, AudioTrack.MODE_STREAM);
track.play();
}
public void flush()
{
track.flush();
}
public void writeSamples(byte[] samples, int length)
{
bufferLength = bufferLength+length;
track.write( samples, 0, length);
list.add(samples);
}
}
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
It's been asked a lot, but I still stuck about implement FFT class on Android
I need to process my audio data using FFT...
I already read the almost same question here How can I get frequency data from PCM using FFT
and here How to get frequency from fft result?
and more questions but still find no answer even after I tried the answers given...
FFT Class I'm using:
http://www.cs.princeton.edu/introcs/97data/FFT.java
The complex class to go with it: http://introcs.cs.princeton.edu/java/97data/Complex.java.html
Here's my code
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.os.Environment;
import android.view.View;
import android.widget.Button;
public class Latihan extends Activity{
private static final int RECORDER_BPP = 16;
private static final String AUDIO_RECORDER_FILE_EXT_WAV = ".wav";
private static final String AUDIO_RECORDER_FOLDER = "AudioRecorder";
private static final String AUDIO_RECORDER_TEMP_FILE = "record_temp.raw";
private static final int RECORDER_SAMPLERATE = 44100;
private static final int RECORDER_CHANNELS = AudioFormat.CHANNEL_IN_STEREO;
private static final int RECORDER_AUDIO_ENCODING = AudioFormat.ENCODING_PCM_16BIT;
short[] audioData;
private AudioRecord recorder = null;
private int bufferSize = 0;
private Thread recordingThread = null;
private boolean isRecording = false;
Complex[] fftTempArray;
Complex[] fftArray;
int[] bufferData;
int bytesRecorded;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.p1);
setButtonHandlers();
enableButtons(false);
bufferSize = AudioRecord.getMinBufferSize
(RECORDER_SAMPLERATE,RECORDER_CHANNELS,RECORDER_AUDIO_ENCODING)*3;
audioData = new short [bufferSize]; //short array that pcm data is put into.
}
private void setButtonHandlers() {
((Button)findViewById(R.id.btStart)).setOnClickListener(btnClick);
((Button)findViewById(R.id.btStop)).setOnClickListener(btnClick);
}
private void enableButton(int id,boolean isEnable){
((Button)findViewById(id)).setEnabled(isEnable);
}
private void enableButtons(boolean isRecording) {
enableButton(R.id.btStart,!isRecording);
enableButton(R.id.btStop,isRecording);
}
private String getFilename(){
String filepath = Environment.getExternalStorageDirectory().getPath();
File file = new File(filepath,AUDIO_RECORDER_FOLDER);
if(!file.exists()){
file.mkdirs();
}
return (file.getAbsolutePath() + "/" + System.currentTimeMillis() + AUDIO_RECORDER_FILE_EXT_WAV);
}
public void convert(){
}
public void calculate(){
Complex[] fftTempArray = new Complex[bufferSize];
for (int i=0; i<bufferSize; i++)
{
fftTempArray[i] = new Complex(audioData[i], 0);
}
Complex[] fftArray = FFT.fft(fftTempArray);
double[] micBufferData = new double[bufferSize];
final int bytesPerSample = 2;
final double amplification = 100.0;
for (int index = 0, floatIndex = 0; index < bytesRecorded - bytesPerSample + 1; index += bytesPerSample, floatIndex++) {
double sample = 0;
for (int b = 0; b < bytesPerSample; b++) {
int v = bufferData[index + b];
if (b < bytesPerSample - 1 || bytesPerSample == 1) {
v &= 0xFF;
}
sample += v << (b * 8);
}
double sample32 = amplification * (sample / 32768.0);
micBufferData[floatIndex] = sample32;
}
}
private String getTempFilename(){
String filepath = Environment.getExternalStorageDirectory().getPath();
File file = new File(filepath,AUDIO_RECORDER_FOLDER);
if(!file.exists()){
file.mkdirs();
}
File tempFile = new File(filepath,AUDIO_RECORDER_TEMP_FILE);
if(tempFile.exists())
tempFile.delete();
return (file.getAbsolutePath() + "/" + AUDIO_RECORDER_TEMP_FILE);
}
private void startRecording(){
recorder = new AudioRecord(MediaRecorder.AudioSource.MIC,
RECORDER_SAMPLERATE, RECORDER_CHANNELS,RECORDER_AUDIO_ENCODING, bufferSize);
recorder.startRecording();
isRecording = true;
recordingThread = new Thread(new Runnable() {
public void run() {
writeAudioDataToFile();
}
},"AudioRecorder Thread");
recordingThread.start();
}
private void writeAudioDataToFile(){
byte data[] = new byte[bufferSize];
String filename = getTempFilename();
FileOutputStream os = null;
try {
os = new FileOutputStream(filename);
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
int read = 0;
if(null != os){
while(isRecording){
read = recorder.read(data, 0, bufferSize);
if(AudioRecord.ERROR_INVALID_OPERATION != read){
try {
os.write(data);
} catch (IOException e) {
e.printStackTrace();
}
}
}
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void stopRecording(){
if(null != recorder){
isRecording = false;
recorder.stop();
recorder.release();
recorder = null;
recordingThread = null;
}
copyWaveFile(getTempFilename(),getFilename());
// deleteTempFile();
}
private void deleteTempFile() {
File file = new File(getTempFilename());
file.delete();
}
private void copyWaveFile(String inFilename,String outFilename){
FileInputStream in = null;
FileOutputStream out = null;
long totalAudioLen = 0;
long totalDataLen = totalAudioLen + 36;
long longSampleRate = RECORDER_SAMPLERATE;
int channels = 2;
long byteRate = RECORDER_BPP * RECORDER_SAMPLERATE * channels/8;
byte[] data = new byte[bufferSize];
try {
in = new FileInputStream(inFilename);
out = new FileOutputStream(outFilename);
totalAudioLen = in.getChannel().size();
totalDataLen = totalAudioLen + 36;
AppLog.logString("File size: " + totalDataLen);
WriteWaveFileHeader(out, totalAudioLen, totalDataLen,
longSampleRate, channels, byteRate);
while(in.read(data) != -1){
out.write(data);
}
in.close();
out.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private void WriteWaveFileHeader(
FileOutputStream out, long totalAudioLen,
long totalDataLen, long longSampleRate, int channels,
long byteRate) throws IOException {
//another code
}
private View.OnClickListener btnClick = new View.OnClickListener() {
public void onClick(View v) {
switch(v.getId()){
case R.id.btStart:{
AppLog.logString("Start Recording");
enableButtons(true);
startRecording();
break;
}
case R.id.btStop:{
AppLog.logString("Stop Recording");
enableButtons(false);
stopRecording();
calculate();
break;
}
}
}
};
}
I assume the audioData array contains the raw audio data,but my code catch the exception and return "N is not a power of 2"
Is it anything wrong with my code ??
How do I pass it to FFT.java class and get the fftResult ??
Or is there an other way to convert time domain data to frequency data that more easier ?
It's been a few months since I get stuck with this... My project is too compare 2 audio of *.wav files,
Any help would be appreciated... :)
I already found the answer... :)
I create method to calculate array value from audio...
public double[] calculateFFT(byte[] signal)
{
final int mNumberOfFFTPoints =1024;
double mMaxFFTSample;
double temp;
Complex[] y;
Complex[] complexSignal = new Complex[mNumberOfFFTPoints];
double[] absSignal = new double[mNumberOfFFTPoints/2];
for(int i = 0; i < mNumberOfFFTPoints; i++){
temp = (double)((signal[2*i] & 0xFF) | (signal[2*i+1] << 8)) / 32768.0F;
complexSignal[i] = new Complex(temp,0.0);
}
y = FFT.fft(complexSignal); // --> Here I use FFT class
mMaxFFTSample = 0.0;
mPeakPos = 0;
for(int i = 0; i < (mNumberOfFFTPoints/2); i++)
{
absSignal[i] = Math.sqrt(Math.pow(y[i].re(), 2) + Math.pow(y[i].im(), 2));
if(absSignal[i] > mMaxFFTSample)
{
mMaxFFTSample = absSignal[i];
mPeakPos = i;
}
}
return absSignal;
}
Then I called it in class Write Audio..
private void writeAudioDataToFile(){
byte data[] = new byte[bufferSize];
String filename = getTempFilename();
FileOutputStream os = null;
try {
os = new FileOutputStream(filename);
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
int read = 0;
if(null != os){
while(isRecording){
read = recorder.read(data, 0, bufferSize);
if(read > 0){
absNormalizedSignal = calculateFFT(data); // --> HERE ^__^
}
if(AudioRecord.ERROR_INVALID_OPERATION != read){
try {
os.write(data);
} catch (IOException e) {
e.printStackTrace();
}
}
}
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
It sounds like your immediate problem is "N is not a power of 2." In this case, N is probably referring to the size of the data you are putting into your FFT. Most FFT algorithms only work on blocks of data that have a size that is a power of 2.
Are you trying to put the entire file into an FFT at once? If so, you may need to read more background material to understand what you are doing. Maybe start here: http://blog.bjornroche.com/2012/07/frequency-detection-using-fft-aka-pitch.html
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
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.