UsbRequest.queue сбой приложения Android 3.1 - PullRequest
2 голосов
/ 10 марта 2012

Я разрабатываю приложение для Android 3.1, которое использует режим USB Host для связи с моей клавиатурой (Korg M3) через MIDI через USB. Это работает на моем Xoom с установленным Android 4.0.3. Я могу получать MIDI-сообщения через USB без каких-либо проблем, но отправка данных заметок обратно на клавиатуру имеет смешанный успех, с частыми сбоями после полсекундной задержки.

Вот ошибка, которую я продолжаю получать, нажимая кнопку на панели действий, чтобы отправить заметку:

E / dalvikvm (6422): ОШИБКА JNI (ошибка приложения): доступ к устаревшей глобальной ссылке 0x1da0020a (индекс 130 в таблице размера 130)

Что я проверял / пытался отследить причину:

  • Поскольку код является многопоточным, у меня есть блоки Java synchronized, окружающие доступы к пулу выходных запросов, пулу входных запросов (в соответствии с ADB sample в документации Android) и пользовательский объект блокировки для текущего запроса на вывод и связанных ByteBuffer ссылок на объекты. Я структурировал код, который выполняет эти блокировки, чтобы минимизировать вероятность возникновения взаимоблокировок.
  • При извлечении доступного объекта UsbRequest из соответствующего пула запросов я устанавливаю ссылку clientData на новый объект ByteBuffer, а не повторно использую ранее связанный объект ByteBuffer и вызываю clear() для него.
  • Я добавил расширенные вызовы журналирования (для logCat) в критических точках кода, чтобы попытаться отследить, где именно происходит сбой. Я обнаружил, что ошибка в конечном итоге возникает в следующей точке (этот код работает несколько раз до тех пор):

    public void sendMidiData()
    {
        synchronized(_outputLock)
        {
            if(_currentOutputRequest == null || _outputBuffer.position() < 2)
                return;
    
            Log.d(_tag, "Queuing Send request");
            //// ERROR - happens in this next statement:
            _currentOutputRequest.queue(_outputBuffer, _maxPacketSize);
            Log.d(_tag, "Send request queued; resetting references...");
            //Initialise for next packet
            _currentOutputRequest = null; //getAvailableSendRequest();
            _outputBuffer = null; //(ByteBuffer)_currentOutputRequest.getClientData();
            Log.d(_tag, "Output request & buffer references set.");
        }
    }
    
  • Я также попытался установить ссылки _currentOutputRequest и _outputBuffer на null, чтобы доступный запрос извлекался только тогда, когда следующее MIDI-событие должно быть записано в буфер. Если null заменяется исходными вызовами (как показано, закомментировано), следующий доступный запрос извлекается немедленно. Это не имеет значения.

Есть ли какие-либо идеи относительно того, что может вызвать эту проблему? Может ли это быть ранее неизвестной ошибкой в ​​Android? Поиск по возвращенной ошибке мало что дает; в основном ссылки на программирование NDK (чего я не делаю).

Приветствие.

Ответы [ 2 ]

6 голосов
/ 24 марта 2012

Хорошо, у меня был небольшой прорыв, и я обнаружил две проблемы:

  • При вызове _currentOutputRequest.queue(_outputBuffer, _maxPacketSize); прошла вся емкость буфера _maxPacketSize, которая имеет постоянное значение 64 (байт).По-видимому, это работает только для массового чтения, где будет прочитано до 64 байт;В массовых запросах на отправку необходимо указать точное количество отправляемых байтов.
  • Вызовы методов _currentOutputRequest.queue() и _connection.requestWait() не являются поточно-ориентированными, особенно в реализации UsbDeviceConnection (то естьтип _connection).Я подозреваю, что UsbRequest _currentOutputRequest внутренне использует объект UsbConnection при постановке в очередь запроса на отправку.

Как я это исправил:

Вызов queue()изменено на:

_currentOutputRequest.queue(_outputBuffer, _outputBuffer.position());

Для второй проблемы оператор queue() уже находится внутри блока synchronized с использованием объекта _outputLock.В методе Run() потока чтения мне пришлось заключить вызов в requestWait() в блоке synchronized, также используя outputLock:

UsbRequest request = null;
synchronized(_outputLock)
{
    // requestWait() and request.queue() appear not to be thread-safe.
    request = _connection.requestWait();
}

Учитывая, что это происходит в while loop и requestWait блокирует поток, одна из основных проблем, которую я обнаружил, заключается в том, что блокировка вызывает голодание при попытке поставить в очередь запрос на отправку.В результате приложение получает и обрабатывает входящие MIDI-данные своевременно, но выводимые MIDI-события значительно задерживаются.

В качестве частичного исправления для этого я вставил оператор yield непосредственно перед концом цикла while, чтобы сохранить поток пользовательского интерфейса разблокированным.(Поток пользовательского интерфейса временно используется, так как событие заметки запускается нажатием кнопки; в конечном итоге это будет использовать отдельный поток воспроизведения.) В результате это лучше, но не идеально, поскольку перед первой выходной заметкой все еще существует довольно большая задержка

Лучшее решение:

Чтобы разрешить требование взаимной блокировки для чтения и записи асинхронно, асинхронные методы queue() и requestWait()используется только для операций чтения, которые остаются в отдельном потоке 'reader'.Из-за этого блок synchronized не нужен, поэтому этот сегмент можно сократить до следующего:

UsbRequest request = _connection.requestWait();

Что касается операций записи / отправки, то ядро ​​этого перемещается в отдельный поток для выполнениясинхронные операторы bulkTransfer():

private class MidiSender extends Thread
{
    private boolean _raiseStop = false;
    private Object _sendLock = new Object();

    private LinkedList<ByteBuffer> _outputQueue = new LinkedList<ByteBuffer>();

    public void queue(ByteBuffer buffer)
    {
        synchronized(_sendLock)
        {
            _outputQueue.add(buffer);
            // Thread will most likely be paused (to save CPU); need to wake it
            _sendLock.notify();
        }
    }

    public void raiseStop()
    {
        synchronized (this)
        {
            _raiseStop = true;
        }

        //Thread may be blocked waiting for a send
        synchronized(_sendLock)
        {
            _sendLock.notify();
        }
    }

    public void run()
    {
        while (true)
        {
            synchronized (this)
            {
                if (_raiseStop) 
                    return;
            }

            ByteBuffer currentBuffer = null;
            synchronized(_sendLock)
            {
                if(!_outputQueue.isEmpty())
                    currentBuffer =_outputQueue.removeFirst(); 
            }

            while(currentBuffer != null)
            {
                // Here's the synchronous equivalent (timeout is a reasonable 0.1s):
                int transferred = _connection.bulkTransfer(_outPort, currentBuffer.array(), currentBuffer.position(), 100);

                if(transferred < 0)
                    Log.w(_tag, "Failed to send MIDI packet");

                //Process any remaining packets on the queue
                synchronized(_sendLock)
                {
                    if(!_outputQueue.isEmpty())
                        currentBuffer =_outputQueue.removeFirst();
                    else
                        currentBuffer = null;
                }
            }

            synchronized(_sendLock)
            {
                try
                {
                    //Sleep; save unnecessary processing
                    _sendLock.wait();
                }
                catch(InterruptedException e)
                {
                    //Don't care about being interrupted
                }
            }

        }           
    }
}

Сначала я беспокоился об асинхронном коде, конфликтующем с вышеприведенным (поскольку они совместно используют один и тот же UsbDeviceConnection), но, похоже, это не проблема, поскольку они 'мы используем совершенно разные UsbEndpoint экземпляры.

Хорошая новость заключается в том, что приложение работает более плавно и не вылетает при отправке заметок одновременно с игрой на клавиатуре.

Таким образом, в целом (для двунаправленной передачи данных по USB на отдельных конечных точках) кажется, что асинхронные методы лучше всего подходят для операций чтения / ввода, когда нам не нужно беспокоиться об определении поведения опроса (независимо от того, происходит это или нет)внутренне), тогда как операции вывода / отправки лучше обслуживаются синхронным методом bulkTransfer().

1 голос
/ 27 августа 2013

Я думаю, что есть ошибка в android_hardware_UsbRequest.cpp.

В функции android_hardware_UsbRequest_queue_direct () Global Ref устанавливается после запроса в очереди, поэтому ожидающий поток может получить управление раньшессылка установлена.

Глобальный Ref относится к объекту Java UsbRequest, и UsbDeviceConnection.requestWait использует это для восстановления объекта UsbRequest.Если ожидающий поток входит до того, как ссылка установлена, это будет устаревшей ссылкой.

Очевидное решение состоит в том, чтобы установить глобальную ссылку перед вызовом usb_request_queue () и удалить ссылку в случае сбоя вызова.

if (usb_request_queue(request)) {
    request->buffer = NULL;
    return false;
} else {
    // save a reference to ourselves so UsbDeviceConnection.waitRequest() can find us
    // we also need this to make sure our native buffer is not deallocated
    // while IO is active
    request->client_data = (void *)env->NewGlobalRef(thiz);
    return true;
}

Обновление: Я поднял это как Проблема AOSP # 59467 .

...