[client] audio: schedule USB feedback independently

This commit is contained in:
Geoffrey McRae
2026-08-12 13:02:58 +10:00
parent b411d345d0
commit a4d38ccb98

View File

@@ -74,6 +74,7 @@ enum
USB_AUDIO_RECORD_PACKET_FRAMES = 97, USB_AUDIO_RECORD_PACKET_FRAMES = 97,
USB_AUDIO_RECORD_PACKETS_PER_SECOND = 2000, USB_AUDIO_RECORD_PACKETS_PER_SECOND = 2000,
USB_AUDIO_RECORD_PACKET_INTERVAL_NS = 500000, USB_AUDIO_RECORD_PACKET_INTERVAL_NS = 500000,
USB_AUDIO_FEEDBACK_PERIOD_NS = 1000000,
USB_AUDIO_RECORD_RATE_SCALE = 65536, USB_AUDIO_RECORD_RATE_SCALE = 65536,
USB_AUDIO_RECORD_RATE_SETTLE_NS = 2000000000, USB_AUDIO_RECORD_RATE_SETTLE_NS = 2000000000,
USB_AUDIO_RECORD_RATE_GAP_NS = 100000000, USB_AUDIO_RECORD_RATE_GAP_NS = 100000000,
@@ -371,7 +372,8 @@ struct LG_USBAudio
uint16_t playbackBatchFrames; uint16_t playbackBatchFrames;
atomic_bool feedbackStreaming; atomic_bool feedbackStreaming;
uint64_t feedbackPacketId; uint64_t feedbackPacketId;
uint8_t feedbackDataPackets; uint64_t feedbackNextPacketTime;
uint32_t feedbackQueueTarget;
atomic_uint feedbackValue; atomic_uint feedbackValue;
atomic_bool recordStreaming; atomic_bool recordStreaming;
@@ -413,9 +415,9 @@ static LG_USBAudio * getAudio(void * opaque)
return lgUsbRedir_device(opaque); return lgUsbRedir_device(opaque);
} }
static uint64_t recordTime(void) static uint64_t streamTime(void)
{ {
/* PipeWire timestamps use CLOCK_MONOTONIC. Keep USB capture scheduling in /* PipeWire timestamps use CLOCK_MONOTONIC. Keep USB stream scheduling in
* that domain instead of introducing MONOTONIC_RAW clock drift. */ * that domain instead of introducing MONOTONIC_RAW clock drift. */
return microtime() * UINT64_C(1000); return microtime() * UINT64_C(1000);
} }
@@ -593,7 +595,9 @@ static void stopFeedback(LG_USBAudio * audio)
{ {
atomic_store_explicit( atomic_store_explicit(
&audio->feedbackStreaming, false, memory_order_release); &audio->feedbackStreaming, false, memory_order_release);
audio->feedbackDataPackets = 0; audio->feedbackPacketId = 0;
audio->feedbackNextPacketTime = 0;
audio->feedbackQueueTarget = 0;
} }
static void stopPlaybackStreams(LG_USBAudio * audio) static void stopPlaybackStreams(LG_USBAudio * audio)
@@ -688,7 +692,7 @@ static void startRecordArrivalWindow(
static void updateRecordArrivalRate( static void updateRecordArrivalRate(
LG_USBAudio * audio, uint32_t sampleRate, size_t frames) LG_USBAudio * audio, uint32_t sampleRate, size_t frames)
{ {
const uint64_t now = recordTime(); const uint64_t now = streamTime();
if (audio->recordRateMode != RECORD_RATE_ARRIVAL) if (audio->recordRateMode != RECORD_RATE_ARRIVAL)
{ {
/* Preserve the current rate while a temporarily absent source clock /* Preserve the current rate while a temporarily absent source clock
@@ -820,7 +824,7 @@ static void startRecord(LG_USBAudio * audio)
/* Backend startup may block while the capture graph is configured. Do not /* Backend startup may block while the capture graph is configured. Do not
* turn that setup time into a burst of stale USB packets. */ * turn that setup time into a burst of stale USB packets. */
recordWaitForInput(audio); recordWaitForInput(audio);
audio->recordNextPacketTime = recordTime(); audio->recordNextPacketTime = streamTime();
atomic_store_explicit( atomic_store_explicit(
&audio->recordAccepting, true, memory_order_seq_cst); &audio->recordAccepting, true, memory_order_seq_cst);
} }
@@ -843,7 +847,7 @@ static void reconfigureRecord(
* queued around the format transition and restart the USB sample phase. */ * queued around the format transition and restart the USB sample phase. */
recordWaitForInput(audio); recordWaitForInput(audio);
resetRecordData(audio); resetRecordData(audio);
audio->recordNextPacketTime = recordTime(); audio->recordNextPacketTime = streamTime();
atomic_store_explicit( atomic_store_explicit(
&audio->recordAccepting, true, memory_order_seq_cst); &audio->recordAccepting, true, memory_order_seq_cst);
} }
@@ -1116,6 +1120,30 @@ static void sendFeedbackPackets(LG_USBAudio * audio, uint32_t count)
&packet, data, USB_AUDIO_FEEDBACK_SIZE); &packet, data, USB_AUDIO_FEEDBACK_SIZE);
} }
static void sendFeedbackPacketsDue(LG_USBAudio * audio)
{
if (!atomic_load_explicit(
&audio->feedbackStreaming, memory_order_acquire) ||
!audio->feedbackQueueTarget)
return;
const uint64_t now = streamTime();
if (now < audio->feedbackNextPacketTime)
return;
const uint64_t due =
(now - audio->feedbackNextPacketTime) /
USB_AUDIO_FEEDBACK_PERIOD_NS + 1;
sendFeedbackPackets(audio, (uint32_t)min(
due, (uint64_t)audio->feedbackQueueTarget));
if (due > audio->feedbackQueueTarget)
audio->feedbackNextPacketTime =
now + USB_AUDIO_FEEDBACK_PERIOD_NS;
else
audio->feedbackNextPacketTime += due * USB_AUDIO_FEEDBACK_PERIOD_NS;
}
static void sendRecordPacketBatch(LG_USBAudio * audio, uint32_t count, static void sendRecordPacketBatch(LG_USBAudio * audio, uint32_t count,
uint64_t rateQ16, bool forceSilence, uint64_t leadingSilenceFrames, uint64_t rateQ16, bool forceSilence, uint64_t leadingSilenceFrames,
uint64_t availableFrames) uint64_t availableFrames)
@@ -1174,7 +1202,7 @@ static void sendRecordRefill(LG_USBAudio * audio, uint64_t rateQ16)
audio->recordRefillPackets -= count; audio->recordRefillPackets -= count;
if (!audio->recordRefillPackets) if (!audio->recordRefillPackets)
audio->recordNextPacketTime = audio->recordNextPacketTime =
recordTime() + USB_AUDIO_RECORD_PACKET_INTERVAL_NS; streamTime() + USB_AUDIO_RECORD_PACKET_INTERVAL_NS;
} }
static void sendRecordPackets(LG_USBAudio * audio) static void sendRecordPackets(LG_USBAudio * audio)
@@ -1191,7 +1219,7 @@ static void sendRecordPackets(LG_USBAudio * audio)
return; return;
} }
const uint64_t now = recordTime(); const uint64_t now = streamTime();
if (audio->recordNextPacketTime > now) if (audio->recordNextPacketTime > now)
return; return;
@@ -1281,11 +1309,8 @@ static void startISOStream(void * opaque, uint64_t id,
case USB_AUDIO_FEEDBACK_ENDPOINT: case USB_AUDIO_FEEDBACK_ENDPOINT:
if (!getLayout(audio->playbackAlt)) if (!getLayout(audio->playbackAlt))
break; break;
stopFeedback(audio);
resetFeedbackRate(audio); resetFeedbackRate(audio);
atomic_store_explicit(
&audio->feedbackStreaming, true, memory_order_release);
audio->feedbackDataPackets = 0;
audio->feedbackPacketId = 0;
feedbackPrefill = feedbackPrefill =
(uint32_t)request->pkts_per_urb * request->no_urbs; (uint32_t)request->pkts_per_urb * request->no_urbs;
if (!feedbackPrefill) if (!feedbackPrefill)
@@ -1293,6 +1318,7 @@ static void startISOStream(void * opaque, uint64_t id,
else if (feedbackPrefill > else if (feedbackPrefill >
USB_AUDIO_FEEDBACK_MAX_QUEUE) USB_AUDIO_FEEDBACK_MAX_QUEUE)
feedbackPrefill = USB_AUDIO_FEEDBACK_MAX_QUEUE; feedbackPrefill = USB_AUDIO_FEEDBACK_MAX_QUEUE;
audio->feedbackQueueTarget = feedbackPrefill;
result = usb_redir_success; result = usb_redir_success;
break; break;
@@ -1313,7 +1339,13 @@ static void startISOStream(void * opaque, uint64_t id,
sendISOStatus(audio, id, request->endpoint, result); sendISOStatus(audio, id, request->endpoint, result);
if (feedbackPrefill) if (feedbackPrefill)
{
sendFeedbackPackets(audio, feedbackPrefill); sendFeedbackPackets(audio, feedbackPrefill);
audio->feedbackNextPacketTime =
streamTime() + USB_AUDIO_FEEDBACK_PERIOD_NS;
atomic_store_explicit(
&audio->feedbackStreaming, true, memory_order_release);
}
} }
static void stopISOStream(void * opaque, uint64_t id, static void stopISOStream(void * opaque, uint64_t id,
@@ -1630,15 +1662,8 @@ static void isoPacket(void * opaque, uint64_t id,
{ {
queuePlaybackPacket( queuePlaybackPacket(
audio, data, dataLength / frameSize, frameSize); audio, data, dataLength / frameSize, frameSize);
if (audio->playbackBatchPackets == USB_AUDIO_PLAYBACK_BATCH_PACKETS)
if (atomic_load_explicit(
&audio->feedbackStreaming, memory_order_acquire) &&
++audio->feedbackDataPackets == USB_AUDIO_FEEDBACK_INTERVAL)
{
flushPlayback(audio); flushPlayback(audio);
audio->feedbackDataPackets = 0;
sendFeedbackPackets(audio, 1);
}
} }
} }
@@ -1693,7 +1718,7 @@ static void reportRecordDebug(LG_USBAudio * audio)
if (!audio->debug) if (!audio->debug)
return; return;
const uint64_t now = recordTime(); const uint64_t now = streamTime();
if (!audio->recordNextDebugTime) if (!audio->recordNextDebugTime)
{ {
audio->recordNextDebugTime = audio->recordNextDebugTime =
@@ -1733,6 +1758,7 @@ static void reportRecordDebug(LG_USBAudio * audio)
static void processDevice(void * opaque) static void processDevice(void * opaque)
{ {
LG_USBAudio * audio = opaque; LG_USBAudio * audio = opaque;
sendFeedbackPacketsDue(audio);
flushPlayback(audio); flushPlayback(audio);
sendRecordPackets(audio); sendRecordPackets(audio);
reportRecordDebug(audio); reportRecordDebug(audio);
@@ -1888,18 +1914,31 @@ bool lgUsbAudio_recordData(
uint64_t lgUsbAudio_processDelayNs(const LG_USBAudio * audio) uint64_t lgUsbAudio_processDelayNs(const LG_USBAudio * audio)
{ {
if (!audio || !atomic_load_explicit( if (!audio)
&audio->recordStreaming, memory_order_acquire))
return UINT64_MAX; return UINT64_MAX;
if (audio->recordRefillPackets) const uint64_t now = streamTime();
return 0; uint64_t delay = UINT64_MAX;
const uint64_t now = recordTime(); if (atomic_load_explicit(
const uint64_t deadline = audio->recordNextPacketTime + &audio->feedbackStreaming, memory_order_acquire))
(uint64_t)(audio->recordBatchPackets - 1) * delay = audio->feedbackNextPacketTime > now ?
USB_AUDIO_RECORD_PACKET_INTERVAL_NS; audio->feedbackNextPacketTime - now : 0;
return deadline > now ? deadline - now : 0;
if (!atomic_load_explicit(
&audio->recordStreaming, memory_order_acquire))
return delay;
uint64_t recordDelay = 0;
if (!audio->recordRefillPackets)
{
const uint64_t deadline = audio->recordNextPacketTime +
(uint64_t)(audio->recordBatchPackets - 1) *
USB_AUDIO_RECORD_PACKET_INTERVAL_NS;
recordDelay = deadline > now ? deadline - now : 0;
}
return min(delay, recordDelay);
} }
void lgUsbAudio_destroy(LG_USBAudio * audio) void lgUsbAudio_destroy(LG_USBAudio * audio)