Loading core/java/com/android/internal/midi/EventScheduler.java +37 −16 Original line number Original line Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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. Loading @@ -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; Loading Loading @@ -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 Loading @@ -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); Loading @@ -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. Loading @@ -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? Loading @@ -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()) { Loading @@ -231,7 +237,7 @@ public class EventScheduler { } } } } } } mLock.wait((int) millisToWait); lock.wait((int) millisToWait); } } } } return event; return event; Loading @@ -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; } } } core/java/com/android/internal/midi/MidiEventMultiScheduler.java 0 → 100644 +128 −0 Original line number Original line Diff line number Diff line /* * 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(); } } } core/java/com/android/internal/midi/MidiEventScheduler.java +1 −1 Original line number Original line Diff line number Diff line Loading @@ -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) { Loading services/usb/java/com/android/server/usb/UsbDirectMidiDevice.java +212 −100 File changed.Preview size limit exceeded, changes collapsed. Show changes services/usb/java/com/android/server/usb/UsbMidiPacketConverter.java +117 −25 Original line number Original line Diff line number Diff line Loading @@ -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[]{ Loading Loading @@ -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; } } } } Loading @@ -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. Loading @@ -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; } } Loading @@ -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); Loading @@ -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++; Loading @@ -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); Loading @@ -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); Loading Loading
core/java/com/android/internal/midi/EventScheduler.java +37 −16 Original line number Original line Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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. Loading @@ -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; Loading Loading @@ -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 Loading @@ -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); Loading @@ -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. Loading @@ -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? Loading @@ -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()) { Loading @@ -231,7 +237,7 @@ public class EventScheduler { } } } } } } mLock.wait((int) millisToWait); lock.wait((int) millisToWait); } } } } return event; return event; Loading @@ -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; } } }
core/java/com/android/internal/midi/MidiEventMultiScheduler.java 0 → 100644 +128 −0 Original line number Original line Diff line number Diff line /* * 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(); } } }
core/java/com/android/internal/midi/MidiEventScheduler.java +1 −1 Original line number Original line Diff line number Diff line Loading @@ -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) { Loading
services/usb/java/com/android/server/usb/UsbDirectMidiDevice.java +212 −100 File changed.Preview size limit exceeded, changes collapsed. Show changes
services/usb/java/com/android/server/usb/UsbMidiPacketConverter.java +117 −25 Original line number Original line Diff line number Diff line Loading @@ -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[]{ Loading Loading @@ -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; } } } } Loading @@ -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. Loading @@ -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; } } Loading @@ -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); Loading @@ -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++; Loading @@ -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); Loading @@ -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); Loading