diff --git a/services/core/jni/com_android_server_UsbMidiDevice.cpp b/services/core/jni/com_android_server_UsbMidiDevice.cpp index d6b5bed173ebe..c8e769878b8c5 100644 --- a/services/core/jni/com_android_server_UsbMidiDevice.cpp +++ b/services/core/jni/com_android_server_UsbMidiDevice.cpp @@ -24,14 +24,14 @@ #include "android_runtime/AndroidRuntime.h" #include "android_runtime/Log.h" -#include -#include #include -#include -#include +#include #include -#include #include +#include +#include +#include +#include namespace android { @@ -39,78 +39,62 @@ namespace android static jclass sFileDescriptorClass; static jfieldID sPipeFDField; -static jint -android_server_UsbMidiDevice_get_subdevice_count(JNIEnv *env, jobject /* thiz */, - jint card, jint device) -{ - char path[100]; - int fd; - const int kMaxRetries = 10; - const int kSleepMicroseconds = 2000; - - snprintf(path, sizeof(path), "/dev/snd/controlC%d", card); - // This control device may not have been created yet. So we should - // try to open it several times to prevent intermittent failure - // from a race condition. - int retryCounter = 0; - while ((fd = open(path, O_RDWR)) < 0) { - if (++retryCounter > kMaxRetries) { - ALOGE("timed out after %d tries, could not open %s", retryCounter, path); - return 0; - } else { - ALOGW("attempt #%d, could not open %s", retryCounter, path); - // Increase the sleep interval each time. - // 10 retries will total 2 * sum(1..10) = 110 milliseconds. - // Typically the device should be ready in 5-10 milliseconds. - usleep(kSleepMicroseconds * retryCounter); - } - } - - struct snd_rawmidi_info info; - memset(&info, 0, sizeof(info)); - info.device = device; - int ret = ioctl(fd, SNDRV_CTL_IOCTL_RAWMIDI_INFO, &info); - close(fd); - - if (ret < 0) { - ALOGE("SNDRV_CTL_IOCTL_RAWMIDI_INFO failed, errno: %d path: %s", errno, path); - return -1; - } - - ALOGD("subdevices_count: %d", info.subdevices_count); - return info.subdevices_count; -} - -static jobjectArray -android_server_UsbMidiDevice_open(JNIEnv *env, jobject thiz, jint card, jint device, - jint subdevice_count) -{ +// This function returns an array of integers, each representing a file descriptor. +// The will be in the order of inputs then outputs. +// The last input fd will be for a file descriptor that simply allows Os.poll() to keep working. +// For example, if numInputs is 2 and numOutputs is 1, the resulting fds are as follows: +// 1. Input O_RDONLY file descriptor +// 2. Special input file descriptor to block the input thread +// 3. Output O_WRONLY file descriptor +static jobjectArray android_server_UsbMidiDevice_open(JNIEnv *env, jobject thiz, jint card, + jint device, jint numInputs, + jint numOutputs) { char path[100]; + int fd; snprintf(path, sizeof(path), "/dev/snd/midiC%dD%d", card, device); - // allocate one extra file descriptor for close pipe - jobjectArray fds = env->NewObjectArray(subdevice_count + 1, sFileDescriptorClass, NULL); + ALOGD("Opening %d inputs and %d outputs", numInputs, numOutputs); + + jobjectArray fds = env->NewObjectArray(numInputs + numOutputs, sFileDescriptorClass, NULL); if (!fds) { return NULL; } - // to support multiple subdevices we open the same file multiple times - for (int i = 0; i < subdevice_count; i++) { - int fd = open(path, O_RDWR); + // open the path for the read pipes. The last one is special and used to + // unblock Os.poll() + for (int i = 0; i < numInputs - 1; i++) { + fd = open(path, O_RDONLY); if (fd < 0) { ALOGE("open failed on %s for index %d", path, i); goto release_fds; } ScopedLocalRef jifd(env, jniCreateFileDescriptor(env, fd)); if (jifd.get() == NULL) { + close(fd); goto release_fds; } env->SetObjectArrayElement(fds, i, jifd.get()); } - // create a pipe to use for unblocking our input thread - { + // open the path for the write pipes + for (int i = 0; i < numOutputs; i++) { + fd = open(path, O_WRONLY); + if (fd < 0) { + ALOGE("open failed on %s for index %d", path, i); + goto release_fds; + } + ScopedLocalRef jifd(env, jniCreateFileDescriptor(env, fd)); + if (jifd.get() == NULL) { + close(fd); + goto release_fds; + } + env->SetObjectArrayElement(fds, i + numInputs, jifd.get()); + } + + // create a pipe to use for unblocking our input thread. The caller should + // set numInputs as 0 when there are zero real input threads. + if (numInputs > 0) { int pipeFD[2]; if (pipe(pipeFD) == -1) { ALOGE("pipe() failed, errno = %d", errno); @@ -123,20 +107,21 @@ android_server_UsbMidiDevice_open(JNIEnv *env, jobject thiz, jint card, jint dev close(pipeFD[1]); goto release_fds; } - env->SetObjectArrayElement(fds, subdevice_count, jifd.get()); + + // store as last input file descriptor + env->SetObjectArrayElement(fds, numInputs - 1, jifd.get()); // store our end of the pipe in mPipeFD env->SetIntField(thiz, sPipeFDField, pipeFD[1]); } return fds; release_fds: - for (int i = 0; i < subdevice_count + 1; ++i) { + for (int i = 0; i < numInputs + numOutputs; ++i) { ScopedLocalRef jifd(env, env->GetObjectArrayElement(fds, i)); - if (jifd.get() == NULL) { - break; + if (jifd.get() != NULL) { + int fd = jniGetFDFromFileDescriptor(env, jifd.get()); + close(fd); } - int fd = jniGetFDFromFileDescriptor(env, jifd.get()); - close(fd); } return NULL; } @@ -158,9 +143,9 @@ android_server_UsbMidiDevice_close(JNIEnv *env, jobject thiz, jobjectArray fds) } static JNINativeMethod method_table[] = { - { "nativeGetSubdeviceCount", "(II)I", (void*)android_server_UsbMidiDevice_get_subdevice_count }, - { "nativeOpen", "(III)[Ljava/io/FileDescriptor;", (void*)android_server_UsbMidiDevice_open }, - { "nativeClose", "([Ljava/io/FileDescriptor;)V", (void*)android_server_UsbMidiDevice_close }, + {"nativeOpen", "(IIII)[Ljava/io/FileDescriptor;", + (void *)android_server_UsbMidiDevice_open}, + {"nativeClose", "([Ljava/io/FileDescriptor;)V", (void *)android_server_UsbMidiDevice_close}, }; int register_android_server_UsbMidiDevice(JNIEnv *env) diff --git a/services/usb/java/com/android/server/usb/UsbAlsaManager.java b/services/usb/java/com/android/server/usb/UsbAlsaManager.java index 22629dd526088..0aa62c53e269f 100644 --- a/services/usb/java/com/android/server/usb/UsbAlsaManager.java +++ b/services/usb/java/com/android/server/usb/UsbAlsaManager.java @@ -257,6 +257,8 @@ public final class UsbAlsaManager { // look for MIDI devices boolean hasMidi = parser.hasMIDIInterface(); + int midiNumInputs = parser.calculateNumMidiInputs(); + int midiNumOutputs = parser.calculateNumMidiOutputs(); if (DEBUG) { Slog.d(TAG, "hasMidi: " + hasMidi + " mHasMidiFeature:" + mHasMidiFeature); } @@ -285,7 +287,7 @@ public final class UsbAlsaManager { properties.putParcelable(MidiDeviceInfo.PROPERTY_USB_DEVICE, usbDevice); UsbMidiDevice usbMidiDevice = UsbMidiDevice.create(mContext, properties, - cardRec.getCardNum(), 0 /*device*/); + cardRec.getCardNum(), 0 /*device*/, midiNumInputs, midiNumOutputs); if (usbMidiDevice != null) { mMidiDevices.put(deviceAddress, usbMidiDevice); } @@ -338,7 +340,8 @@ public final class UsbAlsaManager { com.android.internal.R.string.usb_midi_peripheral_product_name)); properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_CARD, card); properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_DEVICE, device); - mPeripheralMidiDevice = UsbMidiDevice.create(mContext, properties, card, device); + mPeripheralMidiDevice = UsbMidiDevice.create(mContext, properties, card, device, + 1 /* numInputs */, 1 /* numOutputs */); } else if (!enabled && mPeripheralMidiDevice != null) { IoUtils.closeQuietly(mPeripheralMidiDevice); mPeripheralMidiDevice = null; diff --git a/services/usb/java/com/android/server/usb/UsbMidiDevice.java b/services/usb/java/com/android/server/usb/UsbMidiDevice.java index f47636ed8ea4a..b61e93b36e58f 100644 --- a/services/usb/java/com/android/server/usb/UsbMidiDevice.java +++ b/services/usb/java/com/android/server/usb/UsbMidiDevice.java @@ -48,8 +48,10 @@ public final class UsbMidiDevice implements Closeable { private final int mAlsaCard; private final int mAlsaDevice; - private final int mSubdeviceCount; - private final InputReceiverProxy[] mInputPortReceivers; + // USB outputs are MIDI inputs + private final InputReceiverProxy[] mMidiInputPortReceivers; + private final int mNumInputs; + private final int mNumOutputs; private MidiDeviceServer mServer; @@ -139,15 +141,14 @@ public final class UsbMidiDevice implements Closeable { } } - public static UsbMidiDevice create(Context context, Bundle properties, int card, int device) { - // FIXME - support devices with different number of input and output ports - int subDeviceCount = nativeGetSubdeviceCount(card, device); - if (subDeviceCount <= 0) { - Log.e(TAG, "nativeGetSubdeviceCount failed"); - return null; - } - - UsbMidiDevice midiDevice = new UsbMidiDevice(card, device, subDeviceCount); + /** + * Creates an UsbMidiDevice based on the input parameters. Read/Write streams + * will be created individually as some devices don't have the same number of + * inputs and outputs. + */ + public static UsbMidiDevice create(Context context, Bundle properties, int card, + int device, int numInputs, int numOutputs) { + UsbMidiDevice midiDevice = new UsbMidiDevice(card, device, numInputs, numOutputs); if (!midiDevice.register(context, properties)) { IoUtils.closeQuietly(midiDevice); Log.e(TAG, "createDeviceServer failed"); @@ -156,100 +157,107 @@ public final class UsbMidiDevice implements Closeable { return midiDevice; } - private UsbMidiDevice(int card, int device, int subdeviceCount) { + private UsbMidiDevice(int card, int device, int numInputs, int numOutputs) { mAlsaCard = card; mAlsaDevice = device; - mSubdeviceCount = subdeviceCount; + mNumInputs = numInputs; + mNumOutputs = numOutputs; - // FIXME - support devices with different number of input and output ports - int inputPortCount = subdeviceCount; - mInputPortReceivers = new InputReceiverProxy[inputPortCount]; - for (int port = 0; port < inputPortCount; port++) { - mInputPortReceivers[port] = new InputReceiverProxy(); + // Create MIDI port receivers based on the number of output ports. The + // output of USB is the input of MIDI. + mMidiInputPortReceivers = new InputReceiverProxy[numOutputs]; + for (int port = 0; port < numOutputs; port++) { + mMidiInputPortReceivers[port] = new InputReceiverProxy(); } } private boolean openLocked() { - // FIXME - support devices with different number of input and output ports - FileDescriptor[] fileDescriptors = nativeOpen(mAlsaCard, mAlsaDevice, mSubdeviceCount); + int inputStreamCount = mNumInputs; + // Create an extra stream for unblocking Os.poll() + if (inputStreamCount > 0) { + inputStreamCount++; + } + int outputStreamCount = mNumOutputs; + + // The resulting file descriptors will be O_RDONLY following by O_WRONLY + FileDescriptor[] fileDescriptors = nativeOpen(mAlsaCard, mAlsaDevice, + inputStreamCount, outputStreamCount); if (fileDescriptors == null) { Log.e(TAG, "nativeOpen failed"); return false; } - mFileDescriptors = fileDescriptors; - int inputStreamCount = fileDescriptors.length; - // last file descriptor returned from nativeOpen() is only used for unblocking Os.poll() - // in our input thread - int outputStreamCount = fileDescriptors.length - 1; mPollFDs = new StructPollfd[inputStreamCount]; mInputStreams = new FileInputStream[inputStreamCount]; + for (int i = 0; i < inputStreamCount; i++) { FileDescriptor fd = fileDescriptors[i]; StructPollfd pollfd = new StructPollfd(); pollfd.fd = fd; - pollfd.events = (short)OsConstants.POLLIN; + pollfd.events = (short) OsConstants.POLLIN; mPollFDs[i] = pollfd; mInputStreams[i] = new FileInputStream(fd); } mOutputStreams = new FileOutputStream[outputStreamCount]; mEventSchedulers = new MidiEventScheduler[outputStreamCount]; - for (int i = 0; i < outputStreamCount; i++) { - mOutputStreams[i] = new FileOutputStream(fileDescriptors[i]); + int curOutputStream = 0; + for (int i = 0; i < outputStreamCount; i++) { + mOutputStreams[i] = new FileOutputStream(fileDescriptors[inputStreamCount + i]); MidiEventScheduler scheduler = new MidiEventScheduler(); mEventSchedulers[i] = scheduler; - mInputPortReceivers[i].setReceiver(scheduler.getReceiver()); + mMidiInputPortReceivers[i].setReceiver(scheduler.getReceiver()); } final MidiReceiver[] outputReceivers = mServer.getOutputPortReceivers(); - // Create input thread which will read from all output ports of the physical device - new Thread("UsbMidiDevice input thread") { - @Override - public void run() { - byte[] buffer = new byte[BUFFER_SIZE]; - try { - while (true) { - // Record time of event immediately after waking. - long timestamp = System.nanoTime(); - synchronized (mLock) { - if (!mIsOpen) break; + if (inputStreamCount > 0) { + // Create input thread which will read from all output ports of the physical device + new Thread("UsbMidiDevice input thread") { + @Override + public void run() { + byte[] buffer = new byte[BUFFER_SIZE]; + try { + while (true) { + // Record time of event immediately after waking. + long timestamp = System.nanoTime(); + synchronized (mLock) { + if (!mIsOpen) break; - // look for a readable FileDescriptor - for (int index = 0; index < mPollFDs.length; index++) { - StructPollfd pfd = mPollFDs[index]; - if ((pfd.revents & (OsConstants.POLLERR - | OsConstants.POLLHUP)) != 0) { - break; - } else if ((pfd.revents & OsConstants.POLLIN) != 0) { - // clear readable flag - pfd.revents = 0; - - if (index == mInputStreams.length - 1) { - // last file descriptor is used only for unblocking Os.poll() + // look for a readable FileDescriptor + for (int index = 0; index < mPollFDs.length; index++) { + StructPollfd pfd = mPollFDs[index]; + if ((pfd.revents & (OsConstants.POLLERR + | OsConstants.POLLHUP)) != 0) { break; - } + } else if ((pfd.revents & OsConstants.POLLIN) != 0) { + // clear readable flag + pfd.revents = 0; + if (index == mInputStreams.length - 1) { + // last fd is used only for unblocking Os.poll() + break; + } - int count = mInputStreams[index].read(buffer); - outputReceivers[index].send(buffer, 0, count, timestamp); + int count = mInputStreams[index].read(buffer); + outputReceivers[index].send(buffer, 0, count, timestamp); + } } } - } - // wait until we have a readable port or we are signalled to close - Os.poll(mPollFDs, -1 /* infinite timeout */); - } - } catch (IOException e) { - Log.d(TAG, "reader thread exiting"); - } catch (ErrnoException e) { - Log.d(TAG, "reader thread exiting"); + // wait until we have a readable port or we are signalled to close + Os.poll(mPollFDs, -1 /* infinite timeout */); + } + } catch (IOException e) { + Log.d(TAG, "reader thread exiting"); + } catch (ErrnoException e) { + Log.d(TAG, "reader thread exiting"); + } + Log.d(TAG, "input thread exit"); } - Log.d(TAG, "input thread exit"); - } - }.start(); + }.start(); + } // Create output thread for each input port of the physical device for (int port = 0; port < outputStreamCount; port++) { @@ -294,7 +302,7 @@ public final class UsbMidiDevice implements Closeable { return false; } - mServer = midiManager.createDeviceServer(mInputPortReceivers, mSubdeviceCount, + mServer = midiManager.createDeviceServer(mMidiInputPortReceivers, mNumInputs, null, null, properties, MidiDeviceInfo.TYPE_USB, mCallback); if (mServer == null) { return false; @@ -318,7 +326,7 @@ public final class UsbMidiDevice implements Closeable { private void closeLocked() { for (int i = 0; i < mEventSchedulers.length; i++) { - mInputPortReceivers[i].setReceiver(null); + mMidiInputPortReceivers[i].setReceiver(null); mEventSchedulers[i].close(); } mEventSchedulers = null; @@ -354,7 +362,7 @@ public final class UsbMidiDevice implements Closeable { dump.end(token); } - private static native int nativeGetSubdeviceCount(int card, int device); - private native FileDescriptor[] nativeOpen(int card, int device, int subdeviceCount); + private native FileDescriptor[] nativeOpen(int card, int device, int numInputs, + int numOutputs); private native void nativeClose(FileDescriptor[] fileDescriptors); } diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java index 43d5bf323abbd..7250a071835d8 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java @@ -656,6 +656,45 @@ public final class UsbDescriptorParser { return false; } + private int calculateNumMidiPorts(boolean isOutput) { + int count = 0; + ArrayList descriptors = + getInterfaceDescriptorsForClass(UsbDescriptor.CLASSID_AUDIO); + for (UsbDescriptor descriptor : descriptors) { + // ensure that this isn't an unrecognized interface descriptor + if (descriptor instanceof UsbInterfaceDescriptor) { + UsbInterfaceDescriptor interfaceDescr = (UsbInterfaceDescriptor) descriptor; + if (interfaceDescr.getUsbSubclass() == UsbDescriptor.AUDIO_MIDISTREAMING) { + for (int i = 0; i < interfaceDescr.getNumEndpoints(); i++) { + UsbEndpointDescriptor endpoint = interfaceDescr.getEndpointDescriptor(i); + // 0 is output, 1 << 7 is input. + if ((endpoint.getDirection() == 0) == isOutput) { + count++; + } + } + } + } else { + Log.w(TAG, "Undefined Audio Class Interface l: " + descriptor.getLength() + + " t:0x" + Integer.toHexString(descriptor.getType())); + } + } + return count; + } + + /** + * @hide + */ + public int calculateNumMidiInputs() { + return calculateNumMidiPorts(false /*isOutput*/); + } + + /** + * @hide + */ + public int calculateNumMidiOutputs() { + return calculateNumMidiPorts(true /*isOutput*/); + } + /** * @hide */