blob: 99ea3532fc4c79f860a79fafa6c7bfb3d5f6c8cc [file] [log] [blame]
Mike Lockwoodf0a41d12015-03-24 08:27:11 -07001/*
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
Mike Lockwood8c26d842015-05-01 14:36:44 -070047 private byte mRunningStatus;
Mike Lockwoodf0a41d12015-03-24 08:27:11 -070048
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
Mike Lockwoodf0a41d12015-03-24 08:27:11 -070059 synchronized (mLock) {
Mike Lockwood8c26d842015-05-01 14:36:44 -070060 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
Mike Lockwoodf0a41d12015-03-24 08:27:11 -070073 boolean needsTimestamp = (milliTimestamp != mPacketTimestamp);
Mike Lockwood8c26d842015-05-01 14:36:44 -070074 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 }
Mike Lockwoodf0a41d12015-03-24 08:27:11 -070081 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
Mike Lockwood8c26d842015-05-01 14:36:44 -070090 // write the header if necessary
91 if (appendHeader(milliTimestamp)) {
92 needsTimestamp = !isSysExContinuation;
Mike Lockwoodf0a41d12015-03-24 08:27:11 -070093 }
94
Mike Lockwood8c26d842015-05-01 14:36:44 -070095 // write new timestamp byte if necessary
Mike Lockwoodf0a41d12015-03-24 08:27:11 -070096 if (needsTimestamp) {
97 // timestamp byte with bits 0 - 6 of timestamp
98 mAccumulationBuffer[mAccumulatedBytes++] =
99 (byte)(0x80 | (milliTimestamp & 0x7F));
100 mPacketTimestamp = milliTimestamp;
101 }
102
Mike Lockwood8c26d842015-05-01 14:36:44 -0700103 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);
Mike Lockwoodf0a41d12015-03-24 08:27:11 -0700108
Mike Lockwood8c26d842015-05-01 14:36:44 -0700109 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 }
Mike Lockwoodf0a41d12015-03-24 08:27:11 -0700152
153 // write the packet if possible, but do not block
154 flushLocked(false);
155 }
156 }
157 };
158
Mike Lockwood8c26d842015-05-01 14:36:44 -0700159 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
Mike Lockwoodf0a41d12015-03-24 08:27:11 -0700171 // 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}