BLE-MIDI: fix timestamps for sysex data
Blocks of data in the middle of a SysEx used to get a zero timestamp.
Now they get a proper timestamp.
Thanks to Teenage Engineering for suggesting this fix.
I also cleaned up the code to make it easier to understand.
Bug: 140638458
Test: Send long, >50 byte, SysEx over BLE to an Android
Test: device running the MIDI scope. Check timestamps
Test: for the data blocks. See bug for more details.
Change-Id: I1a553d54cc76025f854e53229d233a033ab7c297
Merged-In: I1a553d54cc76025f854e53229d233a033ab7c297
(cherry picked from commit 5664e50ac9fd04e169006e821110a2bc73f59e80)
diff --git a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothPacketDecoder.java b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothPacketDecoder.java
index ea95a01..c51c8fa 100644
--- a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothPacketDecoder.java
+++ b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothPacketDecoder.java
@@ -22,24 +22,41 @@
import java.io.IOException;
/**
- * This is an abstract base class that decodes a packet buffer and passes it to a
- * {@link android.media.midi.MidiReceiver}
+ * This is an abstract base class that decodes a BLE-MIDI packet
+ * buffer and passes it to a {@link android.media.midi.MidiReceiver}
*/
public class BluetoothPacketDecoder extends PacketDecoder {
private static final String TAG = "BluetoothPacketDecoder";
private final byte[] mBuffer;
+ private int mBytesInBuffer;
private MidiBtleTimeTracker mTimeTracker;
- private final int TIMESTAMP_MASK_HIGH = 0x1F80;
- private final int TIMESTAMP_MASK_LOW = 0x7F;
- private final int HEADER_TIMESTAMP_MASK = 0x3F;
+ private int mLowTimestamp;
+ private long mNanoTimestamp;
+
+ private static final int TIMESTAMP_MASK_HIGH = 0x1F80; // top 7 bits
+ private static final int TIMESTAMP_MASK_LOW = 0x7F; // bottom 7 bits
+ private static final int HEADER_TIMESTAMP_MASK = 0x3F; // bottom 6 bits
public BluetoothPacketDecoder(int maxPacketSize) {
mBuffer = new byte[maxPacketSize];
}
+ private void flushOutput(MidiReceiver receiver) {
+ if (mBytesInBuffer > 0) {
+ try {
+ receiver.send(mBuffer, 0, mBytesInBuffer, mNanoTimestamp);
+ } catch (IOException e) {
+ // ???
+ }
+ mBytesInBuffer = 0;
+ }
+ }
+
+ // NOTE: this code allows running status across packets,
+ // although the specification does not allow that.
@Override
public void decodePacket(byte[] buffer, MidiReceiver receiver) {
if (mTimeTracker == null) {
@@ -47,14 +64,11 @@
}
int length = buffer.length;
-
- // NOTE his code allows running status across packets,
- // although the specification does not allow that.
-
if (length < 1) {
Log.e(TAG, "empty packet");
return;
}
+
byte header = buffer[0];
if ((header & 0xC0) != 0x80) {
Log.e(TAG, "packet does not start with header");
@@ -64,52 +78,46 @@
// shift bits 0 - 5 to bits 7 - 12
int highTimestamp = (header & HEADER_TIMESTAMP_MASK) << 7;
boolean lastWasTimestamp = false;
- int dataCount = 0;
int previousLowTimestamp = 0;
- long nanoTimestamp = 0;
- int currentTimestamp = 0;
+ int currentTimestamp = highTimestamp | mLowTimestamp;
- // iterate through the rest of the packet, separating MIDI data from timestamps
+ // Iterate through the rest of the packet, separating MIDI data from timestamps.
for (int i = 1; i < buffer.length; i++) {
byte b = buffer[i];
+ // Is this a timestamp byte?
if ((b & 0x80) != 0 && !lastWasTimestamp) {
lastWasTimestamp = true;
- int lowTimestamp = b & TIMESTAMP_MASK_LOW;
- if (lowTimestamp < previousLowTimestamp) {
+ mLowTimestamp = b & TIMESTAMP_MASK_LOW;
+
+ // If the low timestamp byte wraps within the packet then
+ // increment the high timestamp byte.
+ if (mLowTimestamp < previousLowTimestamp) {
highTimestamp = (highTimestamp + 0x0080) & TIMESTAMP_MASK_HIGH;
}
- previousLowTimestamp = lowTimestamp;
+ previousLowTimestamp = mLowTimestamp;
- int newTimestamp = highTimestamp | lowTimestamp;
+ // If the timestamp advances then send any pending data.
+ int newTimestamp = highTimestamp | mLowTimestamp;
if (newTimestamp != currentTimestamp) {
- if (dataCount > 0) {
- // send previous message separately since it has a different timestamp
- try {
- receiver.send(mBuffer, 0, dataCount, nanoTimestamp);
- } catch (IOException e) {
- // ???
- }
- dataCount = 0;
- }
+ // Send previous message separately since it has a different timestamp.
+ flushOutput(receiver);
currentTimestamp = newTimestamp;
}
- // calculate nanoTimestamp
+ // Calculate MIDI nanosecond timestamp from BLE timestamp.
long now = System.nanoTime();
- nanoTimestamp = mTimeTracker.convertTimestampToNanotime(currentTimestamp, now);
+ mNanoTimestamp = mTimeTracker.convertTimestampToNanotime(currentTimestamp, now);
} else {
lastWasTimestamp = false;
- mBuffer[dataCount++] = b;
+ // Flush if full before adding more data.
+ if (mBytesInBuffer == mBuffer.length) {
+ flushOutput(receiver);
+ }
+ mBuffer[mBytesInBuffer++] = b;
}
}
- if (dataCount > 0) {
- try {
- receiver.send(mBuffer, 0, dataCount, nanoTimestamp);
- } catch (IOException e) {
- // ???
- }
- }
+ flushOutput(receiver);
}
}