OSDN Git Service

BluetoothMidiService: Add support for sending SysEx messages that span multiple Bluet...
authorMike Lockwood <lockwood@google.com>
Fri, 1 May 2015 21:36:44 +0000 (14:36 -0700)
committerMike Lockwood <lockwood@google.com>
Fri, 1 May 2015 23:16:42 +0000 (16:16 -0700)
Change-Id: Id56f7c82ec97b6a46258111bbfd46ab1dc14dfe9

core/java/com/android/internal/midi/MidiConstants.java
media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java
media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothPacketEncoder.java

index f78f75a..c13e5fc 100644 (file)
@@ -87,13 +87,13 @@ public final class MidiConstants {
     }
 
     // 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
         return (command >= STATUS_NOTE_OFF && command < STATUS_SYSTEM_EXCLUSIVE);
     }
 
     // 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
         return (command >= STATUS_SYSTEM_EXCLUSIVE && command <= STATUS_END_SYSEX);
     }
index 8d194e5..63a8da7 100644 (file)
@@ -47,6 +47,7 @@ import java.util.UUID;
 public final class BluetoothMidiDevice {
 
     private static final String TAG = "BluetoothMidiDevice";
+    private static final boolean DEBUG = false;
 
     private static final int MAX_PACKET_SIZE = 20;
 
@@ -152,8 +153,10 @@ public final class BluetoothMidiDevice {
         @Override
         public void onCharacteristicChanged(BluetoothGatt gatt,
                                             BluetoothGattCharacteristic characteristic) {
-//            logByteArray("Received ", characteristic.getValue(), 0,
-//                    characteristic.getValue().length);
+            if (DEBUG) {
+                logByteArray("Received ", characteristic.getValue(), 0,
+                        characteristic.getValue().length);
+            }
             mPacketDecoder.decodePacket(characteristic.getValue(), mOutputReceiver);
         }
     };
@@ -182,8 +185,10 @@ public final class BluetoothMidiDevice {
             byte[] writeBuffer = mWriteBuffers[count];
             System.arraycopy(buffer, 0, writeBuffer, 0, count);
             mCharacteristic.setValue(writeBuffer);
-//            logByteArray("Sent ", mCharacteristic.getValue(), 0,
-//                    mCharacteristic.getValue().length);
+            if (DEBUG) {
+                logByteArray("Sent ", mCharacteristic.getValue(), 0,
+                       mCharacteristic.getValue().length);
+            }
             mBluetoothGatt.writeCharacteristic(mCharacteristic);
         }
     }
@@ -259,14 +264,7 @@ public final class BluetoothMidiDevice {
     private static void logByteArray(String prefix, byte[] value, int offset, int count) {
         StringBuilder builder = new StringBuilder(prefix);
         for (int i = offset; i < count; i++) {
-            String hex = Integer.toHexString(value[i]);
-            int length = hex.length();
-            if (length == 1) {
-                hex = "0x" + hex;
-            } else {
-                hex = hex.substring(length - 2, length);
-            }
-            builder.append(hex);
+            builder.append(String.format("0x%02X", value[i]));
             if (i != value.length - 1) {
                 builder.append(", ");
             }
index 463edcf..99ea353 100644 (file)
@@ -44,7 +44,7 @@ public class BluetoothPacketEncoder extends PacketEncoder {
     // timestamp for first message in current packet
     private int mPacketTimestamp;
     // current running status, or zero if none
-    private int mRunningStatus;
+    private byte mRunningStatus;
 
     private boolean mWritePending;
 
@@ -56,12 +56,28 @@ public class BluetoothPacketEncoder extends PacketEncoder {
         public void onReceive(byte[] msg, int offset, int count, long timestamp)
                 throws IOException {
 
-            int milliTimestamp = (int)(timestamp / MILLISECOND_NANOS) & MILLISECOND_MASK;
-            int status = msg[0] & 0xFF;
-
             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);
-                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 (status == mRunningStatus) bytesNeeded--;    // subtract one for status byte
 
@@ -71,15 +87,12 @@ public class BluetoothPacketEncoder extends PacketEncoder {
                     flushLocked(true);
                 }
 
-                // 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));
-                    mPacketTimestamp = milliTimestamp;
-                    needsTimestamp = true;
+                // write the header if necessary
+                if (appendHeader(milliTimestamp)) {
+                     needsTimestamp = !isSysExContinuation;
                 }
 
-                // write new timestamp byte and status byte if necessary
+                // write new timestamp byte if necessary
                 if (needsTimestamp) {
                     // timestamp byte with bits 0 - 6 of timestamp
                     mAccumulationBuffer[mAccumulatedBytes++] =
@@ -87,20 +100,55 @@ public class BluetoothPacketEncoder extends PacketEncoder {
                     mPacketTimestamp = milliTimestamp;
                 }
 
-                if (status != mRunningStatus) {
-                    mAccumulationBuffer[mAccumulatedBytes++] = (byte)status;
-                    if (MidiConstants.allowRunningStatus(status)) {
-                        mRunningStatus = status;
-                    } else if (MidiConstants.allowRunningStatus(status)) {
-                        mRunningStatus = 0;
+                if (isSysExStart || isSysExContinuation) {
+                    // MidiFramer will end the packet with SysEx End if there is one in the buffer
+                    boolean hasSysExEnd =
+                            (msg[offset + count - 1] == MidiConstants.STATUS_END_SYSEX);
+                    int remaining = (hasSysExEnd ? count - 1 : count);
+
+                    while (remaining > 0) {
+                        if (mAccumulatedBytes == mAccumulationBuffer.length) {
+                            // write out our data if there is no more room
+                            // if necessary, block until previous packet is sent
+                            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;
                     }
-                }
 
-                // now copy data bytes
-                int dataLength = count - 1;
-                System.arraycopy(msg, 1, mAccumulationBuffer, mAccumulatedBytes, dataLength);
-                // FIXME - handle long SysEx properly
-                mAccumulatedBytes += dataLength;
+                    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
                 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
     private final MidiFramer mMidiFramer = new MidiFramer(mFramedDataReceiver);