diff options
author | Mike Lockwood <lockwood@google.com> | 2015-04-21 09:33:09 -0700 |
---|---|---|
committer | Mike Lockwood <lockwood@google.com> | 2015-04-21 13:28:08 -0700 |
commit | ff001809f60b937c63d2db39e99a567af54414ac (patch) | |
tree | a72f1e10e81a55308fe96bd5554f0c438071521e /media/packages | |
parent | 93ea09a37696e60c9659c51868091b6b492c3bc3 (diff) | |
download | frameworks_base-ff001809f60b937c63d2db39e99a567af54414ac.zip frameworks_base-ff001809f60b937c63d2db39e99a567af54414ac.tar.gz frameworks_base-ff001809f60b937c63d2db39e99a567af54414ac.tar.bz2 |
BluetoothMidiService: Use MidiBtleTimeTracker to interpret incoming Bluetooth MIDI timestamps
Also fixed some problems handling timestamp wrapping.
Change-Id: Ic0aefc54f2560425bea6d07ca0c4529d16699eaa
Diffstat (limited to 'media/packages')
2 files changed, 127 insertions, 9 deletions
diff --git a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothPacketDecoder.java b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothPacketDecoder.java index c5bfb5f..1bce9fb 100644 --- a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothPacketDecoder.java +++ b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothPacketDecoder.java @@ -30,6 +30,7 @@ public class BluetoothPacketDecoder extends PacketDecoder { private static final String TAG = "BluetoothPacketDecoder"; private final byte[] mBuffer; + private MidiBtleTimeTracker mTimeTracker; private final int TIMESTAMP_MASK_HIGH = 0x1F80; private final int TIMESTAMP_MASK_LOW = 0x7F; @@ -41,6 +42,10 @@ public class BluetoothPacketDecoder extends PacketDecoder { @Override public void decodePacket(byte[] buffer, MidiReceiver receiver) { + if (mTimeTracker == null) { + mTimeTracker = new MidiBtleTimeTracker(System.nanoTime()); + } + int length = buffer.length; // NOTE his code allows running status across packets, @@ -57,10 +62,12 @@ public class BluetoothPacketDecoder extends PacketDecoder { } // shift bits 0 - 5 to bits 7 - 12 - int timestamp = (header & HEADER_TIMESTAMP_MASK) << 7; + int highTimestamp = (header & HEADER_TIMESTAMP_MASK) << 7; boolean lastWasTimestamp = false; int dataCount = 0; int previousLowTimestamp = 0; + long nanoTimestamp = 0; + int currentTimestamp = 0; // iterate through the rest of the packet, separating MIDI data from timestamps for (int i = 1; i < buffer.length; i++) { @@ -69,25 +76,28 @@ public class BluetoothPacketDecoder extends PacketDecoder { if ((b & 0x80) != 0 && !lastWasTimestamp) { lastWasTimestamp = true; int lowTimestamp = b & TIMESTAMP_MASK_LOW; - int newTimestamp = (timestamp & TIMESTAMP_MASK_HIGH) | lowTimestamp; if (lowTimestamp < previousLowTimestamp) { - newTimestamp = (newTimestamp + 0x0080) & TIMESTAMP_MASK_HIGH; + highTimestamp = (highTimestamp + 0x0080) & TIMESTAMP_MASK_HIGH; } previousLowTimestamp = lowTimestamp; - if (newTimestamp != timestamp) { + int newTimestamp = highTimestamp | lowTimestamp; + if (newTimestamp != currentTimestamp) { if (dataCount > 0) { // send previous message separately since it has a different timestamp try { - // FIXME use sendWithTimestamp - receiver.send(mBuffer, 0, dataCount); + receiver.sendWithTimestamp(mBuffer, 0, dataCount, nanoTimestamp); } catch (IOException e) { // ??? } dataCount = 0; } + currentTimestamp = newTimestamp; } - timestamp = newTimestamp; + + // calculate nanoTimestamp + long now = System.nanoTime(); + nanoTimestamp = mTimeTracker.convertTimestampToNanotime(currentTimestamp, now); } else { lastWasTimestamp = false; mBuffer[dataCount++] = b; @@ -96,8 +106,7 @@ public class BluetoothPacketDecoder extends PacketDecoder { if (dataCount > 0) { try { - // FIXME use sendWithTimestamp - receiver.send(mBuffer, 0, dataCount); + receiver.sendWithTimestamp(mBuffer, 0, dataCount, nanoTimestamp); } catch (IOException e) { // ??? } diff --git a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/MidiBtleTimeTracker.java b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/MidiBtleTimeTracker.java new file mode 100644 index 0000000..5202f9a --- /dev/null +++ b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/MidiBtleTimeTracker.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2015 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.bluetoothmidiservice; + +/** + * Convert MIDI over BTLE timestamps to system nanotime. + */ +public class MidiBtleTimeTracker { + + public final static long NANOS_PER_MILLI = 1000000L; + + private final static long RANGE_MILLIS = 0x2000; // per MIDI / BTLE standard + private final static long RANGE_NANOS = RANGE_MILLIS * NANOS_PER_MILLI; + + private int mWindowMillis = 20; // typical max connection interval + private long mWindowNanos = mWindowMillis * NANOS_PER_MILLI; + + private int mPreviousTimestamp; // Used to calculate deltas. + private long mPreviousNow; + // Our model of the peripherals millisecond clock. + private long mPeripheralTimeMillis; + // Host time that corresponds to time=0 on the peripheral. + private long mBaseHostTimeNanos; + private long mPreviousResult; // To prevent retrograde timestamp + + public MidiBtleTimeTracker(long now) { + mPeripheralTimeMillis = 0; + mBaseHostTimeNanos = now; + mPreviousNow = now; + } + + /** + * @param timestamp + * 13-bit millis in range of 0 to 8191 + * @param now + * current time in nanoseconds + * @return nanoseconds corresponding to the timestamp + */ + public long convertTimestampToNanotime(int timestamp, long now) { + long deltaMillis = timestamp - mPreviousTimestamp; + // will be negative when timestamp wraps + if (deltaMillis < 0) { + deltaMillis += RANGE_MILLIS; + } + mPeripheralTimeMillis += deltaMillis; + + // If we have not been called for a long time then + // make sure we have not wrapped multiple times. + if ((now - mPreviousNow) > (RANGE_NANOS / 2)) { + // Handle missed wraps. + long minimumTimeNanos = (now - mBaseHostTimeNanos) + - (RANGE_NANOS / 2); + long minimumTimeMillis = minimumTimeNanos / NANOS_PER_MILLI; + while (mPeripheralTimeMillis < minimumTimeMillis) { + mPeripheralTimeMillis += RANGE_MILLIS; + } + } + + // Convert peripheral time millis to host time nanos. + long timestampHostNanos = (mPeripheralTimeMillis * NANOS_PER_MILLI) + + mBaseHostTimeNanos; + + // The event cannot be in the future. So move window if we hit that. + if (timestampHostNanos > now) { + mPeripheralTimeMillis = 0; + mBaseHostTimeNanos = now; + timestampHostNanos = now; + } else { + // Timestamp should not be older than our window time. + long windowBottom = now - mWindowNanos; + if (timestampHostNanos < windowBottom) { + mPeripheralTimeMillis = 0; + mBaseHostTimeNanos = windowBottom; + timestampHostNanos = windowBottom; + } + } + // prevent retrograde timestamp + if (timestampHostNanos < mPreviousResult) { + timestampHostNanos = mPreviousResult; + } + mPreviousResult = timestampHostNanos; + mPreviousTimestamp = timestamp; + mPreviousNow = now; + return timestampHostNanos; + } + + public int getWindowMillis() { + return mWindowMillis; + } + + public void setWindowMillis(int window) { + this.mWindowMillis = window; + } + +} |