BluetoothPacketEncoder.java revision f0a41d1c591193fbe02c9ddbaf24c79af4da9972
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 int 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 int milliTimestamp = (int)(timestamp / MILLISECOND_NANOS) & MILLISECOND_MASK; 60 int status = msg[0] & 0xFF; 61 62 synchronized (mLock) { 63 boolean needsTimestamp = (milliTimestamp != mPacketTimestamp); 64 int bytesNeeded = count; 65 if (needsTimestamp) bytesNeeded++; // add one for timestamp byte 66 if (status == mRunningStatus) bytesNeeded--; // subtract one for status byte 67 68 if (mAccumulatedBytes + bytesNeeded > mAccumulationBuffer.length) { 69 // write out our data if there is no more room 70 // if necessary, block until previous packet is sent 71 flushLocked(true); 72 } 73 74 // write header if we are starting a new packet 75 if (mAccumulatedBytes == 0) { 76 // header byte with timestamp bits 7 - 12 77 mAccumulationBuffer[mAccumulatedBytes++] = (byte)(0x80 | (milliTimestamp >> 7)); 78 mPacketTimestamp = milliTimestamp; 79 needsTimestamp = true; 80 } 81 82 // write new timestamp byte and status byte if necessary 83 if (needsTimestamp) { 84 // timestamp byte with bits 0 - 6 of timestamp 85 mAccumulationBuffer[mAccumulatedBytes++] = 86 (byte)(0x80 | (milliTimestamp & 0x7F)); 87 mPacketTimestamp = milliTimestamp; 88 } 89 90 if (status != mRunningStatus) { 91 mAccumulationBuffer[mAccumulatedBytes++] = (byte)status; 92 if (MidiConstants.allowRunningStatus(status)) { 93 mRunningStatus = status; 94 } else if (MidiConstants.allowRunningStatus(status)) { 95 mRunningStatus = 0; 96 } 97 } 98 99 // now copy data bytes 100 int dataLength = count - 1; 101 System.arraycopy(msg, 1, mAccumulationBuffer, mAccumulatedBytes, dataLength); 102 // FIXME - handle long SysEx properly 103 mAccumulatedBytes += dataLength; 104 105 // write the packet if possible, but do not block 106 flushLocked(false); 107 } 108 } 109 }; 110 111 // MidiFramer for normalizing incoming data 112 private final MidiFramer mMidiFramer = new MidiFramer(mFramedDataReceiver); 113 114 public BluetoothPacketEncoder(PacketReceiver packetReceiver, int maxPacketSize) { 115 mPacketReceiver = packetReceiver; 116 mAccumulationBuffer = new byte[maxPacketSize]; 117 } 118 119 @Override 120 public void onReceive(byte[] msg, int offset, int count, long timestamp) 121 throws IOException { 122 // normalize the data by passing it through a MidiFramer first 123 mMidiFramer.sendWithTimestamp(msg, offset, count, timestamp); 124 } 125 126 @Override 127 public void writeComplete() { 128 synchronized (mLock) { 129 mWritePending = false; 130 flushLocked(false); 131 mLock.notify(); 132 } 133 } 134 135 private void flushLocked(boolean canBlock) { 136 if (mWritePending && !canBlock) { 137 return; 138 } 139 140 while (mWritePending && mAccumulatedBytes > 0) { 141 try { 142 mLock.wait(); 143 } catch (InterruptedException e) { 144 // try again 145 continue; 146 } 147 } 148 149 if (mAccumulatedBytes > 0) { 150 mPacketReceiver.writePacket(mAccumulationBuffer, mAccumulatedBytes); 151 mAccumulatedBytes = 0; 152 mPacketTimestamp = 0; 153 mRunningStatus = 0; 154 mWritePending = true; 155 } 156 } 157} 158