1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.bluetoothmidiservice; 18 19 import android.media.midi.MidiReceiver; 20 import android.util.Log; 21 22 import java.io.IOException; 23 24 /** 25 * This is an abstract base class that decodes a BLE-MIDI packet 26 * buffer and passes it to a {@link android.media.midi.MidiReceiver} 27 */ 28 public class BluetoothPacketDecoder extends PacketDecoder { 29 30 private static final String TAG = "BluetoothPacketDecoder"; 31 32 private final byte[] mBuffer; 33 private int mMaxPacketSize; 34 private int mBytesInBuffer; 35 private MidiBtleTimeTracker mTimeTracker; 36 37 private int mLowTimestamp; 38 private long mNanoTimestamp; 39 40 private static final int TIMESTAMP_MASK_HIGH = 0x1F80; // top 7 bits 41 private static final int TIMESTAMP_MASK_LOW = 0x7F; // bottom 7 bits 42 private static final int HEADER_TIMESTAMP_MASK = 0x3F; // bottom 6 bits 43 BluetoothPacketDecoder(int maxPacketSize)44 public BluetoothPacketDecoder(int maxPacketSize) { 45 mBuffer = new byte[maxPacketSize]; 46 setMaxPacketSize(maxPacketSize); 47 } 48 49 /** 50 * Dynamically sets the maximum packet size 51 */ setMaxPacketSize(int maxPacketSize)52 public void setMaxPacketSize(int maxPacketSize) { 53 mMaxPacketSize = Math.min(maxPacketSize, mBuffer.length); 54 } 55 flushOutput(MidiReceiver receiver)56 private void flushOutput(MidiReceiver receiver) { 57 if (mBytesInBuffer > 0) { 58 try { 59 receiver.send(mBuffer, 0, mBytesInBuffer, mNanoTimestamp); 60 } catch (IOException e) { 61 // ??? 62 } 63 mBytesInBuffer = 0; 64 } 65 } 66 67 // NOTE: this code allows running status across packets, 68 // although the specification does not allow that. 69 @Override decodePacket(byte[] buffer, MidiReceiver receiver)70 public void decodePacket(byte[] buffer, MidiReceiver receiver) { 71 if (mTimeTracker == null) { 72 mTimeTracker = new MidiBtleTimeTracker(System.nanoTime()); 73 } 74 75 int length = buffer.length; 76 if (length < 1) { 77 Log.e(TAG, "empty packet"); 78 return; 79 } 80 81 byte header = buffer[0]; 82 // Check for the header bit 7. 83 // Ignore the reserved bit 6. 84 if ((header & 0x80) != 0x80) { 85 Log.e(TAG, "packet does not start with header"); 86 return; 87 } 88 89 // shift bits 0 - 5 to bits 7 - 12 90 int highTimestamp = (header & HEADER_TIMESTAMP_MASK) << 7; 91 boolean lastWasTimestamp = false; 92 int previousLowTimestamp = 0; 93 int currentTimestamp = highTimestamp | mLowTimestamp; 94 95 int curMaxPacketSize = mMaxPacketSize; 96 // Iterate through the rest of the packet, separating MIDI data from timestamps. 97 for (int i = 1; i < buffer.length; i++) { 98 byte b = buffer[i]; 99 100 // Is this a timestamp byte? 101 if ((b & 0x80) != 0 && !lastWasTimestamp) { 102 lastWasTimestamp = true; 103 mLowTimestamp = b & TIMESTAMP_MASK_LOW; 104 105 // If the low timestamp byte wraps within the packet then 106 // increment the high timestamp byte. 107 if (mLowTimestamp < previousLowTimestamp) { 108 highTimestamp = (highTimestamp + 0x0080) & TIMESTAMP_MASK_HIGH; 109 } 110 previousLowTimestamp = mLowTimestamp; 111 112 // If the timestamp advances then send any pending data. 113 int newTimestamp = highTimestamp | mLowTimestamp; 114 if (newTimestamp != currentTimestamp) { 115 // Send previous message separately since it has a different timestamp. 116 flushOutput(receiver); 117 currentTimestamp = newTimestamp; 118 } 119 120 // Calculate MIDI nanosecond timestamp from BLE timestamp. 121 long now = System.nanoTime(); 122 mNanoTimestamp = mTimeTracker.convertTimestampToNanotime(currentTimestamp, now); 123 } else { 124 lastWasTimestamp = false; 125 // Flush if full before adding more data. 126 if (mBytesInBuffer >= curMaxPacketSize) { 127 flushOutput(receiver); 128 } 129 mBuffer[mBytesInBuffer++] = b; 130 } 131 } 132 133 flushOutput(receiver); 134 } 135 } 136