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