Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3b4472fc authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "USB MIDI: Multijack MIDI 1.0 for MIDI 2.0 devices" into udc-dev

parents e8d5d68e 88e10487
Loading
Loading
Loading
Loading
+37 −16
Original line number Original line Diff line number Diff line
@@ -16,7 +16,6 @@


package com.android.internal.midi;
package com.android.internal.midi;


import java.util.Iterator;
import java.util.SortedMap;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.TreeMap;


@@ -26,11 +25,11 @@ import java.util.TreeMap;
 * And only one Thread can read from the buffer.
 * And only one Thread can read from the buffer.
 */
 */
public class EventScheduler {
public class EventScheduler {
    private static final long NANOS_PER_MILLI = 1000000;
    public static final long NANOS_PER_MILLI = 1000000;


    private final Object mLock = new Object();
    private final Object mLock = new Object();
    volatile private SortedMap<Long, FastEventQueue> mEventBuffer;
    protected volatile SortedMap<Long, FastEventQueue> mEventBuffer;
    private FastEventQueue mEventPool = null;
    protected FastEventQueue mEventPool = null;
    private int mMaxPoolSize = 200;
    private int mMaxPoolSize = 200;
    private boolean mClosed;
    private boolean mClosed;


@@ -38,9 +37,13 @@ public class EventScheduler {
        mEventBuffer = new TreeMap<Long, FastEventQueue>();
        mEventBuffer = new TreeMap<Long, FastEventQueue>();
    }
    }


    // If we keep at least one node in the list then it can be atomic
    /**
    // and non-blocking.
     * Class for a fast event queue.
    private class FastEventQueue {
     *
     * If we keep at least one node in the list then it can be atomic
     * and non-blocking.
     */
    public static class FastEventQueue {
        // One thread takes from the beginning of the list.
        // One thread takes from the beginning of the list.
        volatile SchedulableEvent mFirst;
        volatile SchedulableEvent mFirst;
        // A second thread returns events to the end of the list.
        // A second thread returns events to the end of the list.
@@ -48,7 +51,7 @@ public class EventScheduler {
        volatile long mEventsAdded;
        volatile long mEventsAdded;
        volatile long mEventsRemoved;
        volatile long mEventsRemoved;


        FastEventQueue(SchedulableEvent event) {
        public FastEventQueue(SchedulableEvent event) {
            mFirst = event;
            mFirst = event;
            mLast = mFirst;
            mLast = mFirst;
            mEventsAdded = 1;
            mEventsAdded = 1;
@@ -149,7 +152,8 @@ public class EventScheduler {
     * @param event
     * @param event
     */
     */
    public void add(SchedulableEvent event) {
    public void add(SchedulableEvent event) {
        synchronized (mLock) {
        Object lock = getLock();
        synchronized (lock) {
            FastEventQueue list = mEventBuffer.get(event.getTimestamp());
            FastEventQueue list = mEventBuffer.get(event.getTimestamp());
            if (list == null) {
            if (list == null) {
                long lowestTime = mEventBuffer.isEmpty() ? Long.MAX_VALUE
                long lowestTime = mEventBuffer.isEmpty() ? Long.MAX_VALUE
@@ -159,7 +163,7 @@ public class EventScheduler {
                // If the event we added is earlier than the previous earliest
                // If the event we added is earlier than the previous earliest
                // event then notify any threads waiting for the next event.
                // event then notify any threads waiting for the next event.
                if (event.getTimestamp() < lowestTime) {
                if (event.getTimestamp() < lowestTime) {
                    mLock.notify();
                    lock.notify();
                }
                }
            } else {
            } else {
                list.add(event);
                list.add(event);
@@ -167,7 +171,7 @@ public class EventScheduler {
        }
        }
    }
    }


    private SchedulableEvent removeNextEventLocked(long lowestTime) {
    protected SchedulableEvent removeNextEventLocked(long lowestTime) {
        SchedulableEvent event;
        SchedulableEvent event;
        FastEventQueue list = mEventBuffer.get(lowestTime);
        FastEventQueue list = mEventBuffer.get(lowestTime);
        // Remove list from tree if this is the last node.
        // Remove list from tree if this is the last node.
@@ -186,7 +190,8 @@ public class EventScheduler {
     */
     */
    public SchedulableEvent getNextEvent(long time) {
    public SchedulableEvent getNextEvent(long time) {
        SchedulableEvent event = null;
        SchedulableEvent event = null;
        synchronized (mLock) {
        Object lock = getLock();
        synchronized (lock) {
            if (!mEventBuffer.isEmpty()) {
            if (!mEventBuffer.isEmpty()) {
                long lowestTime = mEventBuffer.firstKey();
                long lowestTime = mEventBuffer.firstKey();
                // Is it time for this list to be processed?
                // Is it time for this list to be processed?
@@ -209,7 +214,8 @@ public class EventScheduler {
     */
     */
    public SchedulableEvent waitNextEvent() throws InterruptedException {
    public SchedulableEvent waitNextEvent() throws InterruptedException {
        SchedulableEvent event = null;
        SchedulableEvent event = null;
        synchronized (mLock) {
        Object lock = getLock();
        synchronized (lock) {
            while (!mClosed) {
            while (!mClosed) {
                long millisToWait = Integer.MAX_VALUE;
                long millisToWait = Integer.MAX_VALUE;
                if (!mEventBuffer.isEmpty()) {
                if (!mEventBuffer.isEmpty()) {
@@ -231,7 +237,7 @@ public class EventScheduler {
                        }
                        }
                    }
                    }
                }
                }
                mLock.wait((int) millisToWait);
                lock.wait((int) millisToWait);
            }
            }
        }
        }
        return event;
        return event;
@@ -242,10 +248,25 @@ public class EventScheduler {
        mEventBuffer = new TreeMap<Long, FastEventQueue>();
        mEventBuffer = new TreeMap<Long, FastEventQueue>();
    }
    }


    /**
     * Stops the EventScheduler.
     * The subscriber calling waitNextEvent() will get one final SchedulableEvent returning null.
     */
    public void close() {
    public void close() {
        synchronized (mLock) {
        Object lock = getLock();
        synchronized (lock) {
            mClosed = true;
            mClosed = true;
            mLock.notify();
            lock.notify();
        }
        }
    }
    }

    /**
     * Gets the lock. This doesn't lock it in anyway.
     * Subclasses can override this.
     *
     * @return Object
     */
    protected Object getLock() {
        return mLock;
    }
}
}
+128 −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();
        }
    }
}
+1 −1
Original line number Original line Diff line number Diff line
@@ -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) {
+212 −100

File changed.

Preview size limit exceeded, changes collapsed.

+117 −25
Original line number Original line Diff line number Diff line
@@ -16,12 +16,17 @@


package com.android.server.usb;
package com.android.server.usb;


import android.util.Log;

import java.io.ByteArrayOutputStream;
import java.io.ByteArrayOutputStream;


/**
/**
 * Converts between MIDI packets and USB MIDI 1.0 packets.
 * Converts between raw MIDI packets and USB MIDI 1.0 packets.
 * This is NOT thread-safe. Please handle locking outside this function for multiple threads.
 * For data mapping to an invalid cable number, this converter will use the first cable.
 */
 */
public class UsbMidiPacketConverter {
public class UsbMidiPacketConverter {
    private static final String TAG = "UsbMidiPacketConverter";


    // Refer to Table 4-1 in USB MIDI 1.0 spec.
    // Refer to Table 4-1 in USB MIDI 1.0 spec.
    private static final int[] PAYLOAD_SIZE = new int[]{
    private static final int[] PAYLOAD_SIZE = new int[]{
@@ -74,54 +79,133 @@ public class UsbMidiPacketConverter {
    private static final byte SYSEX_START_EXCLUSIVE = (byte) 0xF0;
    private static final byte SYSEX_START_EXCLUSIVE = (byte) 0xF0;
    private static final byte SYSEX_END_EXCLUSIVE = (byte) 0xF7;
    private static final byte SYSEX_END_EXCLUSIVE = (byte) 0xF7;


    private UsbMidiDecoder mUsbMidiDecoder = new UsbMidiDecoder();
    private UsbMidiEncoder[] mUsbMidiEncoders;
    private UsbMidiEncoder[] mUsbMidiEncoders;
    private ByteArrayOutputStream mEncoderOutputStream = new ByteArrayOutputStream();


    public UsbMidiPacketConverter(int numEncoders) {
    private UsbMidiDecoder mUsbMidiDecoder;
        mUsbMidiEncoders = new UsbMidiEncoder[numEncoders];
        for (int i = 0; i < numEncoders; i++) {
            mUsbMidiEncoders[i] = new UsbMidiEncoder();
        }
    }


    /**
    /**
     * Converts a USB MIDI array into a raw MIDI array.
     * Creates encoders.
     *
     *
     * @param usbMidiBytes the USB MIDI bytes to convert
     * createEncoders() must be called before raw MIDI can be converted to USB MIDI.
     * @param size the size of usbMidiBytes
     *
     * @return byte array of raw MIDI packets
     * @param size the number of encoders to create
     */
     */
    public byte[] usbMidiToRawMidi(byte[] usbMidiBytes, int size) {
    public void createEncoders(int size) {
        return mUsbMidiDecoder.decode(usbMidiBytes, size);
        mUsbMidiEncoders = new UsbMidiEncoder[size];
        for (int i = 0; i < size; i++) {
            mUsbMidiEncoders[i] = new UsbMidiEncoder(i);
        }
    }
    }


    /**
    /**
     * Converts a raw MIDI array into a USB MIDI array.
     * Converts a raw MIDI array into a USB MIDI array.
     *
     *
     * Call pullEncodedMidiPackets to retrieve the byte array.
     *
     * @param midiBytes the raw MIDI bytes to convert
     * @param midiBytes the raw MIDI bytes to convert
     * @param size the size of usbMidiBytes
     * @param size the size of usbMidiBytes
     * @param encoderId which encoder to use
     * @param encoderId which encoder to use
     */
    public void encodeMidiPackets(byte[] midiBytes, int size, int encoderId) {
        // Use the first encoder if the encoderId is invalid.
        if (encoderId >= mUsbMidiEncoders.length) {
            Log.w(TAG, "encoderId " + encoderId + " invalid");
            encoderId = 0;
        }
        byte[] encodedPacket = mUsbMidiEncoders[encoderId].encode(midiBytes, size);
        mEncoderOutputStream.write(encodedPacket, 0, encodedPacket.length);
    }

    /**
     * Returns the encoded MIDI packets from encodeMidiPackets
     *
     * @return byte array of USB MIDI packets
     * @return byte array of USB MIDI packets
     */
     */
    public byte[] rawMidiToUsbMidi(byte[] midiBytes, int size, int encoderId) {
    public byte[] pullEncodedMidiPackets() {
        return mUsbMidiEncoders[encoderId].encode(midiBytes, size);
        byte[] output = mEncoderOutputStream.toByteArray();
        mEncoderOutputStream.reset();
        return output;
    }

    /**
     * Creates decoders.
     *
     * createDecoders() must be called before USB MIDI can be converted to raw MIDI.
     *
     * @param size the number of decoders to create
     */
    public void createDecoders(int size) {
        mUsbMidiDecoder = new UsbMidiDecoder(size);
    }

    /**
     * Converts a USB MIDI array into a multiple MIDI arrays, one per cable.
     *
     * Call pullDecodedMidiPackets to retrieve the byte array.
     *
     * @param usbMidiBytes the USB MIDI bytes to convert
     * @param size the size of usbMidiBytes
     */
    public void decodeMidiPackets(byte[] usbMidiBytes, int size) {
        mUsbMidiDecoder.decode(usbMidiBytes, size);
    }

    /**
     * Returns the decoded MIDI packets from decodeMidiPackets
     *
     * @param cableNumber the cable to pull data from
     * @return byte array of raw MIDI packets
     */
    public byte[] pullDecodedMidiPackets(int cableNumber) {
        return mUsbMidiDecoder.pullBytes(cableNumber);
    }
    }


    private class UsbMidiDecoder {
    private class UsbMidiDecoder {
        int mNumJacks;
        ByteArrayOutputStream[] mDecodedByteArrays;

        UsbMidiDecoder(int numJacks) {
            mNumJacks = numJacks;
            mDecodedByteArrays = new ByteArrayOutputStream[numJacks];
            for (int i = 0; i < numJacks; i++) {
                mDecodedByteArrays[i] = new ByteArrayOutputStream();
            }
        }

        // Decodes the data from USB MIDI to raw MIDI.
        // Decodes the data from USB MIDI to raw MIDI.
        // Each valid 4 byte input maps to a 1-3 byte output.
        // Each valid 4 byte input maps to a 1-3 byte output.
        // Reference the USB MIDI 1.0 spec for more info.
        // Reference the USB MIDI 1.0 spec for more info.
        public byte[] decode(byte[] usbMidiBytes, int size) {
        public void decode(byte[] usbMidiBytes, int size) {
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            if (size % 4 != 0) {
                Log.w(TAG, "size " + size + " not multiple of 4");
            }
            for (int i = 0; i + 3 < size; i += 4) {
            for (int i = 0; i + 3 < size; i += 4) {
                int cableNumber = (usbMidiBytes[i] >> 4) & 0x0f;
                int codeIndex = usbMidiBytes[i] & 0x0f;
                int codeIndex = usbMidiBytes[i] & 0x0f;
                int numPayloadBytes = PAYLOAD_SIZE[codeIndex];
                int numPayloadBytes = PAYLOAD_SIZE[codeIndex];
                if (numPayloadBytes < 0) {
                if (numPayloadBytes < 0) {
                    continue;
                    continue;
                }
                }
                outputStream.write(usbMidiBytes, i + 1, numPayloadBytes);
                // Use the first cable if the cable number is invalid.
                if (cableNumber >= mNumJacks) {
                    Log.w(TAG, "cableNumber " + cableNumber + " invalid");
                    cableNumber = 0;
                }
                mDecodedByteArrays[cableNumber].write(usbMidiBytes, i + 1, numPayloadBytes);
            }
            }
            return outputStream.toByteArray();
        }

        public byte[] pullBytes(int cableNumber) {
            // Use the first cable if the cable number is invalid.
            if (cableNumber >= mNumJacks) {
                Log.w(TAG, "cableNumber " + cableNumber + " invalid");
                cableNumber = 0;
            }
            byte[] output = mDecodedByteArrays[cableNumber].toByteArray();
            mDecodedByteArrays[cableNumber].reset();
            return output;
        }
        }
    }
    }


@@ -135,6 +219,13 @@ public class UsbMidiPacketConverter {


        private byte[] mEmptyBytes = new byte[3]; // Used to fill out extra data
        private byte[] mEmptyBytes = new byte[3]; // Used to fill out extra data


        private byte mShiftedCableNumber;

        UsbMidiEncoder(int cableNumber) {
            // Jack Id is always the left nibble of every byte so shift this now.
            mShiftedCableNumber = (byte) (cableNumber << 4);
        }

        // Encodes the data from raw MIDI to USB MIDI.
        // Encodes the data from raw MIDI to USB MIDI.
        // Each valid 1-3 byte input maps to a 4 byte output.
        // Each valid 1-3 byte input maps to a 4 byte output.
        // Reference the USB MIDI 1.0 spec for more info.
        // Reference the USB MIDI 1.0 spec for more info.
@@ -153,7 +244,8 @@ public class UsbMidiPacketConverter {
                                midiBytes[curLocation];
                                midiBytes[curLocation];
                        mNumStoredSystemExclusiveBytes++;
                        mNumStoredSystemExclusiveBytes++;
                        if (mNumStoredSystemExclusiveBytes == 3) {
                        if (mNumStoredSystemExclusiveBytes == 3) {
                            outputStream.write(CODE_INDEX_NUMBER_SYSEX_STARTS_OR_CONTINUES);
                            outputStream.write(CODE_INDEX_NUMBER_SYSEX_STARTS_OR_CONTINUES
                                    | mShiftedCableNumber);
                            outputStream.write(mStoredSystemExclusiveBytes, 0, 3);
                            outputStream.write(mStoredSystemExclusiveBytes, 0, 3);
                            mNumStoredSystemExclusiveBytes = 0;
                            mNumStoredSystemExclusiveBytes = 0;
                        }
                        }
@@ -179,7 +271,7 @@ public class UsbMidiPacketConverter {
                    byte codeIndexNumber = (byte) ((midiBytes[curLocation] >> 4) & 0x0f);
                    byte codeIndexNumber = (byte) ((midiBytes[curLocation] >> 4) & 0x0f);
                    int channelMessageSize = PAYLOAD_SIZE[codeIndexNumber];
                    int channelMessageSize = PAYLOAD_SIZE[codeIndexNumber];
                    if (curLocation + channelMessageSize <= size) {
                    if (curLocation + channelMessageSize <= size) {
                        outputStream.write(codeIndexNumber);
                        outputStream.write(codeIndexNumber | mShiftedCableNumber);
                        outputStream.write(midiBytes, curLocation, channelMessageSize);
                        outputStream.write(midiBytes, curLocation, channelMessageSize);
                        // Fill in the rest of the bytes with 0.
                        // Fill in the rest of the bytes with 0.
                        outputStream.write(mEmptyBytes, 0, 3 - channelMessageSize);
                        outputStream.write(mEmptyBytes, 0, 3 - channelMessageSize);
@@ -197,8 +289,8 @@ public class UsbMidiPacketConverter {
                    curLocation++;
                    curLocation++;
                } else if (midiBytes[curLocation] == SYSEX_END_EXCLUSIVE) {
                } else if (midiBytes[curLocation] == SYSEX_END_EXCLUSIVE) {
                    // 1 byte is 0x05, 2 bytes is 0x06, and 3 bytes is 0x07
                    // 1 byte is 0x05, 2 bytes is 0x06, and 3 bytes is 0x07
                    outputStream.write(CODE_INDEX_NUMBER_SYSEX_END_SINGLE_BYTE
                    outputStream.write((CODE_INDEX_NUMBER_SYSEX_END_SINGLE_BYTE
                            + mNumStoredSystemExclusiveBytes);
                            + mNumStoredSystemExclusiveBytes) | mShiftedCableNumber);
                    mStoredSystemExclusiveBytes[mNumStoredSystemExclusiveBytes] =
                    mStoredSystemExclusiveBytes[mNumStoredSystemExclusiveBytes] =
                            midiBytes[curLocation];
                            midiBytes[curLocation];
                    mNumStoredSystemExclusiveBytes++;
                    mNumStoredSystemExclusiveBytes++;
@@ -218,7 +310,7 @@ public class UsbMidiPacketConverter {
                    } else {
                    } else {
                        int systemMessageSize = PAYLOAD_SIZE[codeIndexNumber];
                        int systemMessageSize = PAYLOAD_SIZE[codeIndexNumber];
                        if (curLocation + systemMessageSize <= size) {
                        if (curLocation + systemMessageSize <= size) {
                            outputStream.write(codeIndexNumber);
                            outputStream.write(codeIndexNumber | mShiftedCableNumber);
                            outputStream.write(midiBytes, curLocation, systemMessageSize);
                            outputStream.write(midiBytes, curLocation, systemMessageSize);
                            // Fill in the rest of the bytes with 0.
                            // Fill in the rest of the bytes with 0.
                            outputStream.write(mEmptyBytes, 0, 3 - systemMessageSize);
                            outputStream.write(mEmptyBytes, 0, 3 - systemMessageSize);
@@ -236,7 +328,7 @@ public class UsbMidiPacketConverter {
        }
        }


        private void writeSingleByte(ByteArrayOutputStream outputStream, byte byteToWrite) {
        private void writeSingleByte(ByteArrayOutputStream outputStream, byte byteToWrite) {
            outputStream.write(CODE_INDEX_NUMBER_SINGLE_BYTE);
            outputStream.write(CODE_INDEX_NUMBER_SINGLE_BYTE | mShiftedCableNumber);
            outputStream.write(byteToWrite);
            outputStream.write(byteToWrite);
            outputStream.write(0);
            outputStream.write(0);
            outputStream.write(0);
            outputStream.write(0);
Loading