Merge "USB MIDI: Multijack MIDI 1.0 for MIDI 2.0 devices" into udc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
3b4472fc03
@@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
package com.android.internal.midi;
|
package com.android.internal.midi;
|
||||||
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.SortedMap;
|
import java.util.SortedMap;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
|
|
||||||
@@ -26,11 +25,11 @@ import java.util.TreeMap;
|
|||||||
* And only one Thread can read from the buffer.
|
* And only one Thread can read from the buffer.
|
||||||
*/
|
*/
|
||||||
public class EventScheduler {
|
public class EventScheduler {
|
||||||
private static final long NANOS_PER_MILLI = 1000000;
|
public static final long NANOS_PER_MILLI = 1000000;
|
||||||
|
|
||||||
private final Object mLock = new Object();
|
private final Object mLock = new Object();
|
||||||
volatile private SortedMap<Long, FastEventQueue> mEventBuffer;
|
protected volatile SortedMap<Long, FastEventQueue> mEventBuffer;
|
||||||
private FastEventQueue mEventPool = null;
|
protected FastEventQueue mEventPool = null;
|
||||||
private int mMaxPoolSize = 200;
|
private int mMaxPoolSize = 200;
|
||||||
private boolean mClosed;
|
private boolean mClosed;
|
||||||
|
|
||||||
@@ -38,9 +37,13 @@ public class EventScheduler {
|
|||||||
mEventBuffer = new TreeMap<Long, FastEventQueue>();
|
mEventBuffer = new TreeMap<Long, FastEventQueue>();
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we keep at least one node in the list then it can be atomic
|
/**
|
||||||
// and non-blocking.
|
* Class for a fast event queue.
|
||||||
private class FastEventQueue {
|
*
|
||||||
|
* If we keep at least one node in the list then it can be atomic
|
||||||
|
* and non-blocking.
|
||||||
|
*/
|
||||||
|
public static class FastEventQueue {
|
||||||
// One thread takes from the beginning of the list.
|
// One thread takes from the beginning of the list.
|
||||||
volatile SchedulableEvent mFirst;
|
volatile SchedulableEvent mFirst;
|
||||||
// A second thread returns events to the end of the list.
|
// A second thread returns events to the end of the list.
|
||||||
@@ -48,7 +51,7 @@ public class EventScheduler {
|
|||||||
volatile long mEventsAdded;
|
volatile long mEventsAdded;
|
||||||
volatile long mEventsRemoved;
|
volatile long mEventsRemoved;
|
||||||
|
|
||||||
FastEventQueue(SchedulableEvent event) {
|
public FastEventQueue(SchedulableEvent event) {
|
||||||
mFirst = event;
|
mFirst = event;
|
||||||
mLast = mFirst;
|
mLast = mFirst;
|
||||||
mEventsAdded = 1;
|
mEventsAdded = 1;
|
||||||
@@ -149,7 +152,8 @@ public class EventScheduler {
|
|||||||
* @param event
|
* @param event
|
||||||
*/
|
*/
|
||||||
public void add(SchedulableEvent event) {
|
public void add(SchedulableEvent event) {
|
||||||
synchronized (mLock) {
|
Object lock = getLock();
|
||||||
|
synchronized (lock) {
|
||||||
FastEventQueue list = mEventBuffer.get(event.getTimestamp());
|
FastEventQueue list = mEventBuffer.get(event.getTimestamp());
|
||||||
if (list == null) {
|
if (list == null) {
|
||||||
long lowestTime = mEventBuffer.isEmpty() ? Long.MAX_VALUE
|
long lowestTime = mEventBuffer.isEmpty() ? Long.MAX_VALUE
|
||||||
@@ -159,7 +163,7 @@ public class EventScheduler {
|
|||||||
// If the event we added is earlier than the previous earliest
|
// If the event we added is earlier than the previous earliest
|
||||||
// event then notify any threads waiting for the next event.
|
// event then notify any threads waiting for the next event.
|
||||||
if (event.getTimestamp() < lowestTime) {
|
if (event.getTimestamp() < lowestTime) {
|
||||||
mLock.notify();
|
lock.notify();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
list.add(event);
|
list.add(event);
|
||||||
@@ -167,7 +171,7 @@ public class EventScheduler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private SchedulableEvent removeNextEventLocked(long lowestTime) {
|
protected SchedulableEvent removeNextEventLocked(long lowestTime) {
|
||||||
SchedulableEvent event;
|
SchedulableEvent event;
|
||||||
FastEventQueue list = mEventBuffer.get(lowestTime);
|
FastEventQueue list = mEventBuffer.get(lowestTime);
|
||||||
// Remove list from tree if this is the last node.
|
// Remove list from tree if this is the last node.
|
||||||
@@ -186,7 +190,8 @@ public class EventScheduler {
|
|||||||
*/
|
*/
|
||||||
public SchedulableEvent getNextEvent(long time) {
|
public SchedulableEvent getNextEvent(long time) {
|
||||||
SchedulableEvent event = null;
|
SchedulableEvent event = null;
|
||||||
synchronized (mLock) {
|
Object lock = getLock();
|
||||||
|
synchronized (lock) {
|
||||||
if (!mEventBuffer.isEmpty()) {
|
if (!mEventBuffer.isEmpty()) {
|
||||||
long lowestTime = mEventBuffer.firstKey();
|
long lowestTime = mEventBuffer.firstKey();
|
||||||
// Is it time for this list to be processed?
|
// Is it time for this list to be processed?
|
||||||
@@ -209,7 +214,8 @@ public class EventScheduler {
|
|||||||
*/
|
*/
|
||||||
public SchedulableEvent waitNextEvent() throws InterruptedException {
|
public SchedulableEvent waitNextEvent() throws InterruptedException {
|
||||||
SchedulableEvent event = null;
|
SchedulableEvent event = null;
|
||||||
synchronized (mLock) {
|
Object lock = getLock();
|
||||||
|
synchronized (lock) {
|
||||||
while (!mClosed) {
|
while (!mClosed) {
|
||||||
long millisToWait = Integer.MAX_VALUE;
|
long millisToWait = Integer.MAX_VALUE;
|
||||||
if (!mEventBuffer.isEmpty()) {
|
if (!mEventBuffer.isEmpty()) {
|
||||||
@@ -231,7 +237,7 @@ public class EventScheduler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mLock.wait((int) millisToWait);
|
lock.wait((int) millisToWait);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return event;
|
return event;
|
||||||
@@ -242,10 +248,25 @@ public class EventScheduler {
|
|||||||
mEventBuffer = new TreeMap<Long, FastEventQueue>();
|
mEventBuffer = new TreeMap<Long, FastEventQueue>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops the EventScheduler.
|
||||||
|
* The subscriber calling waitNextEvent() will get one final SchedulableEvent returning null.
|
||||||
|
*/
|
||||||
public void close() {
|
public void close() {
|
||||||
synchronized (mLock) {
|
Object lock = getLock();
|
||||||
|
synchronized (lock) {
|
||||||
mClosed = true;
|
mClosed = true;
|
||||||
mLock.notify();
|
lock.notify();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the lock. This doesn't lock it in anyway.
|
||||||
|
* Subclasses can override this.
|
||||||
|
*
|
||||||
|
* @return Object
|
||||||
|
*/
|
||||||
|
protected Object getLock() {
|
||||||
|
return mLock;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
128
core/java/com/android/internal/midi/MidiEventMultiScheduler.java
Normal file
128
core/java/com/android/internal/midi/MidiEventMultiScheduler.java
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.internal.midi;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uses multiple MidiEventSchedulers for waiting for events.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class MidiEventMultiScheduler {
|
||||||
|
private MultiLockMidiEventScheduler[] mMidiEventSchedulers;
|
||||||
|
private int mNumEventSchedulers;
|
||||||
|
private int mNumClosedSchedulers = 0;
|
||||||
|
private final Object mMultiLock = new Object();
|
||||||
|
|
||||||
|
private class MultiLockMidiEventScheduler extends MidiEventScheduler {
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
synchronized (mMultiLock) {
|
||||||
|
mNumClosedSchedulers++;
|
||||||
|
}
|
||||||
|
super.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Object getLock() {
|
||||||
|
return mMultiLock;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEventBufferEmptyLocked() {
|
||||||
|
return mEventBuffer.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getLowestTimeLocked() {
|
||||||
|
return mEventBuffer.firstKey();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MidiEventMultiScheduler constructor
|
||||||
|
*
|
||||||
|
* @param numSchedulers the number of schedulers to create
|
||||||
|
*/
|
||||||
|
public MidiEventMultiScheduler(int numSchedulers) {
|
||||||
|
mNumEventSchedulers = numSchedulers;
|
||||||
|
mMidiEventSchedulers = new MultiLockMidiEventScheduler[numSchedulers];
|
||||||
|
for (int i = 0; i < numSchedulers; i++) {
|
||||||
|
mMidiEventSchedulers[i] = new MultiLockMidiEventScheduler();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Waits for the next MIDI event. This will return true when it receives it.
|
||||||
|
* If all MidiEventSchedulers have been closed, this will return false.
|
||||||
|
*
|
||||||
|
* @return true if a MIDI event is received and false if all schedulers are closed.
|
||||||
|
*/
|
||||||
|
public boolean waitNextEvent() throws InterruptedException {
|
||||||
|
synchronized (mMultiLock) {
|
||||||
|
while (true) {
|
||||||
|
if (mNumClosedSchedulers >= mNumEventSchedulers) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
long lowestTime = Long.MAX_VALUE;
|
||||||
|
long now = System.nanoTime();
|
||||||
|
for (MultiLockMidiEventScheduler eventScheduler : mMidiEventSchedulers) {
|
||||||
|
if (!eventScheduler.isEventBufferEmptyLocked()) {
|
||||||
|
lowestTime = Math.min(lowestTime,
|
||||||
|
eventScheduler.getLowestTimeLocked());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (lowestTime <= now) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
long nanosToWait = lowestTime - now;
|
||||||
|
// Add 1 millisecond so we don't wake up before it is
|
||||||
|
// ready.
|
||||||
|
long millisToWait = 1 + (nanosToWait / EventScheduler.NANOS_PER_MILLI);
|
||||||
|
// Clip 64-bit value to 32-bit max.
|
||||||
|
if (millisToWait > Integer.MAX_VALUE) {
|
||||||
|
millisToWait = Integer.MAX_VALUE;
|
||||||
|
}
|
||||||
|
mMultiLock.wait(millisToWait);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the number of MidiEventSchedulers.
|
||||||
|
*
|
||||||
|
* @return the number of MidiEventSchedulers.
|
||||||
|
*/
|
||||||
|
public int getNumEventSchedulers() {
|
||||||
|
return mNumEventSchedulers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a specific MidiEventScheduler based on the index.
|
||||||
|
*
|
||||||
|
* @param index the zero indexed index of a MIDI event scheduler
|
||||||
|
* @return a MidiEventScheduler
|
||||||
|
*/
|
||||||
|
public MidiEventScheduler getEventScheduler(int index) {
|
||||||
|
return mMidiEventSchedulers[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes all event schedulers.
|
||||||
|
*/
|
||||||
|
public void close() {
|
||||||
|
for (MidiEventScheduler eventScheduler : mMidiEventSchedulers) {
|
||||||
|
eventScheduler.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -79,7 +79,7 @@ public class MidiEventScheduler extends EventScheduler {
|
|||||||
/**
|
/**
|
||||||
* Create an event that contains the message.
|
* Create an event that contains the message.
|
||||||
*/
|
*/
|
||||||
private MidiEvent createScheduledEvent(byte[] msg, int offset, int count,
|
public MidiEvent createScheduledEvent(byte[] msg, int offset, int count,
|
||||||
long timestamp) {
|
long timestamp) {
|
||||||
MidiEvent event;
|
MidiEvent event;
|
||||||
if (count > POOL_EVENT_SIZE) {
|
if (count > POOL_EVENT_SIZE) {
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ package com.android.server.usb;
|
|||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.hardware.usb.UsbConfiguration;
|
import android.hardware.usb.UsbConfiguration;
|
||||||
import android.hardware.usb.UsbConstants;
|
|
||||||
import android.hardware.usb.UsbDevice;
|
import android.hardware.usb.UsbDevice;
|
||||||
import android.hardware.usb.UsbDeviceConnection;
|
import android.hardware.usb.UsbDeviceConnection;
|
||||||
import android.hardware.usb.UsbEndpoint;
|
import android.hardware.usb.UsbEndpoint;
|
||||||
@@ -35,9 +34,12 @@ import android.os.Bundle;
|
|||||||
import android.service.usb.UsbDirectMidiDeviceProto;
|
import android.service.usb.UsbDirectMidiDeviceProto;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.android.internal.midi.MidiEventMultiScheduler;
|
||||||
import com.android.internal.midi.MidiEventScheduler;
|
import com.android.internal.midi.MidiEventScheduler;
|
||||||
import com.android.internal.midi.MidiEventScheduler.MidiEvent;
|
import com.android.internal.midi.MidiEventScheduler.MidiEvent;
|
||||||
import com.android.internal.util.dump.DualDumpOutputStream;
|
import com.android.internal.util.dump.DualDumpOutputStream;
|
||||||
|
import com.android.server.usb.descriptors.UsbACMidi10Endpoint;
|
||||||
|
import com.android.server.usb.descriptors.UsbDescriptor;
|
||||||
import com.android.server.usb.descriptors.UsbDescriptorParser;
|
import com.android.server.usb.descriptors.UsbDescriptorParser;
|
||||||
import com.android.server.usb.descriptors.UsbEndpointDescriptor;
|
import com.android.server.usb.descriptors.UsbEndpointDescriptor;
|
||||||
import com.android.server.usb.descriptors.UsbInterfaceDescriptor;
|
import com.android.server.usb.descriptors.UsbInterfaceDescriptor;
|
||||||
@@ -45,6 +47,7 @@ import com.android.server.usb.descriptors.UsbMidiBlockParser;
|
|||||||
|
|
||||||
import libcore.io.IoUtils;
|
import libcore.io.IoUtils;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
@@ -56,7 +59,7 @@ import java.util.ArrayList;
|
|||||||
*/
|
*/
|
||||||
public final class UsbDirectMidiDevice implements Closeable {
|
public final class UsbDirectMidiDevice implements Closeable {
|
||||||
private static final String TAG = "UsbDirectMidiDevice";
|
private static final String TAG = "UsbDirectMidiDevice";
|
||||||
private static final boolean DEBUG = false;
|
private static final boolean DEBUG = true;
|
||||||
|
|
||||||
private Context mContext;
|
private Context mContext;
|
||||||
private String mName;
|
private String mName;
|
||||||
@@ -74,9 +77,6 @@ public final class UsbDirectMidiDevice implements Closeable {
|
|||||||
|
|
||||||
private MidiDeviceServer mServer;
|
private MidiDeviceServer mServer;
|
||||||
|
|
||||||
// event schedulers for each input port of the physical device
|
|
||||||
private MidiEventScheduler[] mEventSchedulers;
|
|
||||||
|
|
||||||
// Timeout for sending a packet to a device.
|
// Timeout for sending a packet to a device.
|
||||||
// If bulkTransfer times out, retry sending the packet up to 20 times.
|
// If bulkTransfer times out, retry sending the packet up to 20 times.
|
||||||
private static final int BULK_TRANSFER_TIMEOUT_MILLISECONDS = 50;
|
private static final int BULK_TRANSFER_TIMEOUT_MILLISECONDS = 50;
|
||||||
@@ -86,8 +86,21 @@ public final class UsbDirectMidiDevice implements Closeable {
|
|||||||
private static final int THREAD_JOIN_TIMEOUT_MILLISECONDS = 200;
|
private static final int THREAD_JOIN_TIMEOUT_MILLISECONDS = 200;
|
||||||
|
|
||||||
private ArrayList<UsbDeviceConnection> mUsbDeviceConnections;
|
private ArrayList<UsbDeviceConnection> mUsbDeviceConnections;
|
||||||
|
|
||||||
|
// Array of endpoints by device connection.
|
||||||
private ArrayList<ArrayList<UsbEndpoint>> mInputUsbEndpoints;
|
private ArrayList<ArrayList<UsbEndpoint>> mInputUsbEndpoints;
|
||||||
private ArrayList<ArrayList<UsbEndpoint>> mOutputUsbEndpoints;
|
private ArrayList<ArrayList<UsbEndpoint>> mOutputUsbEndpoints;
|
||||||
|
|
||||||
|
// Array of cable counts by device connection.
|
||||||
|
// Each number here maps to an entry in mInputUsbEndpoints or mOutputUsbEndpoints.
|
||||||
|
// This is needed because this info is part of UsbEndpointDescriptor but not UsbEndpoint.
|
||||||
|
private ArrayList<ArrayList<Integer>> mInputUsbEndpointCableCounts;
|
||||||
|
private ArrayList<ArrayList<Integer>> mOutputUsbEndpointCableCounts;
|
||||||
|
|
||||||
|
// Array of event schedulers by device connection.
|
||||||
|
// Each number here maps to an entry in mOutputUsbEndpoints.
|
||||||
|
private ArrayList<ArrayList<MidiEventMultiScheduler>> mMidiEventMultiSchedulers;
|
||||||
|
|
||||||
private ArrayList<Thread> mThreads;
|
private ArrayList<Thread> mThreads;
|
||||||
|
|
||||||
private UsbMidiBlockParser mMidiBlockParser = new UsbMidiBlockParser();
|
private UsbMidiBlockParser mMidiBlockParser = new UsbMidiBlockParser();
|
||||||
@@ -97,8 +110,6 @@ public final class UsbDirectMidiDevice implements Closeable {
|
|||||||
private boolean mIsOpen;
|
private boolean mIsOpen;
|
||||||
private boolean mServerAvailable;
|
private boolean mServerAvailable;
|
||||||
|
|
||||||
private UsbMidiPacketConverter mUsbMidiPacketConverter;
|
|
||||||
|
|
||||||
private PowerBoostSetter mPowerBoostSetter = null;
|
private PowerBoostSetter mPowerBoostSetter = null;
|
||||||
|
|
||||||
private static final byte MESSAGE_TYPE_MIDI_1_CHANNEL_VOICE = 0x02;
|
private static final byte MESSAGE_TYPE_MIDI_1_CHANNEL_VOICE = 0x02;
|
||||||
@@ -238,9 +249,9 @@ public final class UsbDirectMidiDevice implements Closeable {
|
|||||||
interfaceDescriptor.getEndpointDescriptor(endpointIndex);
|
interfaceDescriptor.getEndpointDescriptor(endpointIndex);
|
||||||
// 0 is output, 1 << 7 is input.
|
// 0 is output, 1 << 7 is input.
|
||||||
if (endpoint.getDirection() == 0) {
|
if (endpoint.getDirection() == 0) {
|
||||||
numOutputs++;
|
numOutputs += getNumJacks(endpoint);
|
||||||
} else {
|
} else {
|
||||||
numInputs++;
|
numInputs += getNumJacks(endpoint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -307,19 +318,21 @@ public final class UsbDirectMidiDevice implements Closeable {
|
|||||||
Log.d(TAG, "openLocked()");
|
Log.d(TAG, "openLocked()");
|
||||||
UsbManager manager = mContext.getSystemService(UsbManager.class);
|
UsbManager manager = mContext.getSystemService(UsbManager.class);
|
||||||
|
|
||||||
// Converting from raw MIDI to USB MIDI is not thread-safe.
|
|
||||||
// UsbMidiPacketConverter creates a converter from raw MIDI
|
|
||||||
// to USB MIDI for each USB output.
|
|
||||||
mUsbMidiPacketConverter = new UsbMidiPacketConverter(mNumOutputs);
|
|
||||||
|
|
||||||
mUsbDeviceConnections = new ArrayList<UsbDeviceConnection>();
|
mUsbDeviceConnections = new ArrayList<UsbDeviceConnection>();
|
||||||
mInputUsbEndpoints = new ArrayList<ArrayList<UsbEndpoint>>();
|
mInputUsbEndpoints = new ArrayList<ArrayList<UsbEndpoint>>();
|
||||||
mOutputUsbEndpoints = new ArrayList<ArrayList<UsbEndpoint>>();
|
mOutputUsbEndpoints = new ArrayList<ArrayList<UsbEndpoint>>();
|
||||||
|
mInputUsbEndpointCableCounts = new ArrayList<ArrayList<Integer>>();
|
||||||
|
mOutputUsbEndpointCableCounts = new ArrayList<ArrayList<Integer>>();
|
||||||
|
mMidiEventMultiSchedulers = new ArrayList<ArrayList<MidiEventMultiScheduler>>();
|
||||||
mThreads = new ArrayList<Thread>();
|
mThreads = new ArrayList<Thread>();
|
||||||
|
|
||||||
for (int interfaceIndex = 0; interfaceIndex < mUsbInterfaces.size(); interfaceIndex++) {
|
for (int interfaceIndex = 0; interfaceIndex < mUsbInterfaces.size(); interfaceIndex++) {
|
||||||
ArrayList<UsbEndpoint> inputEndpoints = new ArrayList<UsbEndpoint>();
|
ArrayList<UsbEndpoint> inputEndpoints = new ArrayList<UsbEndpoint>();
|
||||||
ArrayList<UsbEndpoint> outputEndpoints = new ArrayList<UsbEndpoint>();
|
ArrayList<UsbEndpoint> outputEndpoints = new ArrayList<UsbEndpoint>();
|
||||||
|
ArrayList<Integer> inputEndpointCableCounts = new ArrayList<Integer>();
|
||||||
|
ArrayList<Integer> outputEndpointCableCounts = new ArrayList<Integer>();
|
||||||
|
ArrayList<MidiEventMultiScheduler> midiEventMultiSchedulers =
|
||||||
|
new ArrayList<MidiEventMultiScheduler>();
|
||||||
UsbInterfaceDescriptor interfaceDescriptor = mUsbInterfaces.get(interfaceIndex);
|
UsbInterfaceDescriptor interfaceDescriptor = mUsbInterfaces.get(interfaceIndex);
|
||||||
for (int endpointIndex = 0; endpointIndex < interfaceDescriptor.getNumEndpoints();
|
for (int endpointIndex = 0; endpointIndex < interfaceDescriptor.getNumEndpoints();
|
||||||
endpointIndex++) {
|
endpointIndex++) {
|
||||||
@@ -328,8 +341,13 @@ public final class UsbDirectMidiDevice implements Closeable {
|
|||||||
// 0 is output, 1 << 7 is input.
|
// 0 is output, 1 << 7 is input.
|
||||||
if (endpoint.getDirection() == 0) {
|
if (endpoint.getDirection() == 0) {
|
||||||
outputEndpoints.add(endpoint.toAndroid(mParser));
|
outputEndpoints.add(endpoint.toAndroid(mParser));
|
||||||
|
outputEndpointCableCounts.add(getNumJacks(endpoint));
|
||||||
|
MidiEventMultiScheduler scheduler =
|
||||||
|
new MidiEventMultiScheduler(getNumJacks(endpoint));
|
||||||
|
midiEventMultiSchedulers.add(scheduler);
|
||||||
} else {
|
} else {
|
||||||
inputEndpoints.add(endpoint.toAndroid(mParser));
|
inputEndpoints.add(endpoint.toAndroid(mParser));
|
||||||
|
inputEndpointCableCounts.add(getNumJacks(endpoint));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!outputEndpoints.isEmpty() || !inputEndpoints.isEmpty()) {
|
if (!outputEndpoints.isEmpty() || !inputEndpoints.isEmpty()) {
|
||||||
@@ -341,40 +359,69 @@ public final class UsbDirectMidiDevice implements Closeable {
|
|||||||
mUsbDeviceConnections.add(connection);
|
mUsbDeviceConnections.add(connection);
|
||||||
mInputUsbEndpoints.add(inputEndpoints);
|
mInputUsbEndpoints.add(inputEndpoints);
|
||||||
mOutputUsbEndpoints.add(outputEndpoints);
|
mOutputUsbEndpoints.add(outputEndpoints);
|
||||||
|
mInputUsbEndpointCableCounts.add(inputEndpointCableCounts);
|
||||||
|
mOutputUsbEndpointCableCounts.add(outputEndpointCableCounts);
|
||||||
|
mMidiEventMultiSchedulers.add(midiEventMultiSchedulers);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mEventSchedulers = new MidiEventScheduler[mNumOutputs];
|
// Set up event schedulers
|
||||||
|
int outputIndex = 0;
|
||||||
for (int i = 0; i < mNumOutputs; i++) {
|
for (int connectionIndex = 0; connectionIndex < mMidiEventMultiSchedulers.size();
|
||||||
MidiEventScheduler scheduler = new MidiEventScheduler();
|
connectionIndex++) {
|
||||||
mEventSchedulers[i] = scheduler;
|
for (int endpointIndex = 0;
|
||||||
mMidiInputPortReceivers[i].setReceiver(scheduler.getReceiver());
|
endpointIndex < mMidiEventMultiSchedulers.get(connectionIndex).size();
|
||||||
|
endpointIndex++) {
|
||||||
|
int cableCount =
|
||||||
|
mOutputUsbEndpointCableCounts.get(connectionIndex).get(endpointIndex);
|
||||||
|
MidiEventMultiScheduler multiScheduler =
|
||||||
|
mMidiEventMultiSchedulers.get(connectionIndex).get(endpointIndex);
|
||||||
|
for (int cableNumber = 0; cableNumber < cableCount; cableNumber++) {
|
||||||
|
MidiEventScheduler scheduler = multiScheduler.getEventScheduler(cableNumber);
|
||||||
|
mMidiInputPortReceivers[outputIndex].setReceiver(scheduler.getReceiver());
|
||||||
|
outputIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final MidiReceiver[] outputReceivers = mServer.getOutputPortReceivers();
|
final MidiReceiver[] outputReceivers = mServer.getOutputPortReceivers();
|
||||||
|
|
||||||
// Create input thread for each input port of the physical device
|
// Create input thread for each input port of the physical device
|
||||||
int portNumber = 0;
|
int portStartNumber = 0;
|
||||||
for (int connectionIndex = 0; connectionIndex < mInputUsbEndpoints.size();
|
for (int connectionIndex = 0; connectionIndex < mInputUsbEndpoints.size();
|
||||||
connectionIndex++) {
|
connectionIndex++) {
|
||||||
for (int endpointIndex = 0;
|
for (int endpointIndex = 0;
|
||||||
endpointIndex < mInputUsbEndpoints.get(connectionIndex).size();
|
endpointIndex < mInputUsbEndpoints.get(connectionIndex).size();
|
||||||
endpointIndex++) {
|
endpointIndex++) {
|
||||||
|
// Each USB endpoint maps to one or more outputReceivers. USB MIDI data from an
|
||||||
|
// endpoint should be sent to the appropriate outputReceiver. A new thread is
|
||||||
|
// created and waits for incoming USB data. Once the data is received, it is added
|
||||||
|
// to the packet converter. The packet converter acts as an inverse multiplexer.
|
||||||
|
// With a for loop, data is pulled per cable and sent to the correct output
|
||||||
|
// receiver. The first byte of each legacy MIDI 1.0 USB message indicates which
|
||||||
|
// cable the data should be used and is how the reverse multiplexer directs data.
|
||||||
|
// For MIDI UMP endpoints, a multiplexer is not needed as we are just swapping
|
||||||
|
// the endianness of the packets.
|
||||||
final UsbDeviceConnection connectionFinal =
|
final UsbDeviceConnection connectionFinal =
|
||||||
mUsbDeviceConnections.get(connectionIndex);
|
mUsbDeviceConnections.get(connectionIndex);
|
||||||
final UsbEndpoint endpointFinal =
|
final UsbEndpoint endpointFinal =
|
||||||
mInputUsbEndpoints.get(connectionIndex).get(endpointIndex);
|
mInputUsbEndpoints.get(connectionIndex).get(endpointIndex);
|
||||||
final int portFinal = portNumber;
|
final int portStartFinal = portStartNumber;
|
||||||
|
final int cableCountFinal =
|
||||||
|
mInputUsbEndpointCableCounts.get(connectionIndex).get(endpointIndex);
|
||||||
|
|
||||||
Thread newThread = new Thread("UsbDirectMidiDevice input thread " + portFinal) {
|
Thread newThread = new Thread("UsbDirectMidiDevice input thread "
|
||||||
|
+ portStartFinal) {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
final UsbRequest request = new UsbRequest();
|
final UsbRequest request = new UsbRequest();
|
||||||
|
final UsbMidiPacketConverter packetConverter = new UsbMidiPacketConverter();
|
||||||
|
packetConverter.createDecoders(cableCountFinal);
|
||||||
try {
|
try {
|
||||||
request.initialize(connectionFinal, endpointFinal);
|
request.initialize(connectionFinal, endpointFinal);
|
||||||
byte[] inputBuffer = new byte[endpointFinal.getMaxPacketSize()];
|
byte[] inputBuffer = new byte[endpointFinal.getMaxPacketSize()];
|
||||||
while (true) {
|
boolean keepGoing = true;
|
||||||
|
while (keepGoing) {
|
||||||
if (Thread.currentThread().interrupted()) {
|
if (Thread.currentThread().interrupted()) {
|
||||||
Log.w(TAG, "input thread interrupted");
|
Log.w(TAG, "input thread interrupted");
|
||||||
break;
|
break;
|
||||||
@@ -404,42 +451,56 @@ public final class UsbDirectMidiDevice implements Closeable {
|
|||||||
logByteArray("Input before conversion ", inputBuffer,
|
logByteArray("Input before conversion ", inputBuffer,
|
||||||
0, bytesRead);
|
0, bytesRead);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add packets into the packet decoder.
|
||||||
|
if (!mIsUniversalMidiDevice) {
|
||||||
|
packetConverter.decodeMidiPackets(inputBuffer, bytesRead);
|
||||||
|
}
|
||||||
|
|
||||||
byte[] convertedArray;
|
byte[] convertedArray;
|
||||||
if (mIsUniversalMidiDevice) {
|
for (int cableNumber = 0; cableNumber < cableCountFinal;
|
||||||
// For USB, each 32 bit word of a UMP is
|
cableNumber++) {
|
||||||
// sent with the least significant byte first.
|
if (mIsUniversalMidiDevice) {
|
||||||
convertedArray = swapEndiannessPerWord(inputBuffer,
|
// For USB, each 32 bit word of a UMP is
|
||||||
bytesRead);
|
// sent with the least significant byte first.
|
||||||
} else {
|
convertedArray = swapEndiannessPerWord(inputBuffer,
|
||||||
if (mUsbMidiPacketConverter == null) {
|
bytesRead);
|
||||||
Log.w(TAG, "mUsbMidiPacketConverter is null");
|
} else {
|
||||||
|
convertedArray =
|
||||||
|
packetConverter.pullDecodedMidiPackets(
|
||||||
|
cableNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DEBUG) {
|
||||||
|
logByteArray("Input " + cableNumber
|
||||||
|
+ " after conversion ", convertedArray, 0,
|
||||||
|
convertedArray.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (convertedArray.length == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((outputReceivers == null)
|
||||||
|
|| (outputReceivers[portStartFinal + cableNumber]
|
||||||
|
== null)) {
|
||||||
|
Log.w(TAG, "outputReceivers is null");
|
||||||
|
keepGoing = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
convertedArray =
|
outputReceivers[portStartFinal + cableNumber].send(
|
||||||
mUsbMidiPacketConverter.usbMidiToRawMidi(
|
convertedArray, 0, convertedArray.length,
|
||||||
inputBuffer, bytesRead);
|
timestamp);
|
||||||
}
|
|
||||||
|
|
||||||
if (DEBUG) {
|
// Boost power if there seems to be a voice message.
|
||||||
logByteArray("Input after conversion ", convertedArray,
|
// For legacy devices, boost if message length > 1.
|
||||||
0, convertedArray.length);
|
// For UMP devices, boost for channel voice messages.
|
||||||
}
|
if ((mPowerBoostSetter != null
|
||||||
|
&& convertedArray.length > 1)
|
||||||
if ((outputReceivers == null)
|
&& (!mIsUniversalMidiDevice
|
||||||
|| (outputReceivers[portFinal] == null)) {
|
|| isChannelVoiceMessage(convertedArray))) {
|
||||||
Log.w(TAG, "outputReceivers is null");
|
mPowerBoostSetter.boostPower();
|
||||||
break;
|
}
|
||||||
}
|
|
||||||
outputReceivers[portFinal].send(convertedArray, 0,
|
|
||||||
convertedArray.length, timestamp);
|
|
||||||
|
|
||||||
// Boost power if there seems to be a voice message.
|
|
||||||
// For legacy devices, boost when message is more than size 1.
|
|
||||||
// For UMP devices, boost for channel voice messages.
|
|
||||||
if ((mPowerBoostSetter != null && convertedArray.length > 1)
|
|
||||||
&& (!mIsUniversalMidiDevice
|
|
||||||
|| isChannelVoiceMessage(convertedArray))) {
|
|
||||||
mPowerBoostSetter.boostPower();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -455,64 +516,93 @@ public final class UsbDirectMidiDevice implements Closeable {
|
|||||||
};
|
};
|
||||||
newThread.start();
|
newThread.start();
|
||||||
mThreads.add(newThread);
|
mThreads.add(newThread);
|
||||||
portNumber++;
|
portStartNumber += cableCountFinal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create output thread for each output port of the physical device
|
// Create output thread for each output port of the physical device
|
||||||
portNumber = 0;
|
portStartNumber = 0;
|
||||||
for (int connectionIndex = 0; connectionIndex < mOutputUsbEndpoints.size();
|
for (int connectionIndex = 0; connectionIndex < mOutputUsbEndpoints.size();
|
||||||
connectionIndex++) {
|
connectionIndex++) {
|
||||||
for (int endpointIndex = 0;
|
for (int endpointIndex = 0;
|
||||||
endpointIndex < mOutputUsbEndpoints.get(connectionIndex).size();
|
endpointIndex < mOutputUsbEndpoints.get(connectionIndex).size();
|
||||||
endpointIndex++) {
|
endpointIndex++) {
|
||||||
|
// Each USB endpoint maps to one or more MIDI ports. Each port has an event
|
||||||
|
// scheduler that is used to pull incoming raw MIDI bytes from Android apps.
|
||||||
|
// With a MidiEventMultiScheduler, data can be pulled if any of the schedulers
|
||||||
|
// have new incoming data. This data is then packaged as USB MIDI packets before
|
||||||
|
// getting sent through USB. One thread will be created per endpoint that pulls
|
||||||
|
// data from all relevant event schedulers. Raw MIDI from the event schedulers
|
||||||
|
// will be converted to the correct USB MIDI format before getting sent through
|
||||||
|
// USB.
|
||||||
|
|
||||||
final UsbDeviceConnection connectionFinal =
|
final UsbDeviceConnection connectionFinal =
|
||||||
mUsbDeviceConnections.get(connectionIndex);
|
mUsbDeviceConnections.get(connectionIndex);
|
||||||
final UsbEndpoint endpointFinal =
|
final UsbEndpoint endpointFinal =
|
||||||
mOutputUsbEndpoints.get(connectionIndex).get(endpointIndex);
|
mOutputUsbEndpoints.get(connectionIndex).get(endpointIndex);
|
||||||
final int portFinal = portNumber;
|
final int portStartFinal = portStartNumber;
|
||||||
final MidiEventScheduler eventSchedulerFinal = mEventSchedulers[portFinal];
|
final int cableCountFinal =
|
||||||
|
mOutputUsbEndpointCableCounts.get(connectionIndex).get(endpointIndex);
|
||||||
|
final MidiEventMultiScheduler multiSchedulerFinal =
|
||||||
|
mMidiEventMultiSchedulers.get(connectionIndex).get(endpointIndex);
|
||||||
|
|
||||||
Thread newThread = new Thread("UsbDirectMidiDevice output thread " + portFinal) {
|
Thread newThread = new Thread("UsbDirectMidiDevice output write thread "
|
||||||
|
+ portStartFinal) {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
while (true) {
|
final ByteArrayOutputStream midi2ByteStream =
|
||||||
if (Thread.currentThread().interrupted()) {
|
new ByteArrayOutputStream();
|
||||||
Log.w(TAG, "output thread interrupted");
|
final UsbMidiPacketConverter packetConverter =
|
||||||
|
new UsbMidiPacketConverter();
|
||||||
|
packetConverter.createEncoders(cableCountFinal);
|
||||||
|
boolean isInterrupted = false;
|
||||||
|
while (!isInterrupted) {
|
||||||
|
boolean wasSuccessful = multiSchedulerFinal.waitNextEvent();
|
||||||
|
if (!wasSuccessful) {
|
||||||
|
Log.d(TAG, "output thread closed");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
MidiEvent event;
|
long now = System.nanoTime();
|
||||||
try {
|
for (int cableNumber = 0; cableNumber < cableCountFinal;
|
||||||
event = (MidiEvent) eventSchedulerFinal.waitNextEvent();
|
cableNumber++) {
|
||||||
} catch (InterruptedException e) {
|
MidiEventScheduler eventScheduler =
|
||||||
Log.w(TAG, "event scheduler interrupted");
|
multiSchedulerFinal.getEventScheduler(cableNumber);
|
||||||
break;
|
MidiEvent event =
|
||||||
}
|
(MidiEvent) eventScheduler.getNextEvent(now);
|
||||||
if (event == null) {
|
while (event != null) {
|
||||||
Log.w(TAG, "event is null");
|
if (DEBUG) {
|
||||||
break;
|
logByteArray("Output before conversion ",
|
||||||
}
|
event.data, 0, event.count);
|
||||||
|
}
|
||||||
if (DEBUG) {
|
if (mIsUniversalMidiDevice) {
|
||||||
logByteArray("Output before conversion ", event.data, 0,
|
// For USB, each 32 bit word of a UMP is
|
||||||
event.count);
|
// sent with the least significant byte first.
|
||||||
}
|
byte[] convertedArray = swapEndiannessPerWord(
|
||||||
|
event.data, event.count);
|
||||||
byte[] convertedArray;
|
midi2ByteStream.write(convertedArray, 0,
|
||||||
if (mIsUniversalMidiDevice) {
|
convertedArray.length);
|
||||||
// For USB, each 32 bit word of a UMP is
|
} else {
|
||||||
// sent with the least significant byte first.
|
packetConverter.encodeMidiPackets(event.data,
|
||||||
convertedArray = swapEndiannessPerWord(event.data,
|
event.count, cableNumber);
|
||||||
event.count);
|
}
|
||||||
} else {
|
eventScheduler.addEventToPool(event);
|
||||||
if (mUsbMidiPacketConverter == null) {
|
event = (MidiEvent) eventScheduler.getNextEvent(now);
|
||||||
Log.w(TAG, "mUsbMidiPacketConverter is null");
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Thread.currentThread().interrupted()) {
|
||||||
|
Log.d(TAG, "output thread interrupted");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] convertedArray = new byte[0];
|
||||||
|
if (mIsUniversalMidiDevice) {
|
||||||
|
convertedArray = midi2ByteStream.toByteArray();
|
||||||
|
midi2ByteStream.reset();
|
||||||
|
} else {
|
||||||
convertedArray =
|
convertedArray =
|
||||||
mUsbMidiPacketConverter.rawMidiToUsbMidi(
|
packetConverter.pullEncodedMidiPackets();
|
||||||
event.data, event.count, portFinal);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
@@ -520,7 +610,6 @@ public final class UsbDirectMidiDevice implements Closeable {
|
|||||||
convertedArray.length);
|
convertedArray.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isInterrupted = false;
|
|
||||||
// Split the packet into multiple if they are greater than the
|
// Split the packet into multiple if they are greater than the
|
||||||
// endpoint's max packet size.
|
// endpoint's max packet size.
|
||||||
for (int curPacketStart = 0;
|
for (int curPacketStart = 0;
|
||||||
@@ -558,11 +647,9 @@ public final class UsbDirectMidiDevice implements Closeable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isInterrupted == true) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
eventSchedulerFinal.addEventToPool(event);
|
|
||||||
}
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Log.w(TAG, "output thread: ", e);
|
||||||
} catch (NullPointerException e) {
|
} catch (NullPointerException e) {
|
||||||
Log.e(TAG, "output thread: ", e);
|
Log.e(TAG, "output thread: ", e);
|
||||||
}
|
}
|
||||||
@@ -571,7 +658,7 @@ public final class UsbDirectMidiDevice implements Closeable {
|
|||||||
};
|
};
|
||||||
newThread.start();
|
newThread.start();
|
||||||
mThreads.add(newThread);
|
mThreads.add(newThread);
|
||||||
portNumber++;
|
portStartNumber += cableCountFinal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -667,11 +754,21 @@ public final class UsbDirectMidiDevice implements Closeable {
|
|||||||
}
|
}
|
||||||
mThreads = null;
|
mThreads = null;
|
||||||
|
|
||||||
for (int i = 0; i < mEventSchedulers.length; i++) {
|
for (int i = 0; i < mMidiInputPortReceivers.length; i++) {
|
||||||
mMidiInputPortReceivers[i].setReceiver(null);
|
mMidiInputPortReceivers[i].setReceiver(null);
|
||||||
mEventSchedulers[i].close();
|
|
||||||
}
|
}
|
||||||
mEventSchedulers = null;
|
|
||||||
|
for (int connectionIndex = 0; connectionIndex < mMidiEventMultiSchedulers.size();
|
||||||
|
connectionIndex++) {
|
||||||
|
for (int endpointIndex = 0;
|
||||||
|
endpointIndex < mMidiEventMultiSchedulers.get(connectionIndex).size();
|
||||||
|
endpointIndex++) {
|
||||||
|
MidiEventMultiScheduler multiScheduler =
|
||||||
|
mMidiEventMultiSchedulers.get(connectionIndex).get(endpointIndex);
|
||||||
|
multiScheduler.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mMidiEventMultiSchedulers = null;
|
||||||
|
|
||||||
for (UsbDeviceConnection connection : mUsbDeviceConnections) {
|
for (UsbDeviceConnection connection : mUsbDeviceConnections) {
|
||||||
connection.close();
|
connection.close();
|
||||||
@@ -679,8 +776,8 @@ public final class UsbDirectMidiDevice implements Closeable {
|
|||||||
mUsbDeviceConnections = null;
|
mUsbDeviceConnections = null;
|
||||||
mInputUsbEndpoints = null;
|
mInputUsbEndpoints = null;
|
||||||
mOutputUsbEndpoints = null;
|
mOutputUsbEndpoints = null;
|
||||||
|
mInputUsbEndpointCableCounts = null;
|
||||||
mUsbMidiPacketConverter = null;
|
mOutputUsbEndpointCableCounts = null;
|
||||||
|
|
||||||
mIsOpen = false;
|
mIsOpen = false;
|
||||||
}
|
}
|
||||||
@@ -788,4 +885,19 @@ public final class UsbDirectMidiDevice implements Closeable {
|
|||||||
return messageType == MESSAGE_TYPE_MIDI_1_CHANNEL_VOICE
|
return messageType == MESSAGE_TYPE_MIDI_1_CHANNEL_VOICE
|
||||||
|| messageType == MESSAGE_TYPE_MIDI_2_CHANNEL_VOICE;
|
|| messageType == MESSAGE_TYPE_MIDI_2_CHANNEL_VOICE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns the number of jacks for MIDI 1.0 endpoints.
|
||||||
|
// For MIDI 2.0 endpoints, this concept does not exist and each endpoint should be treated as
|
||||||
|
// one port.
|
||||||
|
private int getNumJacks(UsbEndpointDescriptor usbEndpointDescriptor) {
|
||||||
|
UsbDescriptor classSpecificEndpointDescriptor =
|
||||||
|
usbEndpointDescriptor.getClassSpecificEndpointDescriptor();
|
||||||
|
if (classSpecificEndpointDescriptor != null
|
||||||
|
&& (classSpecificEndpointDescriptor instanceof UsbACMidi10Endpoint)) {
|
||||||
|
UsbACMidi10Endpoint midiEndpoint =
|
||||||
|
(UsbACMidi10Endpoint) classSpecificEndpointDescriptor;
|
||||||
|
return midiEndpoint.getNumJacks();
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,12 +16,17 @@
|
|||||||
|
|
||||||
package com.android.server.usb;
|
package com.android.server.usb;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts between MIDI packets and USB MIDI 1.0 packets.
|
* Converts between raw MIDI packets and USB MIDI 1.0 packets.
|
||||||
|
* This is NOT thread-safe. Please handle locking outside this function for multiple threads.
|
||||||
|
* For data mapping to an invalid cable number, this converter will use the first cable.
|
||||||
*/
|
*/
|
||||||
public class UsbMidiPacketConverter {
|
public class UsbMidiPacketConverter {
|
||||||
|
private static final String TAG = "UsbMidiPacketConverter";
|
||||||
|
|
||||||
// Refer to Table 4-1 in USB MIDI 1.0 spec.
|
// Refer to Table 4-1 in USB MIDI 1.0 spec.
|
||||||
private static final int[] PAYLOAD_SIZE = new int[]{
|
private static final int[] PAYLOAD_SIZE = new int[]{
|
||||||
@@ -74,54 +79,133 @@ public class UsbMidiPacketConverter {
|
|||||||
private static final byte SYSEX_START_EXCLUSIVE = (byte) 0xF0;
|
private static final byte SYSEX_START_EXCLUSIVE = (byte) 0xF0;
|
||||||
private static final byte SYSEX_END_EXCLUSIVE = (byte) 0xF7;
|
private static final byte SYSEX_END_EXCLUSIVE = (byte) 0xF7;
|
||||||
|
|
||||||
private UsbMidiDecoder mUsbMidiDecoder = new UsbMidiDecoder();
|
|
||||||
private UsbMidiEncoder[] mUsbMidiEncoders;
|
private UsbMidiEncoder[] mUsbMidiEncoders;
|
||||||
|
private ByteArrayOutputStream mEncoderOutputStream = new ByteArrayOutputStream();
|
||||||
|
|
||||||
public UsbMidiPacketConverter(int numEncoders) {
|
private UsbMidiDecoder mUsbMidiDecoder;
|
||||||
mUsbMidiEncoders = new UsbMidiEncoder[numEncoders];
|
|
||||||
for (int i = 0; i < numEncoders; i++) {
|
|
||||||
mUsbMidiEncoders[i] = new UsbMidiEncoder();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts a USB MIDI array into a raw MIDI array.
|
* Creates encoders.
|
||||||
*
|
*
|
||||||
* @param usbMidiBytes the USB MIDI bytes to convert
|
* createEncoders() must be called before raw MIDI can be converted to USB MIDI.
|
||||||
* @param size the size of usbMidiBytes
|
*
|
||||||
* @return byte array of raw MIDI packets
|
* @param size the number of encoders to create
|
||||||
*/
|
*/
|
||||||
public byte[] usbMidiToRawMidi(byte[] usbMidiBytes, int size) {
|
public void createEncoders(int size) {
|
||||||
return mUsbMidiDecoder.decode(usbMidiBytes, size);
|
mUsbMidiEncoders = new UsbMidiEncoder[size];
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
mUsbMidiEncoders[i] = new UsbMidiEncoder(i);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts a raw MIDI array into a USB MIDI array.
|
* Converts a raw MIDI array into a USB MIDI array.
|
||||||
*
|
*
|
||||||
|
* Call pullEncodedMidiPackets to retrieve the byte array.
|
||||||
|
*
|
||||||
* @param midiBytes the raw MIDI bytes to convert
|
* @param midiBytes the raw MIDI bytes to convert
|
||||||
* @param size the size of usbMidiBytes
|
* @param size the size of usbMidiBytes
|
||||||
* @param encoderId which encoder to use
|
* @param encoderId which encoder to use
|
||||||
|
*/
|
||||||
|
public void encodeMidiPackets(byte[] midiBytes, int size, int encoderId) {
|
||||||
|
// Use the first encoder if the encoderId is invalid.
|
||||||
|
if (encoderId >= mUsbMidiEncoders.length) {
|
||||||
|
Log.w(TAG, "encoderId " + encoderId + " invalid");
|
||||||
|
encoderId = 0;
|
||||||
|
}
|
||||||
|
byte[] encodedPacket = mUsbMidiEncoders[encoderId].encode(midiBytes, size);
|
||||||
|
mEncoderOutputStream.write(encodedPacket, 0, encodedPacket.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the encoded MIDI packets from encodeMidiPackets
|
||||||
|
*
|
||||||
* @return byte array of USB MIDI packets
|
* @return byte array of USB MIDI packets
|
||||||
*/
|
*/
|
||||||
public byte[] rawMidiToUsbMidi(byte[] midiBytes, int size, int encoderId) {
|
public byte[] pullEncodedMidiPackets() {
|
||||||
return mUsbMidiEncoders[encoderId].encode(midiBytes, size);
|
byte[] output = mEncoderOutputStream.toByteArray();
|
||||||
|
mEncoderOutputStream.reset();
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates decoders.
|
||||||
|
*
|
||||||
|
* createDecoders() must be called before USB MIDI can be converted to raw MIDI.
|
||||||
|
*
|
||||||
|
* @param size the number of decoders to create
|
||||||
|
*/
|
||||||
|
public void createDecoders(int size) {
|
||||||
|
mUsbMidiDecoder = new UsbMidiDecoder(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a USB MIDI array into a multiple MIDI arrays, one per cable.
|
||||||
|
*
|
||||||
|
* Call pullDecodedMidiPackets to retrieve the byte array.
|
||||||
|
*
|
||||||
|
* @param usbMidiBytes the USB MIDI bytes to convert
|
||||||
|
* @param size the size of usbMidiBytes
|
||||||
|
*/
|
||||||
|
public void decodeMidiPackets(byte[] usbMidiBytes, int size) {
|
||||||
|
mUsbMidiDecoder.decode(usbMidiBytes, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the decoded MIDI packets from decodeMidiPackets
|
||||||
|
*
|
||||||
|
* @param cableNumber the cable to pull data from
|
||||||
|
* @return byte array of raw MIDI packets
|
||||||
|
*/
|
||||||
|
public byte[] pullDecodedMidiPackets(int cableNumber) {
|
||||||
|
return mUsbMidiDecoder.pullBytes(cableNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class UsbMidiDecoder {
|
private class UsbMidiDecoder {
|
||||||
|
int mNumJacks;
|
||||||
|
ByteArrayOutputStream[] mDecodedByteArrays;
|
||||||
|
|
||||||
|
UsbMidiDecoder(int numJacks) {
|
||||||
|
mNumJacks = numJacks;
|
||||||
|
mDecodedByteArrays = new ByteArrayOutputStream[numJacks];
|
||||||
|
for (int i = 0; i < numJacks; i++) {
|
||||||
|
mDecodedByteArrays[i] = new ByteArrayOutputStream();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Decodes the data from USB MIDI to raw MIDI.
|
// Decodes the data from USB MIDI to raw MIDI.
|
||||||
// Each valid 4 byte input maps to a 1-3 byte output.
|
// Each valid 4 byte input maps to a 1-3 byte output.
|
||||||
// Reference the USB MIDI 1.0 spec for more info.
|
// Reference the USB MIDI 1.0 spec for more info.
|
||||||
public byte[] decode(byte[] usbMidiBytes, int size) {
|
public void decode(byte[] usbMidiBytes, int size) {
|
||||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
|
if (size % 4 != 0) {
|
||||||
|
Log.w(TAG, "size " + size + " not multiple of 4");
|
||||||
|
}
|
||||||
for (int i = 0; i + 3 < size; i += 4) {
|
for (int i = 0; i + 3 < size; i += 4) {
|
||||||
|
int cableNumber = (usbMidiBytes[i] >> 4) & 0x0f;
|
||||||
int codeIndex = usbMidiBytes[i] & 0x0f;
|
int codeIndex = usbMidiBytes[i] & 0x0f;
|
||||||
int numPayloadBytes = PAYLOAD_SIZE[codeIndex];
|
int numPayloadBytes = PAYLOAD_SIZE[codeIndex];
|
||||||
if (numPayloadBytes < 0) {
|
if (numPayloadBytes < 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
outputStream.write(usbMidiBytes, i + 1, numPayloadBytes);
|
// Use the first cable if the cable number is invalid.
|
||||||
|
if (cableNumber >= mNumJacks) {
|
||||||
|
Log.w(TAG, "cableNumber " + cableNumber + " invalid");
|
||||||
|
cableNumber = 0;
|
||||||
|
}
|
||||||
|
mDecodedByteArrays[cableNumber].write(usbMidiBytes, i + 1, numPayloadBytes);
|
||||||
}
|
}
|
||||||
return outputStream.toByteArray();
|
}
|
||||||
|
|
||||||
|
public byte[] pullBytes(int cableNumber) {
|
||||||
|
// Use the first cable if the cable number is invalid.
|
||||||
|
if (cableNumber >= mNumJacks) {
|
||||||
|
Log.w(TAG, "cableNumber " + cableNumber + " invalid");
|
||||||
|
cableNumber = 0;
|
||||||
|
}
|
||||||
|
byte[] output = mDecodedByteArrays[cableNumber].toByteArray();
|
||||||
|
mDecodedByteArrays[cableNumber].reset();
|
||||||
|
return output;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,6 +219,13 @@ public class UsbMidiPacketConverter {
|
|||||||
|
|
||||||
private byte[] mEmptyBytes = new byte[3]; // Used to fill out extra data
|
private byte[] mEmptyBytes = new byte[3]; // Used to fill out extra data
|
||||||
|
|
||||||
|
private byte mShiftedCableNumber;
|
||||||
|
|
||||||
|
UsbMidiEncoder(int cableNumber) {
|
||||||
|
// Jack Id is always the left nibble of every byte so shift this now.
|
||||||
|
mShiftedCableNumber = (byte) (cableNumber << 4);
|
||||||
|
}
|
||||||
|
|
||||||
// Encodes the data from raw MIDI to USB MIDI.
|
// Encodes the data from raw MIDI to USB MIDI.
|
||||||
// Each valid 1-3 byte input maps to a 4 byte output.
|
// Each valid 1-3 byte input maps to a 4 byte output.
|
||||||
// Reference the USB MIDI 1.0 spec for more info.
|
// Reference the USB MIDI 1.0 spec for more info.
|
||||||
@@ -153,7 +244,8 @@ public class UsbMidiPacketConverter {
|
|||||||
midiBytes[curLocation];
|
midiBytes[curLocation];
|
||||||
mNumStoredSystemExclusiveBytes++;
|
mNumStoredSystemExclusiveBytes++;
|
||||||
if (mNumStoredSystemExclusiveBytes == 3) {
|
if (mNumStoredSystemExclusiveBytes == 3) {
|
||||||
outputStream.write(CODE_INDEX_NUMBER_SYSEX_STARTS_OR_CONTINUES);
|
outputStream.write(CODE_INDEX_NUMBER_SYSEX_STARTS_OR_CONTINUES
|
||||||
|
| mShiftedCableNumber);
|
||||||
outputStream.write(mStoredSystemExclusiveBytes, 0, 3);
|
outputStream.write(mStoredSystemExclusiveBytes, 0, 3);
|
||||||
mNumStoredSystemExclusiveBytes = 0;
|
mNumStoredSystemExclusiveBytes = 0;
|
||||||
}
|
}
|
||||||
@@ -179,7 +271,7 @@ public class UsbMidiPacketConverter {
|
|||||||
byte codeIndexNumber = (byte) ((midiBytes[curLocation] >> 4) & 0x0f);
|
byte codeIndexNumber = (byte) ((midiBytes[curLocation] >> 4) & 0x0f);
|
||||||
int channelMessageSize = PAYLOAD_SIZE[codeIndexNumber];
|
int channelMessageSize = PAYLOAD_SIZE[codeIndexNumber];
|
||||||
if (curLocation + channelMessageSize <= size) {
|
if (curLocation + channelMessageSize <= size) {
|
||||||
outputStream.write(codeIndexNumber);
|
outputStream.write(codeIndexNumber | mShiftedCableNumber);
|
||||||
outputStream.write(midiBytes, curLocation, channelMessageSize);
|
outputStream.write(midiBytes, curLocation, channelMessageSize);
|
||||||
// Fill in the rest of the bytes with 0.
|
// Fill in the rest of the bytes with 0.
|
||||||
outputStream.write(mEmptyBytes, 0, 3 - channelMessageSize);
|
outputStream.write(mEmptyBytes, 0, 3 - channelMessageSize);
|
||||||
@@ -197,8 +289,8 @@ public class UsbMidiPacketConverter {
|
|||||||
curLocation++;
|
curLocation++;
|
||||||
} else if (midiBytes[curLocation] == SYSEX_END_EXCLUSIVE) {
|
} else if (midiBytes[curLocation] == SYSEX_END_EXCLUSIVE) {
|
||||||
// 1 byte is 0x05, 2 bytes is 0x06, and 3 bytes is 0x07
|
// 1 byte is 0x05, 2 bytes is 0x06, and 3 bytes is 0x07
|
||||||
outputStream.write(CODE_INDEX_NUMBER_SYSEX_END_SINGLE_BYTE
|
outputStream.write((CODE_INDEX_NUMBER_SYSEX_END_SINGLE_BYTE
|
||||||
+ mNumStoredSystemExclusiveBytes);
|
+ mNumStoredSystemExclusiveBytes) | mShiftedCableNumber);
|
||||||
mStoredSystemExclusiveBytes[mNumStoredSystemExclusiveBytes] =
|
mStoredSystemExclusiveBytes[mNumStoredSystemExclusiveBytes] =
|
||||||
midiBytes[curLocation];
|
midiBytes[curLocation];
|
||||||
mNumStoredSystemExclusiveBytes++;
|
mNumStoredSystemExclusiveBytes++;
|
||||||
@@ -218,7 +310,7 @@ public class UsbMidiPacketConverter {
|
|||||||
} else {
|
} else {
|
||||||
int systemMessageSize = PAYLOAD_SIZE[codeIndexNumber];
|
int systemMessageSize = PAYLOAD_SIZE[codeIndexNumber];
|
||||||
if (curLocation + systemMessageSize <= size) {
|
if (curLocation + systemMessageSize <= size) {
|
||||||
outputStream.write(codeIndexNumber);
|
outputStream.write(codeIndexNumber | mShiftedCableNumber);
|
||||||
outputStream.write(midiBytes, curLocation, systemMessageSize);
|
outputStream.write(midiBytes, curLocation, systemMessageSize);
|
||||||
// Fill in the rest of the bytes with 0.
|
// Fill in the rest of the bytes with 0.
|
||||||
outputStream.write(mEmptyBytes, 0, 3 - systemMessageSize);
|
outputStream.write(mEmptyBytes, 0, 3 - systemMessageSize);
|
||||||
@@ -236,7 +328,7 @@ public class UsbMidiPacketConverter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void writeSingleByte(ByteArrayOutputStream outputStream, byte byteToWrite) {
|
private void writeSingleByte(ByteArrayOutputStream outputStream, byte byteToWrite) {
|
||||||
outputStream.write(CODE_INDEX_NUMBER_SINGLE_BYTE);
|
outputStream.write(CODE_INDEX_NUMBER_SINGLE_BYTE | mShiftedCableNumber);
|
||||||
outputStream.write(byteToWrite);
|
outputStream.write(byteToWrite);
|
||||||
outputStream.write(0);
|
outputStream.write(0);
|
||||||
outputStream.write(0);
|
outputStream.write(0);
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ public class UsbEndpointDescriptor extends UsbDescriptor {
|
|||||||
mClassSpecificEndpointDescriptor = descriptor;
|
mClassSpecificEndpointDescriptor = descriptor;
|
||||||
}
|
}
|
||||||
|
|
||||||
UsbDescriptor getClassSpecificEndpointDescriptor() {
|
public UsbDescriptor getClassSpecificEndpointDescriptor() {
|
||||||
return mClassSpecificEndpointDescriptor;
|
return mClassSpecificEndpointDescriptor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user