问题描述
我有一个自制的蓝牙设备,以 500Hz 的频率测量心电图:设备每 2 毫秒发送 9 个字节的数据(标题、心电图测量、页脚).所以这大概是一个 9*500=4.5kbytes/s 的数据流.
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.
我有一个 C++ Windows 程序能够连接设备并检索数据流(用 Qt/qwt 显示它).在这种情况下,我使用 Windows 控制面板来绑定设备,并使用 boost serial_port 接口通过虚拟 COM 端口连接它.这非常有效,我正在实时接收我的数据流:我每 2 毫秒左右得到一个测量点.
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.
我通过 QtCreator (Qt 5.3.2) 在 Android 上移植了整个 C++ 程序.我有实时问题.数据流在前 5 秒处于实时"状态,然后性能会显着降低(参见 如何使用 Java Android SDK 做好实时数据流).
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 How to do good real-time data streaming using Java Android SDK).
因为我认为问题可能是由 C++/Qt 引起的,我使用 Eclipse 编写了一个完全空白的纯 Java/Android 项目.它也有同样的问题!!!
Because I thougth the problem could be due to C++/Qt, I wrote a completely blank pure Java/Android project using Eclipse. And it has the same problem!!!
问题是:这段代码有问题吗?为什么我只在前 5 秒实时接收数据?在Android平台上密集使用BT 5秒后会发生什么,为什么会减慢BT数据接收速度?
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?
这是我的 Java 程序:
Here is my Java program:
BluetoothHelper.java(具有连接/断开/读取和写入数据的功能:
BluetoothHelper.java (with functions to connect/disconnect/read and write data:
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
}
}
主Java文件,连接并进行10秒获取:
main Java file, connecting and doing a 10sec acquisition:
// 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();
}
这里是程序的输出:
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...
如您所见,在前 5 秒内,读取缓冲区仍然很小,并且缓冲区空的时间不会超过 100 毫秒(请参阅输出无挂起数据"的代码).然后,从第五秒开始:
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 :
- 即使我的设备永久向 Android 发送数据,读取缓冲区仍保持为空(InputStream::available() 返回 0)的时间很长(约 500 毫秒).
- 可以看到缓冲区最大大小显着增加.
在数据采集的前 5 秒之后,就好像数据在某处被缓冲,并且可以通过大约 500 毫秒的块在 InputStream 中读取......
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.....
有时,情况可能更糟,5 秒后根本没有收到任何数据:
Sometimes, it could be even worst, there is no data being received at all after 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!
注意:我尝试在创建 BluetoothHelper
之前和调用 startECG()
之前休眠几秒钟.相同的行为(获取速度减慢或在 5 秒后停止).
Note: I tried to sleep some seconds before creating BluetoothHelper
and before calling startECG()
. Same behaviour (acquisition slows down or stops after 5 seconds).
我正在经历:
- Nexus 5 手机,Android 4.4.2
- Nexus 7 平板电脑,Android 4.4.2
- 搭载 Android 4.4.2 的 Galaxy S4
但不是在带有自定义 CyanogenMod 11 Android 4.4.2 的 Galaxy S3 上:数据流看起来很完美,5 秒后没有冻结,数据实时到达......
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...
编辑 12 月 15 日:
按照建议,将 read 移至单独的线程:使 BluetoothHelper
实现 Runnable
并将这些方法/属性添加到类中:
As proposed, moved read to a separate thread:Made BluetoothHelper
implement Runnable
and added those methods/attributes to the class:
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;
}
}
}
然后将来电者更改为:
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();
它并没有解决问题,这是输出:
And it did not fix the problem, here is the output:
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
推荐答案
这个问题显然与here.
5 秒后,我要么失去了连接,要么实时流传输速度显着变慢.
After 5 seconds, I had either a connection lost, either real-time streaming being dramatically slow down.
正如此处所说,Android >4.3 显然不喜欢超过 5 秒的单向通信.所以我现在每 1 秒向设备发送一个虚拟命令(一种保持活动"命令),现在 Android 很高兴,因为它不再是单向通信......所以数据流也一样好比之前的第五秒!
As said here Android >4.3 apparently does not like one-way communication exceeding 5 secondes. So I'm now sending a dummy command to the device every 1 seconde (kind of "keep-alive" command) and now Android is happy because it's not a one-way communication anymore...and so data streaming is as good after the fifth second than before!
这篇关于Android 上的实时蓝牙 SPP 数据流仅工作 5 秒的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!