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

Commit 6ad1d8cc authored by Robert Wu's avatar Robert Wu
Browse files

Use ALSA for USB MIDI 1.0 devices

In Android T, we added USB MIDI 2.0 support. As part of the change,
we changed USB MIDI 1.0 devices to also not use ALSA.

Users have reported bugs where audio stops working after MIDI is used.
The root cause of this is because ALSA disconnects the sound card if
one part of the sound card is removed. Android disconnects the ALSA
MIDI interface as a direct USB connection is made.

As long as ALSA is used for audio, ALSA needs to be used for MIDI.
Thus, Android will go back to using ALSA for MIDI 1.0 USB devices.

Refer to ag/16835029 for the change where ALSA is no longer used for
host mode. This change is a partial revert of it.

Bug: 251243033
Bug: 259365701
Test: MIDI Scope on Korg device
Test: MIDI Keyboard and Scope loopback on 2x2 MIDI interface
Test: MIDI Scope on MIDI 2.0 device
Test: OboeTester Tap to Tone with MIDI and Audio
Change-Id: Ia6a6021619e05dc5b483d4644d7fd152d89e1d46
(cherry picked from commit 768236ce)
parent 7f47d9d4
Loading
Loading
Loading
Loading
+70 −0
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@ import libcore.io.IoUtils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;

/**
@@ -106,6 +107,12 @@ public final class UsbAlsaManager {
        return false;
    }

    /**
     * List of connected MIDI devices
     */
    private final HashMap<String, UsbMidiDevice>
            mMidiDevices = new HashMap<String, UsbMidiDevice>();

    // UsbMidiDevice for USB peripheral mode (gadget) device
    private UsbMidiDevice mPeripheralMidiDevice = null;

@@ -249,6 +256,8 @@ public final class UsbAlsaManager {
            }
        }

        addMidiDevice(deviceAddress, usbDevice, parser, cardRec);

        logDevices("deviceAdded()");

        if (DEBUG) {
@@ -256,6 +265,54 @@ public final class UsbAlsaManager {
        }
    }

    private void addMidiDevice(String deviceAddress, UsbDevice usbDevice,
            UsbDescriptorParser parser, AlsaCardsParser.AlsaCardRecord cardRec) {
        boolean hasMidi = parser.hasMIDIInterface();
        // UsbHostManager will create UsbDirectMidiDevices instead if MIDI 2 is supported.
        boolean hasMidi2 = parser.containsUniversalMidiDeviceEndpoint();
        if (DEBUG) {
            Slog.d(TAG, "hasMidi: " + hasMidi + " mHasMidiFeature:" + mHasMidiFeature);
            Slog.d(TAG, "hasMidi2: " + hasMidi2);
        }
        if (mHasMidiFeature && hasMidi && !hasMidi2) {
            Bundle properties = new Bundle();
            String manufacturer = usbDevice.getManufacturerName();
            String product = usbDevice.getProductName();
            String version = usbDevice.getVersion();
            String name;
            if (manufacturer == null || manufacturer.isEmpty()) {
                name = product;
            } else if (product == null || product.isEmpty()) {
                name = manufacturer;
            } else {
                name = manufacturer + " " + product;
            }
            properties.putString(MidiDeviceInfo.PROPERTY_NAME, name);
            properties.putString(MidiDeviceInfo.PROPERTY_MANUFACTURER, manufacturer);
            properties.putString(MidiDeviceInfo.PROPERTY_PRODUCT, product);
            properties.putString(MidiDeviceInfo.PROPERTY_VERSION, version);
            properties.putString(MidiDeviceInfo.PROPERTY_SERIAL_NUMBER,
                    usbDevice.getSerialNumber());
            properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_CARD, cardRec.getCardNum());
            properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_DEVICE, 0 /*deviceNum*/);
            properties.putParcelable(MidiDeviceInfo.PROPERTY_USB_DEVICE, usbDevice);

            int numLegacyMidiInputs = parser.calculateNumLegacyMidiInputs();
            int numLegacyMidiOutputs = parser.calculateNumLegacyMidiOutputs();
            if (DEBUG) {
                Slog.d(TAG, "numLegacyMidiInputs: " + numLegacyMidiInputs);
                Slog.d(TAG, "numLegacyMidiOutputs:" + numLegacyMidiOutputs);
            }

            UsbMidiDevice usbMidiDevice = UsbMidiDevice.create(mContext, properties,
                    cardRec.getCardNum(), 0 /*device*/, numLegacyMidiInputs,
                    numLegacyMidiOutputs);
            if (usbMidiDevice != null) {
                mMidiDevices.put(deviceAddress, usbMidiDevice);
            }
        }
    }

    /* package */ synchronized void usbDeviceRemoved(String deviceAddress/*UsbDevice usbDevice*/) {
        if (DEBUG) {
            Slog.d(TAG, "deviceRemoved(" + deviceAddress + ")");
@@ -269,6 +326,13 @@ public final class UsbAlsaManager {
            selectDefaultDevice(); // if there any external devices left, select one of them
        }

        // MIDI
        UsbMidiDevice usbMidiDevice = mMidiDevices.remove(deviceAddress);
        if (usbMidiDevice != null) {
            Slog.i(TAG, "USB MIDI Device Removed: " + deviceAddress);
            IoUtils.closeQuietly(usbMidiDevice);
        }

        logDevices("usbDeviceRemoved()");

    }
@@ -324,6 +388,12 @@ public final class UsbAlsaManager {
            usbAlsaDevice.dump(dump, "alsa_devices", UsbAlsaManagerProto.ALSA_DEVICES);
        }

        for (String deviceAddr : mMidiDevices.keySet()) {
            // A UsbMidiDevice does not have a handle to the UsbDevice anymore
            mMidiDevices.get(deviceAddr).dump(deviceAddr, dump, "midi_devices",
                    UsbAlsaManagerProto.MIDI_DEVICES);
        }

        dump.end(token);
    }

+13 −8
Original line number Diff line number Diff line
@@ -444,9 +444,13 @@ public class UsbHostManager {
                        } else {
                            Slog.e(TAG, "Universal Midi Device is null.");
                        }
                    }

                        // Use UsbDirectMidiDevice only if this supports MIDI 2.0 as well.
                        // ALSA removes the audio sound card if MIDI interfaces are removed.
                        // This means that as long as ALSA is used for audio, MIDI 1.0 USB
                        // devices should use the ALSA path for MIDI.
                        if (parser.containsLegacyMidiDeviceEndpoint()) {
                        UsbDirectMidiDevice midiDevice = UsbDirectMidiDevice.create(mContext,
                            midiDevice = UsbDirectMidiDevice.create(mContext,
                                    newDevice, parser, false, uniqueUsbDeviceIdentifier);
                            if (midiDevice != null) {
                                midiDevices.add(midiDevice);
@@ -454,6 +458,7 @@ public class UsbHostManager {
                                Slog.e(TAG, "Legacy Midi Device is null.");
                            }
                        }
                    }

                    if (!midiDevices.isEmpty()) {
                        mMidiDevices.put(deviceAddress, midiDevices);
+4 −0
Original line number Diff line number Diff line
@@ -79,6 +79,10 @@ public final class UsbConfigDescriptor extends UsbDescriptor {
        mInterfaceDescriptors.add(interfaceDesc);
    }

    ArrayList<UsbInterfaceDescriptor> getInterfaceDescriptors() {
        return mInterfaceDescriptors;
    }

    private boolean isAudioInterface(UsbInterfaceDescriptor descriptor) {
        return descriptor.getUsbClass() == UsbDescriptor.CLASSID_AUDIO
                && descriptor.getUsbSubclass() == UsbDescriptor.AUDIO_AUDIOSTREAMING;
+83 −1
Original line number Diff line number Diff line
@@ -40,6 +40,7 @@ public final class UsbDescriptorParser {
    private UsbDeviceDescriptor mDeviceDescriptor;
    private UsbConfigDescriptor mCurConfigDescriptor;
    private UsbInterfaceDescriptor mCurInterfaceDescriptor;
    private UsbEndpointDescriptor mCurEndpointDescriptor;

    // The AudioClass spec implemented by the AudioClass Interfaces
    // This may well be different than the overall USB Spec.
@@ -165,7 +166,7 @@ public final class UsbDescriptorParser {
                break;

            case UsbDescriptor.DESCRIPTORTYPE_ENDPOINT:
                descriptor = new UsbEndpointDescriptor(length, type);
                descriptor = mCurEndpointDescriptor = new UsbEndpointDescriptor(length, type);
                if (mCurInterfaceDescriptor != null) {
                    mCurInterfaceDescriptor.addEndpointDescriptor(
                            (UsbEndpointDescriptor) descriptor);
@@ -265,6 +266,9 @@ public final class UsbDescriptorParser {
                                    + Integer.toHexString(subClass));
                            break;
                    }
                    if (mCurEndpointDescriptor != null && descriptor != null) {
                        mCurEndpointDescriptor.setClassSpecificEndpointDescriptor(descriptor);
                    }
                }
                break;

@@ -795,6 +799,84 @@ public final class UsbDescriptorParser {
        return count;
    }

    /**
     * @hide
     */
    private int calculateNumLegacyMidiPorts(boolean isOutput) {
        // Only look at the first config.
        UsbConfigDescriptor configDescriptor = null;
        for (UsbDescriptor descriptor : mDescriptors) {
            if (descriptor.getType() == UsbDescriptor.DESCRIPTORTYPE_CONFIG) {
                if (descriptor instanceof UsbConfigDescriptor) {
                    configDescriptor = (UsbConfigDescriptor) descriptor;
                    break;
                } else {
                    Log.w(TAG, "Unrecognized Config l: " + descriptor.getLength()
                            + " t:0x" + Integer.toHexString(descriptor.getType()));
                }
            }
        }
        if (configDescriptor == null) {
            Log.w(TAG, "Config not found");
            return 0;
        }

        ArrayList<UsbInterfaceDescriptor> legacyMidiInterfaceDescriptors =
                new ArrayList<UsbInterfaceDescriptor>();
        for (UsbInterfaceDescriptor interfaceDescriptor
                : configDescriptor.getInterfaceDescriptors()) {
            if (interfaceDescriptor.getUsbClass() == UsbDescriptor.CLASSID_AUDIO) {
                if (interfaceDescriptor.getUsbSubclass() == UsbDescriptor.AUDIO_MIDISTREAMING) {
                    UsbDescriptor midiHeaderDescriptor =
                            interfaceDescriptor.getMidiHeaderInterfaceDescriptor();
                    if (midiHeaderDescriptor != null) {
                        if (midiHeaderDescriptor instanceof UsbMSMidiHeader) {
                            UsbMSMidiHeader midiHeader =
                                    (UsbMSMidiHeader) midiHeaderDescriptor;
                            if (midiHeader.getMidiStreamingClass() == MS_MIDI_1_0) {
                                legacyMidiInterfaceDescriptors.add(interfaceDescriptor);
                            }
                        }
                    }
                }
            }
        }

        int count = 0;
        for (UsbInterfaceDescriptor interfaceDescriptor : legacyMidiInterfaceDescriptors) {
            for (int i = 0; i < interfaceDescriptor.getNumEndpoints(); i++) {
                UsbEndpointDescriptor endpoint =
                        interfaceDescriptor.getEndpointDescriptor(i);
                // 0 is output, 1 << 7 is input.
                if ((endpoint.getDirection() == 0) == isOutput) {
                    UsbDescriptor classSpecificEndpointDescriptor =
                            endpoint.getClassSpecificEndpointDescriptor();
                    if (classSpecificEndpointDescriptor != null
                            && (classSpecificEndpointDescriptor instanceof UsbACMidi10Endpoint)) {
                        UsbACMidi10Endpoint midiEndpoint =
                                (UsbACMidi10Endpoint) classSpecificEndpointDescriptor;
                        count += midiEndpoint.getNumJacks();
                    }
                }
            }
        }
        return count;
    }

    /**
     * @hide
     */
    public int calculateNumLegacyMidiInputs() {
        return calculateNumLegacyMidiPorts(false /*isOutput*/);
    }

    /**
     * @hide
     */
    public int calculateNumLegacyMidiOutputs() {
        return calculateNumLegacyMidiPorts(true /*isOutput*/);
    }

    /**
     * @hide
     */
+10 −0
Original line number Diff line number Diff line
@@ -79,6 +79,8 @@ public class UsbEndpointDescriptor extends UsbDescriptor {
    private byte mRefresh;
    private byte mSyncAddress;

    private UsbDescriptor mClassSpecificEndpointDescriptor;

    public UsbEndpointDescriptor(int length, byte type) {
        super(length, type);
        mHierarchyLevel = 4;
@@ -112,6 +114,14 @@ public class UsbEndpointDescriptor extends UsbDescriptor {
        return mEndpointAddress & UsbEndpointDescriptor.MASK_ENDPOINT_DIRECTION;
    }

    void setClassSpecificEndpointDescriptor(UsbDescriptor descriptor) {
        mClassSpecificEndpointDescriptor = descriptor;
    }

    UsbDescriptor getClassSpecificEndpointDescriptor() {
        return mClassSpecificEndpointDescriptor;
    }

    /**
    * Returns a UsbEndpoint that this UsbEndpointDescriptor is describing.
    */