/* Calf Box, an open source musical instrument. Copyright (C) 2010-2013 Krzysztof Foltman This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "config.h" #if USE_LIBUSB #include "usbio_impl.h" #include //////////////////////////////////////////////////////////////////////////////// static const struct libusb_endpoint_descriptor *get_midi_endpoint(const struct libusb_interface_descriptor *asdescr, gboolean is_output) { for (int epi = 0; epi < asdescr->bNumEndpoints; epi++) { const struct libusb_endpoint_descriptor *ep = &asdescr->endpoint[epi]; if ((ep->bEndpointAddress >= 0x80) == !is_output) return ep; } return NULL; } static const struct libusb_endpoint_descriptor *get_audio_output_endpoint(const struct libusb_interface_descriptor *asdescr, const struct libusb_endpoint_descriptor **sync_endpoint) { for (int epi = 0; epi < asdescr->bNumEndpoints; epi++) { const struct libusb_endpoint_descriptor *ep = &asdescr->endpoint[epi]; uint8_t ep_type = (ep->bmAttributes & 0xF); if (ep->bEndpointAddress < 0x80 && (ep_type == 9 || ep_type == 13)) // output, isochronous, adaptive or synchronous { if (sync_endpoint) *sync_endpoint = NULL; return ep; } if (ep->bEndpointAddress < 0x80 && ep_type == 5) // output, isochronous, asynchronous { // Look for corresponding synch endpoint. It must be placed after the original output endpoint. if (sync_endpoint && ep->bSynchAddress >= 0x81) { *sync_endpoint = NULL; for(int epi2 = epi + 1; epi2 < asdescr->bNumEndpoints; epi2++) { const struct libusb_endpoint_descriptor *ep2 = &asdescr->endpoint[epi2]; if (ep2->bEndpointAddress == ep->bSynchAddress && (ep2->bmAttributes & 0xF) == 1) // input, isochronous, no sync { *sync_endpoint = ep2; break; } } } return ep; } } return NULL; } static void fill_endpoint_desc(struct usbio_endpoint_descriptor *epdesc, const struct libusb_endpoint_descriptor *ep) { epdesc->found = TRUE; epdesc->interrupt = FALSE; epdesc->bEndpointAddress = ep->bEndpointAddress; epdesc->wMaxPacketSize = ep->wMaxPacketSize; } static gboolean fill_endpoint_by_address(const struct libusb_interface_descriptor *asdescr, uint8_t addr, struct usbio_endpoint_descriptor *epdesc) { epdesc->found = FALSE; epdesc->interrupt = FALSE; for (int epi = 0; epi < asdescr->bNumEndpoints; epi++) { const struct libusb_endpoint_descriptor *ep = &asdescr->endpoint[epi]; if (ep->bEndpointAddress == addr) { fill_endpoint_desc(epdesc, ep); return TRUE; } } return FALSE; } //////////////////////////////////////////////////////////////////////////////// struct usb_ut_descriptor_header { uint8_t bLength; uint8_t bDescriptorType; uint8_t bDescriptorSubtype; }; struct usb_class_specific_header { struct usb_ut_descriptor_header hdr; uint8_t bcdADC[2]; uint8_t wTotalLengthLo, wTotalLengthHi; uint8_t bInCollection; uint8_t baInterfaceNr[0]; }; struct usbaudio_input_terminal_descriptor { uint8_t bTerminalID; uint8_t wTerminalTypeLo, wTerminalTypeHi; uint8_t bAssocTerminal; uint8_t bNrChannels; uint8_t wChannelConfigLo, wChannelConfigHi; uint8_t iChannelNames; uint8_t iTerminal; }; struct usbaudio_output_terminal_descriptor { uint8_t bTerminalID; uint8_t wTerminalTypeLo, wTerminalTypeHi; uint8_t bAssocTerminal; uint8_t bSourceID; uint8_t iTerminal; }; struct usbaudio_mixer_unit_descriptor1 { uint8_t bUnitID; uint8_t bNrInPins; uint8_t baSourceID[0]; }; struct usbaudio_mixer_unit_descriptor2 { uint8_t bNrChannels; uint8_t wChannelConfigLo, wChannelConfigHi; uint8_t bmControls[0]; // after the last index, 8-bit iMixer }; struct usbaudio_selector_unit_descriptor1 { uint8_t bUnitID; uint8_t bNrInPins; uint8_t baSourceID[0]; // after the last index, 8-bit iSelector }; struct usbaudio_feature_unit_descriptor1 { uint8_t bUnitID; uint8_t bSourceID; uint8_t bControlSize; uint8_t bmaControls[0]; // after the last index, 8-bit iFeature }; // Unit/Terminal Descriptor header from the spec #include #if 0 // termt10.pdf static const char *get_terminal_type_name(int type) { switch(type) { case 0x101: return "USB Streaming"; case 0x1FF: return "USB vendor specific"; case 0x201: return "Microphone"; case 0x202: return "Desktop microphone"; case 0x203: return "Personal microphone"; case 0x204: return "Omni-directional microphone"; case 0x205: return "Microphone array"; case 0x206: return "Processing microphone array"; case 0x301: return "Speaker"; case 0x302: return "Headphones"; case 0x303: return "Head-mounted display audio"; case 0x304: return "Desktop speaker"; case 0x305: return "Room speaker"; case 0x306: return "Communication speaker"; case 0x307: return "Low-frequency effects speaker"; case 0x400: return "Bi-directional Undefined"; case 0x401: return "Handset (handheld)"; case 0x402: return "Handset (head-mounted)"; case 0x403: return "Speakerphone"; case 0x404: return "Echo-suppressing speakerphone"; case 0x405: return "Echo-cancelling speakerphone"; case 0x501: return "Phone line"; case 0x502: return "Telephone"; case 0x503: return "Down line phone"; case 0x600: return "External Undefined"; case 0x601: return "Analog connector"; case 0x602: return "Digital audio interface"; case 0x603: return "Line connector"; case 0x604: return "Legacy audio"; case 0x605: return "S/PDIF"; case 0x606: return "1394 D/A Stream"; case 0x607: return "1394 D/V Stream Soundtrack"; case 0x700: return "Embedded undefined"; case 0x701: return "Level calibration noise source"; case 0x702: return "Equalization noise"; case 0x703: return "CD player"; case 0x704: return "DAT"; case 0x705: return "DCC"; case 0x706: return "MiniDisc"; case 0x707: return "Analog tape"; case 0x708: return "Phono"; case 0x709: return "VCR Audio"; case 0x70A: return "VideoDisc Audio"; case 0x70B: return "DVD Audio"; case 0x70C: return "TV Tuner Audio"; case 0x70D: return "Satellite Receiver Audio"; case 0x70E: return "Cable Tuner Audio"; case 0x70F: return "DSS Audio"; case 0x710: return "Radio Receiver"; case 0x711: return "Radio Transmitter"; case 0x712: return "Multitrack Recorder"; case 0x713: return "Synthesizer"; default: return "Unknown"; } } #endif static gboolean parse_audio_control_class(struct cbox_usb_audio_info *uai, const struct libusb_interface_descriptor *asdescr, gboolean other_config) { #if 0 if (asdescr->extra_length < 8) { g_warning("AudioControl interface descriptor length is %d, should be at least 8", (int)asdescr->extra_length); return FALSE; } const struct usb_class_specific_header *extra = (const struct usb_class_specific_header *)asdescr->extra; uint16_t wTotalLength = extra->wTotalLengthLo + 256 * extra->wTotalLengthHi; if (wTotalLength > asdescr->extra_length) { g_warning("AudioControl interface descriptor total length is %d, but libusb value is %d", (int)wTotalLength, (int)asdescr->extra_length); return FALSE; } if (extra->hdr.bDescriptorType != 36) { g_warning("AudioControl interface descriptor type is %d, but expected 36 (CS_INTERFACE)", (int)extra->hdr.bDescriptorType); return FALSE; } if (extra->hdr.bDescriptorSubtype != 1) { g_warning("AudioControl interface descriptor type is %d, but expected 1 (HEADER)", (int)extra->hdr.bDescriptorSubtype); return FALSE; } printf("Device %04x:%04x\n", uai->udi->vid, uai->udi->pid); printf("hdrlen=%d dt=%d dst=%d ver=%02x%02x total len=%d\n", extra->hdr.bLength, extra->hdr.bDescriptorType, extra->hdr.bDescriptorSubtype, extra->bcdADC[0], extra->bcdADC[1], wTotalLength); for(int i = 0; i < extra->bInCollection; i++) { printf("interface nr %d = %d\n", i, extra->baInterfaceNr[i]); } for(uint32_t pos = extra->hdr.bLength; pos < wTotalLength; pos += asdescr->extra[pos]) { const struct usb_ut_descriptor_header *hdr = (const struct usb_ut_descriptor_header *)(asdescr->extra + pos); if (hdr->bDescriptorType != 36) { g_warning("Skipping unit/terminal descriptor type %d,%d", (int)hdr->bDescriptorType, (int)hdr->bDescriptorSubtype); continue; } printf("hdr %d,%d len %d\n", hdr->bDescriptorType, hdr->bDescriptorSubtype, hdr->bLength); switch(hdr->bDescriptorSubtype) { case 2: // INPUT_TERMINAL { const struct usbaudio_input_terminal_descriptor *itd = (const struct usbaudio_input_terminal_descriptor *)(hdr + 1); int wTerminalType = itd->wTerminalTypeHi * 256 + itd->wTerminalTypeLo; printf("INPUT TERMINAL %d: type %04x (%s)\n", (int)itd->bTerminalID, wTerminalType, get_terminal_type_name(wTerminalType)); break; } case 3: // OUTPUT_TERMINAL { const struct usbaudio_output_terminal_descriptor *otd = (const struct usbaudio_output_terminal_descriptor *)(hdr + 1); int wTerminalType = otd->wTerminalTypeHi * 256 + otd->wTerminalTypeLo; printf("OUTPUT TERMINAL %d: type %04x (%s) source %d\n", (int)otd->bTerminalID, wTerminalType, get_terminal_type_name(wTerminalType), otd->bSourceID); break; } case 4: // MIXER_UNIT { const struct usbaudio_mixer_unit_descriptor1 *mud = (const struct usbaudio_mixer_unit_descriptor1 *)(hdr + 1); printf("MIXER UNIT %d\n", (int)mud->bUnitID); for (int i = 0; i < mud->bNrInPins; i++) { printf("Input[%d] = %d\n", i, mud->baSourceID[i]); } break; } case 5: // SELECTOR_UNIT { const struct usbaudio_selector_unit_descriptor1 *sud = (const struct usbaudio_selector_unit_descriptor1 *)(hdr + 1); printf("SELECTOR UNIT %d (%d pins)\n", (int)sud->bUnitID, sud->bNrInPins); for (int i = 0; i < sud->bNrInPins; i++) { printf("Input[%d] = %d\n", i, sud->baSourceID[i]); } break; } case 6: // FEATURE_UNIT { static const char *features[] = {"Mute", "Volume", "Bass", "Mid", "Treble", "EQ", "AGC", "Delay", "Bass Boost", "Loudness" }; const struct usbaudio_feature_unit_descriptor1 *fud = (const struct usbaudio_feature_unit_descriptor1 *)(hdr + 1); printf("FEATURE UNIT %d (source = %d, control size %d)\n", (int)fud->bUnitID, fud->bSourceID, fud->bControlSize); for (int i = 0; i < fud->bControlSize * 8; i++) { if (i >= sizeof(features) / sizeof(features[0])) break; if (fud->bmaControls[i >> 3] & (1 << (i & 7))) printf("Master %s\n", features[i]); } for (int ch = 1; ch < (hdr->bLength - 7) / fud->bControlSize; ch++) { int chofs = ch * fud->bControlSize; for (int i = 0; i < fud->bControlSize * 8; i++) { if (i >= sizeof(features) / sizeof(features[0])) break; if (fud->bmaControls[(i >> 3) + chofs] & (1 << (i & 7))) printf("Channel %d %s\n", ch, features[i]); } } break; } default: printf("Unsupported unit type %d\n", hdr->bDescriptorSubtype); break; } } #endif return FALSE; } struct usbaudio_streaming_interface_descriptor_general { struct usb_ut_descriptor_header hdr; // 7, 36, 1 uint8_t bTerminalLink; uint8_t bDelay; uint8_t wFormatTagLo, wFormatTagHi; }; struct usbaudio_streaming_interface_descriptor_format_type_pcm { struct usb_ut_descriptor_header hdr; // 7, 36, 2 uint8_t bFormatType; uint8_t bNrChannels; uint8_t bSubframeSize; uint8_t bBitResolution; uint8_t bSamFreqType; uint8_t taSamFreq[0][3]; }; static gboolean parse_audio_class(struct cbox_usb_io_impl *uii, struct cbox_usb_audio_info *uai, const struct libusb_interface_descriptor *asdescr, gboolean other_config) { // omit alternate setting 0, as it's used to describe a 'standby' setting of the interface (no bandwidth) if (asdescr->bAlternateSetting == 0) return FALSE; if (asdescr->extra_length < 7) { g_warning("AudioStreaming interface descriptor length is %d, should be at least 7", (int)asdescr->extra_length); return FALSE; } const struct usbaudio_streaming_interface_descriptor_general *extra = (struct usbaudio_streaming_interface_descriptor_general *)asdescr->extra; if (extra->hdr.bLength != 7 || extra->hdr.bDescriptorType != 36 || extra->hdr.bDescriptorSubtype != 1) { g_warning("The AudioStreaming descriptor does not start with the general descriptor (len %d, type %d, subtype %d)", (int)extra->hdr.bLength, (int)extra->hdr.bDescriptorType, (int)extra->hdr.bDescriptorSubtype); return FALSE; } if (extra->wFormatTagLo != 1 || extra->wFormatTagHi != 0) { g_warning("The AudioStreaming descriptor does not describe a PCM device"); return FALSE; } const struct usbaudio_streaming_interface_descriptor_format_type_pcm *fmt = (struct usbaudio_streaming_interface_descriptor_format_type_pcm *)(asdescr->extra + extra->hdr.bLength); if (fmt->hdr.bLength < 11 || ((fmt->hdr.bLength - 11) % 3) || fmt->hdr.bDescriptorType != 36 || fmt->hdr.bDescriptorSubtype != 2) { g_warning("The AudioStreaming descriptor does not have a format type descriptor after general descriptor (len %d, type %d, subtype %d)", (int)fmt->hdr.bLength, (int)fmt->hdr.bDescriptorType, (int)fmt->hdr.bDescriptorSubtype); return FALSE; } // We need this to tell inputs from outputs - until I implement reasonable // Audio Control support const struct libusb_endpoint_descriptor *sync_ep = NULL; const struct libusb_endpoint_descriptor *ep = get_audio_output_endpoint(asdescr, &sync_ep); if (ep) { if (fmt->bBitResolution != uii->output_resolution * 8) { g_warning("Interface %d alternate setting %d does not support %d bit resolution, only %d", asdescr->bInterfaceNumber, asdescr->bAlternateSetting, uii->output_resolution * 8, fmt->bBitResolution); return FALSE; } if (fmt->bSamFreqType) { gboolean found = FALSE; for (int i = 0; i < fmt->bSamFreqType; i++) { int sf = fmt->taSamFreq[i][0] + 256 * fmt->taSamFreq[i][1] + 65536 * fmt->taSamFreq[i][2]; if (sf == uii->sample_rate) { found = TRUE; break; } } if (!found) { g_warning("Interface %d alternate setting %d does not support sample rate of %d Hz", asdescr->bInterfaceNumber, asdescr->bAlternateSetting, uii->sample_rate); for (int i = 0; i < fmt->bSamFreqType; i++) { int sf = fmt->taSamFreq[i][0] + 256 * fmt->taSamFreq[i][1] + 65536 * fmt->taSamFreq[i][2]; g_warning("Sample rate[%d] = %d Hz", i, sf); } return FALSE; } } // XXXKF in case of continuous sample rate, check the limits... assuming // that there are any devices with continuous sample rate, that is. if (other_config) return TRUE; if (uai->epdesc.found) return FALSE; if (sync_ep) g_warning("Interface %d alt-setting %d endpoint %02x (synched via %02x) looks promising", asdescr->bInterfaceNumber, asdescr->bAlternateSetting, ep->bEndpointAddress, sync_ep->bEndpointAddress); else g_warning("Interface %d alt-setting %d endpoint %02x looks promising", asdescr->bInterfaceNumber, asdescr->bAlternateSetting, ep->bEndpointAddress); uai->intf = asdescr->bInterfaceNumber; uai->alt_setting = asdescr->bAlternateSetting; fill_endpoint_desc(&uai->epdesc, ep); uai->sync_protocol = (sync_ep != NULL) ? USBAUDIOSYNC_PROTOCOL_CLASS : USBAUDIOSYNC_PROTOCOL_NONE; if (sync_ep) fill_endpoint_desc(&uai->sync_epdesc, sync_ep); else uai->sync_epdesc.found = FALSE; return TRUE; } return FALSE; } static gboolean parse_midi_class(struct cbox_usb_midi_info *umi, const struct libusb_interface_descriptor *asdescr, gboolean other_config, gboolean is_output) { const struct libusb_endpoint_descriptor *ep = get_midi_endpoint(asdescr, is_output); if (!ep) return FALSE; if (other_config) return TRUE; struct usbio_endpoint_descriptor *epd = is_output ? &umi->epdesc_out : &umi->epdesc_in; if (epd->found) return FALSE; umi->intf = asdescr->bInterfaceNumber; umi->alt_setting = asdescr->bAlternateSetting; fill_endpoint_desc(epd, ep); return TRUE; } static gboolean inspect_device(struct cbox_usb_io_impl *uii, struct libusb_device *dev, uint16_t busdevadr, gboolean probe_only) { struct libusb_device_descriptor dev_descr; int bus = busdevadr >> 8; int devadr = busdevadr & 255; if (0 != libusb_get_device_descriptor(dev, &dev_descr)) { g_warning("USB device %03d:%03d - cannot get device descriptor (will retry)", bus, devadr); return FALSE; } struct cbox_usb_device_info *udi = g_hash_table_lookup(uii->device_table, GINT_TO_POINTER(busdevadr)); if (!udi) { struct libusb_config_descriptor *cfg_descr = NULL; if (0 != libusb_get_active_config_descriptor(dev, &cfg_descr)) return FALSE; udi = malloc(sizeof(struct cbox_usb_device_info)); udi->dev = dev; udi->handle = NULL; udi->status = CBOX_DEVICE_STATUS_PROBING; udi->active_config = cfg_descr->bConfigurationValue; udi->bus = bus; udi->devadr = devadr; udi->busdevadr = busdevadr; udi->vid = dev_descr.idVendor; udi->pid = dev_descr.idProduct; udi->configs_with_midi = 0; udi->configs_with_audio = 0; udi->is_midi = FALSE; udi->is_audio = FALSE; udi->last_probe_time = time(NULL); udi->failures = 0; g_hash_table_insert(uii->device_table, GINT_TO_POINTER(busdevadr), udi); libusb_free_config_descriptor(cfg_descr); } else if (udi->vid == dev_descr.idVendor && udi->pid == dev_descr.idProduct) { // device already open or determined to be if (udi->status == CBOX_DEVICE_STATUS_OPENED || udi->status == CBOX_DEVICE_STATUS_UNSUPPORTED) return FALSE; // give up after 10 attempts to query or open the device if (udi->failures > 10) return FALSE; // only do ~1 attempt per second if (probe_only && time(NULL) == udi->last_probe_time) return FALSE; udi->last_probe_time = time(NULL); } struct cbox_usb_audio_info uainf; cbox_usb_audio_info_init(&uainf, udi); struct cbox_usb_midi_info uminf; cbox_usb_midi_info_init(&uminf, udi); gboolean is_audio = FALSE; // printf("%03d:%03d Device %04X:%04X\n", bus, devadr, dev_descr.idVendor, dev_descr.idProduct); for (int ci = 0; ci < (int)dev_descr.bNumConfigurations; ci++) { struct libusb_config_descriptor *cfg_descr = NULL; // if this is not the current config, and another config with MIDI input // has already been found, do not look any further if (0 != libusb_get_config_descriptor(dev, ci, &cfg_descr)) { udi->failures++; g_warning("%03d:%03d - cannot get configuration descriptor (try %d)", bus, devadr, udi->failures); return FALSE; } int cur_config = cfg_descr->bConfigurationValue; gboolean other_config = cur_config != udi->active_config; uint32_t config_mask = 0; // XXXKF not sure about legal range for bConfigurationValue if(cfg_descr->bConfigurationValue >= 0 && cfg_descr->bConfigurationValue < 32) config_mask = 1 << cfg_descr->bConfigurationValue; else g_warning("Unexpected configuration value %d", cfg_descr->bConfigurationValue); for (int ii = 0; ii < cfg_descr->bNumInterfaces; ii++) { const struct libusb_interface *idescr = &cfg_descr->interface[ii]; for (int as = 0; as < idescr->num_altsetting; as++) { const struct libusb_interface_descriptor *asdescr = &idescr->altsetting[as]; if (asdescr->bInterfaceClass == LIBUSB_CLASS_AUDIO && asdescr->bInterfaceSubClass == 1) // Audio control { if (parse_audio_control_class(&uainf, asdescr, other_config) && other_config) udi->configs_with_audio |= config_mask; } else if (asdescr->bInterfaceClass == LIBUSB_CLASS_AUDIO && asdescr->bInterfaceSubClass == 2) // Audio streaming { if (parse_audio_class(uii, &uainf, asdescr, other_config) && other_config) udi->configs_with_audio |= config_mask; } else if (asdescr->bInterfaceClass == LIBUSB_CLASS_AUDIO && asdescr->bInterfaceSubClass == 3) // MIDI streaming { if (parse_midi_class(&uminf, asdescr, other_config, FALSE) && other_config) udi->configs_with_midi |= config_mask; if (parse_midi_class(&uminf, asdescr, other_config, TRUE) && other_config) udi->configs_with_midi |= config_mask; uminf.protocol = USBMIDI_PROTOCOL_CLASS; } else if (udi->vid == 0x09e8 && udi->pid == 0x0062) // Akai MPD16 { uminf.intf = asdescr->bInterfaceNumber; uminf.alt_setting = asdescr->bAlternateSetting; uminf.protocol = USBMIDI_PROTOCOL_MPD16; fill_endpoint_by_address(asdescr, 0x82, &uminf.epdesc_in); } else if (udi->vid == 0x1235 && udi->pid == 0x000a) // Novation Nocturn { uminf.intf = asdescr->bInterfaceNumber; uminf.alt_setting = asdescr->bAlternateSetting; fill_endpoint_by_address(asdescr, 0x81, &uminf.epdesc_in); fill_endpoint_by_address(asdescr, 0x02, &uminf.epdesc_out); uminf.epdesc_in.interrupt = TRUE; uminf.epdesc_out.interrupt = TRUE; uminf.protocol = USBMIDI_PROTOCOL_NOCTURN; } } } libusb_free_config_descriptor(cfg_descr); } if (!uminf.epdesc_in.found && !uminf.epdesc_out.found && udi->configs_with_midi) g_warning("%03d:%03d - MIDI port available on different configs: mask=0x%x", bus, devadr, udi->configs_with_midi); if (uainf.epdesc.found) // Class-compliant USB audio device is_audio = TRUE; if (udi->vid == 0x13b2 && udi->pid == 0x0030) // Alesis Multimix 8 { uainf.sync_protocol = USBAUDIOSYNC_PROTOCOL_MULTIMIX8; // not used later is_audio = TRUE; } // All configs/interfaces/alts scanned, nothing interesting found -> mark as unsupported udi->is_midi = uminf.epdesc_in.found || uminf.epdesc_out.found; udi->is_audio = is_audio; if (!udi->is_midi && !udi->is_audio) { udi->status = CBOX_DEVICE_STATUS_UNSUPPORTED; return FALSE; } gboolean opened = FALSE; struct libusb_device_handle *handle = NULL; int err = libusb_open(dev, &handle); if (0 != err) { g_warning("Cannot open device %03d:%03d: %s; errno = %s", bus, devadr, libusb_error_name(err), strerror(errno)); udi->failures++; return FALSE; } if (probe_only) { libusb_close(handle); // Make sure that the reconnection code doesn't bail out due to // last_probe_time == now. udi->last_probe_time = 0; return udi->is_midi || udi->is_audio; } if (uminf.epdesc_in.found || uminf.epdesc_out.found) { g_debug("Found MIDI device %03d:%03d, trying to open", bus, devadr); if (0 != usbio_open_midi_interface(uii, &uminf, handle)) opened = TRUE; } if (uainf.epdesc.found) { GError *error = NULL; if (usbio_open_audio_interface(uii, &uainf, handle, &error)) { // should have already been marked as opened by the MIDI code, but // I might add the ability to disable some MIDI interfaces at some point udi->status = CBOX_DEVICE_STATUS_OPENED; opened = TRUE; } else { g_warning("Cannot open class-compliant USB audio output: %s", error->message); g_error_free(error); } } else if (udi->vid == 0x13b2 && udi->pid == 0x0030) { GError *error = NULL; if (usbio_open_audio_interface_multimix(uii, bus, devadr, handle, &error)) { // should have already been marked as opened by the MIDI code, but // I might add the ability to disable some MIDI interfaces at some point udi->status = CBOX_DEVICE_STATUS_OPENED; opened = TRUE; } else { g_warning("Cannot open Alesis Multimix audio output: %s", error->message); g_error_free(error); } } if (!opened) { udi->failures++; libusb_close(handle); } else udi->handle = handle; return opened; } gboolean usbio_scan_devices(struct cbox_usb_io_impl *uii, gboolean probe_only) { struct libusb_device **dev_list; size_t i, num_devices; gboolean added = FALSE; gboolean removed = FALSE; num_devices = libusb_get_device_list(probe_only ? uii->usbctx_probe : uii->usbctx, &dev_list); uint16_t *busdevadrs = malloc(sizeof(uint16_t) * num_devices); for (i = 0; i < num_devices; i++) { struct libusb_device *dev = dev_list[i]; int bus = libusb_get_bus_number(dev); int devadr = libusb_get_device_address(dev); busdevadrs[i] = (bus << 8) | devadr; } GList *prev_keys = g_hash_table_get_values(uii->device_table); for (GList *p = prev_keys; p; p = p->next) { gboolean found = FALSE; struct cbox_usb_device_info *udi = p->data; for (i = 0; !found && i < num_devices; i++) found = busdevadrs[i] == udi->busdevadr; if (!found) { // Only specifically trigger removal if the device is ours if (udi->status == CBOX_DEVICE_STATUS_OPENED) { g_message("Disconnected: %03d:%03d (%s)", udi->bus, udi->devadr, probe_only ? "probe" : "reconfigure"); removed = TRUE; } if (!probe_only) usbio_forget_device(uii, udi); } } g_list_free(prev_keys); for (i = 0; i < num_devices; i++) added = inspect_device(uii, dev_list[i], busdevadrs[i], probe_only) || added; free(busdevadrs); libusb_free_device_list(dev_list, 0); return added || removed; } void usbio_forget_device(struct cbox_usb_io_impl *uii, struct cbox_usb_device_info *devinfo) { g_hash_table_remove(uii->device_table, GINT_TO_POINTER(devinfo->busdevadr)); for (GList *p = uii->midi_ports, *pNext = NULL; p; p = pNext) { pNext = p->next; struct cbox_usb_midi_interface *umi = p->data; if (umi->busdevadr == devinfo->busdevadr) { uii->midi_ports = g_list_delete_link(uii->midi_ports, p); free(umi); } } if (uii->handle_audiodev == devinfo->handle) uii->handle_audiodev = NULL; libusb_close(devinfo->handle); free(devinfo); } #endif