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