Как правильно выполнять потоковую передачу данных в режиме реального времени с помощью Java Android SDK
У меня есть самодельное устройство Bluetooth, измеряющее ЭКГ с частотой 500 Гц: каждые 2 мс устройство отправляет 9 байтов данных (заголовок, измерение ЭКГ, нижний колонтитул). Так что это примерно 9*500=4,5 Кбайт / с потока данных.
У меня есть программа на C++ для Windows, способная подключить устройство и получить поток данных (отображая его с помощью Qt/qwt). В этом случае я использую панель управления Windows для подключения устройства и подключаю его через виртуальный COM-порт, используя интерфейс boost serial_port. Это работает отлично, и я получаю свой поток данных в режиме реального времени: я получаю точку измерения каждые 2 мс или около того.
Я портировал всю программу на Android через QtCreator 3.0.1 (Qt 5.2.1). Похоже, что виртуальные COM-порты не могут быть доступны с помощью boost (возможно, разрешения SDK этого не позволят), поэтому я написал фрагмент кода Java для открытия и управления соединением Bluetooth. Таким образом, мое приложение остается C++/Qt, но только слой, соединяющий и считывающий данные с устройства, был переработан в Java (открытие соединения с помощью createInsecureRfcommSocketToServiceRecord):
Java-код для чтения данных:
public int readData( byte[] buffer )
{
if( mInputStream == null )
{
traceErrorString("No connection, can't receive data");
}
else
{
try
{
final boolean verbose = false;
int available = mInputStream.available();
if ( verbose )
{
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);
traceDebugString( currentTime + ":" + c.get(Calendar.MILLISECOND) + " - " + available + " bytes available, requested " + buffer.length );
}
if ( available >= buffer.length )
return mInputStream.read( buffer ); // only call read if we know it's not blocking
else
return 0;
}
catch (IOException e)
{
traceDebugString( "Failed to read data...disconnected?" );
}
}
return -1;
}
Вызывается из C++ так:
bool ReceiveData( JNIEnv* env,
char* data,
size_t length,
bool& haserror )
{
bool result = false;
jbyteArray array = env->NewByteArray(length);
jint res = env->CallIntMethod(j_object, s_patchIfReceiveDataID, array );
if ( static_cast<size_t>(res) == length )
{
env->GetByteArrayRegion(array, 0, length, reinterpret_cast<jbyte*>(data));
result = true;
}
else if ( res == -1 )
{
haserror = true;
}
else
{
// not enough data in the stream buffer
haserror = false;
}
return result;
}
bool readThread( size_t blockSize )
{
BTGETANDCHECKENV // retrieving environment
char* buf = new char[blockSize];
bool haserror = false;
while ( !haserror )
{
if ( !ReceiveData( env, buf, blockSize, haserror ) )
{
// could not read data
if ( haserror )
{
// will stop this thread soon
}
else
{
boost::this_thread::sleep( boost::posix_time::milliseconds( 10 ) );
}
}
}
delete [] buf;
return true;
}
Это работает довольно хорошо... втечение первых пяти секунд я получаю значения в реальном времени, затем:
- Иногда он останавливается навсегда, что означает, что значение mInputStream.available() остается ниже запрошенного.
- Иногда он останавливается только на секунду или около того, а затем продолжается, но данные принимаются блоками ~1 секунды. Значение mInputStream.available() может перемещаться от 0 до более 3000 между двумя вызовами (по истечении 10 мс). На самом деле, я вижу то же самое в течение 5 первых секунд, но доступность буфера никогда не превышает 150 байт, через 5 секунд он может достигать 3000 байт.
Вот как может выглядеть журнал, если для verbose установлено значение true:
14:59:30:756 - 0 bytes available, requested 3
14:59:30:767 - 0 bytes available, requested 3
14:59:30:778 - 0 bytes available, requested 3
14:59:30:789 - 1728 bytes available, requested 3
14:59:30:790 - 1725 bytes available, requested 6
14:59:30:792 - 1719 bytes available, requested 3
Мое устройство ЭКГ определенно не отправило 1728 байт за 11 мс!!
Я знаю, что мое устройство отправляет 9 байтов каждые 2 мс (в противном случае оно не будет работать на моем ПК-приложении). Похоже, что Java делает некоторую неожиданную буферизацию и не делает доступными 9 байтов каждые 2 мс... Также странно, что поначалу все работает нормально всего 5 секунд.
Обратите внимание, что я пытался использовать read(), не проверяя available() (блокирующая версия), но испытал точно такое же поведение.
Вот и мне интересно, что я делаю не так...
- Есть ли способ заставить поток ввода Java обновить себя?
- Есть ли способ попросить Java продолжить ожидающие события (например, у нас есть QApplication::processEvents)?
- Есть ли глобальные настройки для указания размеров буфера для потоков (я не нашел ни одного на уровне BluetoothDevice/BluetoothSocket)
- На ПК, при открытии виртуального COM-порта, я должен указать скорость передачи, стоп-бит, квитирование и тому подобное. На Android я просто открываю гнездо Rfcomm без опций, может ли это быть проблемой (тогда устройство ЭКГ и смартфон не будут синхронизированы...)?
Любая помощь или идея будет приветствоваться!
Изменить: я испытываю, что на телефоне Nexus 5, Android 4.4.2 я только что протестировал один и тот же пакет apk на разных устройствах:
- Galaxy S4 с Android 4.4.2: та же проблема.
- Galaxy S3 с пользовательским CyanogenMod 11 Android 4.4.2: потоковая передача данных кажется идеальной, не останавливается через 5 секунд и данные поступают в режиме реального времени.... похоже, что вся система способна достичь того, чего я хочу, но выглядит так Настройка Android по умолчанию делает вещи слишком медленными.... не знаю, может ли быть параметр, который нужно изменить на уровне ОС, чтобы решить эту проблему.
Редактировать: Поскольку я не получил ответа:-(Я пытался сделать то же самое, используя чистую программу Java (без C++, без Qt). Была та же проблема: потоковая передача данных Bluetooth SPP в реальном времени на Android работает только в течение 5 секунд
1 ответ
Эта проблема, по-видимому, похожа на ту, о которой сообщалось здесь.
Через 5 секунд у меня либо пропало соединение, либо поток в реальном времени резко замедлился.
Как уже говорилось, Android >4.3 явно не любит одностороннюю связь, превышающую 5 секунд. Так что теперь я отправляю на устройство фиктивную команду каждую 1 секунду (своего рода команда "keep-alive"), и теперь Android счастлив, потому что это больше не односторонняя связь... и поэтому поток данных так же хорош после пятая секунда, чем раньше!