BluetoothPacketEncoder.java revision 8c26d843a786e5ee56046245fbf72a81b533bcb9
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 onReceive(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++] = (byte)(0x80 | (milliTimestamp & 0x7F)); 134 mAccumulationBuffer[mAccumulatedBytes++] = MidiConstants.STATUS_END_SYSEX; 135 } 136 } else { 137 // Non-SysEx message 138 if (status != mRunningStatus) { 139 mAccumulationBuffer[mAccumulatedBytes++] = status; 140 if (MidiConstants.allowRunningStatus(status)) { 141 mRunningStatus = status; 142 } else if (MidiConstants.cancelsRunningStatus(status)) { 143 mRunningStatus = 0; 144 } 145 } 146 147 // now copy data bytes 148 int dataLength = count - 1; 149 System.arraycopy(msg, offset + 1, mAccumulationBuffer, mAccumulatedBytes, dataLength); 150 mAccumulatedBytes += dataLength; 151 } 152 153 // write the packet if possible, but do not block 154 flushLocked(false); 155 } 156 } 157 }; 158 159 private boolean appendHeader(int milliTimestamp) { 160 // write header if we are starting a new packet 161 if (mAccumulatedBytes == 0) { 162 // header byte with timestamp bits 7 - 12 163 mAccumulationBuffer[mAccumulatedBytes++] = (byte)(0x80 | ((milliTimestamp >> 7) & 0x3F)); 164 mPacketTimestamp = milliTimestamp; 165 return true; 166 } else { 167 return false; 168 } 169 } 170 171 // MidiFramer for normalizing incoming data 172 private final MidiFramer mMidiFramer = new MidiFramer(mFramedDataReceiver); 173 174 public BluetoothPacketEncoder(PacketReceiver packetReceiver, int maxPacketSize) { 175 mPacketReceiver = packetReceiver; 176 mAccumulationBuffer = new byte[maxPacketSize]; 177 } 178 179 @Override 180 public void onReceive(byte[] msg, int offset, int count, long timestamp) 181 throws IOException { 182 // normalize the data by passing it through a MidiFramer first 183 mMidiFramer.sendWithTimestamp(msg, offset, count, timestamp); 184 } 185 186 @Override 187 public void writeComplete() { 188 synchronized (mLock) { 189 mWritePending = false; 190 flushLocked(false); 191 mLock.notify(); 192 } 193 } 194 195 private void flushLocked(boolean canBlock) { 196 if (mWritePending && !canBlock) { 197 return; 198 } 199 200 while (mWritePending && mAccumulatedBytes > 0) { 201 try { 202 mLock.wait(); 203 } catch (InterruptedException e) { 204 // try again 205 continue; 206 } 207 } 208 209 if (mAccumulatedBytes > 0) { 210 mPacketReceiver.writePacket(mAccumulationBuffer, mAccumulatedBytes); 211 mAccumulatedBytes = 0; 212 mPacketTimestamp = 0; 213 mRunningStatus = 0; 214 mWritePending = true; 215 } 216 } 217} 218