[client] usb audio: add microphone recording
Some checks failed
build / client (Debug, map[cc:clang cxx:clang++], libdecor) (push) Has been cancelled
build / client (Debug, map[cc:clang cxx:clang++], xdg-shell) (push) Has been cancelled
build / client (Debug, map[cc:gcc cxx:g++], libdecor) (push) Has been cancelled
build / client (Debug, map[cc:gcc cxx:g++], xdg-shell) (push) Has been cancelled
build / client (Release, map[cc:clang cxx:clang++], libdecor) (push) Has been cancelled
build / client (Release, map[cc:clang cxx:clang++], xdg-shell) (push) Has been cancelled
build / client (Release, map[cc:gcc cxx:g++], libdecor) (push) Has been cancelled
build / client (Release, map[cc:gcc cxx:g++], xdg-shell) (push) Has been cancelled
build / module (push) Has been cancelled
build / host-linux (push) Has been cancelled
build / host-windows-cross (push) Has been cancelled
build / host-windows-native (push) Has been cancelled
build / idd (push) Has been cancelled
build / obs (clang) (push) Has been cancelled
build / obs (gcc) (push) Has been cancelled
build / docs (push) Has been cancelled

Add a composite UAC2 microphone function with its own clock so
recording and playback can use independent sample rates.

Packetize asynchronous capture through usbredir, follow the host
capture clock, and preserve the newest frames across stalls.

Reconfigure active recording without another permission prompt and
fix lost PipeWire capture wakeups.
This commit is contained in:
Geoffrey McRae
2026-08-10 06:32:05 +10:00
parent d660574a93
commit 4434985aa3
10 changed files with 862 additions and 200 deletions

View File

@@ -915,7 +915,10 @@ static void pipewire_recordQueueFrames(const void * data, int frames)
atomic_fetch_add_explicit( atomic_fetch_add_explicit(
&pw.record.droppedFrames, frames - append, memory_order_relaxed); &pw.record.droppedFrames, frames - append, memory_order_relaxed);
if (occupancy == 0 && append > 0 && sem_post(&pw.record.sendWake) < 0) /* Posting only when the producer observes an empty queue can lose a wake
* while the consumer drains the previous batch. Always notify for appended
* data; the sender coalesces all queued frames when it wakes. */
if (append > 0 && sem_post(&pw.record.sendWake) < 0)
atomic_fetch_add_explicit( atomic_fetch_add_explicit(
&pw.record.signalErrors, 1, memory_order_relaxed); &pw.record.signalErrors, 1, memory_order_relaxed);
} }
@@ -964,6 +967,10 @@ static void pipewire_recordStart(const LG_AudioFormat * format,
const int channels = format->channelCount; const int channels = format->channelCount;
const int sampleRate = format->sampleRate; const int sampleRate = format->sampleRate;
const int sampleSize = pipewire_sampleSize(format->sampleFormat); const int sampleSize = pipewire_sampleSize(format->sampleFormat);
const int periodFrames = max(sampleRate / 1000, 1);
char requestedNodeLatency[32];
snprintf(requestedNodeLatency, sizeof(requestedNodeLatency), "%d/%d",
periodFrames, sampleRate);
const struct spa_pod * params[1]; const struct spa_pod * params[1];
uint8_t buffer[1024]; uint8_t buffer[1024];
struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
@@ -1000,6 +1007,7 @@ static void pipewire_recordStart(const LG_AudioFormat * format,
PW_KEY_MEDIA_TYPE , "Audio", PW_KEY_MEDIA_TYPE , "Audio",
PW_KEY_MEDIA_CATEGORY, "Capture", PW_KEY_MEDIA_CATEGORY, "Capture",
PW_KEY_MEDIA_ROLE , "Music", PW_KEY_MEDIA_ROLE , "Music",
PW_KEY_NODE_LATENCY , requestedNodeLatency,
NULL NULL
); );
if (!props) if (!props)

View File

@@ -120,6 +120,7 @@ typedef struct LG_AudioEventOps
const void * data, size_t frames, const void * data, size_t frames,
const LG_AudioClock * sourceClock); const LG_AudioClock * sourceClock);
/* May repeat with the same generation to update an active stream format. */
void (*recordStart)(void * opaque, uint32_t generation, void (*recordStart)(void * opaque, uint32_t generation,
const LG_AudioFormat * format); const LG_AudioFormat * format);
void (*recordStop)(void * opaque, uint32_t generation); void (*recordStop)(void * opaque, uint32_t generation);

View File

@@ -2156,7 +2156,7 @@ static void realRecordStartLocked(const LG_AudioFormat * format)
audio.audioDev->record.start(format, recordPushFrames); audio.audioDev->record.start(format, recordPushFrames);
// if a volume level was stored, set it before we return // if a volume level was stored, set it before we return
if (audio.record.volumeChannels) if (audio.record.volumeChannels && audio.audioDev->record.volume)
audio.audioDev->record.volume( audio.audioDev->record.volume(
audio.record.volumeChannels, audio.record.volumeChannels,
audio.record.volume); audio.record.volume);
@@ -2201,7 +2201,8 @@ static void recordConfirm(bool yes, void * opaque)
static void recordStart(const LG_AudioFormat * format) static void recordStart(const LG_AudioFormat * format)
{ {
LG_LOCK(audio.record.lock); LG_LOCK(audio.record.lock);
if (!audio.audioDev || audio.record.shuttingDown || if (!audio.audioDev || !audio.audioDev->record.start ||
audio.record.shuttingDown ||
!audioFormatValid(format)) !audioFormatValid(format))
{ {
if (format && !audioFormatValid(format)) if (format && !audioFormatValid(format))
@@ -2271,6 +2272,29 @@ static void recordStart(const LG_AudioFormat * format)
app_msgBoxClose(oldConfirm); app_msgBoxClose(oldConfirm);
} }
static void recordReconfigure(const LG_AudioFormat * format)
{
LG_LOCK(audio.record.lock);
if (!audio.audioDev || !audio.audioDev->record.start ||
audio.record.shuttingDown || !audioFormatValid(format))
{
LG_UNLOCK(audio.record.lock);
return;
}
audio.record.lastFormat = *format;
if (audio.record.started &&
!audioFormatEqual(format, &audio.record.format))
{
realRecordStopLocked();
realRecordStartLocked(format);
}
else if (audio.record.confirmPending)
audio.record.confirmFormat = *format;
LG_UNLOCK(audio.record.lock);
}
static void realRecordStopLocked(void) static void realRecordStopLocked(void)
{ {
audio.audioDev->record.stop(); audio.audioDev->record.stop();
@@ -2522,8 +2546,11 @@ static void eventRecordStart(void * opaque, uint32_t generation,
binding->ops->recordData; binding->ops->recordData;
if (active) if (active)
{ {
atomic_store_explicit(&audio.record.streamGeneration, const uint32_t previous = atomic_exchange_explicit(
generation, memory_order_release); &audio.record.streamGeneration, generation, memory_order_acq_rel);
if (previous == generation)
recordReconfigure(format);
else
recordStart(format); recordStart(format);
} }
LG_UNLOCK_SHARED(audio.activeLock); LG_UNLOCK_SHARED(audio.activeLock);

View File

@@ -67,12 +67,15 @@ struct LGA_USBState
const LG_AudioEventOps * events; const LG_AudioEventOps * events;
void * eventOpaque; void * eventOpaque;
LG_AudioFormat streamFormat; LG_AudioFormat playbackFormat;
uint32_t streamGeneration; uint32_t playbackGeneration;
LG_AudioFormat recordFormat;
uint32_t recordGeneration;
uint32_t generationSerial; uint32_t generationSerial;
int64_t clockOrigin; int64_t playbackClockOrigin;
atomic_uint_fast64_t position; atomic_uint_fast64_t playbackPosition;
atomic_uint deliveryGeneration; atomic_uint playbackDeliveryGeneration;
atomic_uint recordDeliveryGeneration;
atomic_uint inFlight; atomic_uint inFlight;
}; };
@@ -153,12 +156,12 @@ static void endCallback(USBAudioCallbackFrame * frame,
atomic_fetch_sub_explicit(inFlight, 1, memory_order_seq_cst); atomic_fetch_sub_explicit(inFlight, 1, memory_order_seq_cst);
} }
static LG_AudioClock makeClock( static LG_AudioClock makePlaybackClock(
const LGA_USBState * state, uint64_t position) const LGA_USBState * state, uint64_t position)
{ {
/* 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 uint32_t sampleRate = state->playbackFormat.sampleRate;
const int64_t elapsed = const int64_t elapsed =
position / sampleRate * USB_AUDIO_NS_PER_SECOND + position / sampleRate * USB_AUDIO_NS_PER_SECOND +
position % sampleRate * USB_AUDIO_NS_PER_SECOND / sampleRate; position % sampleRate * USB_AUDIO_NS_PER_SECOND / sampleRate;
@@ -166,7 +169,7 @@ static LG_AudioClock makeClock(
return (LG_AudioClock) return (LG_AudioClock)
{ {
.position = position, .position = position,
.time = state->clockOrigin + elapsed, .time = state->playbackClockOrigin + elapsed,
.rate = 0.0, .rate = 0.0,
.stable = false, .stable = false,
}; };
@@ -197,11 +200,18 @@ static bool attachmentCurrentNL(const LGA_USBState * state,
state->eventOpaque == target->opaque; state->eventOpaque == target->opaque;
} }
static bool eventCurrentNL(const LGA_USBState * state, static bool playbackEventCurrentNL(const LGA_USBState * state,
const USBAudioEventTarget * target, uint32_t streamGeneration) const USBAudioEventTarget * target, uint32_t streamGeneration)
{ {
return attachmentCurrentNL(state, target) && return attachmentCurrentNL(state, target) &&
state->streamGeneration == streamGeneration; 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(LGA_USBState * state, USBAudioEventTarget * target) static void endEvent(LGA_USBState * state, USBAudioEventTarget * target)
@@ -258,7 +268,7 @@ static void waitStatusCallbacks(const LGA_USBState * state)
; ;
} }
static void usbAudioStart( static void usbPlaybackStart(
void * opaque, uint32_t sampleRate, uint32_t channelMask) void * opaque, uint32_t sampleRate, uint32_t channelMask)
{ {
LGA_USBState * state = opaque; LGA_USBState * state = opaque;
@@ -268,21 +278,22 @@ static void usbAudioStart(
LG_AudioClock clock; LG_AudioClock clock;
LG_LOCK(state->stateLock); LG_LOCK(state->stateLock);
if (state->streamGeneration) if (state->playbackGeneration)
{ {
LG_UNLOCK(state->stateLock); LG_UNLOCK(state->stateLock);
return; return;
} }
setStreamFormat(&state->streamFormat, sampleRate, channelMask); setStreamFormat(&state->playbackFormat, sampleRate, channelMask);
generation = state->generationSerial = generation = state->generationSerial =
nextGeneration(state->generationSerial); nextGeneration(state->generationSerial);
state->streamGeneration = generation; state->playbackGeneration = generation;
state->clockOrigin = (int64_t)nanotime(); state->playbackClockOrigin = (int64_t)nanotime();
atomic_store_explicit(&state->position, 0, memory_order_relaxed);
atomic_store_explicit( atomic_store_explicit(
&state->deliveryGeneration, 0, memory_order_seq_cst); &state->playbackPosition, 0, memory_order_relaxed);
clock = makeClock(state, 0); atomic_store_explicit(
&state->playbackDeliveryGeneration, 0, memory_order_seq_cst);
clock = makePlaybackClock(state, 0);
const bool admitted = beginEventNL(state, &target); const bool admitted = beginEventNL(state, &target);
dispatch = admitted && target.events->playbackStart; dispatch = admitted && target.events->playbackStart;
if (admitted && !dispatch) if (admitted && !dispatch)
@@ -293,32 +304,32 @@ static void usbAudioStart(
return; return;
target.events->playbackStart( target.events->playbackStart(
target.opaque, generation, &state->streamFormat, &clock); target.opaque, generation, &state->playbackFormat, &clock);
LG_LOCK(state->stateLock); LG_LOCK(state->stateLock);
if (eventCurrentNL(state, &target, generation)) if (playbackEventCurrentNL(state, &target, generation))
atomic_store_explicit(&state->deliveryGeneration, atomic_store_explicit(&state->playbackDeliveryGeneration,
generation, memory_order_seq_cst); generation, memory_order_seq_cst);
LG_UNLOCK(state->stateLock); LG_UNLOCK(state->stateLock);
endEvent(state, &target); endEvent(state, &target);
} }
static void usbAudioStop(void * opaque) static void usbPlaybackStop(void * opaque)
{ {
LGA_USBState * state = opaque; LGA_USBState * state = opaque;
USBAudioEventTarget target; USBAudioEventTarget target;
LG_LOCK(state->stateLock); LG_LOCK(state->stateLock);
const uint32_t generation = state->streamGeneration; const uint32_t generation = state->playbackGeneration;
if (!generation) if (!generation)
{ {
LG_UNLOCK(state->stateLock); LG_UNLOCK(state->stateLock);
return; return;
} }
state->streamGeneration = 0; state->playbackGeneration = 0;
atomic_store_explicit( atomic_store_explicit(
&state->deliveryGeneration, 0, memory_order_seq_cst); &state->playbackDeliveryGeneration, 0, memory_order_seq_cst);
const bool admitted = beginEventNL(state, &target); const bool admitted = beginEventNL(state, &target);
LG_UNLOCK(state->stateLock); LG_UNLOCK(state->stateLock);
@@ -337,23 +348,24 @@ static void usbAudioStop(void * opaque)
endEvent(state, &target); endEvent(state, &target);
} }
static void usbAudioData(void * opaque, const void * data, size_t frames) static void usbPlaybackData(
void * opaque, const void * data, size_t frames)
{ {
LGA_USBState * state = opaque; LGA_USBState * state = opaque;
USBAudioCallbackFrame frame; USBAudioCallbackFrame frame;
beginCallback(state, &frame, &l_eventFrames, &state->inFlight); beginCallback(state, &frame, &l_eventFrames, &state->inFlight);
const uint64_t position = atomic_fetch_add_explicit( const uint64_t position = atomic_fetch_add_explicit(
&state->position, frames, memory_order_relaxed); &state->playbackPosition, frames, memory_order_relaxed);
const uint32_t generation = atomic_load_explicit( const uint32_t generation = atomic_load_explicit(
&state->deliveryGeneration, memory_order_seq_cst); &state->playbackDeliveryGeneration, memory_order_seq_cst);
if (generation) if (generation)
{ {
const LG_AudioEventOps * events = state->events; const LG_AudioEventOps * events = state->events;
void * target = state->eventOpaque; void * target = state->eventOpaque;
if (events && events->playbackData) if (events && events->playbackData)
{ {
const LG_AudioClock clock = makeClock(state, position); const LG_AudioClock clock = makePlaybackClock(state, position);
events->playbackData( events->playbackData(
target, generation, data, frames, &clock); target, generation, data, frames, &clock);
} }
@@ -361,11 +373,86 @@ static void usbAudioData(void * opaque, const void * data, size_t frames)
endCallback(&frame, &l_eventFrames, &state->inFlight); endCallback(&frame, &l_eventFrames, &state->inFlight);
} }
static void usbRecordStart(
void * opaque, uint32_t sampleRate, uint32_t channelMask)
{
LGA_USBState * state = opaque;
USBAudioEventTarget target;
bool dispatch;
uint32_t generation;
LG_LOCK(state->stateLock);
setStreamFormat(&state->recordFormat, sampleRate, channelMask);
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 = beginEventNL(state, &target);
dispatch = admitted && target.events->recordStart;
if (admitted && !dispatch)
endEvent(state, &target);
LG_UNLOCK(state->stateLock);
if (!dispatch)
return;
target.events->recordStart(
target.opaque, generation, &state->recordFormat);
LG_LOCK(state->stateLock);
if (recordEventCurrentNL(state, &target, generation))
atomic_store_explicit(&state->recordDeliveryGeneration,
generation, memory_order_seq_cst);
LG_UNLOCK(state->stateLock);
endEvent(state, &target);
}
static void usbRecordStop(void * opaque)
{
LGA_USBState * state = opaque;
USBAudioEventTarget target;
LG_LOCK(state->stateLock);
const uint32_t generation = state->recordGeneration;
if (!generation)
{
LG_UNLOCK(state->stateLock);
return;
}
state->recordGeneration = 0;
atomic_store_explicit(
&state->recordDeliveryGeneration, 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->recordStop;
LG_UNLOCK(state->stateLock);
if (dispatch)
target.events->recordStop(target.opaque, generation);
endEvent(state, &target);
}
static const LG_USBAudioEventOps l_usbAudioEvents = static const LG_USBAudioEventOps l_usbAudioEvents =
{ {
.start = usbAudioStart, .playbackStart = usbPlaybackStart,
.stop = usbAudioStop, .playbackStop = usbPlaybackStop,
.data = usbAudioData, .playbackData = usbPlaybackData,
.recordStart = usbRecordStart,
.recordStop = usbRecordStop,
}; };
static void usbSetAvailable(void * opaque, bool available) static void usbSetAvailable(void * opaque, bool available)
@@ -443,9 +530,14 @@ static bool usbAttach(void * opaque, const LG_AudioEventOps * events,
return false; return false;
USBAudioEventTarget target; USBAudioEventTarget target;
LG_AudioClock clock; LG_AudioFormat playbackFormat;
uint32_t generation; LG_AudioFormat recordFormat;
bool dispatch; LG_AudioClock playbackClock;
uint32_t playbackGeneration;
uint32_t recordGeneration;
bool playbackDispatch;
bool recordDispatch;
bool admitted;
for (;;) for (;;)
{ {
@@ -469,28 +561,61 @@ static bool usbAttach(void * opaque, const LG_AudioEventOps * events,
nextGeneration(state->attachmentGeneration); nextGeneration(state->attachmentGeneration);
state->events = events; state->events = events;
state->eventOpaque = eventOpaque; state->eventOpaque = eventOpaque;
generation = state->streamGeneration; playbackGeneration = state->playbackGeneration;
dispatch = generation && events->playbackStart && recordGeneration = state->recordGeneration;
playbackDispatch = playbackGeneration && events->playbackStart;
recordDispatch = recordGeneration && events->recordStart;
admitted = (playbackDispatch || recordDispatch) &&
beginEventNL(state, &target); beginEventNL(state, &target);
if (dispatch) playbackDispatch = playbackDispatch && admitted;
clock = makeClock(state, atomic_load_explicit( recordDispatch = recordDispatch && admitted;
&state->position, memory_order_relaxed)); if (playbackDispatch)
{
playbackFormat = state->playbackFormat;
playbackClock = makePlaybackClock(state, atomic_load_explicit(
&state->playbackPosition, memory_order_relaxed));
}
if (recordDispatch)
recordFormat = state->recordFormat;
lgUsbRedir_setPlugged(state->redir, true); lgUsbRedir_setPlugged(state->redir, true);
LG_UNLOCK(state->stateLock); LG_UNLOCK(state->stateLock);
if (dispatch) if (playbackDispatch)
{ {
target.events->playbackStart( target.events->playbackStart(
target.opaque, generation, &state->streamFormat, &clock); target.opaque, playbackGeneration,
&playbackFormat, &playbackClock);
LG_LOCK(state->stateLock); LG_LOCK(state->stateLock);
if (eventCurrentNL(state, &target, generation)) if (playbackEventCurrentNL(
atomic_store_explicit(&state->deliveryGeneration, state, &target, playbackGeneration))
generation, memory_order_seq_cst); atomic_store_explicit(&state->playbackDeliveryGeneration,
playbackGeneration, memory_order_seq_cst);
LG_UNLOCK(state->stateLock); LG_UNLOCK(state->stateLock);
endEvent(state, &target);
} }
if (recordDispatch)
{
LG_LOCK(state->stateLock);
recordDispatch = recordEventCurrentNL(
state, &target, recordGeneration);
LG_UNLOCK(state->stateLock);
if (recordDispatch)
target.events->recordStart(
target.opaque, recordGeneration, &recordFormat);
LG_LOCK(state->stateLock);
if (recordDispatch &&
recordEventCurrentNL(state, &target, recordGeneration))
atomic_store_explicit(&state->recordDeliveryGeneration,
recordGeneration, memory_order_seq_cst);
LG_UNLOCK(state->stateLock);
}
if (admitted)
endEvent(state, &target);
return true; return true;
} }
@@ -515,7 +640,9 @@ static void usbDetach(void * opaque)
const uint32_t attachmentGeneration = const uint32_t attachmentGeneration =
state->attachmentGeneration; state->attachmentGeneration;
atomic_store_explicit( atomic_store_explicit(
&state->deliveryGeneration, 0, memory_order_seq_cst); &state->playbackDeliveryGeneration, 0, memory_order_seq_cst);
atomic_store_explicit(
&state->recordDeliveryGeneration, 0, memory_order_seq_cst);
lgUsbRedir_setPlugged(state->redir, false); lgUsbRedir_setPlugged(state->redir, false);
LG_UNLOCK(state->stateLock); LG_UNLOCK(state->stateLock);
@@ -538,13 +665,14 @@ static bool usbClockFeedback(void * opaque, uint32_t generation,
LGA_USBState * state = opaque; LGA_USBState * state = opaque;
LG_LOCK(state->stateLock); LG_LOCK(state->stateLock);
if (!state->attached || state->streamGeneration != generation) if (!state->attached ||
state->playbackGeneration != generation)
{ {
LG_UNLOCK(state->stateLock); LG_UNLOCK(state->stateLock);
return false; return false;
} }
const double nominalRate = state->streamFormat.sampleRate; const double nominalRate = state->playbackFormat.sampleRate;
const double rate = playbackClock && const double rate = playbackClock &&
targetRate >= nominalRate * 0.995 && targetRate >= nominalRate * 0.995 &&
targetRate <= nominalRate * 1.005 ? targetRate <= nominalRate * 1.005 ?
@@ -554,13 +682,31 @@ static bool usbClockFeedback(void * opaque, uint32_t generation,
return true; return true;
} }
static bool usbRecordData(void * opaque, uint32_t generation,
const void * data, size_t frames,
const LG_AudioClock * sourceClock)
{
(void)sourceClock;
LGA_USBState * state = opaque;
USBAudioCallbackFrame frame;
beginCallback(state, &frame, &l_eventFrames, &state->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);
endCallback(&frame, &l_eventFrames, &state->inFlight);
return result;
}
const LG_AudioOps LGA_USB = const LG_AudioOps LGA_USB =
{ {
.name = "USB Audio", .name = "USB Audio",
.setStatusListener = usbSetStatusListener, .setStatusListener = usbSetStatusListener,
.attach = usbAttach, .attach = usbAttach,
.detach = usbDetach, .detach = usbDetach,
.recordData = NULL, .recordData = usbRecordData,
.clockFeedback = usbClockFeedback, .clockFeedback = usbClockFeedback,
}; };
@@ -573,13 +719,15 @@ LGA_USBState * lgaUsb_create(void)
LG_LOCK_INIT(state->statusLock); LG_LOCK_INIT(state->statusLock);
LG_LOCK_INIT(state->stateLock); LG_LOCK_INIT(state->stateLock);
atomic_init(&state->available, false); atomic_init(&state->available, false);
atomic_init(&state->position, 0); atomic_init(&state->playbackPosition, 0);
atomic_init(&state->deliveryGeneration, 0); atomic_init(&state->playbackDeliveryGeneration, 0);
atomic_init(&state->recordDeliveryGeneration, 0);
atomic_init(&state->inFlight, 0); atomic_init(&state->inFlight, 0);
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->playbackFormat = l_formatTemplate;
state->recordFormat = l_formatTemplate;
state->device = lgUsbAudio_create(&l_usbAudioEvents, state); state->device = lgUsbAudio_create(&l_usbAudioEvents, state);
if (!state->device) if (!state->device)
@@ -618,3 +766,8 @@ LG_USBRedir * lgaUsb_redir(LGA_USBState * state)
{ {
return state ? state->redir : NULL; return state ? state->redir : NULL;
} }
bool lgaUsb_recording(const LGA_USBState * state)
{
return state && lgUsbAudio_recording(state->device);
}

View File

@@ -31,6 +31,7 @@ LGA_USBState * lgaUsb_create(void);
void lgaUsb_destroy(LGA_USBState * state); void lgaUsb_destroy(LGA_USBState * state);
LG_USBRedir * lgaUsb_redir(LGA_USBState * state); LG_USBRedir * lgaUsb_redir(LGA_USBState * state);
bool lgaUsb_recording(const LGA_USBState * state);
extern const LG_AudioOps LGA_USB; extern const LG_AudioOps LGA_USB;

View File

@@ -1838,6 +1838,8 @@ int spiceThread(void * arg)
#if ENABLE_USB_AUDIO #if ENABLE_USB_AUDIO
if (usbRedir && !lgUsbRedir_process(usbRedir)) if (usbRedir && !lgUsbRedir_process(usbRedir))
DEBUG_WARN("Failed to process USB audio redirection"); DEBUG_WARN("Failed to process USB audio redirection");
if (usbAudio)
processTimeout = lgaUsb_recording(usbAudio) ? 1 : 10;
#endif #endif
if ((status = purespice_process(processTimeout)) != PS_STATUS_RUN) if ((status = purespice_process(processTimeout)) != PS_STATUS_RUN)

File diff suppressed because it is too large Load Diff

View File

@@ -32,13 +32,19 @@ typedef struct LG_USBAudio LG_USBAudio;
typedef struct LG_USBAudioEventOps typedef struct LG_USBAudioEventOps
{ {
void (*start)(void * opaque, uint32_t sampleRate, uint32_t channelMask); void (*playbackStart)(
void (*stop)(void * opaque); void * opaque, uint32_t sampleRate, uint32_t channelMask);
void (*playbackStop)(void * opaque);
/* Data is borrowed interleaved packed signed 24-bit PCM, 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 * Channels are ordered by ascending set bits in the selected UAC channel
* mask. */ * mask. */
void (*data)(void * opaque, const void * data, size_t frames); void (*playbackData)(void * opaque, const void * data, size_t frames);
/* Repeated while active when the recording clock changes rate. */
void (*recordStart)(
void * opaque, uint32_t sampleRate, uint32_t channelMask);
void (*recordStop)(void * opaque);
} }
LG_USBAudioEventOps; LG_USBAudioEventOps;
@@ -51,6 +57,14 @@ void lgUsbAudio_destroy(LG_USBAudio * audio);
* feedback thread. */ * feedback thread. */
void lgUsbAudio_setFeedbackRate(LG_USBAudio * audio, double sampleRate); void lgUsbAudio_setFeedbackRate(LG_USBAudio * audio, double sampleRate);
/* Queue interleaved packed signed 24-bit microphone frames. This may be
* called from the audio recording thread. */
bool lgUsbAudio_recordData(
LG_USBAudio * audio, const void * data, size_t frames);
/* This must be queried on the PureSpice processing thread. */
bool lgUsbAudio_recording(const LG_USBAudio * audio);
const LG_USBRedirDeviceOps * lgUsbAudio_deviceOps(void); const LG_USBRedirDeviceOps * lgUsbAudio_deviceOps(void);
#endif #endif

View File

@@ -296,6 +296,9 @@ bool lgUsbRedir_process(LG_USBRedir * usbredir)
} }
} }
if (usbredir->plugged && usbredir->deviceOps->process)
usbredir->deviceOps->process(usbredir->deviceOpaque);
return flushUSBRedir(usbredir); return flushUSBRedir(usbredir);
} }

View File

@@ -42,6 +42,10 @@ typedef struct LG_USBRedirDeviceOps
/* Queue the device descriptors and connection announcement. */ /* Queue the device descriptors and connection announcement. */
void (*plug)(void * opaque, struct usbredirparser * parser); void (*plug)(void * opaque, struct usbredirparser * parser);
/* Queue time-sensitive device data. This runs on the PureSpice processing
* thread immediately before parser output is flushed. */
void (*process)(void * opaque);
/* Stop all device activity. The bridge sends the disconnect packet. */ /* Stop all device activity. The bridge sends the disconnect packet. */
void (*unplug)(void * opaque); void (*unplug)(void * opaque);
} }