Merge "BluetoothMidiService: Add support for sending SysEx messages that span multiple Bluetooth packets" into mnc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
5ba44affe6
@@ -87,13 +87,13 @@ public final class MidiConstants {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Returns true if this command can be used for running status
|
// Returns true if this command can be used for running status
|
||||||
public static boolean allowRunningStatus(int command) {
|
public static boolean allowRunningStatus(byte command) {
|
||||||
// only Channel Voice and Channel Mode commands can use running status
|
// only Channel Voice and Channel Mode commands can use running status
|
||||||
return (command >= STATUS_NOTE_OFF && command < STATUS_SYSTEM_EXCLUSIVE);
|
return (command >= STATUS_NOTE_OFF && command < STATUS_SYSTEM_EXCLUSIVE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns true if this command cancels running status
|
// Returns true if this command cancels running status
|
||||||
public static boolean cancelsRunningStatus(int command) {
|
public static boolean cancelsRunningStatus(byte command) {
|
||||||
// System Common messages cancel running status
|
// System Common messages cancel running status
|
||||||
return (command >= STATUS_SYSTEM_EXCLUSIVE && command <= STATUS_END_SYSEX);
|
return (command >= STATUS_SYSTEM_EXCLUSIVE && command <= STATUS_END_SYSEX);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ import java.util.UUID;
|
|||||||
public final class BluetoothMidiDevice {
|
public final class BluetoothMidiDevice {
|
||||||
|
|
||||||
private static final String TAG = "BluetoothMidiDevice";
|
private static final String TAG = "BluetoothMidiDevice";
|
||||||
|
private static final boolean DEBUG = false;
|
||||||
|
|
||||||
private static final int MAX_PACKET_SIZE = 20;
|
private static final int MAX_PACKET_SIZE = 20;
|
||||||
|
|
||||||
@@ -152,8 +153,10 @@ public final class BluetoothMidiDevice {
|
|||||||
@Override
|
@Override
|
||||||
public void onCharacteristicChanged(BluetoothGatt gatt,
|
public void onCharacteristicChanged(BluetoothGatt gatt,
|
||||||
BluetoothGattCharacteristic characteristic) {
|
BluetoothGattCharacteristic characteristic) {
|
||||||
// logByteArray("Received ", characteristic.getValue(), 0,
|
if (DEBUG) {
|
||||||
// characteristic.getValue().length);
|
logByteArray("Received ", characteristic.getValue(), 0,
|
||||||
|
characteristic.getValue().length);
|
||||||
|
}
|
||||||
mPacketDecoder.decodePacket(characteristic.getValue(), mOutputReceiver);
|
mPacketDecoder.decodePacket(characteristic.getValue(), mOutputReceiver);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -182,8 +185,10 @@ public final class BluetoothMidiDevice {
|
|||||||
byte[] writeBuffer = mWriteBuffers[count];
|
byte[] writeBuffer = mWriteBuffers[count];
|
||||||
System.arraycopy(buffer, 0, writeBuffer, 0, count);
|
System.arraycopy(buffer, 0, writeBuffer, 0, count);
|
||||||
mCharacteristic.setValue(writeBuffer);
|
mCharacteristic.setValue(writeBuffer);
|
||||||
// logByteArray("Sent ", mCharacteristic.getValue(), 0,
|
if (DEBUG) {
|
||||||
// mCharacteristic.getValue().length);
|
logByteArray("Sent ", mCharacteristic.getValue(), 0,
|
||||||
|
mCharacteristic.getValue().length);
|
||||||
|
}
|
||||||
mBluetoothGatt.writeCharacteristic(mCharacteristic);
|
mBluetoothGatt.writeCharacteristic(mCharacteristic);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -259,14 +264,7 @@ public final class BluetoothMidiDevice {
|
|||||||
private static void logByteArray(String prefix, byte[] value, int offset, int count) {
|
private static void logByteArray(String prefix, byte[] value, int offset, int count) {
|
||||||
StringBuilder builder = new StringBuilder(prefix);
|
StringBuilder builder = new StringBuilder(prefix);
|
||||||
for (int i = offset; i < count; i++) {
|
for (int i = offset; i < count; i++) {
|
||||||
String hex = Integer.toHexString(value[i]);
|
builder.append(String.format("0x%02X", value[i]));
|
||||||
int length = hex.length();
|
|
||||||
if (length == 1) {
|
|
||||||
hex = "0x" + hex;
|
|
||||||
} else {
|
|
||||||
hex = hex.substring(length - 2, length);
|
|
||||||
}
|
|
||||||
builder.append(hex);
|
|
||||||
if (i != value.length - 1) {
|
if (i != value.length - 1) {
|
||||||
builder.append(", ");
|
builder.append(", ");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ public class BluetoothPacketEncoder extends PacketEncoder {
|
|||||||
// timestamp for first message in current packet
|
// timestamp for first message in current packet
|
||||||
private int mPacketTimestamp;
|
private int mPacketTimestamp;
|
||||||
// current running status, or zero if none
|
// current running status, or zero if none
|
||||||
private int mRunningStatus;
|
private byte mRunningStatus;
|
||||||
|
|
||||||
private boolean mWritePending;
|
private boolean mWritePending;
|
||||||
|
|
||||||
@@ -56,12 +56,28 @@ public class BluetoothPacketEncoder extends PacketEncoder {
|
|||||||
public void onReceive(byte[] msg, int offset, int count, long timestamp)
|
public void onReceive(byte[] msg, int offset, int count, long timestamp)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
|
|
||||||
int milliTimestamp = (int)(timestamp / MILLISECOND_NANOS) & MILLISECOND_MASK;
|
|
||||||
int status = msg[0] & 0xFF;
|
|
||||||
|
|
||||||
synchronized (mLock) {
|
synchronized (mLock) {
|
||||||
|
int milliTimestamp = (int)(timestamp / MILLISECOND_NANOS) & MILLISECOND_MASK;
|
||||||
|
byte status = msg[offset];
|
||||||
|
boolean isSysExStart = (status == MidiConstants.STATUS_SYSTEM_EXCLUSIVE);
|
||||||
|
boolean isSysExContinuation = ((status & 0x80) == 0);
|
||||||
|
|
||||||
|
int bytesNeeded;
|
||||||
|
if (isSysExStart || isSysExContinuation) {
|
||||||
|
// SysEx messages can be split into multiple packets
|
||||||
|
bytesNeeded = 1;
|
||||||
|
} else {
|
||||||
|
bytesNeeded = count;
|
||||||
|
}
|
||||||
|
|
||||||
boolean needsTimestamp = (milliTimestamp != mPacketTimestamp);
|
boolean needsTimestamp = (milliTimestamp != mPacketTimestamp);
|
||||||
int bytesNeeded = count;
|
if (isSysExStart) {
|
||||||
|
// SysEx start byte must be preceded by a timestamp
|
||||||
|
needsTimestamp = true;
|
||||||
|
} else if (isSysExContinuation) {
|
||||||
|
// SysEx continuation packets must not have timestamp byte
|
||||||
|
needsTimestamp = false;
|
||||||
|
}
|
||||||
if (needsTimestamp) bytesNeeded++; // add one for timestamp byte
|
if (needsTimestamp) bytesNeeded++; // add one for timestamp byte
|
||||||
if (status == mRunningStatus) bytesNeeded--; // subtract one for status byte
|
if (status == mRunningStatus) bytesNeeded--; // subtract one for status byte
|
||||||
|
|
||||||
@@ -71,15 +87,12 @@ public class BluetoothPacketEncoder extends PacketEncoder {
|
|||||||
flushLocked(true);
|
flushLocked(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// write header if we are starting a new packet
|
// write the header if necessary
|
||||||
if (mAccumulatedBytes == 0) {
|
if (appendHeader(milliTimestamp)) {
|
||||||
// header byte with timestamp bits 7 - 12
|
needsTimestamp = !isSysExContinuation;
|
||||||
mAccumulationBuffer[mAccumulatedBytes++] = (byte)(0x80 | (milliTimestamp >> 7));
|
|
||||||
mPacketTimestamp = milliTimestamp;
|
|
||||||
needsTimestamp = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// write new timestamp byte and status byte if necessary
|
// write new timestamp byte if necessary
|
||||||
if (needsTimestamp) {
|
if (needsTimestamp) {
|
||||||
// timestamp byte with bits 0 - 6 of timestamp
|
// timestamp byte with bits 0 - 6 of timestamp
|
||||||
mAccumulationBuffer[mAccumulatedBytes++] =
|
mAccumulationBuffer[mAccumulatedBytes++] =
|
||||||
@@ -87,20 +100,55 @@ public class BluetoothPacketEncoder extends PacketEncoder {
|
|||||||
mPacketTimestamp = milliTimestamp;
|
mPacketTimestamp = milliTimestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status != mRunningStatus) {
|
if (isSysExStart || isSysExContinuation) {
|
||||||
mAccumulationBuffer[mAccumulatedBytes++] = (byte)status;
|
// MidiFramer will end the packet with SysEx End if there is one in the buffer
|
||||||
if (MidiConstants.allowRunningStatus(status)) {
|
boolean hasSysExEnd =
|
||||||
mRunningStatus = status;
|
(msg[offset + count - 1] == MidiConstants.STATUS_END_SYSEX);
|
||||||
} else if (MidiConstants.allowRunningStatus(status)) {
|
int remaining = (hasSysExEnd ? count - 1 : count);
|
||||||
mRunningStatus = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// now copy data bytes
|
while (remaining > 0) {
|
||||||
int dataLength = count - 1;
|
if (mAccumulatedBytes == mAccumulationBuffer.length) {
|
||||||
System.arraycopy(msg, 1, mAccumulationBuffer, mAccumulatedBytes, dataLength);
|
// write out our data if there is no more room
|
||||||
// FIXME - handle long SysEx properly
|
// if necessary, block until previous packet is sent
|
||||||
mAccumulatedBytes += dataLength;
|
flushLocked(true);
|
||||||
|
appendHeader(milliTimestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
int copy = mAccumulationBuffer.length - mAccumulatedBytes;
|
||||||
|
if (copy > remaining) copy = remaining;
|
||||||
|
System.arraycopy(msg, offset, mAccumulationBuffer, mAccumulatedBytes, copy);
|
||||||
|
mAccumulatedBytes += copy;
|
||||||
|
offset += copy;
|
||||||
|
remaining -= copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasSysExEnd) {
|
||||||
|
// SysEx End command must be preceeded by a timestamp byte
|
||||||
|
if (mAccumulatedBytes + 2 > mAccumulationBuffer.length) {
|
||||||
|
// write out our data if there is no more room
|
||||||
|
// if necessary, block until previous packet is sent
|
||||||
|
flushLocked(true);
|
||||||
|
appendHeader(milliTimestamp);
|
||||||
|
}
|
||||||
|
mAccumulationBuffer[mAccumulatedBytes++] = (byte)(0x80 | (milliTimestamp & 0x7F));
|
||||||
|
mAccumulationBuffer[mAccumulatedBytes++] = MidiConstants.STATUS_END_SYSEX;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Non-SysEx message
|
||||||
|
if (status != mRunningStatus) {
|
||||||
|
mAccumulationBuffer[mAccumulatedBytes++] = status;
|
||||||
|
if (MidiConstants.allowRunningStatus(status)) {
|
||||||
|
mRunningStatus = status;
|
||||||
|
} else if (MidiConstants.cancelsRunningStatus(status)) {
|
||||||
|
mRunningStatus = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// now copy data bytes
|
||||||
|
int dataLength = count - 1;
|
||||||
|
System.arraycopy(msg, offset + 1, mAccumulationBuffer, mAccumulatedBytes, dataLength);
|
||||||
|
mAccumulatedBytes += dataLength;
|
||||||
|
}
|
||||||
|
|
||||||
// write the packet if possible, but do not block
|
// write the packet if possible, but do not block
|
||||||
flushLocked(false);
|
flushLocked(false);
|
||||||
@@ -108,6 +156,18 @@ public class BluetoothPacketEncoder extends PacketEncoder {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private boolean appendHeader(int milliTimestamp) {
|
||||||
|
// write header if we are starting a new packet
|
||||||
|
if (mAccumulatedBytes == 0) {
|
||||||
|
// header byte with timestamp bits 7 - 12
|
||||||
|
mAccumulationBuffer[mAccumulatedBytes++] = (byte)(0x80 | ((milliTimestamp >> 7) & 0x3F));
|
||||||
|
mPacketTimestamp = milliTimestamp;
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MidiFramer for normalizing incoming data
|
// MidiFramer for normalizing incoming data
|
||||||
private final MidiFramer mMidiFramer = new MidiFramer(mFramedDataReceiver);
|
private final MidiFramer mMidiFramer = new MidiFramer(mFramedDataReceiver);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user