You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
751 lines
29 KiB
751 lines
29 KiB
3 years ago
|
/*
|
||
|
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 <http://www.gnu.org/licenses/>.
|
||
|
*/
|
||
|
|
||
|
#include "config.h"
|
||
|
|
||
|
#if USE_LIBUSB
|
||
|
#include "usbio_impl.h"
|
||
|
#include <errno.h>
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
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 <stdio.h>
|
||
|
|
||
|
#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
|