mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-22 15:11:31 +00:00
1048 lines
29 KiB
C
1048 lines
29 KiB
C
/**
|
|
* 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/debug.h"
|
|
#include "common/event.h"
|
|
#include "common/locking.h"
|
|
#include "common/time.h"
|
|
|
|
#include <stdalign.h>
|
|
#include <stdatomic.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#define USB_AUDIO_NS_PER_SECOND INT64_C(1000000000)
|
|
|
|
typedef struct USBAudioCallbackFrame
|
|
{
|
|
const struct LGA_USBState * state;
|
|
struct USBAudioCallbackFrame * previous;
|
|
}
|
|
USBAudioCallbackFrame;
|
|
|
|
typedef struct USBAudioCallbackWaiter
|
|
{
|
|
struct USBAudioCallbackWaiter * next;
|
|
LGEvent * event;
|
|
unsigned int depth;
|
|
}
|
|
USBAudioCallbackWaiter;
|
|
|
|
typedef struct USBAudioCallbackWaitQueue
|
|
{
|
|
LG_Lock lock;
|
|
atomic_uint count;
|
|
USBAudioCallbackWaiter * waiters;
|
|
}
|
|
USBAudioCallbackWaitQueue;
|
|
|
|
typedef struct USBAudioCallbackGate
|
|
{
|
|
atomic_uint inFlight;
|
|
USBAudioCallbackWaitQueue wait;
|
|
}
|
|
USBAudioCallbackGate;
|
|
|
|
typedef struct USBAudioEventTarget
|
|
{
|
|
const LG_AudioEventOps * events;
|
|
void * opaque;
|
|
uint32_t attachmentGeneration;
|
|
USBAudioCallbackFrame frame;
|
|
USBAudioCallbackFrame ** frames;
|
|
USBAudioCallbackGate * gate;
|
|
}
|
|
USBAudioEventTarget;
|
|
|
|
typedef struct USBAudioOperationWaiter
|
|
{
|
|
struct USBAudioOperationWaiter * next;
|
|
LGEvent * event;
|
|
}
|
|
USBAudioOperationWaiter;
|
|
|
|
typedef struct USBAudioOperationQueue
|
|
{
|
|
LG_Lock lock;
|
|
bool active;
|
|
USBAudioOperationWaiter * head;
|
|
USBAudioOperationWaiter * tail;
|
|
}
|
|
USBAudioOperationQueue;
|
|
|
|
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;
|
|
USBAudioCallbackWaitQueue statusWait;
|
|
USBAudioOperationQueue statusOperation;
|
|
|
|
LG_Lock stateLock;
|
|
LGEvent * detachEvent;
|
|
bool attached;
|
|
bool detaching;
|
|
uint32_t attachmentGeneration;
|
|
const LG_AudioEventOps * events;
|
|
void * eventOpaque;
|
|
|
|
LG_AudioFormat playbackFormat;
|
|
uint32_t playbackGeneration;
|
|
LG_AudioFormat recordFormat;
|
|
uint32_t recordGeneration;
|
|
uint32_t generationSerial;
|
|
int64_t playbackClockOrigin;
|
|
atomic_uint_fast64_t playbackPosition;
|
|
atomic_uint playbackDeliveryGeneration;
|
|
atomic_uint recordDeliveryGeneration;
|
|
alignas(64) USBAudioCallbackGate playbackGate;
|
|
alignas(64) USBAudioCallbackGate recordGate;
|
|
USBAudioOperationQueue recordOperation;
|
|
};
|
|
|
|
static _Thread_local USBAudioCallbackFrame * l_playbackFrames;
|
|
static _Thread_local USBAudioCallbackFrame * l_recordFrames;
|
|
static _Thread_local USBAudioCallbackFrame * l_statusFrames;
|
|
|
|
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[] =
|
|
{
|
|
LG_AUDIO_CH_FRONT_LEFT,
|
|
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)
|
|
{
|
|
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 LGEvent * createWaitEvent(void)
|
|
{
|
|
LGEvent * event = lgCreateEvent(true, 0);
|
|
if (!event)
|
|
DEBUG_FATAL("Failed to create USB audio wait event");
|
|
return event;
|
|
}
|
|
|
|
static void waitEvent(LGEvent * event)
|
|
{
|
|
if (!lgWaitEvent(event, TIMEOUT_INFINITE))
|
|
DEBUG_FATAL("Failed to wait for USB audio event");
|
|
}
|
|
|
|
static void signalWaitEvent(LGEvent * event)
|
|
{
|
|
if (!lgSignalEvent(event))
|
|
DEBUG_FATAL("Failed to signal USB audio event");
|
|
}
|
|
|
|
static void signalCallbackWaiters(
|
|
USBAudioCallbackWaitQueue * queue, unsigned int remaining)
|
|
{
|
|
if (!atomic_load_explicit(&queue->count, memory_order_seq_cst))
|
|
return;
|
|
|
|
LG_LOCK(queue->lock);
|
|
USBAudioCallbackWaiter ** link = &queue->waiters;
|
|
while (*link)
|
|
{
|
|
USBAudioCallbackWaiter * waiter = *link;
|
|
if (remaining > waiter->depth)
|
|
{
|
|
link = &waiter->next;
|
|
continue;
|
|
}
|
|
|
|
*link = waiter->next;
|
|
atomic_fetch_sub_explicit(
|
|
&queue->count, 1, memory_order_release);
|
|
signalWaitEvent(waiter->event);
|
|
}
|
|
LG_UNLOCK(queue->lock);
|
|
}
|
|
|
|
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,
|
|
USBAudioCallbackWaitQueue * waitQueue)
|
|
{
|
|
*frames = frame->previous;
|
|
const unsigned int previous = atomic_fetch_sub_explicit(
|
|
inFlight, 1, memory_order_seq_cst);
|
|
DEBUG_ASSERT(previous > 0);
|
|
signalCallbackWaiters(waitQueue, previous - 1);
|
|
}
|
|
|
|
static void waitCallbacks(LGA_USBState * state,
|
|
USBAudioCallbackFrame * frames, atomic_uint * inFlight,
|
|
USBAudioCallbackWaitQueue * waitQueue)
|
|
{
|
|
const unsigned int depth = callbackDepth(frames, state);
|
|
if (atomic_load_explicit(inFlight, memory_order_seq_cst) <= depth)
|
|
return;
|
|
|
|
USBAudioCallbackWaiter waiter =
|
|
{
|
|
.event = createWaitEvent(),
|
|
.depth = depth,
|
|
};
|
|
|
|
bool queued = false;
|
|
LG_LOCK(waitQueue->lock);
|
|
|
|
/* Publish the waiter before rechecking inFlight. This prevents the final
|
|
* callback from taking the lock-free path while a waiter is being queued. */
|
|
atomic_fetch_add_explicit(
|
|
&waitQueue->count, 1, memory_order_seq_cst);
|
|
if (atomic_load_explicit(inFlight, memory_order_seq_cst) > depth)
|
|
{
|
|
waiter.next = waitQueue->waiters;
|
|
waitQueue->waiters = &waiter;
|
|
queued = true;
|
|
}
|
|
else
|
|
atomic_fetch_sub_explicit(
|
|
&waitQueue->count, 1, memory_order_seq_cst);
|
|
|
|
LG_UNLOCK(waitQueue->lock);
|
|
|
|
if (queued)
|
|
{
|
|
waitEvent(waiter.event);
|
|
|
|
/* The signaler owns the waiter until it releases the queue lock. */
|
|
LG_LOCK(waitQueue->lock);
|
|
LG_UNLOCK(waitQueue->lock);
|
|
}
|
|
|
|
lgFreeEvent(waiter.event);
|
|
}
|
|
|
|
static LG_AudioClock makePlaybackClock(
|
|
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 uint32_t sampleRate = state->playbackFormat.sampleRate;
|
|
const int64_t elapsed =
|
|
position / sampleRate * USB_AUDIO_NS_PER_SECOND +
|
|
position % sampleRate * USB_AUDIO_NS_PER_SECOND / sampleRate;
|
|
|
|
return (LG_AudioClock)
|
|
{
|
|
.position = position,
|
|
.time = state->playbackClockOrigin + 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,
|
|
USBAudioCallbackFrame ** frames, USBAudioCallbackGate * gate)
|
|
{
|
|
if (!state->attached || !state->events)
|
|
return false;
|
|
|
|
target->events = state->events;
|
|
target->opaque = state->eventOpaque;
|
|
target->attachmentGeneration = state->attachmentGeneration;
|
|
target->frames = frames;
|
|
target->gate = gate;
|
|
beginCallback(
|
|
state, &target->frame, frames, &gate->inFlight);
|
|
return true;
|
|
}
|
|
|
|
static bool beginPlaybackEventNL(
|
|
LGA_USBState * state, USBAudioEventTarget * target)
|
|
{
|
|
return beginEventNL(
|
|
state, target, &l_playbackFrames, &state->playbackGate);
|
|
}
|
|
|
|
static bool beginRecordEventNL(
|
|
LGA_USBState * state, USBAudioEventTarget * target)
|
|
{
|
|
return beginEventNL(
|
|
state, target, &l_recordFrames, &state->recordGate);
|
|
}
|
|
|
|
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 playbackEventCurrentNL(const LGA_USBState * state,
|
|
const USBAudioEventTarget * target, uint32_t streamGeneration)
|
|
{
|
|
return attachmentCurrentNL(state, target) &&
|
|
state->playbackGeneration == streamGeneration;
|
|
}
|
|
|
|
static bool recordEventCurrentNL(const LGA_USBState * state,
|
|
const USBAudioEventTarget * target, uint32_t streamGeneration)
|
|
{
|
|
return attachmentCurrentNL(state, target) &&
|
|
state->recordGeneration == streamGeneration;
|
|
}
|
|
|
|
static void endEvent(USBAudioEventTarget * target)
|
|
{
|
|
endCallback(&target->frame, target->frames,
|
|
&target->gate->inFlight, &target->gate->wait);
|
|
}
|
|
|
|
static void waitPlaybackEvents(LGA_USBState * state)
|
|
{
|
|
waitCallbacks(state, l_playbackFrames,
|
|
&state->playbackGate.inFlight, &state->playbackGate.wait);
|
|
}
|
|
|
|
static void waitRecordEvents(LGA_USBState * state)
|
|
{
|
|
waitCallbacks(state, l_recordFrames,
|
|
&state->recordGate.inFlight, &state->recordGate.wait);
|
|
}
|
|
|
|
static bool beginOperation(LGA_USBState * state,
|
|
USBAudioOperationQueue * operation,
|
|
const USBAudioCallbackFrame * callbackFrames)
|
|
{
|
|
/* Reentrant control must run inline: the current owner may be waiting for
|
|
* this callback to finish before it can release the operation. */
|
|
if (callbackDepth(callbackFrames, state))
|
|
return false;
|
|
|
|
USBAudioOperationWaiter waiter =
|
|
{
|
|
.event = createWaitEvent(),
|
|
};
|
|
|
|
bool queued = false;
|
|
LG_LOCK(operation->lock);
|
|
if (operation->active)
|
|
{
|
|
if (operation->tail)
|
|
operation->tail->next = &waiter;
|
|
else
|
|
operation->head = &waiter;
|
|
operation->tail = &waiter;
|
|
queued = true;
|
|
}
|
|
else
|
|
operation->active = true;
|
|
LG_UNLOCK(operation->lock);
|
|
|
|
if (queued)
|
|
{
|
|
waitEvent(waiter.event);
|
|
|
|
/* The previous owner owns the waiter until it releases the queue lock. */
|
|
LG_LOCK(operation->lock);
|
|
LG_UNLOCK(operation->lock);
|
|
}
|
|
|
|
lgFreeEvent(waiter.event);
|
|
return true;
|
|
}
|
|
|
|
static void endOperation(
|
|
USBAudioOperationQueue * operation, bool owner)
|
|
{
|
|
if (!owner)
|
|
return;
|
|
|
|
LG_LOCK(operation->lock);
|
|
USBAudioOperationWaiter * waiter = operation->head;
|
|
if (waiter)
|
|
{
|
|
operation->head = waiter->next;
|
|
if (!operation->head)
|
|
operation->tail = NULL;
|
|
signalWaitEvent(waiter->event);
|
|
}
|
|
else
|
|
operation->active = false;
|
|
LG_UNLOCK(operation->lock);
|
|
}
|
|
|
|
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, &state->statusWait);
|
|
}
|
|
|
|
static void waitStatusCallbacks(LGA_USBState * state)
|
|
{
|
|
waitCallbacks(state, l_statusFrames,
|
|
&state->statusInFlight, &state->statusWait);
|
|
}
|
|
|
|
/* Returns with stateLock held. A callback must not wait for a detach which
|
|
* may itself be waiting for that callback to return. */
|
|
static bool lockStateAfterDetach(LGA_USBState * state)
|
|
{
|
|
for (;;)
|
|
{
|
|
LG_LOCK(state->stateLock);
|
|
if (!state->detaching)
|
|
return true;
|
|
LG_UNLOCK(state->stateLock);
|
|
|
|
if (callbackDepth(l_playbackFrames, state) ||
|
|
callbackDepth(l_recordFrames, state))
|
|
return false;
|
|
waitEvent(state->detachEvent);
|
|
}
|
|
}
|
|
|
|
static void usbPlaybackStart(
|
|
void * opaque, uint32_t sampleRate, uint32_t channelMask)
|
|
{
|
|
LGA_USBState * state = opaque;
|
|
USBAudioEventTarget target;
|
|
bool dispatch;
|
|
uint32_t generation;
|
|
LG_AudioClock clock;
|
|
|
|
LG_LOCK(state->stateLock);
|
|
if (state->playbackGeneration)
|
|
{
|
|
LG_UNLOCK(state->stateLock);
|
|
return;
|
|
}
|
|
|
|
setStreamFormat(&state->playbackFormat, sampleRate, channelMask);
|
|
generation = state->generationSerial =
|
|
nextGeneration(state->generationSerial);
|
|
state->playbackGeneration = generation;
|
|
state->playbackClockOrigin = (int64_t)nanotime();
|
|
atomic_store_explicit(
|
|
&state->playbackPosition, 0, memory_order_relaxed);
|
|
atomic_store_explicit(
|
|
&state->playbackDeliveryGeneration, 0, memory_order_seq_cst);
|
|
clock = makePlaybackClock(state, 0);
|
|
const bool admitted = beginPlaybackEventNL(state, &target);
|
|
dispatch = admitted && target.events->playbackStart;
|
|
if (admitted && !dispatch)
|
|
endEvent(&target);
|
|
LG_UNLOCK(state->stateLock);
|
|
|
|
if (!dispatch)
|
|
return;
|
|
|
|
target.events->playbackStart(
|
|
target.opaque, generation, &state->playbackFormat, &clock);
|
|
|
|
LG_LOCK(state->stateLock);
|
|
if (playbackEventCurrentNL(state, &target, generation))
|
|
atomic_store_explicit(&state->playbackDeliveryGeneration,
|
|
generation, memory_order_seq_cst);
|
|
LG_UNLOCK(state->stateLock);
|
|
endEvent(&target);
|
|
}
|
|
|
|
static void usbPlaybackStop(void * opaque)
|
|
{
|
|
LGA_USBState * state = opaque;
|
|
USBAudioEventTarget target;
|
|
|
|
LG_LOCK(state->stateLock);
|
|
const uint32_t generation = state->playbackGeneration;
|
|
if (!generation)
|
|
{
|
|
LG_UNLOCK(state->stateLock);
|
|
return;
|
|
}
|
|
|
|
state->playbackGeneration = 0;
|
|
atomic_store_explicit(
|
|
&state->playbackDeliveryGeneration, 0, memory_order_seq_cst);
|
|
const bool admitted = beginPlaybackEventNL(state, &target);
|
|
LG_UNLOCK(state->stateLock);
|
|
|
|
waitPlaybackEvents(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(&target);
|
|
}
|
|
|
|
static void usbPlaybackData(
|
|
void * opaque, const void * data, size_t frames)
|
|
{
|
|
LGA_USBState * state = opaque;
|
|
USBAudioCallbackFrame frame;
|
|
beginCallback(state, &frame, &l_playbackFrames,
|
|
&state->playbackGate.inFlight);
|
|
|
|
const uint64_t position = atomic_fetch_add_explicit(
|
|
&state->playbackPosition, frames, memory_order_relaxed);
|
|
const uint32_t generation = atomic_load_explicit(
|
|
&state->playbackDeliveryGeneration, memory_order_seq_cst);
|
|
if (generation)
|
|
{
|
|
const LG_AudioEventOps * events = state->events;
|
|
void * target = state->eventOpaque;
|
|
if (events && events->playbackData)
|
|
{
|
|
const LG_AudioClock clock = makePlaybackClock(state, position);
|
|
events->playbackData(
|
|
target, generation, data, frames, &clock);
|
|
}
|
|
}
|
|
endCallback(&frame, &l_playbackFrames,
|
|
&state->playbackGate.inFlight, &state->playbackGate.wait);
|
|
}
|
|
|
|
static void usbRecordStart(
|
|
void * opaque, uint32_t sampleRate, uint32_t channelMask)
|
|
{
|
|
LGA_USBState * state = opaque;
|
|
USBAudioEventTarget target;
|
|
LG_AudioFormat format;
|
|
bool dispatch;
|
|
uint32_t generation;
|
|
const bool recordOwner = beginOperation(
|
|
state, &state->recordOperation, l_recordFrames);
|
|
|
|
LG_LOCK(state->stateLock);
|
|
setStreamFormat(&state->recordFormat, sampleRate, channelMask);
|
|
format = state->recordFormat;
|
|
generation = state->recordGeneration;
|
|
if (!generation)
|
|
{
|
|
generation = state->generationSerial =
|
|
nextGeneration(state->generationSerial);
|
|
state->recordGeneration = generation;
|
|
}
|
|
atomic_store_explicit(
|
|
&state->recordDeliveryGeneration, 0, memory_order_seq_cst);
|
|
const bool admitted = beginRecordEventNL(state, &target);
|
|
dispatch = admitted && target.events->recordStart;
|
|
if (dispatch)
|
|
atomic_store_explicit(&state->recordDeliveryGeneration,
|
|
generation, memory_order_seq_cst);
|
|
if (admitted && !dispatch)
|
|
endEvent(&target);
|
|
LG_UNLOCK(state->stateLock);
|
|
|
|
if (!dispatch)
|
|
{
|
|
endOperation(&state->recordOperation, recordOwner);
|
|
return;
|
|
}
|
|
|
|
target.events->recordStart(
|
|
target.opaque, generation, &format);
|
|
endEvent(&target);
|
|
endOperation(&state->recordOperation, recordOwner);
|
|
}
|
|
|
|
static void usbRecordStop(void * opaque)
|
|
{
|
|
LGA_USBState * state = opaque;
|
|
USBAudioEventTarget target;
|
|
const bool recordOwner = beginOperation(
|
|
state, &state->recordOperation, l_recordFrames);
|
|
|
|
LG_LOCK(state->stateLock);
|
|
const uint32_t generation = state->recordGeneration;
|
|
if (!generation)
|
|
{
|
|
LG_UNLOCK(state->stateLock);
|
|
endOperation(&state->recordOperation, recordOwner);
|
|
return;
|
|
}
|
|
|
|
state->recordGeneration = 0;
|
|
atomic_store_explicit(
|
|
&state->recordDeliveryGeneration, 0, memory_order_seq_cst);
|
|
const bool admitted = beginRecordEventNL(state, &target);
|
|
LG_UNLOCK(state->stateLock);
|
|
|
|
/* A reentrant stop cannot quiesce callbacks for the same reason that it
|
|
* cannot wait for ownership above. */
|
|
waitRecordEvents(state);
|
|
|
|
if (!admitted)
|
|
{
|
|
endOperation(&state->recordOperation, recordOwner);
|
|
return;
|
|
}
|
|
|
|
LG_LOCK(state->stateLock);
|
|
const bool dispatch = attachmentCurrentNL(state, &target) &&
|
|
target.events->recordStop;
|
|
LG_UNLOCK(state->stateLock);
|
|
|
|
if (dispatch)
|
|
target.events->recordStop(target.opaque, generation);
|
|
endEvent(&target);
|
|
endOperation(&state->recordOperation, recordOwner);
|
|
}
|
|
|
|
static const LG_USBAudioEventOps l_usbAudioEvents =
|
|
{
|
|
.playbackStart = usbPlaybackStart,
|
|
.playbackStop = usbPlaybackStop,
|
|
.playbackData = usbPlaybackData,
|
|
.recordStart = usbRecordStart,
|
|
.recordStop = usbRecordStop,
|
|
};
|
|
|
|
static void usbSetAvailable(void * opaque, bool available)
|
|
{
|
|
LGA_USBState * state = opaque;
|
|
const bool statusOwner = beginOperation(
|
|
state, &state->statusOperation, l_statusFrames);
|
|
|
|
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);
|
|
}
|
|
endOperation(&state->statusOperation, statusOwner);
|
|
}
|
|
|
|
static void usbSetStatusListener(void * opaque,
|
|
LG_AudioStatusFn callback, void * callbackOpaque)
|
|
{
|
|
LGA_USBState * state = opaque;
|
|
const bool statusOwner = beginOperation(
|
|
state, &state->statusOperation, l_statusFrames);
|
|
|
|
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);
|
|
endOperation(&state->statusOperation, 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 playbackTarget;
|
|
USBAudioEventTarget recordTarget;
|
|
LG_AudioFormat playbackFormat;
|
|
LG_AudioFormat recordFormat;
|
|
LG_AudioClock playbackClock;
|
|
uint32_t attachmentGeneration;
|
|
uint32_t playbackGeneration;
|
|
uint32_t recordGeneration;
|
|
bool playbackDispatch;
|
|
bool recordDispatch;
|
|
bool playbackAdmitted;
|
|
bool recordAdmitted;
|
|
|
|
if (!lockStateAfterDetach(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;
|
|
attachmentGeneration = state->attachmentGeneration;
|
|
playbackGeneration = state->playbackGeneration;
|
|
recordGeneration = state->recordGeneration;
|
|
playbackDispatch = playbackGeneration && events->playbackStart;
|
|
recordDispatch = recordGeneration && events->recordStart;
|
|
playbackAdmitted = playbackDispatch &&
|
|
beginPlaybackEventNL(state, &playbackTarget);
|
|
playbackDispatch = playbackDispatch && playbackAdmitted;
|
|
recordAdmitted = false;
|
|
if (playbackDispatch)
|
|
{
|
|
playbackFormat = state->playbackFormat;
|
|
playbackClock = makePlaybackClock(state, atomic_load_explicit(
|
|
&state->playbackPosition, memory_order_relaxed));
|
|
}
|
|
lgUsbRedir_setPlugged(state->redir, true);
|
|
LG_UNLOCK(state->stateLock);
|
|
|
|
if (playbackDispatch)
|
|
{
|
|
playbackTarget.events->playbackStart(
|
|
playbackTarget.opaque, playbackGeneration,
|
|
&playbackFormat, &playbackClock);
|
|
|
|
LG_LOCK(state->stateLock);
|
|
if (playbackEventCurrentNL(
|
|
state, &playbackTarget, playbackGeneration))
|
|
atomic_store_explicit(&state->playbackDeliveryGeneration,
|
|
playbackGeneration, memory_order_seq_cst);
|
|
LG_UNLOCK(state->stateLock);
|
|
}
|
|
if (playbackAdmitted)
|
|
endEvent(&playbackTarget);
|
|
|
|
if (recordDispatch)
|
|
{
|
|
const bool recordOwner = beginOperation(
|
|
state, &state->recordOperation, l_recordFrames);
|
|
|
|
LG_LOCK(state->stateLock);
|
|
recordDispatch = state->attached &&
|
|
state->attachmentGeneration == attachmentGeneration &&
|
|
state->events == events &&
|
|
state->eventOpaque == eventOpaque &&
|
|
state->recordGeneration == recordGeneration;
|
|
recordAdmitted = recordDispatch &&
|
|
beginRecordEventNL(state, &recordTarget);
|
|
recordDispatch = recordAdmitted &&
|
|
recordEventCurrentNL(state, &recordTarget, recordGeneration);
|
|
if (recordDispatch)
|
|
{
|
|
recordFormat = state->recordFormat;
|
|
atomic_store_explicit(&state->recordDeliveryGeneration,
|
|
recordGeneration, memory_order_seq_cst);
|
|
}
|
|
LG_UNLOCK(state->stateLock);
|
|
|
|
if (recordDispatch)
|
|
recordTarget.events->recordStart(
|
|
recordTarget.opaque, recordGeneration, &recordFormat);
|
|
|
|
if (recordAdmitted)
|
|
endEvent(&recordTarget);
|
|
endOperation(&state->recordOperation, recordOwner);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void usbDetach(void * opaque)
|
|
{
|
|
LGA_USBState * state = opaque;
|
|
|
|
if (!lockStateAfterDetach(state))
|
|
return;
|
|
|
|
state->detaching = true;
|
|
lgResetEvent(state->detachEvent);
|
|
state->attached = false;
|
|
state->attachmentGeneration =
|
|
nextGeneration(state->attachmentGeneration);
|
|
const uint32_t attachmentGeneration =
|
|
state->attachmentGeneration;
|
|
atomic_store_explicit(
|
|
&state->playbackDeliveryGeneration, 0, memory_order_seq_cst);
|
|
atomic_store_explicit(
|
|
&state->recordDeliveryGeneration, 0, memory_order_seq_cst);
|
|
lgUsbRedir_setPlugged(state->redir, false);
|
|
LG_UNLOCK(state->stateLock);
|
|
|
|
waitPlaybackEvents(state);
|
|
waitRecordEvents(state);
|
|
|
|
LG_LOCK(state->stateLock);
|
|
if (!state->attached &&
|
|
state->attachmentGeneration == attachmentGeneration)
|
|
{
|
|
state->events = NULL;
|
|
state->eventOpaque = NULL;
|
|
state->detaching = false;
|
|
signalWaitEvent(state->detachEvent);
|
|
}
|
|
LG_UNLOCK(state->stateLock);
|
|
}
|
|
|
|
static bool usbClockFeedback(void * opaque, uint32_t generation,
|
|
const LG_AudioClock * playbackClock, double targetRate)
|
|
{
|
|
LGA_USBState * state = opaque;
|
|
|
|
LG_LOCK(state->stateLock);
|
|
if (!state->attached ||
|
|
state->playbackGeneration != generation ||
|
|
!lgUsbAudio_feedbackActive(state->device))
|
|
{
|
|
LG_UNLOCK(state->stateLock);
|
|
return false;
|
|
}
|
|
|
|
const double nominalRate = state->playbackFormat.sampleRate;
|
|
const double rate = playbackClock &&
|
|
targetRate >= nominalRate * 0.995 &&
|
|
targetRate <= nominalRate * 1.005 ?
|
|
targetRate : nominalRate;
|
|
lgUsbAudio_setFeedbackRate(state->device, rate);
|
|
LG_UNLOCK(state->stateLock);
|
|
return true;
|
|
}
|
|
|
|
static bool usbRecordData(void * opaque, uint32_t generation,
|
|
const void * data, size_t frames,
|
|
const LG_AudioClock * sourceClock)
|
|
{
|
|
LGA_USBState * state = opaque;
|
|
USBAudioCallbackFrame frame;
|
|
beginCallback(state, &frame, &l_recordFrames,
|
|
&state->recordGate.inFlight);
|
|
|
|
const bool valid = generation && generation == atomic_load_explicit(
|
|
&state->recordDeliveryGeneration, memory_order_seq_cst);
|
|
const bool result = valid &&
|
|
lgUsbAudio_recordData(
|
|
state->device, data, frames, sourceClock);
|
|
|
|
endCallback(&frame, &l_recordFrames,
|
|
&state->recordGate.inFlight, &state->recordGate.wait);
|
|
return result;
|
|
}
|
|
|
|
const LG_AudioOps LGA_USB =
|
|
{
|
|
.name = "USB Audio",
|
|
.setStatusListener = usbSetStatusListener,
|
|
.attach = usbAttach,
|
|
.detach = usbDetach,
|
|
.recordData = usbRecordData,
|
|
.clockFeedback = usbClockFeedback,
|
|
};
|
|
|
|
LGA_USBState * lgaUsb_create(bool debug)
|
|
{
|
|
LGA_USBState * state = aligned_alloc(
|
|
alignof(LGA_USBState), sizeof(*state));
|
|
if (!state)
|
|
return NULL;
|
|
memset(state, 0, sizeof(*state));
|
|
|
|
LG_LOCK_INIT(state->statusLock);
|
|
LG_LOCK_INIT(state->statusWait.lock);
|
|
LG_LOCK_INIT(state->statusOperation.lock);
|
|
LG_LOCK_INIT(state->stateLock);
|
|
LG_LOCK_INIT(state->playbackGate.wait.lock);
|
|
LG_LOCK_INIT(state->recordGate.wait.lock);
|
|
LG_LOCK_INIT(state->recordOperation.lock);
|
|
atomic_init(&state->available, false);
|
|
atomic_init(&state->playbackPosition, 0);
|
|
atomic_init(&state->playbackDeliveryGeneration, 0);
|
|
atomic_init(&state->recordDeliveryGeneration, 0);
|
|
atomic_init(&state->playbackGate.inFlight, 0);
|
|
atomic_init(&state->recordGate.inFlight, 0);
|
|
atomic_init(&state->statusInFlight, 0);
|
|
atomic_init(&state->playbackGate.wait.count, 0);
|
|
atomic_init(&state->recordGate.wait.count, 0);
|
|
atomic_init(&state->statusWait.count, 0);
|
|
state->playbackFormat = l_formatTemplate;
|
|
state->recordFormat = l_formatTemplate;
|
|
|
|
state->detachEvent = lgCreateEvent(false, 0);
|
|
if (!state->detachEvent || !lgSignalEvent(state->detachEvent))
|
|
{
|
|
if (state->detachEvent)
|
|
lgFreeEvent(state->detachEvent);
|
|
free(state);
|
|
return NULL;
|
|
}
|
|
|
|
state->device = lgUsbAudio_create(&l_usbAudioEvents, state, debug);
|
|
if (!state->device)
|
|
{
|
|
lgFreeEvent(state->detachEvent);
|
|
free(state);
|
|
return NULL;
|
|
}
|
|
|
|
state->redir = lgUsbRedir_create(
|
|
lgUsbAudio_deviceOps(), state->device, usbSetAvailable, state);
|
|
if (!state->redir)
|
|
{
|
|
lgUsbAudio_destroy(state->device);
|
|
lgFreeEvent(state->detachEvent);
|
|
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);
|
|
lgFreeEvent(state->detachEvent);
|
|
LG_LOCK_FREE(state->recordOperation.lock);
|
|
LG_LOCK_FREE(state->recordGate.wait.lock);
|
|
LG_LOCK_FREE(state->playbackGate.wait.lock);
|
|
LG_LOCK_FREE(state->stateLock);
|
|
LG_LOCK_FREE(state->statusOperation.lock);
|
|
LG_LOCK_FREE(state->statusWait.lock);
|
|
LG_LOCK_FREE(state->statusLock);
|
|
free(state);
|
|
}
|
|
|
|
LG_USBRedir * lgaUsb_redir(LGA_USBState * state)
|
|
{
|
|
return state ? state->redir : NULL;
|
|
}
|
|
|
|
uint64_t lgaUsb_processDelayNs(const LGA_USBState * state)
|
|
{
|
|
return state ? lgUsbAudio_processDelayNs(state->device) : UINT64_MAX;
|
|
}
|