title: Real-time Bluetooth SPP data streaming on Android only works for 5 seconds
body: <p>I have a home-made bluetooth device measuring ECG at 500Hz: every 2 ms the device sends 9 bytes of data (header, ECG measurment, footer). So this is roughly a 9*500=4.5kbytes/s data stream.</p>
  
  <p>I have a C++ Windows program able to connect the device and retrieve the data stream (displaying it with Qt/qwt). In this case, I use Windows control panel to bond the device and I connect it via a virtual COM port using boost serial_port interface. This works perfectly and I'm receiving my data stream in real time: I get a measurment point every 2ms or so.</p>
  
  <p>I ported the whole C++ program on Android via QtCreator (Qt 5.3.2). I had real-time issues. Data stream was in "real-time" for the first 5 seconds, and then performance would dramatically slow down (see <a href="https://stackoverflow.com/questions/24934464/how-to-do-good-real-time-data-streaming-using-java-android-sdk">How to do good real-time data streaming using Java Android SDK</a>).</p>
  
  <p>Because I thougth the problem could be due to C++/Qt, <strong>I wrote a completely blank pure Java/Android project using Eclipse</strong>. And it has the same problem!!!</p>
  
  <p>Questions are: Is there something wrong with this code? Why am I receiving data in real-time for only the 5 first seconds? What happens after 5 seconds of intensive BT usage on Android platform and why does it slow down the BT data reception?</p>
  
  <p>Here is my Java program:</p>
  
  <p>BluetoothHelper.java (with functions to connect/disconnect/read and write data:</p>
  
  <pre><code>package com.example.helloworld;
  
  import android.util.Log;
  import android.content.Context;
  import android.os.Bundle;
  import java.util.Locale;
  import java.util.concurrent.Semaphore;
  import java.lang.String;
  import java.lang.Thread;
  import java.io.IOException;
  import java.io.OutputStream;
  import java.io.InputStream;
  import java.text.SimpleDateFormat;
  import java.lang.InterruptedException;
  import android.app.Activity;
  import android.app.AlertDialog;
  import android.bluetooth.BluetoothAdapter;
  import android.bluetooth.BluetoothDevice;
  import android.bluetooth.BluetoothSocket;
  import android.bluetooth.BluetoothManager;
  import android.util.SparseArray;
  import android.content.Intent;
  import android.content.IntentFilter;
  import android.content.BroadcastReceiver;
  import java.util.UUID;
  import java.util.Date;
  import java.util.Calendar;
  import java.util.Vector;
  import java.util.Set;
  import java.util.Arrays;
  
  public class BluetoothHelper
  {
      private BluetoothManager mBluetoothManager;
      private BluetoothAdapter mBluetoothAdapter;
      private BluetoothDevice mDevice;
      private BluetoothSocket mSocket;
      private OutputStream mOutputStream;
      private InputStream mInputStream;
      private BroadcastReceiver mReceiver;
      private Activity myActivity;
      private Vector&lt;BluetoothDevice&gt; mDevices;
      private byte[] mHeader;
      private byte[] mFrame;
  
      public BluetoothHelper(Activity a)
      {
          myActivity = a;
          mHeader = new byte[3];
          mFrame = new byte[256];
          mDevices = new Vector();
      }
  
      /* Check bluetooth is enabled, return "" if OK, else, return error string */
      public String initializeBluetooth(){
  
          String error = "";
          System.out.println("Initializing bluetooth...");
  
          mBluetoothManager = (BluetoothManager) myActivity.getSystemService(Context.BLUETOOTH_SERVICE);
          if ( mBluetoothManager == null )
          {
              error = "Bluetooth manager is not found";
          }
          else
          {
              mBluetoothAdapter = mBluetoothManager.getAdapter();
              if( mBluetoothAdapter == null )
              {
                  error = "Bluetooth adapter is not found";
              }
              else if( ! mBluetoothAdapter.isEnabled() )
              {
                  error = "Bluetooth adapter is off";
              }
              else
              {
                  System.out.println("Bluetooth successfully initialized");
                  return "";
              }
          }
  
          return error;
      }
  
      private void addDevice( final BluetoothDevice device )
      {
          mDevices.add(device);
      }
  
      public Vector&lt;BluetoothDevice&gt; getDevices() { return mDevices; }
  
      /* Clear previously detected device list */
      public boolean clearDeviceList(){
          // Clear old list
          mDevices.clear();
          return true;
      }
  
      /* Fill local device list with paired devices */
      public boolean addPairedDevices(){
          //System.out.println("Entering addPairedDevices");
  
          if( mBluetoothAdapter == null )
          {
              System.out.println("No bluetooth adapter");
              return false;
          }
  
          Set&lt;BluetoothDevice&gt; pairedDevices = mBluetoothAdapter.getBondedDevices();
          // If there are paired devices
          if (pairedDevices.size() &gt; 0)
          {
              //System.out.println("Found paired devices");
              // Loop through paired devices
              for (BluetoothDevice device : pairedDevices)
              {
                  addDevice( device );
              }
          }
  
          return true;
      }
  
      public String connectToDevice(final BluetoothDevice device)
      {
          if ( mDevice != null )
              disconnectDevice();
  
          if( mBluetoothAdapter == null || myActivity == null )
              return "System not initialized or bluetooth not active";
  
          if ( device.getBondState() != BluetoothDevice.BOND_BONDED )
          {
              // TODO: find a way to do a synchronized bounding operation
              return "Device is not bonded";
          }
  
          final boolean[] the_result = new boolean[1];
          the_result[0] = false;
  
          final Semaphore mutex = new Semaphore(0);
  
          Runnable connectRunnable = new Runnable() {
                  @Override
                  public void run()                {
  
                      UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
  
                     try
                     {
                          mSocket = device.createInsecureRfcommSocketToServiceRecord( MY_UUID );
                          System.out.println("Created RFcomm socket");
                          mSocket.connect();
                          if ( mSocket.isConnected() )
                          {
                              System.out.println("Connected RFcomm socket");
                              mOutputStream = mSocket.getOutputStream();
                              mInputStream = mSocket.getInputStream();
                              System.out.println("Retrieved output stream");
                              the_result[0] = true;
                          }
                          else
                          {
                              System.out.println("Failed to connect RFcomm socket");
                          }
                     }
                     catch (IOException e)
                     {
                          System.out.println("Failed to open RFcomm socket (createRfcommSocketToServiceRecord)");
                          System.out.println(e.toString());
                     }
  
                     mutex.release();
                  }
              };
  
          myActivity.runOnUiThread( connectRunnable );
  
          // waiting for thread to be completed...
          try {
              mutex.acquire();
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
  
          if ( the_result[0] )
          {
              System.out.println("Connection succeeded");
              return "";
          }
          else
          {
              System.out.println("Connection failed");
              return "Failed to connect device";
          }
      }
  
      /* Request to disconnect the device */
      public boolean disconnectDevice(){
  
          System.out.println("Disconnecting device...");
  
          if ( mSocket != null )
          {
              // block read/write
              mOutputStream = null;
              mInputStream = null;
  
              try
              {
                  mSocket.close();
              }
              catch( IOException e )
              {
                  e.printStackTrace();
                  return false;
              }
              mSocket = null;
          }
  
          mDevice = null;
  
          return true;
      }
  
      /* Send bytes to the connected device */
      public boolean writeData( byte[] buffer )
      {
          if( mOutputStream == null )
          {
              System.out.println("No connection, can't send data");
          }
          else
          {
              try
              {
                  mOutputStream.write( buffer );
                  return true;
              }
              catch (IOException e)
              {
                  System.out.println( "Failed to send data" );
                  e.printStackTrace();
              }
          }
          return false;
      }
  
      public static String byteArrayToHex(byte[] a, int size) {
          StringBuilder sb = new StringBuilder(size * 5);
          for( int i = 0; i != size; ++i )
             sb.append(String.format("0x%02x ", a[i] &amp; 0xff));
          return sb.toString();
      }
  
      public int getBytesPending()
      { 
          try
          {
              return mInputStream.available();
          }
          catch (IOException e)
          {
              return 0;
          }
      }
  
      /* Non blocking read function. Read bytes from the connected device.
       * Return number of bytes read
       * return 0 if not enough bytes available
       * return -1 in case of error
       */
      public int readData( byte[] buffer, int size, boolean blocking )
      {
          if ( mInputStream == null )
          {
              System.out.println("No connection, can't receive data");
          }
          else
          {
              try
              {
                  final boolean verbose = false;
  
                  if ( blocking )
                  {
                      if ( verbose )
                          System.out.println( "Blocking request of " + buffer.length + " byte(s)" );    
                      int res = 0;
                      int temp = 0;
                      while ( true )
                      {
                          temp = mInputStream.read( buffer, res, size - res );
  
                          res += temp;
  
                          if ( res &gt;= size )
                          {
                              break;
                          }
                          else
                          {
                              if ( verbose )
                                  System.out.println( "Received " + res + " byte(s) to far : " + byteArrayToHex(buffer,size) );
                          }
  
                          try {
                              Thread.sleep(10);
                          } catch(InterruptedException ex) {
  
                          }
                      }
                      if ( verbose )
                          System.out.println( "Received " + res + " byte(s) : " + byteArrayToHex(buffer,size) );
                      return res;
                  }
                  else
                  {
                      int available = mInputStream.available();
  
                      if ( verbose &amp;&amp; available != 0 )
                      {
                          Calendar c = Calendar.getInstance();
                          Date date = new Date();
                          c.setTime(date);
                          c.get(Calendar.MILLISECOND);
  
                          SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
                          String currentTime = sdf.format(date);
  
                          System.out.println( currentTime + ":" + c.get(Calendar.MILLISECOND) + " - " + available + " bytes available, requested " + buffer.length );
                      }
  
                      if ( available &gt;= size )
                      {
                          int res = mInputStream.read( buffer, 0, size ); // only call read if we know it's not blocking
                          if ( verbose )
                              System.out.println( "Received " + res + " byte(s) : " + byteArrayToHex(buffer,size) );
                          return res;
                      }
                      else
                      {
                          return 0;
                      }
                  }
              }
              catch (IOException e)
              {
                  System.out.println( "Failed to read data...disconnected?" );
                  //e.printStackTrace();
              }
          }
          return -1;
      }
  
      public byte[] readNextFrame( boolean blocking )
      {
          if ( readData( mHeader, mHeader.length, blocking ) == mHeader.length )
          {
              int size = mHeader[2];
              if ( size &lt; 0 )
                  size = -size;
  
              if ( readData( mFrame, size, blocking ) == size )
              {
                  byte[] res = new byte[mHeader.length + size];
                  System.arraycopy(mHeader, 0, res, 0, mHeader.length);
                  System.arraycopy(mFrame, 0, res, mHeader.length, size);
                  return res;
              }
          }
  
          return null;        
      }
  
      */ read frame but without allocating any memory, does not retur condumed bytes */
      public boolean eatNextFrame( boolean blocking )
      {
          if ( readData( mHeader, mHeader.length, blocking ) == mHeader.length )
          {
              int size = mHeader[2];
              if ( size &lt; 0 )
                  size = -size;
  
              if ( readData( mFrame, size, blocking ) == size )
              {
                  return true;
              }
          }
  
          return false;       
      }
  
      public boolean startECG()
      {
          // some code sending instructions to configure my device
      }
  }
  </code></pre>
  
  <p>main Java file, connecting and doing a 10sec acquisition:</p>
  
  <pre><code>    // Here is the code for Medoc:
      BluetoothHelper helper = new BluetoothHelper(this);
      String error = helper.initializeBluetooth();
      if ( error.isEmpty() )
      {
          if ( helper.addPairedDevices( ) )
          {
              if ( !helper.getDevices().isEmpty() )
              {
                  if ( helper.getDevices().size() == 1 )
                  {
                      BluetoothDevice device = helper.getDevices().firstElement();
  
                      error = helper.connectToDevice( device );
  
                      if ( error.isEmpty() )
                      {
                          if ( helper.startECG() )
                          {
                              // acquiere data for 10 seconds
                              Date start = new Date();
                              Date end = new Date();
                              Date empty = null;
                              int lastMinute = 0;
                              int maxBufferSize = 0;
                              boolean receivedData = false;
                              while ( end.getTime() - start.getTime() &lt; 10 * 1000 )
                              {
                                  int currentMinute = (int) (( end.getTime() - start.getTime() ) / 1000);
                                  if ( currentMinute != lastMinute )
                                  {
                                      if ( receivedData )
                                          System.out.println( "During second #" + lastMinute + " max buffer size was : " + maxBufferSize );
                                      else
                                          System.out.println( "During second #" + lastMinute + " no data was received!" );
                                      maxBufferSize = 0;
                                      receivedData = false;
                                      lastMinute = currentMinute;
                                  }
  
                                  if ( helper.eatNextFrame(false) )
                                  {
                                      receivedData = true;
                                  }
                                  if ( helper.getBytesPending() == 0 )
                                  {
                                      if ( empty == null )
                                      {
                                          empty = new Date();
                                      }
                                  }
                                  else
                                  {
                                      if ( empty != null )
                                      {
                                          Date now = new Date();
                                          int elapsed = (int) ( now.getTime() - empty.getTime() );
                                          if ( elapsed &gt; 100 )
                                              System.out.println( "No pending data, during " + elapsed + "ms" );
                                          empty = null;                                                   
                                      }
                                  }
  
                                  maxBufferSize = Math.max( helper.getBytesPending(), maxBufferSize );
  
                                  end = new Date();
                              }
  
                              AlertDialog.Builder dlgAlert = new AlertDialog.Builder(this);
                              dlgAlert.setMessage( "Done" );
                              dlgAlert.setPositiveButton("Ok",null);
                              dlgAlert.create().show();
                          }
                          else
                          {
                              error = "Failed to start ECG";
                          }
  
                          helper.disconnectDevice();
                      }
                  }
                  else
                  {
                      error = "Too many devices found";
                  }
              }
              else
              {
                  error = "No device found";
              }
          }
          else
          {
              error = "Failed to scan for devices";
          }
      }
  
      if ( !error.isEmpty() )
      {
          AlertDialog.Builder dlgAlert2 = new AlertDialog.Builder(this);
          dlgAlert2.setMessage( error );
          dlgAlert2.setPositiveButton("Ok",null);
          dlgAlert2.create().show();
      }
  </code></pre>
  
  <p>And here, the output of the program:</p>
  
  <pre><code>12-01 14:12:51.755: I/System.out(15940): During second #0 max buffer size was : 63
  12-01 14:12:52.755: I/System.out(15940): During second #1 max buffer size was : 133
  12-01 14:12:53.755: I/System.out(15940): During second #2 max buffer size was : 66
  12-01 14:12:54.755: I/System.out(15940): During second #3 max buffer size was : 61
  12-01 14:12:55.755: I/System.out(15940): During second #4 max buffer size was : 129
  12-01 14:12:56.705: I/System.out(15940): No pending data, during 501ms
  12-01 14:12:56.755: I/System.out(15940): During second #5 max buffer size was : 939
  12-01 14:12:57.755: I/System.out(15940): During second #6 max buffer size was : 980
  12-01 14:12:58.755: I/System.out(15940): During second #7 max buffer size was : 1008
  12-01 14:12:59.195: I/System.out(15940): No pending data, during 488ms
  12-01 14:12:59.695: I/System.out(15940): No pending data, during 489ms
  12-01 14:12:59.755: I/System.out(15940): During second #8 max buffer size was : 990
  12-01 14:13:00.185: I/System.out(15940): No pending data, during 490ms
  12-01 14:13:01.205: I/System.out(15940): Disconnecting device...
  </code></pre>
  
  <p>As you can see, during the 5 first seconds, read buffer remains prettry small and there is no moment when buffer is empty for more than 100ms (see code outputing "No pending data"). Then, from the fifth second we :</p>
  
  <ul>
  <li>start having long periods (~500ms) where the read buffer remains empty (InputStream::available() returns 0) even if my device is permanently sending data to Android.</li>
  <li>can see the buffer max size grows significantly.</li>
  </ul>
  
  <p>After the 5 first seconds of data acquisition, it's as if data are getting bufferized somewhere and are made available for reading in the InputStream by blocks of ~500ms.....</p>
  
  <p>Sometimes, it could be even worst, there is no data being received at all after 5sec:</p>
  
  <pre><code>12-01 14:35:54.595: I/System.out(16386): During second #0 max buffer size was : 22
  12-01 14:35:55.595: I/System.out(16386): During second #1 max buffer size was : 93
  12-01 14:35:56.595: I/System.out(16386): During second #2 max buffer size was : 108
  12-01 14:35:57.595: I/System.out(16386): During second #3 max buffer size was : 61
  12-01 14:35:58.595: I/System.out(16386): During second #4 max buffer size was : 64
  12-01 14:35:59.595: I/System.out(16386): During second #5 max buffer size was : 63
  12-01 14:36:00.595: I/System.out(16386): During second #6 no data was received!
  12-01 14:36:01.595: I/System.out(16386): During second #7 no data was received!
  12-01 14:36:02.595: I/System.out(16386): During second #8 no data was received!
  </code></pre>
  
  <p>Note: I tried to sleep some seconds before creating <code>BluetoothHelper</code> and before calling <code>startECG()</code>. Same behaviour (acquisition slows down or stops after 5 seconds).</p>
  
  <p><strong>Edit: I'm experiencing that on:</strong></p>
  
  <ul>
  <li>Nexus 5 phone, Android 4.4.2</li>
  <li>Nexus 7 tablet, Android 4.4.2</li>
  <li>Galaxy S4 with Android 4.4.2</li>
  </ul>
  
  <p>But not on a  Galaxy S3 with custom CyanogenMod 11 Android 4.4.2: data streaming seems perfect, no freezing after 5sec and data are arriving in real-time...</p>
  
  <p><strong>Edit December 15th:</strong></p>
  
  <p>As proposed, moved read to a separate thread:
  Made <code>BluetoothHelper</code> implement <code>Runnable</code> and added those methods/attributes to the class:</p>
  
  <pre><code>private int mFramesReceived;
  private long mLongestPause;
  public void clearReceived()
  {
      mFramesReceived = 0;
      mLongestPause = 0;
  }
  
  public int received()
  {
      return mFramesReceived;
  }
  
  public long longestPause()
  {
      return mLongestPause;
  }
  
  @Override
  public void run() {
  
      System.out.println( "Started thread" );
  
      int lastSeconde = 0;
      long currentTimeMillis = System.currentTimeMillis();
      long started = System.currentTimeMillis();
  
      // Keep listening to the InputStream until an exception occurs
      while (true) {
          if ( eatNextFrame( true ) )
          {
              //System.out.println( "Got some data" );
              mLongestPause = Math.max( mLongestPause, System.currentTimeMillis() - currentTimeMillis );
              currentTimeMillis = System.currentTimeMillis();
              mFramesReceived++;
  
              int currentSeconde = (int) (( System.currentTimeMillis() - started ) / 1000);
              if ( currentSeconde != lastSeconde )
              {
                  if ( mFramesReceived != 0 )
                      System.out.println( "During second #" + lastSeconde + " max pause was : " + mLongestPause );
                  else
                      System.out.println( "During second #" + lastSeconde + " no data was received!" );
  
                  clearReceived();
  
                  lastSeconde = currentSeconde;
              }
          }
          else
          {
              System.out.println( "Failed to get some data, connection closed?" );
              break;
          }
      }
  }
  </code></pre>
  
  <p>Then changed caller to:</p>
  
  <pre><code>if ( helper.startECG() )
  {
      new Thread(helper).start();
  
      try {
          Thread.sleep(10000); // wait 10 seconds
      } catch(InterruptedException ex) {
          Thread.currentThread().interrupt();
      }
  
      AlertDialog.Builder dlgAlert = new AlertDialog.Builder(this);
      dlgAlert.setMessage( "Done" );
      dlgAlert.setPositiveButton("Ok",null);
      dlgAlert.create().show();
  }
  else
  {
      error = "Failed to start ECG";
  }
  
  helper.disconnectDevice();
  </code></pre>
  
  <p>And it did not fix the problem, here is the output:</p>
  
  <pre><code>During second #0 max pause was : 48
  During second #1 max pause was : 45
  During second #2 max pause was : 33
  During second #3 max pause was : 35
  During second #4 max pause was : 58
  During second #5 max pause was : 498
  During second #6 max pause was : 477
  During second #7 max pause was : 480
  During second #8 max pause was : 986
  During second #9 max pause was : 497
  </code></pre>