USB MIDI: Multijack MIDI 1.0 for MIDI 2.0 devices
MIDI 2.0 devices only show one input and output port for its backup MIDI 1.0 path. This CL reads the jack counts and creates encoders and decoders accordingly. UsbMidiPacketConverter has been refactored to use cable numbers for multiplexing USB MIDI streams. Bug: 266473128 Test: MidiScope/MidiKeyboard on multiport MIDI device Test: UmpMidiScope on MIDI 2.0 devices Change-Id: I58799393ab0bddf7c6a6c63f1ab014accf1ab10e
This commit is contained in:
@@ -16,7 +16,6 @@
|
||||
|
||||
package com.android.internal.midi;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.SortedMap;
|
||||
import java.util.TreeMap;
|
||||
|
||||
@@ -26,11 +25,11 @@ import java.util.TreeMap;
|
||||
* And only one Thread can read from the buffer.
|
||||
*/
|
||||
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();
|
||||
volatile private SortedMap<Long, FastEventQueue> mEventBuffer;
|
||||
private FastEventQueue mEventPool = null;
|
||||
protected volatile SortedMap<Long, FastEventQueue> mEventBuffer;
|
||||
protected FastEventQueue mEventPool = null;
|
||||
private int mMaxPoolSize = 200;
|
||||
private boolean mClosed;
|
||||
|
||||
@@ -38,9 +37,13 @@ public class EventScheduler {
|
||||
mEventBuffer = new TreeMap<Long, FastEventQueue>();
|
||||
}
|
||||
|
||||
// If we keep at least one node in the list then it can be atomic
|
||||
// and non-blocking.
|
||||
private class FastEventQueue {
|
||||
/**
|
||||
* Class for a fast event queue.
|
||||
*
|
||||
* 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.
|
||||
volatile SchedulableEvent mFirst;
|
||||
// A second thread returns events to the end of the list.
|
||||
@@ -48,7 +51,7 @@ public class EventScheduler {
|
||||
volatile long mEventsAdded;
|
||||
volatile long mEventsRemoved;
|
||||
|
||||
FastEventQueue(SchedulableEvent event) {
|
||||
public FastEventQueue(SchedulableEvent event) {
|
||||
mFirst = event;
|
||||
mLast = mFirst;
|
||||
mEventsAdded = 1;
|
||||
@@ -149,7 +152,8 @@ public class EventScheduler {
|
||||
* @param event
|
||||
*/
|
||||
public void add(SchedulableEvent event) {
|
||||
synchronized (mLock) {
|
||||
Object lock = getLock();
|
||||
synchronized (lock) {
|
||||
FastEventQueue list = mEventBuffer.get(event.getTimestamp());
|
||||
if (list == null) {
|
||||
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
|
||||
// event then notify any threads waiting for the next event.
|
||||
if (event.getTimestamp() < lowestTime) {
|
||||
mLock.notify();
|
||||
lock.notify();
|
||||
}
|
||||
} else {
|
||||
list.add(event);
|
||||
@@ -167,7 +171,7 @@ public class EventScheduler {
|
||||
}
|
||||
}
|
||||
|
||||
private SchedulableEvent removeNextEventLocked(long lowestTime) {
|
||||
protected SchedulableEvent removeNextEventLocked(long lowestTime) {
|
||||
SchedulableEvent event;
|
||||
FastEventQueue list = mEventBuffer.get(lowestTime);
|
||||
// Remove list from tree if this is the last node.
|
||||
@@ -186,7 +190,8 @@ public class EventScheduler {
|
||||
*/
|
||||
public SchedulableEvent getNextEvent(long time) {
|
||||
SchedulableEvent event = null;
|
||||
synchronized (mLock) {
|
||||
Object lock = getLock();
|
||||
synchronized (lock) {
|
||||
if (!mEventBuffer.isEmpty()) {
|
||||
long lowestTime = mEventBuffer.firstKey();
|
||||
// Is it time for this list to be processed?
|
||||
@@ -209,7 +214,8 @@ public class EventScheduler {
|
||||
*/
|
||||
public SchedulableEvent waitNextEvent() throws InterruptedException {
|
||||
SchedulableEvent event = null;
|
||||
synchronized (mLock) {
|
||||
Object lock = getLock();
|
||||
synchronized (lock) {
|
||||
while (!mClosed) {
|
||||
long millisToWait = Integer.MAX_VALUE;
|
||||
if (!mEventBuffer.isEmpty()) {
|
||||
@@ -231,7 +237,7 @@ public class EventScheduler {
|
||||
}
|
||||
}
|
||||
}
|
||||
mLock.wait((int) millisToWait);
|
||||
lock.wait((int) millisToWait);
|
||||
}
|
||||
}
|
||||
return event;
|
||||
@@ -242,10 +248,25 @@ public class EventScheduler {
|
||||
mEventBuffer = new TreeMap<Long, FastEventQueue>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the EventScheduler.
|
||||
* The subscriber calling waitNextEvent() will get one final SchedulableEvent returning null.
|
||||
*/
|
||||
public void close() {
|
||||
synchronized (mLock) {
|
||||
Object lock = getLock();
|
||||
synchronized (lock) {
|
||||
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
core/java/com/android/internal/midi/MidiEventMultiScheduler.java
Normal file
128
core/java/com/android/internal/midi/MidiEventMultiScheduler.java
Normal file
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -79,7 +79,7 @@ public class MidiEventScheduler extends EventScheduler {
|
||||
/**
|
||||
* 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) {
|
||||
MidiEvent event;
|
||||
if (count > POOL_EVENT_SIZE) {
|
||||
|
||||
@@ -19,7 +19,6 @@ package com.android.server.usb;
|
||||
import android.annotation.NonNull;
|
||||
import android.content.Context;
|
||||
import android.hardware.usb.UsbConfiguration;
|
||||
import android.hardware.usb.UsbConstants;
|
||||
import android.hardware.usb.UsbDevice;
|
||||
import android.hardware.usb.UsbDeviceConnection;
|
||||
import android.hardware.usb.UsbEndpoint;
|
||||
@@ -35,9 +34,12 @@ import android.os.Bundle;
|
||||
import android.service.usb.UsbDirectMidiDeviceProto;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.midi.MidiEventMultiScheduler;
|
||||
import com.android.internal.midi.MidiEventScheduler;
|
||||
import com.android.internal.midi.MidiEventScheduler.MidiEvent;
|
||||
import com.android.internal.util.dump.DualDumpOutputStream;
|
||||
import com.android.server.usb.descriptors.UsbACMidi10Endpoint;
|
||||
import com.android.server.usb.descriptors.UsbDescriptor;
|
||||
import com.android.server.usb.descriptors.UsbDescriptorParser;
|
||||
import com.android.server.usb.descriptors.UsbEndpointDescriptor;
|
||||
import com.android.server.usb.descriptors.UsbInterfaceDescriptor;
|
||||
@@ -45,6 +47,7 @@ import com.android.server.usb.descriptors.UsbMidiBlockParser;
|
||||
|
||||
import libcore.io.IoUtils;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
@@ -56,7 +59,7 @@ import java.util.ArrayList;
|
||||
*/
|
||||
public final class UsbDirectMidiDevice implements Closeable {
|
||||
private static final String TAG = "UsbDirectMidiDevice";
|
||||
private static final boolean DEBUG = false;
|
||||
private static final boolean DEBUG = true;
|
||||
|
||||
private Context mContext;
|
||||
private String mName;
|
||||
@@ -74,9 +77,6 @@ public final class UsbDirectMidiDevice implements Closeable {
|
||||
|
||||
private MidiDeviceServer mServer;
|
||||
|
||||
// event schedulers for each input port of the physical device
|
||||
private MidiEventScheduler[] mEventSchedulers;
|
||||
|
||||
// Timeout for sending a packet to a device.
|
||||
// If bulkTransfer times out, retry sending the packet up to 20 times.
|
||||
private static final int BULK_TRANSFER_TIMEOUT_MILLISECONDS = 50;
|
||||
@@ -86,8 +86,21 @@ public final class UsbDirectMidiDevice implements Closeable {
|
||||
private static final int THREAD_JOIN_TIMEOUT_MILLISECONDS = 200;
|
||||
|
||||
private ArrayList<UsbDeviceConnection> mUsbDeviceConnections;
|
||||
|
||||
// Array of endpoints by device connection.
|
||||
private ArrayList<ArrayList<UsbEndpoint>> mInputUsbEndpoints;
|
||||
private ArrayList<ArrayList<UsbEndpoint>> mOutputUsbEndpoints;
|
||||
|
||||
// Array of cable counts by device connection.
|
||||
// Each number here maps to an entry in mInputUsbEndpoints or mOutputUsbEndpoints.
|
||||
// This is needed because this info is part of UsbEndpointDescriptor but not UsbEndpoint.
|
||||
private ArrayList<ArrayList<Integer>> mInputUsbEndpointCableCounts;
|
||||
private ArrayList<ArrayList<Integer>> mOutputUsbEndpointCableCounts;
|
||||
|
||||
// Array of event schedulers by device connection.
|
||||
// Each number here maps to an entry in mOutputUsbEndpoints.
|
||||
private ArrayList<ArrayList<MidiEventMultiScheduler>> mMidiEventMultiSchedulers;
|
||||
|
||||
private ArrayList<Thread> mThreads;
|
||||
|
||||
private UsbMidiBlockParser mMidiBlockParser = new UsbMidiBlockParser();
|
||||
@@ -97,8 +110,6 @@ public final class UsbDirectMidiDevice implements Closeable {
|
||||
private boolean mIsOpen;
|
||||
private boolean mServerAvailable;
|
||||
|
||||
private UsbMidiPacketConverter mUsbMidiPacketConverter;
|
||||
|
||||
private PowerBoostSetter mPowerBoostSetter = null;
|
||||
|
||||
private static final byte MESSAGE_TYPE_MIDI_1_CHANNEL_VOICE = 0x02;
|
||||
@@ -238,9 +249,9 @@ public final class UsbDirectMidiDevice implements Closeable {
|
||||
interfaceDescriptor.getEndpointDescriptor(endpointIndex);
|
||||
// 0 is output, 1 << 7 is input.
|
||||
if (endpoint.getDirection() == 0) {
|
||||
numOutputs++;
|
||||
numOutputs += getNumJacks(endpoint);
|
||||
} else {
|
||||
numInputs++;
|
||||
numInputs += getNumJacks(endpoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -307,19 +318,21 @@ public final class UsbDirectMidiDevice implements Closeable {
|
||||
Log.d(TAG, "openLocked()");
|
||||
UsbManager manager = mContext.getSystemService(UsbManager.class);
|
||||
|
||||
// Converting from raw MIDI to USB MIDI is not thread-safe.
|
||||
// UsbMidiPacketConverter creates a converter from raw MIDI
|
||||
// to USB MIDI for each USB output.
|
||||
mUsbMidiPacketConverter = new UsbMidiPacketConverter(mNumOutputs);
|
||||
|
||||
mUsbDeviceConnections = new ArrayList<UsbDeviceConnection>();
|
||||
mInputUsbEndpoints = new ArrayList<ArrayList<UsbEndpoint>>();
|
||||
mOutputUsbEndpoints = new ArrayList<ArrayList<UsbEndpoint>>();
|
||||
mInputUsbEndpointCableCounts = new ArrayList<ArrayList<Integer>>();
|
||||
mOutputUsbEndpointCableCounts = new ArrayList<ArrayList<Integer>>();
|
||||
mMidiEventMultiSchedulers = new ArrayList<ArrayList<MidiEventMultiScheduler>>();
|
||||
mThreads = new ArrayList<Thread>();
|
||||
|
||||
for (int interfaceIndex = 0; interfaceIndex < mUsbInterfaces.size(); interfaceIndex++) {
|
||||
ArrayList<UsbEndpoint> inputEndpoints = new ArrayList<UsbEndpoint>();
|
||||
ArrayList<UsbEndpoint> outputEndpoints = new ArrayList<UsbEndpoint>();
|
||||
ArrayList<Integer> inputEndpointCableCounts = new ArrayList<Integer>();
|
||||
ArrayList<Integer> outputEndpointCableCounts = new ArrayList<Integer>();
|
||||
ArrayList<MidiEventMultiScheduler> midiEventMultiSchedulers =
|
||||
new ArrayList<MidiEventMultiScheduler>();
|
||||
UsbInterfaceDescriptor interfaceDescriptor = mUsbInterfaces.get(interfaceIndex);
|
||||
for (int endpointIndex = 0; endpointIndex < interfaceDescriptor.getNumEndpoints();
|
||||
endpointIndex++) {
|
||||
@@ -328,8 +341,13 @@ public final class UsbDirectMidiDevice implements Closeable {
|
||||
// 0 is output, 1 << 7 is input.
|
||||
if (endpoint.getDirection() == 0) {
|
||||
outputEndpoints.add(endpoint.toAndroid(mParser));
|
||||
outputEndpointCableCounts.add(getNumJacks(endpoint));
|
||||
MidiEventMultiScheduler scheduler =
|
||||
new MidiEventMultiScheduler(getNumJacks(endpoint));
|
||||
midiEventMultiSchedulers.add(scheduler);
|
||||
} else {
|
||||
inputEndpoints.add(endpoint.toAndroid(mParser));
|
||||
inputEndpointCableCounts.add(getNumJacks(endpoint));
|
||||
}
|
||||
}
|
||||
if (!outputEndpoints.isEmpty() || !inputEndpoints.isEmpty()) {
|
||||
@@ -341,40 +359,69 @@ public final class UsbDirectMidiDevice implements Closeable {
|
||||
mUsbDeviceConnections.add(connection);
|
||||
mInputUsbEndpoints.add(inputEndpoints);
|
||||
mOutputUsbEndpoints.add(outputEndpoints);
|
||||
mInputUsbEndpointCableCounts.add(inputEndpointCableCounts);
|
||||
mOutputUsbEndpointCableCounts.add(outputEndpointCableCounts);
|
||||
mMidiEventMultiSchedulers.add(midiEventMultiSchedulers);
|
||||
}
|
||||
}
|
||||
|
||||
mEventSchedulers = new MidiEventScheduler[mNumOutputs];
|
||||
|
||||
for (int i = 0; i < mNumOutputs; i++) {
|
||||
MidiEventScheduler scheduler = new MidiEventScheduler();
|
||||
mEventSchedulers[i] = scheduler;
|
||||
mMidiInputPortReceivers[i].setReceiver(scheduler.getReceiver());
|
||||
// Set up event schedulers
|
||||
int outputIndex = 0;
|
||||
for (int connectionIndex = 0; connectionIndex < mMidiEventMultiSchedulers.size();
|
||||
connectionIndex++) {
|
||||
for (int endpointIndex = 0;
|
||||
endpointIndex < mMidiEventMultiSchedulers.get(connectionIndex).size();
|
||||
endpointIndex++) {
|
||||
int cableCount =
|
||||
mOutputUsbEndpointCableCounts.get(connectionIndex).get(endpointIndex);
|
||||
MidiEventMultiScheduler multiScheduler =
|
||||
mMidiEventMultiSchedulers.get(connectionIndex).get(endpointIndex);
|
||||
for (int cableNumber = 0; cableNumber < cableCount; cableNumber++) {
|
||||
MidiEventScheduler scheduler = multiScheduler.getEventScheduler(cableNumber);
|
||||
mMidiInputPortReceivers[outputIndex].setReceiver(scheduler.getReceiver());
|
||||
outputIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final MidiReceiver[] outputReceivers = mServer.getOutputPortReceivers();
|
||||
|
||||
// Create input thread for each input port of the physical device
|
||||
int portNumber = 0;
|
||||
int portStartNumber = 0;
|
||||
for (int connectionIndex = 0; connectionIndex < mInputUsbEndpoints.size();
|
||||
connectionIndex++) {
|
||||
for (int endpointIndex = 0;
|
||||
endpointIndex < mInputUsbEndpoints.get(connectionIndex).size();
|
||||
endpointIndex++) {
|
||||
// Each USB endpoint maps to one or more outputReceivers. USB MIDI data from an
|
||||
// endpoint should be sent to the appropriate outputReceiver. A new thread is
|
||||
// created and waits for incoming USB data. Once the data is received, it is added
|
||||
// to the packet converter. The packet converter acts as an inverse multiplexer.
|
||||
// With a for loop, data is pulled per cable and sent to the correct output
|
||||
// receiver. The first byte of each legacy MIDI 1.0 USB message indicates which
|
||||
// cable the data should be used and is how the reverse multiplexer directs data.
|
||||
// For MIDI UMP endpoints, a multiplexer is not needed as we are just swapping
|
||||
// the endianness of the packets.
|
||||
final UsbDeviceConnection connectionFinal =
|
||||
mUsbDeviceConnections.get(connectionIndex);
|
||||
final UsbEndpoint endpointFinal =
|
||||
mInputUsbEndpoints.get(connectionIndex).get(endpointIndex);
|
||||
final int portFinal = portNumber;
|
||||
final int portStartFinal = portStartNumber;
|
||||
final int cableCountFinal =
|
||||
mInputUsbEndpointCableCounts.get(connectionIndex).get(endpointIndex);
|
||||
|
||||
Thread newThread = new Thread("UsbDirectMidiDevice input thread " + portFinal) {
|
||||
Thread newThread = new Thread("UsbDirectMidiDevice input thread "
|
||||
+ portStartFinal) {
|
||||
@Override
|
||||
public void run() {
|
||||
final UsbRequest request = new UsbRequest();
|
||||
final UsbMidiPacketConverter packetConverter = new UsbMidiPacketConverter();
|
||||
packetConverter.createDecoders(cableCountFinal);
|
||||
try {
|
||||
request.initialize(connectionFinal, endpointFinal);
|
||||
byte[] inputBuffer = new byte[endpointFinal.getMaxPacketSize()];
|
||||
while (true) {
|
||||
boolean keepGoing = true;
|
||||
while (keepGoing) {
|
||||
if (Thread.currentThread().interrupted()) {
|
||||
Log.w(TAG, "input thread interrupted");
|
||||
break;
|
||||
@@ -403,42 +450,56 @@ public final class UsbDirectMidiDevice implements Closeable {
|
||||
logByteArray("Input before conversion ", inputBuffer,
|
||||
0, bytesRead);
|
||||
}
|
||||
|
||||
// Add packets into the packet decoder.
|
||||
if (!mIsUniversalMidiDevice) {
|
||||
packetConverter.decodeMidiPackets(inputBuffer, bytesRead);
|
||||
}
|
||||
|
||||
byte[] convertedArray;
|
||||
if (mIsUniversalMidiDevice) {
|
||||
// For USB, each 32 bit word of a UMP is
|
||||
// sent with the least significant byte first.
|
||||
convertedArray = swapEndiannessPerWord(inputBuffer,
|
||||
bytesRead);
|
||||
} else {
|
||||
if (mUsbMidiPacketConverter == null) {
|
||||
Log.w(TAG, "mUsbMidiPacketConverter is null");
|
||||
for (int cableNumber = 0; cableNumber < cableCountFinal;
|
||||
cableNumber++) {
|
||||
if (mIsUniversalMidiDevice) {
|
||||
// For USB, each 32 bit word of a UMP is
|
||||
// sent with the least significant byte first.
|
||||
convertedArray = swapEndiannessPerWord(inputBuffer,
|
||||
bytesRead);
|
||||
} else {
|
||||
convertedArray =
|
||||
packetConverter.pullDecodedMidiPackets(
|
||||
cableNumber);
|
||||
}
|
||||
|
||||
if (DEBUG) {
|
||||
logByteArray("Input " + cableNumber
|
||||
+ " after conversion ", convertedArray, 0,
|
||||
convertedArray.length);
|
||||
}
|
||||
|
||||
if (convertedArray.length == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((outputReceivers == null)
|
||||
|| (outputReceivers[portStartFinal + cableNumber]
|
||||
== null)) {
|
||||
Log.w(TAG, "outputReceivers is null");
|
||||
keepGoing = false;
|
||||
break;
|
||||
}
|
||||
convertedArray =
|
||||
mUsbMidiPacketConverter.usbMidiToRawMidi(
|
||||
inputBuffer, bytesRead);
|
||||
}
|
||||
outputReceivers[portStartFinal + cableNumber].send(
|
||||
convertedArray, 0, convertedArray.length,
|
||||
timestamp);
|
||||
|
||||
if (DEBUG) {
|
||||
logByteArray("Input after conversion ", convertedArray,
|
||||
0, convertedArray.length);
|
||||
}
|
||||
|
||||
if ((outputReceivers == null)
|
||||
|| (outputReceivers[portFinal] == null)) {
|
||||
Log.w(TAG, "outputReceivers is null");
|
||||
break;
|
||||
}
|
||||
outputReceivers[portFinal].send(convertedArray, 0,
|
||||
convertedArray.length, timestamp);
|
||||
|
||||
// Boost power if there seems to be a voice message.
|
||||
// For legacy devices, boost when message is more than size 1.
|
||||
// For UMP devices, boost for channel voice messages.
|
||||
if ((mPowerBoostSetter != null && convertedArray.length > 1)
|
||||
&& (!mIsUniversalMidiDevice
|
||||
|| isChannelVoiceMessage(convertedArray))) {
|
||||
mPowerBoostSetter.boostPower();
|
||||
// Boost power if there seems to be a voice message.
|
||||
// For legacy devices, boost if message length > 1.
|
||||
// For UMP devices, boost for channel voice messages.
|
||||
if ((mPowerBoostSetter != null
|
||||
&& convertedArray.length > 1)
|
||||
&& (!mIsUniversalMidiDevice
|
||||
|| isChannelVoiceMessage(convertedArray))) {
|
||||
mPowerBoostSetter.boostPower();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -454,64 +515,93 @@ public final class UsbDirectMidiDevice implements Closeable {
|
||||
};
|
||||
newThread.start();
|
||||
mThreads.add(newThread);
|
||||
portNumber++;
|
||||
portStartNumber += cableCountFinal;
|
||||
}
|
||||
}
|
||||
|
||||
// Create output thread for each output port of the physical device
|
||||
portNumber = 0;
|
||||
portStartNumber = 0;
|
||||
for (int connectionIndex = 0; connectionIndex < mOutputUsbEndpoints.size();
|
||||
connectionIndex++) {
|
||||
for (int endpointIndex = 0;
|
||||
endpointIndex < mOutputUsbEndpoints.get(connectionIndex).size();
|
||||
endpointIndex++) {
|
||||
// Each USB endpoint maps to one or more MIDI ports. Each port has an event
|
||||
// scheduler that is used to pull incoming raw MIDI bytes from Android apps.
|
||||
// With a MidiEventMultiScheduler, data can be pulled if any of the schedulers
|
||||
// have new incoming data. This data is then packaged as USB MIDI packets before
|
||||
// getting sent through USB. One thread will be created per endpoint that pulls
|
||||
// data from all relevant event schedulers. Raw MIDI from the event schedulers
|
||||
// will be converted to the correct USB MIDI format before getting sent through
|
||||
// USB.
|
||||
|
||||
final UsbDeviceConnection connectionFinal =
|
||||
mUsbDeviceConnections.get(connectionIndex);
|
||||
final UsbEndpoint endpointFinal =
|
||||
mOutputUsbEndpoints.get(connectionIndex).get(endpointIndex);
|
||||
final int portFinal = portNumber;
|
||||
final MidiEventScheduler eventSchedulerFinal = mEventSchedulers[portFinal];
|
||||
final int portStartFinal = portStartNumber;
|
||||
final int cableCountFinal =
|
||||
mOutputUsbEndpointCableCounts.get(connectionIndex).get(endpointIndex);
|
||||
final MidiEventMultiScheduler multiSchedulerFinal =
|
||||
mMidiEventMultiSchedulers.get(connectionIndex).get(endpointIndex);
|
||||
|
||||
Thread newThread = new Thread("UsbDirectMidiDevice output thread " + portFinal) {
|
||||
Thread newThread = new Thread("UsbDirectMidiDevice output write thread "
|
||||
+ portStartFinal) {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
while (true) {
|
||||
if (Thread.currentThread().interrupted()) {
|
||||
Log.w(TAG, "output thread interrupted");
|
||||
final ByteArrayOutputStream midi2ByteStream =
|
||||
new ByteArrayOutputStream();
|
||||
final UsbMidiPacketConverter packetConverter =
|
||||
new UsbMidiPacketConverter();
|
||||
packetConverter.createEncoders(cableCountFinal);
|
||||
boolean isInterrupted = false;
|
||||
while (!isInterrupted) {
|
||||
boolean wasSuccessful = multiSchedulerFinal.waitNextEvent();
|
||||
if (!wasSuccessful) {
|
||||
Log.d(TAG, "output thread closed");
|
||||
break;
|
||||
}
|
||||
MidiEvent event;
|
||||
try {
|
||||
event = (MidiEvent) eventSchedulerFinal.waitNextEvent();
|
||||
} catch (InterruptedException e) {
|
||||
Log.w(TAG, "event scheduler interrupted");
|
||||
break;
|
||||
}
|
||||
if (event == null) {
|
||||
Log.w(TAG, "event is null");
|
||||
break;
|
||||
}
|
||||
|
||||
if (DEBUG) {
|
||||
logByteArray("Output before conversion ", event.data, 0,
|
||||
event.count);
|
||||
}
|
||||
|
||||
byte[] convertedArray;
|
||||
if (mIsUniversalMidiDevice) {
|
||||
// For USB, each 32 bit word of a UMP is
|
||||
// sent with the least significant byte first.
|
||||
convertedArray = swapEndiannessPerWord(event.data,
|
||||
event.count);
|
||||
} else {
|
||||
if (mUsbMidiPacketConverter == null) {
|
||||
Log.w(TAG, "mUsbMidiPacketConverter is null");
|
||||
break;
|
||||
long now = System.nanoTime();
|
||||
for (int cableNumber = 0; cableNumber < cableCountFinal;
|
||||
cableNumber++) {
|
||||
MidiEventScheduler eventScheduler =
|
||||
multiSchedulerFinal.getEventScheduler(cableNumber);
|
||||
MidiEvent event =
|
||||
(MidiEvent) eventScheduler.getNextEvent(now);
|
||||
while (event != null) {
|
||||
if (DEBUG) {
|
||||
logByteArray("Output before conversion ",
|
||||
event.data, 0, event.count);
|
||||
}
|
||||
if (mIsUniversalMidiDevice) {
|
||||
// For USB, each 32 bit word of a UMP is
|
||||
// sent with the least significant byte first.
|
||||
byte[] convertedArray = swapEndiannessPerWord(
|
||||
event.data, event.count);
|
||||
midi2ByteStream.write(convertedArray, 0,
|
||||
convertedArray.length);
|
||||
} else {
|
||||
packetConverter.encodeMidiPackets(event.data,
|
||||
event.count, cableNumber);
|
||||
}
|
||||
eventScheduler.addEventToPool(event);
|
||||
event = (MidiEvent) eventScheduler.getNextEvent(now);
|
||||
}
|
||||
}
|
||||
|
||||
if (Thread.currentThread().interrupted()) {
|
||||
Log.d(TAG, "output thread interrupted");
|
||||
break;
|
||||
}
|
||||
|
||||
byte[] convertedArray = new byte[0];
|
||||
if (mIsUniversalMidiDevice) {
|
||||
convertedArray = midi2ByteStream.toByteArray();
|
||||
midi2ByteStream.reset();
|
||||
} else {
|
||||
convertedArray =
|
||||
mUsbMidiPacketConverter.rawMidiToUsbMidi(
|
||||
event.data, event.count, portFinal);
|
||||
packetConverter.pullEncodedMidiPackets();
|
||||
}
|
||||
|
||||
if (DEBUG) {
|
||||
@@ -519,7 +609,6 @@ public final class UsbDirectMidiDevice implements Closeable {
|
||||
convertedArray.length);
|
||||
}
|
||||
|
||||
boolean isInterrupted = false;
|
||||
// Split the packet into multiple if they are greater than the
|
||||
// endpoint's max packet size.
|
||||
for (int curPacketStart = 0;
|
||||
@@ -557,11 +646,9 @@ public final class UsbDirectMidiDevice implements Closeable {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isInterrupted == true) {
|
||||
break;
|
||||
}
|
||||
eventSchedulerFinal.addEventToPool(event);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Log.w(TAG, "output thread: ", e);
|
||||
} catch (NullPointerException e) {
|
||||
Log.e(TAG, "output thread: ", e);
|
||||
}
|
||||
@@ -570,7 +657,7 @@ public final class UsbDirectMidiDevice implements Closeable {
|
||||
};
|
||||
newThread.start();
|
||||
mThreads.add(newThread);
|
||||
portNumber++;
|
||||
portStartNumber += cableCountFinal;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -666,11 +753,21 @@ public final class UsbDirectMidiDevice implements Closeable {
|
||||
}
|
||||
mThreads = null;
|
||||
|
||||
for (int i = 0; i < mEventSchedulers.length; i++) {
|
||||
for (int i = 0; i < mMidiInputPortReceivers.length; i++) {
|
||||
mMidiInputPortReceivers[i].setReceiver(null);
|
||||
mEventSchedulers[i].close();
|
||||
}
|
||||
mEventSchedulers = null;
|
||||
|
||||
for (int connectionIndex = 0; connectionIndex < mMidiEventMultiSchedulers.size();
|
||||
connectionIndex++) {
|
||||
for (int endpointIndex = 0;
|
||||
endpointIndex < mMidiEventMultiSchedulers.get(connectionIndex).size();
|
||||
endpointIndex++) {
|
||||
MidiEventMultiScheduler multiScheduler =
|
||||
mMidiEventMultiSchedulers.get(connectionIndex).get(endpointIndex);
|
||||
multiScheduler.close();
|
||||
}
|
||||
}
|
||||
mMidiEventMultiSchedulers = null;
|
||||
|
||||
for (UsbDeviceConnection connection : mUsbDeviceConnections) {
|
||||
connection.close();
|
||||
@@ -678,8 +775,8 @@ public final class UsbDirectMidiDevice implements Closeable {
|
||||
mUsbDeviceConnections = null;
|
||||
mInputUsbEndpoints = null;
|
||||
mOutputUsbEndpoints = null;
|
||||
|
||||
mUsbMidiPacketConverter = null;
|
||||
mInputUsbEndpointCableCounts = null;
|
||||
mOutputUsbEndpointCableCounts = null;
|
||||
|
||||
mIsOpen = false;
|
||||
}
|
||||
@@ -787,4 +884,19 @@ public final class UsbDirectMidiDevice implements Closeable {
|
||||
return messageType == MESSAGE_TYPE_MIDI_1_CHANNEL_VOICE
|
||||
|| messageType == MESSAGE_TYPE_MIDI_2_CHANNEL_VOICE;
|
||||
}
|
||||
|
||||
// Returns the number of jacks for MIDI 1.0 endpoints.
|
||||
// For MIDI 2.0 endpoints, this concept does not exist and each endpoint should be treated as
|
||||
// one port.
|
||||
private int getNumJacks(UsbEndpointDescriptor usbEndpointDescriptor) {
|
||||
UsbDescriptor classSpecificEndpointDescriptor =
|
||||
usbEndpointDescriptor.getClassSpecificEndpointDescriptor();
|
||||
if (classSpecificEndpointDescriptor != null
|
||||
&& (classSpecificEndpointDescriptor instanceof UsbACMidi10Endpoint)) {
|
||||
UsbACMidi10Endpoint midiEndpoint =
|
||||
(UsbACMidi10Endpoint) classSpecificEndpointDescriptor;
|
||||
return midiEndpoint.getNumJacks();
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,12 +16,17 @@
|
||||
|
||||
package com.android.server.usb;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
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 {
|
||||
private static final String TAG = "UsbMidiPacketConverter";
|
||||
|
||||
// Refer to Table 4-1 in USB MIDI 1.0 spec.
|
||||
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_END_EXCLUSIVE = (byte) 0xF7;
|
||||
|
||||
private UsbMidiDecoder mUsbMidiDecoder = new UsbMidiDecoder();
|
||||
private UsbMidiEncoder[] mUsbMidiEncoders;
|
||||
private ByteArrayOutputStream mEncoderOutputStream = new ByteArrayOutputStream();
|
||||
|
||||
public UsbMidiPacketConverter(int numEncoders) {
|
||||
mUsbMidiEncoders = new UsbMidiEncoder[numEncoders];
|
||||
for (int i = 0; i < numEncoders; i++) {
|
||||
mUsbMidiEncoders[i] = new UsbMidiEncoder();
|
||||
}
|
||||
}
|
||||
private UsbMidiDecoder mUsbMidiDecoder;
|
||||
|
||||
/**
|
||||
* Converts a USB MIDI array into a raw MIDI array.
|
||||
* Creates encoders.
|
||||
*
|
||||
* @param usbMidiBytes the USB MIDI bytes to convert
|
||||
* @param size the size of usbMidiBytes
|
||||
* @return byte array of raw MIDI packets
|
||||
* createEncoders() must be called before raw MIDI can be converted to USB MIDI.
|
||||
*
|
||||
* @param size the number of encoders to create
|
||||
*/
|
||||
public byte[] usbMidiToRawMidi(byte[] usbMidiBytes, int size) {
|
||||
return mUsbMidiDecoder.decode(usbMidiBytes, size);
|
||||
public void createEncoders(int 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.
|
||||
*
|
||||
* Call pullEncodedMidiPackets to retrieve the byte array.
|
||||
*
|
||||
* @param midiBytes the raw MIDI bytes to convert
|
||||
* @param size the size of usbMidiBytes
|
||||
* @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
|
||||
*/
|
||||
public byte[] rawMidiToUsbMidi(byte[] midiBytes, int size, int encoderId) {
|
||||
return mUsbMidiEncoders[encoderId].encode(midiBytes, size);
|
||||
public byte[] pullEncodedMidiPackets() {
|
||||
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 {
|
||||
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.
|
||||
// Each valid 4 byte input maps to a 1-3 byte output.
|
||||
// Reference the USB MIDI 1.0 spec for more info.
|
||||
public byte[] decode(byte[] usbMidiBytes, int size) {
|
||||
public void decode(byte[] usbMidiBytes, int size) {
|
||||
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) {
|
||||
int cableNumber = (usbMidiBytes[i] >> 4) & 0x0f;
|
||||
int codeIndex = usbMidiBytes[i] & 0x0f;
|
||||
int numPayloadBytes = PAYLOAD_SIZE[codeIndex];
|
||||
if (numPayloadBytes < 0) {
|
||||
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 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.
|
||||
// Each valid 1-3 byte input maps to a 4 byte output.
|
||||
// Reference the USB MIDI 1.0 spec for more info.
|
||||
@@ -153,7 +244,8 @@ public class UsbMidiPacketConverter {
|
||||
midiBytes[curLocation];
|
||||
mNumStoredSystemExclusiveBytes++;
|
||||
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);
|
||||
mNumStoredSystemExclusiveBytes = 0;
|
||||
}
|
||||
@@ -179,7 +271,7 @@ public class UsbMidiPacketConverter {
|
||||
byte codeIndexNumber = (byte) ((midiBytes[curLocation] >> 4) & 0x0f);
|
||||
int channelMessageSize = PAYLOAD_SIZE[codeIndexNumber];
|
||||
if (curLocation + channelMessageSize <= size) {
|
||||
outputStream.write(codeIndexNumber);
|
||||
outputStream.write(codeIndexNumber | mShiftedCableNumber);
|
||||
outputStream.write(midiBytes, curLocation, channelMessageSize);
|
||||
// Fill in the rest of the bytes with 0.
|
||||
outputStream.write(mEmptyBytes, 0, 3 - channelMessageSize);
|
||||
@@ -197,8 +289,8 @@ public class UsbMidiPacketConverter {
|
||||
curLocation++;
|
||||
} else if (midiBytes[curLocation] == SYSEX_END_EXCLUSIVE) {
|
||||
// 1 byte is 0x05, 2 bytes is 0x06, and 3 bytes is 0x07
|
||||
outputStream.write(CODE_INDEX_NUMBER_SYSEX_END_SINGLE_BYTE
|
||||
+ mNumStoredSystemExclusiveBytes);
|
||||
outputStream.write((CODE_INDEX_NUMBER_SYSEX_END_SINGLE_BYTE
|
||||
+ mNumStoredSystemExclusiveBytes) | mShiftedCableNumber);
|
||||
mStoredSystemExclusiveBytes[mNumStoredSystemExclusiveBytes] =
|
||||
midiBytes[curLocation];
|
||||
mNumStoredSystemExclusiveBytes++;
|
||||
@@ -218,7 +310,7 @@ public class UsbMidiPacketConverter {
|
||||
} else {
|
||||
int systemMessageSize = PAYLOAD_SIZE[codeIndexNumber];
|
||||
if (curLocation + systemMessageSize <= size) {
|
||||
outputStream.write(codeIndexNumber);
|
||||
outputStream.write(codeIndexNumber | mShiftedCableNumber);
|
||||
outputStream.write(midiBytes, curLocation, systemMessageSize);
|
||||
// Fill in the rest of the bytes with 0.
|
||||
outputStream.write(mEmptyBytes, 0, 3 - systemMessageSize);
|
||||
@@ -236,7 +328,7 @@ public class UsbMidiPacketConverter {
|
||||
}
|
||||
|
||||
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(0);
|
||||
outputStream.write(0);
|
||||
|
||||
@@ -118,7 +118,7 @@ public class UsbEndpointDescriptor extends UsbDescriptor {
|
||||
mClassSpecificEndpointDescriptor = descriptor;
|
||||
}
|
||||
|
||||
UsbDescriptor getClassSpecificEndpointDescriptor() {
|
||||
public UsbDescriptor getClassSpecificEndpointDescriptor() {
|
||||
return mClassSpecificEndpointDescriptor;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user