Merge "USB MIDI: Multijack MIDI 1.0 for MIDI 2.0 devices" into udc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
3b4472fc03
@@ -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;
|
||||
@@ -404,42 +451,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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -455,64 +516,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) {
|
||||
@@ -520,7 +610,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;
|
||||
@@ -558,11 +647,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);
|
||||
}
|
||||
@@ -571,7 +658,7 @@ public final class UsbDirectMidiDevice implements Closeable {
|
||||
};
|
||||
newThread.start();
|
||||
mThreads.add(newThread);
|
||||
portNumber++;
|
||||
portStartNumber += cableCountFinal;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -667,11 +754,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();
|
||||
@@ -679,8 +776,8 @@ public final class UsbDirectMidiDevice implements Closeable {
|
||||
mUsbDeviceConnections = null;
|
||||
mInputUsbEndpoints = null;
|
||||
mOutputUsbEndpoints = null;
|
||||
|
||||
mUsbMidiPacketConverter = null;
|
||||
mInputUsbEndpointCableCounts = null;
|
||||
mOutputUsbEndpointCableCounts = null;
|
||||
|
||||
mIsOpen = false;
|
||||
}
|
||||
@@ -788,4 +885,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