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                 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 
     73                 boolean needsTimestamp = (milliTimestamp != mPacketTimestamp);
     74                 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                 }
     81                 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 
     90                 // write the header if necessary
     91                 if (appendHeader(milliTimestamp)) {
     92                      needsTimestamp = !isSysExContinuation;
     93                 }
     94 
     95                 // write new timestamp byte if necessary
     96                 if (needsTimestamp) {
     97                     // timestamp byte with bits 0 - 6 of timestamp
     98                     mAccumulationBuffer[mAccumulatedBytes++] =
     99                             (byte)(0x80 | (milliTimestamp & 0x7F));
    100                     mPacketTimestamp = milliTimestamp;
    101                 }
    102 
    103                 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);
    108 
    109                     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++] =
    134                                 (byte)(0x80 | (milliTimestamp & 0x7F));
    135                         mAccumulationBuffer[mAccumulatedBytes++] = MidiConstants.STATUS_END_SYSEX;
    136                     }
    137                 } else {
    138                     // Non-SysEx message
    139                     if (status != mRunningStatus) {
    140                         mAccumulationBuffer[mAccumulatedBytes++] = status;
    141                         if (MidiConstants.allowRunningStatus(status)) {
    142                             mRunningStatus = status;
    143                         } else if (MidiConstants.cancelsRunningStatus(status)) {
    144                             mRunningStatus = 0;
    145                         }
    146                     }
    147 
    148                     // now copy data bytes
    149                     int dataLength = count - 1;
    150                     System.arraycopy(msg, offset + 1, mAccumulationBuffer, mAccumulatedBytes,
    151                             dataLength);
    152                     mAccumulatedBytes += dataLength;
    153                 }
    154 
    155                 // write the packet if possible, but do not block
    156                 flushLocked(false);
    157             }
    158         }
    159     };
    160 
    161     private boolean appendHeader(int milliTimestamp) {
    162         // write header if we are starting a new packet
    163         if (mAccumulatedBytes == 0) {
    164             // header byte with timestamp bits 7 - 12
    165             mAccumulationBuffer[mAccumulatedBytes++] =
    166                     (byte)(0x80 | ((milliTimestamp >> 7) & 0x3F));
    167             mPacketTimestamp = milliTimestamp;
    168             return true;
    169         } else {
    170             return false;
    171         }
    172     }
    173 
    174     // MidiFramer for normalizing incoming data
    175     private final MidiFramer mMidiFramer = new MidiFramer(mFramedDataReceiver);
    176 
    177     public BluetoothPacketEncoder(PacketReceiver packetReceiver, int maxPacketSize) {
    178         mPacketReceiver = packetReceiver;
    179         mAccumulationBuffer = new byte[maxPacketSize];
    180     }
    181 
    182     @Override
    183     public void onSend(byte[] msg, int offset, int count, long timestamp)
    184             throws IOException {
    185         // normalize the data by passing it through a MidiFramer first
    186         mMidiFramer.send(msg, offset, count, timestamp);
    187     }
    188 
    189     @Override
    190     public void writeComplete() {
    191         synchronized (mLock) {
    192             mWritePending = false;
    193             flushLocked(false);
    194             mLock.notify();
    195         }
    196     }
    197 
    198     private void flushLocked(boolean canBlock) {
    199         if (mWritePending && !canBlock) {
    200             return;
    201         }
    202 
    203         while (mWritePending && mAccumulatedBytes > 0) {
    204             try {
    205                 mLock.wait();
    206             } catch (InterruptedException e) {
    207                 // try again
    208                 continue;
    209             }
    210         }
    211 
    212         if (mAccumulatedBytes > 0) {
    213             mPacketReceiver.writePacket(mAccumulationBuffer, mAccumulatedBytes);
    214             mAccumulatedBytes = 0;
    215             mPacketTimestamp = 0;
    216             mRunningStatus = 0;
    217             mWritePending = true;
    218         }
    219     }
    220 }
    221