Add support for flushing MIDI all scheduled MIDI events on a port
Change-Id: I39d7862540d4d4b9e2df1265f9dd253541adb4c2
diff --git a/api/current.txt b/api/current.txt
index d3ae427..90020d1 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -16847,6 +16847,7 @@
public abstract class MidiReceiver {
ctor public MidiReceiver();
+ method public void flush() throws java.io.IOException;
method public int getMaxMessageSize();
method public abstract void onReceive(byte[], int, int, long) throws java.io.IOException;
method public void send(byte[], int, int) throws java.io.IOException;
diff --git a/api/system-current.txt b/api/system-current.txt
index 431809e..b13ecd3 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -18122,6 +18122,7 @@
public abstract class MidiReceiver {
ctor public MidiReceiver();
+ method public void flush() throws java.io.IOException;
method public int getMaxMessageSize();
method public abstract void onReceive(byte[], int, int, long) throws java.io.IOException;
method public void send(byte[], int, int) throws java.io.IOException;
diff --git a/core/java/com/android/internal/midi/EventScheduler.java b/core/java/com/android/internal/midi/EventScheduler.java
index 7b9a48c..506902f6 100644
--- a/core/java/com/android/internal/midi/EventScheduler.java
+++ b/core/java/com/android/internal/midi/EventScheduler.java
@@ -16,6 +16,7 @@
package com.android.internal.midi;
+import java.util.Iterator;
import java.util.SortedMap;
import java.util.TreeMap;
@@ -28,7 +29,7 @@
private static final long NANOS_PER_MILLI = 1000000;
private final Object mLock = new Object();
- private SortedMap<Long, FastEventQueue> mEventBuffer;
+ volatile private SortedMap<Long, FastEventQueue> mEventBuffer;
private FastEventQueue mEventPool = null;
private int mMaxPoolSize = 200;
private boolean mClosed;
@@ -68,6 +69,7 @@
mEventsRemoved++;
SchedulableEvent event = mFirst;
mFirst = event.mNext;
+ event.mNext = null;
return event;
}
@@ -87,7 +89,7 @@
*/
public static class SchedulableEvent {
private long mTimestamp;
- private SchedulableEvent mNext = null;
+ volatile private SchedulableEvent mNext = null;
/**
* @param timestamp
@@ -235,6 +237,11 @@
return event;
}
+ protected void flush() {
+ // Replace our event buffer with a fresh empty one
+ mEventBuffer = new TreeMap<Long, FastEventQueue>();
+ }
+
public void close() {
synchronized (mLock) {
mClosed = true;
diff --git a/core/java/com/android/internal/midi/MidiDispatcher.java b/core/java/com/android/internal/midi/MidiDispatcher.java
index 377bc68..70e699a 100644
--- a/core/java/com/android/internal/midi/MidiDispatcher.java
+++ b/core/java/com/android/internal/midi/MidiDispatcher.java
@@ -83,4 +83,11 @@
}
}
}
+
+ @Override
+ public void flush() throws IOException {
+ for (MidiReceiver receiver : mReceivers) {
+ receiver.flush();
+ }
+ }
}
diff --git a/core/java/com/android/internal/midi/MidiEventScheduler.java b/core/java/com/android/internal/midi/MidiEventScheduler.java
index 42d70f6..4dc5838 100644
--- a/core/java/com/android/internal/midi/MidiEventScheduler.java
+++ b/core/java/com/android/internal/midi/MidiEventScheduler.java
@@ -28,16 +28,9 @@
// Maintain a pool of scheduled events to reduce memory allocation.
// This pool increases performance by about 14%.
private final static int POOL_EVENT_SIZE = 16;
-
- private final MidiReceiver[] mReceivers;
+ private MidiReceiver mReceiver = new SchedulingReceiver();
private class SchedulingReceiver extends MidiReceiver {
- private final int mPortNumber;
-
- public SchedulingReceiver(int portNumber) {
- mPortNumber = portNumber;
- }
-
/**
* Store these bytes in the EventScheduler to be delivered at the specified
* time.
@@ -47,14 +40,17 @@
throws IOException {
MidiEvent event = createScheduledEvent(msg, offset, count, timestamp);
if (event != null) {
- event.portNumber = mPortNumber;
add(event);
}
}
+
+ @Override
+ public void flush() {
+ MidiEventScheduler.this.flush();
+ }
}
public static class MidiEvent extends SchedulableEvent {
- public int portNumber;
public int count = 0;
public byte[] data;
@@ -80,17 +76,6 @@
}
}
- public MidiEventScheduler() {
- this(0);
- }
-
- public MidiEventScheduler(int portCount) {
- mReceivers = new MidiReceiver[portCount];
- for (int i = 0; i < portCount; i++) {
- mReceivers[i] = new SchedulingReceiver(i);
- }
- }
-
/**
* Create an event that contains the message.
*/
@@ -132,15 +117,7 @@
* @return the MidiReceiver
*/
public MidiReceiver getReceiver() {
- return mReceivers[0];
- }
-
- /**
- * This MidiReceiver will write date to the scheduling buffer.
- * @return the MidiReceiver
- */
- public MidiReceiver getReceiver(int portNumber) {
- return mReceivers[portNumber];
+ return mReceiver;
}
}
diff --git a/media/java/android/media/midi/MidiInputPort.java b/media/java/android/media/midi/MidiInputPort.java
index 1d3b37a..ff16a57 100644
--- a/media/java/android/media/midi/MidiInputPort.java
+++ b/media/java/android/media/midi/MidiInputPort.java
@@ -83,7 +83,18 @@
if (mOutputStream == null) {
throw new IOException("MidiInputPort is closed");
}
- int length = MidiPortImpl.packMessage(msg, offset, count, timestamp, mBuffer);
+ int length = MidiPortImpl.packData(msg, offset, count, timestamp, mBuffer);
+ mOutputStream.write(mBuffer, 0, length);
+ }
+ }
+
+ @Override
+ public void flush() throws IOException {
+ synchronized (mBuffer) {
+ if (mOutputStream == null) {
+ throw new IOException("MidiInputPort is closed");
+ }
+ int length = MidiPortImpl.packFlush(mBuffer);
mOutputStream.write(mBuffer, 0, length);
}
}
diff --git a/media/java/android/media/midi/MidiOutputPort.java b/media/java/android/media/midi/MidiOutputPort.java
index 0290a76..7491f3c 100644
--- a/media/java/android/media/midi/MidiOutputPort.java
+++ b/media/java/android/media/midi/MidiOutputPort.java
@@ -62,12 +62,24 @@
// FIXME - inform receivers here?
}
- int offset = MidiPortImpl.getMessageOffset(buffer, count);
- int size = MidiPortImpl.getMessageSize(buffer, count);
- long timestamp = MidiPortImpl.getMessageTimeStamp(buffer, count);
+ int packetType = MidiPortImpl.getPacketType(buffer, count);
+ switch (packetType) {
+ case MidiPortImpl.PACKET_TYPE_DATA: {
+ int offset = MidiPortImpl.getDataOffset(buffer, count);
+ int size = MidiPortImpl.getDataSize(buffer, count);
+ long timestamp = MidiPortImpl.getPacketTimestamp(buffer, count);
- // dispatch to all our receivers
- mDispatcher.sendWithTimestamp(buffer, offset, size, timestamp);
+ // dispatch to all our receivers
+ mDispatcher.sendWithTimestamp(buffer, offset, size, timestamp);
+ break;
+ }
+ case MidiPortImpl.PACKET_TYPE_FLUSH:
+ mDispatcher.flush();
+ break;
+ default:
+ Log.e(TAG, "Unknown packet type " + packetType);
+ break;
+ }
}
} catch (IOException e) {
// FIXME report I/O failure?
diff --git a/media/java/android/media/midi/MidiPortImpl.java b/media/java/android/media/midi/MidiPortImpl.java
index 5795045..16fc214 100644
--- a/media/java/android/media/midi/MidiPortImpl.java
+++ b/media/java/android/media/midi/MidiPortImpl.java
@@ -24,6 +24,16 @@
private static final String TAG = "MidiPort";
/**
+ * Packet type for data packet
+ */
+ public static final int PACKET_TYPE_DATA = 1;
+
+ /**
+ * Packet type for flush packet
+ */
+ public static final int PACKET_TYPE_FLUSH = 2;
+
+ /**
* Maximum size of a packet that can pass through our ParcelFileDescriptor.
*/
public static final int MAX_PACKET_SIZE = 1024;
@@ -34,12 +44,17 @@
private static final int TIMESTAMP_SIZE = 8;
/**
- * Maximum amount of MIDI data that can be included in a packet
+ * Data packet overhead is timestamp size plus packet type byte
*/
- public static final int MAX_PACKET_DATA_SIZE = MAX_PACKET_SIZE - TIMESTAMP_SIZE;
+ private static final int DATA_PACKET_OVERHEAD = TIMESTAMP_SIZE + 1;
/**
- * Utility function for packing a MIDI message to be sent through our ParcelFileDescriptor
+ * Maximum amount of MIDI data that can be included in a packet
+ */
+ public static final int MAX_PACKET_DATA_SIZE = MAX_PACKET_SIZE - DATA_PACKET_OVERHEAD;
+
+ /**
+ * Utility function for packing MIDI data to be sent through our ParcelFileDescriptor
*
* message byte array contains variable length MIDI message.
* messageSize is size of variable length MIDI message
@@ -47,46 +62,65 @@
* dest is buffer to pack into
* returns size of packed message
*/
- public static int packMessage(byte[] message, int offset, int size, long timestamp,
+ public static int packData(byte[] message, int offset, int size, long timestamp,
byte[] dest) {
- if (size + TIMESTAMP_SIZE > MAX_PACKET_SIZE) {
- size = MAX_PACKET_SIZE - TIMESTAMP_SIZE;
+ if (size > MAX_PACKET_DATA_SIZE) {
+ size = MAX_PACKET_DATA_SIZE;
}
- // message data goes first
- System.arraycopy(message, offset, dest, 0, size);
+ int length = 0;
+ // packet type goes first
+ dest[length++] = PACKET_TYPE_DATA;
+ // data goes next
+ System.arraycopy(message, offset, dest, length, size);
+ length += size;
// followed by timestamp
for (int i = 0; i < TIMESTAMP_SIZE; i++) {
- dest[size++] = (byte)timestamp;
+ dest[length++] = (byte)timestamp;
timestamp >>= 8;
}
- return size;
+ return length;
}
/**
- * Utility function for unpacking a MIDI message received from our ParcelFileDescriptor
+ * Utility function for packing a flush command to be sent through our ParcelFileDescriptor
+ */
+ public static int packFlush(byte[] dest) {
+ dest[0] = PACKET_TYPE_FLUSH;
+ return 1;
+ }
+
+ /**
+ * Returns the packet type (PACKET_TYPE_DATA or PACKET_TYPE_FLUSH)
+ */
+ public static int getPacketType(byte[] buffer, int bufferLength) {
+ return buffer[0];
+ }
+
+ /**
+ * Utility function for unpacking MIDI data received from our ParcelFileDescriptor
* returns the offset of the MIDI message in packed buffer
*/
- public static int getMessageOffset(byte[] buffer, int bufferLength) {
- // message is at the beginning
- return 0;
+ public static int getDataOffset(byte[] buffer, int bufferLength) {
+ // data follows packet type byte
+ return 1;
}
/**
- * Utility function for unpacking a MIDI message received from our ParcelFileDescriptor
+ * Utility function for unpacking MIDI data received from our ParcelFileDescriptor
* returns size of MIDI data in packed buffer
*/
- public static int getMessageSize(byte[] buffer, int bufferLength) {
+ public static int getDataSize(byte[] buffer, int bufferLength) {
// message length is total buffer length minus size of the timestamp
- return bufferLength - TIMESTAMP_SIZE;
+ return bufferLength - DATA_PACKET_OVERHEAD;
}
/**
- * Utility function for unpacking a MIDI message received from our ParcelFileDescriptor
+ * Utility function for unpacking MIDI data received from our ParcelFileDescriptor
* unpacks timestamp from packed buffer
*/
- public static long getMessageTimeStamp(byte[] buffer, int bufferLength) {
+ public static long getPacketTimestamp(byte[] buffer, int bufferLength) {
// timestamp is at end of the packet
int offset = bufferLength;
long timestamp = 0;
diff --git a/media/java/android/media/midi/MidiReceiver.java b/media/java/android/media/midi/MidiReceiver.java
index 6f4c266..d069075 100644
--- a/media/java/android/media/midi/MidiReceiver.java
+++ b/media/java/android/media/midi/MidiReceiver.java
@@ -42,6 +42,13 @@
throws IOException;
/**
+ * Instructs the receiver to discard all pending events.
+ * @throws IOException
+ */
+ public void flush() throws IOException {
+ }
+
+ /**
* Returns the maximum size of a message this receiver can receive.
* Defaults to {@link java.lang.Integer#MAX_VALUE} unless overridden.
* @return maximum message size
diff --git a/services/usb/java/com/android/server/usb/UsbMidiDevice.java b/services/usb/java/com/android/server/usb/UsbMidiDevice.java
index 6ece888..671cf01 100644
--- a/services/usb/java/com/android/server/usb/UsbMidiDevice.java
+++ b/services/usb/java/com/android/server/usb/UsbMidiDevice.java
@@ -45,7 +45,8 @@
private MidiDeviceServer mServer;
- private final MidiEventScheduler mEventScheduler;
+ // event schedulers for each output port
+ private final MidiEventScheduler[] mEventSchedulers;
private static final int BUFFER_SIZE = 512;
@@ -99,10 +100,11 @@
}
mOutputStreams = new FileOutputStream[outputCount];
+ mEventSchedulers = new MidiEventScheduler[outputCount];
for (int i = 0; i < outputCount; i++) {
mOutputStreams[i] = new FileOutputStream(fileDescriptors[i]);
+ mEventSchedulers[i] = new MidiEventScheduler();
}
- mEventScheduler = new MidiEventScheduler(inputCount);
}
private boolean register(Context context, Bundle properties) {
@@ -116,7 +118,7 @@
int outputCount = mOutputStreams.length;
MidiReceiver[] inputPortReceivers = new MidiReceiver[inputCount];
for (int port = 0; port < inputCount; port++) {
- inputPortReceivers[port] = mEventScheduler.getReceiver(port);
+ inputPortReceivers[port] = mEventSchedulers[port].getReceiver();
}
mServer = midiManager.createDeviceServer(inputPortReceivers, outputCount,
@@ -126,7 +128,7 @@
}
final MidiReceiver[] outputReceivers = mServer.getOutputPortReceivers();
- // Create input thread
+ // Create input thread which will read from all input ports
new Thread("UsbMidiDevice input thread") {
@Override
public void run() {
@@ -161,38 +163,46 @@
}
}.start();
- // Create output thread
- new Thread("UsbMidiDevice output thread") {
- @Override
- public void run() {
- while (true) {
- MidiEvent event;
- try {
- event = (MidiEvent)mEventScheduler.waitNextEvent();
- } catch (InterruptedException e) {
- // try again
- continue;
+ // Create output thread for each output port
+ for (int port = 0; port < outputCount; port++) {
+ final MidiEventScheduler eventSchedulerF = mEventSchedulers[port];
+ final FileOutputStream outputStreamF = mOutputStreams[port];
+ final int portF = port;
+
+ new Thread("UsbMidiDevice output thread " + port) {
+ @Override
+ public void run() {
+ while (true) {
+ MidiEvent event;
+ try {
+ event = (MidiEvent)eventSchedulerF.waitNextEvent();
+ } catch (InterruptedException e) {
+ // try again
+ continue;
+ }
+ if (event == null) {
+ break;
+ }
+ try {
+ outputStreamF.write(event.data, 0, event.count);
+ } catch (IOException e) {
+ Log.e(TAG, "write failed for port " + portF);
+ }
+ eventSchedulerF.addEventToPool(event);
}
- if (event == null) {
- break;
- }
- try {
- mOutputStreams[event.portNumber].write(event.data, 0, event.count);
- } catch (IOException e) {
- Log.e(TAG, "write failed for port " + event.portNumber);
- }
- mEventScheduler.addEventToPool(event);
+ Log.d(TAG, "output thread exit");
}
- Log.d(TAG, "output thread exit");
- }
- }.start();
+ }.start();
+ }
return true;
}
@Override
public void close() throws IOException {
- mEventScheduler.close();
+ for (int i = 0; i < mEventSchedulers.length; i++) {
+ mEventSchedulers[i].close();
+ }
if (mServer != null) {
mServer.close();