UsbMidiDevice.java revision 2776133be7ac60dc8d6aea5b12e35449ca331836
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.MidiManager; 23import android.media.midi.MidiReceiver; 24import android.media.midi.MidiSender; 25import android.os.Bundle; 26import android.system.ErrnoException; 27import android.system.Os; 28import android.system.OsConstants; 29import android.system.StructPollfd; 30import android.util.Log; 31 32import com.android.internal.midi.MidiEventScheduler; 33import com.android.internal.midi.MidiEventScheduler.MidiEvent; 34 35import libcore.io.IoUtils; 36 37import java.io.Closeable; 38import java.io.FileDescriptor; 39import java.io.FileInputStream; 40import java.io.FileOutputStream; 41import java.io.IOException; 42 43public final class UsbMidiDevice implements Closeable { 44 private static final String TAG = "UsbMidiDevice"; 45 46 private MidiDeviceServer mServer; 47 48 private final MidiEventScheduler mEventScheduler; 49 50 private static final int BUFFER_SIZE = 512; 51 52 private final FileDescriptor[] mFileDescriptors; 53 54 // for polling multiple FileDescriptors for MIDI events 55 private final StructPollfd[] mPollFDs; 56 // streams for reading from ALSA driver 57 private final FileInputStream[] mInputStreams; 58 // streams for writing to ALSA driver 59 private final FileOutputStream[] mOutputStreams; 60 61 public static UsbMidiDevice create(Context context, Bundle properties, int card, int device) { 62 // FIXME - support devices with different number of input and output ports 63 int subDevices = nativeGetSubdeviceCount(card, device); 64 if (subDevices <= 0) { 65 Log.e(TAG, "nativeGetSubdeviceCount failed"); 66 return null; 67 } 68 69 // FIXME - support devices with different number of input and output ports 70 FileDescriptor[] fileDescriptors = nativeOpen(card, device, subDevices); 71 if (fileDescriptors == null) { 72 Log.e(TAG, "nativeOpen failed"); 73 return null; 74 } 75 76 UsbMidiDevice midiDevice = new UsbMidiDevice(fileDescriptors); 77 if (!midiDevice.register(context, properties)) { 78 IoUtils.closeQuietly(midiDevice); 79 Log.e(TAG, "createDeviceServer failed"); 80 return null; 81 } 82 return midiDevice; 83 } 84 85 private UsbMidiDevice(FileDescriptor[] fileDescriptors) { 86 mFileDescriptors = fileDescriptors; 87 int inputCount = fileDescriptors.length; 88 int outputCount = fileDescriptors.length; 89 90 mPollFDs = new StructPollfd[inputCount]; 91 mInputStreams = new FileInputStream[inputCount]; 92 for (int i = 0; i < inputCount; i++) { 93 FileDescriptor fd = fileDescriptors[i]; 94 StructPollfd pollfd = new StructPollfd(); 95 pollfd.fd = fd; 96 pollfd.events = (short)OsConstants.POLLIN; 97 mPollFDs[i] = pollfd; 98 mInputStreams[i] = new FileInputStream(fd); 99 } 100 101 mOutputStreams = new FileOutputStream[outputCount]; 102 for (int i = 0; i < outputCount; i++) { 103 mOutputStreams[i] = new FileOutputStream(fileDescriptors[i]); 104 } 105 mEventScheduler = new MidiEventScheduler(inputCount); 106 } 107 108 private boolean register(Context context, Bundle properties) { 109 MidiManager midiManager = (MidiManager)context.getSystemService(Context.MIDI_SERVICE); 110 if (midiManager == null) { 111 Log.e(TAG, "No MidiManager in UsbMidiDevice.create()"); 112 return false; 113 } 114 115 int inputCount = mInputStreams.length; 116 int outputCount = mOutputStreams.length; 117 MidiReceiver[] inputPortReceivers = new MidiReceiver[inputCount]; 118 for (int port = 0; port < inputCount; port++) { 119 inputPortReceivers[port] = mEventScheduler.getReceiver(port); 120 } 121 122 mServer = midiManager.createDeviceServer(inputPortReceivers, outputCount, 123 null, null, properties, MidiDeviceInfo.TYPE_USB, null); 124 if (mServer == null) { 125 return false; 126 } 127 final MidiReceiver[] outputReceivers = mServer.getOutputPortReceivers(); 128 129 // Create input thread 130 new Thread("UsbMidiDevice input thread") { 131 @Override 132 public void run() { 133 byte[] buffer = new byte[BUFFER_SIZE]; 134 try { 135 boolean done = false; 136 while (!done) { 137 // look for a readable FileDescriptor 138 for (int index = 0; index < mPollFDs.length; index++) { 139 StructPollfd pfd = mPollFDs[index]; 140 if ((pfd.revents & OsConstants.POLLIN) != 0) { 141 // clear readable flag 142 pfd.revents = 0; 143 144 int count = mInputStreams[index].read(buffer); 145 outputReceivers[index].send(buffer, 0, count); 146 } else if ((pfd.revents & (OsConstants.POLLERR 147 | OsConstants.POLLHUP)) != 0) { 148 done = true; 149 } 150 } 151 152 // wait until we have a readable port 153 Os.poll(mPollFDs, -1 /* infinite timeout */); 154 } 155 } catch (IOException e) { 156 Log.d(TAG, "reader thread exiting"); 157 } catch (ErrnoException e) { 158 Log.d(TAG, "reader thread exiting"); 159 } 160 Log.d(TAG, "input thread exit"); 161 } 162 }.start(); 163 164 // Create output thread 165 new Thread("UsbMidiDevice output thread") { 166 @Override 167 public void run() { 168 while (true) { 169 MidiEvent event; 170 try { 171 event = (MidiEvent)mEventScheduler.waitNextEvent(); 172 } catch (InterruptedException e) { 173 // try again 174 continue; 175 } 176 if (event == null) { 177 break; 178 } 179 try { 180 mOutputStreams[event.portNumber].write(event.data, 0, event.count); 181 } catch (IOException e) { 182 Log.e(TAG, "write failed for port " + event.portNumber); 183 } 184 mEventScheduler.addEventToPool(event); 185 } 186 Log.d(TAG, "output thread exit"); 187 } 188 }.start(); 189 190 return true; 191 } 192 193 @Override 194 public void close() throws IOException { 195 mEventScheduler.close(); 196 197 if (mServer != null) { 198 mServer.close(); 199 } 200 201 for (int i = 0; i < mInputStreams.length; i++) { 202 mInputStreams[i].close(); 203 } 204 for (int i = 0; i < mOutputStreams.length; i++) { 205 mOutputStreams[i].close(); 206 } 207 nativeClose(mFileDescriptors); 208 } 209 210 private static native int nativeGetSubdeviceCount(int card, int device); 211 private static native FileDescriptor[] nativeOpen(int card, int device, int subdeviceCount); 212 private static native void nativeClose(FileDescriptor[] fileDescriptors); 213} 214