mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-10 09:11:31 +00:00
[client] usb audio: add audio provider
Adapt the emulated UAC2 playback stream to LG_AudioOps. Preserve borrowed packet delivery, publish reconstructed timing, and synchronize provider availability and attachment transitions.
This commit is contained in:
@@ -216,6 +216,7 @@ endif()
|
|||||||
|
|
||||||
if(ENABLE_USB_AUDIO)
|
if(ENABLE_USB_AUDIO)
|
||||||
list(APPEND SOURCES
|
list(APPEND SOURCES
|
||||||
|
src/audio_usb.c
|
||||||
src/usb_audio.c
|
src/usb_audio.c
|
||||||
src/usbredir.c
|
src/usbredir.c
|
||||||
)
|
)
|
||||||
|
|||||||
569
client/src/audio_usb.c
Normal file
569
client/src/audio_usb.c
Normal file
@@ -0,0 +1,569 @@
|
|||||||
|
/**
|
||||||
|
* Looking Glass
|
||||||
|
* Copyright © 2017-2026 The Looking Glass Authors
|
||||||
|
* https://looking-glass.io
|
||||||
|
*
|
||||||
|
* 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 2 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, write to the Free Software Foundation, Inc., 59
|
||||||
|
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "audio_usb.h"
|
||||||
|
|
||||||
|
#include "usb_audio.h"
|
||||||
|
|
||||||
|
#include "common/locking.h"
|
||||||
|
#include "common/time.h"
|
||||||
|
|
||||||
|
#include <stdatomic.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#define USB_AUDIO_NS_PER_SECOND INT64_C(1000000000)
|
||||||
|
|
||||||
|
typedef struct USBAudioCallbackFrame
|
||||||
|
{
|
||||||
|
const struct LGA_USBState * state;
|
||||||
|
struct USBAudioCallbackFrame * previous;
|
||||||
|
}
|
||||||
|
USBAudioCallbackFrame;
|
||||||
|
|
||||||
|
typedef struct USBAudioEventTarget
|
||||||
|
{
|
||||||
|
const LG_AudioEventOps * events;
|
||||||
|
void * opaque;
|
||||||
|
uint32_t attachmentGeneration;
|
||||||
|
USBAudioCallbackFrame frame;
|
||||||
|
}
|
||||||
|
USBAudioEventTarget;
|
||||||
|
|
||||||
|
struct LGA_USBState
|
||||||
|
{
|
||||||
|
LG_USBAudio * device;
|
||||||
|
LG_USBRedir * redir;
|
||||||
|
|
||||||
|
LG_Lock statusLock;
|
||||||
|
atomic_bool available;
|
||||||
|
uint32_t statusGeneration;
|
||||||
|
LG_AudioStatusFn statusCallback;
|
||||||
|
void * statusOpaque;
|
||||||
|
atomic_uint statusInFlight;
|
||||||
|
atomic_uint_fast64_t statusNextTicket;
|
||||||
|
atomic_uint_fast64_t statusServingTicket;
|
||||||
|
|
||||||
|
LG_Lock stateLock;
|
||||||
|
bool attached;
|
||||||
|
bool detaching;
|
||||||
|
uint32_t attachmentGeneration;
|
||||||
|
const LG_AudioEventOps * events;
|
||||||
|
void * eventOpaque;
|
||||||
|
|
||||||
|
uint32_t streamGeneration;
|
||||||
|
uint32_t generationSerial;
|
||||||
|
int64_t clockOrigin;
|
||||||
|
atomic_uint_fast64_t position;
|
||||||
|
atomic_uint deliveryGeneration;
|
||||||
|
atomic_uint inFlight;
|
||||||
|
};
|
||||||
|
|
||||||
|
static _Thread_local USBAudioCallbackFrame * l_eventFrames;
|
||||||
|
static _Thread_local USBAudioCallbackFrame * l_statusFrames;
|
||||||
|
|
||||||
|
static const LG_AudioFormat l_format =
|
||||||
|
{
|
||||||
|
.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_RIGHT,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
static uint32_t nextGeneration(uint32_t generation)
|
||||||
|
{
|
||||||
|
if (++generation == 0)
|
||||||
|
++generation;
|
||||||
|
return generation;
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned int callbackDepth(
|
||||||
|
const USBAudioCallbackFrame * frame, const LGA_USBState * state)
|
||||||
|
{
|
||||||
|
unsigned int depth = 0;
|
||||||
|
while (frame)
|
||||||
|
{
|
||||||
|
if (frame->state == state)
|
||||||
|
++depth;
|
||||||
|
frame = frame->previous;
|
||||||
|
}
|
||||||
|
return depth;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void beginCallback(LGA_USBState * state,
|
||||||
|
USBAudioCallbackFrame * frame, USBAudioCallbackFrame ** frames,
|
||||||
|
atomic_uint * inFlight)
|
||||||
|
{
|
||||||
|
atomic_fetch_add_explicit(inFlight, 1, memory_order_seq_cst);
|
||||||
|
frame->state = state;
|
||||||
|
frame->previous = *frames;
|
||||||
|
*frames = frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void endCallback(USBAudioCallbackFrame * frame,
|
||||||
|
USBAudioCallbackFrame ** frames, atomic_uint * inFlight)
|
||||||
|
{
|
||||||
|
*frames = frame->previous;
|
||||||
|
atomic_fetch_sub_explicit(inFlight, 1, memory_order_seq_cst);
|
||||||
|
}
|
||||||
|
|
||||||
|
static LG_AudioClock makeClock(
|
||||||
|
const LGA_USBState * state, uint64_t position)
|
||||||
|
{
|
||||||
|
/* USB redirection carries no publisher timestamp or USB frame number.
|
||||||
|
* Preserve the sample timeline, but do not claim a measured source rate. */
|
||||||
|
const int64_t elapsed =
|
||||||
|
position / LG_USB_AUDIO_SAMPLE_RATE * USB_AUDIO_NS_PER_SECOND +
|
||||||
|
position % LG_USB_AUDIO_SAMPLE_RATE * USB_AUDIO_NS_PER_SECOND /
|
||||||
|
LG_USB_AUDIO_SAMPLE_RATE;
|
||||||
|
|
||||||
|
return (LG_AudioClock)
|
||||||
|
{
|
||||||
|
.position = position,
|
||||||
|
.time = state->clockOrigin + elapsed,
|
||||||
|
.rate = 0.0,
|
||||||
|
.stable = false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* stateLock must be held while admitting a control event. The data event
|
||||||
|
* path performs the equivalent admission using deliveryGeneration. */
|
||||||
|
static bool beginEventNL(
|
||||||
|
LGA_USBState * state, USBAudioEventTarget * target)
|
||||||
|
{
|
||||||
|
if (!state->attached || !state->events)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
target->events = state->events;
|
||||||
|
target->opaque = state->eventOpaque;
|
||||||
|
target->attachmentGeneration = state->attachmentGeneration;
|
||||||
|
beginCallback(
|
||||||
|
state, &target->frame, &l_eventFrames, &state->inFlight);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool attachmentCurrentNL(const LGA_USBState * state,
|
||||||
|
const USBAudioEventTarget * target)
|
||||||
|
{
|
||||||
|
return state->attached &&
|
||||||
|
state->attachmentGeneration == target->attachmentGeneration &&
|
||||||
|
state->events == target->events &&
|
||||||
|
state->eventOpaque == target->opaque;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool eventCurrentNL(const LGA_USBState * state,
|
||||||
|
const USBAudioEventTarget * target, uint32_t streamGeneration)
|
||||||
|
{
|
||||||
|
return attachmentCurrentNL(state, target) &&
|
||||||
|
state->streamGeneration == streamGeneration;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void endEvent(LGA_USBState * state, USBAudioEventTarget * target)
|
||||||
|
{
|
||||||
|
endCallback(&target->frame, &l_eventFrames, &state->inFlight);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void waitEvents(const LGA_USBState * state)
|
||||||
|
{
|
||||||
|
const unsigned int depth = callbackDepth(l_eventFrames, state);
|
||||||
|
while (atomic_load_explicit(
|
||||||
|
&state->inFlight, memory_order_seq_cst) > depth)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool beginStatusOperation(LGA_USBState * state)
|
||||||
|
{
|
||||||
|
if (callbackDepth(l_statusFrames, state))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const uint_fast64_t ticket = atomic_fetch_add_explicit(
|
||||||
|
&state->statusNextTicket, 1, memory_order_relaxed);
|
||||||
|
while (atomic_load_explicit(
|
||||||
|
&state->statusServingTicket, memory_order_acquire) != ticket)
|
||||||
|
;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void endStatusOperation(LGA_USBState * state, bool owner)
|
||||||
|
{
|
||||||
|
if (owner)
|
||||||
|
atomic_fetch_add_explicit(
|
||||||
|
&state->statusServingTicket, 1, memory_order_release);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void beginStatusCallback(
|
||||||
|
LGA_USBState * state, USBAudioCallbackFrame * frame)
|
||||||
|
{
|
||||||
|
beginCallback(
|
||||||
|
state, frame, &l_statusFrames, &state->statusInFlight);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void endStatusCallback(
|
||||||
|
LGA_USBState * state, USBAudioCallbackFrame * frame)
|
||||||
|
{
|
||||||
|
endCallback(frame, &l_statusFrames, &state->statusInFlight);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void waitStatusCallbacks(const LGA_USBState * state)
|
||||||
|
{
|
||||||
|
const unsigned int depth = callbackDepth(l_statusFrames, state);
|
||||||
|
while (atomic_load_explicit(
|
||||||
|
&state->statusInFlight, memory_order_acquire) > depth)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void usbAudioStart(void * opaque)
|
||||||
|
{
|
||||||
|
LGA_USBState * state = opaque;
|
||||||
|
USBAudioEventTarget target;
|
||||||
|
bool dispatch;
|
||||||
|
uint32_t generation;
|
||||||
|
LG_AudioClock clock;
|
||||||
|
|
||||||
|
LG_LOCK(state->stateLock);
|
||||||
|
if (state->streamGeneration)
|
||||||
|
{
|
||||||
|
LG_UNLOCK(state->stateLock);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
generation = state->generationSerial =
|
||||||
|
nextGeneration(state->generationSerial);
|
||||||
|
state->streamGeneration = generation;
|
||||||
|
state->clockOrigin = (int64_t)nanotime();
|
||||||
|
atomic_store_explicit(&state->position, 0, memory_order_relaxed);
|
||||||
|
atomic_store_explicit(
|
||||||
|
&state->deliveryGeneration, 0, memory_order_seq_cst);
|
||||||
|
clock = makeClock(state, 0);
|
||||||
|
const bool admitted = beginEventNL(state, &target);
|
||||||
|
dispatch = admitted && target.events->playbackStart;
|
||||||
|
if (admitted && !dispatch)
|
||||||
|
endEvent(state, &target);
|
||||||
|
LG_UNLOCK(state->stateLock);
|
||||||
|
|
||||||
|
if (!dispatch)
|
||||||
|
return;
|
||||||
|
|
||||||
|
target.events->playbackStart(
|
||||||
|
target.opaque, generation, &l_format, &clock);
|
||||||
|
|
||||||
|
LG_LOCK(state->stateLock);
|
||||||
|
if (eventCurrentNL(state, &target, generation))
|
||||||
|
atomic_store_explicit(&state->deliveryGeneration,
|
||||||
|
generation, memory_order_seq_cst);
|
||||||
|
LG_UNLOCK(state->stateLock);
|
||||||
|
endEvent(state, &target);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void usbAudioStop(void * opaque)
|
||||||
|
{
|
||||||
|
LGA_USBState * state = opaque;
|
||||||
|
USBAudioEventTarget target;
|
||||||
|
|
||||||
|
LG_LOCK(state->stateLock);
|
||||||
|
const uint32_t generation = state->streamGeneration;
|
||||||
|
if (!generation)
|
||||||
|
{
|
||||||
|
LG_UNLOCK(state->stateLock);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state->streamGeneration = 0;
|
||||||
|
atomic_store_explicit(
|
||||||
|
&state->deliveryGeneration, 0, memory_order_seq_cst);
|
||||||
|
const bool admitted = beginEventNL(state, &target);
|
||||||
|
LG_UNLOCK(state->stateLock);
|
||||||
|
|
||||||
|
waitEvents(state);
|
||||||
|
|
||||||
|
if (!admitted)
|
||||||
|
return;
|
||||||
|
|
||||||
|
LG_LOCK(state->stateLock);
|
||||||
|
const bool dispatch = attachmentCurrentNL(state, &target) &&
|
||||||
|
target.events->playbackStop;
|
||||||
|
LG_UNLOCK(state->stateLock);
|
||||||
|
|
||||||
|
if (dispatch)
|
||||||
|
target.events->playbackStop(target.opaque, generation);
|
||||||
|
endEvent(state, &target);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void usbAudioData(void * opaque, const void * data, size_t frames)
|
||||||
|
{
|
||||||
|
LGA_USBState * state = opaque;
|
||||||
|
USBAudioCallbackFrame frame;
|
||||||
|
beginCallback(state, &frame, &l_eventFrames, &state->inFlight);
|
||||||
|
|
||||||
|
const uint64_t position = atomic_fetch_add_explicit(
|
||||||
|
&state->position, frames, memory_order_relaxed);
|
||||||
|
const uint32_t generation = atomic_load_explicit(
|
||||||
|
&state->deliveryGeneration, memory_order_seq_cst);
|
||||||
|
if (generation)
|
||||||
|
{
|
||||||
|
const LG_AudioEventOps * events = state->events;
|
||||||
|
void * target = state->eventOpaque;
|
||||||
|
if (events && events->playbackData)
|
||||||
|
{
|
||||||
|
const LG_AudioClock clock = makeClock(state, position);
|
||||||
|
events->playbackData(
|
||||||
|
target, generation, data, frames, &clock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
endCallback(&frame, &l_eventFrames, &state->inFlight);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const LG_USBAudioEventOps l_usbAudioEvents =
|
||||||
|
{
|
||||||
|
.start = usbAudioStart,
|
||||||
|
.stop = usbAudioStop,
|
||||||
|
.data = usbAudioData,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void usbSetAvailable(void * opaque, bool available)
|
||||||
|
{
|
||||||
|
LGA_USBState * state = opaque;
|
||||||
|
const bool statusOwner = beginStatusOperation(state);
|
||||||
|
|
||||||
|
LG_LOCK(state->statusLock);
|
||||||
|
const bool changed = atomic_load_explicit(
|
||||||
|
&state->available, memory_order_relaxed) != available;
|
||||||
|
if (changed)
|
||||||
|
{
|
||||||
|
atomic_store_explicit(
|
||||||
|
&state->available, available, memory_order_release);
|
||||||
|
state->statusGeneration =
|
||||||
|
nextGeneration(state->statusGeneration);
|
||||||
|
}
|
||||||
|
|
||||||
|
const LG_AudioStatusFn callback = state->statusCallback;
|
||||||
|
void * callbackOpaque = state->statusOpaque;
|
||||||
|
const LG_AudioStatus status =
|
||||||
|
{
|
||||||
|
.available = available,
|
||||||
|
.generation = state->statusGeneration,
|
||||||
|
};
|
||||||
|
USBAudioCallbackFrame frame;
|
||||||
|
if (changed && callback)
|
||||||
|
beginStatusCallback(state, &frame);
|
||||||
|
LG_UNLOCK(state->statusLock);
|
||||||
|
|
||||||
|
if (changed && callback)
|
||||||
|
{
|
||||||
|
callback(callbackOpaque, &status);
|
||||||
|
endStatusCallback(state, &frame);
|
||||||
|
}
|
||||||
|
endStatusOperation(state, statusOwner);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void usbSetStatusListener(void * opaque,
|
||||||
|
LG_AudioStatusFn callback, void * callbackOpaque)
|
||||||
|
{
|
||||||
|
LGA_USBState * state = opaque;
|
||||||
|
const bool statusOwner = beginStatusOperation(state);
|
||||||
|
|
||||||
|
LG_LOCK(state->statusLock);
|
||||||
|
state->statusCallback = callback;
|
||||||
|
state->statusOpaque = callbackOpaque;
|
||||||
|
const LG_AudioStatus status =
|
||||||
|
{
|
||||||
|
.available = atomic_load_explicit(
|
||||||
|
&state->available, memory_order_acquire),
|
||||||
|
.generation = state->statusGeneration,
|
||||||
|
};
|
||||||
|
USBAudioCallbackFrame frame;
|
||||||
|
if (callback)
|
||||||
|
beginStatusCallback(state, &frame);
|
||||||
|
LG_UNLOCK(state->statusLock);
|
||||||
|
|
||||||
|
if (callback)
|
||||||
|
{
|
||||||
|
callback(callbackOpaque, &status);
|
||||||
|
endStatusCallback(state, &frame);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
waitStatusCallbacks(state);
|
||||||
|
endStatusOperation(state, statusOwner);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool usbAttach(void * opaque, const LG_AudioEventOps * events,
|
||||||
|
void * eventOpaque)
|
||||||
|
{
|
||||||
|
LGA_USBState * state = opaque;
|
||||||
|
if (!events || !atomic_load_explicit(
|
||||||
|
&state->available, memory_order_acquire))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
USBAudioEventTarget target;
|
||||||
|
LG_AudioClock clock;
|
||||||
|
uint32_t generation;
|
||||||
|
bool dispatch;
|
||||||
|
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
LG_LOCK(state->stateLock);
|
||||||
|
if (!state->detaching)
|
||||||
|
break;
|
||||||
|
LG_UNLOCK(state->stateLock);
|
||||||
|
if (callbackDepth(l_eventFrames, state))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state->attached || !atomic_load_explicit(
|
||||||
|
&state->available, memory_order_acquire))
|
||||||
|
{
|
||||||
|
LG_UNLOCK(state->stateLock);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
state->attached = true;
|
||||||
|
state->attachmentGeneration =
|
||||||
|
nextGeneration(state->attachmentGeneration);
|
||||||
|
state->events = events;
|
||||||
|
state->eventOpaque = eventOpaque;
|
||||||
|
generation = state->streamGeneration;
|
||||||
|
dispatch = generation && events->playbackStart &&
|
||||||
|
beginEventNL(state, &target);
|
||||||
|
if (dispatch)
|
||||||
|
clock = makeClock(state, atomic_load_explicit(
|
||||||
|
&state->position, memory_order_relaxed));
|
||||||
|
lgUsbRedir_setPlugged(state->redir, true);
|
||||||
|
LG_UNLOCK(state->stateLock);
|
||||||
|
|
||||||
|
if (dispatch)
|
||||||
|
{
|
||||||
|
target.events->playbackStart(
|
||||||
|
target.opaque, generation, &l_format, &clock);
|
||||||
|
|
||||||
|
LG_LOCK(state->stateLock);
|
||||||
|
if (eventCurrentNL(state, &target, generation))
|
||||||
|
atomic_store_explicit(&state->deliveryGeneration,
|
||||||
|
generation, memory_order_seq_cst);
|
||||||
|
LG_UNLOCK(state->stateLock);
|
||||||
|
endEvent(state, &target);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void usbDetach(void * opaque)
|
||||||
|
{
|
||||||
|
LGA_USBState * state = opaque;
|
||||||
|
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
LG_LOCK(state->stateLock);
|
||||||
|
if (!state->detaching)
|
||||||
|
break;
|
||||||
|
LG_UNLOCK(state->stateLock);
|
||||||
|
if (callbackDepth(l_eventFrames, state))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state->detaching = true;
|
||||||
|
state->attached = false;
|
||||||
|
state->attachmentGeneration =
|
||||||
|
nextGeneration(state->attachmentGeneration);
|
||||||
|
const uint32_t attachmentGeneration =
|
||||||
|
state->attachmentGeneration;
|
||||||
|
atomic_store_explicit(
|
||||||
|
&state->deliveryGeneration, 0, memory_order_seq_cst);
|
||||||
|
lgUsbRedir_setPlugged(state->redir, false);
|
||||||
|
LG_UNLOCK(state->stateLock);
|
||||||
|
|
||||||
|
waitEvents(state);
|
||||||
|
|
||||||
|
LG_LOCK(state->stateLock);
|
||||||
|
if (!state->attached &&
|
||||||
|
state->attachmentGeneration == attachmentGeneration)
|
||||||
|
{
|
||||||
|
state->events = NULL;
|
||||||
|
state->eventOpaque = NULL;
|
||||||
|
state->detaching = false;
|
||||||
|
}
|
||||||
|
LG_UNLOCK(state->stateLock);
|
||||||
|
}
|
||||||
|
|
||||||
|
const LG_AudioOps LGA_USB =
|
||||||
|
{
|
||||||
|
.name = "USB Audio",
|
||||||
|
.setStatusListener = usbSetStatusListener,
|
||||||
|
.attach = usbAttach,
|
||||||
|
.detach = usbDetach,
|
||||||
|
.recordData = NULL,
|
||||||
|
.clockFeedback = NULL,
|
||||||
|
};
|
||||||
|
|
||||||
|
LGA_USBState * lgaUsb_create(void)
|
||||||
|
{
|
||||||
|
LGA_USBState * state = calloc(1, sizeof(*state));
|
||||||
|
if (!state)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
LG_LOCK_INIT(state->statusLock);
|
||||||
|
LG_LOCK_INIT(state->stateLock);
|
||||||
|
atomic_init(&state->available, false);
|
||||||
|
atomic_init(&state->position, 0);
|
||||||
|
atomic_init(&state->deliveryGeneration, 0);
|
||||||
|
atomic_init(&state->inFlight, 0);
|
||||||
|
atomic_init(&state->statusInFlight, 0);
|
||||||
|
atomic_init(&state->statusNextTicket, 0);
|
||||||
|
atomic_init(&state->statusServingTicket, 0);
|
||||||
|
|
||||||
|
state->device = lgUsbAudio_create(&l_usbAudioEvents, state);
|
||||||
|
if (!state->device)
|
||||||
|
{
|
||||||
|
free(state);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
state->redir = lgUsbRedir_create(
|
||||||
|
lgUsbAudio_deviceOps(), state->device, usbSetAvailable, state);
|
||||||
|
if (!state->redir)
|
||||||
|
{
|
||||||
|
lgUsbAudio_destroy(state->device);
|
||||||
|
free(state);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
void lgaUsb_destroy(LGA_USBState * state)
|
||||||
|
{
|
||||||
|
if (!state)
|
||||||
|
return;
|
||||||
|
|
||||||
|
usbSetStatusListener(state, NULL, NULL);
|
||||||
|
usbDetach(state);
|
||||||
|
lgUsbRedir_destroy(state->redir);
|
||||||
|
lgUsbAudio_destroy(state->device);
|
||||||
|
LG_LOCK_FREE(state->stateLock);
|
||||||
|
LG_LOCK_FREE(state->statusLock);
|
||||||
|
free(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
LG_USBRedir * lgaUsb_redir(LGA_USBState * state)
|
||||||
|
{
|
||||||
|
return state ? state->redir : NULL;
|
||||||
|
}
|
||||||
37
client/src/audio_usb.h
Normal file
37
client/src/audio_usb.h
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* Looking Glass
|
||||||
|
* Copyright © 2017-2026 The Looking Glass Authors
|
||||||
|
* https://looking-glass.io
|
||||||
|
*
|
||||||
|
* 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 2 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, write to the Free Software Foundation, Inc., 59
|
||||||
|
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _H_LG_CLIENT_AUDIO_USB_
|
||||||
|
#define _H_LG_CLIENT_AUDIO_USB_
|
||||||
|
|
||||||
|
#include "interface/audio.h"
|
||||||
|
#include "usbredir.h"
|
||||||
|
|
||||||
|
typedef struct LGA_USBState LGA_USBState;
|
||||||
|
|
||||||
|
LGA_USBState * lgaUsb_create(void);
|
||||||
|
/* Detach this provider and stop PureSpice before destroying its state. */
|
||||||
|
void lgaUsb_destroy(LGA_USBState * state);
|
||||||
|
|
||||||
|
LG_USBRedir * lgaUsb_redir(LGA_USBState * state);
|
||||||
|
|
||||||
|
extern const LG_AudioOps LGA_USB;
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -34,6 +34,8 @@ struct LG_USBRedir
|
|||||||
{
|
{
|
||||||
const LG_USBRedirDeviceOps * deviceOps;
|
const LG_USBRedirDeviceOps * deviceOps;
|
||||||
void * deviceOpaque;
|
void * deviceOpaque;
|
||||||
|
LG_USBRedirStatusFn status;
|
||||||
|
void * statusOpaque;
|
||||||
|
|
||||||
PSUSBRedirChannel * channels[USB_REDIR_CHANNEL_COUNT];
|
PSUSBRedirChannel * channels[USB_REDIR_CHANNEL_COUNT];
|
||||||
PSUSBRedirChannel * channel;
|
PSUSBRedirChannel * channel;
|
||||||
@@ -48,6 +50,14 @@ struct LG_USBRedir
|
|||||||
bool plugged;
|
bool plugged;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static void setAvailable(LG_USBRedir * usbredir, bool available)
|
||||||
|
{
|
||||||
|
const bool previous = atomic_exchange_explicit(
|
||||||
|
&usbredir->available, available, memory_order_acq_rel);
|
||||||
|
if (previous != available && usbredir->status)
|
||||||
|
usbredir->status(usbredir->statusOpaque, available);
|
||||||
|
}
|
||||||
|
|
||||||
static int readUSBRedir(void * opaque, uint8_t * data, int count)
|
static int readUSBRedir(void * opaque, uint8_t * data, int count)
|
||||||
{
|
{
|
||||||
LG_USBRedir * usbredir = opaque;
|
LG_USBRedir * usbredir = opaque;
|
||||||
@@ -105,7 +115,7 @@ static void helloUSBRedir(void * opaque,
|
|||||||
{
|
{
|
||||||
(void)hello;
|
(void)hello;
|
||||||
LG_USBRedir * usbredir = opaque;
|
LG_USBRedir * usbredir = opaque;
|
||||||
atomic_store_explicit(&usbredir->available, true, memory_order_release);
|
setAvailable(usbredir, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void unplugDevice(LG_USBRedir * usbredir)
|
static void unplugDevice(LG_USBRedir * usbredir)
|
||||||
@@ -119,7 +129,7 @@ static void unplugDevice(LG_USBRedir * usbredir)
|
|||||||
|
|
||||||
static void destroyParser(LG_USBRedir * usbredir)
|
static void destroyParser(LG_USBRedir * usbredir)
|
||||||
{
|
{
|
||||||
atomic_store_explicit(&usbredir->available, false, memory_order_release);
|
setAvailable(usbredir, false);
|
||||||
unplugDevice(usbredir);
|
unplugDevice(usbredir);
|
||||||
|
|
||||||
if (!usbredir->parser)
|
if (!usbredir->parser)
|
||||||
@@ -202,7 +212,8 @@ static bool createParser(LG_USBRedir * usbredir)
|
|||||||
}
|
}
|
||||||
|
|
||||||
LG_USBRedir * lgUsbRedir_create(
|
LG_USBRedir * lgUsbRedir_create(
|
||||||
const LG_USBRedirDeviceOps * deviceOps, void * deviceOpaque)
|
const LG_USBRedirDeviceOps * deviceOps, void * deviceOpaque,
|
||||||
|
LG_USBRedirStatusFn status, void * statusOpaque)
|
||||||
{
|
{
|
||||||
if (!deviceOps || !deviceOps->setup || !deviceOps->plug ||
|
if (!deviceOps || !deviceOps->setup || !deviceOps->plug ||
|
||||||
!deviceOps->unplug)
|
!deviceOps->unplug)
|
||||||
@@ -214,6 +225,8 @@ LG_USBRedir * lgUsbRedir_create(
|
|||||||
|
|
||||||
usbredir->deviceOps = deviceOps;
|
usbredir->deviceOps = deviceOps;
|
||||||
usbredir->deviceOpaque = deviceOpaque;
|
usbredir->deviceOpaque = deviceOpaque;
|
||||||
|
usbredir->status = status;
|
||||||
|
usbredir->statusOpaque = statusOpaque;
|
||||||
atomic_init(&usbredir->desiredPlugged, false);
|
atomic_init(&usbredir->desiredPlugged, false);
|
||||||
atomic_init(&usbredir->available, false);
|
atomic_init(&usbredir->available, false);
|
||||||
return usbredir;
|
return usbredir;
|
||||||
|
|||||||
@@ -31,6 +31,9 @@ struct usbredirparser;
|
|||||||
|
|
||||||
typedef struct LG_USBRedir LG_USBRedir;
|
typedef struct LG_USBRedir LG_USBRedir;
|
||||||
|
|
||||||
|
/* Availability transitions are delivered on the PureSpice process thread. */
|
||||||
|
typedef void (*LG_USBRedirStatusFn)(void * opaque, bool available);
|
||||||
|
|
||||||
typedef struct LG_USBRedirDeviceOps
|
typedef struct LG_USBRedirDeviceOps
|
||||||
{
|
{
|
||||||
/* Install the device packet callbacks on a newly-created parser. */
|
/* Install the device packet callbacks on a newly-created parser. */
|
||||||
@@ -45,7 +48,8 @@ typedef struct LG_USBRedirDeviceOps
|
|||||||
LG_USBRedirDeviceOps;
|
LG_USBRedirDeviceOps;
|
||||||
|
|
||||||
LG_USBRedir * lgUsbRedir_create(
|
LG_USBRedir * lgUsbRedir_create(
|
||||||
const LG_USBRedirDeviceOps * deviceOps, void * deviceOpaque);
|
const LG_USBRedirDeviceOps * deviceOps, void * deviceOpaque,
|
||||||
|
LG_USBRedirStatusFn status, void * statusOpaque);
|
||||||
/* The PureSpice session must be stopped before destroying the bridge. */
|
/* The PureSpice session must be stopped before destroying the bridge. */
|
||||||
void lgUsbRedir_destroy(LG_USBRedir * usbredir);
|
void lgUsbRedir_destroy(LG_USBRedir * usbredir);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user