Хорошо, у меня был небольшой прорыв, и я обнаружил две проблемы:
- При вызове
_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()
.