Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7a46f462 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Use USB API to get # of i/o ports for MIDI with USB."

parents 2f65063b ce8791c1
Loading
Loading
Loading
Loading
+52 −67
Original line number Diff line number Diff line
@@ -24,14 +24,14 @@
#include "android_runtime/AndroidRuntime.h"
#include "android_runtime/Log.h"

#include <stdio.h>
#include <errno.h>
#include <asm/byteorder.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sound/asound.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>

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)
{
// 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;
    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)
{
    char    path[100];

    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<jobject> 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<jobject> 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,21 +107,22 @@ 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<jobject> jifd(env, env->GetObjectArrayElement(fds, i));
        if (jifd.get() == NULL) {
            break;
        }
        if (jifd.get() != NULL) {
            int fd = jniGetFDFromFileDescriptor(env, jifd.get());
            close(fd);
        }
    }
    return NULL;
}

@@ -158,8 +143,8 @@ 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 },
        {"nativeOpen", "(IIII)[Ljava/io/FileDescriptor;",
         (void *)android_server_UsbMidiDevice_open},
        {"nativeClose", "([Ljava/io/FileDescriptor;)V", (void *)android_server_UsbMidiDevice_close},
};

+5 −2
Original line number Diff line number Diff line
@@ -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;
+80 −72
Original line number Diff line number Diff line
@@ -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,35 +157,40 @@ 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();
@@ -196,16 +202,18 @@ public final class UsbMidiDevice implements Closeable {

        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();

        if (inputStreamCount > 0) {
            // Create input thread which will read from all output ports of the physical device
            new Thread("UsbMidiDevice input thread") {
                @Override
@@ -227,9 +235,8 @@ public final class UsbMidiDevice implements Closeable {
                                    } 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()
                                            // last fd is used only for unblocking Os.poll()
                                            break;
                                        }

@@ -250,6 +257,7 @@ public final class UsbMidiDevice implements Closeable {
                    Log.d(TAG, "input thread exit");
                }
            }.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);
}
+39 −0
Original line number Diff line number Diff line
@@ -656,6 +656,45 @@ public final class UsbDescriptorParser {
        return false;
    }

    private int calculateNumMidiPorts(boolean isOutput) {
        int count = 0;
        ArrayList<UsbDescriptor> 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
     */