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