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()));
}
}
}