0

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

}
Mark
  • 1,754
  • 3
  • 26
  • 43
  • When you first click the start button, does the Arduino respond immediately but get slower over time, or does it always take a few seconds to react right from the beginning? – user2461391 Aug 01 '13 at 12:44
  • If you think "data getting backed up" is the issue, then you should try to slow down the rate of transmission (fewer, smaller packets per second) to establish when the Arduino starts to respond "instantaneously". I suspect the problem is your `sleep(10)` statement - it might be causing delays at the Android end. Why is it there? See http://stackoverflow.com/questions/1520887/how-to-pause-sleep-thread-or-process-in-android for some alternatives. – Floris Aug 01 '13 at 14:47

1 Answers1

0

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

Mark
  • 1,754
  • 3
  • 26
  • 43