UsbMidiDevice.java revision 2a57bc7fd602853dc1a22dcee1ff50f92cc29060
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.midi.MidiDeviceInfo; 21import android.midi.MidiDeviceServer; 22import android.midi.MidiManager; 23import android.midi.MidiPort; 24import android.midi.MidiReceiver; 25import android.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 java.io.Closeable; 34import java.io.FileDescriptor; 35import java.io.FileInputStream; 36import java.io.FileOutputStream; 37import java.io.IOException; 38 39public final class UsbMidiDevice implements Closeable { 40 private static final String TAG = "UsbMidiDevice"; 41 42 private final MidiDeviceServer mServer; 43 private final MidiReceiver[] mOutputPortReceivers; 44 45 // for polling multiple FileDescriptors for MIDI events 46 private final StructPollfd[] mPollFDs; 47 // streams for reading from ALSA driver 48 private final FileInputStream[] mInputStreams; 49 // streams for writing to ALSA driver 50 private final FileOutputStream[] mOutputStreams; 51 52 public static UsbMidiDevice create(Context context, Bundle properties, int card, int device) { 53 MidiManager midiManager = (MidiManager)context.getSystemService(Context.MIDI_SERVICE); 54 if (midiManager == null) { 55 Log.e(TAG, "No MidiManager in UsbMidiDevice.create()"); 56 return null; 57 } 58 59 // FIXME - support devices with different number of input and output ports 60 int subDevices = nativeGetSubdeviceCount(card, device); 61 if (subDevices <= 0) { 62 Log.e(TAG, "nativeGetSubdeviceCount failed"); 63 return null; 64 } 65 66 // FIXME - support devices with different number of input and output ports 67 FileDescriptor[] fileDescriptors = nativeOpen(card, device, subDevices); 68 if (fileDescriptors == null) { 69 Log.e(TAG, "nativeOpen failed"); 70 return null; 71 } 72 73 MidiDeviceServer server = midiManager.createDeviceServer(subDevices, subDevices, properties, 74 false, MidiDeviceInfo.TYPE_USB); 75 if (server == null) { 76 Log.e(TAG, "createDeviceServer failed"); 77 return null; 78 } 79 80 return new UsbMidiDevice(server, fileDescriptors, fileDescriptors); 81 } 82 83 private UsbMidiDevice(MidiDeviceServer server, FileDescriptor[] inputFiles, 84 FileDescriptor[] outputFiles) { 85 mServer = server; 86 int inputCount = inputFiles.length; 87 int outputCount = outputFiles.length; 88 89 mPollFDs = new StructPollfd[inputCount]; 90 mInputStreams = new FileInputStream[inputCount]; 91 for (int i = 0; i < inputCount; i++) { 92 FileDescriptor fd = inputFiles[i]; 93 StructPollfd pollfd = new StructPollfd(); 94 pollfd.fd = fd; 95 pollfd.events = (short)OsConstants.POLLIN; 96 mPollFDs[i] = pollfd; 97 mInputStreams[i] = new FileInputStream(fd); 98 } 99 100 mOutputStreams = new FileOutputStream[outputCount]; 101 for (int i = 0; i < outputCount; i++) { 102 mOutputStreams[i] = new FileOutputStream(outputFiles[i]); 103 } 104 105 mOutputPortReceivers = new MidiReceiver[outputCount]; 106 for (int port = 0; port < outputCount; port++) { 107 mOutputPortReceivers[port] = server.openOutputPortReceiver(port); 108 } 109 110 for (int port = 0; port < inputCount; port++) { 111 final int portNumberF = port; 112 MidiReceiver receiver = new MidiReceiver() { 113 114 @Override 115 public void onPost(byte[] data, int offset, int count, long timestamp) 116 throws IOException { 117 // FIXME - timestamps are ignored, future posting not supported yet. 118 mOutputStreams[portNumberF].write(data, offset, count); 119 } 120 }; 121 MidiSender sender = server.openInputPortSender(port); 122 sender.connect(receiver); 123 } 124 125 new Thread() { 126 @Override 127 public void run() { 128 byte[] buffer = new byte[MidiPort.MAX_PACKET_DATA_SIZE]; 129 try { 130 boolean done = false; 131 while (!done) { 132 // look for a readable FileDescriptor 133 for (int index = 0; index < mPollFDs.length; index++) { 134 StructPollfd pfd = mPollFDs[index]; 135 if ((pfd.revents & OsConstants.POLLIN) != 0) { 136 // clear readable flag 137 pfd.revents = 0; 138 139 int count = mInputStreams[index].read(buffer); 140 mOutputPortReceivers[index].onPost(buffer, 0, count, 141 System.nanoTime()); 142 } else if ((pfd.revents & (OsConstants.POLLERR 143 | OsConstants.POLLHUP)) != 0) { 144 done = true; 145 } 146 } 147 148 // wait until we have a readable port 149 Os.poll(mPollFDs, -1 /* infinite timeout */); 150 } 151 } catch (IOException e) { 152 Log.d(TAG, "reader thread exiting"); 153 } catch (ErrnoException e) { 154 Log.d(TAG, "reader thread exiting"); 155 } 156 } 157 }.start(); 158 } 159 160 @Override 161 public void close() throws IOException { 162 mServer.close(); 163 164 for (int i = 0; i < mInputStreams.length; i++) { 165 mInputStreams[i].close(); 166 } 167 for (int i = 0; i < mOutputStreams.length; i++) { 168 mOutputStreams[i].close(); 169 } 170 } 171 172 private static native int nativeGetSubdeviceCount(int card, int device); 173 private static native FileDescriptor[] nativeOpen(int card, int device, int subdeviceCount); 174} 175