MIDI: API changes to support multiple ports per device
Add MidiInputPort and MidiOutputPort classes (with MidiPort base class)
A MidiDevice can now have multiple input and output ports.
Multiple ports are currently supported only for virtual devices, USB support coming later.
Change-Id: Ib55076aa1374aa46ae4ae76ad93bd717df6d7e21
diff --git a/core/java/android/midi/IMidiManager.aidl b/core/java/android/midi/IMidiManager.aidl
index 06b675b..7a9f887 100644
--- a/core/java/android/midi/IMidiManager.aidl
+++ b/core/java/android/midi/IMidiManager.aidl
@@ -37,7 +37,8 @@
ParcelFileDescriptor openDevice(IBinder token, in MidiDeviceInfo device);
// for implementing virtual MIDI devices
- MidiDevice registerVirtualDevice(IBinder token, in Bundle properties);
+ MidiDevice registerVirtualDevice(IBinder token, int numInputPorts, int numOutputPorts,
+ in Bundle properties);
void unregisterVirtualDevice(IBinder token, in MidiDeviceInfo device);
// for use by UsbAudioManager
diff --git a/core/java/android/midi/MidiDevice.java b/core/java/android/midi/MidiDevice.java
index 8954d2b..e704ea0 100644
--- a/core/java/android/midi/MidiDevice.java
+++ b/core/java/android/midi/MidiDevice.java
@@ -28,7 +28,7 @@
import java.util.ArrayList;
/**
- * This class is used for sending and receiving data to and from an midi device
+ * This class is used for sending and receiving data to and from an MIDI device
* Instances of this class are created by {@link MidiManager#openDevice}.
* This class can also be used to provide the implementation for a virtual device.
*
@@ -44,21 +44,29 @@
private ParcelFileDescriptor mParcelFileDescriptor;
private FileInputStream mInputStream;
private FileOutputStream mOutputStream;
- private final ArrayList<MidiReceiver> mReceivers = new ArrayList<MidiReceiver>();
+
+ // lazily populated lists of ports
+ private final MidiInputPort[] mInputPorts;
+ private final MidiOutputPort[] mOutputPorts;
+
+ // array of receiver lists, indexed by port number
+ private final ArrayList<MidiReceiver>[] mReceivers;
+
+ private int mReceiverCount; // total number of receivers for all ports
/**
* Minimum size of packed message as sent through our ParcelFileDescriptor
- * 8 bytes for timestamp and 1 to 3 bytes for message
+ * 8 bytes for timestamp, 1 byte for port number and 1 to 3 bytes for message
* @hide
*/
- public static final int MIN_PACKED_MESSAGE_SIZE = 9;
+ public static final int MIN_PACKED_MESSAGE_SIZE = 10;
/**
* Maximum size of packed message as sent through our ParcelFileDescriptor
- * 8 bytes for timestamp and 1 to 3 bytes for message
+ * 8 bytes for timestamp, 1 byte for port number and 1 to 3 bytes for message
* @hide
*/
- public static final int MAX_PACKED_MESSAGE_SIZE = 11;
+ public static final int MAX_PACKED_MESSAGE_SIZE = 12;
// This thread reads MIDI events from a socket and distributes them to the list of
// MidiReceivers attached to this device.
@@ -80,27 +88,35 @@
int offset = getMessageOffset(buffer, count);
int size = getMessageSize(buffer, count);
long timestamp = getMessageTimeStamp(buffer, count);
+ int port = getMessagePortNumber(buffer, count);
synchronized (mReceivers) {
- for (int i = 0; i < mReceivers.size(); i++) {
- MidiReceiver receiver = mReceivers.get(i);
- try {
- mReceivers.get(i).onPost(buffer, offset, size, timestamp);
- } catch (IOException e) {
- Log.e(TAG, "post failed");
- deadReceivers.add(receiver);
+ ArrayList<MidiReceiver> receivers = mReceivers[port];
+ if (receivers != null) {
+ for (int i = 0; i < receivers.size(); i++) {
+ MidiReceiver receiver = receivers.get(i);
+ try {
+ receivers.get(i).onPost(buffer, offset, size, timestamp);
+ } catch (IOException e) {
+ Log.e(TAG, "post failed");
+ deadReceivers.add(receiver);
+ }
}
- }
- // remove any receivers that failed
- if (deadReceivers.size() > 0) {
- for (MidiReceiver receiver: deadReceivers) {
- mReceivers.remove(receiver);
+ // remove any receivers that failed
+ if (deadReceivers.size() > 0) {
+ for (MidiReceiver receiver: deadReceivers) {
+ receivers.remove(receiver);
+ mReceiverCount--;
+ }
+ deadReceivers.clear();
}
- deadReceivers.clear();
- }
- // exit if we have no receivers left
- if (mReceivers.size() == 0) {
- break;
+ if (receivers.size() == 0) {
+ mReceivers[port] = null;
+ }
+ // exit if we have no receivers left
+ if (mReceiverCount == 0) {
+ break;
+ }
}
}
}
@@ -110,38 +126,6 @@
}
};
- // This is the receiver that clients use for sending events to this device.
- private final MidiReceiver mReceiver = new MidiReceiver() {
- private final byte[] mBuffer = new byte[MAX_PACKED_MESSAGE_SIZE];
- public void onPost(byte[] msg, int offset, int count, long timestamp) throws IOException {
- synchronized (mBuffer) {
- int length = packMessage(msg, offset, count, timestamp, mBuffer);
- mOutputStream.write(mBuffer, 0, length);
- }
- }
- };
-
- // Our MidiSender object, to which clients can attach MidiReceivers.
- private final MidiSender mSender = new MidiSender() {
- public void connect(MidiReceiver receiver) {
- synchronized (mReceivers) {
- if (mReceivers.size() == 0) {
- mThread.start();
- }
- mReceivers.add(receiver);
- }
- }
-
- public void disconnect(MidiReceiver receiver) {
- synchronized (mReceivers) {
- mReceivers.remove(receiver);
- if (mReceivers.size() == 0) {
- // ???
- }
- }
- }
- };
-
/**
* MidiDevice should only be instantiated by MidiManager or MidiService
* @hide
@@ -149,9 +133,59 @@
public MidiDevice(MidiDeviceInfo deviceInfo, ParcelFileDescriptor pfd) {
mDeviceInfo = deviceInfo;
mParcelFileDescriptor = pfd;
+ int inputPorts = deviceInfo.getInputPortCount();
+ int outputPorts = deviceInfo.getOutputPortCount();
+ mInputPorts = new MidiInputPort[inputPorts];
+ mOutputPorts = new MidiOutputPort[outputPorts];
+ mReceivers = new ArrayList[outputPorts];
}
- public boolean open() {
+ public MidiInputPort openInputPort(int portNumber) {
+ if (portNumber < 0 || portNumber >= mDeviceInfo.getInputPortCount()) {
+ throw new IllegalArgumentException("input port number out of range");
+ }
+ synchronized (mInputPorts) {
+ if (mInputPorts[portNumber] == null) {
+ mInputPorts[portNumber] = new MidiInputPort(mOutputStream, portNumber);
+ }
+ return mInputPorts[portNumber];
+ }
+ }
+
+ public MidiOutputPort openOutputPort(int portNumber) {
+ if (portNumber < 0 || portNumber >= mDeviceInfo.getOutputPortCount()) {
+ throw new IllegalArgumentException("output port number out of range");
+ }
+ synchronized (mOutputPorts) {
+ if (mOutputPorts[portNumber] == null) {
+ mOutputPorts[portNumber] = new MidiOutputPort(this, portNumber);
+ }
+ return mOutputPorts[portNumber];
+ }
+ }
+
+ /* package */ void connect(MidiReceiver receiver, int portNumber) {
+ synchronized (mReceivers) {
+ if (mReceivers[portNumber] == null) {
+ mReceivers[portNumber] = new ArrayList<MidiReceiver>();
+ }
+ mReceivers[portNumber].add(receiver);
+ if (mReceiverCount++ == 0) {
+ mThread.start();
+ }
+ }
+ }
+
+ /* package */ void disconnect(MidiReceiver receiver, int portNumber) {
+ synchronized (mReceivers) {
+ ArrayList<MidiReceiver> receivers = mReceivers[portNumber];
+ if (receivers != null && receivers.remove(receiver)) {
+ mReceiverCount--;
+ }
+ }
+ }
+
+ /* package */ boolean open() {
FileDescriptor fd = mParcelFileDescriptor.getFileDescriptor();
try {
mInputStream = new FileInputStream(fd);
@@ -187,16 +221,6 @@
return mDeviceInfo;
}
- // returns our MidiReceiver, which clients can use for sending events to this device.
- public MidiReceiver getReceiver() {
- return mReceiver;
- }
-
- // Returns our MidiSender object, to which clients can attach MidiReceivers.
- public MidiSender getSender() {
- return mSender;
- }
-
@Override
public String toString() {
return ("MidiDevice: " + mDeviceInfo.toString() + " fd: " + mParcelFileDescriptor);
@@ -236,7 +260,7 @@
* @hide
*/
public static int packMessage(byte[] message, int offset, int size, long timestamp,
- byte[] dest) {
+ int portNumber, byte[] dest) {
// pack variable length message first
System.arraycopy(message, offset, dest, 0, size);
int destOffset = size;
@@ -245,6 +269,9 @@
dest[destOffset++] = (byte)timestamp;
timestamp >>= 8;
}
+ // portNumber is last
+ dest[destOffset++] = (byte)portNumber;
+
return destOffset;
}
@@ -266,8 +293,8 @@
* @hide
*/
public static int getMessageSize(byte[] buffer, int bufferLength) {
- // message length is total buffer length minus size of the timestamp
- return bufferLength - 8;
+ // message length is total buffer length minus size of the timestamp and port number
+ return bufferLength - 9 /* (sizeof(timestamp) + sizeof(portNumber)) */;
}
/**
@@ -289,4 +316,16 @@
}
return timestamp;
}
+
+ /**
+ * Utility function for unpacking a MIDI message to be sent through our ParcelFileDescriptor
+ * unpacks port number from packed buffer
+ *
+ * @hide
+ */
+ public static int getMessagePortNumber(byte[] buffer, int bufferLength) {
+ // timestamp follows variable length message data and timestamp
+ int dataLength = getMessageSize(buffer, bufferLength);
+ return buffer[dataLength + 8 /* sizeof(timestamp) */];
+ }
}
diff --git a/core/java/android/midi/MidiDeviceInfo.java b/core/java/android/midi/MidiDeviceInfo.java
index def6878c..239481b 100644
--- a/core/java/android/midi/MidiDeviceInfo.java
+++ b/core/java/android/midi/MidiDeviceInfo.java
@@ -39,6 +39,8 @@
private final int mType; // USB or virtual
private final int mId; // unique ID generated by MidiService
+ private final int mInputPortCount;
+ private final int mOutputPortCount;
private final Bundle mProperties;
// used for USB devices only
@@ -77,9 +79,12 @@
* MidiDeviceInfo should only be instantiated by MidiService implementation
* @hide
*/
- public MidiDeviceInfo(int type, int id, Bundle properties) {
+ public MidiDeviceInfo(int type, int id, int numInputPorts, int numOutputPorts,
+ Bundle properties) {
mType = type;
mId = id;
+ mInputPortCount = numInputPorts;
+ mOutputPortCount = numOutputPorts;
mProperties = properties;
mAlsaCard = -1;
mAlsaDevice = -1;
@@ -89,10 +94,12 @@
* MidiDeviceInfo should only be instantiated by MidiService implementation
* @hide
*/
- public MidiDeviceInfo(int type, int id, Bundle properties,
- int alsaCard, int alsaDevice) {
+ public MidiDeviceInfo(int type, int id, int numInputPorts, int numOutputPorts,
+ Bundle properties, int alsaCard, int alsaDevice) {
mType = type;
mId = id;
+ mInputPortCount = numInputPorts;
+ mOutputPortCount = numOutputPorts;
mProperties = properties;
mAlsaCard = alsaCard;
mAlsaDevice = alsaDevice;
@@ -118,6 +125,24 @@
}
/**
+ * Returns the device's number of input ports.
+ *
+ * @return the number of input ports
+ */
+ public int getInputPortCount() {
+ return mInputPortCount;
+ }
+
+ /**
+ * Returns the device's number of output ports.
+ *
+ * @return the number of output ports
+ */
+ public int getOutputPortCount() {
+ return mOutputPortCount;
+ }
+
+ /**
* Returns the {@link android.os.Bundle} containing the device's properties.
*
* @return the device's properties
@@ -157,7 +182,8 @@
@Override
public String toString() {
return ("MidiDeviceInfo[mType=" + mType +
- ",mId=" + mId +
+ ",mInputPortCount=" + mInputPortCount +
+ ",mOutputPortCount=" + mOutputPortCount +
",mProperties=" + mProperties +
",mAlsaCard=" + mAlsaCard +
",mAlsaDevice=" + mAlsaDevice);
@@ -168,10 +194,12 @@
public MidiDeviceInfo createFromParcel(Parcel in) {
int type = in.readInt();
int id = in.readInt();
+ int inputPorts = in.readInt();
+ int outputPorts = in.readInt();
Bundle properties = in.readBundle();
int card = in.readInt();
int device = in.readInt();
- return new MidiDeviceInfo(type, id, properties, card, device);
+ return new MidiDeviceInfo(type, id, inputPorts, outputPorts, properties, card, device);
}
public MidiDeviceInfo[] newArray(int size) {
@@ -186,6 +214,8 @@
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeInt(mType);
parcel.writeInt(mId);
+ parcel.writeInt(mInputPortCount);
+ parcel.writeInt(mOutputPortCount);
parcel.writeBundle(mProperties);
parcel.writeInt(mAlsaCard);
parcel.writeInt(mAlsaDevice);
diff --git a/core/java/android/midi/MidiInputPort.java b/core/java/android/midi/MidiInputPort.java
new file mode 100644
index 0000000..583c367
--- /dev/null
+++ b/core/java/android/midi/MidiInputPort.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2014 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 android.midi;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * This class is used for sending data to a port on a MIDI device
+ *
+ * @hide
+ */
+public final class MidiInputPort extends MidiPort implements MidiReceiver {
+
+ private final FileOutputStream mOutputStream;
+ // buffer to use for sending messages out our output stream
+ private final byte[] mBuffer = new byte[MidiDevice.MAX_PACKED_MESSAGE_SIZE];
+
+ /* package */ MidiInputPort(FileOutputStream outputStream, int portNumber) {
+ super(portNumber);
+ mOutputStream = outputStream;
+ }
+
+ /**
+ * Writes a MIDI message to the input port
+ *
+ * @param msg message bytes
+ * @param offset offset of first byte of the message in msg array
+ * @param count size of the message in bytes
+ * @param timestamp future time to post the message
+ */
+ public void onPost(byte[] msg, int offset, int count, long timestamp) throws IOException {
+ synchronized (mBuffer) {
+ int length = MidiDevice.packMessage(msg, offset, count, timestamp, mPortNumber,
+ mBuffer);
+ mOutputStream.write(mBuffer, 0, length);
+ }
+ }
+}
diff --git a/core/java/android/midi/MidiManager.java b/core/java/android/midi/MidiManager.java
index ec869b7..f4d1918 100644
--- a/core/java/android/midi/MidiManager.java
+++ b/core/java/android/midi/MidiManager.java
@@ -132,9 +132,11 @@
// Use this if you want to register and implement a virtual device.
// The MidiDevice returned by this method is the proxy you use to implement the device.
- public MidiDevice createVirtualDevice(Bundle properties) {
+ public MidiDevice createVirtualDevice(int numInputPorts, int numOutputPorts,
+ Bundle properties) {
try {
- MidiDevice device = mService.registerVirtualDevice(mToken, properties);
+ MidiDevice device = mService.registerVirtualDevice(mToken,
+ numInputPorts, numOutputPorts, properties);
if (device != null && !device.open()) {
device = null;
}
diff --git a/core/java/android/midi/MidiOutputPort.java b/core/java/android/midi/MidiOutputPort.java
new file mode 100644
index 0000000..69a33cb
--- /dev/null
+++ b/core/java/android/midi/MidiOutputPort.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2014 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 android.midi;
+
+import java.io.FileInputStream;
+
+/**
+ * This class is used for receiving data to a port on a MIDI device
+ *
+ * @hide
+ */
+public final class MidiOutputPort extends MidiPort implements MidiSender {
+
+ private final MidiDevice mDevice;
+
+ /* package */ MidiOutputPort(MidiDevice device, int portNumber) {
+ super(portNumber);
+ mDevice = device;
+ }
+
+ public void connect(MidiReceiver receiver) {
+ mDevice.connect(receiver, mPortNumber);
+ }
+
+ public void disconnect(MidiReceiver receiver) {
+ mDevice.disconnect(receiver, mPortNumber);
+ }
+}
diff --git a/core/java/android/midi/MidiPort.java b/core/java/android/midi/MidiPort.java
new file mode 100644
index 0000000..e94f62d
--- /dev/null
+++ b/core/java/android/midi/MidiPort.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2014 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 android.midi;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * This class represents a MIDI input or output port
+ *
+ * @hide
+ */
+public class MidiPort {
+
+ protected final int mPortNumber;
+
+ /* package */ MidiPort(int portNumber) {
+ mPortNumber = portNumber;
+ }
+
+ /**
+ * Returns the port number of this port
+ *
+ * @return the port's port number
+ */
+ public int getPortNumber() {
+ return mPortNumber;
+ }
+}
diff --git a/services/core/java/com/android/server/midi/MidiService.java b/services/core/java/com/android/server/midi/MidiService.java
index ff8dda0..29a8339 100644
--- a/services/core/java/com/android/server/midi/MidiService.java
+++ b/services/core/java/com/android/server/midi/MidiService.java
@@ -172,7 +172,8 @@
return device.getFileDescriptor();
}
- public MidiDevice registerVirtualDevice(IBinder token, Bundle properties) {
+ public MidiDevice registerVirtualDevice(IBinder token, int numInputPorts, int numOutputPorts,
+ Bundle properties) {
VirtualMidiDevice device;
Client client = getClient(token);
if (client == null) return null;
@@ -180,7 +181,7 @@
synchronized (mDevices) {
int id = mNextDeviceId++;
MidiDeviceInfo deviceInfo = new MidiDeviceInfo(MidiDeviceInfo.TYPE_VIRTUAL, id,
- properties);
+ numInputPorts, numOutputPorts, properties);
device = new VirtualMidiDevice(deviceInfo);
if (!device.open()) {
@@ -238,7 +239,12 @@
usbDevice.getSerialNumber());
properties.putParcelable(MidiDeviceInfo.PROPERTY_USB_DEVICE, usbDevice);
- deviceInfo = new MidiDeviceInfo(MidiDeviceInfo.TYPE_USB, id, properties, card, device);
+ // FIXME - multiple ports not supported yet
+ int inputPorts = 1;
+ int outputPorts = 1;
+
+ deviceInfo = new MidiDeviceInfo(MidiDeviceInfo.TYPE_USB, id, inputPorts, outputPorts,
+ properties, card, device);
UsbMidiDevice midiDevice = new UsbMidiDevice(deviceInfo);
mDevices.put(id, midiDevice);
mUsbDevices.put(usbDevice, midiDevice);
diff --git a/services/core/java/com/android/server/midi/UsbMidiDevice.java b/services/core/java/com/android/server/midi/UsbMidiDevice.java
index 1bc91f05..3d42c67 100644
--- a/services/core/java/com/android/server/midi/UsbMidiDevice.java
+++ b/services/core/java/com/android/server/midi/UsbMidiDevice.java
@@ -100,13 +100,16 @@
return -1;
}
}
- return MidiDevice.packMessage(mBuffer, 0, dataSize + 1, System.nanoTime(), buffer);
+ return MidiDevice.packMessage(mBuffer, 0, dataSize + 1, System.nanoTime(),
+ 0, // FIXME - multiple ports not supported yet
+ buffer);
}
// writes a message to the ALSA driver
void writeMessage(byte[] buffer, int count) throws IOException {
int offset = MidiDevice.getMessageOffset(buffer, count);
int size = MidiDevice.getMessageSize(buffer, count);
+ // FIXME - multiple ports not supported yet
mOutputStream.write(buffer, offset, count);
}
}