Merge "Rewrite MIDI 1.0 USB Host mode in Java"
This commit is contained in:
@@ -63,6 +63,7 @@ import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
@@ -128,6 +129,19 @@ public class MidiService extends IMidiManager.Stub {
|
||||
|
||||
private final PackageManager mPackageManager;
|
||||
|
||||
private static final String MIDI_LEGACY_STRING = "MIDI 1.0";
|
||||
private static final String MIDI_UNIVERSAL_STRING = "MIDI 2.0";
|
||||
|
||||
// Used to lock mUsbMidiLegacyDeviceOpenCount and mUsbMidiUniversalDeviceInUse.
|
||||
private final Object mUsbMidiLock = new Object();
|
||||
|
||||
// Number of times a USB MIDI 1.0 device has opened, based on the device name.
|
||||
private final HashMap<String, Integer> mUsbMidiLegacyDeviceOpenCount =
|
||||
new HashMap<String, Integer>();
|
||||
|
||||
// Whether a USB MIDI device has opened, based on the device name.
|
||||
private final HashSet<String> mUsbMidiUniversalDeviceInUse = new HashSet<String>();
|
||||
|
||||
// UID of BluetoothMidiService
|
||||
private int mBluetoothServiceUid;
|
||||
|
||||
@@ -271,6 +285,12 @@ public class MidiService extends IMidiManager.Stub {
|
||||
}
|
||||
|
||||
for (DeviceConnection connection : mDeviceConnections.values()) {
|
||||
if (connection.getDevice().getDeviceInfo().getType()
|
||||
== MidiDeviceInfo.TYPE_USB) {
|
||||
synchronized (mUsbMidiLock) {
|
||||
removeUsbMidiDeviceLocked(connection.getDevice().getDeviceInfo());
|
||||
}
|
||||
}
|
||||
connection.getDevice().removeDeviceConnection(connection);
|
||||
}
|
||||
}
|
||||
@@ -748,6 +768,7 @@ public class MidiService extends IMidiManager.Stub {
|
||||
synchronized (mDevicesByInfo) {
|
||||
for (Device device : mDevicesByInfo.values()) {
|
||||
if (device.isUidAllowed(uid)) {
|
||||
deviceInfos.add(device.getDeviceInfo());
|
||||
// UMP devices have protocols that are not PROTOCOL_UNKNOWN
|
||||
if (transport == MidiManager.TRANSPORT_UNIVERSAL_MIDI_PACKETS) {
|
||||
if (device.getDeviceInfo().getDefaultProtocol()
|
||||
@@ -784,6 +805,15 @@ public class MidiService extends IMidiManager.Stub {
|
||||
}
|
||||
}
|
||||
|
||||
if (deviceInfo.getType() == MidiDeviceInfo.TYPE_USB) {
|
||||
synchronized (mUsbMidiLock) {
|
||||
if (isUsbMidiDeviceInUseLocked(deviceInfo)) {
|
||||
throw new IllegalArgumentException("device already in use: " + deviceInfo);
|
||||
}
|
||||
addUsbMidiDeviceLocked(deviceInfo);
|
||||
}
|
||||
}
|
||||
|
||||
// clear calling identity so bindService does not fail
|
||||
final long identity = Binder.clearCallingIdentity();
|
||||
try {
|
||||
@@ -1216,4 +1246,82 @@ public class MidiService extends IMidiManager.Stub {
|
||||
}
|
||||
pw.decreaseIndent();
|
||||
}
|
||||
|
||||
// hold mUsbMidiLock before calling this
|
||||
private boolean isUsbMidiDeviceInUseLocked(MidiDeviceInfo info) {
|
||||
String name = info.getProperties().getString(MidiDeviceInfo.PROPERTY_NAME);
|
||||
if (name.length() < MIDI_LEGACY_STRING.length()) {
|
||||
return false;
|
||||
}
|
||||
String deviceName = extractUsbDeviceName(name);
|
||||
String tagName = extractUsbDeviceTag(name);
|
||||
|
||||
// Only one MIDI 2.0 device can be used at once.
|
||||
// Multiple MIDI 1.0 devices can be used at once.
|
||||
if (mUsbMidiUniversalDeviceInUse.contains(deviceName)
|
||||
|| ((tagName).equals(MIDI_UNIVERSAL_STRING)
|
||||
&& (mUsbMidiLegacyDeviceOpenCount.containsKey(deviceName)))) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// hold mUsbMidiLock before calling this
|
||||
void addUsbMidiDeviceLocked(MidiDeviceInfo info) {
|
||||
String name = info.getProperties().getString(MidiDeviceInfo.PROPERTY_NAME);
|
||||
if (name.length() < MIDI_LEGACY_STRING.length()) {
|
||||
return;
|
||||
}
|
||||
String deviceName = extractUsbDeviceName(name);
|
||||
String tagName = extractUsbDeviceTag(name);
|
||||
|
||||
if ((tagName).equals(MIDI_UNIVERSAL_STRING)) {
|
||||
mUsbMidiUniversalDeviceInUse.add(deviceName);
|
||||
} else if ((tagName).equals(MIDI_LEGACY_STRING)) {
|
||||
int count = mUsbMidiLegacyDeviceOpenCount.getOrDefault(deviceName, 0) + 1;
|
||||
mUsbMidiLegacyDeviceOpenCount.put(deviceName, count);
|
||||
}
|
||||
}
|
||||
|
||||
// hold mUsbMidiLock before calling this
|
||||
void removeUsbMidiDeviceLocked(MidiDeviceInfo info) {
|
||||
String name = info.getProperties().getString(MidiDeviceInfo.PROPERTY_NAME);
|
||||
if (name.length() < MIDI_LEGACY_STRING.length()) {
|
||||
return;
|
||||
}
|
||||
String deviceName = extractUsbDeviceName(name);
|
||||
String tagName = extractUsbDeviceTag(name);
|
||||
|
||||
if ((tagName).equals(MIDI_UNIVERSAL_STRING)) {
|
||||
mUsbMidiUniversalDeviceInUse.remove(deviceName);
|
||||
} else if ((tagName).equals(MIDI_LEGACY_STRING)) {
|
||||
if (mUsbMidiLegacyDeviceOpenCount.containsKey(deviceName)) {
|
||||
int count = mUsbMidiLegacyDeviceOpenCount.get(deviceName);
|
||||
if (count > 1) {
|
||||
mUsbMidiLegacyDeviceOpenCount.put(deviceName, count - 1);
|
||||
} else {
|
||||
mUsbMidiLegacyDeviceOpenCount.remove(deviceName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The USB property name is in the form "manufacturer product#Id MIDI 1.0".
|
||||
// This is defined in UsbDirectMidiDevice.java.
|
||||
// This function extracts out the "manufacturer product#Id " part.
|
||||
// Two devices would have the same device name if they had the following property name:
|
||||
// "manufacturer product#Id MIDI 1.0"
|
||||
// "manufacturer product#Id MIDI 2.0"
|
||||
// Note that MIDI_LEGACY_STRING and MIDI_UNIVERSAL_STRING are the same length.
|
||||
String extractUsbDeviceName(String propertyName) {
|
||||
return propertyName.substring(0, propertyName.length() - MIDI_LEGACY_STRING.length());
|
||||
}
|
||||
|
||||
// The USB property name is in the form "manufacturer product#Id MIDI 1.0".
|
||||
// This is defined in UsbDirectMidiDevice.java.
|
||||
// This function extracts the "MIDI 1.0" part.
|
||||
// Note that MIDI_LEGACY_STRING and MIDI_UNIVERSAL_STRING are the same length.
|
||||
String extractUsbDeviceTag(String propertyName) {
|
||||
return propertyName.substring(propertyName.length() - MIDI_LEGACY_STRING.length());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,6 @@ import libcore.io.IoUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -107,12 +106,6 @@ public final class UsbAlsaManager {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* List of connected MIDI devices
|
||||
*/
|
||||
private final HashMap<String, UsbMidiDevice>
|
||||
mMidiDevices = new HashMap<String, UsbMidiDevice>();
|
||||
|
||||
// UsbMidiDevice for USB peripheral mode (gadget) device
|
||||
private UsbMidiDevice mPeripheralMidiDevice = null;
|
||||
|
||||
@@ -256,45 +249,6 @@ public final class UsbAlsaManager {
|
||||
}
|
||||
}
|
||||
|
||||
// look for MIDI devices
|
||||
boolean hasMidi = parser.hasMIDIInterface();
|
||||
int midiNumInputs = parser.calculateNumLegacyMidiInputs();
|
||||
int midiNumOutputs = parser.calculateNumLegacyMidiOutputs();
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "hasMidi: " + hasMidi + " mHasMidiFeature:" + mHasMidiFeature);
|
||||
Slog.d(TAG, "midiNumInputs: " + midiNumInputs + " midiNumOutputs:" + midiNumOutputs);
|
||||
}
|
||||
if (hasMidi && mHasMidiFeature) {
|
||||
int device = 0;
|
||||
Bundle properties = new Bundle();
|
||||
String manufacturer = usbDevice.getManufacturerName();
|
||||
String product = usbDevice.getProductName();
|
||||
String version = usbDevice.getVersion();
|
||||
String name;
|
||||
if (manufacturer == null || manufacturer.isEmpty()) {
|
||||
name = product;
|
||||
} else if (product == null || product.isEmpty()) {
|
||||
name = manufacturer;
|
||||
} else {
|
||||
name = manufacturer + " " + product;
|
||||
}
|
||||
properties.putString(MidiDeviceInfo.PROPERTY_NAME, name);
|
||||
properties.putString(MidiDeviceInfo.PROPERTY_MANUFACTURER, manufacturer);
|
||||
properties.putString(MidiDeviceInfo.PROPERTY_PRODUCT, product);
|
||||
properties.putString(MidiDeviceInfo.PROPERTY_VERSION, version);
|
||||
properties.putString(MidiDeviceInfo.PROPERTY_SERIAL_NUMBER,
|
||||
usbDevice.getSerialNumber());
|
||||
properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_CARD, cardRec.getCardNum());
|
||||
properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_DEVICE, 0 /*deviceNum*/);
|
||||
properties.putParcelable(MidiDeviceInfo.PROPERTY_USB_DEVICE, usbDevice);
|
||||
|
||||
UsbMidiDevice usbMidiDevice = UsbMidiDevice.create(mContext, properties,
|
||||
cardRec.getCardNum(), 0 /*device*/, midiNumInputs, midiNumOutputs);
|
||||
if (usbMidiDevice != null) {
|
||||
mMidiDevices.put(deviceAddress, usbMidiDevice);
|
||||
}
|
||||
}
|
||||
|
||||
logDevices("deviceAdded()");
|
||||
|
||||
if (DEBUG) {
|
||||
@@ -315,13 +269,6 @@ public final class UsbAlsaManager {
|
||||
selectDefaultDevice(); // if there any external devices left, select one of them
|
||||
}
|
||||
|
||||
// MIDI
|
||||
UsbMidiDevice usbMidiDevice = mMidiDevices.remove(deviceAddress);
|
||||
if (usbMidiDevice != null) {
|
||||
Slog.i(TAG, "USB MIDI Device Removed: " + usbMidiDevice);
|
||||
IoUtils.closeQuietly(usbMidiDevice);
|
||||
}
|
||||
|
||||
logDevices("usbDeviceRemoved()");
|
||||
|
||||
}
|
||||
@@ -377,12 +324,6 @@ public final class UsbAlsaManager {
|
||||
usbAlsaDevice.dump(dump, "alsa_devices", UsbAlsaManagerProto.ALSA_DEVICES);
|
||||
}
|
||||
|
||||
for (String deviceAddr : mMidiDevices.keySet()) {
|
||||
// A UsbMidiDevice does not have a handle to the UsbDevice anymore
|
||||
mMidiDevices.get(deviceAddr).dump(deviceAddr, dump, "midi_devices",
|
||||
UsbAlsaManagerProto.MIDI_DEVICES);
|
||||
}
|
||||
|
||||
dump.end(token);
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import android.content.Context;
|
||||
import android.hardware.usb.UsbDevice;
|
||||
import android.hardware.usb.UsbDeviceConnection;
|
||||
import android.hardware.usb.UsbEndpoint;
|
||||
import android.hardware.usb.UsbInterface;
|
||||
import android.hardware.usb.UsbManager;
|
||||
import android.media.midi.MidiDeviceInfo;
|
||||
import android.media.midi.MidiDeviceServer;
|
||||
@@ -43,16 +44,20 @@ import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* A MIDI device that opens device connections to MIDI 2.0 endpoints.
|
||||
* Opens device connections to MIDI 1.0 or MIDI 2.0 endpoints.
|
||||
* This endpoint will not use ALSA and opens a UsbDeviceConnection directly.
|
||||
*/
|
||||
public final class UsbUniversalMidiDevice implements Closeable {
|
||||
private static final String TAG = "UsbUniversalMidiDevice";
|
||||
public final class UsbDirectMidiDevice implements Closeable {
|
||||
private static final String TAG = "UsbDirectMidiDevice";
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private Context mContext;
|
||||
private UsbDevice mUsbDevice;
|
||||
private UsbDescriptorParser mParser;
|
||||
private ArrayList<UsbInterfaceDescriptor> mUsbInterfaces;
|
||||
private final boolean mIsUniversalMidiDevice;
|
||||
private final String mUniqueUsbDeviceIdentifier;
|
||||
private final boolean mShouldCallSetInterface;
|
||||
|
||||
// USB outputs are MIDI inputs
|
||||
private final InputReceiverProxy[] mMidiInputPortReceivers;
|
||||
@@ -64,6 +69,11 @@ public final class UsbUniversalMidiDevice implements Closeable {
|
||||
// event schedulers for each input port of the physical device
|
||||
private MidiEventScheduler[] mEventSchedulers;
|
||||
|
||||
// Arbitrary number for timeout to not continue sending/receiving number from
|
||||
// an inactive device. This number tries to balances the number of cycles and
|
||||
// not being permanently stuck.
|
||||
private static final int BULK_TRANSFER_TIMEOUT_MILLISECONDS = 100;
|
||||
|
||||
private ArrayList<UsbDeviceConnection> mUsbDeviceConnections;
|
||||
private ArrayList<ArrayList<UsbEndpoint>> mInputUsbEndpoints;
|
||||
private ArrayList<ArrayList<UsbEndpoint>> mOutputUsbEndpoints;
|
||||
@@ -74,6 +84,8 @@ public final class UsbUniversalMidiDevice implements Closeable {
|
||||
private final Object mLock = new Object();
|
||||
private boolean mIsOpen;
|
||||
|
||||
private final UsbMidiPacketConverter mUsbMidiPacketConverter = new UsbMidiPacketConverter();
|
||||
|
||||
private final MidiDeviceServer.Callback mCallback = new MidiDeviceServer.Callback() {
|
||||
|
||||
@Override
|
||||
@@ -140,13 +152,15 @@ public final class UsbUniversalMidiDevice implements Closeable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an UsbUniversalMidiDevice based on the input parameters. Read/Write streams
|
||||
* Creates an UsbDirectMidiDevice based on the input parameters. Read/Write streams
|
||||
* will be created individually as some devices don't have the same number of
|
||||
* inputs and outputs.
|
||||
*/
|
||||
public static UsbUniversalMidiDevice create(Context context, UsbDevice usbDevice,
|
||||
UsbDescriptorParser parser) {
|
||||
UsbUniversalMidiDevice midiDevice = new UsbUniversalMidiDevice(usbDevice, parser);
|
||||
public static UsbDirectMidiDevice create(Context context, UsbDevice usbDevice,
|
||||
UsbDescriptorParser parser, boolean isUniversalMidiDevice,
|
||||
String uniqueUsbDeviceIdentifier) {
|
||||
UsbDirectMidiDevice midiDevice = new UsbDirectMidiDevice(usbDevice, parser,
|
||||
isUniversalMidiDevice, uniqueUsbDeviceIdentifier);
|
||||
if (!midiDevice.register(context)) {
|
||||
IoUtils.closeQuietly(midiDevice);
|
||||
Log.e(TAG, "createDeviceServer failed");
|
||||
@@ -155,11 +169,22 @@ public final class UsbUniversalMidiDevice implements Closeable {
|
||||
return midiDevice;
|
||||
}
|
||||
|
||||
private UsbUniversalMidiDevice(UsbDevice usbDevice, UsbDescriptorParser parser) {
|
||||
private UsbDirectMidiDevice(UsbDevice usbDevice, UsbDescriptorParser parser,
|
||||
boolean isUniversalMidiDevice, String uniqueUsbDeviceIdentifier) {
|
||||
mUsbDevice = usbDevice;
|
||||
mParser = parser;
|
||||
mUniqueUsbDeviceIdentifier = uniqueUsbDeviceIdentifier;
|
||||
mIsUniversalMidiDevice = isUniversalMidiDevice;
|
||||
|
||||
mUsbInterfaces = parser.findUniversalMidiInterfaceDescriptors();
|
||||
// Set interface should only be called when alternate interfaces exist.
|
||||
// Otherwise, USB devices may not handle this gracefully.
|
||||
mShouldCallSetInterface = (parser.calculateMidiInterfaceDescriptorsCount() > 1);
|
||||
|
||||
if (isUniversalMidiDevice) {
|
||||
mUsbInterfaces = parser.findUniversalMidiInterfaceDescriptors();
|
||||
} else {
|
||||
mUsbInterfaces = parser.findLegacyMidiInterfaceDescriptors();
|
||||
}
|
||||
|
||||
int numInputs = 0;
|
||||
int numOutputs = 0;
|
||||
@@ -182,8 +207,8 @@ public final class UsbUniversalMidiDevice implements Closeable {
|
||||
mNumInputs = numInputs;
|
||||
mNumOutputs = numOutputs;
|
||||
|
||||
Log.d(TAG, "Created UsbUniversalMidiDevice with " + numInputs + " inputs and "
|
||||
+ numOutputs + " outputs");
|
||||
Log.d(TAG, "Created UsbDirectMidiDevice with " + numInputs + " inputs and "
|
||||
+ numOutputs + " outputs. isUniversalMidiDevice: " + isUniversalMidiDevice);
|
||||
|
||||
// Create MIDI port receivers based on the number of output ports. The
|
||||
// output of USB is the input of MIDI.
|
||||
@@ -218,23 +243,25 @@ public final class UsbUniversalMidiDevice implements Closeable {
|
||||
if (doesInterfaceContainInput
|
||||
&& doesInterfaceContainOutput) {
|
||||
UsbDeviceConnection connection = manager.openDevice(mUsbDevice);
|
||||
|
||||
// The ALSA does not handle switching to the MIDI 2.0 interface correctly
|
||||
// and stops exposing /dev/snd/midiC1D0 after calling connection.setInterface().
|
||||
// Thus, simply use the control interface (interface zero).
|
||||
UsbInterface usbInterface = interfaceDescriptor.toAndroid(mParser);
|
||||
if (!updateUsbInterface(usbInterface, connection)) {
|
||||
continue;
|
||||
}
|
||||
int defaultMidiProtocol = mMidiBlockParser.calculateMidiType(connection,
|
||||
0,
|
||||
interfaceDescriptor.getInterfaceNumber(),
|
||||
interfaceDescriptor.getAlternateSetting());
|
||||
|
||||
connection.close();
|
||||
return defaultMidiProtocol;
|
||||
}
|
||||
}
|
||||
|
||||
Log.d(TAG, "Cannot find interface with both input and output endpoints");
|
||||
Log.w(TAG, "Cannot find interface with both input and output endpoints");
|
||||
return MidiDeviceInfo.PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS;
|
||||
}
|
||||
|
||||
private boolean openLocked() {
|
||||
Log.d(TAG, "openLocked()");
|
||||
UsbManager manager = mContext.getSystemService(UsbManager.class);
|
||||
|
||||
mUsbDeviceConnections = new ArrayList<UsbDeviceConnection>(mUsbInterfaces.size());
|
||||
@@ -258,8 +285,10 @@ public final class UsbUniversalMidiDevice implements Closeable {
|
||||
}
|
||||
if (!outputEndpoints.isEmpty() || !inputEndpoints.isEmpty()) {
|
||||
UsbDeviceConnection connection = manager.openDevice(mUsbDevice);
|
||||
connection.setInterface(interfaceDescriptor.toAndroid(mParser));
|
||||
connection.claimInterface(interfaceDescriptor.toAndroid(mParser), true);
|
||||
UsbInterface usbInterface = interfaceDescriptor.toAndroid(mParser);
|
||||
if (!updateUsbInterface(usbInterface, connection)) {
|
||||
continue;
|
||||
}
|
||||
mUsbDeviceConnections.add(connection);
|
||||
mInputUsbEndpoints.add(inputEndpoints);
|
||||
mOutputUsbEndpoints.add(outputEndpoints);
|
||||
@@ -283,14 +312,17 @@ public final class UsbUniversalMidiDevice implements Closeable {
|
||||
for (int endpointIndex = 0;
|
||||
endpointIndex < mInputUsbEndpoints.get(connectionIndex).size();
|
||||
endpointIndex++) {
|
||||
final UsbDeviceConnection connectionF = mUsbDeviceConnections.get(connectionIndex);
|
||||
final UsbEndpoint epF = mInputUsbEndpoints.get(connectionIndex).get(endpointIndex);
|
||||
final int portF = portNumber;
|
||||
final UsbDeviceConnection connectionFinal =
|
||||
mUsbDeviceConnections.get(connectionIndex);
|
||||
final UsbEndpoint endpointFinal =
|
||||
mInputUsbEndpoints.get(connectionIndex).get(endpointIndex);
|
||||
final int portFinal = portNumber;
|
||||
|
||||
new Thread("UsbUniversalMidiDevice input thread " + portF) {
|
||||
new Thread("UsbDirectMidiDevice input thread " + portFinal) {
|
||||
@Override
|
||||
public void run() {
|
||||
byte[] inputBuffer = new byte[epF.getMaxPacketSize()];
|
||||
byte[] inputBuffer = new byte[endpointFinal.getMaxPacketSize()];
|
||||
Log.d(TAG, "input buffer size: " + inputBuffer.length);
|
||||
try {
|
||||
while (true) {
|
||||
// Record time of event immediately after waking.
|
||||
@@ -298,20 +330,34 @@ public final class UsbUniversalMidiDevice implements Closeable {
|
||||
synchronized (mLock) {
|
||||
if (!mIsOpen) break;
|
||||
|
||||
int nRead = connectionF.bulkTransfer(epF, inputBuffer,
|
||||
inputBuffer.length, 0);
|
||||
|
||||
// For USB, each 32 bit word of a UMP is
|
||||
// sent with the least significant byte first.
|
||||
swapEndiannessPerWord(inputBuffer, inputBuffer.length);
|
||||
int nRead = connectionFinal.bulkTransfer(endpointFinal,
|
||||
inputBuffer, inputBuffer.length,
|
||||
BULK_TRANSFER_TIMEOUT_MILLISECONDS);
|
||||
|
||||
if (nRead > 0) {
|
||||
if (DEBUG) {
|
||||
logByteArray("Input ", inputBuffer, 0,
|
||||
nRead);
|
||||
logByteArray("Input before conversion ", inputBuffer,
|
||||
0, nRead);
|
||||
}
|
||||
outputReceivers[portF].send(inputBuffer, 0, nRead,
|
||||
timestamp);
|
||||
byte[] convertedArray;
|
||||
if (mIsUniversalMidiDevice) {
|
||||
// For USB, each 32 bit word of a UMP is
|
||||
// sent with the least significant byte first.
|
||||
convertedArray = swapEndiannessPerWord(inputBuffer,
|
||||
nRead);
|
||||
} else {
|
||||
convertedArray =
|
||||
mUsbMidiPacketConverter.usbMidiToRawMidi(
|
||||
inputBuffer, nRead);
|
||||
}
|
||||
|
||||
if (DEBUG) {
|
||||
logByteArray("Input after conversion ", convertedArray,
|
||||
0, convertedArray.length);
|
||||
}
|
||||
|
||||
outputReceivers[portFinal].send(convertedArray, 0,
|
||||
convertedArray.length, timestamp);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -333,19 +379,20 @@ public final class UsbUniversalMidiDevice implements Closeable {
|
||||
for (int endpointIndex = 0;
|
||||
endpointIndex < mOutputUsbEndpoints.get(connectionIndex).size();
|
||||
endpointIndex++) {
|
||||
final UsbDeviceConnection connectionF = mUsbDeviceConnections.get(connectionIndex);
|
||||
final UsbEndpoint epF =
|
||||
final UsbDeviceConnection connectionFinal =
|
||||
mUsbDeviceConnections.get(connectionIndex);
|
||||
final UsbEndpoint endpointFinal =
|
||||
mOutputUsbEndpoints.get(connectionIndex).get(endpointIndex);
|
||||
final int portF = portNumber;
|
||||
final MidiEventScheduler eventSchedulerF = mEventSchedulers[portF];
|
||||
final int portFinal = portNumber;
|
||||
final MidiEventScheduler eventSchedulerFinal = mEventSchedulers[portFinal];
|
||||
|
||||
new Thread("UsbUniversalMidiDevice output thread " + portF) {
|
||||
new Thread("UsbDirectMidiDevice output thread " + portFinal) {
|
||||
@Override
|
||||
public void run() {
|
||||
while (true) {
|
||||
MidiEvent event;
|
||||
try {
|
||||
event = (MidiEvent) eventSchedulerF.waitNextEvent();
|
||||
event = (MidiEvent) eventSchedulerFinal.waitNextEvent();
|
||||
} catch (InterruptedException e) {
|
||||
// try again
|
||||
continue;
|
||||
@@ -354,16 +401,32 @@ public final class UsbUniversalMidiDevice implements Closeable {
|
||||
break;
|
||||
}
|
||||
|
||||
// For USB, each 32 bit word of a UMP is
|
||||
// sent with the least significant byte first.
|
||||
swapEndiannessPerWord(event.data, event.count);
|
||||
|
||||
if (DEBUG) {
|
||||
logByteArray("Output ", event.data, 0,
|
||||
logByteArray("Output before conversion ", event.data, 0,
|
||||
event.count);
|
||||
}
|
||||
connectionF.bulkTransfer(epF, event.data, event.count, 0);
|
||||
eventSchedulerF.addEventToPool(event);
|
||||
|
||||
byte[] convertedArray;
|
||||
if (mIsUniversalMidiDevice) {
|
||||
// For USB, each 32 bit word of a UMP is
|
||||
// sent with the least significant byte first.
|
||||
convertedArray = swapEndiannessPerWord(event.data,
|
||||
event.count);
|
||||
} else {
|
||||
convertedArray =
|
||||
mUsbMidiPacketConverter.rawMidiToUsbMidi(
|
||||
event.data, event.count);
|
||||
}
|
||||
|
||||
if (DEBUG) {
|
||||
logByteArray("Output after conversion ", convertedArray, 0,
|
||||
convertedArray.length);
|
||||
}
|
||||
|
||||
connectionFinal.bulkTransfer(endpointFinal, convertedArray,
|
||||
convertedArray.length,
|
||||
BULK_TRANSFER_TIMEOUT_MILLISECONDS);
|
||||
eventSchedulerFinal.addEventToPool(event);
|
||||
}
|
||||
Log.d(TAG, "output thread exit");
|
||||
}
|
||||
@@ -381,11 +444,15 @@ public final class UsbUniversalMidiDevice implements Closeable {
|
||||
mContext = context;
|
||||
MidiManager midiManager = context.getSystemService(MidiManager.class);
|
||||
if (midiManager == null) {
|
||||
Log.e(TAG, "No MidiManager in UsbUniversalMidiDevice.create()");
|
||||
Log.e(TAG, "No MidiManager in UsbDirectMidiDevice.create()");
|
||||
return false;
|
||||
}
|
||||
|
||||
mDefaultMidiProtocol = calculateDefaultMidiProtocol();
|
||||
if (mIsUniversalMidiDevice) {
|
||||
mDefaultMidiProtocol = calculateDefaultMidiProtocol();
|
||||
} else {
|
||||
mDefaultMidiProtocol = MidiDeviceInfo.PROTOCOL_UNKNOWN;
|
||||
}
|
||||
|
||||
Bundle properties = new Bundle();
|
||||
String manufacturer = mUsbDevice.getManufacturerName();
|
||||
@@ -397,8 +464,15 @@ public final class UsbUniversalMidiDevice implements Closeable {
|
||||
} else if (product == null || product.isEmpty()) {
|
||||
name = manufacturer;
|
||||
} else {
|
||||
name = manufacturer + " " + product + " MIDI 2.0";
|
||||
name = manufacturer + " " + product;
|
||||
}
|
||||
name += "#" + mUniqueUsbDeviceIdentifier;
|
||||
if (mIsUniversalMidiDevice) {
|
||||
name += " MIDI 2.0";
|
||||
} else {
|
||||
name += " MIDI 1.0";
|
||||
}
|
||||
Log.e(TAG, name);
|
||||
properties.putString(MidiDeviceInfo.PROPERTY_NAME, name);
|
||||
properties.putString(MidiDeviceInfo.PROPERTY_MANUFACTURER, manufacturer);
|
||||
properties.putString(MidiDeviceInfo.PROPERTY_PRODUCT, product);
|
||||
@@ -430,10 +504,13 @@ public final class UsbUniversalMidiDevice implements Closeable {
|
||||
}
|
||||
|
||||
private void closeLocked() {
|
||||
Log.d(TAG, "closeLocked()");
|
||||
for (int i = 0; i < mEventSchedulers.length; i++) {
|
||||
mMidiInputPortReceivers[i].setReceiver(null);
|
||||
mEventSchedulers[i].close();
|
||||
}
|
||||
mEventSchedulers = null;
|
||||
|
||||
for (UsbDeviceConnection connection : mUsbDeviceConnections) {
|
||||
connection.close();
|
||||
}
|
||||
@@ -444,15 +521,19 @@ public final class UsbUniversalMidiDevice implements Closeable {
|
||||
mIsOpen = false;
|
||||
}
|
||||
|
||||
private void swapEndiannessPerWord(byte[] array, int size) {
|
||||
for (int i = 0; i + 3 < size; i += 4) {
|
||||
byte tmp = array[i];
|
||||
array[i] = array[i + 3];
|
||||
array[i + 3] = tmp;
|
||||
tmp = array[i + 1];
|
||||
array[i + 1] = array[i + 2];
|
||||
array[i + 2] = tmp;
|
||||
private byte[] swapEndiannessPerWord(byte[] inputArray, int size) {
|
||||
int numberOfExcessBytes = size & 3;
|
||||
if (numberOfExcessBytes != 0) {
|
||||
Log.e(TAG, "size not multiple of 4: " + size);
|
||||
}
|
||||
byte[] outputArray = new byte[size - numberOfExcessBytes];
|
||||
for (int i = 0; i + 3 < size; i += 4) {
|
||||
outputArray[i] = inputArray[i + 3];
|
||||
outputArray[i + 1] = inputArray[i + 2];
|
||||
outputArray[i + 2] = inputArray[i + 1];
|
||||
outputArray[i + 3] = inputArray[i];
|
||||
}
|
||||
return outputArray;
|
||||
}
|
||||
|
||||
private static void logByteArray(String prefix, byte[] value, int offset, int count) {
|
||||
@@ -465,4 +546,24 @@ public final class UsbUniversalMidiDevice implements Closeable {
|
||||
}
|
||||
Log.d(TAG, builder.toString());
|
||||
}
|
||||
|
||||
private boolean updateUsbInterface(UsbInterface usbInterface,
|
||||
UsbDeviceConnection connection) {
|
||||
if (usbInterface == null) {
|
||||
Log.e(TAG, "Usb Interface is null");
|
||||
return false;
|
||||
}
|
||||
if (!connection.claimInterface(usbInterface, true)) {
|
||||
Log.e(TAG, "Can't claim interface");
|
||||
return false;
|
||||
}
|
||||
if (mShouldCallSetInterface) {
|
||||
if (!connection.setInterface(usbInterface)) {
|
||||
Log.w(TAG, "Can't set interface");
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "no alternate interface");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -50,9 +50,12 @@ import com.android.server.usb.descriptors.tree.UsbDescriptorsTree;
|
||||
import libcore.io.IoUtils;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* UsbHostManager manages USB state in host mode.
|
||||
@@ -94,10 +97,12 @@ public class UsbHostManager {
|
||||
private final ArrayMap<String, ConnectionRecord> mConnected = new ArrayMap<>();
|
||||
|
||||
/**
|
||||
* List of connected MIDI devices
|
||||
* List of connected MIDI devices. Key on deviceAddress.
|
||||
*/
|
||||
private final HashMap<String, UsbUniversalMidiDevice>
|
||||
mMidiDevices = new HashMap<String, UsbUniversalMidiDevice>();
|
||||
private final HashMap<String, ArrayList<UsbDirectMidiDevice>>
|
||||
mMidiDevices = new HashMap<String, ArrayList<UsbDirectMidiDevice>>();
|
||||
private final HashSet<String> mMidiUniqueCodes = new HashSet<String>();
|
||||
private final Random mRandom = new Random();
|
||||
private final boolean mHasMidiFeature;
|
||||
|
||||
/*
|
||||
@@ -423,15 +428,35 @@ public class UsbHostManager {
|
||||
mUsbAlsaManager.usbDeviceAdded(deviceAddress, newDevice, parser);
|
||||
|
||||
if (mHasMidiFeature) {
|
||||
// Use a 3 digit code to associate MIDI devices with one another.
|
||||
// Each MIDI device already has mId for uniqueness. mId is generated
|
||||
// sequentially. For clarity, this code is not generated sequentially.
|
||||
String uniqueUsbDeviceIdentifier = generateNewUsbDeviceIdentifier();
|
||||
|
||||
ArrayList<UsbDirectMidiDevice> midiDevices =
|
||||
new ArrayList<UsbDirectMidiDevice>();
|
||||
if (parser.containsUniversalMidiDeviceEndpoint()) {
|
||||
UsbUniversalMidiDevice midiDevice = UsbUniversalMidiDevice.create(mContext,
|
||||
newDevice, parser);
|
||||
UsbDirectMidiDevice midiDevice = UsbDirectMidiDevice.create(mContext,
|
||||
newDevice, parser, true, uniqueUsbDeviceIdentifier);
|
||||
if (midiDevice != null) {
|
||||
mMidiDevices.put(deviceAddress, midiDevice);
|
||||
midiDevices.add(midiDevice);
|
||||
} else {
|
||||
Slog.e(TAG, "Universal Midi Device is null.");
|
||||
}
|
||||
}
|
||||
if (parser.containsLegacyMidiDeviceEndpoint()) {
|
||||
UsbDirectMidiDevice midiDevice = UsbDirectMidiDevice.create(mContext,
|
||||
newDevice, parser, false, uniqueUsbDeviceIdentifier);
|
||||
if (midiDevice != null) {
|
||||
midiDevices.add(midiDevice);
|
||||
} else {
|
||||
Slog.e(TAG, "Legacy Midi Device is null.");
|
||||
}
|
||||
}
|
||||
|
||||
if (!midiDevices.isEmpty()) {
|
||||
mMidiDevices.put(deviceAddress, midiDevices);
|
||||
}
|
||||
}
|
||||
|
||||
// Tracking
|
||||
@@ -469,10 +494,13 @@ public class UsbHostManager {
|
||||
mPermissionManager.usbDeviceRemoved(device);
|
||||
|
||||
// MIDI
|
||||
UsbUniversalMidiDevice midiDevice = mMidiDevices.remove(deviceAddress);
|
||||
if (midiDevice != null) {
|
||||
Slog.i(TAG, "USB Universal MIDI Device Removed: " + deviceAddress);
|
||||
IoUtils.closeQuietly(midiDevice);
|
||||
ArrayList<UsbDirectMidiDevice> midiDevices =
|
||||
mMidiDevices.remove(deviceAddress);
|
||||
for (UsbDirectMidiDevice midiDevice : midiDevices) {
|
||||
if (midiDevice != null) {
|
||||
Slog.i(TAG, "USB MIDI Device Removed: " + deviceAddress);
|
||||
IoUtils.closeQuietly(midiDevice);
|
||||
}
|
||||
}
|
||||
|
||||
getCurrentUserSettings().usbDeviceRemoved(device);
|
||||
@@ -606,6 +634,19 @@ public class UsbHostManager {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Generate a 3 digit code.
|
||||
private String generateNewUsbDeviceIdentifier() {
|
||||
String code;
|
||||
do {
|
||||
code = "";
|
||||
for (int i = 0; i < 3; i++) {
|
||||
code += mRandom.nextInt(10);
|
||||
}
|
||||
} while (mMidiUniqueCodes.contains(code));
|
||||
mMidiUniqueCodes.add(code);
|
||||
return code;
|
||||
}
|
||||
|
||||
private native void monitorUsbHostBus();
|
||||
private native ParcelFileDescriptor nativeOpenDevice(String deviceAddress);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,237 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.server.usb;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
||||
/**
|
||||
* Converts between MIDI packets and USB MIDI 1.0 packets.
|
||||
*/
|
||||
public class UsbMidiPacketConverter {
|
||||
|
||||
// Refer to Table 4-1 in USB MIDI 1.0 spec.
|
||||
private static final int[] PAYLOAD_SIZE = new int[]{
|
||||
/* 0x00 */ -1, // Miscellaneous function codes. Reserved for future extensions.
|
||||
/* 0x01 */ -1, // Cable events. Reserved for future expansion.
|
||||
/* 0x02 */ 2, // Two-byte System Common messages like MTC, SongSelect, etc
|
||||
/* 0x03 */ 3, // Three-byte System Common messages like SPP, etc.
|
||||
/* 0x04 */ 3, // SysEx starts or continues
|
||||
/* 0x05 */ 1, // Single-byte System Common Message or single-byte SysEx ends.
|
||||
/* 0x06 */ 2, // SysEx ends with following two bytes.
|
||||
/* 0x07 */ 3, // SysEx ends with following three bytes.
|
||||
/* 0x08 */ 3, // Note-off
|
||||
/* 0x09 */ 3, // Note-on
|
||||
/* 0x0a */ 3, // Poly-KeyPress
|
||||
/* 0x0b */ 3, // Control Change
|
||||
/* 0x0c */ 2, // Program Change
|
||||
/* 0x0d */ 2, // Channel Pressure
|
||||
/* 0x0e */ 3, // PitchBend Change
|
||||
/* 0x0f */ 1 // Single Byte
|
||||
};
|
||||
|
||||
// Each System MIDI message is a certain size. These can be mapped to a
|
||||
// Code Index number defined in Table 4-1 of USB MIDI 1.0.
|
||||
private static final int[] CODE_INDEX_NUMBER_FROM_SYSTEM_TYPE = new int[]{
|
||||
/* 0x00 */ -1, // Start of Exclusive. Special case.
|
||||
/* 0x01 */ 2, // MIDI Time Code. Two byte message
|
||||
/* 0x02 */ 3, // Song Point Pointer. Three byte message
|
||||
/* 0x03 */ 2, // Song Select. Two byte message
|
||||
/* 0x04 */ -1, // Undefined MIDI System Common
|
||||
/* 0x05 */ -1, // Undefined MIDI System Common
|
||||
/* 0x06 */ 5, // Tune Request. One byte message
|
||||
/* 0x07 */ -1, // End of Exclusive. Special case.
|
||||
/* 0x08 */ 5, // Timing clock. One byte message
|
||||
/* 0x09 */ -1, // Undefined MIDI System Real-time
|
||||
/* 0x0a */ 5, // Start. One byte message
|
||||
/* 0x0b */ 5, // Continue. One byte message
|
||||
/* 0x0c */ 5, // Stop. One byte message
|
||||
/* 0x0d */ -1, // Undefined MIDI System Real-time
|
||||
/* 0x0e */ 5, // Active Sensing. One byte message
|
||||
/* 0x0f */ 5 // System Reset. One byte message
|
||||
};
|
||||
|
||||
// These code index numbers also come from Table 4-1 in USB MIDI 1.0 spec.
|
||||
private static final byte CODE_INDEX_NUMBER_SYSEX_STARTS_OR_CONTINUES = 0x4;
|
||||
private static final byte CODE_INDEX_NUMBER_SINGLE_BYTE = 0xF;
|
||||
private static final byte CODE_INDEX_NUMBER_SYSEX_END_SINGLE_BYTE = (byte) 0x5;
|
||||
|
||||
// System messages are defined in MIDI.
|
||||
private static final byte FIRST_SYSTEM_MESSAGE_VALUE = (byte) 0xF0;
|
||||
private static final byte SYSEX_START_EXCLUSIVE = (byte) 0xF0;
|
||||
private static final byte SYSEX_END_EXCLUSIVE = (byte) 0xF7;
|
||||
|
||||
private UsbMidiEncoder mUsbMidiEncoder = new UsbMidiEncoder();
|
||||
private UsbMidiDecoder mUsbMidiDecoder = new UsbMidiDecoder();
|
||||
|
||||
/**
|
||||
* Converts a USB MIDI array into a raw MIDI array.
|
||||
*
|
||||
* @param usbMidiBytes the USB MIDI bytes to convert
|
||||
* @param size the size of usbMidiBytes
|
||||
* @return byte array of raw MIDI packets
|
||||
*/
|
||||
public byte[] usbMidiToRawMidi(byte[] usbMidiBytes, int size) {
|
||||
return mUsbMidiDecoder.decode(usbMidiBytes, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a raw MIDI array into a USB MIDI array.
|
||||
*
|
||||
* @param midiBytes the raw MIDI bytes to convert
|
||||
* @param size the size of usbMidiBytes
|
||||
* @return byte array of USB MIDI packets
|
||||
*/
|
||||
public byte[] rawMidiToUsbMidi(byte[] midiBytes, int size) {
|
||||
return mUsbMidiEncoder.encode(midiBytes, size);
|
||||
}
|
||||
|
||||
private class UsbMidiDecoder {
|
||||
// Decodes the data from USB MIDI to raw MIDI.
|
||||
// Each valid 4 byte input maps to a 1-3 byte output.
|
||||
// Reference the USB MIDI 1.0 spec for more info.
|
||||
public byte[] decode(byte[] usbMidiBytes, int size) {
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
for (int i = 0; i + 3 < size; i += 4) {
|
||||
int codeIndex = usbMidiBytes[i] & 0x0f;
|
||||
int numPayloadBytes = PAYLOAD_SIZE[codeIndex];
|
||||
if (numPayloadBytes < 0) {
|
||||
continue;
|
||||
}
|
||||
outputStream.write(usbMidiBytes, i + 1, numPayloadBytes);
|
||||
}
|
||||
return outputStream.toByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
private class UsbMidiEncoder {
|
||||
// In order to facilitate large scale transfers, SysEx can be sent in multiple packets.
|
||||
// If encode() is called without an SysEx end, we must continue SysEx for the next packet.
|
||||
// All other packets should be 3 bytes or less and must be not be broken between packets.
|
||||
private byte[] mStoredSystemExclusiveBytes = new byte[3];
|
||||
private int mNumStoredSystemExclusiveBytes = 0;
|
||||
private boolean mHasSystemExclusiveStarted = false;
|
||||
|
||||
private byte[] mEmptyBytes = new byte[3]; // Used to fill out extra data
|
||||
|
||||
// Encodes the data from raw MIDI to USB MIDI.
|
||||
// Each valid 1-3 byte input maps to a 4 byte output.
|
||||
// Reference the USB MIDI 1.0 spec for more info.
|
||||
// MidiFramer is not needed here as this code handles partial packets.
|
||||
// Long SysEx messages split between packets will encode and return a
|
||||
// byte stream even if the SysEx end has not been sent.
|
||||
// If there are less than 3 remaining data bytes in a SysEx message left,
|
||||
// these bytes will be combined with the next set of packets.
|
||||
public byte[] encode(byte[] midiBytes, int size) {
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
int curLocation = 0;
|
||||
while (curLocation < size) {
|
||||
if (midiBytes[curLocation] >= 0) { // Data byte
|
||||
if (mHasSystemExclusiveStarted) {
|
||||
mStoredSystemExclusiveBytes[mNumStoredSystemExclusiveBytes] =
|
||||
midiBytes[curLocation];
|
||||
mNumStoredSystemExclusiveBytes++;
|
||||
if (mNumStoredSystemExclusiveBytes == 3) {
|
||||
outputStream.write(CODE_INDEX_NUMBER_SYSEX_STARTS_OR_CONTINUES);
|
||||
outputStream.write(mStoredSystemExclusiveBytes, 0, 3);
|
||||
mNumStoredSystemExclusiveBytes = 0;
|
||||
}
|
||||
} else {
|
||||
writeSingleByte(outputStream, midiBytes[curLocation]);
|
||||
}
|
||||
curLocation++;
|
||||
continue;
|
||||
} else if (midiBytes[curLocation] != SYSEX_END_EXCLUSIVE) {
|
||||
// SysEx operation was interrupted. Pass the data directly down.
|
||||
if (mHasSystemExclusiveStarted) {
|
||||
int index = 0;
|
||||
while (index < mNumStoredSystemExclusiveBytes) {
|
||||
writeSingleByte(outputStream, mStoredSystemExclusiveBytes[index]);
|
||||
index++;
|
||||
}
|
||||
mNumStoredSystemExclusiveBytes = 0;
|
||||
mHasSystemExclusiveStarted = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (midiBytes[curLocation] < FIRST_SYSTEM_MESSAGE_VALUE) { // Channel message
|
||||
byte codeIndexNumber = (byte) ((midiBytes[curLocation] >> 4) & 0x0f);
|
||||
int channelMessageSize = PAYLOAD_SIZE[codeIndexNumber];
|
||||
if (curLocation + channelMessageSize <= size) {
|
||||
outputStream.write(codeIndexNumber);
|
||||
outputStream.write(midiBytes, curLocation, channelMessageSize);
|
||||
// Fill in the rest of the bytes with 0.
|
||||
outputStream.write(mEmptyBytes, 0, 3 - channelMessageSize);
|
||||
curLocation += channelMessageSize;
|
||||
} else { // The packet is missing data. Use single byte messages.
|
||||
while (curLocation < size) {
|
||||
writeSingleByte(outputStream, midiBytes[curLocation]);
|
||||
curLocation++;
|
||||
}
|
||||
}
|
||||
} else if (midiBytes[curLocation] == SYSEX_START_EXCLUSIVE) {
|
||||
mHasSystemExclusiveStarted = true;
|
||||
mStoredSystemExclusiveBytes[0] = midiBytes[curLocation];
|
||||
mNumStoredSystemExclusiveBytes = 1;
|
||||
curLocation++;
|
||||
} else if (midiBytes[curLocation] == SYSEX_END_EXCLUSIVE) {
|
||||
// 1 byte is 0x05, 2 bytes is 0x06, and 3 bytes is 0x07
|
||||
outputStream.write(CODE_INDEX_NUMBER_SYSEX_END_SINGLE_BYTE
|
||||
+ mNumStoredSystemExclusiveBytes);
|
||||
mStoredSystemExclusiveBytes[mNumStoredSystemExclusiveBytes] =
|
||||
midiBytes[curLocation];
|
||||
mNumStoredSystemExclusiveBytes++;
|
||||
outputStream.write(mStoredSystemExclusiveBytes, 0,
|
||||
mNumStoredSystemExclusiveBytes);
|
||||
// Fill in the rest of the bytes with 0.
|
||||
outputStream.write(mEmptyBytes, 0, 3 - mNumStoredSystemExclusiveBytes);
|
||||
mHasSystemExclusiveStarted = false;
|
||||
mNumStoredSystemExclusiveBytes = 0;
|
||||
curLocation++;
|
||||
} else {
|
||||
int systemType = midiBytes[curLocation] & 0x0f;
|
||||
int codeIndexNumber = CODE_INDEX_NUMBER_FROM_SYSTEM_TYPE[systemType];
|
||||
if (codeIndexNumber < 0) { // Unknown type. Use single byte messages.
|
||||
writeSingleByte(outputStream, midiBytes[curLocation]);
|
||||
curLocation++;
|
||||
} else {
|
||||
int systemMessageSize = PAYLOAD_SIZE[codeIndexNumber];
|
||||
if (curLocation + systemMessageSize <= size) {
|
||||
outputStream.write(codeIndexNumber);
|
||||
outputStream.write(midiBytes, curLocation, systemMessageSize);
|
||||
// Fill in the rest of the bytes with 0.
|
||||
outputStream.write(mEmptyBytes, 0, 3 - systemMessageSize);
|
||||
curLocation += systemMessageSize;
|
||||
} else { // The packet is missing data. Use single byte messages.
|
||||
while (curLocation < size) {
|
||||
writeSingleByte(outputStream, midiBytes[curLocation]);
|
||||
curLocation++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return outputStream.toByteArray();
|
||||
}
|
||||
|
||||
private void writeSingleByte(ByteArrayOutputStream outputStream, byte byteToWrite) {
|
||||
outputStream.write(CODE_INDEX_NUMBER_SINGLE_BYTE);
|
||||
outputStream.write(byteToWrite);
|
||||
outputStream.write(0);
|
||||
outputStream.write(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -198,14 +198,20 @@ public final class UsbDescriptorParser {
|
||||
if (mCurInterfaceDescriptor != null) {
|
||||
switch (mCurInterfaceDescriptor.getUsbClass()) {
|
||||
case UsbDescriptor.CLASSID_AUDIO:
|
||||
descriptor = UsbACInterface.allocDescriptor(this, stream, length, type);
|
||||
descriptor =
|
||||
UsbACInterface.allocDescriptor(this, stream, length, type);
|
||||
if (descriptor instanceof UsbMSMidiHeader) {
|
||||
mCurInterfaceDescriptor.setMidiHeaderInterfaceDescriptor(
|
||||
descriptor);
|
||||
}
|
||||
break;
|
||||
|
||||
case UsbDescriptor.CLASSID_VIDEO:
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, " UsbDescriptor.CLASSID_VIDEO");
|
||||
}
|
||||
descriptor = UsbVCInterface.allocDescriptor(this, stream, length, type);
|
||||
descriptor =
|
||||
UsbVCInterface.allocDescriptor(this, stream, length, type);
|
||||
break;
|
||||
|
||||
case UsbDescriptor.CLASSID_AUDIOVIDEO:
|
||||
@@ -218,7 +224,6 @@ public final class UsbDescriptorParser {
|
||||
Log.w(TAG, " Unparsed Class-specific");
|
||||
break;
|
||||
}
|
||||
mCurInterfaceDescriptor.setClassSpecificInterfaceDescriptor(descriptor);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -674,6 +679,23 @@ public final class UsbDescriptorParser {
|
||||
public boolean containsUniversalMidiDeviceEndpoint() {
|
||||
ArrayList<UsbInterfaceDescriptor> interfaceDescriptors =
|
||||
findUniversalMidiInterfaceDescriptors();
|
||||
return doesInterfaceContainEndpoint(interfaceDescriptors);
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public boolean containsLegacyMidiDeviceEndpoint() {
|
||||
ArrayList<UsbInterfaceDescriptor> interfaceDescriptors =
|
||||
findLegacyMidiInterfaceDescriptors();
|
||||
return doesInterfaceContainEndpoint(interfaceDescriptors);
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public boolean doesInterfaceContainEndpoint(
|
||||
ArrayList<UsbInterfaceDescriptor> interfaceDescriptors) {
|
||||
int outputCount = 0;
|
||||
int inputCount = 0;
|
||||
for (int interfaceIndex = 0; interfaceIndex < interfaceDescriptors.size();
|
||||
@@ -698,10 +720,24 @@ public final class UsbDescriptorParser {
|
||||
* @hide
|
||||
*/
|
||||
public ArrayList<UsbInterfaceDescriptor> findUniversalMidiInterfaceDescriptors() {
|
||||
return findMidiInterfaceDescriptors(MS_MIDI_2_0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public ArrayList<UsbInterfaceDescriptor> findLegacyMidiInterfaceDescriptors() {
|
||||
return findMidiInterfaceDescriptors(MS_MIDI_1_0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
private ArrayList<UsbInterfaceDescriptor> findMidiInterfaceDescriptors(int type) {
|
||||
int count = 0;
|
||||
ArrayList<UsbDescriptor> descriptors =
|
||||
getInterfaceDescriptorsForClass(UsbDescriptor.CLASSID_AUDIO);
|
||||
ArrayList<UsbInterfaceDescriptor> universalMidiInterfaces =
|
||||
ArrayList<UsbInterfaceDescriptor> midiInterfaces =
|
||||
new ArrayList<UsbInterfaceDescriptor>();
|
||||
|
||||
for (UsbDescriptor descriptor : descriptors) {
|
||||
@@ -709,14 +745,14 @@ public final class UsbDescriptorParser {
|
||||
if (descriptor instanceof UsbInterfaceDescriptor) {
|
||||
UsbInterfaceDescriptor interfaceDescriptor = (UsbInterfaceDescriptor) descriptor;
|
||||
if (interfaceDescriptor.getUsbSubclass() == UsbDescriptor.AUDIO_MIDISTREAMING) {
|
||||
UsbDescriptor classSpecificDescriptor =
|
||||
interfaceDescriptor.getClassSpecificInterfaceDescriptor();
|
||||
if (classSpecificDescriptor != null) {
|
||||
if (classSpecificDescriptor instanceof UsbMSMidiHeader) {
|
||||
UsbDescriptor midiHeaderDescriptor =
|
||||
interfaceDescriptor.getMidiHeaderInterfaceDescriptor();
|
||||
if (midiHeaderDescriptor != null) {
|
||||
if (midiHeaderDescriptor instanceof UsbMSMidiHeader) {
|
||||
UsbMSMidiHeader midiHeader =
|
||||
(UsbMSMidiHeader) classSpecificDescriptor;
|
||||
if (midiHeader.getMidiStreamingClass() == MS_MIDI_2_0) {
|
||||
universalMidiInterfaces.add(interfaceDescriptor);
|
||||
(UsbMSMidiHeader) midiHeaderDescriptor;
|
||||
if (midiHeader.getMidiStreamingClass() == type) {
|
||||
midiInterfaces.add(interfaceDescriptor);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -726,10 +762,13 @@ public final class UsbDescriptorParser {
|
||||
+ " t:0x" + Integer.toHexString(descriptor.getType()));
|
||||
}
|
||||
}
|
||||
return universalMidiInterfaces;
|
||||
return midiInterfaces;
|
||||
}
|
||||
|
||||
private int calculateNumLegacyMidiPorts(boolean isOutput) {
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public int calculateMidiInterfaceDescriptorsCount() {
|
||||
int count = 0;
|
||||
ArrayList<UsbDescriptor> descriptors =
|
||||
getInterfaceDescriptorsForClass(UsbDescriptor.CLASSID_AUDIO);
|
||||
@@ -738,22 +777,12 @@ public final class UsbDescriptorParser {
|
||||
if (descriptor instanceof UsbInterfaceDescriptor) {
|
||||
UsbInterfaceDescriptor interfaceDescriptor = (UsbInterfaceDescriptor) descriptor;
|
||||
if (interfaceDescriptor.getUsbSubclass() == UsbDescriptor.AUDIO_MIDISTREAMING) {
|
||||
UsbDescriptor classSpecificDescriptor =
|
||||
interfaceDescriptor.getClassSpecificInterfaceDescriptor();
|
||||
if (classSpecificDescriptor != null) {
|
||||
if (classSpecificDescriptor instanceof UsbMSMidiHeader) {
|
||||
UsbDescriptor midiHeaderDescriptor =
|
||||
interfaceDescriptor.getMidiHeaderInterfaceDescriptor();
|
||||
if (midiHeaderDescriptor != null) {
|
||||
if (midiHeaderDescriptor instanceof UsbMSMidiHeader) {
|
||||
UsbMSMidiHeader midiHeader =
|
||||
(UsbMSMidiHeader) classSpecificDescriptor;
|
||||
if (midiHeader.getMidiStreamingClass() != MS_MIDI_1_0) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < interfaceDescriptor.getNumEndpoints(); i++) {
|
||||
UsbEndpointDescriptor endpoint =
|
||||
interfaceDescriptor.getEndpointDescriptor(i);
|
||||
// 0 is output, 1 << 7 is input.
|
||||
if ((endpoint.getDirection() == 0) == isOutput) {
|
||||
(UsbMSMidiHeader) midiHeaderDescriptor;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
@@ -766,20 +795,6 @@ public final class UsbDescriptorParser {
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public int calculateNumLegacyMidiInputs() {
|
||||
return calculateNumLegacyMidiPorts(false /*isOutput*/);
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public int calculateNumLegacyMidiOutputs() {
|
||||
return calculateNumLegacyMidiPorts(true /*isOutput*/);
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
|
||||
@@ -42,7 +42,8 @@ public class UsbInterfaceDescriptor extends UsbDescriptor {
|
||||
private ArrayList<UsbEndpointDescriptor> mEndpointDescriptors =
|
||||
new ArrayList<UsbEndpointDescriptor>();
|
||||
|
||||
private UsbDescriptor mClassSpecificInterfaceDescriptor;
|
||||
// Used for MIDI only.
|
||||
private UsbDescriptor mMidiHeaderInterfaceDescriptor;
|
||||
|
||||
UsbInterfaceDescriptor(int length, byte type) {
|
||||
super(length, type);
|
||||
@@ -107,12 +108,12 @@ public class UsbInterfaceDescriptor extends UsbDescriptor {
|
||||
mEndpointDescriptors.add(endpoint);
|
||||
}
|
||||
|
||||
public void setClassSpecificInterfaceDescriptor(UsbDescriptor descriptor) {
|
||||
mClassSpecificInterfaceDescriptor = descriptor;
|
||||
public void setMidiHeaderInterfaceDescriptor(UsbDescriptor descriptor) {
|
||||
mMidiHeaderInterfaceDescriptor = descriptor;
|
||||
}
|
||||
|
||||
public UsbDescriptor getClassSpecificInterfaceDescriptor() {
|
||||
return mClassSpecificInterfaceDescriptor;
|
||||
public UsbDescriptor getMidiHeaderInterfaceDescriptor() {
|
||||
return mMidiHeaderInterfaceDescriptor;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user