[client] usb audio: add asynchronous clock feedback

Advertise an asynchronous UAC2 OUT endpoint with explicit feedback so
Windows paces USB audio from the host playback clock.

Drive feedback from measured backend consumption plus the existing
buffer phase controller. Preserve the local and backend resampler paths
for providers without active feedback.

Keep audio diagnostics in the correct clock domains, report ring and
backend latency separately, and avoid hot-path feedback wakeups.
This commit is contained in:
Geoffrey McRae
2026-08-10 04:44:55 +10:00
parent fa8a95569a
commit 0f3c1db261
5 changed files with 420 additions and 134 deletions

View File

@@ -156,12 +156,15 @@ typedef struct LG_AudioOps
bool (*recordData)(void * opaque, uint32_t generation, bool (*recordData)(void * opaque, uint32_t generation,
const void * data, size_t frames, const LG_AudioClock * sourceClock); const void * data, size_t frames, const LG_AudioClock * sourceClock);
/* Optional active synchronization feedback. This is called outside the /* Optional active synchronization feedback. A provider implementing this
* realtime audio callback with the measured playback device clock. Its * owns playback rate control, so the client preserves its frame rate and
* position uses the device's independent output-frame timeline and its time * does not activate a local resampler. This is called outside the realtime
* includes the backend's estimated presentation latency. */ * audio callback with the measured playback device clock. Its position uses
* the device's independent output-frame timeline and its time includes the
* backend's estimated presentation latency. targetRate is the source frame
* rate requested by the client's buffer controller. */
bool (*clockFeedback)(void * opaque, uint32_t generation, bool (*clockFeedback)(void * opaque, uint32_t generation,
const LG_AudioClock * playbackClock); const LG_AudioClock * playbackClock, double targetRate);
} }
LG_AudioOps; LG_AudioOps;

View File

@@ -65,6 +65,7 @@
#define PLAYBACK_DEVICE_RATE_STABLE_SEC 2.0 #define PLAYBACK_DEVICE_RATE_STABLE_SEC 2.0
#define PLAYBACK_DEVICE_RATE_STABLE_DELTA_PPM 50.0 #define PLAYBACK_DEVICE_RATE_STABLE_DELTA_PPM 50.0
#define PLAYBACK_DEVICE_RATE_MAX_ACQUIRE_SEC 20.0 #define PLAYBACK_DEVICE_RATE_MAX_ACQUIRE_SEC 20.0
#define PLAYBACK_FEEDBACK_INTERVAL_NS INT64_C(1000000)
typedef enum typedef enum
{ {
@@ -78,6 +79,14 @@ typedef enum
} }
StreamState; StreamState;
typedef enum
{
PLAYBACK_RATE_SOFTWARE,
PLAYBACK_RATE_BACKEND,
PLAYBACK_RATE_PROVIDER,
}
PlaybackRateControl;
#define STREAM_ACTIVE(state) \ #define STREAM_ACTIVE(state) \
(state == STREAM_STATE_RUN || \ (state == STREAM_STATE_RUN || \
state == STREAM_STATE_KEEP_ALIVE || \ state == STREAM_STATE_KEEP_ALIVE || \
@@ -163,6 +172,7 @@ typedef struct
double ratioIntegral; double ratioIntegral;
double lastRatio; double lastRatio;
double lastClockRatio; double lastClockRatio;
int64_t nextFeedbackTime;
int64_t nextLogTime; int64_t nextLogTime;
unsigned int bufferOverruns; unsigned int bufferOverruns;
@@ -224,7 +234,8 @@ typedef struct
int startupLowWaterFrames; int startupLowWaterFrames;
int64_t startupPacketDeadline; int64_t startupPacketDeadline;
int64_t startupPacketPeriod; int64_t startupPacketPeriod;
bool backendResampler; PlaybackRateControl rateControl;
bool lastProviderRateControl;
_Atomic(double) backendResampleRatio; _Atomic(double) backendResampleRatio;
atomic_bool backendResamplerFailed; atomic_bool backendResamplerFailed;
RingBuffer buffer; RingBuffer buffer;
@@ -274,6 +285,7 @@ typedef struct
uint32_t bindingGeneration; uint32_t bindingGeneration;
uint32_t generation; uint32_t generation;
LG_AudioClock clock; LG_AudioClock clock;
double targetRate;
} }
feedback; feedback;
} }
@@ -590,10 +602,9 @@ static void playbackSourceRateReset(PlaybackSourceData * sourceData)
sourceData->sourceRateValid = false; sourceData->sourceRateValid = false;
} }
static void playbackSourceRateAdd( static void playbackSourceRateAdd(PlaybackSourceData * sourceData,
PlaybackSourceData * sourceData, double nominalFrameSec) int64_t timeMs, double nominalFrameSec)
{ {
const int64_t timeMs = sourceData->mediaTimeMs;
if (sourceData->rateLastSampleTimeMs != INT64_MIN && if (sourceData->rateLastSampleTimeMs != INT64_MIN &&
timeMs - sourceData->rateLastSampleTimeMs < timeMs - sourceData->rateLastSampleTimeMs <
PLAYBACK_RATE_SAMPLE_INTERVAL_MS) PLAYBACK_RATE_SAMPLE_INTERVAL_MS)
@@ -925,7 +936,7 @@ static int playbackPullFrames(uint8_t * dst, int frames)
PlaybackDeviceData * data = &audio.playback.deviceData; PlaybackDeviceData * data = &audio.playback.deviceData;
double nextRatio = 1.0; double nextRatio = 1.0;
if (audio.playback.backendResampler) if (audio.playback.rateControl == PLAYBACK_RATE_BACKEND)
{ {
nextRatio = atomic_load_explicit( nextRatio = atomic_load_explicit(
&audio.playback.backendResampleRatio, memory_order_acquire); &audio.playback.backendResampleRatio, memory_order_acquire);
@@ -1045,15 +1056,16 @@ static int playbackPullFrames(uint8_t * dst, int frames)
} }
static bool playbackSetupDevice(const LG_AudioFormat * format, static bool playbackSetupDevice(const LG_AudioFormat * format,
int requestedPeriodFrames, bool requestResampler) int requestedPeriodFrames, bool requestResampler,
bool * backendResampler)
{ {
audio.playback.backendResampler = false;
audio.playback.deviceMaxPeriodFrames = 0; audio.playback.deviceMaxPeriodFrames = 0;
audio.playback.deviceStartFrames = 0; audio.playback.deviceStartFrames = 0;
*backendResampler = false;
return audio.audioDev->playback.setup(format, return audio.audioDev->playback.setup(format,
requestedPeriodFrames, requestResampler, requestedPeriodFrames, requestResampler,
&audio.playback.backendResampler, backendResampler,
&audio.playback.deviceMaxPeriodFrames, &audio.playback.deviceMaxPeriodFrames,
&audio.playback.deviceStartFrames, playbackPullFrames) && &audio.playback.deviceStartFrames, playbackPullFrames) &&
audio.playback.deviceMaxPeriodFrames > 0 && audio.playback.deviceMaxPeriodFrames > 0 &&
@@ -1061,7 +1073,7 @@ static bool playbackSetupDevice(const LG_AudioFormat * format,
} }
static void playbackStart(const LG_AudioFormat * format, static void playbackStart(const LG_AudioFormat * format,
const LG_AudioClock * sourceClock) const LG_AudioClock * sourceClock, bool providerRateControl)
{ {
if (!audio.audioDev) if (!audio.audioDev)
return; return;
@@ -1080,6 +1092,7 @@ static void playbackStart(const LG_AudioFormat * format,
StreamState state = playbackGetState(); StreamState state = playbackGetState();
if (state == STREAM_STATE_KEEP_ALIVE && if (state == STREAM_STATE_KEEP_ALIVE &&
audio.playback.lastFormatValid && audio.playback.lastFormatValid &&
audio.playback.lastProviderRateControl == providerRateControl &&
audioFormatEqual(format, &audio.playback.lastFormat)) audioFormatEqual(format, &audio.playback.lastFormat))
{ {
StreamState expected = STREAM_STATE_KEEP_ALIVE; StreamState expected = STREAM_STATE_KEEP_ALIVE;
@@ -1098,9 +1111,10 @@ static void playbackStart(const LG_AudioFormat * format,
if (state != STREAM_STATE_STOP) if (state != STREAM_STATE_STOP)
playbackStop(); playbackStop();
audio.playback.format = *format; audio.playback.format = *format;
audio.playback.lastFormat = *format; audio.playback.lastFormat = *format;
audio.playback.lastFormatValid = true; audio.playback.lastFormatValid = true;
audio.playback.lastProviderRateControl = providerRateControl;
audio.playback.channels = channels; audio.playback.channels = channels;
audio.playback.sampleRate = sampleRate; audio.playback.sampleRate = sampleRate;
@@ -1122,6 +1136,7 @@ static void playbackStart(const LG_AudioFormat * format,
audio.playback.sourceData.ratioIntegral = 0.0; audio.playback.sourceData.ratioIntegral = 0.0;
audio.playback.sourceData.lastRatio = 1.0; audio.playback.sourceData.lastRatio = 1.0;
audio.playback.sourceData.lastClockRatio = 1.0; audio.playback.sourceData.lastClockRatio = 1.0;
audio.playback.sourceData.nextFeedbackTime = 0;
audio.playback.sourceData.bufferOverrunPending = false; audio.playback.sourceData.bufferOverrunPending = false;
audio.playback.sourceData.bufferOverruns = 0; audio.playback.sourceData.bufferOverruns = 0;
audio.playback.sourceData.nextLogTime = audio.playback.sourceData.nextLogTime =
@@ -1161,28 +1176,32 @@ static void playbackStart(const LG_AudioFormat * format,
audio.playback.startupLowWaterFrames = 0; audio.playback.startupLowWaterFrames = 0;
audio.playback.startupPacketDeadline = 0; audio.playback.startupPacketDeadline = 0;
audio.playback.startupPacketPeriod = 0; audio.playback.startupPacketPeriod = 0;
const bool requestBackendResampler = const bool requestBackendResampler = !providerRateControl &&
g_params.audioResampler != AUDIO_RESAMPLER_LIBSAMPLERATE; g_params.audioResampler != AUDIO_RESAMPLER_LIBSAMPLERATE;
LG_AudioFormat deviceFormat = *format; LG_AudioFormat deviceFormat = *format;
/* The ring generates zero-filled silence. Keep unsigned PCM on the float /* The ring generates zero-filled silence. Keep unsigned PCM on the float
* path because its silence level is biased rather than zero. */ * path because its silence level is biased rather than zero. */
if (!requestBackendResampler || if ((!requestBackendResampler && !providerRateControl) ||
deviceFormat.sampleFormat == LG_AUDIO_FMT_U8) deviceFormat.sampleFormat == LG_AUDIO_FMT_U8)
deviceFormat.sampleFormat = LG_AUDIO_FMT_F32_NE; deviceFormat.sampleFormat = LG_AUDIO_FMT_F32_NE;
bool backendResampler;
bool deviceConfigured = playbackSetupDevice( bool deviceConfigured = playbackSetupDevice(
&deviceFormat, requestedPeriodFrames, requestBackendResampler); &deviceFormat, requestedPeriodFrames, requestBackendResampler,
&backendResampler);
/* Native samples require the backend to own rate correction. If it cannot, /* Native samples require either provider feedback or backend rate control.
* reconnect using float samples for the libsamplerate path. This also * Otherwise reconnect using float samples for the libsamplerate path. This
* provides a float fallback for formats unsupported by the backend. */ * also provides a float fallback for formats unsupported by the backend. */
if ((!deviceConfigured || !audio.playback.backendResampler) && if ((!deviceConfigured ||
(!providerRateControl && !backendResampler)) &&
deviceFormat.sampleFormat != LG_AUDIO_FMT_F32_NE) deviceFormat.sampleFormat != LG_AUDIO_FMT_F32_NE)
{ {
deviceFormat.sampleFormat = LG_AUDIO_FMT_F32_NE; deviceFormat.sampleFormat = LG_AUDIO_FMT_F32_NE;
deviceConfigured = playbackSetupDevice( deviceConfigured = playbackSetupDevice(
&deviceFormat, requestedPeriodFrames, requestBackendResampler); &deviceFormat, requestedPeriodFrames, requestBackendResampler,
&backendResampler);
} }
if (!deviceConfigured) if (!deviceConfigured)
@@ -1195,8 +1214,11 @@ static void playbackStart(const LG_AudioFormat * format,
audio.playback.stride = channels * audio.playback.stride = channels *
audioSampleSize(deviceFormat.sampleFormat); audioSampleSize(deviceFormat.sampleFormat);
audio.playback.convertToFloat = audio.playback.convertToFloat =
!audio.playback.backendResampler || (!providerRateControl && !backendResampler) ||
deviceFormat.sampleFormat != format->sampleFormat; deviceFormat.sampleFormat != format->sampleFormat;
audio.playback.rateControl = providerRateControl ?
PLAYBACK_RATE_PROVIDER : backendResampler ?
PLAYBACK_RATE_BACKEND : PLAYBACK_RATE_SOFTWARE;
audio.playback.buffer = ringbuffer_newUnbounded( audio.playback.buffer = ringbuffer_newUnbounded(
sampleRate, audio.playback.stride); sampleRate, audio.playback.stride);
@@ -1207,11 +1229,11 @@ static void playbackStart(const LG_AudioFormat * format,
} }
if (g_params.audioResampler == AUDIO_RESAMPLER_BACKEND && if (g_params.audioResampler == AUDIO_RESAMPLER_BACKEND &&
!audio.playback.backendResampler) !providerRateControl && !backendResampler)
DEBUG_WARN("%s could not activate backend resampling; " DEBUG_WARN("%s could not activate backend resampling; "
"using libsamplerate", audio.audioDev->name); "using libsamplerate", audio.audioDev->name);
if (!audio.playback.backendResampler) if (audio.playback.rateControl == PLAYBACK_RATE_SOFTWARE)
{ {
int srcError; int srcError;
audio.playback.sourceData.src = audio.playback.sourceData.src =
@@ -1226,9 +1248,20 @@ static void playbackStart(const LG_AudioFormat * format,
else else
audio.playback.sourceData.src = NULL; audio.playback.sourceData.src = NULL;
DEBUG_INFO("Using audio resampler: %s", switch (audio.playback.rateControl)
audio.playback.backendResampler ? {
audio.audioDev->name : "libsamplerate"); case PLAYBACK_RATE_PROVIDER:
DEBUG_INFO("Using audio rate control: provider feedback");
break;
case PLAYBACK_RATE_BACKEND:
DEBUG_INFO("Using audio resampler: %s", audio.audioDev->name);
break;
case PLAYBACK_RATE_SOFTWARE:
DEBUG_INFO("Using audio resampler: libsamplerate");
break;
}
// if a volume level was stored, set it before we return // if a volume level was stored, set it before we return
if (audio.playback.volumeChannels) if (audio.playback.volumeChannels)
@@ -1329,6 +1362,19 @@ static double computeDevicePosition(int64_t curTime)
sourceData->devicePositionOffsetFrames; sourceData->devicePositionOffsetFrames;
} }
static double playbackProviderRate(const PlaybackSourceData * sourceData)
{
const double nominalRate = audio.playback.sampleRate;
if (!sourceData->deviceClock.valid ||
sourceData->deviceClock.frameSec <= 0.0)
return nominalRate;
return clamp(
sourceData->lastRatio / sourceData->deviceClock.frameSec,
nominalRate * (1.0 - PLAYBACK_MAX_RATE_CORRECTION),
nominalRate * (1.0 + PLAYBACK_MAX_RATE_CORRECTION));
}
static bool playbackEnsureConversionBuffers( static bool playbackEnsureConversionBuffers(
PlaybackSourceData * sourceData, int frames) PlaybackSourceData * sourceData, int frames)
{ {
@@ -1347,7 +1393,7 @@ static bool playbackEnsureConversionBuffers(
sourceData->framesInSize = frames; sourceData->framesInSize = frames;
} }
if (!audio.playback.backendResampler) if (audio.playback.rateControl == PLAYBACK_RATE_SOFTWARE)
{ {
const int framesOut = const int framesOut =
(int)ceil(frames * (1.0 + PLAYBACK_MAX_RATE_CORRECTION)) + 64; (int)ceil(frames * (1.0 + PLAYBACK_MAX_RATE_CORRECTION)) + 64;
@@ -1427,7 +1473,7 @@ static void playbackData(const void * data, size_t frameCount,
if (state == STREAM_STATE_STOP || !audio.audioDev || frameCount == 0) if (state == STREAM_STATE_STOP || !audio.audioDev || frameCount == 0)
return; return;
if (audio.playback.backendResampler && if (audio.playback.rateControl == PLAYBACK_RATE_BACKEND &&
atomic_exchange_explicit( atomic_exchange_explicit(
&audio.playback.backendResamplerFailed, false, &audio.playback.backendResamplerFailed, false,
memory_order_acq_rel)) memory_order_acq_rel))
@@ -1441,7 +1487,8 @@ static void playbackData(const void * data, size_t frameCount,
/* Backend resampling changes how many source frames PipeWire requests per /* Backend resampling changes how many source frames PipeWire requests per
* device period. Use the command-normalized output clock for rate matching, * device period. Use the command-normalized output clock for rate matching,
* while deviceClock remains in the ring's source-frame domain for latency. */ * while deviceClock remains in the ring's source-frame domain for latency. */
const PlaybackClock * rateClock = audio.playback.backendResampler ? const PlaybackClock * rateClock =
audio.playback.rateControl == PLAYBACK_RATE_BACKEND ?
&sourceData->outputClock : &sourceData->deviceClock; &sourceData->outputClock : &sourceData->deviceClock;
const int64_t now = nanotime(); const int64_t now = nanotime();
const double nominalFrameSec = 1.0 / audio.playback.sampleRate; const double nominalFrameSec = 1.0 / audio.playback.sampleRate;
@@ -1506,7 +1553,13 @@ static void playbackData(const void * data, size_t frameCount,
const bool sourceRateWasValid = const bool sourceRateWasValid =
sourceData->sourceRateValid; sourceData->sourceRateValid;
playbackSourceRateAdd(sourceData, nominalFrameSec); const bool providerRateControl =
audio.playback.rateControl == PLAYBACK_RATE_PROVIDER;
const int64_t sourceRateTimeMs = providerRateControl ?
(now - sourceData->mediaLocalOrigin) / INT64_C(1000000) :
sourceData->mediaTimeMs;
playbackSourceRateAdd(
sourceData, sourceRateTimeMs, nominalFrameSec);
if (sourceClock && sourceClock->stable && sourceClock->rate > 0.0) if (sourceClock && sourceClock->stable && sourceClock->rate > 0.0)
{ {
const double frameSec = 1.0 / sourceClock->rate; const double frameSec = 1.0 / sourceClock->rate;
@@ -1524,7 +1577,7 @@ static void playbackData(const void * data, size_t frameCount,
if (!playbackSourceClockUpdate(&sourceData->sourceClock, if (!playbackSourceClockUpdate(&sourceData->sourceClock,
packetTime, sourceData->inputPosition, nominalFrameSec)) packetTime, sourceData->inputPosition, nominalFrameSec))
discontinuity = true; discontinuity = true;
if (sourceData->sourceRateValid) if (sourceData->sourceRateValid && !providerRateControl)
sourceData->sourceClock.frameSec = sourceData->sourceRateFrameSec; sourceData->sourceClock.frameSec = sourceData->sourceRateFrameSec;
/* Track phase variation around its local baseline, not its absolute value. /* Track phase variation around its local baseline, not its absolute value.
@@ -1587,7 +1640,7 @@ static void playbackData(const void * data, size_t frameCount,
playbackClockUpdate(&sourceData->deviceClock, playbackClockUpdate(&sourceData->deviceClock,
deviceTick.nextTime, deviceTick.nextPosition, nominalFrameSec); deviceTick.nextTime, deviceTick.nextPosition, nominalFrameSec);
const bool outputClockUpdated = const bool outputClockUpdated =
!audio.playback.backendResampler || audio.playback.rateControl != PLAYBACK_RATE_BACKEND ||
playbackClockUpdate(&sourceData->outputClock, playbackClockUpdate(&sourceData->outputClock,
deviceTick.nextTime, deviceTick.outputPosition, deviceTick.nextTime, deviceTick.outputPosition,
nominalFrameSec); nominalFrameSec);
@@ -1608,11 +1661,12 @@ static void playbackData(const void * data, size_t frameCount,
* switching models would create a false phase step and drive the resampler * switching models would create a false phase step and drive the resampler
* despite an already-correct ring level. Keep the source clock untouched: * despite an already-correct ring level. Keep the source clock untouched:
* changing it would also disturb source phase and jitter tracking. */ * changing it would also disturb source phase and jitter tracking. */
const int64_t referenceTime = providerRateControl ? now : curTime;
const double rawDevicePosition = const double rawDevicePosition =
playbackClockPosition(&sourceData->deviceClock, curTime); playbackClockPosition(&sourceData->deviceClock, referenceTime);
sourceData->devicePositionOffsetFrames = sourceData->devicePositionOffsetFrames =
sourceData->devReadPosition - sourceReserveFrames - sourceData->devReadPosition - rawDevicePosition -
rawDevicePosition; (providerRateControl ? 0.0 : sourceReserveFrames);
} }
const int maxPeriodFrames = const int maxPeriodFrames =
@@ -1637,8 +1691,8 @@ static void playbackData(const void * data, size_t frameCount,
const double targetBufferFrames = const double targetBufferFrames =
minimumBufferFrames + latencyOffsetFrames; minimumBufferFrames + latencyOffsetFrames;
const double resamplerDelayFrames = const double resamplerDelayFrames =
audio.playback.backendResampler ? audio.playback.rateControl == PLAYBACK_RATE_SOFTWARE ?
0.0 : PLAYBACK_RESAMPLER_DELAY_FRAMES; PLAYBACK_RESAMPLER_DELAY_FRAMES : 0.0;
const double minimumLatencyFrames = const double minimumLatencyFrames =
minimumBufferFrames + resamplerDelayFrames; minimumBufferFrames + resamplerDelayFrames;
const double targetLatencyFrames = const double targetLatencyFrames =
@@ -1646,9 +1700,27 @@ static void playbackData(const void * data, size_t frameCount,
double devPosition = DBL_MIN; double devPosition = DBL_MIN;
state = playbackGetState(); state = playbackGetState();
if ((discontinuity || if (providerRateControl &&
(discontinuity ||
state == STREAM_STATE_KEEP_ALIVE || state == STREAM_STATE_KEEP_ALIVE ||
state == STREAM_STATE_RESUMING) && state == STREAM_STATE_RESUMING))
{
const int occupancy = ringbuffer_getCount(audio.playback.buffer);
const int slewFrames = clamp(
llrint(targetLowWaterFrames - occupancy),
(int64_t)INT_MIN, (int64_t)INT_MAX);
const int actualSlew = playbackSlewBuffer(sourceData, slewFrames);
sourceData->outputPosition += actualSlew;
curPosition += actualSlew;
sourceData->offsetError = 0.0;
sourceData->offsetErrorIntegral = 0.0;
sourceData->ratioIntegral = 0.0;
playbackSetState(STREAM_STATE_RUN);
}
else if ((discontinuity ||
state == STREAM_STATE_KEEP_ALIVE ||
state == STREAM_STATE_RESUMING) &&
sourceData->deviceClock.valid && sourceData->deviceClock.valid &&
sourceData->deviceClockStable) sourceData->deviceClockStable)
{ {
@@ -1668,7 +1740,25 @@ static void playbackData(const void * data, size_t frameCount,
double actualLatencyFrames = 0.0; double actualLatencyFrames = 0.0;
double actualOffsetError = 0.0; double actualOffsetError = 0.0;
if (sourceData->deviceClock.valid) if (providerRateControl)
{
const int occupancy = ringbuffer_getCount(audio.playback.buffer);
actualLatencyFrames = occupancy + sourceReserveFrames;
actualOffsetError = targetLowWaterFrames - occupancy;
const double error =
actualOffsetError - sourceData->offsetError;
const double periodSec = frames * nominalFrameSec;
const double omega =
2.0 * M_PI * PLAYBACK_OFFSET_FILTER_BANDWIDTH_HZ * periodSec;
const double b = M_SQRT2 * omega;
const double c = omega * omega;
sourceData->offsetError += b * error +
sourceData->offsetErrorIntegral;
sourceData->offsetErrorIntegral += c * error;
}
else if (sourceData->deviceClock.valid)
{ {
if (sourceData->deviceClockStable) if (sourceData->deviceClockStable)
{ {
@@ -1722,10 +1812,11 @@ static void playbackData(const void * data, size_t frameCount,
2.0 * naturalFrequency / audio.playback.sampleRate; 2.0 * naturalFrequency / audio.playback.sampleRate;
const double ki = const double ki =
naturalFrequency * naturalFrequency / audio.playback.sampleRate; naturalFrequency * naturalFrequency / audio.playback.sampleRate;
if (sourceRateBecameValid) if (sourceRateBecameValid && !providerRateControl)
sourceData->ratioIntegral = 0.0; sourceData->ratioIntegral = 0.0;
if (sourceData->deviceClockStable && if (!providerRateControl &&
sourceData->deviceClockStable &&
sourceData->sourceRateValid && sourceData->sourceRateValid &&
rateClock->updates >= 2) rateClock->updates >= 2)
{ {
@@ -1755,7 +1846,8 @@ static void playbackData(const void * data, size_t frameCount,
double controllerKp = kp; double controllerKp = kp;
double controllerKi = ki; double controllerKi = ki;
double controllerError = phaseError; double controllerError = phaseError;
double controllerBase = sourceData->lastClockRatio; double controllerBase = providerRateControl ?
1.0 : sourceData->lastClockRatio;
if (acquiringDeviceClock) if (acquiringDeviceClock)
{ {
@@ -1811,7 +1903,7 @@ static void playbackData(const void * data, size_t frameCount,
sourceData->lastRatio + maxRatioStep); sourceData->lastRatio + maxRatioStep);
sourceData->lastRatio = ratio; sourceData->lastRatio = ratio;
if (audio.playback.backendResampler) if (audio.playback.rateControl == PLAYBACK_RATE_BACKEND)
{ {
atomic_store_explicit( atomic_store_explicit(
&audio.playback.backendResampleRatio, ratio, &audio.playback.backendResampleRatio, ratio,
@@ -1820,6 +1912,12 @@ static void playbackData(const void * data, size_t frameCount,
playbackAppendFrames(sourceData, inputFrames, frames); playbackAppendFrames(sourceData, inputFrames, frames);
sourceData->outputPosition += outputFrames; sourceData->outputPosition += outputFrames;
} }
else if (audio.playback.rateControl == PLAYBACK_RATE_PROVIDER)
{
const int outputFrames =
playbackAppendFrames(sourceData, inputFrames, frames);
sourceData->outputPosition += outputFrames;
}
else else
{ {
int consumed = 0; int consumed = 0;
@@ -1887,7 +1985,7 @@ static void playbackData(const void * data, size_t frameCount,
const float graphMax = const float graphMax =
targetLatencyFrames * 1000.0 / targetLatencyFrames * 1000.0 /
audio.playback.sampleRate * 2; audio.playback.sampleRate * 2;
audio.playback.graph = app_registerGraph("PLAYBACK", audio.playback.graph = app_registerGraph("PLAYBACK RING",
audio.playback.timings, 0.0f, graphMax, audio.playback.timings, 0.0f, graphMax,
audioGraphFormatFn); audioGraphFormatFn);
if (!audio.playback.graph) if (!audio.playback.graph)
@@ -1929,21 +2027,36 @@ static void playbackData(const void * data, size_t frameCount,
if (now >= sourceData->nextLogTime) if (now >= sourceData->nextLogTime)
{ {
const double backendLatencyMs =
audio.audioDev->playback.latency ?
audio.audioDev->playback.latency() / 1000.0 : 0.0;
const double sourcePpm = sourceData->sourceRateValid ? const double sourcePpm = sourceData->sourceRateValid ?
(sourceData->sourceRateFrameSec / nominalFrameSec - 1.0) * 1.0e6 : (nominalFrameSec / sourceData->sourceRateFrameSec - 1.0) * 1.0e6 :
0.0; 0.0;
const double devicePpm = rateClock->valid ? const double devicePpm = rateClock->valid ?
(rateClock->frameSec / nominalFrameSec - 1.0) * 1.0e6 : (nominalFrameSec / rateClock->frameSec - 1.0) * 1.0e6 :
0.0; 0.0;
const bool providerControl =
audio.playback.rateControl == PLAYBACK_RATE_PROVIDER;
const double controlPpm = providerControl ?
(playbackProviderRate(sourceData) /
audio.playback.sampleRate - 1.0) * 1.0e6 :
(ratio - 1.0) * 1.0e6;
const char * controlName = providerControl ? "feedback" :
audio.playback.rateControl == PLAYBACK_RATE_BACKEND ?
"backend" : "software";
const char * sourceRateName = providerControl ? "arrival" : "source";
const unsigned int underruns = atomic_exchange_explicit( const unsigned int underruns = atomic_exchange_explicit(
&audio.playback.underruns, 0, memory_order_relaxed); &audio.playback.underruns, 0, memory_order_relaxed);
DEBUG_INFO( DEBUG_INFO(
"Audio sync: %.2f/%.2f ms, ratio %+.1f ppm, " "Audio sync: ring %.2f/%.2f ms, backend %.2f ms, "
"clocks %+.1f/%+.1f ppm, jitter %.2f ms, xruns %u/%u", "%s %+.1f ppm, rates %s/device %+.1f/%+.1f ppm, "
"jitter %.2f ms, xruns %u/%u",
softwareLatencyMs, softwareLatencyMs,
targetLatencyFrames * 1000.0 / audio.playback.sampleRate, targetLatencyFrames * 1000.0 / audio.playback.sampleRate,
(ratio - 1.0) * 1.0e6, sourcePpm, devicePpm, backendLatencyMs, controlName, controlPpm,
sourceRateName, sourcePpm, devicePpm,
sourceData->arrivalJitterSec * 1000.0, sourceData->arrivalJitterSec * 1000.0,
underruns, sourceData->bufferOverruns); underruns, sourceData->bufferOverruns);
@@ -1952,24 +2065,37 @@ static void playbackData(const void * data, size_t frameCount,
} }
} }
static bool playbackGetFeedback(LG_AudioClock * clock) static bool playbackGetFeedback(
LG_AudioClock * clock, double * targetRate)
{ {
const PlaybackSourceData * sourceData = &audio.playback.sourceData; PlaybackSourceData * sourceData = &audio.playback.sourceData;
if (!clock || !sourceData->deviceClock.valid || if (!clock || !targetRate ||
sourceData->deviceClock.position < 0.0 || audio.playback.rateControl != PLAYBACK_RATE_PROVIDER ||
!sourceData->deviceClock.valid ||
sourceData->deviceClock.frameSec <= 0.0) sourceData->deviceClock.frameSec <= 0.0)
return false; return false;
const int64_t now = nanotime();
if (now < sourceData->nextFeedbackTime)
return false;
sourceData->nextFeedbackTime = now + PLAYBACK_FEEDBACK_INTERVAL_NS;
const double position =
playbackClockPosition(&sourceData->deviceClock, now);
if (position < 0.0)
return false;
const uint64_t latency = audio.audioDev->playback.latency ? const uint64_t latency = audio.audioDev->playback.latency ?
audio.audioDev->playback.latency() : 0; audio.audioDev->playback.latency() : 0;
*clock = (LG_AudioClock) *clock = (LG_AudioClock)
{ {
.position = llrint(sourceData->deviceClock.position), .position = llrint(position),
.time = sourceData->deviceClock.time + latency * 1000, .time = now + latency * 1000,
.rate = 1.0 / sourceData->deviceClock.frameSec, .rate = 1.0 / sourceData->deviceClock.frameSec,
.stable = sourceData->deviceClockStable, .stable = sourceData->deviceClockStable,
}; };
*targetRate = playbackProviderRate(sourceData);
return true; return true;
} }
@@ -2249,18 +2375,19 @@ static bool bindingActiveNL(const AudioBinding * binding)
static void queueFeedback(const LG_AudioOps * ops, void * opaque, static void queueFeedback(const LG_AudioOps * ops, void * opaque,
uint32_t bindingGeneration, uint32_t generation, uint32_t bindingGeneration, uint32_t generation,
const LG_AudioClock * clock) const LG_AudioClock * clock, double targetRate)
{ {
if (!audio.feedback.event || !audio.feedback.thread || !clock) if (!audio.feedback.event || !audio.feedback.thread || !clock)
return; return;
LG_LOCK(audio.feedback.lock); LG_LOCK(audio.feedback.lock);
audio.feedback.ops = ops; audio.feedback.ops = ops;
audio.feedback.opaque = opaque; audio.feedback.opaque = opaque;
audio.feedback.bindingGeneration = bindingGeneration; audio.feedback.bindingGeneration = bindingGeneration;
audio.feedback.generation = generation; audio.feedback.generation = generation;
audio.feedback.clock = *clock; audio.feedback.clock = *clock;
audio.feedback.pending = true; audio.feedback.targetRate = targetRate;
audio.feedback.pending = true;
LG_UNLOCK(audio.feedback.lock); LG_UNLOCK(audio.feedback.lock);
lgSignalEvent(audio.feedback.event); lgSignalEvent(audio.feedback.event);
} }
@@ -2276,7 +2403,9 @@ static void eventPlaybackStart(void * opaque, uint32_t generation,
LG_LOCK(audio.playback.sourceLock); LG_LOCK(audio.playback.sourceLock);
atomic_store_explicit(&audio.playback.streamGeneration, atomic_store_explicit(&audio.playback.streamGeneration,
generation, memory_order_release); generation, memory_order_release);
playbackStart(format, sourceClock); playbackStart(format, sourceClock,
binding->ops->clockFeedback &&
audio.feedback.event && audio.feedback.thread);
LG_UNLOCK(audio.playback.sourceLock); LG_UNLOCK(audio.playback.sourceLock);
} }
LG_UNLOCK_SHARED(audio.activeLock); LG_UNLOCK_SHARED(audio.activeLock);
@@ -2356,9 +2485,11 @@ static void eventPlaybackData(void * opaque, uint32_t generation,
playbackData(data, frames, sourceClock); playbackData(data, frames, sourceClock);
LG_AudioClock feedback; LG_AudioClock feedback;
if (ops->clockFeedback && playbackGetFeedback(&feedback)) double targetRate;
if (ops->clockFeedback &&
playbackGetFeedback(&feedback, &targetRate))
queueFeedback(ops, providerOpaque, binding->generation, queueFeedback(ops, providerOpaque, binding->generation,
generation, &feedback); generation, &feedback, targetRate);
} }
LG_UNLOCK(audio.playback.sourceLock); LG_UNLOCK(audio.playback.sourceLock);
} }
@@ -2616,18 +2747,20 @@ static int feedbackThread(void * opaque)
break; break;
const LG_AudioOps * ops; const LG_AudioOps * ops;
void * providerOpaque; void * providerOpaque;
uint32_t bindingGeneration; uint32_t bindingGeneration;
uint32_t generation; uint32_t generation;
LG_AudioClock clock; LG_AudioClock clock;
double targetRate;
LG_LOCK(audio.feedback.lock); LG_LOCK(audio.feedback.lock);
const bool pending = audio.feedback.pending; const bool pending = audio.feedback.pending;
ops = audio.feedback.ops; ops = audio.feedback.ops;
providerOpaque = audio.feedback.opaque; providerOpaque = audio.feedback.opaque;
bindingGeneration = audio.feedback.bindingGeneration; bindingGeneration = audio.feedback.bindingGeneration;
generation = audio.feedback.generation; generation = audio.feedback.generation;
clock = audio.feedback.clock; clock = audio.feedback.clock;
targetRate = audio.feedback.targetRate;
audio.feedback.pending = false; audio.feedback.pending = false;
LG_UNLOCK(audio.feedback.lock); LG_UNLOCK(audio.feedback.lock);
@@ -2640,7 +2773,8 @@ static int feedbackThread(void * opaque)
audio.active.generation == bindingGeneration && audio.active.generation == bindingGeneration &&
atomic_load_explicit(&audio.playback.streamGeneration, atomic_load_explicit(&audio.playback.streamGeneration,
memory_order_acquire) == generation) memory_order_acquire) == generation)
ops->clockFeedback(providerOpaque, generation, &clock); ops->clockFeedback(
providerOpaque, generation, &clock, targetRate);
LG_UNLOCK_SHARED(audio.activeLock); LG_UNLOCK_SHARED(audio.activeLock);
} }

View File

@@ -532,6 +532,28 @@ static void usbDetach(void * opaque)
LG_UNLOCK(state->stateLock); LG_UNLOCK(state->stateLock);
} }
static bool usbClockFeedback(void * opaque, uint32_t generation,
const LG_AudioClock * playbackClock, double targetRate)
{
LGA_USBState * state = opaque;
LG_LOCK(state->stateLock);
if (!state->attached || state->streamGeneration != generation)
{
LG_UNLOCK(state->stateLock);
return false;
}
const double nominalRate = state->streamFormat.sampleRate;
const double rate = playbackClock &&
targetRate >= nominalRate * 0.995 &&
targetRate <= nominalRate * 1.005 ?
targetRate : nominalRate;
lgUsbAudio_setFeedbackRate(state->device, rate);
LG_UNLOCK(state->stateLock);
return true;
}
const LG_AudioOps LGA_USB = const LG_AudioOps LGA_USB =
{ {
.name = "USB Audio", .name = "USB Audio",
@@ -539,7 +561,7 @@ const LG_AudioOps LGA_USB =
.attach = usbAttach, .attach = usbAttach,
.detach = usbDetach, .detach = usbDetach,
.recordData = NULL, .recordData = NULL,
.clockFeedback = NULL, .clockFeedback = usbClockFeedback,
}; };
LGA_USBState * lgaUsb_create(void) LGA_USBState * lgaUsb_create(void)

View File

@@ -25,27 +25,35 @@
#include <usbredirparser.h> #include <usbredirparser.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdatomic.h>
#include <stdint.h> #include <stdint.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
enum enum
{ {
USB_AUDIO_VENDOR_ID = 0x043e, USB_AUDIO_VENDOR_ID = 0x043e,
USB_AUDIO_PRODUCT_ID = 0x0001, USB_AUDIO_PRODUCT_ID = 0x0001,
USB_AUDIO_DEVICE_VERSION = 0x010a, USB_AUDIO_DEVICE_VERSION = 0x010b,
USB_AUDIO_CONFIGURATION = 1, USB_AUDIO_CONFIGURATION = 1,
USB_AUDIO_CONTROL_IFACE = 0, USB_AUDIO_CONTROL_IFACE = 0,
USB_AUDIO_STREAM_IFACE = 1, USB_AUDIO_STREAM_IFACE = 1,
USB_AUDIO_CLOCK_ID = 1, USB_AUDIO_CLOCK_ID = 1,
USB_AUDIO_STREAM_ENDPOINT = 0x01, USB_AUDIO_DATA_ENDPOINT = 0x01,
USB_AUDIO_EP_INTERVAL = 1, USB_AUDIO_FEEDBACK_ENDPOINT = 0x81,
USB_AUDIO_SAMPLE_SIZE = 3, USB_AUDIO_DATA_EP_INDEX = 1,
USB_AUDIO_HS_PACKET_FRAMES = 25, USB_AUDIO_FEEDBACK_EP_INDEX = 17,
USB_AUDIO_FS_PACKET_FRAMES = 97, USB_AUDIO_DATA_INTERVAL = 1,
USB_AUDIO_DESCRIPTOR_BASE = 81, USB_AUDIO_FEEDBACK_INTERVAL = 8,
USB_AUDIO_STREAM_DESC_SIZE = 46, USB_AUDIO_FEEDBACK_BINTERVAL = 4,
USB_AUDIO_OTHER_SPEED_SIZE = USB_AUDIO_FEEDBACK_SIZE = 4,
USB_AUDIO_FEEDBACK_MAX_QUEUE = 512,
USB_AUDIO_SAMPLE_SIZE = 3,
USB_AUDIO_HS_PACKET_FRAMES = 25,
USB_AUDIO_FS_PACKET_FRAMES = 97,
USB_AUDIO_DESCRIPTOR_BASE = 81,
USB_AUDIO_STREAM_DESC_SIZE = 53,
USB_AUDIO_OTHER_SPEED_SIZE =
USB_AUDIO_DESCRIPTOR_BASE + USB_AUDIO_STREAM_DESC_SIZE, USB_AUDIO_DESCRIPTOR_BASE + USB_AUDIO_STREAM_DESC_SIZE,
}; };
@@ -134,8 +142,9 @@ static const uint8_t l_deviceDescriptor[] =
0x03, 0x01, 0x03, 0x01,
}; };
#define USB_AUDIO_STREAM_DESCRIPTOR(alt, channels, mask, packetFrames) \ #define USB_AUDIO_STREAM_DESCRIPTOR(alt, channels, mask, packetFrames, \
0x09, 0x04, 0x01, alt, 0x01, 0x01, 0x02, 0x20, 0x00, \ feedbackSize, feedbackInterval) \
0x09, 0x04, 0x01, alt, 0x02, 0x01, 0x02, 0x20, 0x00, \
0x10, 0x24, 0x01, 0x02, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, \ 0x10, 0x24, 0x01, 0x02, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, \
channels, \ channels, \
(mask) & 0xff, \ (mask) & 0xff, \
@@ -144,15 +153,18 @@ static const uint8_t l_deviceDescriptor[] =
(mask) >> 24 & 0xff, \ (mask) >> 24 & 0xff, \
0x00, \ 0x00, \
0x06, 0x24, 0x02, 0x01, 0x03, 0x18, \ 0x06, 0x24, 0x02, 0x01, 0x03, 0x18, \
0x07, 0x05, 0x01, 0x09, \ 0x07, 0x05, USB_AUDIO_DATA_ENDPOINT, 0x05, \
USB_AUDIO_PACKET_SIZE(packetFrames, channels) & 0xff, \ USB_AUDIO_PACKET_SIZE(packetFrames, channels) & 0xff, \
USB_AUDIO_PACKET_SIZE(packetFrames, channels) >> 8 & 0xff, \ USB_AUDIO_PACKET_SIZE(packetFrames, channels) >> 8 & 0xff, \
USB_AUDIO_EP_INTERVAL, \ USB_AUDIO_DATA_INTERVAL, \
0x08, 0x25, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x25, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, \
0x07, 0x05, USB_AUDIO_FEEDBACK_ENDPOINT, 0x11, \
feedbackSize, 0x00, feedbackInterval,
#define USB_AUDIO_HS_STREAM_DESCRIPTOR(alt, channels, mask) \ #define USB_AUDIO_HS_STREAM_DESCRIPTOR(alt, channels, mask) \
USB_AUDIO_STREAM_DESCRIPTOR( \ USB_AUDIO_STREAM_DESCRIPTOR( \
alt, channels, mask, USB_AUDIO_HS_PACKET_FRAMES) alt, channels, mask, USB_AUDIO_HS_PACKET_FRAMES, \
USB_AUDIO_FEEDBACK_SIZE, USB_AUDIO_FEEDBACK_BINTERVAL)
static const uint8_t l_configurationDescriptor[] = static const uint8_t l_configurationDescriptor[] =
{ {
@@ -186,7 +198,7 @@ static const uint8_t l_configurationDescriptor[] =
static const uint8_t l_fullSpeedStreamDescriptor[] = static const uint8_t l_fullSpeedStreamDescriptor[] =
{ {
USB_AUDIO_STREAM_DESCRIPTOR(1, 2, USB_AUDIO_LAYOUT_STEREO, USB_AUDIO_STREAM_DESCRIPTOR(1, 2, USB_AUDIO_LAYOUT_STEREO,
USB_AUDIO_FS_PACKET_FRAMES) USB_AUDIO_FS_PACKET_FRAMES, 3, 1)
}; };
#undef USB_AUDIO_STREAM_DESCRIPTOR #undef USB_AUDIO_STREAM_DESCRIPTOR
#undef USB_AUDIO_PACKET_SIZE #undef USB_AUDIO_PACKET_SIZE
@@ -235,7 +247,11 @@ struct LG_USBAudio
uint8_t configuration; uint8_t configuration;
uint8_t streamAlt; uint8_t streamAlt;
uint32_t sampleRate; uint32_t sampleRate;
bool streaming; bool dataStreaming;
bool feedbackStreaming;
uint64_t feedbackPacketId;
uint8_t feedbackDataPackets;
atomic_uint feedbackValue;
}; };
static LG_USBAudio * getAudio(void * opaque) static LG_USBAudio * getAudio(void * opaque)
@@ -266,6 +282,17 @@ static void writeLE32(uint8_t * data, uint32_t value)
data[3] = value >> 24; data[3] = value >> 24;
} }
static uint32_t encodeFeedbackRate(double sampleRate)
{
return (uint32_t)(sampleRate * UINT32_C(65536) / 8000.0 + 0.5);
}
static void resetFeedbackRate(LG_USBAudio * audio)
{
atomic_store_explicit(&audio->feedbackValue,
encodeFeedbackRate(audio->sampleRate), memory_order_release);
}
static uint32_t nearestSampleRate(uint32_t sampleRate) static uint32_t nearestSampleRate(uint32_t sampleRate)
{ {
uint32_t selected = l_sampleRates[0]; uint32_t selected = l_sampleRates[0];
@@ -320,21 +347,35 @@ static size_t writeSampleRateRange(uint8_t * buffer)
static void stopPlayback(LG_USBAudio * audio) static void stopPlayback(LG_USBAudio * audio)
{ {
if (!audio->streaming) if (!audio->dataStreaming)
return; return;
audio->streaming = false; audio->dataStreaming = false;
resetFeedbackRate(audio);
if (audio->events && audio->events->stop) if (audio->events && audio->events->stop)
audio->events->stop(audio->eventOpaque); audio->events->stop(audio->eventOpaque);
} }
static void stopFeedback(LG_USBAudio * audio)
{
audio->feedbackStreaming = false;
audio->feedbackDataPackets = 0;
}
static void stopStreams(LG_USBAudio * audio)
{
stopPlayback(audio);
stopFeedback(audio);
}
static void startPlayback(LG_USBAudio * audio) static void startPlayback(LG_USBAudio * audio)
{ {
const USBAudioLayout * layout = getLayout(audio->streamAlt); const USBAudioLayout * layout = getLayout(audio->streamAlt);
if (audio->streaming || !layout) if (audio->dataStreaming || !layout)
return; return;
audio->streaming = true; resetFeedbackRate(audio);
audio->dataStreaming = true;
if (audio->events && audio->events->start) if (audio->events && audio->events->start)
audio->events->start( audio->events->start(
audio->eventOpaque, audio->sampleRate, layout->channelMask); audio->eventOpaque, audio->sampleRate, layout->channelMask);
@@ -345,20 +386,22 @@ static void setSampleRate(LG_USBAudio * audio, uint32_t sampleRate)
if (audio->sampleRate == sampleRate) if (audio->sampleRate == sampleRate)
return; return;
const bool restart = audio->streaming; const bool restart = audio->dataStreaming;
stopPlayback(audio); stopPlayback(audio);
audio->sampleRate = sampleRate; audio->sampleRate = sampleRate;
resetFeedbackRate(audio);
if (restart) if (restart)
startPlayback(audio); startPlayback(audio);
} }
static void resetDevice(LG_USBAudio * audio) static void resetDevice(LG_USBAudio * audio)
{ {
stopPlayback(audio); stopStreams(audio);
audio->configuration = 0; audio->configuration = 0;
audio->streamAlt = 0; audio->streamAlt = 0;
audio->sampleRate = LG_USB_AUDIO_DEFAULT_SAMPLE_RATE; audio->sampleRate = LG_USB_AUDIO_DEFAULT_SAMPLE_RATE;
resetFeedbackRate(audio);
} }
static void sendInterfaceInfo(LG_USBAudio * audio) static void sendInterfaceInfo(LG_USBAudio * audio)
@@ -387,11 +430,19 @@ static void sendEndpointInfo(LG_USBAudio * audio)
if (audio->configuration == USB_AUDIO_CONFIGURATION && layout) if (audio->configuration == USB_AUDIO_CONFIGURATION && layout)
{ {
info.type[USB_AUDIO_STREAM_ENDPOINT] = usb_redir_type_iso; info.type[USB_AUDIO_DATA_EP_INDEX] = usb_redir_type_iso;
info.interval[USB_AUDIO_STREAM_ENDPOINT] = USB_AUDIO_EP_INTERVAL; info.interval[USB_AUDIO_DATA_EP_INDEX] = USB_AUDIO_DATA_INTERVAL;
info.interface[USB_AUDIO_STREAM_ENDPOINT] = USB_AUDIO_STREAM_IFACE; info.interface[USB_AUDIO_DATA_EP_INDEX] = USB_AUDIO_STREAM_IFACE;
info.max_packet_size[USB_AUDIO_STREAM_ENDPOINT] = info.max_packet_size[USB_AUDIO_DATA_EP_INDEX] =
layoutPacketSize(layout); layoutPacketSize(layout);
info.type[USB_AUDIO_FEEDBACK_EP_INDEX] = usb_redir_type_iso;
info.interval[USB_AUDIO_FEEDBACK_EP_INDEX] =
USB_AUDIO_FEEDBACK_INTERVAL;
info.interface[USB_AUDIO_FEEDBACK_EP_INDEX] =
USB_AUDIO_STREAM_IFACE;
info.max_packet_size[USB_AUDIO_FEEDBACK_EP_INDEX] =
USB_AUDIO_FEEDBACK_SIZE;
} }
usbredirparser_send_ep_info(audio->parser, &info); usbredirparser_send_ep_info(audio->parser, &info);
@@ -462,7 +513,7 @@ static void setAltSetting(void * opaque, uint64_t id,
request->interface == USB_AUDIO_STREAM_IFACE && request->interface == USB_AUDIO_STREAM_IFACE &&
(request->alt == 0 || getLayout(request->alt))) (request->alt == 0 || getLayout(request->alt)))
{ {
stopPlayback(audio); stopStreams(audio);
audio->streamAlt = request->alt; audio->streamAlt = request->alt;
status.status = usb_redir_success; status.status = usb_redir_success;
} }
@@ -507,31 +558,80 @@ static void sendISOStatus(LG_USBAudio * audio, uint64_t id,
usbredirparser_send_iso_stream_status(audio->parser, id, &status); usbredirparser_send_iso_stream_status(audio->parser, id, &status);
} }
static void sendFeedbackPackets(LG_USBAudio * audio, uint32_t count)
{
uint8_t data[USB_AUDIO_FEEDBACK_SIZE];
writeLE32(data, atomic_load_explicit(
&audio->feedbackValue, memory_order_acquire));
struct usb_redir_iso_packet_header packet =
{
.endpoint = USB_AUDIO_FEEDBACK_ENDPOINT,
.status = usb_redir_success,
.length = sizeof(data),
};
for (uint32_t i = 0; i < count; ++i)
usbredirparser_send_iso_packet(audio->parser,
audio->feedbackPacketId++,
&packet, data, USB_AUDIO_FEEDBACK_SIZE);
}
static void startISOStream(void * opaque, uint64_t id, static void startISOStream(void * opaque, uint64_t id,
struct usb_redir_start_iso_stream_header * request) struct usb_redir_start_iso_stream_header * request)
{ {
LG_USBAudio * audio = getAudio(opaque); LG_USBAudio * audio = getAudio(opaque);
uint8_t result = usb_redir_stall; uint8_t result = usb_redir_stall;
uint32_t feedbackPrefill = 0;
if (request->endpoint == USB_AUDIO_STREAM_ENDPOINT && if (audio->configuration == USB_AUDIO_CONFIGURATION &&
audio->configuration == USB_AUDIO_CONFIGURATION &&
getLayout(audio->streamAlt)) getLayout(audio->streamAlt))
{ {
result = usb_redir_success; switch (request->endpoint)
startPlayback(audio); {
case USB_AUDIO_DATA_ENDPOINT:
result = usb_redir_success;
startPlayback(audio);
break;
case USB_AUDIO_FEEDBACK_ENDPOINT:
resetFeedbackRate(audio);
audio->feedbackStreaming = true;
audio->feedbackDataPackets = 0;
audio->feedbackPacketId = 0;
feedbackPrefill =
(uint32_t)request->pkts_per_urb * request->no_urbs;
if (!feedbackPrefill)
feedbackPrefill = 1;
else if (feedbackPrefill >
USB_AUDIO_FEEDBACK_MAX_QUEUE)
feedbackPrefill = USB_AUDIO_FEEDBACK_MAX_QUEUE;
result = usb_redir_success;
break;
}
} }
sendISOStatus(audio, id, request->endpoint, result); sendISOStatus(audio, id, request->endpoint, result);
if (feedbackPrefill)
sendFeedbackPackets(audio, feedbackPrefill);
} }
static void stopISOStream(void * opaque, uint64_t id, static void stopISOStream(void * opaque, uint64_t id,
struct usb_redir_stop_iso_stream_header * request) struct usb_redir_stop_iso_stream_header * request)
{ {
LG_USBAudio * audio = getAudio(opaque); LG_USBAudio * audio = getAudio(opaque);
const uint8_t result = request->endpoint == USB_AUDIO_STREAM_ENDPOINT ? uint8_t result = usb_redir_stall;
usb_redir_success : usb_redir_stall; switch (request->endpoint)
if (result == usb_redir_success) {
stopPlayback(audio); case USB_AUDIO_DATA_ENDPOINT:
stopPlayback(audio);
result = usb_redir_success;
break;
case USB_AUDIO_FEEDBACK_ENDPOINT:
stopFeedback(audio);
result = usb_redir_success;
break;
}
sendISOStatus(audio, id, request->endpoint, result); sendISOStatus(audio, id, request->endpoint, result);
} }
@@ -556,7 +656,7 @@ static size_t stringDescriptor(uint8_t index, uint8_t * buffer,
{ {
case 1: text = "Looking Glass" ; break; case 1: text = "Looking Glass" ; break;
case 2: text = "Looking Glass USB Audio"; break; case 2: text = "Looking Glass USB Audio"; break;
case 3: text = "LG-UAC2-0003" ; break; case 3: text = "LG-UAC2-0004" ; break;
default: return 0; default: return 0;
} }
@@ -695,7 +795,8 @@ static void controlPacket(void * opaque, uint64_t id,
valid = request->index == 0x00 || request->index == 0x80 || valid = request->index == 0x00 || request->index == 0x80 ||
(audio->configuration == USB_AUDIO_CONFIGURATION && (audio->configuration == USB_AUDIO_CONFIGURATION &&
getLayout(audio->streamAlt) && getLayout(audio->streamAlt) &&
request->index == USB_AUDIO_STREAM_ENDPOINT); (request->index == USB_AUDIO_DATA_ENDPOINT ||
request->index == USB_AUDIO_FEEDBACK_ENDPOINT));
break; break;
} }
@@ -756,7 +857,7 @@ static void controlPacket(void * opaque, uint64_t id,
static void stallStream(LG_USBAudio * audio) static void stallStream(LG_USBAudio * audio)
{ {
stopPlayback(audio); stopPlayback(audio);
sendISOStatus(audio, 0, USB_AUDIO_STREAM_ENDPOINT, usb_redir_stall); sendISOStatus(audio, 0, USB_AUDIO_DATA_ENDPOINT, usb_redir_stall);
} }
static void isoPacket(void * opaque, uint64_t id, static void isoPacket(void * opaque, uint64_t id,
@@ -767,11 +868,11 @@ static void isoPacket(void * opaque, uint64_t id,
LG_USBAudio * audio = getAudio(opaque); LG_USBAudio * audio = getAudio(opaque);
const USBAudioLayout * layout = getLayout(audio->streamAlt); const USBAudioLayout * layout = getLayout(audio->streamAlt);
if (audio->streaming) if (audio->dataStreaming)
{ {
const uint16_t frameSize = const uint16_t frameSize =
layout ? layout->channelCount * USB_AUDIO_SAMPLE_SIZE : 0; layout ? layout->channelCount * USB_AUDIO_SAMPLE_SIZE : 0;
if (packet->endpoint != USB_AUDIO_STREAM_ENDPOINT || if (packet->endpoint != USB_AUDIO_DATA_ENDPOINT ||
packet->status != usb_redir_success || dataLength < 0 || packet->status != usb_redir_success || dataLength < 0 ||
packet->length != dataLength || packet->length != dataLength ||
!layout || dataLength > layoutPacketSize(layout) || !layout || dataLength > layoutPacketSize(layout) ||
@@ -780,9 +881,19 @@ static void isoPacket(void * opaque, uint64_t id,
DEBUG_WARN("Invalid USB audio isochronous packet"); DEBUG_WARN("Invalid USB audio isochronous packet");
stallStream(audio); stallStream(audio);
} }
else if (dataLength && audio->events && audio->events->data) else
audio->events->data(audio->eventOpaque, data, {
dataLength / frameSize); if (dataLength && audio->events && audio->events->data)
audio->events->data(audio->eventOpaque, data,
dataLength / frameSize);
if (audio->feedbackStreaming &&
++audio->feedbackDataPackets == USB_AUDIO_FEEDBACK_INTERVAL)
{
audio->feedbackDataPackets = 0;
sendFeedbackPackets(audio, 1);
}
}
} }
if (data) if (data)
@@ -847,9 +958,21 @@ LG_USBAudio * lgUsbAudio_create(
audio->events = events; audio->events = events;
audio->eventOpaque = eventOpaque; audio->eventOpaque = eventOpaque;
audio->sampleRate = LG_USB_AUDIO_DEFAULT_SAMPLE_RATE;
atomic_init(&audio->feedbackValue,
encodeFeedbackRate(audio->sampleRate));
return audio; return audio;
} }
void lgUsbAudio_setFeedbackRate(LG_USBAudio * audio, double sampleRate)
{
if (!audio)
return;
atomic_store_explicit(&audio->feedbackValue,
encodeFeedbackRate(sampleRate), memory_order_release);
}
void lgUsbAudio_destroy(LG_USBAudio * audio) void lgUsbAudio_destroy(LG_USBAudio * audio)
{ {
if (!audio) if (!audio)

View File

@@ -47,6 +47,10 @@ LG_USBAudio * lgUsbAudio_create(
/* 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
* feedback thread. */
void lgUsbAudio_setFeedbackRate(LG_USBAudio * audio, double sampleRate);
const LG_USBRedirDeviceOps * lgUsbAudio_deviceOps(void); const LG_USBRedirDeviceOps * lgUsbAudio_deviceOps(void);
#endif #endif