blob: 8ac6b5674256efc6927e87953eb83cc78c9b14db [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
Phil Burk94ec5842017-02-21 16:15:33 -080051 private final Object mLock = new Object();
Mike Lockwoodf0a41d12015-03-24 08:27:11 -070052
53 // This receives normalized data from mMidiFramer and accumulates it into a packet buffer
54 private final MidiReceiver mFramedDataReceiver = new MidiReceiver() {
55 @Override
Mike Lockwood7eb441c2015-05-12 13:32:16 -070056 public void onSend(byte[] msg, int offset, int count, long timestamp)
Mike Lockwoodf0a41d12015-03-24 08:27:11 -070057 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);
Phil Burk94ec5842017-02-21 16:15:33 -080063 // Because of the MidiFramer, if it is not a status byte then it
64 // must be a continuation.
Mike Lockwood8c26d842015-05-01 14:36:44 -070065 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
Phil Burk94ec5842017-02-21 16:15:33 -080075 // Status bytes must be preceded by a timestamp
76 boolean needsTimestamp = (status != mRunningStatus)
77 || (milliTimestamp != mPacketTimestamp);
Mike Lockwood8c26d842015-05-01 14:36:44 -070078 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 }
Phil Burk94ec5842017-02-21 16:15:33 -080085
Mike Lockwoodf0a41d12015-03-24 08:27:11 -070086 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
Mike Lockwood8c26d842015-05-01 14:36:44 -070095 // write the header if necessary
96 if (appendHeader(milliTimestamp)) {
97 needsTimestamp = !isSysExContinuation;
Mike Lockwoodf0a41d12015-03-24 08:27:11 -070098 }
99
Mike Lockwood8c26d842015-05-01 14:36:44 -0700100 // write new timestamp byte if necessary
Mike Lockwoodf0a41d12015-03-24 08:27:11 -0700101 if (needsTimestamp) {
102 // timestamp byte with bits 0 - 6 of timestamp
103 mAccumulationBuffer[mAccumulatedBytes++] =
104 (byte)(0x80 | (milliTimestamp & 0x7F));
105 mPacketTimestamp = milliTimestamp;
106 }
107
Mike Lockwood8c26d842015-05-01 14:36:44 -0700108 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);
Mike Lockwoodf0a41d12015-03-24 08:27:11 -0700113
Mike Lockwood8c26d842015-05-01 14:36:44 -0700114 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 }
Mike Lockwood7eb441c2015-05-12 13:32:16 -0700138 mAccumulationBuffer[mAccumulatedBytes++] =
139 (byte)(0x80 | (milliTimestamp & 0x7F));
Mike Lockwood8c26d842015-05-01 14:36:44 -0700140 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;
Mike Lockwood7eb441c2015-05-12 13:32:16 -0700155 System.arraycopy(msg, offset + 1, mAccumulationBuffer, mAccumulatedBytes,
156 dataLength);
Mike Lockwood8c26d842015-05-01 14:36:44 -0700157 mAccumulatedBytes += dataLength;
158 }
Mike Lockwoodf0a41d12015-03-24 08:27:11 -0700159
160 // write the packet if possible, but do not block
161 flushLocked(false);
162 }
163 }
164 };
165
Mike Lockwood8c26d842015-05-01 14:36:44 -0700166 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
Mike Lockwood7eb441c2015-05-12 13:32:16 -0700170 mAccumulationBuffer[mAccumulatedBytes++] =
171 (byte)(0x80 | ((milliTimestamp >> 7) & 0x3F));
Mike Lockwood8c26d842015-05-01 14:36:44 -0700172 mPacketTimestamp = milliTimestamp;
173 return true;
174 } else {
175 return false;
176 }
177 }
178
Mike Lockwoodf0a41d12015-03-24 08:27:11 -0700179 // 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
Mike Lockwood7eb441c2015-05-12 13:32:16 -0700188 public void onSend(byte[] msg, int offset, int count, long timestamp)
Mike Lockwoodf0a41d12015-03-24 08:27:11 -0700189 throws IOException {
190 // normalize the data by passing it through a MidiFramer first
Mike Lockwood7eb441c2015-05-12 13:32:16 -0700191 mMidiFramer.send(msg, offset, count, timestamp);
Mike Lockwoodf0a41d12015-03-24 08:27:11 -0700192 }
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}