Как правильно выполнять потоковую передачу данных в режиме реального времени с помощью 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 счастлив, потому что это больше не односторонняя связь... и поэтому поток данных так же хорош после пятая секунда, чем раньше!

Другие вопросы по тегам