Merge "Use USB API to get # of i/o ports for MIDI with USB."
This commit is contained in:
committed by
Android (Google) Code Review
commit
7a46f462fa
@@ -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)
|
||||
{
|
||||
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<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,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<jobject> 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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user