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