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 import android.util.Log;
21 
22 import com.android.internal.midi.MidiConstants;
23 import com.android.internal.midi.MidiFramer;
24 
25 import java.io.IOException;
26 import java.util.ArrayDeque;
27 import java.util.Queue;
28 
29 /**
30  * This class accumulates MIDI messages to form a MIDI packet.
31  */
32 public class BluetoothPacketEncoder extends PacketEncoder {
33 
34     private static final String TAG = "BluetoothPacketEncoder";
35 
36     private static final long MILLISECOND_NANOS = 1000000L;
37 
38     // mask for generating 13 bit timestamps
39     private static final int MILLISECOND_MASK = 0x1FFF;
40 
41     private final PacketReceiver mPacketReceiver;
42 
43     // buffer for accumulating messages to write
44     private final byte[] mAccumulationBuffer;
45     // number of bytes currently in mAccumulationBuffer
46     private int mAccumulatedBytes;
47     // timestamp for first message in current packet
48     private int mPacketTimestamp;
49     // current running status, or zero if none
50     private byte mRunningStatus;
51     // max size of a packet
52     private int mMaxPacketSize;
53 
54     private boolean mWritePending;
55 
56     private final Object mLock = new Object();
57 
58     private Queue<byte[]> mFailedToSendQueue = new ArrayDeque<byte[]>();
59 
60     // This receives normalized data from mMidiFramer and accumulates it into a packet buffer
61     private final MidiReceiver mFramedDataReceiver = new MidiReceiver() {
62         @Override
63         public void onSend(byte[] msg, int offset, int count, long timestamp)
64                 throws IOException {
65 
66             synchronized (mLock) {
67                 flushFailedToSendQueueLocked();
68 
69                 int milliTimestamp = (int)(timestamp / MILLISECOND_NANOS) & MILLISECOND_MASK;
70                 byte status = msg[offset];
71                 boolean isSysExStart = (status == MidiConstants.STATUS_SYSTEM_EXCLUSIVE);
72                 // Because of the MidiFramer, if it is not a status byte then it
73                 // must be a continuation.
74                 boolean isSysExContinuation = ((status & 0x80) == 0);
75 
76                 int bytesNeeded;
77                 if (isSysExStart || isSysExContinuation) {
78                     // SysEx messages can be split into multiple packets
79                     bytesNeeded = 1;
80                 } else {
81                     bytesNeeded = count;
82                 }
83 
84                 // Status bytes must be preceded by a timestamp
85                 boolean needsTimestamp = (status != mRunningStatus)
86                         || (milliTimestamp != mPacketTimestamp);
87                 if (isSysExStart) {
88                     // SysEx start byte must be preceded by a timestamp
89                     needsTimestamp = true;
90                 } else if (isSysExContinuation) {
91                     // SysEx continuation packets must not have timestamp byte
92                     needsTimestamp = false;
93                 }
94 
95                 if (needsTimestamp) bytesNeeded++;  // add one for timestamp byte
96                 if (status == mRunningStatus) bytesNeeded--;    // subtract one for status byte
97 
98                 if (mAccumulatedBytes + bytesNeeded > mMaxPacketSize) {
99                     // write out our data if there is no more room
100                     // if necessary, block until previous packet is sent
101                     flushLocked(true);
102                 }
103 
104                 // write the header if necessary
105                 if (appendHeader(milliTimestamp)) {
106                      needsTimestamp = !isSysExContinuation;
107                 }
108 
109                 // write new timestamp byte if necessary
110                 if (needsTimestamp) {
111                     // timestamp byte with bits 0 - 6 of timestamp
112                     mAccumulationBuffer[mAccumulatedBytes++] =
113                             (byte)(0x80 | (milliTimestamp & 0x7F));
114                     mPacketTimestamp = milliTimestamp;
115                 }
116 
117                 if (isSysExStart || isSysExContinuation) {
118                     // MidiFramer will end the packet with SysEx End if there is one in the buffer
119                     boolean hasSysExEnd =
120                             (msg[offset + count - 1] == MidiConstants.STATUS_END_SYSEX);
121                     int remaining = (hasSysExEnd ? count - 1 : count);
122 
123                     while (remaining > 0) {
124                         if (mAccumulatedBytes == mMaxPacketSize) {
125                             // write out our data if there is no more room
126                             // if necessary, block until previous packet is sent
127                             flushLocked(true);
128                             appendHeader(milliTimestamp);
129                         }
130 
131                         int copy = mMaxPacketSize - mAccumulatedBytes;
132                         if (copy > remaining) copy = remaining;
133                         System.arraycopy(msg, offset, mAccumulationBuffer, mAccumulatedBytes, copy);
134                         mAccumulatedBytes += copy;
135                         offset += copy;
136                         remaining -= copy;
137                     }
138 
139                     if (hasSysExEnd) {
140                         // SysEx End command must be preceeded by a timestamp byte
141                         if (mAccumulatedBytes + 2 > mMaxPacketSize) {
142                             // write out our data if there is no more room
143                             // if necessary, block until previous packet is sent
144                             flushLocked(true);
145                             appendHeader(milliTimestamp);
146                         }
147                         mAccumulationBuffer[mAccumulatedBytes++] =
148                                 (byte)(0x80 | (milliTimestamp & 0x7F));
149                         mAccumulationBuffer[mAccumulatedBytes++] = MidiConstants.STATUS_END_SYSEX;
150                     }
151                 } else {
152                     // Non-SysEx message
153                     if (status != mRunningStatus) {
154                         mAccumulationBuffer[mAccumulatedBytes++] = status;
155                         if (MidiConstants.allowRunningStatus(status)) {
156                             mRunningStatus = status;
157                         } else if (MidiConstants.cancelsRunningStatus(status)) {
158                             mRunningStatus = 0;
159                         }
160                     }
161 
162                     // now copy data bytes
163                     int dataLength = count - 1;
164                     System.arraycopy(msg, offset + 1, mAccumulationBuffer, mAccumulatedBytes,
165                             dataLength);
166                     mAccumulatedBytes += dataLength;
167                 }
168 
169                 // write the packet if possible, but do not block
170                 flushLocked(false);
171             }
172         }
173     };
174 
appendHeader(int milliTimestamp)175     private boolean appendHeader(int milliTimestamp) {
176         // write header if we are starting a new packet
177         if (mAccumulatedBytes == 0) {
178             // header byte with timestamp bits 7 - 12
179             mAccumulationBuffer[mAccumulatedBytes++] =
180                     (byte)(0x80 | ((milliTimestamp >> 7) & 0x3F));
181             mPacketTimestamp = milliTimestamp;
182             return true;
183         } else {
184             return false;
185         }
186     }
187 
188     // MidiFramer for normalizing incoming data
189     private final MidiFramer mMidiFramer = new MidiFramer(mFramedDataReceiver);
190 
BluetoothPacketEncoder(PacketReceiver packetReceiver, int maxPacketSize)191     public BluetoothPacketEncoder(PacketReceiver packetReceiver, int maxPacketSize) {
192         mPacketReceiver = packetReceiver;
193         mAccumulationBuffer = new byte[maxPacketSize];
194         setMaxPacketSize(maxPacketSize);
195     }
196 
197     /**
198      * Dynamically sets the maximum packet size
199      */
setMaxPacketSize(int maxPacketSize)200     public void setMaxPacketSize(int maxPacketSize) {
201         synchronized (mLock) {
202             mMaxPacketSize = Math.min(maxPacketSize, mAccumulationBuffer.length);
203         }
204     }
205 
206     @Override
onSend(byte[] msg, int offset, int count, long timestamp)207     public void onSend(byte[] msg, int offset, int count, long timestamp)
208             throws IOException {
209         // normalize the data by passing it through a MidiFramer first
210         mMidiFramer.send(msg, offset, count, timestamp);
211     }
212 
213     @Override
writeComplete()214     public void writeComplete() {
215         synchronized (mLock) {
216             mWritePending = false;
217             flushLocked(false);
218             mLock.notify();
219         }
220     }
221 
flushLocked(boolean canBlock)222     private void flushLocked(boolean canBlock) {
223         if (mWritePending && !canBlock) {
224             return;
225         }
226 
227         while (mWritePending && mAccumulatedBytes > 0) {
228             try {
229                 mLock.wait();
230             } catch (InterruptedException e) {
231                 // try again
232                 continue;
233             }
234         }
235 
236         if (mAccumulatedBytes > 0) {
237             boolean wasSendSuccessful = mPacketReceiver.writePacket(mAccumulationBuffer,
238                     mAccumulatedBytes);
239 
240             if (!wasSendSuccessful) {
241                 byte[] failedBuffer = new byte[mAccumulatedBytes];
242                 System.arraycopy(mAccumulationBuffer, 0, failedBuffer, 0, mAccumulatedBytes);
243                 mFailedToSendQueue.add(failedBuffer);
244                 Log.d(TAG, "Enqueued data into failed queue.");
245             }
246 
247             mAccumulatedBytes = 0;
248             mPacketTimestamp = 0;
249             mRunningStatus = 0;
250             mWritePending = wasSendSuccessful;
251         }
252     }
253 
flushFailedToSendQueueLocked()254     private void flushFailedToSendQueueLocked() {
255         while (!mFailedToSendQueue.isEmpty()) {
256             while (mWritePending) {
257                 try {
258                     mLock.wait();
259                 } catch (InterruptedException e) {
260                     // try again
261                     continue;
262                 }
263             }
264             byte[] currentBuffer = mFailedToSendQueue.element();
265 
266             boolean wasSendSuccessful = mPacketReceiver.writePacket(currentBuffer,
267                     currentBuffer.length);
268             mWritePending = wasSendSuccessful;
269             if (wasSendSuccessful) {
270                 mFailedToSendQueue.remove();
271                 Log.d(TAG, "Dequeued data from failed queue.");
272             } else {
273                 return;
274             }
275         }
276     }
277 }
278