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 com.android.internal.midi.MidiConstants; 23 import com.android.internal.midi.MidiFramer; 24 25 import java.io.IOException; 26 import java.util.ArrayDeque; 27 import java.util.Queue; 28 29 /** 30 * This class accumulates MIDI messages to form a MIDI packet. 31 */ 32 public class BluetoothPacketEncoder extends PacketEncoder { 33 34 private static final String TAG = "BluetoothPacketEncoder"; 35 36 private static final long MILLISECOND_NANOS = 1000000L; 37 38 // mask for generating 13 bit timestamps 39 private static final int MILLISECOND_MASK = 0x1FFF; 40 41 private final PacketReceiver mPacketReceiver; 42 43 // buffer for accumulating messages to write 44 private final byte[] mAccumulationBuffer; 45 // number of bytes currently in mAccumulationBuffer 46 private int mAccumulatedBytes; 47 // timestamp for first message in current packet 48 private int mPacketTimestamp; 49 // current running status, or zero if none 50 private byte mRunningStatus; 51 // max size of a packet 52 private int mMaxPacketSize; 53 54 private boolean mWritePending; 55 56 private final Object mLock = new Object(); 57 58 private Queue<byte[]> mFailedToSendQueue = new ArrayDeque<byte[]>(); 59 60 // This receives normalized data from mMidiFramer and accumulates it into a packet buffer 61 private final MidiReceiver mFramedDataReceiver = new MidiReceiver() { 62 @Override 63 public void onSend(byte[] msg, int offset, int count, long timestamp) 64 throws IOException { 65 66 synchronized (mLock) { 67 flushFailedToSendQueueLocked(); 68 69 int milliTimestamp = (int)(timestamp / MILLISECOND_NANOS) & MILLISECOND_MASK; 70 byte status = msg[offset]; 71 boolean isSysExStart = (status == MidiConstants.STATUS_SYSTEM_EXCLUSIVE); 72 // Because of the MidiFramer, if it is not a status byte then it 73 // must be a continuation. 74 boolean isSysExContinuation = ((status & 0x80) == 0); 75 76 int bytesNeeded; 77 if (isSysExStart || isSysExContinuation) { 78 // SysEx messages can be split into multiple packets 79 bytesNeeded = 1; 80 } else { 81 bytesNeeded = count; 82 } 83 84 // Status bytes must be preceded by a timestamp 85 boolean needsTimestamp = (status != mRunningStatus) 86 || (milliTimestamp != mPacketTimestamp); 87 if (isSysExStart) { 88 // SysEx start byte must be preceded by a timestamp 89 needsTimestamp = true; 90 } else if (isSysExContinuation) { 91 // SysEx continuation packets must not have timestamp byte 92 needsTimestamp = false; 93 } 94 95 if (needsTimestamp) bytesNeeded++; // add one for timestamp byte 96 if (status == mRunningStatus) bytesNeeded--; // subtract one for status byte 97 98 if (mAccumulatedBytes + bytesNeeded > mMaxPacketSize) { 99 // write out our data if there is no more room 100 // if necessary, block until previous packet is sent 101 flushLocked(true); 102 } 103 104 // write the header if necessary 105 if (appendHeader(milliTimestamp)) { 106 needsTimestamp = !isSysExContinuation; 107 } 108 109 // write new timestamp byte if necessary 110 if (needsTimestamp) { 111 // timestamp byte with bits 0 - 6 of timestamp 112 mAccumulationBuffer[mAccumulatedBytes++] = 113 (byte)(0x80 | (milliTimestamp & 0x7F)); 114 mPacketTimestamp = milliTimestamp; 115 } 116 117 if (isSysExStart || isSysExContinuation) { 118 // MidiFramer will end the packet with SysEx End if there is one in the buffer 119 boolean hasSysExEnd = 120 (msg[offset + count - 1] == MidiConstants.STATUS_END_SYSEX); 121 int remaining = (hasSysExEnd ? count - 1 : count); 122 123 while (remaining > 0) { 124 if (mAccumulatedBytes == mMaxPacketSize) { 125 // write out our data if there is no more room 126 // if necessary, block until previous packet is sent 127 flushLocked(true); 128 appendHeader(milliTimestamp); 129 } 130 131 int copy = mMaxPacketSize - mAccumulatedBytes; 132 if (copy > remaining) copy = remaining; 133 System.arraycopy(msg, offset, mAccumulationBuffer, mAccumulatedBytes, copy); 134 mAccumulatedBytes += copy; 135 offset += copy; 136 remaining -= copy; 137 } 138 139 if (hasSysExEnd) { 140 // SysEx End command must be preceeded by a timestamp byte 141 if (mAccumulatedBytes + 2 > mMaxPacketSize) { 142 // write out our data if there is no more room 143 // if necessary, block until previous packet is sent 144 flushLocked(true); 145 appendHeader(milliTimestamp); 146 } 147 mAccumulationBuffer[mAccumulatedBytes++] = 148 (byte)(0x80 | (milliTimestamp & 0x7F)); 149 mAccumulationBuffer[mAccumulatedBytes++] = MidiConstants.STATUS_END_SYSEX; 150 } 151 } else { 152 // Non-SysEx message 153 if (status != mRunningStatus) { 154 mAccumulationBuffer[mAccumulatedBytes++] = status; 155 if (MidiConstants.allowRunningStatus(status)) { 156 mRunningStatus = status; 157 } else if (MidiConstants.cancelsRunningStatus(status)) { 158 mRunningStatus = 0; 159 } 160 } 161 162 // now copy data bytes 163 int dataLength = count - 1; 164 System.arraycopy(msg, offset + 1, mAccumulationBuffer, mAccumulatedBytes, 165 dataLength); 166 mAccumulatedBytes += dataLength; 167 } 168 169 // write the packet if possible, but do not block 170 flushLocked(false); 171 } 172 } 173 }; 174 appendHeader(int milliTimestamp)175 private boolean appendHeader(int milliTimestamp) { 176 // write header if we are starting a new packet 177 if (mAccumulatedBytes == 0) { 178 // header byte with timestamp bits 7 - 12 179 mAccumulationBuffer[mAccumulatedBytes++] = 180 (byte)(0x80 | ((milliTimestamp >> 7) & 0x3F)); 181 mPacketTimestamp = milliTimestamp; 182 return true; 183 } else { 184 return false; 185 } 186 } 187 188 // MidiFramer for normalizing incoming data 189 private final MidiFramer mMidiFramer = new MidiFramer(mFramedDataReceiver); 190 BluetoothPacketEncoder(PacketReceiver packetReceiver, int maxPacketSize)191 public BluetoothPacketEncoder(PacketReceiver packetReceiver, int maxPacketSize) { 192 mPacketReceiver = packetReceiver; 193 mAccumulationBuffer = new byte[maxPacketSize]; 194 setMaxPacketSize(maxPacketSize); 195 } 196 197 /** 198 * Dynamically sets the maximum packet size 199 */ setMaxPacketSize(int maxPacketSize)200 public void setMaxPacketSize(int maxPacketSize) { 201 synchronized (mLock) { 202 mMaxPacketSize = Math.min(maxPacketSize, mAccumulationBuffer.length); 203 } 204 } 205 206 @Override onSend(byte[] msg, int offset, int count, long timestamp)207 public void onSend(byte[] msg, int offset, int count, long timestamp) 208 throws IOException { 209 // normalize the data by passing it through a MidiFramer first 210 mMidiFramer.send(msg, offset, count, timestamp); 211 } 212 213 @Override writeComplete()214 public void writeComplete() { 215 synchronized (mLock) { 216 mWritePending = false; 217 flushLocked(false); 218 mLock.notify(); 219 } 220 } 221 flushLocked(boolean canBlock)222 private void flushLocked(boolean canBlock) { 223 if (mWritePending && !canBlock) { 224 return; 225 } 226 227 while (mWritePending && mAccumulatedBytes > 0) { 228 try { 229 mLock.wait(); 230 } catch (InterruptedException e) { 231 // try again 232 continue; 233 } 234 } 235 236 if (mAccumulatedBytes > 0) { 237 boolean wasSendSuccessful = mPacketReceiver.writePacket(mAccumulationBuffer, 238 mAccumulatedBytes); 239 240 if (!wasSendSuccessful) { 241 byte[] failedBuffer = new byte[mAccumulatedBytes]; 242 System.arraycopy(mAccumulationBuffer, 0, failedBuffer, 0, mAccumulatedBytes); 243 mFailedToSendQueue.add(failedBuffer); 244 Log.d(TAG, "Enqueued data into failed queue."); 245 } 246 247 mAccumulatedBytes = 0; 248 mPacketTimestamp = 0; 249 mRunningStatus = 0; 250 mWritePending = wasSendSuccessful; 251 } 252 } 253 flushFailedToSendQueueLocked()254 private void flushFailedToSendQueueLocked() { 255 while (!mFailedToSendQueue.isEmpty()) { 256 while (mWritePending) { 257 try { 258 mLock.wait(); 259 } catch (InterruptedException e) { 260 // try again 261 continue; 262 } 263 } 264 byte[] currentBuffer = mFailedToSendQueue.element(); 265 266 boolean wasSendSuccessful = mPacketReceiver.writePacket(currentBuffer, 267 currentBuffer.length); 268 mWritePending = wasSendSuccessful; 269 if (wasSendSuccessful) { 270 mFailedToSendQueue.remove(); 271 Log.d(TAG, "Dequeued data from failed queue."); 272 } else { 273 return; 274 } 275 } 276 } 277 } 278