Home | History | Annotate | Download | only in bluetoothmidiservice
      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 
     21 import com.android.internal.midi.MidiConstants;
     22 import com.android.internal.midi.MidiFramer;
     23 
     24 import java.io.IOException;
     25 
     26 /**
     27  * This class accumulates MIDI messages to form a MIDI packet.
     28  */
     29 public 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 onSend(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                 // Because of the MidiFramer, if it is not a status byte then it
     64                 // must be a continuation.
     65                 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 
     75                 // Status bytes must be preceded by a timestamp
     76                 boolean needsTimestamp = (status != mRunningStatus)
     77                         || (milliTimestamp != mPacketTimestamp);
     78                 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                 }
     85 
     86                 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 
     95                 // write the header if necessary
     96                 if (appendHeader(milliTimestamp)) {
     97                      needsTimestamp = !isSysExContinuation;
     98                 }
     99 
    100                 // write new timestamp byte if necessary
    101                 if (needsTimestamp) {
    102                     // timestamp byte with bits 0 - 6 of timestamp
    103                     mAccumulationBuffer[mAccumulatedBytes++] =
    104                             (byte)(0x80 | (milliTimestamp & 0x7F));
    105                     mPacketTimestamp = milliTimestamp;
    106                 }
    107 
    108                 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);
    113 
    114                     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                         }
    138                         mAccumulationBuffer[mAccumulatedBytes++] =
    139                                 (byte)(0x80 | (milliTimestamp & 0x7F));
    140                         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;
    155                     System.arraycopy(msg, offset + 1, mAccumulationBuffer, mAccumulatedBytes,
    156                             dataLength);
    157                     mAccumulatedBytes += dataLength;
    158                 }
    159 
    160                 // write the packet if possible, but do not block
    161                 flushLocked(false);
    162             }
    163         }
    164     };
    165 
    166     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
    170             mAccumulationBuffer[mAccumulatedBytes++] =
    171                     (byte)(0x80 | ((milliTimestamp >> 7) & 0x3F));
    172             mPacketTimestamp = milliTimestamp;
    173             return true;
    174         } else {
    175             return false;
    176         }
    177     }
    178 
    179     // 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
    188     public void onSend(byte[] msg, int offset, int count, long timestamp)
    189             throws IOException {
    190         // normalize the data by passing it through a MidiFramer first
    191         mMidiFramer.send(msg, offset, count, timestamp);
    192     }
    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 }
    226