[client] audio: harden low-latency streaming

Rework audio provider and backend lifecycles so playback and capture
callbacks quiesce without blocking real-time threads. Move activation,
teardown, controls, retries, and diagnostics onto bounded workers.

Harden USB audio cadence, feedback, and capture recovery.
Preserve source clocks through recording and pace packets from the
device clock. Bound queues, waits, conversion buffers, and packet sizes.

Make PipeWire and PulseAudio stream control thread-safe and recoverable.
Correct latency clock domains, coalesce rate updates, preserve recent
capture under overload, and keep logging outside real-time callbacks.
This commit is contained in:
Geoffrey McRae
2026-08-10 16:36:12 +10:00
parent 4434985aa3
commit 87aa61510c
13 changed files with 4795 additions and 1217 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -28,18 +28,23 @@
#include "common/debug.h" #include "common/debug.h"
#include "common/time.h" #include "common/time.h"
#define PULSEAUDIO_ERROR_REPORT_INTERVAL_NS INT64_C(1000000000)
#define PULSEAUDIO_OPERATION_TIMEOUT_US UINT64_C(250000)
#define PULSEAUDIO_READY_TIMEOUT_US UINT64_C(5000000)
#define PULSEAUDIO_RATE_UPDATE_INTERVAL_NS INT64_C(50000000)
#define PULSEAUDIO_RATE_UPDATE_MAX_HOLD_NS INT64_C(250000000)
#define PULSEAUDIO_RATE_UPDATE_DEADBAND_HZ UINT32_C(1)
struct PulseAudio struct PulseAudio
{ {
pa_threaded_mainloop * loop; pa_threaded_mainloop * loop;
pa_mainloop_api * api; pa_mainloop_api * api;
pa_context * context; pa_context * context;
pa_operation * contextSub; bool loopStarted;
pa_stream * sink; pa_stream * sink;
int sinkIndex; uint32_t sinkIndex;
bool sinkCorked; bool sinkCorked;
bool sinkMuted;
bool sinkStarting;
bool sinkResamplerEnabled; bool sinkResamplerEnabled;
bool sinkRateFailed; bool sinkRateFailed;
int sinkMaxPeriodFrames; int sinkMaxPeriodFrames;
@@ -50,9 +55,26 @@ struct PulseAudio
uint32_t sinkAppliedRate; uint32_t sinkAppliedRate;
uint32_t sinkPendingRate; uint32_t sinkPendingRate;
uint32_t sinkRequestedRate; uint32_t sinkRequestedRate;
uint32_t sinkDeferredRate;
int64_t sinkNextRateUpdate;
int64_t sinkRateDeferredSince;
bool sinkRateUpdateArmed;
pa_operation * sinkRateOperation; pa_operation * sinkRateOperation;
pa_operation * sinkStartOperation;
pa_time_event * sinkStartTimer;
uint32_t sinkStartSerial;
LG_AudioPullFn sinkPullFn; LG_AudioPullFn sinkPullFn;
_Atomic(int64_t) sinkPresentationDeadline; LG_AudioFailureFn sinkFailureFn;
uint32_t sinkFailureCookie;
_Atomic(int64_t) sinkLatencyNs;
atomic_bool sinkLatencyUpdateRequested;
atomic_bool sinkErrorsPending;
_Atomic(int64_t) sinkNextErrorReport;
atomic_uint sinkUnderflows;
atomic_uint sinkOverflows;
atomic_uint sinkWriteErrors;
atomic_uint sinkRateErrors;
atomic_uint sinkControlErrors;
}; };
static struct PulseAudio pa = {0}; static struct PulseAudio pa = {0};
@@ -136,13 +158,83 @@ static pa_channel_position_t pulseaudio_channel(
return (pa_channel_position_t)(PA_CHANNEL_POSITION_AUX0 + index); return (pa_channel_position_t)(PA_CHANNEL_POSITION_AUX0 + index);
} }
static void pulseaudio_unrefOperation(pa_operation * operation) static void pulseaudio_noteError(atomic_uint * counter)
{ {
if (operation) atomic_fetch_add_explicit(counter, 1, memory_order_relaxed);
atomic_store_explicit(
&pa.sinkErrorsPending, true, memory_order_release);
}
static void pulseaudio_trackControlOperation(pa_operation * operation)
{
if (!operation)
pulseaudio_noteError(&pa.sinkControlErrors);
else
pa_operation_unref(operation); pa_operation_unref(operation);
} }
static bool pulseaudio_submitRateUpdate(void); static void pulseaudio_streamControl_cb(
pa_stream * stream, int success, void * userdata)
{
if (!success)
pulseaudio_noteError(&pa.sinkControlErrors);
}
static void pulseaudio_contextControl_cb(
pa_context * context, int success, void * userdata)
{
if (!success)
pulseaudio_noteError(&pa.sinkControlErrors);
}
static void pulseaudio_reportErrors(bool force)
{
if (!atomic_load_explicit(
&pa.sinkErrorsPending, memory_order_acquire))
return;
const int64_t now = (int64_t)nanotime();
if (!force)
{
int64_t next = atomic_load_explicit(
&pa.sinkNextErrorReport, memory_order_relaxed);
if (now < next || !atomic_compare_exchange_strong_explicit(
&pa.sinkNextErrorReport, &next,
now + PULSEAUDIO_ERROR_REPORT_INTERVAL_NS,
memory_order_relaxed, memory_order_relaxed))
return;
}
if (!atomic_exchange_explicit(
&pa.sinkErrorsPending, false, memory_order_acq_rel))
return;
const unsigned int underflows = atomic_exchange_explicit(
&pa.sinkUnderflows, 0, memory_order_relaxed);
const unsigned int overflows = atomic_exchange_explicit(
&pa.sinkOverflows, 0, memory_order_relaxed);
const unsigned int writeErrors = atomic_exchange_explicit(
&pa.sinkWriteErrors, 0, memory_order_relaxed);
const unsigned int rateErrors = atomic_exchange_explicit(
&pa.sinkRateErrors, 0, memory_order_relaxed);
const unsigned int controlErrors = atomic_exchange_explicit(
&pa.sinkControlErrors, 0, memory_order_relaxed);
if (underflows)
DEBUG_WARN("PulseAudio playback underflowed %u time(s)", underflows);
if (overflows)
DEBUG_WARN("PulseAudio playback overflowed %u time(s)", overflows);
if (writeErrors)
DEBUG_WARN("PulseAudio playback write failed %u time(s)", writeErrors);
if (rateErrors)
DEBUG_WARN("PulseAudio sample rate update failed %u time(s)",
rateErrors);
if (controlErrors)
DEBUG_WARN("PulseAudio control operation failed %u time(s)",
controlErrors);
}
static bool pulseaudio_submitRateUpdate(bool force);
static void pulseaudio_rateUpdate_cb(pa_stream * stream, int success, static void pulseaudio_rateUpdate_cb(pa_stream * stream, int success,
void * userdata) void * userdata)
@@ -156,33 +248,46 @@ static void pulseaudio_rateUpdate_cb(pa_stream * stream, int success,
if (!success) if (!success)
{ {
DEBUG_ERROR("Failed to update PulseAudio sample rate: %s", pulseaudio_noteError(&pa.sinkRateErrors);
pa_strerror(pa_context_errno(pa.context)));
pa.sinkRateFailed = true; pa.sinkRateFailed = true;
return; return;
} }
pa.sinkAppliedRate = pa.sinkPendingRate; pa.sinkAppliedRate = pa.sinkPendingRate;
if (pa.sinkCorked) if (pa.sinkCorked)
pulseaudio_submitRateUpdate(); pulseaudio_submitRateUpdate(true);
} }
static bool pulseaudio_submitRateUpdate(void) static bool pulseaudio_submitRateUpdate(bool force)
{ {
if (pa.sinkRateFailed) if (pa.sinkRateFailed)
return false; return false;
if (pa.sinkRateOperation || if (pa.sinkRateOperation)
pa.sinkRequestedRate == pa.sinkAppliedRate)
return true; return true;
pa.sinkPendingRate = pa.sinkRequestedRate; if (pa.sinkRequestedRate == pa.sinkAppliedRate)
{
pa.sinkDeferredRate = pa.sinkRequestedRate;
pa.sinkRateUpdateArmed = false;
pa.sinkRateDeferredSince = 0;
return true;
}
if (!force && !pa.sinkRateUpdateArmed)
return true;
pa.sinkPendingRate = pa.sinkRequestedRate;
pa.sinkDeferredRate = pa.sinkPendingRate;
pa.sinkRateUpdateArmed = false;
pa.sinkRateDeferredSince = 0;
pa.sinkNextRateUpdate =
(int64_t)nanotime() + PULSEAUDIO_RATE_UPDATE_INTERVAL_NS;
pa.sinkRateOperation = pa_stream_update_sample_rate(pa.sink, pa.sinkRateOperation = pa_stream_update_sample_rate(pa.sink,
pa.sinkPendingRate, pulseaudio_rateUpdate_cb, NULL); pa.sinkPendingRate, pulseaudio_rateUpdate_cb, NULL);
if (!pa.sinkRateOperation) if (!pa.sinkRateOperation)
{ {
DEBUG_ERROR("Failed to request a PulseAudio sample rate update: %s", pulseaudio_noteError(&pa.sinkRateErrors);
pa_strerror(pa_context_errno(pa.context)));
pa.sinkRateFailed = true; pa.sinkRateFailed = true;
return false; return false;
} }
@@ -190,31 +295,43 @@ static bool pulseaudio_submitRateUpdate(void)
return true; return true;
} }
static void pulseaudio_sink_input_cb(pa_context *c, const pa_sink_input_info *i, static void pulseaudio_cancelStartTimer_nl(void)
int eol, void *userdata)
{ {
if (eol < 0 || eol == 1) if (!pa.sinkStartTimer)
return; return;
pa.sinkIndex = i->index; pa_time_event * timer = pa.sinkStartTimer;
pa.sinkStartTimer = NULL;
pa.api->time_free(timer);
} }
static void pulseaudio_subscribe_cb(pa_context *c, static void pulseaudio_cancelStartOperation_nl(void)
pa_subscription_event_type_t t, uint32_t index, void *userdata)
{ {
switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) if (!pa.sinkStartOperation)
{ return;
case PA_SUBSCRIPTION_EVENT_SINK_INPUT:
if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) pa_operation * operation = pa.sinkStartOperation;
pa.sinkIndex = 0; pa.sinkStartOperation = NULL;
else pa_operation_cancel(operation);
{ pa_operation_unref(operation);
pa_operation *o = pa_context_get_sink_input_info(c, index, }
pulseaudio_sink_input_cb, NULL);
pulseaudio_unrefOperation(o); static void pulseaudio_sinkDisarm_nl(void)
} {
break; ++pa.sinkStartSerial;
} pulseaudio_cancelStartTimer_nl();
pulseaudio_cancelStartOperation_nl();
pa.sinkFailureFn = NULL;
pa.sinkFailureCookie = 0;
}
static void pulseaudio_sinkFailed_nl(void)
{
LG_AudioFailureFn failureFn = pa.sinkFailureFn;
const uint32_t failureCookie = pa.sinkFailureCookie;
pulseaudio_sinkDisarm_nl();
if (failureFn)
failureFn(failureCookie);
} }
static void pulseaudio_ctx_state_change_cb(pa_context * c, void * userdata) static void pulseaudio_ctx_state_change_cb(pa_context * c, void * userdata)
@@ -228,28 +345,120 @@ static void pulseaudio_ctx_state_change_cb(pa_context * c, void * userdata)
case PA_CONTEXT_READY: case PA_CONTEXT_READY:
DEBUG_INFO("Connected to PulseAudio server"); DEBUG_INFO("Connected to PulseAudio server");
pa_context_set_subscribe_callback(c, pulseaudio_subscribe_cb, NULL);
pa_context_subscribe(c, PA_SUBSCRIPTION_MASK_SINK_INPUT, NULL, NULL);
pa_threaded_mainloop_signal(pa.loop, 0); pa_threaded_mainloop_signal(pa.loop, 0);
break; break;
case PA_CONTEXT_TERMINATED: case PA_CONTEXT_TERMINATED:
if (pa.contextSub) if (c == pa.context)
{ pulseaudio_sinkFailed_nl();
pa_operation_unref(pa.contextSub); pa_threaded_mainloop_signal(pa.loop, 0);
pa.contextSub = NULL;
}
break; break;
case PA_CONTEXT_FAILED: case PA_CONTEXT_FAILED:
default: default:
if (c == pa.context)
pulseaudio_sinkFailed_nl();
DEBUG_ERROR("context error: %s", pa_strerror(pa_context_errno(c))); DEBUG_ERROR("context error: %s", pa_strerror(pa_context_errno(c)));
pa_threaded_mainloop_signal(pa.loop, 0);
break; break;
} }
} }
static void pulseaudio_context_close_nl(void)
{
if (!pa.context)
return;
pa_context_set_state_callback(pa.context, NULL, NULL);
pa_context_disconnect(pa.context);
pa_context_unref(pa.context);
pa.context = NULL;
}
struct PulseWait
{
bool timedOut;
};
static void pulseaudio_timeout_cb(pa_mainloop_api * api,
pa_time_event * event, const struct timeval * tv, void * userdata)
{
(void)api;
(void)event;
(void)tv;
struct PulseWait * wait = userdata;
wait->timedOut = true;
pa_threaded_mainloop_signal(pa.loop, 0);
}
/* The PulseAudio threaded mainloop must be locked. */
static bool pulseaudio_contextConnect_nl(void)
{
pa_proplist * propList = pa_proplist_new();
if (!propList)
{
DEBUG_ERROR("Failed to create the PulseAudio property list");
return false;
}
pa_proplist_sets(propList, PA_PROP_MEDIA_ROLE, "video");
pa.context = pa_context_new_with_proplist(
pa.api, "Looking Glass", propList);
pa_proplist_free(propList);
if (!pa.context)
{
DEBUG_ERROR("Failed to create the PulseAudio context");
return false;
}
pa_context_set_state_callback(pa.context,
pulseaudio_ctx_state_change_cb, NULL);
if (pa_context_connect(
pa.context, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL) < 0)
{
DEBUG_ERROR("Failed to connect to the PulseAudio server: %s",
pa_strerror(pa_context_errno(pa.context)));
pulseaudio_context_close_nl();
return false;
}
struct PulseWait wait = {0};
pa_time_event * timer = pa_context_rttime_new(pa.context,
pa_rtclock_now() + PULSEAUDIO_READY_TIMEOUT_US,
pulseaudio_timeout_cb, &wait);
if (!timer)
{
DEBUG_ERROR("Failed to create the PulseAudio connection timer");
pulseaudio_context_close_nl();
return false;
}
pa_context_state_t state;
while (!wait.timedOut)
{
state = pa_context_get_state(pa.context);
if (state == PA_CONTEXT_READY || !PA_CONTEXT_IS_GOOD(state))
break;
pa_threaded_mainloop_wait(pa.loop);
}
state = pa_context_get_state(pa.context);
pa.api->time_free(timer);
if (state == PA_CONTEXT_READY)
return true;
if (wait.timedOut)
DEBUG_ERROR("Timed out connecting to the PulseAudio server");
else
DEBUG_ERROR("PulseAudio context did not become ready: %s",
pa_strerror(pa_context_errno(pa.context)));
pulseaudio_context_close_nl();
return false;
}
static bool pulseaudio_init(void) static bool pulseaudio_init(void)
{ {
pa.sinkIndex = PA_INVALID_INDEX;
pa.loop = pa_threaded_mainloop_new(); pa.loop = pa_threaded_mainloop_new();
if (!pa.loop) if (!pa.loop)
{ {
@@ -258,74 +467,33 @@ static bool pulseaudio_init(void)
} }
pa.api = pa_threaded_mainloop_get_api(pa.loop); pa.api = pa_threaded_mainloop_get_api(pa.loop);
if (pa_signal_init(pa.api) != 0)
{
DEBUG_ERROR("Failed to init signals");
goto err_loop;
}
if (pa_threaded_mainloop_start(pa.loop) < 0) if (pa_threaded_mainloop_start(pa.loop) < 0)
{ {
DEBUG_ERROR("Failed to start the main loop"); DEBUG_ERROR("Failed to start the main loop");
goto err_loop; goto err_loop;
} }
pa.loopStarted = true;
pa_proplist * propList = pa_proplist_new();
if (!propList)
{
DEBUG_ERROR("Failed to create the proplist");
goto err_thread;
}
pa_proplist_sets(propList, PA_PROP_MEDIA_ROLE, "video");
pa_threaded_mainloop_lock(pa.loop); pa_threaded_mainloop_lock(pa.loop);
pa.context = pa_context_new_with_proplist( if (!pulseaudio_contextConnect_nl())
pa.api,
"Looking Glass",
propList);
if (!pa.context)
{ {
DEBUG_ERROR("Failed to create the context"); pa_threaded_mainloop_unlock(pa.loop);
goto err_context; goto err_thread;
} }
pa_context_set_state_callback(pa.context,
pulseaudio_ctx_state_change_cb, NULL);
if (pa_context_connect(pa.context, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL) < 0)
{
DEBUG_ERROR("Failed to connect to the context server");
goto err_context;
}
for(;;)
{
pa_context_state_t state = pa_context_get_state(pa.context);
if(!PA_CONTEXT_IS_GOOD(state))
{
DEBUG_ERROR("Context is bad");
goto err_context;
}
if (state == PA_CONTEXT_READY)
break;
pa_threaded_mainloop_wait(pa.loop);
}
pa_threaded_mainloop_unlock(pa.loop); pa_threaded_mainloop_unlock(pa.loop);
pa_proplist_free(propList);
return true; return true;
err_context:
pa_threaded_mainloop_unlock(pa.loop);
pa_proplist_free(propList);
err_thread: err_thread:
pa_threaded_mainloop_stop(pa.loop); if (pa.loopStarted)
{
pa_threaded_mainloop_stop(pa.loop);
pa.loopStarted = false;
}
err_loop: err_loop:
pa_threaded_mainloop_free(pa.loop); pa_threaded_mainloop_free(pa.loop);
pa.loop = NULL;
pa.api = NULL;
err: err:
return false; return false;
@@ -333,6 +501,7 @@ err:
static void pulseaudio_sink_close_nl(void) static void pulseaudio_sink_close_nl(void)
{ {
pulseaudio_sinkDisarm_nl();
if (!pa.sink) if (!pa.sink)
return; return;
@@ -347,51 +516,92 @@ static void pulseaudio_sink_close_nl(void)
pa_operation_cancel(operation); pa_operation_cancel(operation);
pa_operation_unref(operation); pa_operation_unref(operation);
} }
pulseaudio_unrefOperation(pa_stream_flush(pa.sink, NULL, NULL)); pa_stream_disconnect(pa.sink);
pa_stream_unref(pa.sink); pa_stream_unref(pa.sink);
pa.sink = NULL; pa.sink = NULL;
pa.sinkResamplerEnabled = false; pa.sinkIndex = PA_INVALID_INDEX;
pa.sinkRateFailed = false; pa.sinkResamplerEnabled = false;
pa.sinkNominalRate = 0; pa.sinkRateFailed = false;
pa.sinkAppliedRate = 0; pa.sinkNominalRate = 0;
pa.sinkPendingRate = 0; pa.sinkAppliedRate = 0;
pa.sinkRequestedRate = 0; pa.sinkPendingRate = 0;
pa.sinkRequestedRate = 0;
pa.sinkDeferredRate = 0;
pa.sinkNextRateUpdate = 0;
pa.sinkRateDeferredSince = 0;
pa.sinkRateUpdateArmed = false;
atomic_store_explicit( atomic_store_explicit(
&pa.sinkPresentationDeadline, 0, memory_order_release); &pa.sinkLatencyNs, 0, memory_order_release);
atomic_store_explicit(
&pa.sinkLatencyUpdateRequested, false, memory_order_relaxed);
} }
static void pulseaudio_free(void) static void pulseaudio_free(void)
{ {
if (!pa.loop)
return;
pa_threaded_mainloop_lock(pa.loop); pa_threaded_mainloop_lock(pa.loop);
pulseaudio_sink_close_nl(); pulseaudio_sink_close_nl();
pulseaudio_context_close_nl();
pa_context_set_state_callback(pa.context, NULL, NULL);
pa_context_set_subscribe_callback(pa.context, NULL, NULL);
pa_context_disconnect(pa.context);
pa_context_unref(pa.context);
if (pa.contextSub)
{
pa_operation_unref(pa.contextSub);
pa.contextSub = NULL;
}
pa_threaded_mainloop_unlock(pa.loop); pa_threaded_mainloop_unlock(pa.loop);
pulseaudio_reportErrors(true);
if (pa.loopStarted)
{
pa_threaded_mainloop_stop(pa.loop);
pa.loopStarted = false;
}
pa_threaded_mainloop_free(pa.loop);
pa.loop = NULL;
pa.api = NULL;
} }
static void pulseaudio_state_cb(pa_stream * p, void * userdata) static void pulseaudio_state_cb(pa_stream * p, void * userdata)
{ {
if (pa.sinkStarting && pa_stream_get_state(pa.sink) == PA_STREAM_READY) const pa_stream_state_t state = pa_stream_get_state(p);
{ if (p == pa.sink &&
pulseaudio_unrefOperation(pa_stream_cork(pa.sink, 0, NULL, NULL)); (state == PA_STREAM_FAILED || state == PA_STREAM_TERMINATED))
pa.sinkCorked = false; pulseaudio_sinkFailed_nl();
pa.sinkStarting = false;
}
pa_threaded_mainloop_signal(pa.loop, 0); pa_threaded_mainloop_signal(pa.loop, 0);
} }
static void pulseaudio_start_cb(
pa_stream * stream, int success, void * userdata)
{
const uint32_t serial = (uint32_t)(uintptr_t)userdata;
if (stream != pa.sink || serial != pa.sinkStartSerial)
return;
pa_operation * operation = pa.sinkStartOperation;
pa.sinkStartOperation = NULL;
if (operation)
pa_operation_unref(operation);
pulseaudio_cancelStartTimer_nl();
if (!success || pa_stream_get_state(stream) != PA_STREAM_READY ||
!pa.context ||
pa_context_get_state(pa.context) != PA_CONTEXT_READY)
{
pa.sinkCorked = true;
pulseaudio_sinkFailed_nl();
}
}
static void pulseaudio_startTimeout_cb(pa_mainloop_api * api,
pa_time_event * event, const struct timeval * tv, void * userdata)
{
(void)tv;
const uint32_t serial = (uint32_t)(uintptr_t)userdata;
if (event != pa.sinkStartTimer || serial != pa.sinkStartSerial)
return;
pa.sinkStartTimer = NULL;
api->time_free(event);
pa.sinkCorked = true;
pulseaudio_sinkFailed_nl();
}
static void pulseaudio_write_cb(pa_stream * p, size_t nbytes, void * userdata) static void pulseaudio_write_cb(pa_stream * p, size_t nbytes, void * userdata)
{ {
// PulseAudio tries to pull data from the stream as soon as it is created for // PulseAudio tries to pull data from the stream as soon as it is created for
@@ -403,11 +613,17 @@ static void pulseaudio_write_cb(pa_stream * p, size_t nbytes, void * userdata)
if (pa_stream_begin_write(p, (void **)&dst, &nbytes) < 0) if (pa_stream_begin_write(p, (void **)&dst, &nbytes) < 0)
{ {
DEBUG_ERROR("pa_stream_begin_write failed: %s", pulseaudio_noteError(&pa.sinkWriteErrors);
pa_strerror(pa_context_errno(pa.context)));
return; return;
} }
pa_usec_t latency = 0;
int negative = 0;
bool latencyValid = false;
if (atomic_exchange_explicit(
&pa.sinkLatencyUpdateRequested, false, memory_order_acquire))
latencyValid = pa_stream_get_latency(p, &latency, &negative) == 0;
int frames = nbytes / pa.sinkStride; int frames = nbytes / pa.sinkStride;
frames = pa.sinkPullFn(dst, frames); frames = pa.sinkPullFn(dst, frames);
if (frames <= 0) if (frames <= 0)
@@ -419,8 +635,7 @@ static void pulseaudio_write_cb(pa_stream * p, size_t nbytes, void * userdata)
if (pa_stream_write( if (pa_stream_write(
p, dst, frames * pa.sinkStride, NULL, 0, PA_SEEK_RELATIVE) < 0) p, dst, frames * pa.sinkStride, NULL, 0, PA_SEEK_RELATIVE) < 0)
{ {
DEBUG_ERROR("pa_stream_write failed: %s", pulseaudio_noteError(&pa.sinkWriteErrors);
pa_strerror(pa_context_errno(pa.context)));
pa_stream_cancel_write(p); pa_stream_cancel_write(p);
return; return;
} }
@@ -428,26 +643,24 @@ static void pulseaudio_write_cb(pa_stream * p, size_t nbytes, void * userdata)
/* Queue rate changes after the current audio block. This keeps the rate /* Queue rate changes after the current audio block. This keeps the rate
* reported by the preceding pull aligned with the block PulseAudio has * reported by the preceding pull aligned with the block PulseAudio has
* already received. */ * already received. */
pulseaudio_submitRateUpdate(); pulseaudio_submitRateUpdate(false);
pa_usec_t latency; if (latencyValid)
int negative;
if (pa_stream_get_latency(p, &latency, &negative) == 0)
{ {
const int64_t latencyNs = negative ? 0 : (int64_t)latency * 1000; const int64_t latencyNs = negative ? 0 : (int64_t)latency * 1000;
atomic_store_explicit(&pa.sinkPresentationDeadline, atomic_store_explicit(
(int64_t)nanotime() + latencyNs, memory_order_release); &pa.sinkLatencyNs, latencyNs, memory_order_release);
} }
} }
static void pulseaudio_underflow_cb(pa_stream * p, void * userdata) static void pulseaudio_underflow_cb(pa_stream * p, void * userdata)
{ {
DEBUG_WARN("Underflow"); pulseaudio_noteError(&pa.sinkUnderflows);
} }
static void pulseaudio_overflow_cb(pa_stream * p, void * userdata) static void pulseaudio_overflow_cb(pa_stream * p, void * userdata)
{ {
DEBUG_WARN("Overflow"); pulseaudio_noteError(&pa.sinkOverflows);
} }
static bool pulseaudio_setup(const LG_AudioFormat * format, static bool pulseaudio_setup(const LG_AudioFormat * format,
@@ -456,6 +669,7 @@ static bool pulseaudio_setup(const LG_AudioFormat * format,
LG_AudioPullFn pullFn) LG_AudioPullFn pullFn)
{ {
*resamplerEnabled = false; *resamplerEnabled = false;
pulseaudio_reportErrors(false);
const int channels = format->channelCount; const int channels = format->channelCount;
const int sampleRate = format->sampleRate; const int sampleRate = format->sampleRate;
@@ -488,10 +702,25 @@ static bool pulseaudio_setup(const LG_AudioFormat * format,
}; };
pa_threaded_mainloop_lock(pa.loop); pa_threaded_mainloop_lock(pa.loop);
if (!pa.context ||
pa_context_get_state(pa.context) != PA_CONTEXT_READY)
{
pulseaudio_sink_close_nl();
pulseaudio_context_close_nl();
if (!pulseaudio_contextConnect_nl())
{
pa_threaded_mainloop_unlock(pa.loop);
return false;
}
}
/* pa_stream_update_sample_rate requires protocol version 12. */ /* pa_stream_update_sample_rate requires protocol version 12. */
const bool enableResampler = requestResampler && const bool enableResampler = requestResampler &&
pa_context_get_server_protocol_version(pa.context) >= 12; pa_context_get_server_protocol_version(pa.context) >= 12;
if (pa.sink && !pa.sinkRateFailed && if (pa.sink && pa.context &&
pa_stream_get_state(pa.sink) == PA_STREAM_READY &&
pa_context_get_state(pa.context) == PA_CONTEXT_READY &&
!pa.sinkRateFailed &&
pa.sinkResamplerEnabled == enableResampler && pa.sinkResamplerEnabled == enableResampler &&
!pa.sinkRateOperation && !pa.sinkRateOperation &&
pa.sinkAppliedRate == pa.sinkNominalRate && pa.sinkAppliedRate == pa.sinkNominalRate &&
@@ -511,13 +740,16 @@ static bool pulseaudio_setup(const LG_AudioFormat * format,
pa.sinkStride = stride; pa.sinkStride = stride;
pa.sinkPullFn = pullFn; pa.sinkPullFn = pullFn;
pa.sinkCorked = true; pa.sinkCorked = true;
pa.sinkStarting = false;
pa.sinkResamplerEnabled = enableResampler; pa.sinkResamplerEnabled = enableResampler;
pa.sinkRateFailed = false; pa.sinkRateFailed = false;
pa.sinkNominalRate = sampleRate; pa.sinkNominalRate = sampleRate;
pa.sinkAppliedRate = sampleRate; pa.sinkAppliedRate = sampleRate;
pa.sinkPendingRate = sampleRate; pa.sinkPendingRate = sampleRate;
pa.sinkRequestedRate = sampleRate; pa.sinkRequestedRate = sampleRate;
pa.sinkDeferredRate = sampleRate;
pa.sinkNextRateUpdate = 0;
pa.sinkRateDeferredSince = 0;
pa.sinkRateUpdateArmed = false;
pa.sink = pa_stream_new( pa.sink = pa_stream_new(
pa.context, "Looking Glass", &spec, &channelMap); pa.context, "Looking Glass", &spec, &channelMap);
@@ -550,18 +782,44 @@ static bool pulseaudio_setup(const LG_AudioFormat * format,
return false; return false;
} }
while (pa_stream_get_state(pa.sink) == PA_STREAM_CREATING) pa_stream * sink = pa.sink;
pa_threaded_mainloop_wait(pa.loop); pa_context * context = pa.context;
struct PulseWait wait = {0};
if (pa_stream_get_state(pa.sink) != PA_STREAM_READY) pa_time_event * timer = pa_context_rttime_new(context,
pa_rtclock_now() + PULSEAUDIO_READY_TIMEOUT_US,
pulseaudio_timeout_cb, &wait);
if (!timer)
{ {
DEBUG_ERROR("PulseAudio stream did not become ready: %s", DEBUG_ERROR("Failed to create the PulseAudio stream setup timer");
pa_strerror(pa_context_errno(pa.context)));
pulseaudio_sink_close_nl(); pulseaudio_sink_close_nl();
pa_threaded_mainloop_unlock(pa.loop); pa_threaded_mainloop_unlock(pa.loop);
return false; return false;
} }
while (!wait.timedOut && pa.sink == sink &&
pa_stream_get_state(sink) == PA_STREAM_CREATING &&
pa.context == context &&
PA_CONTEXT_IS_GOOD(pa_context_get_state(context)))
pa_threaded_mainloop_wait(pa.loop);
const bool ready = pa.sink == sink && pa.context == context &&
pa_stream_get_state(sink) == PA_STREAM_READY &&
pa_context_get_state(context) == PA_CONTEXT_READY;
pa.api->time_free(timer);
if (!ready)
{
if (wait.timedOut)
DEBUG_ERROR("Timed out setting up the PulseAudio stream");
else
DEBUG_ERROR("PulseAudio stream did not become ready: %s",
pa_strerror(pa_context_errno(context)));
pulseaudio_sink_close_nl();
pa_threaded_mainloop_unlock(pa.loop);
return false;
}
pa.sinkIndex = pa_stream_get_index(pa.sink);
const pa_buffer_attr * actual = pa_stream_get_buffer_attr(pa.sink); const pa_buffer_attr * actual = pa_stream_get_buffer_attr(pa.sink);
const uint64_t minRequestFrames = const uint64_t minRequestFrames =
actual && actual->minreq != UINT32_MAX ? actual && actual->minreq != UINT32_MAX ?
@@ -584,79 +842,123 @@ static bool pulseaudio_setup(const LG_AudioFormat * format,
*resamplerEnabled = pa.sinkResamplerEnabled; *resamplerEnabled = pa.sinkResamplerEnabled;
atomic_store_explicit( atomic_store_explicit(
&pa.sinkPresentationDeadline, 0, memory_order_release); &pa.sinkLatencyNs, 0, memory_order_release);
atomic_store_explicit(
&pa.sinkLatencyUpdateRequested, false, memory_order_relaxed);
pa_threaded_mainloop_unlock(pa.loop); pa_threaded_mainloop_unlock(pa.loop);
return true; return true;
} }
static void pulseaudio_start(void) static bool pulseaudio_start(
LG_AudioFailureFn failureFn, uint32_t failureCookie)
{ {
if (!pa.sink) pulseaudio_reportErrors(false);
return; if (!pa.loop || pa_threaded_mainloop_in_thread(pa.loop))
return false;
pa_threaded_mainloop_lock(pa.loop); pa_threaded_mainloop_lock(pa.loop);
pa_stream_state_t state = pa_stream_get_state(pa.sink); pa_stream * sink = pa.sink;
if (state == PA_STREAM_CREATING) pa_context * context = pa.context;
pa.sinkStarting = true; if (!sink || !context ||
else pa_stream_get_state(sink) != PA_STREAM_READY ||
pa_context_get_state(context) != PA_CONTEXT_READY)
{ {
pulseaudio_unrefOperation(pa_stream_cork(pa.sink, 0, NULL, NULL)); pa_threaded_mainloop_unlock(pa.loop);
pa.sinkCorked = false; return false;
} }
pulseaudio_sinkDisarm_nl();
pa.sinkFailureFn = failureFn;
pa.sinkFailureCookie = failureCookie;
const uint32_t serial = pa.sinkStartSerial;
pa.sinkStartTimer = pa_context_rttime_new(context,
pa_rtclock_now() + PULSEAUDIO_OPERATION_TIMEOUT_US,
pulseaudio_startTimeout_cb, (void *)(uintptr_t)serial);
if (!pa.sinkStartTimer)
{
pulseaudio_sinkDisarm_nl();
pa_threaded_mainloop_unlock(pa.loop);
return false;
}
pa.sinkStartOperation = pa_stream_cork(sink, 0,
pulseaudio_start_cb, (void *)(uintptr_t)serial);
if (!pa.sinkStartOperation)
{
pulseaudio_sinkDisarm_nl();
pa_threaded_mainloop_unlock(pa.loop);
return false;
}
pa.sinkCorked = false;
pa_threaded_mainloop_unlock(pa.loop); pa_threaded_mainloop_unlock(pa.loop);
return true;
} }
static void pulseaudio_stop(void) static void pulseaudio_stop(void)
{ {
if (!pa.sink) if (!pa.loop)
return; return;
bool needLock = !pa_threaded_mainloop_in_thread(pa.loop); bool needLock = !pa_threaded_mainloop_in_thread(pa.loop);
if (needLock) if (needLock)
pa_threaded_mainloop_lock(pa.loop); pa_threaded_mainloop_lock(pa.loop);
pulseaudio_unrefOperation(pa_stream_cork(pa.sink, 1, NULL, NULL)); pulseaudio_sinkDisarm_nl();
pa.sinkCorked = true; if (!pa.sink)
pa.sinkStarting = false; {
if (needLock)
pa_threaded_mainloop_unlock(pa.loop);
return;
}
pa.sinkCorked = true;
if (pa_stream_get_state(pa.sink) == PA_STREAM_READY)
{
pulseaudio_trackControlOperation(
pa_stream_cork(pa.sink, 1, pulseaudio_streamControl_cb, NULL));
pulseaudio_trackControlOperation(
pa_stream_flush(pa.sink, pulseaudio_streamControl_cb, NULL));
}
if (pa.sinkResamplerEnabled) if (pa.sinkResamplerEnabled)
{ {
pa.sinkRequestedRate = pa.sinkNominalRate; pa.sinkRequestedRate = pa.sinkNominalRate;
pulseaudio_submitRateUpdate(); pulseaudio_submitRateUpdate(true);
} }
atomic_store_explicit( atomic_store_explicit(
&pa.sinkPresentationDeadline, 0, memory_order_release); &pa.sinkLatencyNs, 0, memory_order_release);
atomic_store_explicit(
&pa.sinkLatencyUpdateRequested, false, memory_order_relaxed);
if (needLock) if (needLock)
{
pa_threaded_mainloop_unlock(pa.loop); pa_threaded_mainloop_unlock(pa.loop);
pulseaudio_reportErrors(false);
}
} }
static void pulseaudio_volume(int channels, const uint16_t volume[]) static void pulseaudio_volume(int channels, const uint16_t volume[])
{ {
if (!pa.sink || !pa.sinkIndex)
return;
struct pa_cvolume v = { .channels = channels }; struct pa_cvolume v = { .channels = channels };
for(int i = 0; i < channels; ++i) for(int i = 0; i < channels; ++i)
v.values[i] = pa_sw_volume_from_linear( v.values[i] = pa_sw_volume_from_linear(
9.3234e-7 * pow(1.000211902, volume[i]) - 0.000172787); max(0.0,
9.3234e-7 * pow(1.000211902, volume[i]) - 0.000172787));
pa_threaded_mainloop_lock(pa.loop); pa_threaded_mainloop_lock(pa.loop);
pulseaudio_unrefOperation(pa_context_set_sink_input_volume( if (pa.sink && pa.sinkIndex != PA_INVALID_INDEX)
pa.context, pa.sinkIndex, &v, NULL, NULL)); pulseaudio_trackControlOperation(pa_context_set_sink_input_volume(
pa.context, pa.sinkIndex, &v, pulseaudio_contextControl_cb, NULL));
pa_threaded_mainloop_unlock(pa.loop); pa_threaded_mainloop_unlock(pa.loop);
} }
static void pulseaudio_mute(bool mute) static void pulseaudio_mute(bool mute)
{ {
if (!pa.sink || !pa.sinkIndex || pa.sinkMuted == mute)
return;
pa.sinkMuted = mute;
pa_threaded_mainloop_lock(pa.loop); pa_threaded_mainloop_lock(pa.loop);
pulseaudio_unrefOperation(pa_context_set_sink_input_mute( if (pa.sink && pa.sinkIndex != PA_INVALID_INDEX)
pa.context, pa.sinkIndex, mute, NULL, NULL)); pulseaudio_trackControlOperation(pa_context_set_sink_input_mute(
pa.context, pa.sinkIndex, mute, pulseaudio_contextControl_cb, NULL));
pa_threaded_mainloop_unlock(pa.loop); pa_threaded_mainloop_unlock(pa.loop);
} }
@@ -671,8 +973,53 @@ static bool pulseaudio_setRate(double * ratio)
return false; return false;
pa.sinkRequestedRate = (uint32_t)llround(requestedRate); pa.sinkRequestedRate = (uint32_t)llround(requestedRate);
const uint32_t scheduledRate = pa.sinkRateOperation ?
pa.sinkPendingRate : pa.sinkRequestedRate; uint32_t scheduledRate;
if (pa.sinkRateOperation)
{
scheduledRate = pa.sinkPendingRate;
if (pa.sinkRequestedRate == scheduledRate)
pa.sinkRateDeferredSince = 0;
else if (!pa.sinkRateDeferredSince ||
pa.sinkDeferredRate != pa.sinkRequestedRate)
{
pa.sinkDeferredRate = pa.sinkRequestedRate;
pa.sinkRateDeferredSince = (int64_t)nanotime();
}
}
else
{
scheduledRate = pa.sinkAppliedRate;
pa.sinkRateUpdateArmed = false;
if (pa.sinkRequestedRate == scheduledRate)
pa.sinkRateDeferredSince = 0;
else
{
const int64_t now = (int64_t)nanotime();
if (!pa.sinkRateDeferredSince ||
pa.sinkDeferredRate != pa.sinkRequestedRate)
{
pa.sinkDeferredRate = pa.sinkRequestedRate;
pa.sinkRateDeferredSince = now;
}
const uint32_t difference = pa.sinkRequestedRate > scheduledRate ?
pa.sinkRequestedRate - scheduledRate :
scheduledRate - pa.sinkRequestedRate;
/* Coalesce controller noise around an integer-Hz boundary while still
* applying a persistent one-Hz correction. Larger corrections only
* wait for the operation-rate limit. */
if (now >= pa.sinkNextRateUpdate &&
(difference > PULSEAUDIO_RATE_UPDATE_DEADBAND_HZ ||
now - pa.sinkRateDeferredSince >=
PULSEAUDIO_RATE_UPDATE_MAX_HOLD_NS))
{
pa.sinkRateUpdateArmed = true;
scheduledRate = pa.sinkRequestedRate;
}
}
}
if (!scheduledRate) if (!scheduledRate)
return false; return false;
@@ -682,12 +1029,15 @@ static bool pulseaudio_setRate(double * ratio)
static uint64_t pulseaudio_latency(void) static uint64_t pulseaudio_latency(void)
{ {
const int64_t deadline = atomic_load_explicit( atomic_store_explicit(
&pa.sinkPresentationDeadline, memory_order_acquire); &pa.sinkLatencyUpdateRequested, true, memory_order_release);
if (deadline <= 0)
const int64_t latencyNs = atomic_load_explicit(
&pa.sinkLatencyNs, memory_order_acquire);
if (latencyNs <= 0)
return 0; return 0;
return max(INT64_C(0), deadline - (int64_t)nanotime()) / 1000; return latencyNs / 1000;
} }
struct LG_AudioDevOps LGAD_PulseAudio = struct LG_AudioDevOps LGAD_PulseAudio =

View File

@@ -106,7 +106,8 @@ typedef struct LG_AudioEventOps
{ {
/* Format and clock pointers are borrowed for the duration of each call. /* Format and clock pointers are borrowed for the duration of each call.
* A NULL source clock indicates that the provider has no usable clock. * A NULL source clock indicates that the provider has no usable clock.
* Providers must serialize event delivery for an attachment. Stream * Providers must serialize event delivery within each stream direction.
* Playback and recording events may be delivered concurrently. Stream
* generations are nonzero and uniquely identify each stream instance. */ * generations are nonzero and uniquely identify each stream instance. */
void (*playbackStart)(void * opaque, uint32_t generation, void (*playbackStart)(void * opaque, uint32_t generation,
const LG_AudioFormat * format, const LG_AudioClock * sourceClock); const LG_AudioFormat * format, const LG_AudioClock * sourceClock);
@@ -163,7 +164,9 @@ typedef struct LG_AudioOps
* audio callback with the measured playback device clock. Its position uses * audio callback with the measured playback device clock. Its position uses
* the device's independent output-frame timeline and its time includes the * the device's independent output-frame timeline and its time includes the
* backend's estimated presentation latency. targetRate is the source frame * backend's estimated presentation latency. targetRate is the source frame
* rate requested by the client's buffer controller. */ * rate requested by the client's buffer controller. Return false while
* active rate feedback is unavailable; sustained failure makes the client
* restart playback with local rate control. */
bool (*clockFeedback)(void * opaque, uint32_t generation, bool (*clockFeedback)(void * opaque, uint32_t generation,
const LG_AudioClock * playbackClock, double targetRate); const LG_AudioClock * playbackClock, double targetRate);
} }

View File

@@ -28,7 +28,9 @@
#include "interface/audio.h" #include "interface/audio.h"
typedef int (*LG_AudioPullFn)(uint8_t * dst, int frames); typedef int (*LG_AudioPullFn)(uint8_t * dst, int frames);
typedef void (*LG_AudioPushFn)(uint8_t * src, int frames); typedef bool (*LG_AudioPushFn)(uint8_t * src, int frames,
const LG_AudioClock * sourceClock);
typedef void (*LG_AudioFailureFn)(uint32_t cookie);
struct LG_AudioDevOps struct LG_AudioDevOps
{ {
@@ -55,10 +57,13 @@ struct LG_AudioDevOps
bool requestResampler, bool * resamplerEnabled, bool requestResampler, bool * resamplerEnabled,
int * maxPeriodFrames, int * startFrames, LG_AudioPullFn pullFn); int * maxPeriodFrames, int * startFrames, LG_AudioPullFn pullFn);
/* called when there is data available to start playback */ /* Called when there is data available to start playback. failureFn may
void (*start)(void); * report an asynchronous failure using the supplied cookie. Returning
* false means the failure callback is already quiescent. */
bool (*start)(LG_AudioFailureFn failureFn, uint32_t cookie);
/* called when the source reports the audio stream has stopped */ /* Called when the source reports the audio stream has stopped. This must
* synchronously quiesce the failure callback before returning. */
void (*stop)(void); void (*stop)(void);
/* [optional] called to set the volume of the channels */ /* [optional] called to set the volume of the channels */
@@ -80,10 +85,17 @@ struct LG_AudioDevOps
struct struct
{ {
/* start the record stream using the requested interleaved format */ /* Start the record stream using the requested interleaved format.
void (*start)(const LG_AudioFormat * format, LG_AudioPushFn pushFn); * sourceClock is borrowed for the duration of pushFn and describes the
* first frame. It is NULL when the backend has no source clock. pushFn
* returns false when the active provider rejects the frames.
* failureFn may report an asynchronous failure using the supplied cookie.
* Returning false means both callbacks are already quiescent. */
bool (*start)(const LG_AudioFormat * format, LG_AudioPushFn pushFn,
LG_AudioFailureFn failureFn, uint32_t cookie);
/* called when the source reports the audio stream has stopped */ /* Called when the source reports the audio stream has stopped. This must
* synchronously quiesce both callbacks before returning. */
void (*stop)(void); void (*stop)(void);
/* [optional] called to set the volume of the channels */ /* [optional] called to set the volume of the channels */

File diff suppressed because it is too large Load Diff

View File

@@ -21,6 +21,7 @@
#include "audio_spice.h" #include "audio_spice.h"
#include "common/debug.h" #include "common/debug.h"
#include "common/event.h"
#include "common/locking.h" #include "common/locking.h"
#include <stdatomic.h> #include <stdatomic.h>
@@ -50,6 +51,22 @@ typedef struct SpiceAudioEventTarget
} }
SpiceAudioEventTarget; SpiceAudioEventTarget;
typedef struct SpiceAudioCallbackWaiter
{
struct SpiceAudioCallbackWaiter * next;
LGEvent * event;
unsigned int depth;
}
SpiceAudioCallbackWaiter;
typedef struct SpiceAudioCallbackWaitQueue
{
LG_Lock lock;
atomic_uint count;
SpiceAudioCallbackWaiter * waiters;
}
SpiceAudioCallbackWaitQueue;
static struct static struct
{ {
LG_RWLock lock; LG_RWLock lock;
@@ -59,11 +76,13 @@ static struct
LG_AudioStatusFn statusCallback; LG_AudioStatusFn statusCallback;
void * statusOpaque; void * statusOpaque;
atomic_uint statusInFlight; atomic_uint statusInFlight;
SpiceAudioCallbackWaitQueue statusWait;
const LG_AudioEventOps * events; const LG_AudioEventOps * events;
void * eventOpaque; void * eventOpaque;
uint32_t eventGeneration; uint32_t eventGeneration;
atomic_uint inFlight; atomic_uint inFlight;
SpiceAudioCallbackWaitQueue eventWait;
SpiceAudioStream playback; SpiceAudioStream playback;
SpiceAudioStream record; SpiceAudioStream record;
@@ -83,7 +102,17 @@ l_spice =
.writer = ATOMIC_FLAG_INIT, .writer = ATOMIC_FLAG_INIT,
}, },
.statusInFlight = ATOMIC_VAR_INIT(0), .statusInFlight = ATOMIC_VAR_INIT(0),
.statusWait =
{
.lock = ATOMIC_FLAG_INIT,
.count = ATOMIC_VAR_INIT(0),
},
.inFlight = ATOMIC_VAR_INIT(0), .inFlight = ATOMIC_VAR_INIT(0),
.eventWait =
{
.lock = ATOMIC_FLAG_INIT,
.count = ATOMIC_VAR_INIT(0),
},
}; };
static _Thread_local unsigned int l_eventDepth; static _Thread_local unsigned int l_eventDepth;
@@ -96,6 +125,96 @@ static uint32_t nextGeneration(uint32_t generation)
return generation; return generation;
} }
static LGEvent * createWaitEvent(void)
{
LGEvent * event = lgCreateEvent(true, 0);
if (!event)
DEBUG_FATAL("Failed to create SPICE audio wait event");
return event;
}
static void waitEvent(LGEvent * event)
{
if (!lgWaitEvent(event, TIMEOUT_INFINITE))
DEBUG_FATAL("Failed to wait for SPICE audio event");
}
static void signalWaitEvent(LGEvent * event)
{
if (!lgSignalEvent(event))
DEBUG_FATAL("Failed to signal SPICE audio event");
}
static void signalCallbackWaiters(
SpiceAudioCallbackWaitQueue * queue, unsigned int remaining)
{
if (!atomic_load_explicit(&queue->count, memory_order_acquire))
return;
LG_LOCK(queue->lock);
SpiceAudioCallbackWaiter ** link = &queue->waiters;
while (*link)
{
SpiceAudioCallbackWaiter * 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 endCallback(atomic_uint * inFlight,
SpiceAudioCallbackWaitQueue * waitQueue)
{
const unsigned int previous = atomic_fetch_sub_explicit(
inFlight, 1, memory_order_release);
DEBUG_ASSERT(previous > 0);
signalCallbackWaiters(waitQueue, previous - 1);
}
static void waitCallbacks(atomic_uint * inFlight,
SpiceAudioCallbackWaitQueue * waitQueue, unsigned int depth)
{
if (atomic_load_explicit(inFlight, memory_order_acquire) <= depth)
return;
SpiceAudioCallbackWaiter waiter =
{
.event = createWaitEvent(),
.depth = depth,
};
bool queued = false;
LG_LOCK(waitQueue->lock);
if (atomic_load_explicit(inFlight, memory_order_acquire) > depth)
{
waiter.next = waitQueue->waiters;
waitQueue->waiters = &waiter;
atomic_fetch_add_explicit(
&waitQueue->count, 1, memory_order_release);
queued = true;
}
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);
}
/* l_spice.lock must be held exclusively while admitting a callback so detach /* l_spice.lock must be held exclusively while admitting a callback so detach
* cannot invalidate the target between the snapshot and in-flight increment. */ * cannot invalidate the target between the snapshot and in-flight increment. */
static bool beginEventNL(SpiceAudioEventTarget * target) static bool beginEventNL(SpiceAudioEventTarget * target)
@@ -142,7 +261,7 @@ static bool recordEventCurrent(const SpiceAudioEventTarget * target,
static void endEvent(void) static void endEvent(void)
{ {
--l_eventDepth; --l_eventDepth;
atomic_fetch_sub_explicit(&l_spice.inFlight, 1, memory_order_release); endCallback(&l_spice.inFlight, &l_spice.eventWait);
} }
static bool sampleFormat(PSAudioFormat source, static bool sampleFormat(PSAudioFormat source,
@@ -318,13 +437,11 @@ static void spiceSetStatusListener(void * opaque,
{ {
callback(callbackOpaque, &status); callback(callbackOpaque, &status);
--l_statusDepth; --l_statusDepth;
atomic_fetch_sub_explicit( endCallback(&l_spice.statusInFlight, &l_spice.statusWait);
&l_spice.statusInFlight, 1, memory_order_release);
} }
else else
while (atomic_load_explicit( waitCallbacks(&l_spice.statusInFlight,
&l_spice.statusInFlight, memory_order_acquire) > l_statusDepth) &l_spice.statusWait, l_statusDepth);
;
} }
static bool spiceAttach(void * opaque, const LG_AudioEventOps * events, static bool spiceAttach(void * opaque, const LG_AudioEventOps * events,
@@ -413,9 +530,7 @@ static void spiceDetach(void * opaque)
nextGeneration(l_spice.eventGeneration); nextGeneration(l_spice.eventGeneration);
LG_UNLOCK_EXCLUSIVE(l_spice.lock); LG_UNLOCK_EXCLUSIVE(l_spice.lock);
while (atomic_load_explicit( waitCallbacks(&l_spice.inFlight, &l_spice.eventWait, l_eventDepth);
&l_spice.inFlight, memory_order_acquire) > l_eventDepth)
;
} }
static bool spiceRecordData(void * opaque, uint32_t generation, static bool spiceRecordData(void * opaque, uint32_t generation,
@@ -501,8 +616,7 @@ void lgaSpice_setAvailable(bool available)
{ {
callback(callbackOpaque, &status); callback(callbackOpaque, &status);
--l_statusDepth; --l_statusDepth;
atomic_fetch_sub_explicit( endCallback(&l_spice.statusInFlight, &l_spice.statusWait);
&l_spice.statusInFlight, 1, memory_order_release);
} }
} }

View File

@@ -22,11 +22,15 @@
#include "usb_audio.h" #include "usb_audio.h"
#include "common/debug.h"
#include "common/event.h"
#include "common/locking.h" #include "common/locking.h"
#include "common/time.h" #include "common/time.h"
#include <stdalign.h>
#include <stdatomic.h> #include <stdatomic.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h>
#define USB_AUDIO_NS_PER_SECOND INT64_C(1000000000) #define USB_AUDIO_NS_PER_SECOND INT64_C(1000000000)
@@ -37,49 +41,94 @@ typedef struct USBAudioCallbackFrame
} }
USBAudioCallbackFrame; 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 typedef struct USBAudioEventTarget
{ {
const LG_AudioEventOps * events; const LG_AudioEventOps * events;
void * opaque; void * opaque;
uint32_t attachmentGeneration; uint32_t attachmentGeneration;
USBAudioCallbackFrame frame; USBAudioCallbackFrame frame;
USBAudioCallbackFrame ** frames;
USBAudioCallbackGate * gate;
} }
USBAudioEventTarget; 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 struct LGA_USBState
{ {
LG_USBAudio * device; LG_USBAudio * device;
LG_USBRedir * redir; LG_USBRedir * redir;
LG_Lock statusLock; LG_Lock statusLock;
atomic_bool available; atomic_bool available;
uint32_t statusGeneration; uint32_t statusGeneration;
LG_AudioStatusFn statusCallback; LG_AudioStatusFn statusCallback;
void * statusOpaque; void * statusOpaque;
atomic_uint statusInFlight; atomic_uint statusInFlight;
atomic_uint_fast64_t statusNextTicket; USBAudioCallbackWaitQueue statusWait;
atomic_uint_fast64_t statusServingTicket; USBAudioOperationQueue statusOperation;
LG_Lock stateLock; LG_Lock stateLock;
bool attached; LGEvent * detachEvent;
bool detaching; bool attached;
uint32_t attachmentGeneration; bool detaching;
const LG_AudioEventOps * events; uint32_t attachmentGeneration;
void * eventOpaque; const LG_AudioEventOps * events;
void * eventOpaque;
LG_AudioFormat playbackFormat; LG_AudioFormat playbackFormat;
uint32_t playbackGeneration; uint32_t playbackGeneration;
LG_AudioFormat recordFormat; LG_AudioFormat recordFormat;
uint32_t recordGeneration; uint32_t recordGeneration;
uint32_t generationSerial; uint32_t generationSerial;
int64_t playbackClockOrigin; int64_t playbackClockOrigin;
atomic_uint_fast64_t playbackPosition; atomic_uint_fast64_t playbackPosition;
atomic_uint playbackDeliveryGeneration; atomic_uint playbackDeliveryGeneration;
atomic_uint recordDeliveryGeneration; atomic_uint recordDeliveryGeneration;
atomic_uint inFlight; alignas(64) USBAudioCallbackGate playbackGate;
alignas(64) USBAudioCallbackGate recordGate;
USBAudioOperationQueue recordOperation;
}; };
static _Thread_local USBAudioCallbackFrame * l_eventFrames; static _Thread_local USBAudioCallbackFrame * l_playbackFrames;
static _Thread_local USBAudioCallbackFrame * l_recordFrames;
static _Thread_local USBAudioCallbackFrame * l_statusFrames; static _Thread_local USBAudioCallbackFrame * l_statusFrames;
static const LG_AudioFormat l_formatTemplate = static const LG_AudioFormat l_formatTemplate =
@@ -139,6 +188,51 @@ static unsigned int callbackDepth(
return depth; 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, static void beginCallback(LGA_USBState * state,
USBAudioCallbackFrame * frame, USBAudioCallbackFrame ** frames, USBAudioCallbackFrame * frame, USBAudioCallbackFrame ** frames,
atomic_uint * inFlight) atomic_uint * inFlight)
@@ -150,10 +244,59 @@ static void beginCallback(LGA_USBState * state,
} }
static void endCallback(USBAudioCallbackFrame * frame, static void endCallback(USBAudioCallbackFrame * frame,
USBAudioCallbackFrame ** frames, atomic_uint * inFlight) USBAudioCallbackFrame ** frames, atomic_uint * inFlight,
USBAudioCallbackWaitQueue * waitQueue)
{ {
*frames = frame->previous; *frames = frame->previous;
atomic_fetch_sub_explicit(inFlight, 1, memory_order_seq_cst); 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( static LG_AudioClock makePlaybackClock(
@@ -178,7 +321,8 @@ static LG_AudioClock makePlaybackClock(
/* stateLock must be held while admitting a control event. The data event /* stateLock must be held while admitting a control event. The data event
* path performs the equivalent admission using deliveryGeneration. */ * path performs the equivalent admission using deliveryGeneration. */
static bool beginEventNL( static bool beginEventNL(
LGA_USBState * state, USBAudioEventTarget * target) LGA_USBState * state, USBAudioEventTarget * target,
USBAudioCallbackFrame ** frames, USBAudioCallbackGate * gate)
{ {
if (!state->attached || !state->events) if (!state->attached || !state->events)
return false; return false;
@@ -186,11 +330,27 @@ static bool beginEventNL(
target->events = state->events; target->events = state->events;
target->opaque = state->eventOpaque; target->opaque = state->eventOpaque;
target->attachmentGeneration = state->attachmentGeneration; target->attachmentGeneration = state->attachmentGeneration;
target->frames = frames;
target->gate = gate;
beginCallback( beginCallback(
state, &target->frame, &l_eventFrames, &state->inFlight); state, &target->frame, frames, &gate->inFlight);
return true; 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, static bool attachmentCurrentNL(const LGA_USBState * state,
const USBAudioEventTarget * target) const USBAudioEventTarget * target)
{ {
@@ -214,37 +374,84 @@ static bool recordEventCurrentNL(const LGA_USBState * state,
state->recordGeneration == streamGeneration; state->recordGeneration == streamGeneration;
} }
static void endEvent(LGA_USBState * state, USBAudioEventTarget * target) static void endEvent(USBAudioEventTarget * target)
{ {
endCallback(&target->frame, &l_eventFrames, &state->inFlight); endCallback(&target->frame, target->frames,
&target->gate->inFlight, &target->gate->wait);
} }
static void waitEvents(const LGA_USBState * state) static void waitPlaybackEvents(LGA_USBState * state)
{ {
const unsigned int depth = callbackDepth(l_eventFrames, state); waitCallbacks(state, l_playbackFrames,
while (atomic_load_explicit( &state->playbackGate.inFlight, &state->playbackGate.wait);
&state->inFlight, memory_order_seq_cst) > depth)
;
} }
static bool beginStatusOperation(LGA_USBState * state) static void waitRecordEvents(LGA_USBState * state)
{ {
if (callbackDepth(l_statusFrames, 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; return false;
const uint_fast64_t ticket = atomic_fetch_add_explicit( USBAudioOperationWaiter waiter =
&state->statusNextTicket, 1, memory_order_relaxed); {
while (atomic_load_explicit( .event = createWaitEvent(),
&state->statusServingTicket, memory_order_acquire) != ticket) };
;
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; return true;
} }
static void endStatusOperation(LGA_USBState * state, bool owner) static void endOperation(
USBAudioOperationQueue * operation, bool owner)
{ {
if (owner) if (!owner)
atomic_fetch_add_explicit( return;
&state->statusServingTicket, 1, memory_order_release);
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( static void beginStatusCallback(
@@ -257,15 +464,32 @@ static void beginStatusCallback(
static void endStatusCallback( static void endStatusCallback(
LGA_USBState * state, USBAudioCallbackFrame * frame) LGA_USBState * state, USBAudioCallbackFrame * frame)
{ {
endCallback(frame, &l_statusFrames, &state->statusInFlight); endCallback(frame, &l_statusFrames,
&state->statusInFlight, &state->statusWait);
} }
static void waitStatusCallbacks(const LGA_USBState * state) static void waitStatusCallbacks(LGA_USBState * state)
{ {
const unsigned int depth = callbackDepth(l_statusFrames, state); waitCallbacks(state, l_statusFrames,
while (atomic_load_explicit( &state->statusInFlight, &state->statusWait);
&state->statusInFlight, memory_order_acquire) > depth) }
;
/* 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( static void usbPlaybackStart(
@@ -294,10 +518,10 @@ static void usbPlaybackStart(
atomic_store_explicit( atomic_store_explicit(
&state->playbackDeliveryGeneration, 0, memory_order_seq_cst); &state->playbackDeliveryGeneration, 0, memory_order_seq_cst);
clock = makePlaybackClock(state, 0); clock = makePlaybackClock(state, 0);
const bool admitted = beginEventNL(state, &target); const bool admitted = beginPlaybackEventNL(state, &target);
dispatch = admitted && target.events->playbackStart; dispatch = admitted && target.events->playbackStart;
if (admitted && !dispatch) if (admitted && !dispatch)
endEvent(state, &target); endEvent(&target);
LG_UNLOCK(state->stateLock); LG_UNLOCK(state->stateLock);
if (!dispatch) if (!dispatch)
@@ -311,7 +535,7 @@ static void usbPlaybackStart(
atomic_store_explicit(&state->playbackDeliveryGeneration, 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(&target);
} }
static void usbPlaybackStop(void * opaque) static void usbPlaybackStop(void * opaque)
@@ -330,10 +554,10 @@ static void usbPlaybackStop(void * opaque)
state->playbackGeneration = 0; state->playbackGeneration = 0;
atomic_store_explicit( atomic_store_explicit(
&state->playbackDeliveryGeneration, 0, memory_order_seq_cst); &state->playbackDeliveryGeneration, 0, memory_order_seq_cst);
const bool admitted = beginEventNL(state, &target); const bool admitted = beginPlaybackEventNL(state, &target);
LG_UNLOCK(state->stateLock); LG_UNLOCK(state->stateLock);
waitEvents(state); waitPlaybackEvents(state);
if (!admitted) if (!admitted)
return; return;
@@ -345,7 +569,7 @@ static void usbPlaybackStop(void * opaque)
if (dispatch) if (dispatch)
target.events->playbackStop(target.opaque, generation); target.events->playbackStop(target.opaque, generation);
endEvent(state, &target); endEvent(&target);
} }
static void usbPlaybackData( static void usbPlaybackData(
@@ -353,7 +577,8 @@ static void usbPlaybackData(
{ {
LGA_USBState * state = opaque; LGA_USBState * state = opaque;
USBAudioCallbackFrame frame; USBAudioCallbackFrame frame;
beginCallback(state, &frame, &l_eventFrames, &state->inFlight); beginCallback(state, &frame, &l_playbackFrames,
&state->playbackGate.inFlight);
const uint64_t position = atomic_fetch_add_explicit( const uint64_t position = atomic_fetch_add_explicit(
&state->playbackPosition, frames, memory_order_relaxed); &state->playbackPosition, frames, memory_order_relaxed);
@@ -370,7 +595,8 @@ static void usbPlaybackData(
target, generation, data, frames, &clock); target, generation, data, frames, &clock);
} }
} }
endCallback(&frame, &l_eventFrames, &state->inFlight); endCallback(&frame, &l_playbackFrames,
&state->playbackGate.inFlight, &state->playbackGate.wait);
} }
static void usbRecordStart( static void usbRecordStart(
@@ -378,11 +604,15 @@ static void usbRecordStart(
{ {
LGA_USBState * state = opaque; LGA_USBState * state = opaque;
USBAudioEventTarget target; USBAudioEventTarget target;
LG_AudioFormat format;
bool dispatch; bool dispatch;
uint32_t generation; uint32_t generation;
const bool recordOwner = beginOperation(
state, &state->recordOperation, l_recordFrames);
LG_LOCK(state->stateLock); LG_LOCK(state->stateLock);
setStreamFormat(&state->recordFormat, sampleRate, channelMask); setStreamFormat(&state->recordFormat, sampleRate, channelMask);
format = state->recordFormat;
generation = state->recordGeneration; generation = state->recordGeneration;
if (!generation) if (!generation)
{ {
@@ -392,49 +622,58 @@ static void usbRecordStart(
} }
atomic_store_explicit( atomic_store_explicit(
&state->recordDeliveryGeneration, 0, memory_order_seq_cst); &state->recordDeliveryGeneration, 0, memory_order_seq_cst);
const bool admitted = beginEventNL(state, &target); const bool admitted = beginRecordEventNL(state, &target);
dispatch = admitted && target.events->recordStart; dispatch = admitted && target.events->recordStart;
if (dispatch)
atomic_store_explicit(&state->recordDeliveryGeneration,
generation, memory_order_seq_cst);
if (admitted && !dispatch) if (admitted && !dispatch)
endEvent(state, &target); endEvent(&target);
LG_UNLOCK(state->stateLock); LG_UNLOCK(state->stateLock);
if (!dispatch) if (!dispatch)
{
endOperation(&state->recordOperation, recordOwner);
return; return;
}
target.events->recordStart( target.events->recordStart(
target.opaque, generation, &state->recordFormat); target.opaque, generation, &format);
endEvent(&target);
LG_LOCK(state->stateLock); endOperation(&state->recordOperation, recordOwner);
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) static void usbRecordStop(void * opaque)
{ {
LGA_USBState * state = opaque; LGA_USBState * state = opaque;
USBAudioEventTarget target; USBAudioEventTarget target;
const bool recordOwner = beginOperation(
state, &state->recordOperation, l_recordFrames);
LG_LOCK(state->stateLock); LG_LOCK(state->stateLock);
const uint32_t generation = state->recordGeneration; const uint32_t generation = state->recordGeneration;
if (!generation) if (!generation)
{ {
LG_UNLOCK(state->stateLock); LG_UNLOCK(state->stateLock);
endOperation(&state->recordOperation, recordOwner);
return; return;
} }
state->recordGeneration = 0; state->recordGeneration = 0;
atomic_store_explicit( atomic_store_explicit(
&state->recordDeliveryGeneration, 0, memory_order_seq_cst); &state->recordDeliveryGeneration, 0, memory_order_seq_cst);
const bool admitted = beginEventNL(state, &target); const bool admitted = beginRecordEventNL(state, &target);
LG_UNLOCK(state->stateLock); LG_UNLOCK(state->stateLock);
waitEvents(state); /* A reentrant stop cannot quiesce callbacks for the same reason that it
* cannot wait for ownership above. */
waitRecordEvents(state);
if (!admitted) if (!admitted)
{
endOperation(&state->recordOperation, recordOwner);
return; return;
}
LG_LOCK(state->stateLock); LG_LOCK(state->stateLock);
const bool dispatch = attachmentCurrentNL(state, &target) && const bool dispatch = attachmentCurrentNL(state, &target) &&
@@ -443,7 +682,8 @@ static void usbRecordStop(void * opaque)
if (dispatch) if (dispatch)
target.events->recordStop(target.opaque, generation); target.events->recordStop(target.opaque, generation);
endEvent(state, &target); endEvent(&target);
endOperation(&state->recordOperation, recordOwner);
} }
static const LG_USBAudioEventOps l_usbAudioEvents = static const LG_USBAudioEventOps l_usbAudioEvents =
@@ -458,7 +698,8 @@ static const LG_USBAudioEventOps l_usbAudioEvents =
static void usbSetAvailable(void * opaque, bool available) static void usbSetAvailable(void * opaque, bool available)
{ {
LGA_USBState * state = opaque; LGA_USBState * state = opaque;
const bool statusOwner = beginStatusOperation(state); const bool statusOwner = beginOperation(
state, &state->statusOperation, l_statusFrames);
LG_LOCK(state->statusLock); LG_LOCK(state->statusLock);
const bool changed = atomic_load_explicit( const bool changed = atomic_load_explicit(
@@ -488,14 +729,15 @@ static void usbSetAvailable(void * opaque, bool available)
callback(callbackOpaque, &status); callback(callbackOpaque, &status);
endStatusCallback(state, &frame); endStatusCallback(state, &frame);
} }
endStatusOperation(state, statusOwner); endOperation(&state->statusOperation, statusOwner);
} }
static void usbSetStatusListener(void * opaque, static void usbSetStatusListener(void * opaque,
LG_AudioStatusFn callback, void * callbackOpaque) LG_AudioStatusFn callback, void * callbackOpaque)
{ {
LGA_USBState * state = opaque; LGA_USBState * state = opaque;
const bool statusOwner = beginStatusOperation(state); const bool statusOwner = beginOperation(
state, &state->statusOperation, l_statusFrames);
LG_LOCK(state->statusLock); LG_LOCK(state->statusLock);
state->statusCallback = callback; state->statusCallback = callback;
@@ -518,7 +760,7 @@ static void usbSetStatusListener(void * opaque,
} }
else else
waitStatusCallbacks(state); waitStatusCallbacks(state);
endStatusOperation(state, statusOwner); endOperation(&state->statusOperation, statusOwner);
} }
static bool usbAttach(void * opaque, const LG_AudioEventOps * events, static bool usbAttach(void * opaque, const LG_AudioEventOps * events,
@@ -529,25 +771,21 @@ static bool usbAttach(void * opaque, const LG_AudioEventOps * events,
&state->available, memory_order_acquire)) &state->available, memory_order_acquire))
return false; return false;
USBAudioEventTarget target; USBAudioEventTarget playbackTarget;
USBAudioEventTarget recordTarget;
LG_AudioFormat playbackFormat; LG_AudioFormat playbackFormat;
LG_AudioFormat recordFormat; LG_AudioFormat recordFormat;
LG_AudioClock playbackClock; LG_AudioClock playbackClock;
uint32_t attachmentGeneration;
uint32_t playbackGeneration; uint32_t playbackGeneration;
uint32_t recordGeneration; uint32_t recordGeneration;
bool playbackDispatch; bool playbackDispatch;
bool recordDispatch; bool recordDispatch;
bool admitted; bool playbackAdmitted;
bool recordAdmitted;
for (;;) if (!lockStateAfterDetach(state))
{ return false;
LG_LOCK(state->stateLock);
if (!state->detaching)
break;
LG_UNLOCK(state->stateLock);
if (callbackDepth(l_eventFrames, state))
return false;
}
if (state->attached || !atomic_load_explicit( if (state->attached || !atomic_load_explicit(
&state->available, memory_order_acquire)) &state->available, memory_order_acquire))
@@ -561,61 +799,72 @@ 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;
attachmentGeneration = state->attachmentGeneration;
playbackGeneration = state->playbackGeneration; playbackGeneration = state->playbackGeneration;
recordGeneration = state->recordGeneration; recordGeneration = state->recordGeneration;
playbackDispatch = playbackGeneration && events->playbackStart; playbackDispatch = playbackGeneration && events->playbackStart;
recordDispatch = recordGeneration && events->recordStart; recordDispatch = recordGeneration && events->recordStart;
admitted = (playbackDispatch || recordDispatch) && playbackAdmitted = playbackDispatch &&
beginEventNL(state, &target); beginPlaybackEventNL(state, &playbackTarget);
playbackDispatch = playbackDispatch && admitted; playbackDispatch = playbackDispatch && playbackAdmitted;
recordDispatch = recordDispatch && admitted; recordAdmitted = false;
if (playbackDispatch) if (playbackDispatch)
{ {
playbackFormat = state->playbackFormat; playbackFormat = state->playbackFormat;
playbackClock = makePlaybackClock(state, atomic_load_explicit( playbackClock = makePlaybackClock(state, atomic_load_explicit(
&state->playbackPosition, memory_order_relaxed)); &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 (playbackDispatch) if (playbackDispatch)
{ {
target.events->playbackStart( playbackTarget.events->playbackStart(
target.opaque, playbackGeneration, playbackTarget.opaque, playbackGeneration,
&playbackFormat, &playbackClock); &playbackFormat, &playbackClock);
LG_LOCK(state->stateLock); LG_LOCK(state->stateLock);
if (playbackEventCurrentNL( if (playbackEventCurrentNL(
state, &target, playbackGeneration)) state, &playbackTarget, playbackGeneration))
atomic_store_explicit(&state->playbackDeliveryGeneration, atomic_store_explicit(&state->playbackDeliveryGeneration,
playbackGeneration, memory_order_seq_cst); playbackGeneration, memory_order_seq_cst);
LG_UNLOCK(state->stateLock); LG_UNLOCK(state->stateLock);
} }
if (playbackAdmitted)
endEvent(&playbackTarget);
if (recordDispatch) if (recordDispatch)
{ {
const bool recordOwner = beginOperation(
state, &state->recordOperation, l_recordFrames);
LG_LOCK(state->stateLock); LG_LOCK(state->stateLock);
recordDispatch = recordEventCurrentNL( recordDispatch = state->attached &&
state, &target, recordGeneration); 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); LG_UNLOCK(state->stateLock);
if (recordDispatch) if (recordDispatch)
target.events->recordStart( recordTarget.events->recordStart(
target.opaque, recordGeneration, &recordFormat); recordTarget.opaque, recordGeneration, &recordFormat);
LG_LOCK(state->stateLock); if (recordAdmitted)
if (recordDispatch && endEvent(&recordTarget);
recordEventCurrentNL(state, &target, recordGeneration)) endOperation(&state->recordOperation, recordOwner);
atomic_store_explicit(&state->recordDeliveryGeneration,
recordGeneration, memory_order_seq_cst);
LG_UNLOCK(state->stateLock);
} }
if (admitted)
endEvent(state, &target);
return true; return true;
} }
@@ -623,17 +872,11 @@ static void usbDetach(void * opaque)
{ {
LGA_USBState * state = opaque; LGA_USBState * state = opaque;
for (;;) if (!lockStateAfterDetach(state))
{ return;
LG_LOCK(state->stateLock);
if (!state->detaching)
break;
LG_UNLOCK(state->stateLock);
if (callbackDepth(l_eventFrames, state))
return;
}
state->detaching = true; state->detaching = true;
lgResetEvent(state->detachEvent);
state->attached = false; state->attached = false;
state->attachmentGeneration = state->attachmentGeneration =
nextGeneration(state->attachmentGeneration); nextGeneration(state->attachmentGeneration);
@@ -646,7 +889,8 @@ static void usbDetach(void * opaque)
lgUsbRedir_setPlugged(state->redir, false); lgUsbRedir_setPlugged(state->redir, false);
LG_UNLOCK(state->stateLock); LG_UNLOCK(state->stateLock);
waitEvents(state); waitPlaybackEvents(state);
waitRecordEvents(state);
LG_LOCK(state->stateLock); LG_LOCK(state->stateLock);
if (!state->attached && if (!state->attached &&
@@ -655,6 +899,7 @@ static void usbDetach(void * opaque)
state->events = NULL; state->events = NULL;
state->eventOpaque = NULL; state->eventOpaque = NULL;
state->detaching = false; state->detaching = false;
signalWaitEvent(state->detachEvent);
} }
LG_UNLOCK(state->stateLock); LG_UNLOCK(state->stateLock);
} }
@@ -666,7 +911,8 @@ static bool usbClockFeedback(void * opaque, uint32_t generation,
LG_LOCK(state->stateLock); LG_LOCK(state->stateLock);
if (!state->attached || if (!state->attached ||
state->playbackGeneration != generation) state->playbackGeneration != generation ||
!lgUsbAudio_feedbackActive(state->device))
{ {
LG_UNLOCK(state->stateLock); LG_UNLOCK(state->stateLock);
return false; return false;
@@ -686,17 +932,19 @@ static bool usbRecordData(void * opaque, uint32_t generation,
const void * data, size_t frames, const void * data, size_t frames,
const LG_AudioClock * sourceClock) const LG_AudioClock * sourceClock)
{ {
(void)sourceClock;
LGA_USBState * state = opaque; LGA_USBState * state = opaque;
USBAudioCallbackFrame frame; USBAudioCallbackFrame frame;
beginCallback(state, &frame, &l_eventFrames, &state->inFlight); beginCallback(state, &frame, &l_recordFrames,
&state->recordGate.inFlight);
const bool valid = generation && generation == atomic_load_explicit( const bool valid = generation && generation == atomic_load_explicit(
&state->recordDeliveryGeneration, memory_order_seq_cst); &state->recordDeliveryGeneration, memory_order_seq_cst);
const bool result = valid && const bool result = valid &&
lgUsbAudio_recordData(state->device, data, frames); lgUsbAudio_recordData(
state->device, data, frames, sourceClock);
endCallback(&frame, &l_eventFrames, &state->inFlight); endCallback(&frame, &l_recordFrames,
&state->recordGate.inFlight, &state->recordGate.wait);
return result; return result;
} }
@@ -710,28 +958,47 @@ const LG_AudioOps LGA_USB =
.clockFeedback = usbClockFeedback, .clockFeedback = usbClockFeedback,
}; };
LGA_USBState * lgaUsb_create(void) LGA_USBState * lgaUsb_create(bool debug)
{ {
LGA_USBState * state = calloc(1, sizeof(*state)); LGA_USBState * state = aligned_alloc(
alignof(LGA_USBState), sizeof(*state));
if (!state) if (!state)
return NULL; return NULL;
memset(state, 0, sizeof(*state));
LG_LOCK_INIT(state->statusLock); 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->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->available, false);
atomic_init(&state->playbackPosition, 0); atomic_init(&state->playbackPosition, 0);
atomic_init(&state->playbackDeliveryGeneration, 0); atomic_init(&state->playbackDeliveryGeneration, 0);
atomic_init(&state->recordDeliveryGeneration, 0); atomic_init(&state->recordDeliveryGeneration, 0);
atomic_init(&state->inFlight, 0); atomic_init(&state->playbackGate.inFlight, 0);
atomic_init(&state->recordGate.inFlight, 0);
atomic_init(&state->statusInFlight, 0); atomic_init(&state->statusInFlight, 0);
atomic_init(&state->statusNextTicket, 0); atomic_init(&state->playbackGate.wait.count, 0);
atomic_init(&state->statusServingTicket, 0); atomic_init(&state->recordGate.wait.count, 0);
atomic_init(&state->statusWait.count, 0);
state->playbackFormat = l_formatTemplate; state->playbackFormat = l_formatTemplate;
state->recordFormat = l_formatTemplate; state->recordFormat = l_formatTemplate;
state->device = lgUsbAudio_create(&l_usbAudioEvents, state); 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) if (!state->device)
{ {
lgFreeEvent(state->detachEvent);
free(state); free(state);
return NULL; return NULL;
} }
@@ -741,6 +1008,7 @@ LGA_USBState * lgaUsb_create(void)
if (!state->redir) if (!state->redir)
{ {
lgUsbAudio_destroy(state->device); lgUsbAudio_destroy(state->device);
lgFreeEvent(state->detachEvent);
free(state); free(state);
return NULL; return NULL;
} }
@@ -757,7 +1025,13 @@ void lgaUsb_destroy(LGA_USBState * state)
usbDetach(state); usbDetach(state);
lgUsbRedir_destroy(state->redir); lgUsbRedir_destroy(state->redir);
lgUsbAudio_destroy(state->device); 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->stateLock);
LG_LOCK_FREE(state->statusOperation.lock);
LG_LOCK_FREE(state->statusWait.lock);
LG_LOCK_FREE(state->statusLock); LG_LOCK_FREE(state->statusLock);
free(state); free(state);
} }
@@ -767,7 +1041,7 @@ LG_USBRedir * lgaUsb_redir(LGA_USBState * state)
return state ? state->redir : NULL; return state ? state->redir : NULL;
} }
bool lgaUsb_recording(const LGA_USBState * state) uint64_t lgaUsb_processDelayNs(const LGA_USBState * state)
{ {
return state && lgUsbAudio_recording(state->device); return state ? lgUsbAudio_processDelayNs(state->device) : UINT64_MAX;
} }

View File

@@ -24,14 +24,16 @@
#include "interface/audio.h" #include "interface/audio.h"
#include "usbredir.h" #include "usbredir.h"
#include <stdint.h>
typedef struct LGA_USBState LGA_USBState; typedef struct LGA_USBState LGA_USBState;
LGA_USBState * lgaUsb_create(void); LGA_USBState * lgaUsb_create(bool debug);
/* Detach this provider and stop PureSpice before destroying its state. */ /* Detach this provider and stop PureSpice before destroying its state. */
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); uint64_t lgaUsb_processDelayNs(const LGA_USBState * state);
extern const LG_AudioOps LGA_USB; extern const LG_AudioOps LGA_USB;

View File

@@ -1728,7 +1728,7 @@ int spiceThread(void * arg)
DEBUG_WARN("USB audio requires a playback backend, using SPICE audio"); DEBUG_WARN("USB audio requires a playback backend, using SPICE audio");
g_params.useSpiceUSBAudio = false; g_params.useSpiceUSBAudio = false;
} }
else if (!(usbAudio = lgaUsb_create())) else if (!(usbAudio = lgaUsb_create(g_params.audioDebug)))
{ {
DEBUG_WARN("Failed to initialize USB audio, using SPICE audio"); DEBUG_WARN("Failed to initialize USB audio, using SPICE audio");
g_params.useSpiceUSBAudio = false; g_params.useSpiceUSBAudio = false;
@@ -1839,7 +1839,17 @@ int spiceThread(void * arg)
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) if (usbAudio)
processTimeout = lgaUsb_recording(usbAudio) ? 1 : 10; {
const uint64_t delay = lgaUsb_processDelayNs(usbAudio);
if (delay == UINT64_MAX)
processTimeout = 10;
else
{
const uint64_t timeout = delay / UINT64_C(1000000) +
(delay % UINT64_C(1000000) != 0);
processTimeout = (int)min(timeout, UINT64_C(10));
}
}
#endif #endif
if ((status = purespice_process(processTimeout)) != PS_STATUS_RUN) if ((status = purespice_process(processTimeout)) != PS_STATUS_RUN)

View File

@@ -21,6 +21,7 @@
#include "interface/overlay.h" #include "interface/overlay.h"
#include "math.h" #include "math.h"
#include "cimgui.h" #include "cimgui.h"
#include <stdatomic.h>
#include "../overlays.h" #include "../overlays.h"
#include "../main.h" #include "../main.h"
@@ -32,7 +33,7 @@
//TODO: Make this user configurable? //TODO: Make this user configurable?
#define ICON_SIZE 32 #define ICON_SIZE 32
static bool l_state[LG_USER_STATUS_MAX] = { 0 }; static atomic_bool l_state[LG_USER_STATUS_MAX] = { 0 };
static OverlayImage l_image[LG_USER_STATUS_MAX] = { 0 }; static OverlayImage l_image[LG_USER_STATUS_MAX] = { 0 };
static bool l_recordToggle; static bool l_recordToggle;
static double l_scale = 1.0; static double l_scale = 1.0;
@@ -92,7 +93,8 @@ static int status_render(void * udata, bool interactive, struct Rect * windowRec
for(int i = 0; i < LG_USER_STATUS_MAX; ++i) for(int i = 0; i < LG_USER_STATUS_MAX; ++i)
{ {
OverlayImage * img = &l_image[i]; OverlayImage * img = &l_image[i];
if (!l_state[i] || !img->tex) if (!atomic_load_explicit(&l_state[i], memory_order_relaxed) ||
!img->tex)
continue; continue;
// if the recording indicator is off, don't draw but reserve space // if the recording indicator is off, don't draw but reserve space
@@ -147,9 +149,9 @@ struct LG_OverlayOps LGOverlayStatus =
void overlayStatus_set(LGUserStatus status, bool value) void overlayStatus_set(LGUserStatus status, bool value)
{ {
if (l_state[status] == value) if (atomic_exchange_explicit(
&l_state[status], value, memory_order_relaxed) == value)
return; return;
l_state[status] = value;
app_invalidateOverlay(true); app_invalidateOverlay(true);
}; };

File diff suppressed because it is too large Load Diff

View File

@@ -21,6 +21,7 @@
#ifndef _H_LG_CLIENT_USB_AUDIO_ #ifndef _H_LG_CLIENT_USB_AUDIO_
#define _H_LG_CLIENT_USB_AUDIO_ #define _H_LG_CLIENT_USB_AUDIO_
#include "interface/audio.h"
#include "usbredir.h" #include "usbredir.h"
#include <stddef.h> #include <stddef.h>
@@ -49,21 +50,25 @@ typedef struct LG_USBAudioEventOps
LG_USBAudioEventOps; LG_USBAudioEventOps;
LG_USBAudio * lgUsbAudio_create( LG_USBAudio * lgUsbAudio_create(
const LG_USBAudioEventOps * events, void * eventOpaque); const LG_USBAudioEventOps * events, void * eventOpaque, bool debug);
/* Destroy the LG_USBRedir using this device before destroying the device. */ /* Destroy the LG_USBRedir using this device before destroying the device. */
void lgUsbAudio_destroy(LG_USBAudio * audio); void lgUsbAudio_destroy(LG_USBAudio * audio);
/* Publish the requested source rate without touching usbredir from the audio /* Publish the requested source rate without touching usbredir from the audio
* feedback thread. */ * feedback thread. */
void lgUsbAudio_setFeedbackRate(LG_USBAudio * audio, double sampleRate); void lgUsbAudio_setFeedbackRate(LG_USBAudio * audio, double sampleRate);
bool lgUsbAudio_feedbackActive(const LG_USBAudio * audio);
/* Queue interleaved packed signed 24-bit microphone frames. This may be /* Queue interleaved packed signed 24-bit microphone frames. sourceClock is
* called from the audio recording thread. */ * borrowed for the call and identifies the first frame when present. This may
* be called from the audio recording thread. */
bool lgUsbAudio_recordData( bool lgUsbAudio_recordData(
LG_USBAudio * audio, const void * data, size_t frames); LG_USBAudio * audio, const void * data, size_t frames,
const LG_AudioClock * sourceClock);
/* This must be queried on the PureSpice processing thread. */ /* Return the time until ISO-IN processing is needed. This must be queried on
bool lgUsbAudio_recording(const LG_USBAudio * audio); * the PureSpice processing thread. */
uint64_t lgUsbAudio_processDelayNs(const LG_USBAudio * audio);
const LG_USBRedirDeviceOps * lgUsbAudio_deviceOps(void); const LG_USBRedirDeviceOps * lgUsbAudio_deviceOps(void);

View File

@@ -21,6 +21,7 @@
#include "usbredir.h" #include "usbredir.h"
#include "common/debug.h" #include "common/debug.h"
#include "common/time.h"
#include <usbredirparser.h> #include <usbredirparser.h>
@@ -29,6 +30,9 @@
#include <string.h> #include <string.h>
#define USB_REDIR_CHANNEL_COUNT 256 #define USB_REDIR_CHANNEL_COUNT 256
#define USB_REDIR_DISCONNECT_TIMEOUT_NS INT64_C(500000000)
#define USB_REDIR_RECONNECT_DELAY_NS INT64_C(250000000)
#define USB_REDIR_MAX_OUTPUT_BYTES UINT64_C(1048576)
struct LG_USBRedir struct LG_USBRedir
{ {
@@ -49,6 +53,8 @@ struct LG_USBRedir
atomic_bool available; atomic_bool available;
bool plugged; bool plugged;
bool disconnectPending; bool disconnectPending;
int64_t disconnectDeadline;
int64_t reconnectDeadline;
}; };
static void setAvailable(LG_USBRedir * usbredir, bool available) static void setAvailable(LG_USBRedir * usbredir, bool available)
@@ -123,6 +129,7 @@ static void deviceDisconnectAck(void * opaque)
{ {
LG_USBRedir * usbredir = opaque; LG_USBRedir * usbredir = opaque;
usbredir->disconnectPending = false; usbredir->disconnectPending = false;
usbredir->disconnectDeadline = 0;
} }
static void unplugDevice(LG_USBRedir * usbredir) static void unplugDevice(LG_USBRedir * usbredir)
@@ -139,6 +146,7 @@ static void destroyParser(LG_USBRedir * usbredir)
setAvailable(usbredir, false); setAvailable(usbredir, false);
unplugDevice(usbredir); unplugDevice(usbredir);
usbredir->disconnectPending = false; usbredir->disconnectPending = false;
usbredir->disconnectDeadline = 0;
if (!usbredir->parser) if (!usbredir->parser)
return; return;
@@ -149,8 +157,20 @@ static void destroyParser(LG_USBRedir * usbredir)
static bool flushUSBRedir(LG_USBRedir * usbredir) static bool flushUSBRedir(LG_USBRedir * usbredir)
{ {
return !usbredir->parser || if (!usbredir->parser)
usbredirparser_do_write(usbredir->parser) == 0; return true;
if (usbredirparser_do_write(usbredir->parser) != 0)
return false;
const uint64_t buffered =
usbredirparser_get_bufferered_output_size(usbredir->parser);
if (buffered <= USB_REDIR_MAX_OUTPUT_BYTES)
return true;
DEBUG_ERROR("USB redirection output queue exceeded %u bytes",
(unsigned int)USB_REDIR_MAX_OUTPUT_BYTES);
return false;
} }
static bool connectChannel(LG_USBRedir * usbredir, static bool connectChannel(LG_USBRedir * usbredir,
@@ -166,10 +186,10 @@ static bool connectChannel(LG_USBRedir * usbredir,
return false; return false;
} }
static void selectChannel(LG_USBRedir * usbredir) static bool selectChannel(LG_USBRedir * usbredir)
{ {
if (usbredir->channel) if (usbredir->channel)
return; return true;
for (unsigned int i = 0; i < USB_REDIR_CHANNEL_COUNT; ++i) for (unsigned int i = 0; i < USB_REDIR_CHANNEL_COUNT; ++i)
{ {
@@ -178,8 +198,28 @@ static void selectChannel(LG_USBRedir * usbredir)
continue; continue;
if (connectChannel(usbredir, channel)) if (connectChannel(usbredir, channel))
return; {
usbredir->reconnectDeadline = 0;
return true;
}
} }
usbredir->reconnectDeadline =
(int64_t)nanotime() + USB_REDIR_RECONNECT_DELAY_NS;
return false;
}
static void resetChannel(LG_USBRedir * usbredir)
{
PSUSBRedirChannel * channel = usbredir->channel;
usbredir->channel = NULL;
destroyParser(usbredir);
if (channel && purespice_usbRedirConnected(channel))
purespice_usbRedirDisconnect(channel);
usbredir->reconnectDeadline =
(int64_t)nanotime() + USB_REDIR_RECONNECT_DELAY_NS;
} }
static bool createParser(LG_USBRedir * usbredir) static bool createParser(LG_USBRedir * usbredir)
@@ -268,14 +308,37 @@ bool lgUsbRedir_disconnectPending(const LG_USBRedir * usbredir)
return usbredir->disconnectPending; return usbredir->disconnectPending;
} }
bool lgUsbRedir_process(LG_USBRedir * usbredir) static bool processUSBRedir(LG_USBRedir * usbredir, bool recover)
{ {
const int64_t now = (int64_t)nanotime();
if (!usbredir->channel &&
now >= usbredir->reconnectDeadline)
selectChannel(usbredir);
if (!usbredir->parser || if (!usbredir->parser ||
!atomic_load_explicit(&usbredir->available, memory_order_acquire)) !atomic_load_explicit(&usbredir->available, memory_order_acquire))
return flushUSBRedir(usbredir); {
const bool result = flushUSBRedir(usbredir);
if (!result && recover)
resetChannel(usbredir);
return result;
}
if (usbredir->disconnectPending) if (usbredir->disconnectPending)
return flushUSBRedir(usbredir); {
if (usbredir->disconnectDeadline &&
now >= usbredir->disconnectDeadline)
{
DEBUG_WARN("USB redirection disconnect acknowledgement timed out");
resetChannel(usbredir);
return true;
}
const bool result = flushUSBRedir(usbredir);
if (!result && recover)
resetChannel(usbredir);
return result;
}
const bool desired = atomic_load_explicit(&usbredir->desiredPlugged, const bool desired = atomic_load_explicit(&usbredir->desiredPlugged,
memory_order_acquire); memory_order_acquire);
@@ -292,6 +355,8 @@ bool lgUsbRedir_process(LG_USBRedir * usbredir)
unplugDevice(usbredir); unplugDevice(usbredir);
usbredir->disconnectPending = usbredirparser_peer_has_cap( usbredir->disconnectPending = usbredirparser_peer_has_cap(
usbredir->parser, usb_redir_cap_device_disconnect_ack); usbredir->parser, usb_redir_cap_device_disconnect_ack);
usbredir->disconnectDeadline = usbredir->disconnectPending ?
now + USB_REDIR_DISCONNECT_TIMEOUT_NS : 0;
usbredirparser_send_device_disconnect(usbredir->parser); usbredirparser_send_device_disconnect(usbredir->parser);
} }
} }
@@ -299,7 +364,15 @@ bool lgUsbRedir_process(LG_USBRedir * usbredir)
if (usbredir->plugged && usbredir->deviceOps->process) if (usbredir->plugged && usbredir->deviceOps->process)
usbredir->deviceOps->process(usbredir->deviceOpaque); usbredir->deviceOps->process(usbredir->deviceOpaque);
return flushUSBRedir(usbredir); const bool result = flushUSBRedir(usbredir);
if (!result && recover)
resetChannel(usbredir);
return result;
}
bool lgUsbRedir_process(LG_USBRedir * usbredir)
{
return processUSBRedir(usbredir, true);
} }
void lgUsbRedir_state(PSUSBRedirChannel * channel, void lgUsbRedir_state(PSUSBRedirChannel * channel,
@@ -328,7 +401,13 @@ void lgUsbRedir_state(PSUSBRedirChannel * channel,
case PS_USB_REDIR_DISCONNECTED: case PS_USB_REDIR_DISCONNECTED:
if (channel == usbredir->channel) if (channel == usbredir->channel)
{
destroyParser(usbredir); destroyParser(usbredir);
usbredir->channel = NULL;
if (purespice_usbRedirAvailable(channel))
usbredir->reconnectDeadline =
(int64_t)nanotime() + USB_REDIR_RECONNECT_DELAY_NS;
}
break; break;
case PS_USB_REDIR_UNAVAILABLE: case PS_USB_REDIR_UNAVAILABLE:
@@ -375,7 +454,9 @@ bool lgUsbRedir_data(PSUSBRedirChannel * channel,
return false; return false;
} }
return lgUsbRedir_process(usbredir); /* Returning false lets PureSpice disconnect after this callback unwinds;
* resetting it here would destroy the channel during its own dispatch. */
return processUSBRedir(usbredir, false);
} }
void * lgUsbRedir_device(void * parserOpaque) void * lgUsbRedir_device(void * parserOpaque)