UsbMidiDevice.java revision 6d5a0f916499a69f28b860fd66d09b5025c30450
1/* 2 * Copyright (C) 2014 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 an 14 * limitations under the License. 15 */ 16 17package com.android.server.usb; 18 19import android.content.Context; 20import android.media.midi.MidiDeviceInfo; 21import android.media.midi.MidiDeviceServer; 22import android.media.midi.MidiDeviceStatus; 23import android.media.midi.MidiManager; 24import android.media.midi.MidiReceiver; 25import android.media.midi.MidiSender; 26import android.os.Bundle; 27import android.system.ErrnoException; 28import android.system.Os; 29import android.system.OsConstants; 30import android.system.StructPollfd; 31import android.util.Log; 32 33import com.android.internal.midi.MidiEventScheduler; 34import com.android.internal.midi.MidiEventScheduler.MidiEvent; 35 36import libcore.io.IoUtils; 37 38import java.io.Closeable; 39import java.io.FileDescriptor; 40import java.io.FileInputStream; 41import java.io.FileOutputStream; 42import java.io.IOException; 43 44public final class UsbMidiDevice implements Closeable { 45 private static final String TAG = "UsbMidiDevice"; 46 47 private final int mAlsaCard; 48 private final int mAlsaDevice; 49 private final int mSubdeviceCount; 50 private final InputReceiverProxy[] mInputPortReceivers; 51 52 private MidiDeviceServer mServer; 53 54 // event schedulers for each output port 55 private MidiEventScheduler[] mEventSchedulers; 56 57 private static final int BUFFER_SIZE = 512; 58 59 private FileDescriptor[] mFileDescriptors; 60 61 // for polling multiple FileDescriptors for MIDI events 62 private StructPollfd[] mPollFDs; 63 // streams for reading from ALSA driver 64 private FileInputStream[] mInputStreams; 65 // streams for writing to ALSA driver 66 private FileOutputStream[] mOutputStreams; 67 68 private final Object mLock = new Object(); 69 private boolean mIsOpen; 70 71 // pipe file descriptor for signalling input thread to exit 72 // only accessed from JNI code 73 private int mPipeFD = -1; 74 75 private final MidiDeviceServer.Callback mCallback = new MidiDeviceServer.Callback() { 76 77 @Override 78 public void onDeviceStatusChanged(MidiDeviceServer server, MidiDeviceStatus status) { 79 MidiDeviceInfo deviceInfo = status.getDeviceInfo(); 80 int inputPorts = deviceInfo.getInputPortCount(); 81 int outputPorts = deviceInfo.getOutputPortCount(); 82 boolean hasOpenPorts = false; 83 84 for (int i = 0; i < inputPorts; i++) { 85 if (status.isInputPortOpen(i)) { 86 hasOpenPorts = true; 87 break; 88 } 89 } 90 91 if (!hasOpenPorts) { 92 for (int i = 0; i < outputPorts; i++) { 93 if (status.getOutputPortOpenCount(i) > 0) { 94 hasOpenPorts = true; 95 break; 96 } 97 } 98 } 99 100 synchronized (mLock) { 101 if (hasOpenPorts && !mIsOpen) { 102 openLocked(); 103 } else if (!hasOpenPorts && mIsOpen) { 104 closeLocked(); 105 } 106 } 107 } 108 109 @Override 110 public void onClose() { 111 } 112 }; 113 114 // This class acts as a proxy for our MidiEventScheduler receivers, which do not exist 115 // until the device has active clients 116 private final class InputReceiverProxy extends MidiReceiver { 117 private MidiReceiver mReceiver; 118 119 @Override 120 public void onSend(byte[] msg, int offset, int count, long timestamp) throws IOException { 121 MidiReceiver receiver = mReceiver; 122 if (receiver != null) { 123 receiver.send(msg, offset, count, timestamp); 124 } 125 } 126 127 public void setReceiver(MidiReceiver receiver) { 128 mReceiver = receiver; 129 } 130 } 131 132 public static UsbMidiDevice create(Context context, Bundle properties, int card, int device) { 133 // FIXME - support devices with different number of input and output ports 134 int subDeviceCount = nativeGetSubdeviceCount(card, device); 135 if (subDeviceCount <= 0) { 136 Log.e(TAG, "nativeGetSubdeviceCount failed"); 137 return null; 138 } 139 140 UsbMidiDevice midiDevice = new UsbMidiDevice(card, device, subDeviceCount); 141 if (!midiDevice.register(context, properties)) { 142 IoUtils.closeQuietly(midiDevice); 143 Log.e(TAG, "createDeviceServer failed"); 144 return null; 145 } 146 return midiDevice; 147 } 148 149 private UsbMidiDevice(int card, int device, int subdeviceCount) { 150 mAlsaCard = card; 151 mAlsaDevice = device; 152 mSubdeviceCount = subdeviceCount; 153 154 // FIXME - support devices with different number of input and output ports 155 int inputCount = subdeviceCount; 156 mInputPortReceivers = new InputReceiverProxy[inputCount]; 157 for (int port = 0; port < inputCount; port++) { 158 mInputPortReceivers[port] = new InputReceiverProxy(); 159 } 160 } 161 162 private boolean openLocked() { 163 // FIXME - support devices with different number of input and output ports 164 FileDescriptor[] fileDescriptors = nativeOpen(mAlsaCard, mAlsaDevice, mSubdeviceCount); 165 if (fileDescriptors == null) { 166 Log.e(TAG, "nativeOpen failed"); 167 return false; 168 } 169 170 mFileDescriptors = fileDescriptors; 171 int inputCount = fileDescriptors.length; 172 // last file descriptor returned from nativeOpen() is only used for unblocking Os.poll() 173 // in our input thread 174 int outputCount = fileDescriptors.length - 1; 175 176 mPollFDs = new StructPollfd[inputCount]; 177 mInputStreams = new FileInputStream[inputCount]; 178 for (int i = 0; i < inputCount; i++) { 179 FileDescriptor fd = fileDescriptors[i]; 180 StructPollfd pollfd = new StructPollfd(); 181 pollfd.fd = fd; 182 pollfd.events = (short)OsConstants.POLLIN; 183 mPollFDs[i] = pollfd; 184 mInputStreams[i] = new FileInputStream(fd); 185 } 186 187 mOutputStreams = new FileOutputStream[outputCount]; 188 mEventSchedulers = new MidiEventScheduler[outputCount]; 189 for (int i = 0; i < outputCount; i++) { 190 mOutputStreams[i] = new FileOutputStream(fileDescriptors[i]); 191 192 MidiEventScheduler scheduler = new MidiEventScheduler(); 193 mEventSchedulers[i] = scheduler; 194 mInputPortReceivers[i].setReceiver(scheduler.getReceiver()); 195 } 196 197 final MidiReceiver[] outputReceivers = mServer.getOutputPortReceivers(); 198 199 // Create input thread which will read from all input ports 200 new Thread("UsbMidiDevice input thread") { 201 @Override 202 public void run() { 203 byte[] buffer = new byte[BUFFER_SIZE]; 204 try { 205 while (true) { 206 synchronized (mLock) { 207 if (!mIsOpen) break; 208 209 // look for a readable FileDescriptor 210 for (int index = 0; index < mPollFDs.length; index++) { 211 StructPollfd pfd = mPollFDs[index]; 212 if ((pfd.revents & (OsConstants.POLLERR 213 | OsConstants.POLLHUP)) != 0) { 214 break; 215 } else if ((pfd.revents & OsConstants.POLLIN) != 0) { 216 // clear readable flag 217 pfd.revents = 0; 218 219 if (index == mInputStreams.length - 1) { 220 // last file descriptor is used only for unblocking Os.poll() 221 break; 222 } 223 224 int count = mInputStreams[index].read(buffer); 225 outputReceivers[index].send(buffer, 0, count); 226 } 227 } 228 } 229 230 // wait until we have a readable port or we are signalled to close 231 Os.poll(mPollFDs, -1 /* infinite timeout */); 232 } 233 } catch (IOException e) { 234 Log.d(TAG, "reader thread exiting"); 235 } catch (ErrnoException e) { 236 Log.d(TAG, "reader thread exiting"); 237 } 238 Log.d(TAG, "input thread exit"); 239 } 240 }.start(); 241 242 // Create output thread for each output port 243 for (int port = 0; port < outputCount; port++) { 244 final MidiEventScheduler eventSchedulerF = mEventSchedulers[port]; 245 final FileOutputStream outputStreamF = mOutputStreams[port]; 246 final int portF = port; 247 248 new Thread("UsbMidiDevice output thread " + port) { 249 @Override 250 public void run() { 251 while (true) { 252 MidiEvent event; 253 try { 254 event = (MidiEvent)eventSchedulerF.waitNextEvent(); 255 } catch (InterruptedException e) { 256 // try again 257 continue; 258 } 259 if (event == null) { 260 break; 261 } 262 try { 263 outputStreamF.write(event.data, 0, event.count); 264 } catch (IOException e) { 265 Log.e(TAG, "write failed for port " + portF); 266 } 267 eventSchedulerF.addEventToPool(event); 268 } 269 Log.d(TAG, "output thread exit"); 270 } 271 }.start(); 272 } 273 274 mIsOpen = true; 275 return true; 276 } 277 278 private boolean register(Context context, Bundle properties) { 279 MidiManager midiManager = (MidiManager)context.getSystemService(Context.MIDI_SERVICE); 280 if (midiManager == null) { 281 Log.e(TAG, "No MidiManager in UsbMidiDevice.create()"); 282 return false; 283 } 284 285 mServer = midiManager.createDeviceServer(mInputPortReceivers, mSubdeviceCount, 286 null, null, properties, MidiDeviceInfo.TYPE_USB, mCallback); 287 if (mServer == null) { 288 return false; 289 } 290 291 return true; 292 } 293 294 @Override 295 public void close() throws IOException { 296 synchronized (mLock) { 297 if (mIsOpen) { 298 closeLocked(); 299 } 300 } 301 302 if (mServer != null) { 303 IoUtils.closeQuietly(mServer); 304 } 305 } 306 307 private void closeLocked() { 308 for (int i = 0; i < mEventSchedulers.length; i++) { 309 mInputPortReceivers[i].setReceiver(null); 310 mEventSchedulers[i].close(); 311 } 312 mEventSchedulers = null; 313 314 for (int i = 0; i < mInputStreams.length; i++) { 315 IoUtils.closeQuietly(mInputStreams[i]); 316 } 317 mInputStreams = null; 318 319 for (int i = 0; i < mOutputStreams.length; i++) { 320 IoUtils.closeQuietly(mOutputStreams[i]); 321 } 322 mOutputStreams = null; 323 324 // nativeClose will close the file descriptors and signal the input thread to exit 325 nativeClose(mFileDescriptors); 326 mFileDescriptors = null; 327 328 mIsOpen = false; 329 } 330 331 private static native int nativeGetSubdeviceCount(int card, int device); 332 private native FileDescriptor[] nativeOpen(int card, int device, int subdeviceCount); 333 private native void nativeClose(FileDescriptor[] fileDescriptors); 334} 335