mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-10 17:21:32 +00:00
[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
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:
@@ -915,7 +915,10 @@ static void pipewire_recordQueueFrames(const void * data, int frames)
|
||||
atomic_fetch_add_explicit(
|
||||
&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(
|
||||
&pw.record.signalErrors, 1, memory_order_relaxed);
|
||||
}
|
||||
@@ -961,9 +964,13 @@ static void pipewire_onRecordProcess(void * userdata)
|
||||
static void pipewire_recordStart(const LG_AudioFormat * format,
|
||||
LG_AudioPushFn pushFn)
|
||||
{
|
||||
const int channels = format->channelCount;
|
||||
const int sampleRate = format->sampleRate;
|
||||
const int sampleSize = pipewire_sampleSize(format->sampleFormat);
|
||||
const int channels = format->channelCount;
|
||||
const int sampleRate = format->sampleRate;
|
||||
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];
|
||||
uint8_t buffer[1024];
|
||||
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_CATEGORY, "Capture",
|
||||
PW_KEY_MEDIA_ROLE , "Music",
|
||||
PW_KEY_NODE_LATENCY , requestedNodeLatency,
|
||||
NULL
|
||||
);
|
||||
if (!props)
|
||||
|
||||
@@ -120,6 +120,7 @@ typedef struct LG_AudioEventOps
|
||||
const void * data, size_t frames,
|
||||
const LG_AudioClock * sourceClock);
|
||||
|
||||
/* May repeat with the same generation to update an active stream format. */
|
||||
void (*recordStart)(void * opaque, uint32_t generation,
|
||||
const LG_AudioFormat * format);
|
||||
void (*recordStop)(void * opaque, uint32_t generation);
|
||||
|
||||
@@ -2156,7 +2156,7 @@ static void realRecordStartLocked(const LG_AudioFormat * format)
|
||||
audio.audioDev->record.start(format, recordPushFrames);
|
||||
|
||||
// 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.record.volumeChannels,
|
||||
audio.record.volume);
|
||||
@@ -2201,7 +2201,8 @@ static void recordConfirm(bool yes, void * opaque)
|
||||
static void recordStart(const LG_AudioFormat * format)
|
||||
{
|
||||
LG_LOCK(audio.record.lock);
|
||||
if (!audio.audioDev || audio.record.shuttingDown ||
|
||||
if (!audio.audioDev || !audio.audioDev->record.start ||
|
||||
audio.record.shuttingDown ||
|
||||
!audioFormatValid(format))
|
||||
{
|
||||
if (format && !audioFormatValid(format))
|
||||
@@ -2271,6 +2272,29 @@ static void recordStart(const LG_AudioFormat * format)
|
||||
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)
|
||||
{
|
||||
audio.audioDev->record.stop();
|
||||
@@ -2522,9 +2546,12 @@ static void eventRecordStart(void * opaque, uint32_t generation,
|
||||
binding->ops->recordData;
|
||||
if (active)
|
||||
{
|
||||
atomic_store_explicit(&audio.record.streamGeneration,
|
||||
generation, memory_order_release);
|
||||
recordStart(format);
|
||||
const uint32_t previous = atomic_exchange_explicit(
|
||||
&audio.record.streamGeneration, generation, memory_order_acq_rel);
|
||||
if (previous == generation)
|
||||
recordReconfigure(format);
|
||||
else
|
||||
recordStart(format);
|
||||
}
|
||||
LG_UNLOCK_SHARED(audio.activeLock);
|
||||
}
|
||||
|
||||
@@ -67,12 +67,15 @@ struct LGA_USBState
|
||||
const LG_AudioEventOps * events;
|
||||
void * eventOpaque;
|
||||
|
||||
LG_AudioFormat streamFormat;
|
||||
uint32_t streamGeneration;
|
||||
LG_AudioFormat playbackFormat;
|
||||
uint32_t playbackGeneration;
|
||||
LG_AudioFormat recordFormat;
|
||||
uint32_t recordGeneration;
|
||||
uint32_t generationSerial;
|
||||
int64_t clockOrigin;
|
||||
atomic_uint_fast64_t position;
|
||||
atomic_uint deliveryGeneration;
|
||||
int64_t playbackClockOrigin;
|
||||
atomic_uint_fast64_t playbackPosition;
|
||||
atomic_uint playbackDeliveryGeneration;
|
||||
atomic_uint recordDeliveryGeneration;
|
||||
atomic_uint inFlight;
|
||||
};
|
||||
|
||||
@@ -153,12 +156,12 @@ static void endCallback(USBAudioCallbackFrame * frame,
|
||||
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)
|
||||
{
|
||||
/* 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->streamFormat.sampleRate;
|
||||
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;
|
||||
@@ -166,7 +169,7 @@ static LG_AudioClock makeClock(
|
||||
return (LG_AudioClock)
|
||||
{
|
||||
.position = position,
|
||||
.time = state->clockOrigin + elapsed,
|
||||
.time = state->playbackClockOrigin + elapsed,
|
||||
.rate = 0.0,
|
||||
.stable = false,
|
||||
};
|
||||
@@ -197,11 +200,18 @@ static bool attachmentCurrentNL(const LGA_USBState * state,
|
||||
state->eventOpaque == target->opaque;
|
||||
}
|
||||
|
||||
static bool eventCurrentNL(const LGA_USBState * state,
|
||||
static bool playbackEventCurrentNL(const LGA_USBState * state,
|
||||
const USBAudioEventTarget * target, uint32_t streamGeneration)
|
||||
{
|
||||
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)
|
||||
@@ -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)
|
||||
{
|
||||
LGA_USBState * state = opaque;
|
||||
@@ -268,21 +278,22 @@ static void usbAudioStart(
|
||||
LG_AudioClock clock;
|
||||
|
||||
LG_LOCK(state->stateLock);
|
||||
if (state->streamGeneration)
|
||||
if (state->playbackGeneration)
|
||||
{
|
||||
LG_UNLOCK(state->stateLock);
|
||||
return;
|
||||
}
|
||||
|
||||
setStreamFormat(&state->streamFormat, sampleRate, channelMask);
|
||||
setStreamFormat(&state->playbackFormat, sampleRate, channelMask);
|
||||
generation = state->generationSerial =
|
||||
nextGeneration(state->generationSerial);
|
||||
state->streamGeneration = generation;
|
||||
state->clockOrigin = (int64_t)nanotime();
|
||||
atomic_store_explicit(&state->position, 0, memory_order_relaxed);
|
||||
state->playbackGeneration = generation;
|
||||
state->playbackClockOrigin = (int64_t)nanotime();
|
||||
atomic_store_explicit(
|
||||
&state->deliveryGeneration, 0, memory_order_seq_cst);
|
||||
clock = makeClock(state, 0);
|
||||
&state->playbackPosition, 0, memory_order_relaxed);
|
||||
atomic_store_explicit(
|
||||
&state->playbackDeliveryGeneration, 0, memory_order_seq_cst);
|
||||
clock = makePlaybackClock(state, 0);
|
||||
const bool admitted = beginEventNL(state, &target);
|
||||
dispatch = admitted && target.events->playbackStart;
|
||||
if (admitted && !dispatch)
|
||||
@@ -293,32 +304,32 @@ static void usbAudioStart(
|
||||
return;
|
||||
|
||||
target.events->playbackStart(
|
||||
target.opaque, generation, &state->streamFormat, &clock);
|
||||
target.opaque, generation, &state->playbackFormat, &clock);
|
||||
|
||||
LG_LOCK(state->stateLock);
|
||||
if (eventCurrentNL(state, &target, generation))
|
||||
atomic_store_explicit(&state->deliveryGeneration,
|
||||
if (playbackEventCurrentNL(state, &target, generation))
|
||||
atomic_store_explicit(&state->playbackDeliveryGeneration,
|
||||
generation, memory_order_seq_cst);
|
||||
LG_UNLOCK(state->stateLock);
|
||||
endEvent(state, &target);
|
||||
}
|
||||
|
||||
static void usbAudioStop(void * opaque)
|
||||
static void usbPlaybackStop(void * opaque)
|
||||
{
|
||||
LGA_USBState * state = opaque;
|
||||
USBAudioEventTarget target;
|
||||
|
||||
LG_LOCK(state->stateLock);
|
||||
const uint32_t generation = state->streamGeneration;
|
||||
const uint32_t generation = state->playbackGeneration;
|
||||
if (!generation)
|
||||
{
|
||||
LG_UNLOCK(state->stateLock);
|
||||
return;
|
||||
}
|
||||
|
||||
state->streamGeneration = 0;
|
||||
state->playbackGeneration = 0;
|
||||
atomic_store_explicit(
|
||||
&state->deliveryGeneration, 0, memory_order_seq_cst);
|
||||
&state->playbackDeliveryGeneration, 0, memory_order_seq_cst);
|
||||
const bool admitted = beginEventNL(state, &target);
|
||||
LG_UNLOCK(state->stateLock);
|
||||
|
||||
@@ -337,23 +348,24 @@ static void usbAudioStop(void * opaque)
|
||||
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;
|
||||
USBAudioCallbackFrame frame;
|
||||
beginCallback(state, &frame, &l_eventFrames, &state->inFlight);
|
||||
|
||||
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(
|
||||
&state->deliveryGeneration, memory_order_seq_cst);
|
||||
&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 = makeClock(state, position);
|
||||
const LG_AudioClock clock = makePlaybackClock(state, position);
|
||||
events->playbackData(
|
||||
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);
|
||||
}
|
||||
|
||||
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 =
|
||||
{
|
||||
.start = usbAudioStart,
|
||||
.stop = usbAudioStop,
|
||||
.data = usbAudioData,
|
||||
.playbackStart = usbPlaybackStart,
|
||||
.playbackStop = usbPlaybackStop,
|
||||
.playbackData = usbPlaybackData,
|
||||
.recordStart = usbRecordStart,
|
||||
.recordStop = usbRecordStop,
|
||||
};
|
||||
|
||||
static void usbSetAvailable(void * opaque, bool available)
|
||||
@@ -443,9 +530,14 @@ static bool usbAttach(void * opaque, const LG_AudioEventOps * events,
|
||||
return false;
|
||||
|
||||
USBAudioEventTarget target;
|
||||
LG_AudioClock clock;
|
||||
uint32_t generation;
|
||||
bool dispatch;
|
||||
LG_AudioFormat playbackFormat;
|
||||
LG_AudioFormat recordFormat;
|
||||
LG_AudioClock playbackClock;
|
||||
uint32_t playbackGeneration;
|
||||
uint32_t recordGeneration;
|
||||
bool playbackDispatch;
|
||||
bool recordDispatch;
|
||||
bool admitted;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
@@ -469,28 +561,61 @@ static bool usbAttach(void * opaque, const LG_AudioEventOps * events,
|
||||
nextGeneration(state->attachmentGeneration);
|
||||
state->events = events;
|
||||
state->eventOpaque = eventOpaque;
|
||||
generation = state->streamGeneration;
|
||||
dispatch = generation && events->playbackStart &&
|
||||
playbackGeneration = state->playbackGeneration;
|
||||
recordGeneration = state->recordGeneration;
|
||||
playbackDispatch = playbackGeneration && events->playbackStart;
|
||||
recordDispatch = recordGeneration && events->recordStart;
|
||||
admitted = (playbackDispatch || recordDispatch) &&
|
||||
beginEventNL(state, &target);
|
||||
if (dispatch)
|
||||
clock = makeClock(state, atomic_load_explicit(
|
||||
&state->position, memory_order_relaxed));
|
||||
playbackDispatch = playbackDispatch && admitted;
|
||||
recordDispatch = recordDispatch && admitted;
|
||||
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);
|
||||
LG_UNLOCK(state->stateLock);
|
||||
|
||||
if (dispatch)
|
||||
if (playbackDispatch)
|
||||
{
|
||||
target.events->playbackStart(
|
||||
target.opaque, generation, &state->streamFormat, &clock);
|
||||
target.opaque, playbackGeneration,
|
||||
&playbackFormat, &playbackClock);
|
||||
|
||||
LG_LOCK(state->stateLock);
|
||||
if (eventCurrentNL(state, &target, generation))
|
||||
atomic_store_explicit(&state->deliveryGeneration,
|
||||
generation, memory_order_seq_cst);
|
||||
if (playbackEventCurrentNL(
|
||||
state, &target, playbackGeneration))
|
||||
atomic_store_explicit(&state->playbackDeliveryGeneration,
|
||||
playbackGeneration, memory_order_seq_cst);
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -515,7 +640,9 @@ static void usbDetach(void * opaque)
|
||||
const uint32_t attachmentGeneration =
|
||||
state->attachmentGeneration;
|
||||
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);
|
||||
LG_UNLOCK(state->stateLock);
|
||||
|
||||
@@ -538,13 +665,14 @@ static bool usbClockFeedback(void * opaque, uint32_t generation,
|
||||
LGA_USBState * state = opaque;
|
||||
|
||||
LG_LOCK(state->stateLock);
|
||||
if (!state->attached || state->streamGeneration != generation)
|
||||
if (!state->attached ||
|
||||
state->playbackGeneration != generation)
|
||||
{
|
||||
LG_UNLOCK(state->stateLock);
|
||||
return false;
|
||||
}
|
||||
|
||||
const double nominalRate = state->streamFormat.sampleRate;
|
||||
const double nominalRate = state->playbackFormat.sampleRate;
|
||||
const double rate = playbackClock &&
|
||||
targetRate >= nominalRate * 0.995 &&
|
||||
targetRate <= nominalRate * 1.005 ?
|
||||
@@ -554,13 +682,31 @@ static bool usbClockFeedback(void * opaque, uint32_t generation,
|
||||
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 =
|
||||
{
|
||||
.name = "USB Audio",
|
||||
.setStatusListener = usbSetStatusListener,
|
||||
.attach = usbAttach,
|
||||
.detach = usbDetach,
|
||||
.recordData = NULL,
|
||||
.recordData = usbRecordData,
|
||||
.clockFeedback = usbClockFeedback,
|
||||
};
|
||||
|
||||
@@ -573,13 +719,15 @@ LGA_USBState * lgaUsb_create(void)
|
||||
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->playbackPosition, 0);
|
||||
atomic_init(&state->playbackDeliveryGeneration, 0);
|
||||
atomic_init(&state->recordDeliveryGeneration, 0);
|
||||
atomic_init(&state->inFlight, 0);
|
||||
atomic_init(&state->statusInFlight, 0);
|
||||
atomic_init(&state->statusNextTicket, 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);
|
||||
if (!state->device)
|
||||
@@ -618,3 +766,8 @@ LG_USBRedir * lgaUsb_redir(LGA_USBState * state)
|
||||
{
|
||||
return state ? state->redir : NULL;
|
||||
}
|
||||
|
||||
bool lgaUsb_recording(const LGA_USBState * state)
|
||||
{
|
||||
return state && lgUsbAudio_recording(state->device);
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ LGA_USBState * lgaUsb_create(void);
|
||||
void lgaUsb_destroy(LGA_USBState * state);
|
||||
|
||||
LG_USBRedir * lgaUsb_redir(LGA_USBState * state);
|
||||
bool lgaUsb_recording(const LGA_USBState * state);
|
||||
|
||||
extern const LG_AudioOps LGA_USB;
|
||||
|
||||
|
||||
@@ -1838,6 +1838,8 @@ int spiceThread(void * arg)
|
||||
#if ENABLE_USB_AUDIO
|
||||
if (usbRedir && !lgUsbRedir_process(usbRedir))
|
||||
DEBUG_WARN("Failed to process USB audio redirection");
|
||||
if (usbAudio)
|
||||
processTimeout = lgaUsb_recording(usbAudio) ? 1 : 10;
|
||||
#endif
|
||||
|
||||
if ((status = purespice_process(processTimeout)) != PS_STATUS_RUN)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -32,13 +32,19 @@ typedef struct LG_USBAudio LG_USBAudio;
|
||||
|
||||
typedef struct LG_USBAudioEventOps
|
||||
{
|
||||
void (*start)(void * opaque, uint32_t sampleRate, uint32_t channelMask);
|
||||
void (*stop)(void * opaque);
|
||||
void (*playbackStart)(
|
||||
void * opaque, uint32_t sampleRate, uint32_t channelMask);
|
||||
void (*playbackStop)(void * opaque);
|
||||
|
||||
/* 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 (*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;
|
||||
|
||||
@@ -51,6 +57,14 @@ void lgUsbAudio_destroy(LG_USBAudio * audio);
|
||||
* feedback thread. */
|
||||
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);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -296,6 +296,9 @@ bool lgUsbRedir_process(LG_USBRedir * usbredir)
|
||||
}
|
||||
}
|
||||
|
||||
if (usbredir->plugged && usbredir->deviceOps->process)
|
||||
usbredir->deviceOps->process(usbredir->deviceOpaque);
|
||||
|
||||
return flushUSBRedir(usbredir);
|
||||
}
|
||||
|
||||
|
||||
@@ -42,6 +42,10 @@ typedef struct LG_USBRedirDeviceOps
|
||||
/* Queue the device descriptors and connection announcement. */
|
||||
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. */
|
||||
void (*unplug)(void * opaque);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user