La transmisión de datos en tiempo real Bluetooth SPP en Android sólo funciona durante 5 segundos

Tengo un dispositivo bluetooth hecho en casa midiendo ECG a 500Hz: cada 2 ms el dispositivo envía 9 bytes de datos (encabezado, mediciones de ECG, pie de página). Así que esto es aproximadamente un flujo de datos de 9 * 500 = 4.5kbytes / s.

Tengo un programa de C ++ Windows capaz de conectar el dispositivo y recuperar la corriente de datos (que la exhibe con Qt / qwt). En este caso, utilizo el panel de control de Windows para unir el dispositivo y lo conecto a través de un puerto COM virtual usando la interfaz boost serial_port. Esto funciona perfectamente y estoy recibiendo mi flujo de datos en tiempo real: Tengo un punto de medición cada 2ms o menos.

Porté todo el programa C ++ en Android a través de QtCreator (Qt 5.3.2). Tenía problemas en tiempo real. El flujo de datos estaba en "tiempo real" durante los primeros 5 segundos y, a continuación, el rendimiento disminuiría drásticamente (consulte Cómo realizar un buen flujo de datos en tiempo real con Java Android SDK ).

Debido a que thougth el problema podría ser debido a C + + / Qt, escribí un proyecto completamente en blanco Java / Android puro utilizando Eclipse . Y tiene el mismo problema!

Las preguntas son: ¿Hay algo mal con este código? ¿Por qué estoy recibiendo datos en tiempo real por sólo los 5 primeros segundos? ¿Qué sucede después de 5 segundos de uso intensivo de BT en la plataforma Android y por qué disminuye la recepción de datos de BT?

Aquí está mi programa Java:

BluetoothHelper.java (con funciones para conectar / desconectar / leer y escribir datos:

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<BluetoothDevice> 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<BluetoothDevice> 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<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices(); // If there are paired devices if (pairedDevices.size() > 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] & 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 >= 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 && 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 >= 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 < 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 < 0 ) size = -size; if ( readData( mFrame, size, blocking ) == size ) { return true; } } return false; } public boolean startECG() { // some code sending instructions to configure my device } } 

Archivo principal de Java, conectando y haciendo una adquisición 10sec:

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

Y aquí, la salida del programa:

 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... 

Como se puede ver, durante los 5 primeros segundos, el búfer de lectura permanece pequeño y no hay ningún momento en que el búfer esté vacío durante más de 100ms (vea el código que emite "Sin datos pendientes"). Entonces, desde el quinto segundo:

  • Comienza a tener períodos largos (~ 500ms) donde el búfer de lectura permanece vacío (InputStream :: available () devuelve 0) incluso si mi dispositivo está enviando datos permanentemente a Android.
  • Puede ver el tamaño máximo del búfer crece significativamente.

Después de los 5 primeros segundos de adquisición de datos, es como si los datos se están almacenando en algún lugar y están disponibles para leer en el InputStream por bloques de ~ 500ms …..

A veces, podría ser incluso peor, no hay datos recibidos en absoluto después de 5sec:

 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! 

Nota: He intentado dormir unos segundos antes de crear BluetoothHelper y antes de llamar a startECG() . Mismo comportamiento (la adquisición se ralentiza o se detiene después de 5 segundos).

Editar: Estoy experimentando que en:

  • Teléfono Nexus 5, Android 4.4.2
  • Nexus 7 tablet, Android 4.4.2
  • Galaxy S4 con Android 4.4.2

Pero no en un Galaxy S3 con CyanogenMod 11 personalizado Android 4.4.2: streaming de datos parece perfecto, no hay congelación después de 5sec y los datos están llegando en tiempo real …

Editar 15 de diciembre:

Según lo propuesto, movido leído a un hilo de rosca separado: Hecho BluetoothHelper implementa Runnable y agregó esos métodos / atributos a la clase:

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

Luego cambió el llamador a:

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

Y no solucionó el problema, aquí está la salida:

 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 

3 Solutions collect form web for “La transmisión de datos en tiempo real Bluetooth SPP en Android sólo funciona durante 5 segundos”

Utilice el concepto de subprocesamiento para leer y escribir simultáneamente los bytes en dispositivos periféricos. Utilice el ejemplo de la transferencia de datos del androide Bluetooth para solucionar problema. Está utilizando la clase java normal para enviar y recibir datos a otro dispositivo que no es válido. Debe utilizar el concepto de Threading para enviar y recibir datos a través de Bluetooth.

Por favor, consulte el enlace a continuación para leer y escribir datos a través de Bluetooth.

http://developer.android.com/guide/topics/connectivity/bluetooth.html

Este problema es aparentemente similar al que se informa aquí .

Después de 5 segundos, tuve una conexión perdida, ya sea streaming en tiempo real que se redujo drásticamente.

Como se dijo aquí Android> 4.3 al parecer no le gusta la comunicación unidireccional superior a 5 secondes. Así que ahora estoy enviando un comando ficticio al dispositivo cada 1 seconde (tipo de comando "keep-alive") y ahora Android está feliz porque ya no es una comunicación unidireccional … y así streaming de datos es tan bueno después El quinto segundo que antes!

No debe confiar en InputStream.available() para indicar cuántos bytes están disponibles en la secuencia (consulte https://developer.android.com/reference/java/io/InputStream.html#available () para los detalles). Dado que conoce el tamaño exacto de su paquete de datos (9 bytes), lea 9 bytes cada vez en el búfer: mInputStream.read(buffer, 0, 9) .

Con Bluetooth es difícil garantizar la entrega binaria en tiempo real, ya que puede haber muchas razones para retrasos (por ejemplo, mayor distancia entre dispositivos, obstáculos, etc.). Por lo tanto, es usualmente mejor llamar constantemente a read y reenviar partes de datos recuperadas a componentes de manipulación. Por ejemplo, en uno de mis proyectos implementé Android Service esperando los paquetes de datos de Bluetooth y notificando la interfaz de usuario con los datos recibidos. Puede implementar un Service o un AsyncTask para esto.

Otra recomendación: evite hacer asignaciones innecesarias de memoria en los métodos que usted llama con frecuencia (como readData ). Puede medir el tiempo transcurrido utilizando System.currentTimeMillis() . La recolección de basura podría ser una de las razones por las que experimenta un desempeño degradante.

  • Android gson streaming analizador o android.util.jsonreader?
  • Problema de transmisión en vivo de Android RTSP
  • Reproducir archivos mp3 durante la descarga?
  • Reproducir archivos multimedia desde una red telefónica WiFi
  • Transmisión desde M3U en Android
  • Android - radio por internet streaming
  • No se pueden transmitir vídeos
  • Uso de JWplayer en Android para reproducir video en Phonegap
  • Reproducir stream de RTMP de audio en android
  • Prácticas recomendadas para la transmisión de audio
  • MediaPlayer error (1, -1004) también conocido como MEDIA_ERROR_IO intentando transmitir música en Samsung S3
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.