mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-10 09:11:31 +00:00
[client] usb audio: support up to 192khz 24-bit
We can not support 32-bit because Windows is buggy: The exact Windows 11 mmsys.cpl from build 26100.8737 populates Speaker Setup testing against hard-coded speaker masks and for each calls IKsFormatSupport::IsFormatSupported() using 24 hard-coded PCM formats. These formats do not include 32-bit in 32-bit, consequently, every one of the wizard’s format probes fails and it inserts zero channel-list entries. It does however test for 24-bit in 32-bit which passes, however this exposes another breakage due to an inconsistency inside Windows usbaudio2.sys For bSubslotSize = 4, bBitResolution = 24, Windows does this: 1. It publishes a KS range whose minimum and maximum bit depths are both taken from bBitResolution, so the range says 24-bit. 2. It correctly constructs the actual stream format as: wBitsPerSample = 32, wValidBitsPerSample = 24 3. KS range intersection rejects that 24-in-32 format because its container is 32 bits while the published range says 24. 4. A packed 24-bit candidate passes range intersection, but the subsequent exact stream check rejects it because it uses a three-byte container while USB advertises four bytes. Therefore no format can pass both checks. That exactly explains: * USB Device Tree Viewer seeing 24-bit device ranges. * The MMDevice endpoint exposing no usable formats. * Windows being unable to start playback. I verified this in the exact Windows 10.0.26100.8737 driver: * KSPin::AddDataRange stores bBitResolution into both KS bit-depth bounds. * MiniportWave::DataRangeIntersection tests those bounds. * WaveFormatInfo::IsMatchingStreamFormat separately compares both the container width and valid-bit count.
This commit is contained in:
@@ -67,6 +67,7 @@ struct LGA_USBState
|
|||||||
const LG_AudioEventOps * events;
|
const LG_AudioEventOps * events;
|
||||||
void * eventOpaque;
|
void * eventOpaque;
|
||||||
|
|
||||||
|
LG_AudioFormat streamFormat;
|
||||||
uint32_t streamGeneration;
|
uint32_t streamGeneration;
|
||||||
uint32_t generationSerial;
|
uint32_t generationSerial;
|
||||||
int64_t clockOrigin;
|
int64_t clockOrigin;
|
||||||
@@ -78,18 +79,43 @@ struct LGA_USBState
|
|||||||
static _Thread_local USBAudioCallbackFrame * l_eventFrames;
|
static _Thread_local USBAudioCallbackFrame * l_eventFrames;
|
||||||
static _Thread_local USBAudioCallbackFrame * l_statusFrames;
|
static _Thread_local USBAudioCallbackFrame * l_statusFrames;
|
||||||
|
|
||||||
static const LG_AudioFormat l_format =
|
static const LG_AudioFormat l_formatTemplate =
|
||||||
|
{
|
||||||
|
.sampleFormat = LG_AUDIO_FMT_S24_LE,
|
||||||
|
.sampleRate = LG_USB_AUDIO_DEFAULT_SAMPLE_RATE,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const LG_AudioChannel l_uacChannels[] =
|
||||||
{
|
{
|
||||||
.sampleFormat = LG_AUDIO_FMT_F32_LE,
|
|
||||||
.sampleRate = LG_USB_AUDIO_SAMPLE_RATE,
|
|
||||||
.channelCount = LG_USB_AUDIO_CHANNELS,
|
|
||||||
.channels =
|
|
||||||
{
|
|
||||||
LG_AUDIO_CH_FRONT_LEFT,
|
LG_AUDIO_CH_FRONT_LEFT,
|
||||||
LG_AUDIO_CH_FRONT_RIGHT,
|
LG_AUDIO_CH_FRONT_RIGHT,
|
||||||
},
|
LG_AUDIO_CH_FRONT_CENTER,
|
||||||
|
LG_AUDIO_CH_LFE,
|
||||||
|
LG_AUDIO_CH_REAR_LEFT,
|
||||||
|
LG_AUDIO_CH_REAR_RIGHT,
|
||||||
|
LG_AUDIO_CH_FRONT_LEFT_CENTER,
|
||||||
|
LG_AUDIO_CH_FRONT_RIGHT_CENTER,
|
||||||
|
LG_AUDIO_CH_REAR_CENTER,
|
||||||
|
LG_AUDIO_CH_SIDE_LEFT,
|
||||||
|
LG_AUDIO_CH_SIDE_RIGHT,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static void setStreamFormat(LG_AudioFormat * format,
|
||||||
|
uint32_t sampleRate, uint32_t channelMask)
|
||||||
|
{
|
||||||
|
*format = l_formatTemplate;
|
||||||
|
format->sampleRate = sampleRate;
|
||||||
|
|
||||||
|
for (size_t bit = 0;
|
||||||
|
bit < sizeof(l_uacChannels) / sizeof(*l_uacChannels); ++bit)
|
||||||
|
{
|
||||||
|
if (!(channelMask & (UINT32_C(1) << bit)))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
format->channels[format->channelCount++] = l_uacChannels[bit];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static uint32_t nextGeneration(uint32_t generation)
|
static uint32_t nextGeneration(uint32_t generation)
|
||||||
{
|
{
|
||||||
if (++generation == 0)
|
if (++generation == 0)
|
||||||
@@ -132,10 +158,10 @@ static LG_AudioClock makeClock(
|
|||||||
{
|
{
|
||||||
/* USB redirection carries no publisher timestamp or USB frame number.
|
/* USB redirection carries no publisher timestamp or USB frame number.
|
||||||
* Preserve the sample timeline, but do not claim a measured source rate. */
|
* Preserve the sample timeline, but do not claim a measured source rate. */
|
||||||
|
const uint32_t sampleRate = state->streamFormat.sampleRate;
|
||||||
const int64_t elapsed =
|
const int64_t elapsed =
|
||||||
position / LG_USB_AUDIO_SAMPLE_RATE * USB_AUDIO_NS_PER_SECOND +
|
position / sampleRate * USB_AUDIO_NS_PER_SECOND +
|
||||||
position % LG_USB_AUDIO_SAMPLE_RATE * USB_AUDIO_NS_PER_SECOND /
|
position % sampleRate * USB_AUDIO_NS_PER_SECOND / sampleRate;
|
||||||
LG_USB_AUDIO_SAMPLE_RATE;
|
|
||||||
|
|
||||||
return (LG_AudioClock)
|
return (LG_AudioClock)
|
||||||
{
|
{
|
||||||
@@ -232,7 +258,8 @@ static void waitStatusCallbacks(const LGA_USBState * state)
|
|||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void usbAudioStart(void * opaque)
|
static void usbAudioStart(
|
||||||
|
void * opaque, uint32_t sampleRate, uint32_t channelMask)
|
||||||
{
|
{
|
||||||
LGA_USBState * state = opaque;
|
LGA_USBState * state = opaque;
|
||||||
USBAudioEventTarget target;
|
USBAudioEventTarget target;
|
||||||
@@ -247,6 +274,7 @@ static void usbAudioStart(void * opaque)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setStreamFormat(&state->streamFormat, sampleRate, channelMask);
|
||||||
generation = state->generationSerial =
|
generation = state->generationSerial =
|
||||||
nextGeneration(state->generationSerial);
|
nextGeneration(state->generationSerial);
|
||||||
state->streamGeneration = generation;
|
state->streamGeneration = generation;
|
||||||
@@ -265,7 +293,7 @@ static void usbAudioStart(void * opaque)
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
target.events->playbackStart(
|
target.events->playbackStart(
|
||||||
target.opaque, generation, &l_format, &clock);
|
target.opaque, generation, &state->streamFormat, &clock);
|
||||||
|
|
||||||
LG_LOCK(state->stateLock);
|
LG_LOCK(state->stateLock);
|
||||||
if (eventCurrentNL(state, &target, generation))
|
if (eventCurrentNL(state, &target, generation))
|
||||||
@@ -453,7 +481,7 @@ static bool usbAttach(void * opaque, const LG_AudioEventOps * events,
|
|||||||
if (dispatch)
|
if (dispatch)
|
||||||
{
|
{
|
||||||
target.events->playbackStart(
|
target.events->playbackStart(
|
||||||
target.opaque, generation, &l_format, &clock);
|
target.opaque, generation, &state->streamFormat, &clock);
|
||||||
|
|
||||||
LG_LOCK(state->stateLock);
|
LG_LOCK(state->stateLock);
|
||||||
if (eventCurrentNL(state, &target, generation))
|
if (eventCurrentNL(state, &target, generation))
|
||||||
@@ -529,6 +557,7 @@ LGA_USBState * lgaUsb_create(void)
|
|||||||
atomic_init(&state->statusInFlight, 0);
|
atomic_init(&state->statusInFlight, 0);
|
||||||
atomic_init(&state->statusNextTicket, 0);
|
atomic_init(&state->statusNextTicket, 0);
|
||||||
atomic_init(&state->statusServingTicket, 0);
|
atomic_init(&state->statusServingTicket, 0);
|
||||||
|
state->streamFormat = l_formatTemplate;
|
||||||
|
|
||||||
state->device = lgUsbAudio_create(&l_usbAudioEvents, state);
|
state->device = lgUsbAudio_create(&l_usbAudioEvents, state);
|
||||||
if (!state->device)
|
if (!state->device)
|
||||||
|
|||||||
@@ -31,22 +31,67 @@
|
|||||||
|
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
/* Reserved prototype identity for the initial evaluation device. */
|
USB_AUDIO_VENDOR_ID = 0x043e,
|
||||||
USB_AUDIO_VENDOR_ID = 0xffff,
|
|
||||||
USB_AUDIO_PRODUCT_ID = 0x0001,
|
USB_AUDIO_PRODUCT_ID = 0x0001,
|
||||||
USB_AUDIO_DEVICE_VERSION = 0x0100,
|
USB_AUDIO_DEVICE_VERSION = 0x010a,
|
||||||
USB_AUDIO_CONFIGURATION = 1,
|
USB_AUDIO_CONFIGURATION = 1,
|
||||||
USB_AUDIO_CONTROL_IFACE = 0,
|
USB_AUDIO_CONTROL_IFACE = 0,
|
||||||
USB_AUDIO_STREAM_IFACE = 1,
|
USB_AUDIO_STREAM_IFACE = 1,
|
||||||
USB_AUDIO_STREAM_ALT = 1,
|
|
||||||
USB_AUDIO_CLOCK_ID = 1,
|
USB_AUDIO_CLOCK_ID = 1,
|
||||||
USB_AUDIO_STREAM_ENDPOINT = 0x01,
|
USB_AUDIO_STREAM_ENDPOINT = 0x01,
|
||||||
USB_AUDIO_MAX_PACKET_SIZE = 392,
|
USB_AUDIO_EP_INTERVAL = 1,
|
||||||
USB_AUDIO_FRAME_SIZE = 8,
|
USB_AUDIO_SAMPLE_SIZE = 3,
|
||||||
USB_AUDIO_EP_INTERVAL = 8,
|
USB_AUDIO_HS_PACKET_FRAMES = 25,
|
||||||
USB_AUDIO_FS_INTERVAL_POS = 118,
|
USB_AUDIO_FS_PACKET_FRAMES = 97,
|
||||||
|
USB_AUDIO_DESCRIPTOR_BASE = 81,
|
||||||
|
USB_AUDIO_STREAM_DESC_SIZE = 46,
|
||||||
|
USB_AUDIO_OTHER_SPEED_SIZE =
|
||||||
|
USB_AUDIO_DESCRIPTOR_BASE + USB_AUDIO_STREAM_DESC_SIZE,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
USB_AUDIO_LAYOUT_STEREO = 0x00000003,
|
||||||
|
USB_AUDIO_LAYOUT_QUAD = 0x00000033,
|
||||||
|
USB_AUDIO_LAYOUT_5POINT1 = 0x0000003f,
|
||||||
|
USB_AUDIO_LAYOUT_7POINT1 = 0x0000063f,
|
||||||
|
};
|
||||||
|
|
||||||
|
#define USB_AUDIO_LAYOUTS(X) \
|
||||||
|
X(1, 8, USB_AUDIO_LAYOUT_7POINT1) \
|
||||||
|
X(2, 6, USB_AUDIO_LAYOUT_5POINT1) \
|
||||||
|
X(3, 4, USB_AUDIO_LAYOUT_QUAD ) \
|
||||||
|
X(4, 2, USB_AUDIO_LAYOUT_STEREO )
|
||||||
|
|
||||||
|
#define USB_AUDIO_COUNT_LAYOUT(alt, channels, mask) + 1
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
USB_AUDIO_LAYOUT_COUNT =
|
||||||
|
0 USB_AUDIO_LAYOUTS(USB_AUDIO_COUNT_LAYOUT),
|
||||||
|
USB_AUDIO_CONFIGURATION_SIZE =
|
||||||
|
USB_AUDIO_DESCRIPTOR_BASE +
|
||||||
|
USB_AUDIO_LAYOUT_COUNT * USB_AUDIO_STREAM_DESC_SIZE,
|
||||||
|
};
|
||||||
|
#undef USB_AUDIO_COUNT_LAYOUT
|
||||||
|
|
||||||
|
#define USB_AUDIO_PACKET_SIZE(frames, channels) \
|
||||||
|
((frames) * (channels) * USB_AUDIO_SAMPLE_SIZE)
|
||||||
|
|
||||||
|
typedef struct USBAudioLayout
|
||||||
|
{
|
||||||
|
uint32_t channelMask;
|
||||||
|
uint8_t channelCount;
|
||||||
|
}
|
||||||
|
USBAudioLayout;
|
||||||
|
|
||||||
|
#define USB_AUDIO_LAYOUT_ENTRY(alt, channels, mask) \
|
||||||
|
[alt - 1] = { .channelMask = mask, .channelCount = channels },
|
||||||
|
static const USBAudioLayout l_channelLayouts[] =
|
||||||
|
{
|
||||||
|
USB_AUDIO_LAYOUTS(USB_AUDIO_LAYOUT_ENTRY)
|
||||||
|
};
|
||||||
|
#undef USB_AUDIO_LAYOUT_ENTRY
|
||||||
|
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
USB_REQUEST_GET_STATUS = 0x00,
|
USB_REQUEST_GET_STATUS = 0x00,
|
||||||
@@ -60,6 +105,7 @@ enum
|
|||||||
USB_REQUEST_TYPE_IN_STANDARD_DEVICE = 0x80,
|
USB_REQUEST_TYPE_IN_STANDARD_DEVICE = 0x80,
|
||||||
USB_REQUEST_TYPE_IN_STANDARD_INTERFACE = 0x81,
|
USB_REQUEST_TYPE_IN_STANDARD_INTERFACE = 0x81,
|
||||||
USB_REQUEST_TYPE_IN_STANDARD_ENDPOINT = 0x82,
|
USB_REQUEST_TYPE_IN_STANDARD_ENDPOINT = 0x82,
|
||||||
|
USB_REQUEST_TYPE_OUT_CLASS_INTERFACE = 0x21,
|
||||||
USB_REQUEST_TYPE_IN_CLASS_INTERFACE = 0xa1,
|
USB_REQUEST_TYPE_IN_CLASS_INTERFACE = 0xa1,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -81,55 +127,83 @@ enum
|
|||||||
static const uint8_t l_deviceDescriptor[] =
|
static const uint8_t l_deviceDescriptor[] =
|
||||||
{
|
{
|
||||||
0x12, 0x01, 0x00, 0x02, 0xef, 0x02, 0x01, 0x40,
|
0x12, 0x01, 0x00, 0x02, 0xef, 0x02, 0x01, 0x40,
|
||||||
0xff, 0xff, 0x01, 0x00, 0x00, 0x01, 0x01, 0x02,
|
0x3e, 0x04, 0x01, 0x00,
|
||||||
|
USB_AUDIO_DEVICE_VERSION & 0xff,
|
||||||
|
USB_AUDIO_DEVICE_VERSION >> 8 & 0xff,
|
||||||
|
0x01, 0x02,
|
||||||
0x03, 0x01,
|
0x03, 0x01,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#define USB_AUDIO_STREAM_DESCRIPTOR(alt, channels, mask, packetFrames) \
|
||||||
|
0x09, 0x04, 0x01, alt, 0x01, 0x01, 0x02, 0x20, 0x00, \
|
||||||
|
0x10, 0x24, 0x01, 0x02, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, \
|
||||||
|
channels, \
|
||||||
|
(mask) & 0xff, \
|
||||||
|
(mask) >> 8 & 0xff, \
|
||||||
|
(mask) >> 16 & 0xff, \
|
||||||
|
(mask) >> 24 & 0xff, \
|
||||||
|
0x00, \
|
||||||
|
0x06, 0x24, 0x02, 0x01, 0x03, 0x18, \
|
||||||
|
0x07, 0x05, 0x01, 0x09, \
|
||||||
|
USB_AUDIO_PACKET_SIZE(packetFrames, channels) & 0xff, \
|
||||||
|
USB_AUDIO_PACKET_SIZE(packetFrames, channels) >> 8 & 0xff, \
|
||||||
|
USB_AUDIO_EP_INTERVAL, \
|
||||||
|
0x08, 0x25, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
|
||||||
|
#define USB_AUDIO_HS_STREAM_DESCRIPTOR(alt, channels, mask) \
|
||||||
|
USB_AUDIO_STREAM_DESCRIPTOR( \
|
||||||
|
alt, channels, mask, USB_AUDIO_HS_PACKET_FRAMES)
|
||||||
|
|
||||||
static const uint8_t l_configurationDescriptor[] =
|
static const uint8_t l_configurationDescriptor[] =
|
||||||
{
|
{
|
||||||
/* Configuration */
|
/* Configuration */
|
||||||
0x09, 0x02, 0x7f, 0x00, 0x02, 0x01, 0x00, 0x80, 0x32,
|
0x09, 0x02,
|
||||||
|
USB_AUDIO_CONFIGURATION_SIZE & 0xff,
|
||||||
|
USB_AUDIO_CONFIGURATION_SIZE >> 8 & 0xff,
|
||||||
|
0x02, 0x01, 0x00, 0x80, 0x32,
|
||||||
|
|
||||||
/* Audio function interface association */
|
/* Audio function interface association */
|
||||||
0x08, 0x0b, 0x00, 0x02, 0x01, 0x00, 0x20, 0x00,
|
0x08, 0x0b, 0x00, 0x02, 0x01, 0x00, 0x20, 0x00,
|
||||||
|
|
||||||
/* Audio control interface */
|
/* Audio control interface */
|
||||||
0x09, 0x04, 0x00, 0x00, 0x00, 0x01, 0x01, 0x20, 0x00,
|
0x09, 0x04, 0x00, 0x00, 0x00, 0x01, 0x01, 0x20, 0x00,
|
||||||
0x09, 0x24, 0x01, 0x00, 0x02, 0x01, 0x2e, 0x00, 0x00,
|
0x09, 0x24, 0x01, 0x00, 0x02, 0x02, 0x2e, 0x00, 0x00,
|
||||||
0x08, 0x24, 0x0a, 0x01, 0x01, 0x05, 0x00, 0x00,
|
0x08, 0x24, 0x0a, 0x01, 0x03, 0x07, 0x00, 0x00,
|
||||||
0x11, 0x24, 0x02, 0x02, 0x01, 0x01, 0x00, 0x01, 0x02,
|
0x11, 0x24, 0x02, 0x02, 0x01, 0x01, 0x00, 0x01, 0x08,
|
||||||
0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x3f, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
0x0c, 0x24, 0x03, 0x03, 0x01, 0x03, 0x00, 0x02, 0x01,
|
0x0c, 0x24, 0x03, 0x03, 0x01, 0x03, 0x00, 0x02, 0x01,
|
||||||
0x00, 0x00, 0x00,
|
0x00, 0x00, 0x00,
|
||||||
|
|
||||||
/* Audio streaming interface, idle and active alternate settings */
|
/* Audio streaming interface, idle alternate setting */
|
||||||
0x09, 0x04, 0x01, 0x00, 0x00, 0x01, 0x02, 0x20, 0x00,
|
0x09, 0x04, 0x01, 0x00, 0x00, 0x01, 0x02, 0x20, 0x00,
|
||||||
0x09, 0x04, 0x01, 0x01, 0x01, 0x01, 0x02, 0x20, 0x00,
|
|
||||||
0x10, 0x24, 0x01, 0x02, 0x00, 0x01, 0x04, 0x00, 0x00,
|
|
||||||
0x00, 0x02, 0x03, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x06, 0x24, 0x02, 0x01, 0x04, 0x20,
|
|
||||||
|
|
||||||
/* High-speed 1 ms adaptive isochronous output endpoint */
|
/* Publish the maximum topology first for the Windows endpoint model. */
|
||||||
0x07, 0x05, 0x01, 0x09, 0x88, 0x01, 0x04,
|
USB_AUDIO_LAYOUTS(USB_AUDIO_HS_STREAM_DESCRIPTOR)
|
||||||
0x08, 0x25, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
};
|
};
|
||||||
|
#undef USB_AUDIO_HS_STREAM_DESCRIPTOR
|
||||||
|
#undef USB_AUDIO_LAYOUTS
|
||||||
|
|
||||||
|
static const uint8_t l_fullSpeedStreamDescriptor[] =
|
||||||
|
{
|
||||||
|
USB_AUDIO_STREAM_DESCRIPTOR(1, 2, USB_AUDIO_LAYOUT_STEREO,
|
||||||
|
USB_AUDIO_FS_PACKET_FRAMES)
|
||||||
|
};
|
||||||
|
#undef USB_AUDIO_STREAM_DESCRIPTOR
|
||||||
|
#undef USB_AUDIO_PACKET_SIZE
|
||||||
|
|
||||||
static const uint8_t l_deviceQualifierDescriptor[] =
|
static const uint8_t l_deviceQualifierDescriptor[] =
|
||||||
{
|
{
|
||||||
0x0a, 0x06, 0x00, 0x02, 0xef, 0x02, 0x01, 0x40, 0x01, 0x00,
|
0x0a, 0x06, 0x00, 0x02, 0xef, 0x02, 0x01, 0x40, 0x01, 0x00,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const uint8_t l_clockFrequency[] =
|
static const uint32_t l_sampleRates[] =
|
||||||
{
|
{
|
||||||
0x80, 0xbb, 0x00, 0x00,
|
44100,
|
||||||
};
|
48000,
|
||||||
|
88200,
|
||||||
static const uint8_t l_clockFrequencyRange[] =
|
96000,
|
||||||
{
|
176400,
|
||||||
0x01, 0x00,
|
192000,
|
||||||
0x80, 0xbb, 0x00, 0x00,
|
|
||||||
0x80, 0xbb, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static const uint8_t l_clockValid[] = { 0x01 };
|
static const uint8_t l_clockValid[] = { 0x01 };
|
||||||
@@ -137,12 +211,20 @@ static const uint8_t l_zeroStatus[] = { 0x00, 0x00 };
|
|||||||
|
|
||||||
_Static_assert(sizeof(l_deviceDescriptor) == 18,
|
_Static_assert(sizeof(l_deviceDescriptor) == 18,
|
||||||
"invalid USB device descriptor size");
|
"invalid USB device descriptor size");
|
||||||
_Static_assert(sizeof(l_configurationDescriptor) == 127,
|
_Static_assert(sizeof(l_channelLayouts) / sizeof(*l_channelLayouts) ==
|
||||||
|
USB_AUDIO_LAYOUT_COUNT, "invalid USB audio layout count");
|
||||||
|
_Static_assert(sizeof(l_configurationDescriptor) ==
|
||||||
|
USB_AUDIO_CONFIGURATION_SIZE,
|
||||||
"invalid USB configuration descriptor size");
|
"invalid USB configuration descriptor size");
|
||||||
|
_Static_assert(sizeof(l_fullSpeedStreamDescriptor) ==
|
||||||
|
USB_AUDIO_STREAM_DESC_SIZE,
|
||||||
|
"invalid full-speed stream descriptor size");
|
||||||
_Static_assert(sizeof(l_deviceQualifierDescriptor) == 10,
|
_Static_assert(sizeof(l_deviceQualifierDescriptor) == 10,
|
||||||
"invalid USB device qualifier descriptor size");
|
"invalid USB device qualifier descriptor size");
|
||||||
_Static_assert(USB_AUDIO_FS_INTERVAL_POS <
|
_Static_assert(USB_AUDIO_OTHER_SPEED_SIZE <=
|
||||||
sizeof(l_configurationDescriptor), "invalid endpoint interval offset");
|
sizeof(l_configurationDescriptor), "invalid other-speed descriptor size");
|
||||||
|
_Static_assert(USB_AUDIO_FS_PACKET_FRAMES * 2 * USB_AUDIO_SAMPLE_SIZE <=
|
||||||
|
1023, "full-speed stream packet is too large");
|
||||||
|
|
||||||
struct LG_USBAudio
|
struct LG_USBAudio
|
||||||
{
|
{
|
||||||
@@ -152,6 +234,7 @@ struct LG_USBAudio
|
|||||||
|
|
||||||
uint8_t configuration;
|
uint8_t configuration;
|
||||||
uint8_t streamAlt;
|
uint8_t streamAlt;
|
||||||
|
uint32_t sampleRate;
|
||||||
bool streaming;
|
bool streaming;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -160,6 +243,81 @@ static LG_USBAudio * getAudio(void * opaque)
|
|||||||
return lgUsbRedir_device(opaque);
|
return lgUsbRedir_device(opaque);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static uint32_t readLE32(const uint8_t * data)
|
||||||
|
{
|
||||||
|
return
|
||||||
|
(uint32_t)data[0] |
|
||||||
|
(uint32_t)data[1] << 8 |
|
||||||
|
(uint32_t)data[2] << 16 |
|
||||||
|
(uint32_t)data[3] << 24;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void writeLE16(uint8_t * data, uint16_t value)
|
||||||
|
{
|
||||||
|
data[0] = value;
|
||||||
|
data[1] = value >> 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void writeLE32(uint8_t * data, uint32_t value)
|
||||||
|
{
|
||||||
|
data[0] = value;
|
||||||
|
data[1] = value >> 8;
|
||||||
|
data[2] = value >> 16;
|
||||||
|
data[3] = value >> 24;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t nearestSampleRate(uint32_t sampleRate)
|
||||||
|
{
|
||||||
|
uint32_t selected = l_sampleRates[0];
|
||||||
|
uint64_t bestDelta = sampleRate > selected ?
|
||||||
|
(uint64_t)sampleRate - selected : (uint64_t)selected - sampleRate;
|
||||||
|
|
||||||
|
for (size_t i = 1;
|
||||||
|
i < sizeof(l_sampleRates) / sizeof(*l_sampleRates); ++i)
|
||||||
|
{
|
||||||
|
const uint32_t candidate = l_sampleRates[i];
|
||||||
|
const uint64_t delta = sampleRate > candidate ?
|
||||||
|
(uint64_t)sampleRate - candidate : (uint64_t)candidate - sampleRate;
|
||||||
|
if (delta < bestDelta)
|
||||||
|
{
|
||||||
|
selected = candidate;
|
||||||
|
bestDelta = delta;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return selected;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const USBAudioLayout * getLayout(uint8_t alt)
|
||||||
|
{
|
||||||
|
if (alt == 0 || alt > USB_AUDIO_LAYOUT_COUNT)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
return &l_channelLayouts[alt - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint16_t layoutPacketSize(const USBAudioLayout * layout)
|
||||||
|
{
|
||||||
|
return USB_AUDIO_HS_PACKET_FRAMES * layout->channelCount *
|
||||||
|
USB_AUDIO_SAMPLE_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t writeSampleRateRange(uint8_t * buffer)
|
||||||
|
{
|
||||||
|
const size_t count = sizeof(l_sampleRates) / sizeof(*l_sampleRates);
|
||||||
|
writeLE16(buffer, (uint16_t)count);
|
||||||
|
|
||||||
|
uint8_t * range = buffer + 2;
|
||||||
|
for (size_t i = 0; i < count; ++i, range += 12)
|
||||||
|
{
|
||||||
|
writeLE32(range , l_sampleRates[i]);
|
||||||
|
writeLE32(range + 4, l_sampleRates[i]);
|
||||||
|
writeLE32(range + 8, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 2 + count * 12;
|
||||||
|
}
|
||||||
|
|
||||||
static void stopPlayback(LG_USBAudio * audio)
|
static void stopPlayback(LG_USBAudio * audio)
|
||||||
{
|
{
|
||||||
if (!audio->streaming)
|
if (!audio->streaming)
|
||||||
@@ -170,11 +328,37 @@ static void stopPlayback(LG_USBAudio * audio)
|
|||||||
audio->events->stop(audio->eventOpaque);
|
audio->events->stop(audio->eventOpaque);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void startPlayback(LG_USBAudio * audio)
|
||||||
|
{
|
||||||
|
const USBAudioLayout * layout = getLayout(audio->streamAlt);
|
||||||
|
if (audio->streaming || !layout)
|
||||||
|
return;
|
||||||
|
|
||||||
|
audio->streaming = true;
|
||||||
|
if (audio->events && audio->events->start)
|
||||||
|
audio->events->start(
|
||||||
|
audio->eventOpaque, audio->sampleRate, layout->channelMask);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void setSampleRate(LG_USBAudio * audio, uint32_t sampleRate)
|
||||||
|
{
|
||||||
|
if (audio->sampleRate == sampleRate)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const bool restart = audio->streaming;
|
||||||
|
|
||||||
|
stopPlayback(audio);
|
||||||
|
audio->sampleRate = sampleRate;
|
||||||
|
if (restart)
|
||||||
|
startPlayback(audio);
|
||||||
|
}
|
||||||
|
|
||||||
static void resetDevice(LG_USBAudio * audio)
|
static void resetDevice(LG_USBAudio * audio)
|
||||||
{
|
{
|
||||||
stopPlayback(audio);
|
stopPlayback(audio);
|
||||||
audio->configuration = 0;
|
audio->configuration = 0;
|
||||||
audio->streamAlt = 0;
|
audio->streamAlt = 0;
|
||||||
|
audio->sampleRate = LG_USB_AUDIO_DEFAULT_SAMPLE_RATE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void sendInterfaceInfo(LG_USBAudio * audio)
|
static void sendInterfaceInfo(LG_USBAudio * audio)
|
||||||
@@ -193,6 +377,7 @@ static void sendInterfaceInfo(LG_USBAudio * audio)
|
|||||||
static void sendEndpointInfo(LG_USBAudio * audio)
|
static void sendEndpointInfo(LG_USBAudio * audio)
|
||||||
{
|
{
|
||||||
struct usb_redir_ep_info_header info = { 0 };
|
struct usb_redir_ep_info_header info = { 0 };
|
||||||
|
const USBAudioLayout * layout = getLayout(audio->streamAlt);
|
||||||
memset(info.type, usb_redir_type_invalid, sizeof(info.type));
|
memset(info.type, usb_redir_type_invalid, sizeof(info.type));
|
||||||
|
|
||||||
info.type[0] = usb_redir_type_control;
|
info.type[0] = usb_redir_type_control;
|
||||||
@@ -200,14 +385,13 @@ static void sendEndpointInfo(LG_USBAudio * audio)
|
|||||||
info.max_packet_size[0] = 64;
|
info.max_packet_size[0] = 64;
|
||||||
info.max_packet_size[16] = 64;
|
info.max_packet_size[16] = 64;
|
||||||
|
|
||||||
if (audio->configuration == USB_AUDIO_CONFIGURATION &&
|
if (audio->configuration == USB_AUDIO_CONFIGURATION && layout)
|
||||||
audio->streamAlt == USB_AUDIO_STREAM_ALT)
|
|
||||||
{
|
{
|
||||||
info.type[USB_AUDIO_STREAM_ENDPOINT] = usb_redir_type_iso;
|
info.type[USB_AUDIO_STREAM_ENDPOINT] = usb_redir_type_iso;
|
||||||
info.interval[USB_AUDIO_STREAM_ENDPOINT] = USB_AUDIO_EP_INTERVAL;
|
info.interval[USB_AUDIO_STREAM_ENDPOINT] = USB_AUDIO_EP_INTERVAL;
|
||||||
info.interface[USB_AUDIO_STREAM_ENDPOINT] = USB_AUDIO_STREAM_IFACE;
|
info.interface[USB_AUDIO_STREAM_ENDPOINT] = USB_AUDIO_STREAM_IFACE;
|
||||||
info.max_packet_size[USB_AUDIO_STREAM_ENDPOINT] =
|
info.max_packet_size[USB_AUDIO_STREAM_ENDPOINT] =
|
||||||
USB_AUDIO_MAX_PACKET_SIZE;
|
layoutPacketSize(layout);
|
||||||
}
|
}
|
||||||
|
|
||||||
usbredirparser_send_ep_info(audio->parser, &info);
|
usbredirparser_send_ep_info(audio->parser, &info);
|
||||||
@@ -221,7 +405,9 @@ static void sendDeviceInfo(LG_USBAudio * audio)
|
|||||||
|
|
||||||
static void resetUSB(void * opaque)
|
static void resetUSB(void * opaque)
|
||||||
{
|
{
|
||||||
resetDevice(getAudio(opaque));
|
LG_USBAudio * audio = getAudio(opaque);
|
||||||
|
resetDevice(audio);
|
||||||
|
sendEndpointInfo(audio);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void setConfiguration(void * opaque, uint64_t id,
|
static void setConfiguration(void * opaque, uint64_t id,
|
||||||
@@ -274,7 +460,7 @@ static void setAltSetting(void * opaque, uint64_t id,
|
|||||||
status.status = usb_redir_success;
|
status.status = usb_redir_success;
|
||||||
else if (audio->configuration == USB_AUDIO_CONFIGURATION &&
|
else if (audio->configuration == USB_AUDIO_CONFIGURATION &&
|
||||||
request->interface == USB_AUDIO_STREAM_IFACE &&
|
request->interface == USB_AUDIO_STREAM_IFACE &&
|
||||||
(request->alt == 0 || request->alt == USB_AUDIO_STREAM_ALT))
|
(request->alt == 0 || getLayout(request->alt)))
|
||||||
{
|
{
|
||||||
stopPlayback(audio);
|
stopPlayback(audio);
|
||||||
audio->streamAlt = request->alt;
|
audio->streamAlt = request->alt;
|
||||||
@@ -329,15 +515,10 @@ static void startISOStream(void * opaque, uint64_t id,
|
|||||||
|
|
||||||
if (request->endpoint == USB_AUDIO_STREAM_ENDPOINT &&
|
if (request->endpoint == USB_AUDIO_STREAM_ENDPOINT &&
|
||||||
audio->configuration == USB_AUDIO_CONFIGURATION &&
|
audio->configuration == USB_AUDIO_CONFIGURATION &&
|
||||||
audio->streamAlt == USB_AUDIO_STREAM_ALT)
|
getLayout(audio->streamAlt))
|
||||||
{
|
{
|
||||||
result = usb_redir_success;
|
result = usb_redir_success;
|
||||||
if (!audio->streaming)
|
startPlayback(audio);
|
||||||
{
|
|
||||||
audio->streaming = true;
|
|
||||||
if (audio->events && audio->events->start)
|
|
||||||
audio->events->start(audio->eventOpaque);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sendISOStatus(audio, id, request->endpoint, result);
|
sendISOStatus(audio, id, request->endpoint, result);
|
||||||
@@ -375,7 +556,7 @@ static size_t stringDescriptor(uint8_t index, uint8_t * buffer,
|
|||||||
{
|
{
|
||||||
case 1: text = "Looking Glass" ; break;
|
case 1: text = "Looking Glass" ; break;
|
||||||
case 2: text = "Looking Glass USB Audio"; break;
|
case 2: text = "Looking Glass USB Audio"; break;
|
||||||
case 3: text = "LG-UAC2-0001" ; break;
|
case 3: text = "LG-UAC2-0003" ; break;
|
||||||
default: return 0;
|
default: return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -433,7 +614,6 @@ static void controlPacket(void * opaque, uint64_t id,
|
|||||||
const uint8_t * response = NULL;
|
const uint8_t * response = NULL;
|
||||||
size_t responseSize = 0;
|
size_t responseSize = 0;
|
||||||
uint8_t status = usb_redir_stall;
|
uint8_t status = usb_redir_stall;
|
||||||
(void)dataLength;
|
|
||||||
|
|
||||||
if (request->requesttype == USB_REQUEST_TYPE_IN_STANDARD_DEVICE &&
|
if (request->requesttype == USB_REQUEST_TYPE_IN_STANDARD_DEVICE &&
|
||||||
request->request == USB_REQUEST_GET_DESCRIPTOR)
|
request->request == USB_REQUEST_GET_DESCRIPTOR)
|
||||||
@@ -476,13 +656,18 @@ static void controlPacket(void * opaque, uint64_t id,
|
|||||||
case USB_DESCRIPTOR_OTHER_SPEED:
|
case USB_DESCRIPTOR_OTHER_SPEED:
|
||||||
if (index == 0)
|
if (index == 0)
|
||||||
{
|
{
|
||||||
|
/* Full-speed USB cannot carry the maximum PCM24 stream. Expose a
|
||||||
|
* functional stereo alternate sized for rates through 96 kHz. */
|
||||||
memcpy(buffer, l_configurationDescriptor,
|
memcpy(buffer, l_configurationDescriptor,
|
||||||
sizeof(l_configurationDescriptor));
|
USB_AUDIO_DESCRIPTOR_BASE);
|
||||||
|
memcpy(buffer + USB_AUDIO_DESCRIPTOR_BASE,
|
||||||
|
l_fullSpeedStreamDescriptor,
|
||||||
|
sizeof(l_fullSpeedStreamDescriptor));
|
||||||
buffer[1] = USB_DESCRIPTOR_OTHER_SPEED;
|
buffer[1] = USB_DESCRIPTOR_OTHER_SPEED;
|
||||||
buffer[USB_AUDIO_FS_INTERVAL_POS] = 1;
|
buffer[2] = USB_AUDIO_OTHER_SPEED_SIZE;
|
||||||
|
buffer[3] = 0;
|
||||||
response = buffer;
|
response = buffer;
|
||||||
responseSize =
|
responseSize = USB_AUDIO_OTHER_SPEED_SIZE;
|
||||||
sizeof(l_configurationDescriptor);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -490,32 +675,57 @@ static void controlPacket(void * opaque, uint64_t id,
|
|||||||
if (response)
|
if (response)
|
||||||
status = usb_redir_success;
|
status = usb_redir_success;
|
||||||
}
|
}
|
||||||
else if ((request->requesttype == USB_REQUEST_TYPE_IN_STANDARD_DEVICE ||
|
else if (request->request == USB_REQUEST_GET_STATUS &&
|
||||||
request->requesttype == USB_REQUEST_TYPE_IN_STANDARD_INTERFACE ||
|
request->value == 0 && request->length == sizeof(l_zeroStatus))
|
||||||
request->requesttype == USB_REQUEST_TYPE_IN_STANDARD_ENDPOINT) &&
|
{
|
||||||
request->request == USB_REQUEST_GET_STATUS && request->value == 0)
|
bool valid = false;
|
||||||
|
switch (request->requesttype)
|
||||||
|
{
|
||||||
|
case USB_REQUEST_TYPE_IN_STANDARD_DEVICE:
|
||||||
|
valid = request->index == 0;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case USB_REQUEST_TYPE_IN_STANDARD_INTERFACE:
|
||||||
|
valid = audio->configuration == USB_AUDIO_CONFIGURATION &&
|
||||||
|
(request->index == USB_AUDIO_CONTROL_IFACE ||
|
||||||
|
request->index == USB_AUDIO_STREAM_IFACE);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case USB_REQUEST_TYPE_IN_STANDARD_ENDPOINT:
|
||||||
|
valid = request->index == 0x00 || request->index == 0x80 ||
|
||||||
|
(audio->configuration == USB_AUDIO_CONFIGURATION &&
|
||||||
|
getLayout(audio->streamAlt) &&
|
||||||
|
request->index == USB_AUDIO_STREAM_ENDPOINT);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (valid)
|
||||||
{
|
{
|
||||||
response = l_zeroStatus;
|
response = l_zeroStatus;
|
||||||
responseSize = sizeof(l_zeroStatus);
|
responseSize = sizeof(l_zeroStatus);
|
||||||
status = usb_redir_success;
|
status = usb_redir_success;
|
||||||
}
|
}
|
||||||
else if (request->requesttype == USB_REQUEST_TYPE_IN_CLASS_INTERFACE &&
|
}
|
||||||
request->index == (USB_AUDIO_CLOCK_ID << 8) &&
|
else if (audio->configuration == USB_AUDIO_CONFIGURATION &&
|
||||||
|
request->requesttype == USB_REQUEST_TYPE_IN_CLASS_INTERFACE &&
|
||||||
|
request->index ==
|
||||||
|
((USB_AUDIO_CLOCK_ID << 8) | USB_AUDIO_CONTROL_IFACE) &&
|
||||||
(uint8_t)request->value == 0)
|
(uint8_t)request->value == 0)
|
||||||
{
|
{
|
||||||
const uint8_t control = request->value >> 8;
|
const uint8_t control = request->value >> 8;
|
||||||
if (request->request == USB_REQUEST_CUR &&
|
if (request->request == USB_REQUEST_CUR &&
|
||||||
control == USB_AUDIO_CONTROL_FREQUENCY)
|
control == USB_AUDIO_CONTROL_FREQUENCY)
|
||||||
{
|
{
|
||||||
response = l_clockFrequency;
|
writeLE32(buffer, audio->sampleRate);
|
||||||
responseSize = sizeof(l_clockFrequency);
|
response = buffer;
|
||||||
|
responseSize = 4;
|
||||||
status = usb_redir_success;
|
status = usb_redir_success;
|
||||||
}
|
}
|
||||||
else if (request->request == USB_REQUEST_RANGE &&
|
else if (request->request == USB_REQUEST_RANGE &&
|
||||||
control == USB_AUDIO_CONTROL_FREQUENCY)
|
control == USB_AUDIO_CONTROL_FREQUENCY)
|
||||||
{
|
{
|
||||||
response = l_clockFrequencyRange;
|
responseSize = writeSampleRateRange(buffer);
|
||||||
responseSize = sizeof(l_clockFrequencyRange);
|
response = buffer;
|
||||||
status = usb_redir_success;
|
status = usb_redir_success;
|
||||||
}
|
}
|
||||||
else if (request->request == USB_REQUEST_CUR &&
|
else if (request->request == USB_REQUEST_CUR &&
|
||||||
@@ -526,6 +736,17 @@ static void controlPacket(void * opaque, uint64_t id,
|
|||||||
status = usb_redir_success;
|
status = usb_redir_success;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (audio->configuration == USB_AUDIO_CONFIGURATION &&
|
||||||
|
request->requesttype == USB_REQUEST_TYPE_OUT_CLASS_INTERFACE &&
|
||||||
|
request->request == USB_REQUEST_CUR &&
|
||||||
|
request->index ==
|
||||||
|
((USB_AUDIO_CLOCK_ID << 8) | USB_AUDIO_CONTROL_IFACE) &&
|
||||||
|
request->value == (USB_AUDIO_CONTROL_FREQUENCY << 8) &&
|
||||||
|
request->length == 4 && dataLength == 4 && data)
|
||||||
|
{
|
||||||
|
setSampleRate(audio, nearestSampleRate(readLE32(data)));
|
||||||
|
status = usb_redir_success;
|
||||||
|
}
|
||||||
|
|
||||||
sendControlResponse(audio, id, request, status, response, responseSize);
|
sendControlResponse(audio, id, request, status, response, responseSize);
|
||||||
if (data)
|
if (data)
|
||||||
@@ -544,21 +765,24 @@ static void isoPacket(void * opaque, uint64_t id,
|
|||||||
{
|
{
|
||||||
(void)id;
|
(void)id;
|
||||||
LG_USBAudio * audio = getAudio(opaque);
|
LG_USBAudio * audio = getAudio(opaque);
|
||||||
|
const USBAudioLayout * layout = getLayout(audio->streamAlt);
|
||||||
|
|
||||||
if (audio->streaming)
|
if (audio->streaming)
|
||||||
{
|
{
|
||||||
|
const uint16_t frameSize =
|
||||||
|
layout ? layout->channelCount * USB_AUDIO_SAMPLE_SIZE : 0;
|
||||||
if (packet->endpoint != USB_AUDIO_STREAM_ENDPOINT ||
|
if (packet->endpoint != USB_AUDIO_STREAM_ENDPOINT ||
|
||||||
packet->status != usb_redir_success || dataLength < 0 ||
|
packet->status != usb_redir_success || dataLength < 0 ||
|
||||||
packet->length != dataLength ||
|
packet->length != dataLength ||
|
||||||
dataLength > USB_AUDIO_MAX_PACKET_SIZE ||
|
!layout || dataLength > layoutPacketSize(layout) ||
|
||||||
dataLength % USB_AUDIO_FRAME_SIZE != 0)
|
dataLength % frameSize != 0)
|
||||||
{
|
{
|
||||||
DEBUG_WARN("Invalid USB audio isochronous packet");
|
DEBUG_WARN("Invalid USB audio isochronous packet");
|
||||||
stallStream(audio);
|
stallStream(audio);
|
||||||
}
|
}
|
||||||
else if (dataLength && audio->events && audio->events->data)
|
else if (dataLength && audio->events && audio->events->data)
|
||||||
audio->events->data(audio->eventOpaque, data,
|
audio->events->data(audio->eventOpaque, data,
|
||||||
dataLength / USB_AUDIO_FRAME_SIZE);
|
dataLength / frameSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data)
|
if (data)
|
||||||
|
|||||||
@@ -24,18 +24,20 @@
|
|||||||
#include "usbredir.h"
|
#include "usbredir.h"
|
||||||
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
#define LG_USB_AUDIO_SAMPLE_RATE 48000
|
#define LG_USB_AUDIO_DEFAULT_SAMPLE_RATE 48000
|
||||||
#define LG_USB_AUDIO_CHANNELS 2
|
|
||||||
|
|
||||||
typedef struct LG_USBAudio LG_USBAudio;
|
typedef struct LG_USBAudio LG_USBAudio;
|
||||||
|
|
||||||
typedef struct LG_USBAudioEventOps
|
typedef struct LG_USBAudioEventOps
|
||||||
{
|
{
|
||||||
void (*start)(void * opaque);
|
void (*start)(void * opaque, uint32_t sampleRate, uint32_t channelMask);
|
||||||
void (*stop)(void * opaque);
|
void (*stop)(void * opaque);
|
||||||
|
|
||||||
/* Data is borrowed interleaved stereo 32-bit IEEE float, little endian. */
|
/* Data is borrowed interleaved packed signed 24-bit PCM, little endian.
|
||||||
|
* Channels are ordered by ascending set bits in the selected UAC channel
|
||||||
|
* mask. */
|
||||||
void (*data)(void * opaque, const void * data, size_t frames);
|
void (*data)(void * opaque, const void * data, size_t frames);
|
||||||
}
|
}
|
||||||
LG_USBAudioEventOps;
|
LG_USBAudioEventOps;
|
||||||
|
|||||||
Reference in New Issue
Block a user